| // Copyright (c) 2018, 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. |
| |
| // @dart = 2.9 |
| |
| import '../ast.dart' |
| show |
| BottomType, |
| Class, |
| DartType, |
| DynamicType, |
| FunctionType, |
| FutureOrType, |
| InterfaceType, |
| InvalidType, |
| Library, |
| NamedType, |
| NeverType, |
| NullType, |
| Nullability, |
| TypeParameter, |
| TypeParameterType, |
| Typedef, |
| TypedefType, |
| Variance, |
| VoidType; |
| |
| import '../type_algebra.dart' show Substitution, substitute; |
| |
| import '../type_environment.dart' show SubtypeCheckMode, TypeEnvironment; |
| |
| import '../util/graph.dart' show Graph, computeStrongComponents; |
| |
| import '../visitor.dart' show DartTypeVisitor, DartTypeVisitor1; |
| |
| import 'legacy_erasure.dart'; |
| |
| class TypeVariableGraph extends Graph<int> { |
| List<int> vertices; |
| List<TypeParameter> typeParameters; |
| List<DartType> bounds; |
| |
| // `edges[i]` is the list of indices of type variables that reference the type |
| // variable with the index `i` in their bounds. |
| List<List<int>> edges; |
| |
| TypeVariableGraph(this.typeParameters, this.bounds) { |
| assert(typeParameters.length == bounds.length); |
| |
| vertices = new List<int>.filled(typeParameters.length, null); |
| Map<TypeParameter, int> typeParameterIndices = <TypeParameter, int>{}; |
| edges = new List<List<int>>.filled(typeParameters.length, null); |
| for (int i = 0; i < vertices.length; i++) { |
| vertices[i] = i; |
| typeParameterIndices[typeParameters[i]] = i; |
| edges[i] = <int>[]; |
| } |
| |
| for (int i = 0; i < vertices.length; i++) { |
| OccurrenceCollectorVisitor collector = |
| new OccurrenceCollectorVisitor(typeParameters.toSet()); |
| collector.visit(bounds[i]); |
| for (TypeParameter typeParameter in collector.occurred) { |
| edges[typeParameterIndices[typeParameter]].add(i); |
| } |
| } |
| } |
| |
| Iterable<int> neighborsOf(int index) { |
| return edges[index]; |
| } |
| } |
| |
| class OccurrenceCollectorVisitor extends DartTypeVisitor { |
| final Set<TypeParameter> typeParameters; |
| Set<TypeParameter> occurred = new Set<TypeParameter>(); |
| |
| OccurrenceCollectorVisitor(this.typeParameters); |
| |
| visit(DartType node) => node.accept(this); |
| |
| visitNamedType(NamedType node) { |
| node.type.accept(this); |
| } |
| |
| visitInvalidType(InvalidType node); |
| visitDynamicType(DynamicType node); |
| visitVoidType(VoidType node); |
| |
| visitInterfaceType(InterfaceType node) { |
| for (DartType argument in node.typeArguments) { |
| argument.accept(this); |
| } |
| } |
| |
| visitTypedefType(TypedefType node) { |
| for (DartType argument in node.typeArguments) { |
| argument.accept(this); |
| } |
| } |
| |
| visitFunctionType(FunctionType node) { |
| for (TypeParameter typeParameter in node.typeParameters) { |
| typeParameter.bound.accept(this); |
| typeParameter.defaultType?.accept(this); |
| } |
| for (DartType parameter in node.positionalParameters) { |
| parameter.accept(this); |
| } |
| for (NamedType namedParameter in node.namedParameters) { |
| namedParameter.type.accept(this); |
| } |
| node.returnType.accept(this); |
| } |
| |
| visitTypeParameterType(TypeParameterType node) { |
| if (typeParameters.contains(node.parameter)) { |
| occurred.add(node.parameter); |
| } |
| } |
| } |
| |
| DartType instantiateToBounds( |
| DartType type, Class objectClass, Library contextLibrary) { |
| if (type is InterfaceType) { |
| if (type.typeArguments.isEmpty) return type; |
| for (DartType typeArgument in type.typeArguments) { |
| // If at least one of the arguments is not dynamic, we assume that the |
| // type is not raw and does not need instantiation of its type parameters |
| // to their bounds. |
| if (typeArgument is! DynamicType) { |
| return type; |
| } |
| } |
| return new InterfaceType.byReference( |
| type.className, |
| type.nullability, |
| calculateBounds( |
| type.classNode.typeParameters, objectClass, contextLibrary)); |
| } |
| if (type is TypedefType) { |
| if (type.typeArguments.isEmpty) return type; |
| for (DartType typeArgument in type.typeArguments) { |
| if (typeArgument is! DynamicType) { |
| return type; |
| } |
| } |
| return new TypedefType.byReference( |
| type.typedefReference, |
| type.nullability, |
| calculateBounds( |
| type.typedefNode.typeParameters, objectClass, contextLibrary)); |
| } |
| return type; |
| } |
| |
| /// Calculates bounds to be provided as type arguments in place of missing type |
| /// arguments on raw types with the given type parameters. |
| /// |
| /// See the [description] |
| /// (https://github.com/dart-lang/sdk/blob/master/docs/language/informal/instantiate-to-bound.md) |
| /// of the algorithm for details. |
| List<DartType> calculateBounds(List<TypeParameter> typeParameters, |
| Class objectClass, Library contextLibrary) { |
| return calculateBoundsInternal(typeParameters, objectClass, |
| isNonNullableByDefault: contextLibrary.isNonNullableByDefault); |
| } |
| |
| List<DartType> calculateBoundsInternal( |
| List<TypeParameter> typeParameters, Class objectClass, |
| {bool isNonNullableByDefault}) { |
| assert(isNonNullableByDefault != null); |
| |
| List<DartType> bounds = |
| new List<DartType>.filled(typeParameters.length, null); |
| for (int i = 0; i < typeParameters.length; i++) { |
| DartType bound = typeParameters[i].bound; |
| if (bound == null) { |
| bound = const DynamicType(); |
| } else if (bound is InterfaceType && bound.classNode == objectClass) { |
| DartType defaultType = typeParameters[i].defaultType; |
| if (!(defaultType is InterfaceType && |
| defaultType.classNode == objectClass)) { |
| bound = const DynamicType(); |
| } |
| } |
| bounds[i] = bound; |
| } |
| |
| TypeVariableGraph graph = new TypeVariableGraph(typeParameters, bounds); |
| List<List<int>> stronglyConnected = computeStrongComponents(graph); |
| final DartType topType = const DynamicType(); |
| final DartType bottomType = isNonNullableByDefault |
| ? const NeverType(Nullability.nonNullable) |
| : const BottomType(); |
| for (List<int> component in stronglyConnected) { |
| Map<TypeParameter, DartType> upperBounds = <TypeParameter, DartType>{}; |
| Map<TypeParameter, DartType> lowerBounds = <TypeParameter, DartType>{}; |
| for (int typeParameterIndex in component) { |
| upperBounds[typeParameters[typeParameterIndex]] = topType; |
| lowerBounds[typeParameters[typeParameterIndex]] = bottomType; |
| } |
| Substitution substitution = |
| Substitution.fromUpperAndLowerBounds(upperBounds, lowerBounds); |
| for (int typeParameterIndex in component) { |
| bounds[typeParameterIndex] = |
| substitution.substituteType(bounds[typeParameterIndex]); |
| } |
| } |
| |
| for (int i = 0; i < typeParameters.length; i++) { |
| Map<TypeParameter, DartType> upperBounds = <TypeParameter, DartType>{}; |
| Map<TypeParameter, DartType> lowerBounds = <TypeParameter, DartType>{}; |
| upperBounds[typeParameters[i]] = bounds[i]; |
| lowerBounds[typeParameters[i]] = bottomType; |
| Substitution substitution = |
| Substitution.fromUpperAndLowerBounds(upperBounds, lowerBounds); |
| for (int j = 0; j < typeParameters.length; j++) { |
| bounds[j] = substitution.substituteType(bounds[j]); |
| } |
| } |
| |
| return bounds; |
| } |
| |
| class TypeArgumentIssue { |
| /// The index for type argument within the passed type arguments. |
| final int index; |
| |
| /// The type argument that violated the bound. |
| final DartType argument; |
| |
| /// The type parameter with the bound that was violated. |
| final TypeParameter typeParameter; |
| |
| /// The enclosing type of the issue, that is, the one with [typeParameter]. |
| final DartType enclosingType; |
| |
| /// The type computed from [enclosingType] for the super-boundness check. |
| /// |
| /// This field can be null. [invertedType] is supposed to enhance error |
| /// messages, providing the auxiliary type for super-boundness checks for the |
| /// user. It is set to null if it's not helpful, for example, if |
| /// [enclosingType] is well-bounded or is strictly required to be |
| /// regular-bounded, so the super-boundness check is skipped. It is set to |
| /// null also if the inversion didn't change the type at all, and it's not |
| /// helpful to show the same type to the user. |
| DartType invertedType; |
| |
| TypeArgumentIssue( |
| this.index, this.argument, this.typeParameter, this.enclosingType, |
| {this.invertedType}); |
| |
| int get hashCode { |
| int hash = 0x3fffffff & index; |
| hash = 0x3fffffff & (hash * 31 + (hash ^ argument.hashCode)); |
| hash = 0x3fffffff & (hash * 31 + (hash ^ typeParameter.hashCode)); |
| hash = 0x3fffffff & (hash * 31 + (hash ^ enclosingType.hashCode)); |
| return hash; |
| } |
| |
| bool operator ==(Object other) { |
| assert(other is TypeArgumentIssue); |
| return other is TypeArgumentIssue && |
| index == other.index && |
| argument == other.argument && |
| typeParameter == other.typeParameter && |
| enclosingType == other.enclosingType; |
| } |
| |
| String toString() { |
| return "TypeArgumentIssue(index=${index}, argument=${argument}, " |
| "typeParameter=${typeParameter}, enclosingType=${enclosingType}"; |
| } |
| } |
| |
| // Finds type arguments that don't follow the rules of well-boundness. |
| // |
| // [bottomType] should be either Null or Never, depending on what should be |
| // taken for the bottom type at the call site. The bottom type is used in the |
| // checks for super-boundness for construction of the auxiliary type. For |
| // details see Dart Language Specification, Section 14.3.2 The Instantiation to |
| // Bound Algorithm. |
| // TODO(dmitryas): Remove [typedefInstantiations] when type arguments passed to |
| // typedefs are preserved in the Kernel output. |
| List<TypeArgumentIssue> findTypeArgumentIssues(Library library, DartType type, |
| TypeEnvironment typeEnvironment, SubtypeCheckMode subtypeCheckMode, |
| {bool allowSuperBounded = false}) { |
| List<TypeParameter> variables = const <TypeParameter>[]; |
| List<DartType> arguments = const <DartType>[]; |
| List<TypeArgumentIssue> typedefRhsResult = const <TypeArgumentIssue>[]; |
| |
| if (type is FunctionType && type.typedefType != null) { |
| // [type] is a function type that is an application of a parametrized |
| // typedef. We need to check both the l.h.s. and the r.h.s. of the |
| // definition in that case. For details, see [link] |
| // (https://github.com/dart-lang/sdk/blob/master/docs/language/informal/super-bounded-types.md). |
| FunctionType functionType = type; |
| FunctionType cloned = new FunctionType(functionType.positionalParameters, |
| functionType.returnType, functionType.nullability, |
| namedParameters: functionType.namedParameters, |
| typeParameters: functionType.typeParameters, |
| requiredParameterCount: functionType.requiredParameterCount, |
| typedefType: null); |
| typedefRhsResult = findTypeArgumentIssues( |
| library, cloned, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true); |
| type = functionType.typedefType; |
| } |
| |
| if (type is InterfaceType) { |
| variables = type.classNode.typeParameters; |
| arguments = type.typeArguments; |
| } else if (type is TypedefType) { |
| variables = type.typedefNode.typeParameters; |
| arguments = type.typeArguments; |
| } else if (type is FunctionType) { |
| List<TypeArgumentIssue> result = <TypeArgumentIssue>[]; |
| |
| for (TypeParameter parameter in type.typeParameters) { |
| result.addAll(findTypeArgumentIssues( |
| library, parameter.bound, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| } |
| |
| for (DartType formal in type.positionalParameters) { |
| result.addAll(findTypeArgumentIssues( |
| library, formal, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| } |
| |
| for (NamedType named in type.namedParameters) { |
| result.addAll(findTypeArgumentIssues( |
| library, named.type, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| } |
| |
| result.addAll(findTypeArgumentIssues( |
| library, type.returnType, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| |
| return result; |
| } else if (type is FutureOrType) { |
| variables = typeEnvironment.coreTypes.futureClass.typeParameters; |
| arguments = <DartType>[type.typeArgument]; |
| } else { |
| return const <TypeArgumentIssue>[]; |
| } |
| |
| if (variables.isEmpty) return const <TypeArgumentIssue>[]; |
| |
| List<TypeArgumentIssue> result = <TypeArgumentIssue>[]; |
| List<TypeArgumentIssue> argumentsResult = <TypeArgumentIssue>[]; |
| |
| Map<TypeParameter, DartType> substitutionMap = |
| new Map<TypeParameter, DartType>.fromIterables(variables, arguments); |
| for (int i = 0; i < arguments.length; ++i) { |
| DartType argument = arguments[i]; |
| if (isGenericFunctionTypeOrAlias(argument)) { |
| // Generic function types aren't allowed as type arguments either. |
| result.add(new TypeArgumentIssue(i, argument, variables[i], type)); |
| } else if (variables[i].bound is! InvalidType) { |
| DartType bound = substitute(variables[i].bound, substitutionMap); |
| if (!library.isNonNullableByDefault) { |
| bound = legacyErasure(bound); |
| } |
| if (!typeEnvironment.isSubtypeOf(argument, bound, subtypeCheckMode)) { |
| result.add(new TypeArgumentIssue(i, argument, variables[i], type)); |
| } |
| } else { |
| // The bound is InvalidType so it's not checked, because an error was |
| // reported already at the time of the creation of InvalidType. |
| } |
| |
| argumentsResult.addAll(findTypeArgumentIssues( |
| library, argument, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| } |
| result.addAll(argumentsResult); |
| result.addAll(typedefRhsResult); |
| |
| // [type] is regular-bounded. |
| if (result.isEmpty) return const <TypeArgumentIssue>[]; |
| if (!allowSuperBounded) return result; |
| |
| bool isCorrectSuperBounded = true; |
| DartType invertedType = |
| convertSuperBoundedToRegularBounded(library, typeEnvironment, type); |
| |
| // The auxiliary type is the same as [type]. At this point we know that |
| // [type] is not regular-bounded, which means that the inverted type is also |
| // not regular-bounded. These two judgments together allow us to conclude |
| // that [type] is not well-bounded. |
| if (invertedType == null) return result; |
| |
| if (invertedType is InterfaceType) { |
| variables = invertedType.classNode.typeParameters; |
| arguments = invertedType.typeArguments; |
| } else if (invertedType is TypedefType) { |
| variables = invertedType.typedefNode.typeParameters; |
| arguments = invertedType.typeArguments; |
| } else if (invertedType is FutureOrType) { |
| variables = typeEnvironment.coreTypes.futureClass.typeParameters; |
| arguments = <DartType>[invertedType.typeArgument]; |
| } |
| substitutionMap = |
| new Map<TypeParameter, DartType>.fromIterables(variables, arguments); |
| for (int i = 0; i < arguments.length; ++i) { |
| DartType argument = arguments[i]; |
| if (isGenericFunctionTypeOrAlias(argument)) { |
| // Generic function types aren't allowed as type arguments either. |
| isCorrectSuperBounded = false; |
| } else if (!typeEnvironment.isSubtypeOf(argument, |
| substitute(variables[i].bound, substitutionMap), subtypeCheckMode)) { |
| isCorrectSuperBounded = false; |
| } |
| } |
| if (argumentsResult.isNotEmpty) { |
| isCorrectSuperBounded = false; |
| } |
| if (typedefRhsResult.isNotEmpty) { |
| isCorrectSuperBounded = false; |
| } |
| |
| // The inverted type is regular-bounded, which means that [type] is |
| // well-bounded. |
| if (isCorrectSuperBounded) return const <TypeArgumentIssue>[]; |
| |
| // The inverted type isn't regular-bounded, but it's different from [type]. |
| // In this case we'll provide the programmer with the inverted type as a hint, |
| // in case they were going for a super-bounded type and will benefit from that |
| // information correcting the program. |
| for (TypeArgumentIssue issue in result) { |
| issue.invertedType = invertedType; |
| } |
| return result; |
| } |
| |
| // Finds type arguments that don't follow the rules of well-boundness. |
| // |
| // [bottomType] should be either Null or Never, depending on what should be |
| // taken for the bottom type at the call site. The bottom type is used in the |
| // checks for super-boundness for construction of the auxiliary type. For |
| // details see Dart Language Specification, Section 14.3.2 The Instantiation to |
| // Bound Algorithm. |
| // TODO(dmitryas): Remove [typedefInstantiations] when type arguments passed to |
| // typedefs are preserved in the Kernel output. |
| List<TypeArgumentIssue> findTypeArgumentIssuesForInvocation( |
| Library library, |
| List<TypeParameter> parameters, |
| List<DartType> arguments, |
| TypeEnvironment typeEnvironment, |
| SubtypeCheckMode subtypeCheckMode, |
| DartType bottomType, |
| {Map<FunctionType, List<DartType>> typedefInstantiations}) { |
| assert(arguments.length == parameters.length); |
| assert(bottomType == const NeverType(Nullability.nonNullable) || |
| bottomType is NullType); |
| List<TypeArgumentIssue> result = <TypeArgumentIssue>[]; |
| Map<TypeParameter, DartType> substitutionMap = <TypeParameter, DartType>{}; |
| for (int i = 0; i < arguments.length; ++i) { |
| substitutionMap[parameters[i]] = arguments[i]; |
| } |
| for (int i = 0; i < arguments.length; ++i) { |
| DartType argument = arguments[i]; |
| if (argument is TypeParameterType && argument.promotedBound != null) { |
| result.add(new TypeArgumentIssue(i, argument, parameters[i], null)); |
| } else if (isGenericFunctionTypeOrAlias(argument)) { |
| // Generic function types aren't allowed as type arguments either. |
| result.add(new TypeArgumentIssue(i, argument, parameters[i], null)); |
| } else if (parameters[i].bound is! InvalidType) { |
| DartType bound = substitute(parameters[i].bound, substitutionMap); |
| if (!library.isNonNullableByDefault) { |
| bound = legacyErasure(bound); |
| } |
| if (!typeEnvironment.isSubtypeOf(argument, bound, subtypeCheckMode)) { |
| result.add(new TypeArgumentIssue(i, argument, parameters[i], null)); |
| } |
| } |
| |
| result.addAll(findTypeArgumentIssues( |
| library, argument, typeEnvironment, subtypeCheckMode, |
| allowSuperBounded: true)); |
| } |
| return result; |
| } |
| |
| String getGenericTypeName(DartType type) { |
| if (type is InterfaceType) { |
| return type.classNode.name; |
| } else if (type is TypedefType) { |
| return type.typedefNode.name; |
| } |
| return type.toString(); |
| } |
| |
| /// Replaces all covariant occurrences of `dynamic`, `Object`, and `void` with |
| /// [BottomType] and all contravariant occurrences of `Null` and [BottomType] |
| /// with `Object`. Returns null if the converted type is the same as [type]. |
| DartType convertSuperBoundedToRegularBounded( |
| Library clientLibrary, TypeEnvironment typeEnvironment, DartType type, |
| {int variance = Variance.covariant}) { |
| bool flipTop; |
| bool flipBottom; |
| DartType topType; |
| DartType bottomType; |
| bool isTop; |
| bool isBottom; |
| if (clientLibrary.isNonNullableByDefault) { |
| flipTop = variance != Variance.contravariant; |
| flipBottom = variance == Variance.contravariant; |
| topType = typeEnvironment.coreTypes.objectNullableRawType; |
| bottomType = const NeverType(Nullability.nonNullable); |
| isTop = typeEnvironment.coreTypes.isTop(type); |
| isBottom = typeEnvironment.coreTypes.isBottom(type); |
| } else { |
| flipTop = variance == Variance.covariant; |
| flipBottom = variance != Variance.covariant; |
| topType = const DynamicType(); |
| bottomType = const NullType(); |
| isTop = type is DynamicType || |
| type is VoidType || |
| type is InterfaceType && |
| type.classNode == typeEnvironment.coreTypes.objectClass; |
| isBottom = type is NullType || type is BottomType; |
| } |
| |
| if (isTop && flipTop) { |
| return bottomType; |
| } else if (isBottom && flipBottom) { |
| return topType; |
| } else if (type is InterfaceType && |
| type.classNode.typeParameters.isNotEmpty) { |
| assert(type.classNode.typeParameters.length == type.typeArguments.length); |
| |
| List<DartType> convertedTypeArguments = null; |
| for (int i = 0; i < type.typeArguments.length; ++i) { |
| DartType convertedTypeArgument = convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.typeArguments[i], |
| variance: variance); |
| if (convertedTypeArgument != null) { |
| convertedTypeArguments ??= new List<DartType>.of(type.typeArguments); |
| convertedTypeArguments[i] = convertedTypeArgument; |
| } |
| } |
| if (convertedTypeArguments == null) return null; |
| return new InterfaceType( |
| type.classNode, type.nullability, convertedTypeArguments); |
| } else if (type is TypedefType && |
| type.typedefNode.typeParameters.isNotEmpty) { |
| assert(type.typedefNode.typeParameters.length == type.typeArguments.length); |
| |
| List<DartType> convertedTypeArguments = null; |
| for (int i = 0; i < type.typeArguments.length; i++) { |
| // The implementation of instantiate-to-bound in legacy mode ignored the |
| // variance of type parameters of the typedef. This behavior is preserved |
| // here in passing the 'variance' parameter unchanged in for legacy |
| // libraries. |
| DartType convertedTypeArgument = convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.typeArguments[i], |
| variance: clientLibrary.isNonNullableByDefault |
| ? Variance.combine( |
| variance, type.typedefNode.typeParameters[i].variance) |
| : variance); |
| if (convertedTypeArgument != null) { |
| convertedTypeArguments ??= new List<DartType>.of(type.typeArguments); |
| convertedTypeArguments[i] = convertedTypeArgument; |
| } |
| } |
| if (convertedTypeArguments == null) return null; |
| return new TypedefType( |
| type.typedefNode, type.nullability, convertedTypeArguments); |
| } else if (type is FunctionType) { |
| if (clientLibrary.isNonNullableByDefault && type.typedefType != null) { |
| return convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.typedefType, |
| variance: variance); |
| } |
| |
| DartType convertedReturnType = convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.returnType, |
| variance: variance); |
| |
| List<DartType> convertedPositionalParameters = null; |
| for (int i = 0; i < type.positionalParameters.length; i++) { |
| DartType convertedPositionalParameter = |
| convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.positionalParameters[i], |
| variance: Variance.combine(variance, Variance.contravariant)); |
| if (convertedPositionalParameter != null) { |
| convertedPositionalParameters ??= |
| new List<DartType>.of(type.positionalParameters); |
| convertedPositionalParameters[i] = convertedPositionalParameter; |
| } |
| } |
| |
| List<NamedType> convertedNamedParameters = null; |
| for (int i = 0; i < type.namedParameters.length; i++) { |
| NamedType convertedNamedParameter = new NamedType( |
| type.namedParameters[i].name, |
| convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.namedParameters[i].type, |
| variance: Variance.combine(variance, Variance.contravariant)), |
| isRequired: type.namedParameters[i].isRequired); |
| if (convertedNamedParameter != null) { |
| convertedNamedParameters ??= |
| new List<NamedType>.of(type.namedParameters); |
| convertedNamedParameters[i] = convertedNamedParameter; |
| } |
| } |
| |
| List<TypeParameter> convertedTypeParameters = null; |
| if (clientLibrary.isNonNullableByDefault && |
| type.typeParameters.isNotEmpty) { |
| for (int i = 0; i < type.typeParameters.length; ++i) { |
| DartType convertedBound = convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.typeParameters[i].bound, |
| variance: Variance.combine(variance, Variance.invariant)); |
| if (convertedBound != null) { |
| if (convertedTypeParameters == null) { |
| convertedTypeParameters = <TypeParameter>[]; |
| for (TypeParameter parameter in type.typeParameters) { |
| convertedTypeParameters.add(new TypeParameter(parameter.name)); |
| } |
| } |
| convertedTypeParameters[i].bound = convertedBound; |
| } |
| } |
| |
| Map<TypeParameter, DartType> substitutionMap = |
| <TypeParameter, DartType>{}; |
| for (int i = 0; i < type.typeParameters.length; ++i) { |
| substitutionMap[type.typeParameters[i]] = |
| new TypeParameterType.forAlphaRenaming( |
| type.typeParameters[i], convertedTypeParameters[i]); |
| } |
| for (TypeParameter parameter in convertedTypeParameters) { |
| parameter.bound = substitute(parameter.bound, substitutionMap); |
| } |
| List<DartType> defaultTypes = calculateBounds(convertedTypeParameters, |
| typeEnvironment.coreTypes.objectClass, clientLibrary); |
| for (int i = 0; i < convertedTypeParameters.length; ++i) { |
| convertedTypeParameters[i].defaultType = defaultTypes[i]; |
| } |
| |
| if (convertedReturnType != null) { |
| convertedReturnType = substitute(convertedReturnType, substitutionMap); |
| } |
| if (convertedPositionalParameters != null) { |
| for (int i = 0; i < convertedPositionalParameters.length; ++i) { |
| convertedPositionalParameters[i] = |
| substitute(convertedPositionalParameters[i], substitutionMap); |
| } |
| } |
| if (convertedNamedParameters != null) { |
| for (int i = 0; i < convertedNamedParameters.length; ++i) { |
| convertedNamedParameters[i] = new NamedType( |
| convertedNamedParameters[i].name, |
| substitute(convertedNamedParameters[i].type, substitutionMap), |
| isRequired: convertedNamedParameters[i].isRequired); |
| } |
| } |
| } |
| |
| if (convertedReturnType == null && |
| convertedPositionalParameters == null && |
| convertedNamedParameters == null && |
| convertedTypeParameters == null) { |
| return null; |
| } |
| |
| convertedReturnType ??= type.returnType; |
| convertedPositionalParameters ??= type.positionalParameters; |
| convertedNamedParameters ??= type.namedParameters; |
| convertedTypeParameters ??= type.typeParameters; |
| |
| return new FunctionType( |
| convertedPositionalParameters, convertedReturnType, type.nullability, |
| namedParameters: convertedNamedParameters, |
| typeParameters: type.typeParameters, |
| requiredParameterCount: type.requiredParameterCount, |
| typedefType: type.typedefType); |
| } else if (type is FutureOrType) { |
| DartType convertedTypeArgument = convertSuperBoundedToRegularBounded( |
| clientLibrary, typeEnvironment, type.typeArgument, |
| variance: variance); |
| if (convertedTypeArgument == null) return null; |
| return new FutureOrType(convertedTypeArgument, type.declaredNullability); |
| } |
| |
| return null; |
| } |
| |
| int computeVariance(TypeParameter typeParameter, DartType type, |
| {Map<TypeParameter, Map<DartType, int>> computedVariances}) { |
| computedVariances ??= new Map<TypeParameter, Map<DartType, int>>.identity(); |
| computedVariances[typeParameter] ??= new Map<DartType, int>.identity(); |
| |
| int variance = computedVariances[typeParameter][type]; |
| if (variance != null) return variance; |
| computedVariances[typeParameter][type] = VarianceCalculator._visitMarker; |
| |
| return computedVariances[typeParameter][type] = |
| type.accept1(new VarianceCalculator(typeParameter), computedVariances); |
| } |
| |
| class VarianceCalculator |
| implements DartTypeVisitor1<int, Map<TypeParameter, Map<DartType, int>>> { |
| final TypeParameter typeParameter; |
| |
| static const int _visitMarker = -2; |
| |
| VarianceCalculator(this.typeParameter); |
| |
| @override |
| int defaultDartType( |
| DartType node, Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| throw new StateError("Unhandled ${node.runtimeType} " |
| "when computing variance of a type parameter."); |
| } |
| |
| @override |
| int visitTypeParameterType(TypeParameterType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| if (node.parameter == typeParameter) return Variance.covariant; |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitInterfaceType(InterfaceType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| int result = Variance.unrelated; |
| for (int i = 0; i < node.typeArguments.length; ++i) { |
| result = Variance.meet( |
| result, |
| Variance.combine( |
| node.classNode.typeParameters[i].variance, |
| computeVariance(typeParameter, node.typeArguments[i], |
| computedVariances: computedVariances))); |
| } |
| return result; |
| } |
| |
| @override |
| int visitFutureOrType(FutureOrType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return computeVariance(typeParameter, node.typeArgument, |
| computedVariances: computedVariances); |
| } |
| |
| @override |
| int visitTypedefType(TypedefType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| int result = Variance.unrelated; |
| for (int i = 0; i < node.typeArguments.length; ++i) { |
| Typedef typedefNode = node.typedefNode; |
| TypeParameter typedefTypeParameter = typedefNode.typeParameters[i]; |
| if (computedVariances.containsKey(typedefTypeParameter) && |
| computedVariances[typedefTypeParameter][typedefNode.type] == |
| _visitMarker) { |
| throw new StateError("The typedef '${node.typedefNode.name}' " |
| "has a reference to itself."); |
| } |
| |
| result = Variance.meet( |
| result, |
| Variance.combine( |
| computeVariance(typeParameter, node.typeArguments[i], |
| computedVariances: computedVariances), |
| computeVariance(typedefTypeParameter, typedefNode.type, |
| computedVariances: computedVariances))); |
| } |
| return result; |
| } |
| |
| @override |
| int visitFunctionType(FunctionType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| int result = Variance.unrelated; |
| result = Variance.meet( |
| result, |
| computeVariance(typeParameter, node.returnType, |
| computedVariances: computedVariances)); |
| for (TypeParameter functionTypeParameter in node.typeParameters) { |
| // If [typeParameter] is referenced in the bound at all, it makes the |
| // variance of [typeParameter] in the entire type invariant. The |
| // invocation of the visitor below is made to simply figure out if |
| // [typeParameter] occurs in the bound. |
| if (computeVariance(typeParameter, functionTypeParameter.bound, |
| computedVariances: computedVariances) != |
| Variance.unrelated) { |
| result = Variance.invariant; |
| } |
| } |
| for (DartType positionalType in node.positionalParameters) { |
| result = Variance.meet( |
| result, |
| Variance.combine( |
| Variance.contravariant, |
| computeVariance(typeParameter, positionalType, |
| computedVariances: computedVariances))); |
| } |
| for (NamedType namedType in node.namedParameters) { |
| result = Variance.meet( |
| result, |
| Variance.combine( |
| Variance.contravariant, |
| computeVariance(typeParameter, namedType.type, |
| computedVariances: computedVariances))); |
| } |
| return result; |
| } |
| |
| @override |
| int visitBottomType(BottomType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitNeverType(NeverType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitNullType( |
| NullType node, Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitVoidType( |
| VoidType node, Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitDynamicType(DynamicType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| |
| @override |
| int visitInvalidType(InvalidType node, |
| Map<TypeParameter, Map<DartType, int>> computedVariances) { |
| return Variance.unrelated; |
| } |
| } |
| |
| bool isGenericFunctionTypeOrAlias(DartType type) { |
| if (type is TypedefType) type = type.unalias; |
| return type is FunctionType && type.typeParameters.isNotEmpty; |
| } |