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