| // Copyright (c) 2016, 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. |
| library kernel.analyzer.loader; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io' as io; |
| |
| import 'package:analyzer/analyzer.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/physical_file_system.dart'; |
| import 'package:analyzer/source/package_map_resolver.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/scanner/scanner.dart'; |
| import 'package:analyzer/src/dart/sdk/sdk.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/parser.dart'; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/summary/summary_sdk.dart'; |
| import 'package:kernel/application_root.dart'; |
| import 'package:package_config/discovery.dart'; |
| import 'package:package_config/packages.dart'; |
| |
| import 'package:kernel/ast.dart' as ast; |
| import 'package:kernel/target/targets.dart' show Target; |
| import 'package:kernel/type_algebra.dart'; |
| import 'package:analyzer/src/kernel/ast_from_analyzer.dart'; |
| |
| /// Options passed to the Dart frontend. |
| class DartOptions { |
| /// True if user code should be loaded in strong mode. |
| bool strongMode; |
| |
| /// True if the Dart SDK should be loaded in strong mode. |
| bool strongModeSdk; |
| |
| /// Path to the sdk sources, ignored if sdkSummary is provided. |
| String sdk; |
| |
| /// Path to a summary of the sdk sources. |
| String sdkSummary; |
| |
| /// Path to the `.packages` file. |
| String packagePath; |
| |
| /// Root used to relativize app file-urls, making them machine agnostic. |
| ApplicationRoot applicationRoot; |
| |
| Map<Uri, Uri> customUriMappings; |
| |
| /// Environment definitions provided via `-Dkey=value`. |
| Map<String, String> declaredVariables; |
| |
| DartOptions( |
| {bool strongMode: false, |
| bool strongModeSdk, |
| this.sdk, |
| this.sdkSummary, |
| this.packagePath, |
| ApplicationRoot applicationRoot, |
| Map<Uri, Uri> customUriMappings, |
| Map<String, String> declaredVariables}) |
| : this.customUriMappings = customUriMappings ?? <Uri, Uri>{}, |
| this.declaredVariables = declaredVariables ?? <String, String>{}, |
| this.strongMode = strongMode, |
| this.strongModeSdk = strongModeSdk ?? strongMode, |
| this.applicationRoot = applicationRoot ?? new ApplicationRoot.none(); |
| } |
| |
| abstract class ReferenceLevelLoader { |
| ast.Library getLibraryReference(LibraryElement element); |
| ast.Class getClassReference(ClassElement element); |
| ast.Member getMemberReference(Element element); |
| ast.Class getRootClassReference(); |
| ast.Constructor getRootClassConstructorReference(); |
| ast.Class getCoreClassReference(String className); |
| ast.Constructor getCoreClassConstructorReference(String className, |
| {String constructorName, String library}); |
| ast.TypeParameter tryGetClassTypeParameter(TypeParameterElement element); |
| ast.Class getSharedMixinApplicationClass( |
| ast.Library library, ast.Class supertype, ast.Class mixin); |
| bool get strongMode; |
| |
| /// Whether or not to include redirecting factories in the output. |
| bool get ignoreRedirectingFactories; |
| } |
| |
| class DartLoader implements ReferenceLevelLoader { |
| final ast.Program program; |
| final ApplicationRoot applicationRoot; |
| final Bimap<ClassElement, ast.Class> _classes = |
| new Bimap<ClassElement, ast.Class>(); |
| final Bimap<Element, ast.Member> _members = new Bimap<Element, ast.Member>(); |
| final Map<TypeParameterElement, ast.TypeParameter> _classTypeParameters = |
| <TypeParameterElement, ast.TypeParameter>{}; |
| final Map<ast.Library, Map<String, ast.Class>> _mixinApplications = |
| <ast.Library, Map<String, ast.Class>>{}; |
| final Map<LibraryElement, ast.Library> _libraries = |
| <LibraryElement, ast.Library>{}; |
| final AnalysisContext context; |
| LibraryElement _dartCoreLibrary; |
| final List errors = []; |
| final List libraryElements = []; |
| |
| /// Classes that have been referenced, and must be promoted to type level |
| /// so as not to expose partially initialized classes. |
| final List<ast.Class> temporaryClassWorklist = []; |
| |
| final Map<LibraryElement, List<ClassElement>> mixinLibraryWorklist = {}; |
| |
| final bool ignoreRedirectingFactories; |
| |
| LibraryElement _libraryBeingLoaded = null; |
| ClassElement _classBeingPromotedToMixin = null; |
| |
| bool get strongMode => context.analysisOptions.strongMode; |
| |
| DartLoader(this.program, DartOptions options, Packages packages, |
| {DartSdk dartSdk, |
| AnalysisContext context, |
| this.ignoreRedirectingFactories: true}) |
| : this.context = |
| context ?? createContext(options, packages, dartSdk: dartSdk), |
| this.applicationRoot = options.applicationRoot; |
| |
| String getLibraryName(LibraryElement element) { |
| return element.name.isEmpty ? null : element.name; |
| } |
| |
| LibraryElement getLibraryElementFromUri(Uri uri) { |
| var source = context.sourceFactory.forUri2(uri); |
| if (source == null) return null; |
| return context.computeLibraryElement(source); |
| } |
| |
| ast.Library getLibraryReference(LibraryElement element) { |
| var uri = applicationRoot.relativeUri(element.source.uri); |
| var library = _libraries[element]; |
| if (library == null) { |
| library = new ast.Library(uri) |
| ..isExternal = true |
| ..name = getLibraryName(element) |
| ..fileUri = element.source.uri; |
| program.libraries.add(library..parent = program); |
| _libraries[element] = library; |
| } |
| return library; |
| } |
| |
| ast.Library getLibraryReferenceFromUri(Uri uri) { |
| return getLibraryReference(getLibraryElementFromUri(uri)); |
| } |
| |
| void _buildTopLevelMember( |
| ast.Member member, Element element, Declaration astNode) { |
| assert(member.parent != null); |
| new MemberBodyBuilder(this, member, element).build(astNode); |
| } |
| |
| /// True if [element] is in the process of being loaded by |
| /// [_buildLibraryBody]. |
| /// |
| /// If this is the case, we should avoid adding new members to the classes |
| /// in the library, since the AST builder will rebuild the member lists. |
| bool isLibraryBeingLoaded(LibraryElement element) { |
| return _libraryBeingLoaded == element; |
| } |
| |
| bool isClassBeingPromotedToMixin(ClassElement element) { |
| return _classBeingPromotedToMixin == element; |
| } |
| |
| void _buildLibraryBody(LibraryElement element, ast.Library library, |
| List<CompilationUnit> units) { |
| assert(_libraryBeingLoaded == null); |
| _libraryBeingLoaded = element; |
| var classes = <ast.Class>[]; |
| var procedures = <ast.Procedure>[]; |
| var fields = <ast.Field>[]; |
| |
| void loadClass(NamedCompilationUnitMember declaration) { |
| // [declaration] can be a ClassDeclaration, EnumDeclaration, or a |
| // ClassTypeAlias. |
| ClassElement element = declaration.element; |
| var node = getClassReference(element); |
| promoteToBodyLevel(node, element, declaration); |
| classes.add(node); |
| } |
| |
| void loadProcedure(FunctionDeclaration declaration) { |
| var element = declaration.element; |
| var node = getMemberReference(element); |
| _buildTopLevelMember(node, element, declaration); |
| procedures.add(node); |
| } |
| |
| void loadField(TopLevelVariableDeclaration declaration) { |
| for (var field in declaration.variables.variables) { |
| var element = field.element; |
| // Ignore fields inserted through error recovery. |
| if (element.name == '') continue; |
| var node = getMemberReference(element); |
| _buildTopLevelMember(node, element, field); |
| fields.add(node); |
| } |
| } |
| |
| for (var unit in units) { |
| for (CompilationUnitMember declaration in unit.declarations) { |
| if (declaration is ClassDeclaration || |
| declaration is EnumDeclaration || |
| declaration is ClassTypeAlias) { |
| loadClass(declaration); |
| } else if (declaration is FunctionDeclaration) { |
| loadProcedure(declaration); |
| } else if (declaration is TopLevelVariableDeclaration) { |
| loadField(declaration); |
| } else if (declaration is FunctionTypeAlias) { |
| // Nothing to do. Typedefs are handled lazily while constructing type |
| // references. |
| } else { |
| throw "unexpected node: ${declaration.runtimeType} $declaration"; |
| } |
| } |
| } |
| libraryElements.add(element); |
| _iterateTemporaryClassWorklist(); |
| // Ensure everything is stored in the original declaration order. |
| library.classes |
| ..clear() |
| ..addAll(classes) |
| ..addAll(_mixinApplications[library]?.values ?? const []); |
| library.fields |
| ..clear() |
| ..addAll(fields); |
| library.procedures |
| ..clear() |
| ..addAll(procedures); |
| _libraryBeingLoaded = null; |
| } |
| |
| LibraryElement getDartCoreLibrary() { |
| return _dartCoreLibrary ??= _findLibraryElement('dart:core'); |
| } |
| |
| LibraryElement _findLibraryElement(String uri) { |
| var source = context.sourceFactory.forUri(uri); |
| if (source == null) return null; |
| return context.computeLibraryElement(source); |
| } |
| |
| ast.Class getRootClassReference() { |
| return getCoreClassReference('Object'); |
| } |
| |
| ast.Constructor getRootClassConstructorReference() { |
| var element = getDartCoreLibrary().getType('Object').constructors[0]; |
| return getMemberReference(element); |
| } |
| |
| ast.Class getCoreClassReference(String className) { |
| return getClassReference(getDartCoreLibrary().getType(className)); |
| } |
| |
| ast.Constructor getCoreClassConstructorReference(String className, |
| {String constructorName, String library}) { |
| LibraryElement libraryElement = |
| library != null ? _findLibraryElement(library) : getDartCoreLibrary(); |
| ClassElement element = libraryElement.getType(className); |
| if (element == null) { |
| throw 'Missing core class $className from ${libraryElement.name}'; |
| } |
| var constructor = element.constructors.firstWhere((constructor) { |
| return (constructorName == null) |
| ? (constructor.nameLength == 0) |
| : (constructor.name == constructorName); |
| }); |
| return getMemberReference(constructor); |
| } |
| |
| ClassElement getClassElement(ast.Class node) { |
| return _classes.inverse[node]; |
| } |
| |
| void addMixinClassToLibrary(ast.Class class_, ast.Library library) { |
| assert(class_.parent == null); |
| library.addClass(class_); |
| var map = |
| _mixinApplications.putIfAbsent(library, () => <String, ast.Class>{}); |
| map[class_.name] = class_; |
| } |
| |
| /// Returns the IR for a class, at a temporary loading level. |
| /// |
| /// The returned class has the correct name, flags, type parameter arity, |
| /// and enclosing library. |
| ast.Class getClassReference(ClassElement element) { |
| var classNode = _classes[element]; |
| if (classNode != null) return classNode; |
| _classes[element] = classNode = new ast.Class( |
| name: element.name, |
| isAbstract: element.isAbstract, |
| fileUri: element.source.uri) |
| ..fileOffset = element.nameOffset; |
| classNode.level = ast.ClassLevel.Temporary; |
| var library = getLibraryReference(element.library); |
| library.addClass(classNode); |
| // Initialize type parameter list without bounds. |
| for (var parameter in element.typeParameters) { |
| var parameterNode = new ast.TypeParameter(parameter.name); |
| _classTypeParameters[parameter] = parameterNode; |
| classNode.typeParameters.add(parameterNode); |
| parameterNode.parent = classNode; |
| } |
| // Ensure the class is at least promoted to type level before exposing it |
| // to kernel consumers. |
| temporaryClassWorklist.add(classNode); |
| return classNode; |
| } |
| |
| /// Ensures the supertypes and type parameter bounds have been generated for |
| /// the given class. |
| void promoteToTypeLevel(ast.Class classNode) { |
| if (classNode.level.index >= ast.ClassLevel.Type.index) return; |
| classNode.level = ast.ClassLevel.Type; |
| var element = getClassElement(classNode); |
| assert(element != null); |
| var library = getLibraryReference(element.library); |
| var scope = new ClassScope(this, library); |
| // Initialize bounds on type parameters. |
| for (int i = 0; i < classNode.typeParameters.length; ++i) { |
| var parameter = element.typeParameters[i]; |
| var parameterNode = classNode.typeParameters[i]; |
| parameterNode.bound = parameter.bound == null |
| ? scope.defaultTypeParameterBound |
| : scope.buildType(parameter.bound); |
| } |
| // Initialize supertypes. |
| Iterable<InterfaceType> mixins = element.mixins; |
| if (element.isMixinApplication && mixins.isNotEmpty) { |
| classNode.mixedInType = scope.buildSupertype(mixins.last); |
| mixins = mixins.take(mixins.length - 1); |
| } |
| if (element.supertype != null) { |
| ast.Supertype supertype = scope.buildSupertype(element.supertype); |
| bool useSharedMixin = true; |
| for (var mixin in mixins) { |
| var mixinType = scope.buildSupertype(mixin); |
| if (useSharedMixin && |
| areDistinctUnboundTypeVariables(supertype, mixinType)) { |
| // Use a shared mixin application class for this library. |
| var mixinClass = getSharedMixinApplicationClass( |
| scope.currentLibrary, supertype.classNode, mixinType.classNode); |
| if (mixinClass.fileOffset < 0) { |
| mixinClass.fileOffset = element.nameOffset; |
| } |
| supertype = new ast.Supertype( |
| mixinClass, |
| supertype.typeArguments.length > mixinType.typeArguments.length |
| ? supertype.typeArguments |
| : mixinType.typeArguments); |
| } else { |
| // Generate a new class specific for this mixin application. |
| var freshParameters = |
| getFreshTypeParameters(classNode.typeParameters); |
| var mixinClass = new ast.Class( |
| name: '${classNode.name}^${mixinType.classNode.name}', |
| isAbstract: true, |
| typeParameters: freshParameters.freshTypeParameters, |
| supertype: freshParameters.substituteSuper(supertype), |
| mixedInType: freshParameters.substituteSuper(mixinType), |
| fileUri: classNode.fileUri) |
| ..fileOffset = element.nameOffset; |
| mixinClass.level = ast.ClassLevel.Type; |
| addMixinClassToLibrary(mixinClass, classNode.enclosingLibrary); |
| supertype = new ast.Supertype(mixinClass, |
| classNode.typeParameters.map(makeTypeParameterType).toList()); |
| // This class cannot be used from anywhere else, so don't try to |
| // generate shared mixin applications using it. |
| useSharedMixin = false; |
| } |
| } |
| classNode.supertype = supertype; |
| for (var implementedType in element.interfaces) { |
| classNode.implementedTypes.add(scope.buildSupertype(implementedType)); |
| } |
| } |
| } |
| |
| void promoteToHierarchyLevel(ast.Class classNode) { |
| if (classNode.level.index >= ast.ClassLevel.Hierarchy.index) return; |
| promoteToTypeLevel(classNode); |
| classNode.level = ast.ClassLevel.Hierarchy; |
| var element = getClassElement(classNode); |
| if (element != null) { |
| // Ensure all instance members are at present. |
| for (var field in element.fields) { |
| if (!field.isStatic && !field.isSynthetic) { |
| getMemberReference(field); |
| } |
| } |
| for (var accessor in element.accessors) { |
| if (!accessor.isStatic && !accessor.isSynthetic) { |
| getMemberReference(accessor); |
| } |
| } |
| for (var method in element.methods) { |
| if (!method.isStatic && !method.isSynthetic) { |
| getMemberReference(method); |
| } |
| } |
| } |
| for (var supertype in classNode.supers) { |
| promoteToHierarchyLevel(supertype.classNode); |
| } |
| } |
| |
| void promoteToMixinLevel(ast.Class classNode, ClassElement element, |
| NamedCompilationUnitMember astNode) { |
| if (classNode.level.index >= ast.ClassLevel.Mixin.index) return; |
| _classBeingPromotedToMixin = element; |
| promoteToHierarchyLevel(classNode); |
| classNode.level = ast.ClassLevel.Mixin; |
| // Clear out the member references that were put in the class. |
| // The AST builder will load them all put back in the right order. |
| classNode..fields.clear()..procedures.clear()..constructors.clear(); |
| new ClassBodyBuilder(this, classNode, element).build(astNode); |
| _classBeingPromotedToMixin = null; |
| |
| // Ensure mixed-in classes are available. |
| for (var mixin in element.mixins) { |
| _ensureMixinBecomesLoaded(mixin.element); |
| } |
| } |
| |
| /// Ensures that [element] eventually becomes loaded at least at mixin level. |
| void _ensureMixinBecomesLoaded(ClassElement element) { |
| if (isClassBeingPromotedToMixin(element)) { |
| return; |
| } |
| var class_ = getClassReference(element); |
| if (class_.level.index >= ast.ClassLevel.Mixin.index) { |
| return; |
| } |
| var list = mixinLibraryWorklist[element.library] ??= <ClassElement>[]; |
| list.add(element); |
| } |
| |
| void promoteToBodyLevel(ast.Class classNode, ClassElement element, |
| NamedCompilationUnitMember astNode) { |
| if (classNode.level == ast.ClassLevel.Body) return; |
| promoteToMixinLevel(classNode, element, astNode); |
| classNode.level = ast.ClassLevel.Body; |
| // This frontend delivers the same contents for classes at body and mixin |
| // levels, even though as specified, the mixin level does not require all |
| // the static members to be present. So no additional work is needed. |
| } |
| |
| ast.TypeParameter tryGetClassTypeParameter(TypeParameterElement element) { |
| return _classTypeParameters[element]; |
| } |
| |
| Element getMemberElement(ast.Member node) { |
| return _members.inverse[node]; |
| } |
| |
| ast.Member getMemberReference(Element element) { |
| assert(element != null); |
| assert(element is! Member); // Use the "base element". |
| return _members[element] ??= _buildMemberReference(element); |
| } |
| |
| ast.Member _buildMemberReference(Element element) { |
| assert(element != null); |
| var member = _buildOrphanedMemberReference(element); |
| // Set the parent pointer and store it in the enclosing class or library. |
| // If the enclosing library is being built from the AST, do not add the |
| // member, since the AST builder will put it in there. |
| var parent = element.enclosingElement; |
| if (parent is ClassElement) { |
| var class_ = getClassReference(parent); |
| member.parent = class_; |
| if (!isLibraryBeingLoaded(element.library)) { |
| class_.addMember(member); |
| } |
| } else { |
| var library = getLibraryReference(element.library); |
| member.parent = library; |
| if (!isLibraryBeingLoaded(element.library)) { |
| library.addMember(member); |
| } |
| } |
| return member; |
| } |
| |
| ast.Member _buildOrphanedMemberReference(Element element) { |
| assert(element != null); |
| ClassElement classElement = element.enclosingElement is ClassElement |
| ? element.enclosingElement |
| : null; |
| TypeScope scope = classElement != null |
| ? new ClassScope(this, getLibraryReference(element.library)) |
| : new TypeScope(this); |
| if (classElement != null) { |
| getClassReference(classElement); |
| } |
| switch (element.kind) { |
| case ElementKind.CONSTRUCTOR: |
| ConstructorElement constructor = element; |
| if (constructor.isFactory) { |
| return new ast.Procedure( |
| _nameOfMember(constructor), |
| ast.ProcedureKind.Factory, |
| scope.buildFunctionInterface(constructor), |
| isAbstract: false, |
| isStatic: true, |
| isExternal: constructor.isExternal, |
| isConst: constructor.isConst, |
| fileUri: element.source.uri) |
| ..fileOffset = element.nameOffset; |
| } |
| return new ast.Constructor(scope.buildFunctionInterface(constructor), |
| name: _nameOfMember(element), |
| isConst: constructor.isConst, |
| isExternal: constructor.isExternal, |
| isSynthetic: constructor.isSynthetic) |
| ..fileOffset = element.nameOffset; |
| |
| case ElementKind.FIELD: |
| case ElementKind.TOP_LEVEL_VARIABLE: |
| VariableElement variable = element; |
| return new ast.Field(_nameOfMember(variable), |
| isStatic: variable.isStatic, |
| isFinal: variable.isFinal, |
| isConst: variable.isConst, |
| type: scope.buildType(variable.type), |
| fileUri: element.source.uri) |
| ..fileOffset = element.nameOffset; |
| |
| case ElementKind.METHOD: |
| case ElementKind.GETTER: |
| case ElementKind.SETTER: |
| case ElementKind.FUNCTION: |
| if (element is FunctionElement && |
| element.enclosingElement is! CompilationUnitElement) { |
| throw 'Function $element is nested in ${element.enclosingElement} ' |
| 'and hence is not a member'; |
| } |
| ExecutableElement executable = element; |
| return new ast.Procedure( |
| _nameOfMember(element), |
| _procedureKindOf(executable), |
| scope.buildFunctionInterface(executable), |
| isAbstract: executable.isAbstract, |
| isStatic: executable.isStatic, |
| isExternal: executable.isExternal, |
| fileUri: element.source.uri) |
| ..fileOffset = element.nameOffset; |
| |
| default: |
| throw 'Unexpected member kind: $element'; |
| } |
| } |
| |
| ast.ProcedureKind _procedureKindOf(ExecutableElement element) { |
| if (element is PropertyAccessorElement) { |
| return element.isGetter |
| ? ast.ProcedureKind.Getter |
| : ast.ProcedureKind.Setter; |
| } |
| if (element is MethodElement) { |
| if (element.isOperator) return ast.ProcedureKind.Operator; |
| return ast.ProcedureKind.Method; |
| } |
| if (element is FunctionElement) { |
| return ast.ProcedureKind.Method; |
| } |
| if (element is ConstructorElement) { |
| assert(element.isFactory); |
| return ast.ProcedureKind.Factory; |
| } |
| throw 'Unexpected procedure: $element'; |
| } |
| |
| ast.Name _nameOfMember(Element element) { |
| // Use 'displayName' to avoid a trailing '=' for setters and 'name' to |
| // ensure unary minus is called 'unary-'. |
| String name = |
| element is PropertyAccessorElement ? element.displayName : element.name; |
| return new ast.Name(name, getLibraryReference(element.library)); |
| } |
| |
| /// True if the two types have form `C<T1 ... Tm>` and `D<T1 ... Tn>`, and |
| /// `T1 ... TN` are distinct type variables with no upper bound, where |
| /// `N = max(m,n)`. |
| bool areDistinctUnboundTypeVariables( |
| ast.Supertype first, ast.Supertype second) { |
| var seen = new Set<ast.TypeParameter>(); |
| if (first.typeArguments.length < second.typeArguments.length) { |
| var tmp = first; |
| first = second; |
| second = tmp; |
| } |
| for (int i = 0; i < first.typeArguments.length; ++i) { |
| var firstArg = first.typeArguments[i]; |
| if (!(firstArg is ast.TypeParameterType && |
| seen.add(firstArg.parameter) && |
| firstArg.parameter.bound is ast.DynamicType)) { |
| return false; |
| } |
| if (i < second.typeArguments.length && |
| firstArg != second.typeArguments[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// Returns the canonical mixin application of two classes, instantiated with |
| /// the same list of unbound type variables. |
| /// |
| /// Given two classes: |
| /// class C<C1 ... Cm> |
| /// class D<D1 ... Dn> |
| /// |
| /// This creates or reuses a mixin application class in the library of form: |
| /// |
| /// abstract class C&D<T1 ... TN> = C<T1 ... Tm> with D<T1 ... Tn> |
| /// |
| /// where `N = max(m,n)`. |
| /// |
| /// Such a class can in general contain type errors due to incompatible |
| /// inheritance from `C` and `D`. This method therefore should only be called |
| /// if a mixin application `C<S1 ... Sm> with D<S1 ... Sn>` is seen, where |
| /// `S1 ... SN` are distinct, unbound type variables. |
| ast.Class getSharedMixinApplicationClass( |
| ast.Library library, ast.Class superclass, ast.Class mixedInClass) { |
| // TODO(asgerf): Avoid potential name clash due to associativity. |
| // As it is, these mixins get the same name: |
| // (A with B) with C |
| // A with (B with C) |
| String name = '${superclass.name}&${mixedInClass.name}'; |
| return _mixinApplications |
| .putIfAbsent(library, () => <String, ast.Class>{}) |
| .putIfAbsent(name, () { |
| var fresh = |
| superclass.typeParameters.length >= mixedInClass.typeParameters.length |
| ? getFreshTypeParameters(superclass.typeParameters) |
| : getFreshTypeParameters(mixedInClass.typeParameters); |
| var typeArguments = |
| fresh.freshTypeParameters.map(makeTypeParameterType).toList(); |
| var superArgs = typeArguments.length != superclass.typeParameters.length |
| ? typeArguments.sublist(0, superclass.typeParameters.length) |
| : typeArguments; |
| var mixinArgs = typeArguments.length != mixedInClass.typeParameters.length |
| ? typeArguments.sublist(0, mixedInClass.typeParameters.length) |
| : typeArguments; |
| var result = new ast.Class( |
| name: name, |
| isAbstract: true, |
| typeParameters: fresh.freshTypeParameters, |
| supertype: new ast.Supertype(superclass, superArgs), |
| mixedInType: new ast.Supertype(mixedInClass, mixinArgs), |
| fileUri: library.fileUri); |
| result.level = ast.ClassLevel.Type; |
| library.addClass(result); |
| return result; |
| }); |
| } |
| |
| String formatErrorMessage( |
| AnalysisError error, String filename, LineInfo lines) { |
| var location = lines.getLocation(error.offset); |
| return '[error] ${error.message} ($filename, ' |
| 'line ${location.lineNumber}, ' |
| 'col ${location.columnNumber})'; |
| } |
| |
| void ensureLibraryIsLoaded(ast.Library node) { |
| _ensureLibraryIsLoaded(node); |
| _iterateMixinLibraryWorklist(); |
| } |
| |
| void _ensureLibraryIsLoaded(ast.Library node) { |
| if (!node.isExternal) return; |
| node.isExternal = false; |
| var source = context.sourceFactory |
| .forUri2(applicationRoot.absoluteUri(node.importUri)); |
| assert(source != null); |
| var element = context.computeLibraryElement(source); |
| var units = <CompilationUnit>[]; |
| bool reportErrors = node.importUri.scheme != 'dart'; |
| var tree = context.resolveCompilationUnit(source, element); |
| units.add(tree); |
| if (reportErrors) _processErrors(source); |
| for (var part in element.parts) { |
| var source = part.source; |
| units.add(context.resolveCompilationUnit(source, element)); |
| if (reportErrors) _processErrors(source); |
| } |
| for (var import in element.imports) { |
| if (import.isDeferred && import.prefix != null) { |
| node.addDependency(new ast.LibraryDependency.deferredImport( |
| getLibraryReference(import.importedLibrary), import.prefix.name)); |
| } else { |
| node.addDependency(new ast.LibraryDependency.import( |
| getLibraryReference(import.importedLibrary), |
| name: import.prefix?.name)); |
| } |
| } |
| for (var export in element.exports) { |
| node.addDependency(new ast.LibraryDependency.export( |
| getLibraryReference(export.exportedLibrary))); |
| } |
| _buildLibraryBody(element, node, units); |
| } |
| |
| void _processErrors(Source source) { |
| LineInfo lines; |
| for (var error in context.computeErrors(source)) { |
| if (error.errorCode is CompileTimeErrorCode || |
| error.errorCode is ParserErrorCode || |
| error.errorCode is ScannerErrorCode || |
| error.errorCode is StrongModeCode) { |
| lines ??= context.computeLineInfo(source); |
| errors.add(formatErrorMessage(error, source.shortName, lines)); |
| } |
| } |
| } |
| |
| void loadSdkInterface(ast.Program program, Target target) { |
| var requiredSdkMembers = target.requiredSdkClasses; |
| for (var libraryUri in requiredSdkMembers.keys) { |
| var source = context.sourceFactory.forUri2(Uri.parse(libraryUri)); |
| var libraryElement = context.computeLibraryElement(source); |
| for (var member in requiredSdkMembers[libraryUri]) { |
| var type = libraryElement.getType(member); |
| if (type == null) { |
| throw 'Could not find $member in $libraryUri'; |
| } |
| promoteToTypeLevel(getClassReference(type)); |
| } |
| } |
| _iterateTemporaryClassWorklist(); |
| _iterateMixinLibraryWorklist(); |
| } |
| |
| void loadEverything({Target target, bool compileSdk}) { |
| compileSdk ??= true; |
| if (compileSdk) { |
| ensureLibraryIsLoaded(getLibraryReference(getDartCoreLibrary())); |
| if (target != null) { |
| for (var uri in target.extraRequiredLibraries) { |
| var library = _findLibraryElement(uri); |
| if (library == null) { |
| errors.add('Could not find required library $uri'); |
| continue; |
| } |
| ensureLibraryIsLoaded(getLibraryReference(library)); |
| } |
| } |
| } |
| for (int i = 0; i < program.libraries.length; ++i) { |
| var library = program.libraries[i]; |
| if (compileSdk || library.importUri.scheme != 'dart') { |
| ensureLibraryIsLoaded(library); |
| } |
| } |
| } |
| |
| /// Builds a list of sources that have been loaded. |
| /// |
| /// This operation may be expensive and should only be used for diagnostics. |
| List<String> getLoadedFileNames() { |
| var list = <String>[]; |
| for (var library in program.libraries) { |
| LibraryElement element = context.computeLibraryElement(context |
| .sourceFactory |
| .forUri2(applicationRoot.absoluteUri(library.importUri))); |
| for (var unit in element.units) { |
| list.add(unit.source.fullName); |
| } |
| } |
| return list; |
| } |
| |
| void _iterateTemporaryClassWorklist() { |
| while (temporaryClassWorklist.isNotEmpty) { |
| var element = temporaryClassWorklist.removeLast(); |
| promoteToTypeLevel(element); |
| } |
| } |
| |
| void _iterateMixinLibraryWorklist() { |
| // The worklist groups classes in the same library together so that we |
| // request resolved ASTs for each library only once. |
| while (mixinLibraryWorklist.isNotEmpty) { |
| LibraryElement library = mixinLibraryWorklist.keys.first; |
| _libraryBeingLoaded = library; |
| List<ClassElement> classes = mixinLibraryWorklist.remove(library); |
| for (var class_ in classes) { |
| var classNode = getClassReference(class_); |
| promoteToMixinLevel(classNode, class_, class_.computeNode()); |
| } |
| _libraryBeingLoaded = null; |
| } |
| _iterateTemporaryClassWorklist(); |
| } |
| |
| ast.Procedure _getMainMethod(Uri uri) { |
| Source source = context.sourceFactory.forUri2(uri); |
| LibraryElement library = context.computeLibraryElement(source); |
| var mainElement = library.entryPoint; |
| if (mainElement == null) return null; |
| var mainMember = getMemberReference(mainElement); |
| if (mainMember is ast.Procedure && !mainMember.isAccessor) { |
| return mainMember; |
| } |
| // Top-level 'main' getters are not supported at the moment. |
| return null; |
| } |
| |
| ast.Procedure _makeMissingMainMethod(ast.Library library) { |
| var main = new ast.Procedure( |
| new ast.Name('main'), |
| ast.ProcedureKind.Method, |
| new ast.FunctionNode(new ast.ExpressionStatement(new ast.Throw( |
| new ast.StringLiteral('Program has no main method')))), |
| isStatic: true) |
| ..fileUri = library.fileUri; |
| library.addMember(main); |
| return main; |
| } |
| |
| void loadProgram(Uri mainLibrary, {Target target, bool compileSdk}) { |
| ast.Library library = getLibraryReferenceFromUri(mainLibrary); |
| ensureLibraryIsLoaded(library); |
| var mainMethod = _getMainMethod(mainLibrary); |
| loadEverything(target: target, compileSdk: compileSdk); |
| if (mainMethod == null) { |
| mainMethod = _makeMissingMainMethod(library); |
| } |
| program.mainMethod = mainMethod; |
| for (LibraryElement libraryElement in libraryElements) { |
| for (CompilationUnitElement compilationUnitElement |
| in libraryElement.units) { |
| var source = compilationUnitElement.source; |
| LineInfo lineInfo = context.computeLineInfo(source); |
| List<int> sourceCode; |
| try { |
| sourceCode = |
| const Utf8Encoder().convert(context.getContents(source).data); |
| } catch (e) { |
| // The source's contents could not be accessed. |
| sourceCode = const <int>[]; |
| } |
| program.uriToSource[source.uri] = |
| new ast.Source(lineInfo.lineStarts, sourceCode); |
| } |
| } |
| } |
| |
| ast.Library loadLibrary(Uri uri) { |
| ast.Library library = getLibraryReferenceFromUri(uri); |
| ensureLibraryIsLoaded(library); |
| return library; |
| } |
| } |
| |
| class Bimap<K, V> { |
| final Map<K, V> nodeMap = <K, V>{}; |
| final Map<V, K> inverse = <V, K>{}; |
| |
| bool containsKey(K key) => nodeMap.containsKey(key); |
| |
| V operator [](K key) => nodeMap[key]; |
| |
| void operator []=(K key, V value) { |
| assert(!nodeMap.containsKey(key)); |
| nodeMap[key] = value; |
| inverse[value] = key; |
| } |
| } |
| |
| /// Creates [DartLoader]s for a given configuration, while reusing the |
| /// [DartSdk] and [Packages] object if possible. |
| class DartLoaderBatch { |
| Packages packages; |
| DartSdk dartSdk; |
| |
| String lastSdk; |
| String lastPackagePath; |
| bool lastStrongMode; |
| |
| Future<DartLoader> getLoader(ast.Program program, DartOptions options, |
| {String packageDiscoveryPath}) async { |
| if (dartSdk == null || |
| lastSdk != options.sdk || |
| lastStrongMode != options.strongMode) { |
| lastSdk = options.sdk; |
| lastStrongMode = options.strongMode; |
| dartSdk = createDartSdk(options.sdk, strongMode: options.strongModeSdk); |
| } |
| if (packages == null || |
| lastPackagePath != options.packagePath || |
| packageDiscoveryPath != null) { |
| lastPackagePath = options.packagePath; |
| packages = await createPackages(options.packagePath, |
| discoveryPath: packageDiscoveryPath); |
| } |
| return new DartLoader(program, options, packages, dartSdk: dartSdk); |
| } |
| } |
| |
| Future<Packages> createPackages(String packagePath, |
| {String discoveryPath}) async { |
| if (packagePath != null) { |
| var absolutePath = new io.File(packagePath).absolute.path; |
| if (await new io.Directory(packagePath).exists()) { |
| return getPackagesDirectory(new Uri.file(absolutePath)); |
| } else if (await new io.File(packagePath).exists()) { |
| return loadPackagesFile(new Uri.file(absolutePath)); |
| } else { |
| throw 'Packages not found: $packagePath'; |
| } |
| } |
| if (discoveryPath != null) { |
| return findPackagesFromFile(Uri.parse(discoveryPath)); |
| } |
| return Packages.noPackages; |
| } |
| |
| AnalysisOptions createAnalysisOptions(bool strongMode) { |
| return new AnalysisOptionsImpl() |
| ..strongMode = strongMode |
| ..generateImplicitErrors = false |
| ..generateSdkErrors = false |
| ..preserveComments = false |
| ..hint = false |
| ..enableSuperMixins = true; |
| } |
| |
| DartSdk createDartSdk(String path, {bool strongMode, bool isSummary}) { |
| if (isSummary ?? false) { |
| return new SummaryBasedDartSdk(path, strongMode); |
| } |
| var resources = PhysicalResourceProvider.INSTANCE; |
| return new FolderBasedDartSdk(resources, resources.getFolder(path)) |
| ..context |
| .analysisOptions |
| .setCrossContextOptionsFrom(createAnalysisOptions(strongMode)); |
| } |
| |
| class CustomUriResolver extends UriResolver { |
| final ResourceUriResolver _resourceUriResolver; |
| final Map<Uri, Uri> _customUrlMappings; |
| |
| CustomUriResolver(this._resourceUriResolver, this._customUrlMappings); |
| |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| // TODO(kustermann): Once dartk supports configurable imports we should be |
| // able to get rid of this. |
| if (uri.toString() == 'package:mojo/src/internal_contract.dart') { |
| uri = actualUri = Uri.parse('dart:mojo.internal'); |
| } |
| |
| Uri baseUri = uri; |
| String relative; |
| String path = uri.path; |
| int index = path.indexOf('/'); |
| if (index > 0) { |
| baseUri = uri.replace(path: path.substring(0, index)); |
| relative = path.substring(index + 1); |
| } |
| Uri baseMapped = _customUrlMappings[baseUri]; |
| if (baseMapped == null) return null; |
| |
| Uri mapped = relative != null ? baseMapped.resolve(relative) : baseMapped; |
| return _resourceUriResolver.resolveAbsolute(mapped, actualUri); |
| } |
| |
| Uri restoreAbsolute(Source source) { |
| return _resourceUriResolver.restoreAbsolute(source); |
| } |
| } |
| |
| AnalysisContext createContext(DartOptions options, Packages packages, |
| {DartSdk dartSdk}) { |
| bool fromSummary = options.sdkSummary != null; |
| dartSdk ??= createDartSdk(fromSummary ? options.sdkSummary : options.sdk, |
| strongMode: options.strongModeSdk, isSummary: fromSummary); |
| |
| var resourceProvider = PhysicalResourceProvider.INSTANCE; |
| var resourceUriResolver = new ResourceUriResolver(resourceProvider); |
| List<UriResolver> resolvers = []; |
| var customUriMappings = options.customUriMappings; |
| if (customUriMappings != null && customUriMappings.length > 0) { |
| resolvers |
| .add(new CustomUriResolver(resourceUriResolver, customUriMappings)); |
| } |
| resolvers.add(new DartUriResolver(dartSdk)); |
| resolvers.add(resourceUriResolver); |
| |
| if (packages != null) { |
| var folderMap = <String, List<Folder>>{}; |
| packages.asMap().forEach((String packagePath, Uri uri) { |
| String path = resourceProvider.pathContext.fromUri(uri); |
| folderMap[packagePath] = [resourceProvider.getFolder(path)]; |
| }); |
| resolvers.add(new PackageMapUriResolver(resourceProvider, folderMap)); |
| } |
| |
| AnalysisContext context = AnalysisEngine.instance.createAnalysisContext() |
| ..sourceFactory = new SourceFactory(resolvers) |
| ..analysisOptions = createAnalysisOptions(options.strongMode); |
| |
| options.declaredVariables.forEach((String name, String value) { |
| context.declaredVariables.define(name, value); |
| }); |
| |
| return context; |
| } |