| // 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 the association between runtime objects and |
| /// runtime types. |
| part of dart._runtime; |
| |
| /// Runtime type information. This module defines the mapping from |
| /// runtime objects to their runtime type information. See the types |
| /// module for the definition of how type information is represented. |
| /// |
| /// There are two kinds of objects that represent "types" at runtime. A |
| /// "runtime type" contains all of the data needed to implement the runtime |
| /// type checking inserted by the compiler. These objects fall into four |
| /// categories: |
| /// |
| /// - Things represented by javascript primitives, such as |
| /// null, numbers, booleans, strings, and symbols. For these |
| /// we map directly from the javascript type (given by typeof) |
| /// to the appropriate class type from core, which serves as their |
| /// rtti. |
| /// |
| /// - Functions, which are represented by javascript functions. |
| /// Representations of Dart functions always have a |
| /// _runtimeType property attached to them with the appropriate |
| /// rtti. |
| /// |
| /// - Objects (instances) which are represented by instances of |
| /// javascript (ES6) classes. Their types are given by their |
| /// classes, and the rtti is accessed by projecting out their |
| /// constructor field. |
| /// |
| /// - Types objects, which are represented as described in the types |
| /// module. Types always have a _runtimeType property attached to |
| /// them with the appropriate rtti. The rtti for these is always |
| /// core.Type. TODO(leafp): consider the possibility that we can |
| /// reliably recognize type objects and map directly to core.Type |
| /// rather than attaching this property everywhere. |
| /// |
| /// The other kind of object representing a "type" is the instances of the |
| /// dart:core Type class. These are the user visible objects you get by calling |
| /// "runtimeType" on an object or using a class literal expression. These are |
| /// different from the above objects, and are created by calling `wrapType()` |
| /// on a runtime type. |
| |
| /// Tag a closure with a type, using one of two forms: |
| /// |
| /// `dart.fn(cls)` marks cls has having no optional or named |
| /// parameters, with all argument and return types as dynamic. |
| /// |
| /// `dart.fn(cls, rType, argsT, extras)` marks cls as having the |
| /// runtime type dart.functionType(rType, argsT, extras). |
| /// |
| /// Note that since we are producing a type for a concrete function, |
| /// it is sound to use the definite arrow type. |
| /// |
| fn(closure, t) { |
| if (t == null) { |
| // No type arguments, it's all dynamic |
| t = definiteFunctionType(JS('', '#', dynamic), |
| JS('', 'Array(#.length).fill(#)', closure, dynamic), JS('', 'void 0')); |
| } |
| tag(closure, t); |
| return closure; |
| } |
| |
| lazyFn(closure, computeType) { |
| tagLazy(closure, computeType); |
| return closure; |
| } |
| |
| // TODO(vsm): How should we encode the runtime type? |
| final _runtimeType = JS('', 'Symbol("_runtimeType")'); |
| final isNamedConstructor = JS('', 'Symbol("isNamedConstructor")'); |
| |
| _checkPrimitiveType(obj) { |
| // TODO(jmesserly): JS is used to prevent type literal wrapping. Is there a |
| // better way we can handle this? (sra: It is super dodgy that the values |
| // passed to JS are different to the values passed to a regular function - the |
| // semantics are not longer that of calling an interpreter. dart2js has other |
| // special functions, we could do the same.) |
| |
| // Check for null and undefined |
| if (obj == null) return JS('', '#', Null); |
| |
| if (JS('bool', 'typeof # == "number"', obj)) { |
| if (JS('bool', 'Math.floor(#) == #', obj, obj)) { |
| return JS('', '#', int); |
| } |
| return JS('', '#', double); |
| } |
| |
| if (JS('bool', 'typeof # == "boolean"', obj)) { |
| return JS('', '#', bool); |
| } |
| |
| if (JS('bool', 'typeof # == "string"', obj)) { |
| return JS('', '#', String); |
| } |
| |
| if (JS('bool', 'typeof # == "symbol"', obj)) { |
| // Note: this is a JS Symbol, not a Dart one. |
| return JS('', '#', jsobject); |
| } |
| |
| return null; |
| } |
| |
| getFunctionType(obj) { |
| // TODO(vsm): Encode this properly on the function for Dart-generated code. |
| var args = JS('', 'Array(#.length).fill(#)', obj, dynamic); |
| return definiteFunctionType(bottom, args, JS('', 'void 0')); |
| } |
| |
| /// Returns the runtime representation of the type of obj. |
| /// |
| /// The resulting object is used internally for runtime type checking. This is |
| /// different from the user-visible Type object returned by calling |
| /// `runtimeType` on some Dart object. |
| getReifiedType(obj) { |
| var result = _checkPrimitiveType(obj); |
| if (result != null) return result; |
| return _nonPrimitiveRuntimeType(obj); |
| } |
| |
| _nonPrimitiveRuntimeType(obj) { |
| // Lookup recorded *real* type (not user definable runtimeType) |
| // TODO(vsm): Should we treat Dart and JS objects differently here? |
| // E.g., we can check if obj instanceof core.Object to differentiate. |
| var result = _getRuntimeType(obj); |
| if (result != null) return result; |
| |
| // Lookup extension type |
| result = getExtensionType(obj); |
| if (result != null) return result; |
| |
| // Fallback on constructor for class types |
| result = JS('', '#.constructor', obj); |
| if (JS('bool', '# === Function', result)) { |
| // An undecorated Function should have come from JavaScript. |
| // Treat as untyped. |
| return JS('', '#', jsobject); |
| } |
| if (result == null) { |
| return JS('', '#', jsobject); |
| } |
| return result; |
| } |
| |
| /// Given an internal runtime type object, wraps it in a `WrappedType` object |
| /// that implements the dart:core Type interface. |
| wrapType(type) { |
| // If we've already wrapped this type once, use the previous wrapper. This |
| // way, multiple references to the same type return an identical Type. |
| if (JS('bool', '#.hasOwnProperty(#)', type, _typeObject)) { |
| return JS('', '#[#]', type, _typeObject); |
| } |
| return JS('', '#[#] = new #(#)', type, _typeObject, WrappedType, type); |
| } |
| |
| var _lazyJSTypes = JS('', 'new Map()'); |
| |
| lazyJSType(getJSTypeCallback, name) { |
| var key = JS('String', '#.toString()', getJSTypeCallback); |
| if (JS('bool', '#.has(#)', _lazyJSTypes, key)) { |
| return JS('', '#.get(#)', _lazyJSTypes, key); |
| } |
| var ret = JS('', 'new #(#, #)', LazyJSType, getJSTypeCallback, name); |
| JS('', '#.set(#, #)', _lazyJSTypes, key, ret); |
| return ret; |
| } |
| |
| // TODO(jacobr): do not use the same LazyJSType object for anonymous JS types |
| // from different libraries. |
| lazyAnonymousJSType(name) { |
| if (JS('bool', '#.has(#)', _lazyJSTypes, name)) { |
| return JS('', '#.get(#)', _lazyJSTypes, name); |
| } |
| var ret = JS('', 'new #(null, #)', LazyJSType, name); |
| JS('', '#.set(#, #)', _lazyJSTypes, name, ret); |
| return ret; |
| } |
| |
| /// Given a WrappedType, return the internal runtime type object. |
| unwrapType(WrappedType obj) => obj._wrappedType; |
| |
| _getRuntimeType(value) => JS('', '#[#]', value, _runtimeType); |
| getIsNamedConstructor(value) => JS('', '#[#]', value, isNamedConstructor); |
| // TODO(bmilligan): Define the symbol in rtti.dart instead of dart_library.js |
| // and get rid of the call to dart_library in the JS here. |
| getDartLibraryName(value) => JS('', '#[dart_library.dartLibraryName]', value); |
| |
| /// Tag the runtime type of [value] to be type [t]. |
| void tag(value, t) { |
| JS('', '#[#] = #', value, _runtimeType, t); |
| } |
| |
| void tagComputed(value, compute) { |
| JS('', '#(#, #, { get: # })', defineProperty, value, _runtimeType, compute); |
| } |
| |
| void tagLazy(value, compute) { |
| JS('', '#(#, #, { get: # })', defineLazyProperty, value, _runtimeType, |
| compute); |
| } |