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>>; + fn principal_id(&self) -> Option>; } impl DynTyExt for DynTy { - fn principal(&self) -> Option<&TraitRef> { + fn principal(&self) -> Option>> { + 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> { 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 { #[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>; + #[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, parent_generics: Option>, @@ -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 { + pub fn type_or_const_param_idx(&self, param: TypeOrConstParamId) -> Option { self.find_type_or_const_param(param) } @@ -174,7 +174,7 @@ impl Generics { } } - pub(crate) fn lifetime_idx(&self, lifetime: LifetimeParamId) -> Option { + pub fn lifetime_idx(&self, lifetime: LifetimeParamId) -> Option { 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/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 = 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/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> { + 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> { + 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 <: T iff A <: B -- e.g., function return type + Invariant, // T <: T iff B == A -- e.g., type of mutable cell + Contravariant, // T <: T iff B <: A -- e.g., function param type + Bivariant, // T <: T -- 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 + /// ``` + /// Here, the "ambient" variance starts as covariant. `*mut T` is + /// invariant with respect to `T`, so the variance in which the + /// `Vec` appears is `Covariant.xform(Invariant)`, which + /// yields `Invariant`. Now, the type `Vec` 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, *mut Vec` appears is + /// `Contravariant.xform(Covariant)` or `Contravariant`. The same + /// is true for its `i32` argument. In the `*mut T` case, the + /// variance of `Vec` is `Contravariant.xform(Invariant)`, + /// and hence the outermost type is `Invariant` with respect to + /// `Vec` (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, +} + +impl Context<'_> { + fn solve(mut self) -> Vec { + 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 +'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, + 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 { + t: core::marker::PhantomData +} +"#, + 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 { //~ ERROR [A: o] + t: UnsafeCell +} + +struct InvariantIndirect { //~ ERROR [A: o] + t: InvariantCell +} + +struct Covariant { //~ ERROR [A: +] + t: A, u: fn() -> A +} + +struct Contravariant { //~ ERROR [A: -] + t: fn(A) +} + +enum Enum { //~ ERROR [A: +, B: -, C: o] + Foo(Covariant), + Bar(Contravariant),` + Zed(Covariant,Contravariant) +} +"#, + 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 { //~ ERROR [T: o] + field: [u8; ::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: >::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 {} +"#, + expect![""], + ); + } + + #[test] + fn rustc_test_variance_trait_bounds() { + check( + r#" +trait Getter { + fn get(&self) -> T; +} + +trait Setter { + fn get(&self, _: T); +} + +struct TestStruct> { //~ ERROR [U: +, T: +] + t: T, u: U +} + +enum TestEnum> { //~ ERROR [U: *, T: +] + //~^ ERROR: `U` is never used + Foo(T) +} + +struct TestContraStruct> { //~ ERROR [U: *, T: +] + //~^ ERROR: `U` is never used + t: T +} + +struct TestBox+Setter> { //~ 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 { + fn get(&self) -> T; +} + +struct Cloner { + t: T +} + +impl Get for Cloner { + 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 { + 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 { //~ ERROR [A: +, B: +] + x: A, + y: B, +} + +struct TestMut { //~ ERROR [A: +, B: o] + x: A, + y: &'static mut B, +} + +struct TestIndirect { //~ ERROR [A: +, B: o] + m: TestMut +} + +struct TestIndirect2 { //~ ERROR [A: o, B: o] + n: TestMut, + m: TestMut +} + +trait Getter { + fn get(&self) -> A; +} + +trait Setter { + fn set(&mut self, a: A); +} + +struct TestObject { //~ ERROR [A: o, R: o] + n: *const (dyn Setter + Send), + m: *const (dyn Getter + 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 { x: u32 } +enum SomeEnum { Nothing } +enum ListCell { + Cons(*const ListCell), + Nil +} + +struct SelfTyAlias(*const Self); +struct WithBounds {} +struct WithWhereBounds where T: Sized {} +struct WithOutlivesBounds {} +struct DoubleNothing { + s: SomeStruct, +} + +"#, + 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(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(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); + +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); + +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(*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); +struct S2(S<>); +struct S3(S); +"#, + 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(&'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 = 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 { + 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(¬able_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/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); -impl Foo {} +impl Foo {} "#, expect![[r#" *T* @@ -4736,7 +4736,7 @@ impl Foo {} //- minicore: sized trait Trait {} struct Foo(T); -impl Foo {} +impl Foo {} "#, expect![[r#" *T* @@ -4764,6 +4764,10 @@ fn foo() {} ```rust T ``` + + --- + + invariant "#]], ); } @@ -4781,6 +4785,10 @@ fn foo() {} ```rust T ``` + + --- + + invariant "#]], ); } @@ -4798,6 +4806,10 @@ fn foo() {} ```rust T: ?Sized ``` + + --- + + invariant "#]], ); } @@ -4816,6 +4828,10 @@ fn foo() {} ```rust T: Trait ``` + + --- + + invariant "#]], ); } @@ -4834,6 +4850,10 @@ fn foo() {} ```rust T: Trait ``` + + --- + + invariant "#]], ); } @@ -4852,6 +4872,10 @@ fn foo() {} ```rust T: Trait + ?Sized ``` + + --- + + invariant "#]], ); } @@ -4869,6 +4893,10 @@ fn foo() {} ```rust T ``` + + --- + + invariant "#]], ); } @@ -4887,6 +4915,10 @@ fn foo() {} ```rust T: Trait ``` + + --- + + invariant "#]], ); } 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 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 { 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>, key: &Q::Key, ) -> StampedValue { - 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 { - 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 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 { 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>, key: &Q::Key, ) -> StampedValue { - 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 { - 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, { 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) -> Option> { - 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) -> Option> { - 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 { 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, { - 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 );