blob: 629db3861208593319cb1cd1953a06c4d2f4e64d [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.
/// This library defines the operations that define and manipulate Dart
/// classes. Included in this are:
/// - Generics
/// - Class metadata
/// - Extension methods
///
// TODO(leafp): Consider splitting some of this out.
part of dart._runtime;
///
/// Returns a new type that mixes members from base and all mixins.
///
/// Each mixin applies in sequence, with further to the right ones overriding
/// previous entries.
///
/// For each mixin, we only take its own properties, not anything from its
/// superclass (prototype).
///
mixin(base, @rest mixins) => JS(
'',
'''(() => {
// Create an initializer for the mixin, so when derived constructor calls
// super, we can correctly initialize base and mixins.
// Create a class that will hold all of the mixin methods.
class Mixin extends $base {}
// Copy each mixin's methods, with later ones overwriting earlier entries.
for (let m of $mixins) {
$copyProperties(Mixin.prototype, m.prototype);
}
// Initializer method: run mixin initializers, then the base.
Mixin.prototype.new = function(...args) {
// Run mixin initializers. They cannot have arguments.
// Run them backwards so most-derived mixin is initialized first.
for (let i = $mixins.length - 1; i >= 0; i--) {
$mixins[i].prototype.new.call(this);
}
// Run base initializer.
$base.prototype.new.apply(this, args);
};
// Set the signature of the Mixin class to be the composition
// of the signatures of the mixins.
$setSignature(Mixin, {
methods: () => {
let s = {};
for (let m of $mixins) {
$copyProperties(s, m[$_methodSig]);
}
return s;
}
});
// Save mixins for reflection
Mixin[$_mixins] = $mixins;
return Mixin;
})()''');
/// The Symbol for storing type arguments on a specialized generic type.
final _mixins = JS('', 'Symbol("mixins")');
getMixins(clazz) => JS('', 'Object.hasOwnProperty.call(#, #) ? #[#] : null', clazz, _mixins, clazz, _mixins);
@JSExportName('implements')
final _implements = JS('', 'Symbol("implements")');
getImplements(clazz) => JS('', 'Object.hasOwnProperty.call(#, #) ? #[#] : null', clazz, _implements, clazz, _implements);
/// The Symbol for storing type arguments on a specialized generic type.
final _typeArguments = JS('', 'Symbol("typeArguments")');
final _originalDeclaration = JS('', 'Symbol("originalDeclaration")');
/// Wrap a generic class builder function with future flattening.
flattenFutures(builder) => JS(
'',
'''(() => {
function flatten(T) {
if (!T) return $builder($dynamic);
let futureClass = $getGenericClass($Future);
//TODO(leafp): This only handles the direct flattening case.
// It would probably be good to at least search up the class
// hierarchy. If we keep doing flattening long term, we may
// want to implement the full future flattening per spec.
if ($getGenericClass(T) == futureClass) {
let args = $getGenericArgs(T);
if (args) return $builder(args[0]);
}
return $builder(T);
}
return flatten;
})()''');
/// Memoize a generic type constructor function.
generic(typeConstructor, [setBaseClass]) => JS(
'',
'''(() => {
let length = $typeConstructor.length;
if (length < 1) {
$throwInternalError('must have at least one generic type argument');
}
let resultMap = new Map();
function makeGenericType(...args) {
if (args.length != length && args.length != 0) {
$throwInternalError('requires ' + length + ' or 0 type arguments');
}
while (args.length < length) args.push($dynamic);
let value = resultMap;
for (let i = 0; i < length; i++) {
let arg = args[i];
if (arg == null) {
$throwInternalError('type arguments should not be null: '
+ $typeConstructor);
}
let map = value;
value = map.get(arg);
if (value === void 0) {
if (i + 1 == length) {
value = $typeConstructor.apply(null, args);
// Save the type constructor and arguments for reflection.
if (value) {
value[$_typeArguments] = args;
value[$_originalDeclaration] = makeGenericType;
}
map.set(arg, value);
if ($setBaseClass) $setBaseClass(value);
} else {
value = new Map();
map.set(arg, value);
}
}
}
return value;
}
makeGenericType[$_genericTypeCtor] = $typeConstructor;
return makeGenericType;
})()''');
getGenericClass(type) =>
JS('', '$safeGetOwnProperty($type, $_originalDeclaration)');
getGenericArgs(type) => JS('', '$safeGetOwnProperty($type, $_typeArguments)');
// TODO(vsm): Collapse into one expando.
final _constructorSig = JS('', 'Symbol("sigCtor")');
final _methodSig = JS('', 'Symbol("sigMethod")');
final _fieldSig = JS('', 'Symbol("sigField")');
final _getterSig= JS('', 'Symbol("sigGetter")');
final _setterSig= JS('', 'Symbol("sigSetter")');
final _staticSig = JS('', 'Symbol("sigStaticMethod")');
final _staticFieldSig = JS('', 'Symbol("sigStaticField")');
final _staticGetterSig= JS('', 'Symbol("sigStaticGetter")');
final _staticSetterSig= JS('', 'Symbol("sigStaticSetter")');
final _genericTypeCtor = JS('', 'Symbol("genericType")');
// TODO(vsm): Collapse this as well - just provide a dart map to mirrors code.
// These are queried by mirrors code.
getConstructorSig(value) => JS('', '#[#]', value, _constructorSig);
getMethodSig(value) => JS('', '#[#]', value, _methodSig);
getFieldSig(value) => JS('', '#[#]', value, _fieldSig);
getGetterSig(value) => JS('', '#[#]', value, _getterSig);
getSetterSig(value) => JS('', '#[#]', value, _setterSig);
getStaticSig(value) => JS('', '#[#]', value, _staticSig);
getStaticFieldSig(value) => JS('', '#[#]', value, _staticFieldSig);
getStaticGetterSig(value) => JS('', '#[#]', value, _staticGetterSig);
getStaticSetterSig(value) => JS('', '#[#]', value, _staticSetterSig);
getGenericTypeCtor(value) => JS('', '#[#]', value, _genericTypeCtor);
/// Get the type of a method from an object using the stored signature
getMethodType(obj, name) => JS(
'',
'''(() => {
let type = $obj == null ? $Object : $obj.__proto__.constructor;
return $getMethodTypeFromType(type, $name);
})()''');
/// Get the type of a method from a type using the stored signature
getMethodTypeFromType(type, name) => JS(
'',
'''(() => {
let sigObj = $type[$_methodSig];
if (sigObj === void 0) return void 0;
return sigObj[$name];
})()''');
/// Get the type of a constructor from a class using the stored signature
/// If name is undefined, returns the type of the default constructor
/// Returns undefined if the constructor is not found.
classGetConstructorType(cls, name) => JS(
'',
'''(() => {
if(!$name) $name = 'new';
if ($cls === void 0) return void 0;
if ($cls == null) return void 0;
let sigCtor = $cls[$_constructorSig];
if (sigCtor === void 0) return void 0;
return sigCtor[$name];
})()''');
/// Given an object and a method name, tear off the method.
/// Sets the runtime type of the torn off method appropriately,
/// and also binds the object.
///
/// If the optional `f` argument is passed in, it will be used as the method.
/// This supports cases like `super.foo` where we need to tear off the method
/// from the superclass, not from the `obj` directly.
/// TODO(leafp): Consider caching the tearoff on the object?
bind(obj, name, f) => JS(
'',
'''(() => {
if ($f === void 0) $f = $obj[$name];
$f = $f.bind($obj);
// TODO(jmesserly): track the function's signature on the function, instead
// of having to go back to the class?
let sig = $getMethodType($obj, $name);
$assert_(sig);
$tag($f, sig);
return $f;
})()''');
/// Instantiate a generic method.
///
/// We need to apply the type arguments both to the function, as well as its
/// associated function type.
gbind(f, @rest typeArgs) {
var result = JS('', '#.apply(null, #)', f, typeArgs);
var sig = JS('', '#.apply(null, #)', _getRuntimeType(f), typeArgs);
tag(result, sig);
return result;
}
// Set up the method signature field on the constructor
_setInstanceSignature(f, sigF, kind) => JS(
'',
'''(() => {
$defineMemoizedGetter($f, $kind, () => {
let sigObj = $sigF();
sigObj.__proto__ = $f.__proto__[$kind];
return sigObj;
});
})()''');
_setMethodSignature(f, sigF) => _setInstanceSignature(f, sigF, _methodSig);
_setFieldSignature(f, sigF) => _setInstanceSignature(f, sigF, _fieldSig);
_setGetterSignature(f, sigF) => _setInstanceSignature(f, sigF, _getterSig);
_setSetterSignature(f, sigF) => _setInstanceSignature(f, sigF, _setterSig);
// Set up the constructor signature field on the constructor
_setConstructorSignature(f, sigF) =>
JS('', '$defineMemoizedGetter($f, $_constructorSig, $sigF)');
// Set up the static signature field on the constructor
_setStaticSignature(f, sigF) =>
JS('', '$defineMemoizedGetter($f, $_staticSig, $sigF)');
_setStaticFieldSignature(f, sigF) =>
JS('', '$defineMemoizedGetter($f, $_staticFieldSig, $sigF)');
_setStaticGetterSignature(f, sigF) =>
JS('', '$defineMemoizedGetter($f, $_staticGetterSig, $sigF)');
_setStaticSetterSignature(f, sigF) =>
JS('', '$defineMemoizedGetter($f, $_staticSetterSig, $sigF)');
// Set the lazily computed runtime type field on static methods
_setStaticTypes(f, names) => JS(
'',
'''(() => {
for (let name of $names) {
// TODO(vsm): Need to generate static methods.
if (!$f[name]) continue;
$tagLazy($f[name], function() {
return $f[$_staticSig][name];
})
}
})()''');
/// Set up the type signature of a class (constructor object)
/// f is a constructor object
/// signature is an object containing optional properties as follows:
/// methods: A function returning an object mapping method names
/// to method types. The function is evaluated lazily and cached.
/// statics: A function returning an object mapping static method
/// names to types. The function is evalutated lazily and cached.
/// names: An array of the names of the static methods. Used to
/// permit eagerly setting the runtimeType field on the methods
/// while still lazily computing the type descriptor object.
/// fields: A function returning an object mapping instance field
/// names to types.
setSignature(f, signature) => JS(
'',
'''(() => {
// TODO(ochafik): Deconstruct these when supported by Chrome.
let constructors =
('constructors' in signature) ? signature.constructors : () => ({});
let methods =
('methods' in signature) ? signature.methods : () => ({});
let fields =
('fields' in signature) ? signature.fields : () => ({});
let getters =
('getters' in signature) ? signature.getters : () => ({});
let setters =
('setters' in signature) ? signature.setters : () => ({});
let statics =
('statics' in signature) ? signature.statics : () => ({});
let staticFields =
('sfields' in signature) ? signature.sfields : () => ({});
let staticGetters =
('sgetters' in signature) ? signature.sgetters : () => ({});
let staticSetters =
('ssetters' in signature) ? signature.ssetters : () => ({});
let names =
('names' in signature) ? signature.names : [];
$_setConstructorSignature($f, constructors);
$_setMethodSignature($f, methods);
$_setFieldSignature($f, fields);
$_setGetterSignature($f, getters);
$_setSetterSignature($f, setters);
$_setStaticSignature($f, statics);
$_setStaticFieldSignature($f, staticFields);
$_setStaticGetterSignature($f, staticGetters);
$_setStaticSetterSignature($f, staticSetters);
$_setStaticTypes($f, names);
})()''');
hasMethod(obj, name) => JS('', '$getMethodType($obj, $name) !== void 0');
/// Given a class and an initializer method name, creates a constructor
/// function with the same name.
///
/// After we define the named constructor, the class can be constructed with
/// `new SomeClass.name(args)`.
defineNamedConstructor(clazz, name) => JS(
'',
'''(() => {
let proto = $clazz.prototype;
let initMethod = proto[$name];
let ctor = function(...args) { initMethod.apply(this, args); };
ctor[$isNamedConstructor] = true;
ctor.prototype = proto;
// Use defineProperty so we don't hit a property defined on Function,
// like `caller` and `arguments`.
$defineProperty($clazz, $name, { value: ctor, configurable: true });
})()''');
final _extensionType = JS('', 'Symbol("extensionType")');
getExtensionType(obj) => JS('', '#[#]', obj, _extensionType);
final dartx = JS('', 'dartx');
getExtensionSymbol(name) {
var sym = JS('', 'dartx[#]', name);
if (sym == null) {
sym = JS('', 'Symbol("dartx." + #.toString())', name);
JS('', 'dartx[#] = #', name, sym);
}
return sym;
}
defineExtensionNames(names) =>
JS('', '#.forEach(#)', names, getExtensionSymbol);
/// Install properties in prototype-first order. Properties / descriptors from
/// more specific types should overwrite ones from less specific types.
void _installProperties(jsProto, extProto) {
// This relies on the Dart type literal evaluating to the JavaScript
// constructor.
var coreObjProto = JS('', '#.prototype', Object);
var parentsExtension = JS('', '(#.__proto__)[#]', jsProto, _extensionType);
var installedParent =
JS('', '# && #.prototype', parentsExtension, parentsExtension);
_installProperties2(jsProto, extProto, coreObjProto, installedParent);
}
void _installProperties2(jsProto, extProto, coreObjProto, installedParent) {
if (JS('bool', '# === #', extProto, coreObjProto)) {
_installPropertiesForObject(jsProto, coreObjProto);
return;
}
if (JS('bool', '# !== #', jsProto, extProto)) {
var extParent = JS('', '#.__proto__', extProto);
// If the extension methods of the parent have been installed on the parent
// of [jsProto], the methods will be available via prototype inheritance.
if (JS('bool', '# !== #', installedParent, extParent)) {
_installProperties2(jsProto, extParent, coreObjProto, installedParent);
}
}
copyTheseProperties(jsProto, extProto, getOwnPropertySymbols(extProto));
}
void _installPropertiesForObject(jsProto, coreObjProto) {
// core.Object members need to be copied from the non-symbol name to the
// symbol name.
var names = getOwnPropertyNames(coreObjProto);
for (int i = 0; i < JS('int', '#.length', names); ++i) {
var name = JS('', '#[#]', names, i);
var desc = getOwnPropertyDescriptor(coreObjProto, name);
defineProperty(jsProto, getExtensionSymbol(name), desc);
}
return;
}
/// Copy symbols from the prototype of the source to destination.
/// These are the only properties safe to copy onto an existing public
/// JavaScript class.
registerExtension(jsType, dartExtType) => JS(
'',
'''(() => {
// TODO(vsm): Not all registered js types are real.
if (!jsType) return;
let extProto = $dartExtType.prototype;
let jsProto = $jsType.prototype;
// TODO(vsm): This sometimes doesn't exist on FF. These types will be
// broken.
if (!jsProto) return;
// Mark the JS type's instances so we can easily check for extensions.
jsProto[$_extensionType] = $dartExtType;
$_installProperties(jsProto, extProto);
let originalSigFn = $getOwnPropertyDescriptor($dartExtType, $_methodSig).get;
$assert_(originalSigFn);
$defineMemoizedGetter($jsType, $_methodSig, originalSigFn);
})()''');
///
/// Mark a concrete type as implementing extension methods.
/// For example: `class MyIter implements Iterable`.
///
/// This takes a list of names, which are the extension methods implemented.
/// It will add a forwarder, so the extension method name redirects to the
/// normal Dart method name. For example:
///
/// defineExtensionMembers(MyType, ['add', 'remove']);
///
/// Results in:
///
/// MyType.prototype[dartx.add] = MyType.prototype.add;
/// MyType.prototype[dartx.remove] = MyType.prototype.remove;
///
// TODO(jmesserly): essentially this gives two names to the same method.
// This benefit is roughly equivalent call performance either way, but the
// cost is we need to call defineExtensionMembers any time a subclass
// overrides one of these methods.
defineExtensionMembers(type, methodNames) => JS(
'',
'''(() => {
let proto = $type.prototype;
for (let name of $methodNames) {
let method = $getOwnPropertyDescriptor(proto, name);
$defineProperty(proto, $getExtensionSymbol(name), method);
}
// Ensure the signature is available too.
// TODO(jmesserly): not sure if we can do this in a cleaner way. Essentially
// we need to copy the signature (and in the future, other data like
// annotations) any time we copy a method as part of our metaprogramming.
// It might be more friendly to JS metaprogramming if we include this info
// on the function.
let originalSigFn = $getOwnPropertyDescriptor($type, $_methodSig).get;
$defineMemoizedGetter(type, $_methodSig, function() {
let sig = originalSigFn();
for (let name of $methodNames) {
sig[$getExtensionSymbol(name)] = sig[name];
}
return sig;
});
})()''');
/// Sets the type of `obj` to be `type`
setType(obj, type) {
JS('', '#.__proto__ = #.prototype', obj, type);
return obj;
}
/// Sets the element type of a list literal.
list(obj, elementType) =>
JS('', '$setType($obj, ${getGenericClass(JSArray)}($elementType))');
/// Link the extension to the type it's extending as a base class.
setBaseClass(derived, base) {
JS('', '#.prototype.__proto__ = #.prototype', derived, base);
// We use __proto__ to track the superclass hierarchy (see isSubtype).
JS('', '#.__proto__ = #', derived, base);
}
/// Like [setBaseClass] but for generic extension types, e.g. `JSArray<E>`
setExtensionBaseClass(derived, base) {
// Mark the generic type as an extension type and link the prototype objects
return JS('', '''(() => {
if ($base) {
$derived.prototype[$_extensionType] = $derived;
$derived.prototype.__proto__ = $base.prototype
}
})()''');
}
/// Given a special constructor function that creates a function instances,
/// and a class with a `call` method, merge them so the constructor function
/// will have the correct methods and prototype.
///
/// For example:
///
/// lib.Foo = dart.callableClass(
/// function Foo { function call(...args) { ... } ... return call; },
/// class Foo { call(x) { ... } });
/// ...
/// let f = new lib.Foo();
/// f(42);
callableClass(callableCtor, classExpr) {
JS('', '#.prototype = #.prototype', callableCtor, classExpr);
// We're not going to use the original class, so we can safely replace it to
// point at this constructor for the runtime type information.
JS('', '#.prototype.constructor = #', callableCtor, callableCtor);
JS('', '#.__proto__ = #', callableCtor, classExpr);
return callableCtor;
}
/// Given a class and an initializer method name and a call method, creates a
/// constructor function with the same name.
///
/// For example it can be called with `new SomeClass.name(args)`.
///
/// The constructor
defineNamedConstructorCallable(clazz, name, ctor) => JS(
'',
'''(() => {
ctor.prototype = $clazz.prototype;
// Use defineProperty so we don't hit a property defined on Function,
// like `caller` and `arguments`.
$defineProperty($clazz, $name, { value: ctor, configurable: true });
})()''');
defineEnumValues(enumClass, names) => JS(
'',
'''(() => {
let values = [];
for (var i = 0; i < $names.length; i++) {
let value = $const_(new $enumClass(i));
values.push(value);
Object.defineProperty($enumClass, $names[i],
{ value: value, configurable: true });
}
$enumClass.values = $constList(values, $enumClass);
})()''');