| // Copyright (c) 2016, 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. |
| |
| library kernel.type_algebra; |
| |
| import 'ast.dart'; |
| import 'src/find_type_visitor.dart'; |
| import 'src/replacement_visitor.dart'; |
| |
| /// Returns all free type variables in [type]. |
| /// |
| /// Returns the set of all [TypeParameter]s that are referred to by a |
| /// [TypeParameterType] in [type] that are not bound to an enclosing |
| /// [FunctionType]. |
| Set<StructuralParameter> allFreeTypeVariables(DartType type) { |
| _AllFreeTypeVariablesVisitor visitor = new _AllFreeTypeVariablesVisitor(); |
| visitor.visit(type); |
| return visitor.freeStructuralVariables; |
| } |
| |
| /// Returns a type where all occurrences of the given type parameters have been |
| /// replaced with the corresponding types. |
| /// |
| /// This will copy only the subterms of [type] that contain substituted |
| /// variables; all other [DartType] objects will be reused. |
| /// |
| /// In particular, if no variables were substituted, this is guaranteed to |
| /// return the [type] instance (not a copy), so the caller may use [identical] |
| /// to efficiently check if a distinct type was created. |
| DartType substitute(DartType type, Map<TypeParameter, DartType> substitution) { |
| if (substitution.isEmpty) return type; |
| return Substitution.fromMap(substitution).substituteType(type); |
| } |
| |
| /// Returns a mapping from the type parameters declared on the class of [type] |
| /// to the actual type arguments provided in [type]. |
| /// |
| /// This can be passed as argument to [substitute]. |
| Map<TypeParameter, DartType> getSubstitutionMap(Supertype type) { |
| return type.typeArguments.isEmpty |
| ? const <TypeParameter, DartType>{} |
| : new Map<TypeParameter, DartType>.fromIterables( |
| type.classNode.typeParameters, type.typeArguments); |
| } |
| |
| /// Returns true if [type] contains a reference to any of the given [variables]. |
| /// |
| /// [unhandledTypeHandler] is a helper function invoked on unknown implementers |
| /// of [DartType]. Its arguments are the unhandled type and the function that |
| /// can be invoked from within the handler on parts of the unknown type to |
| /// recursively call the visitor. If not passed, an exception is thrown then an |
| /// unhandled implementer of [DartType] is encountered. |
| /// |
| /// It is an error to call this with a [type] that contains a [FunctionType] |
| /// that declares one of the parameters in [variables]. |
| bool containsTypeVariable(DartType type, Set<TypeParameter> variables, |
| {DartTypeVisitorAuxiliaryFunction<bool>? unhandledTypeHandler}) { |
| if (variables.isEmpty) return false; |
| return new _OccurrenceVisitor(variables, |
| unhandledTypeHandler: unhandledTypeHandler) |
| .visit(type); |
| } |
| |
| /// Returns true if [type] contains a reference to any of the given [parameters] |
| /// |
| /// [unhandledTypeHandler] is a helper function invoked on unknown implementers |
| /// of [DartType]. Its arguments are the unhandled type and the function that |
| /// can be invoked from within the handler on parts of the unknown type to |
| /// recursively call the visitor. If not passed, an exception is thrown then an |
| /// unhandled implementer of [DartType] is encountered. |
| /// |
| /// It is an error to call this with a [type] that contains a [FunctionType] |
| /// that declares one of the parameters in [parameters]. |
| bool containsStructuralTypeVariable( |
| DartType type, Set<StructuralParameter> parameters, |
| {DartTypeVisitorAuxiliaryFunction<bool>? unhandledTypeHandler}) { |
| if (parameters.isEmpty) return false; |
| return new _StructuralParameterOccurrenceVisitor(parameters, |
| unhandledTypeHandler: unhandledTypeHandler) |
| .visit(type); |
| } |
| |
| /// Returns `true` if [type] contains any free type variables, that is, type |
| /// variable for function types whose function type is part of [type]. |
| bool containsFreeFunctionTypeVariables(DartType type) { |
| return new _FreeFunctionTypeVariableVisitor().visit(type); |
| } |
| |
| /// Returns `true` if [type] contains any free type variables |
| /// |
| /// Returns `true` if [type] contains a [TypeParameterType] that doesn't refer |
| /// to an enclosing generic [FunctionType] within [type]. |
| bool containsFreeTypeVariables(DartType type, |
| {Set<StructuralParameter>? boundVariables}) { |
| return new _FreeTypeVariableVisitor(boundVariables: boundVariables) |
| .visit(type); |
| } |
| |
| /// Generates a fresh copy of the given type parameters, with their bounds |
| /// substituted to reference the new parameters. |
| /// |
| /// The returned object contains the fresh type parameter list as well as a |
| /// mapping to be used for replacing other types to use the new type parameters. |
| FreshTypeParameters getFreshTypeParameters(List<TypeParameter> typeParameters) { |
| List<TypeParameter> freshParameters = new List<TypeParameter>.generate( |
| typeParameters.length, |
| (i) => new TypeParameter(typeParameters[i].name) |
| ..flags = typeParameters[i].flags, |
| growable: false); |
| List<DartType> freshTypeArguments = |
| new List<DartType>.generate(typeParameters.length, (int i) { |
| return new TypeParameterType.forAlphaRenaming( |
| typeParameters[i], freshParameters[i]); |
| }, growable: false); |
| Substitution substitution = |
| Substitution.fromPairs(typeParameters, freshTypeArguments); |
| for (int i = 0; i < typeParameters.length; ++i) { |
| TypeParameter typeParameter = typeParameters[i]; |
| TypeParameter freshTypeParameter = freshParameters[i]; |
| |
| freshTypeParameter.bound = substitution.substituteType(typeParameter.bound); |
| freshTypeParameter.defaultType = |
| substitution.substituteType(typeParameter.defaultType); |
| freshTypeParameter.variance = |
| typeParameter.isLegacyCovariant ? null : typeParameter.variance; |
| // Annotations on a type parameter are specific to the declaration of the |
| // type parameter, rather than the type parameter as such, and therefore |
| // should not be copied here. |
| } |
| return new FreshTypeParameters( |
| freshParameters, freshTypeArguments, substitution); |
| } |
| |
| class FreshTypeParameters { |
| /// The newly created type parameters. |
| final List<TypeParameter> freshTypeParameters; |
| |
| /// List of [TypeParameterType]s for [TypeParameter]. |
| final List<DartType> freshTypeArguments; |
| |
| /// Substitution from the original type parameters to [freshTypeArguments]. |
| final Substitution substitution; |
| |
| FreshTypeParameters( |
| this.freshTypeParameters, this.freshTypeArguments, this.substitution); |
| |
| DartType substitute(DartType type) => substitution.substituteType(type); |
| } |
| |
| /// Creates a fresh representation of the given [TypeParameter]s as |
| /// [StructuralParameter]s, with their bounds substituted to reference the new |
| /// parameters. |
| /// |
| /// The returned object contains the fresh list of [StructuralParameter]s as |
| /// well as a mapping to be used for replacing other types to use the new type |
| /// parameters instead of the old. |
| FreshStructuralParametersFromTypeParameters |
| getFreshStructuralParametersFromTypeParameters( |
| List<TypeParameter> typeParameters) { |
| List<StructuralParameter> freshParameters = |
| new List<StructuralParameter>.generate( |
| typeParameters.length, |
| (i) => new StructuralParameter(typeParameters[i].name) |
| ..flags = typeParameters[i].flags |
| ..uri = typeParameters[i].location?.file |
| ..fileOffset = typeParameters[i].fileOffset, |
| growable: false); |
| List<StructuralParameterType> freshTypeArguments = |
| new List<StructuralParameterType>.generate( |
| typeParameters.length, |
| (int i) => |
| new StructuralParameterType.forAlphaRenamingFromTypeParameters( |
| typeParameters[i], freshParameters[i]), |
| growable: false); |
| Substitution substitution; |
| if (typeParameters.length == 1) { |
| substitution = |
| Substitution.fromSingleton(typeParameters[0], freshTypeArguments[0]); |
| } else { |
| substitution = Substitution.fromMap( |
| new Map.fromIterables(typeParameters, freshTypeArguments)); |
| } |
| for (int i = 0; i < typeParameters.length; ++i) { |
| TypeParameter typeParameter = typeParameters[i]; |
| StructuralParameter freshTypeParameter = freshParameters[i]; |
| |
| freshTypeParameter.bound = substitution.substituteType(typeParameter.bound); |
| freshTypeParameter.defaultType = |
| substitution.substituteType(typeParameter.defaultType); |
| freshTypeParameter.variance = |
| typeParameter.isLegacyCovariant ? null : typeParameter.variance; |
| // Annotations on a type parameter are specific to the declaration of the |
| // type parameter, rather than the type parameter as such, and therefore |
| // should not be copied here. |
| } |
| return new FreshStructuralParametersFromTypeParameters( |
| freshParameters, freshTypeArguments, substitution); |
| } |
| |
| /// Representation of [TypeParameter]s as a fresh copy of [StructuralParameter]s |
| /// |
| /// The objects of [FreshStructuralParametersFromTypeParameters] are generated |
| /// from the given list of [TypeParameter]s and contain the list |
| /// [freshTypeParameters] of [StructuralParameter]s intended to replace the |
| /// initial list of [TypeParameter]s, the list [freshTypeArguments] representing |
| /// [freshTypeArguments] as types, and the [substitution] intended to replace |
| /// occurrences [TypeParameter]s from the original list with |
| /// [freshTypeArguments], according to [substitutionMap]. |
| class FreshStructuralParametersFromTypeParameters { |
| /// The newly created type parameters. |
| final List<StructuralParameter> freshTypeParameters; |
| |
| /// List of [StructuralParameterType]s for each of [freshTypeParameters] |
| final List<DartType> freshTypeArguments; |
| |
| /// Substitution from the original type parameters to [freshTypeArguments]. |
| final Substitution substitution; |
| |
| FreshStructuralParametersFromTypeParameters( |
| this.freshTypeParameters, this.freshTypeArguments, this.substitution); |
| |
| DartType substitute(DartType type) => substitution.substituteType(type); |
| } |
| |
| FreshTypeParametersFromStructuralParameters |
| getFreshTypeParametersFromStructuralParameters( |
| List<StructuralParameter> typeParameters) { |
| List<TypeParameter> freshParameters = new List<TypeParameter>.generate( |
| typeParameters.length, |
| (i) => new TypeParameter(typeParameters[i].name) |
| ..flags = typeParameters[i].flags, |
| growable: false); |
| List<DartType> freshTypeArguments = |
| new List<DartType>.generate(typeParameters.length, (int i) { |
| return new TypeParameterType.forAlphaRenamingFromStructuralParameters( |
| typeParameters[i], freshParameters[i]); |
| }, growable: false); |
| FunctionTypeInstantiator instantiator = |
| FunctionTypeInstantiator.fromIterables( |
| typeParameters, freshTypeArguments); |
| for (int i = 0; i < typeParameters.length; ++i) { |
| StructuralParameter typeParameter = typeParameters[i]; |
| TypeParameter freshTypeParameter = freshParameters[i]; |
| |
| freshTypeParameter.bound = instantiator.substitute(typeParameter.bound); |
| freshTypeParameter.defaultType = |
| instantiator.substitute(typeParameter.defaultType); |
| freshTypeParameter.variance = |
| typeParameter.isLegacyCovariant ? null : typeParameter.variance; |
| // Annotations on a type parameter are specific to the declaration of the |
| // type parameter, rather than the type parameter as such, and therefore |
| // should not be copied here. |
| } |
| return new FreshTypeParametersFromStructuralParameters( |
| freshParameters, freshTypeArguments, instantiator); |
| } |
| |
| class FreshTypeParametersFromStructuralParameters { |
| /// The newly created type parameters. |
| final List<TypeParameter> freshTypeParameters; |
| |
| /// List of [TypeParameterType]s for [TypeParameter]. |
| final List<DartType> freshTypeArguments; |
| |
| /// Substitution from the original type parameters to [freshTypeArguments]. |
| final FunctionTypeInstantiator instantiator; |
| |
| FreshTypeParametersFromStructuralParameters( |
| this.freshTypeParameters, this.freshTypeArguments, this.instantiator); |
| |
| DartType substitute(DartType type) => instantiator.substitute(type); |
| } |
| |
| FreshStructuralParameters? getFreshStructuralParametersSubstitutingBounds( |
| List<StructuralParameter> typeParameters, |
| [FunctionTypeInstantiator? outerInstantiator]) { |
| assert(typeParameters.isNotEmpty); |
| List<StructuralParameter> freshParameters = |
| new List<StructuralParameter>.generate( |
| typeParameters.length, |
| (i) => new StructuralParameter(typeParameters[i].name) |
| ..flags = typeParameters[i].flags, |
| growable: false); |
| List<DartType> freshTypeArguments = new List<DartType>.generate( |
| typeParameters.length, |
| (int i) => new StructuralParameterType.forAlphaRenaming( |
| typeParameters[i], freshParameters[i]), |
| growable: false); |
| FunctionTypeInstantiator instantiator = |
| FunctionTypeInstantiator.fromIterables( |
| typeParameters, freshTypeArguments); |
| |
| bool parameterBoundsHaveChanged = false; |
| for (int i = 0; i < typeParameters.length; ++i) { |
| StructuralParameter typeParameter = typeParameters[i]; |
| StructuralParameter freshTypeParameter = freshParameters[i]; |
| |
| DartType? boundVisitedByOuter = |
| outerInstantiator?.visit(typeParameter.bound); |
| DartType? boundVisitedByThis = |
| instantiator.visit(boundVisitedByOuter ?? typeParameter.bound); |
| freshTypeParameter.bound = |
| boundVisitedByThis ?? boundVisitedByOuter ?? typeParameter.bound; |
| if (boundVisitedByThis != null || boundVisitedByOuter != null) { |
| parameterBoundsHaveChanged = true; |
| } |
| |
| // TODO(cstefantsova): Replace the following by an assert checking that the |
| // type parameters don't occur in the default types. |
| DartType? defaultTypeVisitedByOuter = |
| outerInstantiator?.visit(typeParameter.defaultType); |
| DartType? defaultTypeVisitedByThis = instantiator |
| .visit(defaultTypeVisitedByOuter ?? typeParameter.defaultType); |
| freshTypeParameter.defaultType = defaultTypeVisitedByThis ?? |
| defaultTypeVisitedByOuter ?? |
| typeParameter.defaultType; |
| if (defaultTypeVisitedByThis != null || defaultTypeVisitedByThis != null) { |
| parameterBoundsHaveChanged = true; |
| } |
| |
| freshTypeParameter.variance = |
| typeParameter.isLegacyCovariant ? null : typeParameter.variance; |
| // Annotations on a type parameter are specific to the declaration of the |
| // type parameter, rather than the type parameter as such, and therefore |
| // should not be copied here. |
| } |
| if (!parameterBoundsHaveChanged) { |
| return null; |
| } else { |
| return new FreshStructuralParameters( |
| freshParameters, freshTypeArguments, instantiator); |
| } |
| } |
| |
| FreshStructuralParameters getFreshStructuralParametersReusingBounds( |
| List<StructuralParameter> typeParameters) { |
| assert(typeParameters.isNotEmpty); |
| List<StructuralParameter> freshParameters = |
| new List<StructuralParameter>.generate( |
| typeParameters.length, |
| (i) => new StructuralParameter(typeParameters[i].name, |
| typeParameters[i].bound, typeParameters[i].defaultType) |
| ..flags = typeParameters[i].flags |
| ..variance = typeParameters[i].isLegacyCovariant |
| ? null |
| : typeParameters[i].variance, |
| growable: false); |
| List<DartType> freshTypeArguments = new List<DartType>.generate( |
| typeParameters.length, |
| (int i) => new StructuralParameterType.forAlphaRenaming( |
| typeParameters[i], freshParameters[i]), |
| growable: false); |
| FunctionTypeInstantiator instantiator = |
| FunctionTypeInstantiator.fromIterables( |
| typeParameters, freshTypeArguments); |
| return new FreshStructuralParameters( |
| freshParameters, freshTypeArguments, instantiator); |
| } |
| |
| FreshStructuralParameters getFreshStructuralParameters( |
| List<StructuralParameter> typeParameters, |
| [FunctionTypeInstantiator? outerInstantiator]) { |
| assert(typeParameters.isNotEmpty); |
| return getFreshStructuralParametersSubstitutingBounds( |
| typeParameters, outerInstantiator) ?? |
| getFreshStructuralParametersReusingBounds(typeParameters); |
| } |
| |
| class FreshStructuralParameters { |
| /// The newly created type parameters. |
| final List<StructuralParameter> freshTypeParameters; |
| |
| /// List of [TypeParameterType]s for [TypeParameter]. |
| final List<DartType> freshTypeArguments; |
| |
| /// Substitution from the original type parameters to [freshTypeArguments]. |
| final FunctionTypeInstantiator instantiator; |
| |
| FreshStructuralParameters( |
| this.freshTypeParameters, this.freshTypeArguments, this.instantiator); |
| |
| DartType substitute(DartType type) => instantiator.substitute(type); |
| |
| NamedType substituteNamed(NamedType type) => |
| new NamedType(type.name, substitute(type.type), |
| isRequired: type.isRequired); |
| |
| FunctionType applyToFunctionType(FunctionType type) { |
| return new FunctionType(type.positionalParameters.map(substitute).toList(), |
| substitute(type.returnType), type.nullability, |
| namedParameters: type.namedParameters.map(substituteNamed).toList(), |
| typeParameters: freshTypeParameters, |
| requiredParameterCount: type.requiredParameterCount); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // IMPLEMENTATION |
| // ------------------------------------------------------------------------ |
| |
| abstract class Substitution { |
| const Substitution(); |
| |
| static const Substitution empty = _NullSubstitution.instance; |
| |
| bool get isEmpty => identical(this, empty); |
| |
| /// Substitutes each parameter to the type it maps to in [map]. |
| static Substitution fromMap(Map<TypeParameter, DartType> map) { |
| if (map.isEmpty) return _NullSubstitution.instance; |
| return new _MapSubstitution(map, map); |
| } |
| |
| /// Substitutes the [typeParameter] to the [type]. |
| static Substitution fromSingleton( |
| TypeParameter typeParameter, DartType type) { |
| return new _SingletonSubstitution(typeParameter, type); |
| } |
| |
| /// Substitutes all occurrences of the given type parameters with the |
| /// corresponding upper or lower bound, depending on the variance of the |
| /// context where it occurs. |
| /// |
| /// For example the type `(T) => T` with the bounds `bottom <: T <: num` |
| /// becomes `(bottom) => num` (in this example, `num` is the upper bound, |
| /// and `bottom` is the lower bound). |
| /// |
| /// This is a way to obtain an upper bound for a type while eliminating all |
| /// references to certain type variables. |
| static Substitution fromUpperAndLowerBounds( |
| Map<TypeParameter, DartType> upper, Map<TypeParameter, DartType> lower) { |
| if (upper.isEmpty && lower.isEmpty) return _NullSubstitution.instance; |
| return new _MapSubstitution(upper, lower); |
| } |
| |
| /// Substitutes the type parameters on the class of [supertype] with the |
| /// type arguments provided in [supertype]. |
| static Substitution fromSupertype(Supertype supertype) { |
| if (supertype.typeArguments.isEmpty) return _NullSubstitution.instance; |
| return fromMap(new Map<TypeParameter, DartType>.fromIterables( |
| supertype.classNode.typeParameters, supertype.typeArguments)); |
| } |
| |
| /// Returns the [Substitution] for the type parameters on the type declaration |
| /// of [type] with the type arguments provided in [type]. |
| static Substitution fromTypeDeclarationType(TypeDeclarationType type) { |
| if (type.typeArguments.isEmpty) return _NullSubstitution.instance; |
| if (type.typeArguments.length == 1) { |
| return fromSingleton( |
| type.typeDeclaration.typeParameters[0], type.typeArguments[0]); |
| } |
| return fromMap(new Map<TypeParameter, DartType>.fromIterables( |
| type.typeDeclaration.typeParameters, type.typeArguments)); |
| } |
| |
| /// Substitutes the type parameters on the class of [type] with the |
| /// type arguments provided in [type]. |
| static Substitution fromInterfaceType(InterfaceType type) { |
| if (type.typeArguments.isEmpty) return _NullSubstitution.instance; |
| if (type.typeArguments.length == 1) { |
| return fromSingleton( |
| type.classNode.typeParameters[0], type.typeArguments[0]); |
| } |
| return fromMap(new Map<TypeParameter, DartType>.fromIterables( |
| type.classNode.typeParameters, type.typeArguments)); |
| } |
| |
| /// Substitutes the type parameters on the extension type declaration of |
| /// [type] with the type arguments provided in [type]. |
| static Substitution fromExtensionType(ExtensionType type) { |
| if (type.typeArguments.isEmpty) return _NullSubstitution.instance; |
| return fromMap(new Map<TypeParameter, DartType>.fromIterables( |
| type.extensionTypeDeclaration.typeParameters, type.typeArguments)); |
| } |
| |
| /// Substitutes the type parameters on the typedef of [type] with the |
| /// type arguments provided in [type]. |
| static Substitution fromTypedefType(TypedefType type) { |
| if (type.typeArguments.isEmpty) return _NullSubstitution.instance; |
| return fromMap(new Map<TypeParameter, DartType>.fromIterables( |
| type.typedefNode.typeParameters, type.typeArguments)); |
| } |
| |
| /// Substitutes the Nth parameter in [parameters] with the Nth type in |
| /// [types]. |
| static Substitution fromPairs( |
| List<TypeParameter> parameters, List<DartType> types) { |
| // TODO(asgerf): Investigate if it is more efficient to implement |
| // substitution based on parallel pairwise lists instead of Maps. |
| assert(parameters.length == types.length); |
| if (parameters.isEmpty) return _NullSubstitution.instance; |
| if (parameters.length == 1) return fromSingleton(parameters[0], types[0]); |
| return fromMap( |
| new Map<TypeParameter, DartType>.fromIterables(parameters, types)); |
| } |
| |
| /// Substitutes the type parameters on the type declaration with bottom or |
| /// dynamic, depending on the covariance of its use. |
| static Substitution bottomForTypeDeclaration(TypeDeclaration declaration) { |
| if (declaration.typeParameters.isEmpty) return _NullSubstitution.instance; |
| return new _TypeDeclarationBottomSubstitution(declaration); |
| } |
| |
| /// Substitutes both variables from [first] and [second], favoring those from |
| /// [first] if they overlap. |
| /// |
| /// Neither substitution is applied to the results of the other, so this does |
| /// *not* correspond to a sequence of two substitutions. For example, |
| /// combining `{T -> List<G>}` with `{G -> String}` does not correspond to |
| /// `{T -> List<String>}` because the result from substituting `T` is not |
| /// searched for occurrences of `G`. |
| static Substitution combine(Substitution first, Substitution second) { |
| if (first == _NullSubstitution.instance) return second; |
| if (second == _NullSubstitution.instance) return first; |
| return new _CombinedSubstitution(first, second); |
| } |
| |
| /// Returns the substitution for [parameter] |
| DartType? getSubstitute(TypeParameter parameter, bool upperBound); |
| |
| DartType substituteType(DartType node, {bool contravariant = false}) { |
| return new _TopSubstitutor(this, contravariant).visit(node); |
| } |
| |
| Supertype substituteSupertype(Supertype node) { |
| return new _TopSubstitutor(this, false).visitSupertype(node); |
| } |
| } |
| |
| class _AllFreeTypeVariablesVisitor implements DartTypeVisitor<void> { |
| final Set<StructuralParameter> boundVariables = {}; |
| |
| final Set<TypeParameter> freeTypeVariables = {}; |
| |
| final Set<StructuralParameter> freeStructuralVariables = {}; |
| |
| void visit(DartType node) => node.accept(this); |
| |
| @override |
| bool visitAuxiliaryType(AuxiliaryType node) { |
| throw new UnsupportedError( |
| "Unsupported auxiliary type ${node} (${node.runtimeType})."); |
| } |
| |
| @override |
| void visitNeverType(NeverType node) {} |
| @override |
| void visitNullType(NullType node) {} |
| @override |
| void visitInvalidType(InvalidType node) {} |
| @override |
| void visitDynamicType(DynamicType node) {} |
| @override |
| void visitVoidType(VoidType node) {} |
| |
| @override |
| void visitInterfaceType(InterfaceType node) { |
| for (DartType typeArgument in node.typeArguments) { |
| typeArgument.accept(this); |
| } |
| } |
| |
| @override |
| void visitExtensionType(ExtensionType node) { |
| for (DartType typeArgument in node.typeArguments) { |
| typeArgument.accept(this); |
| } |
| } |
| |
| @override |
| void visitFutureOrType(FutureOrType node) { |
| node.typeArgument.accept(this); |
| } |
| |
| @override |
| void visitTypedefType(TypedefType node) { |
| for (DartType typeArgument in node.typeArguments) { |
| typeArgument.accept(this); |
| } |
| } |
| |
| @override |
| void visitFunctionType(FunctionType node) { |
| boundVariables.addAll(node.typeParameters); |
| for (StructuralParameter typeParameter in node.typeParameters) { |
| typeParameter.bound.accept(this); |
| typeParameter.defaultType.accept(this); |
| } |
| for (DartType positionalParameter in node.positionalParameters) { |
| positionalParameter.accept(this); |
| } |
| for (NamedType namedParameter in node.namedParameters) { |
| namedParameter.type.accept(this); |
| } |
| node.returnType.accept(this); |
| boundVariables.removeAll(node.typeParameters); |
| } |
| |
| @override |
| void visitRecordType(RecordType node) { |
| for (DartType positional in node.positional) { |
| positional.accept(this); |
| } |
| for (NamedType named in node.named) { |
| named.type.accept(this); |
| } |
| } |
| |
| @override |
| void visitTypeParameterType(TypeParameterType node) { |
| freeTypeVariables.add(node.parameter); |
| } |
| |
| @override |
| void visitStructuralParameterType(StructuralParameterType node) { |
| if (!boundVariables.contains(node.parameter)) { |
| freeStructuralVariables.add(node.parameter); |
| } |
| } |
| |
| @override |
| void visitIntersectionType(IntersectionType node) { |
| node.left.accept(this); |
| node.right.accept(this); |
| } |
| } |
| |
| class _NullSubstitution extends Substitution { |
| static const _NullSubstitution instance = const _NullSubstitution(); |
| |
| const _NullSubstitution(); |
| |
| @override |
| DartType getSubstitute(TypeParameter parameter, bool upperBound) { |
| return new TypeParameterType.forAlphaRenaming(parameter, parameter); |
| } |
| |
| @override |
| DartType substituteType(DartType node, {bool contravariant = false}) => node; |
| |
| @override |
| Supertype substituteSupertype(Supertype node) => node; |
| |
| @override |
| String toString() => "Substitution.empty"; |
| } |
| |
| class _MapSubstitution extends Substitution { |
| final Map<TypeParameter, DartType> upper; |
| final Map<TypeParameter, DartType> lower; |
| |
| _MapSubstitution(this.upper, this.lower); |
| |
| @override |
| DartType? getSubstitute(TypeParameter parameter, bool upperBound) { |
| return upperBound ? upper[parameter] : lower[parameter]; |
| } |
| |
| @override |
| String toString() => "_MapSubstitution($upper, $lower)"; |
| } |
| |
| class _SingletonSubstitution extends Substitution { |
| final TypeParameter typeParameter; |
| final DartType type; |
| |
| _SingletonSubstitution(this.typeParameter, this.type); |
| |
| @override |
| DartType? getSubstitute(TypeParameter parameter, bool upperBound) { |
| return (parameter == typeParameter) ? type : null; |
| } |
| |
| @override |
| String toString() => "_SingletonSubstitution($typeParameter, $type)"; |
| } |
| |
| class _TopSubstitutor extends _TypeSubstitutor { |
| final Substitution substitution; |
| |
| _TopSubstitutor(this.substitution, bool contravariant) : super(null) { |
| if (contravariant) { |
| invertVariance(); |
| } |
| } |
| |
| @override |
| DartType? lookup(TypeParameter parameter, bool upperBound) { |
| return substitution.getSubstitute(parameter, upperBound); |
| } |
| |
| @override |
| StructuralParameter freshStructuralParameter(StructuralParameter node) { |
| throw 'Create a fresh environment first'; |
| } |
| } |
| |
| class _TypeDeclarationBottomSubstitution extends Substitution { |
| final GenericDeclaration declaration; |
| |
| _TypeDeclarationBottomSubstitution(this.declaration); |
| |
| @override |
| DartType? getSubstitute(TypeParameter parameter, bool upperBound) { |
| if (parameter.declaration == declaration) { |
| return upperBound ? const NeverType.nonNullable() : const DynamicType(); |
| } |
| return null; |
| } |
| } |
| |
| class _CombinedSubstitution extends Substitution { |
| final Substitution first, second; |
| |
| _CombinedSubstitution(this.first, this.second); |
| |
| @override |
| DartType? getSubstitute(TypeParameter parameter, bool upperBound) { |
| return first.getSubstitute(parameter, upperBound) ?? |
| second.getSubstitute(parameter, upperBound); |
| } |
| } |
| |
| typedef bool TypeParameterFilter(TypeParameter P); |
| |
| class _InnerTypeSubstitutor extends _SubstitutorBase { |
| final Map<StructuralParameter, DartType> substitution = |
| <StructuralParameter, DartType>{}; |
| |
| _InnerTypeSubstitutor( |
| {required _SubstitutorBase? outer, required bool covariantContext}) |
| : super(outer: outer, covariantContext: covariantContext); |
| |
| @override |
| DartType? lookup(TypeParameter parameter, bool upperBound) { |
| // [_InnerTypeSubstitution] doesn't substitute [TypeParameter]s. |
| return null; |
| } |
| |
| @override |
| DartType visitStructuralParameterType(StructuralParameterType node) { |
| DartType? replacement; |
| _SubstitutorBase? environment = this; |
| while (replacement == null && environment != null) { |
| if (environment is _InnerTypeSubstitutor) { |
| replacement = environment.substitution[node.parameter]; |
| if (replacement != null) { |
| bumpCountersUntil(environment); |
| break; |
| } |
| } |
| environment = environment.outer; |
| } |
| if (replacement == null) return node; |
| return replacement.withDeclaredNullability( |
| combineNullabilitiesForSubstitution( |
| replacement.nullability, node.nullability)); |
| } |
| |
| @override |
| StructuralParameter freshStructuralParameter(StructuralParameter node) { |
| assert( |
| !substitution.containsKey(node), |
| "StructuralParameter '${node}' is not in the scope " |
| "of the inner substitutor."); |
| |
| StructuralParameter fresh = new StructuralParameter(node.name) |
| ..flags = node.flags; |
| StructuralParameterType typeParameterType = substitution[node] = |
| new StructuralParameterType.forAlphaRenaming(node, fresh); |
| fresh.bound = visit(node.bound); |
| fresh.defaultType = visit(node.defaultType); |
| // If the bound was changed from substituting the bound we need to update |
| // implicit nullability to be based on the new bound. If the bound wasn't |
| // changed the computation below results in the same nullability. |
| // |
| // If the type variable occurred in the bound then the bound was |
| // of the form `Foo<...T..>` or `FutureOr<T>` and the nullability therefore |
| // has not changed. |
| typeParameterType.declaredNullability = |
| StructuralParameterType.computeNullabilityFromBound(fresh); |
| return fresh; |
| } |
| |
| // TODO(johnniwinther): Throw on (unhandled) auxiliary type? |
| @override |
| DartType visitAuxiliaryType(AuxiliaryType node) => node; |
| |
| @override |
| DartType visitInvalidType(InvalidType node) => node; |
| |
| @override |
| DartType visitDynamicType(DynamicType node) => node; |
| |
| @override |
| DartType visitVoidType(VoidType node) => node; |
| |
| @override |
| DartType visitNeverType(NeverType node) => node; |
| |
| @override |
| DartType visitNullType(NullType node) => node; |
| |
| @override |
| DartType visitInterfaceType(InterfaceType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int counterBefore = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == counterBefore) return node; |
| return new InterfaceType.byReference( |
| node.classReference, node.nullability, typeArguments); |
| } |
| |
| @override |
| DartType visitExtensionType(ExtensionType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int counterBefore = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == counterBefore) return node; |
| return new ExtensionType( |
| node.extensionTypeDeclaration, node.nullability, typeArguments); |
| } |
| |
| @override |
| DartType visitRecordType(RecordType node) { |
| int counterBefore = useCounter; |
| List<DartType> positional = node.positional.map(visit).toList(); |
| List<NamedType> named = node.named.map(visitNamedType).toList(); |
| if (useCounter == counterBefore) return node; |
| return new RecordType(positional, named, node.nullability); |
| } |
| |
| @override |
| DartType visitFutureOrType(FutureOrType node) { |
| int counterBefore = useCounter; |
| DartType typeArgument = node.typeArgument.accept(this); |
| if (useCounter == counterBefore) return node; |
| |
| // The top-level nullability of a FutureOr should remain the same, with the |
| // exception of the case of [Nullability.undetermined]. In that case it |
| // remains undetermined if the nullability of [typeArgument] is |
| // undetermined, and otherwise it should become [Nullability.nonNullable]. |
| Nullability nullability; |
| if (node.declaredNullability == Nullability.undetermined) { |
| if (typeArgument.nullability == Nullability.undetermined) { |
| nullability = Nullability.undetermined; |
| } else { |
| nullability = Nullability.nonNullable; |
| } |
| } else { |
| nullability = node.declaredNullability; |
| } |
| return new FutureOrType(typeArgument, nullability); |
| } |
| |
| @override |
| DartType visitTypedefType(TypedefType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int counterBefore = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == counterBefore) return node; |
| return new TypedefType(node.typedefNode, node.nullability, typeArguments); |
| } |
| |
| @override |
| DartType visitFunctionType(FunctionType node) { |
| // This is a bit tricky because we have to generate fresh type parameters |
| // in order to change the bounds. At the same time, if the function type |
| // was unaltered, we have to return the [node] object (not a copy!). |
| // Substituting a type for a fresh type variable should not be confused with |
| // a "real" substitution. |
| // |
| // Create an inner environment to generate fresh type parameters. The use |
| // counter on the inner environment tells if the fresh type parameters have |
| // any uses, but does not tell if the resulting function type is distinct. |
| // Our own use counter will get incremented if something from our |
| // environment has been used inside the function. |
| _SubstitutorBase subtermSubstitutor = |
| node.typeParameters.isEmpty ? this : newInnerEnvironment(); |
| // Invert the variance when translating parameters. |
| subtermSubstitutor.invertVariance(); |
| int counterBefore = useCounter; |
| List<StructuralParameter> typeParameters = |
| subtermSubstitutor.freshTypeParameters(node.typeParameters); |
| List<DartType> positionalParameters = node.positionalParameters.isEmpty |
| ? const <DartType>[] |
| : node.positionalParameters.map(subtermSubstitutor.visit).toList(); |
| List<NamedType> namedParameters = node.namedParameters.isEmpty |
| ? const <NamedType>[] |
| : node.namedParameters.map(subtermSubstitutor.visitNamedType).toList(); |
| subtermSubstitutor.invertVariance(); |
| DartType returnType = subtermSubstitutor.visit(node.returnType); |
| if (useCounter == counterBefore) return node; |
| return new FunctionType(positionalParameters, returnType, node.nullability, |
| namedParameters: namedParameters, |
| typeParameters: typeParameters, |
| requiredParameterCount: node.requiredParameterCount); |
| } |
| |
| @override |
| DartType visitTypeParameterType(TypeParameterType node) { |
| DartType? replacement = getSubstitute(node.parameter); |
| if (replacement is InvalidType) return replacement; |
| if (replacement != null) { |
| return replacement.withDeclaredNullability( |
| combineNullabilitiesForSubstitution( |
| replacement.nullability, node.nullability)); |
| } |
| return node; |
| } |
| |
| @override |
| DartType visitIntersectionType(IntersectionType node) { |
| return node.left.accept(this); |
| } |
| } |
| |
| /// Combines nullabilities of types during type substitution. |
| /// |
| /// In a type substitution, for example, when `int` is substituted for `T` in |
| /// `List<T?>`, the nullability of the occurrence of the type parameter should |
| /// be combined with the nullability of the type that is being substituted for |
| /// that type parameter. In the example above it's the nullability of `T?` |
| /// and `int`. The function computes the nullability for the replacement as |
| /// per the following table: |
| /// |
| /// | a \ b | ! | ? | * | % | |
| /// |-----------|-----|-----|-----|-----| |
| /// | ! | ! | ? | * | ! | |
| /// | ? | N/A | ? | ? | ? | |
| /// | * | * | ? | * | * | |
| /// | % | N/A | ? | * | % | |
| /// |
| /// Here `!` denotes `Nullability.nonNullable`, `?` denotes |
| /// `Nullability.nullable`, `*` denotes `Nullability.legacy`, and `%` denotes |
| /// `Nullability.undetermined`. The table elements marked with N/A denote the |
| /// cases that should yield a type error before the substitution is performed. |
| /// |
| /// a is nonNullable: |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nonNullable, Nullability.nonNullable), |
| /// Nullability.nonNullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nonNullable, Nullability.nullable), |
| /// Nullability.nullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nonNullable, Nullability.legacy), |
| /// Nullability.legacy |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nonNullable, Nullability.undetermined), |
| /// Nullability.nonNullable |
| /// ) |
| /// |
| /// a is nullable: |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nullable, Nullability.nullable), |
| /// Nullability.nullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nullable, Nullability.legacy), |
| /// Nullability.nullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.nullable, Nullability.undetermined), |
| /// Nullability.nullable |
| /// ) |
| /// |
| /// a is legacy: |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.legacy, Nullability.nonNullable), |
| /// Nullability.legacy |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.legacy, Nullability.nullable), |
| /// Nullability.nullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.legacy, Nullability.legacy), |
| /// Nullability.legacy |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.legacy, Nullability.undetermined), |
| /// Nullability.legacy |
| /// ) |
| /// |
| /// a is undetermined: |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.undetermined, Nullability.nullable), |
| /// Nullability.nullable |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.undetermined, Nullability.legacy), |
| /// Nullability.legacy |
| /// ) |
| /// DartDocTest( |
| /// combineNullabilitiesForSubstitution( |
| /// Nullability.undetermined, Nullability.undetermined), |
| /// Nullability.undetermined |
| /// ) |
| Nullability combineNullabilitiesForSubstitution(Nullability a, Nullability b) { |
| // In the table above we may extend the function given by it, replacing N/A |
| // with whatever is easier to implement. In this implementation, we extend |
| // the table function as follows: |
| // |
| // | a \ b | ! | ? | * | % | |
| // |-----------|-----|-----|-----|-----| |
| // | ! | ! | ? | * | ! | |
| // | ? | ? | ? | ? | ? | |
| // | * | * | ? | * | * | |
| // | % | % | ? | * | % | |
| // |
| |
| if (a == Nullability.nullable || b == Nullability.nullable) { |
| return Nullability.nullable; |
| } |
| |
| if (a == Nullability.legacy || b == Nullability.legacy) { |
| return Nullability.legacy; |
| } |
| |
| return a; |
| } |
| |
| abstract class _SubstitutorBase implements DartTypeVisitor<DartType> { |
| final _SubstitutorBase? outer; |
| |
| bool covariantContext = true; |
| |
| /// The number of times a variable from this environment has been used in |
| /// a substitution. |
| /// |
| /// There is a strict requirement that we must return the same instance for |
| /// types that were not altered by the substitution. This counter lets us |
| /// check quickly if anything happened in a substitution. |
| int useCounter = 0; |
| |
| _SubstitutorBase({required this.outer, required this.covariantContext}); |
| |
| DartType? lookup(TypeParameter parameter, bool upperBound); |
| |
| DartType visit(DartType node) => node.accept(this); |
| |
| _InnerTypeSubstitutor newInnerEnvironment() { |
| return new _InnerTypeSubstitutor( |
| outer: this, covariantContext: covariantContext); |
| } |
| |
| void invertVariance() { |
| covariantContext = !covariantContext; |
| } |
| |
| Supertype visitSupertype(Supertype node) { |
| if (node.typeArguments.isEmpty) return node; |
| int before = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == before) return node; |
| return new Supertype(node.classNode, typeArguments); |
| } |
| |
| NamedType visitNamedType(NamedType node) { |
| int before = useCounter; |
| DartType type = visit(node.type); |
| if (useCounter == before) return node; |
| return new NamedType(node.name, type, isRequired: node.isRequired); |
| } |
| |
| void bumpCountersUntil(_SubstitutorBase target) { |
| _SubstitutorBase? node = this; |
| while (node != target) { |
| ++node!.useCounter; |
| node = node.outer; |
| } |
| ++target.useCounter; |
| } |
| |
| DartType? getSubstitute(TypeParameter variable) { |
| _SubstitutorBase? environment = this; |
| while (environment != null) { |
| DartType? replacement = environment.lookup(variable, covariantContext); |
| if (replacement != null) { |
| bumpCountersUntil(environment); |
| return replacement; |
| } |
| environment = environment.outer; |
| } |
| return null; |
| } |
| |
| StructuralParameter freshStructuralParameter(StructuralParameter node); |
| |
| List<StructuralParameter> freshTypeParameters( |
| List<StructuralParameter> parameters) { |
| if (parameters.isEmpty) return const <StructuralParameter>[]; |
| return parameters.map(freshStructuralParameter).toList(); |
| } |
| } |
| |
| abstract class _TypeSubstitutor extends _SubstitutorBase { |
| _TypeSubstitutor(_SubstitutorBase? outer) |
| : super( |
| outer: outer, |
| covariantContext: outer == null ? true : outer.covariantContext); |
| |
| // TODO(johnniwinther): Throw on (unhandled) auxiliary type? |
| @override |
| DartType visitAuxiliaryType(AuxiliaryType node) => node; |
| |
| @override |
| DartType visitInvalidType(InvalidType node) => node; |
| |
| @override |
| DartType visitDynamicType(DynamicType node) => node; |
| |
| @override |
| DartType visitVoidType(VoidType node) => node; |
| |
| @override |
| DartType visitNeverType(NeverType node) => node; |
| |
| @override |
| DartType visitNullType(NullType node) => node; |
| |
| @override |
| DartType visitInterfaceType(InterfaceType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int before = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == before) return node; |
| return new InterfaceType.byReference( |
| node.classReference, node.nullability, typeArguments); |
| } |
| |
| @override |
| DartType visitExtensionType(ExtensionType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int before = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == before) return node; |
| return new ExtensionType( |
| node.extensionTypeDeclaration, node.declaredNullability, typeArguments); |
| } |
| |
| @override |
| DartType visitRecordType(RecordType node) { |
| int before = useCounter; |
| List<DartType> positional = node.positional.map(visit).toList(); |
| List<NamedType> named = node.named.map(visitNamedType).toList(); |
| if (useCounter == before) return node; |
| return new RecordType(positional, named, node.nullability); |
| } |
| |
| @override |
| DartType visitFutureOrType(FutureOrType node) { |
| int before = useCounter; |
| DartType typeArgument = node.typeArgument.accept(this); |
| if (useCounter == before) return node; |
| |
| // The top-level nullability of a FutureOr should remain the same, with the |
| // exception of the case of [Nullability.undetermined]. In that case it |
| // remains undetermined if the nullability of [typeArgument] is |
| // undetermined, and otherwise it should become [Nullability.nonNullable]. |
| Nullability nullability; |
| if (node.declaredNullability == Nullability.undetermined) { |
| if (typeArgument.nullability == Nullability.undetermined) { |
| nullability = Nullability.undetermined; |
| } else { |
| nullability = Nullability.nonNullable; |
| } |
| } else { |
| nullability = node.declaredNullability; |
| } |
| return new FutureOrType(typeArgument, nullability); |
| } |
| |
| @override |
| DartType visitTypedefType(TypedefType node) { |
| if (node.typeArguments.isEmpty) return node; |
| int before = useCounter; |
| List<DartType> typeArguments = node.typeArguments.map(visit).toList(); |
| if (useCounter == before) return node; |
| return new TypedefType(node.typedefNode, node.nullability, typeArguments); |
| } |
| |
| @override |
| DartType visitFunctionType(FunctionType node) { |
| // This is a bit tricky because we have to generate fresh type parameters |
| // in order to change the bounds. At the same time, if the function type |
| // was unaltered, we have to return the [node] object (not a copy!). |
| // Substituting a type for a fresh type variable should not be confused with |
| // a "real" substitution. |
| // |
| // Create an inner environment to generate fresh type parameters. The use |
| // counter on the inner environment tells if the fresh type parameters have |
| // any uses, but does not tell if the resulting function type is distinct. |
| // Our own use counter will get incremented if something from our |
| // environment has been used inside the function. |
| _SubstitutorBase inner = |
| node.typeParameters.isEmpty ? this : newInnerEnvironment(); |
| int before = this.useCounter; |
| // Invert the variance when translating parameters. |
| inner.invertVariance(); |
| List<StructuralParameter> typeParameters = |
| inner.freshTypeParameters(node.typeParameters); |
| List<DartType> positionalParameters = node.positionalParameters.isEmpty |
| ? const <DartType>[] |
| : node.positionalParameters.map(inner.visit).toList(); |
| List<NamedType> namedParameters = node.namedParameters.isEmpty |
| ? const <NamedType>[] |
| : node.namedParameters.map(inner.visitNamedType).toList(); |
| inner.invertVariance(); |
| DartType returnType = inner.visit(node.returnType); |
| if (this.useCounter == before) return node; |
| return new FunctionType(positionalParameters, returnType, node.nullability, |
| namedParameters: namedParameters, |
| typeParameters: typeParameters, |
| requiredParameterCount: node.requiredParameterCount); |
| } |
| |
| @override |
| DartType visitTypeParameterType(TypeParameterType node) { |
| DartType? replacement = getSubstitute(node.parameter); |
| if (replacement is InvalidType) return replacement; |
| if (replacement != null) { |
| return replacement.withDeclaredNullability( |
| combineNullabilitiesForSubstitution( |
| replacement.declaredNullability, node.nullability)); |
| } |
| return node; |
| } |
| |
| @override |
| DartType visitStructuralParameterType(StructuralParameterType node) { |
| DartType? replacement; |
| _SubstitutorBase? environment = this; |
| while (replacement == null && environment != null) { |
| if (environment is _InnerTypeSubstitutor) { |
| replacement = environment.substitution[node.parameter]; |
| if (replacement != null) { |
| bumpCountersUntil(environment); |
| break; |
| } |
| } |
| environment = environment.outer; |
| } |
| if (replacement == null) return node; |
| return replacement.withDeclaredNullability( |
| combineNullabilitiesForSubstitution( |
| node.declaredNullability, replacement.nullability)); |
| } |
| |
| @override |
| DartType visitIntersectionType(IntersectionType node) { |
| return node.left.accept(this); |
| } |
| } |
| |
| class FunctionTypeInstantiator implements DartTypeVisitor<DartType?> { |
| bool covariantContext = true; |
| Map<StructuralParameter, DartType> substitutionMap; |
| FunctionTypeInstantiator? outer; |
| |
| FunctionTypeInstantiator.fromMap(this.substitutionMap, {this.outer}); |
| |
| FunctionTypeInstantiator.fromIterables( |
| List<StructuralParameter> from, List<DartType> to) |
| : this.fromMap( |
| new Map<StructuralParameter, DartType>.fromIterables(from, to)); |
| |
| FunctionTypeInstantiator.fromInstantiation( |
| FunctionType functionType, List<DartType> arguments) |
| : this.fromIterables(functionType.typeParameters, arguments); |
| |
| static FunctionType instantiate( |
| FunctionType functionType, List<DartType> arguments) { |
| assert(functionType.typeParameters.length == arguments.length); |
| if (arguments.isEmpty) return functionType; |
| |
| FunctionTypeInstantiator instantiator = |
| new FunctionTypeInstantiator.fromInstantiation(functionType, arguments); |
| DartType? returnType = instantiator.substitute(functionType.returnType); |
| List<DartType>? positionalParameters; |
| for (int i = 0; i < functionType.positionalParameters.length; i++) { |
| DartType positional = functionType.positionalParameters[i]; |
| DartType? visited = instantiator.visit(positional); |
| if (visited != null) { |
| positionalParameters ??= |
| new List<DartType>.of(functionType.positionalParameters); |
| positionalParameters[i] = visited; |
| } |
| } |
| List<NamedType>? namedParameters; |
| for (int i = 0; i < functionType.namedParameters.length; i++) { |
| NamedType named = functionType.namedParameters[i]; |
| NamedType? visited = instantiator.visitNamedType(named); |
| if (visited != null) { |
| namedParameters ??= |
| new List<NamedType>.of(functionType.namedParameters); |
| namedParameters[i] = visited; |
| } |
| } |
| |
| return new FunctionType( |
| positionalParameters ?? functionType.positionalParameters, |
| returnType, |
| functionType.declaredNullability, |
| namedParameters: namedParameters ?? functionType.namedParameters, |
| typeParameters: const [], |
| requiredParameterCount: functionType.requiredParameterCount); |
| } |
| |
| List<DartType>? _visitDartTypeList(List<DartType> list) { |
| if (list.isEmpty) return null; |
| List<DartType>? result; |
| for (int i = 0; i < list.length; i++) { |
| DartType? visited = visit(list[i]); |
| if (visited != null) { |
| result ??= new List<DartType>.of(list); |
| result[i] = visited; |
| } |
| } |
| return result; |
| } |
| |
| List<NamedType>? _visitNamedTypeList(List<NamedType> list) { |
| if (list.isEmpty) return null; |
| List<NamedType>? result; |
| for (int i = 0; i < list.length; i++) { |
| NamedType? visited = visitNamedType(list[i]); |
| if (visited != null) { |
| result ??= new List<NamedType>.of(list); |
| result[i] = visited; |
| } |
| } |
| return result; |
| } |
| |
| DartType? lookup(StructuralParameter parameter, bool upperBound) { |
| return substitutionMap[parameter]; |
| } |
| |
| void invertVariance() { |
| covariantContext = !covariantContext; |
| } |
| |
| NamedType? visitNamedType(NamedType node) { |
| DartType? type = visit(node.type); |
| if (type == null) { |
| return null; |
| } else { |
| return new NamedType(node.name, type, isRequired: node.isRequired); |
| } |
| } |
| |
| DartType substitute(DartType node) => visit(node) ?? node; |
| |
| DartType? visit(DartType node) => node.accept(this); |
| |
| // TODO(johnniwinther): Throw on (unhandled) auxiliary type? |
| @override |
| DartType? visitAuxiliaryType(AuxiliaryType node) => null; |
| @override |
| DartType? visitInvalidType(InvalidType node) => null; |
| @override |
| DartType? visitDynamicType(DynamicType node) => null; |
| @override |
| DartType? visitVoidType(VoidType node) => null; |
| @override |
| DartType? visitNeverType(NeverType node) => null; |
| @override |
| DartType? visitNullType(NullType node) => null; |
| @override |
| DartType? visitTypeParameterType(TypeParameterType node) => null; |
| @override |
| DartType? visitIntersectionType(IntersectionType node) => null; |
| |
| @override |
| DartType? visitInterfaceType(InterfaceType node) { |
| if (node.typeArguments.isEmpty) return null; |
| List<DartType>? typeArguments = _visitDartTypeList(node.typeArguments); |
| if (typeArguments == null) { |
| return null; |
| } else { |
| return new InterfaceType.byReference( |
| node.classReference, node.nullability, typeArguments); |
| } |
| } |
| |
| @override |
| DartType? visitExtensionType(ExtensionType node) { |
| if (node.typeArguments.isEmpty) return null; |
| List<DartType>? typeArguments = _visitDartTypeList(node.typeArguments); |
| if (typeArguments == null) { |
| return null; |
| } else { |
| return new ExtensionType(node.extensionTypeDeclaration, |
| node.declaredNullability, typeArguments); |
| } |
| } |
| |
| @override |
| DartType? visitRecordType(RecordType node) { |
| List<DartType>? positional = _visitDartTypeList(node.positional); |
| List<NamedType>? named = _visitNamedTypeList(node.named); |
| if (positional == null && named == null) { |
| return null; |
| } else { |
| return new RecordType( |
| positional ?? new List<DartType>.of(node.positional), |
| named ?? new List<NamedType>.of(node.named), |
| node.nullability); |
| } |
| } |
| |
| @override |
| DartType? visitFutureOrType(FutureOrType node) { |
| DartType? typeArgument = node.typeArgument.accept(this); |
| if (typeArgument == null) return null; |
| |
| // The top-level nullability of a FutureOr should remain the same, with the |
| // exception of the case of [Nullability.undetermined]. In that case it |
| // remains undetermined if the nullability of [typeArgument] is |
| // undetermined, and otherwise it should become [Nullability.nonNullable]. |
| Nullability nullability; |
| if (node.declaredNullability == Nullability.undetermined) { |
| if (typeArgument.nullability == Nullability.undetermined) { |
| nullability = Nullability.undetermined; |
| } else { |
| nullability = Nullability.nonNullable; |
| } |
| } else { |
| nullability = node.declaredNullability; |
| } |
| return new FutureOrType(typeArgument, nullability); |
| } |
| |
| @override |
| DartType? visitTypedefType(TypedefType node) { |
| if (node.typeArguments.isEmpty) return null; |
| List<DartType>? typeArguments = _visitDartTypeList(node.typeArguments); |
| if (typeArguments == null) { |
| return null; |
| } else { |
| return new TypedefType(node.typedefNode, node.nullability, typeArguments); |
| } |
| } |
| |
| @override |
| DartType? visitFunctionType(FunctionType node) { |
| // Invert the variance when translating parameters. |
| invertVariance(); |
| |
| FreshStructuralParameters? freshTypeParametersWithSubstitutedBounds; |
| FunctionTypeInstantiator instantiator; |
| List<StructuralParameter> typeParameters; |
| if (node.typeParameters.isEmpty) { |
| instantiator = this; |
| typeParameters = const <StructuralParameter>[]; |
| } else { |
| freshTypeParametersWithSubstitutedBounds = |
| getFreshStructuralParametersSubstitutingBounds( |
| node.typeParameters, this); |
| FreshStructuralParameters freshTypeParameters = |
| freshTypeParametersWithSubstitutedBounds ?? |
| getFreshStructuralParametersReusingBounds(node.typeParameters); |
| instantiator = freshTypeParameters.instantiator; |
| typeParameters = freshTypeParameters.freshTypeParameters; |
| instantiator.outer = this; |
| } |
| |
| List<DartType>? positionalParameters = node.positionalParameters.isEmpty |
| ? null |
| : instantiator._visitDartTypeList(node.positionalParameters); |
| List<NamedType>? namedParameters = node.namedParameters.isEmpty |
| ? null |
| : instantiator._visitNamedTypeList(node.namedParameters); |
| invertVariance(); |
| DartType? returnType = instantiator.visit(node.returnType); |
| |
| if (freshTypeParametersWithSubstitutedBounds == null && |
| positionalParameters == null && |
| namedParameters == null && |
| returnType == null) { |
| return null; |
| } else { |
| return new FunctionType( |
| positionalParameters ?? |
| new List<DartType>.of(node.positionalParameters), |
| returnType ?? node.returnType, |
| node.nullability, |
| namedParameters: |
| namedParameters ?? new List<NamedType>.of(node.namedParameters), |
| typeParameters: typeParameters, |
| requiredParameterCount: node.requiredParameterCount); |
| } |
| } |
| |
| @override |
| DartType? visitStructuralParameterType(StructuralParameterType node) { |
| DartType? replacement = getSubstitute(node.parameter); |
| if (replacement is InvalidType) return replacement; |
| if (replacement != null) { |
| return replacement.withDeclaredNullability( |
| combineNullabilitiesForSubstitution( |
| replacement.declaredNullability, node.nullability)); |
| } |
| return null; |
| } |
| |
| DartType? getSubstitute(StructuralParameter variable) { |
| FunctionTypeInstantiator? environment = this; |
| while (environment != null) { |
| DartType? replacement = environment.lookup(variable, covariantContext); |
| if (replacement != null) { |
| return replacement; |
| } |
| environment = environment.outer; |
| } |
| return null; |
| } |
| } |
| |
| class _OccurrenceVisitor extends FindTypeVisitor { |
| final Set<TypeParameter> variables; |
| |
| /// Helper function invoked on unknown implementers of [DartType]. |
| /// |
| /// Its arguments are the unhandled type and the function that can be invoked |
| /// from within the handler on parts of the unknown type to recursively call |
| /// the visitor. If not set, an exception is thrown then an unhandled |
| /// implementer of [DartType] is encountered. |
| final DartTypeVisitorAuxiliaryFunction<bool>? unhandledTypeHandler; |
| |
| _OccurrenceVisitor(this.variables, {this.unhandledTypeHandler}); |
| |
| bool visit(DartType type) => type.accept(this); |
| |
| bool visitNamedType(NamedType node) { |
| return visit(node.type); |
| } |
| |
| @override |
| bool visitAuxiliaryType(AuxiliaryType node) { |
| if (unhandledTypeHandler == null) { |
| throw new UnsupportedError("Unsupported type '${node.runtimeType}'."); |
| } else { |
| return unhandledTypeHandler!(node, visit); |
| } |
| } |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| return node.typeParameters.any(handleStructuralParameter) || |
| node.positionalParameters.any(visit) || |
| node.namedParameters.any(visitNamedType) || |
| visit(node.returnType); |
| } |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) { |
| return variables.contains(node.parameter); |
| } |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return false; |
| } |
| |
| bool handleStructuralParameter(StructuralParameter node) { |
| if (node.bound.accept(this)) return true; |
| return node.defaultType.accept(this); |
| } |
| } |
| |
| class _StructuralParameterOccurrenceVisitor implements FindTypeVisitor { |
| final Set<StructuralParameter> variables; |
| |
| /// Helper function invoked on unknown implementers of [DartType]. |
| /// |
| /// Its arguments are the unhandled type and the function that can be invoked |
| /// from within the handler on parts of the unknown type to recursively call |
| /// the visitor. If not set, an exception is thrown then an unhandled |
| /// implementer of [DartType] is encountered. |
| final DartTypeVisitorAuxiliaryFunction<bool>? unhandledTypeHandler; |
| |
| _StructuralParameterOccurrenceVisitor(this.variables, |
| {this.unhandledTypeHandler}); |
| |
| bool visit(DartType node) => node.accept(this); |
| |
| bool visitNamedType(NamedType node) { |
| return visit(node.type); |
| } |
| |
| @override |
| bool visitAuxiliaryType(AuxiliaryType node) { |
| if (unhandledTypeHandler == null) { |
| throw new UnsupportedError("Unsupported type '${node.runtimeType}'."); |
| } else { |
| return unhandledTypeHandler!(node, visit); |
| } |
| } |
| |
| @override |
| bool visitNeverType(NeverType node) => false; |
| @override |
| bool visitNullType(NullType node) => false; |
| @override |
| bool visitInvalidType(InvalidType node) => false; |
| @override |
| bool visitDynamicType(DynamicType node) => false; |
| @override |
| bool visitVoidType(VoidType node) => false; |
| |
| @override |
| bool visitInterfaceType(InterfaceType node) { |
| return node.typeArguments.any(visit); |
| } |
| |
| @override |
| bool visitExtensionType(ExtensionType node) { |
| return node.typeArguments.any(visit); |
| } |
| |
| @override |
| bool visitFutureOrType(FutureOrType node) { |
| return visit(node.typeArgument); |
| } |
| |
| @override |
| bool visitTypedefType(TypedefType node) { |
| return node.typeArguments.any(visit); |
| } |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| return node.typeParameters.any(handleStructuralParameter) || |
| node.positionalParameters.any(visit) || |
| node.namedParameters.any(visitNamedType) || |
| visit(node.returnType); |
| } |
| |
| @override |
| bool visitRecordType(RecordType node) { |
| return node.positional.any(visit) || node.named.any(visitNamedType); |
| } |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) { |
| return false; |
| } |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return variables.contains(node.parameter); |
| } |
| |
| @override |
| bool visitIntersectionType(IntersectionType node) { |
| return visit(node.left) || visit(node.right); |
| } |
| |
| bool handleStructuralParameter(StructuralParameter node) { |
| if (node.bound.accept(this)) return true; |
| return node.defaultType.accept(this); |
| } |
| } |
| |
| class _FreeFunctionTypeVariableVisitor extends FindTypeVisitor { |
| final Set<StructuralParameter> variables = new Set<StructuralParameter>(); |
| |
| _FreeFunctionTypeVariableVisitor(); |
| |
| bool visit(DartType node) => node.accept(this); |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| variables.addAll(node.typeParameters); |
| bool result = super.visitFunctionType(node); |
| variables.removeAll(node.typeParameters); |
| return result; |
| } |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return !variables.contains(node.parameter); |
| } |
| } |
| |
| class _FreeTypeVariableVisitor extends FindTypeVisitor { |
| final Set<StructuralParameter> boundVariables; |
| |
| _FreeTypeVariableVisitor({Set<StructuralParameter>? boundVariables}) |
| : this.boundVariables = boundVariables ?? <StructuralParameter>{}; |
| |
| bool visit(DartType type) => type.accept(this); |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| boundVariables.addAll(node.typeParameters); |
| bool result = super.visitFunctionType(node); |
| boundVariables.removeAll(node.typeParameters); |
| return result; |
| } |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) { |
| return true; |
| } |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return !boundVariables.contains(node.parameter); |
| } |
| } |
| |
| Nullability uniteNullabilities(Nullability a, Nullability b) { |
| if (a == Nullability.nullable || b == Nullability.nullable) { |
| return Nullability.nullable; |
| } |
| if (a == Nullability.legacy || b == Nullability.legacy) { |
| return Nullability.legacy; |
| } |
| if (a == Nullability.undetermined || b == Nullability.undetermined) { |
| return Nullability.undetermined; |
| } |
| return Nullability.nonNullable; |
| } |
| |
| Nullability intersectNullabilities(Nullability a, Nullability b) { |
| if (a == Nullability.nonNullable || b == Nullability.nonNullable) { |
| return Nullability.nonNullable; |
| } |
| if (a == Nullability.undetermined || b == Nullability.undetermined) { |
| return Nullability.undetermined; |
| } |
| if (a == Nullability.legacy || b == Nullability.legacy) { |
| return Nullability.legacy; |
| } |
| return Nullability.nullable; |
| } |
| |
| /// Tells if a [DartType] is primitive or not. |
| /// |
| /// This is useful in recursive algorithms over types where the primitive types |
| /// are the base cases of the recursion. According to the visitor a primitive |
| /// type is any [DartType] that doesn't include other [DartType]s as its parts. |
| /// The nullability attributes don't affect the primitiveness of a type. |
| bool isPrimitiveDartType(DartType type) { |
| return type.accept(const _PrimitiveTypeVerifier()); |
| } |
| |
| /// Visitors that implements the algorithm of [isPrimitiveDartType]. |
| /// |
| /// The visitor is shallow, that is, it doesn't recurse over the given type due |
| /// to its purpose. The reason for having a visitor is to make the need for an |
| /// update visible when a new implementer of [DartType] is introduced in Kernel. |
| class _PrimitiveTypeVerifier implements DartTypeVisitor<bool> { |
| const _PrimitiveTypeVerifier(); |
| |
| @override |
| bool visitAuxiliaryType(AuxiliaryType node) { |
| throw new UnsupportedError( |
| "Unsupported auxiliary type ${node} (${node.runtimeType})."); |
| } |
| |
| @override |
| bool visitDynamicType(DynamicType node) => true; |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| // Function types are never primitive because they at least include the |
| // return types as their parts. |
| return false; |
| } |
| |
| @override |
| bool visitRecordType(RecordType node) { |
| return node.positional.isNotEmpty || node.named.isNotEmpty; |
| } |
| |
| @override |
| bool visitFutureOrType(FutureOrType node) => false; |
| |
| @override |
| bool visitInterfaceType(InterfaceType node) { |
| return node.typeArguments.isEmpty; |
| } |
| |
| @override |
| bool visitExtensionType(ExtensionType node) { |
| return node.typeArguments.isEmpty; |
| } |
| |
| @override |
| bool visitInvalidType(InvalidType node) { |
| throw new UnsupportedError( |
| "Unsupported operation: _PrimitiveTypeVerifier(InvalidType)."); |
| } |
| |
| @override |
| bool visitNeverType(NeverType node) => true; |
| |
| @override |
| bool visitNullType(NullType node) => true; |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) => true; |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return true; |
| } |
| |
| @override |
| bool visitIntersectionType(IntersectionType node) => false; |
| |
| @override |
| bool visitTypedefType(TypedefType node) { |
| return node.typeArguments.isEmpty; |
| } |
| |
| @override |
| bool visitVoidType(VoidType node) => true; |
| } |
| |
| /// Removes the application of ? or * from the type. |
| /// |
| /// Some types are nullable even without the application of the nullable type |
| /// constructor at the top level, for example, Null or FutureOr<int?>. |
| // TODO(cstefantsova): Remove [coreTypes] parameter when NullType is landed. |
| DartType unwrapNullabilityConstructor(DartType type) { |
| return type.accept(const _NullabilityConstructorUnwrapper()); |
| } |
| |
| /// Implementation of [unwrapNullabilityConstructor] as a visitor. |
| /// |
| /// Implementing the function as a visitor makes the necessity of supporting a |
| /// new implementation of [DartType] visible at compile time. |
| // TODO(cstefantsova): Remove CoreTypes as the second argument when NullType is |
| // landed. |
| class _NullabilityConstructorUnwrapper implements DartTypeVisitor<DartType> { |
| const _NullabilityConstructorUnwrapper(); |
| |
| @override |
| DartType visitAuxiliaryType(AuxiliaryType node) { |
| throw new UnsupportedError( |
| "Unsupported auxiliary type ${node} (${node.runtimeType})."); |
| } |
| |
| @override |
| DartType visitDynamicType(DynamicType node) => node; |
| |
| @override |
| DartType visitFunctionType(FunctionType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitRecordType(RecordType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitFutureOrType(FutureOrType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitInterfaceType(InterfaceType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitExtensionType(ExtensionType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitInvalidType(InvalidType node) => node; |
| |
| @override |
| DartType visitNeverType(NeverType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitNullType(NullType node) => node; |
| |
| @override |
| DartType visitTypeParameterType(TypeParameterType node) { |
| return node.withDeclaredNullability( |
| TypeParameterType.computeNullabilityFromBound(node.parameter)); |
| } |
| |
| @override |
| DartType visitStructuralParameterType(StructuralParameterType node) { |
| return node.withDeclaredNullability( |
| StructuralParameterType.computeNullabilityFromBound(node.parameter)); |
| } |
| |
| @override |
| DartType visitIntersectionType(IntersectionType node) { |
| // Intersection types don't have their own nullabilities. |
| return node; |
| } |
| |
| @override |
| DartType visitTypedefType(TypedefType node) { |
| return node.withDeclaredNullability(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitVoidType(VoidType node) => node; |
| } |
| |
| abstract class NullabilityAwareTypeVariableEliminatorBase |
| extends ReplacementVisitor { |
| final DartType bottomType; |
| final DartType topType; |
| final DartType topFunctionType; |
| late bool _isLeastClosure; |
| |
| NullabilityAwareTypeVariableEliminatorBase( |
| {required this.bottomType, |
| required this.topType, |
| required this.topFunctionType}); |
| |
| bool containsTypeVariablesToEliminate(DartType type); |
| |
| bool isStructuralVariableToEliminate(StructuralParameter typeParameter); |
| |
| bool isNominalVariableToEliminate(TypeParameter typeParameter); |
| |
| /// Returns a subtype of [type] for all variables to be eliminated. |
| DartType eliminateToLeast(DartType type) { |
| _isLeastClosure = true; |
| return type.accept1(this, Variance.covariant) ?? type; |
| } |
| |
| /// Returns a supertype of [type] for all variables to be eliminated. |
| DartType eliminateToGreatest(DartType type) { |
| _isLeastClosure = false; |
| return type.accept1(this, Variance.covariant) ?? type; |
| } |
| |
| DartType getTypeParameterReplacement(Variance variance) { |
| bool isCovariant = variance == Variance.covariant; |
| return _isLeastClosure && isCovariant || (!_isLeastClosure && !isCovariant) |
| ? bottomType |
| : topType; |
| } |
| |
| DartType getFunctionReplacement(Variance variance) { |
| bool isCovariant = variance == Variance.covariant; |
| return _isLeastClosure && isCovariant || (!_isLeastClosure && !isCovariant) |
| ? bottomType |
| : topFunctionType; |
| } |
| |
| @override |
| DartType? visitFunctionType(FunctionType node, Variance variance) { |
| // - if `S` is |
| // `T Function<X0 extends B0, ...., Xk extends Bk>(T0 x0, ...., Tn xn, |
| // [Tn+1 xn+1, ..., Tm xm])` |
| // or `T Function<X0 extends B0, ...., Xk extends Bk>(T0 x0, ...., Tn xn, |
| // {Tn+1 xn+1, ..., Tm xm})` |
| // and `L` contains any free type variables from any of the `Bi`: |
| // - The least closure of `S` with respect to `L` is `Never` |
| // - The greatest closure of `S` with respect to `L` is `Function` |
| if (node.typeParameters.isNotEmpty) { |
| for (StructuralParameter typeParameter in node.typeParameters) { |
| if (containsTypeVariablesToEliminate(typeParameter.bound)) { |
| return getFunctionReplacement(variance); |
| } |
| } |
| } |
| return super.visitFunctionType(node, variance); |
| } |
| |
| @override |
| DartType? visitTypeParameterType(TypeParameterType node, Variance variance) { |
| if (isNominalVariableToEliminate(node.parameter)) { |
| return getTypeParameterReplacement(variance); |
| } |
| return super.visitTypeParameterType(node, variance); |
| } |
| |
| @override |
| DartType? visitIntersectionType(IntersectionType node, Variance variance) { |
| return visitTypeParameterType(node.left, variance); |
| } |
| |
| @override |
| DartType? visitStructuralParameterType( |
| StructuralParameterType node, Variance variance) { |
| if (isStructuralVariableToEliminate(node.parameter)) { |
| return getTypeParameterReplacement(variance); |
| } |
| return super.visitStructuralParameterType(node, variance); |
| } |
| } |
| |
| /// Eliminates specified free type parameters in a type. |
| /// |
| /// Use this class when only a specific subset of unbound variables in a type |
| /// should be substituted with one of [bottomType], [topType], or |
| /// [topFunctionType]. For example, running a |
| /// `NullabilityAwareTypeVariableEliminatorBase({T}, Never, Object?, |
| /// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return |
| /// `Never Function<S>(S s, R r)`. |
| /// |
| /// The algorithm for elimination of type variables is described in |
| /// https://github.com/dart-lang/language/pull/957 |
| class NullabilityAwareTypeVariableEliminator |
| extends NullabilityAwareTypeVariableEliminatorBase { |
| final Set<StructuralParameter> structuralEliminationTargets; |
| final Set<TypeParameter> nominalEliminationTargets; |
| final DartTypeVisitorAuxiliaryFunction<bool>? unhandledTypeHandler; |
| |
| NullabilityAwareTypeVariableEliminator( |
| {required this.structuralEliminationTargets, |
| required this.nominalEliminationTargets, |
| required DartType bottomType, |
| required DartType topType, |
| required DartType topFunctionType, |
| this.unhandledTypeHandler}) |
| : super( |
| bottomType: bottomType, |
| topType: topType, |
| topFunctionType: topFunctionType); |
| |
| @override |
| bool containsTypeVariablesToEliminate(DartType type) { |
| return containsStructuralTypeVariable(type, structuralEliminationTargets, |
| unhandledTypeHandler: unhandledTypeHandler) || |
| containsTypeVariable(type, nominalEliminationTargets, |
| unhandledTypeHandler: unhandledTypeHandler); |
| } |
| |
| @override |
| bool isStructuralVariableToEliminate(StructuralParameter typeParameter) { |
| return structuralEliminationTargets.contains(typeParameter); |
| } |
| |
| @override |
| bool isNominalVariableToEliminate(TypeParameter typeParameter) { |
| return nominalEliminationTargets.contains(typeParameter); |
| } |
| } |
| |
| /// Eliminates all free type parameters in a type. |
| /// |
| /// Use this class when all unbound variables in a type should be substituted |
| /// with one of [bottomType], [topType], or [topFunctionType]. For example, |
| /// running a `NullabilityAwareFreeTypeVariableEliminator(Never, Object?, |
| /// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return |
| /// `Never Function<S>(S s, Object? r)`. |
| /// |
| /// The algorithm for elimination of type variables is described in |
| /// https://github.com/dart-lang/language/pull/957 |
| class NullabilityAwareFreeTypeVariableEliminator |
| extends NullabilityAwareTypeVariableEliminatorBase { |
| Set<StructuralParameter> _boundVariables = <StructuralParameter>{}; |
| |
| NullabilityAwareFreeTypeVariableEliminator( |
| {required DartType bottomType, |
| required DartType topType, |
| required DartType topFunctionType}) |
| : super( |
| bottomType: bottomType, |
| topType: topType, |
| topFunctionType: topFunctionType); |
| |
| @override |
| DartType? visitFunctionType(FunctionType node, Variance variance) { |
| if (node.typeParameters.isNotEmpty) { |
| _boundVariables.addAll(node.typeParameters); |
| DartType? result = super.visitFunctionType(node, variance); |
| _boundVariables.removeAll(node.typeParameters); |
| return result; |
| } else { |
| return super.visitFunctionType(node, variance); |
| } |
| } |
| |
| @override |
| bool containsTypeVariablesToEliminate(DartType type) { |
| return containsFreeTypeVariables(type, boundVariables: _boundVariables); |
| } |
| |
| @override |
| bool isStructuralVariableToEliminate(StructuralParameter typeParameter) { |
| return !_boundVariables.contains(typeParameter); |
| } |
| |
| @override |
| bool isNominalVariableToEliminate(TypeParameter typeParameter) { |
| // All nominal type variables are considered free in a type term. |
| return true; |
| } |
| } |
| |
| /// Computes [type] as if declared without nullability markers. |
| /// |
| /// For example, int? and int* are considered applications of the nullable and |
| /// the legacy type constructors to type int correspondingly. |
| /// [computeTypeWithoutNullabilityMarker] peels off these type constructors, |
| /// returning the non-nullable version of type int. In case of |
| /// [TypeParameterType]s, the result may be either [Nullability.nonNullable] or |
| /// [Nullability.undetermined], depending on the bound. |
| DartType computeTypeWithoutNullabilityMarker(DartType type) { |
| if (type is TypeParameterType) { |
| // The default nullability for library is used when there are no |
| // nullability markers on the type. |
| return new TypeParameterType(type.parameter, |
| _defaultNullabilityForTypeParameterType(type.parameter)); |
| } else if (type is StructuralParameterType) { |
| // The default nullability for library is used when there are no |
| // nullability markers on the type. |
| return new StructuralParameterType(type.parameter, |
| _defaultNullabilityForStructuralParameterType(type.parameter)); |
| } else if (type is IntersectionType) { |
| // Intersection types can't be arguments to the nullable and the legacy |
| // type constructors, so nothing can be peeled off. |
| return type; |
| } else if (type is NullType) { |
| return type; |
| } else { |
| // For most types, peeling off the nullability constructors means that |
| // they become non-nullable. |
| return type.withDeclaredNullability(Nullability.nonNullable); |
| } |
| } |
| |
| /// Returns true if [type] is declared without nullability markers. |
| /// |
| /// An example of the nullable type constructor application is T? where T is a |
| /// type parameter. Some examples of types declared without nullability markers |
| /// are T% and S, where T and S are type parameters such that T extends Object? |
| /// and S extends Object. |
| bool isTypeParameterTypeWithoutNullabilityMarker(TypeParameterType type) { |
| // The default nullability for library is used when there are no nullability |
| // markers on the type. |
| return type.declaredNullability == |
| _defaultNullabilityForTypeParameterType(type.parameter); |
| } |
| |
| /// Returns true if [type] is declared without nullability markers. |
| /// |
| /// An example of the nullable type constructor application is T? where T is a |
| /// type parameter. Some examples of types declared without nullability markers |
| /// are T% and S, where T and S are type parameters such that T extends Object? |
| /// and S extends Object. |
| bool isStructuralParameterTypeWithoutNullabilityMarker( |
| StructuralParameterType type) { |
| // The default nullability for library is used when there are no nullability |
| // markers on the type. |
| return type.declaredNullability == |
| _defaultNullabilityForStructuralParameterType(type.parameter); |
| } |
| |
| bool isTypeWithoutNullabilityMarker(DartType type) { |
| return !type.accept(const _NullabilityMarkerDetector()); |
| } |
| |
| class _NullabilityMarkerDetector implements DartTypeVisitor<bool> { |
| const _NullabilityMarkerDetector(); |
| |
| @override |
| bool visitAuxiliaryType(AuxiliaryType node) { |
| throw new UnsupportedError( |
| "Unsupported auxiliary type ${node} (${node.runtimeType})."); |
| } |
| |
| @override |
| bool visitDynamicType(DynamicType node) => false; |
| |
| @override |
| bool visitFunctionType(FunctionType node) { |
| assert(node.declaredNullability != Nullability.undetermined); |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitRecordType(RecordType node) { |
| assert(node.declaredNullability != Nullability.undetermined); |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitFutureOrType(FutureOrType node) { |
| if (node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy) { |
| return true; |
| } |
| return false; |
| } |
| |
| @override |
| bool visitInterfaceType(InterfaceType node) { |
| assert(node.declaredNullability != Nullability.undetermined); |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitExtensionType(ExtensionType node) { |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitInvalidType(InvalidType node) => false; |
| |
| @override |
| bool visitNeverType(NeverType node) { |
| assert(node.declaredNullability != Nullability.undetermined); |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitNullType(NullType node) => false; |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) { |
| return !isTypeParameterTypeWithoutNullabilityMarker(node); |
| } |
| |
| @override |
| bool visitStructuralParameterType(StructuralParameterType node) { |
| return !isStructuralParameterTypeWithoutNullabilityMarker(node); |
| } |
| |
| @override |
| bool visitIntersectionType(IntersectionType node) => false; |
| |
| @override |
| bool visitTypedefType(TypedefType node) { |
| assert(node.declaredNullability != Nullability.undetermined); |
| return node.declaredNullability == Nullability.nullable || |
| node.declaredNullability == Nullability.legacy; |
| } |
| |
| @override |
| bool visitVoidType(VoidType node) => false; |
| } |
| |
| /// Returns true if [type] is an application of the nullable type constructor. |
| /// |
| /// A type is considered an application of the nullable type constructor if it |
| /// was declared with the ? marker. Some examples of such types are int?, |
| /// String?, Object?, and T? where T is a type parameter. Types dynamic, void, |
| /// and Null are nullable, but aren't considered applications of the nullable |
| /// type constructor. |
| bool isNullableTypeConstructorApplication(DartType type) { |
| if (type is IntersectionType) { |
| // Promoted types are never considered applications of ?. |
| return false; |
| } |
| return type.declaredNullability == Nullability.nullable && |
| type is! DynamicType && |
| type is! VoidType && |
| type is! NullType && |
| type is! InvalidType; |
| } |
| |
| /// Returns true if [type] is an application of the legacy type constructor. |
| /// |
| /// A type is considered an application of the legacy type constructor if it was |
| /// declared within a legacy library and is not one of exempt types, such as |
| /// dynamic or void. |
| bool isLegacyTypeConstructorApplication(DartType type) { |
| if (type is TypeParameterType) { |
| // The legacy nullability is considered an application of the legacy |
| // nullability constructor if it doesn't match the default nullability |
| // of the type-parameter type for the library. |
| return type.declaredNullability == Nullability.legacy && |
| !isTypeParameterTypeWithoutNullabilityMarker(type); |
| } else if (type is StructuralParameterType) { |
| // The legacy nullability is considered an application of the legacy |
| // nullability constructor if it doesn't match the default nullability |
| // of the type-parameter type for the library. |
| return type.declaredNullability == Nullability.legacy && |
| !isStructuralParameterTypeWithoutNullabilityMarker(type); |
| } else if (type is InvalidType) { |
| return false; |
| } else { |
| return type.declaredNullability == Nullability.legacy; |
| } |
| } |
| |
| Nullability _defaultNullabilityForTypeParameterType(TypeParameter parameter) { |
| return TypeParameterType.computeNullabilityFromBound(parameter); |
| } |
| |
| Nullability _defaultNullabilityForStructuralParameterType( |
| StructuralParameter parameter) { |
| return StructuralParameterType.computeNullabilityFromBound(parameter); |
| } |
| |
| /// Recalculates and updates nullabilities of the bounds in [typeParameters]. |
| /// |
| /// The procedure is intended to be used on type parameters that are in the |
| /// scope of another declaration with type parameters. After a substitution |
| /// involving the outer type parameters is performed, some potentially nullable |
| /// bounds of the inner parameters can change to non-nullable. Since type |
| /// parameters can depend on each other, the occurrences of those with changed |
| /// nullabilities need to be updated in the bounds of the entire type parameter |
| /// set. |
| void updateBoundNullabilities(List<TypeParameter> typeParameters) { |
| if (typeParameters.isEmpty) return; |
| List<bool> visited = |
| new List<bool>.filled(typeParameters.length, false, growable: false); |
| for (int parameterIndex = 0; |
| parameterIndex < typeParameters.length; |
| parameterIndex++) { |
| _updateBoundNullabilities(typeParameters, visited, parameterIndex); |
| } |
| } |
| |
| void _updateBoundNullabilities( |
| List<TypeParameter> typeParameters, List<bool> visited, int startIndex) { |
| if (visited[startIndex]) return; |
| visited[startIndex] = true; |
| |
| TypeParameter parameter = typeParameters[startIndex]; |
| DartType bound = parameter.bound; |
| while (bound is FutureOrType) { |
| bound = bound.typeArgument; |
| } |
| if (bound is TypeParameterType) { |
| int nextIndex = typeParameters.indexOf(bound.parameter); |
| if (nextIndex != -1) { |
| _updateBoundNullabilities(typeParameters, visited, nextIndex); |
| Nullability updatedNullability = |
| TypeParameterType.computeNullabilityFromBound( |
| typeParameters[nextIndex]); |
| if (bound.declaredNullability != updatedNullability) { |
| parameter.bound = _updateNestedFutureOrNullability( |
| parameter.bound, updatedNullability); |
| } |
| } |
| } |
| } |
| |
| DartType _updateNestedFutureOrNullability( |
| DartType typeToUpdate, Nullability updatedNullability) { |
| if (typeToUpdate is FutureOrType) { |
| return new FutureOrType( |
| _updateNestedFutureOrNullability( |
| typeToUpdate.typeArgument, updatedNullability), |
| typeToUpdate.declaredNullability); |
| } else { |
| return typeToUpdate.withDeclaredNullability(updatedNullability); |
| } |
| } |
| |
| /// Returns true if [type] is a bottom type |
| /// |
| /// Some examples of bottom types are `Never`, `X` where `X extends Never`, and |
| /// `X & Never`. |
| /// |
| /// For the definition of BOTTOM see the following: |
| /// https://github.com/dart-lang/language/blob/master/resources/type-system/upper-lower-bounds.md#helper-predicates |
| bool isBottom(DartType type) { |
| if (type is InterfaceType) return false; |
| if (type is InvalidType) return false; |
| |
| // BOTTOM(Never) is true. |
| if (type is NeverType && type.nullability == Nullability.nonNullable) { |
| return true; |
| } |
| |
| // BOTTOM(X&T) is true iff BOTTOM(T). |
| if (type is IntersectionType && type.isPotentiallyNonNullable) { |
| return isBottom(type.right); |
| } |
| |
| // BOTTOM(X extends T) is true iff BOTTOM(T). |
| if (type is TypeParameterType && type.isPotentiallyNonNullable) { |
| return isBottom(type.parameter.bound); |
| } |
| |
| return false; |
| } |