| // 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. |
| |
| import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'; |
| import 'package:front_end/src/api_prototype/front_end.dart'; |
| import 'package:front_end/src/api_prototype/kernel_generator.dart'; |
| import 'package:front_end/src/api_prototype/terminal_color_support.dart'; |
| import 'package:front_end/src/api_unstable/ddc.dart'; |
| import 'package:front_end/src/compute_platform_binaries_location.dart'; |
| import 'package:front_end/src/fasta/kernel/kernel_api.dart'; |
| import 'package:front_end/src/kernel_generator_impl.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/src/printer.dart'; |
| import 'package:kernel/target/targets.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:vm/target/vm.dart'; |
| |
| final Uri astLibraryUri = Uri.parse('package:kernel/ast.dart'); |
| final Uri canonicalNameLibraryUri = |
| Uri.parse('package:kernel/canonical_name.dart'); |
| Uri computePackageConfig(Uri repoDir) => |
| repoDir.resolve('.dart_tool/package_config.json'); |
| |
| /// Map from names of node classes that declares nominal entities that are _not_ |
| /// referenced through a [Reference] to their identifying name, if any. |
| /// |
| /// For these classes, [_fieldRuleMap] must defined whether fields of these |
| /// types should be consider declarations or references to the declarations. |
| /// |
| /// If the identifying name is non-null, this is used to determine the |
| /// nominality. For instance the name of a variable declaration is taking as |
| /// defining its identity. |
| const Map<String, String?> _declarativeClassesNames = const { |
| 'VariableDeclaration': 'name', |
| 'TypeParameter': 'name', |
| 'LabeledStatement': null, |
| 'SwitchCase': null, |
| }; |
| |
| /// Names of non-node, non-enum classes that should be treated as atomic |
| /// values. |
| const Set<String> _utilityClassesAsValues = const { |
| 'Version', |
| }; |
| |
| /// Names of subclasses of [Node] that do _not_ have `visitX` or `defaultX` |
| /// methods. |
| const Set<String> _classesWithoutVisitMethods = const { |
| 'InvocationExpression', |
| 'InstanceInvocationExpression', |
| 'NamedNode', |
| 'PrimitiveConstant', |
| }; |
| |
| /// Names of inner [Node] classes that are used as interfaces for (generally) |
| /// interchangeable classes. |
| /// |
| /// For instance, when [Expression] is used as the field type, any subtype of |
| /// [Expression] can be used to populate the field. |
| const Set<String> _interchangeableClasses = const { |
| 'Member', |
| 'Statement', |
| 'Expression', |
| 'Constant', |
| 'DartType', |
| 'Initializer', |
| }; |
| |
| /// Names of subclasses of [NamedNode] that do _not_ have `visitXReference` or |
| /// `defaultXReference` methods. |
| const Set<String> _classesWithoutVisitReference = const { |
| 'NamedNode', |
| 'Library', |
| 'PrimitiveConstant', |
| }; |
| |
| /// Map of [FieldRule]s. These consist of a map for each class name, with |
| /// `null` used for "any class". For each class, a map from field names to |
| /// [FieldRule] define exceptions to how a field should be treated. |
| /// |
| /// If a field name maps to `null`, the field is not included in the [AstModel]. |
| const Map<String?, Map<String, FieldRule?>> _fieldRuleMap = { |
| null: { |
| 'hashCode': null, |
| 'parent': null, |
| }, |
| 'Component': { |
| 'root': null, |
| '_mainMethodName': FieldRule(name: 'mainMethodName'), |
| '_mode': FieldRule(name: 'mode'), |
| }, |
| 'Library': { |
| '_languageVersion': FieldRule(name: 'languageVersion'), |
| '_libraryId': null, |
| '_classes': FieldRule(name: 'classes'), |
| '_typedefs': FieldRule(name: 'typedefs'), |
| '_extensions': FieldRule(name: 'extensions'), |
| '_fields': FieldRule(name: 'fields'), |
| '_procedures': FieldRule(name: 'procedures'), |
| }, |
| 'Class': { |
| 'typeParameters': FieldRule(isDeclaration: true), |
| '_constructorsView': null, |
| '_constructorsInternal': FieldRule(name: 'constructors'), |
| '_fieldsView': null, |
| '_fieldsInternal': FieldRule(name: 'fields'), |
| '_proceduresView': null, |
| '_proceduresInternal': FieldRule(name: 'procedures'), |
| '_redirectingFactoriesView': null, |
| '_redirectingFactoriesInternal': FieldRule(name: 'redirectingFactories'), |
| 'lazyBuilder': null, |
| 'dirty': null, |
| }, |
| 'Extension': { |
| 'typeParameters': FieldRule(isDeclaration: true), |
| }, |
| 'Field': { |
| 'reference': FieldRule(name: 'getterReference'), |
| }, |
| 'TypeParameter': { |
| '_variance': FieldRule(name: 'variance'), |
| }, |
| 'FunctionNode': { |
| '_body': FieldRule(name: 'body'), |
| 'typeParameters': FieldRule(isDeclaration: true), |
| 'positionalParameters': FieldRule(isDeclaration: true), |
| 'namedParameters': FieldRule(isDeclaration: true), |
| }, |
| 'Typedef': { |
| 'typeParameters': FieldRule(isDeclaration: true), |
| 'typeParametersOfFunctionType': FieldRule(isDeclaration: false), |
| 'positionalParameters': FieldRule(isDeclaration: false), |
| 'namedParameters': FieldRule(isDeclaration: false), |
| }, |
| 'TypedefTearOff': { |
| 'typeParameters': FieldRule(isDeclaration: true), |
| }, |
| 'TypedefTearOffConstant': { |
| 'parameters': FieldRule(isDeclaration: true), |
| }, |
| 'LocalInitializer': { |
| 'variable': FieldRule(isDeclaration: true), |
| }, |
| 'Let': { |
| 'variable': FieldRule(isDeclaration: true), |
| }, |
| 'VariableGet': { |
| 'variable': FieldRule(isDeclaration: false), |
| }, |
| 'VariableSet': { |
| 'variable': FieldRule(isDeclaration: false), |
| }, |
| 'LocalFunctionInvocation': { |
| 'variable': FieldRule(isDeclaration: false), |
| }, |
| 'BreakStatement': { |
| 'target': FieldRule(isDeclaration: false), |
| }, |
| 'ForStatement': { |
| 'variables': FieldRule(isDeclaration: true), |
| }, |
| 'ForInStatement': { |
| 'variable': FieldRule(isDeclaration: true), |
| }, |
| 'SwitchStatement': { |
| 'cases': FieldRule(isDeclaration: true), |
| }, |
| 'ContinueSwitchStatement': { |
| 'target': FieldRule(isDeclaration: false), |
| }, |
| 'Catch': { |
| 'exception': FieldRule(isDeclaration: true), |
| 'stackTrace': FieldRule(isDeclaration: true), |
| }, |
| 'FunctionDeclaration': { |
| 'variable': FieldRule(isDeclaration: true), |
| }, |
| 'FunctionType': { |
| 'typeParameters': FieldRule(isDeclaration: true), |
| }, |
| 'TypeParameterType': { |
| 'parameter': FieldRule(isDeclaration: false), |
| }, |
| }; |
| |
| /// Data that determines exceptions to how fields are used. |
| class FieldRule { |
| /// If non-null, the field should be accessed by this name. |
| /// |
| /// This is for instance used for private fields accessed through a public |
| /// getter. |
| final String? name; |
| |
| /// For fields contain ast class of kind `AstClassKind.declarative`, this |
| /// value defines whether the field should be treated as a declaration or |
| /// a reference to the declaration. |
| final bool? isDeclaration; |
| |
| const FieldRule({this.name, this.isDeclaration}); |
| } |
| |
| /// Return the [FieldRule] to use for the [field] in [AstClass]. |
| FieldRule? getFieldRule(AstClass astClass, Field field) { |
| String name = field.name.text; |
| Map<String?, FieldRule?>? leafClassMap = _fieldRuleMap[astClass.name]; |
| if (leafClassMap != null && leafClassMap.containsKey(name)) { |
| return leafClassMap[name]; |
| } |
| Map<String?, FieldRule?>? enclosingClassMap = |
| _fieldRuleMap[field.enclosingClass!.name]; |
| if (enclosingClassMap != null && enclosingClassMap.containsKey(name)) { |
| return enclosingClassMap[name]; |
| } |
| Map<String?, FieldRule?>? defaultClassMap = _fieldRuleMap[null]; |
| if (defaultClassMap != null && defaultClassMap.containsKey(name)) { |
| return defaultClassMap[name]; |
| } |
| return new FieldRule(name: name); |
| } |
| |
| /// Categorization of classes declared in 'package:kernel/ast.dart'. |
| enum AstClassKind { |
| /// The root [Node] class. |
| root, |
| |
| /// An abstract node class that is a superclass of a public class. Most of |
| /// these have a corresponding `defaultX` visitor method. |
| /// |
| /// For instance [Statement] and [Expression]. |
| inner, |
| |
| /// A concrete node class. These are the classes for which we have `visitX` |
| /// methods. |
| /// |
| /// For instance [Procedure] and [VariableGet]. |
| public, |
| |
| /// A concrete node class that serves only as an implementation of public |
| /// node class. |
| /// |
| /// For instance [PrivateName] and [PublicName] that implements the public |
| /// node [Name]. |
| implementation, |
| |
| /// A node class that serves as an interface. |
| /// |
| /// For instance `FileUriNode`. |
| interface, |
| |
| /// A named node class that declares a nominal entity that is used with a |
| /// reference. |
| named, |
| |
| /// A node class that declares a nominal entity but used without reference. |
| /// |
| /// For instance [VariableDeclaration] which introduces a new entity when it |
| /// occurs in a [Block] but is used as a reference when it occurs in a |
| /// [VariableGet]. |
| declarative, |
| |
| /// A none-node class that should be treated as a composite structure. |
| /// |
| /// For instance [ConstantMapEntry] which is a tuple of a [Constant] key and |
| /// a [Constant] value. |
| utilityAsStructure, |
| |
| /// A none-node class that should be treated as an atomic value. |
| /// |
| /// For instance enum classes. |
| utilityAsValue, |
| } |
| |
| class AstClass { |
| final Class node; |
| AstClassKind? _kind; |
| final String? declarativeName; |
| final bool isInterchangeable; |
| |
| AstClass? superclass; |
| List<AstClass> interfaces = []; |
| List<AstClass> subclasses = []; |
| List<AstClass> subtypes = []; |
| |
| Map<String, AstField> fields = {}; |
| |
| AstClass(this.node, |
| {this.superclass, |
| AstClassKind? kind, |
| this.declarativeName, |
| required this.isInterchangeable}) |
| : _kind = kind { |
| if (superclass != null) { |
| superclass!.subclasses.add(this); |
| } |
| } |
| |
| String get name => node.name; |
| |
| AstClassKind get kind { |
| if (_kind == null) { |
| if (node.isAbstract) { |
| if (subclasses.isNotEmpty) { |
| if (subclasses.every( |
| (element) => element.kind == AstClassKind.implementation)) { |
| _kind = AstClassKind.public; |
| } else { |
| _kind = AstClassKind.inner; |
| } |
| } else { |
| _kind = AstClassKind.interface; |
| } |
| } else { |
| if (node.name.startsWith('_')) { |
| _kind = AstClassKind.implementation; |
| } else { |
| _kind = AstClassKind.public; |
| } |
| } |
| } |
| return _kind!; |
| } |
| |
| /// Returns `true` if this class has a `visitX` or `defaultX` method. |
| /// |
| /// This is only valid for subclass of [Node]. |
| bool get hasVisitMethod { |
| switch (kind) { |
| case AstClassKind.root: |
| case AstClassKind.inner: |
| case AstClassKind.public: |
| case AstClassKind.named: |
| case AstClassKind.declarative: |
| return !_classesWithoutVisitMethods.contains(name); |
| case AstClassKind.implementation: |
| case AstClassKind.interface: |
| case AstClassKind.utilityAsStructure: |
| case AstClassKind.utilityAsValue: |
| return false; |
| } |
| } |
| |
| /// Returns `true` if this class has a `visitXReference` or |
| /// `defaultXReference` method. |
| /// |
| /// This is only valid for subclass of [NamedNode] or [Constant]. |
| bool get hasVisitReferenceMethod { |
| switch (kind) { |
| case AstClassKind.root: |
| case AstClassKind.inner: |
| case AstClassKind.public: |
| case AstClassKind.named: |
| case AstClassKind.declarative: |
| return !_classesWithoutVisitReference.contains(name); |
| case AstClassKind.implementation: |
| case AstClassKind.interface: |
| case AstClassKind.utilityAsStructure: |
| case AstClassKind.utilityAsValue: |
| return false; |
| } |
| } |
| |
| String dump([String indent = ""]) { |
| StringBuffer sb = new StringBuffer(); |
| sb.writeln('${indent}${node.name} ($kind)'); |
| for (AstField field in fields.values) { |
| sb.write(field.dump('${indent} ')); |
| } |
| for (AstClass subclass in subclasses) { |
| sb.write(subclass.dump('${indent} ')); |
| } |
| return sb.toString(); |
| } |
| |
| @override |
| String toString() => '${runtimeType}(${name})'; |
| } |
| |
| /// Categorization of field types. |
| enum AstFieldKind { |
| /// An atomic non-node value. |
| /// |
| /// For instance an [int] value. |
| value, |
| |
| /// A [Node] value that is part of the tree. |
| /// |
| /// For instance an [Expression] value. |
| node, |
| |
| /// A [Reference] value. |
| reference, |
| |
| /// A reference to a declarative [Node]. |
| /// |
| /// For instance the reference to [VariableDeclaration] in [VariableGet]. |
| use, |
| |
| /// A list of values. |
| list, |
| |
| /// A set of values. |
| set, |
| |
| /// A map of values. |
| map, |
| |
| /// A non-node composite value. |
| /// |
| /// For instance a [ConstantMapEntry] that must be treat as a tuple of |
| /// a [Constant] key and a [Constant] value. |
| utility, |
| } |
| |
| /// Structural description of a field type. |
| class FieldType { |
| final DartType type; |
| final AstFieldKind kind; |
| |
| FieldType(this.type, this.kind); |
| |
| @override |
| String toString() => 'FieldType($type,$kind)'; |
| } |
| |
| class ListFieldType extends FieldType { |
| final FieldType elementType; |
| |
| ListFieldType(DartType type, this.elementType) |
| : super(type, AstFieldKind.list); |
| |
| @override |
| String toString() => 'ListFieldType($type,$elementType)'; |
| } |
| |
| class SetFieldType extends FieldType { |
| final FieldType elementType; |
| |
| SetFieldType(DartType type, this.elementType) : super(type, AstFieldKind.set); |
| |
| @override |
| String toString() => 'SetFieldType($type,$elementType)'; |
| } |
| |
| class MapFieldType extends FieldType { |
| final FieldType keyType; |
| final FieldType valueType; |
| |
| MapFieldType(DartType type, this.keyType, this.valueType) |
| : super(type, AstFieldKind.map); |
| |
| @override |
| String toString() => 'MapFieldType($type,$keyType,$valueType)'; |
| } |
| |
| class UtilityFieldType extends FieldType { |
| final AstClass astClass; |
| |
| UtilityFieldType(DartType type, this.astClass) |
| : super(type, AstFieldKind.utility); |
| |
| @override |
| String toString() => 'UtilityFieldType($type,$astClass)'; |
| } |
| |
| class AstField { |
| final AstClass astClass; |
| final Field node; |
| final String name; |
| final FieldType type; |
| final AstField? parentField; |
| |
| AstField(this.astClass, this.node, this.name, this.type, this.parentField); |
| |
| String dump([String indent = ""]) { |
| StringBuffer sb = new StringBuffer(); |
| sb.writeln('$indent$name $type'); |
| return sb.toString(); |
| } |
| |
| @override |
| String toString() => '${runtimeType}(${name})'; |
| } |
| |
| class AstModel { |
| final AstClass nodeClass; |
| final AstClass namedNodeClass; |
| final AstClass constantClass; |
| |
| AstModel(this.nodeClass, this.namedNodeClass, this.constantClass); |
| |
| /// Returns an [Iterable] for all declarative [Node] classes in the AST model. |
| Iterable<AstClass> get declarativeClasses { |
| return classes.where((cls) => cls.kind == AstClassKind.declarative); |
| } |
| |
| /// Returns an [Iterable] for all [Node] (sub)classes in the AST model. |
| Iterable<AstClass> get classes sync* { |
| Iterable<AstClass> visitClass(AstClass cls) sync* { |
| yield cls; |
| for (AstClass subclass in cls.subclasses) { |
| yield* visitClass(subclass); |
| } |
| } |
| |
| yield* visitClass(nodeClass); |
| } |
| } |
| |
| /// Computes the [AstModel] from 'package:kernel/ast' using [repoDir] to locate |
| /// the package config file. |
| /// |
| /// If [printDump] is `true`, a dump of the model printed to stdout. |
| Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async { |
| bool errorsFound = false; |
| CompilerOptions options = new CompilerOptions(); |
| options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); |
| options.compileSdk = true; |
| options.target = new VmTarget(new TargetFlags()); |
| options.librariesSpecificationUri = repoDir.resolve("sdk/lib/libraries.json"); |
| options.environmentDefines = const {}; |
| options.packagesFileUri = computePackageConfig(repoDir); |
| options.onDiagnostic = (DiagnosticMessage message) { |
| printDiagnosticMessage(message, print); |
| if (message.severity == Severity.error) { |
| errorsFound = true; |
| } |
| }; |
| |
| InternalCompilerResult compilerResult = (await kernelForProgramInternal( |
| astLibraryUri, options, |
| retainDataForTesting: true, |
| requireMain: false)) as InternalCompilerResult; |
| if (errorsFound) { |
| throw 'Errors found'; |
| } |
| ClassHierarchy classHierarchy = compilerResult.classHierarchy!; |
| CoreTypes coreTypes = compilerResult.coreTypes!; |
| TypeEnvironment typeEnvironment = |
| new TypeEnvironment(coreTypes, classHierarchy); |
| |
| Library astLibrary = compilerResult.component!.libraries |
| .singleWhere((library) => library.importUri == astLibraryUri); |
| |
| void reportError(String message) { |
| print(message); |
| errorsFound = true; |
| } |
| |
| Map<String, String?> declarativeClassesNames = {..._declarativeClassesNames}; |
| Set<String> classesWithoutVisitMethods = _classesWithoutVisitMethods.toSet(); |
| Set<String> classesWithoutVisitReference = |
| _classesWithoutVisitReference.toSet(); |
| Map<String?, Map<String, FieldRule?>> fieldRuleMap = {..._fieldRuleMap}; |
| Map<String, FieldRule?> nullFieldRules = {...?fieldRuleMap.remove(null)}; |
| Set<String> interchangeableClasses = _interchangeableClasses.toSet(); |
| for (Class cls in astLibrary.classes) { |
| declarativeClassesNames.remove(cls.name); |
| classesWithoutVisitMethods.remove(cls.name); |
| classesWithoutVisitReference.remove(cls.name); |
| interchangeableClasses.remove(cls.name); |
| |
| Map<String, FieldRule?> fieldRules = {...?fieldRuleMap.remove(cls.name)}; |
| Set<String> renames = {}; |
| Class? parent = cls; |
| while (parent != null && parent.enclosingLibrary == astLibrary) { |
| for (Field field in parent.fields) { |
| bool hasFieldRule = fieldRules.containsKey(field.name.text); |
| FieldRule? fieldRule = fieldRules.remove(field.name.text); |
| if (fieldRule != null) { |
| if (fieldRule.name != null) { |
| renames.add(fieldRule.name!); |
| } |
| } |
| if (!hasFieldRule) { |
| if (!cls.isEnum && !field.isStatic && field.name.isPrivate) { |
| reportError( |
| 'Private field `${field.name.text}` in ${parent.name} must ' |
| 'have a field rule.'); |
| } |
| } |
| if (nullFieldRules.containsKey(field.name.text)) { |
| FieldRule? nullFieldRule = nullFieldRules.remove(field.name.text); |
| if (nullFieldRule != null) { |
| reportError('Only `null` is allowed for class `null`.'); |
| } |
| } |
| } |
| parent = parent.superclass; |
| } |
| for (Procedure procedure in cls.procedures) { |
| renames.remove(procedure.name.text); |
| } |
| if (renames.isNotEmpty) { |
| reportError('Unknown procedure(s) for field redirections in ' |
| '${cls.name}: ${renames}'); |
| } |
| if (fieldRules.isNotEmpty) { |
| reportError( |
| 'Unknown field(s) for rules in ${cls.name}: ${fieldRules.keys}'); |
| } |
| } |
| if (declarativeClassesNames.isNotEmpty) { |
| reportError('Unknown declarative classes: ${declarativeClassesNames}'); |
| } |
| if (classesWithoutVisitMethods.isNotEmpty) { |
| reportError('Unknown classes without visit methods: ' |
| '${classesWithoutVisitMethods}'); |
| } |
| if (classesWithoutVisitReference.isNotEmpty) { |
| reportError('Unknown classes without visit reference methods: ' |
| '${classesWithoutVisitReference}'); |
| } |
| if (interchangeableClasses.isNotEmpty) { |
| reportError('Unknown interchangeable classes: ' |
| '${interchangeableClasses}'); |
| } |
| if (fieldRuleMap.isNotEmpty) { |
| reportError('Unknown classes with field rules: ${fieldRuleMap.keys}'); |
| } |
| |
| Class classNode = astLibrary.classes.singleWhere((cls) => cls.name == 'Node'); |
| DartType nullableNodeType = |
| classNode.getThisType(coreTypes, Nullability.nullable); |
| |
| Class classNamedNode = |
| astLibrary.classes.singleWhere((cls) => cls.name == 'NamedNode'); |
| |
| Class classConstant = |
| astLibrary.classes.singleWhere((cls) => cls.name == 'Constant'); |
| |
| Library canonicalNameLibrary = compilerResult.component!.libraries |
| .singleWhere((library) => library.importUri == canonicalNameLibraryUri); |
| |
| Class referenceClass = canonicalNameLibrary.classes |
| .singleWhere((cls) => cls.name == 'Reference'); |
| DartType nullableReferenceType = |
| referenceClass.getThisType(coreTypes, Nullability.nullable); |
| |
| Set<Class> declarativeClasses = {}; |
| Set<DartType> declarativeTypes = {}; |
| for (String name in _declarativeClassesNames.keys) { |
| Class cls = astLibrary.classes.singleWhere((cls) => cls.name == name); |
| declarativeClasses.add(cls); |
| declarativeTypes.add(cls.getThisType(coreTypes, Nullability.nullable)); |
| } |
| |
| Map<Class, AstClass> classMap = {}; |
| |
| /// Computes the [AstClass] corresponding to [node] if [node] is declared in |
| /// 'package:kernel/ast.dart'. |
| AstClass? computeAstClass(Class? node) { |
| if (node == null) return null; |
| if (node.enclosingLibrary != astLibrary) return null; |
| |
| AstClass? astClass = classMap[node]; |
| if (astClass == null) { |
| bool isInterchangeable = _interchangeableClasses.contains(node.name); |
| if (node == classNode) { |
| astClass = new AstClass(node, |
| kind: AstClassKind.root, isInterchangeable: isInterchangeable); |
| } else if (classHierarchy.isSubtypeOf(node, classNode)) { |
| AstClass? superclass = computeAstClass(node.superclass); |
| AstClassKind? kind; |
| String? declarativeName; |
| if (!node.isAbstract && |
| classHierarchy.isSubtypeOf(node, classNamedNode)) { |
| kind = AstClassKind.named; |
| } else if (declarativeClasses.contains(node)) { |
| kind = AstClassKind.declarative; |
| declarativeName = _declarativeClassesNames[node.name]; |
| } |
| astClass = new AstClass(node, |
| superclass: superclass, |
| kind: kind, |
| declarativeName: declarativeName, |
| isInterchangeable: isInterchangeable); |
| for (Supertype supertype in node.implementedTypes) { |
| AstClass? astSupertype = computeAstClass(supertype.classNode); |
| if (astSupertype != null) { |
| astClass.interfaces.add(astSupertype); |
| astSupertype.subtypes.add(astClass); |
| } |
| } |
| } else if (node.isEnum || _utilityClassesAsValues.contains(node.name)) { |
| astClass = new AstClass(node, |
| kind: AstClassKind.utilityAsValue, |
| isInterchangeable: isInterchangeable); |
| } else { |
| AstClass? superclass = computeAstClass(node.superclass); |
| astClass = new AstClass(node, |
| superclass: superclass, |
| kind: AstClassKind.utilityAsStructure, |
| isInterchangeable: isInterchangeable); |
| } |
| classMap[node] = astClass; |
| } |
| return astClass; |
| } |
| |
| for (Class cls in astLibrary.classes) { |
| computeAstClass(cls); |
| } |
| |
| Set<AstClass> hasComputedFields = {}; |
| |
| void computeAstFields(AstClass astClass) { |
| if (!hasComputedFields.add(astClass)) return; |
| |
| void computeAstField(Field field, Substitution substitution) { |
| if (field.isStatic) { |
| return; |
| } |
| FieldRule? rule = getFieldRule(astClass, field); |
| if (rule == null) { |
| return; |
| } |
| DartType type = substitution.substituteType(field.type); |
| |
| FieldType computeFieldType(DartType type) { |
| bool isDeclarativeType = false; |
| for (DartType declarativeType in declarativeTypes) { |
| if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf( |
| type, declarativeType, SubtypeCheckMode.withNullabilities)) { |
| isDeclarativeType = true; |
| break; |
| } |
| } |
| if (isDeclarativeType) { |
| if (rule.isDeclaration == null) { |
| reportError( |
| "No field rule for '${field.name}' in ${astClass.name}.\n" |
| "The field type contains the declarative type " |
| "'${type.toText(defaultAstTextStrategy)}' " |
| "and a rule must therefore specify " |
| "whether this constitutes declarative or referential use."); |
| } |
| if (!rule.isDeclaration!) { |
| return new FieldType(type, AstFieldKind.use); |
| } |
| } |
| if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf(type, coreTypes.listNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| DartType elementType = typeEnvironment |
| .getTypeArgumentsAsInstanceOf(type, coreTypes.listClass)! |
| .single; |
| return new ListFieldType(type, computeFieldType(elementType)); |
| } else if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf(type, coreTypes.setNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| DartType elementType = typeEnvironment |
| .getTypeArgumentsAsInstanceOf(type, coreTypes.setClass)! |
| .single; |
| return new SetFieldType(type, computeFieldType(elementType)); |
| } else if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf(type, coreTypes.mapNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| List<DartType> typeArguments = typeEnvironment |
| .getTypeArgumentsAsInstanceOf(type, coreTypes.mapClass)!; |
| return new MapFieldType(type, computeFieldType(typeArguments[0]), |
| computeFieldType(typeArguments[1])); |
| } else if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf( |
| type, nullableNodeType, SubtypeCheckMode.withNullabilities)) { |
| return new FieldType(type, AstFieldKind.node); |
| } else if (type is InterfaceType && |
| typeEnvironment.isSubtypeOf(type, nullableReferenceType, |
| SubtypeCheckMode.withNullabilities)) { |
| return new FieldType(type, AstFieldKind.reference); |
| } else { |
| if (type is InterfaceType) { |
| AstClass? astClass = classMap[type.classNode]; |
| if (astClass != null && |
| astClass.kind == AstClassKind.utilityAsStructure) { |
| return new UtilityFieldType(type, astClass); |
| } |
| } |
| return new FieldType(type, AstFieldKind.value); |
| } |
| } |
| |
| FieldType? fieldType = computeFieldType(type); |
| String name = rule.name ?? field.name.text; |
| astClass.fields[name] = new AstField( |
| astClass, field, name, fieldType, astClass.superclass?.fields[name]); |
| } |
| |
| AstClass? parent = astClass; |
| Substitution substitution = Substitution.empty; |
| while (parent != null) { |
| for (Field field in parent.node.fields) { |
| computeAstField(field, substitution); |
| } |
| parent = parent.superclass; |
| if (parent != null) { |
| substitution = Substitution.fromSupertype( |
| classHierarchy.getClassAsInstanceOf(astClass.node, parent.node)!); |
| } |
| } |
| } |
| |
| for (AstClass astClass in classMap.values) { |
| computeAstFields(astClass); |
| } |
| |
| AstClass astClassNode = classMap[classNode]!; |
| AstClass astClassNamedNode = classMap[classNamedNode]!; |
| AstClass astClassConstant = classMap[classConstant]!; |
| |
| if (printDump) { |
| print(classMap[classNode]!.dump()); |
| for (AstClass astClass in classMap.values) { |
| if (astClass != astClassNode && astClass.superclass == null) { |
| print(astClass.dump()); |
| } |
| } |
| } |
| if (errorsFound) { |
| throw 'Errors found'; |
| } |
| return new AstModel(astClassNode, astClassNamedNode, astClassConstant); |
| } |