| 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 // TODO(vsm): Need to generate static methods. | |
| 190 if (!$f[name]) continue; | |
| 191 $tagMemoized($f[name], function() { | |
| 192 let parts = $f[$_staticSig][name]; | |
| 193 return $definiteFunctionType.apply(null, parts); | |
| 194 }) | |
| 195 } | |
| 196 })()'''); | |
| 197 | |
| 198 /// Set up the type signature of a class (constructor object) | |
| 199 /// f is a constructor object | |
| 200 /// signature is an object containing optional properties as follows: | |
| 201 /// methods: A function returning an object mapping method names | |
| 202 /// to method types. The function is evaluated lazily and cached. | |
| 203 /// statics: A function returning an object mapping static method | |
| 204 /// names to types. The function is evalutated lazily and cached. | |
| 205 /// names: An array of the names of the static methods. Used to | |
| 206 /// permit eagerly setting the runtimeType field on the methods | |
| 207 /// while still lazily computing the type descriptor object. | |
| 208 setSignature(f, signature) => JS('', '''(() => { | |
| 209 // TODO(ochafik): Deconstruct these when supported by Chrome. | |
| 210 let constructors = | |
| 211 ('constructors' in signature) ? signature.constructors : () => ({}); | |
| 212 let methods = | |
| 213 ('methods' in signature) ? signature.methods : () => ({}); | |
| 214 let statics = | |
| 215 ('statics' in signature) ? signature.statics : () => ({}); | |
| 216 let names = | |
| 217 ('names' in signature) ? signature.names : []; | |
| 218 $_setConstructorSignature($f, constructors); | |
| 219 $_setMethodSignature($f, methods); | |
| 220 $_setStaticSignature($f, statics); | |
| 221 $_setStaticTypes($f, names); | |
| 222 $tagMemoized($f, () => $Type); | |
| 223 })()'''); | |
| 224 | |
| 225 hasMethod(obj, name) => JS('', '$getMethodType($obj, $name) !== void 0'); | |
| 226 | |
| 227 /// | |
| 228 /// This is called whenever a derived class needs to introduce a new field, | |
| 229 /// shadowing a field or getter/setter pair on its parent. | |
| 230 /// | |
| 231 /// This is important because otherwise, trying to read or write the field | |
| 232 /// would end up calling the getter or setter, and one of those might not even | |
| 233 /// exist, resulting in a runtime error. Even if they did exist, that's the | |
| 234 /// wrong behavior if a new field was declared. | |
| 235 /// | |
| 236 virtualField(subclass, fieldName) => JS('', '''(() => { | |
| 237 // If the field is already overridden, do nothing. | |
| 238 let prop = $getOwnPropertyDescriptor($subclass.prototype, $fieldName); | |
| 239 if (prop) return; | |
| 240 | |
| 241 let symbol = Symbol($subclass.name + '.' + $fieldName.toString()); | |
| 242 $defineProperty($subclass.prototype, $fieldName, { | |
| 243 get: function() { return this[symbol]; }, | |
| 244 set: function(x) { this[symbol] = x; } | |
| 245 }); | |
| 246 })()'''); | |
| 247 | |
| 248 /// | |
| 249 /// Given a class and an initializer method name, creates a constructor | |
| 250 /// function with the same name. For example `new SomeClass.name(args)`. | |
| 251 /// | |
| 252 defineNamedConstructor(clazz, name) => JS('', '''(() => { | |
| 253 let proto = $clazz.prototype; | |
| 254 let initMethod = proto[$name]; | |
| 255 let ctor = function() { return initMethod.apply(this, arguments); }; | |
| 256 ctor.prototype = proto; | |
| 257 // Use defineProperty so we don't hit a property defined on Function, | |
| 258 // like `caller` and `arguments`. | |
| 259 $defineProperty($clazz, $name, { value: ctor, configurable: true }); | |
| 260 })()'''); | |
| 261 | |
| 262 final _extensionType = JS('', 'Symbol("extensionType")'); | |
| 263 | |
| 264 getExtensionType(obj) => JS('', '$obj[$_extensionType]'); | |
| 265 | |
| 266 final dartx = JS('', '{}'); | |
| 267 | |
| 268 getExtensionSymbol(name) => JS('', '''(() => { | |
| 269 let sym = $dartx[$name]; | |
| 270 if (!sym) $dartx[$name] = sym = Symbol('dartx.' + $name.toString()); | |
| 271 return sym; | |
| 272 })()'''); | |
| 273 | |
| 274 defineExtensionNames(names) => JS('', '$names.forEach($getExtensionSymbol)'); | |
| 275 | |
| 276 // Install properties in prototype order. Properties / descriptors from | |
| 277 // more specific types should overwrite ones from less specific types. | |
| 278 _installProperties(jsProto, extProto) => JS('', '''(() => { | |
| 279 if (extProto !== $Object.prototype && extProto !== jsProto) { | |
| 280 $_installProperties(jsProto, extProto.__proto__); | |
| 281 } | |
| 282 $copyTheseProperties(jsProto, extProto, $getOwnPropertySymbols(extProto)); | |
| 283 })()'''); | |
| 284 | |
| 285 /// | |
| 286 /// Copy symbols from the prototype of the source to destination. | |
| 287 /// These are the only properties safe to copy onto an existing public | |
| 288 /// JavaScript class. | |
| 289 /// | |
| 290 registerExtension(jsType, dartExtType) => JS('', '''(() => { | |
| 291 // TODO(vsm): Not all registered js types are real. | |
| 292 if (!jsType) return; | |
| 293 | |
| 294 let extProto = $dartExtType.prototype; | |
| 295 let jsProto = $jsType.prototype; | |
| 296 | |
| 297 // Mark the JS type's instances so we can easily check for extensions. | |
| 298 jsProto[$_extensionType] = $dartExtType; | |
| 299 $_installProperties(jsProto, extProto); | |
| 300 let originalSigFn = $getOwnPropertyDescriptor($dartExtType, $_methodSig).get; | |
| 301 $assert_(originalSigFn); | |
| 302 $defineMemoizedGetter($jsType, $_methodSig, originalSigFn); | |
| 303 })()'''); | |
| 304 | |
| 305 /// | |
| 306 /// Mark a concrete type as implementing extension methods. | |
| 307 /// For example: `class MyIter implements Iterable`. | |
| 308 /// | |
| 309 /// This takes a list of names, which are the extension methods implemented. | |
| 310 /// It will add a forwarder, so the extension method name redirects to the | |
| 311 /// normal Dart method name. For example: | |
| 312 /// | |
| 313 /// defineExtensionMembers(MyType, ['add', 'remove']); | |
| 314 /// | |
| 315 /// Results in: | |
| 316 /// | |
| 317 /// MyType.prototype[dartx.add] = MyType.prototype.add; | |
| 318 /// MyType.prototype[dartx.remove] = MyType.prototype.remove; | |
| 319 /// | |
| 320 // TODO(jmesserly): essentially this gives two names to the same method. | |
| 321 // This benefit is roughly equivalent call performance either way, but the | |
| 322 // cost is we need to call defineExtensionMembers any time a subclass | |
| 323 // overrides one of these methods. | |
| 324 defineExtensionMembers(type, methodNames) => JS('', '''(() => { | |
| 325 let proto = $type.prototype; | |
| 326 for (let name of $methodNames) { | |
| 327 let method = $getOwnPropertyDescriptor(proto, name); | |
| 328 // TODO(vsm): We should be able to generate code to avoid this case. | |
| 329 // The method may be null if this type implements a potentially native | |
| 330 // interface but isn't native itself. For a field on this type, we're not | |
| 331 // generating a corresponding getter/setter method - it's just a field. | |
| 332 if (!method) continue; | |
| 333 $defineProperty(proto, $getExtensionSymbol(name), method); | |
| 334 } | |
| 335 // Ensure the signature is available too. | |
| 336 // TODO(jmesserly): not sure if we can do this in a cleaner way. Essentially | |
| 337 // we need to copy the signature (and in the future, other data like | |
| 338 // annotations) any time we copy a method as part of our metaprogramming. | |
| 339 // It might be more friendly to JS metaprogramming if we include this info | |
| 340 // on the function. | |
| 341 let originalSigFn = $getOwnPropertyDescriptor($type, $_methodSig).get; | |
| 342 $defineMemoizedGetter(type, $_methodSig, function() { | |
| 343 let sig = originalSigFn(); | |
| 344 for (let name of $methodNames) { | |
| 345 sig[$getExtensionSymbol(name)] = sig[name]; | |
| 346 } | |
| 347 return sig; | |
| 348 }); | |
| 349 })()'''); | |
| 350 | |
| 351 canonicalMember(obj, name) => JS('', '''(() => { | |
| 352 if ($obj != null && $obj[$_extensionType]) return $dartx[$name]; | |
| 353 // Check for certain names that we can't use in JS | |
| 354 if ($name == 'constructor' || $name == 'prototype') { | |
| 355 $name = '+' + $name; | |
| 356 } | |
| 357 return $name; | |
| 358 })()'''); | |
| 359 | |
| 360 /// Sets the type of `obj` to be `type` | |
| 361 setType(obj, type) => JS('', '''(() => { | |
| 362 $obj.__proto__ = $type.prototype; | |
| 363 // TODO(vsm): This should be set in registerExtension, but that is only | |
| 364 // invoked on the generic type (e.g., JSArray<dynamic>, not JSArray<int>). | |
| 365 $obj.__proto__[$_extensionType] = $type; | |
| 366 return $obj; | |
| 367 })()'''); | |
| 368 | |
| 369 /// Sets the element type of a list literal. | |
| 370 list(obj, elementType) => | |
| 371 JS('', '$setType($obj, ${getGenericClass(JSArray)}($elementType))'); | |
| 372 | |
| 373 setBaseClass(derived, base) => JS('', '''(() => { | |
| 374 // Link the extension to the type it's extending as a base class. | |
| 375 $derived.prototype.__proto__ = $base.prototype; | |
| 376 })()'''); | |
| OLD | NEW |