Index: tool/input_sdk/private/operations.dart |
diff --git a/tool/input_sdk/private/operations.dart b/tool/input_sdk/private/operations.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5c7becfb2df983e8717484bad2b21eff3085c45b |
--- /dev/null |
+++ b/tool/input_sdk/private/operations.dart |
@@ -0,0 +1,438 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/// This library defines runtime operations on objects used by the code |
+/// generator. |
+ |
+library dart._operations; |
+import 'dart:_foreign_helper' show JS, JsName, rest; |
+import 'dart:_utils' as utils; |
+import 'dart:_js_helper' show getTraceFromException; |
+import 'dart:_classes' show canonicalMember, hasMethod, bind, getMethodType; |
+import 'dart:_errors'; |
+import 'dart:_errors' as errors; |
+import 'dart:_rtti' show read, realRuntimeType; |
+import 'dart:_types' show isGroundType, isSubtype, jsobject, typeName; |
+ |
+import 'dart:async'; |
+import 'dart:collection'; |
+ |
+final getOwnNamesAndSymbols = JS('', '${utils.getOwnNamesAndSymbols}'); |
+ |
+final getOwnPropertyNames = JS('', 'Object.getOwnPropertyNames'); |
+final hasOwnProperty = JS('', 'Object.prototype.hasOwnProperty'); |
+ |
+_canonicalFieldName(obj, name, args, displayName) => JS('', '''(() => { |
+ $name = $canonicalMember($obj, $name); |
+ if ($name) return $name; |
+ // TODO(jmesserly): in the future we might have types that "overlay" Dart |
+ // methods while also exposing the full native API, e.g. dart:html vs |
+ // dart:dom. To support that we'd need to fall back to the normal name |
+ // if an extension method wasn't found. |
+ $throwNoSuchMethod($obj, $displayName, $args); |
+})()'''); |
+ |
+dload(obj, field) => JS('', '''(() => { |
+ $field = $_canonicalFieldName($obj, $field, [], $field); |
+ if ($hasMethod($obj, $field)) { |
+ return $bind($obj, $field); |
+ } |
+ // TODO(vsm): Implement NSM robustly. An 'in' check breaks on certain |
+ // types. hasOwnProperty doesn't chase the proto chain. |
+ // Also, do we want an NSM on regular JS objects? |
+ // See: https://github.com/dart-lang/dev_compiler/issues/169 |
+ let result = $obj[$field]; |
+ return result; |
+})()'''); |
+ |
+dput(obj, field, value) => JS('', '''(() => { |
+ $field = $_canonicalFieldName($obj, $field, [$value], $field); |
+ // TODO(vsm): Implement NSM and type checks. |
+ // See: https://github.com/dart-lang/dev_compiler/issues/170 |
+ $obj[$field] = $value; |
+ return $value; |
+})()'''); |
+ |
+/// Check that a function of a given type can be applied to |
+/// actuals. |
+checkApply(type, actuals) => JS('', '''(() => { |
+ if ($actuals.length < $type.args.length) return false; |
+ let index = 0; |
+ for(let i = 0; i < $type.args.length; ++i) { |
+ if (!$instanceOfOrNull($actuals[i], $type.args[i])) return false; |
+ ++index; |
+ } |
+ if ($actuals.length == $type.args.length) return true; |
+ let extras = $actuals.length - $type.args.length; |
+ if ($type.optionals.length > 0) { |
+ if (extras > $type.optionals.length) return false; |
+ for(let i = 0, j=index; i < extras; ++i, ++j) { |
+ if (!$instanceOfOrNull($actuals[j], $type.optionals[i])) return false; |
+ } |
+ return true; |
+ } |
+ // TODO(leafp): We can't tell when someone might be calling |
+ // something expecting an optional argument with named arguments |
+ |
+ if (extras != 1) return false; |
+ // An empty named list means no named arguments |
+ if ($getOwnPropertyNames($type.named).length == 0) return false; |
+ let opts = $actuals[index]; |
+ let names = $getOwnPropertyNames(opts); |
+ // Type is something other than a map |
+ if (names.length == 0) return false; |
+ for (var name of names) { |
+ if (!($hasOwnProperty.call($type.named, name))) { |
+ return false; |
+ } |
+ if (!$instanceOfOrNull(opts[name], $type.named[name])) return false; |
+ } |
+ return true; |
+})()'''); |
+ |
+throwNoSuchMethod(obj, name, args, opt_func) => JS('', '''(() => { |
+ if ($obj === void 0) $obj = $opt_func; |
+ ${errors.throwNoSuchMethod}($obj, $name, $args); |
+})()'''); |
+ |
+checkAndCall(f, ftype, obj, args, name) => JS('', '''(() => { |
+ if (!($f instanceof Function)) { |
+ // We're not a function (and hence not a method either) |
+ // Grab the `call` method if it's not a function. |
+ if ($f != null) { |
+ $ftype = $getMethodType($f, 'call'); |
+ $f = $f.call; |
+ } |
+ if (!($f instanceof Function)) { |
+ $throwNoSuchMethod($obj, $name, $args); |
+ } |
+ } |
+ // If f is a function, but not a method (no method type) |
+ // then it should have been a function valued field, so |
+ // get the type from the function. |
+ if ($ftype === void 0) { |
+ $ftype = $read($f); |
+ } |
+ |
+ if (!$ftype) { |
+ // TODO(leafp): Allow JS objects to go through? |
+ // This includes the DOM. |
+ return $f.apply($obj, $args); |
+ } |
+ |
+ if ($checkApply($ftype, $args)) { |
+ return $f.apply($obj, $args); |
+ } |
+ |
+ // TODO(leafp): throw a type error (rather than NSM) |
+ // if the arity matches but the types are wrong. |
+ $throwNoSuchMethod($obj, $name, $args, $f); |
+})()'''); |
+ |
+dcall(f, @rest args) => JS('', '''(() => { |
+ let ftype = $read($f); |
+ return $checkAndCall($f, ftype, void 0, $args, 'call'); |
+})()'''); |
+ |
+/// Shared code for dsend, dindex, and dsetindex. */ |
+callMethod(obj, name, args, displayName) => JS('', '''(() => { |
+ let symbol = $_canonicalFieldName($obj, $name, $args, $displayName); |
+ let f = $obj != null ? $obj[symbol] : null; |
+ let ftype = $getMethodType($obj, $name); |
+ return $checkAndCall(f, ftype, $obj, $args, $displayName); |
+})()'''); |
+ |
+dsend(obj, method, @rest args) => JS('', '''(() => { |
+ return $callMethod($obj, $method, $args, $method); |
+})()'''); |
+ |
+dindex(obj, index) => JS('', '''(() => { |
+ return $callMethod($obj, 'get', [$index], '[]'); |
+})()'''); |
+ |
+dsetindex(obj, index, value) => JS('', '''(() => { |
+ $callMethod($obj, 'set', [$index, $value], '[]='); |
+ return $value; |
+})()'''); |
+ |
+_ignoreTypeFailure(actual, type) => JS('', '''(() => { |
+ // TODO(vsm): Remove this hack ... |
+ // This is primarily due to the lack of generic methods, |
+ // but we need to triage all the types. |
+ let isSubtype = $isSubtype; |
+ if (isSubtype($type, $Iterable) && isSubtype($actual, $Iterable) || |
+ isSubtype($type, $Future) && isSubtype($actual, $Future) || |
+ isSubtype($type, $Map) && isSubtype($actual, $Map) || |
+ isSubtype($type, $Function) && isSubtype($actual, $Function) || |
+ isSubtype($type, $Stream) && isSubtype($actual, $Stream) || |
+ isSubtype($type, $StreamSubscription) && |
+ isSubtype($actual, $StreamSubscription)) { |
+ console.warn('Ignoring cast fail from ' + $typeName($actual) + |
+ ' to ' + $typeName($type)); |
+ return true; |
+ } |
+ return false; |
+})()'''); |
+ |
+strongInstanceOf(obj, type, ignoreFromWhiteList) => JS('', '''(() => { |
+ let actual = $realRuntimeType($obj); |
+ if ($isSubtype(actual, $type) || actual == $jsobject) return true; |
+ if ($ignoreFromWhiteList == void 0) return false; |
+ if ($isGroundType($type)) return false; |
+ if ($_ignoreTypeFailure(actual, $type)) return true; |
+ return false; |
+})()'''); |
+ |
+instanceOfOrNull(obj, type) => JS('', '''(() => { |
+ if (($obj == null) || $strongInstanceOf($obj, $type, true)) return true; |
+ return false; |
+})()'''); |
+ |
+instanceOf(obj, type) => JS('', '''(() => { |
+ if ($strongInstanceOf($obj, $type)) return true; |
+ // TODO(#296): This is perhaps too eager to throw a StrongModeError? |
+ // It will throw on <int>[] is List<String>. |
+ // TODO(vsm): We can statically detect many cases where this |
+ // check is unnecessary. |
+ if ($isGroundType($type)) return false; |
+ let actual = $realRuntimeType($obj); |
+ ${utils.throwStrongModeError}('Strong mode is check failure: ' + |
+ $typeName(actual) + ' does not soundly subtype ' + |
+ $typeName($type)); |
+})()'''); |
+ |
+cast(obj, type) => JS('', '''(() => { |
+ // TODO(#296): This is perhaps too eager to throw a StrongModeError? |
+ // TODO(vsm): handle non-nullable types |
+ if ($instanceOfOrNull($obj, $type)) return $obj; |
+ let actual = $realRuntimeType($obj); |
+ if ($isGroundType($type)) $throwCastError(actual, $type); |
+ |
+ if ($_ignoreTypeFailure(actual, $type)) return $obj; |
+ |
+ ${utils.throwStrongModeError}('Strong mode cast failure from ' + |
+ $typeName(actual) + ' to ' + $typeName($type)); |
+})()'''); |
+ |
+asInt(obj) => JS('', '''(() => { |
+ if ($obj == null) { |
+ return null; |
+ } |
+ if (Math.floor($obj) != $obj) { |
+ // Note: null will also be caught by this check |
+ $throwCastError($realRuntimeType($obj), $int); |
+ } |
+ return $obj; |
+})()'''); |
+ |
+arity(f) => JS('', '''(() => { |
+ // TODO(jmesserly): need to parse optional params. |
+ // In ES6, length is the number of required arguments. |
+ return { min: $f.length, max: $f.length }; |
+})()'''); |
+ |
+equals(x, y) => JS('', '''(() => { |
+ if ($x == null || $y == null) return $x == $y; |
+ let eq = $x['==']; |
+ return eq ? eq.call($x, $y) : $x === $y; |
+})()'''); |
+ |
+/// Checks that `x` is not null or undefined. */ |
+notNull(x) => JS('', '''(() => { |
+ if ($x == null) $throwNullValueError(); |
+ return $x; |
+})()'''); |
+ |
+/// |
+/// Creates a dart:collection LinkedHashMap. |
+/// |
+/// For a map with string keys an object literal can be used, for example |
+/// `map({'hi': 1, 'there': 2})`. |
+/// |
+/// Otherwise an array should be used, for example `map([1, 2, 3, 4])` will |
+/// create a map with keys [1, 3] and values [2, 4]. Each key-value pair |
+/// should be adjacent entries in the array. |
+/// |
+/// For a map with no keys the function can be called with no arguments, for |
+/// example `map()`. |
+/// |
+// TODO(jmesserly): this could be faster |
+map(values) => JS('', '''(() => { |
+ let map = $LinkedHashMap.new(); |
+ if (Array.isArray($values)) { |
+ for (let i = 0, end = $values.length - 1; i < end; i += 2) { |
+ let key = $values[i]; |
+ let value = $values[i + 1]; |
+ map.set(key, value); |
+ } |
+ } else if (typeof $values === 'object') { |
+ for (let key of $getOwnPropertyNames($values)) { |
+ map.set(key, $values[key]); |
+ } |
+ } |
+ return map; |
+})()'''); |
+ |
+@JsName('assert') |
+assert_(condition) => JS('', '''(() => { |
+ if (!$condition) $throwAssertionError(); |
+})()'''); |
+ |
+final _stack = JS('', 'new WeakMap()'); |
+@JsName('throw') |
+throw_(obj) => JS('', '''(() => { |
+ if ($obj != null && (typeof $obj == 'object' || typeof $obj == 'function')) { |
+ // TODO(jmesserly): couldn't we store the most recent stack in a single |
+ // variable? There should only be one active stack trace. That would |
+ // allow it to work for things like strings and numbers. |
+ _stack.set($obj, new Error()); |
+ } |
+ throw $obj; |
+})()'''); |
+ |
+getError(exception) => JS('', '''(() => { |
+ var stack = _stack.get($exception); |
+ return stack !== void 0 ? stack : $exception; |
+})()'''); |
+ |
+// This is a utility function: it is only intended to be called from dev |
+// tools. |
+stackPrint(exception) => JS('', '''(() => { |
+ var error = $getError($exception); |
+ console.log(error.stack ? error.stack : 'No stack trace for: ' + error); |
+})()'''); |
+ |
+stackTrace(exception) => JS('', '''(() => { |
+ var error = $getError($exception); |
+ return $getTraceFromException(error); |
+})()'''); |
+ |
+/// |
+/// Implements a sequence of .? operations. |
+/// |
+/// Will call each successive callback, unless one returns null, which stops |
+/// the sequence. |
+/// |
+nullSafe(obj, @rest callbacks) => JS('', '''(() => { |
+ if ($obj == null) return $obj; |
+ for (const callback of $callbacks) { |
+ $obj = callback($obj); |
+ if ($obj == null) break; |
+ } |
+ return $obj; |
+})()'''); |
+ |
+final _value = JS('', 'Symbol("_value")'); |
+/// |
+/// Looks up a sequence of [keys] in [map], recursively, and |
+/// returns the result. If the value is not found, [valueFn] will be called to |
+/// add it. For example: |
+/// |
+/// let map = new Map(); |
+/// putIfAbsent(map, [1, 2, 'hi ', 'there '], () => 'world'); |
+/// |
+/// ... will create a Map with a structure like: |
+/// |
+/// { 1: { 2: { 'hi ': { 'there ': 'world' } } } } |
+/// |
+multiKeyPutIfAbsent(map, keys, valueFn) => JS('', '''(() => { |
+ for (let k of $keys) { |
+ let value = $map.get(k); |
+ if (!value) { |
+ // TODO(jmesserly): most of these maps are very small (e.g. 1 item), |
+ // so it may be worth optimizing for that. |
+ $map.set(k, value = new Map()); |
+ } |
+ $map = value; |
+ } |
+ if ($map.has(_value)) return $map.get(_value); |
+ let value = $valueFn(); |
+ $map.set(_value, value); |
+ return value; |
+})()'''); |
+ |
+/// The global constant table. */ |
+final constants = JS('', 'new Map()'); |
+ |
+/// |
+/// Canonicalize a constant object. |
+/// |
+/// Preconditions: |
+/// - `obj` is an objects or array, not a primitive. |
+/// - nested values of the object are themselves already canonicalized. |
+/// |
+@JsName('const') |
+const_(obj) => JS('', '''(() => { |
+ let objectKey = [$realRuntimeType($obj)]; |
+ // TODO(jmesserly): there's no guarantee in JS that names/symbols are |
+ // returned in the same order. |
+ // |
+ // We could probably get the same order if we're judicious about |
+ // initializing fields in a consistent order across all const constructors. |
+ // Alternatively we need a way to sort them to make consistent. |
+ // |
+ // Right now we use the (name,value) pairs in sequence, which prevents |
+ // an object with incorrect field values being returned, but won't |
+ // canonicalize correctly if key order is different. |
+ for (let name of $getOwnNamesAndSymbols($obj)) { |
+ objectKey.push(name); |
+ objectKey.push($obj[name]); |
+ } |
+ return $multiKeyPutIfAbsent($constants, objectKey, () => $obj); |
+})()'''); |
+ |
+ |
+// The following are helpers for Object methods when the receiver |
+// may be null or primitive. These should only be generated by |
+// the compiler. |
+hashCode(obj) => JS('', '''(() => { |
+ if ($obj == null) { |
+ return 0; |
+ } |
+ // TODO(vsm): What should we do for primitives and non-Dart objects? |
+ switch (typeof $obj) { |
+ case "number": |
+ case "boolean": |
+ return $obj & 0x1FFFFFFF; |
+ case "string": |
+ // TODO(vsm): Call the JSString hashCode? |
+ return $obj.length; |
+ } |
+ return $obj.hashCode; |
+})()'''); |
+ |
+toString(obj) => JS('', '''(() => { |
+ if ($obj == null) { |
+ return "null"; |
+ } |
+ return $obj.toString(); |
+})()'''); |
+ |
+noSuchMethod(obj, invocation) => JS('', '''(() => { |
+ if ($obj == null) { |
+ $throwNoSuchMethod($obj, $invocation.memberName, |
+ $invocation.positionalArguments, $invocation.namedArguments); |
+ } |
+ switch (typeof $obj) { |
+ case "number": |
+ case "boolean": |
+ case "string": |
+ $throwNoSuchMethod($obj, $invocation.memberName, |
+ $invocation.positionalArguments, $invocation.namedArguments); |
+ } |
+ return $obj.noSuchMethod($invocation); |
+})()'''); |
+ |
+final JsIterator = JS('', ''' |
+ class JsIterator { |
+ constructor(dartIterator) { |
+ this.dartIterator = dartIterator; |
+ } |
+ next() { |
+ let i = this.dartIterator; |
+ let done = !i.moveNext(); |
+ return { done: done, value: done ? void 0 : i.current }; |
+ } |
+ } |
+'''); |