// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // @dart=2.9 import 'dart:io' show Directory, Platform; import 'package:_fe_analyzer_shared/src/testing/features.dart'; import 'package:_fe_analyzer_shared/src/testing/id.dart'; import 'package:_fe_analyzer_shared/src/testing/id_testing.dart'; import 'package:front_end/src/fasta/kernel/kernel_api.dart'; import 'package:front_end/src/testing/id_testing_helper.dart'; import 'package:front_end/src/testing/id_testing_utils.dart'; import 'package:front_end/src/fasta/kernel/class_hierarchy_builder.dart'; import 'package:front_end/src/testing/id_extractor.dart'; import 'package:kernel/ast.dart'; main(List args) async { Directory dataDir = new Directory.fromUri(Platform.script.resolve('data')); await runTests(dataDir, args: args, createUriForFileName: createUriForFileName, onFailure: onFailure, runTest: runTestFor( const ClassHierarchyDataComputer(), [cfeNonNullableConfig])); } class ClassHierarchyDataComputer extends DataComputer { const ClassHierarchyDataComputer(); /// Function that computes a data mapping for [library]. /// /// Fills [actualMap] with the data. void computeLibraryData( TestConfig config, InternalCompilerResult compilerResult, Library library, Map> actualMap, {bool verbose}) { new InheritanceDataExtractor(compilerResult, actualMap) .computeForLibrary(library); } @override void computeClassData( TestConfig config, InternalCompilerResult compilerResult, Class cls, Map> actualMap, {bool verbose}) { new InheritanceDataExtractor(compilerResult, actualMap) .computeForClass(cls); } @override bool get supportsErrors => true; @override Features computeErrorData(TestConfig config, InternalCompilerResult compiler, Id id, List errors) { return null; //errorsToText(errors, useCodes: true); } @override DataInterpreter get dataValidator => const FeaturesDataInterpreter(); } class Tag { static const String superclasses = 'superclasses'; static const String interfaces = 'interfaces'; static const String hasNoSuchMethod = 'hasNoSuchMethod'; static const String abstractMembers = 'abstractMembers'; static const String classBuilder = 'classBuilder'; static const String isSourceDeclaration = 'isSourceDeclaration'; static const String isSynthesized = 'isSynthesized'; static const String member = 'member'; static const String maxInheritancePath = 'maxInheritancePath'; static const String declaredOverrides = 'declared-overrides'; static const String mixinApplicationOverrides = 'mixin-overrides'; static const String inheritedImplements = 'inherited-implements'; static const String abstractForwardingStub = 'abstractForwardingStub'; static const String concreteForwardingStub = 'concreteForwardingStub'; static const String memberSignature = 'memberSignature'; static const String abstractMixinStub = 'abstractMixinStub'; static const String concreteMixinStub = 'concreteMixinStub'; static const String declarations = 'declarations'; static const String stubTarget = 'stubTarget'; static const String type = 'type'; static const String covariance = 'covariance'; } class InheritanceDataExtractor extends CfeDataExtractor { final InternalCompilerResult _compilerResult; InheritanceDataExtractor( this._compilerResult, Map> actualMap) : super(_compilerResult, actualMap); CoreTypes get _coreTypes => _compilerResult.coreTypes; ClassHierarchyBuilder get _classHierarchyBuilder => _compilerResult.kernelTargetForTesting.loader.builderHierarchy; @override void computeForClass(Class node) { super.computeForClass(node); ClassHierarchyNode classHierarchyNode = _classHierarchyBuilder.getNodeFromClass(node); ClassHierarchyNodeDataForTesting data = classHierarchyNode.dataForTesting; void addMember(ClassMember classMember, {bool isSetter, bool isClassMember}) { Member member = classMember.getMember(_classHierarchyBuilder); Member memberOrigin = member.memberSignatureOrigin ?? member; if (memberOrigin.enclosingClass == _coreTypes.objectClass) { return; } Features features = new Features(); String memberName = classMemberName(classMember); memberName += isClassMember ? '#cls' : '#int'; MemberId id = new MemberId.internal(memberName, className: node.name); TreeNode nodeWithOffset; if (member.enclosingClass == node) { nodeWithOffset = computeTreeNodeWithOffset(member); } else { nodeWithOffset = computeTreeNodeWithOffset(node); } if (classMember.isSourceDeclaration) { features.add(Tag.isSourceDeclaration); } if (classMember.isSynthesized) { features.add(Tag.isSynthesized); if (member.enclosingClass != node) { features[Tag.member] = memberQualifiedName(member); } if (classMember.hasDeclarations) { for (ClassMember declaration in classMember.declarations) { features.addElement( Tag.declarations, classMemberQualifiedName(declaration)); } } } features[Tag.classBuilder] = classMember.classBuilder.name; Set declaredOverrides = data.declaredOverrides[data.aliasMap[classMember] ?? classMember]; if (declaredOverrides != null) { for (ClassMember override in declaredOverrides) { features.addElement( Tag.declaredOverrides, classMemberQualifiedName(override)); } } Set mixinApplicationOverrides = data .mixinApplicationOverrides[data.aliasMap[classMember] ?? classMember]; if (mixinApplicationOverrides != null) { for (ClassMember override in mixinApplicationOverrides) { features.addElement(Tag.mixinApplicationOverrides, classMemberQualifiedName(override)); } } Set inheritedImplements = data.inheritedImplements[data.aliasMap[classMember] ?? classMember]; if (inheritedImplements != null) { for (ClassMember implement in inheritedImplements) { features.addElement( Tag.inheritedImplements, classMemberQualifiedName(implement)); } } if (member.enclosingClass == node && member is Procedure) { switch (member.stubKind) { case ProcedureStubKind.Regular: // TODO: Handle this case. break; case ProcedureStubKind.AbstractForwardingStub: features.add(Tag.abstractForwardingStub); features[Tag.type] = procedureType(member); features[Tag.covariance] = classMember.getCovariance(_classHierarchyBuilder).toString(); break; case ProcedureStubKind.ConcreteForwardingStub: features.add(Tag.concreteForwardingStub); features[Tag.type] = procedureType(member); features[Tag.covariance] = classMember.getCovariance(_classHierarchyBuilder).toString(); features[Tag.stubTarget] = memberQualifiedName(member.stubTarget); break; case ProcedureStubKind.NoSuchMethodForwarder: // TODO: Handle this case. break; case ProcedureStubKind.MemberSignature: features.add(Tag.memberSignature); features[Tag.type] = procedureType(member); features[Tag.covariance] = classMember.getCovariance(_classHierarchyBuilder).toString(); break; case ProcedureStubKind.AbstractMixinStub: features.add(Tag.abstractMixinStub); break; case ProcedureStubKind.ConcreteMixinStub: features.add(Tag.concreteMixinStub); features[Tag.stubTarget] = memberQualifiedName(member.stubTarget); break; } } registerValue(nodeWithOffset?.location?.file, nodeWithOffset?.fileOffset, id, features, member); } classHierarchyNode.classMemberMap ?.forEach((Name name, ClassMember classMember) { addMember(classMember, isSetter: false, isClassMember: true); }); classHierarchyNode.classSetterMap ?.forEach((Name name, ClassMember classMember) { addMember(classMember, isSetter: true, isClassMember: true); }); classHierarchyNode.interfaceMemberMap ?.forEach((Name name, ClassMember classMember) { if (!identical(classMember, classHierarchyNode.classMemberMap[name])) { addMember(classMember, isSetter: false, isClassMember: false); } }); classHierarchyNode.interfaceSetterMap ?.forEach((Name name, ClassMember classMember) { if (!identical(classMember, classHierarchyNode.classSetterMap[name])) { addMember(classMember, isSetter: true, isClassMember: false); } }); } @override Features computeClassValue(Id id, Class node) { Features features = new Features(); ClassHierarchyNode classHierarchyNode = _classHierarchyBuilder.getNodeFromClass(node); ClassHierarchyNodeDataForTesting data = classHierarchyNode.dataForTesting; classHierarchyNode.superclasses.forEach((Supertype supertype) { features.addElement(Tag.superclasses, supertypeToText(supertype)); }); classHierarchyNode.interfaces.forEach((Supertype supertype) { features.addElement(Tag.interfaces, supertypeToText(supertype)); }); if (data.abstractMembers != null) { for (ClassMember abstractMember in data.abstractMembers) { features.addElement( Tag.abstractMembers, classMemberQualifiedName(abstractMember)); } } features[Tag.maxInheritancePath] = '${classHierarchyNode.maxInheritancePath}'; if (classHierarchyNode.hasNoSuchMethod) { features.add(Tag.hasNoSuchMethod); } return features; } } String classMemberName(ClassMember classMember) { String name = classMember.name.text; if (classMember.forSetter) { name += '='; } return name; } String classMemberQualifiedName(ClassMember classMember) { return '${classMember.classBuilder.name}.${classMemberName(classMember)}'; } String memberName(Member member) { String name = member.name.text; if (member is Procedure && member.isSetter) { name += '='; } return name; } String memberQualifiedName(Member member) { return '${member.enclosingClass.name}.${memberName(member)}'; } String procedureType(Procedure procedure) { if (procedure.kind == ProcedureKind.Getter) { return typeToText(procedure.function.returnType, TypeRepresentation.analyzerNonNullableByDefault); } else if (procedure.kind == ProcedureKind.Setter) { return typeToText(procedure.function.positionalParameters.single.type, TypeRepresentation.analyzerNonNullableByDefault); } else { Nullability functionTypeNullability; if (procedure.enclosingLibrary.isNonNullableByDefault) { functionTypeNullability = procedure.enclosingLibrary.nonNullable; } else { // We don't create a member signature when the member is just // a substitution. We should still take the nullability to be // legacy, though. functionTypeNullability = procedure.enclosingLibrary.nonNullable; } return typeToText( procedure.function.computeThisFunctionType(functionTypeNullability), TypeRepresentation.analyzerNonNullableByDefault); } }