| // Copyright (c) 2022, 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. |
| |
| import '../flow_analysis/flow_analysis_operations.dart'; |
| import '../types/shared_type.dart'; |
| import 'nullability_suffix.dart'; |
| |
| /// Callback API used by the shared type analyzer to query and manipulate the |
| /// client's representation of variables and types. |
| /// |
| /// Concrete classes that implement this class should also mix in |
| /// [TypeAnalyzerOperationsMixin], which provides the implementations of the |
| /// operations for types and type schemas using the related operations on type |
| /// structures, implemented by the concrete class itself. For example, |
| /// [TypeAnalyzerOperationsMixin] adds [TypeAnalyzerOperationsMixin.futureType] |
| /// and [TypeAnalyzerOperationsMixin.futureTypeSchema] that are defined in terms |
| /// of [TypeAnalyzerOperations.futureTypeInternal], so a concrete class |
| /// implementing [TypeAnalyzerOperations] needs to implement only |
| /// `futureTypeInternal` to receive the implementations of both `futureType` and |
| /// `futureTypeSchema` by mixing in [TypeAnalyzerOperationsMixin]. |
| abstract interface class TypeAnalyzerOperations< |
| TypeStructure extends SharedTypeStructure<TypeStructure>, |
| Variable extends Object, |
| InferableParameter extends Object, |
| TypeDeclarationType extends Object, |
| TypeDeclaration extends Object> |
| implements FlowAnalysisOperations<Variable, SharedTypeView<TypeStructure>> { |
| /// Returns the type `double`. |
| SharedTypeView<TypeStructure> get doubleType; |
| |
| /// Returns the type `dynamic`. |
| SharedTypeView<TypeStructure> get dynamicType; |
| |
| /// Returns the type used by the client in the case of errors. |
| SharedTypeView<TypeStructure> get errorType; |
| |
| /// Returns the type `int`. |
| SharedTypeView<TypeStructure> get intType; |
| |
| /// Returns the type `Never`. |
| SharedTypeView<TypeStructure> get neverType; |
| |
| /// Returns the type `Null`. |
| SharedTypeView<TypeStructure> get nullType; |
| |
| /// Returns the type `Object?`. |
| SharedTypeView<TypeStructure> get objectQuestionType; |
| |
| /// Returns the type `Object`. |
| SharedTypeView<TypeStructure> get objectType; |
| |
| /// Returns the unknown type schema (`_`) used in type inference. |
| SharedTypeSchemaView<TypeStructure> get unknownType; |
| |
| /// Returns the type `Future` with omitted nullability and type argument |
| /// [argumentType]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [futureTypeInternal] to |
| /// receive a concrete implementation of [futureType] instead of implementing |
| /// [futureType] directly. |
| SharedTypeView<TypeStructure> futureType( |
| SharedTypeView<TypeStructure> argumentType); |
| |
| /// Returns the type schema `Future` with omitted nullability and type |
| /// argument [argumentTypeSchema]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [futureTypeInternal] to |
| /// receive a concrete implementation of [futureTypeSchema] instead of |
| /// implementing [futureTypeSchema] directly. |
| SharedTypeSchemaView<TypeStructure> futureTypeSchema( |
| SharedTypeSchemaView<TypeStructure> argumentTypeSchema); |
| |
| /// [futureTypeInternal] should be implemented by concrete classes |
| /// implementing [TypeAnalyzerOperations]. The implementations of [futureType] |
| /// and [futureTypeSchema] are provided by mixing in |
| /// [TypeAnalyzerOperationsMixin], which defines [futureType] and |
| /// [futureTypeSchema] in terms of [futureTypeInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [futureTypeInternal], and receive |
| /// the implementation of both [futureType] and [futureTypeSchema] from the |
| /// mixin. |
| /// |
| /// The auxiliary purpose of [futureTypeInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [futureTypeInternal] instead of the tool-specific ways of constructing a |
| /// future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure futureTypeInternal(TypeStructure typeStructure); |
| |
| /// If [type] was introduced by a class, mixin, enum, or extension type, |
| /// returns a [TypeDeclarationKind] indicating what kind of thing it was |
| /// introduced by. Otherwise, returns `null`. |
| /// |
| /// Examples of types derived from a class declarations are `A`, `A?`, `A*`, |
| /// `B<T, S>`, where `A` and `B` are the names of class declarations or |
| /// extension type declarations, `T` and `S` are types. |
| TypeDeclarationKind? getTypeDeclarationKind( |
| SharedTypeView<TypeStructure> type); |
| |
| /// Returns variance for of the type parameter at index [parameterIndex] in |
| /// [typeDeclaration]. |
| Variance getTypeParameterVariance( |
| TypeDeclaration typeDeclaration, int parameterIndex); |
| |
| /// If at top level [typeSchema] describes a type that was introduced by a |
| /// class, mixin, enum, or extension type, returns a [TypeDeclarationKind] |
| /// indicating what kind of thing it was introduced by. Otherwise, returns |
| /// `null`. |
| /// |
| /// Examples of type schemas at top level describing types derived from a |
| /// declaration are `A`, `A?`, `A*`, `B<T, S>`, `B<_, B<_, _>>?`, where `A` |
| /// and `B` are class declarations or extension type declarations, `T` and |
| /// `S` are type schemas. |
| TypeDeclarationKind? getTypeSchemaDeclarationKind( |
| SharedTypeSchemaView<TypeStructure> typeSchema); |
| |
| TypeDeclarationKind? getTypeDeclarationKindInternal(TypeStructure type); |
| |
| /// Computes the greatest lower bound of [type1] and [type2]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [glbInternal] to receive a |
| /// concrete implementation of [glb] instead of implementing [glb] directly. |
| SharedTypeView<TypeStructure> glb( |
| SharedTypeView<TypeStructure> type1, SharedTypeView<TypeStructure> type2); |
| |
| /// Computes the greatest lower bound of [typeSchema1] and [typeSchema2]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [glbInternal] to receive a |
| /// concrete implementation of [typeSchemaGlb] instead of implementing |
| /// [typeSchemaGlb] directly. |
| SharedTypeSchemaView<TypeStructure> typeSchemaGlb( |
| SharedTypeSchemaView<TypeStructure> typeSchema1, |
| SharedTypeSchemaView<TypeStructure> typeSchema2); |
| |
| /// [glbInternal] should be implemented by concrete classes implementing |
| /// [TypeAnalyzerOperations]. The implementations of [glb] and [typeSchemaGlb] |
| /// are provided by mixing in [TypeAnalyzerOperationsMixin], which defines |
| /// [glb] and [typeSchemaGlb] in terms of [glbInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [glbInternal], and receive the |
| /// implementation of both [glb] and [typeSchemaGlb] from the mixin. |
| /// |
| /// The auxiliary purpose of [glbInternal] is to facilitate the development of |
| /// the shared code at early stages. Sometimes the sharing of the code starts |
| /// by unifying the implementations of some concrete members in the Analyzer |
| /// and the CFE by bringing them in a form that looks syntactically very |
| /// similar in both tools, and then continues by abstracting the two concrete |
| /// members and using the shared abstracted one instead of the two concrete |
| /// methods existing previously. During the early stages of unifying the two |
| /// concrete members it can be beneficial to use [glbInternal] instead of the |
| /// tool-specific ways of constructing a future type, for the sake of |
| /// uniformity, and to simplify the abstraction step too. |
| TypeStructure glbInternal(TypeStructure type1, TypeStructure type2); |
| |
| /// Returns the greatest closure of [schema] with respect to the unknown type |
| /// (`_`). |
| SharedTypeView<TypeStructure> greatestClosure( |
| SharedTypeSchemaView<TypeStructure> schema); |
| |
| /// Queries whether [type] is an "always-exhaustive" type (as defined in the |
| /// patterns spec). Exhaustive types are types for which the switch statement |
| /// is required to be exhaustive when patterns support is enabled. |
| bool isAlwaysExhaustiveType(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if [fromType] is assignable to [toType]. |
| bool isAssignableTo(SharedTypeView<TypeStructure> fromType, |
| SharedTypeView<TypeStructure> toType); |
| |
| /// Returns `true` if [type] is `Function` from `dart:core`. The method |
| /// returns `false` for `Object?` and `Object*`. |
| bool isDartCoreFunction(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if [type] is `E<T1, ..., Tn>`, `E<T1, ..., Tn>?`, or |
| /// `E<T1, ..., Tn>*` for some extension type declaration E, some |
| /// non-negative n, and some types T1, ..., Tn. |
| bool isExtensionType(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if [type] is `F`, `F?`, or `F*` for some function type `F`. |
| bool isFunctionType(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if [type] is `A<T1, ..., Tn>`, `A<T1, ..., Tn>?`, or |
| /// `A<T1, ..., Tn>*` for some class, mixin, or enum A, some non-negative n, |
| /// and some types T1, ..., Tn. The method returns `false` if [type] is an |
| /// extension type, a type alias, `Null`, `Never`, or `FutureOr<X>` for any |
| /// type `X`. |
| bool isInterfaceType(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if `Null` is not a subtype of all types matching |
| /// [typeSchema]. |
| /// |
| /// The predicate of [isNonNullable] could be computed directly with a subtype |
| /// query, but the implementations can do that more efficiently. |
| bool isNonNullable(SharedTypeSchemaView<TypeStructure> typeSchema); |
| |
| /// Returns `true` if [type] is `Null`. |
| bool isNull(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if [type] is `Object` from `dart:core`. The method returns |
| /// `false` for `Object?` and `Object*`. |
| bool isObject(SharedTypeView<TypeStructure> type); |
| |
| /// Returns `true` if the type [type] satisfies the type schema [typeSchema]. |
| bool isTypeSchemaSatisfied( |
| {required SharedTypeSchemaView<TypeStructure> typeSchema, |
| required SharedTypeView<TypeStructure> type}); |
| |
| /// Returns whether [node] is final. |
| bool isVariableFinal(Variable node); |
| |
| /// Returns the type schema `Iterable`, with type argument. |
| SharedTypeSchemaView<TypeStructure> iterableTypeSchema( |
| SharedTypeSchemaView<TypeStructure> elementTypeSchema); |
| |
| /// Returns the type `List`, with type argument [elementType]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [listTypeInternal] to receive |
| /// a concrete implementation of [listType] instead of implementing [listType] |
| /// directly. |
| SharedTypeView<TypeStructure> listType( |
| SharedTypeView<TypeStructure> elementType); |
| |
| /// Returns the type schema `List`, with type argument [elementTypeSchema]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [listTypeInternal] to receive |
| /// a concrete implementation of [listTypeSchema] instead of implementing |
| /// [listTypeSchema] directly. |
| SharedTypeSchemaView<TypeStructure> listTypeSchema( |
| SharedTypeSchemaView<TypeStructure> elementTypeSchema); |
| |
| /// [listTypeInternal] should be implemented by concrete classes implementing |
| /// [TypeAnalyzerOperations]. The implementations of [listType] and |
| /// [listTypeSchema] are provided by mixing in [TypeAnalyzerOperationsMixin], |
| /// which defines [listType] and [listTypeSchema] in terms of |
| /// [listTypeInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [listTypeInternal], and receive |
| /// the implementation of both [listType] and [listTypeSchema] from the mixin. |
| /// |
| /// The auxiliary purpose of [listTypeInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [listTypeInternal] instead of the tool-specific ways of constructing a |
| /// future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure listTypeInternal(TypeStructure elementType); |
| |
| /// Computes the least upper bound of [type1] and [type2]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [lubInternal] to receive a |
| /// concrete implementation of [lub] instead of implementing [lub] directly. |
| SharedTypeView<TypeStructure> lub( |
| SharedTypeView<TypeStructure> type1, SharedTypeView<TypeStructure> type2); |
| |
| /// Computes the least upper bound of [typeSchema1] and [typeSchema2]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [lubInternal] to receive a |
| /// concrete implementation of [typeSchemaLub] instead of implementing |
| /// [typeSchemaLub] directly. |
| SharedTypeSchemaView<TypeStructure> typeSchemaLub( |
| SharedTypeSchemaView<TypeStructure> typeSchema1, |
| SharedTypeSchemaView<TypeStructure> typeSchema2); |
| |
| /// [lubInternal] should be implemented by concrete classes implementing |
| /// [TypeAnalyzerOperations]. The implementations of [lub] and [typeSchemaLub] |
| /// are provided by mixing in [TypeAnalyzerOperationsMixin], which defines |
| /// [lub] and [typeSchemaLub] in terms of [lubInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [lubInternal], and receive the |
| /// implementation of both [lub] and [typeSchemaLub] from the mixin. |
| /// |
| /// The auxiliary purpose of [lubInternal] is to facilitate the development of |
| /// the shared code at early stages. Sometimes the sharing of the code starts |
| /// by unifying the implementations of some concrete members in the Analyzer |
| /// and the CFE by bringing them in a form that looks syntactically very |
| /// similar in both tools, and then continues by abstracting the two concrete |
| /// members and using the shared abstracted one instead of the two concrete |
| /// methods existing previously. During the early stages of unifying the two |
| /// concrete members it can be beneficial to use [lubInternal] instead of the |
| /// tool-specific ways of constructing a future type, for the sake of |
| /// uniformity, and to simplify the abstraction step too. |
| TypeStructure lubInternal(TypeStructure type1, TypeStructure type2); |
| |
| /// Computes the nullable form of [type], in other words the least upper bound |
| /// of [type] and `Null`. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [makeNullableInternal] to |
| /// receive a concrete implementation of [makeNullable] instead of |
| /// implementing [makeNullable] directly. |
| SharedTypeView<TypeStructure> makeNullable( |
| SharedTypeView<TypeStructure> type); |
| |
| /// Computes the nullable form of [typeSchema]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [makeNullableInternal] to |
| /// receive a concrete implementation of [makeTypeSchemaNullable] instead of |
| /// implementing [makeTypeSchemaNullable] directly. |
| SharedTypeSchemaView<TypeStructure> makeTypeSchemaNullable( |
| SharedTypeSchemaView<TypeStructure> typeSchema); |
| |
| /// [makeNullableInternal] should be implemented by concrete classes |
| /// implementing [TypeAnalyzerOperations]. The implementations of |
| /// [makeNullable] and [makeTypeSchemaNullable] are provided by mixing in |
| /// [TypeAnalyzerOperationsMixin], which defines [makeNullable] and |
| /// [makeTypeSchemaNullable] in terms of [makeNullableInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [makeNullableInternal], and |
| /// receive the implementation of both [makeNullable] and |
| /// [makeTypeSchemaNullable] from the mixin. |
| /// |
| /// The auxiliary purpose of [makeNullableInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [makeNullableInternal] instead of the tool-specific ways of constructing a |
| /// future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure makeNullableInternal(TypeStructure type); |
| |
| /// Returns the type `Map`, with type arguments. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [mapTypeInternal] to receive a |
| /// concrete implementation of [mapType] instead of implementing [mapType] |
| /// directly. |
| SharedTypeView<TypeStructure> mapType({ |
| required SharedTypeView<TypeStructure> keyType, |
| required SharedTypeView<TypeStructure> valueType, |
| }); |
| |
| /// Returns the type schema `Map`, with type arguments [keyTypeSchema] and |
| /// [valueTypeSchema]. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [mapTypeInternal] to receive a |
| /// concrete implementation of [makeTypeSchemaNullable] instead of |
| /// implementing [makeTypeSchemaNullable] directly. |
| SharedTypeSchemaView<TypeStructure> mapTypeSchema({ |
| required SharedTypeSchemaView<TypeStructure> keyTypeSchema, |
| required SharedTypeSchemaView<TypeStructure> valueTypeSchema, |
| }); |
| |
| /// [mapTypeInternal] should be implemented by concrete classes implementing |
| /// [TypeAnalyzerOperations]. The implementations of [mapType] and |
| /// [makeTypeSchemaNullable] are provided by mixing in |
| /// [TypeAnalyzerOperationsMixin], which defines [mapType] and |
| /// [makeTypeSchemaNullable] in terms of [mapTypeInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [mapTypeInternal], and receive the |
| /// implementation of both [mapType] and [makeTypeSchemaNullable] from the |
| /// mixin. |
| /// |
| /// The auxiliary purpose of [mapTypeInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [mapTypeInternal] instead of the tool-specific ways of constructing a |
| /// future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure mapTypeInternal({ |
| required TypeStructure keyType, |
| required TypeStructure valueType, |
| }); |
| |
| /// If [type] takes the form `FutureOr<T>`, `FutureOr<T>?`, or `FutureOr<T>*` |
| /// for some `T`, returns the type `T`. Otherwise returns `null`. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [matchFutureOrInternal] to |
| /// receive a concrete implementation of [matchFutureOr] instead of |
| /// implementing [matchFutureOr] directly. |
| SharedTypeView<TypeStructure>? matchFutureOr( |
| SharedTypeView<TypeStructure> type); |
| |
| /// If [typeSchema] takes the form `FutureOr<T>`, `FutureOr<T>?`, or |
| /// `FutureOr<T>*` for some `T`, returns the type schema `T`. Otherwise |
| /// returns `null`. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [matchFutureOrInternal] to |
| /// receive a concrete implementation of [matchTypeSchemaFutureOr] instead of |
| /// implementing [matchTypeSchemaFutureOr] directly. |
| SharedTypeSchemaView<TypeStructure>? matchTypeSchemaFutureOr( |
| SharedTypeSchemaView<TypeStructure> typeSchema); |
| |
| /// [matchFutureOrInternal] should be implemented by concrete classes |
| /// implementing [TypeAnalyzerOperations]. The implementations of |
| /// [matchFutureOr] and [matchTypeSchemaFutureOr] are provided by mixing in |
| /// [TypeAnalyzerOperationsMixin], which defines [matchFutureOr] and |
| /// [matchTypeSchemaFutureOr] in terms of [matchFutureOrInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [matchFutureOrInternal], and |
| /// receive the implementation of both [matchFutureOr] and |
| /// [matchTypeSchemaFutureOr] from the mixin. |
| /// |
| /// The auxiliary purpose of [matchFutureOrInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [matchFutureOrInternal] instead of the tool-specific ways of constructing |
| /// a future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure? matchFutureOrInternal(TypeStructure type); |
| |
| /// If [type] is a parameter type that is of a kind used in type inference, |
| /// returns the corresponding parameter. |
| /// |
| /// In the example below the appearance of `X` in the return type of `foo` is |
| /// a parameter type of a kind used in type inference. When passed into |
| /// [matchInferableParameter] it will yield the parameter `X` defined by |
| /// `foo`. |
| /// |
| /// X foo<X>(bool c, X x1, X x2) => c ? x1 : x2; |
| InferableParameter? matchInferableParameter( |
| SharedTypeView<TypeStructure> type); |
| |
| /// If [type] is a subtype of the type `Iterable<T>?` for some `T`, returns |
| /// the type `T`. Otherwise returns `null`. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [matchIterableTypeInternal] to |
| /// receive a concrete implementation of [matchIterableType] instead of |
| /// implementing [matchIterableType] directly. |
| SharedTypeView<TypeStructure>? matchIterableType( |
| SharedTypeView<TypeStructure> type); |
| |
| /// If [typeSchema] is the type schema `Iterable<T>?` (or a subtype thereof), |
| /// for some `T`, returns the type `T`. Otherwise returns `null`. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [matchIterableTypeInternal] to |
| /// receive a concrete implementation of [matchIterableTypeSchema] instead of |
| /// implementing [matchIterableTypeSchema] directly. |
| SharedTypeSchemaView<TypeStructure>? matchIterableTypeSchema( |
| SharedTypeSchemaView<TypeStructure> typeSchema); |
| |
| /// [matchIterableTypeInternal] should be implemented by concrete classes |
| /// implementing [TypeAnalyzerOperations]. The implementations of |
| /// [matchIterableType] and [matchIterableTypeSchema] are provided by mixing |
| /// in [TypeAnalyzerOperationsMixin], which defines [matchIterableType] and |
| /// [matchIterableTypeSchema] in terms of [matchIterableTypeInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [matchIterableTypeInternal], and |
| /// receive the implementation of both [matchIterableType] and |
| /// [matchIterableTypeSchema] from the mixin. |
| /// |
| /// The auxiliary purpose of [matchIterableTypeInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [matchIterableTypeInternal] instead of the tool-specific ways of |
| /// constructing a future type, for the sake of uniformity, and to simplify |
| /// the abstraction step too. |
| TypeStructure? matchIterableTypeInternal(TypeStructure type); |
| |
| /// If [type] is a subtype of the type `List<T>?` for some `T`, returns the |
| /// type `T`. Otherwise returns `null`. |
| SharedTypeView<TypeStructure>? matchListType( |
| SharedTypeView<TypeStructure> type); |
| |
| /// If [type] is a subtype of the type `Map<K, V>?` for some `K` and `V`, |
| /// returns these `K` and `V`. Otherwise returns `null`. |
| ({ |
| SharedTypeView<TypeStructure> keyType, |
| SharedTypeView<TypeStructure> valueType |
| })? matchMapType(SharedTypeView<TypeStructure> type); |
| |
| /// If [type] is a subtype of the type `Stream<T>?` for some `T`, returns |
| /// the type `T`. Otherwise returns `null`. |
| SharedTypeView<TypeStructure>? matchStreamType( |
| SharedTypeView<TypeStructure> type); |
| |
| /// If [type] was introduced by a class, mixin, enum, or extension type, |
| /// returns an object of [TypeDeclarationMatchResult] describing the |
| /// constituents of the matched type. |
| /// |
| /// If [type] isn't introduced by a class, mixin, enum, or extension type, |
| /// returns null. |
| TypeDeclarationMatchResult? matchTypeDeclarationType( |
| SharedTypeView<TypeStructure> type); |
| |
| /// Computes `NORM` of [type]. |
| /// https://github.com/dart-lang/language |
| /// See `resources/type-system/normalization.md` |
| SharedTypeView<TypeStructure> normalize(SharedTypeView<TypeStructure> type); |
| |
| /// Builds the client specific record type. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [recordTypeInternal] to |
| /// receive a concrete implementation of [recordType] instead of implementing |
| /// [recordType] directly. |
| SharedTypeView<TypeStructure> recordType( |
| {required List<SharedTypeView<TypeStructure>> positional, |
| required List<(String, SharedTypeView<TypeStructure>)> named}); |
| |
| /// Builds the client specific record type schema. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should mix in |
| /// [TypeAnalyzerOperationsMixin] and implement [recordTypeInternal] to |
| /// receive a concrete implementation of [recordTypeSchema] instead of |
| /// implementing [recordTypeSchema] directly. |
| SharedTypeSchemaView<TypeStructure> recordTypeSchema( |
| {required List<SharedTypeSchemaView<TypeStructure>> positional, |
| required List<(String, SharedTypeSchemaView<TypeStructure>)> named}); |
| |
| /// [recordTypeInternal] should be implemented by concrete classes |
| /// implementing [TypeAnalyzerOperations]. The implementations of [recordType] |
| /// and [recordTypeSchema] are provided by mixing in |
| /// [TypeAnalyzerOperationsMixin], which defines [recordType] and |
| /// [recordTypeSchema] in terms of [recordTypeInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case [recordTypeInternal], and receive |
| /// the implementation of both [recordType] and [recordTypeSchema] from the |
| /// mixin. |
| /// |
| /// The auxiliary purpose of [recordTypeInternal] is to facilitate the |
| /// development of the shared code at early stages. Sometimes the sharing of |
| /// the code starts by unifying the implementations of some concrete members |
| /// in the Analyzer and the CFE by bringing them in a form that looks |
| /// syntactically very similar in both tools, and then continues by |
| /// abstracting the two concrete members and using the shared abstracted one |
| /// instead of the two concrete methods existing previously. During the early |
| /// stages of unifying the two concrete members it can be beneficial to use |
| /// [recordTypeInternal] instead of the tool-specific ways of constructing a |
| /// future type, for the sake of uniformity, and to simplify the abstraction |
| /// step too. |
| TypeStructure recordTypeInternal( |
| {required List<TypeStructure> positional, |
| required List<(String, TypeStructure)> named}); |
| |
| /// Returns the type schema `Stream`, with type argument [elementTypeSchema]. |
| SharedTypeSchemaView<TypeStructure> streamTypeSchema( |
| SharedTypeSchemaView<TypeStructure> elementTypeSchema); |
| |
| /// Returns `true` if [leftType] is a subtype of the greatest closure of |
| /// [rightSchema]. |
| /// |
| /// This method can be implemented directly, by computing the greatest |
| /// closure of [rightSchema] and then comparing the resulting type and |
| /// [leftType] via [isSubtypeOf]. However, that would mean at least two |
| /// recursive descends over types. This method is supposed to have optimized |
| /// implementations that only use one recursive descend. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should |
| /// implement [isSubtypeOfInternal] and mix in [TypeAnalyzerOperationsMixin] |
| /// to receive an implementation of [typeIsSubtypeOfTypeSchema] instead of |
| /// implementing it directly. |
| bool typeIsSubtypeOfTypeSchema(SharedTypeView<TypeStructure> leftType, |
| SharedTypeSchemaView<TypeStructure> rightSchema); |
| |
| /// Returns `true` if the least closure of [leftSchema] is a subtype of |
| /// [rightType]. |
| /// |
| /// This method can be implemented directly, by computing the least closure |
| /// of [leftSchema] and then comparing the resulting type and [rightType] via |
| /// [isSubtypeOf]. However, that would mean at least two recursive descends |
| /// over types. This method is supposed to have optimized implementations |
| /// that only use one recursive descend. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should |
| /// implement [isSubtypeOfInternal] and mix in [TypeAnalyzerOperationsMixin] |
| /// to receive an implementation of [typeSchemaIsSubtypeOfType] instead of |
| /// implementing it directly. |
| bool typeSchemaIsSubtypeOfType(SharedTypeSchemaView<TypeStructure> leftSchema, |
| SharedTypeView<TypeStructure> rightType); |
| |
| /// Returns `true` if least closure of [leftSchema] is a subtype of |
| /// the greatest closure of [rightSchema]. |
| /// |
| /// This method can be implemented directly, by computing the least closure of |
| /// [leftSchema], the greatest closure of [rightSchema], and then comparing |
| /// the resulting types via [isSubtypeOf]. However, that would mean at least |
| /// three recursive descends over types. This method is supposed to have |
| /// optimized implementations that only use one recursive descend. |
| /// |
| /// The concrete classes implementing [TypeAnalyzerOperations] should |
| /// implement [isSubtypeOfInternal] and mix in [TypeAnalyzerOperationsMixin] |
| /// to receive an implementation of [typeSchemaIsSubtypeOfTypeSchema] instead |
| /// of implementing it directly. |
| bool typeSchemaIsSubtypeOfTypeSchema( |
| SharedTypeSchemaView<TypeStructure> leftSchema, |
| SharedTypeSchemaView<TypeStructure> rightSchema); |
| |
| /// The concrete classes implementing [TypeAnalyzerOperations] should |
| /// implement [isSubtypeOfInternal] in order to receive the implementations of |
| /// [typeIsSubtypeOfTypeSchema], [typeSchemaIsSubtypeOfType], and |
| /// [typeSchemaIsSubtypeOfTypeSchema] by mixing in |
| /// [TypeAnalyzerOperationsMixin]. |
| bool isSubtypeOfInternal(TypeStructure left, TypeStructure right); |
| |
| /// Converts a type into a corresponding type schema. |
| SharedTypeSchemaView<TypeStructure> typeToSchema( |
| SharedTypeView<TypeStructure> type); |
| |
| /// Returns [type] suffixed with the [suffix]. |
| SharedTypeView<TypeStructure> withNullabilitySuffix( |
| SharedTypeView<TypeStructure> type, NullabilitySuffix suffix); |
| |
| @override |
| bool isNever(SharedTypeView<TypeStructure> type); |
| |
| @override |
| bool isTypeParameterType(SharedTypeView<TypeStructure> type); |
| |
| @override |
| SharedTypeView<TypeStructure> promoteToNonNull( |
| SharedTypeView<TypeStructure> type); |
| |
| @override |
| SharedTypeView<TypeStructure>? tryPromoteToType( |
| SharedTypeView<TypeStructure> to, SharedTypeView<TypeStructure> from); |
| } |
| |
| mixin TypeAnalyzerOperationsMixin< |
| TypeStructure extends SharedTypeStructure<TypeStructure>, |
| Variable extends Object, |
| InferableParameter extends Object, |
| TypeDeclarationType extends Object, |
| TypeDeclaration extends Object> |
| implements |
| TypeAnalyzerOperations<TypeStructure, Variable, InferableParameter, |
| TypeDeclarationType, TypeDeclaration> { |
| @override |
| SharedTypeView<TypeStructure> futureType( |
| SharedTypeView<TypeStructure> argumentType) { |
| return new SharedTypeView( |
| futureTypeInternal(argumentType.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> futureTypeSchema( |
| SharedTypeSchemaView<TypeStructure> argumentTypeSchema) { |
| return new SharedTypeSchemaView( |
| futureTypeInternal(argumentTypeSchema.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| TypeDeclarationKind? getTypeDeclarationKind( |
| SharedTypeView<TypeStructure> type) { |
| return getTypeDeclarationKindInternal(type.unwrapTypeView()); |
| } |
| |
| @override |
| TypeDeclarationKind? getTypeSchemaDeclarationKind( |
| SharedTypeSchemaView<TypeStructure> typeSchema) { |
| return getTypeDeclarationKindInternal(typeSchema.unwrapTypeSchemaView()); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> glb(SharedTypeView<TypeStructure> type1, |
| SharedTypeView<TypeStructure> type2) { |
| return new SharedTypeView( |
| glbInternal(type1.unwrapTypeView(), type2.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> typeSchemaGlb( |
| SharedTypeSchemaView<TypeStructure> typeSchema1, |
| SharedTypeSchemaView<TypeStructure> typeSchema2) { |
| return new SharedTypeSchemaView(glbInternal( |
| typeSchema1.unwrapTypeSchemaView(), |
| typeSchema2.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> listType( |
| SharedTypeView<TypeStructure> elementType) { |
| return new SharedTypeView(listTypeInternal(elementType.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> listTypeSchema( |
| SharedTypeSchemaView<TypeStructure> elementTypeSchema) { |
| return new SharedTypeSchemaView( |
| listTypeInternal(elementTypeSchema.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> lub(SharedTypeView<TypeStructure> type1, |
| SharedTypeView<TypeStructure> type2) { |
| return new SharedTypeView( |
| lubInternal(type1.unwrapTypeView(), type2.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> typeSchemaLub( |
| SharedTypeSchemaView<TypeStructure> typeSchema1, |
| SharedTypeSchemaView<TypeStructure> typeSchema2) { |
| return new SharedTypeSchemaView(lubInternal( |
| typeSchema1.unwrapTypeSchemaView(), |
| typeSchema2.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> makeNullable( |
| SharedTypeView<TypeStructure> type) { |
| return new SharedTypeView(makeNullableInternal(type.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> makeTypeSchemaNullable( |
| SharedTypeSchemaView<TypeStructure> typeSchema) { |
| return new SharedTypeSchemaView( |
| makeNullableInternal(typeSchema.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> mapType({ |
| required SharedTypeView<TypeStructure> keyType, |
| required SharedTypeView<TypeStructure> valueType, |
| }) { |
| return new SharedTypeView(mapTypeInternal( |
| keyType: keyType.unwrapTypeView(), |
| valueType: valueType.unwrapTypeView())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> mapTypeSchema( |
| {required SharedTypeSchemaView<TypeStructure> keyTypeSchema, |
| required SharedTypeSchemaView<TypeStructure> valueTypeSchema}) { |
| return new SharedTypeSchemaView(mapTypeInternal( |
| keyType: keyTypeSchema.unwrapTypeSchemaView(), |
| valueType: valueTypeSchema.unwrapTypeSchemaView())); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure>? matchFutureOr( |
| SharedTypeView<TypeStructure> type) { |
| return matchFutureOrInternal(type.unwrapTypeView())?.wrapSharedTypeView(); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure>? matchTypeSchemaFutureOr( |
| SharedTypeSchemaView<TypeStructure> typeSchema) { |
| return matchFutureOrInternal(typeSchema.unwrapTypeSchemaView()) |
| ?.wrapSharedTypeSchemaView(); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure>? matchIterableType( |
| SharedTypeView<TypeStructure> type) { |
| return matchIterableTypeInternal(type.unwrapTypeView()) |
| ?.wrapSharedTypeView(); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure>? matchIterableTypeSchema( |
| SharedTypeSchemaView<TypeStructure> typeSchema) { |
| return matchIterableTypeInternal(typeSchema.unwrapTypeSchemaView()) |
| ?.wrapSharedTypeSchemaView(); |
| } |
| |
| @override |
| SharedTypeView<TypeStructure> recordType( |
| {required List<SharedTypeView<TypeStructure>> positional, |
| required List<(String, SharedTypeView<TypeStructure>)> named}) { |
| return new SharedTypeView(recordTypeInternal( |
| positional: positional.cast<TypeStructure>(), |
| named: named.cast<(String, TypeStructure)>())); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> recordTypeSchema( |
| {required List<SharedTypeSchemaView<TypeStructure>> positional, |
| required List<(String, SharedTypeSchemaView<TypeStructure>)> named}) { |
| return new SharedTypeSchemaView(recordTypeInternal( |
| positional: positional.cast<TypeStructure>(), |
| named: named.cast<(String, TypeStructure)>())); |
| } |
| |
| @override |
| bool isSubtypeOf(SharedTypeView<TypeStructure> leftType, |
| SharedTypeView<TypeStructure> rightType) { |
| return isSubtypeOfInternal( |
| leftType.unwrapTypeView(), rightType.unwrapTypeView()); |
| } |
| |
| @override |
| bool typeIsSubtypeOfTypeSchema(SharedTypeView<TypeStructure> leftType, |
| SharedTypeSchemaView<TypeStructure> rightSchema) { |
| return isSubtypeOfInternal( |
| leftType.unwrapTypeView(), rightSchema.unwrapTypeSchemaView()); |
| } |
| |
| @override |
| bool typeSchemaIsSubtypeOfType(SharedTypeSchemaView<TypeStructure> leftSchema, |
| SharedTypeView<TypeStructure> rightType) { |
| return isSubtypeOfInternal( |
| leftSchema.unwrapTypeSchemaView(), rightType.unwrapTypeView()); |
| } |
| |
| @override |
| bool typeSchemaIsSubtypeOfTypeSchema( |
| SharedTypeSchemaView<TypeStructure> leftSchema, |
| SharedTypeSchemaView<TypeStructure> rightSchema) { |
| return isSubtypeOfInternal( |
| leftSchema.unwrapTypeSchemaView(), rightSchema.unwrapTypeSchemaView()); |
| } |
| |
| @override |
| SharedTypeSchemaView<TypeStructure> typeToSchema( |
| SharedTypeView<TypeStructure> type) { |
| return new SharedTypeSchemaView(type.unwrapTypeView()); |
| } |
| } |
| |
| /// Describes all possibility for a type to be derived from a declaration. |
| /// |
| /// This enum is intended to exhaustively handle all possibilities for a type to |
| /// be derived from a type declaration. Currently, there are two such kinds of |
| /// declarations: declarations inducing interfaces for dynamic dispatch (such as |
| /// classes, mixins, and enums), and extension types. |
| enum TypeDeclarationKind { |
| /// Indication that the type is derived from a declaration inducing interface. |
| /// |
| /// An example of such declaration can be a class declaration, a mixin |
| /// declaration, or an enum declaration. |
| interfaceDeclaration, |
| |
| /// Indication that the type is derived from an extension type declaration. |
| extensionTypeDeclaration, |
| } |
| |
| /// Describes constituents of a type derived from a declaration. |
| /// |
| /// If a type is derived from a declaration, as described in the documentation |
| /// for [TypeDeclarationKind], objects of [TypeDeclarationMatchResult] describe |
| /// its components that can be used for the further analysis of the type in the |
| /// algorithms related to type inference. |
| class TypeDeclarationMatchResult<TypeDeclarationType extends Object, |
| TypeDeclaration extends Object, Type extends Object> { |
| /// The kind of type declaration the matched type is of. |
| final TypeDeclarationKind typeDeclarationKind; |
| |
| /// A more specific subtype of [Type] describing the matched type. |
| /// |
| /// This is client-specific is needed to avoid unnecessary downcasts. |
| final TypeDeclarationType typeDeclarationType; |
| |
| /// The type declaration that the matched type is derived from. |
| /// |
| /// The type declaration is defined in the documentation for |
| /// [TypeDeclarationKind] and is a client-specific object representing a |
| /// class, an enum, a mixin, or an extension type. |
| final TypeDeclaration typeDeclaration; |
| |
| /// Type arguments instantiating [typeDeclaration] to the matched type. |
| /// |
| /// If [typeDeclaration] is not generic, [typeArguments] is an empty list. |
| final List<Type> typeArguments; |
| |
| TypeDeclarationMatchResult( |
| {required this.typeDeclarationKind, |
| required this.typeDeclarationType, |
| required this.typeDeclaration, |
| required this.typeArguments}); |
| } |
| |
| /// The variance of a type parameter `X` in a type `T`. |
| enum Variance { |
| /// Used when `X` does not occur free in `T`. |
| unrelated(keyword: ''), |
| |
| /// Used when `X` occurs free in `T`, and `U <: V` implies `[U/X]T <: [V/X]T`. |
| covariant(keyword: 'out'), |
| |
| /// Used when `X` occurs free in `T`, and `U <: V` implies `[V/X]T <: [U/X]T`. |
| contravariant(keyword: 'in'), |
| |
| /// Used when there exists a pair `U` and `V` such that `U <: V`, but |
| /// `[U/X]T` and `[V/X]T` are incomparable. |
| invariant(keyword: 'inout'); |
| |
| final String keyword; |
| |
| const Variance({required this.keyword}); |
| |
| /// Return the variance with the given [encoding]. |
| factory Variance.fromEncoding(int encoding) => values[encoding]; |
| |
| /// Return the variance associated with the string representation of variance. |
| factory Variance.fromKeywordString(String keywordString) { |
| Variance? result; |
| if (keywordString == "in") { |
| result = contravariant; |
| } else if (keywordString == "inout") { |
| result = invariant; |
| } else if (keywordString == "out") { |
| result = covariant; |
| } else if (keywordString == "unrelated") { |
| result = unrelated; |
| } |
| if (result != null) { |
| assert(result.keyword == keywordString); |
| return result; |
| } else { |
| throw new ArgumentError( |
| 'Invalid keyword string for variance: $keywordString'); |
| } |
| } |
| |
| /// Return `true` if this represents the case when `X` occurs free in `T`, and |
| /// `U <: V` implies `[V/X]T <: [U/X]T`. |
| bool get isContravariant => this == contravariant; |
| |
| /// Return `true` if this represents the case when `X` occurs free in `T`, and |
| /// `U <: V` implies `[U/X]T <: [V/X]T`. |
| bool get isCovariant => this == covariant; |
| |
| /// Return `true` if this represents the case when there exists a pair `U` and |
| /// `V` such that `U <: V`, but `[U/X]T` and `[V/X]T` are incomparable. |
| bool get isInvariant => this == invariant; |
| |
| /// Return `true` if this represents the case when `X` does not occur free in |
| /// `T`. |
| bool get isUnrelated => this == unrelated; |
| |
| /// Combines variances of `X` in `T` and `Y` in `S` into variance of `X` in |
| /// `[Y/T]S`. |
| /// |
| /// Consider the following examples: |
| /// |
| /// * variance of `X` in `Function(X)` is contravariant, variance of `Y` |
| /// in `List<Y>` is covariant, so variance of `X` in `List<Function(X)>` is |
| /// contravariant; |
| /// |
| /// * variance of `X` in `List<X>` is covariant, variance of `Y` in |
| /// `Function(Y)` is contravariant, so variance of `X` in |
| /// `Function(List<X>)` is contravariant; |
| /// |
| /// * variance of `X` in `Function(X)` is contravariant, variance of `Y` in |
| /// `Function(Y)` is contravariant, so variance of `X` in |
| /// `Function(Function(X))` is covariant; |
| /// |
| /// * let the following be declared: |
| /// |
| /// typedef F<Z> = Function(); |
| /// |
| /// then variance of `X` in `F<X>` is unrelated, variance of `Y` in |
| /// `List<Y>` is covariant, so variance of `X` in `List<F<X>>` is |
| /// unrelated; |
| /// |
| /// * let the following be declared: |
| /// |
| /// typedef G<Z> = Z Function(Z); |
| /// |
| /// then variance of `X` in `List<X>` is covariant, variance of `Y` in |
| /// `G<Y>` is invariant, so variance of `X` in `G<List<X>>` is invariant. |
| Variance combine(Variance other) { |
| if (isUnrelated || other.isUnrelated) return unrelated; |
| if (isInvariant || other.isInvariant) return invariant; |
| return this == other ? covariant : contravariant; |
| } |
| |
| /// Returns true if this variance is greater than (above) or equal to the |
| /// [other] variance in the partial order induced by the variance lattice. |
| /// |
| /// unrelated |
| /// covariant contravariant |
| /// invariant |
| bool greaterThanOrEqual(Variance other) { |
| if (isUnrelated) { |
| return true; |
| } else if (isCovariant) { |
| return other.isCovariant || other.isInvariant; |
| } else if (isContravariant) { |
| return other.isContravariant || other.isInvariant; |
| } else { |
| assert(isInvariant); |
| return other.isInvariant; |
| } |
| } |
| |
| /// Variance values form a lattice where unrelated is the top, invariant is |
| /// the bottom, and covariant and contravariant are incomparable. [meet] |
| /// calculates the meet of two elements of such lattice. It can be used, for |
| /// example, to calculate the variance of a typedef type parameter if it's |
| /// encountered on the RHS of the typedef multiple times. |
| /// |
| /// unrelated |
| /// covariant contravariant |
| /// invariant |
| Variance meet(Variance other) { |
| return new Variance.fromEncoding(index | other.index); |
| } |
| } |
| |
| /// Abstract interface of a type constraint generator. |
| abstract class TypeConstraintGenerator< |
| TypeStructure extends SharedTypeStructure<TypeStructure>, |
| Variable extends Object, |
| InferableParameter extends Object, |
| TypeDeclarationType extends Object, |
| TypeDeclaration extends Object, |
| AstNode extends Object> { |
| /// The current sate of the constraint generator. |
| /// |
| /// The states of the generator obtained via [currentState] can be treated as |
| /// checkpoints in the constraint generation process, and the generator can |
| /// be rolled back to a state via [restoreState]. |
| TypeConstraintGeneratorState get currentState; |
| |
| /// Restores the constraint generator to [state]. |
| /// |
| /// The [state] to restore the constraint generator to can be obtained via |
| /// [currentState]. |
| void restoreState(TypeConstraintGeneratorState state); |
| |
| /// Abstract type operations to be used in the matching methods. |
| TypeAnalyzerOperations<TypeStructure, Variable, InferableParameter, |
| TypeDeclarationType, TypeDeclaration> get typeAnalyzerOperations; |
| |
| /// True if FutureOr types are required to have the empty [NullabilitySuffix] |
| /// when they are matched. |
| /// |
| /// For more information about the discrepancy between the Analyzer and the |
| /// CFE in treatment of FutureOr types, see |
| /// https://github.com/dart-lang/sdk/issues/55344 and |
| /// https://github.com/dart-lang/sdk/issues/51156#issuecomment-2158825417. |
| bool get enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr; |
| |
| /// Matches type [p] against type schema [q] as a subtype against supertype, |
| /// assuming [p] contains the type parameters to constrain, and [q] is the |
| /// constraining type schema, and returns true if [p] is a subtype of [q] |
| /// under some constraints, and false otherwise. |
| /// |
| /// As the generator computes the constraints making the relation possible, |
| /// it changes its internal state. The current state of the generator can be |
| /// obtained by [currentState], and the generator can be restored to a state |
| /// via [restoreState]. All of the shared constraint generation methods are |
| /// supposed to restore the generator to the prior state in case of a |
| /// mismatch, taking that responsibility away from the caller. |
| /// |
| /// The algorithm for subtype constraint generation is described in |
| /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#subtype-constraint-generation |
| bool performSubtypeConstraintGenerationRightSchema( |
| SharedTypeView<TypeStructure> p, SharedTypeSchemaView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}); |
| |
| /// Matches type schema [p] against type [q] as a subtype against supertype, |
| /// assuming [p] is the constraining type schema, and [q] contains the type |
| /// parameters to constrain, and returns true if [p] is a subtype of [q] |
| /// under some constraints, and false otherwise. |
| /// |
| /// As the generator computes the constraints making the relation possible, |
| /// it changes its internal state. The current state of the generator can be |
| /// obtained by [currentState], and the generator can be restored to a state |
| /// via [restoreState]. All of the shared constraint generation methods are |
| /// supposed to restore the generator to the prior state in case of a |
| /// mismatch, taking that responsibility away from the caller. |
| /// |
| /// The algorithm for subtype constraint generation is described in |
| /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#subtype-constraint-generation |
| bool performSubtypeConstraintGenerationLeftSchema( |
| SharedTypeSchemaView<TypeStructure> p, SharedTypeView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}); |
| |
| /// [performSubtypeConstraintGenerationInternal] should be implemented by |
| /// concrete classes implementing [TypeConstraintGenerator]. The |
| /// implementations of [performSubtypeConstraintGenerationLeftSchema] and |
| /// [performSubtypeConstraintGenerationRightSchema] are provided by mixing in |
| /// [TypeConstraintGeneratorMixin], which defines |
| /// [performSubtypeConstraintGenerationLeftSchema] and |
| /// [performSubtypeConstraintGenerationRightSchema] in terms of |
| /// [performSubtypeConstraintGenerationInternal]. |
| /// |
| /// The main purpose of this method is to avoid code duplication in the |
| /// concrete classes implementing [TypeAnalyzerOperations], so they can |
| /// implement only one member, in this case |
| /// [performSubtypeConstraintGenerationInternal], and receive the |
| /// implementation of both [performSubtypeConstraintGenerationLeftSchema] and |
| /// [performSubtypeConstraintGenerationRightSchema] from the mixin. |
| bool performSubtypeConstraintGenerationInternal( |
| TypeStructure p, TypeStructure q, |
| {required bool leftSchema, required AstNode? astNodeForTesting}); |
| |
| /// Matches type [p] against type schema [q] as a subtype against supertype |
| /// and returns true if [p] and [q] are both FutureOr, with or without |
| /// nullability suffixes as defined by |
| /// [enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr], and [p] is |
| /// a subtype of [q] under some constraints imposed on type parameters |
| /// occurring in [p], and false otherwise. |
| /// |
| /// As the generator computes the constraints making the relation possible, |
| /// it changes its internal state. The current state of the generator can be |
| /// obtained by [currentState], and the generator can be restored to a state |
| /// via [restoreState]. All of the shared constraint generation methods are |
| /// supposed to restore the generator to the prior state in case of a |
| /// mismatch, taking that responsibility away from the caller. |
| bool performSubtypeConstraintGenerationForFutureOrRightSchema( |
| SharedTypeView<TypeStructure> p, SharedTypeSchemaView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}) { |
| return _performSubtypeConstraintGenerationForFutureOrInternal( |
| p.unwrapTypeView(), q.unwrapTypeSchemaView(), |
| leftSchema: false, astNodeForTesting: astNodeForTesting); |
| } |
| |
| /// Matches type schema [p] against type [q] as a subtype against supertype |
| /// and returns true if [p] and [q] are both FutureOr, with or without |
| /// nullability suffixes as defined by |
| /// [enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr], and [p] is |
| /// a subtype of [q] under some constraints imposed on type parameters |
| /// occurring in [q], and false otherwise. |
| /// |
| /// As the generator computes the constraints making the relation possible, |
| /// it changes its internal state. The current state of the generator can be |
| /// obtained by [currentState], and the generator can be restored to a state |
| /// via [restoreState]. All of the shared constraint generation methods are |
| /// supposed to restore the generator to the prior state in case of a |
| /// mismatch, taking that responsibility away from the caller. |
| bool performSubtypeConstraintGenerationForFutureOrLeftSchema( |
| SharedTypeSchemaView<TypeStructure> p, SharedTypeView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}) { |
| return _performSubtypeConstraintGenerationForFutureOrInternal( |
| p.unwrapTypeSchemaView(), q.unwrapTypeView(), |
| leftSchema: true, astNodeForTesting: astNodeForTesting); |
| } |
| |
| bool _performSubtypeConstraintGenerationForFutureOrInternal( |
| TypeStructure p, TypeStructure q, |
| {required bool leftSchema, required AstNode? astNodeForTesting}) { |
| // If `Q` is `FutureOr<Q0>` the match holds under constraint set `C`: |
| if (typeAnalyzerOperations.matchFutureOrInternal(q) case TypeStructure q0? |
| when enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr || |
| q.nullabilitySuffix == NullabilitySuffix.none) { |
| final TypeConstraintGeneratorState state = currentState; |
| |
| // If `P` is `FutureOr<P0>` and `P0` is a subtype match for `Q0` under |
| // constraint set `C`. |
| if (typeAnalyzerOperations.matchFutureOrInternal(p) case TypeStructure p0? |
| when enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr || |
| p.nullabilitySuffix == NullabilitySuffix.none) { |
| if (performSubtypeConstraintGenerationInternal(p0, q0, |
| leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) { |
| return true; |
| } |
| restoreState(state); |
| } |
| |
| // Or if `P` is a subtype match for `Future<Q0>` under non-empty |
| // constraint set `C`. |
| bool isMatchWithFuture = performSubtypeConstraintGenerationInternal( |
| p, typeAnalyzerOperations.futureTypeInternal(q0), |
| leftSchema: leftSchema, astNodeForTesting: astNodeForTesting); |
| bool matchWithFutureAddsConstraints = currentState != state; |
| if (isMatchWithFuture && matchWithFutureAddsConstraints) { |
| return true; |
| } |
| restoreState(state); |
| |
| // Or if `P` is a subtype match for `Q0` under constraint set `C`. |
| if (performSubtypeConstraintGenerationInternal(p, q0, |
| leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) { |
| return true; |
| } |
| restoreState(state); |
| |
| // Or if `P` is a subtype match for `Future<Q0>` under empty |
| // constraint set `C`. |
| if (isMatchWithFuture && !matchWithFutureAddsConstraints) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /// Matches [p] against [q] as a subtype against supertype and returns true if |
| /// [p] and [q] are both type declaration types as defined by the enum |
| /// [TypeDeclarationKind], and [p] is a subtype of [q] under some constraints |
| /// imposed on type parameters occurring in [q], and false otherwise. |
| /// |
| /// An invariant of the type inference is that only [p] or [q] may be a |
| /// schema (in other words, may contain the unknown type `_`); the other must |
| /// be simply a type. If [leftSchema] is `true`, [p] may contain `_`; if it is |
| /// `false`, [q] may contain `_`. |
| /// |
| /// As the generator computes the constraints making the relation possible, it |
| /// changes its internal state. The current state of the generator can be |
| /// obtained by [currentState], and the generator can be restored to a state |
| /// via [restoreState]. All of the shared constraint generation methods are |
| /// supposed to restore the generator to the prior state in case of a |
| /// mismatch, taking that responsibility away from the caller. |
| bool? performSubtypeConstraintGenerationForTypeDeclarationTypes( |
| TypeStructure p, TypeStructure q, |
| {required bool leftSchema, required AstNode? astNodeForTesting}) { |
| switch (( |
| typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(p)), |
| typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(q)) |
| )) { |
| // If `P` is `C<M0, ..., Mk> and `Q` is `C<N0, ..., Nk>`, then the match |
| // holds under constraints `C0 + ... + Ck`: |
| // If `Mi` is a subtype match for `Ni` with respect to L under |
| // constraints `Ci`. |
| case ( |
| TypeDeclarationMatchResult( |
| typeDeclarationKind: TypeDeclarationKind pTypeDeclarationKind, |
| typeDeclaration: TypeDeclaration pDeclarationObject, |
| typeArguments: List<TypeStructure> pTypeArguments |
| ), |
| TypeDeclarationMatchResult( |
| typeDeclarationKind: TypeDeclarationKind qTypeDeclarationKind, |
| typeDeclaration: TypeDeclaration qDeclarationObject, |
| typeArguments: List<TypeStructure> qTypeArguments |
| ) |
| ) |
| when pTypeDeclarationKind == qTypeDeclarationKind && |
| pDeclarationObject == qDeclarationObject: |
| return _interfaceTypeArguments( |
| pDeclarationObject, pTypeArguments, qTypeArguments, leftSchema, |
| astNodeForTesting: astNodeForTesting); |
| |
| case (TypeDeclarationMatchResult(), TypeDeclarationMatchResult()): |
| return _interfaceTypes(p, q, leftSchema, |
| astNodeForTesting: astNodeForTesting); |
| |
| case ( |
| TypeDeclarationMatchResult? pMatched, |
| TypeDeclarationMatchResult? qMatched |
| ): |
| assert(pMatched == null || qMatched == null); |
| return null; |
| } |
| } |
| |
| /// Match arguments [pTypeArguments] of P against arguments [qTypeArguments] |
| /// of Q, taking into account the variance of type variables in [declaration]. |
| /// If returns `false`, the constraints are unchanged. |
| bool _interfaceTypeArguments( |
| TypeDeclaration declaration, |
| List<TypeStructure> pTypeArguments, |
| List<TypeStructure> qTypeArguments, |
| bool leftSchema, |
| {required AstNode? astNodeForTesting}) { |
| assert(pTypeArguments.length == qTypeArguments.length); |
| |
| final TypeConstraintGeneratorState state = currentState; |
| |
| for (int i = 0; i < pTypeArguments.length; i++) { |
| Variance variance = |
| typeAnalyzerOperations.getTypeParameterVariance(declaration, i); |
| TypeStructure M = pTypeArguments[i]; |
| TypeStructure N = qTypeArguments[i]; |
| if ((variance == Variance.covariant || variance == Variance.invariant) && |
| !performSubtypeConstraintGenerationInternal(M, N, |
| leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) { |
| restoreState(state); |
| return false; |
| } |
| if ((variance == Variance.contravariant || |
| variance == Variance.invariant) && |
| !performSubtypeConstraintGenerationInternal(N, M, |
| leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) { |
| restoreState(state); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool _interfaceTypes(TypeStructure p, TypeStructure q, bool leftSchema, |
| {required AstNode? astNodeForTesting}) { |
| if (p.nullabilitySuffix != NullabilitySuffix.none) { |
| return false; |
| } |
| |
| if (q.nullabilitySuffix != NullabilitySuffix.none) { |
| return false; |
| } |
| |
| // If `P` is `C0<M0, ..., Mk>` and `Q` is `C1<N0, ..., Nj>` then the match |
| // holds with respect to `L` under constraints `C`: |
| // If `C1<B0, ..., Bj>` is a superinterface of `C0<M0, ..., Mk>` and |
| // `C1<B0, ..., Bj>` is a subtype match for `C1<N0, ..., Nj>` with |
| // respect to `L` under constraints `C`. |
| |
| if (( |
| typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(p)), |
| typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(q)) |
| ) |
| case ( |
| TypeDeclarationMatchResult( |
| typeDeclarationType: TypeDeclarationType pTypeDeclarationType |
| ), |
| TypeDeclarationMatchResult( |
| typeDeclaration: TypeDeclaration qTypeDeclaration, |
| typeArguments: List<TypeStructure> qTypeArguments |
| ) |
| )) { |
| if (getTypeArgumentsAsInstanceOf(pTypeDeclarationType, qTypeDeclaration) |
| case List<TypeStructure> typeArguments) { |
| return _interfaceTypeArguments( |
| qTypeDeclaration, typeArguments, qTypeArguments, leftSchema, |
| astNodeForTesting: astNodeForTesting); |
| } |
| } |
| |
| return false; |
| } |
| |
| /// Returns the type arguments of the supertype of [type] that is an |
| /// instantiation of [typeDeclaration]. If none of the supertypes of [type] |
| /// are instantiations of [typeDeclaration], returns null. |
| List<TypeStructure>? getTypeArgumentsAsInstanceOf( |
| TypeDeclarationType type, TypeDeclaration typeDeclaration); |
| } |
| |
| /// Representation of the state of [TypeConstraintGenerator]. |
| /// |
| /// The state can be obtained via [TypeConstraintGenerator.currentState]. A |
| /// [TypeConstraintGenerator] can be restored to a state via |
| /// [TypeConstraintGenerator.restoreState]. |
| /// |
| /// In practice, the state is represented as an integer: the count of the |
| /// constraints generated so far. Since the count only increases as the |
| /// generator proceeds, restoring to a state means discarding some constraints. |
| extension type TypeConstraintGeneratorState(int count) {} |
| |
| mixin TypeConstraintGeneratorMixin< |
| TypeStructure extends SharedTypeStructure<TypeStructure>, |
| Variable extends Object, |
| InferableParameter extends Object, |
| TypeDeclarationType extends Object, |
| TypeDeclaration extends Object, |
| AstNode extends Object> |
| on TypeConstraintGenerator<TypeStructure, Variable, InferableParameter, |
| TypeDeclarationType, TypeDeclaration, AstNode> { |
| @override |
| bool performSubtypeConstraintGenerationLeftSchema( |
| SharedTypeSchemaView<TypeStructure> p, SharedTypeView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}) { |
| return performSubtypeConstraintGenerationInternal( |
| p.unwrapTypeSchemaView(), q.unwrapTypeView(), |
| leftSchema: true, astNodeForTesting: astNodeForTesting); |
| } |
| |
| @override |
| bool performSubtypeConstraintGenerationRightSchema( |
| SharedTypeView<TypeStructure> p, SharedTypeSchemaView<TypeStructure> q, |
| {required AstNode? astNodeForTesting}) { |
| return performSubtypeConstraintGenerationInternal( |
| p.unwrapTypeView(), q.unwrapTypeSchemaView(), |
| leftSchema: false, astNodeForTesting: astNodeForTesting); |
| } |
| } |