mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00

To the extent possible. Previously they were confused. Sometimes generic params were treated as `Param` and sometimes as `Placeholder`. A completely redundant (in the new solver) mapping of salsa::Id to ints to intern some info where we could just store it uninterned (not in Chalk though, for some weird reason). Plus fix a cute bug in closure substitution that was caught by the assertions of Chalk but the next solver did not have such assertions. Do we need more assertions?
1065 lines
34 KiB
Rust
1065 lines
34 KiB
Rust
//! Module for inferring the variance of type and lifetime parameters. See the [rustc dev guide]
|
|
//! chapter for more info.
|
|
//!
|
|
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/variance.html
|
|
//!
|
|
//! The implementation here differs from rustc. Rustc does a crate wide fixpoint resolution
|
|
//! as the algorithm for determining variance is a fixpoint computation with potential cycles that
|
|
//! need to be resolved. rust-analyzer does not want a crate-wide analysis though as that would hurt
|
|
//! incrementality too much and as such our query is based on a per item basis.
|
|
//!
|
|
//! This does unfortunately run into the issue that we can run into query cycles which salsa
|
|
//! currently does not allow to be resolved via a fixpoint computation. This will likely be resolved
|
|
//! by the next salsa version. If not, we will likely have to adapt and go with the rustc approach
|
|
//! while installing firewall per item queries to prevent invalidation issues.
|
|
|
|
use crate::db::HirDatabase;
|
|
use crate::generics::{Generics, generics};
|
|
use crate::{
|
|
AliasTy, Const, ConstScalar, DynTyExt, GenericArg, GenericArgData, Interner, Lifetime,
|
|
LifetimeData, Ty, TyKind,
|
|
};
|
|
use chalk_ir::Mutability;
|
|
use hir_def::signatures::StructFlags;
|
|
use hir_def::{AdtId, GenericDefId, GenericParamId, VariantId};
|
|
use std::fmt;
|
|
use std::ops::Not;
|
|
use stdx::never;
|
|
use triomphe::Arc;
|
|
|
|
pub(crate) fn variances_of(db: &dyn HirDatabase, def: GenericDefId) -> Option<Arc<[Variance]>> {
|
|
tracing::debug!("variances_of(def={:?})", def);
|
|
match def {
|
|
GenericDefId::FunctionId(_) => (),
|
|
GenericDefId::AdtId(adt) => {
|
|
if let AdtId::StructId(id) = adt {
|
|
let flags = &db.struct_signature(id).flags;
|
|
if flags.contains(StructFlags::IS_UNSAFE_CELL) {
|
|
return Some(Arc::from_iter(vec![Variance::Invariant; 1]));
|
|
} else if flags.contains(StructFlags::IS_PHANTOM_DATA) {
|
|
return Some(Arc::from_iter(vec![Variance::Covariant; 1]));
|
|
}
|
|
}
|
|
}
|
|
_ => return None,
|
|
}
|
|
|
|
let generics = generics(db, def);
|
|
let count = generics.len();
|
|
if count == 0 {
|
|
return None;
|
|
}
|
|
let variances = Context { generics, variances: vec![Variance::Bivariant; count], db }.solve();
|
|
|
|
variances.is_empty().not().then(|| Arc::from_iter(variances))
|
|
}
|
|
|
|
// pub(crate) fn variances_of_cycle_fn(
|
|
// _db: &dyn HirDatabase,
|
|
// _result: &Option<Arc<[Variance]>>,
|
|
// _count: u32,
|
|
// _def: GenericDefId,
|
|
// ) -> salsa::CycleRecoveryAction<Option<Arc<[Variance]>>> {
|
|
// salsa::CycleRecoveryAction::Iterate
|
|
// }
|
|
|
|
pub(crate) fn variances_of_cycle_initial(
|
|
db: &dyn HirDatabase,
|
|
def: GenericDefId,
|
|
) -> Option<Arc<[Variance]>> {
|
|
let generics = generics(db, def);
|
|
let count = generics.len();
|
|
|
|
if count == 0 {
|
|
return None;
|
|
}
|
|
Some(Arc::from(vec![Variance::Bivariant; count]))
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum Variance {
|
|
Covariant, // T<A> <: T<B> iff A <: B -- e.g., function return type
|
|
Invariant, // T<A> <: T<B> iff B == A -- e.g., type of mutable cell
|
|
Contravariant, // T<A> <: T<B> iff B <: A -- e.g., function param type
|
|
Bivariant, // T<A> <: T<B> -- e.g., unused type parameter
|
|
}
|
|
|
|
impl fmt::Display for Variance {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Variance::Covariant => write!(f, "covariant"),
|
|
Variance::Invariant => write!(f, "invariant"),
|
|
Variance::Contravariant => write!(f, "contravariant"),
|
|
Variance::Bivariant => write!(f, "bivariant"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Variance {
|
|
/// `a.xform(b)` combines the variance of a context with the
|
|
/// variance of a type with the following meaning. If we are in a
|
|
/// context with variance `a`, and we encounter a type argument in
|
|
/// a position with variance `b`, then `a.xform(b)` is the new
|
|
/// variance with which the argument appears.
|
|
///
|
|
/// Example 1:
|
|
/// ```ignore (illustrative)
|
|
/// *mut Vec<i32>
|
|
/// ```
|
|
/// Here, the "ambient" variance starts as covariant. `*mut T` is
|
|
/// invariant with respect to `T`, so the variance in which the
|
|
/// `Vec<i32>` appears is `Covariant.xform(Invariant)`, which
|
|
/// yields `Invariant`. Now, the type `Vec<T>` is covariant with
|
|
/// respect to its type argument `T`, and hence the variance of
|
|
/// the `i32` here is `Invariant.xform(Covariant)`, which results
|
|
/// (again) in `Invariant`.
|
|
///
|
|
/// Example 2:
|
|
/// ```ignore (illustrative)
|
|
/// fn(*const Vec<i32>, *mut Vec<i32)
|
|
/// ```
|
|
/// The ambient variance is covariant. A `fn` type is
|
|
/// contravariant with respect to its parameters, so the variance
|
|
/// within which both pointer types appear is
|
|
/// `Covariant.xform(Contravariant)`, or `Contravariant`. `*const
|
|
/// T` is covariant with respect to `T`, so the variance within
|
|
/// which the first `Vec<i32>` appears is
|
|
/// `Contravariant.xform(Covariant)` or `Contravariant`. The same
|
|
/// is true for its `i32` argument. In the `*mut T` case, the
|
|
/// variance of `Vec<i32>` is `Contravariant.xform(Invariant)`,
|
|
/// and hence the outermost type is `Invariant` with respect to
|
|
/// `Vec<i32>` (and its `i32` argument).
|
|
///
|
|
/// Source: Figure 1 of "Taming the Wildcards:
|
|
/// Combining Definition- and Use-Site Variance" published in PLDI'11.
|
|
fn xform(self, v: Variance) -> Variance {
|
|
match (self, v) {
|
|
// Figure 1, column 1.
|
|
(Variance::Covariant, Variance::Covariant) => Variance::Covariant,
|
|
(Variance::Covariant, Variance::Contravariant) => Variance::Contravariant,
|
|
(Variance::Covariant, Variance::Invariant) => Variance::Invariant,
|
|
(Variance::Covariant, Variance::Bivariant) => Variance::Bivariant,
|
|
|
|
// Figure 1, column 2.
|
|
(Variance::Contravariant, Variance::Covariant) => Variance::Contravariant,
|
|
(Variance::Contravariant, Variance::Contravariant) => Variance::Covariant,
|
|
(Variance::Contravariant, Variance::Invariant) => Variance::Invariant,
|
|
(Variance::Contravariant, Variance::Bivariant) => Variance::Bivariant,
|
|
|
|
// Figure 1, column 3.
|
|
(Variance::Invariant, _) => Variance::Invariant,
|
|
|
|
// Figure 1, column 4.
|
|
(Variance::Bivariant, _) => Variance::Bivariant,
|
|
}
|
|
}
|
|
|
|
fn glb(self, v: Variance) -> Variance {
|
|
// Greatest lower bound of the variance lattice as
|
|
// defined in The Paper:
|
|
//
|
|
// *
|
|
// - +
|
|
// o
|
|
match (self, v) {
|
|
(Variance::Invariant, _) | (_, Variance::Invariant) => Variance::Invariant,
|
|
|
|
(Variance::Covariant, Variance::Contravariant) => Variance::Invariant,
|
|
(Variance::Contravariant, Variance::Covariant) => Variance::Invariant,
|
|
|
|
(Variance::Covariant, Variance::Covariant) => Variance::Covariant,
|
|
|
|
(Variance::Contravariant, Variance::Contravariant) => Variance::Contravariant,
|
|
|
|
(x, Variance::Bivariant) | (Variance::Bivariant, x) => x,
|
|
}
|
|
}
|
|
|
|
pub fn invariant(self) -> Self {
|
|
self.xform(Variance::Invariant)
|
|
}
|
|
|
|
pub fn covariant(self) -> Self {
|
|
self.xform(Variance::Covariant)
|
|
}
|
|
|
|
pub fn contravariant(self) -> Self {
|
|
self.xform(Variance::Contravariant)
|
|
}
|
|
}
|
|
|
|
struct Context<'db> {
|
|
db: &'db dyn HirDatabase,
|
|
generics: Generics,
|
|
variances: Vec<Variance>,
|
|
}
|
|
|
|
impl Context<'_> {
|
|
fn solve(mut self) -> Vec<Variance> {
|
|
tracing::debug!("solve(generics={:?})", self.generics);
|
|
match self.generics.def() {
|
|
GenericDefId::AdtId(adt) => {
|
|
let db = self.db;
|
|
let mut add_constraints_from_variant = |variant| {
|
|
let subst = self.generics.placeholder_subst(db);
|
|
for (_, field) in db.field_types(variant).iter() {
|
|
self.add_constraints_from_ty(
|
|
&field.clone().substitute(Interner, &subst),
|
|
Variance::Covariant,
|
|
);
|
|
}
|
|
};
|
|
match adt {
|
|
AdtId::StructId(s) => add_constraints_from_variant(VariantId::StructId(s)),
|
|
AdtId::UnionId(u) => add_constraints_from_variant(VariantId::UnionId(u)),
|
|
AdtId::EnumId(e) => {
|
|
e.enum_variants(db).variants.iter().for_each(|&(variant, _, _)| {
|
|
add_constraints_from_variant(VariantId::EnumVariantId(variant))
|
|
});
|
|
}
|
|
}
|
|
}
|
|
GenericDefId::FunctionId(f) => {
|
|
let subst = self.generics.placeholder_subst(self.db);
|
|
self.add_constraints_from_sig(
|
|
self.db
|
|
.callable_item_signature(f.into())
|
|
.substitute(Interner, &subst)
|
|
.params_and_return
|
|
.iter(),
|
|
Variance::Covariant,
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
let mut variances = self.variances;
|
|
|
|
// Const parameters are always invariant.
|
|
// Make all const parameters invariant.
|
|
for (idx, param) in self.generics.iter_id().enumerate() {
|
|
if let GenericParamId::ConstParamId(_) = param {
|
|
variances[idx] = Variance::Invariant;
|
|
}
|
|
}
|
|
|
|
// Functions are permitted to have unused generic parameters: make those invariant.
|
|
if let GenericDefId::FunctionId(_) = self.generics.def() {
|
|
variances
|
|
.iter_mut()
|
|
.filter(|&&mut v| v == Variance::Bivariant)
|
|
.for_each(|v| *v = Variance::Invariant);
|
|
}
|
|
|
|
variances
|
|
}
|
|
|
|
/// Adds constraints appropriate for an instance of `ty` appearing
|
|
/// in a context with the generics defined in `generics` and
|
|
/// ambient variance `variance`
|
|
fn add_constraints_from_ty(&mut self, ty: &Ty, variance: Variance) {
|
|
tracing::debug!("add_constraints_from_ty(ty={:?}, variance={:?})", ty, variance);
|
|
match ty.kind(Interner) {
|
|
TyKind::Scalar(_) | TyKind::Never | TyKind::Str | TyKind::Foreign(..) => {
|
|
// leaf type -- noop
|
|
}
|
|
TyKind::FnDef(..) | TyKind::Coroutine(..) | TyKind::Closure(..) => {
|
|
never!("Unexpected unnameable type in variance computation: {:?}", ty);
|
|
}
|
|
TyKind::Ref(mutbl, lifetime, ty) => {
|
|
self.add_constraints_from_region(lifetime, variance);
|
|
self.add_constraints_from_mt(ty, *mutbl, variance);
|
|
}
|
|
TyKind::Array(typ, len) => {
|
|
self.add_constraints_from_const(len, variance);
|
|
self.add_constraints_from_ty(typ, variance);
|
|
}
|
|
TyKind::Slice(typ) => {
|
|
self.add_constraints_from_ty(typ, variance);
|
|
}
|
|
TyKind::Raw(mutbl, ty) => {
|
|
self.add_constraints_from_mt(ty, *mutbl, variance);
|
|
}
|
|
TyKind::Tuple(_, subtys) => {
|
|
for subty in subtys.type_parameters(Interner) {
|
|
self.add_constraints_from_ty(&subty, variance);
|
|
}
|
|
}
|
|
TyKind::Adt(def, args) => {
|
|
self.add_constraints_from_args(def.0.into(), args.as_slice(Interner), variance);
|
|
}
|
|
TyKind::Alias(AliasTy::Opaque(opaque)) => {
|
|
self.add_constraints_from_invariant_args(
|
|
opaque.substitution.as_slice(Interner),
|
|
variance,
|
|
);
|
|
}
|
|
TyKind::Alias(AliasTy::Projection(proj)) => {
|
|
self.add_constraints_from_invariant_args(
|
|
proj.substitution.as_slice(Interner),
|
|
variance,
|
|
);
|
|
}
|
|
// FIXME: check this
|
|
TyKind::AssociatedType(_, subst) => {
|
|
self.add_constraints_from_invariant_args(subst.as_slice(Interner), variance);
|
|
}
|
|
// FIXME: check this
|
|
TyKind::OpaqueType(_, subst) => {
|
|
self.add_constraints_from_invariant_args(subst.as_slice(Interner), variance);
|
|
}
|
|
TyKind::Dyn(it) => {
|
|
// The type `dyn Trait<T> +'a` is covariant w/r/t `'a`:
|
|
self.add_constraints_from_region(&it.lifetime, variance);
|
|
|
|
if let Some(trait_ref) = it.principal() {
|
|
// Trait are always invariant so we can take advantage of that.
|
|
self.add_constraints_from_invariant_args(
|
|
trait_ref
|
|
.map(|it| it.map(|it| it.substitution.clone()))
|
|
.substitute(
|
|
Interner,
|
|
&[GenericArg::new(
|
|
Interner,
|
|
chalk_ir::GenericArgData::Ty(TyKind::Error.intern(Interner)),
|
|
)],
|
|
)
|
|
.skip_binders()
|
|
.as_slice(Interner),
|
|
variance,
|
|
);
|
|
}
|
|
|
|
// FIXME
|
|
// for projection in data.projection_bounds() {
|
|
// match projection.skip_binder().term.unpack() {
|
|
// TyKind::TermKind::Ty(ty) => {
|
|
// self.add_constraints_from_ty( ty, self.invariant);
|
|
// }
|
|
// TyKind::TermKind::Const(c) => {
|
|
// self.add_constraints_from_const( c, self.invariant)
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// Chalk has no params, so use placeholders for now?
|
|
TyKind::Placeholder(index) => {
|
|
let idx = crate::from_placeholder_idx(self.db, *index).0;
|
|
let index = self.generics.type_or_const_param_idx(idx).unwrap();
|
|
self.constrain(index, variance);
|
|
}
|
|
TyKind::Function(f) => {
|
|
self.add_constraints_from_sig(
|
|
f.substitution.0.iter(Interner).filter_map(move |p| p.ty(Interner)),
|
|
variance,
|
|
);
|
|
}
|
|
TyKind::Error => {
|
|
// we encounter this when walking the trait references for object
|
|
// types, where we use Error as the Self type
|
|
}
|
|
TyKind::CoroutineWitness(..) | TyKind::BoundVar(..) | TyKind::InferenceVar(..) => {
|
|
never!("unexpected type encountered in variance inference: {:?}", ty)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_constraints_from_invariant_args(&mut self, args: &[GenericArg], variance: Variance) {
|
|
let variance_i = variance.invariant();
|
|
|
|
for k in args {
|
|
match k.data(Interner) {
|
|
GenericArgData::Lifetime(lt) => self.add_constraints_from_region(lt, variance_i),
|
|
GenericArgData::Ty(ty) => self.add_constraints_from_ty(ty, variance_i),
|
|
GenericArgData::Const(val) => self.add_constraints_from_const(val, variance_i),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds constraints appropriate for a nominal type (enum, struct,
|
|
/// object, etc) appearing in a context with ambient variance `variance`
|
|
fn add_constraints_from_args(
|
|
&mut self,
|
|
def_id: GenericDefId,
|
|
args: &[GenericArg],
|
|
variance: Variance,
|
|
) {
|
|
// We don't record `inferred_starts` entries for empty generics.
|
|
if args.is_empty() {
|
|
return;
|
|
}
|
|
let Some(variances) = self.db.variances_of(def_id) else {
|
|
return;
|
|
};
|
|
|
|
for (i, k) in args.iter().enumerate() {
|
|
match k.data(Interner) {
|
|
GenericArgData::Lifetime(lt) => {
|
|
self.add_constraints_from_region(lt, variance.xform(variances[i]))
|
|
}
|
|
GenericArgData::Ty(ty) => {
|
|
self.add_constraints_from_ty(ty, variance.xform(variances[i]))
|
|
}
|
|
GenericArgData::Const(val) => self.add_constraints_from_const(val, variance),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds constraints appropriate for a const expression `val`
|
|
/// in a context with ambient variance `variance`
|
|
fn add_constraints_from_const(&mut self, c: &Const, variance: Variance) {
|
|
match &c.data(Interner).value {
|
|
chalk_ir::ConstValue::Concrete(c) => {
|
|
if let ConstScalar::UnevaluatedConst(_, subst) = &c.interned {
|
|
self.add_constraints_from_invariant_args(subst.as_slice(Interner), variance);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// Adds constraints appropriate for a function with signature
|
|
/// `sig` appearing in a context with ambient variance `variance`
|
|
fn add_constraints_from_sig<'a>(
|
|
&mut self,
|
|
mut sig_tys: impl DoubleEndedIterator<Item = &'a Ty>,
|
|
variance: Variance,
|
|
) {
|
|
let contra = variance.contravariant();
|
|
let Some(output) = sig_tys.next_back() else {
|
|
return never!("function signature has no return type");
|
|
};
|
|
self.add_constraints_from_ty(output, variance);
|
|
for input in sig_tys {
|
|
self.add_constraints_from_ty(input, contra);
|
|
}
|
|
}
|
|
|
|
/// Adds constraints appropriate for a region appearing in a
|
|
/// context with ambient variance `variance`
|
|
fn add_constraints_from_region(&mut self, region: &Lifetime, variance: Variance) {
|
|
tracing::debug!(
|
|
"add_constraints_from_region(region={:?}, variance={:?})",
|
|
region,
|
|
variance
|
|
);
|
|
match region.data(Interner) {
|
|
LifetimeData::Placeholder(index) => {
|
|
let idx = crate::lt_from_placeholder_idx(self.db, *index).0;
|
|
let inferred = self.generics.lifetime_idx(idx).unwrap();
|
|
self.constrain(inferred, variance);
|
|
}
|
|
LifetimeData::Static => {}
|
|
LifetimeData::BoundVar(..) => {
|
|
// Either a higher-ranked region inside of a type or a
|
|
// late-bound function parameter.
|
|
//
|
|
// We do not compute constraints for either of these.
|
|
}
|
|
LifetimeData::Error => {}
|
|
LifetimeData::Phantom(..) | LifetimeData::InferenceVar(..) | LifetimeData::Erased => {
|
|
// We don't expect to see anything but 'static or bound
|
|
// regions when visiting member types or method types.
|
|
never!(
|
|
"unexpected region encountered in variance \
|
|
inference: {:?}",
|
|
region
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds constraints appropriate for a mutability-type pair
|
|
/// appearing in a context with ambient variance `variance`
|
|
fn add_constraints_from_mt(&mut self, ty: &Ty, mt: Mutability, variance: Variance) {
|
|
self.add_constraints_from_ty(
|
|
ty,
|
|
match mt {
|
|
Mutability::Mut => variance.invariant(),
|
|
Mutability::Not => variance,
|
|
},
|
|
);
|
|
}
|
|
|
|
fn constrain(&mut self, index: usize, variance: Variance) {
|
|
tracing::debug!(
|
|
"constrain(index={:?}, variance={:?}, to={:?})",
|
|
index,
|
|
self.variances[index],
|
|
variance
|
|
);
|
|
self.variances[index] = self.variances[index].glb(variance);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use expect_test::{Expect, expect};
|
|
use hir_def::{
|
|
AdtId, GenericDefId, ModuleDefId, hir::generics::GenericParamDataRef, src::HasSource,
|
|
};
|
|
use itertools::Itertools;
|
|
use stdx::format_to;
|
|
use syntax::{AstNode, ast::HasName};
|
|
use test_fixture::WithFixture;
|
|
|
|
use hir_def::Lookup;
|
|
|
|
use crate::{db::HirDatabase, test_db::TestDB, variance::generics};
|
|
|
|
#[test]
|
|
fn phantom_data() {
|
|
check(
|
|
r#"
|
|
//- minicore: phantom_data
|
|
|
|
struct Covariant<A> {
|
|
t: core::marker::PhantomData<A>
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
Covariant[A: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_types() {
|
|
check(
|
|
r#"
|
|
//- minicore: cell
|
|
|
|
use core::cell::UnsafeCell;
|
|
|
|
struct InvariantMut<'a,A:'a,B:'a> { //~ ERROR ['a: +, A: o, B: o]
|
|
t: &'a mut (A,B)
|
|
}
|
|
|
|
struct InvariantCell<A> { //~ ERROR [A: o]
|
|
t: UnsafeCell<A>
|
|
}
|
|
|
|
struct InvariantIndirect<A> { //~ ERROR [A: o]
|
|
t: InvariantCell<A>
|
|
}
|
|
|
|
struct Covariant<A> { //~ ERROR [A: +]
|
|
t: A, u: fn() -> A
|
|
}
|
|
|
|
struct Contravariant<A> { //~ ERROR [A: -]
|
|
t: fn(A)
|
|
}
|
|
|
|
enum Enum<A,B,C> { //~ ERROR [A: +, B: -, C: o]
|
|
Foo(Covariant<A>),
|
|
Bar(Contravariant<B>),`
|
|
Zed(Covariant<C>,Contravariant<C>)
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
InvariantMut['a: covariant, A: invariant, B: invariant]
|
|
InvariantCell[A: invariant]
|
|
InvariantIndirect[A: invariant]
|
|
Covariant[A: covariant]
|
|
Contravariant[A: contravariant]
|
|
Enum[A: covariant, B: contravariant, C: invariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn type_resolve_error_two_structs_deep() {
|
|
check(
|
|
r#"
|
|
struct Hello<'a> {
|
|
missing: Missing<'a>,
|
|
}
|
|
|
|
struct Other<'a> {
|
|
hello: Hello<'a>,
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
Hello['a: bivariant]
|
|
Other['a: bivariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_associated_consts() {
|
|
// FIXME: Should be invariant
|
|
check(
|
|
r#"
|
|
trait Trait {
|
|
const Const: usize;
|
|
}
|
|
|
|
struct Foo<T: Trait> { //~ ERROR [T: o]
|
|
field: [u8; <T as Trait>::Const]
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
Foo[T: bivariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_associated_types() {
|
|
check(
|
|
r#"
|
|
trait Trait<'a> {
|
|
type Type;
|
|
|
|
fn method(&'a self) { }
|
|
}
|
|
|
|
struct Foo<'a, T : Trait<'a>> { //~ ERROR ['a: +, T: +]
|
|
field: (T, &'a ())
|
|
}
|
|
|
|
struct Bar<'a, T : Trait<'a>> { //~ ERROR ['a: o, T: o]
|
|
field: <T as Trait<'a>>::Type
|
|
}
|
|
|
|
"#,
|
|
expect![[r#"
|
|
method[Self: contravariant, 'a: contravariant]
|
|
Foo['a: covariant, T: covariant]
|
|
Bar['a: invariant, T: invariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_associated_types2() {
|
|
// FIXME: RPITs have variance, but we can't treat them as their own thing right now
|
|
check(
|
|
r#"
|
|
trait Foo {
|
|
type Bar;
|
|
}
|
|
|
|
fn make() -> *const dyn Foo<Bar = &'static u32> {}
|
|
"#,
|
|
expect![""],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_trait_bounds() {
|
|
check(
|
|
r#"
|
|
trait Getter<T> {
|
|
fn get(&self) -> T;
|
|
}
|
|
|
|
trait Setter<T> {
|
|
fn get(&self, _: T);
|
|
}
|
|
|
|
struct TestStruct<U,T:Setter<U>> { //~ ERROR [U: +, T: +]
|
|
t: T, u: U
|
|
}
|
|
|
|
enum TestEnum<U,T:Setter<U>> { //~ ERROR [U: *, T: +]
|
|
//~^ ERROR: `U` is never used
|
|
Foo(T)
|
|
}
|
|
|
|
struct TestContraStruct<U,T:Setter<U>> { //~ ERROR [U: *, T: +]
|
|
//~^ ERROR: `U` is never used
|
|
t: T
|
|
}
|
|
|
|
struct TestBox<U,T:Getter<U>+Setter<U>> { //~ ERROR [U: *, T: +]
|
|
//~^ ERROR: `U` is never used
|
|
t: T
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
get[Self: contravariant, T: covariant]
|
|
get[Self: contravariant, T: contravariant]
|
|
TestStruct[U: covariant, T: covariant]
|
|
TestEnum[U: bivariant, T: covariant]
|
|
TestContraStruct[U: bivariant, T: covariant]
|
|
TestBox[U: bivariant, T: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_trait_matching() {
|
|
check(
|
|
r#"
|
|
|
|
trait Get<T> {
|
|
fn get(&self) -> T;
|
|
}
|
|
|
|
struct Cloner<T:Clone> {
|
|
t: T
|
|
}
|
|
|
|
impl<T:Clone> Get<T> for Cloner<T> {
|
|
fn get(&self) -> T {}
|
|
}
|
|
|
|
fn get<'a, G>(get: &G) -> i32
|
|
where G : Get<&'a i32>
|
|
{}
|
|
|
|
fn pick<'b, G>(get: &'b G, if_odd: &'b i32) -> i32
|
|
where G : Get<&'b i32>
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
get[Self: contravariant, T: covariant]
|
|
Cloner[T: covariant]
|
|
get[T: invariant]
|
|
get['a: invariant, G: contravariant]
|
|
pick['b: contravariant, G: contravariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_trait_object_bound() {
|
|
check(
|
|
r#"
|
|
enum Option<T> {
|
|
Some(T),
|
|
None
|
|
}
|
|
trait T { fn foo(&self); }
|
|
|
|
struct TOption<'a> { //~ ERROR ['a: +]
|
|
v: Option<*const (dyn T + 'a)>,
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
Option[T: covariant]
|
|
foo[Self: contravariant]
|
|
TOption['a: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_types_bounds() {
|
|
check(
|
|
r#"
|
|
//- minicore: send
|
|
struct TestImm<A, B> { //~ ERROR [A: +, B: +]
|
|
x: A,
|
|
y: B,
|
|
}
|
|
|
|
struct TestMut<A, B:'static> { //~ ERROR [A: +, B: o]
|
|
x: A,
|
|
y: &'static mut B,
|
|
}
|
|
|
|
struct TestIndirect<A:'static, B:'static> { //~ ERROR [A: +, B: o]
|
|
m: TestMut<A, B>
|
|
}
|
|
|
|
struct TestIndirect2<A:'static, B:'static> { //~ ERROR [A: o, B: o]
|
|
n: TestMut<A, B>,
|
|
m: TestMut<B, A>
|
|
}
|
|
|
|
trait Getter<A> {
|
|
fn get(&self) -> A;
|
|
}
|
|
|
|
trait Setter<A> {
|
|
fn set(&mut self, a: A);
|
|
}
|
|
|
|
struct TestObject<A, R> { //~ ERROR [A: o, R: o]
|
|
n: *const (dyn Setter<A> + Send),
|
|
m: *const (dyn Getter<R> + Send),
|
|
}
|
|
"#,
|
|
expect![[r#"
|
|
TestImm[A: covariant, B: covariant]
|
|
TestMut[A: covariant, B: invariant]
|
|
TestIndirect[A: covariant, B: invariant]
|
|
TestIndirect2[A: invariant, B: invariant]
|
|
get[Self: contravariant, A: covariant]
|
|
set[Self: invariant, A: contravariant]
|
|
TestObject[A: invariant, R: invariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_unused_region_param() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<'a> { x: u32 } //~ ERROR parameter `'a` is never used
|
|
enum SomeEnum<'a> { Nothing } //~ ERROR parameter `'a` is never used
|
|
trait SomeTrait<'a> { fn foo(&self); } // OK on traits.
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct['a: bivariant]
|
|
SomeEnum['a: bivariant]
|
|
foo[Self: contravariant, 'a: invariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_unused_type_param() {
|
|
check(
|
|
r#"
|
|
//- minicore: sized
|
|
struct SomeStruct<A> { x: u32 }
|
|
enum SomeEnum<A> { Nothing }
|
|
enum ListCell<T> {
|
|
Cons(*const ListCell<T>),
|
|
Nil
|
|
}
|
|
|
|
struct SelfTyAlias<T>(*const Self);
|
|
struct WithBounds<T: Sized> {}
|
|
struct WithWhereBounds<T> where T: Sized {}
|
|
struct WithOutlivesBounds<T: 'static> {}
|
|
struct DoubleNothing<T> {
|
|
s: SomeStruct<T>,
|
|
}
|
|
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[A: bivariant]
|
|
SomeEnum[A: bivariant]
|
|
ListCell[T: bivariant]
|
|
SelfTyAlias[T: bivariant]
|
|
WithBounds[T: bivariant]
|
|
WithWhereBounds[T: bivariant]
|
|
WithOutlivesBounds[T: bivariant]
|
|
DoubleNothing[T: bivariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_use_contravariant_struct1() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<T>(fn(T));
|
|
|
|
fn foo<'min,'max>(v: SomeStruct<&'max ()>)
|
|
-> SomeStruct<&'min ()>
|
|
where 'max : 'min
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[T: contravariant]
|
|
foo['min: contravariant, 'max: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_use_contravariant_struct2() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<T>(fn(T));
|
|
|
|
fn bar<'min,'max>(v: SomeStruct<&'min ()>)
|
|
-> SomeStruct<&'max ()>
|
|
where 'max : 'min
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[T: contravariant]
|
|
bar['min: covariant, 'max: contravariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_use_covariant_struct1() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<T>(T);
|
|
|
|
fn foo<'min,'max>(v: SomeStruct<&'min ()>)
|
|
-> SomeStruct<&'max ()>
|
|
where 'max : 'min
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[T: covariant]
|
|
foo['min: contravariant, 'max: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_use_covariant_struct2() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<T>(T);
|
|
|
|
fn foo<'min,'max>(v: SomeStruct<&'max ()>)
|
|
-> SomeStruct<&'min ()>
|
|
where 'max : 'min
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[T: covariant]
|
|
foo['min: covariant, 'max: contravariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rustc_test_variance_use_invariant_struct1() {
|
|
check(
|
|
r#"
|
|
struct SomeStruct<T>(*mut T);
|
|
|
|
fn foo<'min,'max>(v: SomeStruct<&'max ()>)
|
|
-> SomeStruct<&'min ()>
|
|
where 'max : 'min
|
|
{}
|
|
|
|
fn bar<'min,'max>(v: SomeStruct<&'min ()>)
|
|
-> SomeStruct<&'max ()>
|
|
where 'max : 'min
|
|
{}
|
|
"#,
|
|
expect![[r#"
|
|
SomeStruct[T: invariant]
|
|
foo['min: invariant, 'max: invariant]
|
|
bar['min: invariant, 'max: invariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_arg_counts() {
|
|
check(
|
|
r#"
|
|
struct S<T>(T);
|
|
struct S2<T>(S<>);
|
|
struct S3<T>(S<T, T>);
|
|
"#,
|
|
expect![[r#"
|
|
S[T: covariant]
|
|
S2[T: bivariant]
|
|
S3[T: covariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn prove_fixedpoint() {
|
|
check(
|
|
r#"
|
|
struct FixedPoint<T, U, V>(&'static FixedPoint<(), T, U>, V);
|
|
"#,
|
|
expect![[r#"
|
|
FixedPoint[T: bivariant, U: bivariant, V: bivariant]
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: Expect) {
|
|
// use tracing_subscriber::{layer::SubscriberExt, Layer};
|
|
// let my_layer = tracing_subscriber::fmt::layer();
|
|
// let _g = tracing::subscriber::set_default(tracing_subscriber::registry().with(
|
|
// my_layer.with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
|
|
// metadata.target().starts_with("hir_ty::variance")
|
|
// })),
|
|
// ));
|
|
let (db, file_id) = TestDB::with_single_file(ra_fixture);
|
|
|
|
let mut defs: Vec<GenericDefId> = Vec::new();
|
|
let module = db.module_for_file_opt(file_id.file_id(&db)).unwrap();
|
|
let def_map = module.def_map(&db);
|
|
crate::tests::visit_module(&db, def_map, module.local_id, &mut |it| {
|
|
defs.push(match it {
|
|
ModuleDefId::FunctionId(it) => it.into(),
|
|
ModuleDefId::AdtId(it) => it.into(),
|
|
ModuleDefId::ConstId(it) => it.into(),
|
|
ModuleDefId::TraitId(it) => it.into(),
|
|
ModuleDefId::TypeAliasId(it) => it.into(),
|
|
_ => return,
|
|
})
|
|
});
|
|
let defs = defs
|
|
.into_iter()
|
|
.filter_map(|def| {
|
|
Some((
|
|
def,
|
|
match def {
|
|
GenericDefId::FunctionId(it) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::AdtId(AdtId::EnumId(it)) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::AdtId(AdtId::StructId(it)) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::AdtId(AdtId::UnionId(it)) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::TraitId(it) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::TypeAliasId(it) => {
|
|
let loc = it.lookup(&db);
|
|
loc.source(&db).value.name().unwrap()
|
|
}
|
|
GenericDefId::ImplId(_) => return None,
|
|
GenericDefId::ConstId(_) => return None,
|
|
GenericDefId::StaticId(_) => return None,
|
|
},
|
|
))
|
|
})
|
|
.sorted_by_key(|(_, n)| n.syntax().text_range().start());
|
|
let mut res = String::new();
|
|
for (def, name) in defs {
|
|
let Some(variances) = db.variances_of(def) else {
|
|
continue;
|
|
};
|
|
format_to!(
|
|
res,
|
|
"{name}[{}]\n",
|
|
generics(&db, def)
|
|
.iter()
|
|
.map(|(_, param)| match param {
|
|
GenericParamDataRef::TypeParamData(type_param_data) => {
|
|
type_param_data.name.as_ref().unwrap()
|
|
}
|
|
GenericParamDataRef::ConstParamData(const_param_data) =>
|
|
&const_param_data.name,
|
|
GenericParamDataRef::LifetimeParamData(lifetime_param_data) => {
|
|
&lifetime_param_data.name
|
|
}
|
|
})
|
|
.zip_eq(&*variances)
|
|
.format_with(", ", |(name, var), f| f(&format_args!(
|
|
"{}: {var}",
|
|
name.as_str()
|
|
)))
|
|
);
|
|
}
|
|
|
|
expected.assert_eq(&res);
|
|
}
|
|
}
|