4e7920c487
This changes the way we handle dynamic at runtime to be more correct and cleaner. We now simply emit dynamic as dynamic instead of as core.Object. There are now two ways to construct a function type: one can construct a fuzzy function type (the default), or a definite function type. The constructor for a fuzzy function type replaces all uses of dynamic with bottom. This function type is used for all type annotations. Definite function types do not replace dynamic with bottom. These only occur as the runtime type of actual functions, for which we really know the type. Because we now eagerly sort this out when we create the function type, the subtyping code doesn't need to deal with this. This allows some additional subtyping: closures which actually are typed to take dynamic would previously not have been allowed to be cast to something with a concrete argument type. Now this is allowed (see the change in runtime_tests.js for an example of this). This fixes #107. BUG= R=vsm@google.com Review URL: https://codereview.chromium.org/1195523002
409 lines
14 KiB
JavaScript
409 lines
14 KiB
JavaScript
// 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.
|
|
dart_library.library('dart_runtime/_classes', null, /* Imports */[
|
|
], /* Lazy Imports */[
|
|
'dart/core',
|
|
'dart/_interceptors',
|
|
'dart_runtime/_types',
|
|
'dart_runtime/_rtti',
|
|
], function(exports, core, _interceptors, types, rtti) {
|
|
'use strict';
|
|
|
|
const assert = dart_utils.assert;
|
|
const copyProperties = dart_utils.copyProperties;
|
|
const copyTheseProperties = dart_utils.copyTheseProperties;
|
|
const defineMemoizedGetter = dart_utils.defineMemoizedGetter;
|
|
const safeGetOwnProperty = dart_utils.safeGetOwnProperty;
|
|
const throwError = dart_utils.throwError;
|
|
|
|
const defineProperty = Object.defineProperty;
|
|
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
const getOwnPropertySymbols = Object.getOwnPropertySymbols;
|
|
|
|
const slice = [].slice;
|
|
|
|
/** The Symbol for storing type arguments on a specialized generic type. */
|
|
const _mixins = Symbol('mixins');
|
|
const _implements = Symbol('implements');
|
|
exports.implements = _implements;
|
|
const _metadata = Symbol('metadata');
|
|
exports.metadata = _metadata;
|
|
|
|
/**
|
|
* 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).
|
|
*/
|
|
function mixin(base/*, ...mixins*/) {
|
|
// Create an initializer for the mixin, so when derived constructor calls
|
|
// super, we can correctly initialize base and mixins.
|
|
let mixins = slice.call(arguments, 1);
|
|
|
|
// Create a class that will hold all of the mixin methods.
|
|
class Mixin extends base {
|
|
// Initializer method: run mixin initializers, then the base.
|
|
[base.name](/*...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--) {
|
|
let mixin = mixins[i];
|
|
let init = mixin.prototype[mixin.name];
|
|
if (init) init.call(this);
|
|
}
|
|
// Run base initializer.
|
|
let init = base.prototype[base.name];
|
|
if (init) init.apply(this, arguments);
|
|
}
|
|
}
|
|
// Copy each mixin's methods, with later ones overwriting earlier entries.
|
|
for (let m of mixins) {
|
|
copyProperties(Mixin.prototype, m.prototype);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
exports.mixin = mixin;
|
|
|
|
function getMixins (clazz) {
|
|
return clazz[_mixins];
|
|
}
|
|
exports.getMixins = getMixins;
|
|
|
|
function getImplements (clazz) {
|
|
return clazz[_implements];
|
|
}
|
|
exports.getImplements = getImplements;
|
|
|
|
/** The Symbol for storing type arguments on a specialized generic type. */
|
|
let _typeArguments = Symbol('typeArguments');
|
|
let _originalDeclaration = Symbol('originalDeclaration');
|
|
|
|
/** Memoize a generic type constructor function. */
|
|
function generic(typeConstructor) {
|
|
let length = typeConstructor.length;
|
|
if (length < 1) {
|
|
throwError('must have at least one generic type argument');
|
|
}
|
|
let resultMap = new Map();
|
|
function makeGenericType(/*...arguments*/) {
|
|
if (arguments.length != length && arguments.length != 0) {
|
|
throwError('requires ' + length + ' or 0 type arguments');
|
|
}
|
|
let args = slice.call(arguments);
|
|
while (args.length < length) args.push(types.dynamic);
|
|
|
|
let value = resultMap;
|
|
for (let i = 0; i < length; i++) {
|
|
let arg = args[i];
|
|
if (arg == null) {
|
|
throwError('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;
|
|
}
|
|
} else {
|
|
value = new Map();
|
|
}
|
|
map.set(arg, value);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
return makeGenericType;
|
|
}
|
|
exports.generic = generic;
|
|
|
|
function getGenericClass(type) {
|
|
return safeGetOwnProperty(type, _originalDeclaration);
|
|
};
|
|
exports.getGenericClass = getGenericClass;
|
|
|
|
function getGenericArgs(type) {
|
|
return safeGetOwnProperty(type, _typeArguments);
|
|
};
|
|
exports.getGenericArgs = getGenericArgs;
|
|
|
|
let _constructorSig = Symbol('sigCtor');
|
|
let _methodSig = Symbol("sig");
|
|
let _staticSig = Symbol("sigStatic");
|
|
|
|
/// Get the type of a method using the stored signature
|
|
function _getMethodType(obj, name) {
|
|
if (obj === void 0) return void 0;
|
|
if (obj == null) return void 0;
|
|
let sigObj = obj.__proto__.constructor[_methodSig];
|
|
if (sigObj === void 0) return void 0;
|
|
let parts = sigObj[name];
|
|
if (parts === void 0) return void 0;
|
|
return types.definiteFunctionType.apply(null, parts);
|
|
}
|
|
|
|
/// 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.
|
|
function _getConstructorType(cls, name) {
|
|
if(!name) name = cls.name;
|
|
if (cls === void 0) return void 0;
|
|
if (cls == null) return void 0;
|
|
let sigCtor = cls[_constructorSig];
|
|
if (sigCtor === void 0) return void 0;
|
|
let parts = sigCtor[name];
|
|
if (parts === void 0) return void 0;
|
|
return types.definiteFunctionType.apply(null, parts);
|
|
}
|
|
exports.classGetConstructorType = _getConstructorType;
|
|
|
|
/// 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.
|
|
/// TODO(leafp): Consider caching the tearoff on the object?
|
|
function bind(obj, name) {
|
|
let f = obj[name].bind(obj);
|
|
let sig = _getMethodType(obj, name);
|
|
assert(sig);
|
|
rtti.tag(f, sig);
|
|
return f;
|
|
}
|
|
exports.bind = bind;
|
|
|
|
// Set up the method signature field on the constructor
|
|
function _setMethodSignature(f, sigF) {
|
|
defineMemoizedGetter(f, _methodSig, () => {
|
|
let sigObj = sigF();
|
|
sigObj.__proto__ = f.__proto__[_methodSig];
|
|
return sigObj;
|
|
});
|
|
}
|
|
|
|
// Set up the constructor signature field on the constructor
|
|
function _setConstructorSignature(f, sigF) {
|
|
defineMemoizedGetter(f, _constructorSig, sigF);
|
|
}
|
|
|
|
// Set up the static signature field on the constructor
|
|
function _setStaticSignature(f, sigF) {
|
|
defineMemoizedGetter(f, _staticSig, sigF);
|
|
}
|
|
|
|
// Set the lazily computed runtime type field on static methods
|
|
function _setStaticTypes(f, names) {
|
|
for (let name of names) {
|
|
rtti.tagMemoized(f[name], function() {
|
|
let parts = f[_staticSig][name];
|
|
return types.definiteFunctionType.apply(null, parts);
|
|
})
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
function setSignature(f, signature) {
|
|
let constructors =
|
|
('constructors' in signature) ? signature.constructors : () => ({});
|
|
let methods =
|
|
('methods' in signature) ? signature.methods : () => ({});
|
|
let statics =
|
|
('statics' in signature) ? signature.statics : () => ({});
|
|
let names =
|
|
('names' in signature) ? signature.names : [];
|
|
_setConstructorSignature(f, constructors);
|
|
_setMethodSignature(f, methods);
|
|
_setStaticSignature(f, statics);
|
|
_setStaticTypes(f, names);
|
|
rtti.tagMemoized(f, () => core.Type);
|
|
}
|
|
exports.setSignature = setSignature;
|
|
|
|
function hasMethod(obj, name) {
|
|
return _getMethodType(obj, name) !== void 0;
|
|
}
|
|
exports.hasMethod = hasMethod;
|
|
|
|
exports.getMethodType = _getMethodType;
|
|
|
|
/**
|
|
* This is called whenever a derived class needs to introduce a new field,
|
|
* shadowing a field or getter/setter pair on its parent.
|
|
*
|
|
* This is important because otherwise, trying to read or write the field
|
|
* would end up calling the getter or setter, and one of those might not even
|
|
* exist, resulting in a runtime error. Even if they did exist, that's the
|
|
* wrong behavior if a new field was declared.
|
|
*/
|
|
function virtualField(subclass, fieldName) {
|
|
// If the field is already overridden, do nothing.
|
|
let prop = getOwnPropertyDescriptor(subclass.prototype, fieldName);
|
|
if (prop) return;
|
|
|
|
let symbol = Symbol(subclass.name + '.' + fieldName);
|
|
defineProperty(subclass.prototype, fieldName, {
|
|
get: function() { return this[symbol]; },
|
|
set: function(x) { this[symbol] = x; }
|
|
});
|
|
}
|
|
exports.virtualField = virtualField;
|
|
|
|
/**
|
|
* Given a class and an initializer method name, creates a constructor
|
|
* function with the same name. For example `new SomeClass.name(args)`.
|
|
*/
|
|
function defineNamedConstructor(clazz, name) {
|
|
let proto = clazz.prototype;
|
|
let initMethod = proto[name];
|
|
let ctor = function() { return initMethod.apply(this, arguments); };
|
|
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 });
|
|
}
|
|
exports.defineNamedConstructor = defineNamedConstructor;
|
|
|
|
let _extensionType = Symbol('extensionType');
|
|
|
|
let dartx = {};
|
|
exports.dartx = dartx;
|
|
|
|
function getExtensionSymbol(name) {
|
|
let sym = dartx[name];
|
|
if (!sym) dartx[name] = sym = Symbol('dartx.' + name);
|
|
return sym;
|
|
}
|
|
|
|
function defineExtensionNames(names) {
|
|
names.forEach(getExtensionSymbol);
|
|
}
|
|
exports.defineExtensionNames = defineExtensionNames;
|
|
|
|
/**
|
|
* Copy symbols from the prototype of the source to destination.
|
|
* These are the only properties safe to copy onto an existing public
|
|
* JavaScript class.
|
|
*/
|
|
function registerExtension(jsType, dartExtType) {
|
|
let extProto = dartExtType.prototype;
|
|
let jsProto = jsType.prototype;
|
|
|
|
// Mark the JS type's instances so we can easily check for extensions.
|
|
assert(jsProto[_extensionType] === void 0);
|
|
jsProto[_extensionType] = extProto;
|
|
|
|
let dartObjProto = core.Object.prototype;
|
|
while (extProto !== dartObjProto && extProto !== jsProto) {
|
|
copyTheseProperties(jsProto, extProto, getOwnPropertySymbols(extProto));
|
|
extProto = extProto.__proto__;
|
|
}
|
|
}
|
|
exports.registerExtension = registerExtension;
|
|
|
|
/**
|
|
* 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.
|
|
function defineExtensionMembers(type, methodNames) {
|
|
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;
|
|
});
|
|
}
|
|
exports.defineExtensionMembers = defineExtensionMembers;
|
|
|
|
function canonicalMember(obj, name) {
|
|
if (obj[_extensionType]) return dartx[name];
|
|
return name;
|
|
}
|
|
exports.canonicalMember = canonicalMember;
|
|
|
|
/** Sets the type of `obj` to be `type` */
|
|
function setType(obj, type) {
|
|
obj.__proto__ = type.prototype;
|
|
return obj;
|
|
}
|
|
|
|
/** Sets the element type of a list literal. */
|
|
function list(obj, elementType) {
|
|
return setType(obj, _interceptors.JSArray$(elementType));
|
|
}
|
|
exports.list = list;
|
|
|
|
function setBaseClass(derived, base) {
|
|
// Link the extension to the type it's extending as a base class.
|
|
derived.prototype.__proto__ = base.prototype;
|
|
}
|
|
exports.setBaseClass = setBaseClass;
|
|
|
|
});
|