| Index: dart/sdk/lib/_internal/compiler/implementation/mirrors_used.dart
|
| diff --git a/dart/sdk/lib/_internal/compiler/implementation/mirrors_used.dart b/dart/sdk/lib/_internal/compiler/implementation/mirrors_used.dart
|
| index 7b795a2137857b62b99a644d5ea76acee99561b0..e868ce0841e5e3cd885c30b2948ae10e672bf8e3 100644
|
| --- a/dart/sdk/lib/_internal/compiler/implementation/mirrors_used.dart
|
| +++ b/dart/sdk/lib/_internal/compiler/implementation/mirrors_used.dart
|
| @@ -13,23 +13,36 @@ import 'dart2jslib.dart' show
|
| MessageKind,
|
| SourceString,
|
| StringConstant,
|
| - TypeConstant;
|
| + TreeElements,
|
| + TypeConstant,
|
| + invariant;
|
|
|
| import 'elements/elements.dart' show
|
| + ClassElement,
|
| Element,
|
| LibraryElement,
|
| MetadataAnnotation,
|
| + ScopeContainerElement,
|
| VariableElement;
|
|
|
| import 'util/util.dart' show
|
| - Link;
|
| + Link,
|
| + Spannable;
|
|
|
| import 'dart_types.dart' show
|
| - DartType;
|
| + DartType,
|
| + InterfaceType,
|
| + TypeKind;
|
|
|
| import 'tree/tree.dart' show
|
| Import,
|
| - LibraryTag;
|
| + LibraryTag,
|
| + NamedArgument,
|
| + NewExpression,
|
| + Node;
|
| +
|
| +import 'resolution/resolution.dart' show
|
| + ConstantMapper;
|
|
|
| /**
|
| * Compiler task that analyzes MirrorsUsed annotations.
|
| @@ -81,13 +94,17 @@ import 'tree/tree.dart' show
|
| */
|
| class MirrorUsageAnalyzerTask extends CompilerTask {
|
| Set<LibraryElement> librariesWithUsage;
|
| + MirrorUsageAnalyzer analyzer;
|
|
|
| MirrorUsageAnalyzerTask(Compiler compiler)
|
| - : super(compiler);
|
| + : super(compiler) {
|
| + analyzer = new MirrorUsageAnalyzer(compiler, this);
|
| + }
|
|
|
| + /// Collect @MirrorsUsed annotations in all libraries. Called by the
|
| + /// compiler after all libraries are loaded, but before resolution.
|
| void analyzeUsage(LibraryElement mainApp) {
|
| if (compiler.mirrorsLibrary == null) return;
|
| - MirrorUsageAnalyzer analyzer = new MirrorUsageAnalyzer(compiler, this);
|
| measure(analyzer.run);
|
| List<String> symbols = analyzer.mergedMirrorUsage.symbols;
|
| List<Element> targets = analyzer.mergedMirrorUsage.targets;
|
| @@ -99,31 +116,76 @@ class MirrorUsageAnalyzerTask extends CompilerTask {
|
| librariesWithUsage = analyzer.librariesWithUsage;
|
| }
|
|
|
| + /// Is there a @MirrorsUsed annotation in the library of [element]? Used by
|
| + /// the resolver to suppress hints about using new Symbol or
|
| + /// MirrorSystem.getName.
|
| bool hasMirrorUsage(Element element) {
|
| return librariesWithUsage != null
|
| && librariesWithUsage.contains(element.getLibrary());
|
| }
|
| +
|
| + /// Call-back from the resolver to analyze MirorsUsed annotations. The result
|
| + /// is stored in [analyzer] and later used to compute
|
| + /// [:analyzer.mergedMirrorUsage:].
|
| + void validate(NewExpression node, TreeElements mapping) {
|
| + for (Node argument in node.send.arguments) {
|
| + NamedArgument named = argument.asNamedArgument();
|
| + if (named == null) continue;
|
| + Constant value = compiler.metadataHandler.compileNodeWithDefinitions(
|
| + named.expression, mapping, isConst: true);
|
| +
|
| + ConstantMapper mapper =
|
| + new ConstantMapper(compiler.metadataHandler, mapping, compiler);
|
| + named.expression.accept(mapper);
|
| +
|
| + MirrorUsageBuilder builder =
|
| + new MirrorUsageBuilder(
|
| + analyzer, mapping.currentElement.getLibrary(), named.expression,
|
| + value, mapper.constantToNodeMap);
|
| +
|
| + if (named.name.source == const SourceString('symbols')) {
|
| + analyzer.cachedValues[value] =
|
| + builder.convertToListOfStrings(
|
| + builder.convertConstantToUsageList(value, onlyStrings: true));
|
| + } else if (named.name.source == const SourceString('targets')) {
|
| + analyzer.cachedValues[value] =
|
| + builder.resolveUsageList(builder.convertConstantToUsageList(value));
|
| + } else if (named.name.source == const SourceString('metaTargets')) {
|
| + analyzer.cachedValues[value] =
|
| + builder.resolveUsageList(builder.convertConstantToUsageList(value));
|
| + } else if (named.name.source == const SourceString('override')) {
|
| + analyzer.cachedValues[value] =
|
| + builder.resolveUsageList(builder.convertConstantToUsageList(value));
|
| + }
|
| + }
|
| + }
|
| }
|
|
|
| class MirrorUsageAnalyzer {
|
| final Compiler compiler;
|
| final MirrorUsageAnalyzerTask task;
|
| - final List<LibraryElement> wildcard;
|
| + List<LibraryElement> wildcard;
|
| final Set<LibraryElement> librariesWithUsage;
|
| - final Set<LibraryElement> librariesWithoutUsage;
|
| + final Map<Constant, List> cachedValues;
|
| MirrorUsage mergedMirrorUsage;
|
|
|
| MirrorUsageAnalyzer(Compiler compiler, this.task)
|
| : compiler = compiler,
|
| - wildcard = compiler.libraries.values.toList(),
|
| librariesWithUsage = new Set<LibraryElement>(),
|
| - librariesWithoutUsage = new Set<LibraryElement>();
|
| + cachedValues = new Map<Constant, List>();
|
|
|
| + /// Collect and merge all @MirrorsUsed annotations. As a side-effect, also
|
| + /// compute which libraries have the annotation (which is used by
|
| + /// [MirrorUsageAnalyzerTask.hasMirrorUsage]).
|
| void run() {
|
| + wildcard = compiler.libraries.values.toList();
|
| Map<LibraryElement, List<MirrorUsage>> usageMap =
|
| collectMirrorsUsedAnnotation();
|
| propagateOverrides(usageMap);
|
| - librariesWithoutUsage.removeAll(usageMap.keys);
|
| + Set<LibraryElement> librariesWithoutUsage = new Set<LibraryElement>();
|
| + usageMap.forEach((LibraryElement library, List<MirrorUsage> usage) {
|
| + if (usage.isEmpty) librariesWithoutUsage.add(library);
|
| + });
|
| if (librariesWithoutUsage.isEmpty) {
|
| mergedMirrorUsage = mergeUsages(usageMap);
|
| } else {
|
| @@ -131,12 +193,13 @@ class MirrorUsageAnalyzer {
|
| }
|
| }
|
|
|
| + /// Collect all @MirrorsUsed from all libraries and represent them as
|
| + /// [MirrorUsage].
|
| Map<LibraryElement, List<MirrorUsage>> collectMirrorsUsedAnnotation() {
|
| Map<LibraryElement, List<MirrorUsage>> result =
|
| new Map<LibraryElement, List<MirrorUsage>>();
|
| for (LibraryElement library in compiler.libraries.values) {
|
| if (library.isInternalLibrary) continue;
|
| - librariesWithoutUsage.add(library);
|
| for (LibraryTag tag in library.tags) {
|
| Import importTag = tag.asImport();
|
| if (importTag == null) continue;
|
| @@ -157,6 +220,7 @@ class MirrorUsageAnalyzer {
|
| return result;
|
| }
|
|
|
| + /// Apply [MirrorUsage] with 'override' to libraries they override.
|
| void propagateOverrides(Map<LibraryElement, List<MirrorUsage>> usageMap) {
|
| Map<LibraryElement, List<MirrorUsage>> propagatedOverrides =
|
| new Map<LibraryElement, List<MirrorUsage>>();
|
| @@ -189,6 +253,8 @@ class MirrorUsageAnalyzer {
|
| });
|
| }
|
|
|
| + /// Find @MirrorsUsed annotations on the given import [tag] in [library]. The
|
| + /// annotations are represented as [MirrorUsage].
|
| List<MirrorUsage> mirrorsUsedOnLibraryTag(LibraryElement library,
|
| Import tag) {
|
| LibraryElement importedLibrary = library.getLibraryFromTag(tag);
|
| @@ -200,19 +266,13 @@ class MirrorUsageAnalyzer {
|
| metadata.ensureResolved(compiler);
|
| Element element = metadata.value.computeType(compiler).element;
|
| if (element == compiler.mirrorsUsedClass) {
|
| - try {
|
| - MirrorUsage usage =
|
| - new MirrorUsageBuilder(this, library).build(metadata.value);
|
| - result.add(usage);
|
| - } on BadMirrorsUsedAnnotation catch (e) {
|
| - compiler.reportError(
|
| - metadata, MessageKind.GENERIC, {'text': e.message});
|
| - }
|
| + result.add(buildUsage(metadata.value));
|
| }
|
| }
|
| return result;
|
| }
|
|
|
| + /// Merge all [MirrorUsage] instances accross all libraries.
|
| MirrorUsage mergeUsages(Map<LibraryElement, List<MirrorUsage>> usageMap) {
|
| Set<MirrorUsage> usagesToMerge = new Set<MirrorUsage>();
|
| usageMap.forEach((LibraryElement library, List<MirrorUsage> usages) {
|
| @@ -230,7 +290,11 @@ class MirrorUsageAnalyzer {
|
| }
|
| }
|
|
|
| + /// Merge [a] with [b]. The resulting [MirrorUsage] simply has the symbols,
|
| + /// targets, and metaTargets of [a] and [b] concatenated. 'override' is
|
| + /// ignored.
|
| MirrorUsage merge(MirrorUsage a, MirrorUsage b) {
|
| + // TOOO(ahe): Should be an instance method on MirrorUsage.
|
| if (a.symbols == null && a.targets == null && a.metaTargets == null) {
|
| return b;
|
| } else if (
|
| @@ -258,8 +322,30 @@ class MirrorUsageAnalyzer {
|
| }
|
| return new MirrorUsage(symbols, targets, metaTargets, null);
|
| }
|
| +
|
| + /// Convert a [constant] to an instance of [MirrorUsage] using information
|
| + /// that was resolved during [MirrorUsageAnalyzerTask.validate].
|
| + MirrorUsage buildUsage(ConstructedConstant constant) {
|
| + Map<Element, Constant> fields = constant.fieldElements;
|
| + VariableElement symbolsField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| + const SourceString('symbols'));
|
| + VariableElement targetsField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| + const SourceString('targets'));
|
| + VariableElement metaTargetsField =
|
| + compiler.mirrorsUsedClass.lookupLocalMember(
|
| + const SourceString('metaTargets'));
|
| + VariableElement overrideField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| + const SourceString('override'));
|
| +
|
| + return new MirrorUsage(
|
| + cachedValues[fields[symbolsField]],
|
| + cachedValues[fields[targetsField]],
|
| + cachedValues[fields[metaTargetsField]],
|
| + cachedValues[fields[overrideField]]);
|
| + }
|
| }
|
|
|
| +/// Used to represent a resolved MirrorsUsed constant.
|
| class MirrorUsage {
|
| final List<String> symbols;
|
| final List<Element> targets;
|
| @@ -281,38 +367,28 @@ class MirrorUsage {
|
| }
|
|
|
| class MirrorUsageBuilder {
|
| - MirrorUsageAnalyzer analyzer;
|
| - LibraryElement enclosingLibrary;
|
| + final MirrorUsageAnalyzer analyzer;
|
| + final LibraryElement enclosingLibrary;
|
| + final Spannable spannable;
|
| + final Constant constant;
|
| + final Map<Constant, Node> constantToNodeMap;
|
|
|
| - MirrorUsageBuilder(this.analyzer, this.enclosingLibrary);
|
| + MirrorUsageBuilder(
|
| + this.analyzer,
|
| + this.enclosingLibrary,
|
| + this.spannable,
|
| + this.constant,
|
| + this.constantToNodeMap);
|
|
|
| Compiler get compiler => analyzer.compiler;
|
|
|
| - MirrorUsage build(ConstructedConstant constant) {
|
| - Map<Element, Constant> fields = constant.fieldElements;
|
| - VariableElement symbolsField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| - const SourceString('symbols'));
|
| - VariableElement targetsField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| - const SourceString('targets'));
|
| - VariableElement metaTargetsField =
|
| - compiler.mirrorsUsedClass.lookupLocalMember(
|
| - const SourceString('metaTargets'));
|
| - VariableElement overrideField = compiler.mirrorsUsedClass.lookupLocalMember(
|
| - const SourceString('override'));
|
| - List<String> symbols =
|
| - convertToListOfStrings(
|
| - convertConstantToUsageList(fields[symbolsField]));
|
| - List<Element> targets =
|
| - resolveUsageList(convertConstantToUsageList(fields[targetsField]));
|
| -
|
| - List<Element> metaTargets =
|
| - resolveUsageList(convertConstantToUsageList(fields[metaTargetsField]));
|
| - List<Element> override =
|
| - resolveUsageList(convertConstantToUsageList(fields[overrideField]));
|
| - return new MirrorUsage(symbols, targets, metaTargets, override);
|
| - }
|
| -
|
| - List convertConstantToUsageList(Constant constant) {
|
| + /// Convert a constant to a list of [String] and [Type] values. If the
|
| + /// constant is a single [String], it is assumed to be a comma-separated list
|
| + /// of qualified names. If the constant is a [Type] t, the result is [:[t]:].
|
| + /// Otherwise, the constant is assumed to represent a list of strings (each a
|
| + /// qualified name) and types, and such a list is constructed.
|
| + List convertConstantToUsageList(
|
| + Constant constant, { bool onlyStrings: false }) {
|
| if (constant.isNull()) {
|
| return null;
|
| } else if (constant.isList()) {
|
| @@ -322,16 +398,21 @@ class MirrorUsageBuilder {
|
| if (entry.isString()) {
|
| StringConstant string = entry;
|
| result.add(string.value.slowToString());
|
| - } else if (entry.isType()) {
|
| + } else if (!onlyStrings && entry.isType()) {
|
| TypeConstant type = entry;
|
| result.add(type.representedType);
|
| } else {
|
| - throw new BadMirrorsUsedAnnotation(
|
| - 'Expected a string or type, but got "$entry".');
|
| + Spannable node = positionOf(entry);
|
| + MessageKind kind = onlyStrings
|
| + ? MessageKind.MIRRORS_EXPECTED_STRING
|
| + : MessageKind.MIRRORS_EXPECTED_STRING_OR_TYPE;
|
| + compiler.reportHint(
|
| + node,
|
| + kind, {'name': node, 'type': apiTypeOf(entry)});
|
| }
|
| }
|
| return result;
|
| - } else if (constant.isType()) {
|
| + } else if (!onlyStrings && constant.isType()) {
|
| TypeConstant type = constant;
|
| return [type.representedType];
|
| } else if (constant.isString()) {
|
| @@ -339,25 +420,53 @@ class MirrorUsageBuilder {
|
| return
|
| string.value.slowToString().split(',').map((e) => e.trim()).toList();
|
| } else {
|
| - throw new BadMirrorsUsedAnnotation(
|
| - 'Expected a string or a list of string, but got "$constant".');
|
| + Spannable node = positionOf(constant);
|
| + MessageKind kind = onlyStrings
|
| + ? MessageKind.MIRRORS_EXPECTED_STRING_OR_LIST
|
| + : MessageKind.MIRRORS_EXPECTED_STRING_TYPE_OR_LIST;
|
| + compiler.reportHint(
|
| + node,
|
| + kind, {'name': node, 'type': apiTypeOf(constant)});
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + /// Find the first non-implementation interface of constant.
|
| + DartType apiTypeOf(Constant constant) {
|
| + DartType type = constant.computeType(compiler);
|
| + LibraryElement library = type.element.getLibrary();
|
| + if (type.kind == TypeKind.INTERFACE && library.isInternalLibrary) {
|
| + InterfaceType interface = type;
|
| + ClassElement cls = type.element;
|
| + for (DartType supertype in cls.ensureResolved(compiler).allSupertypes) {
|
| + if (supertype.kind == TypeKind.INTERFACE
|
| + && !supertype.element.getLibrary().isInternalLibrary) {
|
| + return interface.asInstanceOf(supertype.element);
|
| + }
|
| + }
|
| }
|
| + return type;
|
| }
|
|
|
| + /// Ensure a list contains only strings.
|
| List<String> convertToListOfStrings(List list) {
|
| if (list == null) return null;
|
| List<String> result = new List<String>(list.length);
|
| int count = 0;
|
| for (var entry in list) {
|
| - if (entry is! String) {
|
| - throw new BadMirrorsUsedAnnotation(
|
| - 'Expected a string, but got "$entry"');
|
| - }
|
| + assert(invariant(spannable, entry is String));
|
| result[count++] = entry;
|
| }
|
| return result;
|
| }
|
|
|
| + /// Convert a list of strings and types to a list of elements. Types are
|
| + /// converted to their corresponding element, and strings are resolved as
|
| + /// follows:
|
| + ///
|
| + /// First find the longest library name that is a prefix of the string, if
|
| + /// there are none, resolve using [resolveExpression]. Otherwise, resolve the
|
| + /// rest of the string using [resolveLocalExpression].
|
| List<Element> resolveUsageList(List list) {
|
| if (list == null) return null;
|
| if (list.length == 1 && list[0] == '*') {
|
| @@ -370,22 +479,114 @@ class MirrorUsageBuilder {
|
| result.add(type.element);
|
| } else {
|
| String string = entry;
|
| + LibraryElement libraryCandiate;
|
| + String libraryNameCandiate;
|
| for (LibraryElement l in compiler.libraries.values) {
|
| if (l.hasLibraryName()) {
|
| String libraryName = l.getLibraryOrScriptName();
|
| - if (string == libraryName || string.startsWith('$libraryName.')) {
|
| - result.add(l);
|
| + if (string == libraryName) {
|
| + // Found an exact match.
|
| + libraryCandiate = l;
|
| + libraryNameCandiate = libraryName;
|
| break;
|
| + } else if (string.startsWith('$libraryName.')) {
|
| + if (libraryNameCandiate == null
|
| + || libraryNameCandiate.length < libraryName.length) {
|
| + // Found a better candiate
|
| + libraryCandiate = l;
|
| + libraryNameCandiate = libraryName;
|
| + }
|
| }
|
| }
|
| }
|
| + Element e;
|
| + if (libraryNameCandiate == string) {
|
| + e = libraryCandiate;
|
| + } else if (libraryNameCandiate != null) {
|
| + e = resolveLocalExpression(
|
| + libraryCandiate,
|
| + string.substring(libraryNameCandiate.length + 1).split('.'));
|
| + } else {
|
| + e = resolveExpression(string);
|
| + }
|
| + if (e != null) result.add(e);
|
| }
|
| }
|
| return result;
|
| }
|
| -}
|
|
|
| -class BadMirrorsUsedAnnotation {
|
| - final String message;
|
| - BadMirrorsUsedAnnotation(this.message);
|
| + /// Resolve [expression] in [enclosingLibrary]'s import scope.
|
| + Element resolveExpression(String expression) {
|
| + List<String> identifiers = expression.split('.');
|
| + Element element = enclosingLibrary.find(new SourceString(identifiers[0]));
|
| + if (element == null) {
|
| + compiler.reportHint(
|
| + spannable, MessageKind.MIRRORS_CANNOT_RESOLVE_IN_CURRENT_LIBRARY,
|
| + {'name': expression});
|
| + return null;
|
| + } else {
|
| + if (identifiers.length == 1) return element;
|
| + return resolveLocalExpression(element, identifiers.sublist(1));
|
| + }
|
| + }
|
| +
|
| + /// Resolve [identifiers] in [element]'s local members.
|
| + Element resolveLocalExpression(Element element, List<String> identifiers) {
|
| + Element current = element;
|
| + for (String identifier in identifiers) {
|
| + Element e = findLocalMemberIn(current, new SourceString(identifier));
|
| + if (e == null) {
|
| + if (current.isLibrary()) {
|
| + LibraryElement library = current;
|
| + compiler.reportHint(
|
| + spannable, MessageKind.MIRRORS_CANNOT_RESOLVE_IN_LIBRARY,
|
| + {'name': identifiers[0],
|
| + 'library': library.getLibraryOrScriptName()});
|
| + } else {
|
| + compiler.reportHint(
|
| + spannable, MessageKind.MIRRORS_CANNOT_FIND_IN_ELEMENT,
|
| + {'name': identifier, 'element': current.name});
|
| + }
|
| + return current;
|
| + }
|
| + current = e;
|
| + }
|
| + return current;
|
| + }
|
| +
|
| + /// Helper method to lookup members in a [ScopeContainerElement]. If
|
| + /// [element] is not a ScopeContainerElement, return null.
|
| + Element findLocalMemberIn(Element element, SourceString name) {
|
| + if (element is ScopeContainerElement) {
|
| + ScopeContainerElement scope = element;
|
| + return scope.localLookup(name);
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /// Attempt to find a [Spannable] corresponding to constant.
|
| + Spannable positionOf(Constant constant) {
|
| + Node node = constantToNodeMap[constant];
|
| + if (node == null) {
|
| + // TODO(ahe): Returning [spannable] here leads to confusing error
|
| + // messages. For example, consider:
|
| + // @MirrorsUsed(targets: fisk)
|
| + // import 'dart:mirrors';
|
| + //
|
| + // const fisk = const [main];
|
| + //
|
| + // main() {}
|
| + //
|
| + // The message is:
|
| + // example.dart:1:23: Hint: Can't use 'fisk' here because ...
|
| + // Did you forget to add quotes?
|
| + // @MirrorsUsed(targets: fisk)
|
| + // ^^^^
|
| + //
|
| + // Instead of saying 'fisk' should pretty print the problematic constant
|
| + // value.
|
| + return spannable;
|
| + }
|
| + return node;
|
| + }
|
| }
|
|
|