| // Copyright (c) 2018, 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. |
| |
| import 'dart:io'; |
| import 'package:_fe_analyzer_shared/src/testing/features.dart'; |
| import 'package:expect/async_helper.dart'; |
| import 'package:compiler/src/closure.dart'; |
| import 'package:compiler/src/common.dart'; |
| import 'package:compiler/src/common/elements.dart'; |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/elements/entities.dart'; |
| import 'package:compiler/src/elements/names.dart'; |
| import 'package:compiler/src/elements/types.dart'; |
| import 'package:compiler/src/js_backend/runtime_types_resolution.dart'; |
| import 'package:compiler/src/js_model/js_world.dart'; |
| import 'package:compiler/src/js_model/element_map.dart'; |
| import 'package:compiler/src/kernel/element_map.dart'; |
| import 'package:compiler/src/kernel/kernel_strategy.dart'; |
| import 'package:compiler/src/universe/feature.dart'; |
| import 'package:compiler/src/universe/resolution_world_builder.dart'; |
| import 'package:compiler/src/universe/selector.dart'; |
| import 'package:kernel/ast.dart' as ir; |
| import '../equivalence/check_helpers.dart'; |
| import '../equivalence/id_equivalence.dart'; |
| import '../equivalence/id_equivalence_helper.dart'; |
| |
| main(List<String> args) { |
| runTests(args); |
| } |
| |
| runTests(List<String> args, [int? shardIndex]) { |
| asyncTest(() async { |
| Directory dataDir = Directory.fromUri(Platform.script.resolve('data')); |
| await checkTests( |
| dataDir, |
| const RtiNeedDataComputer(), |
| args: args, |
| shardIndex: shardIndex ?? 0, |
| shards: shardIndex != null ? 4 : 1, |
| ); |
| }); |
| } |
| |
| class Tags { |
| static const String needsTypeArguments = 'needsArgs'; |
| static const String needsSignature = 'needsSignature'; |
| static const String dependencies = 'deps'; |
| static const String explicitTypeCheck = 'explicit'; |
| static const String implicitTypeCheck = 'implicit'; |
| static const String typeArgumentTest = 'test'; |
| static const String typeLiteral = 'exp'; |
| static const String selectors = 'selectors'; |
| static const String instantiationsNeedTypeArguments = 'needsInst'; |
| } |
| |
| mixin ComputeValueMixin { |
| Compiler get compiler; |
| |
| KernelFrontendStrategy get frontendStrategy => compiler.frontendStrategy; |
| ResolutionWorldBuilder get resolutionWorldBuilder => |
| compiler.resolutionWorldBuilderForTesting!; |
| RuntimeTypesNeedBuilderImpl get rtiNeedBuilder => |
| frontendStrategy.runtimeTypesNeedBuilderForTesting |
| as RuntimeTypesNeedBuilderImpl; |
| RuntimeTypesNeedImpl get rtiNeed => |
| compiler.backendClosedWorldForTesting!.rtiNeed as RuntimeTypesNeedImpl; |
| ClassEntity? getFrontendClass(ClassEntity cls); |
| MemberEntity? getFrontendMember(MemberEntity member); |
| Local? getFrontendClosure(MemberEntity member); |
| |
| void findChecks( |
| Features features, |
| String key, |
| Entity? entity, |
| Set<DartType> checks, |
| ) { |
| Set<DartType> types = Set<DartType>(); |
| FindTypeVisitor finder = FindTypeVisitor(entity); |
| for (DartType type in checks) { |
| if (type.accept(finder, null)) { |
| types.add(type); |
| } |
| } |
| List<String> list = types.map(typeToString).toList()..sort(); |
| if (list.isNotEmpty) { |
| features[key] = '[${list.join(',')}]'; |
| } |
| } |
| |
| void findDependencies(Features features, Entity? entity) { |
| Iterable<Entity> dependencies = entity == null |
| ? const [] |
| : rtiNeedBuilder.typeVariableTestsForTesting! |
| .getTypeArgumentDependencies(entity); |
| if (dependencies.isNotEmpty) { |
| List<String> names = dependencies.map((Entity d) { |
| if (d is MemberEntity && d.enclosingClass != null) { |
| return '${d.enclosingClass!.name}.${d.name}'; |
| } |
| return d.name!; |
| }).toList()..sort(); |
| features[Tags.dependencies] = '[${names.join(',')}]'; |
| } |
| } |
| |
| String getClassValue(ClassEntity backendClass) { |
| Features features = Features(); |
| |
| if (rtiNeed.classNeedsTypeArguments(backendClass)) { |
| features.add(Tags.needsTypeArguments); |
| } |
| ClassEntity? frontendClass = getFrontendClass(backendClass); |
| findDependencies(features, frontendClass); |
| if (rtiNeedBuilder.classesUsingTypeVariableLiterals.contains( |
| frontendClass, |
| )) { |
| features.add(Tags.typeLiteral); |
| } |
| if (rtiNeedBuilder.typeVariableTestsForTesting!.classTestsForTesting |
| .contains(frontendClass)) { |
| features.add(Tags.typeArgumentTest); |
| } |
| findChecks( |
| features, |
| Tags.explicitTypeCheck, |
| frontendClass, |
| rtiNeedBuilder.typeVariableTestsForTesting!.explicitIsChecks, |
| ); |
| findChecks( |
| features, |
| Tags.implicitTypeCheck, |
| frontendClass, |
| rtiNeedBuilder.typeVariableTestsForTesting!.implicitIsChecks, |
| ); |
| return features.getText(); |
| } |
| |
| String getMemberValue(MemberEntity backendMember) { |
| MemberEntity? frontendMember = getFrontendMember(backendMember); |
| Local? frontendClosure = getFrontendClosure(backendMember); |
| |
| Features features = Features(); |
| |
| if (backendMember is FunctionEntity) { |
| if (rtiNeed.methodNeedsTypeArguments(backendMember)) { |
| features.add(Tags.needsTypeArguments); |
| } |
| if (rtiNeed.methodNeedsSignature(backendMember)) { |
| features.add(Tags.needsSignature); |
| } |
| |
| void addFrontendData(Entity entity) { |
| findDependencies(features, entity); |
| if (rtiNeedBuilder.typeVariableTestsForTesting!.methodTestsForTesting |
| .contains(entity)) { |
| features.add(Tags.typeArgumentTest); |
| } |
| findChecks( |
| features, |
| Tags.explicitTypeCheck, |
| entity, |
| rtiNeedBuilder.typeVariableTestsForTesting!.explicitIsChecks, |
| ); |
| findChecks( |
| features, |
| Tags.implicitTypeCheck, |
| entity, |
| rtiNeedBuilder.typeVariableTestsForTesting!.implicitIsChecks, |
| ); |
| rtiNeedBuilder.selectorsNeedingTypeArgumentsForTesting?.forEach(( |
| Selector selector, |
| Set<Entity> targets, |
| ) { |
| if (targets.contains(entity)) { |
| features.addElement(Tags.selectors, selector); |
| } |
| }); |
| rtiNeedBuilder |
| .instantiatedEntitiesNeedingTypeArgumentsForTesting[entity] |
| ?.forEach((GenericInstantiation instantiation) { |
| features.addElement( |
| Tags.instantiationsNeedTypeArguments, |
| instantiation.shortText, |
| ); |
| }); |
| } |
| |
| if (frontendClosure != null) { |
| addFrontendData(frontendClosure); |
| if (rtiNeedBuilder.localFunctionsUsingTypeVariableLiterals.contains( |
| frontendClosure, |
| )) { |
| features.add(Tags.typeLiteral); |
| } |
| } else if (frontendMember != null) { |
| addFrontendData(frontendMember); |
| if (rtiNeedBuilder.methodsUsingTypeVariableLiterals.contains( |
| frontendMember, |
| )) { |
| features.add(Tags.typeLiteral); |
| } |
| } |
| } |
| return features.getText(); |
| } |
| } |
| |
| /// Visitor that determines whether a type refers to [entity]. |
| class FindTypeVisitor extends DartTypeVisitor<bool, Null> { |
| final Entity? entity; |
| |
| FindTypeVisitor(this.entity); |
| |
| bool check(DartType type) => visit(type, null); |
| |
| bool checkList(List<DartType> types) { |
| for (DartType type in types) { |
| if (check(type)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @override |
| bool visitNullableType(NullableType type, _) => visit(type.baseType, null); |
| |
| @override |
| bool visitNeverType(NeverType type, _) => false; |
| |
| @override |
| bool visitVoidType(VoidType type, _) => false; |
| |
| @override |
| bool visitTypeVariableType(TypeVariableType type, _) => |
| type.element.typeDeclaration == entity; |
| |
| @override |
| bool visitFunctionTypeVariable(FunctionTypeVariable type, _) => false; |
| |
| @override |
| bool visitFunctionType(FunctionType type, _) => |
| type.returnType.accept(this, null) || |
| checkList(type.typeVariables) || |
| checkList(type.parameterTypes) || |
| checkList(type.optionalParameterTypes) || |
| checkList(type.namedParameterTypes); |
| |
| @override |
| bool visitInterfaceType(InterfaceType type, _) => |
| type.element == entity || checkList(type.typeArguments); |
| |
| @override |
| bool visitRecordType(RecordType type, _) => throw UnimplementedError(); |
| |
| @override |
| bool visitDynamicType(DynamicType type, _) => false; |
| |
| @override |
| bool visitErasedType(ErasedType type, _) => false; |
| |
| @override |
| bool visitAnyType(AnyType type, _) => false; |
| |
| @override |
| bool visitFutureOrType(FutureOrType type, _) => check(type.typeArgument); |
| } |
| |
| class RtiNeedDataComputer extends DataComputer<String> { |
| const RtiNeedDataComputer(); |
| |
| /// Compute RTI need data for [member] from the new frontend. |
| /// |
| /// Fills [actualMap] with the data. |
| @override |
| void computeMemberData( |
| Compiler compiler, |
| MemberEntity member, |
| Map<Id, ActualData<String>> actualMap, { |
| bool verbose = false, |
| }) { |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; |
| JsToElementMap elementMap = closedWorld.elementMap; |
| MemberDefinition definition = elementMap.getMemberDefinition(member); |
| RtiNeedIrComputer( |
| compiler.reporter, |
| actualMap, |
| elementMap, |
| compiler, |
| closedWorld.closureDataLookup, |
| ).run(definition.node); |
| } |
| |
| /// Compute RTI need data for [cls] from the new frontend. |
| /// |
| /// Fills [actualMap] with the data. |
| @override |
| void computeClassData( |
| Compiler compiler, |
| ClassEntity cls, |
| Map<Id, ActualData<String>> actualMap, { |
| bool verbose = false, |
| }) { |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; |
| JsToElementMap elementMap = closedWorld.elementMap; |
| RtiNeedIrComputer( |
| compiler.reporter, |
| actualMap, |
| elementMap, |
| compiler, |
| closedWorld.closureDataLookup, |
| ).computeForClass(elementMap.getClassDefinition(cls).node as ir.Class); |
| } |
| |
| @override |
| DataInterpreter<String> get dataValidator => const StringDataInterpreter(); |
| } |
| |
| mixin IrMixin implements ComputeValueMixin { |
| @override |
| MemberEntity? getFrontendMember(MemberEntity backendMember) { |
| ElementEnvironment elementEnvironment = |
| compiler.frontendClosedWorldForTesting!.elementEnvironment; |
| LibraryEntity frontendLibrary = elementEnvironment.lookupLibrary( |
| backendMember.library.canonicalUri, |
| )!; |
| if (backendMember.enclosingClass != null) { |
| if (backendMember.enclosingClass!.isClosure) return null; |
| ClassEntity frontendClass = elementEnvironment.lookupClass( |
| frontendLibrary, |
| backendMember.enclosingClass!.name, |
| )!; |
| if (backendMember is ConstructorEntity) { |
| return elementEnvironment.lookupConstructor( |
| frontendClass, |
| backendMember.name!, |
| ); |
| } else { |
| return elementEnvironment.lookupClassMember( |
| frontendClass, |
| Name( |
| backendMember.name!, |
| frontendClass.library.canonicalUri, |
| isSetter: backendMember.isSetter, |
| ), |
| ); |
| } |
| } |
| return elementEnvironment.lookupLibraryMember( |
| frontendLibrary, |
| backendMember.name!, |
| setter: backendMember.isSetter, |
| ); |
| } |
| |
| @override |
| ClassEntity? getFrontendClass(ClassEntity backendClass) { |
| if (backendClass.isClosure) return null; |
| ElementEnvironment elementEnvironment = |
| compiler.frontendClosedWorldForTesting!.elementEnvironment; |
| LibraryEntity frontendLibrary = elementEnvironment.lookupLibrary( |
| backendClass.library.canonicalUri, |
| )!; |
| return elementEnvironment.lookupClass(frontendLibrary, backendClass.name); |
| } |
| |
| @override |
| Local? getFrontendClosure(MemberEntity member) { |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; |
| ir.Node node = closedWorld.elementMap.getMemberDefinition(member).node; |
| if (node is ir.FunctionDeclaration || node is ir.FunctionExpression) { |
| KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy; |
| KernelToElementMap frontendElementMap = frontendStrategy.elementMap; |
| return frontendElementMap.getLocalFunction(node as ir.LocalFunction); |
| } |
| return null; |
| } |
| } |
| |
| class RtiClassNeedIrComputer |
| with |
| DataRegistry<String>, |
| ComputeValueMixin, |
| IrMixin, |
| IrDataRegistryMixin<String> { |
| @override |
| final Compiler compiler; |
| final JsToElementMap _elementMap; |
| @override |
| final Map<Id, ActualData<String>> actualMap; |
| |
| RtiClassNeedIrComputer(this.compiler, this._elementMap, this.actualMap); |
| |
| @override |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| void computeClassValue(ClassEntity cls) { |
| Id id = ClassId(cls.name); |
| final node = _elementMap.getClassDefinition(cls).node as ir.TreeNode; |
| ir.TreeNode nodeWithOffset = computeTreeNodeWithOffset(node)!; |
| registerValue( |
| nodeWithOffset.location!.file, |
| nodeWithOffset.fileOffset, |
| id, |
| getClassValue(cls), |
| cls, |
| ); |
| } |
| } |
| |
| /// AST visitor for computing inference data for a member. |
| class RtiNeedIrComputer extends IrDataExtractor<String> |
| with ComputeValueMixin, IrMixin { |
| final JsToElementMap _elementMap; |
| final ClosureData _closureDataLookup; |
| @override |
| final Compiler compiler; |
| |
| RtiNeedIrComputer( |
| DiagnosticReporter reporter, |
| Map<Id, ActualData<String>> actualMap, |
| this._elementMap, |
| this.compiler, |
| this._closureDataLookup, |
| ) : super(reporter, actualMap); |
| |
| @override |
| String computeClassValue(Id id, ir.Class node) { |
| return getClassValue(_elementMap.getClass(node)); |
| } |
| |
| @override |
| String computeMemberValue(Id id, ir.Member node) { |
| return getMemberValue(_elementMap.getMember(node)); |
| } |
| |
| @override |
| String? computeNodeValue(Id id, ir.TreeNode node) { |
| if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) { |
| ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo( |
| node as ir.LocalFunction, |
| ); |
| return getMemberValue(info.callMethod!); |
| } |
| return null; |
| } |
| } |