blob: b24877494d83fb7ddb1574a116195eecdb370def [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
/// The variance of a type parameter `X` in a type `T`.
class Variance {
/// Used when `X` does not occur free in `T`.
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 Variance covariant = Variance._(1);
/// Used when `X` occurs free in `T`, and `U <: V` implies `[V/X]T <: [U/X]T`.
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 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 (int i = 0; i < type.typeArguments.length; ++i) {
var argument = type.typeArguments[i];
var parameter = type.element.typeParameters[i];
// TODO (kallentu) : Clean up TypeParameterElementImpl casting once
// variance is added to the interface.
var parameterVariance =
(parameter as TypeParameterElementImpl).variance;
result = result
.meet(parameterVariance.combine(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;
}
/// Return the variance associated with the string representation of variance.
factory Variance.fromKeywordString(String varianceString) {
if (varianceString == "in") {
return contravariant;
} else if (varianceString == "inout") {
return invariant;
} else if (varianceString == "out") {
return covariant;
} else if (varianceString == "unrelated") {
return unrelated;
}
throw ArgumentError('Invalid keyword string for variance: $varianceString');
}
/// 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 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<Y>` is covariant, so variance of `X` in `List<Function(X)>` is
/// contravariant;
///
/// * variance of `X` in `List<X>` is covariant, variance of `Y` in
/// `Function(Y)` is contravariant, so variance of `X` in
/// `Function(List<X>)` is contravariant;
///
/// * variance of `X` in `Function(X)` is contravariant, variance of `Y` in
/// `Function(Y)` is contravariant, so variance of `X` in
/// `Function(Function(X))` is covariant;
///
/// * let the following be declared:
///
/// typedef F<Z> = Function();
///
/// then variance of `X` in `F<X>` is unrelated, variance of `Y` in
/// `List<Y>` is covariant, so variance of `X` in `List<F<X>>` is
/// unrelated;
///
/// * let the following be declared:
///
/// typedef G<Z> = Z Function(Z);
///
/// then variance of `X` in `List<X>` is covariant, variance of `Y` in
/// `G<Y>` is invariant, so variance of `X` in `G<List<X>>` is invariant.
Variance combine(Variance other) {
if (isUnrelated || other.isUnrelated) return unrelated;
if (isInvariant || other.isInvariant) return invariant;
return this == other ? covariant : contravariant;
}
/// Returns true if this variance is greater than (above) or equal to the
/// [other] variance in the partial order induced by the variance lattice.
///
/// unrelated
/// covariant contravariant
/// invariant
bool greaterThanOrEqual(Variance other) {
if (isUnrelated) {
return true;
} else if (isCovariant) {
return other.isCovariant || other.isInvariant;
} else if (isContravariant) {
return other.isContravariant || other.isInvariant;
} else {
assert(isInvariant);
return other.isInvariant;
}
}
/// Variance values form a lattice where unrelated is the top, invariant
/// is the bottom, and covariant and contravariant are incomparable.
/// [meet] calculates the meet of two elements of such lattice. It can be
/// used, for example, to calculate the variance of a typedef type parameter
/// if it's encountered on the RHS of the typedef multiple times.
///
/// unrelated
/// covariant contravariant
/// invariant
Variance meet(Variance other) =>
Variance._fromEncoding(_encoding | other._encoding);
/// Returns the associated keyword lexeme.
String toKeywordString() {
switch (this) {
case contravariant:
return 'in';
case invariant:
return 'inout';
case covariant:
return 'out';
case unrelated:
return '';
default:
throw ArgumentError(
'Missing keyword lexeme representation for variance: $this');
}
}
@override
String toString() {
switch (this) {
case contravariant:
return 'contravariant';
case invariant:
return 'invariant';
case covariant:
return 'covariant';
case unrelated:
return 'unrelated';
default:
throw UnimplementedError('encoding: $_encoding');
}
}
}