Chromium Code Reviews| Index: lib/runtime/classes.js |
| diff --git a/lib/runtime/classes.js b/lib/runtime/classes.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..86f5c531f7e3bcb98faf50d4ab6b2771e0b14764 |
| --- /dev/null |
| +++ b/lib/runtime/classes.js |
| @@ -0,0 +1,415 @@ |
| +// 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. |
| +loader.library('dart/classes', null, /* Imports */[ |
|
vsm
2015/06/12 15:50:32
Perhaps rename 'dart/classes', etc., to 'dart/_cla
Leaf
2015/06/12 19:52:13
Done.
|
| +], /* Lazy Imports */[ |
| + 'dart/core', |
| + 'dart/dartx', |
| + 'dart/types', |
| + 'dart/rtti', |
| + 'dart/_interceptors' |
| +], function(exports, core, dartx, types, rtti, _interceptors) { |
| + 'use strict'; |
| + |
| + const assert = js_utils.assert; |
| + const copyProperties = js_utils.copyProperties; |
| + const copyTheseProperties = js_utils.copyTheseProperties; |
| + const defineMemoizedGetter = js_utils.defineMemoizedGetter; |
| + const defineProperty = js_utils.defineProperty; |
| + const getOwnPropertyDescriptor = js_utils.getOwnPropertyDescriptor; |
| + const getOwnPropertySymbols = js_utils.getOwnPropertySymbols; |
| + const getOwnPropertyNames = js_utils.getOwnPropertyNames; |
| + const safeGetOwnProperty = js_utils.safeGetOwnProperty; |
| + const slice = js_utils.slice; |
| + const throwRuntimeError = js_utils.throwRuntimeError; |
| + |
| + |
| + /** The Symbol for storing type arguments on a specialized generic type. */ |
| + let _mixins = Symbol('mixins'); |
| + let _implements = Symbol('implements'); |
| + exports.implements = _implements; |
| + let _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) throwRuntimeError('must have at least one generic type argument'); |
| + |
| + let resultMap = new Map(); |
| + function makeGenericType(/*...arguments*/) { |
| + if (arguments.length != length && arguments.length != 0) { |
| + throwRuntimeError('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) { |
| + throwRuntimeError('type arguments should not be null: ' + typeConstructor); |
|
vsm
2015/06/12 15:50:32
nit: length
Leaf
2015/06/12 19:52:13
Done.
|
| + } |
| + 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'); |
| + |
| + 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 |
|
vsm
2015/06/12 15:50:32
nit: line len && fix cap on MEmbers
Leaf
2015/06/12 19:52:13
Done.
|
| + // 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; |
| + |
| + // TODO(vsm): Rationalize these type methods. We're currently using the |
| + // setType / proto scheme for nominal types (e.g., classes) and the |
| + // setRuntimeType / field scheme for structural types (e.g., functions |
|
vsm
2015/06/12 15:50:32
I think setRuntimeType is gone. Can you either re
Leaf
2015/06/12 19:52:13
Done.
|
| + // - and only in tests for now). |
| + // See: https://github.com/dart-lang/dev_compiler/issues/172 |
| + |
| + /** Sets the type of `obj` to be `type` */ |
| + function setType(obj, type) { |
| + obj.__proto__ = type.prototype; |
| + return obj; |
| + } |
| + exports.setType = setType; |
| + |
| + /** 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; |
| + |
| +}); |