OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// This library defines the operations that define and manipulate Dart |
| 6 /// classes. Included in this are: |
| 7 /// - Generics |
| 8 /// - Class metadata |
| 9 /// - Extension methods |
| 10 /// |
| 11 |
| 12 // TODO(leafp): Consider splitting some of this out. |
| 13 part of dart._runtime; |
| 14 |
| 15 /// |
| 16 /// Returns a new type that mixes members from base and all mixins. |
| 17 /// |
| 18 /// Each mixin applies in sequence, with further to the right ones overriding |
| 19 /// previous entries. |
| 20 /// |
| 21 /// For each mixin, we only take its own properties, not anything from its |
| 22 /// superclass (prototype). |
| 23 /// |
| 24 mixin(base, @rest mixins) => JS('', '''(() => { |
| 25 // Create an initializer for the mixin, so when derived constructor calls |
| 26 // super, we can correctly initialize base and mixins. |
| 27 |
| 28 // Create a class that will hold all of the mixin methods. |
| 29 class Mixin extends $base { |
| 30 // Initializer method: run mixin initializers, then the base. |
| 31 [$base.name](...args) { |
| 32 // Run mixin initializers. They cannot have arguments. |
| 33 // Run them backwards so most-derived mixin is initialized first. |
| 34 for (let i = $mixins.length - 1; i >= 0; i--) { |
| 35 let mixin = $mixins[i]; |
| 36 let init = mixin.prototype[mixin.name]; |
| 37 if (init) init.call(this); |
| 38 } |
| 39 // Run base initializer. |
| 40 let init = $base.prototype[base.name]; |
| 41 if (init) init.apply(this, args); |
| 42 } |
| 43 } |
| 44 // Copy each mixin's methods, with later ones overwriting earlier entries. |
| 45 for (let m of $mixins) { |
| 46 $copyProperties(Mixin.prototype, m.prototype); |
| 47 } |
| 48 |
| 49 // Set the signature of the Mixin class to be the composition |
| 50 // of the signatures of the mixins. |
| 51 $setSignature(Mixin, { |
| 52 methods: () => { |
| 53 let s = {}; |
| 54 for (let m of $mixins) { |
| 55 $copyProperties(s, m[$_methodSig]); |
| 56 } |
| 57 return s; |
| 58 } |
| 59 }); |
| 60 |
| 61 // Save mixins for reflection |
| 62 Mixin[$_mixins] = $mixins; |
| 63 return Mixin; |
| 64 })()'''); |
| 65 |
| 66 getMixins(clazz) => JS('', '$clazz[$_mixins]'); |
| 67 |
| 68 getImplements(clazz) => JS('', '$clazz[$implements_]'); |
| 69 |
| 70 /// The Symbol for storing type arguments on a specialized generic type. |
| 71 final _typeArguments = JS('', 'Symbol("typeArguments")'); |
| 72 final _originalDeclaration = JS('', 'Symbol("originalDeclaration")'); |
| 73 |
| 74 /// Memoize a generic type constructor function. |
| 75 generic(typeConstructor) => JS('', '''(() => { |
| 76 let length = $typeConstructor.length; |
| 77 if (length < 1) { |
| 78 $throwInternalError('must have at least one generic type argument'); |
| 79 } |
| 80 let resultMap = new Map(); |
| 81 function makeGenericType(...args) { |
| 82 if (args.length != length && args.length != 0) { |
| 83 $throwInternalError('requires ' + length + ' or 0 type arguments'); |
| 84 } |
| 85 while (args.length < length) args.push($dynamicR); |
| 86 |
| 87 let value = resultMap; |
| 88 for (let i = 0; i < length; i++) { |
| 89 let arg = args[i]; |
| 90 if (arg == null) { |
| 91 $throwInternalError('type arguments should not be null: ' |
| 92 + $typeConstructor); |
| 93 } |
| 94 let map = value; |
| 95 value = map.get(arg); |
| 96 if (value === void 0) { |
| 97 if (i + 1 == length) { |
| 98 value = $typeConstructor.apply(null, args); |
| 99 // Save the type constructor and arguments for reflection. |
| 100 if (value) { |
| 101 value[$_typeArguments] = args; |
| 102 value[$_originalDeclaration] = makeGenericType; |
| 103 } |
| 104 } else { |
| 105 value = new Map(); |
| 106 } |
| 107 map.set(arg, value); |
| 108 } |
| 109 } |
| 110 return value; |
| 111 } |
| 112 return makeGenericType; |
| 113 })()'''); |
| 114 |
| 115 getGenericClass(type) => |
| 116 JS('', '$safeGetOwnProperty($type, $_originalDeclaration)'); |
| 117 |
| 118 getGenericArgs(type) => |
| 119 JS('', '$safeGetOwnProperty($type, $_typeArguments)'); |
| 120 |
| 121 final _constructorSig = JS('', 'Symbol("sigCtor")'); |
| 122 final _methodSig = JS('', 'Symbol("sig")'); |
| 123 final _staticSig = JS('', 'Symbol("sigStatic")'); |
| 124 |
| 125 /// Get the type of a method using the stored signature |
| 126 getMethodType(obj, name) => JS('', '''(() => { |
| 127 if ($obj === void 0) return void 0; |
| 128 if ($obj == null) return void 0; |
| 129 let sigObj = $obj.__proto__.constructor[$_methodSig]; |
| 130 if (sigObj === void 0) return void 0; |
| 131 let parts = sigObj[$name]; |
| 132 if (parts === void 0) return void 0; |
| 133 return $definiteFunctionType.apply(null, parts); |
| 134 })()'''); |
| 135 |
| 136 /// Get the type of a constructor from a class using the stored signature |
| 137 /// If name is undefined, returns the type of the default constructor |
| 138 /// Returns undefined if the constructor is not found. |
| 139 classGetConstructorType(cls, name) => JS('', '''(() => { |
| 140 if(!$name) $name = $cls.name; |
| 141 if ($cls === void 0) return void 0; |
| 142 if ($cls == null) return void 0; |
| 143 let sigCtor = $cls[$_constructorSig]; |
| 144 if (sigCtor === void 0) return void 0; |
| 145 let parts = sigCtor[$name]; |
| 146 if (parts === void 0) return void 0; |
| 147 return $definiteFunctionType.apply(null, parts); |
| 148 })()'''); |
| 149 |
| 150 /// Given an object and a method name, tear off the method. |
| 151 /// Sets the runtime type of the torn off method appropriately, |
| 152 /// and also binds the object. |
| 153 /// |
| 154 /// If the optional `f` argument is passed in, it will be used as the method. |
| 155 /// This supports cases like `super.foo` where we need to tear off the method |
| 156 /// from the superclass, not from the `obj` directly. |
| 157 /// TODO(leafp): Consider caching the tearoff on the object? |
| 158 bind(obj, name, f) => JS('', '''(() => { |
| 159 if ($f === void 0) $f = $obj[$name]; |
| 160 $f = $f.bind($obj); |
| 161 // TODO(jmesserly): track the function's signature on the function, instead |
| 162 // of having to go back to the class? |
| 163 let sig = $getMethodType($obj, $name); |
| 164 $assert_(sig); |
| 165 $tag($f, sig); |
| 166 return $f; |
| 167 })()'''); |
| 168 |
| 169 // Set up the method signature field on the constructor |
| 170 _setMethodSignature(f, sigF) => JS('', '''(() => { |
| 171 $defineMemoizedGetter($f, $_methodSig, () => { |
| 172 let sigObj = $sigF(); |
| 173 sigObj.__proto__ = $f.__proto__[$_methodSig]; |
| 174 return sigObj; |
| 175 }); |
| 176 })()'''); |
| 177 |
| 178 // Set up the constructor signature field on the constructor |
| 179 _setConstructorSignature(f, sigF) => |
| 180 JS('', '$defineMemoizedGetter($f, $_constructorSig, $sigF)'); |
| 181 |
| 182 // Set up the static signature field on the constructor |
| 183 _setStaticSignature(f, sigF) => |
| 184 JS('', '$defineMemoizedGetter($f, $_staticSig, $sigF)'); |
| 185 |
| 186 // Set the lazily computed runtime type field on static methods |
| 187 _setStaticTypes(f, names) => JS('', '''(() => { |
| 188 for (let name of $names) { |
| 189 $tagMemoized($f[name], function() { |
| 190 let parts = $f[$_staticSig][name]; |
| 191 return $definiteFunctionType.apply(null, parts); |
| 192 }) |
| 193 } |
| 194 })()'''); |
| 195 |
| 196 /// Set up the type signature of a class (constructor object) |
| 197 /// f is a constructor object |
| 198 /// signature is an object containing optional properties as follows: |
| 199 /// methods: A function returning an object mapping method names |
| 200 /// to method types. The function is evaluated lazily and cached. |
| 201 /// statics: A function returning an object mapping static method |
| 202 /// names to types. The function is evalutated lazily and cached. |
| 203 /// names: An array of the names of the static methods. Used to |
| 204 /// permit eagerly setting the runtimeType field on the methods |
| 205 /// while still lazily computing the type descriptor object. |
| 206 setSignature(f, signature) => JS('', '''(() => { |
| 207 // TODO(ochafik): Deconstruct these when supported by Chrome. |
| 208 let constructors = |
| 209 ('constructors' in signature) ? signature.constructors : () => ({}); |
| 210 let methods = |
| 211 ('methods' in signature) ? signature.methods : () => ({}); |
| 212 let statics = |
| 213 ('statics' in signature) ? signature.statics : () => ({}); |
| 214 let names = |
| 215 ('names' in signature) ? signature.names : []; |
| 216 $_setConstructorSignature($f, constructors); |
| 217 $_setMethodSignature($f, methods); |
| 218 $_setStaticSignature($f, statics); |
| 219 $_setStaticTypes($f, names); |
| 220 $tagMemoized($f, () => $Type); |
| 221 })()'''); |
| 222 |
| 223 hasMethod(obj, name) => JS('', '$getMethodType($obj, $name) !== void 0'); |
| 224 |
| 225 /// |
| 226 /// This is called whenever a derived class needs to introduce a new field, |
| 227 /// shadowing a field or getter/setter pair on its parent. |
| 228 /// |
| 229 /// This is important because otherwise, trying to read or write the field |
| 230 /// would end up calling the getter or setter, and one of those might not even |
| 231 /// exist, resulting in a runtime error. Even if they did exist, that's the |
| 232 /// wrong behavior if a new field was declared. |
| 233 /// |
| 234 virtualField(subclass, fieldName) => JS('', '''(() => { |
| 235 // If the field is already overridden, do nothing. |
| 236 let prop = $getOwnPropertyDescriptor($subclass.prototype, $fieldName); |
| 237 if (prop) return; |
| 238 |
| 239 let symbol = Symbol($subclass.name + '.' + $fieldName); |
| 240 $defineProperty($subclass.prototype, $fieldName, { |
| 241 get: function() { return this[symbol]; }, |
| 242 set: function(x) { this[symbol] = x; } |
| 243 }); |
| 244 })()'''); |
| 245 |
| 246 /// |
| 247 /// Given a class and an initializer method name, creates a constructor |
| 248 /// function with the same name. For example `new SomeClass.name(args)`. |
| 249 /// |
| 250 defineNamedConstructor(clazz, name) => JS('', '''(() => { |
| 251 let proto = $clazz.prototype; |
| 252 let initMethod = proto[$name]; |
| 253 let ctor = function() { return initMethod.apply(this, arguments); }; |
| 254 ctor.prototype = proto; |
| 255 // Use defineProperty so we don't hit a property defined on Function, |
| 256 // like `caller` and `arguments`. |
| 257 $defineProperty($clazz, $name, { value: ctor, configurable: true }); |
| 258 })()'''); |
| 259 |
| 260 final _extensionType = JS('', 'Symbol("extensionType")'); |
| 261 |
| 262 final dartx = JS('', '{}'); |
| 263 |
| 264 getExtensionSymbol(name) => JS('', '''(() => { |
| 265 let sym = $dartx[$name]; |
| 266 if (!sym) $dartx[$name] = sym = Symbol('dartx.' + $name); |
| 267 return sym; |
| 268 })()'''); |
| 269 |
| 270 defineExtensionNames(names) => JS('', '$names.forEach($getExtensionSymbol)'); |
| 271 |
| 272 /// |
| 273 /// Copy symbols from the prototype of the source to destination. |
| 274 /// These are the only properties safe to copy onto an existing public |
| 275 /// JavaScript class. |
| 276 /// |
| 277 registerExtension(jsType, dartExtType) => JS('', '''(() => { |
| 278 let extProto = $dartExtType.prototype; |
| 279 let jsProto = $jsType.prototype; |
| 280 |
| 281 // Mark the JS type's instances so we can easily check for extensions. |
| 282 $assert_(jsProto[$_extensionType] === void 0); |
| 283 jsProto[$_extensionType] = extProto; |
| 284 |
| 285 let dartObjProto = $Object.prototype; |
| 286 while (extProto !== dartObjProto && extProto !== jsProto) { |
| 287 $copyTheseProperties(jsProto, extProto, $getOwnPropertySymbols(extProto)); |
| 288 extProto = extProto.__proto__; |
| 289 } |
| 290 let originalSigFn = $getOwnPropertyDescriptor($dartExtType, $_methodSig).get; |
| 291 $assert_(originalSigFn); |
| 292 $defineMemoizedGetter($jsType, $_methodSig, originalSigFn); |
| 293 })()'''); |
| 294 |
| 295 /// |
| 296 /// Mark a concrete type as implementing extension methods. |
| 297 /// For example: `class MyIter implements Iterable`. |
| 298 /// |
| 299 /// This takes a list of names, which are the extension methods implemented. |
| 300 /// It will add a forwarder, so the extension method name redirects to the |
| 301 /// normal Dart method name. For example: |
| 302 /// |
| 303 /// defineExtensionMembers(MyType, ['add', 'remove']); |
| 304 /// |
| 305 /// Results in: |
| 306 /// |
| 307 /// MyType.prototype[dartx.add] = MyType.prototype.add; |
| 308 /// MyType.prototype[dartx.remove] = MyType.prototype.remove; |
| 309 /// |
| 310 // TODO(jmesserly): essentially this gives two names to the same method. |
| 311 // This benefit is roughly equivalent call performance either way, but the |
| 312 // cost is we need to call defineExtensionMembers any time a subclass |
| 313 // overrides one of these methods. |
| 314 defineExtensionMembers(type, methodNames) => JS('', '''(() => { |
| 315 let proto = $type.prototype; |
| 316 for (let name of $methodNames) { |
| 317 let method = $getOwnPropertyDescriptor(proto, name); |
| 318 $defineProperty(proto, $getExtensionSymbol(name), method); |
| 319 } |
| 320 // Ensure the signature is available too. |
| 321 // TODO(jmesserly): not sure if we can do this in a cleaner way. Essentially |
| 322 // we need to copy the signature (and in the future, other data like |
| 323 // annotations) any time we copy a method as part of our metaprogramming. |
| 324 // It might be more friendly to JS metaprogramming if we include this info |
| 325 // on the function. |
| 326 let originalSigFn = $getOwnPropertyDescriptor($type, $_methodSig).get; |
| 327 $defineMemoizedGetter(type, $_methodSig, function() { |
| 328 let sig = originalSigFn(); |
| 329 for (let name of $methodNames) { |
| 330 sig[$getExtensionSymbol(name)] = sig[name]; |
| 331 } |
| 332 return sig; |
| 333 }); |
| 334 })()'''); |
| 335 |
| 336 canonicalMember(obj, name) => JS('', '''(() => { |
| 337 if ($obj != null && $obj[$_extensionType]) return $dartx[$name]; |
| 338 // Check for certain names that we can't use in JS |
| 339 if ($name == 'constructor' || $name == 'prototype') { |
| 340 $name = '+' + $name; |
| 341 } |
| 342 return $name; |
| 343 })()'''); |
| 344 |
| 345 /// Sets the type of `obj` to be `type` |
| 346 setType(obj, type) => JS('', '''(() => { |
| 347 $obj.__proto__ = $type.prototype; |
| 348 return $obj; |
| 349 })()'''); |
| 350 |
| 351 /// Sets the element type of a list literal. |
| 352 list(obj, elementType) => |
| 353 JS('', '$setType($obj, ${getGenericClass(JSArray)}($elementType))'); |
| 354 |
| 355 setBaseClass(derived, base) => JS('', '''(() => { |
| 356 // Link the extension to the type it's extending as a base class. |
| 357 $derived.prototype.__proto__ = $base.prototype; |
| 358 })()'''); |
OLD | NEW |