blob: b3e9a44349fd6c8985af205d42de3b7f04a9f909 [file] [log] [blame]
// 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.
import 'package:kernel/src/replacement_visitor.dart';
import '../ast.dart';
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> {
late 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.
late List<List<int>> edges;
TypeVariableGraph(this.typeParameters, this.bounds) {
assert(typeParameters.length == bounds.length);
vertices = new List<int>.filled(
typeParameters.length,
// Dummy value.
-1);
Map<TypeParameter, int> typeParameterIndices = <TypeParameter, int>{};
edges = new List<List<int>>.filled(typeParameters.length,
// Dummy value.
const []);
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<void> {
final Set<TypeParameter> typeParameters;
Set<TypeParameter> occurred = new Set<TypeParameter>();
OccurrenceCollectorVisitor(this.typeParameters);
void visit(DartType node) => node.accept(this);
void visitNamedType(NamedType node) {
node.type.accept(this);
}
void visitInvalidType(InvalidType node);
void visitDynamicType(DynamicType node);
void visitVoidType(VoidType node);
void visitInterfaceType(InterfaceType node) {
for (DartType argument in node.typeArguments) {
argument.accept(this);
}
}
void visitTypedefType(TypedefType node) {
for (DartType argument in node.typeArguments) {
argument.accept(this);
}
}
void 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);
}
void visitTypeParameterType(TypeParameterType node) {
if (typeParameters.contains(node.parameter)) {
occurred.add(node.parameter);
}
}
@override
void defaultDartType(DartType node) {}
}
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,
{required bool isNonNullableByDefault}) {
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
List<DartType> bounds =
new List<DartType>.filled(typeParameters.length, dummyDartType);
for (int i = 0; i < typeParameters.length; i++) {
DartType bound = typeParameters[i].bound;
bool isContravariant = typeParameters[i].variance == Variance.contravariant;
if (identical(bound, TypeParameter.unsetBoundSentinel)) {
bound = isNonNullableByDefault && isContravariant
? const NeverType.nonNullable()
: const DynamicType();
} else if (bound is InterfaceType && bound.classNode == objectClass) {
DartType defaultType = typeParameters[i].defaultType;
if (!(defaultType is InterfaceType &&
defaultType.classNode == objectClass)) {
bound = isNonNullableByDefault && isContravariant
? const NeverType.nonNullable()
: 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.nonNullable()
: const NeverType.legacy();
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],
contravariant: typeParameters[typeParameterIndex].variance ==
Variance.contravariant);
}
}
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],
contravariant: typeParameters[j].variance == Variance.contravariant);
}
}
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;
final bool isGenericTypeAsArgumentIssue;
TypeArgumentIssue(
this.index, this.argument, this.typeParameter, this.enclosingType,
{this.invertedType, this.isGenericTypeAsArgumentIssue = false});
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.
List<TypeArgumentIssue> findTypeArgumentIssues(DartType type,
TypeEnvironment typeEnvironment, SubtypeCheckMode subtypeCheckMode,
{bool allowSuperBounded = false,
required bool isNonNullableByDefault,
required bool areGenericArgumentsAllowed}) {
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
// ignore: unnecessary_null_comparison
assert(areGenericArgumentsAllowed != null);
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(
cloned, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed);
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(
parameter.bound, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
}
for (DartType formal in type.positionalParameters) {
result.addAll(findTypeArgumentIssues(
formal, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
}
for (NamedType named in type.namedParameters) {
result.addAll(findTypeArgumentIssues(
named.type, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
}
result.addAll(findTypeArgumentIssues(
type.returnType, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
return result;
} else if (type is FutureOrType) {
variables = typeEnvironment.coreTypes.futureClass.typeParameters;
arguments = <DartType>[type.typeArgument];
} else {
return const <TypeArgumentIssue>[];
}
if (variables.isEmpty) {
return typedefRhsResult.isNotEmpty
? typedefRhsResult
: 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 (!areGenericArgumentsAllowed && isGenericFunctionTypeOrAlias(argument)) {
// Generic function types aren't allowed as type arguments either.
result.add(new TypeArgumentIssue(i, argument, variables[i], type,
isGenericTypeAsArgumentIssue: true));
} else if (variables[i].bound is! InvalidType) {
DartType bound = substitute(variables[i].bound, substitutionMap);
if (!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(
argument, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
}
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(
typeEnvironment, type,
isNonNullableByDefault: isNonNullableByDefault);
// 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.
List<TypeArgumentIssue> findTypeArgumentIssuesForInvocation(
List<TypeParameter> parameters,
List<DartType> arguments,
TypeEnvironment typeEnvironment,
SubtypeCheckMode subtypeCheckMode,
DartType bottomType,
{required bool isNonNullableByDefault,
required bool areGenericArgumentsAllowed}) {
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
// ignore: unnecessary_null_comparison
assert(areGenericArgumentsAllowed != null);
assert(arguments.length == parameters.length);
assert(bottomType == const NeverType.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) {
// TODO(dmitryas): Consider recognizing this case with a flag on the issue
// object.
result.add(new TypeArgumentIssue(i, argument, parameters[i], null));
} else if (!areGenericArgumentsAllowed &&
isGenericFunctionTypeOrAlias(argument)) {
// Generic function types aren't allowed as type arguments either.
result.add(new TypeArgumentIssue(i, argument, parameters[i], null,
isGenericTypeAsArgumentIssue: true));
} else if (parameters[i].bound is! InvalidType) {
DartType bound = substitute(parameters[i].bound, substitutionMap);
if (!isNonNullableByDefault) {
bound = legacyErasure(bound);
}
if (!typeEnvironment.isSubtypeOf(argument, bound, subtypeCheckMode)) {
result.add(new TypeArgumentIssue(i, argument, parameters[i], null));
}
}
result.addAll(findTypeArgumentIssues(
argument, typeEnvironment, subtypeCheckMode,
allowSuperBounded: true,
isNonNullableByDefault: isNonNullableByDefault,
areGenericArgumentsAllowed: areGenericArgumentsAllowed));
}
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(
TypeEnvironment typeEnvironment, DartType type,
{int variance = Variance.covariant, required bool isNonNullableByDefault}) {
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
return type.accept1(
new _SuperBoundedTypeInverter(typeEnvironment,
isNonNullableByDefault: isNonNullableByDefault),
variance);
}
class _SuperBoundedTypeInverter extends ReplacementVisitor {
final TypeEnvironment typeEnvironment;
final bool isNonNullableByDefault;
_SuperBoundedTypeInverter(this.typeEnvironment,
{required this.isNonNullableByDefault})
// ignore: unnecessary_null_comparison
: assert(typeEnvironment != null),
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
bool flipTop(int variance) {
return isNonNullableByDefault
? variance != Variance.contravariant
: variance == Variance.covariant;
}
bool flipBottom(int variance) {
return isNonNullableByDefault
? variance == Variance.contravariant
: variance != Variance.covariant;
}
DartType get topType {
return isNonNullableByDefault
? typeEnvironment.coreTypes.objectNullableRawType
: const DynamicType();
}
DartType get bottomType {
return isNonNullableByDefault
? const NeverType.nonNullable()
: const NullType();
}
bool isTop(DartType node) {
if (isNonNullableByDefault) {
return typeEnvironment.coreTypes.isTop(node);
} else {
return node is DynamicType ||
node is VoidType ||
node is InterfaceType &&
node.classNode == typeEnvironment.coreTypes.objectClass;
}
}
bool isBottom(DartType node) {
if (isNonNullableByDefault) {
return typeEnvironment.coreTypes.isBottom(node);
} else {
return node is NullType;
}
}
@override
DartType? visitDynamicType(DynamicType node, int variance) {
// dynamic is always a top type.
assert(isTop(node));
if (flipTop(variance)) {
return bottomType;
} else {
return null;
}
}
@override
DartType? visitVoidType(VoidType node, int variance) {
// void is always a top type.
assert(isTop(node));
if (flipTop(variance)) {
return bottomType;
} else {
return null;
}
}
@override
DartType? visitInterfaceType(InterfaceType node, int variance) {
// Check for Object-based top types.
if (isTop(node) && flipTop(variance)) {
return bottomType;
} else {
return super.visitInterfaceType(node, variance);
}
}
@override
DartType? visitFutureOrType(FutureOrType node, int variance) {
// Check FutureOr-based top types.
if (isTop(node) && flipTop(variance)) {
return bottomType;
} else {
return super.visitFutureOrType(node, variance);
}
}
@override
DartType? visitNullType(NullType node, int variance) {
// Null isn't a bottom type in NNBD.
if (isBottom(node) && flipBottom(variance)) {
return topType;
} else {
return null;
}
}
@override
DartType? visitNeverType(NeverType node, int variance) {
// Depending on the variance, Never may not be a bottom type.
if (isBottom(node) && flipBottom(variance)) {
return topType;
} else {
return null;
}
}
@override
DartType? visitTypeParameterType(TypeParameterType node, int variance) {
// Types such as X extends Never are bottom types.
if (isBottom(node) && flipBottom(variance)) {
return topType;
} else {
return null;
}
}
// TypedefTypes receive special treatment because the variance of their
// arguments' positions depend on the opt-in status of the library.
// TODO(dmitryas): Remove the method when the discrepancy between the NNBD
// modes is resolved.
@override
DartType? visitTypedefType(TypedefType node, int variance) {
Nullability? newNullability = visitNullability(node);
List<DartType>? newTypeArguments = null;
for (int i = 0; i < node.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? newTypeArgument = node.typeArguments[i].accept1(
this,
isNonNullableByDefault
? Variance.combine(
variance, node.typedefNode.typeParameters[i].variance)
: variance);
if (newTypeArgument != null) {
newTypeArguments ??= new List<DartType>.of(node.typeArguments);
newTypeArguments[i] = newTypeArgument;
}
}
return createTypedef(node, newNullability, newTypeArguments);
}
@override
DartType? visitFunctionType(FunctionType node, int variance) {
// The variance of the Typedef parameters should be taken into account only
// when for the NNBD code.
if (node.typedefType != null && isNonNullableByDefault) {
return node.typedefType!.accept1(this, variance);
} else {
return super.visitFunctionType(node, variance);
}
}
}
int computeVariance(TypeParameter typeParameter, DartType type,
{Map<TypeParameter, Map<DartType, int>>? computedVariances}) {
computedVariances ??= new Map<TypeParameter, Map<DartType, int>>.identity();
Map<DartType, int> variancesFromTypeParameter =
computedVariances[typeParameter] ??= new Map<DartType, int>.identity();
int? variance = variancesFromTypeParameter[type];
if (variance != null) return variance;
variancesFromTypeParameter[type] = VarianceCalculator._visitMarker;
return variancesFromTypeParameter[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 visitExtensionType(ExtensionType 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.extension.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 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;
}