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