| 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_runtime/_operations', null, /* Imports */[ | |
| 9 ], /* Lazy Imports */[ | |
| 10 'dart/async', | |
| 11 'dart/collection', | |
| 12 'dart/core', | |
| 13 'dart/_js_helper', | |
| 14 'dart_runtime/_classes', | |
| 15 'dart_runtime/_errors', | |
| 16 'dart_runtime/_rtti', | |
| 17 'dart_runtime/_types' | |
| 18 ], function(exports, async, collection, core, _js_helper, classes, errors, rtti, | |
| 19 types) { | |
| 20 'use strict'; | |
| 21 | |
| 22 const getOwnNamesAndSymbols = dart_utils.getOwnNamesAndSymbols; | |
| 23 const throwError = dart_utils.throwError; | |
| 24 | |
| 25 const getOwnPropertyNames = Object.getOwnPropertyNames; | |
| 26 const hasOwnProperty = Object.prototype.hasOwnProperty; | |
| 27 | |
| 28 const slice = [].slice; | |
| 29 | |
| 30 function _canonicalFieldName(obj, name, args, displayName) { | |
| 31 name = classes.canonicalMember(obj, name); | |
| 32 if (name) return name; | |
| 33 // TODO(jmesserly): in the future we might have types that "overlay" Dart | |
| 34 // methods while also exposing the full native API, e.g. dart:html vs | |
| 35 // dart:dom. To support that we'd need to fall back to the normal name | |
| 36 // if an extension method wasn't found. | |
| 37 errors.throwNoSuchMethod(obj, displayName, args); | |
| 38 } | |
| 39 | |
| 40 function dload(obj, field) { | |
| 41 field = _canonicalFieldName(obj, field, [], field); | |
| 42 if (classes.hasMethod(obj, field)) { | |
| 43 return classes.bind(obj, field); | |
| 44 } | |
| 45 // TODO(vsm): Implement NSM robustly. An 'in' check breaks on certain | |
| 46 // types. hasOwnProperty doesn't chase the proto chain. | |
| 47 // Also, do we want an NSM on regular JS objects? | |
| 48 // See: https://github.com/dart-lang/dev_compiler/issues/169 | |
| 49 let result = obj[field]; | |
| 50 | |
| 51 // TODO(vsm): Check this more robustly. | |
| 52 if (typeof result == "function" && !hasOwnProperty.call(obj, field)) { | |
| 53 // This appears to be a method tearoff. Bind this. | |
| 54 return result.bind(obj); | |
| 55 } | |
| 56 return result; | |
| 57 } | |
| 58 exports.dload = dload; | |
| 59 | |
| 60 function dput(obj, field, value) { | |
| 61 field = _canonicalFieldName(obj, field, [value], field); | |
| 62 // TODO(vsm): Implement NSM and type checks. | |
| 63 // See: https://github.com/dart-lang/dev_compiler/issues/170 | |
| 64 obj[field] = value; | |
| 65 return value; | |
| 66 } | |
| 67 exports.dput = dput; | |
| 68 | |
| 69 | |
| 70 /// Check that a function of a given type can be applied to | |
| 71 /// actuals. | |
| 72 function checkApply(type, actuals) { | |
| 73 if (actuals.length < type.args.length) return false; | |
| 74 let index = 0; | |
| 75 for(let i = 0; i < type.args.length; ++i) { | |
| 76 if (!instanceOfOrNull(actuals[i], type.args[i])) return false; | |
| 77 ++index; | |
| 78 } | |
| 79 if (actuals.length == type.args.length) return true; | |
| 80 let extras = actuals.length - type.args.length; | |
| 81 if (type.optionals.length > 0) { | |
| 82 if (extras > type.optionals.length) return false; | |
| 83 for(let i = 0, j=index; i < extras; ++i, ++j) { | |
| 84 if (!instanceOfOrNull(actuals[j], type.optionals[i])) return false; | |
| 85 } | |
| 86 return true; | |
| 87 } | |
| 88 // TODO(leafp): We can't tell when someone might be calling | |
| 89 // something expecting an optional argument with named arguments | |
| 90 | |
| 91 if (extras != 1) return false; | |
| 92 // An empty named list means no named arguments | |
| 93 if (getOwnPropertyNames(type.named).length == 0) return false; | |
| 94 let opts = actuals[index]; | |
| 95 let names = getOwnPropertyNames(opts); | |
| 96 // Type is something other than a map | |
| 97 if (names.length == 0) return false; | |
| 98 for (var name of names) { | |
| 99 if (!(hasOwnProperty.call(type.named, name))) { | |
| 100 return false; | |
| 101 } | |
| 102 if (!instanceOfOrNull(opts[name], type.named[name])) return false; | |
| 103 } | |
| 104 return true; | |
| 105 } | |
| 106 | |
| 107 function throwNoSuchMethod(obj, name, args, opt_func) { | |
| 108 if (obj === void 0) obj = opt_func; | |
| 109 errors.throwNoSuchMethod(obj, name, args); | |
| 110 } | |
| 111 | |
| 112 function checkAndCall(f, ftype, obj, args, name) { | |
| 113 if (!(f instanceof Function)) { | |
| 114 // We're not a function (and hence not a method either) | |
| 115 // Grab the `call` method if it's not a function. | |
| 116 if (f != null) { | |
| 117 ftype = classes.getMethodType(f, 'call'); | |
| 118 f = f.call; | |
| 119 } | |
| 120 if (!(f instanceof Function)) { | |
| 121 throwNoSuchMethod(obj, name, args); | |
| 122 } | |
| 123 } | |
| 124 // If f is a function, but not a method (no method type) | |
| 125 // then it should have been a function valued field, so | |
| 126 // get the type from the function. | |
| 127 if (ftype === void 0) { | |
| 128 ftype = rtti.read(f); | |
| 129 } | |
| 130 | |
| 131 if (!ftype) { | |
| 132 // TODO(leafp): Allow JS objects to go through? | |
| 133 // This includes the DOM. | |
| 134 return f.apply(obj, args); | |
| 135 } | |
| 136 | |
| 137 if (checkApply(ftype, args)) { | |
| 138 return f.apply(obj, args); | |
| 139 } | |
| 140 | |
| 141 // TODO(leafp): throw a type error (rather than NSM) | |
| 142 // if the arity matches but the types are wrong. | |
| 143 throwNoSuchMethod(obj, name, args, f); | |
| 144 } | |
| 145 | |
| 146 function dcall(f/*, ...args*/) { | |
| 147 let args = slice.call(arguments, 1); | |
| 148 let ftype = rtti.read(f); | |
| 149 return checkAndCall(f, ftype, void 0, args, 'call'); | |
| 150 } | |
| 151 exports.dcall = dcall; | |
| 152 | |
| 153 /** Shared code for dsend, dindex, and dsetindex. */ | |
| 154 function callMethod(obj, name, args, displayName) { | |
| 155 let symbol = _canonicalFieldName(obj, name, args, displayName); | |
| 156 let f = obj != null ? obj[symbol] : null; | |
| 157 let ftype = classes.getMethodType(obj, name); | |
| 158 return checkAndCall(f, ftype, obj, args, displayName); | |
| 159 } | |
| 160 | |
| 161 function dsend(obj, method/*, ...args*/) { | |
| 162 return callMethod(obj, method, slice.call(arguments, 2), method); | |
| 163 } | |
| 164 exports.dsend = dsend; | |
| 165 | |
| 166 function dindex(obj, index) { | |
| 167 return callMethod(obj, 'get', [index], '[]'); | |
| 168 } | |
| 169 exports.dindex = dindex; | |
| 170 | |
| 171 function dsetindex(obj, index, value) { | |
| 172 callMethod(obj, 'set', [index, value], '[]='); | |
| 173 return value; | |
| 174 } | |
| 175 exports.dsetindex = dsetindex; | |
| 176 | |
| 177 function _ignoreTypeFailure(actual, type) { | |
| 178 // TODO(vsm): Remove this hack ... | |
| 179 // This is primarily due to the lack of generic methods, | |
| 180 // but we need to triage all the errors. | |
| 181 let isSubtype = types.isSubtype; | |
| 182 if (isSubtype(type, core.Iterable) && isSubtype(actual, core.Iterable) || | |
| 183 isSubtype(type, async.Future) && isSubtype(actual, async.Future) || | |
| 184 isSubtype(type, core.Map) && isSubtype(actual, core.Map) || | |
| 185 isSubtype(type, core.Function) && isSubtype(actual, core.Function) || | |
| 186 isSubtype(type, async.Stream) && isSubtype(actual, async.Stream) || | |
| 187 isSubtype(type, async.StreamSubscription) && | |
| 188 isSubtype(actual, async.StreamSubscription)) { | |
| 189 console.warn('Ignoring cast fail from ' + types.typeName(actual) + | |
| 190 ' to ' + types.typeName(type)); | |
| 191 return true; | |
| 192 } | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 196 function strongInstanceOf(obj, type) { | |
| 197 let actual = rtti.realRuntimeType(obj); | |
| 198 return types.isSubtype(actual, type) || actual == types.jsobject; | |
| 199 } | |
| 200 exports.strongInstanceOf = strongInstanceOf; | |
| 201 | |
| 202 function instanceOfOrNull(obj, type) { | |
| 203 if ((obj == null) || strongInstanceOf(obj, type)) return true; | |
| 204 return false; | |
| 205 } | |
| 206 | |
| 207 function instanceOf(obj, type) { | |
| 208 if (strongInstanceOf(obj, type)) return true; | |
| 209 // TODO(#296): This is perhaps too eager to throw a StrongModeError? | |
| 210 // It will throw on <int>[] is List<String>. | |
| 211 // TODO(vsm): We can statically detect many cases where this | |
| 212 // check is unnecessary. | |
| 213 if (types.isGroundType(type)) return false; | |
| 214 let actual = rtti.realRuntimeType(obj); | |
| 215 dart_utils.throwStrongModeError('Strong mode is check failure: ' + | |
| 216 types.typeName(actual) + ' does not soundly subtype ' + | |
| 217 types.typeName(type)); | |
| 218 } | |
| 219 exports.instanceOf = instanceOf; | |
| 220 | |
| 221 function cast(obj, type) { | |
| 222 // TODO(#296): This is perhaps too eager to throw a StrongModeError? | |
| 223 // TODO(vsm): handle non-nullable types | |
| 224 if (instanceOfOrNull(obj, type)) return obj; | |
| 225 let actual = rtti.realRuntimeType(obj); | |
| 226 if (types.isGroundType(type)) errors.throwCastError(actual, type); | |
| 227 | |
| 228 if (_ignoreTypeFailure(actual, type)) return obj; | |
| 229 | |
| 230 dart_utils.throwStrongModeError('Strong mode cast failure from ' + | |
| 231 types.typeName(actual) + ' to ' + types.typeName(type)); | |
| 232 } | |
| 233 exports.cast = cast; | |
| 234 | |
| 235 function asInt(obj) { | |
| 236 if (Math.floor(obj) != obj) { | |
| 237 // Note: null will also be caught by this check | |
| 238 errors.throwCastError(rtti.realRuntimeType(obj), core.int); | |
| 239 } | |
| 240 return obj; | |
| 241 } | |
| 242 exports.asInt = asInt; | |
| 243 | |
| 244 function arity(f) { | |
| 245 // TODO(jmesserly): need to parse optional params. | |
| 246 // In ES6, length is the number of required arguments. | |
| 247 return { min: f.length, max: f.length }; | |
| 248 } | |
| 249 exports.arity = arity; | |
| 250 | |
| 251 function equals(x, y) { | |
| 252 if (x == null || y == null) return x == y; | |
| 253 let eq = x['==']; | |
| 254 return eq ? eq.call(x, y) : x === y; | |
| 255 } | |
| 256 exports.equals = equals; | |
| 257 | |
| 258 /** Checks that `x` is not null or undefined. */ | |
| 259 function notNull(x) { | |
| 260 if (x == null) errors.throwNullValueError(); | |
| 261 return x; | |
| 262 } | |
| 263 exports.notNull = notNull; | |
| 264 | |
| 265 /** | |
| 266 * Creates a dart:collection LinkedHashMap. | |
| 267 * | |
| 268 * For a map with string keys an object literal can be used, for example | |
| 269 * `map({'hi': 1, 'there': 2})`. | |
| 270 * | |
| 271 * Otherwise an array should be used, for example `map([1, 2, 3, 4])` will | |
| 272 * create a map with keys [1, 3] and values [2, 4]. Each key-value pair | |
| 273 * should be adjacent entries in the array. | |
| 274 * | |
| 275 * For a map with no keys the function can be called with no arguments, for | |
| 276 * example `map()`. | |
| 277 */ | |
| 278 // TODO(jmesserly): this could be faster | |
| 279 function map(values) { | |
| 280 let map = collection.LinkedHashMap.new(); | |
| 281 if (Array.isArray(values)) { | |
| 282 for (let i = 0, end = values.length - 1; i < end; i += 2) { | |
| 283 let key = values[i]; | |
| 284 let value = values[i + 1]; | |
| 285 map.set(key, value); | |
| 286 } | |
| 287 } else if (typeof values === 'object') { | |
| 288 for (let key of getOwnPropertyNames(values)) { | |
| 289 map.set(key, values[key]); | |
| 290 } | |
| 291 } | |
| 292 return map; | |
| 293 } | |
| 294 exports.map = map; | |
| 295 | |
| 296 function assert(condition) { | |
| 297 if (!condition) errors.throwAssertionError(); | |
| 298 } | |
| 299 exports.assert = assert; | |
| 300 | |
| 301 let _stack = new WeakMap(); | |
| 302 function throw_(obj) { | |
| 303 if (obj != null && (typeof obj == 'object' || typeof obj == 'function')) { | |
| 304 // TODO(jmesserly): couldn't we store the most recent stack in a single | |
| 305 // variable? There should only be one active stack trace. That would | |
| 306 // allow it to work for things like strings and numbers. | |
| 307 _stack.set(obj, new Error()); | |
| 308 } | |
| 309 throw obj; | |
| 310 } | |
| 311 exports.throw = throw_; | |
| 312 | |
| 313 function getError(exception) { | |
| 314 var stack = _stack.get(exception); | |
| 315 return stack !== void 0 ? stack : exception; | |
| 316 } | |
| 317 | |
| 318 // This is a utility function: it is only intended to be called from dev | |
| 319 // tools. | |
| 320 function stackPrint(exception) { | |
| 321 var error = getError(exception); | |
| 322 console.log(error.stack ? error.stack : 'No stack trace for: ' + error); | |
| 323 } | |
| 324 exports.stackPrint = stackPrint; | |
| 325 | |
| 326 function stackTrace(exception) { | |
| 327 var error = getError(exception); | |
| 328 return _js_helper.getTraceFromException(error); | |
| 329 } | |
| 330 exports.stackTrace = stackTrace; | |
| 331 | |
| 332 /** | |
| 333 * Implements a sequence of .? operations. | |
| 334 * | |
| 335 * Will call each successive callback, unless one returns null, which stops | |
| 336 * the sequence. | |
| 337 */ | |
| 338 function nullSafe(obj /*, ...callbacks*/) { | |
| 339 let callbacks = slice.call(arguments, 1); | |
| 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 |