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