blob: 270f571d7040e874ce2f40781357830a928991ea [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:_runtime' as dart;
import 'dart:_foreign_helper' show JS;
import 'dart:_internal' as _internal show Symbol;
import 'dart:_js_helper' show PrivateSymbol;
String getName(Symbol symbol) {
if (symbol is PrivateSymbol) {
return PrivateSymbol.getName(symbol);
} else {
return _internal.Symbol.getName(symbol as _internal.Symbol);
}
}
Symbol getSymbol(name, library) =>
throw new UnimplementedError("MirrorSystem.getSymbol unimplemented");
final currentJsMirrorSystem = new JsMirrorSystem();
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 = dart.unwrapType(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;
}
typedef T _Lazy<T>();
dynamic _getESSymbol(Symbol symbol) => PrivateSymbol.getNativeSymbol(symbol);
dynamic _getMember(Symbol symbol) {
var privateSymbol = _getESSymbol(symbol);
if (privateSymbol != null) {
return privateSymbol;
}
var name = getName(symbol);
// TODO(jacobr): this code is duplicated in code_generator.dart
switch (name) {
case '[]':
name = '_get';
break;
case '[]=':
name = '_set';
break;
case 'unary-':
name = '_negate';
break;
case 'constructor':
case 'prototype':
name = '_$name';
break;
}
return name;
}
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 PrivateSymbol(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 PrivateSymbol(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;
}
dynamic _runtimeType(obj) => dart.wrapType(dart.getReifiedType(obj));
_unimplemented(Type t, Invocation i) {
throw new UnimplementedError('$t.${getName(i.memberName)} unimplemented');
}
dynamic _toJsMap(Map<Symbol, dynamic> map) {
if (map == null) return null;
var obj = JS('', '{}');
map.forEach((Symbol key, value) {
JS('', '#[#] = #', obj, getName(key), value);
});
return obj;
}
class JsMirrorSystem implements MirrorSystem {
get libraries => const {};
noSuchMethod(Invocation i) {
_unimplemented(this.runtimeType, i);
}
}
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;
}
InstanceMirror getField(Symbol symbol) {
var name = _getMember(symbol);
var field = dart.dloadMirror(reflectee, name);
return reflect(field);
}
InstanceMirror setField(Symbol symbol, Object value) {
var name = _getMember(symbol);
dart.dputMirror(reflectee, name, value);
return reflect(value);
}
InstanceMirror invoke(Symbol symbol, List<dynamic> args,
[Map<Symbol, dynamic> namedArgs]) {
var name = _getMember(symbol);
var result =
dart.callMethod(reflectee, name, null, args, _toJsMap(namedArgs), name);
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]) {
var result = dart.dcall(reflectee, args, _toJsMap(namedArgs));
return reflect(result);
}
}
// For generic classes, mirrors uses the same representation, [ClassMirror],
// for the instantiated and uninstantiated type. Somewhat awkwardly, most APIs
// (e.g., [newInstance]) treat the uninstantiated type as if instantiated
// with all dynamic. The representation below is correspondingly a bit wonky.
// For an uninstantiated generic class, [_cls] is the instantiated type (with
// dynamic) and [_raw] is null. For an instantiated generic class, [_cls] is
// the instantiated type (with the corresponding type parameters), and [_raw]
// is the generic factory.
class JsClassMirror extends JsMirror implements ClassMirror {
final Type _cls;
final Symbol simpleName;
// Generic class factory for instantiated types.
final dynamic _raw;
ClassMirror _originalDeclaration;
// 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 = dart.unwrapType(_cls);
// Only get metadata directly embedded on this class, not its
// superclasses.
Function fn = JS(
'',
'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 = dart.unwrapType(_cls);
var constructors = _toDartMap(dart.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 = dart.fnType(dart.unwrapType(_cls), []);
var symbol = new Symbol(name);
_declarations[symbol] =
new JsMethodMirror._constructor(this, symbol, ft);
}
var fields = _toDartMap(dart.getFields(unwrapped));
fields.forEach((symbol, t) {
_declarations[symbol] = new JsVariableMirror._fromField(symbol, t);
});
var methods = _toDartMap(dart.getMethods(unwrapped));
methods.forEach((symbol, ft) {
var name = getName(symbol);
_declarations[symbol] =
new JsMethodMirror._instanceMethod(this, symbol, ft);
});
getterType(type) {
if (JS('bool', '# instanceof Array', type)) {
var array = JS('', '#.slice()', type);
type = JS('', '#[0]', array);
JS('', '#[0] = #', array, dart.fnType(type, []));
return array;
} else {
return dart.fnType(type, []);
}
}
var getters = _toDartMap(dart.getGetters(unwrapped));
getters.forEach((symbol, type) {
_declarations[symbol] =
new JsMethodMirror._instanceMethod(this, symbol, getterType(type));
});
setterType(type) {
if (JS('bool', '# instanceof Array', type)) {
var array = JS('', '#.slice()', type);
type = JS('', '#[0]', array);
JS('', '#[0] = #', array, dart.fnType(dart.void_, [type]));
return array;
} else {
return dart.fnType(dart.void_, [type]);
}
}
var setters = _toDartMap(dart.getSetters(unwrapped));
setters.forEach((symbol, type) {
var name = getName(symbol) + '=';
// Create a separate symbol for the setter.
symbol = new PrivateSymbol(name, _getESSymbol(symbol));
_declarations[symbol] =
new JsMethodMirror._instanceMethod(this, symbol, setterType(type));
});
var staticFields = _toDartMap(dart.getStaticFields(unwrapped));
staticFields.forEach((symbol, t) {
_declarations[symbol] = new JsVariableMirror._fromField(symbol, t);
});
var statics = _toDartMap(dart.getStaticMethods(unwrapped));
statics.forEach((symbol, ft) {
_declarations[symbol] =
new JsMethodMirror._staticMethod(this, symbol, ft);
});
var staticGetters = _toDartMap(dart.getStaticGetters(unwrapped));
staticGetters.forEach((symbol, type) {
_declarations[symbol] =
new JsMethodMirror._staticMethod(this, symbol, getterType(type));
});
var staticSetters = _toDartMap(dart.getStaticSetters(unwrapped));
staticSetters.forEach((symbol, type) {
_declarations[symbol] =
new JsMethodMirror._staticMethod(this, symbol, setterType(type));
});
_declarations =
new Map<Symbol, DeclarationMirror>.unmodifiable(_declarations);
}
return _declarations;
}
JsClassMirror._(Type cls, {bool instantiated: true})
: _cls = cls,
_raw = instantiated ? dart.getGenericClass(dart.unwrapType(cls)) : null,
simpleName = new Symbol(JS('String', '#.name', dart.unwrapType(cls))) {
var typeArgs = dart.getGenericArgs(dart.unwrapType(_cls));
if (typeArgs == null) {
_typeArguments = const [];
} else {
_typeArguments = new List.unmodifiable(
typeArgs.map((t) => reflectType(dart.wrapType(t))));
}
}
InstanceMirror newInstance(Symbol constructorName, List args,
[Map<Symbol, dynamic> namedArgs]) {
// TODO(vsm): Support named arguments.
var name = getName(constructorName);
assert(namedArgs == null || namedArgs.isEmpty);
// Default constructors are mapped to new.
if (name == '') name = 'new';
var cls = dart.unwrapType(_cls);
var ctr = JS('', '#.#', cls, name);
// Only generative Dart constructors are wired up as real JS constructors.
var instance = JS('bool', '#.prototype == #.prototype', cls, ctr)
// Generative
? JS('', 'new #(...#)', ctr, args)
// Factory
: JS('', '#(...#)', ctr, 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('', '#[#]', dart.unwrapType(_cls), name));
}
InstanceMirror setField(Symbol symbol, Object value) {
var name = getName(symbol);
JS('', '#[#] = #', dart.unwrapType(_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('', '#.#(...#)', dart.unwrapType(_cls), name, args);
return reflect(result);
}
List<ClassMirror> get superinterfaces {
_Lazy<List<Type>> interfaceThunk =
JS('', '#[dart.implements]', dart.unwrapType(_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 {
if (_raw == null) {
return this;
}
if (_originalDeclaration != null) {
return _originalDeclaration;
}
_originalDeclaration = new JsClassMirror._(
dart.wrapType(JS('', '#()', _raw)),
instantiated: false);
return _originalDeclaration;
}
ClassMirror get superclass {
if (_cls == Object) {
return null;
} else {
return reflectType(
dart.wrapType(JS('Type', '#.__proto__', dart.unwrapType(_cls))));
}
}
ClassMirror get mixin {
if (_mixin != null) {
return _mixin;
}
var mixin = dart.getMixin(dart.unwrapType(_cls));
if (mixin == null) {
// If there is no mixin, return this mirror per API.
_mixin = this;
return _mixin;
}
_mixin = reflectType(dart.wrapType(mixin));
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;
final bool isFinal;
// TODO(vsm): Refactor this out.
Symbol get simpleName => _symbol;
// TODO(vsm): Fix this
final bool isStatic = false;
JsVariableMirror._(Symbol symbol, Type t, List annotations,
{this.isFinal: false})
: _symbol = symbol,
_name = getName(symbol),
type = reflectType(t),
metadata = new List<InstanceMirror>.unmodifiable(
annotations?.map(reflect) ?? []);
JsVariableMirror._fromField(Symbol symbol, fieldInfo)
: this._(symbol, dart.wrapType(JS('', '#.type', fieldInfo)),
JS('', '#.metadata', fieldInfo),
isFinal: JS('bool', '#.isFinal', fieldInfo));
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!
ftype = dart.getFunctionTypeMirror(ftype);
// 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(''), dart.wrapType(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(''), dart.wrapType(type), metadata);
params[i + args.length] = param;
}
_params = new List.unmodifiable(params);
}
String toString() => "MethodMirror on '$_name'";
}