| // 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 'analyzer.dart'; |
| import '../repository.dart'; |
| import '../ast.dart' as ast; |
| import 'ast_from_analyzer.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/generated/java_io.dart'; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/sdk_io.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| |
| 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.TypeParameter tryGetClassTypeParameter(TypeParameterElement element); |
| bool get strongMode; |
| } |
| |
| class AnalyzerLoader implements ReferenceLevelLoader { |
| final Repository repository; |
| final LoadMap<ClassElement, ast.Class> _classes = |
| new LoadMap<ClassElement, ast.Class>(); |
| final LoadMap<Element, ast.Member> _members = |
| new LoadMap<Element, ast.Member>(); |
| final Map<TypeParameterElement, ast.TypeParameter> _classTypeParameters = |
| <TypeParameterElement, ast.TypeParameter>{}; |
| final AnalysisContext context; |
| LibraryElement _dartCoreLibrary; |
| |
| bool get strongMode => context.analysisOptions.strongMode; |
| |
| AnalyzerLoader(Repository repository, |
| {AnalysisContext context, bool strongMode: false}) |
| : this.repository = repository, |
| this.context = context ?? createContext(repository, strongMode); |
| |
| LibraryElement getLibraryElement(ast.Library node) { |
| return context |
| .getLibraryElement(context.sourceFactory.forUri2(node.importUri)); |
| } |
| |
| ast.Library getLibraryReference(LibraryElement element) { |
| return repository.getLibraryReference(element.source.uri); |
| } |
| |
| ast.Library getLibraryBody(LibraryElement element) { |
| var library = getLibraryReference(element); |
| library.name = element.name; |
| if (!library.isLoaded) { |
| _buildLibraryBody(element, library); |
| } |
| return library; |
| } |
| |
| void _buildLibraryBody(LibraryElement element, ast.Library library) { |
| for (var unit in element.units) { |
| for (var type in unit.types) { |
| library.addClass(getClassReference(type)); |
| } |
| for (var type in unit.enums) { |
| library.addClass(getClassReference(type)); |
| } |
| for (var accessor in unit.accessors) { |
| if (!accessor.isSynthetic) { |
| library.addMember(getMemberReference(accessor)); |
| } |
| } |
| for (var function in unit.functions) { |
| library.addMember(getMemberReference(function)); |
| } |
| for (var field in unit.topLevelVariables) { |
| if (!field.isSynthetic) { |
| library.addMember(getMemberReference(field)); |
| } |
| } |
| } |
| library.isLoaded = true; |
| } |
| |
| LibraryElement getDartCoreLibrary() { |
| return _dartCoreLibrary ??= context |
| .computeLibraryElement(context.sourceFactory.forUri('dart:core')); |
| } |
| |
| 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)); |
| } |
| |
| ClassElement getClassElement(ast.Class node) { |
| return _classes.inverse[node]; |
| } |
| |
| ast.Class getClassReference(ClassElement element) { |
| return _classes.getReference(element, _buildClassReference); |
| } |
| |
| ast.Class _buildClassReference(ClassElement element) { |
| var classNode = element.isMixinApplication |
| ? new ast.MixinClass(null, null, |
| name: element.name, isAbstract: element.isAbstract) |
| : new ast.NormalClass(null, |
| name: element.name, isAbstract: element.isAbstract); |
| for (TypeParameterElement parameter in element.typeParameters) { |
| var parameterNode = new ast.TypeParameter(parameter.name); |
| _classTypeParameters[parameter] = parameterNode; |
| classNode.typeParameters.add(parameterNode); |
| parameterNode.parent = classNode; |
| } |
| return classNode; |
| } |
| |
| ast.Class getClassBody(ClassElement element) { |
| var classNode = getClassReference(element); |
| if (_classes.level[classNode] == LoadingLevel.Reference) { |
| _buildClassBody(element, classNode); |
| } |
| return classNode; |
| } |
| |
| void _buildClassBody(ClassElement element, ast.Class classNode) { |
| // Ensure that the enclosing library is loaded first. |
| var library = getLibraryBody(element.library); |
| assert(classNode.enclosingLibrary == library); |
| new ClassBodyBuilder(this, classNode, element).build(element.computeNode()); |
| _classes.level[classNode] = LoadingLevel.Body; |
| } |
| |
| ast.TypeParameter tryGetClassTypeParameter(TypeParameterElement element) { |
| return _classTypeParameters[element]; |
| } |
| |
| Element getMemberElement(ast.Member node) { |
| return _members.inverse[node]; |
| } |
| |
| ast.Member getMemberReference(Element element) { |
| return _members.getReference(element, _buildMemberReference); |
| } |
| |
| ast.Member _buildMemberReference(Element element) { |
| assert(element is! Member); // Use the "base element". |
| switch (element.kind) { |
| case ElementKind.CONSTRUCTOR: |
| ConstructorElement constructor = element; |
| if (constructor.isFactory) { |
| return new ast.Procedure( |
| _nameOfMember(constructor), ast.ProcedureKind.Factory, null, |
| isAbstract: false, |
| isStatic: true, |
| isExternal: constructor.isExternal); |
| } |
| return new ast.Constructor(null, |
| name: _nameOfMember(element), |
| isConst: constructor.isConst, |
| isExternal: constructor.isExternal); |
| |
| 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); |
| |
| 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), null, |
| isAbstract: executable.isAbstract, isStatic: executable.isStatic); |
| |
| default: |
| throw 'Unexpected member kind: $element'; |
| } |
| } |
| |
| ast.Member getMemberBody(Element element) { |
| var member = getMemberReference(element); |
| if (_members.level[member] == LoadingLevel.Reference) { |
| _buildMemberBody(element, member); |
| } |
| return member; |
| } |
| |
| void _buildMemberBody(Element element, ast.Member member) { |
| // Ensure the enclosing library and class are loaded. |
| var libraryNode = getLibraryBody(element.library); |
| var enclosing = element.enclosingElement; |
| if (enclosing is ClassElement) { |
| var classNode = getClassBody(enclosing); |
| assert(classNode.parent == libraryNode); |
| assert(member.parent == classNode); |
| } else { |
| assert(member.parent == libraryNode); |
| } |
| new MemberBodyBuilder(this, member, element).build(element.computeNode()); |
| _members.level[member] = LoadingLevel.Body; |
| } |
| |
| ast.ProcedureKind _procedureKindOf(ExecutableElement element) { |
| if (element is PropertyAccessorElement) { |
| return element.isGetter |
| ? ast.ProcedureKind.Getter |
| : ast.ProcedureKind.Setter; |
| } |
| if (element is MethodElement) { |
| if (element.name == '[]') return ast.ProcedureKind.IndexGetter; |
| if (element.name == '[]=') return ast.ProcedureKind.IndexSetter; |
| 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) { |
| return new ast.Name(element.name, getLibraryReference(element.library)); |
| } |
| |
| void ensureLibraryIsLoaded(ast.Library node) { |
| if (node.isLoaded) return; |
| var source = context.sourceFactory.forUri2(node.importUri); |
| assert(source != null); |
| var element = context.computeLibraryElement(source); |
| context.resolveCompilationUnit(source, element); |
| _buildLibraryBody(element, node); |
| } |
| |
| void loadEverything() { |
| int libraryIndex = 0; |
| bool changed = true; |
| while (changed) { |
| changed = false; |
| while (libraryIndex < repository.libraries.length) { |
| ensureLibraryIsLoaded(repository.libraries[libraryIndex]); |
| ++libraryIndex; |
| } |
| while (_classes.referencedNodes.isNotEmpty) { |
| getClassBody(_classes.referencedNodes.removeLast()); |
| changed = true; |
| } |
| while (_members.referencedNodes.isNotEmpty) { |
| getMemberBody(_members.referencedNodes.removeLast()); |
| changed = true; |
| } |
| } |
| } |
| |
| /// 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 repository.libraries) { |
| LibraryElement element = context.computeLibraryElement( |
| context.sourceFactory.forUri2(library.importUri)); |
| for (var unit in element.units) { |
| list.add(unit.source.fullName); |
| } |
| } |
| return list; |
| } |
| } |
| |
| enum LoadingLevel { |
| /// A library, member, or class whose object has been created so it can |
| /// be referenced, but its contents have not been completely initialized. |
| /// |
| /// Classes contain their name and type parameter arity. |
| /// |
| /// Members contain their name and modifiers (abstract, static, etc). |
| /// |
| /// At this level, a class may be used in an [ast.InterfaceType]. |
| Reference, |
| |
| /// Everything is loaded. |
| Body, |
| } |
| |
| class LoadMap<K, V> { |
| final Map<K, V> nodeMap = <K, V>{}; |
| final Map<V, K> inverse = <V, K>{}; |
| final Map<V, LoadingLevel> level = <V, LoadingLevel>{}; |
| final List<K> referencedNodes = <K>[]; |
| |
| V getReference(K key, V build(K key)) { |
| return nodeMap.putIfAbsent(key, () { |
| var result = build(key); |
| referencedNodes.add(key); |
| level[result] = LoadingLevel.Reference; |
| inverse[result] = key; |
| return result; |
| }); |
| } |
| } |
| |
| AnalysisContext createContext(Repository repository, bool strongMode) { |
| if (repository.sdk != null) { |
| JavaSystemIO.setProperty("com.google.dart.sdk", repository.sdk); |
| } |
| DartSdk dartSdk = DirectoryBasedDartSdk.defaultSdk; |
| |
| List<UriResolver> resolvers = [ |
| new DartUriResolver(dartSdk), |
| new FileUriResolver() |
| ]; |
| |
| if (repository.packageRoot != null) { |
| var packageDirectory = new JavaFile(repository.packageRoot); |
| resolvers.add(new PackageUriResolver([packageDirectory])); |
| } |
| |
| AnalysisContext context = AnalysisEngine.instance.createAnalysisContext() |
| ..sourceFactory = new SourceFactory(resolvers); |
| |
| context.analysisOptions = new AnalysisOptionsImpl() |
| ..strongMode = strongMode |
| ..preserveComments = false |
| ..hint = false |
| ..generateImplicitErrors = false; |
| |
| return context; |
| } |