Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(552)

Unified Diff: reflectable/lib/src/transformer_implementation.dart

Issue 1391543003: Creates `reflectors`, as a meta-meta feature that enables dynamic selection of a "mirror system". (Closed) Base URL: https://github.com/dart-lang/reflectable.git@master
Patch Set: Added lots of LibraryMirror support in order to make reflectors work in transformed code Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: reflectable/lib/src/transformer_implementation.dart
diff --git a/reflectable/lib/src/transformer_implementation.dart b/reflectable/lib/src/transformer_implementation.dart
index 8bb6decee619f24e8f7f1b4d11f491640db0e8f2..66ada78711fc6b21f905d494e7ac2f7ca02db9e7 100644
--- a/reflectable/lib/src/transformer_implementation.dart
+++ b/reflectable/lib/src/transformer_implementation.dart
@@ -525,18 +525,32 @@ class _ReflectorDomain {
/// `_reflector` to behave correctly.
String _generateCode(
_ImportCollector importCollector, TransformLogger logger) {
- Enumerator<ExecutableElement> members = new Enumerator<ExecutableElement>();
+ // Library related collections.
+ Enumerator<_LibraryDomain> libraries = new Enumerator<_LibraryDomain>();
+ Map<LibraryElement, _LibraryDomain> libraryMap =
+ <LibraryElement, _LibraryDomain>{};
+ Enumerator<TopLevelVariableElement> topLevelVariables =
+ new Enumerator<TopLevelVariableElement>();
+
+ // Class related collections.
Enumerator<FieldElement> fields = new Enumerator<FieldElement>();
Enumerator<ParameterElement> parameters =
new Enumerator<ParameterElement>();
- Enumerator<LibraryElement> libraries = new Enumerator<LibraryElement>();
Set<String> instanceGetterNames = new Set<String>();
Set<String> instanceSetterNames = new Set<String>();
+ // Library and class related collections.
+ Enumerator<ExecutableElement> members = new Enumerator<ExecutableElement>();
+
// Fill in [libraries], [members], [fields], [parameters],
// [instanceGetterNames], and [instanceSetterNames].
for (LibraryElement library in _libraries) {
- libraries.add(library);
+ _LibraryDomain libraryDomain = _createLibraryDomain(library, this);
+ libraries.add(libraryDomain);
+ libraryMap[library] = libraryDomain;
+ libraryDomain._declarations.forEach(members.add);
+ libraryDomain._declaredParameters.forEach(parameters.add);
+ libraryDomain._declaredVariables.forEach(topLevelVariables.add);
}
for (_ClassDomain classDomain in classes.domains) {
// Gather the behavioral interface into [members]. Note that
@@ -588,13 +602,25 @@ class _ReflectorDomain {
});
}
+ // Find the offsets of fields and of methods and functions in members.
+ int fieldsOffset = topLevelVariables.length;
+ int methodsOffset = fieldsOffset + fields.length;
+
// Generate code for creation of class mirrors.
String classMirrorsCode = _formatAsList(
"m.ClassMirror",
_capabilities._impliesTypes
? classes.domains.map((_ClassDomain classDomain) =>
- _classMirrorCode(classDomain, fields, members, libraries,
- importCollector, logger))
+ _classMirrorCode(
+ classDomain,
+ fields,
+ fieldsOffset,
+ methodsOffset,
+ members,
+ libraries,
+ libraryMap,
+ importCollector,
+ logger))
: <String>[]);
// Generate code for creation of getter and setter closures.
@@ -630,8 +656,9 @@ class _ReflectorDomain {
librariesCode = "null";
} else {
librariesCode = _formatAsList("m.LibraryMirror",
- libraries.items.map((LibraryElement library) {
- return _libraryMirrorCode(library, importCollector, logger);
+ libraries.items.map((_LibraryDomain library) {
+ return _libraryMirrorCode(library, members, topLevelVariables,
+ fields.length, importCollector, logger);
}));
}
@@ -691,8 +718,7 @@ class _ReflectorDomain {
Iterable<Element> getters = <Iterable<Element>>[
part.accessors
.where((PropertyAccessorElement accessor) => accessor.isGetter),
- part.functions,
- part.topLevelVariables
+ part.functions
].expand((Iterable<Element> elements) => elements);
return getters.where((Element getter) =>
_capabilities.supportsTopLevelInvoke(getter.name, getter.metadata));
@@ -703,9 +729,7 @@ class _ReflectorDomain {
return library.units.map((CompilationUnitElement part) {
Iterable setters = <Iterable<Element>>[
part.accessors
- .where((PropertyAccessorElement accessor) => accessor.isSetter),
- part.topLevelVariables
- .where((TopLevelVariableElement variable) => !variable.isFinal)
+ .where((PropertyAccessorElement accessor) => accessor.isSetter)
].expand((Iterable<Element> elements) => elements);
return setters.where((Element setter) =>
_capabilities.supportsTopLevelInvoke(setter.name, setter.metadata));
@@ -715,15 +739,20 @@ class _ReflectorDomain {
String _classMirrorCode(
_ClassDomain classDomain,
Enumerator<FieldElement> fields,
+ int fieldsOffset,
+ int methodsOffset,
Enumerator<ExecutableElement> members,
- Enumerator<LibraryElement> libraries,
+ Enumerator<_LibraryDomain> libraries,
+ Map<LibraryElement, _LibraryDomain> libraryMap,
_ImportCollector importCollector,
TransformLogger logger) {
+ int descriptor = _classDescriptor(classDomain._classElement);
+
// Fields go first in [memberMirrors], so they will get the
// same index as in [fields].
Iterable<int> fieldsIndices =
classDomain._declaredFields.map((FieldElement element) {
- return fields.indexOf(element);
+ return fields.indexOf(element) + fieldsOffset;
});
// All the elements in the behavioral interface go after the
@@ -742,17 +771,17 @@ class _ReflectorDomain {
int index = members.indexOf(element);
return index == null
? constants.NO_CAPABILITY_INDEX
- : index + fields.length;
+ : index + methodsOffset;
});
- String declarationsCode = "<int>[${constants
- .NO_CAPABILITY_INDEX}]";
- if (_capabilities._impliesDeclarations) {
- List<int> declarationsIndices = <int>[]
- ..addAll(fieldsIndices)
- ..addAll(methodsIndices);
- declarationsCode = _formatAsList("int", declarationsIndices);
- }
+ String declarationsCode = _capabilities._impliesDeclarations
+ ? _formatAsList(
+ "int",
+ () sync* {
+ yield* fieldsIndices;
+ yield* methodsIndices;
+ }())
+ : "<int>[${constants.NO_CAPABILITY_INDEX}]";
// All instance members belong to the behavioral interface, so they
// also get an offset of `fields.length`.
@@ -764,7 +793,7 @@ class _ReflectorDomain {
int index = members.indexOf(element);
return index == null
? constants.NO_CAPABILITY_INDEX
- : index + fields.length;
+ : index + methodsOffset;
}));
// All static members belong to the behavioral interface, so they
@@ -774,7 +803,7 @@ class _ReflectorDomain {
int index = members.indexOf(element);
return index == null
? constants.NO_CAPABILITY_INDEX
- : index + fields.length;
+ : index + methodsOffset;
}));
ClassElement classElement = classDomain._classElement;
@@ -832,7 +861,7 @@ class _ReflectorDomain {
if (mixinIndex == null) mixinIndex = constants.NO_CAPABILITY_INDEX;
int ownerIndex = _capabilities.supportsLibraries
- ? libraries.indexOf(classElement.library)
+ ? libraries.indexOf(libraryMap[classElement.library])
: constants.NO_CAPABILITY_INDEX;
String superinterfaceIndices = _formatAsList(
@@ -853,7 +882,7 @@ class _ReflectorDomain {
int classIndex = classes.indexOf(classElement);
String result = 'new r.ClassMirrorImpl(r"${classDomain._simpleName}", '
- 'r"${_qualifiedName(classElement)}", $classIndex, '
+ 'r"${_qualifiedName(classElement)}", $descriptor, $classIndex, '
'${_constConstructionCode(importCollector)}, '
'$declarationsCode, $instanceMembersCode, $staticMembersCode, '
'$superclassIndex, $staticGettersCode, $staticSettersCode, '
@@ -950,15 +979,19 @@ class _ReflectorDomain {
if (classElement is MixinApplication && classElement.declaredName == null) {
return 'new r.FakeType(r"${_qualifiedName(classElement)}")';
}
- if (classElement is! MixinApplication && classElement.type.isDynamic) {
- return "dynamic";
- }
+ if (classElement.type.isDynamic) return "dynamic";
String prefix = importCollector._getPrefix(classElement.library);
return "$prefix.${classElement.name}";
}
- String _libraryMirrorCode(LibraryElement library,
- _ImportCollector importCollector, TransformLogger logger) {
+ String _libraryMirrorCode(
+ _LibraryDomain libraryDomain,
+ Enumerator<ExecutableElement> members,
+ Enumerator<TopLevelVariableElement> variables,
+ int fieldsLength,
+ _ImportCollector importCollector,
+ TransformLogger logger) {
+ LibraryElement library = libraryDomain._libraryElement;
String gettersCode = _formatAsMap(
_gettersOfLibrary(library).map((PropertyAccessorElement getter) {
return _topLevelGettingClosure(importCollector, library, getter.name);
@@ -968,6 +1001,31 @@ class _ReflectorDomain {
return topLevelSettingClosure(importCollector, library, setter.name);
}));
+ // Fields go first in [memberMirrors], so they will get the
+ // same index as in [fields].
+ Iterable<int> variableIndices =
+ libraryDomain._declaredVariables.map((TopLevelVariableElement element) {
+ return variables.indexOf(element);
+ });
+
+ // All the elements in the behavioral interface go after the
+ // fields in [memberMirrors], so they must get an offset of
+ // `fields.length` on the index.
+ Iterable<int> methodsIndices = libraryDomain._declarations
+ .where(_executableIsntImplicitGetterOrSetter)
+ .map((ExecutableElement element) {
+ int index = members.indexOf(element);
+ return index + fieldsLength;
+ });
+
+ String declarationsCode = "<int>[${constants.NO_CAPABILITY_INDEX}]";
+ if (_capabilities._impliesDeclarations) {
+ List<int> declarationsIndices = <int>[]
+ ..addAll(variableIndices)
+ ..addAll(methodsIndices);
+ declarationsCode = _formatAsList("int", declarationsIndices);
+ }
+
// TODO(sigurdm) clarify: Find out how to get good uri's in a
// transformer.
// TODO(sigurdm) implement: Check for `uriCapability`.
@@ -982,7 +1040,8 @@ class _ReflectorDomain {
}
return 'new r.LibraryMirrorImpl(r"${library.name}", $uriCode, '
- '$gettersCode, $settersCode, $metadataCode)';
+ '${_constConstructionCode(importCollector)}, '
+ '$declarationsCode, $gettersCode, $settersCode, $metadataCode)';
}
String _parameterMirrorCode(
@@ -1304,10 +1363,9 @@ String _gettingClosure(String getterName) {
// Auxiliary function used by `_generateCode`.
String _settingClosure(String setterName) {
- assert(setterName.substring(setterName.length - 1) == "=");
- // The [setterName] includes the "=", remove it.
- String name = setterName.substring(0, setterName.length - 1);
-
+ String name = setterName.substring(setterName.length - 1) == "="
sigurdm 2015/10/09 07:52:04 Can we now get a setterName not ending in '='? Whe
eernst 2015/10/09 10:09:05 I found `someName = value` in the generated code a
sigurdm 2015/10/09 10:47:20 I think we should track down where that happens. M
eernst 2015/10/09 11:24:14 Did that, and it doesn't fail now. I think this ma
+ ? setterName.substring(0, setterName.length - 1)
+ : setterName;
return 'r"$setterName": (dynamic instance, value) => '
'instance.$name = value';
}
@@ -1350,34 +1408,101 @@ String topLevelSettingClosure(_ImportCollector importCollector,
return 'r"$setterName": (value) => $prefix.$name = value';
}
+/// Information about reflectability for a given library.
+class _LibraryDomain {
+ /// Element describing the target library.
+ final LibraryElement _libraryElement;
+
+ /// Fields declared by [_libraryElement] and included for reflection support,
+ /// according to the reflector described by the [_reflectorDomain];
+ /// obtained by filtering `_libraryElement.fields`.
+ final Iterable<TopLevelVariableElement> _declaredVariables;
+
+ /// Methods which are declared by [_libraryElement] and included for
+ /// reflection support, according to the reflector described by
+ /// [_reflectorDomain]; obtained by filtering `_libraryElement.functions`.
+ final Iterable<FunctionElement> _declaredFunctions;
+
+ /// Formal parameters declared by one of the [_declaredFunctions].
+ final Iterable<ParameterElement> _declaredParameters;
+
+ /// Getters and setters possessed by [_libraryElement] and included for
+ /// reflection support, according to the reflector described by
+ /// [_reflectorDomain]; obtained by filtering `_libraryElement.accessors`.
+ /// Note that it includes declared as well as synthetic accessors,
+ /// implicitly created as getters/setters for fields.
+ final Iterable<PropertyAccessorElement> _declaredAndImplicitAccessors;
+
+ /// The reflector domain that holds [this] object as one of its
+ /// library domains.
+ final _ReflectorDomain _reflectorDomain;
+
+ _LibraryDomain(
+ this._libraryElement,
+ this._declaredVariables,
+ this._declaredFunctions,
+ this._declaredParameters,
+ this._declaredAndImplicitAccessors,
+ this._reflectorDomain);
+
+ String get _simpleName {
+ return _libraryElement.name;
+ }
+
+ /// Returns the declared methods, accessors and constructors in
+ /// [_classElement]. Note that this includes synthetic getters and
+ /// setters, and omits fields; in other words, it provides the
+ /// behavioral point of view on the class. Also note that this is not
+ /// the same semantics as that of `declarations` in [ClassMirror].
+ Iterable<ExecutableElement> get _declarations sync* {
+ yield* _declaredFunctions;
+ yield* _declaredAndImplicitAccessors;
+ }
+
+ String toString() {
+ return "LibraryDomain($_libraryElement)";
+ }
+
+ bool operator ==(Object other) {
+ if (other is _LibraryDomain) {
+ return _libraryElement == other._libraryElement &&
+ _reflectorDomain == other._reflectorDomain;
+ } else {
+ return false;
+ }
+ }
+
+ int get hashCode => _libraryElement.hashCode ^ _reflectorDomain.hashCode;
+}
+
/// Information about reflectability for a given class.
class _ClassDomain {
/// Element describing the target class.
final ClassElement _classElement;
- /// Fields declared by [classElement] and included for reflection support,
- /// according to the reflector described by the [reflectorDomain];
- /// obtained by filtering `classElement.fields`.
+ /// Fields declared by [_classElement] and included for reflection support,
+ /// according to the reflector described by the [_reflectorDomain];
+ /// obtained by filtering `_classElement.fields`.
final Iterable<FieldElement> _declaredFields;
- /// Methods which are declared by [classElement] and included for
+ /// Methods which are declared by [_classElement] and included for
/// reflection support, according to the reflector described by
- /// [reflectorDomain]; obtained by filtering `classElement.methods`.
+ /// [reflectorDomain]; obtained by filtering `_classElement.methods`.
final Iterable<MethodElement> _declaredMethods;
/// Formal parameters declared by one of the [_declaredMethods].
final Iterable<ParameterElement> _declaredParameters;
- /// Getters and setters possessed by [classElement] and included for
+ /// Getters and setters possessed by [_classElement] and included for
/// reflection support, according to the reflector described by
- /// [reflectorDomain]; obtained by filtering `classElement.accessors`.
+ /// [reflectorDomain]; obtained by filtering `_classElement.accessors`.
/// Note that it includes declared as well as synthetic accessors,
/// implicitly created as getters/setters for fields.
final Iterable<PropertyAccessorElement> _declaredAndImplicitAccessors;
- /// Constructors declared by [classElement] and included for reflection
- /// support, according to the reflector described by [reflectorDomain];
- /// obtained by filtering `classElement.constructors`.
+ /// Constructors declared by [_classElement] and included for reflection
+ /// support, according to the reflector described by [_reflectorDomain];
+ /// obtained by filtering `_classElement.constructors`.
final Iterable<ConstructorElement> _constructors;
/// The reflector domain that holds [this] object as one of its
@@ -1641,7 +1766,7 @@ class _Capabilities {
return true;
}
// Quantifying capabilities do not influence the availability
- // of reflection support for top level invocation.
+ // of reflection support for top-level invocation.
}
// All options exhausted, give up.
@@ -1942,7 +2067,7 @@ class TransformerImplementation {
bool isOk = checkInheritance(result.value.type, focusClass.type);
return isOk ? result.value.type.element : null;
} else {
- // Not a const top level variable, not relevant.
+ // Not a const top-level variable, not relevant.
return null;
}
}
@@ -2133,7 +2258,9 @@ class TransformerImplementation {
for (InterfaceType mixin in type.mixins) {
ClassElement mixinElement = mixin.element;
ClassElement subClass = mixin == type.mixins.last ? type : null;
- String name = subClass == null ? null : type.name;
+ String name = subClass == null
sigurdm 2015/10/09 07:52:04 Could be ``` String name = subClass ?? (type.isMix
eernst 2015/10/09 10:09:04 It's a bit more tricky than that: If `subClass ==
sigurdm 2015/10/09 10:47:20 Oops - you're right!
+ ? null
+ : (type.isMixinApplication ? type.name : null);
MixinApplication mixinApplication = new MixinApplication(
name, superclass, mixinElement, type.library, subClass);
domain._classes.add(mixinApplication);
@@ -2221,6 +2348,15 @@ class TransformerImplementation {
addClassDomain(type, reflector);
}
}
+ for (FunctionElement function in unit.functions) {
+ for (ClassElement reflector in getReflectors(
+ _qualifiedFunctionName(function), function.metadata)) {
+ // We just add the library here, the function itself will be
+ // supported using `invoke` and `declarations` of that library
+ // mirror.
+ addLibrary(library, reflector);
+ }
+ }
}
}
@@ -2588,6 +2724,17 @@ bool _executableIsntImplicitGetterOrSetter(ExecutableElement executable) {
}
/// Returns an integer encoding of the kind and attributes of the given
+/// class.
+int _classDescriptor(ClassElement element) {
+ int result = constants.clazz;
+ if (element.isPrivate) result |= constants.privateAttribute;
+ if (element.isSynthetic) result |= constants.syntheticAttribute;
+ if (element.isAbstract) result |= constants.abstractAttribute;
+ if (element.isEnum) result |= constants.enumAttribute;
+ return result;
+}
+
+/// Returns an integer encoding of the kind and attributes of the given
/// field.
int _fieldDescriptor(FieldElement element) {
int result = constants.field;
@@ -2940,6 +3087,48 @@ String _extractMetadataCode(Element element, Resolver resolver,
return _formatAsList("Object", metadataParts);
}
+Iterable<TopLevelVariableElement> _extractDeclaredVariables(
sigurdm 2015/10/09 07:52:04 Give these functions a (short) comment
eernst 2015/10/09 10:09:05 Done.
+ LibraryElement libraryElement, _Capabilities capabilities) sync* {
+ for (CompilationUnitElement unit in libraryElement.units) {
+ for (TopLevelVariableElement variable in unit.topLevelVariables) {
+ if (variable.isPrivate || variable.isSynthetic) continue;
+ // TODO(eernst) clarify: Do we want to subsume variables under invoke?
+ if (capabilities.supportsTopLevelInvoke(
+ variable.name, variable.metadata)) {
+ yield variable;
+ }
+ }
+ }
+}
+
+Iterable<FunctionElement> _extractDeclaredFunctions(
+ LibraryElement libraryElement, _Capabilities capabilities) sync* {
+ for (CompilationUnitElement unit in libraryElement.units) {
+ for (FunctionElement function in unit.functions) {
+ if (function.isPrivate) continue;
+ if (capabilities.supportsTopLevelInvoke(
+ function.name, function.metadata)) {
+ yield function;
+ }
+ }
+ }
+}
+
+Iterable<ParameterElement> _extractDeclaredFunctionParameters(
+ Iterable<FunctionElement> declaredFunctions,
+ Iterable<PropertyAccessorElement> accessors) {
+ List<ParameterElement> result = <ParameterElement>[];
+ for (FunctionElement declaredFunction in declaredFunctions) {
+ result.addAll(declaredFunction.parameters);
+ }
+ for (PropertyAccessorElement accessor in accessors) {
+ if (accessor.isSetter) {
+ result.addAll(accessor.parameters);
+ }
+ }
+ return result;
+}
+
Iterable<FieldElement> _extractDeclaredFields(
ClassElement classElement, _Capabilities capabilities) {
return classElement.fields.where((FieldElement field) {
@@ -2981,6 +3170,19 @@ Iterable<ParameterElement> _extractDeclaredParameters(
return result;
}
+Iterable<PropertyAccessorElement> _declaredAndImplicitLibraryAccessors(
+ LibraryElement libraryElement, _Capabilities capabilities) sync* {
+ for (CompilationUnitElement unit in libraryElement.units) {
+ for (PropertyAccessorElement accessor in unit.accessors) {
+ if (accessor.isPrivate) continue;
+ if (capabilities.supportsTopLevelInvoke(
+ accessor.name, accessor.metadata)) {
+ yield accessor;
+ }
+ }
+ }
+}
+
/// Returns the [PropertyAccessorElement]s which are the accessors
/// of the given [classElement], including both the declared ones
/// and the implicitly generated ones corresponding to fields. This
@@ -3009,6 +3211,26 @@ Iterable<ConstructorElement> _declaredConstructors(
});
}
+_LibraryDomain _createLibraryDomain(
+ LibraryElement library, _ReflectorDomain domain) {
+ List<TopLevelVariableElement> declaredVariablesOfLibrary =
+ _extractDeclaredVariables(library, domain._capabilities).toList();
+ List<FunctionElement> declaredFunctionsOfLibrary =
+ _extractDeclaredFunctions(library, domain._capabilities).toList();
+ List<PropertyAccessorElement> declaredAndImplicitAccessorsOfLibrary =
+ _declaredAndImplicitLibraryAccessors(library, domain._capabilities);
+ List<ParameterElement> declaredParametersOfLibrary =
+ _extractDeclaredFunctionParameters(
+ declaredFunctionsOfLibrary, declaredAndImplicitAccessorsOfLibrary);
+ return new _LibraryDomain(
+ library,
+ declaredVariablesOfLibrary,
+ declaredFunctionsOfLibrary,
+ declaredParametersOfLibrary,
+ declaredAndImplicitAccessorsOfLibrary,
+ domain);
+}
+
_ClassDomain _createClassDomain(ClassElement type, _ReflectorDomain domain) {
if (type is MixinApplication) {
List<FieldElement> declaredFieldsOfClass =
@@ -3116,13 +3338,16 @@ class MixinApplication implements ClassElement {
}
@override
+ String get displayName => name;
+
+ @override
List<InterfaceType> get interfaces => <InterfaceType>[];
@override
List<ElementAnnotation> get metadata => <ElementAnnotation>[];
@override
- bool get isSynthetic => declaredName != null;
+ bool get isSynthetic => declaredName == null;
// Note that the `InterfaceTypeImpl` may well call methods on this
// `MixinApplication` whose body is `_unImplemented()`, but it still provides
@@ -3155,6 +3380,13 @@ class MixinApplication implements ClassElement {
bool get isMixinApplication => declaredName != null;
@override
+ bool get isAbstract => !isMixinApplication || mixin.isAbstract;
+
+ @override
+ // TODO(eernst) clarify: Is this test correct? Are all cases possible?
+ bool get isEnum => mixin.isEnum || superclass.isEnum;
sigurdm 2015/10/09 07:52:04 As I understand it you cannot mix an enum in or su
eernst 2015/10/09 10:09:04 Yep, the spec confirms that. Removed the comment,
+
+ @override
NamedCompilationUnitMember computeNode() =>
declaredName != null ? subclass.computeNode() : null;
@@ -3196,12 +3428,6 @@ class MixinApplication implements ClassElement {
bool get hasStaticMember => _unImplemented();
@override
- bool get isAbstract => _unImplemented();
-
- @override
- bool get isEnum => _unImplemented();
-
- @override
bool get isOrInheritsProxy => _unImplemented();
@override
@@ -3297,9 +3523,6 @@ class MixinApplication implements ClassElement {
get context => _unImplemented();
@override
- String get displayName => _unImplemented();
-
- @override
Element get enclosingElement => _unImplemented();
@override
@@ -3356,3 +3579,9 @@ String _qualifiedName(ClassElement classElement) {
? "null"
: "${classElement.library.name}.${classElement.name}";
}
+
+String _qualifiedFunctionName(FunctionElement functionElement) {
+ return functionElement == null
+ ? "null"
+ : "${functionElement.library.name}.${functionElement.name}";
+}

Powered by Google App Engine
This is Rietveld 408576698