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; |
+ } |
} |