Files
sdk/pkg/dev_compiler/lib/runtime/_classes.js
T
Leaf Petersen b9c83e0013 Most of this CL just re-organizes the runtime code into multiple sub-libraries. js_utils and loader remain as top level symbols (the former so that it has no dependencies, the latter since it defines the module system that the rest use).
The dart runtime files are roughly split around functional boundaries.  Comments at the top of each file describe the function of the library.  I'm not sure that these are 100% right yet, but they're closer.  We may want to iterate on the organization.

The main dart runtime file just re-exports various symbols from the sub-libraries.  It has hard dependencies on the other runtime files, some of which have soft (lazy) dependencies on SDK files.  The generated code now depends on dart and dartx as a imported libraries, rather than as top level symbols.

This CL also includes some new code to make type representations a bit better.  This is mostly in the new types.js file, which makes all of the non-instance types (dynamic, bottom, void, function types) subtypes of a common representation type.  Among other things, they now have the correct runtime type (core.Type).  Some of the type code has been rationalized around this a bit, but there is more left to do here.  The setSignature code in classes.js now also places the correct runtime type on nominal (instance) types.  There are new tests in the runtime_tests.js file to test for this.

The static object method code that implements the Object methods has not yet been updated to dispatch on type representations.  I will do that in a separate CL.

BUG=
R=jmesserly@google.com, vsm@google.com

Review URL: https://codereview.chromium.org/1182653002
2015-06-12 13:34:39 -07:00

413 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);
// TODO(leafp): This should really be core.Object for
// consistency, but Object is not attached to core
// until the entire core library has been processed,
// which is too late.
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.functionType.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.functionType.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.functionType.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;
});