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 |