Repository: rust-lang/rust-analyzer
Pull request #18774 (2024-12-28 14:08:51)

Implement parameter variance inference
This lacks any support for impl trait in type aliases (assoc or free) and functions are not fully supported as we lack lifetime elision. RPITs should also be supported here, but we don't count them as `GenericDefId` yet so.

The inference does not work perfectly unfortunately as it may run into query cycles which we would need to resolve via fixpoint iteration which current salsa does not support (see module doc). Likewise, Const expressions can affect whether a parameter turns invariant which we also lack sometimes.


Patch:
diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs
index 55d0edd5e0..0a2612219a 100644
--- a/crates/hir-ty/src/chalk_db.rs
+++ b/crates/hir-ty/src/chalk_db.rs
@@ -950,11 +950,18 @@ pub(crate) fn fn_def_datum_query(db: &dyn HirDatabase, fn_def_id: FnDefId) -> Ar
 
 pub(crate) fn fn_def_variance_query(db: &dyn HirDatabase, fn_def_id: FnDefId) -> Variances {
     let callable_def: CallableDefId = from_chalk(db, fn_def_id);
-    let generic_params =
-        generics(db.upcast(), GenericDefId::from_callable(db.upcast(), callable_def));
     Variances::from_iter(
         Interner,
-        std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
+        db.variances_of(GenericDefId::from_callable(db.upcast(), callable_def))
+            .as_deref()
+            .unwrap_or_default()
+            .iter()
+            .map(|v| match v {
+                crate::variance::Variance::Covariant => chalk_ir::Variance::Covariant,
+                crate::variance::Variance::Invariant => chalk_ir::Variance::Invariant,
+                crate::variance::Variance::Contravariant => chalk_ir::Variance::Contravariant,
+                crate::variance::Variance::Bivariant => chalk_ir::Variance::Invariant,
+            }),
     )
 }
 
@@ -962,10 +969,14 @@ pub(crate) fn adt_variance_query(
     db: &dyn HirDatabase,
     chalk_ir::AdtId(adt_id): AdtId,
 ) -> Variances {
-    let generic_params = generics(db.upcast(), adt_id.into());
     Variances::from_iter(
         Interner,
-        std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
+        db.variances_of(adt_id.into()).as_deref().unwrap_or_default().iter().map(|v| match v {
+            crate::variance::Variance::Covariant => chalk_ir::Variance::Covariant,
+            crate::variance::Variance::Invariant => chalk_ir::Variance::Invariant,
+            crate::variance::Variance::Contravariant => chalk_ir::Variance::Contravariant,
+            crate::variance::Variance::Bivariant => chalk_ir::Variance::Invariant,
+        }),
     )
 }
 
diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs
index 302558162a..51c178b90d 100644
--- a/crates/hir-ty/src/chalk_ext.rs
+++ b/crates/hir-ty/src/chalk_ext.rs
@@ -443,13 +443,25 @@ impl ProjectionTyExt for ProjectionTy {
 }
 
 pub trait DynTyExt {
-    fn principal(&self) -> Option<&TraitRef>;
+    fn principal(&self) -> Option<Binders<Binders<&TraitRef>>>;
+    fn principal_id(&self) -> Option<chalk_ir::TraitId<Interner>>;
 }
 
 impl DynTyExt for DynTy {
-    fn principal(&self) -> Option<&TraitRef> {
+    fn principal(&self) -> Option<Binders<Binders<&TraitRef>>> {
+        self.bounds.as_ref().filter_map(|bounds| {
+            bounds.interned().first().and_then(|b| {
+                b.as_ref().filter_map(|b| match b {
+                    crate::WhereClause::Implemented(trait_ref) => Some(trait_ref),
+                    _ => None,
+                })
+            })
+        })
+    }
+
+    fn principal_id(&self) -> Option<chalk_ir::TraitId<Interner>> {
         self.bounds.skip_binders().interned().first().and_then(|b| match b.skip_binders() {
-            crate::WhereClause::Implemented(trait_ref) => Some(trait_ref),
+            crate::WhereClause::Implemented(trait_ref) => Some(trait_ref.trait_id),
             _ => None,
         })
     }
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index 6856eaa3e0..6b05682667 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -271,6 +271,10 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[ra_salsa::invoke(chalk_db::adt_variance_query)]
     fn adt_variance(&self, adt_id: chalk_db::AdtId) -> chalk_db::Variances;
 
+    #[ra_salsa::invoke(crate::variance::variances_of)]
+    #[ra_salsa::cycle(crate::variance::variances_of_cycle)]
+    fn variances_of(&self, def: GenericDefId) -> Option<Arc<[crate::variance::Variance]>>;
+
     #[ra_salsa::invoke(chalk_db::associated_ty_value_query)]
     fn associated_ty_value(
         &self,
diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs
index fe7541d237..abbf2a4f2e 100644
--- a/crates/hir-ty/src/generics.rs
+++ b/crates/hir-ty/src/generics.rs
@@ -26,14 +26,14 @@ use triomphe::Arc;
 
 use crate::{db::HirDatabase, lt_to_placeholder_idx, to_placeholder_idx, Interner, Substitution};
 
-pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics {
+pub fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics {
     let parent_generics = parent_generic_def(db, def).map(|def| Box::new(generics(db, def)));
     let params = db.generic_params(def);
     let has_trait_self_param = params.trait_self_param().is_some();
     Generics { def, params, parent_generics, has_trait_self_param }
 }
 #[derive(Clone, Debug)]
-pub(crate) struct Generics {
+pub struct Generics {
     def: GenericDefId,
     params: Arc<GenericParams>,
     parent_generics: Option<Box<Generics>>,
@@ -153,7 +153,7 @@ impl Generics {
         (parent_len, self_param, type_params, const_params, impl_trait_params, lifetime_params)
     }
 
-    pub(crate) fn type_or_const_param_idx(&self, param: TypeOrConstParamId) -> Option<usize> {
+    pub fn type_or_const_param_idx(&self, param: TypeOrConstParamId) -> Option<usize> {
         self.find_type_or_const_param(param)
     }
 
@@ -174,7 +174,7 @@ impl Generics {
         }
     }
 
-    pub(crate) fn lifetime_idx(&self, lifetime: LifetimeParamId) -> Option<usize> {
+    pub fn lifetime_idx(&self, lifetime: LifetimeParamId) -> Option<usize> {
         self.find_lifetime(lifetime)
     }
 
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index 5a251683b9..c59013beaf 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -96,8 +96,8 @@ impl InferenceContext<'_> {
                     .map(|b| b.into_value_and_skipped_binders().0);
                 self.deduce_closure_kind_from_predicate_clauses(clauses)
             }
-            TyKind::Dyn(dyn_ty) => dyn_ty.principal().and_then(|trait_ref| {
-                self.fn_trait_kind_from_trait_id(from_chalk_trait_id(trait_ref.trait_id))
+            TyKind::Dyn(dyn_ty) => dyn_ty.principal_id().and_then(|trait_id| {
+                self.fn_trait_kind_from_trait_id(from_chalk_trait_id(trait_id))
             }),
             TyKind::InferenceVar(ty, chalk_ir::TyVariableKind::General) => {
                 let clauses = self.clauses_for_self_ty(*ty);
diff --git a/crates/hir-ty/src/lang_items.rs b/crates/hir-ty/src/lang_items.rs
index f704b59d30..ff9c52fbb6 100644
--- a/crates/hir-ty/src/lang_items.rs
+++ b/crates/hir-ty/src/lang_items.rs
@@ -13,6 +13,7 @@ pub fn is_box(db: &dyn HirDatabase, adt: AdtId) -> bool {
 
 pub fn is_unsafe_cell(db: &dyn HirDatabase, adt: AdtId) -> bool {
     let AdtId::StructId(id) = adt else { return false };
+
     db.struct_data(id).flags.contains(StructFlags::IS_UNSAFE_CELL)
 }
 
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 224fcf313a..3c18ea9281 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -24,7 +24,6 @@ extern crate ra_ap_rustc_pattern_analysis as rustc_pattern_analysis;
 mod builder;
 mod chalk_db;
 mod chalk_ext;
-mod generics;
 mod infer;
 mod inhabitedness;
 mod interner;
@@ -39,6 +38,7 @@ pub mod db;
 pub mod diagnostics;
 pub mod display;
 pub mod dyn_compatibility;
+pub mod generics;
 pub mod lang_items;
 pub mod layout;
 pub mod method_resolution;
@@ -50,6 +50,7 @@ pub mod traits;
 mod test_db;
 #[cfg(test)]
 mod tests;
+mod variance;
 
 use std::hash::Hash;
 
@@ -88,10 +89,9 @@ pub use infer::{
     PointerCast,
 };
 pub use interner::Interner;
-pub use lower::diagnostics::*;
 pub use lower::{
-    associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId,
-    TyLoweringContext, ValueTyDefId,
+    associated_type_shorthand_candidates, diagnostics::*, ImplTraitLoweringMode, ParamLoweringMode,
+    TyDefId, TyLoweringContext, ValueTyDefId,
 };
 pub use mapping::{
     from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx,
@@ -101,6 +101,7 @@ pub use mapping::{
 pub use method_resolution::check_orphan_rules;
 pub use traits::TraitEnvironment;
 pub use utils::{all_super_traits, direct_super_traits, is_fn_unsafe_to_call};
+pub use variance::Variance;
 
 pub use chalk_ir::{
     cast::Cast,
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index 5a72b97653..952580c3b7 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -805,8 +805,8 @@ fn is_inherent_impl_coherent(
         | TyKind::Scalar(_) => def_map.is_rustc_coherence_is_core(),
 
         &TyKind::Adt(AdtId(adt), _) => adt.module(db.upcast()).krate() == def_map.krate(),
-        TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| {
-            from_chalk_trait_id(trait_ref.trait_id).module(db.upcast()).krate() == def_map.krate()
+        TyKind::Dyn(it) => it.principal_id().map_or(false, |trait_id| {
+            from_chalk_trait_id(trait_id).module(db.upcast()).krate() == def_map.krate()
         }),
 
         _ => true,
@@ -834,9 +834,8 @@ fn is_inherent_impl_coherent(
                     .contains(StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL),
                 hir_def::AdtId::EnumId(it) => db.enum_data(it).rustc_has_incoherent_inherent_impls,
             },
-            TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| {
-                db.trait_data(from_chalk_trait_id(trait_ref.trait_id))
-                    .rustc_has_incoherent_inherent_impls
+            TyKind::Dyn(it) => it.principal_id().map_or(false, |trait_id| {
+                db.trait_data(from_chalk_trait_id(trait_id)).rustc_has_incoherent_inherent_impls
             }),
 
             _ => false,
@@ -896,8 +895,8 @@ pub fn check_orphan_rules(db: &dyn HirDatabase, impl_: ImplId) -> bool {
         match unwrap_fundamental(ty).kind(Interner) {
             &TyKind::Adt(AdtId(id), _) => is_local(id.module(db.upcast()).krate()),
             TyKind::Error => true,
-            TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| {
-                is_local(from_chalk_trait_id(trait_ref.trait_id).module(db.upcast()).krate())
+            TyKind::Dyn(it) => it.principal_id().map_or(false, |trait_id| {
+                is_local(from_chalk_trait_id(trait_id).module(db.upcast()).krate())
             }),
             _ => false,
         }
diff --git a/crates/hir-ty/src/variance.rs b/crates/hir-ty/src/variance.rs
new file mode 100644
index 0000000000..30711b16df
--- /dev/null
+++ b/crates/hir-ty/src/variance.rs
@@ -0,0 +1,1065 @@
+//! 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 base_db::ra_salsa::Cycle;
+use chalk_ir::Mutability;
+use hir_def::data::adt::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_data(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.upcast(), 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(
+    db: &dyn HirDatabase,
+    _cycle: &Cycle,
+    def: &GenericDefId,
+) -> Option<Arc<[Variance]>> {
+    let generics = generics(db.upcast(), *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) => {
+                        db.enum_data(e).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);
+                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);
+                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::{
+        generics::GenericParamDataRef, src::HasSource, AdtId, GenericDefId, ModuleDefId,
+    };
+    use itertools::Itertools;
+    use stdx::format_to;
+    use syntax::{ast::HasName, AstNode};
+    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() {
+        // FIXME: This is wrong, this should be `FixedPoint[T: covariant, U: covariant, V: covariant]`
+        // This is a limitation of current salsa where a cycle may only set a fallback value to the
+        // query result, but we need to solve a fixpoint here. The new salsa will have this
+        // fortunately.
+        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(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).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::TraitAliasId(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::TraitAliasId(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,
+                    },
+                ))
+            })
+            .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);
+    }
+}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ac8a62ee85..f8af04302f 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -101,7 +101,6 @@ pub use crate::{
         PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits,
     },
 };
-pub use hir_ty::method_resolution::TyFingerprint;
 
 // Be careful with these re-exports.
 //
@@ -151,8 +150,9 @@ pub use {
         display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
         dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode},
         layout::LayoutError,
+        method_resolution::TyFingerprint,
         mir::{MirEvalError, MirLowerError},
-        CastError, FnAbi, PointerCast, Safety,
+        CastError, FnAbi, PointerCast, Safety, Variance,
     },
     // FIXME: Properly encapsulate mir
     hir_ty::{mir, Interner as ChalkTyInterner},
@@ -3957,6 +3957,22 @@ impl GenericParam {
             GenericParam::LifetimeParam(it) => it.id.parent.into(),
         }
     }
+
+    pub fn variance(self, db: &dyn HirDatabase) -> Option<Variance> {
+        let parent = match self {
+            GenericParam::TypeParam(it) => it.id.parent(),
+            // const parameters are always invariant
+            GenericParam::ConstParam(_) => return None,
+            GenericParam::LifetimeParam(it) => it.id.parent,
+        };
+        let generics = hir_ty::generics::generics(db.upcast(), parent);
+        let index = match self {
+            GenericParam::TypeParam(it) => generics.type_or_const_param_idx(it.id.into())?,
+            GenericParam::ConstParam(_) => return None,
+            GenericParam::LifetimeParam(it) => generics.lifetime_idx(it.id)?,
+        };
+        db.variances_of(parent)?.get(index).copied()
+    }
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 119a864eb9..d87c15154e 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -594,12 +594,21 @@ pub(super) fn definition(
         _ => None,
     };
 
+    let variance_info = || match def {
+        Definition::GenericParam(it) => it.variance(db).as_ref().map(ToString::to_string),
+        _ => None,
+    };
+
     let mut extra = String::new();
     if hovered_definition {
         if let Some(notable_traits) = render_notable_trait(db, notable_traits, edition) {
             extra.push_str("\n___\n");
             extra.push_str(&notable_traits);
         }
+        if let Some(variance_info) = variance_info() {
+            extra.push_str("\n___\n");
+            extra.push_str(&variance_info);
+        }
         if let Some(layout_info) = layout_info() {
             extra.push_str("\n___\n");
             extra.push_str(&layout_info);
diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs
index 32569d5c3f..7adedba7c4 100644
--- a/crates/parser/src/shortcuts.rs
+++ b/crates/parser/src/shortcuts.rs
@@ -5,7 +5,7 @@
 //! abstract token parsing, and string tokenization as completely separate
 //! layers.
 //!
-//! However, often you do parse text into syntax trees and the glue code for
+//! However, often you do pares text into syntax trees and the glue code for
 //! that needs to live somewhere. Rather than putting it to lexer or parser, we
 //! use a separate shortcuts module for that.
 
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index c8d9e6cc29..85833dab1b 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -175,7 +175,7 @@ fn expand_id(
             });
         let res = match thread {
             Ok(handle) => handle.join(),
-            Err(e) => return Err(e.to_string()),
+            Err(e) => std::panic::resume_unwind(Box::new(e)),
         };
 
         match res {
@@ -223,7 +223,7 @@ fn expand_ra_span(
             });
         let res = match thread {
             Ok(handle) => handle.join(),
-            Err(e) => return Err(e.to_string()),
+            Err(e) => std::panic::resume_unwind(Box::new(e)),
         };
 
         match res {
diff --git a/crates/project-model/src/target_triple.rs b/crates/project-model/src/target_triple.rs
index 4a32212097..92e0c49885 100644
--- a/crates/project-model/src/target_triple.rs
+++ b/crates/project-model/src/target_triple.rs
@@ -23,8 +23,11 @@ pub(super) fn get(
     let sysroot = match config {
         TargetTipleConfig::Cargo(sysroot, cargo_toml) => {
             match cargo_config_build_target(cargo_toml, extra_env, sysroot) {
-                Some(it) => return Ok(it),
-                None => sysroot,
+                Ok(it) => return Ok(it),
+                Err(e) => {
+                    tracing::warn!("failed to run `cargo rustc --print cfg`, falling back to invoking rustc directly: {e}");
+                    sysroot
+                }
             }
         }
         TargetTipleConfig::Rustc(sysroot) => sysroot,
@@ -55,7 +58,7 @@ fn cargo_config_build_target(
     cargo_toml: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
     sysroot: &Sysroot,
-) -> Option<Vec<String>> {
+) -> anyhow::Result<Vec<String>> {
     let mut cargo_config = sysroot.tool(Tool::Cargo);
     cargo_config.envs(extra_env);
     cargo_config
@@ -65,9 +68,7 @@ fn cargo_config_build_target(
     // if successful we receive `build.target = "target-triple"`
     // or `build.target = ["<target 1>", ..]`
     tracing::debug!("Discovering cargo config target by {:?}", cargo_config);
-    // this might be `error: config value `build.target` is not set` in which case we
-    // don't wanna log the error
-    utf8_stdout(cargo_config).and_then(parse_output_cargo_config_build_target).ok()
+    utf8_stdout(cargo_config).and_then(parse_output_cargo_config_build_target)
 }
 
 fn parse_output_cargo_config_build_target(stdout: String) -> anyhow::Result<Vec<String>> {
diff --git a/crates/ra-salsa/ra-salsa-macros/src/query_group.rs b/crates/ra-salsa/ra-salsa-macros/src/query_group.rs
index 88db6093ee..d761a5e798 100644
--- a/crates/ra-salsa/ra-salsa-macros/src/query_group.rs
+++ b/crates/ra-salsa/ra-salsa-macros/src/query_group.rs
@@ -242,7 +242,7 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
         let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage {
             let s = format!("{trait_name}::{fn_name}");
             Some(quote! {
-                let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
+                let _p = tracing::trace_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
             })
         } else {
             None
diff --git a/crates/ra-salsa/src/derived/slot.rs b/crates/ra-salsa/src/derived/slot.rs
index 6c5ccba173..cfe2c48f41 100644
--- a/crates/ra-salsa/src/derived/slot.rs
+++ b/crates/ra-salsa/src/derived/slot.rs
@@ -13,7 +13,7 @@ use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb};
 use parking_lot::{RawRwLock, RwLock};
 use std::ops::Deref;
 use std::sync::atomic::{AtomicBool, Ordering};
-use tracing::{debug, info};
+use tracing::trace;
 
 pub(super) struct Slot<Q>
 where
@@ -126,7 +126,7 @@ where
         // doing any `set` invocations while the query function runs.
         let revision_now = runtime.current_revision();
 
-        info!("{:?}: invoked at {:?}", self, revision_now,);
+        trace!("{:?}: invoked at {:?}", self, revision_now,);
 
         // First, do a check with a read-lock.
         loop {
@@ -152,7 +152,7 @@ where
     ) -> StampedValue<Q::Value> {
         let runtime = db.salsa_runtime();
 
-        debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
+        trace!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
 
         // Check with an upgradable read to see if there is a value
         // already. (This permits other readers but prevents anyone
@@ -184,7 +184,7 @@ where
         // inputs and check whether they are out of date.
         if let Some(memo) = &mut old_memo {
             if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) {
-                info!("{:?}: validated old memoized value", self,);
+                trace!("{:?}: validated old memoized value", self,);
 
                 db.salsa_event(Event {
                     runtime_id: runtime.id(),
@@ -212,7 +212,7 @@ where
         old_memo: Option<Memo<Q::Value>>,
         key: &Q::Key,
     ) -> StampedValue<Q::Value> {
-        tracing::info!("{:?}: executing query", self.database_key_index().debug(db));
+        tracing::trace!("{:?}: executing query", self.database_key_index().debug(db));
 
         db.salsa_event(Event {
             runtime_id: db.salsa_runtime().id(),
@@ -224,7 +224,7 @@ where
         let value = match Cycle::catch(|| Q::execute(db, key.clone())) {
             Ok(v) => v,
             Err(cycle) => {
-                tracing::debug!(
+                tracing::trace!(
                     "{:?}: caught cycle {:?}, have strategy {:?}",
                     self.database_key_index().debug(db),
                     cycle,
@@ -272,9 +272,10 @@ where
             // consumers must be aware of. Becoming *more* durable
             // is not. See the test `constant_to_non_constant`.
             if revisions.durability >= old_memo.revisions.durability && old_memo.value == value {
-                debug!(
+                trace!(
                     "read_upgrade({:?}): value is equal, back-dating to {:?}",
-                    self, old_memo.revisions.changed_at,
+                    self,
+                    old_memo.revisions.changed_at,
                 );
 
                 assert!(old_memo.revisions.changed_at <= revisions.changed_at);
@@ -290,7 +291,7 @@ where
 
         let memo_value = new_value.value.clone();
 
-        debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
+        trace!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
 
         panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions }));
 
@@ -339,9 +340,11 @@ where
             }
 
             QueryState::Memoized(memo) => {
-                debug!(
+                trace!(
                     "{:?}: found memoized value, verified_at={:?}, changed_at={:?}",
-                    self, memo.verified_at, memo.revisions.changed_at,
+                    self,
+                    memo.verified_at,
+                    memo.revisions.changed_at,
                 );
 
                 if memo.verified_at < revision_now {
@@ -355,7 +358,7 @@ where
                     value: value.clone(),
                 };
 
-                info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
+                trace!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
 
                 ProbeState::UpToDate(value)
             }
@@ -387,7 +390,7 @@ where
     }
 
     pub(super) fn invalidate(&self, new_revision: Revision) -> Option<Durability> {
-        tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision);
+        tracing::trace!("Slot::invalidate(new_revision = {:?})", new_revision);
         match &mut *self.state.write() {
             QueryState::Memoized(memo) => {
                 memo.revisions.untracked = true;
@@ -411,9 +414,11 @@ where
 
         db.unwind_if_cancelled();
 
-        debug!(
+        trace!(
             "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}",
-            self, revision, revision_now,
+            self,
+            revision,
+            revision_now,
         );
 
         // Do an initial probe with just the read-lock.
@@ -680,9 +685,11 @@ where
         assert!(self.verified_at != revision_now);
         let verified_at = self.verified_at;
 
-        debug!(
+        trace!(
             "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}",
-            verified_at, revision_now, self.revisions.inputs
+            verified_at,
+            revision_now,
+            self.revisions.inputs
         );
 
         if self.check_durability(db.salsa_runtime()) {
@@ -708,7 +715,7 @@ where
                 let changed_input =
                     inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at));
                 if let Some(input) = changed_input {
-                    debug!("validate_memoized_value: `{:?}` may have changed", input);
+                    trace!("validate_memoized_value: `{:?}` may have changed", input);
 
                     return false;
                 }
@@ -721,7 +728,7 @@ where
     /// True if this memo is known not to have changed based on its durability.
     fn check_durability(&self, runtime: &Runtime) -> bool {
         let last_changed = runtime.last_changed_revision(self.revisions.durability);
-        debug!(
+        trace!(
             "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
             last_changed,
             self.verified_at,
diff --git a/crates/ra-salsa/src/derived_lru/slot.rs b/crates/ra-salsa/src/derived_lru/slot.rs
index ff9cc4eade..73a5e07aa0 100644
--- a/crates/ra-salsa/src/derived_lru/slot.rs
+++ b/crates/ra-salsa/src/derived_lru/slot.rs
@@ -17,7 +17,7 @@ use parking_lot::{RawRwLock, RwLock};
 use std::marker::PhantomData;
 use std::ops::Deref;
 use std::sync::atomic::{AtomicBool, Ordering};
-use tracing::{debug, info};
+use tracing::trace;
 
 pub(super) struct Slot<Q, MP>
 where
@@ -140,7 +140,7 @@ where
         // doing any `set` invocations while the query function runs.
         let revision_now = runtime.current_revision();
 
-        info!("{:?}: invoked at {:?}", self, revision_now,);
+        trace!("{:?}: invoked at {:?}", self, revision_now,);
 
         // First, do a check with a read-lock.
         loop {
@@ -168,7 +168,7 @@ where
     ) -> StampedValue<Q::Value> {
         let runtime = db.salsa_runtime();
 
-        debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
+        trace!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
 
         // Check with an upgradable read to see if there is a value
         // already. (This permits other readers but prevents anyone
@@ -202,7 +202,7 @@ where
         // inputs and check whether they are out of date.
         if let Some(memo) = &mut old_memo {
             if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) {
-                info!("{:?}: validated old memoized value", self,);
+                trace!("{:?}: validated old memoized value", self,);
 
                 db.salsa_event(Event {
                     runtime_id: runtime.id(),
@@ -230,7 +230,7 @@ where
         old_memo: Option<Memo<Q::Value>>,
         key: &Q::Key,
     ) -> StampedValue<Q::Value> {
-        tracing::info!("{:?}: executing query", self.database_key_index().debug(db));
+        tracing::trace!("{:?}: executing query", self.database_key_index().debug(db));
 
         db.salsa_event(Event {
             runtime_id: db.salsa_runtime().id(),
@@ -242,7 +242,7 @@ where
         let value = match Cycle::catch(|| Q::execute(db, key.clone())) {
             Ok(v) => v,
             Err(cycle) => {
-                tracing::debug!(
+                tracing::trace!(
                     "{:?}: caught cycle {:?}, have strategy {:?}",
                     self.database_key_index().debug(db),
                     cycle,
@@ -293,9 +293,10 @@ where
                 if revisions.durability >= old_memo.revisions.durability
                     && MP::memoized_value_eq(old_value, &value)
                 {
-                    debug!(
+                    trace!(
                         "read_upgrade({:?}): value is equal, back-dating to {:?}",
-                        self, old_memo.revisions.changed_at,
+                        self,
+                        old_memo.revisions.changed_at,
                     );
 
                     assert!(old_memo.revisions.changed_at <= revisions.changed_at);
@@ -313,7 +314,7 @@ where
         let memo_value =
             if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None };
 
-        debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
+        trace!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
 
         panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions }));
 
@@ -362,9 +363,11 @@ where
             }
 
             QueryState::Memoized(memo) => {
-                debug!(
+                trace!(
                     "{:?}: found memoized value, verified_at={:?}, changed_at={:?}",
-                    self, memo.verified_at, memo.revisions.changed_at,
+                    self,
+                    memo.verified_at,
+                    memo.revisions.changed_at,
                 );
 
                 if memo.verified_at < revision_now {
@@ -378,7 +381,11 @@ where
                         value: value.clone(),
                     };
 
-                    info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
+                    trace!(
+                        "{:?}: returning memoized value changed at {:?}",
+                        self,
+                        value.changed_at
+                    );
 
                     ProbeState::UpToDate(value)
                 } else {
@@ -426,7 +433,7 @@ where
     }
 
     pub(super) fn invalidate(&self, new_revision: Revision) -> Option<Durability> {
-        tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision);
+        tracing::trace!("Slot::invalidate(new_revision = {:?})", new_revision);
         match &mut *self.state.write() {
             QueryState::Memoized(memo) => {
                 memo.revisions.untracked = true;
@@ -450,9 +457,11 @@ where
 
         db.unwind_if_cancelled();
 
-        debug!(
+        trace!(
             "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}",
-            self, revision, revision_now,
+            self,
+            revision,
+            revision_now,
         );
 
         // Do an initial probe with just the read-lock.
@@ -734,9 +743,11 @@ where
         assert!(self.verified_at != revision_now);
         let verified_at = self.verified_at;
 
-        debug!(
+        trace!(
             "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}",
-            verified_at, revision_now, self.revisions.inputs
+            verified_at,
+            revision_now,
+            self.revisions.inputs
         );
 
         if self.check_durability(db.salsa_runtime()) {
@@ -762,7 +773,7 @@ where
                 let changed_input =
                     inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at));
                 if let Some(input) = changed_input {
-                    debug!("validate_memoized_value: `{:?}` may have changed", input);
+                    trace!("validate_memoized_value: `{:?}` may have changed", input);
 
                     return false;
                 }
@@ -775,7 +786,7 @@ where
     /// True if this memo is known not to have changed based on its durability.
     fn check_durability(&self, runtime: &Runtime) -> bool {
         let last_changed = runtime.last_changed_revision(self.revisions.durability);
-        debug!(
+        trace!(
             "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
             last_changed,
             self.verified_at,
diff --git a/crates/ra-salsa/src/input.rs b/crates/ra-salsa/src/input.rs
index f04f48e3ba..4992a0c727 100644
--- a/crates/ra-salsa/src/input.rs
+++ b/crates/ra-salsa/src/input.rs
@@ -14,7 +14,7 @@ use crate::{DatabaseKeyIndex, QueryDb};
 use indexmap::map::Entry;
 use parking_lot::RwLock;
 use std::iter;
-use tracing::debug;
+use tracing::trace;
 
 /// Input queries store the result plus a list of the other queries
 /// that they invoked. This means we can avoid recomputing them when
@@ -73,11 +73,11 @@ where
             return true;
         };
 
-        debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
+        trace!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
 
         let changed_at = slot.stamped_value.read().changed_at;
 
-        debug!("maybe_changed_after: changed_at = {:?}", changed_at);
+        trace!("maybe_changed_after: changed_at = {:?}", changed_at);
 
         changed_at > revision
     }
@@ -140,7 +140,7 @@ where
     Q: Query,
 {
     fn set(&self, runtime: &mut Runtime, key: &Q::Key, value: Q::Value, durability: Durability) {
-        tracing::debug!("{:?}({:?}) = {:?} ({:?})", Q::default(), key, value, durability);
+        tracing::trace!("{:?}({:?}) = {:?} ({:?})", Q::default(), key, value, durability);
 
         // The value is changing, so we need a new revision (*). We also
         // need to update the 'last changed' revision by invoking
@@ -234,14 +234,14 @@ where
     ) -> bool {
         debug_assert!(revision < db.salsa_runtime().current_revision());
 
-        debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
+        trace!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
 
         let Some(value) = &*self.slot.stamped_value.read() else {
             return true;
         };
         let changed_at = value.changed_at;
 
-        debug!("maybe_changed_after: changed_at = {:?}", changed_at);
+        trace!("maybe_changed_after: changed_at = {:?}", changed_at);
 
         changed_at > revision
     }
@@ -298,7 +298,7 @@ where
     Q: Query<Key = ()>,
 {
     fn set(&self, runtime: &mut Runtime, (): &Q::Key, value: Q::Value, durability: Durability) {
-        tracing::debug!("{:?} = {:?} ({:?})", Q::default(), value, durability);
+        tracing::trace!("{:?} = {:?} ({:?})", Q::default(), value, durability);
 
         // The value is changing, so we need a new revision (*). We also
         // need to update the 'last changed' revision by invoking
diff --git a/crates/ra-salsa/src/lib.rs b/crates/ra-salsa/src/lib.rs
index 8530521d91..843b6d31f0 100644
--- a/crates/ra-salsa/src/lib.rs
+++ b/crates/ra-salsa/src/lib.rs
@@ -79,7 +79,7 @@ pub trait Database: plumbing::DatabaseOps {
 
         let current_revision = runtime.current_revision();
         let pending_revision = runtime.pending_revision();
-        tracing::debug!(
+        tracing::trace!(
             "unwind_if_cancelled: current_revision={:?}, pending_revision={:?}",
             current_revision,
             pending_revision
@@ -684,7 +684,7 @@ impl Cycle {
     }
 
     pub(crate) fn throw(self) -> ! {
-        tracing::debug!("throwing cycle {:?}", self);
+        tracing::trace!("throwing cycle {:?}", self);
         std::panic::resume_unwind(Box::new(self))
     }
 
diff --git a/crates/ra-salsa/src/lru.rs b/crates/ra-salsa/src/lru.rs
index a6f96beeab..7fbd42f926 100644
--- a/crates/ra-salsa/src/lru.rs
+++ b/crates/ra-salsa/src/lru.rs
@@ -103,11 +103,11 @@ where
 
     /// Records that `node` was used. This may displace an old node (if the LRU limits are
     pub(crate) fn record_use(&self, node: &Arc<Node>) -> Option<Arc<Node>> {
-        tracing::debug!("record_use(node={:?})", node);
+        tracing::trace!("record_use(node={:?})", node);
 
         // Load green zone length and check if the LRU cache is even enabled.
         let green_zone = self.green_zone.load(Ordering::Acquire);
-        tracing::debug!("record_use: green_zone={}", green_zone);
+        tracing::trace!("record_use: green_zone={}", green_zone);
         if green_zone == 0 {
             return None;
         }
@@ -115,7 +115,7 @@ where
         // Find current index of list (if any) and the current length
         // of our green zone.
         let index = node.lru_index().load();
-        tracing::debug!("record_use: index={}", index);
+        tracing::trace!("record_use: index={}", index);
 
         // Already a member of the list, and in the green zone -- nothing to do!
         if index < green_zone {
@@ -162,9 +162,9 @@ where
         let entries =
             std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone as usize));
 
-        tracing::debug!("green_zone = {:?}", self.green_zone());
-        tracing::debug!("yellow_zone = {:?}", self.yellow_zone());
-        tracing::debug!("red_zone = {:?}", self.red_zone());
+        tracing::trace!("green_zone = {:?}", self.green_zone());
+        tracing::trace!("yellow_zone = {:?}", self.yellow_zone());
+        tracing::trace!("red_zone = {:?}", self.red_zone());
 
         // We expect to resize when the LRU cache is basically empty.
         // So just forget all the old LRU indices to start.
@@ -180,7 +180,7 @@ where
     /// list may displace an old member of the red zone, in which case
     /// that is returned.
     fn record_use(&mut self, node: &Arc<Node>) -> Option<Arc<Node>> {
-        tracing::debug!("record_use(node={:?})", node);
+        tracing::trace!("record_use(node={:?})", node);
 
         // NB: When this is invoked, we have typically already loaded
         // the LRU index (to check if it is in green zone). But that
@@ -212,7 +212,7 @@ where
         if len < self.end_red_zone {
             self.entries.push(node.clone());
             node.lru_index().store(len);
-            tracing::debug!("inserted node {:?} at {}", node, len);
+            tracing::trace!("inserted node {:?} at {}", node, len);
             return self.record_use(node);
         }
 
@@ -220,7 +220,7 @@ where
         // zone and then promoting.
         let victim_index = self.pick_index(self.red_zone());
         let victim_node = std::mem::replace(&mut self.entries[victim_index as usize], node.clone());
-        tracing::debug!("evicting red node {:?} from {}", victim_node, victim_index);
+        tracing::trace!("evicting red node {:?} from {}", victim_node, victim_index);
         victim_node.lru_index().clear();
         self.promote_red_to_green(node, victim_index);
         Some(victim_node)
@@ -241,7 +241,7 @@ where
         // going to invoke `self.promote_yellow` next, and it will get
         // updated then.
         let yellow_index = self.pick_index(self.yellow_zone());
-        tracing::debug!(
+        tracing::trace!(
             "demoting yellow node {:?} from {} to red at {}",
             self.entries[yellow_index as usize],
             yellow_index,
@@ -265,7 +265,7 @@ where
 
         // Pick a yellow at random and switch places with it.
         let green_index = self.pick_index(self.green_zone());
-        tracing::debug!(
+        tracing::trace!(
             "demoting green node {:?} from {} to yellow at {}",
             self.entries[green_index as usize],
             green_index,
@@ -275,7 +275,7 @@ where
         self.entries[yellow_index as usize].lru_index().store(yellow_index);
         node.lru_index().store(green_index);
 
-        tracing::debug!("promoted {:?} to green index {}", node, green_index);
+        tracing::trace!("promoted {:?} to green index {}", node, green_index);
     }
 
     fn pick_index(&mut self, zone: std::ops::Range<u16>) -> u16 {
diff --git a/crates/ra-salsa/src/runtime.rs b/crates/ra-salsa/src/runtime.rs
index 5fe5f4b46d..cb16ba0044 100644
--- a/crates/ra-salsa/src/runtime.rs
+++ b/crates/ra-salsa/src/runtime.rs
@@ -9,7 +9,7 @@ use parking_lot::{Mutex, RwLock};
 use std::hash::Hash;
 use std::panic::panic_any;
 use std::sync::atomic::{AtomicU32, Ordering};
-use tracing::debug;
+use tracing::trace;
 use triomphe::{Arc, ThinArc};
 
 mod dependency_graph;
@@ -177,7 +177,7 @@ impl Runtime {
     where
         F: FnOnce(Revision) -> Option<Durability>,
     {
-        tracing::debug!("increment_revision()");
+        tracing::trace!("increment_revision()");
 
         if !self.permits_increment() {
             panic!("increment_revision invoked during a query computation");
@@ -196,7 +196,7 @@ impl Runtime {
 
         let new_revision = current_revision.next();
 
-        debug!("increment_revision: incremented to {:?}", new_revision);
+        trace!("increment_revision: incremented to {:?}", new_revision);
 
         if let Some(d) = op(new_revision) {
             for rev in &self.shared_state.revisions[1..=d.index()] {
@@ -267,7 +267,7 @@ impl Runtime {
         database_key_index: DatabaseKeyIndex,
         to_id: RuntimeId,
     ) {
-        debug!("unblock_cycle_and_maybe_throw(database_key={:?})", database_key_index);
+        trace!("unblock_cycle_and_maybe_throw(database_key={:?})", database_key_index);
 
         let mut from_stack = self.local_state.take_query_stack();
         let from_id = self.id();
@@ -305,7 +305,7 @@ impl Runtime {
 
             Cycle::new(Arc::new(v))
         };
-        debug!("cycle {:?}, cycle_query {:#?}", cycle.debug(db), cycle_query,);
+        trace!("cycle {:?}, cycle_query {:#?}", cycle.debug(db), cycle_query,);
 
         // We can remove the cycle participants from the list of dependencies;
         // they are a strongly connected component (SCC) and we only care about
@@ -323,7 +323,7 @@ impl Runtime {
                     CycleRecoveryStrategy::Fallback => false,
                 })
                 .for_each(|aq| {
-                    debug!("marking {:?} for fallback", aq.database_key_index.debug(db));
+                    trace!("marking {:?} for fallback", aq.database_key_index.debug(db));
                     aq.take_inputs_from(&cycle_query);
                     assert!(aq.cycle.is_none());
                     aq.cycle = Some(cycle.clone());
diff --git a/crates/ra-salsa/src/runtime/local_state.rs b/crates/ra-salsa/src/runtime/local_state.rs
index 7386967188..4ab4bad0cc 100644
--- a/crates/ra-salsa/src/runtime/local_state.rs
+++ b/crates/ra-salsa/src/runtime/local_state.rs
@@ -1,4 +1,4 @@
-use tracing::debug;
+use tracing::trace;
 use triomphe::ThinArc;
 
 use crate::durability::Durability;
@@ -78,7 +78,7 @@ impl LocalState {
         durability: Durability,
         changed_at: Revision,
     ) {
-        debug!(
+        trace!(
             "report_query_read_and_unwind_if_cycle_resulted(input={:?}, durability={:?}, changed_at={:?})",
             input, durability, changed_at
         );
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index c7bb275c5f..53c145f884 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -345,7 +345,6 @@ impl FlycheckActor {
                         );
                     }
                     if self.package_status.is_empty() {
-                        tracing::trace!(flycheck_id = self.id, "clearing diagnostics");
                         // We finished without receiving any diagnostics.
                         // That means all of them are stale.
                         self.send(FlycheckMessage::ClearDiagnostics {
@@ -355,11 +354,6 @@ impl FlycheckActor {
                     } else {
                         for (package_id, status) in mem::take(&mut self.package_status) {
                             if let DiagnosticReceived::No = status {
-                                tracing::trace!(
-                                    flycheck_id = self.id,
-                                    package_id = package_id.repr,
-                                    "clearing diagnostics"
-                                );
                                 self.send(FlycheckMessage::ClearDiagnostics {
                                     id: self.id,
                                     package_id: Some(package_id),
@@ -391,15 +385,9 @@ impl FlycheckActor {
                             "diagnostic received"
                         );
                         if let Some(package_id) = &package_id {
-                            if let None | Some(DiagnosticReceived::No) = self
-                                .package_status
-                                .insert(package_id.clone(), DiagnosticReceived::Yes)
-                            {
-                                tracing::trace!(
-                                    flycheck_id = self.id,
-                                    package_id = package_id.repr,
-                                    "clearing diagnostics"
-                                );
+                            if !self.package_status.contains_key(package_id) {
+                                self.package_status
+                                    .insert(package_id.clone(), DiagnosticReceived::Yes);
                                 self.send(FlycheckMessage::ClearDiagnostics {
                                     id: self.id,
                                     package_id: Some(package_id.clone()),
diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs
index ff50f7533a..2aa4ffbe1d 100644
--- a/crates/rust-analyzer/src/handlers/dispatch.rs
+++ b/crates/rust-analyzer/src/handlers/dispatch.rs
@@ -5,7 +5,6 @@ use std::{
 };
 
 use ide::Cancelled;
-use ide_db::base_db::ra_salsa::Cycle;
 use lsp_server::{ExtractError, Response, ResponseError};
 use serde::{de::DeserializeOwned, Serialize};
 use stdx::thread::ThreadIntent;
@@ -308,31 +307,10 @@ impl RequestDispatcher<'_> {
     }
 }
 
-#[derive(Debug)]
-enum HandlerCancelledError {
-    PropagatedPanic,
-    Inner(ide::Cancelled),
-}
-
-impl std::error::Error for HandlerCancelledError {
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        match self {
-            HandlerCancelledError::PropagatedPanic => None,
-            HandlerCancelledError::Inner(cancelled) => Some(cancelled),
-        }
-    }
-}
-
-impl fmt::Display for HandlerCancelledError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "Cancelled")
-    }
-}
-
 fn thread_result_to_response<R>(
     id: lsp_server::RequestId,
     result: thread::Result<anyhow::Result<R::Result>>,
-) -> Result<lsp_server::Response, HandlerCancelledError>
+) -> Result<lsp_server::Response, Cancelled>
 where
     R: lsp_types::request::Request,
     R::Params: DeserializeOwned,
@@ -350,13 +328,7 @@ where
             if let Some(panic_message) = panic_message {
                 message.push_str(": ");
                 message.push_str(panic_message)
-            } else if let Some(cycle) = panic.downcast_ref::<Cycle>() {
-                tracing::error!("Cycle propagated out of salsa! This is a bug: {cycle:?}");
-                return Err(HandlerCancelledError::PropagatedPanic);
-            } else if let Ok(cancelled) = panic.downcast::<Cancelled>() {
-                tracing::error!("Cancellation propagated out of salsa! This is a bug");
-                return Err(HandlerCancelledError::Inner(*cancelled));
-            }
+            };
 
             Ok(lsp_server::Response::new_err(
                 id,
@@ -370,7 +342,7 @@ where
 fn result_to_response<R>(
     id: lsp_server::RequestId,
     result: anyhow::Result<R::Result>,
-) -> Result<lsp_server::Response, HandlerCancelledError>
+) -> Result<lsp_server::Response, Cancelled>
 where
     R: lsp_types::request::Request,
     R::Params: DeserializeOwned,
@@ -381,7 +353,7 @@ where
         Err(e) => match e.downcast::<LspError>() {
             Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message),
             Err(e) => match e.downcast::<Cancelled>() {
-                Ok(cancelled) => return Err(HandlerCancelledError::Inner(cancelled)),
+                Ok(cancelled) => return Err(cancelled),
                 Err(e) => lsp_server::Response::new_err(
                     id,
                     lsp_server::ErrorCode::InternalError as i32,
diff --git a/crates/tt/src/iter.rs b/crates/tt/src/iter.rs
index 4d7fe0b5a0..587b903aa9 100644
--- a/crates/tt/src/iter.rs
+++ b/crates/tt/src/iter.rs
@@ -132,7 +132,6 @@ impl<'a, S: Copy> TtIter<'a, S> {
             }
             ('-' | '!' | '*' | '/' | '&' | '%' | '^' | '+' | '<' | '=' | '>' | '|', '=', _)
             | ('-' | '=' | '>', '>', _)
-            | (_, _, Some(';'))
             | ('<', '-', _)
             | (':', ':', _)
             | ('.', '.', _)


Test patch:
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index cabeeea2bd..b7607b5f63 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -127,7 +127,15 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
             None => continue,
         };
         let def_map = module.def_map(&db);
-        visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
+        visit_module(&db, &def_map, module.local_id, &mut |it| {
+            defs.push(match it {
+                ModuleDefId::FunctionId(it) => it.into(),
+                ModuleDefId::EnumVariantId(it) => it.into(),
+                ModuleDefId::ConstId(it) => it.into(),
+                ModuleDefId::StaticId(it) => it.into(),
+                _ => return,
+            })
+        });
     }
     defs.sort_by_key(|def| match def {
         DefWithBodyId::FunctionId(it) => {
@@ -375,7 +383,15 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
     let def_map = module.def_map(&db);
 
     let mut defs: Vec<DefWithBodyId> = Vec::new();
-    visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
+    visit_module(&db, &def_map, module.local_id, &mut |it| {
+        defs.push(match it {
+            ModuleDefId::FunctionId(it) => it.into(),
+            ModuleDefId::EnumVariantId(it) => it.into(),
+            ModuleDefId::ConstId(it) => it.into(),
+            ModuleDefId::StaticId(it) => it.into(),
+            _ => return,
+        })
+    });
     defs.sort_by_key(|def| match def {
         DefWithBodyId::FunctionId(it) => {
             let loc = it.lookup(&db);
@@ -405,11 +421,11 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
     buf
 }
 
-fn visit_module(
+pub(crate) fn visit_module(
     db: &TestDB,
     crate_def_map: &DefMap,
     module_id: LocalModuleId,
-    cb: &mut dyn FnMut(DefWithBodyId),
+    cb: &mut dyn FnMut(ModuleDefId),
 ) {
     visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
     for impl_id in crate_def_map[module_id].scope.impls() {
@@ -417,18 +433,18 @@ fn visit_module(
         for &item in impl_data.items.iter() {
             match item {
                 AssocItemId::FunctionId(it) => {
-                    let def = it.into();
-                    cb(def);
-                    let body = db.body(def);
+                    let body = db.body(it.into());
+                    cb(it.into());
                     visit_body(db, &body, cb);
                 }
                 AssocItemId::ConstId(it) => {
-                    let def = it.into();
-                    cb(def);
-                    let body = db.body(def);
+                    let body = db.body(it.into());
+                    cb(it.into());
                     visit_body(db, &body, cb);
                 }
-                AssocItemId::TypeAliasId(_) => (),
+                AssocItemId::TypeAliasId(it) => {
+                    cb(it.into());
+                }
             }
         }
     }
@@ -437,33 +453,27 @@ fn visit_module(
         db: &TestDB,
         crate_def_map: &DefMap,
         scope: &ItemScope,
-        cb: &mut dyn FnMut(DefWithBodyId),
+        cb: &mut dyn FnMut(ModuleDefId),
     ) {
         for decl in scope.declarations() {
+            cb(decl);
             match decl {
                 ModuleDefId::FunctionId(it) => {
-                    let def = it.into();
-                    cb(def);
-                    let body = db.body(def);
+                    let body = db.body(it.into());
                     visit_body(db, &body, cb);
                 }
                 ModuleDefId::ConstId(it) => {
-                    let def = it.into();
-                    cb(def);
-                    let body = db.body(def);
+                    let body = db.body(it.into());
                     visit_body(db, &body, cb);
                 }
                 ModuleDefId::StaticId(it) => {
-                    let def = it.into();
-                    cb(def);
-                    let body = db.body(def);
+                    let body = db.body(it.into());
                     visit_body(db, &body, cb);
                 }
                 ModuleDefId::AdtId(hir_def::AdtId::EnumId(it)) => {
                     db.enum_data(it).variants.iter().for_each(|&(it, _)| {
-                        let def = it.into();
-                        cb(def);
-                        let body = db.body(def);
+                        let body = db.body(it.into());
+                        cb(it.into());
                         visit_body(db, &body, cb);
                     });
                 }
@@ -473,7 +483,7 @@ fn visit_module(
                         match item {
                             AssocItemId::FunctionId(it) => cb(it.into()),
                             AssocItemId::ConstId(it) => cb(it.into()),
-                            AssocItemId::TypeAliasId(_) => (),
+                            AssocItemId::TypeAliasId(it) => cb(it.into()),
                         }
                     }
                 }
@@ -483,7 +493,7 @@ fn visit_module(
         }
     }
 
-    fn visit_body(db: &TestDB, body: &Body, cb: &mut dyn FnMut(DefWithBodyId)) {
+    fn visit_body(db: &TestDB, body: &Body, cb: &mut dyn FnMut(ModuleDefId)) {
         for (_, def_map) in body.blocks(db) {
             for (mod_id, _) in def_map.modules() {
                 visit_module(db, &def_map, mod_id, cb);
@@ -553,7 +563,13 @@ fn salsa_bug() {
     let module = db.module_for_file(pos.file_id);
     let crate_def_map = module.def_map(&db);
     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-        db.infer(def);
+        db.infer(match def {
+            ModuleDefId::FunctionId(it) => it.into(),
+            ModuleDefId::EnumVariantId(it) => it.into(),
+            ModuleDefId::ConstId(it) => it.into(),
+            ModuleDefId::StaticId(it) => it.into(),
+            _ => return,
+        });
     });
 
     let new_text = "
@@ -586,6 +602,12 @@ fn salsa_bug() {
     let module = db.module_for_file(pos.file_id);
     let crate_def_map = module.def_map(&db);
     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-        db.infer(def);
+        db.infer(match def {
+            ModuleDefId::FunctionId(it) => it.into(),
+            ModuleDefId::EnumVariantId(it) => it.into(),
+            ModuleDefId::ConstId(it) => it.into(),
+            ModuleDefId::StaticId(it) => it.into(),
+            _ => return,
+        });
     });
 }
diff --git a/crates/hir-ty/src/tests/closure_captures.rs b/crates/hir-ty/src/tests/closure_captures.rs
index b63d632dd2..7de92d6b16 100644
--- a/crates/hir-ty/src/tests/closure_captures.rs
+++ b/crates/hir-ty/src/tests/closure_captures.rs
@@ -24,6 +24,13 @@ fn check_closure_captures(ra_fixture: &str, expect: Expect) {
 
     let mut captures_info = Vec::new();
     for def in defs {
+        let def = match def {
+            hir_def::ModuleDefId::FunctionId(it) => it.into(),
+            hir_def::ModuleDefId::EnumVariantId(it) => it.into(),
+            hir_def::ModuleDefId::ConstId(it) => it.into(),
+            hir_def::ModuleDefId::StaticId(it) => it.into(),
+            _ => continue,
+        };
         let infer = db.infer(def);
         let db = &db;
         captures_info.extend(infer.closure_info.iter().flat_map(|(closure_id, (captures, _))| {
diff --git a/crates/hir-ty/src/tests/incremental.rs b/crates/hir-ty/src/tests/incremental.rs
index 0a24eeb1fe..3757d722ac 100644
--- a/crates/hir-ty/src/tests/incremental.rs
+++ b/crates/hir-ty/src/tests/incremental.rs
@@ -1,4 +1,5 @@
 use base_db::SourceDatabaseFileInputExt as _;
+use hir_def::ModuleDefId;
 use test_fixture::WithFixture;
 
 use crate::{db::HirDatabase, test_db::TestDB};
@@ -19,7 +20,9 @@ fn foo() -> i32 {
             let module = db.module_for_file(pos.file_id.file_id());
             let crate_def_map = module.def_map(&db);
             visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-                db.infer(def);
+                if let ModuleDefId::FunctionId(it) = def {
+                    db.infer(it.into());
+                }
             });
         });
         assert!(format!("{events:?}").contains("infer"))
@@ -39,7 +42,9 @@ fn foo() -> i32 {
             let module = db.module_for_file(pos.file_id.file_id());
             let crate_def_map = module.def_map(&db);
             visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-                db.infer(def);
+                if let ModuleDefId::FunctionId(it) = def {
+                    db.infer(it.into());
+                }
             });
         });
         assert!(!format!("{events:?}").contains("infer"), "{events:#?}")
@@ -66,7 +71,9 @@ fn baz() -> i32 {
             let module = db.module_for_file(pos.file_id.file_id());
             let crate_def_map = module.def_map(&db);
             visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-                db.infer(def);
+                if let ModuleDefId::FunctionId(it) = def {
+                    db.infer(it.into());
+                }
             });
         });
         assert!(format!("{events:?}").contains("infer"))
@@ -91,7 +98,9 @@ fn baz() -> i32 {
             let module = db.module_for_file(pos.file_id.file_id());
             let crate_def_map = module.def_map(&db);
             visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
-                db.infer(def);
+                if let ModuleDefId::FunctionId(it) = def {
+                    db.infer(it.into());
+                }
             });
         });
         assert!(format!("{events:?}").matches("infer").count() == 1, "{events:#?}")
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index ed8cd64cdb..fe7f0c79f5 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -4721,7 +4721,7 @@ fn hover_type_param_sized_bounds() {
 //- minicore: sized
 trait Trait {}
 struct Foo<T>(T);
-impl<T: Trait> Foo<T$0> {}
+impl<T$0: Trait> Foo<T> {}
 "#,
         expect![[r#"
             *T*
@@ -4736,7 +4736,7 @@ impl<T: Trait> Foo<T$0> {}
 //- minicore: sized
 trait Trait {}
 struct Foo<T>(T);
-impl<T: Trait + ?Sized> Foo<T$0> {}
+impl<T$0: Trait + ?Sized> Foo<T> {}
 "#,
         expect![[r#"
             *T*
@@ -4764,6 +4764,10 @@ fn foo<T$0>() {}
                 ```rust
                 T
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4781,6 +4785,10 @@ fn foo<T$0: Sized>() {}
                 ```rust
                 T
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4798,6 +4806,10 @@ fn foo<T$0: ?Sized>() {}
                 ```rust
                 T: ?Sized
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4816,6 +4828,10 @@ fn foo<T$0: Trait>() {}
                 ```rust
                 T: Trait
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4834,6 +4850,10 @@ fn foo<T$0: Trait + Sized>() {}
                 ```rust
                 T: Trait
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4852,6 +4872,10 @@ fn foo<T$0: Trait + ?Sized>() {}
                 ```rust
                 T: Trait + ?Sized
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4869,6 +4893,10 @@ fn foo<T$0: ?Sized + Sized + Sized>() {}
                 ```rust
                 T
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
@@ -4887,6 +4915,10 @@ fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
                 ```rust
                 T: Trait
                 ```
+
+                ---
+
+                invariant
             "#]],
         );
     }
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index ccd2ecdb13..c1ca596063 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -1085,7 +1085,7 @@ fn resolve_proc_macro() {
     let sysroot = project_model::Sysroot::discover(
         &AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()),
         &Default::default(),
-        &project_model::SysrootQueryMetadata::default(),
+        project_model::SysrootQueryMetadata::CargoMetadata,
     );
 
     let proc_macro_server_path = sysroot.discover_proc_macro_srv().unwrap();