| // Copyright (c) 2013, 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 dart._js_mirrors; |
| |
| import 'dart:async'; |
| import 'dart:collection' show UnmodifiableListView; |
| import 'dart:mirrors'; |
| |
| import 'dart:_foreign_helper' show |
| JS, |
| JS_CURRENT_ISOLATE, |
| JS_CURRENT_ISOLATE_CONTEXT, |
| JS_GET_NAME; |
| import 'dart:_collection-dev' as _symbol_dev; |
| import 'dart:_js_helper' show |
| BoundClosure, |
| Closure, |
| JSInvocationMirror, |
| JsCache, |
| Null, |
| Primitives, |
| RuntimeError, |
| createRuntimeType, |
| createUnmangledInvocationMirror, |
| getMangledTypeName, |
| throwInvalidReflectionError, |
| hasReflectableProperty, |
| runtimeTypeToString; |
| import 'dart:_interceptors' show |
| Interceptor, |
| JSExtendableArray; |
| import 'dart:_js_names'; |
| |
| const String METHODS_WITH_OPTIONAL_ARGUMENTS = r'$methodsWithOptionalArguments'; |
| |
| /// No-op method that is called to inform the compiler that tree-shaking needs |
| /// to be disabled. |
| disableTreeShaking() => preserveNames(); |
| |
| /// No-op method that is called to inform the compiler that metadata must be |
| /// preserved at runtime. |
| preserveMetadata() {} |
| |
| String getName(Symbol symbol) { |
| preserveNames(); |
| return n(symbol); |
| } |
| |
| class JsMirrorSystem implements MirrorSystem { |
| UnmodifiableMapView<Uri, LibraryMirror> _cachedLibraries; |
| |
| final IsolateMirror isolate = new JsIsolateMirror(); |
| |
| TypeMirror get dynamicType => _dynamicType; |
| TypeMirror get voidType => _voidType; |
| |
| final static TypeMirror _dynamicType = |
| new JsTypeMirror(const Symbol('dynamic')); |
| final static TypeMirror _voidType = new JsTypeMirror(const Symbol('void')); |
| |
| static final Map<String, List<LibraryMirror>> librariesByName = |
| computeLibrariesByName(); |
| |
| Map<Uri, LibraryMirror> get libraries { |
| if (_cachedLibraries != null) return _cachedLibraries; |
| Map<Uri, LibraryMirror> result = new Map(); |
| for (List<LibraryMirror> list in librariesByName.values) { |
| for (LibraryMirror library in list) { |
| result[library.uri] = library; |
| } |
| } |
| return _cachedLibraries = |
| new UnmodifiableMapView<Uri, LibraryMirror>(result); |
| } |
| |
| Iterable<LibraryMirror> findLibrary(Symbol libraryName) { |
| return new UnmodifiableListView<LibraryMirror>( |
| librariesByName[n(libraryName)]); |
| } |
| |
| static Map<String, List<LibraryMirror>> computeLibrariesByName() { |
| disableTreeShaking(); |
| var result = new Map<String, List<LibraryMirror>>(); |
| var jsLibraries = JS('JSExtendableArray|Null', 'init.libraries'); |
| if (jsLibraries == null) return result; |
| for (List data in jsLibraries) { |
| String name = data[0]; |
| Uri uri = Uri.parse(data[1]); |
| List<String> classes = data[2]; |
| List<String> functions = data[3]; |
| var metadataFunction = data[4]; |
| var fields = data[5]; |
| bool isRoot = data[6]; |
| var globalObject = data[7]; |
| List metadata = (metadataFunction == null) |
| ? const [] : JS('List', '#()', metadataFunction); |
| var libraries = result.putIfAbsent(name, () => <LibraryMirror>[]); |
| libraries.add( |
| new JsLibraryMirror( |
| s(name), uri, classes, functions, metadata, fields, isRoot, |
| globalObject)); |
| } |
| return result; |
| } |
| } |
| |
| abstract class JsMirror implements Mirror { |
| const JsMirror(); |
| |
| String get _prettyName; |
| |
| String toString() => _prettyName; |
| |
| // TODO(ahe): Remove this method from the API. |
| MirrorSystem get mirrors => currentJsMirrorSystem; |
| |
| _getField(JsMirror receiver) { |
| throw new UnimplementedError(); |
| } |
| |
| void _setField(JsMirror receiver, Object arg) { |
| throw new UnimplementedError(); |
| } |
| |
| _loadField(String name) { |
| throw new UnimplementedError(); |
| } |
| |
| void _storeField(String name, Object arg) { |
| throw new UnimplementedError(); |
| } |
| } |
| |
| // This class is somewhat silly in the current implementation. |
| class JsIsolateMirror extends JsMirror implements IsolateMirror { |
| final _isolateContext = JS_CURRENT_ISOLATE_CONTEXT(); |
| |
| String get _prettyName => 'Isolate'; |
| |
| String get debugName { |
| String id = _isolateContext == null ? 'X' : _isolateContext.id.toString(); |
| // Using name similar to what the VM uses. |
| return '${n(rootLibrary.simpleName)}-$id'; |
| } |
| |
| bool get isCurrent => JS_CURRENT_ISOLATE_CONTEXT() == _isolateContext; |
| |
| LibraryMirror get rootLibrary { |
| return currentJsMirrorSystem.libraries.values.firstWhere( |
| (JsLibraryMirror library) => library._isRoot); |
| } |
| } |
| |
| abstract class JsDeclarationMirror extends JsMirror |
| implements DeclarationMirror { |
| final Symbol simpleName; |
| |
| const JsDeclarationMirror(this.simpleName); |
| |
| Symbol get qualifiedName => computeQualifiedName(owner, simpleName); |
| |
| bool get isPrivate => n(simpleName).startsWith('_'); |
| |
| bool get isTopLevel => owner != null && owner is LibraryMirror; |
| |
| // TODO(ahe): This should use qualifiedName. |
| String toString() => "$_prettyName on '${n(simpleName)}'"; |
| |
| List<JsMethodMirror> get _methods { |
| throw new RuntimeError('Should not call _methods'); |
| } |
| |
| _invoke(List positionalArguments, Map<Symbol, dynamic> namedArguments) { |
| throw new RuntimeError('Should not call _invoke'); |
| } |
| |
| // TODO(ahe): Implement this. |
| SourceLocation get location => throw new UnimplementedError(); |
| } |
| |
| class JsTypeVariableMirror extends JsTypeMirror implements TypeVariableMirror { |
| final TypeMirror upperBound; |
| final DeclarationMirror owner; |
| |
| JsTypeVariableMirror(Symbol simpleName, this.upperBound, this.owner) |
| : super(simpleName); |
| |
| bool operator ==(other) { |
| return (other is JsTypeVariableMirror && |
| simpleName == other.simpleName && |
| owner == other.owner); |
| } |
| |
| int get hashCode { |
| int code = 0x3FFFFFFF & (JsTypeVariableMirror).hashCode; |
| code ^= 17 * simpleName.hashCode; |
| code ^= 19 * owner.hashCode; |
| return code; |
| } |
| |
| String get _prettyName => 'TypeVariableMirror'; |
| } |
| |
| class JsTypeMirror extends JsDeclarationMirror implements TypeMirror { |
| JsTypeMirror(Symbol simpleName) |
| : super(simpleName); |
| |
| String get _prettyName => 'TypeMirror'; |
| |
| DeclarationMirror get owner => null; |
| |
| // TODO(ahe): Doesn't match the specification, see http://dartbug.com/11569. |
| bool get isTopLevel => true; |
| |
| // TODO(ahe): Implement this. |
| List<InstanceMirror> get metadata => throw new UnimplementedError(); |
| } |
| |
| class JsLibraryMirror extends JsDeclarationMirror with JsObjectMirror |
| implements LibraryMirror { |
| final Uri uri; |
| final List<String> _classes; |
| final List<String> _functions; |
| final List _metadata; |
| final String _compactFieldSpecification; |
| final bool _isRoot; |
| final _globalObject; |
| List<JsMethodMirror> _cachedFunctionMirrors; |
| List<JsVariableMirror> _cachedFields; |
| UnmodifiableMapView<Symbol, ClassMirror> _cachedClasses; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedFunctions; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedGetters; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedSetters; |
| UnmodifiableMapView<Symbol, VariableMirror> _cachedVariables; |
| UnmodifiableMapView<Symbol, Mirror> _cachedMembers; |
| UnmodifiableListView<InstanceMirror> _cachedMetadata; |
| |
| JsLibraryMirror(Symbol simpleName, |
| this.uri, |
| this._classes, |
| this._functions, |
| this._metadata, |
| this._compactFieldSpecification, |
| this._isRoot, |
| this._globalObject) |
| : super(simpleName); |
| |
| String get _prettyName => 'LibraryMirror'; |
| |
| Symbol get qualifiedName => simpleName; |
| |
| List<JsMethodMirror> get _methods => _functionMirrors; |
| |
| Map<Symbol, ClassMirror> get classes { |
| if (_cachedClasses != null) return _cachedClasses; |
| var result = new Map(); |
| for (String className in _classes) { |
| var cls = reflectClassByMangledName(className); |
| if (cls is JsClassMirror) { |
| result[cls.simpleName] = cls; |
| cls._owner = this; |
| } |
| } |
| return _cachedClasses = |
| new UnmodifiableMapView<Symbol, ClassMirror>(result); |
| } |
| |
| InstanceMirror setField(Symbol fieldName, Object arg) { |
| String name = n(fieldName); |
| if (name.endsWith('=')) throw new ArgumentError(''); |
| var mirror = functions[s('$name=')]; |
| if (mirror == null) mirror = variables[fieldName]; |
| if (mirror == null) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, setterSymbol(fieldName), [arg], null); |
| } |
| mirror._setField(this, arg); |
| return reflect(arg); |
| } |
| |
| InstanceMirror getField(Symbol fieldName) { |
| JsMirror mirror = members[fieldName]; |
| if (mirror == null) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, fieldName, [], null); |
| } |
| return reflect(mirror._getField(this)); |
| } |
| |
| InstanceMirror invoke(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| throw new UnsupportedError('Named arguments are not implemented.'); |
| } |
| JsDeclarationMirror mirror = members[memberName]; |
| if (mirror == null) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError( |
| this, memberName, positionalArguments, namedArguments); |
| } |
| if (mirror is JsMethodMirror) { |
| JsMethodMirror method = mirror; |
| if (!method.canInvokeReflectively()) { |
| throwInvalidReflectionError(n(memberName)); |
| } |
| } |
| return reflect(mirror._invoke(positionalArguments, namedArguments)); |
| } |
| |
| _loadField(String name) { |
| // TODO(ahe): What about lazily initialized fields? See |
| // [JsClassMirror.getField]. |
| |
| // '$' (JS_CURRENT_ISOLATE()) stores state which is read directly, so we |
| // shouldn't use [_globalObject] here. |
| assert(JS('bool', '# in #', name, JS_CURRENT_ISOLATE())); |
| return JS('', '#[#]', JS_CURRENT_ISOLATE(), name); |
| } |
| |
| void _storeField(String name, Object arg) { |
| // '$' (JS_CURRENT_ISOLATE()) stores state which is stored directly, so we |
| // shouldn't use [_globalObject] here. |
| assert(JS('bool', '# in #', name, JS_CURRENT_ISOLATE())); |
| JS('void', '#[#] = #', JS_CURRENT_ISOLATE(), name, arg); |
| } |
| |
| List<JsMethodMirror> get _functionMirrors { |
| if (_cachedFunctionMirrors != null) return _cachedFunctionMirrors; |
| var result = new List<JsMethodMirror>(); |
| for (int i = 0; i < _functions.length; i++) { |
| String name = _functions[i]; |
| var jsFunction = JS('', '#[#]', _globalObject, name); |
| String unmangledName = mangledGlobalNames[name]; |
| if (unmangledName == null) { |
| // If there is no unmangledName, [jsFunction] is either a synthetic |
| // implementation detail, or something that is excluded |
| // by @MirrorsUsed. |
| continue; |
| } |
| bool isConstructor = unmangledName.startsWith('new '); |
| bool isStatic = !isConstructor; // Top-level functions are static, but |
| // constructors are not. |
| if (isConstructor) { |
| unmangledName = unmangledName.substring(4).replaceAll(r'$', '.'); |
| } |
| JsMethodMirror mirror = |
| new JsMethodMirror.fromUnmangledName( |
| unmangledName, jsFunction, isStatic, isConstructor); |
| result.add(mirror); |
| mirror._owner = this; |
| } |
| return _cachedFunctionMirrors = result; |
| } |
| |
| List<VariableMirror> get _fields { |
| if (_cachedFields != null) return _cachedFields; |
| var result = <VariableMirror>[]; |
| parseCompactFieldSpecification( |
| this, _compactFieldSpecification, true, result); |
| return _cachedFields = result; |
| } |
| |
| Map<Symbol, MethodMirror> get functions { |
| if (_cachedFunctions != null) return _cachedFunctions; |
| var result = new Map(); |
| for (JsMethodMirror mirror in _functionMirrors) { |
| if (!mirror.isConstructor) result[mirror.simpleName] = mirror; |
| } |
| return _cachedFunctions = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, MethodMirror> get getters { |
| if (_cachedGetters != null) return _cachedGetters; |
| var result = new Map(); |
| // TODO(ahe): Implement this. |
| return _cachedGetters = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, MethodMirror> get setters { |
| if (_cachedSetters != null) return _cachedSetters; |
| var result = new Map(); |
| // TODO(ahe): Implement this. |
| return _cachedSetters = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, VariableMirror> get variables { |
| if (_cachedVariables != null) return _cachedVariables; |
| var result = new Map(); |
| for (JsVariableMirror mirror in _fields) { |
| result[mirror.simpleName] = mirror; |
| } |
| return _cachedVariables = |
| new UnmodifiableMapView<Symbol, VariableMirror>(result); |
| } |
| |
| Map<Symbol, Mirror> get members { |
| if (_cachedMembers != null) return _cachedMembers; |
| Map<Symbol, Mirror> result = new Map.from(classes); |
| addToResult(Symbol key, Mirror value) { |
| result[key] = value; |
| } |
| functions.forEach(addToResult); |
| getters.forEach(addToResult); |
| setters.forEach(addToResult); |
| variables.forEach(addToResult); |
| return _cachedMembers = new UnmodifiableMapView<Symbol, Mirror>(result); |
| } |
| |
| List<InstanceMirror> get metadata { |
| if (_cachedMetadata != null) return _cachedMetadata; |
| preserveMetadata(); |
| return _cachedMetadata = |
| new UnmodifiableListView<InstanceMirror>(_metadata.map(reflect)); |
| } |
| |
| // TODO(ahe): Test this getter. |
| DeclarationMirror get owner => null; |
| } |
| |
| String n(Symbol symbol) => _symbol_dev.Symbol.getName(symbol); |
| |
| Symbol s(String name) { |
| if (name == null) return null; |
| return new _symbol_dev.Symbol.unvalidated(name); |
| } |
| |
| Symbol setterSymbol(Symbol symbol) => s("${n(symbol)}="); |
| |
| final JsMirrorSystem currentJsMirrorSystem = new JsMirrorSystem(); |
| |
| InstanceMirror reflect(Object reflectee) { |
| if (reflectee is Closure) { |
| return new JsClosureMirror(reflectee); |
| } else { |
| return new JsInstanceMirror(reflectee); |
| } |
| } |
| |
| ClassMirror reflectType(Type key) { |
| return reflectClassByMangledName(getMangledTypeName(key)); |
| } |
| |
| ClassMirror reflectClassByMangledName(String mangledName) { |
| String unmangledName = mangledGlobalNames[mangledName]; |
| if (unmangledName == null) unmangledName = mangledName; |
| return reflectClassByName(s(unmangledName), mangledName); |
| } |
| |
| var classMirrors; |
| |
| ClassMirror reflectClassByName(Symbol symbol, String mangledName) { |
| if (classMirrors == null) classMirrors = JsCache.allocate(); |
| var mirror = JsCache.fetch(classMirrors, mangledName); |
| if (mirror != null) return mirror; |
| disableTreeShaking(); |
| int typeArgIndex = mangledName.indexOf("<"); |
| if (typeArgIndex != -1) { |
| mirror = new JsTypeBoundClassMirror( |
| reflectClassByMangledName(mangledName.substring(0, typeArgIndex)), |
| // Remove the angle brackets enclosing the type arguments. |
| mangledName.substring(typeArgIndex + 1, mangledName.length - 1)); |
| JsCache.update(classMirrors, mangledName, mirror); |
| return mirror; |
| } |
| var constructorOrInterceptor = |
| Primitives.getConstructorOrInterceptor(mangledName); |
| if (constructorOrInterceptor == null) { |
| int index = JS('int|Null', 'init.functionAliases[#]', mangledName); |
| if (index != null) { |
| mirror = new JsTypedefMirror( |
| symbol, mangledName, JS('=Object', 'init.metadata[#]', index)); |
| JsCache.update(classMirrors, mangledName, mirror); |
| return mirror; |
| } |
| // Probably an intercepted class. |
| // TODO(ahe): How to handle intercepted classes? |
| throw new UnsupportedError('Cannot find class for: ${n(symbol)}'); |
| } |
| var constructor = (constructorOrInterceptor is Interceptor) |
| ? JS('', '#.constructor', constructorOrInterceptor) |
| : constructorOrInterceptor; |
| var descriptor = JS('', '#["@"]', constructor); |
| var fields; |
| var fieldsMetadata; |
| if (descriptor == null) { |
| // This is a native class, or an intercepted class. |
| // TODO(ahe): Preserve descriptor for such classes. |
| } else { |
| fields = JS('', '#[""]', descriptor); |
| if (fields is List) { |
| fieldsMetadata = fields.getRange(1, fields.length).toList(); |
| fields = fields[0]; |
| } |
| if (fields is! String) { |
| // TODO(ahe): This is CSP mode. Find a way to determine the |
| // fields of this class. |
| fields = ''; |
| } |
| } |
| |
| var superclassName = fields.split(';')[0]; |
| var mixins = superclassName.split('+'); |
| if (mixins.length > 1 && mangledGlobalNames[mangledName] == null) { |
| mirror = reflectMixinApplication(mixins, mangledName); |
| } else { |
| mirror = new JsClassMirror( |
| symbol, mangledName, constructorOrInterceptor, fields, fieldsMetadata); |
| } |
| |
| JsCache.update(classMirrors, mangledName, mirror); |
| return mirror; |
| } |
| |
| int counter = 0; |
| |
| ClassMirror reflectMixinApplication(mixinNames, String mangledName) { |
| disableTreeShaking(); |
| var mixins = []; |
| for (String mangledName in mixinNames) { |
| mixins.add(reflectClassByMangledName(mangledName)); |
| } |
| var it = mixins.iterator; |
| it.moveNext(); |
| var superclass = it.current; |
| while (it.moveNext()) { |
| superclass = new JsMixinApplication(superclass, it.current, mangledName); |
| } |
| return superclass; |
| } |
| |
| class JsMixinApplication extends JsTypeMirror with JsObjectMirror |
| implements ClassMirror { |
| final ClassMirror superclass; |
| final ClassMirror mixin; |
| Symbol _cachedSimpleName; |
| |
| JsMixinApplication(ClassMirror superclass, ClassMirror mixin, |
| String mangledName) |
| : this.superclass = superclass, |
| this.mixin = mixin, |
| super(s(mangledName)); |
| |
| String get _prettyName => 'ClassMirror'; |
| |
| Symbol get simpleName { |
| if (_cachedSimpleName != null) return _cachedSimpleName; |
| String superName = n(superclass.qualifiedName); |
| return _cachedSimpleName = (superName.contains(' with ')) |
| ? s('$superName, ${n(mixin.qualifiedName)}') |
| : s('$superName with ${n(mixin.qualifiedName)}'); |
| } |
| |
| Symbol get qualifiedName => simpleName; |
| |
| Map<Symbol, Mirror> get members => mixin.members; |
| |
| Map<Symbol, MethodMirror> get methods => mixin.methods; |
| |
| Map<Symbol, MethodMirror> get getters => mixin.getters; |
| |
| Map<Symbol, MethodMirror> get setters => mixin.setters; |
| |
| Map<Symbol, VariableMirror> get variables => mixin.variables; |
| |
| InstanceMirror invoke( |
| Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol,dynamic> namedArguments]) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, memberName, |
| positionalArguments, namedArguments); |
| } |
| |
| InstanceMirror getField(Symbol fieldName) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, fieldName, null, null); |
| } |
| |
| InstanceMirror setField(Symbol fieldName, Object arg) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, setterSymbol(fieldName), [arg], null); |
| } |
| |
| List<ClassMirror> get superinterfaces => [mixin]; |
| |
| Map<Symbol, MethodMirror> get constructors => mixin.constructors; |
| |
| InstanceMirror newInstance( |
| Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol,dynamic> namedArguments]) { |
| throw new UnsupportedError( |
| "Can't instantiate mixin application '${n(qualifiedName)}'"); |
| } |
| |
| Future<InstanceMirror> newInstanceAsync( |
| Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return new Future<InstanceMirror>( |
| () => this.newInstance( |
| constructorName, positionalArguments, namedArguments)); |
| } |
| |
| bool get isOriginalDeclaration => true; |
| |
| ClassMirror get originalDeclaration => this; |
| |
| // TODO(ahe): Implement this. |
| List<TypeVariableMirror> get typeVariables { |
| throw new UnimplementedError(); |
| } |
| |
| List<TypeMirror> get typeArguments => new List(); |
| } |
| |
| abstract class JsObjectMirror implements ObjectMirror { |
| Future<InstanceMirror> setFieldAsync(Symbol fieldName, Object value) { |
| return new Future<InstanceMirror>(() => this.setField(fieldName, value)); |
| } |
| |
| Future<InstanceMirror> getFieldAsync(Symbol fieldName) { |
| return new Future<InstanceMirror>(() => this.getField(fieldName)); |
| } |
| |
| Future<InstanceMirror> invokeAsync(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return new Future<InstanceMirror>( |
| () => this.invoke(memberName, positionalArguments, namedArguments)); |
| } |
| } |
| |
| class JsInstanceMirror extends JsObjectMirror implements InstanceMirror { |
| final reflectee; |
| |
| JsInstanceMirror(this.reflectee); |
| |
| bool get hasReflectee => true; |
| |
| ClassMirror get type => reflectType(reflectee.runtimeType); |
| |
| Future<InstanceMirror> invokeAsync(Symbol memberName, |
| List<Object> positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return |
| new Future<InstanceMirror>( |
| () => invoke(memberName, positionalArguments, namedArguments)); |
| } |
| |
| InstanceMirror invoke(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol,dynamic> namedArguments]) { |
| String name = n(memberName); |
| String reflectiveName; |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| var methodsWithOptionalArguments = |
| JS('=Object', '#.\$methodsWithOptionalArguments', reflectee); |
| String mangledName = |
| JS('String|Null', '#[#]', methodsWithOptionalArguments, '*$name'); |
| if (mangledName == null) { |
| // TODO(ahe): Invoke noSuchMethod. |
| throw new UnimplementedNoSuchMethodError( |
| 'Invoking noSuchMethod with named arguments not implemented'); |
| } |
| var defaultValueIndices = |
| JS('List|Null', '#[#].\$defaultValues', reflectee, mangledName); |
| var defaultValues = |
| defaultValueIndices.map((int i) => JS('', 'init.metadata[#]', i)) |
| .iterator; |
| var defaultArguments = new Map(); |
| reflectiveName = mangledNames[mangledName]; |
| var reflectiveNames = reflectiveName.split(':'); |
| int requiredPositionalArgumentCount = |
| int.parse(reflectiveNames.elementAt(1)); |
| positionalArguments = new List.from(positionalArguments); |
| // Check the number of positional arguments is valid. |
| if (requiredPositionalArgumentCount != positionalArguments.length) { |
| // TODO(ahe): Invoke noSuchMethod. |
| throw new UnimplementedNoSuchMethodError( |
| 'Invoking noSuchMethod with named arguments not implemented'); |
| } |
| for (String parameter in reflectiveNames.skip(3)) { |
| defaultValues.moveNext(); |
| defaultArguments[parameter] = defaultValues.current; |
| } |
| namedArguments.forEach((Symbol symbol, value) { |
| String parameter = n(symbol); |
| if (defaultArguments.containsKey(parameter)) { |
| defaultArguments[parameter] = value; |
| } else { |
| // Extraneous named argument. |
| // TODO(ahe): Invoke noSuchMethod. |
| throw new UnimplementedNoSuchMethodError( |
| 'Invoking noSuchMethod with named arguments not implemented'); |
| } |
| }); |
| positionalArguments.addAll(defaultArguments.values); |
| } else { |
| reflectiveName = |
| JS('String', '# + ":" + # + ":0"', name, positionalArguments.length); |
| } |
| // We can safely pass positionalArguments to _invoke as it will wrap it in |
| // a JSArray if needed. |
| return _invoke(memberName, JSInvocationMirror.METHOD, reflectiveName, |
| positionalArguments); |
| } |
| |
| InstanceMirror _invoke(Symbol name, |
| int type, |
| String reflectiveName, |
| List arguments) { |
| String cacheName = Primitives.mirrorInvokeCacheName; |
| var cache = JS('', r'#.constructor[#]', reflectee, cacheName); |
| if (cache == null) { |
| cache = JsCache.allocate(); |
| JS('void', r'#.constructor[#] = #', reflectee, cacheName, cache); |
| } |
| var cacheEntry = JsCache.fetch(cache, reflectiveName); |
| var result; |
| Invocation invocation; |
| if (cacheEntry == null) { |
| disableTreeShaking(); |
| String mangledName = reflectiveNames[reflectiveName]; |
| List<String> argumentNames = const []; |
| if (type == JSInvocationMirror.METHOD) { |
| // Note: [argumentNames] are not what the user actually provided, it is |
| // always all the named paramters. |
| argumentNames = reflectiveName.split(':').skip(3).toList(); |
| } |
| |
| // TODO(ahe): We don't need to create an invocation mirror here. The |
| // logic from JSInvocationMirror.getCachedInvocation could easily be |
| // inlined here. |
| invocation = createUnmangledInvocationMirror( |
| name, mangledName, type, arguments, argumentNames); |
| |
| cacheEntry = |
| JSInvocationMirror.getCachedInvocation(invocation, reflectee); |
| JsCache.update(cache, reflectiveName, cacheEntry); |
| } |
| if (cacheEntry.isNoSuchMethod) { |
| if (invocation == null) { |
| String mangledName = reflectiveNames[reflectiveName]; |
| // TODO(ahe): Get the argument names. |
| List<String> argumentNames = []; |
| invocation = createUnmangledInvocationMirror( |
| name, mangledName, type, arguments, argumentNames); |
| } |
| return reflect(cacheEntry.invokeOn(reflectee, invocation)); |
| } else { |
| return reflect(cacheEntry.invokeOn(reflectee, arguments)); |
| } |
| } |
| |
| InstanceMirror setField(Symbol fieldName, Object arg) { |
| String reflectiveName = '${n(fieldName)}='; |
| _invoke( |
| s(reflectiveName), JSInvocationMirror.SETTER, reflectiveName, [arg]); |
| return reflect(arg); |
| } |
| |
| InstanceMirror getField(Symbol fieldName) { |
| String reflectiveName = n(fieldName); |
| return _invoke(fieldName, JSInvocationMirror.GETTER, reflectiveName, []); |
| } |
| |
| delegate(Invocation invocation) { |
| return JSInvocationMirror.invokeFromMirror(invocation, reflectee); |
| } |
| |
| operator ==(other) { |
| return other is JsInstanceMirror && |
| identical(reflectee, other.reflectee); |
| } |
| |
| int get hashCode { |
| // If the reflectee is a built-in type, use the base-level hashCode to |
| // preserve the illusion that, e.g. doubles, with the same value are |
| // identical. Otherwise, use the primitive identity hash to maintain |
| // correctness even if a user-defined hashCode returns different values for |
| // successive invocations. |
| var h = ((JS('bool', 'typeof # != "object"', reflectee)) || |
| (reflectee == null)) |
| ? reflectee.hashCode |
| : Primitives.objectHashCode(reflectee); |
| // Avoid hash collisions with the reflectee. This constant is in Smi range |
| // and happens to be the inner padding from RFC 2104. |
| return h ^ 0x36363636; |
| } |
| |
| String toString() => 'InstanceMirror on ${Error.safeToString(reflectee)}'; |
| |
| // TODO(ahe): Remove this method from the API. |
| MirrorSystem get mirrors => currentJsMirrorSystem; |
| } |
| |
| /** |
| * ClassMirror for generic classes where the type parameters are bound. |
| * |
| * [typeArguments] will return a list of the type arguments, in constrast |
| * to JsCLassMirror that returns an empty list since it represents original |
| * declarations and classes that are not generic. |
| */ |
| class JsTypeBoundClassMirror implements ClassMirror { |
| final JsClassMirror _class; |
| |
| /** |
| * When instantiated this field will hold a string representing the list of |
| * type arguments for the class, i.e. what is inside the outermost angle |
| * brackets. Then, when get typeArguments is called the first time, the string |
| * is parsed into the actual list of TypeMirrors, and the field is overridden |
| * with this value. |
| */ |
| var _typeArgs; |
| |
| JsTypeBoundClassMirror(this._class, this._typeArgs); |
| |
| List<TypeVariableMirror> get typeVariables => _class.typeVariables; |
| |
| List<TypeMirror> get typeArguments { |
| if (_typeArgs is! String) return _typeArgs; |
| List result = new List(); |
| if (_typeArgs.indexOf('<') == -1) { |
| for (String s in _typeArgs.split(',')) { |
| result.add(reflectClassByMangledName(s.trim())); |
| } |
| } else { |
| int level = 0; |
| StringBuffer currentTypeArg = new StringBuffer(); |
| |
| addCurrentTypeArg() { |
| var classMirror = reflectClassByMangledName(currentTypeArg.toString()); |
| result.add(classMirror); |
| currentTypeArg.clear(); |
| } |
| |
| for (int i = 0; i < _typeArgs.length; i++) { |
| var character = _typeArgs[i]; |
| if (character == ' ') { |
| continue; |
| } else if (character == '<') { |
| currentTypeArg.write(character); |
| level++; |
| } else if (character == '>') { |
| currentTypeArg.write(character); |
| level--; |
| } else if (character == ',') { |
| if (level > 0) { |
| currentTypeArg.write(character); |
| } else { |
| addCurrentTypeArg(); |
| } |
| } else { |
| currentTypeArg.write(character); |
| } |
| } |
| addCurrentTypeArg(); |
| } |
| return _typeArgs = new UnmodifiableListView(result); |
| } |
| |
| Map<Symbol, MethodMirror> get constructors => _class.constructors; |
| |
| Map<Symbol, MethodMirror> get methods => _class.methods; |
| |
| Map<Symbol, MethodMirror> get getters => _class.getters; |
| |
| Map<Symbol, MethodMirror> get setters => _class.setters; |
| |
| Map<Symbol, VariableMirror> get variables => _class.variables; |
| |
| Map<Symbol, Mirror> get members => _class.members; |
| |
| InstanceMirror setField(Symbol fieldName, Object arg) { |
| return _class.setField(fieldName, arg); |
| } |
| |
| InstanceMirror getField(Symbol fieldName) => _class.getField(fieldName); |
| |
| InstanceMirror newInstance(Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return _class.newInstance(constructorName, |
| positionalArguments, |
| namedArguments); |
| } |
| |
| Future<InstanceMirror> newInstanceAsync( |
| Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return _class.newInstanceAsync(constructorName, |
| positionalArguments, |
| namedArguments); |
| } |
| |
| JsLibraryMirror get owner => _class.owner; |
| |
| List<InstanceMirror> get metadata => _class.metadata; |
| |
| ClassMirror get superclass => _class.superclass; |
| |
| InstanceMirror invoke(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol,dynamic> namedArguments]) { |
| return _class.invoke(memberName, positionalArguments, namedArguments); |
| } |
| |
| bool get isOriginalDeclaration => false; |
| |
| ClassMirror get originalDeclaration => _class; |
| |
| List<ClassMirror> get superinterfaces => _class.superinterfaces; |
| |
| Future<InstanceMirror> getFieldAsync(Symbol fieldName) { |
| return _class.getFieldAsync(fieldName); |
| } |
| |
| bool get hasReflectedType => _class.hasReflectedType; |
| |
| Future<InstanceMirror> invokeAsync(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return _class.invokeAsync(memberName, positionalArguments, namedArguments); |
| } |
| |
| bool get isPrivate => _class.isPrivate; |
| |
| bool get isTopLevel => _class.isTopLevel; |
| |
| SourceLocation get location => _class.location; |
| |
| MirrorSystem get mirrors => _class.mirrors; |
| |
| Symbol get qualifiedName => _class.qualifiedName; |
| |
| Type get reflectedType => _class.reflectedType; |
| |
| Future<InstanceMirror> setFieldAsync(Symbol fieldName, Object value) { |
| return _class.setFieldAsync(fieldName, value); |
| } |
| |
| Symbol get simpleName => _class.simpleName; |
| } |
| |
| class JsClassMirror extends JsTypeMirror with JsObjectMirror |
| implements ClassMirror { |
| final String _mangledName; |
| final _jsConstructorOrInterceptor; |
| final String _fieldsDescriptor; |
| final List _fieldsMetadata; |
| final _jsConstructorCache = JsCache.allocate(); |
| List _metadata; |
| ClassMirror _superclass; |
| List<JsMethodMirror> _cachedMethods; |
| List<JsVariableMirror> _cachedFields; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedConstructors; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedMethodsMap; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedGetters; |
| UnmodifiableMapView<Symbol, MethodMirror> _cachedSetters; |
| UnmodifiableMapView<Symbol, VariableMirror> _cachedVariables; |
| UnmodifiableMapView<Symbol, Mirror> _cachedMembers; |
| UnmodifiableListView<InstanceMirror> _cachedMetadata; |
| UnmodifiableListView<ClassMirror> _cachedSuperinterfaces; |
| UnmodifiableListView<TypeVariableMirror> _cachedTypeVariables; |
| |
| // Set as side-effect of accessing JsLibraryMirror.classes. |
| JsLibraryMirror _owner; |
| |
| JsClassMirror(Symbol simpleName, |
| this._mangledName, |
| this._jsConstructorOrInterceptor, |
| this._fieldsDescriptor, |
| this._fieldsMetadata) |
| : super(simpleName); |
| |
| String get _prettyName => 'ClassMirror'; |
| |
| get _jsConstructor { |
| if (_jsConstructorOrInterceptor is Interceptor) { |
| return JS('', '#.constructor', _jsConstructorOrInterceptor); |
| } else { |
| return _jsConstructorOrInterceptor; |
| } |
| } |
| |
| Map<Symbol, MethodMirror> get constructors { |
| if (_cachedConstructors != null) return _cachedConstructors; |
| var result = new Map(); |
| for (JsMethodMirror method in _methods) { |
| if (method.isConstructor) { |
| result[method.simpleName] = method; |
| } |
| } |
| return _cachedConstructors = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| List<JsMethodMirror> get _methods { |
| if (_cachedMethods != null) return _cachedMethods; |
| var prototype = JS('', '#.prototype', _jsConstructor); |
| List<String> keys = extractKeys(prototype); |
| var result = <JsMethodMirror>[]; |
| for (String key in keys) { |
| if (isReflectiveDataInPrototype(key)) continue; |
| String simpleName = mangledNames[key]; |
| // [simpleName] can be null if [key] represents an implementation |
| // detail, for example, a bailout method, or runtime type support. |
| // It might also be null if the user has limited what is reified for |
| // reflection with metadata. |
| if (simpleName == null) continue; |
| var function = JS('', '#[#]', prototype, key); |
| var mirror = |
| new JsMethodMirror.fromUnmangledName( |
| simpleName, function, false, false); |
| result.add(mirror); |
| mirror._owner = this; |
| } |
| |
| keys = extractKeys(JS('', 'init.statics[#]', _mangledName)); |
| int length = keys.length; |
| for (int i = 0; i < length; i++) { |
| String mangledName = keys[i]; |
| if (isReflectiveDataInPrototype(mangledName)) continue; |
| String unmangledName = mangledName; |
| var jsFunction = JS('', '#[#]', owner._globalObject, mangledName); |
| |
| bool isConstructor = false; |
| if (i + 1 < length) { |
| String reflectionName = keys[i + 1]; |
| if (reflectionName.startsWith('+')) { |
| i++; |
| reflectionName = reflectionName.substring(1); |
| isConstructor = reflectionName.startsWith('new '); |
| if (isConstructor) { |
| reflectionName = reflectionName.substring(4).replaceAll(r'$', '.'); |
| } |
| } |
| unmangledName = reflectionName; |
| } |
| bool isStatic = !isConstructor; // Constructors are not static. |
| JsMethodMirror mirror = |
| new JsMethodMirror.fromUnmangledName( |
| unmangledName, jsFunction, isStatic, isConstructor); |
| result.add(mirror); |
| mirror._owner = this; |
| } |
| |
| return _cachedMethods = result; |
| } |
| |
| List<VariableMirror> get _fields { |
| if (_cachedFields != null) return _cachedFields; |
| var result = <VariableMirror>[]; |
| |
| var instanceFieldSpecfication = _fieldsDescriptor.split(';')[1]; |
| if (_fieldsMetadata != null) { |
| instanceFieldSpecfication = |
| [instanceFieldSpecfication]..addAll(_fieldsMetadata); |
| } |
| parseCompactFieldSpecification( |
| this, instanceFieldSpecfication, false, result); |
| |
| var staticDescriptor = JS('', 'init.statics[#]', _mangledName); |
| if (staticDescriptor != null) { |
| parseCompactFieldSpecification( |
| this, JS('', '#[""]', staticDescriptor), true, result); |
| } |
| _cachedFields = result; |
| return _cachedFields; |
| } |
| |
| Map<Symbol, MethodMirror> get methods { |
| if (_cachedMethodsMap != null) return _cachedMethodsMap; |
| var result = new Map(); |
| for (JsMethodMirror method in _methods) { |
| if (!method.isConstructor && !method.isGetter && !method.isSetter) { |
| result[method.simpleName] = method; |
| } |
| } |
| return _cachedMethodsMap = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, MethodMirror> get getters { |
| if (_cachedGetters != null) return _cachedGetters; |
| // TODO(ahe): This is a hack to remove getters corresponding to a field. |
| var fields = variables; |
| |
| var result = new Map(); |
| for (JsMethodMirror method in _methods) { |
| if (method.isGetter) { |
| |
| // TODO(ahe): This is a hack to remove getters corresponding to a field. |
| if (fields[method.simpleName] != null) continue; |
| |
| result[method.simpleName] = method; |
| } |
| } |
| return _cachedGetters = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, MethodMirror> get setters { |
| if (_cachedSetters != null) return _cachedSetters; |
| // TODO(ahe): This is a hack to remove setters corresponding to a field. |
| var fields = variables; |
| |
| var result = new Map(); |
| for (JsMethodMirror method in _methods) { |
| if (method.isSetter) { |
| |
| // TODO(ahe): This is a hack to remove setters corresponding to a field. |
| String name = n(method.simpleName); |
| name = name.substring(0, name.length - 1); // Remove '='. |
| if (fields[s(name)] != null) continue; |
| |
| result[method.simpleName] = method; |
| } |
| } |
| return _cachedSetters = |
| new UnmodifiableMapView<Symbol, MethodMirror>(result); |
| } |
| |
| Map<Symbol, VariableMirror> get variables { |
| if (_cachedVariables != null) return _cachedVariables; |
| var result = new Map(); |
| for (JsVariableMirror mirror in _fields) { |
| result[mirror.simpleName] = mirror; |
| } |
| return _cachedVariables = |
| new UnmodifiableMapView<Symbol, VariableMirror>(result); |
| } |
| |
| Map<Symbol, Mirror> get members { |
| if (_cachedMembers != null) return _cachedMembers; |
| Map<Symbol, Mirror> result = new Map.from(variables); |
| for (JsMethodMirror method in _methods) { |
| if (method.isSetter) { |
| String name = n(method.simpleName); |
| name = name.substring(0, name.length - 1); |
| // Filter-out setters corresponding to variables. |
| if (result[s(name)] is VariableMirror) continue; |
| } |
| // Constructors aren't 'members'. |
| if (method.isConstructor) continue; |
| // Use putIfAbsent to filter-out getters corresponding to variables. |
| result.putIfAbsent(method.simpleName, () => method); |
| } |
| return _cachedMembers = new UnmodifiableMapView<Symbol, Mirror>(result); |
| } |
| |
| InstanceMirror setField(Symbol fieldName, Object arg) { |
| JsVariableMirror mirror = variables[fieldName]; |
| if (mirror != null && mirror.isStatic && !mirror.isFinal) { |
| // '$' (JS_CURRENT_ISOLATE()) stores state which is stored directly, so |
| // we shouldn't use [JsLibraryMirror._globalObject] here. |
| String jsName = mirror._jsName; |
| if (!JS('bool', '# in #', jsName, JS_CURRENT_ISOLATE())) { |
| throw new RuntimeError('Cannot find "$jsName" in current isolate.'); |
| } |
| JS('void', '#[#] = #', JS_CURRENT_ISOLATE(), jsName, arg); |
| return reflect(arg); |
| } |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, setterSymbol(fieldName), [arg], null); |
| } |
| |
| InstanceMirror getField(Symbol fieldName) { |
| JsVariableMirror mirror = variables[fieldName]; |
| if (mirror != null && mirror.isStatic) { |
| String jsName = mirror._jsName; |
| // '$' (JS_CURRENT_ISOLATE()) stores state which is read directly, so |
| // we shouldn't use [JsLibraryMirror._globalObject] here. |
| if (!JS('bool', '# in #', jsName, JS_CURRENT_ISOLATE())) { |
| throw new RuntimeError('Cannot find "$jsName" in current isolate.'); |
| } |
| if (JS('bool', '# in init.lazies', jsName)) { |
| String getterName = JS('String', 'init.lazies[#]', jsName); |
| return reflect(JS('', '#[#]()', JS_CURRENT_ISOLATE(), getterName)); |
| } else { |
| return reflect(JS('', '#[#]', JS_CURRENT_ISOLATE(), jsName)); |
| } |
| } |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError(this, fieldName, null, null); |
| } |
| |
| InstanceMirror newInstance(Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| throw new UnsupportedError('Named arguments are not implemented.'); |
| } |
| JsMethodMirror mirror = |
| JsCache.fetch(_jsConstructorCache, n(constructorName)); |
| if (mirror == null) { |
| mirror = constructors.values.firstWhere( |
| (m) => m.constructorName == constructorName, |
| orElse: () { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError( |
| this, constructorName, positionalArguments, namedArguments); |
| }); |
| JsCache.update(_jsConstructorCache, n(constructorName), mirror); |
| } |
| return reflect(mirror._invoke(positionalArguments, namedArguments)); |
| } |
| |
| Future<InstanceMirror> newInstanceAsync( |
| Symbol constructorName, |
| List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| throw new UnsupportedError('Named arguments are not implemented.'); |
| } |
| return new Future<InstanceMirror>( |
| () => newInstance( |
| constructorName, positionalArguments, namedArguments)); |
| } |
| |
| JsLibraryMirror get owner { |
| if (_owner == null) { |
| if (_jsConstructorOrInterceptor is Interceptor) { |
| _owner = reflectType(Object).owner; |
| } else { |
| for (var list in JsMirrorSystem.librariesByName.values) { |
| for (JsLibraryMirror library in list) { |
| // This will set _owner field on all clasess as a side |
| // effect. This gives us a fast path to reflect on a |
| // class without parsing reflection data. |
| library.classes; |
| } |
| } |
| } |
| if (_owner == null) { |
| throw new StateError('Class "${n(simpleName)}" has no owner'); |
| } |
| } |
| return _owner; |
| } |
| |
| List<InstanceMirror> get metadata { |
| if (_cachedMetadata != null) return _cachedMetadata; |
| if (_metadata == null) { |
| _metadata = extractMetadata(JS('', '#.prototype', _jsConstructor)); |
| } |
| return _cachedMetadata = |
| new UnmodifiableListView<InstanceMirror>(_metadata.map(reflect)); |
| } |
| |
| ClassMirror get superclass { |
| if (_superclass == null) { |
| var superclassName = _fieldsDescriptor.split(';')[0]; |
| var mixins = superclassName.split('+'); |
| if (mixins.length > 1) { |
| if (mixins.length != 2) { |
| throw new RuntimeError('Strange mixin: $_fieldsDescriptor'); |
| } |
| _superclass = reflectClassByMangledName(mixins[0]); |
| } else { |
| // Use _superclass == this to represent class with no superclass (Object). |
| _superclass = (superclassName == '') |
| ? this : reflectClassByMangledName(superclassName); |
| } |
| } |
| return _superclass == this ? null : _superclass; |
| } |
| |
| InstanceMirror invoke(Symbol memberName, |
| List positionalArguments, |
| [Map<Symbol,dynamic> namedArguments]) { |
| // Mirror API gotcha: Calling [invoke] on a ClassMirror means invoke a |
| // static method. |
| |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| throw new UnsupportedError('Named arguments are not implemented.'); |
| } |
| JsMethodMirror mirror = methods[memberName]; |
| if (mirror == null || !mirror.isStatic) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError( |
| this, memberName, positionalArguments, namedArguments); |
| } |
| if (!mirror.canInvokeReflectively()) { |
| throwInvalidReflectionError(n(memberName)); |
| } |
| return reflect(mirror._invoke(positionalArguments, namedArguments)); |
| } |
| |
| bool get isOriginalDeclaration => true; |
| |
| ClassMirror get originalDeclaration => this; |
| |
| List<ClassMirror> get superinterfaces { |
| if (_cachedSuperinterfaces != null) return _cachedSuperinterfaces; |
| List<int> interfaces = JS('List|Null', 'init.interfaces[#]', _mangledName); |
| var result = const []; |
| if (interfaces != null) { |
| ClassMirror lookupType(int i) { |
| var type = JS('=Object', 'init.metadata[#]', i); |
| return typeMirrorFromRuntimeTypeRepresentation(type); |
| } |
| result = interfaces.map(lookupType).toList(); |
| } |
| return _cachedSuperinterfaces = |
| new UnmodifiableListView<ClassMirror>(result); |
| } |
| |
| List<TypeVariableMirror> get typeVariables { |
| if (_cachedTypeVariables != null) return _cachedTypeVariables; |
| List result = new List(); |
| List typeVars = |
| JS('JSExtendableArray|Null', '#.prototype["<>"]', _jsConstructor); |
| if (typeVars == null) return result; |
| for (int i = 0; i < typeVars.length; i += 2) { |
| TypeMirror upperBound = |
| typeMirrorFromRuntimeTypeRepresentation(JS('', 'init.metadata[#]', |
| typeVars[i+1])); |
| var typeMirror = |
| new JsTypeVariableMirror(s(typeVars[i]), upperBound, this); |
| result.add(typeMirror); |
| } |
| return _cachedTypeVariables = new UnmodifiableListView(result); |
| } |
| |
| List<TypeMirror> get typeArguments => new List(); |
| } |
| |
| class JsVariableMirror extends JsDeclarationMirror implements VariableMirror { |
| static final int REFLECTION_MARKER = 45; |
| |
| // TODO(ahe): The values in these fields are virtually untested. |
| final String _jsName; |
| final bool isFinal; |
| final bool isStatic; |
| final _metadataFunction; |
| final DeclarationMirror _owner; |
| List _metadata; |
| |
| JsVariableMirror(Symbol simpleName, |
| this._jsName, |
| this.isFinal, |
| this.isStatic, |
| this._metadataFunction, |
| this._owner) |
| : super(simpleName); |
| |
| factory JsVariableMirror.from(String descriptor, |
| metadataFunction, |
| JsDeclarationMirror owner, |
| bool isStatic) { |
| int length = descriptor.length; |
| var code = fieldCode(descriptor.codeUnitAt(length - 1)); |
| if (code == REFLECTION_MARKER) { |
| // If the field descriptor has a reflection marker, remove it by |
| // changing length and getting the real getter/setter code. The |
| // descriptor will be truncated below. |
| length--; |
| code = fieldCode(descriptor.codeUnitAt(length - 1)); |
| } else { |
| // The field is not available for reflection. |
| return null; |
| } |
| bool isFinal = false; |
| if (code == 0) return null; // Inherited field. |
| bool hasGetter = (code & 3) != 0; |
| bool hasSetter = (code >> 2) != 0; |
| isFinal = !hasSetter; |
| length--; |
| String jsName; |
| String accessorName = jsName = descriptor.substring(0, length); |
| int divider = descriptor.indexOf(':'); |
| if (divider > 0) { |
| accessorName = accessorName.substring(0, divider); |
| jsName = descriptor.substring(divider + 1); |
| } |
| var unmangledName; |
| if (isStatic) { |
| unmangledName = mangledGlobalNames[accessorName]; |
| } else { |
| String getterPrefix = JS_GET_NAME('GETTER_PREFIX'); |
| unmangledName = mangledNames['$getterPrefix$accessorName']; |
| } |
| if (unmangledName == null) unmangledName = accessorName; |
| if (!hasSetter) { |
| // TODO(ahe): This is a hack to handle checked setters in checked mode. |
| var setterName = s('$unmangledName='); |
| for (JsMethodMirror method in owner._methods) { |
| if (method.simpleName == setterName) { |
| isFinal = false; |
| break; |
| } |
| } |
| } |
| return new JsVariableMirror( |
| s(unmangledName), jsName, isFinal, isStatic, metadataFunction, owner); |
| } |
| |
| String get _prettyName => 'VariableMirror'; |
| |
| // TODO(ahe): Improve this information and test it. |
| TypeMirror get type => JsMirrorSystem._dynamicType; |
| |
| DeclarationMirror get owner => _owner; |
| |
| List<InstanceMirror> get metadata { |
| preserveMetadata(); |
| if (_metadata == null) { |
| _metadata = (_metadataFunction == null) |
| ? const [] : JS('', '#()', _metadataFunction); |
| } |
| return _metadata.map(reflect).toList(); |
| } |
| |
| static int fieldCode(int code) { |
| if (code == REFLECTION_MARKER) return code; |
| if (code >= 60 && code <= 64) return code - 59; |
| if (code >= 123 && code <= 126) return code - 117; |
| if (code >= 37 && code <= 43) return code - 27; |
| return 0; |
| } |
| |
| _getField(JsMirror receiver) => receiver._loadField(_jsName); |
| |
| void _setField(JsMirror receiver, Object arg) { |
| if (isFinal) { |
| throw new NoSuchMethodError(this, setterSymbol(simpleName), [arg], null); |
| } |
| receiver._storeField(_jsName, arg); |
| } |
| } |
| |
| class JsClosureMirror extends JsInstanceMirror implements ClosureMirror { |
| JsClosureMirror(reflectee) |
| : super(reflectee); |
| |
| MethodMirror get function { |
| String cacheName = Primitives.mirrorFunctionCacheName; |
| JsMethodMirror cachedFunction = |
| JS('JsMethodMirror|Null', r'#.constructor[#]', reflectee, cacheName); |
| if (cachedFunction != null) return cachedFunction; |
| disableTreeShaking(); |
| // TODO(ahe): What about optional parameters (named or not). |
| var extractCallName = JS('', r''' |
| function(reflectee) { |
| for (var property in reflectee) { |
| if ("call$" == property.substring(0, 5)) return property; |
| } |
| return null; |
| } |
| '''); |
| String callName = JS('String|Null', '#(#)', extractCallName, reflectee); |
| if (callName == null) { |
| throw new RuntimeError('Cannot find callName on "$reflectee"'); |
| } |
| int parameterCount = int.parse(callName.split(r'$')[1]); |
| if (reflectee is BoundClosure) { |
| var target = BoundClosure.targetOf(reflectee); |
| var self = BoundClosure.selfOf(reflectee); |
| var name = mangledNames[BoundClosure.nameOf(reflectee)]; |
| if (name == null) { |
| throwInvalidReflectionError(name); |
| } |
| cachedFunction = new JsMethodMirror.fromUnmangledName( |
| name, target, false, false); |
| } else { |
| bool isStatic = true; // TODO(ahe): Compute isStatic correctly. |
| var jsFunction = JS('', '#[#]', reflectee, callName); |
| cachedFunction = new JsMethodMirror( |
| s(callName), jsFunction, parameterCount, |
| false, false, isStatic, false, false); |
| } |
| JS('void', r'#.constructor[#] = #', reflectee, cacheName, cachedFunction); |
| return cachedFunction; |
| } |
| |
| InstanceMirror apply(List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return reflect( |
| Function.apply(reflectee, positionalArguments, namedArguments)); |
| } |
| |
| Future<InstanceMirror> applyAsync(List positionalArguments, |
| [Map<Symbol, dynamic> namedArguments]) { |
| return new Future<InstanceMirror>( |
| () => apply(positionalArguments, namedArguments)); |
| } |
| |
| String toString() => "ClosureMirror on '${Error.safeToString(reflectee)}'"; |
| |
| // TODO(ahe): Implement these. |
| String get source => throw new UnimplementedError(); |
| Future<InstanceMirror> findInContext(Symbol name) |
| => throw new UnimplementedError(); |
| } |
| |
| class JsMethodMirror extends JsDeclarationMirror implements MethodMirror { |
| final _jsFunction; |
| final int _parameterCount; |
| final bool isGetter; |
| final bool isSetter; |
| final bool isStatic; |
| final bool isConstructor; |
| final bool isOperator; |
| DeclarationMirror _owner; |
| List _metadata; |
| var _returnType; |
| UnmodifiableListView<ParameterMirror> _parameters; |
| |
| JsMethodMirror(Symbol simpleName, |
| this._jsFunction, |
| this._parameterCount, |
| this.isGetter, |
| this.isSetter, |
| this.isStatic, |
| this.isConstructor, |
| this.isOperator) |
| : super(simpleName); |
| |
| factory JsMethodMirror.fromUnmangledName(String name, |
| jsFunction, |
| bool isStatic, |
| bool isConstructor) { |
| List<String> info = name.split(':'); |
| name = info[0]; |
| bool isOperator = isOperatorName(name); |
| bool isSetter = !isOperator && name.endsWith('='); |
| int requiredParameterCount = 0; |
| int optionalParameterCount = 0; |
| bool isGetter = false; |
| if (info.length == 1) { |
| if (isSetter) { |
| requiredParameterCount = 1; |
| } else { |
| isGetter = true; |
| requiredParameterCount = 0; |
| } |
| } else { |
| requiredParameterCount = int.parse(info[1]); |
| optionalParameterCount = int.parse(info[2]); |
| } |
| return new JsMethodMirror( |
| s(name), jsFunction, requiredParameterCount + optionalParameterCount, |
| isGetter, isSetter, isStatic, isConstructor, isOperator); |
| } |
| |
| String get _prettyName => 'MethodMirror'; |
| |
| List<ParameterMirror> get parameters { |
| if (_parameters != null) return _parameters; |
| metadata; // Compute _parameters as a side-effect of extracting metadata. |
| return _parameters; |
| } |
| |
| bool canInvokeReflectively() { |
| return hasReflectableProperty(_jsFunction); |
| } |
| |
| DeclarationMirror get owner => _owner; |
| |
| TypeMirror get returnType { |
| metadata; // Compute _returnType as a side-effect of extracting metadata. |
| return typeMirrorFromRuntimeTypeRepresentation(_returnType); |
| } |
| |
| List<InstanceMirror> get metadata { |
| if (_metadata == null) { |
| var raw = extractMetadata(_jsFunction); |
| var formals = new List(_parameterCount); |
| if (!raw.isEmpty) { |
| _returnType = raw[0]; |
| int parameterLength = 1 + _parameterCount * 2; |
| int formalsCount = 0; |
| for (int i = 1; i < parameterLength; i += 2) { |
| var name = raw[i]; |
| var type = raw[i + 1]; |
| formals[formalsCount++] = new JsParameterMirror(name, this, type); |
| } |
| raw = raw.sublist(parameterLength); |
| } else { |
| for (int i = 0; i < _parameterCount; i++) { |
| formals[i] = new JsParameterMirror('argument$i', this, null); |
| } |
| } |
| _parameters = new UnmodifiableListView<ParameterMirror>(formals); |
| _metadata = new UnmodifiableListView(raw.map(reflect)); |
| } |
| return _metadata; |
| } |
| |
| Symbol get constructorName { |
| // TODO(ahe): I believe it is more appropriate to throw an exception or |
| // return null. |
| if (!isConstructor) return const Symbol(''); |
| String name = n(simpleName); |
| int index = name.indexOf('.'); |
| if (index == -1) return const Symbol(''); |
| return s(name.substring(index + 1)); |
| } |
| |
| _invoke(List positionalArguments, Map<Symbol, dynamic> namedArguments) { |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| throw new UnsupportedError('Named arguments are not implemented.'); |
| } |
| if (!isStatic && !isConstructor) { |
| throw new RuntimeError('Cannot invoke instance method without receiver.'); |
| } |
| if (_parameterCount != positionalArguments.length || _jsFunction == null) { |
| // TODO(ahe): What receiver to use? |
| throw new NoSuchMethodError( |
| owner, simpleName, positionalArguments, namedArguments); |
| } |
| // Using JS_CURRENT_ISOLATE() ('$') here is actually correct, although |
| // _jsFunction may not be a property of '$', most static functions do not |
| // care who their receiver is. But to lazy getters, it is important that |
| // 'this' is '$'. |
| return JS('', r'#.apply(#, #)', _jsFunction, JS_CURRENT_ISOLATE(), |
| new List.from(positionalArguments)); |
| } |
| |
| _getField(JsMirror receiver) { |
| if (isGetter) { |
| return _invoke([], null); |
| } else { |
| // TODO(ahe): Closurize method. |
| throw new UnimplementedError('getField on $receiver'); |
| } |
| } |
| |
| _setField(JsMirror receiver, Object arg) { |
| if (isSetter) { |
| return _invoke([arg], null); |
| } else { |
| throw new NoSuchMethodError(this, setterSymbol(simpleName), [], null); |
| } |
| } |
| |
| // Abstract methods are tree-shaken away. |
| bool get isAbstract => false; |
| |
| // TODO(ahe): Test this. |
| bool get isRegularMethod => !isGetter && !isSetter && !isConstructor; |
| |
| // TODO(ahe): Implement these. |
| bool get isConstConstructor => throw new UnimplementedError(); |
| bool get isGenerativeConstructor => throw new UnimplementedError(); |
| bool get isRedirectingConstructor => throw new UnimplementedError(); |
| bool get isFactoryConstructor => throw new UnimplementedError(); |
| } |
| |
| class JsParameterMirror extends JsDeclarationMirror implements ParameterMirror { |
| final DeclarationMirror owner; |
| // A JS object representing the type. |
| final _type; |
| |
| JsParameterMirror(String unmangledName, this.owner, this._type) |
| : super(s(unmangledName)); |
| |
| String get _prettyName => 'ParameterMirror'; |
| |
| TypeMirror get type => typeMirrorFromRuntimeTypeRepresentation(_type); |
| |
| // Only true for static fields, never for a parameter. |
| bool get isStatic => false; |
| |
| // TODO(ahe): Implement this. |
| bool get isFinal => false; |
| |
| // TODO(ahe): Implement this. |
| bool get isOptional => false; |
| |
| // TODO(ahe): Implement this. |
| bool get isNamed => false; |
| |
| // TODO(ahe): Implement this. |
| bool get hasDefaultValue => false; |
| |
| // TODO(ahe): Implement this. |
| get defaultValue => null; |
| |
| // TODO(ahe): Implement this. |
| List<InstanceMirror> get metadata => throw new UnimplementedError(); |
| } |
| |
| class JsTypedefMirror extends JsDeclarationMirror implements TypedefMirror { |
| final String _mangledName; |
| final JsFunctionTypeMirror referent; |
| |
| JsTypedefMirror(Symbol simpleName, this._mangledName, _typeData) |
| : referent = new JsFunctionTypeMirror(_typeData), |
| super(simpleName); |
| |
| JsFunctionTypeMirror get value => referent; |
| |
| String get _prettyName => 'TypedefMirror'; |
| |
| // TODO(zarah): This method doesn't belong here, since TypedefMirror shouldn't |
| // be a subtype of ClassMirror. |
| ClassMirror get originalDeclaration => this; |
| } |
| |
| class JsFunctionTypeMirror implements FunctionTypeMirror { |
| final _typeData; |
| String _cachedToString; |
| TypeMirror _cachedReturnType; |
| UnmodifiableListView<ParameterMirror> _cachedParameters; |
| |
| JsFunctionTypeMirror(this._typeData); |
| |
| bool get _hasReturnType => JS('bool', '"ret" in #', _typeData); |
| get _returnType => JS('', '#.ret', _typeData); |
| |
| bool get _isVoid => JS('bool', '!!#.void', _typeData); |
| |
| bool get _hasArguments => JS('bool', '"args" in #', _typeData); |
| List get _arguments => JS('JSExtendableArray', '#.args', _typeData); |
| |
| bool get _hasOptionalArguments => JS('bool', '"opt" in #', _typeData); |
| List get _optionalArguments => JS('JSExtendableArray', '#.opt', _typeData); |
| |
| bool get _hasNamedArguments => JS('bool', '"named" in #', _typeData); |
| get _namedArguments => JS('=Object', '#.named', _typeData); |
| |
| TypeMirror get returnType { |
| if (_cachedReturnType != null) return _cachedReturnType; |
| if (_isVoid) return _cachedReturnType = JsMirrorSystem._voidType; |
| if (!_hasReturnType) return _cachedReturnType = JsMirrorSystem._dynamicType; |
| return _cachedReturnType = |
| typeMirrorFromRuntimeTypeRepresentation(_returnType); |
| } |
| |
| List<ParameterMirror> get parameters { |
| if (_cachedParameters != null) return _cachedParameters; |
| List result = []; |
| int parameterCount = 0; |
| if (_hasArguments) { |
| for (var type in _arguments) { |
| result.add( |
| new JsParameterMirror('argument${parameterCount++}', this, type)); |
| } |
| } |
| if (_hasOptionalArguments) { |
| for (var type in _optionalArguments) { |
| result.add( |
| new JsParameterMirror('argument${parameterCount++}', this, type)); |
| } |
| } |
| if (_hasNamedArguments) { |
| for (var name in extractKeys(_namedArguments)) { |
| var type = JS('', '#[#]', _namedArguments, name); |
| result.add(new JsParameterMirror(name, this, type)); |
| } |
| } |
| return _cachedParameters = new UnmodifiableListView<ParameterMirror>( |
| result); |
| } |
| |
| String toString() { |
| if (_cachedToString != null) return _cachedToString; |
| var s = "FunctionTypeMirror on '("; |
| var sep = ''; |
| if (_hasArguments) { |
| for (var argument in _arguments) { |
| s += sep; |
| s += runtimeTypeToString(argument); |
| sep = ', '; |
| } |
| } |
| if (_hasOptionalArguments) { |
| s += '$sep['; |
| sep = ''; |
| for (var argument in _optionalArguments) { |
| s += sep; |
| s += runtimeTypeToString(argument); |
| sep = ', '; |
| } |
| s += ']'; |
| } |
| if (_hasNamedArguments) { |
| s += '$sep{'; |
| sep = ''; |
| for (var name in extractKeys(_namedArguments)) { |
| s += sep; |
| s += '$name: '; |
| s += runtimeTypeToString(JS('', '#[#]', _namedArguments, name)); |
| sep = ', '; |
| } |
| s += '}'; |
| } |
| s += ') -> '; |
| if (_isVoid) { |
| s += 'void'; |
| } else if (_hasReturnType) { |
| s += runtimeTypeToString(_returnType); |
| } else { |
| s += 'dynamic'; |
| } |
| return _cachedToString = "$s'"; |
| } |
| } |
| |
| TypeMirror typeMirrorFromRuntimeTypeRepresentation(type) { |
| if (type == null) return JsMirrorSystem._dynamicType; |
| String representation = runtimeTypeToString(type); |
| if (representation == null) return reflectClass(Function); |
| return reflectClass(createRuntimeType(representation)); |
| } |
| |
| Symbol computeQualifiedName(DeclarationMirror owner, Symbol simpleName) { |
| if (owner == null) return simpleName; |
| String ownerName = n(owner.qualifiedName); |
| if (ownerName == '') return simpleName; |
| return s('$ownerName.${n(simpleName)}'); |
| } |
| |
| List extractMetadata(victim) { |
| preserveMetadata(); |
| var metadataFunction = JS('', '#["@"]', victim); |
| if (metadataFunction != null) return JS('', '#()', metadataFunction); |
| if (JS('String', 'typeof #', victim) != 'function') return const []; |
| String source = JS('String', 'Function.prototype.toString.call(#)', victim); |
| int index = source.lastIndexOf(new RegExp('"[0-9,]*";?[ \n\r]*}')); |
| if (index == -1) return const []; |
| index++; |
| int endQuote = source.indexOf('"', index); |
| return source.substring(index, endQuote).split(',').map(int.parse).map( |
| (int i) => JS('', 'init.metadata[#]', i)).toList(); |
| } |
| |
| List<JsVariableMirror> parseCompactFieldSpecification( |
| JsDeclarationMirror owner, |
| fieldSpecification, |
| bool isStatic, |
| List<Mirror> result) { |
| List fieldsMetadata = null; |
| List<String> fieldNames; |
| if (fieldSpecification is List) { |
| fieldNames = splitFields(fieldSpecification[0], ','); |
| fieldsMetadata = fieldSpecification.sublist(1); |
| } else if (fieldSpecification is String) { |
| fieldNames = splitFields(fieldSpecification, ','); |
| } else { |
| fieldNames = []; |
| } |
| int fieldNumber = 0; |
| for (String field in fieldNames) { |
| var metadata; |
| if (fieldsMetadata != null) { |
| metadata = fieldsMetadata[fieldNumber++]; |
| } |
| var mirror = new JsVariableMirror.from(field, metadata, owner, isStatic); |
| if (mirror != null) { |
| result.add(mirror); |
| } |
| } |
| } |
| |
| /// Similar to [String.split], but returns an empty list if [string] is empty. |
| List<String> splitFields(String string, Pattern pattern) { |
| if (string.isEmpty) return <String>[]; |
| return string.split(pattern); |
| } |
| |
| bool isOperatorName(String name) { |
| switch (name) { |
| case '==': |
| case '[]': |
| case '*': |
| case '/': |
| case '%': |
| case '~/': |
| case '+': |
| case '<<': |
| case '>>': |
| case '>=': |
| case '>': |
| case '<=': |
| case '<': |
| case '&': |
| case '^': |
| case '|': |
| case '-': |
| case 'unary-': |
| case '[]=': |
| case '~': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /// Returns true if the key represent ancillary reflection data, that is, not a |
| /// method. |
| bool isReflectiveDataInPrototype(String key) { |
| if (key == '' || key == METHODS_WITH_OPTIONAL_ARGUMENTS) return true; |
| String firstChar = key[0]; |
| return firstChar == '*' || firstChar == '+'; |
| } |
| |
| // Copied from package "unmodifiable_collection". |
| // TODO(ahe): Lobby to get it added to dart:collection. |
| class UnmodifiableMapView<K, V> implements Map<K, V> { |
| Map<K, V> _source; |
| UnmodifiableMapView(Map<K, V> source) : _source = source; |
| |
| static void _throw() { |
| throw new UnsupportedError("Cannot modify an unmodifiable Map"); |
| } |
| |
| int get length => _source.length; |
| |
| bool get isEmpty => _source.isEmpty; |
| |
| bool get isNotEmpty => _source.isNotEmpty; |
| |
| V operator [](K key) => _source[key]; |
| |
| bool containsKey(K key) => _source.containsKey(key); |
| |
| bool containsValue(V value) => _source.containsValue(value); |
| |
| void forEach(void f(K key, V value)) => _source.forEach(f); |
| |
| Iterable<K> get keys => _source.keys; |
| |
| Iterable<V> get values => _source.values; |
| |
| |
| void operator []=(K key, V value) => _throw(); |
| |
| V putIfAbsent(K key, V ifAbsent()) { _throw(); } |
| |
| void addAll(Map<K, V> other) => _throw(); |
| |
| V remove(K key) { _throw(); } |
| |
| void clear() => _throw(); |
| } |
| |
| // TODO(ahe): Remove this class and call noSuchMethod instead. |
| class UnimplementedNoSuchMethodError extends Error |
| implements NoSuchMethodError { |
| final String _message; |
| |
| UnimplementedNoSuchMethodError(this._message); |
| |
| String toString() => "Unsupported operation: $_message"; |
| } |