Index: tool/input_sdk/private/classes.dart |
diff --git a/tool/input_sdk/private/classes.dart b/tool/input_sdk/private/classes.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..973e1c9c7e9833fbac9b66c578b7535ca6ee8d80 |
--- /dev/null |
+++ b/tool/input_sdk/private/classes.dart |
@@ -0,0 +1,397 @@ |
+// 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. |
+library dart._classes; |
+ |
+import 'dart:_foreign_helper' show JS, JsName, rest, genericTypeConstructor; |
+ |
+import 'dart:_interceptors' show JSArray; |
+import 'dart:_types' show definiteFunctionType, dynamicR; |
+import 'dart:_rtti' show tag, tagMemoized; |
+import 'dart:_utils' as utils; |
+ |
+@JsName('assert') |
+final assert_ = JS('', '${utils.assert_}'); |
+final copyProperties = JS('', '${utils.copyProperties}'); |
+final copyTheseProperties = JS('', '${utils.copyTheseProperties}'); |
+final defineMemoizedGetter = JS('', '${utils.defineMemoizedGetter}'); |
+final safeGetOwnProperty = JS('', '${utils.safeGetOwnProperty}'); |
+final throwInternalError = JS('', '${utils.throwInternalError}'); |
+ |
+final defineProperty = JS('', 'Object.defineProperty'); |
+final getOwnPropertyDescriptor = JS('', 'Object.getOwnPropertyDescriptor'); |
+final getOwnPropertySymbols = JS('', 'Object.getOwnPropertySymbols'); |
+ |
+/// The Symbol for storing type arguments on a specialized generic type. |
+final _mixins = JS('', 'Symbol("mixins")'); |
+@JsName('implements') |
+final implements_ = JS('', 'Symbol("implements")'); |
+final metadata = JS('', 'Symbol("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). |
+/// |
+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 { |
+ // 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, args); |
+ } |
+ } |
+ // 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; |
+})()'''); |
+ |
+getMixins(clazz) => JS('', '''(() => { |
+ return $clazz[$_mixins]; |
+})()'''); |
+ |
+getImplements(clazz) => JS('', '''(() => { |
+ return $clazz[$implements_]; |
+})()'''); |
+ |
+/// The Symbol for storing type arguments on a specialized generic type. |
+final _typeArguments = JS('', 'Symbol("typeArguments")'); |
+final _originalDeclaration = JS('', 'Symbol("originalDeclaration")'); |
+ |
+/// Memoize a generic type constructor function. |
+generic(typeConstructor) => 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($dynamicR); |
+ |
+ 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; |
+ } |
+ } else { |
+ value = new Map(); |
+ } |
+ map.set(arg, value); |
+ } |
+ } |
+ return value; |
+ } |
+ return makeGenericType; |
+})()'''); |
+ |
+getGenericClass(type) => JS('', '''(() => { |
+ return $safeGetOwnProperty($type, $_originalDeclaration); |
+})()'''); |
+ |
+getGenericArgs(type) => JS('', '''(() => { |
+ return $safeGetOwnProperty($type, $_typeArguments); |
+})()'''); |
+ |
+final _constructorSig = JS('', 'Symbol("sigCtor")'); |
+final _methodSig = JS('', 'Symbol("sig")'); |
+final _staticSig = JS('', 'Symbol("sigStatic")'); |
+ |
+/// Get the type of a method using the stored signature |
+getMethodType(obj, name) => JS('', '''(() => { |
+ 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 $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. |
+classGetConstructorType(cls, name) => JS('', '''(() => { |
+ 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 $definiteFunctionType.apply(null, parts); |
+})()'''); |
+ |
+/// 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; |
+})()'''); |
+ |
+// Set up the method signature field on the constructor |
+_setMethodSignature(f, sigF) => JS('', '''(() => { |
+ $defineMemoizedGetter($f, $_methodSig, () => { |
+ let sigObj = $sigF(); |
+ sigObj.__proto__ = $f.__proto__[$_methodSig]; |
+ return sigObj; |
+ }); |
+})()'''); |
+ |
+// 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); |
+})()'''); |
+ |
+// Set the lazily computed runtime type field on static methods |
+_setStaticTypes(f, names) => JS('', '''(() => { |
+ for (let name of $names) { |
+ $tagMemoized($f[name], function() { |
+ let parts = $f[$_staticSig][name]; |
+ return $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. |
+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 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); |
+ $tagMemoized($f, () => $Type); |
+})()'''); |
+ |
+hasMethod(obj, name) => JS('', '''(() => { |
+ return $getMethodType($obj, $name) !== void 0; |
+})()'''); |
+ |
+/// |
+/// 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. |
+/// |
+virtualField(subclass, fieldName) => JS('', '''(() => { |
+ // 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; } |
+ }); |
+})()'''); |
+ |
+/// |
+/// Given a class and an initializer method name, creates a constructor |
+/// function with the same name. For example `new SomeClass.name(args)`. |
+/// |
+defineNamedConstructor(clazz, name) => JS('', '''(() => { |
+ 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 }); |
+})()'''); |
+ |
+final _extensionType = JS('', 'Symbol("extensionType")'); |
+ |
+final dartx = JS('', '{}'); |
+ |
+getExtensionSymbol(name) => JS('', '''(() => { |
+ let sym = $dartx[$name]; |
+ if (!sym) $dartx[$name] = sym = Symbol('dartx.' + $name); |
+ return sym; |
+})()'''); |
+ |
+defineExtensionNames(names) => JS('', '''(() => { |
+ $names.forEach($getExtensionSymbol); |
+})()'''); |
+ |
+/// |
+/// 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('', '''(() => { |
+ 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 = $Object.prototype; |
+ while (extProto !== dartObjProto && extProto !== jsProto) { |
+ $copyTheseProperties(jsProto, extProto, $getOwnPropertySymbols(extProto)); |
+ extProto = extProto.__proto__; |
+ } |
+ 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; |
+ }); |
+})()'''); |
+ |
+canonicalMember(obj, name) => JS('', '''(() => { |
+ if ($obj != null && $obj[$_extensionType]) return $dartx[$name]; |
+ // Check for certain names that we can't use in JS |
+ if ($name == 'constructor' || $name == 'prototype') { |
+ $name = '+' + $name; |
+ } |
+ return $name; |
+})()'''); |
+ |
+/// Sets the type of `obj` to be `type` |
+setType(obj, type) => JS('', '''(() => { |
+ $obj.__proto__ = $type.prototype; |
+ return $obj; |
+})()'''); |
+ |
+/// Sets the element type of a list literal. |
+list(obj, elementType) => JS('', '''(() => { |
+ // TODO(ochafik): Expose a way to build imported generics. |
+ return $setType($obj, ${genericTypeConstructor(JSArray)}($elementType)); |
+})()'''); |
+ |
+setBaseClass(derived, base) => JS('', '''(() => { |
+ // Link the extension to the type it's extending as a base class. |
+ $derived.prototype.__proto__ = $base.prototype; |
+})()'''); |