| // Copyright (c) 2019, 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/id.dart'; |
| import 'package:_fe_analyzer_shared/src/testing/id_testing.dart'; |
| import 'package:front_end/src/api_prototype/experimental_flags.dart'; |
| import 'package:front_end/src/fasta/kernel/class_hierarchy_builder.dart'; |
| import 'package:front_end/src/fasta/kernel/kernel_api.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'; |
| |
| const String cfeFromBuilderMarker = 'cfe:builder'; |
| |
| main(List<String> args) async { |
| Directory dataDir = new Directory.fromUri(Platform.script |
| .resolve('../../../_fe_analyzer_shared/test/inheritance/data')); |
| await runTests<String>(dataDir, |
| args: args, |
| createUriForFileName: createUriForFileName, |
| onFailure: onFailure, |
| runTest: runTestFor(const InheritanceDataComputer(), [ |
| new TestConfig(cfeMarker, 'cfe with nnbd', |
| explicitExperimentalFlags: const { |
| ExperimentalFlag.nonNullable: true |
| }, |
| librariesSpecificationUri: createUriForFileName('libraries.json'), |
| compileSdk: true), |
| new TestConfig(cfeFromBuilderMarker, 'cfe from builder', |
| explicitExperimentalFlags: const { |
| ExperimentalFlag.nonNullable: true |
| }, |
| librariesSpecificationUri: createUriForFileName('libraries.json'), |
| compileSdk: true) |
| ])); |
| } |
| |
| class InheritanceDataComputer extends DataComputer<String> { |
| const InheritanceDataComputer(); |
| |
| /// Function that computes a data mapping for [library]. |
| /// |
| /// Fills [actualMap] with the data. |
| void computeLibraryData( |
| TestConfig config, |
| InternalCompilerResult compilerResult, |
| Library library, |
| Map<Id, ActualData<String>> actualMap, |
| {bool verbose}) { |
| new InheritanceDataExtractor(config, compilerResult, actualMap) |
| .computeForLibrary(library); |
| } |
| |
| @override |
| void computeClassData( |
| TestConfig config, |
| InternalCompilerResult compilerResult, |
| Class cls, |
| Map<Id, ActualData<String>> actualMap, |
| {bool verbose}) { |
| new InheritanceDataExtractor(config, compilerResult, actualMap) |
| .computeForClass(cls); |
| } |
| |
| @override |
| bool get supportsErrors => true; |
| |
| @override |
| String computeErrorData(TestConfig config, InternalCompilerResult compiler, |
| Id id, List<FormattedMessage> errors) { |
| return errorsToText(errors, useCodes: true); |
| } |
| |
| @override |
| DataInterpreter<String> get dataValidator => const StringDataInterpreter(); |
| } |
| |
| class InheritanceDataExtractor extends CfeDataExtractor<String> { |
| final TestConfig _config; |
| final InternalCompilerResult _compilerResult; |
| |
| InheritanceDataExtractor( |
| this._config, this._compilerResult, Map<Id, ActualData<String>> actualMap) |
| : super(_compilerResult, actualMap); |
| |
| ClassHierarchy get _hierarchy => _compilerResult.classHierarchy; |
| |
| CoreTypes get _coreTypes => _compilerResult.coreTypes; |
| |
| ClassHierarchyBuilder get _classHierarchyBuilder => |
| _compilerResult.kernelTargetForTesting.loader.builderHierarchy; |
| |
| @override |
| String computeLibraryValue(Id id, Library node) { |
| return 'nnbd=${node.isNonNullableByDefault}'; |
| } |
| |
| @override |
| void computeForClass(Class node) { |
| super.computeForClass(node); |
| if (node.isAnonymousMixin) return; |
| // TODO(johnniwinther): Also compute member data from builders. |
| Set<Name> getters = _hierarchy |
| .getInterfaceMembers(node) |
| .map((Member member) => member.name) |
| .toSet(); |
| Set<Name> setters = _hierarchy |
| .getInterfaceMembers(node, setters: true) |
| .where((Member member) => |
| member is Procedure && member.kind == ProcedureKind.Setter || |
| member is Field && member.hasSetter) |
| .map((Member member) => member.name) |
| .toSet(); |
| |
| void addMember(Name name, {bool isSetter}) { |
| Member member = |
| _hierarchy.getInterfaceMember(node, name, setter: isSetter); |
| if (member.enclosingClass == _coreTypes.objectClass) { |
| return; |
| } |
| InterfaceType supertype = _hierarchy.getTypeAsInstanceOf( |
| _coreTypes.thisInterfaceType(node, node.enclosingLibrary.nonNullable), |
| member.enclosingClass, |
| node.enclosingLibrary); |
| Substitution substitution = Substitution.fromInterfaceType(supertype); |
| DartType type; |
| if (member is Procedure) { |
| if (member.kind == ProcedureKind.Getter) { |
| type = substitution.substituteType(member.function.returnType); |
| } else if (member.kind == ProcedureKind.Setter) { |
| type = substitution |
| .substituteType(member.function.positionalParameters.single.type); |
| } else { |
| Nullability functionTypeNullability; |
| if (node.enclosingLibrary.isNonNullableByDefault) { |
| functionTypeNullability = member.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 = node.enclosingLibrary.nonNullable; |
| } |
| type = substitution.substituteType( |
| member.function.computeThisFunctionType(functionTypeNullability)); |
| } |
| } else if (member is Field) { |
| type = substitution.substituteType(member.type); |
| } |
| if (type == null) { |
| return; |
| } |
| |
| String memberName = name.text; |
| if (isSetter) { |
| memberName += '='; |
| } |
| MemberId id = new MemberId.internal(memberName, className: node.name); |
| |
| TreeNode nodeWithOffset; |
| if (member.enclosingClass == node) { |
| nodeWithOffset = computeTreeNodeWithOffset(member); |
| } else { |
| nodeWithOffset = computeTreeNodeWithOffset(node); |
| } |
| |
| registerValue( |
| nodeWithOffset?.location?.file, |
| nodeWithOffset?.fileOffset, |
| id, |
| typeToText(type, TypeRepresentation.analyzerNonNullableByDefault), |
| member); |
| } |
| |
| for (Name name in getters) { |
| addMember(name, isSetter: false); |
| } |
| |
| for (Name name in setters) { |
| addMember(name, isSetter: true); |
| } |
| } |
| |
| @override |
| String computeClassValue(Id id, Class node) { |
| if (node.isAnonymousMixin) return null; |
| if (_config.marker == cfeMarker) { |
| List<String> supertypes = <String>[]; |
| for (Class superclass in computeAllSuperclasses(node)) { |
| Supertype supertype = _hierarchy.getClassAsInstanceOf(node, superclass); |
| if (supertype.classNode.isAnonymousMixin) continue; |
| assert( |
| supertype != null, "No instance of $superclass found for $node."); |
| supertypes.add(supertypeToText( |
| supertype, TypeRepresentation.analyzerNonNullableByDefault)); |
| } |
| supertypes.sort(); |
| return supertypes.join(','); |
| } else if (_config.marker == cfeFromBuilderMarker) { |
| ClassHierarchyNode classHierarchyNode = |
| _classHierarchyBuilder.getNodeFromClass(node); |
| Set<String> supertypes = <String>{}; |
| void addSupertype(Supertype supertype) { |
| if (supertype.classNode.isAnonymousMixin) return; |
| supertypes.add(supertypeToText( |
| supertype, TypeRepresentation.analyzerNonNullableByDefault)); |
| } |
| |
| addSupertype(new Supertype( |
| classHierarchyNode.classBuilder.cls, |
| getAsTypeArguments(classHierarchyNode.classBuilder.cls.typeParameters, |
| classHierarchyNode.classBuilder.library.library))); |
| classHierarchyNode.superclasses.forEach(addSupertype); |
| classHierarchyNode.interfaces.forEach(addSupertype); |
| List<String> sorted = supertypes.toList()..sort(); |
| return sorted.join(','); |
| } |
| return null; |
| } |
| } |