diff --git a/pkg/analyzer/lib/src/dart/resolver/variance.dart b/pkg/analyzer/lib/src/dart/resolver/variance.dart index 69bc5801f27..757f589899c 100644 --- a/pkg/analyzer/lib/src/dart/resolver/variance.dart +++ b/pkg/analyzer/lib/src/dart/resolver/variance.dart @@ -5,108 +5,142 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -/// Computes the variance of the [typeParameter] in the [type]. -int computeVariance(TypeParameterElement typeParameter, DartType type) { - if (type is TypeParameterType) { - if (type.element == typeParameter) { - return Variance.covariant; - } else { - return Variance.unrelated; - } - } else if (type is InterfaceType) { - var result = Variance.unrelated; - for (var argument in type.typeArguments) { - result = Variance.meet( - result, - computeVariance(typeParameter, argument), - ); - } - return result; - } else if (type is FunctionType) { - var result = computeVariance(typeParameter, type.returnType); - - for (var parameter in type.typeFormals) { - // If [parameter] is referenced in the bound at all, it makes the - // variance of [parameter] in the entire type invariant. The invocation - // of [computeVariance] below is made to simply figure out if [variable] - // occurs in the bound. - var bound = parameter.bound; - if (bound != null && - computeVariance(typeParameter, bound) != Variance.unrelated) { - result = Variance.invariant; - } - } - - for (var parameter in type.parameters) { - result = Variance.meet( - result, - Variance.combine( - Variance.contravariant, - computeVariance(typeParameter, parameter.type), - ), - ); - } - return result; - } - return Variance.unrelated; -} - -/// Value set for variance of a type parameter `X` in a type `T`. +/// The variance of a type parameter `X` in a type `T`. class Variance { /// Used when `X` does not occur free in `T`. - static const int unrelated = 0; + static const Variance _unrelated = Variance._(0); /// Used when `X` occurs free in `T`, and `U <: V` implies `[U/X]T <: [V/X]T`. - static const int covariant = 1; + static const Variance _covariant = Variance._(1); /// Used when `X` occurs free in `T`, and `U <: V` implies `[V/X]T <: [U/X]T`. - static const int contravariant = 2; + static const Variance _contravariant = Variance._(2); /// Used when there exists a pair `U` and `V` such that `U <: V`, but /// `[U/X]T` and `[V/X]T` are incomparable. - static const int invariant = 3; + static const Variance _invariant = Variance._(3); + + /// The encoding associated with the variance. + final int _encoding; + + /// Computes the variance of the [typeParameter] in the [type]. + factory Variance(TypeParameterElement typeParameter, DartType type) { + if (type is TypeParameterType) { + if (type.element == typeParameter) { + return _covariant; + } else { + return _unrelated; + } + } else if (type is InterfaceType) { + var result = _unrelated; + for (var argument in type.typeArguments) { + result = result.meet( + Variance(typeParameter, argument), + ); + } + return result; + } else if (type is FunctionType) { + var result = Variance(typeParameter, type.returnType); + + for (var parameter in type.typeFormals) { + // If [parameter] is referenced in the bound at all, it makes the + // variance of [parameter] in the entire type invariant. The invocation + // of [computeVariance] below is made to simply figure out if [variable] + // occurs in the bound. + var bound = parameter.bound; + if (bound != null && !Variance(typeParameter, bound).isUnrelated) { + result = _invariant; + } + } + + for (var parameter in type.parameters) { + result = result.meet( + _contravariant.combine( + Variance(typeParameter, parameter.type), + ), + ); + } + return result; + } + return _unrelated; + } + + /// Initialize a newly created variance to have the given [encoding]. + const Variance._(this._encoding); + + /// Return the variance with the given [encoding]. + factory Variance._fromEncoding(int encoding) { + switch (encoding) { + case 0: + return _unrelated; + case 1: + return _covariant; + case 2: + return _contravariant; + case 3: + return _invariant; + } + throw new ArgumentError('Invalid encoding for variance: $encoding'); + } + + /// Return `true` if this represents the case when `X` occurs free in `T`, and + /// `U <: V` implies `[V/X]T <: [U/X]T`. + bool get isContravariant => this == _contravariant; + + /// Return `true` if this represents the case when `X` occurs free in `T`, and + /// `U <: V` implies `[U/X]T <: [V/X]T`. + bool get isCovariant => this == _covariant; + + /// Return `true` if this represents the case when there exists a pair `U` and + /// `V` such that `U <: V`, but `[U/X]T` and `[V/X]T` are incomparable. + bool get isInvariant => this == _invariant; + + /// Return `true` if this represents the case when `X` does not occur free in + /// `T`. + bool get isUnrelated => this == _unrelated; /// Combines variances of `X` in `T` and `Y` in `S` into variance of `X` in /// `[Y/T]S`. /// /// Consider the following examples: /// - /// * variance of `X` in `Function(X)` is [contravariant], variance of `Y` - /// in `List` is [covariant], so variance of `X` in `List` is - /// [contravariant]; + /// * variance of `X` in `Function(X)` is contravariant, variance of `Y` + /// in `List` is covariant, so variance of `X` in `List` is + /// contravariant; /// - /// * variance of `X` in `List` is [covariant], variance of `Y` in - /// `Function(Y)` is [contravariant], so variance of `X` in - /// `Function(List)` is [contravariant]; + /// * variance of `X` in `List` is covariant, variance of `Y` in + /// `Function(Y)` is contravariant, so variance of `X` in + /// `Function(List)` is contravariant; /// - /// * variance of `X` in `Function(X)` is [contravariant], variance of `Y` in - /// `Function(Y)` is [contravariant], so variance of `X` in - /// `Function(Function(X))` is [covariant]; + /// * variance of `X` in `Function(X)` is contravariant, variance of `Y` in + /// `Function(Y)` is contravariant, so variance of `X` in + /// `Function(Function(X))` is covariant; /// /// * let the following be declared: /// /// typedef F = Function(); /// - /// then variance of `X` in `F` is [unrelated], variance of `Y` in - /// `List` is [covariant], so variance of `X` in `List>` is - /// [unrelated]; + /// then variance of `X` in `F` is unrelated, variance of `Y` in + /// `List` is covariant, so variance of `X` in `List>` is + /// unrelated; /// /// * let the following be declared: /// /// typedef G = Z Function(Z); /// - /// then variance of `X` in `List` is [covariant], variance of `Y` in - /// `G` is [invariant], so variance of `X` in `G>` is [invariant]. - static int combine(int a, int b) { - if (a == unrelated || b == unrelated) return unrelated; - if (a == invariant || b == invariant) return invariant; - return a == b ? covariant : contravariant; + /// then variance of `X` in `List` is covariant, variance of `Y` in + /// `G` is invariant, so variance of `X` in `G>` is invariant. + Variance combine(Variance other) { + if (isUnrelated || other.isUnrelated) return _unrelated; + if (isInvariant || other.isInvariant) return _invariant; + return this == other ? _covariant : _contravariant; } - /// Variance values form a lattice where [unrelated] is the top, [invariant] - /// is the bottom, and [covariant] and [contravariant] are incomparable. + /// Variance values form a lattice where unrelated is the top, invariant + /// is the bottom, and covariant and contravariant are incomparable. /// [meet] calculates the meet of two elements of such lattice. It can be /// used, for example, to calculate the variance of a typedef type parameter /// if it's encountered on the RHS of the typedef multiple times. - static int meet(int a, int b) => a | b; + Variance meet(Variance other) => + Variance._fromEncoding(_encoding | other._encoding); } diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index 9b4f7ced3f5..9c809b67145 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart @@ -5304,9 +5304,8 @@ class ErrorVerifier extends RecursiveAstVisitor { void checkOne(DartType superInterface) { if (superInterface != null) { for (var typeParameter in _enclosingClass.typeParameters) { - var variance = computeVariance(typeParameter, superInterface); - if (variance == Variance.contravariant || - variance == Variance.invariant) { + var variance = Variance(typeParameter, superInterface); + if (variance.isContravariant || variance.isInvariant) { _errorReporter.reportErrorForElement( CompileTimeErrorCode .WRONG_TYPE_PARAMETER_VARIANCE_IN_SUPERINTERFACE, diff --git a/pkg/analyzer/lib/src/task/strong/checker.dart b/pkg/analyzer/lib/src/task/strong/checker.dart index 6b90f7493ff..0a84a4626e0 100644 --- a/pkg/analyzer/lib/src/task/strong/checker.dart +++ b/pkg/analyzer/lib/src/task/strong/checker.dart @@ -910,9 +910,8 @@ class CodeChecker extends RecursiveAstVisitor { // Check if the return type uses a class type parameter contravariantly. bool needsCheck = false; for (var typeParameter in classElement.typeParameters) { - var variance = computeVariance(typeParameter, rawReturnType); - if (variance == Variance.contravariant || - variance == Variance.invariant) { + var variance = Variance(typeParameter, rawReturnType); + if (variance.isContravariant || variance.isInvariant) { needsCheck = true; break; }