blob: 856009661428ad901676bb45967c5ab67bbcd2e4 [file] [log] [blame]
// Copyright (c) 2015, 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:mirrors';
import 'dart:_foreign_helper' show JS;
import 'dart:_internal' as _internal;
String getName(Symbol symbol) =>
_internal.Symbol.getName(symbol as _internal.Symbol);
Symbol getSymbol(name, library) =>
throw new UnimplementedError("MirrorSystem.getSymbol unimplemented");
final currentJsMirrorSystem = throw new UnimplementedError(
"MirrorSystem.currentJsMirrorSystem unimplemented");
final _typeMirror = JS('', 'Symbol("_typeMirror")');
InstanceMirror reflect(reflectee) {
// TODO(vsm): Consider caching the mirror here. Unlike the type below,
// reflectee may be a primitive - i.e., we can't just add an expando.
if (reflectee is Function) {
return new JsClosureMirror._(reflectee);
} else {
return new JsInstanceMirror._(reflectee);
}
}
TypeMirror reflectType(Type key) {
var unwrapped = _unwrap(key);
var property = JS('', 'Object.getOwnPropertyDescriptor(#, #)', unwrapped, _typeMirror);
if (property != null) {
return JS('', '#.value', property);
}
// TODO(vsm): Might not be a class.
var mirror = new JsClassMirror._(key);
JS('', '#[#] = #', unwrapped, _typeMirror, mirror);
return mirror;
}
final dynamic _dart = JS('', 'dart');
dynamic _dload(obj, String name) {
return JS('', '#.dload(#, #)', _dart, obj, name);
}
void _dput(obj, String name, val) {
JS('', '#.dput(#, #, #)', _dart, obj, name, val);
}
dynamic _dcall(obj, List args) {
return JS('', '#.dcall(#, ...#)', _dart, obj, args);
}
dynamic _dsend(obj, String name, List args) {
return JS('', '#.dsend(#, #, ...#)', _dart, obj, name, args);
}
dynamic _getGenericClass(obj) {
return JS('', '#.getGenericClass(#)', _dart, obj);
}
dynamic _getGenericArgs(obj) {
return JS('', '#.getGenericArgs(#)', _dart, obj);
}
dynamic _defaultConstructorType(type) {
return JS('', '#.definiteFunctionType(#, [])', _dart, type);
}
dynamic _getMixins(type) {
return JS('', '#.getMixins(#, [])', _dart, type);
}
typedef T _Lazy<T>();
dynamic _getESSymbol(Symbol symbol) =>
_internal.Symbol.getNativeSymbol(symbol as _internal.Symbol);
dynamic _getMember(Symbol symbol) {
var privateSymbol = _getESSymbol(symbol);
if (privateSymbol != null) {
return privateSymbol;
}
return getName(symbol);
}
String _getNameForESSymbol(member) {
// Convert private JS symbol "Symbol(_foo)" to string "_foo".
assert(JS('bool', 'typeof # == "symbol"', member));
var str = member.toString();
assert(str.startsWith('Symbol(') && str.endsWith(')'));
return str.substring(7, str.length - 1);
}
Symbol _getSymbolForESSymbol(member) {
var name = _getNameForESSymbol(member);
return new _internal.Symbol.es6(name, member);
}
// The [member] must be either a string (public) or an ES6 symbol (private).
Symbol _getSymbolForMember(member) {
if (member is String) {
return new Symbol(member);
} else {
var name = _getNameForESSymbol(member);
return new _internal.Symbol.es6(name, member);
}
}
Map<Symbol, dynamic> _toDartMap(data) {
if (data == null) return {};
var map = new Map<Symbol, dynamic>();
// Note: we recorded a map from fields/methods to their type and metadata.
// The key is a string name for public members but an ES6 symbol for private
// ones. That's works nicely for dynamic operations, but dart:mirrors expects
// Dart symbols, so we convert here.
var publicMembers = JS('', 'Object.getOwnPropertyNames(#)', data);
for (var member in publicMembers) {
var symbol = new Symbol(member);
map[symbol] = JS('', '#[#]', data, member);
}
var privateMembers = JS('', 'Object.getOwnPropertySymbols(#)', data);
for (var member in privateMembers) {
var symbol = _getSymbolForESSymbol(member);
map[symbol] = JS('', '#[#]', data, member);
}
return map;
}
Map<Symbol, dynamic> _getConstructors(obj) {
List sig = JS('', '#.getConstructorSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getFields(obj) {
List sig = JS('', '#.getFieldSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getMethods(obj) {
List sig = JS('', '#.getMethodSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getGetters(obj) {
List sig = JS('', '#.getGetterSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getSetters(obj) {
List sig = JS('', '#.getSetterSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getStaticFields(obj) {
List sig = JS('', '#.getStaticFieldSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getStatics(obj) {
List sig = JS('', '#.getStaticSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getStaticGetters(obj) {
List sig = JS('', '#.getStaticGetterSig(#)', _dart, obj);
return _toDartMap(sig);
}
Map<Symbol, dynamic> _getStaticSetters(obj) {
List sig = JS('', '#.getStaticSetterSig(#)', _dart, obj);
return _toDartMap(sig);
}
// TODO(vsm): These methods need to validate whether we really have a
// WrappedType or a raw type that should be wrapped (as opposed to a
// function).
dynamic _unwrap(obj) => JS('', '#.unwrapType(#)', _dart, obj);
dynamic _wrap(obj) => JS('', '#.wrapType(#)', _dart, obj);
dynamic _runtimeType(obj) =>
_wrap(JS('', '#.getReifiedType(#)', _dart, obj));
_unimplemented(Type t, Invocation i) {
throw new UnimplementedError('$t.${getName(i.memberName)} unimplemented');
}
dynamic _toJsMap(Map<Symbol, dynamic> map) {
var obj = JS('', '{}');
map.forEach((Symbol key, value) {
JS('', '#[#] = #', obj, getName(key), value);
});
return obj;
}
class JsMirror implements Mirror {
noSuchMethod(Invocation i) {
_unimplemented(this.runtimeType, i);
}
}
class JsCombinatorMirror extends JsMirror implements CombinatorMirror {
}
class JsDeclarationMirror extends JsMirror implements DeclarationMirror {
}
class JsIsolateMirror extends JsMirror implements IsolateMirror {
}
class JsLibraryDependencyMirror extends JsMirror implements LibraryDependencyMirror {
}
class JsObjectMirror extends JsMirror implements ObjectMirror {
}
class JsInstanceMirror extends JsObjectMirror implements InstanceMirror {
// Reflected object
final reflectee;
bool get hasReflectee => true;
ClassMirror get type {
// The spec guarantees that `null` is the singleton instance of the `Null`
// class.
if (reflectee == null) return reflectClass(Null);
return reflectType(_runtimeType(reflectee));
}
JsInstanceMirror._(this.reflectee);
bool operator==(Object other) {
return (other is JsInstanceMirror) && identical(reflectee, other.reflectee);
}
int get hashCode {
// Avoid hash collisions with the reflectee. This constant is in Smi range
// and happens to be the inner padding from RFC 2104.
return identityHashCode(reflectee) ^ 0x36363636;
}
// Returns a String for public members or an ES6 symbol for private members.
_getAccessor(dynamic reflectee, Symbol symbol, [List<dynamic> args,
Map<Symbol, dynamic> namedArgs]) {
return _getMember(symbol);
}
InstanceMirror getField(Symbol symbol) {
var name = _getAccessor(reflectee, symbol);
var field = _dload(reflectee, name);
return reflect(field);
}
InstanceMirror setField(Symbol symbol, Object value) {
var name = _getAccessor(reflectee, symbol);
_dput(reflectee, name, value);
return reflect(value);
}
InstanceMirror invoke(Symbol symbol, List<dynamic> args,
[Map<Symbol, dynamic> namedArgs]) {
var name = _getAccessor(reflectee, symbol, args, namedArgs);
if (namedArgs != null) {
args = new List.from(args);
args.add(_toJsMap(namedArgs));
}
var result = _dsend(reflectee, name, args);
return reflect(result);
}
String toString() => "InstanceMirror on '$reflectee'";
}
class JsClosureMirror extends JsInstanceMirror implements ClosureMirror {
JsClosureMirror._(reflectee) : super._(reflectee);
InstanceMirror apply(List<dynamic> args,
[Map<Symbol, dynamic> namedArgs]) {
if (namedArgs != null) {
args = new List.from(args);
args.add(_toJsMap(namedArgs));
}
var result = _dcall(reflectee, args);
return reflect(result);
}
}
class JsClassMirror extends JsMirror implements ClassMirror {
final Type _cls;
final Symbol simpleName;
// Generic class factory
final dynamic _raw;
// TODO(vsm): Do this properly
ClassMirror _mixin = null;
List<TypeMirror> _typeArguments;
List<InstanceMirror> _metadata;
Map<Symbol, DeclarationMirror> _declarations;
List<InstanceMirror> get metadata {
if (_metadata == null) {
// Load metadata.
var unwrapped = _unwrap(_cls);
// Only get metadata directly embedded on this class, not its
// superclasses.
var fn = JS('Function',
'Object.hasOwnProperty.call(#, dart.metadata) ? #[dart.metadata] : null',
unwrapped, unwrapped);
_metadata = (fn == null)
? const <InstanceMirror>[]
: new List<InstanceMirror>.unmodifiable(
fn().map((i) => reflect(i)));
}
return _metadata;
}
Map<Symbol, DeclarationMirror> get declarations {
if (_declarations == null) {
// Load declarations.
// TODO(vsm): This is only populating the default constructor right now.
_declarations = new Map<Symbol, DeclarationMirror>();
var unwrapped = _unwrap(_cls);
var constructors = _getConstructors(unwrapped);
constructors.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._constructor(this, symbol, ft);
});
if (constructors.isEmpty) {
// Add a default
var name = 'new';
var ft = _defaultConstructorType(_unwrap(_cls));
var symbol = new Symbol(name);
_declarations[symbol] = new JsMethodMirror._constructor(this, symbol, ft);
}
var fields = _getFields(unwrapped);
fields.forEach((symbol, t) {
var metadata = [];
if (t is List) {
metadata = t.skip(1).toList();
t = t[0];
}
_declarations[symbol] = new JsVariableMirror._(symbol, _wrap(t), metadata);
});
var methods = _getMethods(unwrapped);
methods.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._instanceMethod(this, symbol, ft);
});
var getters = _getGetters(unwrapped);
getters.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._instanceMethod(this, symbol, ft);
});
var setters = _getSetters(unwrapped);
setters.forEach((symbol, ft) {
var name = getName(symbol) + '=';
// Create a separate symbol for the setter.
symbol = new _internal.Symbol.es6(name, _getESSymbol(symbol));
_declarations[symbol] = new JsMethodMirror._instanceMethod(this, symbol, ft);
});
var staticFields = _getStaticFields(unwrapped);
staticFields.forEach((symbol, t) {
var name = getName(symbol);
var metadata = [];
if (t is List) {
metadata = t.skip(1).toList();
t = t[0];
}
_declarations[symbol] = new JsVariableMirror._(symbol, _wrap(t), metadata);
});
var statics = _getStatics(unwrapped);
statics.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._staticMethod(this, symbol, ft);
});
var staticGetters = _getStaticGetters(unwrapped);
staticGetters.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._staticMethod(this, symbol, ft);
});
var staticSetters = _getStaticSetters(unwrapped);
staticSetters.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] = new JsMethodMirror._staticMethod(this, symbol, ft);
});
_declarations = new Map<Symbol, DeclarationMirror>.unmodifiable(_declarations);
}
return _declarations;
}
JsClassMirror._(Type cls)
: _cls = cls,
_raw = _getGenericClass(_unwrap(cls)),
simpleName = new Symbol(JS('String', '#.name', _unwrap(cls))) {
var typeArgs = _getGenericArgs(_unwrap(cls));
if (typeArgs == null) {
_typeArguments = const[];
} else {
_typeArguments = new List.unmodifiable(typeArgs.map((t) => reflectType(_wrap(t))));
}
}
InstanceMirror newInstance(Symbol constructorName, List args,
[Map<Symbol, dynamic> namedArgs]) {
// TODO(vsm): Support factory constructors and named arguments.
var name = getName(constructorName);
assert(namedArgs == null || namedArgs.isEmpty);
var instance = (name == 'new' || name == '')
? JS('', 'new #(...#)', _unwrap(_cls), args)
: JS('', 'new (#.#)(...#)', _unwrap(_cls), name, args);
return reflect(instance);
}
// TODO(vsm): Need to check for NSM, types on accessors below. Unlike the
// InstanceMirror case, there is no dynamic helper to delegate to - we never
// need a dload, etc. on a static.
InstanceMirror getField(Symbol symbol) {
var name = getName(symbol);
return reflect(JS('', '#[#]', _unwrap(_cls), name));
}
InstanceMirror setField(Symbol symbol, Object value) {
var name = getName(symbol);
JS('', '#[#] = #', _unwrap(_cls), name, value);
return reflect(value);
}
InstanceMirror invoke(Symbol symbol, List<dynamic> args,
[Map<Symbol, dynamic> namedArgs]) {
var name = getName(symbol);
if (namedArgs != null) {
args = new List.from(args);
args.add(_toJsMap(namedArgs));
}
var result = JS('', '#.#(...#)', _unwrap(_cls), name, args);
return reflect(result);
}
List<ClassMirror> get superinterfaces {
_Lazy<List<Type>> interfaceThunk = JS('', '#[dart.implements]', _unwrap(_cls));
if (interfaceThunk == null) {
return [];
} else {
List<Type> interfaces = interfaceThunk();
return interfaces.map((t) => reflectType(t)).toList();
}
}
bool get hasReflectedType => true;
Type get reflectedType { return _cls; }
bool get isOriginalDeclaration => _raw == null;
List<TypeMirror> get typeArguments => _typeArguments;
TypeMirror get originalDeclaration {
// TODO(vsm): Handle generic case. How should we represent an original
// declaration for a generic class?
if (_raw == null) {
return this;
}
throw new UnimplementedError("ClassMirror.originalDeclaration unimplemented");
}
ClassMirror get superclass {
if (_cls == Object) {
return null;
} else {
return reflectType(_wrap(JS('Type', '#.__proto__', _unwrap(_cls))));
}
}
ClassMirror get mixin {
if (_mixin != null) {
return _mixin;
}
var mixins = _getMixins(_unwrap(_cls));
if (mixins == null || mixins.isEmpty) {
// If there is no mixin, return this mirror per API.
_mixin = this;
return _mixin;
}
if (mixins.length > 1) {
throw new UnsupportedError("ClassMirror.mixin not yet supported for "
"classes ($_cls) with multiple mixins");
}
_mixin = reflectType(_wrap(mixins[0]));
return _mixin;
}
String toString() => "ClassMirror on '$_cls'";
}
class JsVariableMirror extends JsMirror implements VariableMirror {
final Symbol _symbol;
final String _name;
final TypeMirror type;
final List<InstanceMirror> metadata;
// TODO(vsm): Refactor this out.
Symbol get simpleName => _symbol;
// TODO(vsm): Fix this
final bool isStatic = false;
final bool isFinal = false;
JsVariableMirror._(Symbol symbol, Type t, List annotations)
: _symbol = symbol,
_name = getName(symbol),
type = reflectType(t),
metadata = new List<InstanceMirror>.unmodifiable(
annotations.map((a) => reflect(a)));
String toString() => "VariableMirror on '$_name'";
}
class JsParameterMirror extends JsVariableMirror implements ParameterMirror {
JsParameterMirror._(Symbol member, Type t, List annotations)
: super._(member, t, annotations);
String toString() => "ParameterMirror on '$_name'";
}
class JsMethodMirror extends JsMirror implements MethodMirror {
final Symbol _symbol;
final String _name;
List<ParameterMirror> _params;
List<InstanceMirror> _metadata;
final bool isConstructor;
final bool isStatic;
// TODO(vsm): Fix this
final bool isFinal = false;
bool get isSetter => _name.endsWith('=');
bool get isPrivate => _name.startsWith('_');
// TODO(vsm): Refactor this out.
Symbol get simpleName => _symbol;
JsMethodMirror._constructor(JsClassMirror cls, Symbol symbol, ftype)
: _symbol = symbol, _name = getName(symbol), isConstructor = true, isStatic = false {
_createParameterMirrorList(ftype);
}
JsMethodMirror._instanceMethod(JsClassMirror cls, Symbol symbol, ftype)
: _symbol = symbol, _name = getName(symbol), isConstructor = false, isStatic = false {
_createParameterMirrorList(ftype);
}
JsMethodMirror._staticMethod(JsClassMirror cls, Symbol symbol, ftype)
: _symbol = symbol, _name = getName(symbol), isConstructor = false, isStatic = true {
_createParameterMirrorList(ftype);
}
// TODO(vsm): Support named constructors.
Symbol get constructorName => isConstructor ? _symbol : null;
List<ParameterMirror> get parameters => _params;
List<InstanceMirror> get metadata => _metadata;
void _createParameterMirrorList(ftype) {
if (ftype == null) {
// TODO(vsm): No explicit constructor. Verify this.
_params = const [];
_metadata = const [];
return;
}
// TODO(vsm): Why does generic function type trigger true for List?
if (ftype is! Function && ftype is List) {
// Record metadata
_metadata = new List<InstanceMirror>.unmodifiable(
ftype.skip(1).map((a) => reflect(a)));
ftype = ftype[0];
} else {
_metadata = const [];
}
// TODO(vsm): Handle generic function types properly. Or deprecate mirrors
// before we need to!
if (JS('bool', 'typeof(#) == "function"', ftype)) {
// Instantiate the generic version.
// TODO(vsm): Can't use arguments.length on arrow function.
ftype = JS('', '#.apply(null, #)', ftype, [dynamic, dynamic, dynamic]);
}
// TODO(vsm): Add named args.
List args = ftype.args;
List opts = ftype.optionals;
var params = new List<ParameterMirror>(args.length + opts.length);
for (var i = 0; i < args.length; ++i) {
var type = args[i];
var metadata = ftype.metadata[i];
// TODO(vsm): Recover the param name.
var param = new JsParameterMirror._(new Symbol(''), _wrap(type), metadata);
params[i] = param;
}
for (var i = 0; i < opts.length; ++i) {
var type = opts[i];
var metadata = ftype.metadata[args.length + i];
// TODO(vsm): Recover the param name.
var param = new JsParameterMirror._(new Symbol(''), _wrap(type), metadata);
params[i + args.length] = param;
}
_params = new List.unmodifiable(params);
}
String toString() => "MethodMirror on '$_name'";
}