blob: 5e5751884168d8e05c365dd33ee0f76d3cc89c70 [file] [log] [blame]
// Copyright (c) 2019, 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 contains support for runtime type information.
library rti;
import 'dart:_foreign_helper' show JS;
import 'dart:_interceptors' show JSArray, JSUnmodifiableArray;
/// An Rti object represents both a type (e.g `Map<int, String>`) and a type
/// environment (`Map<int, String>` binds `Map.K=int` and `Map.V=String`).
///
/// There is a single [Rti] class to help reduce polymorphism in the JavaScript
/// runtime. The class has a default constructor and no final fields so it can
/// be created before much of the runtime exists.
///
/// The fields are declared in an order that gets shorter minified names for the
/// more commonly used fields. (TODO: we should exploit the fact that an Rti
/// instance never appears in a dynamic context, so does not need field names to
/// be distinct from dynamic selectors).
///
class Rti {
/// JavaScript method for 'as' check. The method is called from generated code,
/// e.g. `o as T` generates something like `rtiForT._as(o)`.
dynamic _as;
/// JavaScript method for type check. The method is called from generated
/// code, e.g. parameter check for `T param` generates something like
/// `rtiForT._check(param)`.
dynamic _check;
/// JavaScript method for 'is' test. The method is called from generated
/// code, e.g. `o is T` generates something like `rtiForT._is(o)`.
dynamic _is;
static void _setAsCheckFunction(Rti rti, fn) {
rti._as = fn;
}
static void _setTypeCheckFunction(Rti rti, fn) {
rti._check = fn;
}
static void _setIsTestFunction(Rti rti, fn) {
rti._is = fn;
}
/// Method called from generated code to evaluate a type environment recipe in
/// `this` type environment.
Rti _eval(String recipe) => _rtiEval(this, recipe);
/// Method called from generated code to extend `this` type environment with a
/// function type parameter.
Rti _bind1(Rti type) => _rtiBind1(this, type);
/// Method called from generated code to extend `this` type environment with a
/// tuple of function type parameters.
Rti _bind(Rti typeTuple) => _rtiBind(this, typeTuple);
// Precomputed derived types. These fields are used to hold derived types that
// are computed eagerly.
// TODO(sra): Implement precomputed type optimizations.
dynamic _precomputed1;
dynamic _precomputed2;
dynamic _precomputed3;
dynamic _precomputed4;
// The Type object corresponding to this Rti.
Type _typeCache;
/// The kind of Rti `this` is, one of the kindXXX constants below.
///
/// We don't use an enum since we need to create Rti objects very early.
///
/// The zero initializer ensures dart2js type analysis considers [_kind] is
/// non-nullable.
int _kind = 0;
static int _getKind(Rti rti) => rti._kind;
static void _setKind(Rti rti, int kind) {
rti._kind = kind;
}
// Terminal terms.
static const kindNever = 1;
static const kindDynamic = 2;
static const kindVoid = 3; // TODO(sra): Use `dynamic` instead?
static const kindAny = 4; // Dart1-style 'dynamic' for JS-interop.
// Unary terms.
static const kindStar = 5;
static const kindQuestion = 6;
static const kindFutureOr = 7;
// More complex terms.
static const kindInterface = 8;
// A vector of type parameters from enclosing functions and closures.
static const kindBinding = 9;
static const kindFunction = 10;
static const kindGenericFunction = 11;
/// Primary data associated with type.
///
/// - Minified name of interface for interface types.
/// - Underlying type for unary terms.
/// - Class part of a type environment inside a generic class, or `null` for
/// type tuple.
/// - Return type of function types.
dynamic _primary;
static _getPrimary(Rti rti) => rti._primary;
/// Additional data associated with type.
///
/// - The type arguments of an interface type.
/// - The type arguments from enclosing functions and closures for a
/// kindBinding.
/// - TBD for kindFunction and kindGenericFunction.
dynamic _rest;
static JSArray _getInterfaceTypeArguments(rti) {
// The array is a plain JavaScript Array, otherwise we would need the type
// `JSArray<Rti>` to exist before we could create the type `JSArray<Rti>`.
assert(_getKind(rti) == kindInterface);
return JS('JSUnmodifiableArray', '#', _getPrimary(rti));
}
/// On [Rti]s that are type environments, derived types are cached on the
/// environment to ensure fast canonicalization. Ground-term types (i.e. not
/// dependent on class or function type parameters) are cached in the
/// universe. This field starts as `null` and the cache is created on demand.
dynamic _evalCache;
static Rti allocate() {
return new Rti();
}
}
Rti _rtiEval(Rti environment, String recipe) {
throw UnimplementedError('_rtiEval');
}
Rti _rtiBind1(Rti environment, Rti type) {
throw UnimplementedError('_rtiBind1');
}
Rti _rtiBind(Rti environment, Rti typeTuple) {
throw UnimplementedError('_rtiBind');
}
Type getRuntimeType(object) {
throw UnimplementedError('getRuntimeType');
}
String _rtiToString(Rti rti, List<String> genericContext) {
int kind = Rti._getKind(rti);
if (kind == Rti.kindDynamic) return 'dynamic';
return '?';
}
/// Class of static methods for the universe of Rti objects.
///
/// The universe itself is allocated at startup before any types or Dart objects
/// can be created, so it does not have a Dart type.
class Universe {
Universe._() {
throw UnimplementedError('Universe is static methods only');
}
@pragma('dart2js:noInline')
static Object create() {
// TODO(sra): For consistency, this expression should be a JS_BUILTIN that
// uses the same template as emitted by the emitter.
return JS(
'',
'{'
'evalCache: new Map(),'
'unprocessedRules:[],'
''
'}');
}
static evalCache(universe) => JS('', '#.evalCache', universe);
static void addRules(universe, String rules) {
JS('', '#.unprocessedRules.push(#)', universe, rules);
}
static eval(universe, String recipe) {
var cache = evalCache(universe);
var probe = _cacheGet(cache, recipe);
if (probe != null) return probe;
var rti = _parseRecipe(universe, recipe);
_cacheSet(cache, recipe, rti);
return rti;
}
static _cacheGet(cache, key) => JS('', '#.get(#)', cache, key);
static void _cacheSet(cache, key, value) {
JS('', '#.set(#, #)', cache, key, value);
}
static _parseRecipe(universe, recipe) {
if (recipe == 'dynamic') return _createDynamicRti(universe);
throw UnimplementedError('Universe._parseRecipe("$recipe")');
}
static _createDynamicRti(universe) {
var rti = Rti.allocate();
Rti._setKind(rti, Rti.kindDynamic);
var alwaysPasses = JS('', 'function(o) { return o; }');
Rti._setAsCheckFunction(rti, alwaysPasses);
Rti._setTypeCheckFunction(rti, alwaysPasses);
Rti._setIsTestFunction(rti, JS('', 'function(o) { return true; }'));
return rti;
}
}
// -------- Subtype tests ------------------------------------------------------
// Future entry point from compiled code.
bool isSubtype(Rti s, Rti t) {
return _isSubtype(s, null, t, null);
}
bool _isSubtype(Rti s, var sEnv, Rti t, var tEnv) {
if (_isIdentical(s, t)) return true;
int tKind = Rti._getKind(t);
if (tKind == Rti.kindDynamic) return true;
if (tKind == Rti.kindNever) return false;
return false;
}
/// Unchecked cast to Rti.
Rti _castToRti(s) => JS('Rti', '#', s);
bool _isIdentical(s, t) => JS('bool', '# === #', s, t);
// -------- Entry points for testing -------------------------------------------
String testingRtiToString(rti) {
return _rtiToString(_castToRti(rti), null);
}
String testingRtiToDebugString(rti) {
// TODO(sra): Create entty point for structural formatting of Rti tree.
return 'Rti';
}
Object testingCreateUniverse() {
return Universe.create();
}
Object testingAddRules(universe, String rules) {
Universe.addRules(universe, rules);
}
bool testingIsSubtype(rti1, rti2) {
return isSubtype(_castToRti(rti1), _castToRti(rti2));
}
Object testingUniverseEval(universe, String recipe) {
return Universe.eval(universe, recipe);
}