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; |
+ |
+}); |