| // 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,
|