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