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 runtime operations on objects used by the code | |
6 /// generator. | |
7 part of dart._runtime; | |
8 | |
9 _canonicalFieldName(obj, name, args, displayName) => JS('', '''(() => { | |
10 $name = $canonicalMember($obj, $name); | |
11 if ($name) return $name; | |
12 // TODO(jmesserly): in the future we might have types that "overlay" Dart | |
13 // methods while also exposing the full native API, e.g. dart:html vs | |
14 // dart:dom. To support that we'd need to fall back to the normal name | |
15 // if an extension method wasn't found. | |
16 $throwNoSuchMethodFunc($obj, $displayName, $args); | |
17 })()'''); | |
18 | |
19 dload(obj, field) => JS('', '''(() => { | |
20 $field = $_canonicalFieldName($obj, $field, [], $field); | |
21 if ($hasMethod($obj, $field)) { | |
22 return $bind($obj, $field); | |
23 } | |
24 // TODO(vsm): Implement NSM robustly. An 'in' check breaks on certain | |
25 // types. hasOwnProperty doesn't chase the proto chain. | |
26 // Also, do we want an NSM on regular JS objects? | |
27 // See: https://github.com/dart-lang/dev_compiler/issues/169 | |
28 let result = $obj[$field]; | |
29 return result; | |
30 })()'''); | |
31 | |
32 dput(obj, field, value) => JS('', '''(() => { | |
33 $field = $_canonicalFieldName($obj, $field, [$value], $field); | |
34 // TODO(vsm): Implement NSM and type checks. | |
35 // See: https://github.com/dart-lang/dev_compiler/issues/170 | |
36 $obj[$field] = $value; | |
37 return $value; | |
38 })()'''); | |
39 | |
40 /// Check that a function of a given type can be applied to | |
41 /// actuals. | |
42 checkApply(type, actuals) => JS('', '''(() => { | |
43 if ($actuals.length < $type.args.length) return false; | |
44 let index = 0; | |
45 for(let i = 0; i < $type.args.length; ++i) { | |
46 if (!$instanceOfOrNull($actuals[i], $type.args[i])) return false; | |
47 ++index; | |
48 } | |
49 if ($actuals.length == $type.args.length) return true; | |
50 let extras = $actuals.length - $type.args.length; | |
51 if ($type.optionals.length > 0) { | |
52 if (extras > $type.optionals.length) return false; | |
53 for(let i = 0, j=index; i < extras; ++i, ++j) { | |
54 if (!$instanceOfOrNull($actuals[j], $type.optionals[i])) return false; | |
55 } | |
56 return true; | |
57 } | |
58 // TODO(leafp): We can't tell when someone might be calling | |
59 // something expecting an optional argument with named arguments | |
60 | |
61 if (extras != 1) return false; | |
62 // An empty named list means no named arguments | |
63 if ($getOwnPropertyNames($type.named).length == 0) return false; | |
64 let opts = $actuals[index]; | |
65 let names = $getOwnPropertyNames(opts); | |
66 // Type is something other than a map | |
67 if (names.length == 0) return false; | |
68 for (var name of names) { | |
69 if (!($hasOwnProperty.call($type.named, name))) { | |
70 return false; | |
71 } | |
72 if (!$instanceOfOrNull(opts[name], $type.named[name])) return false; | |
73 } | |
74 return true; | |
75 })()'''); | |
76 | |
77 | |
78 throwNoSuchMethod(obj, name, pArgs, nArgs, extras) => JS('', '''(() => { | |
79 $throw_(new $NoSuchMethodError($obj, $name, $pArgs, $nArgs, $extras)); | |
80 })()'''); | |
81 | |
82 throwNoSuchMethodFunc(obj, name, pArgs, opt_func) => JS('', '''(() => { | |
83 if ($obj === void 0) $obj = $opt_func; | |
84 $throwNoSuchMethod($obj, $name, $pArgs); | |
85 })()'''); | |
86 | |
87 checkAndCall(f, ftype, obj, args, name) => JS('', '''(() => { | |
88 let originalFunction = $f; | |
89 if (!($f instanceof Function)) { | |
90 // We're not a function (and hence not a method either) | |
91 // Grab the `call` method if it's not a function. | |
92 if ($f != null) { | |
93 $ftype = $getMethodType($f, 'call'); | |
94 $f = $f.call; | |
95 } | |
96 if (!($f instanceof Function)) { | |
97 $throwNoSuchMethodFunc($obj, $name, $args, originalFunction); | |
98 } | |
99 } | |
100 // If f is a function, but not a method (no method type) | |
101 // then it should have been a function valued field, so | |
102 // get the type from the function. | |
103 if ($ftype === void 0) { | |
104 $ftype = $read($f); | |
105 } | |
106 | |
107 if (!$ftype) { | |
108 // TODO(leafp): Allow JS objects to go through? | |
109 // This includes the DOM. | |
110 return $f.apply($obj, $args); | |
111 } | |
112 | |
113 if ($checkApply($ftype, $args)) { | |
114 return $f.apply($obj, $args); | |
115 } | |
116 | |
117 // TODO(leafp): throw a type error (rather than NSM) | |
118 // if the arity matches but the types are wrong. | |
119 $throwNoSuchMethodFunc($obj, $name, $args, originalFunction); | |
120 })()'''); | |
121 | |
122 dcall(f, @rest args) => JS('', '''(() => { | |
123 let ftype = $read($f); | |
124 return $checkAndCall($f, ftype, void 0, $args, 'call'); | |
125 })()'''); | |
126 | |
127 /// Shared code for dsend, dindex, and dsetindex. */ | |
128 callMethod(obj, name, args, displayName) => JS('', '''(() => { | |
129 let symbol = $_canonicalFieldName($obj, $name, $args, $displayName); | |
130 let f = $obj != null ? $obj[symbol] : null; | |
131 let ftype = $getMethodType($obj, $name); | |
132 return $checkAndCall(f, ftype, $obj, $args, $displayName); | |
133 })()'''); | |
134 | |
135 dsend(obj, method, @rest args) => JS('', '''(() => { | |
136 return $callMethod($obj, $method, $args, $method); | |
137 })()'''); | |
138 | |
139 dindex(obj, index) => JS('', '''(() => { | |
140 return $callMethod($obj, 'get', [$index], '[]'); | |
141 })()'''); | |
142 | |
143 dsetindex(obj, index, value) => JS('', '''(() => { | |
144 $callMethod($obj, 'set', [$index, $value], '[]='); | |
145 return $value; | |
146 })()'''); | |
147 | |
148 _ignoreTypeFailure(actual, type) => JS('', '''(() => { | |
149 // TODO(vsm): Remove this hack ... | |
150 // This is primarily due to the lack of generic methods, | |
151 // but we need to triage all the types. | |
152 if (isSubtype($type, $Iterable) && isSubtype($actual, $Iterable) || | |
153 isSubtype($type, $Future) && isSubtype($actual, $Future) || | |
154 isSubtype($type, $Map) && isSubtype($actual, $Map) || | |
155 isSubtype($type, $Function) && isSubtype($actual, $Function) || | |
156 isSubtype($type, $Stream) && isSubtype($actual, $Stream) || | |
157 isSubtype($type, $StreamSubscription) && | |
158 isSubtype($actual, $StreamSubscription)) { | |
159 console.warn('Ignoring cast fail from ' + $typeName($actual) + | |
160 ' to ' + $typeName($type)); | |
161 return true; | |
162 } | |
163 return false; | |
164 })()'''); | |
165 | |
166 strongInstanceOf(obj, type, ignoreFromWhiteList) => JS('', '''(() => { | |
167 let actual = $realRuntimeType($obj); | |
168 if ($isSubtype(actual, $type) || actual == $jsobject || | |
169 actual == $int && type == $double) return true; | |
170 if ($ignoreFromWhiteList == void 0) return false; | |
171 if ($isGroundType($type)) return false; | |
172 if ($_ignoreTypeFailure(actual, $type)) return true; | |
173 return false; | |
174 })()'''); | |
175 | |
176 instanceOfOrNull(obj, type) => JS('', '''(() => { | |
177 if (($obj == null) || $strongInstanceOf($obj, $type, true)) return true; | |
178 return false; | |
179 })()'''); | |
180 | |
181 instanceOf(obj, type) => JS('', '''(() => { | |
182 if ($strongInstanceOf($obj, $type)) return true; | |
183 // TODO(#296): This is perhaps too eager to throw a StrongModeError? | |
184 // It will throw on <int>[] is List<String>. | |
185 // TODO(vsm): We can statically detect many cases where this | |
186 // check is unnecessary. | |
187 if ($isGroundType($type)) return false; | |
188 let actual = $realRuntimeType($obj); | |
189 $throwStrongModeError('Strong mode is check failure: ' + | |
190 $typeName(actual) + ' does not soundly subtype ' + | |
191 $typeName($type)); | |
192 })()'''); | |
193 | |
194 cast(obj, type) => JS('', '''(() => { | |
195 // TODO(#296): This is perhaps too eager to throw a StrongModeError? | |
196 // TODO(vsm): handle non-nullable types | |
197 if ($instanceOfOrNull($obj, $type)) return $obj; | |
198 let actual = $realRuntimeType($obj); | |
199 if ($isGroundType($type)) $throwCastError(actual, $type); | |
200 | |
201 if ($_ignoreTypeFailure(actual, $type)) return $obj; | |
202 | |
203 $throwStrongModeError('Strong mode cast failure from ' + | |
204 $typeName(actual) + ' to ' + $typeName($type)); | |
205 })()'''); | |
206 | |
207 asInt(obj) => JS('', '''(() => { | |
208 if ($obj == null) { | |
209 return null; | |
210 } | |
211 if (Math.floor($obj) != $obj) { | |
212 // Note: null will also be caught by this check | |
213 $throwCastError($realRuntimeType($obj), $int); | |
214 } | |
215 return $obj; | |
216 })()'''); | |
217 | |
218 arity(f) => JS('', '''(() => { | |
219 // TODO(jmesserly): need to parse optional params. | |
220 // In ES6, length is the number of required arguments. | |
221 return { min: $f.length, max: $f.length }; | |
222 })()'''); | |
223 | |
224 equals(x, y) => JS('', '''(() => { | |
225 if ($x == null || $y == null) return $x == $y; | |
226 let eq = $x['==']; | |
227 return eq ? eq.call($x, $y) : $x === $y; | |
228 })()'''); | |
229 | |
230 /// Checks that `x` is not null or undefined. */ | |
231 notNull(x) => JS('', '''(() => { | |
232 if ($x == null) $throwNullValueError(); | |
233 return $x; | |
234 })()'''); | |
235 | |
236 /// | |
237 /// Creates a dart:collection LinkedHashMap. | |
238 /// | |
239 /// For a map with string keys an object literal can be used, for example | |
240 /// `map({'hi': 1, 'there': 2})`. | |
241 /// | |
242 /// Otherwise an array should be used, for example `map([1, 2, 3, 4])` will | |
243 /// create a map with keys [1, 3] and values [2, 4]. Each key-value pair | |
244 /// should be adjacent entries in the array. | |
245 /// | |
246 /// For a map with no keys the function can be called with no arguments, for | |
247 /// example `map()`. | |
248 /// | |
249 // TODO(jmesserly): this could be faster | |
250 // TODO(jmesserly): we can use default values `= dynamic` once #417 is fixed. | |
251 // TODO(jmesserly): move this to classes for consistentcy with list literals? | |
252 map(values, [K, V]) => JS('', '''(() => { | |
253 if ($K == null) $K = $dynamicR; | |
254 if ($V == null) $V = $dynamicR; | |
255 let map = ${getGenericClass(LinkedHashMap)}($K, $V).new(); | |
256 if (Array.isArray($values)) { | |
257 for (let i = 0, end = $values.length - 1; i < end; i += 2) { | |
258 let key = $values[i]; | |
259 let value = $values[i + 1]; | |
260 map.set(key, value); | |
261 } | |
262 } else if (typeof $values === 'object') { | |
263 for (let key of $getOwnPropertyNames($values)) { | |
264 map.set(key, $values[key]); | |
265 } | |
266 } | |
267 return map; | |
268 })()'''); | |
269 | |
270 @JSExportName('assert') | |
271 assert_(condition) => JS('', '''(() => { | |
272 if (!$condition) $throwAssertionError(); | |
273 })()'''); | |
274 | |
275 final _stack = JS('', 'new WeakMap()'); | |
276 @JSExportName('throw') | |
277 throw_(obj) => JS('', '''(() => { | |
278 if ($obj != null && (typeof $obj == 'object' || typeof $obj == 'function')) { | |
279 // TODO(jmesserly): couldn't we store the most recent stack in a single | |
280 // variable? There should only be one active stack trace. That would | |
281 // allow it to work for things like strings and numbers. | |
282 $_stack.set($obj, new Error()); | |
283 } | |
284 throw $obj; | |
285 })()'''); | |
286 | |
287 getError(exception) => JS('', '''(() => { | |
288 var stack = $_stack.get($exception); | |
289 return stack !== void 0 ? stack : $exception; | |
290 })()'''); | |
291 | |
292 // This is a utility function: it is only intended to be called from dev | |
293 // tools. | |
294 stackPrint(exception) => JS('', '''(() => { | |
295 var error = $getError($exception); | |
296 console.log(error.stack ? error.stack : 'No stack trace for: ' + error); | |
297 })()'''); | |
298 | |
299 stackTrace(exception) => JS('', '''(() => { | |
300 var error = $getError($exception); | |
301 return $getTraceFromException(error); | |
302 })()'''); | |
303 | |
304 /// | |
305 /// Implements a sequence of .? operations. | |
306 /// | |
307 /// Will call each successive callback, unless one returns null, which stops | |
308 /// the sequence. | |
309 /// | |
310 nullSafe(obj, @rest callbacks) => JS('', '''(() => { | |
311 if ($obj == null) return $obj; | |
312 for (const callback of $callbacks) { | |
313 $obj = callback($obj); | |
314 if ($obj == null) break; | |
315 } | |
316 return $obj; | |
317 })()'''); | |
318 | |
319 final _value = JS('', 'Symbol("_value")'); | |
320 /// | |
321 /// Looks up a sequence of [keys] in [map], recursively, and | |
322 /// returns the result. If the value is not found, [valueFn] will be called to | |
323 /// add it. For example: | |
324 /// | |
325 /// let map = new Map(); | |
326 /// putIfAbsent(map, [1, 2, 'hi ', 'there '], () => 'world'); | |
327 /// | |
328 /// ... will create a Map with a structure like: | |
329 /// | |
330 /// { 1: { 2: { 'hi ': { 'there ': 'world' } } } } | |
331 /// | |
332 multiKeyPutIfAbsent(map, keys, valueFn) => JS('', '''(() => { | |
333 for (let k of $keys) { | |
334 let value = $map.get(k); | |
335 if (!value) { | |
336 // TODO(jmesserly): most of these maps are very small (e.g. 1 item), | |
337 // so it may be worth optimizing for that. | |
338 $map.set(k, value = new Map()); | |
339 } | |
340 $map = value; | |
341 } | |
342 if ($map.has($_value)) return $map.get($_value); | |
343 let value = $valueFn(); | |
344 $map.set($_value, value); | |
345 return value; | |
346 })()'''); | |
347 | |
348 /// The global constant table. */ | |
349 final constants = JS('', 'new Map()'); | |
350 | |
351 /// | |
352 /// Canonicalize a constant object. | |
353 /// | |
354 /// Preconditions: | |
355 /// - `obj` is an objects or array, not a primitive. | |
356 /// - nested values of the object are themselves already canonicalized. | |
357 /// | |
358 @JSExportName('const') | |
359 const_(obj) => JS('', '''(() => { | |
360 let objectKey = [$realRuntimeType($obj)]; | |
361 // TODO(jmesserly): there's no guarantee in JS that names/symbols are | |
362 // returned in the same order. | |
363 // | |
364 // We could probably get the same order if we're judicious about | |
365 // initializing fields in a consistent order across all const constructors. | |
366 // Alternatively we need a way to sort them to make consistent. | |
367 // | |
368 // Right now we use the (name,value) pairs in sequence, which prevents | |
369 // an object with incorrect field values being returned, but won't | |
370 // canonicalize correctly if key order is different. | |
371 for (let name of $getOwnNamesAndSymbols($obj)) { | |
372 objectKey.push(name); | |
373 objectKey.push($obj[name]); | |
374 } | |
375 return $multiKeyPutIfAbsent($constants, objectKey, () => $obj); | |
376 })()'''); | |
377 | |
378 | |
379 // The following are helpers for Object methods when the receiver | |
380 // may be null or primitive. These should only be generated by | |
381 // the compiler. | |
382 hashCode(obj) => JS('', '''(() => { | |
383 if ($obj == null) { | |
384 return 0; | |
385 } | |
386 // TODO(vsm): What should we do for primitives and non-Dart objects? | |
387 switch (typeof $obj) { | |
388 case "number": | |
389 case "boolean": | |
390 return $obj & 0x1FFFFFFF; | |
391 case "string": | |
392 // TODO(vsm): Call the JSString hashCode? | |
393 return $obj.length; | |
394 } | |
395 return $obj.hashCode; | |
396 })()'''); | |
397 | |
398 toString(obj) => JS('', '''(() => { | |
399 if ($obj == null) { | |
400 return "null"; | |
401 } | |
402 return $obj.toString(); | |
403 })()'''); | |
404 | |
405 noSuchMethod(obj, invocation) => JS('', '''(() => { | |
406 if ($obj == null) { | |
407 $throwNoSuchMethod($obj, $invocation.memberName, | |
408 $invocation.positionalArguments, $invocation.namedArguments); | |
409 } | |
410 switch (typeof $obj) { | |
411 case "number": | |
412 case "boolean": | |
413 case "string": | |
414 $throwNoSuchMethod($obj, $invocation.memberName, | |
415 $invocation.positionalArguments, $invocation.namedArguments); | |
416 } | |
417 return $obj.noSuchMethod($invocation); | |
418 })()'''); | |
419 | |
420 final JsIterator = JS('', ''' | |
421 class JsIterator { | |
422 constructor(dartIterator) { | |
423 this.dartIterator = dartIterator; | |
424 } | |
425 next() { | |
426 let i = this.dartIterator; | |
427 let done = !i.moveNext(); | |
428 return { done: done, value: done ? void 0 : i.current }; | |
429 } | |
430 } | |
431 '''); | |
OLD | NEW |