| // Copyright (c) 2020, 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' 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' | |
| show DataInterpreter, runTests; | |
| import 'package:front_end/src/api_prototype/experimental_flags.dart'; | |
| import 'package:front_end/src/api_prototype/lowering_predicates.dart'; | |
| import 'package:front_end/src/testing/id_extractor.dart'; | |
| import 'package:front_end/src/testing/id_testing_helper.dart'; | |
| import 'package:front_end/src/testing/id_testing_utils.dart'; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/src/printer.dart'; | |
| import 'package:kernel/target/targets.dart'; | |
| const String isNullMarker = 'is-null'; | |
| const String sentinelMarker = 'sentinel'; | |
| Future<void> main(List<String> args) async { | |
| Directory dataDir = new Directory.fromUri(Platform.script.resolve('data')); | |
| await runTests<Features>( | |
| dataDir, | |
| args: args, | |
| createUriForFileName: createUriForFileName, | |
| onFailure: onFailure, | |
| runTest: runTestFor(const PredicateDataComputer(), [ | |
| const CfeTestConfig( | |
| isNullMarker, | |
| 'use is-null', | |
| explicitExperimentalFlags: const {ExperimentalFlag.nonNullable: true}, | |
| targetFlags: const TestTargetFlags( | |
| forceConstructorTearOffLoweringForTesting: | |
| ConstructorTearOffLowering.all, | |
| forceLateLoweringsForTesting: LateLowering.all, | |
| forceLateLoweringSentinelForTesting: false, | |
| ), | |
| ), | |
| const CfeTestConfig( | |
| sentinelMarker, | |
| 'use sentinel', | |
| explicitExperimentalFlags: const {ExperimentalFlag.nonNullable: true}, | |
| targetFlags: const TestTargetFlags( | |
| forceConstructorTearOffLoweringForTesting: | |
| ConstructorTearOffLowering.all, | |
| forceLateLoweringsForTesting: LateLowering.all, | |
| forceLateLoweringSentinelForTesting: true, | |
| ), | |
| ), | |
| ]), | |
| ); | |
| } | |
| class Tags { | |
| static const String name = 'name'; | |
| static const String lateField = 'lateField'; | |
| static const String lateFieldName = 'lateFieldName'; | |
| static const String lateIsSetField = 'lateIsSetField'; | |
| static const String lateFieldGetter = 'lateFieldGetter'; | |
| static const String lateFieldSetter = 'lateFieldSetter'; | |
| static const String lateFieldTarget = 'lateFieldTarget'; | |
| static const String lateFieldInitializer = 'lateFieldInitializer'; | |
| static const String lateLocal = 'lateLocal'; | |
| static const String lateIsSetLocal = 'lateIsSetLocal'; | |
| static const String lateLocalGetter = 'lateLocalGetter'; | |
| static const String lateLocalSetter = 'lateLocalSetter'; | |
| static const String extensionThis = 'extensionThis'; | |
| static const String extensionName = 'extensionName'; | |
| static const String tearoffLowering = 'tearoffLowering'; | |
| static const String tearoffConstructor = 'tearoffConstructor'; | |
| static const String tearoffTypedef = 'tearoffTypedef'; | |
| static const String joinedIntermediate = 'joinedIntermediate'; | |
| } | |
| class PredicateDataComputer extends CfeDataComputer<Features> { | |
| const new(); | |
| /// Function that computes a data mapping for [library]. | |
| /// | |
| /// Fills [actualMap] with the data. | |
| @override | |
| void computeLibraryData( | |
| CfeTestResultData testResultData, | |
| Library library, | |
| ActualDataMap<Features> actualMap, { | |
| bool? verbose, | |
| }) { | |
| new PredicateDataExtractor( | |
| testResultData.compilerResult, | |
| actualMap, | |
| ).computeForLibrary(library); | |
| } | |
| @override | |
| void computeMemberData( | |
| CfeTestResultData testResultData, | |
| Member member, | |
| ActualDataMap<Features> actualMap, { | |
| bool? verbose, | |
| }) { | |
| member.accept( | |
| new PredicateDataExtractor(testResultData.compilerResult, actualMap), | |
| ); | |
| } | |
| @override | |
| DataInterpreter<Features> get dataValidator => | |
| const FeaturesDataInterpreter(); | |
| } | |
| class PredicateDataExtractor extends CfeDataExtractor<Features> { | |
| Map<Object?, Features> featureMap = {}; | |
| Map<Object?, NodeId> nodeIdMap = {}; | |
| new(super.compilerResult, super.actualMap); | |
| @override | |
| Features? computeLibraryValue(Id id, Library node) { | |
| return null; | |
| } | |
| @override | |
| Features? computeMemberValue(Id id, Member node) { | |
| Features features = new Features(); | |
| if (node is Field) { | |
| if (isLateLoweredField(node)) { | |
| features.add(Tags.lateField); | |
| features[Tags.lateFieldName] = extractFieldNameFromLateLoweredField( | |
| node, | |
| ).text; | |
| } | |
| if (isLateLoweredIsSetField(node)) { | |
| features.add(Tags.lateIsSetField); | |
| features[Tags.lateFieldName] = | |
| extractFieldNameFromLateLoweredIsSetField(node).text; | |
| } | |
| Field? target = getLateFieldTarget(node); | |
| if (target != null) { | |
| features[Tags.lateFieldTarget] = getQualifiedMemberName(target); | |
| } | |
| Expression? initializer = getLateFieldInitializer(node); | |
| if (initializer != null) { | |
| features[Tags.lateFieldInitializer] = initializer.toText( | |
| astTextStrategyForTesting, | |
| ); | |
| } | |
| } else if (node is Procedure) { | |
| if (isLateLoweredFieldGetter(node)) { | |
| features.add(Tags.lateFieldGetter); | |
| features[Tags.lateFieldName] = | |
| extractFieldNameFromLateLoweredFieldGetter(node).text; | |
| } | |
| if (isLateLoweredFieldSetter(node)) { | |
| features.add(Tags.lateFieldSetter); | |
| features[Tags.lateFieldName] = | |
| extractFieldNameFromLateLoweredFieldSetter(node).text; | |
| } | |
| Field? target = getLateFieldTarget(node); | |
| if (target != null) { | |
| features[Tags.lateFieldTarget] = getQualifiedMemberName(target); | |
| } | |
| Expression? initializer = getLateFieldInitializer(node); | |
| if (initializer != null) { | |
| features[Tags.lateFieldInitializer] = initializer.toText( | |
| astTextStrategyForTesting, | |
| ); | |
| } | |
| if (isConstructorTearOffLowering(node)) { | |
| features.add(Tags.tearoffConstructor); | |
| } | |
| if (isTypedefTearOffLowering(node)) { | |
| features.add(Tags.tearoffTypedef); | |
| } | |
| if (node.isExtensionMember || node.isExtensionTypeMember) { | |
| features[Tags.extensionName] = extractQualifiedNameFromExtensionMember( | |
| node, | |
| )!; | |
| } | |
| } | |
| if (isTearOffLowering(node)) { | |
| features.add(Tags.tearoffLowering); | |
| } | |
| return features; | |
| } | |
| @override | |
| void visitProcedure(Procedure node) { | |
| super.visitProcedure(node); | |
| nodeIdMap.forEach((Object? identity, NodeId id) { | |
| Features? features = featureMap[identity]; | |
| if (features != null) { | |
| TreeNode nodeWithOffset = computeTreeNodeWithOffset(node)!; | |
| registerValue( | |
| nodeWithOffset.location!.file, | |
| id.value, | |
| id, | |
| features, | |
| '$identity', | |
| ); | |
| } | |
| }); | |
| nodeIdMap.clear(); | |
| featureMap.clear(); | |
| } | |
| @override | |
| void defaultVariable(Variable node) { | |
| Object? identity; | |
| String? name; | |
| String? tag; | |
| if (isLateLoweredLocal(node)) { | |
| name = extractLocalNameFromLateLoweredLocal(node.cosmeticName!); | |
| tag = Tags.lateLocal; | |
| identity = name; | |
| } else if (isLateLoweredIsSetLocal(node)) { | |
| name = extractLocalNameFromLateLoweredIsSet(node.cosmeticName!); | |
| tag = Tags.lateIsSetLocal; | |
| identity = name; | |
| } else if (isLateLoweredLocalGetter(node)) { | |
| name = extractLocalNameFromLateLoweredGetter(node.cosmeticName!); | |
| tag = Tags.lateLocalGetter; | |
| identity = name; | |
| } else if (isLateLoweredLocalSetter(node)) { | |
| name = extractLocalNameFromLateLoweredSetter(node.cosmeticName!); | |
| tag = Tags.lateLocalSetter; | |
| identity = name; | |
| } else if (isExtensionThis(node)) { | |
| name = extractLocalNameForExtensionThis(node.cosmeticName!); | |
| tag = Tags.extensionThis; | |
| identity = name; | |
| } else if (isJoinedIntermediateVariable(node)) { | |
| name = extractJoinedIntermediateName(node.cosmeticName!); | |
| tag = Tags.joinedIntermediate; | |
| identity = node; | |
| } else if (node.cosmeticName != null) { | |
| name = node.cosmeticName; | |
| identity = name; | |
| } | |
| if (name != null) { | |
| if (node.fileOffset != TreeNode.noOffset) { | |
| nodeIdMap[identity] ??= new NodeId(node.fileOffset, IdKind.node); | |
| } | |
| if (tag != null) { | |
| Features features = featureMap[identity] ??= new Features(); | |
| features.add(tag); | |
| features[Tags.name] = name; | |
| } | |
| } | |
| super.defaultVariable(node); | |
| } | |
| @override | |
| ActualData<Features>? mergeData( | |
| ActualData<Features> value1, | |
| ActualData<Features> value2, | |
| ) { | |
| if ('${value1.value}' == '${value2.value}') { | |
| // The extension this parameter is seen twice in the extension method | |
| // and the corresponding tearoff. The features are identical, though, so | |
| // we just use the first. | |
| return value1; | |
| } | |
| return null; | |
| } | |
| } |