blob: 7c915db13a09bc4a68c1b73d873305ad288dc41e [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
getInterceptor,
getJSArrayInteropRti,
JS,
JS_BUILTIN,
JS_EMBEDDED_GLOBAL,
JS_GET_FLAG,
JS_GET_NAME,
JS_STRING_CONCAT,
RAW_DART_FUNCTION_REF,
TYPE_REF,
LEGACY_TYPE_REF;
import 'dart:_interceptors'
show JavaScriptFunction, JSArray, JSNull, JSUnmodifiableArray;
import 'dart:_js_names' show unmangleGlobalNameIfPreservedAnyways;
import 'dart:_js_embedded_names'
show
JsBuiltin,
JsGetName,
RtiUniverseFieldNames,
ARRAY_RTI_PROPERTY,
CONSTRUCTOR_RTI_CACHE_PROPERTY_NAME,
RTI_UNIVERSE,
TYPES;
import 'dart:_recipe_syntax';
// The top type `Object?` is used throughout this library even when values are
// not nullable or have narrower types in order to avoid incurring type checks
// before the type checking infrastructure has been set up.
// We could use `dynamic`, but this would allow inadvertent implicit downcasts.
// TODO(fishythefish, dart-lang/language#115): Replace `Object?` with a typedef
// when possible.
/// 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)`.
@pragma('dart2js:noElision')
Object? _as;
/// JavaScript method for 'is' test. The method is called from generated
/// code, e.g. `o is T` generates something like `rtiForT._is(o)`.
@pragma('dart2js:noElision')
Object? _is;
static void _setAsCheckFunction(Rti rti, Object? fn) {
rti._as = fn;
}
static void _setIsTestFunction(Rti rti, Object? fn) {
rti._is = fn;
}
@pragma('dart2js:tryInline')
static bool _asCheck(Rti rti, Object? object) {
return JS(
'bool', '#.#(#)', rti, JS_GET_NAME(JsGetName.RTI_FIELD_AS), object);
}
@pragma('dart2js:tryInline')
static bool _isCheck(Rti rti, Object? object) {
return JS(
'bool', '#.#(#)', rti, JS_GET_NAME(JsGetName.RTI_FIELD_IS), object);
}
/// Method called from generated code to evaluate a type environment recipe in
/// `this` type environment.
Rti _eval(Object? recipe) {
// TODO(sra): Clone the fast-path of _Universe.evalInEnvironment to here.
return _rtiEval(this, _Utils.asString(recipe));
}
/// Method called from generated code to extend `this` type environment (an
/// interface or binding Rti) with function type arguments (a singleton
/// argument or tuple of arguments).
Rti _bind(Object? typeOrTuple) => _rtiBind(this, _Utils.asRti(typeOrTuple));
/// Method called from generated code to extend `this` type (as a singleton
/// type environment) with function type arguments (a singleton argument or
/// tuple of arguments).
Rti _bind1(Object? typeOrTuple) => _rtiBind1(this, _Utils.asRti(typeOrTuple));
// Precomputed derived types. These fields are used to hold derived types that
// are computed eagerly.
// TODO(sra): Implement precomputed type optimizations.
/// If kind == kindInterface, holds the first type argument (if any).
/// If kind == kindFutureOr, holds Future<T> where T is the base type.
/// - This case is lazily initialized during subtype checks.
/// If kind == kindStar, holds T? where T is the base type.
/// - This case is lazily initialized during subtype checks.
@pragma('dart2js:noElision')
Object? _precomputed1;
static Object? _getPrecomputed1(Rti rti) => rti._precomputed1;
static void _setPrecomputed1(Rti rti, Object? precomputed) {
rti._precomputed1 = precomputed;
}
static Rti _getQuestionFromStar(Object? universe, Rti rti) {
assert(_getKind(rti) == kindStar);
Rti? question = _Utils.asRtiOrNull(_getPrecomputed1(rti));
if (question == null) {
question =
_Universe._lookupQuestionRti(universe, _getStarArgument(rti), true);
Rti._setPrecomputed1(rti, question);
}
return question;
}
static Rti _getFutureFromFutureOr(Object? universe, Rti rti) {
assert(_getKind(rti) == kindFutureOr);
Rti? future = _Utils.asRtiOrNull(_getPrecomputed1(rti));
if (future == null) {
future = _Universe._lookupFutureRti(universe, _getFutureOrArgument(rti));
Rti._setPrecomputed1(rti, future);
}
return future;
}
Object? _precomputed2;
Object? _precomputed3;
Object? _precomputed4;
// Data value used by some tests.
@pragma('dart2js:noElision')
Object? _specializedTestResource;
static Object? _getSpecializedTestResource(Rti rti) {
return rti._specializedTestResource;
}
static void _setSpecializedTestResource(Rti rti, Object? value) {
rti._specializedTestResource = value;
}
// The Type object corresponding to this Rti.
Object? _cachedRuntimeType;
static _Type? _getCachedRuntimeType(Rti rti) =>
JS('_Type|Null', '#', rti._cachedRuntimeType);
static void _setCachedRuntimeType(Rti rti, _Type type) {
rti._cachedRuntimeType = type;
}
/// 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.
Object? /*int*/ _kind = 0;
static int _getKind(Rti rti) => _Utils.asInt(rti._kind);
static void _setKind(Rti rti, int kind) {
rti._kind = kind;
}
// Terminal terms.
static const int kindNever = 1;
static const int kindDynamic = 2;
static const int kindVoid = 3; // TODO(sra): Use `dynamic` instead?
static const int kindAny = 4; // Dart1-style 'dynamic' for JS-interop.
static const int kindErased = 5;
// Unary terms.
static const int kindStar = 6;
static const int kindQuestion = 7;
static const int kindFutureOr = 8;
// More complex terms.
static const int kindInterface = 9;
// A vector of type parameters from enclosing functions and closures.
static const int kindBinding = 10;
static const int kindFunction = 11;
static const int kindGenericFunction = 12;
static const int kindGenericFunctionParameter = 13;
static bool _isUnionOfFunctionType(Rti rti) {
int kind = Rti._getKind(rti);
if (kind == kindStar || kind == kindQuestion || kind == kindFutureOr) {
return _isUnionOfFunctionType(_Utils.asRti(_getPrimary(rti)));
}
return kind == kindFunction || kind == kindGenericFunction;
}
/// 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 a function type.
/// - Underlying function type for a generic function.
/// - de Bruijn index for a generic function parameter.
Object? _primary;
static Object? _getPrimary(Rti rti) => rti._primary;
static void _setPrimary(Rti rti, Object? value) {
rti._primary = value;
}
/// Additional data associated with type.
///
/// - The type arguments of an interface type.
/// - The type arguments from enclosing functions and closures for a
/// kindBinding.
/// - The [_FunctionParameters] of a function type.
/// - The type parameter bounds of a generic function.
Object? _rest;
static Object? _getRest(Rti rti) => rti._rest;
static void _setRest(Rti rti, Object? value) {
rti._rest = value;
}
static String _getInterfaceName(Rti rti) {
assert(_getKind(rti) == kindInterface);
return _Utils.asString(_getPrimary(rti));
}
static JSArray _getInterfaceTypeArguments(Rti 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', '#', _getRest(rti));
}
static Rti _getBindingBase(Rti rti) {
assert(_getKind(rti) == kindBinding);
return _Utils.asRti(_getPrimary(rti));
}
static JSArray _getBindingArguments(Rti rti) {
assert(_getKind(rti) == kindBinding);
return JS('JSUnmodifiableArray', '#', _getRest(rti));
}
static Rti _getStarArgument(Rti rti) {
assert(_getKind(rti) == kindStar);
return _Utils.asRti(_getPrimary(rti));
}
static Rti _getQuestionArgument(Rti rti) {
assert(_getKind(rti) == kindQuestion);
return _Utils.asRti(_getPrimary(rti));
}
static Rti _getFutureOrArgument(Rti rti) {
assert(_getKind(rti) == kindFutureOr);
return _Utils.asRti(_getPrimary(rti));
}
static Rti _getReturnType(Rti rti) {
assert(_getKind(rti) == kindFunction);
return _Utils.asRti(_getPrimary(rti));
}
static _FunctionParameters _getFunctionParameters(Rti rti) {
assert(_getKind(rti) == kindFunction);
return JS('_FunctionParameters', '#', _getRest(rti));
}
static Rti _getGenericFunctionBase(Rti rti) {
assert(_getKind(rti) == kindGenericFunction);
return _Utils.asRti(_getPrimary(rti));
}
static JSArray _getGenericFunctionBounds(Rti rti) {
assert(_getKind(rti) == kindGenericFunction);
return JS('JSUnmodifiableArray', '#', _getRest(rti));
}
static int _getGenericFunctionParameterIndex(Rti rti) {
assert(_getKind(rti) == kindGenericFunctionParameter);
return _Utils.asInt(_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.
///
/// *Any Rti can be a type environment, since we use the type for a function
/// type environment. The ambiguity between 'generic class is the environment'
/// and 'generic class is a singleton type argument' is resolved by using
/// different indexing in the recipe.
Object? _evalCache;
static Object? _getEvalCache(Rti rti) => rti._evalCache;
static void _setEvalCache(Rti rti, Object? value) {
rti._evalCache = value;
}
/// On [Rti]s that are type environments*, extended environments are cached on
/// the base environment to ensure fast canonicalization.
///
/// This field starts as `null` and the cache is created on demand.
///
/// *This is valid only on kindInterface and kindBinding Rtis. The ambiguity
/// between 'generic class is the base environment' and 'generic class is a
/// singleton type argument' is resolved [TBD] (either (1) a bind1 cache, or
/// (2)using `env._eval("@<0>")._bind(args)` in place of `env._bind1(args)`).
///
/// On [Rti]s that are generic function types, results of instantiation are
/// cached on the generic function type to ensure fast repeated
/// instantiations.
Object? _bindCache;
static Object? _getBindCache(Rti rti) => rti._bindCache;
static void _setBindCache(Rti rti, Object? value) {
rti._bindCache = value;
}
static Rti allocate() {
return Rti();
}
Object? _canonicalRecipe;
static String _getCanonicalRecipe(Rti rti) {
var s = rti._canonicalRecipe;
assert(_Utils.isString(s), 'Missing canonical recipe');
return _Utils.asString(s);
}
static void _setCanonicalRecipe(Rti rti, String s) {
rti._canonicalRecipe = s;
}
}
class _FunctionParameters {
static _FunctionParameters allocate() => _FunctionParameters();
Object? _requiredPositional;
static JSArray _getRequiredPositional(_FunctionParameters parameters) =>
JS('JSUnmodifiableArray', '#', parameters._requiredPositional);
static void _setRequiredPositional(
_FunctionParameters parameters, Object? requiredPositional) {
parameters._requiredPositional = requiredPositional;
}
Object? _optionalPositional;
static JSArray _getOptionalPositional(_FunctionParameters parameters) =>
JS('JSUnmodifiableArray', '#', parameters._optionalPositional);
static void _setOptionalPositional(
_FunctionParameters parameters, Object? optionalPositional) {
parameters._optionalPositional = optionalPositional;
}
/// These are a sequence of name/bool/type triplets that correspond to named
/// parameters.
///
/// void foo({int bar, required double baz})
///
/// would be encoded as ["bar", false, int, "baz", true, double], where each
/// triplet consists of the name [String], a bool indicating whether or not
/// the parameter is required, and the [Rti].
///
/// Invariant: These groups are sorted by name in lexicographically ascending order.
Object? _named;
static JSArray _getNamed(_FunctionParameters parameters) =>
JS('JSUnmodifiableArray', '#', parameters._named);
static void _setNamed(_FunctionParameters parameters, Object? named) {
parameters._named = named;
}
}
Object? _theUniverse() => JS_EMBEDDED_GLOBAL('', RTI_UNIVERSE);
Rti _rtiEval(Rti environment, String recipe) {
return _Universe.evalInEnvironment(_theUniverse(), environment, recipe);
}
Rti _rtiBind1(Rti environment, Rti types) {
return _Universe.bind1(_theUniverse(), environment, types);
}
Rti _rtiBind(Rti environment, Rti types) {
return _Universe.bind(_theUniverse(), environment, types);
}
/// Evaluate a ground-term type.
/// Called from generated code.
Rti findType(String recipe) {
// Since [findType] should only be called on recipes computed during
// compilation, we can assume that the recipe is already normalized (since all
// [DartType]s are normalized. This allows us to avoid an unfortunate cycle:
//
// If we attempt to normalize here, then during the course of normalization,
// we may attempt to access a [TYPE_REF]. This uses the `type$` object, and
// the values of this object are calls to [findType]. Thus, if we're currently
// in one of these calls, then `type$` will appear to be undefined.
return _Universe.eval(_theUniverse(), recipe, false);
}
/// Evaluate a type recipe in the environment of an instance.
Rti evalInInstance(Object? instance, String recipe) {
return _rtiEval(instanceType(instance), recipe);
}
/// Returns [genericFunctionRti] with type parameters bound to those specified
/// by [instantiationRti].
///
/// [genericFunctionRti] must be an rti representation with a number of generic
/// type parameters matching the number of types provided by [instantiationRti].
///
/// Called from generated code.
@pragma('dart2js:noInline')
Rti? instantiatedGenericFunctionType(
Rti? genericFunctionRti, Rti instantiationRti) {
// If --lax-runtime-type-to-string is enabled and we never check the function
// type, then the function won't have a signature, so its RTI will be null. In
// this case, there is nothing to instantiate, so we return `null` and the
// instantiation appears to be an interface type instead.
if (genericFunctionRti == null) return null;
var bounds = Rti._getGenericFunctionBounds(genericFunctionRti);
var typeArguments = Rti._getInterfaceTypeArguments(instantiationRti);
assert(_Utils.arrayLength(bounds) == _Utils.arrayLength(typeArguments));
var cache = Rti._getBindCache(genericFunctionRti);
if (cache == null) {
cache = JS('', 'new Map()');
Rti._setBindCache(genericFunctionRti, cache);
}
String key = Rti._getCanonicalRecipe(instantiationRti);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
Rti rti = _substitute(_theUniverse(),
Rti._getGenericFunctionBase(genericFunctionRti), typeArguments, 0);
_Utils.mapSet(cache, key, rti);
return rti;
}
/// Substitutes [typeArguments] for generic function parameters in [rti].
///
/// Generic function parameters are de Bruijn indices counting up through the
/// parameters' scopes to index into [typeArguments].
///
/// [depth] is the number of subsequent generic function parameters that are in
/// scope. This is subtracted off the de Bruijn index for the type parameter to
/// arrive at an potential index into [typeArguments].
///
/// In order to do a partial substitution - that is, substituting only some
/// type parameters rather than all of them - we encode the unsubstituted
/// positions of the argument list as `undefined` or `null`.
Rti _substitute(Object? universe, Rti rti, Object? typeArguments, int depth) {
int kind = Rti._getKind(rti);
switch (kind) {
case Rti.kindErased:
case Rti.kindNever:
case Rti.kindDynamic:
case Rti.kindVoid:
case Rti.kindAny:
return rti;
case Rti.kindStar:
Rti baseType = _Utils.asRti(Rti._getPrimary(rti));
Rti substitutedBaseType =
_substitute(universe, baseType, typeArguments, depth);
if (_Utils.isIdentical(substitutedBaseType, baseType)) return rti;
return _Universe._lookupStarRti(universe, substitutedBaseType, true);
case Rti.kindQuestion:
Rti baseType = _Utils.asRti(Rti._getPrimary(rti));
Rti substitutedBaseType =
_substitute(universe, baseType, typeArguments, depth);
if (_Utils.isIdentical(substitutedBaseType, baseType)) return rti;
return _Universe._lookupQuestionRti(universe, substitutedBaseType, true);
case Rti.kindFutureOr:
Rti baseType = _Utils.asRti(Rti._getPrimary(rti));
Rti substitutedBaseType =
_substitute(universe, baseType, typeArguments, depth);
if (_Utils.isIdentical(substitutedBaseType, baseType)) return rti;
return _Universe._lookupFutureOrRti(universe, substitutedBaseType, true);
case Rti.kindInterface:
var interfaceTypeArguments = Rti._getInterfaceTypeArguments(rti);
var substitutedInterfaceTypeArguments = _substituteArray(
universe, interfaceTypeArguments, typeArguments, depth);
if (_Utils.isIdentical(
substitutedInterfaceTypeArguments, interfaceTypeArguments))
return rti;
return _Universe._lookupInterfaceRti(universe, Rti._getInterfaceName(rti),
substitutedInterfaceTypeArguments);
case Rti.kindBinding:
Rti base = Rti._getBindingBase(rti);
Rti substitutedBase = _substitute(universe, base, typeArguments, depth);
var arguments = Rti._getBindingArguments(rti);
var substitutedArguments =
_substituteArray(universe, arguments, typeArguments, depth);
if (_Utils.isIdentical(substitutedBase, base) &&
_Utils.isIdentical(substitutedArguments, arguments)) return rti;
return _Universe._lookupBindingRti(
universe, substitutedBase, substitutedArguments);
case Rti.kindFunction:
Rti returnType = Rti._getReturnType(rti);
Rti substitutedReturnType =
_substitute(universe, returnType, typeArguments, depth);
_FunctionParameters functionParameters = Rti._getFunctionParameters(rti);
_FunctionParameters substitutedFunctionParameters =
_substituteFunctionParameters(
universe, functionParameters, typeArguments, depth);
if (_Utils.isIdentical(substitutedReturnType, returnType) &&
_Utils.isIdentical(substitutedFunctionParameters, functionParameters))
return rti;
return _Universe._lookupFunctionRti(
universe, substitutedReturnType, substitutedFunctionParameters);
case Rti.kindGenericFunction:
var bounds = Rti._getGenericFunctionBounds(rti);
depth += _Utils.arrayLength(bounds);
var substitutedBounds =
_substituteArray(universe, bounds, typeArguments, depth);
Rti base = Rti._getGenericFunctionBase(rti);
Rti substitutedBase = _substitute(universe, base, typeArguments, depth);
if (_Utils.isIdentical(substitutedBounds, bounds) &&
_Utils.isIdentical(substitutedBase, base)) return rti;
return _Universe._lookupGenericFunctionRti(
universe, substitutedBase, substitutedBounds, true);
case Rti.kindGenericFunctionParameter:
int index = Rti._getGenericFunctionParameterIndex(rti);
// Indices below the current depth are out of scope for substitution and
// can be returned unchanged.
if (index < depth) return rti;
var argument = _Utils.arrayAt(typeArguments, index - depth);
// In order to do a partial substitution - that is, substituting only some
// type parameters rather than all of them - we encode the unsubstituted
// positions of the argument list as `undefined` (which will compare equal
// to `null`).
if (argument == null) return rti;
return _Utils.asRti(argument);
default:
throw AssertionError('Attempted to substitute unexpected RTI kind $kind');
}
}
Object? _substituteArray(
Object? universe, Object? rtiArray, Object? typeArguments, int depth) {
bool changed = false;
int length = _Utils.arrayLength(rtiArray);
Object? result = _Utils.newArrayOrEmpty(length);
for (int i = 0; i < length; i++) {
Rti rti = _Utils.asRti(_Utils.arrayAt(rtiArray, i));
Rti substitutedRti = _substitute(universe, rti, typeArguments, depth);
if (_Utils.isNotIdentical(substitutedRti, rti)) {
changed = true;
}
_Utils.arraySetAt(result, i, substitutedRti);
}
return changed ? result : rtiArray;
}
Object? _substituteNamed(
Object? universe, Object? namedArray, Object? typeArguments, int depth) {
bool changed = false;
int length = _Utils.arrayLength(namedArray);
assert(_Utils.isMultipleOf(length, 3));
Object? result = _Utils.newArrayOrEmpty(length);
for (int i = 0; i < length; i += 3) {
String name = _Utils.asString(_Utils.arrayAt(namedArray, i));
bool isRequired = _Utils.asBool(_Utils.arrayAt(namedArray, i + 1));
Rti rti = _Utils.asRti(_Utils.arrayAt(namedArray, i + 2));
Rti substitutedRti = _substitute(universe, rti, typeArguments, depth);
if (_Utils.isNotIdentical(substitutedRti, rti)) {
changed = true;
}
JS('', '#.splice(#, #, #, #, #)', result, i, 3, name, isRequired,
substitutedRti);
}
return changed ? result : namedArray;
}
_FunctionParameters _substituteFunctionParameters(Object? universe,
_FunctionParameters functionParameters, Object? typeArguments, int depth) {
var requiredPositional =
_FunctionParameters._getRequiredPositional(functionParameters);
var substitutedRequiredPositional =
_substituteArray(universe, requiredPositional, typeArguments, depth);
var optionalPositional =
_FunctionParameters._getOptionalPositional(functionParameters);
var substitutedOptionalPositional =
_substituteArray(universe, optionalPositional, typeArguments, depth);
var named = _FunctionParameters._getNamed(functionParameters);
var substitutedNamed =
_substituteNamed(universe, named, typeArguments, depth);
if (_Utils.isIdentical(substitutedRequiredPositional, requiredPositional) &&
_Utils.isIdentical(substitutedOptionalPositional, optionalPositional) &&
_Utils.isIdentical(substitutedNamed, named)) return functionParameters;
_FunctionParameters result = _FunctionParameters.allocate();
_FunctionParameters._setRequiredPositional(
result, substitutedRequiredPositional);
_FunctionParameters._setOptionalPositional(
result, substitutedOptionalPositional);
_FunctionParameters._setNamed(result, substitutedNamed);
return result;
}
bool _isDartObject(Object? object) => _Utils.instanceOf(object,
JS_BUILTIN('depends:none;effects:none;', JsBuiltin.dartObjectConstructor));
bool _isClosure(Object? object) => _Utils.instanceOf(object,
JS_BUILTIN('depends:none;effects:none;', JsBuiltin.dartClosureConstructor));
/// Stores an Rti on a JavaScript Array (JSArray).
/// Rti is recovered by [_arrayInstanceType].
/// Called from generated code.
// Don't inline. Let the JS engine inline this. The call expression is much
// more compact that the inlined expansion.
@pragma('dart2js:noInline')
Object? _setArrayType(Object? target, Object? rti) {
assert(rti != null);
var rtiProperty = JS_EMBEDDED_GLOBAL('', ARRAY_RTI_PROPERTY);
JS('var', r'#[#] = #', target, rtiProperty, rti);
return target;
}
/// Returns the structural function [Rti] of [closure], or `null`.
/// [closure] must be a subclass of [Closure].
/// Called from generated code.
Rti? closureFunctionType(Object? closure) {
var signatureName = JS_GET_NAME(JsGetName.SIGNATURE_NAME);
var signature = JS('', '#[#]', closure, signatureName);
if (signature != null) {
if (JS('bool', 'typeof # == "number"', signature)) {
return getTypeFromTypesTable(_Utils.asInt(signature));
}
return _Utils.asRti(JS('', '#[#]()', closure, signatureName));
}
return null;
}
/// Returns the Rti type of [object]. Closures have both an interface type
/// (Closures implement `Function`) and a structural function type. Uses
/// [testRti] to choose the appropriate type.
///
/// Called from generated code.
Rti instanceOrFunctionType(Object? object, Rti testRti) {
if (Rti._isUnionOfFunctionType(testRti)) {
if (_isClosure(object)) {
// If [testRti] is e.g. `FutureOr<Action>` (where `Action` is some
// function type), we don't need to worry about the `Future<Action>`
// branch because closures can't be `Future`s.
Rti? rti = closureFunctionType(object);
if (rti != null) return rti;
}
}
return instanceType(object);
}
/// Returns the Rti type of [object].
/// This is the general entry for obtaining the interface type of any value.
/// Called from generated code.
Rti instanceType(Object? object) {
// TODO(sra): Add interceptor-based specializations of this method. Inject a
// _getRti method into (Dart)Object, JSArray, and Interceptor. Then calls to
// this method can be generated as `getInterceptor(o)._getRti(o)`, allowing
// interceptor optimizations to select the specialization. If the only use of
// `getInterceptor` is for calling `_getRti`, then `instanceType` can be
// called, similar to a one-shot interceptor call. This would improve type
// lookup in ListMixin code as the interceptor is JavaScript 'this'.
if (_isDartObject(object)) {
return _instanceType(object);
}
if (_Utils.isArray(object)) {
return _arrayInstanceType(object);
}
var interceptor = getInterceptor(object);
return _instanceTypeFromConstructor(interceptor);
}
/// Returns the Rti type of JavaScript Array [object].
/// Called from generated code.
Rti _arrayInstanceType(Object? object) {
// A JavaScript Array can come from three places:
// 1. This Dart program.
// 2. Another Dart program loaded in the JavaScript environment.
// 3. From outside of a Dart program.
//
// In case 3 we default to a fixed type for all external Arrays. To protect
// against an Array passed between two Dart programs loaded into the same
// JavaScript isolate (communicating e.g. via JS-interop), we check that the
// stored value is 'our' Rti type.
//
// TODO(40175): Investigate if it is more efficient to have each Dart program
// use a unique JavaScript property so that both case 2 and case 3 look like a
// missing value. In ES6 we could use a globally named JavaScript Symbol. For
// IE11 we would have to synthesise a String property-name with almost zero
// chance of conflict.
var rti = JS('', r'#[#]', object, JS_EMBEDDED_GLOBAL('', ARRAY_RTI_PROPERTY));
var defaultRti = getJSArrayInteropRti();
// Case 3.
if (rti == null) return _Utils.asRti(defaultRti);
// Case 2 and perhaps case 3. Check constructor of extracted type against a
// known instance of Rti - this is an easy way to get the constructor.
if (JS('bool', '#.constructor !== #.constructor', rti, defaultRti)) {
return _Utils.asRti(defaultRti);
}
// Case 1.
return _Utils.asRti(rti);
}
/// Returns the Rti type of user-defined class [object].
/// [object] must not be an intercepted class or a closure.
/// Called from generated code.
Rti _instanceType(Object? object) {
var rti = JS('', r'#[#]', object, JS_GET_NAME(JsGetName.RTI_NAME));
return rti != null ? _Utils.asRti(rti) : _instanceTypeFromConstructor(object);
}
String instanceTypeName(Object? object) {
Rti rti = instanceType(object);
return _rtiToString(rti, null);
}
Rti _instanceTypeFromConstructor(Object? instance) {
var constructor = JS('', '#.constructor', instance);
var probe = JS('', r'#[#]', constructor, CONSTRUCTOR_RTI_CACHE_PROPERTY_NAME);
if (probe != null) return _Utils.asRti(probe);
return _instanceTypeFromConstructorMiss(instance, constructor);
}
@pragma('dart2js:noInline')
Rti _instanceTypeFromConstructorMiss(Object? instance, Object? constructor) {
// Subclasses of Closure are synthetic classes. The synthetic classes all
// extend a 'normal' class (Closure, BoundClosure, StaticClosure), so make
// them appear to be the superclass. Instantiations have a `$ti` field so
// don't reach here.
//
// TODO(39214): This will need fixing if we ever use instances of
// StaticClosure for static tear-offs.
//
// TODO(sra): Can this test be avoided, e.g. by putting $ti on the
// prototype of Closure/BoundClosure/StaticClosure classes?
var effectiveConstructor = _isClosure(instance)
? JS('', '#.__proto__.__proto__.constructor', instance)
: constructor;
Rti rti = _Universe.findErasedType(
_theUniverse(), JS('String', '#.name', effectiveConstructor));
JS('', r'#[#] = #', constructor, CONSTRUCTOR_RTI_CACHE_PROPERTY_NAME, rti);
return rti;
}
/// Returns the structural function type of [object], or `null` if the object is
/// not a closure.
Rti? _instanceFunctionType(Object? object) =>
_isClosure(object) ? closureFunctionType(object) : null;
/// Returns Rti from types table. The types table is initialized with recipe
/// strings.
Rti getTypeFromTypesTable(int index) {
var table = JS_EMBEDDED_GLOBAL('', TYPES);
var type = _Utils.arrayAt(table, index);
if (_Utils.isString(type)) {
Rti rti = findType(_Utils.asString(type));
_Utils.arraySetAt(table, index, rti);
return rti;
}
return _Utils.asRti(type);
}
Type getRuntimeType(Object? object) {
Rti rti = _instanceFunctionType(object) ?? instanceType(object);
return createRuntimeType(rti);
}
/// Called from generated code.
Type createRuntimeType(Rti rti) {
_Type? type = Rti._getCachedRuntimeType(rti);
if (type != null) return type;
if (JS_GET_FLAG('PRINT_LEGACY_STARS')) {
return _Type(rti);
} else {
String recipe = Rti._getCanonicalRecipe(rti);
String starErasedRecipe = JS('String', '#.replace(/\\*/g, "")', recipe);
if (starErasedRecipe == recipe) {
return _Type(rti);
}
Rti starErasedRti = _Universe.eval(_theUniverse(), starErasedRecipe, true);
type = Rti._getCachedRuntimeType(starErasedRti) ?? _Type(starErasedRti);
Rti._setCachedRuntimeType(rti, type);
return type;
}
}
/// Called from generated code in the constant pool.
Type typeLiteral(String recipe) {
return createRuntimeType(findType(recipe));
}
/// Implementation of [Type] based on Rti.
class _Type implements Type {
final Rti _rti;
_Type(this._rti) : assert(Rti._getCachedRuntimeType(_rti) == null) {
Rti._setCachedRuntimeType(_rti, this);
}
@override
String toString() => _rtiToString(_rti, null);
}
/// Called from generated code.
///
/// The first time the default `_is` method is called, it replaces itself with a
/// specialized version.
// TODO(sra): Emit code to force-replace the `_is` method, generated dependent
// on the types used in the program. e.g.
//
// findType("bool")._is = H._isBool;
//
// This could be omitted if (1) the `bool` rti is not used directly for a test
// (e.g. we lower a check to a direct helper), and (2) `bool` does not flow to a
// tested type parameter. The trick will be to ensure that `H._isBool` is
// generated.
bool _installSpecializedIsTest(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (isObjectType(testRti)) {
return _finishIsFn(testRti, object, RAW_DART_FUNCTION_REF(_isObject));
}
if (isTopType(testRti)) {
return _finishIsFn(testRti, object, RAW_DART_FUNCTION_REF(_isTop));
}
// `o is T*` generally behaves like `o is T`.
// The exeptions are `Object*` (handled above) and `Never*`
//
// `null is Never` --> `false`
// `null is Never*` --> `true`
Rti unstarred = Rti._getKind(testRti) == Rti.kindStar
? Rti._getStarArgument(testRti)
: testRti;
var isFn = _simpleSpecializedIsTest(unstarred);
if (isFn != null) {
return _finishIsFn(testRti, object, isFn);
}
if (Rti._getKind(unstarred) == Rti.kindInterface) {
String name = Rti._getInterfaceName(unstarred);
var arguments = Rti._getInterfaceTypeArguments(unstarred);
// This recognizes interface types instantiated with Top, which includes the
// common case of interfaces that have no type parameters.
// TODO(sra): Can we easily recognize other interface types instantiated to
// bounds?
if (JS('bool', '#.every(#)', arguments, RAW_DART_FUNCTION_REF(isTopType))) {
String propertyName =
'${JS_GET_NAME(JsGetName.OPERATOR_IS_PREFIX)}${name}';
Rti._setSpecializedTestResource(testRti, propertyName);
if (name == JS_GET_NAME(JsGetName.LIST_CLASS_TYPE_NAME)) {
return _finishIsFn(
testRti, object, RAW_DART_FUNCTION_REF(_isListTestViaProperty));
}
return _finishIsFn(
testRti, object, RAW_DART_FUNCTION_REF(_isTestViaProperty));
}
// fall through to general implementation.
} else if (Rti._getKind(testRti) == Rti.kindQuestion) {
return _finishIsFn(testRti, object,
RAW_DART_FUNCTION_REF(_generalNullableIsTestImplementation));
}
return _finishIsFn(
testRti, object, RAW_DART_FUNCTION_REF(_generalIsTestImplementation));
}
@pragma('dart2js:noInline') // Slightly smaller code.
bool _finishIsFn(Rti testRti, Object? object, Object? isFn) {
Rti._setIsTestFunction(testRti, isFn);
return Rti._isCheck(testRti, object);
}
Object? _simpleSpecializedIsTest(Rti testRti) {
// Note: We must not match `Never` below.
var isFn = null;
if (_Utils.isIdentical(testRti, TYPE_REF<int>())) {
isFn = RAW_DART_FUNCTION_REF(_isInt);
} else if (_Utils.isIdentical(testRti, TYPE_REF<double>()) ||
_Utils.isIdentical(testRti, TYPE_REF<num>())) {
isFn = RAW_DART_FUNCTION_REF(_isNum);
} else if (_Utils.isIdentical(testRti, TYPE_REF<String>())) {
isFn = RAW_DART_FUNCTION_REF(_isString);
} else if (_Utils.isIdentical(testRti, TYPE_REF<bool>())) {
isFn = RAW_DART_FUNCTION_REF(_isBool);
}
return isFn;
}
/// Called from generated code.
///
/// The first time this default `_as` method is called, it replaces itself with
/// a specialized version.
bool _installSpecializedAsCheck(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
var asFn = RAW_DART_FUNCTION_REF(_generalAsCheckImplementation);
if (isTopType(testRti)) {
asFn = RAW_DART_FUNCTION_REF(_asTop);
} else if (isObjectType(testRti)) {
asFn = RAW_DART_FUNCTION_REF(_asObject);
} else {
if (JS_GET_FLAG('LEGACY') || isNullable(testRti)) {
asFn = RAW_DART_FUNCTION_REF(_generalNullableAsCheckImplementation);
}
}
Rti._setAsCheckFunction(testRti, asFn);
return Rti._asCheck(testRti, object);
}
bool _nullIs(Rti testRti) {
int kind = Rti._getKind(testRti);
return isStrongTopType(testRti) ||
isLegacyObjectType(testRti) ||
_Utils.isIdentical(testRti, LEGACY_TYPE_REF<Never>()) ||
kind == Rti.kindQuestion ||
kind == Rti.kindFutureOr && _nullIs(Rti._getFutureOrArgument(testRti)) ||
isNullType(testRti);
}
/// Called from generated code.
bool _generalIsTestImplementation(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (object == null) return _nullIs(testRti);
Rti objectRti = instanceOrFunctionType(object, testRti);
return isSubtype(_theUniverse(), objectRti, testRti);
}
/// Specialized test for `x is T1` where `T1` has the form `T2?`. Test is
/// compositional, calling `T2._is(object)`, so if `T2` has a specialized
/// version, the composed test will be fast (but not quite as fast as a
/// single-step specialization).
///
/// Called from generated code.
bool _generalNullableIsTestImplementation(Object? object) {
if (object == null) return true;
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
Rti baseRti = Rti._getQuestionArgument(testRti);
return Rti._isCheck(baseRti, object);
}
/// Called from generated code.
bool _isTestViaProperty(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (object == null) return _nullIs(testRti);
var tag = Rti._getSpecializedTestResource(testRti);
// This test is redundant with getInterceptor below, but getInterceptor does
// the tests in the wrong order for most tags, so it is usually faster to have
// this check.
if (_isDartObject(object)) {
return JS('bool', '!!#[#]', object, tag);
}
var interceptor = getInterceptor(object);
return JS('bool', '!!#[#]', interceptor, tag);
}
/// Specialized version of [_isTestViaProperty] with faster path for Arrays.
/// Called from generated code.
bool _isListTestViaProperty(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (object == null) return _nullIs(testRti);
// Only JavaScript values with `typeof x == "object"` are Dart Lists. Other
// typeof results (undefined/string/number/boolean/function/symbol/bigint) are
// all non-Lists. Dart `null`, being JavaScript `null` or JavaScript
// `undefined`, is handled above.
if (JS('bool', 'typeof # != "object"', object)) return false;
if (_Utils.isArray(object)) return true;
var tag = Rti._getSpecializedTestResource(testRti);
// This test is redundant with getInterceptor below, but getInterceptor does
// the tests in the wrong order for most tags, so it is usually faster to have
// this check.
if (_isDartObject(object)) {
return JS('bool', '!!#[#]', object, tag);
}
var interceptor = getInterceptor(object);
return JS('bool', '!!#[#]', interceptor, tag);
}
/// General unspecialized 'as' check that works for any type.
/// Called from generated code.
Object? _generalAsCheckImplementation(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (object == null) {
if (JS_GET_FLAG('LEGACY') || isNullable(testRti)) return object;
} else if (Rti._isCheck(testRti, object)) return object;
_failedAsCheck(object, testRti);
}
/// General 'as' check for types that accept `null`.
/// Called from generated code.
Object? _generalNullableAsCheckImplementation(Object? object) {
// This static method is installed on an Rti object as a JavaScript instance
// method. The Rti object is 'this'.
Rti testRti = _Utils.asRti(JS('', 'this'));
if (object == null) {
return object;
} else if (Rti._isCheck(testRti, object)) return object;
_failedAsCheck(object, testRti);
}
void _failedAsCheck(Object? object, Rti testRti) {
Rti objectRti = instanceOrFunctionType(object, testRti);
String message =
_Error.compose(object, objectRti, _rtiToString(testRti, null));
throw _TypeError.fromMessage(message);
}
/// Called from generated code.
Rti checkTypeBound(Rti type, Rti bound, String variable, String methodName) {
if (isSubtype(_theUniverse(), type, bound)) return type;
String message = "The type argument '${_rtiToString(type, null)}' is not"
" a subtype of the type variable bound '${_rtiToString(bound, null)}'"
" of type variable '$variable' in '$methodName'.";
throw _TypeError.fromMessage(message);
}
/// Called from generated code.
throwTypeError(String message) {
throw _TypeError.fromMessage(message);
}
/// Base class to _TypeError.
class _Error extends Error {
final String _message;
_Error(this._message);
static String compose(
Object? object, Rti? objectRti, String checkedTypeDescription) {
String objectDescription = Error.safeToString(object);
objectRti ??= instanceType(object);
String objectTypeDescription = _rtiToString(objectRti, null);
return "${objectDescription}:"
" type '${objectTypeDescription}'"
" is not a subtype of type '${checkedTypeDescription}'";
}
@override
String toString() => _message;
}
class _TypeError extends _Error implements TypeError, CastError {
_TypeError.fromMessage(String message) : super('TypeError: $message');
factory _TypeError.forType(object, String type) {
return _TypeError.fromMessage(_Error.compose(object, null, type));
}
@override
String get message => _message;
}
// Specializations.
//
// Specializations can be placed on Rti objects as the _as and _is
// 'methods'. They can also be called directly called from generated code.
/// Specialization for 'is Object'.
/// Called from generated code via Rti `_is` method.
bool _isObject(Object? object) {
return object != null;
}
/// Specialization for 'as Object'.
/// Called from generated code via Rti `_as` method.
Object? _asObject(Object? object) {
if (JS_GET_FLAG('LEGACY') || object != null) return object;
throw _TypeError.forType(object, 'Object');
}
/// Specialization for 'is dynamic' and other top types.
/// Called from generated code via Rti `_is` method.
bool _isTop(Object? object) {
return true;
}
/// Specialization for 'as dynamic' and other top types.
/// Called from generated code via Rti `_as` methods.
Object? _asTop(Object? object) {
return object;
}
/// Specialization for 'is bool'.
/// Called from generated code.
bool _isBool(Object? object) {
return true == object || false == object;
}
// TODO(fishythefish): Change `dynamic` to `Object?` below once promotion works.
/// Specialization for 'as bool'.
/// Called from generated code.
bool _asBool(Object? object) {
if (true == object) return true;
if (false == object) return false;
throw _TypeError.forType(object, 'bool');
}
/// Specialization for 'as bool*'.
/// Called from generated code.
bool? _asBoolS(dynamic object) {
if (true == object) return true;
if (false == object) return false;
if (object == null) return object;
throw _TypeError.forType(object, 'bool');
}
/// Specialization for 'as bool?'.
/// Called from generated code.
bool? _asBoolQ(dynamic object) {
if (true == object) return true;
if (false == object) return false;
if (object == null) return object;
throw _TypeError.forType(object, 'bool?');
}
/// Specialization for 'as double'.
/// Called from generated code.
double _asDouble(Object? object) {
if (_isNum(object)) return _Utils.asDouble(object);
throw _TypeError.forType(object, 'double');
}
/// Specialization for 'as double*'.
/// Called from generated code.
double? _asDoubleS(dynamic object) {
if (_isNum(object)) return _Utils.asDouble(object);
if (object == null) return object;
throw _TypeError.forType(object, 'double');
}
/// Specialization for 'as double?'.
/// Called from generated code.
double? _asDoubleQ(dynamic object) {
if (_isNum(object)) return _Utils.asDouble(object);
if (object == null) return object;
throw _TypeError.forType(object, 'double?');
}
/// Specialization for 'is int'.
/// Called from generated code.
bool _isInt(Object? object) {
return JS('bool', 'typeof # == "number"', object) &&
JS('bool', 'Math.floor(#) === #', object, object);
}
/// Specialization for 'as int'.
/// Called from generated code.
int _asInt(Object? object) {
if (_isInt(object)) return _Utils.asInt(object);
throw _TypeError.forType(object, 'int');
}
/// Specialization for 'as int*'.
/// Called from generated code.
int? _asIntS(dynamic object) {
if (_isInt(object)) return _Utils.asInt(object);
if (object == null) return object;
throw _TypeError.forType(object, 'int');
}
/// Specialization for 'as int?'.
/// Called from generated code.
int? _asIntQ(dynamic object) {
if (_isInt(object)) return _Utils.asInt(object);
if (object == null) return object;
throw _TypeError.forType(object, 'int?');
}
/// Specialization for 'is num' and 'is double'.
/// Called from generated code.
bool _isNum(Object? object) {
return JS('bool', 'typeof # == "number"', object);
}
/// Specialization for 'as num'.
/// Called from generated code.
num _asNum(Object? object) {
if (_isNum(object)) return _Utils.asNum(object);
throw _TypeError.forType(object, 'num');
}
/// Specialization for 'as num*'.
/// Called from generated code.
num? _asNumS(dynamic object) {
if (_isNum(object)) return _Utils.asNum(object);
if (object == null) return object;
throw _TypeError.forType(object, 'num');
}
/// Specialization for 'as num?'.
/// Called from generated code.
num? _asNumQ(dynamic object) {
if (_isNum(object)) return _Utils.asNum(object);
if (object == null) return object;
throw _TypeError.forType(object, 'num?');
}
/// Specialization for 'is String'.
/// Called from generated code.
bool _isString(Object? object) {
return JS('bool', 'typeof # == "string"', object);
}
/// Specialization for 'as String'.
/// Called from generated code.
String _asString(Object? object) {
if (_isString(object)) return _Utils.asString(object);
throw _TypeError.forType(object, 'String');
}
/// Specialization for 'as String*'.
/// Called from generated code.
String? _asStringS(dynamic object) {
if (_isString(object)) return _Utils.asString(object);
if (object == null) return object;
throw _TypeError.forType(object, 'String');
}
/// Specialization for 'as String?'.
/// Called from generated code.
String? _asStringQ(dynamic object) {
if (_isString(object)) return _Utils.asString(object);
if (object == null) return object;
throw _TypeError.forType(object, 'String?');
}
String _rtiArrayToString(Object? array, List<String>? genericContext) {
String s = '', sep = '';
for (int i = 0; i < _Utils.arrayLength(array); i++) {
s += sep +
_rtiToString(_Utils.asRti(_Utils.arrayAt(array, i)), genericContext);
sep = ', ';
}
return s;
}
String _functionRtiToString(Rti functionType, List<String>? genericContext,
{Object? bounds = null}) {
String typeParametersText = '';
int? outerContextLength;
if (bounds != null) {
int boundsLength = _Utils.arrayLength(bounds);
if (genericContext == null) {
genericContext = <String>[];
} else {
outerContextLength = genericContext.length;
}
int offset = genericContext.length;
for (int i = boundsLength; i > 0; i--) {
genericContext.add('T${offset + i}');
}
String typeSep = '';
typeParametersText = '<';
for (int i = 0; i < boundsLength; i++) {
typeParametersText += typeSep;
typeParametersText += genericContext[genericContext.length - 1 - i];
Rti boundRti = _Utils.asRti(_Utils.arrayAt(bounds, i));
if (!isTopType(boundRti)) {
typeParametersText +=
' extends ' + _rtiToString(boundRti, genericContext);
}
typeSep = ', ';
}
typeParametersText += '>';
}
Rti returnType = Rti._getReturnType(functionType);
_FunctionParameters parameters = Rti._getFunctionParameters(functionType);
var requiredPositional =
_FunctionParameters._getRequiredPositional(parameters);
int requiredPositionalLength = _Utils.arrayLength(requiredPositional);
var optionalPositional =
_FunctionParameters._getOptionalPositional(parameters);
int optionalPositionalLength = _Utils.arrayLength(optionalPositional);
var named = _FunctionParameters._getNamed(parameters);
int namedLength = _Utils.arrayLength(named);
assert(optionalPositionalLength == 0 || namedLength == 0);
String returnTypeText = _rtiToString(returnType, genericContext);
String argumentsText = '';
String sep = '';
for (int i = 0; i < requiredPositionalLength; i++) {
argumentsText += sep +
_rtiToString(_Utils.asRti(_Utils.arrayAt(requiredPositional, i)),
genericContext);
sep = ', ';
}
if (optionalPositionalLength > 0) {
argumentsText += sep + '[';
sep = '';
for (int i = 0; i < optionalPositionalLength; i++) {
argumentsText += sep +
_rtiToString(_Utils.asRti(_Utils.arrayAt(optionalPositional, i)),
genericContext);
sep = ', ';
}
argumentsText += ']';
}
if (namedLength > 0) {
argumentsText += sep + '{';
sep = '';
for (int i = 0; i < namedLength; i += 3) {
argumentsText += sep;
if (_Utils.asBool(_Utils.arrayAt(named, i + 1))) {
argumentsText += 'required ';
}
argumentsText += _rtiToString(
_Utils.asRti(_Utils.arrayAt(named, i + 2)), genericContext) +
' ' +
_Utils.asString(_Utils.arrayAt(named, i));
sep = ', ';
}
argumentsText += '}';
}
if (outerContextLength != null) {
// Pop all of the generic type parameters.
JS('', '#.length = #', genericContext!, outerContextLength);
}
// TODO(fishythefish): Below is the same format as the VM. Change to:
//
// return '${returnTypeText} Function${typeParametersText}(${argumentsText})';
//
return '${typeParametersText}(${argumentsText}) => ${returnTypeText}';
}
String _rtiToString(Rti rti, List<String>? genericContext) {
int kind = Rti._getKind(rti);
if (kind == Rti.kindErased) return 'erased';
if (kind == Rti.kindDynamic) return 'dynamic';
if (kind == Rti.kindVoid) return 'void';
if (kind == Rti.kindNever) return 'Never';
if (kind == Rti.kindAny) return 'any';
if (kind == Rti.kindStar) {
Rti starArgument = Rti._getStarArgument(rti);
String s = _rtiToString(starArgument, genericContext);
if (JS_GET_FLAG('PRINT_LEGACY_STARS')) {
int argumentKind = Rti._getKind(starArgument);
if (argumentKind == Rti.kindFunction ||
argumentKind == Rti.kindGenericFunction) {
s = '(' + s + ')';
}
return s + '*';
} else {
return s;
}
}
if (kind == Rti.kindQuestion) {
Rti questionArgument = Rti._getQuestionArgument(rti);
String s = _rtiToString(questionArgument, genericContext);
int argumentKind = Rti._getKind(questionArgument);
if (argumentKind == Rti.kindFunction ||
argumentKind == Rti.kindGenericFunction) {
s = '(' + s + ')';
}
return s + '?';
}
if (kind == Rti.kindFutureOr) {
Rti futureOrArgument = Rti._getFutureOrArgument(rti);
return 'FutureOr<${_rtiToString(futureOrArgument, genericContext)}>';
}
if (kind == Rti.kindInterface) {
String name = Rti._getInterfaceName(rti);
name = _unminifyOrTag(name);
var arguments = Rti._getInterfaceTypeArguments(rti);
if (arguments.length > 0) {
name += '<' + _rtiArrayToString(arguments, genericContext) + '>';
}
return name;
}
if (kind == Rti.kindFunction) {
return _functionRtiToString(rti, genericContext);
}
if (kind == Rti.kindGenericFunction) {
Rti baseFunctionType = Rti._getGenericFunctionBase(rti);
var bounds = Rti._getGenericFunctionBounds(rti);
return _functionRtiToString(baseFunctionType, genericContext,
bounds: bounds);
}
if (kind == Rti.kindGenericFunctionParameter) {
var context = genericContext!;
int index = Rti._getGenericFunctionParameterIndex(rti);
return context[context.length - 1 - index];
}
return '?';
}
String _unminifyOrTag(String rawClassName) {
String? preserved = unmangleGlobalNameIfPreservedAnyways(rawClassName);
if (preserved != null) return preserved;
return JS_GET_FLAG('MINIFIED') ? 'minified:$rawClassName' : rawClassName;
}
String _rtiArrayToDebugString(Object? array) {
String s = '[', sep = '';
for (int i = 0; i < _Utils.arrayLength(array); i++) {
s += sep + _rtiToDebugString(_Utils.asRti(_Utils.arrayAt(array, i)));
sep = ', ';
}
return s + ']';
}
String functionParametersToString(_FunctionParameters parameters) {
String s = '(', sep = '';
var requiredPositional =
_FunctionParameters._getRequiredPositional(parameters);
int requiredPositionalLength = _Utils.arrayLength(requiredPositional);
var optionalPositional =
_FunctionParameters._getOptionalPositional(parameters);
int optionalPositionalLength = _Utils.arrayLength(optionalPositional);
var named = _FunctionParameters._getNamed(parameters);
int namedLength = _Utils.arrayLength(named);
assert(optionalPositionalLength == 0 || namedLength == 0);
for (int i = 0; i < requiredPositionalLength; i++) {
s += sep +
_rtiToDebugString(_Utils.asRti(_Utils.arrayAt(requiredPositional, i)));
sep = ', ';
}
if (optionalPositionalLength > 0) {
s += sep + '[';
sep = '';
for (int i = 0; i < optionalPositionalLength; i++) {
s += sep +
_rtiToDebugString(
_Utils.asRti(_Utils.arrayAt(optionalPositional, i)));
sep = ', ';
}
s += ']';
}
if (namedLength > 0) {
s += sep + '{';
sep = '';
for (int i = 0; i < namedLength; i += 3) {
s += sep;
if (_Utils.asBool(_Utils.arrayAt(named, i + 1))) {
s += 'required ';
}
s += _rtiToDebugString(_Utils.asRti(_Utils.arrayAt(named, i + 2))) +
' ' +
_Utils.asString(_Utils.arrayAt(named, i));
sep = ', ';
}
s += '}';
}
return s + ')';
}
String _rtiToDebugString(Rti rti) {
int kind = Rti._getKind(rti);
if (kind == Rti.kindErased) return 'erased';
if (kind == Rti.kindDynamic) return 'dynamic';
if (kind == Rti.kindVoid) return 'void';
if (kind == Rti.kindNever) return 'Never';
if (kind == Rti.kindAny) return 'any';
if (kind == Rti.kindStar) {
Rti starArgument = Rti._getStarArgument(rti);
return 'star(${_rtiToDebugString(starArgument)})';
}
if (kind == Rti.kindQuestion) {
Rti questionArgument = Rti._getQuestionArgument(rti);
return 'question(${_rtiToDebugString(questionArgument)})';
}
if (kind == Rti.kindFutureOr) {
Rti futureOrArgument = Rti._getFutureOrArgument(rti);
return 'FutureOr(${_rtiToDebugString(futureOrArgument)})';
}
if (kind == Rti.kindInterface) {
String name = Rti._getInterfaceName(rti);
var arguments = Rti._getInterfaceTypeArguments(rti);
if (_Utils.arrayLength(arguments) == 0) {
return 'interface("$name")';
} else {
return 'interface("$name", ${_rtiArrayToDebugString(arguments)})';
}
}
if (kind == Rti.kindBinding) {
Rti base = Rti._getBindingBase(rti);
var arguments = Rti._getBindingArguments(rti);
return 'binding(${_rtiToDebugString(base)}, ${_rtiArrayToDebugString(arguments)})';
}
if (kind == Rti.kindFunction) {
Rti returnType = Rti._getReturnType(rti);
_FunctionParameters parameters = Rti._getFunctionParameters(rti);
return 'function(${_rtiToDebugString(returnType)}, ${functionParametersToString(parameters)})';
}
if (kind == Rti.kindGenericFunction) {
Rti baseFunctionType = Rti._getGenericFunctionBase(rti);
var bounds = Rti._getGenericFunctionBounds(rti);
return 'genericFunction(${_rtiToDebugString(baseFunctionType)}, ${_rtiArrayToDebugString(bounds)})';
}
if (kind == Rti.kindGenericFunctionParameter) {
int index = Rti._getGenericFunctionParameterIndex(rti);
return 'genericFunctionParameter($index)';
}
return 'other(kind=$kind)';
}
/// Class of static methods for the universe of Rti objects.
///
/// The universe is the manager object for the Rti instances.
///
/// 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() {
// This needs to be kept in sync with `FragmentEmitter.createRtiUniverse` in
// `fragment_emitter.dart`.
return JS(
'',
'{'
'#: new Map(),'
'#: {},'
'#: {},'
'#: {},'
'#: [],' // shared empty array.
'}',
RtiUniverseFieldNames.evalCache,
RtiUniverseFieldNames.typeRules,
RtiUniverseFieldNames.erasedTypes,
RtiUniverseFieldNames.typeParameterVariances,
RtiUniverseFieldNames.sharedEmptyArray);
}
// Field accessors.
static Object evalCache(Object? universe) =>
JS('', '#.#', universe, RtiUniverseFieldNames.evalCache);
static Object typeRules(Object? universe) =>
JS('', '#.#', universe, RtiUniverseFieldNames.typeRules);
static Object erasedTypes(Object? universe) =>
JS('', '#.#', universe, RtiUniverseFieldNames.erasedTypes);
static Object typeParameterVariances(Object? universe) =>
JS('', '#.#', universe, RtiUniverseFieldNames.typeParameterVariances);
static Object? _findRule(Object? universe, String targetType) =>
JS('', '#.#', typeRules(universe), targetType);
static Object? findRule(Object? universe, String targetType) {
var rule = _findRule(universe, targetType);
while (_Utils.isString(rule)) {
rule = _findRule(universe, _Utils.asString(rule));
}
return rule;
}
static Rti findErasedType(Object? universe, String cls) {
var metadata = erasedTypes(universe);
var probe = JS('', '#.#', metadata, cls);
if (probe == null) {
return eval(universe, cls, false);
} else if (_Utils.isNum(probe)) {
int length = _Utils.asInt(probe);
Rti erased = _lookupErasedRti(universe);
Object? arguments = _Utils.newArrayOrEmpty(length);
for (int i = 0; i < length; i++) {
_Utils.arraySetAt(arguments, i, erased);
}
Rti interface = _lookupInterfaceRti(universe, cls, arguments);
JS('', '#.# = #', metadata, cls, interface);
return interface;
} else {
return _Utils.asRti(probe);
}
}
static Object? findTypeParameterVariances(Object? universe, String cls) =>
JS('', '#.#', typeParameterVariances(universe), cls);
static void addRules(Object? universe, Object? rules) =>
_Utils.objectAssign(typeRules(universe), rules);
static void addErasedTypes(Object? universe, Object? types) =>
_Utils.objectAssign(erasedTypes(universe), types);
static void addTypeParameterVariances(Object? universe, Object? variances) =>
_Utils.objectAssign(typeParameterVariances(universe), variances);
static JSArray sharedEmptyArray(Object? universe) => JS('JSUnmodifiableArray',
'#.#', universe, RtiUniverseFieldNames.sharedEmptyArray);
/// Evaluates [recipe] in the global environment.
static Rti eval(Object? universe, String recipe, bool normalize) {
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, recipe);
if (probe != null) return _Utils.asRti(probe);
Rti rti = _parseRecipe(universe, null, recipe, normalize);
_Utils.mapSet(cache, recipe, rti);
return rti;
}
static Rti evalInEnvironment(
Object? universe, Rti environment, String recipe) {
var cache = Rti._getEvalCache(environment);
if (cache == null) {
cache = JS('', 'new Map()');
Rti._setEvalCache(environment, cache);
}
var probe = _Utils.mapGet(cache, recipe);
if (probe != null) return _Utils.asRti(probe);
Rti rti = _parseRecipe(universe, environment, recipe, true);
_Utils.mapSet(cache, recipe, rti);
return rti;
}
static Rti bind(Object? universe, Rti environment, Rti argumentsRti) {
var cache = Rti._getBindCache(environment);
if (cache == null) {
cache = JS('', 'new Map()');
Rti._setBindCache(environment, cache);
}
String argumentsRecipe = Rti._getCanonicalRecipe(argumentsRti);
var probe = _Utils.mapGet(cache, argumentsRecipe);
if (probe != null) return _Utils.asRti(probe);
var argumentsArray;
if (Rti._getKind(argumentsRti) == Rti.kindBinding) {
argumentsArray = Rti._getBindingArguments(argumentsRti);
} else {
argumentsArray = JS('', '[#]', argumentsRti);
}
Rti rti = _lookupBindingRti(universe, environment, argumentsArray);
_Utils.mapSet(cache, argumentsRecipe, rti);
return rti;
}
static Rti bind1(Object? universe, Rti environment, Rti argumentsRti) {
throw UnimplementedError('_Universe.bind1');
}
static Rti evalTypeVariable(Object? universe, Rti environment, String name) {
int kind = Rti._getKind(environment);
if (kind == Rti.kindBinding) {
environment = Rti._getBindingBase(environment);
}
String interfaceName = Rti._getInterfaceName(environment);
var rule = _Universe.findRule(universe, interfaceName);
assert(rule != null);
String? recipe = TypeRule.lookupTypeVariable(rule, name);
if (recipe == null) {
throw 'No "$name" in "${Rti._getCanonicalRecipe(environment)}"';
}
return _Universe.evalInEnvironment(universe, environment, recipe);
}
static Rti _parseRecipe(
Object? universe, Object? environment, String recipe, bool normalize) {
var parser = _Parser.create(universe, environment, recipe, normalize);
Rti rti = _Parser.parse(parser);
return rti;
}
static Rti _installTypeTests(Object? universe, Rti rti) {
// Set up methods to perform type tests. The general as-check methods use
// the is-test method. The is-test method on first use overwrites itself,
// and possibly the as-check methods, with a specialized version.
var asFn = RAW_DART_FUNCTION_REF(_installSpecializedAsCheck);
var isFn = RAW_DART_FUNCTION_REF(_installSpecializedIsTest);
Rti._setAsCheckFunction(rti, asFn);
Rti._setIsTestFunction(rti, isFn);
return rti;
}
static Rti _installRti(Object? universe, String key, Rti rti) {
_Utils.mapSet(evalCache(universe), key, rti);
return rti;
}
// These helpers are used for creating canonical recipes. The key feature of
// the generated code is that it makes no reference to the constant pool,
// which does not exist when the type$ pool is created.
//
// The strange association is so that usage like
//
// s = _recipeJoin3(s, a, b);
//
// associates as `s+=(a+b)` rather than `s=s+a+b`. As recipe fragments are
// small, this tends to create smaller cons-string trees.
static String _recipeJoin(String s1, String s2) => JS_STRING_CONCAT(s1, s2);
static String _recipeJoin3(String s1, String s2, String s3) =>
JS_STRING_CONCAT(s1, JS_STRING_CONCAT(s2, s3));
static String _recipeJoin4(String s1, String s2, String s3, String s4) =>
JS_STRING_CONCAT(s1, JS_STRING_CONCAT(JS_STRING_CONCAT(s2, s3), s4));
static String _recipeJoin5(
String s1, String s2, String s3, String s4, String s5) =>
JS_STRING_CONCAT(s1,
JS_STRING_CONCAT(JS_STRING_CONCAT(JS_STRING_CONCAT(s2, s3), s4), s5));
// For each kind of Rti there are three methods:
//
// * `lookupXXX` which takes the component parts and returns an existing Rti
// object if it exists.
// * `canonicalRecipeOfXXX` that returns the compositional canonical recipe
// for the proposed type.
// * `createXXX` to create the type if it does not exist.
//
// The create method performs normalization before allocating a new Rti. Cache
// keys are not normalized, so if multiple recipes normalize to the same type,
// then their corresponding cache entries will point to the same value. This
// prevents us from having to normalize on every lookup instead of every
// allocation.
static String _canonicalRecipeOfErased() => Recipe.pushErasedString;
static String _canonicalRecipeOfDynamic() => Recipe.pushDynamicString;
static String _canonicalRecipeOfVoid() => Recipe.pushVoidString;
static String _canonicalRecipeOfNever() =>
_recipeJoin(Recipe.pushNeverExtensionString, Recipe.extensionOpString);
static String _canonicalRecipeOfAny() =>
_recipeJoin(Recipe.pushAnyExtensionString, Recipe.extensionOpString);
static String _canonicalRecipeOfStar(Rti baseType) =>
_recipeJoin(Rti._getCanonicalRecipe(baseType), Recipe.wrapStarString);
static String _canonicalRecipeOfQuestion(Rti baseType) =>
_recipeJoin(Rti._getCanonicalRecipe(baseType), Recipe.wrapQuestionString);
static String _canonicalRecipeOfFutureOr(Rti baseType) =>
_recipeJoin(Rti._getCanonicalRecipe(baseType), Recipe.wrapFutureOrString);
static String _canonicalRecipeOfGenericFunctionParameter(int index) =>
_recipeJoin('$index', Recipe.genericFunctionTypeParameterIndexString);
static Rti _lookupErasedRti(Object? universe) {
return _lookupTerminalRti(
universe, Rti.kindErased, _canonicalRecipeOfErased());
}
static Rti _lookupDynamicRti(Object? universe) {
return _lookupTerminalRti(
universe, Rti.kindDynamic, _canonicalRecipeOfDynamic());
}
static Rti _lookupVoidRti(Object? universe) {
return _lookupTerminalRti(universe, Rti.kindVoid, _canonicalRecipeOfVoid());
}
static Rti _lookupNeverRti(Object? universe) {
return _lookupTerminalRti(
universe, Rti.kindNever, _canonicalRecipeOfNever());
}
static Rti _lookupAnyRti(Object? universe) {
return _lookupTerminalRti(universe, Rti.kindAny, _canonicalRecipeOfAny());
}
static Rti _lookupTerminalRti(Object? universe, int kind, String key) {
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(universe, key, _createTerminalRti(universe, kind, key));
}
static Rti _createTerminalRti(Object? universe, int kind, String key) {
Rti rti = Rti.allocate();
Rti._setKind(rti, kind);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static Rti _lookupStarRti(Object? universe, Rti baseType, bool normalize) {
String key = _canonicalRecipeOfStar(baseType);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe, key, _createStarRti(universe, baseType, key, normalize));
}
static Rti _createStarRti(
Object? universe, Rti baseType, String key, bool normalize) {
if (normalize) {
int baseKind = Rti._getKind(baseType);
if (isStrongTopType(baseType) ||
isNullType(baseType) ||
baseKind == Rti.kindQuestion ||
baseKind == Rti.kindStar) {
return baseType;
}
}
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindStar);
Rti._setPrimary(rti, baseType);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static Rti _lookupQuestionRti(
Object? universe, Rti baseType, bool normalize) {
String key = _canonicalRecipeOfQuestion(baseType);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe, key, _createQuestionRti(universe, baseType, key, normalize));
}
static Rti _createQuestionRti(
Object? universe, Rti baseType, String key, bool normalize) {
if (normalize) {
int baseKind = Rti._getKind(baseType);
if (isStrongTopType(baseType) ||
isNullType(baseType) ||
baseKind == Rti.kindQuestion ||
baseKind == Rti.kindFutureOr &&
isNullable(Rti._getFutureOrArgument(baseType))) {
return baseType;
} else if (baseKind == Rti.kindNever ||
_Utils.isIdentical(baseType, LEGACY_TYPE_REF<Never>())) {
return TYPE_REF<Null>();
} else if (baseKind == Rti.kindStar) {
Rti starArgument = Rti._getStarArgument(baseType);
int starArgumentKind = Rti._getKind(starArgument);
if (starArgumentKind == Rti.kindFutureOr &&
isNullable(Rti._getFutureOrArgument(starArgument))) {
return starArgument;
} else {
return Rti._getQuestionFromStar(universe, baseType);
}
}
}
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindQuestion);
Rti._setPrimary(rti, baseType);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static Rti _lookupFutureOrRti(
Object? universe, Rti baseType, bool normalize) {
String key = _canonicalRecipeOfFutureOr(baseType);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe, key, _createFutureOrRti(universe, baseType, key, normalize));
}
static Rti _createFutureOrRti(
Object? universe, Rti baseType, String key, bool normalize) {
if (normalize) {
int baseKind = Rti._getKind(baseType);
if (isTopType(baseType) || isObjectType(baseType)) {
return baseType;
} else if (baseKind == Rti.kindNever) {
return _lookupFutureRti(universe, baseType);
} else if (isNullType(baseType)) {
return TYPE_REF<Future<Null>?>();
}
}
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindFutureOr);
Rti._setPrimary(rti, baseType);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static Rti _lookupGenericFunctionParameterRti(Object? universe, int index) {
String key = _canonicalRecipeOfGenericFunctionParameter(index);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(universe, key,
_createGenericFunctionParameterRti(universe, index, key));
}
static Rti _createGenericFunctionParameterRti(
Object? universe, int index, String key) {
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindGenericFunctionParameter);
Rti._setPrimary(rti, index);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static String _canonicalRecipeJoin(Object? arguments) {
String s = '', sep = '';
int length = _Utils.arrayLength(arguments);
for (int i = 0; i < length; i++) {
Rti argument = _Utils.asRti(_Utils.arrayAt(arguments, i));
String subrecipe = Rti._getCanonicalRecipe(argument);
s = _recipeJoin3(s, sep, subrecipe);
sep = Recipe.separatorString;
}
return s;
}
static String _canonicalRecipeJoinNamed(Object? arguments) {
String s = '', sep = '';
int length = _Utils.arrayLength(arguments);
assert(_Utils.isMultipleOf(length, 3));
for (int i = 0; i < length; i += 3) {
String name = _Utils.asString(_Utils.arrayAt(arguments, i));
bool isRequired = _Utils.asBool(_Utils.arrayAt(arguments, i + 1));
String nameSep = isRequired
? Recipe.requiredNameSeparatorString
: Recipe.nameSeparatorString;
Rti type = _Utils.asRti(_Utils.arrayAt(arguments, i + 2));
String subrecipe = Rti._getCanonicalRecipe(type);
s = _recipeJoin5(s, sep, name, nameSep, subrecipe);
sep = Recipe.separatorString;
}
return s;
}
static String _canonicalRecipeOfInterface(String name, Object? arguments) {
assert(_Utils.isString(name));
String s = _Utils.asString(name);
int length = _Utils.arrayLength(arguments);
if (length > 0) {
s = _recipeJoin4(s, Recipe.startTypeArgumentsString,
_canonicalRecipeJoin(arguments), Recipe.endTypeArgumentsString);
}
return s;
}
static Rti _lookupInterfaceRti(
Object? universe, String name, Object? arguments) {
String key = _canonicalRecipeOfInterface(name, arguments);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe, key, _createInterfaceRti(universe, name, arguments, key));
}
static Rti _createInterfaceRti(
Object? universe, String name, Object? typeArguments, String key) {
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindInterface);
Rti._setPrimary(rti, name);
Rti._setRest(rti, typeArguments);
int length = _Utils.arrayLength(typeArguments);
if (length > 0) {
Rti._setPrecomputed1(rti, _Utils.arrayAt(typeArguments, 0));
}
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static Rti _lookupFutureRti(Object? universe, Rti base) =>
_lookupInterfaceRti(universe,
JS_GET_NAME(JsGetName.FUTURE_CLASS_TYPE_NAME), JS('', '[#]', base));
static String _canonicalRecipeOfBinding(Rti base, Object? arguments) {
return _recipeJoin5(
Rti._getCanonicalRecipe(base),
// TODO(sra): Omit when base encoding is Rti without ToType:
Recipe.toTypeString,
Recipe.startTypeArgumentsString,
_canonicalRecipeJoin(arguments),
Recipe.endTypeArgumentsString);
}
/// [arguments] becomes owned by the created Rti.
static Rti _lookupBindingRti(Object? universe, Rti base, Object? arguments) {
Rti newBase = base;
var newArguments = arguments;
if (Rti._getKind(base) == Rti.kindBinding) {
newBase = Rti._getBindingBase(base);
newArguments =
_Utils.arrayConcat(Rti._getBindingArguments(base), arguments);
}
String key = _canonicalRecipeOfBinding(newBase, newArguments);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe, key, _createBindingRti(universe, newBase, newArguments, key));
}
static Rti _createBindingRti(
Object? universe, Rti base, Object? arguments, String key) {
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindBinding);
Rti._setPrimary(rti, base);
Rti._setRest(rti, arguments);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static String _canonicalRecipeOfFunction(
Rti returnType, _FunctionParameters parameters) =>
_recipeJoin(Rti._getCanonicalRecipe(returnType),
_canonicalRecipeOfFunctionParameters(parameters));
static String _canonicalRecipeOfFunctionParameters(
_FunctionParameters parameters) {
var requiredPositional =
_FunctionParameters._getRequiredPositional(parameters);
int requiredPositionalLength = _Utils.arrayLength(requiredPositional);
var optionalPositional =
_FunctionParameters._getOptionalPositional(parameters);
int optionalPositionalLength = _Utils.arrayLength(optionalPositional);
var named = _FunctionParameters._getNamed(parameters);
int namedLength = _Utils.arrayLength(named);
assert(optionalPositionalLength == 0 || namedLength == 0);
String recipe = _recipeJoin(Recipe.startFunctionArgumentsString,
_canonicalRecipeJoin(requiredPositional));
if (optionalPositionalLength > 0) {
String sep = requiredPositionalLength > 0 ? Recipe.separatorString : '';
recipe = _recipeJoin5(
recipe,
sep,
Recipe.startOptionalGroupString,
_canonicalRecipeJoin(optionalPositional),
Recipe.endOptionalGroupString);
}
if (namedLength > 0) {
String sep = requiredPositionalLength > 0 ? Recipe.separatorString : '';
recipe = _recipeJoin5(recipe, sep, Recipe.startNamedGroupString,
_canonicalRecipeJoinNamed(named), Recipe.endNamedGroupString);
}
return _recipeJoin(recipe, Recipe.endFunctionArgumentsString);
}
static Rti _lookupFunctionRti(
Object? universe, Rti returnType, _FunctionParameters parameters) {
String key = _canonicalRecipeOfFunction(returnType, parameters);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(universe, key,
_createFunctionRti(universe, returnType, parameters, key));
}
static Rti _createFunctionRti(Object? universe, Rti returnType,
_FunctionParameters parameters, String key) {
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindFunction);
Rti._setPrimary(rti, returnType);
Rti._setRest(rti, parameters);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
static String _canonicalRecipeOfGenericFunction(
Rti baseFunctionType, Object? bounds) =>
_recipeJoin4(
Rti._getCanonicalRecipe(baseFunctionType),
Recipe.startTypeArgumentsString,
_canonicalRecipeJoin(bounds),
Recipe.endTypeArgumentsString);
static Rti _lookupGenericFunctionRti(
Object? universe, Rti baseFunctionType, Object? bounds, bool normalize) {
String key = _canonicalRecipeOfGenericFunction(baseFunctionType, bounds);
var cache = evalCache(universe);
var probe = _Utils.mapGet(cache, key);
if (probe != null) return _Utils.asRti(probe);
return _installRti(
universe,
key,
_createGenericFunctionRti(
universe, baseFunctionType, bounds, key, normalize));
}
static Rti _createGenericFunctionRti(Object? universe, Rti baseFunctionType,
Object? bounds, String key, bool normalize) {
if (normalize) {
int length = _Utils.arrayLength(bounds);
int count = 0;
Object? typeArguments = _Utils.newArrayOrEmpty(length);
for (int i = 0; i < length; i++) {
Rti bound = _Utils.asRti(_Utils.arrayAt(bounds, i));
if (Rti._getKind(bound) == Rti.kindNever) {
_Utils.arraySetAt(typeArguments, i, bound);
count++;
}
}
if (count > 0) {
var substitutedBase =
_substitute(universe, baseFunctionType, typeArguments, 0);
var substitutedBounds =
_substituteArray(universe, bounds, typeArguments, 0);
return _lookupGenericFunctionRti(
universe,
substitutedBase,
substitutedBounds,
_Utils.isNotIdentical(bounds, substitutedBounds));
}
}
Rti rti = Rti.allocate();
Rti._setKind(rti, Rti.kindGenericFunction);
Rti._setPrimary(rti, baseFunctionType);
Rti._setRest(rti, bounds);
Rti._setCanonicalRecipe(rti, key);
return _installTypeTests(universe, rti);
}
}
/// Class of static methods implementing recipe parser.
///
/// The recipe is a sequence of operations on a stack machine. The operations
/// are described below using the format
///
/// operation: stack elements before --- stack elements after
///
/// integer: --- integer-value
///
/// identifier: --- string-value
///
/// identifier-with-one-period: --- type-variable-value
///
/// Period may be in any position, including first and last e.g. `.x`.
///
/// ',': ---
///
/// Ignored. Used to separate elements.
///
/// ';': item --- ToType(item)
///
/// Used to separate elements.
///
/// '#': --- erasedType
///
/// '@': --- dynamicType
///
/// '~': --- voidType
///
/// '?': type --- type?
///
/// '&': 0 --- NeverType
/// '&': 1 --- anyType
///
/// Escape op-code with small integer values for encoding rare operations.
///
/// '<': --- position
///
/// Saves (pushes) position register, sets position register to end of stack.
///
/// '>': name saved-position type ... type --- name<type, ..., type>
/// '>': type saved-position type ... type --- binding(type, type, ..., type)
///
/// When first element is a String: Creates interface type from string 'name'
/// and the types pushed since the position register was last set. The types
/// are converted with a ToType operation. Restores position register to
/// previous saved value.
///
/// When first element is an Rti: Creates binding Rti wrapping the first
/// element. Binding Rtis are flattened: if the first element is a binding
/// Rti, the new binding Rti has the concatentation of the first element
/// bindings and new type.
///
///
/// The ToType operation coerces an item to an Rti. This saves encoding looking
/// up simple interface names and indexed variables.
///
/// ToType(string): Creates an interface Rti for the non-generic class.
/// ToType(integer): Indexes into the environment.
/// ToType(Rti): Same Rti
///
///
/// Notes on enviroments and indexing.
///
/// To avoid creating a binding Rti for a single function type parameter, the
/// type is passed without creating a 1-tuple object. This means that the
/// interface Rti for, say, `Map<num,dynamic>` serves as two environments with
/// different shapes. It is a class environment (K=num, V=dynamic) and a simple
/// 1-tuple environment. This is supported by index '0' refering to the whole
/// type, and '1 and '2' refering to K and V positionally:
///
/// interface("Map", [num,dynamic])
/// 0 1 2
///
/// Thus the type expression `List<K>` encodes as `List<1>` and in this
/// environment evaluates to `List<num>`. `List<Map<K,V>>` could be encoded as
/// either `List<0>` or `List<Map<1,2>>` (and in this environment evaluates to
/// `List<Map<num,dynamic>>`).
///
/// When `Map<num,dynamic>` is combined with a binding `<int,bool>` (e.g. inside
/// the instance method `Map<K,V>.cast<RK,RV>()`), '0' refers to the base object
/// of the binding, and then the numbering counts the bindings followed by the
/// class parameters.
///
/// binding(interface("Map", [num,dynamic]), [int, bool])
/// 0 3 4 1 2
///
/// Any environment can be reconstructed via a recipe. The above enviroment for
/// method `cast` can be constructed as the ground term
/// `Map<num,dynamic><int,bool>`, or (somewhat pointlessly) reconstructed via
/// `0<1,2>` or `Map<3,4><1,2>`. The ability to construct an environment
/// directly rather than via `bind` calls is used in folding sequences of `eval`
/// and `bind` calls.
///
/// While a single type parameter is passed as the type, multiple type
/// parameters are passed as a tuple. Tuples are encoded as a binding with an
/// ignored base. `dynamic` can be used as the base, giving an encoding like
/// `@<int,bool>`.
///
/// Bindings flatten, so `@<int><bool><num>` is the same as `@<int,bool,num>`.
///
/// The base of a binding does not have to have type parameters. Consider
/// `CodeUnits`, which mixes in `ListMixin<int>`. The environment inside of
/// `ListMixin.fold` (from the call `x.codeUnits.fold<bool>(...)`) would be
///
/// binding(interface("CodeUnits", []), [bool])
///
/// This can be encoded as `CodeUnits;<bool>` (note the `;` to force ToType to
/// avoid creating an interface type Rti with a single class type
/// argument). Metadata about the supertypes is used to resolve the recipe
/// `ListMixin.E` to `int`.
class _Parser {
_Parser._() {
throw UnimplementedError('_Parser is static methods only');
}
/// Creates a parser object for parsing a recipe against an environment in a
/// universe.
///
/// Marked as no-inline so the object literal is not cloned by inlining.
@pragma('dart2js:noInline')
static Object create(
Object? universe, Object? environment, String recipe, bool normalize) {
return JS(
'',
'{'
'u:#,' // universe
'e:#,' // environment
'r:#,' // recipe
's:[],' // stack
'p:0,' // position of sequence start
'n:#,' // whether to normalize
'}',
universe,
environment,
recipe,
normalize);
}
// Field accessors for the parser.
static Object universe(Object? parser) => JS('', '#.u', parser);
static Rti environment(Object? parser) => JS('Rti', '#.e', parser);
static String recipe(Object? parser) => JS('String', '#.r', parser);
static Object stack(Object? parser) => JS('', '#.s', parser);
static int position(Object? parser) => JS('int', '#.p', parser);
static void setPosition(Object? parser, int p) {
JS('', '#.p = #', parser, p);
}
static bool normalize(Object? parser) => JS('bool', '#.n', parser);
static int charCodeAt(String s, int i) => JS('int', '#.charCodeAt(#)', s, i);
static void push(Object? stack, Object? value) {
JS('', '#.push(#)', stack, value);
}
static Object? pop(Object? stack) => JS('', '#.pop()', stack);
static Rti parse(Object? parser) {
String source = _Parser.recipe(parser);
var stack = _Parser.stack(parser);
int i = 0;
while (i < source.length) {
int ch = charCodeAt(source, i);
if (Recipe.isDigit(ch)) {
i = handleDigit(i + 1, ch, source, stack);
} else if (Recipe.isIdentifierStart(ch)) {
i = handleIdentifier(parser, i, source, stack, false);
} else if (ch == Recipe.period) {
i = handleIdentifier(parser, i, source, stack, true);
} else {
i++;
switch (ch) {
case Recipe.separator:
break;
case Recipe.nameSeparator:
push(stack, false);
break;
case Recipe.requiredNameSeparator:
push(stack, true);
break;
case Recipe.toType:
push(stack,
toType(universe(parser), environment(parser), pop(stack)));
break;
case Recipe.genericFunctionTypeParameterIndex:
push(stack,
toGenericFunctionParameter(universe(parser), pop(stack)));
break;
case Recipe.pushErased:
push(stack, _Universe._lookupErasedRti(universe(parser)));
break;
case Recipe.pushDynamic:
push(stack, _Universe._lookupDynamicRti(universe(parser)));
break;
case Recipe.pushVoid:
push(stack, _Universe._lookupVoidRti(universe(parser)));
break;
case Recipe.startTypeArguments:
pushStackFrame(parser, stack);
break;
case Recipe.endTypeArguments:
handleTypeArguments(parser, stack);
break;
case Recipe.extensionOp:
handleExtendedOperations(parser, stack);
break;
case Recipe.wrapStar:
var u = universe(parser);
push(
stack,
_Universe._lookupStarRti(
u,
toType(u, environment(parser), pop(stack)),
normalize(parser)));
break;
case Recipe.wrapQuestion:
var u = universe(parser);
push(
stack,
_Universe._lookupQuestionRti(
u,
toType(u, environment(parser), pop(stack)),
normalize(parser)));
break;
case Recipe.wrapFutureOr:
var u = universe(parser);
push(
stack,
_Universe._lookupFutureOrRti(
u,
toType(u, environment(parser), pop(stack)),
normalize(parser)));
break;
case Recipe.startFunctionArguments:
pushStackFrame(parser, stack);
break;
case Recipe.endFunctionArguments:
handleFunctionArguments(parser, stack);
break;
case Recipe.startOptionalGroup:
pushStackFrame(parser, stack);
break;
case Recipe.endOptionalGroup:
handleOptionalGroup(parser, stack);
break;
case Recipe.startNamedGroup:
pushStackFrame(parser, stack);
break;
case Recipe.endNamedGroup:
handleNamedGroup(parser, stack);
break;
default:
JS('', 'throw "Bad character " + #', ch);
}
}
}
var item = pop(stack);
return toType(universe(parser), environment(parser), item);
}
static void pushStackFrame(Object? parser, Object? stack) {
push(stack, position(parser));
setPosition(parser, _Utils.arrayLength(stack));
}
static int handleDigit(int i, int digit, String source, Object? stack) {
int value = Recipe.digitValue(digit);
for (; i < source.length; i++) {
int ch = charCodeAt(source, i);
if (!Recipe.isDigit(ch)) break;
value = value * 10 + Recipe.digitValue(ch);
}
push(stack, value);
return i;
}
static int handleIdentifier(
Object? parser, int start, String source, Object? stack, bool hasPeriod) {
int i = start + 1;
for (; i < source.length; i++) {
int ch = charCodeAt(source, i);
if (ch == Recipe.period) {
if (hasPeriod) break;
hasPeriod = true;
} else if (Recipe.isIdentifierStart(ch) || Recipe.isDigit(ch)) {
// Accept.
} else {
break;
}
}
String string = _Utils.substring(source, start, i);
if (hasPeriod) {
push(
stack,
_Universe.evalTypeVariable(
universe(parser), environment(parser), string));
} else {
push(stack, string);
}
return i;
}
static void handleTypeArguments(Object? parser, Object? stack) {
var universe = _Parser.universe(parser);
var arguments = collectArray(parser, stack);
var head = pop(stack);
if (_Utils.isString(head)) {
String name = _Utils.asString(head);
push(stack, _Universe._lookupInterfaceRti(universe, name, arguments));
} else {
Rti base = toType(universe, environment(parser), head);
switch (Rti._getKind(base)) {
case Rti.kindFunction:
push(
stack,
_Universe._lookupGenericFunctionRti(
universe, base, arguments, normalize(parser)));
break;
default:
push(stack, _Universe._lookupBindingRti(universe, base, arguments));
break;
}
}
}
static const int optionalPositionalSentinel = -1;
static const int namedSentinel = -2;
static void handleFunctionArguments(Object? parser, Object? stack) {
var universe = _Parser.universe(parser);
_FunctionParameters parameters = _FunctionParameters.allocate();
Object? optionalPositional = _Universe.sharedEmptyArray(universe);
Object? named = _Universe.sharedEmptyArray(universe);
var head = pop(stack);
if (_Utils.isNum(head)) {
int sentinel = _Utils.asInt(head);
switch (sentinel) {
case optionalPositionalSentinel:
optionalPositional = pop(stack);
break;
case namedSentinel:
named = pop(stack);
break;
default:
push(stack, head);
break;
}
} else {
push(stack, head);
}
_FunctionParameters._setRequiredPositional(
parameters, collectArray(parser, stack));
_FunctionParameters._setOptionalPositional(parameters, optionalPositional);
_FunctionParameters._setNamed(parameters, named);
Rti returnType = toType(universe, environment(parser), pop(stack));
push(stack, _Universe._lookupFunctionRti(universe, returnType, parameters));
}
static void handleOptionalGroup(Object? parser, Object? stack) {
var parameters = collectArray(parser, stack);
push(stack, parameters);
push(stack, optionalPositionalSentinel);
}
static void handleNamedGroup(Object? parser, Object? stack) {
var parameters = collectNamed(parser, stack);
push(stack, parameters);
push(stack, namedSentinel);
}
static void handleExtendedOperations(Object? parser, Object? stack) {
var top = pop(stack);
if (0 == top) {
push(stack, _Universe._lookupNeverRti(universe(parser)));
return;
}
if (1 == top) {
push(stack, _Universe._lookupAnyRti(universe(parser)));
return;
}
throw AssertionError('Unexpected extended operation $top');
}
static JSArray collectArray(Object? parser, Object? stack) {
var array = _Utils.arraySplice(stack, position(parser));
toTypes(_Parser.universe(parser), environment(parser), array);
setPosition(parser, _Utils.asInt(pop(stack)));
return array;
}
static JSArray collectNamed(Object? parser, Object? stack) {
var array = _Utils.arraySplice(stack, position(parser));
toTypesNamed(_Parser.universe(parser), environment(parser), array);
setPosition(parser, _Utils.asInt(pop(stack)));
return array;
}
/// Coerce a stack item into an Rti object. Strings are converted to interface
/// types, integers are looked up in the type environment.
static Rti toType(Object? universe, Rti environment, Object? item) {
if (_Utils.isString(item)) {
String name = _Utils.asString(item);
return _Universe._lookupInterfaceRti(
universe, name, _Universe.sharedEmptyArray(universe));
} else if (_Utils.isNum(item)) {
return _Parser.indexToType(universe, environment, _Utils.asInt(item));
} else {
return _Utils.asRti(item);
}
}
static void toTypes(Object? universe, Rti environment, Object? items) {
int length = _Utils.arrayLength(items);
for (int i = 0; i < length; i++) {
var item = _Utils.arrayAt(items, i);
Rti type = toType(universe, environment, item);
_Utils.arraySetAt(items, i, type);
}
}
static void toTypesNamed(Object? universe, Rti environment, Object? items) {
int length = _Utils.arrayLength(items);
assert(_Utils.isMultipleOf(length, 3));
for (int i = 2; i < length; i += 3) {
var item = _Utils.arrayAt(items, i);
Rti type = toType(universe, environment, item);
_Utils.arraySetAt(items, i, type);
}
}
static Rti indexToType(Object? universe, Rti environment, int index) {
int kind = Rti._getKind(environment);
if (kind == Rti.kindBinding) {
if (index == 0) return Rti._getBindingBase(environment);
var typeArguments = Rti._getBindingArguments(environment);
int len = _Utils.arrayLength(typeArguments);
if (index <= len) {
return _Utils.asRti(_Utils.arrayAt(typeArguments, index - 1));
}
// Is index into interface Rti in base.
index -= len;
environment = Rti._getBindingBase(environment);
kind = Rti._getKind(environment);
} else {
if (index == 0) return environment;
}
if (kind != Rti.kindInterface) {
throw AssertionError('Indexed base must be an interface type');
}
var typeArguments = Rti._getInterfaceTypeArguments(environment);
int len = _Utils.arrayLength(typeArguments);
if (index <= len) {
return _Utils.asRti(_Utils.arrayAt(typeArguments, index - 1));
}
throw AssertionError('Bad index $index for $environment');
}
static Rti toGenericFunctionParameter(Object? universe, Object? item) {
assert(_Utils.isNum(item));
return _Universe._lookupGenericFunctionParameterRti(
universe, _Utils.asInt(item));
}
}
/// Represents the set of supertypes and type variable bindings for a given
/// target type. The target type itself is not stored on the [TypeRule].
class TypeRule {
TypeRule._() {
throw UnimplementedError("TypeRule is static methods only.");
}
static String? lookupTypeVariable(Object? rule, String typeVariable) =>
JS('', '#.#', rule, typeVariable);
static JSArray? lookupSupertype(Object? rule, String supertype) =>
JS('', '#.#', rule, supertype);
}
// This needs to be kept in sync with `Variance` in `entities.dart`.
class Variance {
// TODO(fishythefish): Try bitmask representation.
static const int legacyCovariant = 0;
static const int covariant = 1;
static const int contravariant = 2;
static const int invariant = 3;
}
// -------- Subtype tests ------------------------------------------------------
// Future entry point from compiled code.
bool isSubtype(Object? universe, Rti s, Rti t) {
return _isSubtype(universe, s, null, t, null);
}
/// Based on
/// https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md#rules
/// and https://github.com/dart-lang/language/pull/388.
/// In particular, the bulk of the structure is derived from the former
/// resource, with a few adaptations taken from the latter.
/// - We freely skip subcases which would have already been handled by a
/// previous case.
/// - Some rules are reordered in conjunction with the previous point to reduce
/// the amount of casework.
/// - Left Type Variable Bound in particular is split into two pieces: an
/// optimistic check performed early in the algorithm to reduce the number of
/// backtracking cases when a union appears on the right, and a pessimistic
/// check performed at the usual place in order to completely eliminate the
/// case.
/// - Function type rules are applied before interface type rules.
///
/// [s] is considered a legacy subtype of [t] if [s] would be a subtype of [t]
/// in a modification of the NNBD rules in which `?` on types were ignored, `*`
/// were added to each time, and `required` parameters were treated as
/// optional. In effect, `Never` is equivalent to `Null`, `Null` is restored to
/// the bottom of the type hierarchy, `Object` is treated as nullable, and
/// `required` is ignored on named parameters. This should provide the same
/// subtyping results as pre-NNBD Dart.
bool _isSubtype(Object? universe, Rti s, Object? sEnv, Rti t, Object? tEnv) {
bool isLegacy = JS_GET_FLAG('LEGACY');
// Reflexivity:
if (_Utils.isIdentical(s, t)) return true;
// Right Top:
if (isTopType(t)) return true;
int sKind = Rti._getKind(s);
if (sKind == Rti.kindAny) return true;
// Left Top:
if (isStrongTopType(s)) return false;
// Left Bottom:
if (isBottomType(s)) return true;
// Left Type Variable Bound 1:
bool leftTypeVariable = sKind == Rti.kindGenericFunctionParameter;
if (leftTypeVariable) {
int index = Rti._getGenericFunctionParameterIndex(s);
Rti bound = _Utils.asRti(_Utils.arrayAt(sEnv, index));
if (_isSubtype(universe, bound, sEnv, t, tEnv)) return true;
}
int tKind = Rti._getKind(t);
// Left Null:
// Note: Interchanging the Left Null and Right Object rules allows us to
// reduce casework.
if (!isLegacy && isNullType(s)) {
if (tKind == Rti.kindFutureOr) {
return _isSubtype(universe, s, sEnv, Rti._getFutureOrArgument(t), tEnv);
}
return isNullType(t) || tKind == Rti.kindQuestion || tKind == Rti.kindStar;
}
// Right Object:
if (!isLegacy && isObjectType(t)) {
if (sKind == Rti.kindFutureOr) {
return _isSubtype(universe, Rti._getFutureOrArgument(s), sEnv, t, tEnv);
}
if (sKind == Rti.kindStar) {
return _isSubtype(universe, Rti._getStarArgument(s), sEnv, t, tEnv);
}
return sKind != Rti.kindQuestion;
}
// Left Legacy:
if (sKind == Rti.kindStar) {
return _isSubtype(universe, Rti._getStarArgument(s), sEnv, t, tEnv);
}
// Right Legacy:
if (tKind == Rti.kindStar) {
return _isSubtype(
universe,
s,
sEnv,
isLegacy
? Rti._getStarArgument(t)
: Rti._getQuestionFromStar(universe, t),
tEnv);
}
// Left FutureOr:
if (sKind == Rti.kindFutureOr) {
if (!_isSubtype(universe, Rti._getFutureOrArgument(s), sEnv, t, tEnv)) {
return false;
}
return _isSubtype(
universe, Rti._getFutureFromFutureOr(universe, s), sEnv, t, tEnv);
}
// Left Nullable:
if (sKind == Rti.kindQuestion) {
return (isLegacy ||
_isSubtype(universe, TYPE_REF<Null>(), sEnv, t, tEnv)) &&
_isSubtype(universe, Rti._getQuestionArgument(s), sEnv, t, tEnv);
}
// Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore
// elided.
// Type Variable Reflexivity 2 does not apply at runtime.
// Right Promoted Variable does not apply at runtime.
// Right FutureOr:
if (tKind == Rti.kindFutureOr) {
if (_isSubtype(universe, s, sEnv, Rti._getFutureOrArgument(t), tEnv)) {
return true;
}
return _isSubtype(
universe, s, sEnv, Rti._getFutureFromFutureOr(universe, t), tEnv);
}
// Right Nullable:
if (tKind == Rti.kindQuestion) {
return (!isLegacy &&
_isSubtype(universe, s, sEnv, TYPE_REF<Null>(), tEnv)) ||
_isSubtype(universe, s, sEnv, Rti._getQuestionArgument(t), tEnv);
}
// Left Promoted Variable does not apply at runtime.
// Left Type Variable Bound 2:
if (leftTypeVariable) return false;
// Function Type/Function:
if ((sKind == Rti.kindFunction || sKind == Rti.kindGenericFunction) &&
isFunctionType(t)) {
return true;
}
// Positional Function Types + Named Function Types:
// TODO(fishythefish): Disallow JavaScriptFunction as a subtype of function
// types using features inaccessible from JavaScript.
if (tKind == Rti.kindGenericFunction) {
if (isJsFunctionType(s)) return true;
if (sKind != Rti.kindGenericFunction) return false;
var sBounds = Rti._getGenericFunctionBounds(s);
var tBounds = Rti._getGenericFunctionBounds(t);
int sLength = _Utils.arrayLength(sBounds);
int tLength = _Utils.arrayLength(tBounds);
if (sLength != tLength) return false;
sEnv = sEnv == null ? sBounds : _Utils.arrayConcat(sBounds, sEnv);
tEnv = tEnv == null ? tBounds : _Utils.arrayConcat(tBounds, tEnv);
for (int i = 0; i < sLength; i++) {
var sBound = _Utils.asRti(_Utils.arrayAt(sBounds, i));
var tBound = _Utils.asRti(_Utils.arrayAt(tBounds, i));
if (!_isSubtype(universe, sBound, sEnv, tBound, tEnv) ||
!_isSubtype(universe, tBound, tEnv, sBound, sEnv)) {
return false;
}
}
return _isFunctionSubtype(universe, Rti._getGenericFunctionBase(s), sEnv,
Rti._getGenericFunctionBase(t), tEnv);
}
if (tKind == Rti.kindFunction) {
if (isJsFunctionType(s)) return true;
if (sKind != Rti.kindFunction) return false;
return _isFunctionSubtype(universe, s, sEnv, t, tEnv);
}
// Interface Compositionality + Super-Interface:
if (sKind == Rti.kindInterface) {
if (tKind != Rti.kindInterface) return false;
return _isInterfaceSubtype(universe, s, sEnv, t, tEnv);
}
return false;
}
bool _isFunctionSubtype(
Object? universe, Rti s, Object? sEnv, Rti t, Object? tEnv) {
assert(Rti._getKind(s) == Rti.kindFunction);
assert(Rti._getKind(t) == Rti.kindFunction);
Rti sReturnType = Rti._getReturnType(s);
Rti tReturnType = Rti._getReturnType(t);
if (!_isSubtype(universe, sReturnType, sEnv, tReturnType, tEnv)) {
return false;
}
_FunctionParameters sParameters = Rti._getFunctionParameters(s);
_FunctionParameters tParameters = Rti._getFunctionParameters(t);
var sRequiredPositional =
_FunctionParameters._getRequiredPositional(sParameters);
var tRequiredPositional =
_FunctionParameters._getRequiredPositional(tParameters);
int sRequiredPositionalLength = _Utils.arrayLength(sRequiredPositional);
int tRequiredPositionalLength = _Utils.arrayLength(tRequiredPositional);
if (sRequiredPositionalLength > tRequiredPositionalLength) return false;
int requiredPositionalDelta =
tRequiredPositionalLength - sRequiredPositionalLength;
var sOptionalPositional =
_FunctionParameters._getOptionalPositional(sParameters);
var tOptionalPositional =
_FunctionParameters._getOptionalPositional(tParameters);
int sOptionalPositionalLength = _Utils.arrayLength(sOptionalPositional);
int tOptionalPositionalLength = _Utils.arrayLength(tOptionalPositional);
if (sRequiredPositionalLength + sOptionalPositionalLength <
tRequiredPositionalLength + tOptionalPositionalLength) return false;
for (int i = 0; i < sRequiredPositionalLength; i++) {
Rti sParameter = _Utils.asRti(_Utils.arrayAt(sRequiredPositional, i));
Rti tParameter = _Utils.asRti(_Utils.arrayAt(tRequiredPositional, i));
if (!_isSubtype(universe, tParameter, tEnv, sParameter, sEnv)) {
return false;
}
}
for (int i = 0; i < requiredPositionalDelta; i++) {
Rti sParameter = _Utils.asRti(_Utils.arrayAt(sOptionalPositional, i));
Rti tParameter = _Utils.asRti(
_Utils.arrayAt(tRequiredPositional, sRequiredPositionalLength + i));
if (!_isSubtype(universe, tParameter, tEnv, sParameter, sEnv)) {
return false;
}
}
for (int i = 0; i < tOptionalPositionalLength; i++) {
Rti sParameter = _Utils.asRti(
_Utils.arrayAt(sOptionalPositional, requiredPositionalDelta + i));
Rti tParameter = _Utils.asRti(_Utils.arrayAt(tOptionalPositional, i));
if (!_isSubtype(universe, tParameter, tEnv, sParameter, sEnv)) {
return false;
}
}
var sNamed = _FunctionParameters._getNamed(sParameters);
var tNamed = _FunctionParameters._getNamed(tParameters);
int sNamedLength = _Utils.arrayLength(sNamed);
int tNamedLength = _Utils.arrayLength(tNamed);
int sIndex = 0;
for (int tIndex = 0; tIndex < tNamedLength; tIndex += 3) {
String tName = _Utils.asString(_Utils.arrayAt(tNamed, tIndex));
while (true) {
if (sIndex >= sNamedLength) return false;
String sName = _Utils.asString(_Utils.arrayAt(sNamed, sIndex));
sIndex += 3;
if (_Utils.stringLessThan(tName, sName)) return false;
bool sIsRequired = !JS_GET_FLAG('LEGACY') &&
_Utils.asBool(_Utils.arrayAt(sNamed, sIndex - 2));
if (_Utils.stringLessThan(sName, tName)) {
if (sIsRequired) return false;
continue;
}
bool tIsRequired = !JS_GET_FLAG('LEGACY') &&
_Utils.asBool(_Utils.arrayAt(tNamed, tIndex + 1));
if (sIsRequired && !tIsRequired) return false;
Rti sType = _Utils.asRti(_Utils.arrayAt(sNamed, sIndex - 1));
Rti tType = _Utils.asRti(_Utils.arrayAt(tNamed, tIndex + 2));
if (!_isSubtype(universe, tType, tEnv, sType, sEnv)) return false;
break;
}
}
if (!JS_GET_FLAG('LEGACY')) {
while (sIndex < sNamedLength) {
if (_Utils.asBool(_Utils.arrayAt(sNamed, sIndex + 1))) return false;
sIndex += 3;
}
}
return true;
}
bool _isInterfaceSubtype(
Object? universe, Rti s, Object? sEnv, Rti t, Object? tEnv) {
String sName = Rti._getInterfaceName(s);
String tName = Rti._getInterfaceName(t);
while (sName != tName) {
// The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
// then we need to check if for some i, Ci <: [t]. However, this requires us
// to iterate over the superinterfaces. Instead, we can perform case
// analysis on [t]. By this point, [t] can only be Never, a type variable,
// or an interface type. (Bindings do not participate in subtype checks and
// all other cases have been eliminated.) If [t] is not an interface, then
// [s] </: [t]. Therefore, the only remaining case is that [t] is an
// interface, so rather than iterating over the Ci, we can instead look up
// [t] in our ruleset.
// TODO(fishythefish): Handle variance correctly.
var rule = _Universe._findRule(universe, sName);
if (rule == null) return false;
if (_Utils.isString(rule)) {
sName = _Utils.asString(rule);
continue;
}
var recipes = TypeRule.lookupSupertype(rule, tName);
if (recipes == null) return false;
int length = _Utils.arrayLength(recipes);
Object? supertypeArgs = _Utils.newArrayOrEmpty(length);
for (int i = 0; i < length; i++) {
String recipe = _Utils.asString(_Utils.arrayAt(recipes, i));
Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
_Utils.arraySetAt(supertypeArgs, i, supertypeArg);
}
var tArgs = Rti._getInterfaceTypeArguments(t);
return _areArgumentsSubtypes(
universe, supertypeArgs, null, sEnv, tArgs, tEnv);
}
// Interface Compositionality:
assert(sName == tName);
var sArgs = Rti._getInterfaceTypeArguments(s);
var tArgs = Rti._getInterfaceTypeArguments(t);
var sVariances;
if (JS_GET_FLAG("VARIANCE")) {
sVariances = _Universe.findTypeParameterVariances(universe, sName);
}
return _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv);
}
bool _areArgumentsSubtypes(Object? universe, Object? sArgs, Object? sVariances,
Object? sEnv, Object? tArgs, Object? tEnv) {
int length = _Utils.arrayLength(sArgs);
assert(length == _Utils.arrayLength(tArgs));
bool hasVariances = sVariances != null;
if (JS_GET_FLAG("VARIANCE")) {
assert(!hasVariances || length == _Utils.arrayLength(sVariances));
} else {
assert(!hasVariances);
}
for (int i = 0; i < length; i++) {
Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
if (JS_GET_FLAG("VARIANCE")) {
int sVariance = hasVariances
? _Utils.asInt(_Utils.arrayAt(sVariances, i))
: Variance.legacyCovariant;
switch (sVariance) {
case Variance.legacyCovariant:
case Variance.covariant:
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
return false;
}
break;
case Variance.contravariant:
if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
return false;
}
break;
case Variance.invariant:
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
return false;
}
break;
default:
throw StateError(
"Unknown variance given for subtype check: $sVariance");
}
} else {
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
return false;
}
}
}
return true;
}
bool isNullable(Rti t) {
int kind = Rti._getKind(t);
return isNullType(t) ||
isStrongTopType(t) ||
kind == Rti.kindQuestion ||
kind == Rti.kindStar && isNullable(Rti._getStarArgument(t)) ||
kind == Rti.kindFutureOr && isNullable(Rti._getFutureOrArgument(t));
}
@pragma('dart2js:parameter:trust')
bool isTopType(Rti t) =>
isStrongTopType(t) ||
isLegacyObjectType(t) ||
JS_GET_FLAG('LEGACY') && isObjectType(t);
bool isStrongTopType(Rti t) {
int kind = Rti._getKind(t);
return kind == Rti.kindDynamic ||
kind == Rti.kindVoid ||
kind == Rti.kindAny ||
kind == Rti.kindErased ||
isNullableObjectType(t);
}
bool isBottomType(Rti t) =>
Rti._getKind(t) == Rti.kindNever || JS_GET_FLAG('LEGACY') && isNullType(t);
bool isObjectType(Rti t) => _Utils.isIdentical(t, TYPE_REF<Object>());
bool isLegacyObjectType(Rti t) =>
_Utils.isIdentical(t, LEGACY_TYPE_REF<Object>());
bool isNullableObjectType(Rti t) => _Utils.isIdentical(t, TYPE_REF<Object?>());
bool isNullType(Rti t) =>
_Utils.isIdentical(t, TYPE_REF<Null>()) ||
_Utils.isIdentical(t, TYPE_REF<JSNull>());
bool isFunctionType(Rti t) => _Utils.isIdentical(t, TYPE_REF<Function>());
bool isJsFunctionType(Rti t) =>
_Utils.isIdentical(t, TYPE_REF<JavaScriptFunction>());
class _Utils {
static bool asBool(Object? o) => JS('bool', '#', o);
static double asDouble(Object? o) => JS('double', '#', o);
static int asInt(Object? o) => JS('int', '#', o);
static num asNum(Object? o) => JS('num', '#', o);
static String asString(Object? o) => JS('String', '#', o);
static Rti asRti(Object? s) => JS('Rti', '#', s);
static Rti? asRtiOrNull(Object? s) => JS('Rti|Null', '#', s);
static bool isString(Object? o) => JS('bool', 'typeof # == "string"', o);
static bool isNum(Object? o) => JS('bool', 'typeof # == "number"', o);
static bool instanceOf(Object? o, Object? constructor) =>
JS('bool', '# instanceof #', o, constructor);
static bool isIdentical(Object? s, Object? t) => JS('bool', '# === #', s, t);
static bool isNotIdentical(Object? s, Object? t) =>
JS('bool', '# !== #', s, t);
static bool isMultipleOf(int n, int d) => JS('bool', '# % # === 0', n, d);
static JSArray objectKeys(Object? o) =>
JS('returns:JSArray;new:true;', 'Object.keys(#)', o);
static void objectAssign(Object? o, Object? other) {
// TODO(fishythefish): Use `Object.assign()` when IE11 is deprecated.
var keys = objectKeys(other);
int length = arrayLength(keys);
for (int i = 0; i < length; i++) {
String key = asString(arrayAt(keys, i));
JS('', '#[#] = #[#]', o, key, other, key);
}
}
static Object? newArrayOrEmpty(int length) => length > 0
? JS('', 'new Array(#)', length)
: _Universe.sharedEmptyArray(_theUniverse());
static bool isArray(Object? o) => JS('bool', 'Array.isArray(#)', o);
static int arrayLength(Object? array) => JS('int', '#.length', array);
static Object? arrayAt(Object? array, int i) => JS('', '#[#]', array, i);
static void arraySetAt(Object? array, int i, Object? value) {
JS('', '#[#] = #', array, i, value);
}
static JSArray arrayShallowCopy(Object? array) =>
JS('JSArray', '#.slice()', array);
static JSArray arraySplice(Object? array, int position) =>
JS('JSArray', '#.splice(#)', array, position);
static JSArray arrayConcat(Object? a1, Object? a2) =>
JS('JSArray', '#.concat(#)', a1, a2);
static String substring(String s, int start, int end) =>
JS('String', '#.substring(#, #)', s, start, end);
static bool stringLessThan(String s1, String s2) =>
JS('bool', '# < #', s1, s2);
static Object? mapGet(Object? cache, Object? key) =>
JS('', '#.get(#)', cache, key);
static void mapSet(Object? cache, Object? key, Object? value) {
JS('', '#.set(#, #)', cache, key, value);
}
}
// -------- Entry points for testing -------------------------------------------
String testingCanonicalRecipe(Rti rti) {
return Rti._getCanonicalRecipe(rti);
}
String testingRtiToString(Rti rti) {
return _rtiToString(rti, null);
}
String testingRtiToDebugString(Rti rti) {
return _rtiToDebugString(rti);
}
Object testingCreateUniverse() {
return _Universe.create();
}
void testingAddRules(Object? universe, Object? rules) {
_Universe.addRules(universe, rules);
}
void testingAddTypeParameterVariances(Object? universe, Object? variances) {
_Universe.addTypeParameterVariances(universe, variances);
}
bool testingIsSubtype(Object? universe, Rti rti1, Rti rti2) {
return isSubtype(universe, rti1, rti2);
}
Rti testingUniverseEval(Object? universe, String recipe) {
return _Universe.eval(universe, recipe, true);
}
void testingUniverseEvalOverride(Object? universe, String recipe, Rti rti) {
var cache = _Universe.evalCache(universe);
_Utils.mapSet(cache, recipe, rti);
}
Rti testingEnvironmentEval(Object? universe, Rti environment, String recipe) {
return _Universe.evalInEnvironment(universe, environment, recipe);
}
Rti testingEnvironmentBind(Object? universe, Rti environment, Rti arguments) {
return _Universe.bind(universe, environment, arguments);
}