| // Copyright (c) 2014, 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 resolution.deferred_load; |
| |
| import '../common.dart'; |
| import '../compiler.dart' show Compiler; |
| import '../constants/expressions.dart' show ConstantExpression; |
| import '../constants/values.dart' |
| show ConstantValue, ConstructedConstantValue, StringConstantValue; |
| import '../deferred_load.dart'; |
| import '../elements/elements.dart' |
| show |
| AstElement, |
| AccessorElement, |
| ClassElement, |
| Element, |
| ExportElement, |
| ImportElement, |
| LibraryElement, |
| MemberElement, |
| MetadataAnnotation, |
| PrefixElement, |
| ResolvedAstKind, |
| TypedefElement; |
| import '../elements/resolution_types.dart'; |
| import '../js_backend/js_backend.dart' show JavaScriptBackend; |
| import '../resolution/resolution.dart' show AnalyzableElementX; |
| import '../resolution/tree_elements.dart' show TreeElements; |
| import '../tree/tree.dart' as ast; |
| import '../util/util.dart' show Setlet; |
| import 'tree_elements.dart' show TreeElements; |
| |
| class AstDeferredLoadTask extends DeferredLoadTask { |
| /// DeferredLibrary from dart:async |
| ClassElement get deferredLibraryClass => |
| compiler.resolution.commonElements.deferredLibraryClass; |
| |
| JavaScriptBackend get backend => compiler.backend; |
| |
| AstDeferredLoadTask(Compiler compiler) : super(compiler); |
| |
| Iterable<ImportElement> importsTo( |
| covariant Element element, covariant LibraryElement library) { |
| if (element.isClassMember) { |
| element = element.enclosingClass; |
| } |
| if (element.isAccessor) { |
| element = (element as AccessorElement).abstractField; |
| } |
| return library.getImportsFor(element); |
| } |
| |
| void checkForDeferredErrorCases(covariant LibraryElement library) { |
| var usedPrefixes = new Setlet<String>(); |
| // The last deferred import we saw with a given prefix (if any). |
| var prefixDeferredImport = new Map<String, ImportElement>(); |
| for (ImportElement import in library.imports) { |
| _detectOldSyntax(import); |
| _detectDuplicateErrorCases(import, usedPrefixes, prefixDeferredImport); |
| } |
| } |
| |
| /// Give an error if the old annotation-based syntax has been used. |
| void _detectOldSyntax(ImportElement import) { |
| List<MetadataAnnotation> metadataList = import.metadata; |
| if (metadataList != null) { |
| for (MetadataAnnotation metadata in metadataList) { |
| metadata.ensureResolved(compiler.resolution); |
| ConstantValue value = |
| compiler.constants.getConstantValue(metadata.constant); |
| ResolutionDartType type = |
| value.getType(compiler.resolution.commonElements); |
| Element element = type.element; |
| if (element == deferredLibraryClass) { |
| reporter.reportErrorMessage(import, MessageKind.DEFERRED_OLD_SYNTAX); |
| } |
| } |
| } |
| } |
| |
| /// Detect duplicate prefixes of deferred libraries. |
| /// |
| /// There are 4 cases of duplicate prefixes: |
| /// 1. |
| /// import "lib.dart" deferred as a; |
| /// import "lib2.dart" deferred as a; |
| /// |
| /// 2. |
| /// import "lib.dart" deferred as a; |
| /// import "lib2.dart" as a; |
| /// |
| /// 3. |
| /// import "lib.dart" as a; |
| /// import "lib2.dart" deferred as a; |
| /// |
| /// 4. |
| /// import "lib.dart" as a; |
| /// import "lib2.dart" as a; |
| /// |
| /// We must be able to signal error for case 1, 2, 3, but accept case 4. |
| void _detectDuplicateErrorCases( |
| ImportElement import, |
| Set<String> usedPrefixes, |
| Map<String, ImportElement> prefixDeferredImport) { |
| String prefix = import.name; |
| // The last import we saw with the same prefix. |
| ImportElement previousDeferredImport = prefixDeferredImport[prefix]; |
| if (import.isDeferred) { |
| if (prefix == null) { |
| reporter.reportErrorMessage( |
| import, MessageKind.DEFERRED_LIBRARY_WITHOUT_PREFIX); |
| } else { |
| prefixDeferredImport[prefix] = import; |
| } |
| } |
| if (prefix != null) { |
| if (previousDeferredImport != null || |
| (import.isDeferred && usedPrefixes.contains(prefix))) { |
| ImportElement failingImport = |
| (previousDeferredImport != null) ? previousDeferredImport : import; |
| reporter.reportErrorMessage(failingImport.prefix, |
| MessageKind.DEFERRED_LIBRARY_DUPLICATE_PREFIX); |
| } |
| usedPrefixes.add(prefix); |
| } |
| } |
| |
| @override |
| void collectConstantsFromMetadata( |
| covariant AstElement element, Set<ConstantValue> constants) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| ConstantValue constant = |
| backend.constants.getConstantValueForMetadata(metadata); |
| if (constant != null) constants.add(constant); |
| } |
| } |
| |
| @override |
| void collectConstantsInBody( |
| covariant AstElement element, Set<ConstantValue> constants) { |
| if (element.resolvedAst.kind != ResolvedAstKind.PARSED) return; |
| |
| TreeElements treeElements = element.resolvedAst.elements; |
| assert(treeElements != null); |
| |
| // TODO(johnniwinther): Add only expressions that are actually needed. |
| // Currently we have some noise here: Some potential expressions are |
| // seen that should never be added (for instance field initializers |
| // in constant constructors, like `this.field = parameter`). And some |
| // implicit constant expression are seen that we should be able to add |
| // (like primitive constant literals like `true`, `"foo"` and `0`). |
| // See dartbug.com/26406 for context. |
| treeElements |
| .forEachConstantNode((ast.Node node, ConstantExpression expression) { |
| if (compiler.serialization.isDeserialized(element)) { |
| if (!expression.isPotential) { |
| // Enforce evaluation of [expression]. |
| backend.constants.getConstantValue(expression); |
| } |
| } |
| |
| // Explicitly depend on the backend constants. |
| if (backend.constants.hasConstantValue(expression)) { |
| ConstantValue value = backend.constants.getConstantValue(expression); |
| assert( |
| value != null, |
| failedAt( |
| node, |
| "Constant expression without value: " |
| "${expression.toStructuredText()}.")); |
| constants.add(value); |
| } else { |
| assert( |
| expression.isImplicit || expression.isPotential, |
| failedAt( |
| node, |
| "Unexpected unevaluated constant expression: " |
| "${expression.toStructuredText()}.")); |
| } |
| }); |
| } |
| |
| void addDeferredMirrorElements(WorkQueue queue) { |
| for (ImportElement deferredImport in allDeferredImports) { |
| addMirrorElementsForLibrary(queue, deferredImport.importedLibrary, |
| importSets.singleton(deferredImport)); |
| } |
| } |
| |
| void addMirrorElementsForLibrary( |
| WorkQueue queue, covariant LibraryElement root, ImportSet newSet) { |
| void handleElementIfResolved(Element element) { |
| // If an element is the target of a MirrorsUsed annotation but never used |
| // It will not be resolved, and we should not call isNeededForReflection. |
| // TODO(sigurdm): Unresolved elements should just answer false when |
| // asked isNeededForReflection. Instead an internal error is triggered. |
| // So we have to filter them out here. |
| if (element is AnalyzableElementX && !element.hasTreeElements) return; |
| |
| bool isAccessibleByReflection(Element element) { |
| if (element.isLibrary) { |
| return false; |
| } else if (element.isClass) { |
| ClassElement cls = element; |
| return compiler.backend.mirrorsData |
| .isClassAccessibleByReflection(cls); |
| } else if (element.isTypedef) { |
| TypedefElement typedef = element; |
| return compiler.backend.mirrorsData |
| .isTypedefAccessibleByReflection(typedef); |
| } else { |
| MemberElement member = element; |
| return compiler.backend.mirrorsData |
| .isMemberAccessibleByReflection(member); |
| } |
| } |
| |
| if (isAccessibleByReflection(element)) { |
| queue.addElement(element, newSet, isMirrorUsage: true); |
| } |
| } |
| |
| // For each deferred import we analyze all elements reachable from the |
| // imported library through non-deferred imports. |
| void handleLibrary(LibraryElement library) { |
| library.implementation.forEachLocalMember((Element element) { |
| handleElementIfResolved(element); |
| }); |
| |
| void processMetadata(Element element) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| ConstantValue constant = |
| backend.constants.getConstantValueForMetadata(metadata); |
| if (constant != null) { |
| queue.addConstant(constant, newSet); |
| } |
| } |
| } |
| |
| processMetadata(library); |
| library.imports.forEach(processMetadata); |
| library.exports.forEach(processMetadata); |
| } |
| |
| _nonDeferredReachableLibraries(root).forEach(handleLibrary); |
| } |
| |
| /// Returns the transitive closure of all libraries that are imported |
| /// from root without DeferredLibrary annotations. |
| Set<LibraryElement> _nonDeferredReachableLibraries(LibraryElement root) { |
| Set<LibraryElement> result = new Set<LibraryElement>(); |
| |
| void traverseLibrary(LibraryElement library) { |
| if (result.contains(library)) return; |
| result.add(library); |
| |
| iterateDependencies(LibraryElement library) { |
| for (ImportElement import in library.imports) { |
| if (!import.isDeferred) { |
| LibraryElement importedLibrary = import.importedLibrary; |
| traverseLibrary(importedLibrary); |
| } |
| } |
| for (ExportElement export in library.exports) { |
| LibraryElement exportedLibrary = export.exportedLibrary; |
| traverseLibrary(exportedLibrary); |
| } |
| } |
| |
| iterateDependencies(library); |
| if (library.isPatched) { |
| iterateDependencies(library.implementation); |
| } |
| } |
| |
| traverseLibrary(root); |
| result.add(compiler.resolution.commonElements.coreLibrary); |
| return result; |
| } |
| |
| /// If [send] is a static send with a deferred element, returns the |
| /// [PrefixElement] that the first prefix of the send resolves to. |
| /// Otherwise returns null. |
| /// |
| /// Precondition: send must be static. |
| /// |
| /// Example: |
| /// |
| /// import "a.dart" deferred as a; |
| /// |
| /// main() { |
| /// print(a.loadLibrary.toString()); |
| /// a.loadLibrary().then((_) { |
| /// a.run(); |
| /// a.foo.method(); |
| /// }); |
| /// } |
| /// |
| /// Returns null for a.loadLibrary() (the special |
| /// function loadLibrary is not deferred). And returns the PrefixElement for |
| /// a.run() and a.foo. |
| /// a.loadLibrary.toString() and a.foo.method() are dynamic sends - and |
| /// this functions should not be called on them. |
| ImportElement deferredImportElement(ast.Send send, TreeElements elements) { |
| Element element = elements[send]; |
| // The DeferredLoaderGetter is not deferred, therefore we do not return the |
| // prefix. |
| if (element != null && element.isDeferredLoaderGetter) return null; |
| |
| ast.Node firstNode(ast.Node node) { |
| if (node is! ast.Send) { |
| return node; |
| } else { |
| ast.Send send = node; |
| ast.Node receiver = send.receiver; |
| ast.Node receiverFirst = firstNode(receiver); |
| if (receiverFirst != null) { |
| return receiverFirst; |
| } else { |
| return firstNode(send.selector); |
| } |
| } |
| } |
| |
| ast.Node first = firstNode(send); |
| ast.Node identifier = first.asIdentifier(); |
| if (identifier == null) return null; |
| Element maybePrefix = elements[identifier]; |
| if (maybePrefix != null && maybePrefix.isPrefix) { |
| PrefixElement prefixElement = maybePrefix; |
| if (prefixElement.isDeferred) { |
| return prefixElement.deferredImport; |
| } |
| } |
| return null; |
| } |
| |
| /// Returns a name for a deferred import. |
| // TODO(sigmund): delete support for the old annotation-style syntax. |
| String computeImportDeferName( |
| covariant ImportElement declaration, Compiler compiler) { |
| if (declaration.isDeferred) { |
| return super.computeImportDeferName(declaration, compiler); |
| } |
| |
| String result; |
| // Finds the first argument to the [DeferredLibrary] annotation |
| List<MetadataAnnotation> metadatas = declaration.metadata; |
| assert(metadatas != null); |
| for (MetadataAnnotation metadata in metadatas) { |
| metadata.ensureResolved(compiler.resolution); |
| ConstantValue value = |
| compiler.constants.getConstantValue(metadata.constant); |
| ResolutionDartType type = |
| value.getType(compiler.resolution.commonElements); |
| Element element = type.element; |
| if (element == compiler.resolution.commonElements.deferredLibraryClass) { |
| ConstructedConstantValue constant = value; |
| StringConstantValue s = constant.fields.values.single; |
| result = s.primitiveValue; |
| break; |
| } |
| } |
| assert(result != null); |
| return result; |
| } |
| } |