From 627255dd5afb661819a4cd831b765c846a0c02aa Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Sat, 16 Dec 2023 16:29:23 +0200 Subject: [PATCH] Add static method tactic --- crates/hir-def/src/attr.rs | 39 +- crates/hir/src/lib.rs | 102 ++++- crates/hir/src/term_search/mod.rs | 7 + crates/hir/src/term_search/tactics.rs | 348 +++++++++++++++--- crates/hir/src/term_search/type_tree.rs | 115 ++++-- .../ide-assists/src/handlers/term_search.rs | 38 +- crates/ide-db/src/path_transform.rs | 2 +- .../rust-analyzer/src/cli/analysis_stats.rs | 11 +- 8 files changed, 569 insertions(+), 93 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index c91a5497262..247ec096cbe 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -377,27 +377,36 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { AttrDefId::GenericParamId(it) => match it { GenericParamId::ConstParamId(it) => { let src = it.parent().child_source(db); - RawAttrs::from_attrs_owner( - db.upcast(), - src.with_value(&src.value[it.local_id()]), - db.span_map(src.file_id).as_ref(), - ) + match src.value.get(it.local_id()) { + Some(val) => RawAttrs::from_attrs_owner( + db.upcast(), + src.with_value(val), + db.span_map(src.file_id).as_ref(), + ), + None => RawAttrs::EMPTY, + } } GenericParamId::TypeParamId(it) => { let src = it.parent().child_source(db); - RawAttrs::from_attrs_owner( - db.upcast(), - src.with_value(&src.value[it.local_id()]), - db.span_map(src.file_id).as_ref(), - ) + match src.value.get(it.local_id()) { + Some(val) => RawAttrs::from_attrs_owner( + db.upcast(), + src.with_value(val), + db.span_map(src.file_id).as_ref(), + ), + None => RawAttrs::EMPTY, + } } GenericParamId::LifetimeParamId(it) => { let src = it.parent.child_source(db); - RawAttrs::from_attrs_owner( - db.upcast(), - src.with_value(&src.value[it.local_id]), - db.span_map(src.file_id).as_ref(), - ) + match src.value.get(it.local_id) { + Some(val) => RawAttrs::from_attrs_owner( + db.upcast(), + src.with_value(val), + db.span_map(src.file_id).as_ref(), + ), + None => RawAttrs::EMPTY, + } } }, AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it), diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 24323284e9b..9b8c7b900ae 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1187,6 +1187,10 @@ pub fn kind(self, db: &dyn HirDatabase) -> StructKind { fn variant_data(self, db: &dyn HirDatabase) -> Arc { db.struct_data(self.id).variant_data.clone() } + + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.attrs(self.id.into()).is_unstable() + } } impl HasVisibility for Struct { @@ -1229,6 +1233,10 @@ pub fn fields(self, db: &dyn HirDatabase) -> Vec { fn variant_data(self, db: &dyn HirDatabase) -> Arc { db.union_data(self.id).variant_data.clone() } + + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.attrs(self.id.into()).is_unstable() + } } impl HasVisibility for Union { @@ -1318,6 +1326,10 @@ pub fn is_data_carrying(self, db: &dyn HirDatabase) -> bool { pub fn layout(self, db: &dyn HirDatabase) -> Result { Adt::from(self).layout(db) } + + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.attrs(self.id.into()).is_unstable() + } } impl HasVisibility for Enum { @@ -1393,6 +1405,10 @@ pub fn layout(&self, db: &dyn HirDatabase) -> Result { _ => parent_layout, }) } + + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.attrs(self.id.into()).is_unstable() + } } /// Variants inherit visibility from the parent enum. @@ -2912,7 +2928,7 @@ pub fn lifetime_params(self, db: &dyn HirDatabase) -> Vec { .collect() } - pub fn type_params(self, db: &dyn HirDatabase) -> Vec { + pub fn type_or_const_params(self, db: &dyn HirDatabase) -> Vec { let generics = db.generic_params(self.into()); generics .type_or_consts @@ -2922,6 +2938,40 @@ pub fn type_params(self, db: &dyn HirDatabase) -> Vec { }) .collect() } + + pub fn type_params(self, db: &dyn HirDatabase) -> Vec { + let generics = db.generic_params(self.into()); + generics + .type_or_consts + .iter() + .filter_map(|(local_id, data)| match data { + hir_def::generics::TypeOrConstParamData::TypeParamData(_) => Some(TypeParam { + id: TypeParamId::from_unchecked(TypeOrConstParamId { + parent: self.into(), + local_id, + }), + }), + hir_def::generics::TypeOrConstParamData::ConstParamData(_) => None, + }) + .collect() + } + + pub fn const_params(self, db: &dyn HirDatabase) -> Vec { + let generics = db.generic_params(self.into()); + generics + .type_or_consts + .iter() + .filter_map(|(local_id, data)| match data { + hir_def::generics::TypeOrConstParamData::TypeParamData(_) => None, + hir_def::generics::TypeOrConstParamData::ConstParamData(_) => Some(ConstParam { + id: ConstParamId::from_unchecked(TypeOrConstParamId { + parent: self.into(), + local_id, + }), + }), + }) + .collect() + } } /// A single local definition. @@ -3284,12 +3334,16 @@ pub fn default(self, db: &dyn HirDatabase) -> Option { let ty = generic_arg_from_param(db, self.id.into())?; let resolver = self.id.parent().resolver(db.upcast()); match ty.data(Interner) { - GenericArgData::Ty(it) => { + GenericArgData::Ty(it) if *it.kind(Interner) != TyKind::Error => { Some(Type::new_with_resolver_inner(db, &resolver, it.clone())) } _ => None, } } + + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.attrs(GenericParamId::from(self.id).into()).is_unstable() + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -3773,6 +3827,50 @@ pub fn is_reference(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::Ref(..)) } + pub fn contains_reference(&self, db: &dyn HirDatabase) -> bool { + return go(db, self.env.krate, &self.ty); + + fn go(db: &dyn HirDatabase, krate: CrateId, ty: &Ty) -> bool { + match ty.kind(Interner) { + // Reference itself + TyKind::Ref(_, _, _) => true, + + // For non-phantom_data adts we check variants/fields as well as generic parameters + TyKind::Adt(adt_id, substitution) + if !db.struct_datum(krate, *adt_id).flags.phantom_data => + { + let adt_datum = &db.struct_datum(krate, *adt_id); + let adt_datum_bound = + adt_datum.binders.clone().substitute(Interner, substitution); + adt_datum_bound + .variants + .into_iter() + .flat_map(|variant| variant.fields.into_iter()) + .any(|ty| go(db, krate, &ty)) + || substitution + .iter(Interner) + .filter_map(|x| x.ty(Interner)) + .any(|ty| go(db, krate, ty)) + } + // And for `PhantomData`, we check `T`. + TyKind::Adt(_, substitution) + | TyKind::Tuple(_, substitution) + | TyKind::OpaqueType(_, substitution) + | TyKind::AssociatedType(_, substitution) + | TyKind::FnDef(_, substitution) => substitution + .iter(Interner) + .filter_map(|x| x.ty(Interner)) + .any(|ty| go(db, krate, ty)), + + // For `[T]` or `*T` we check `T` + TyKind::Array(ty, _) | TyKind::Slice(ty) | TyKind::Raw(_, ty) => go(db, krate, ty), + + // Consider everything else as not reference + _ => false, + } + } + } + pub fn as_reference(&self) -> Option<(Type, Mutability)> { let (ty, _lt, m) = self.ty.as_reference()?; let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut)); diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs index 6ea5b105def..009d561e7d1 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search/mod.rs @@ -47,6 +47,8 @@ struct LookupTable { round_scopedef_hits: FxHashSet, /// Amount of rounds since scopedef was first used. rounds_since_sopedef_hit: FxHashMap, + /// Types queried but not present + types_wishlist: FxHashSet, } impl LookupTable { @@ -149,6 +151,10 @@ fn new_round(&mut self) { fn exhausted_scopedefs(&self) -> &FxHashSet { &self.exhausted_scopedefs } + + fn take_types_wishlist(&mut self) -> FxHashSet { + std::mem::take(&mut self.types_wishlist) + } } /// # Term search @@ -205,6 +211,7 @@ pub fn term_search( solutions.extend(tactics::free_function(sema.db, &module, &defs, &mut lookup, goal)); solutions.extend(tactics::impl_method(sema.db, &module, &defs, &mut lookup, goal)); solutions.extend(tactics::struct_projection(sema.db, &module, &defs, &mut lookup, goal)); + solutions.extend(tactics::impl_static_method(sema.db, &module, &defs, &mut lookup, goal)); // Break after 1 round after successful solution if solution_found { diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 34ff420a814..2a9d0d84518 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -8,7 +8,8 @@ //! * `goal` - Term search target type //! And they return iterator that yields type trees that unify with the `goal` type. -use hir_def::generics::TypeOrConstParamData; +use std::iter; + use hir_ty::db::HirDatabase; use hir_ty::mir::BorrowKind; use hir_ty::TyBuilder; @@ -16,8 +17,8 @@ use rustc_hash::FxHashSet; use crate::{ - Adt, AssocItem, Enum, GenericParam, HasVisibility, Impl, Module, ModuleDef, ScopeDef, Type, - Variant, + Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, Module, ModuleDef, + ScopeDef, Type, Variant, }; use crate::term_search::TypeTree; @@ -78,7 +79,7 @@ pub(super) fn trivial<'a>( lookup.insert(ty.clone(), std::iter::once(tt.clone())); // Don't suggest local references as they are not valid for return - if matches!(tt, TypeTree::Local(_)) && ty.is_reference() { + if matches!(tt, TypeTree::Local(_)) && ty.contains_reference(db) { return None; } @@ -113,37 +114,67 @@ fn variant_helper( variant: Variant, goal: &Type, ) -> Vec<(Type, Vec)> { - let generics = db.generic_params(variant.parent_enum(db).id.into()); + let generics = GenericDef::from(variant.parent_enum(db)); + + // Ignore unstable variants + if variant.is_unstable(db) { + return Vec::new(); + } // Ignore enums with const generics - if generics - .type_or_consts - .values() - .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) - { + if !generics.const_params(db).is_empty() { return Vec::new(); } // We currently do not check lifetime bounds so ignore all types that have something to do // with them - if !generics.lifetimes.is_empty() { + if !generics.lifetime_params(db).is_empty() { return Vec::new(); } + // Only account for stable type parameters for now + let type_params = generics.type_params(db); + + // Only account for stable type parameters for now, unstable params can be default + // tho, for example in `Box` + if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) { + return Vec::new(); + } + + let non_default_type_params_len = + type_params.iter().filter(|it| it.default(db).is_none()).count(); + let generic_params = lookup .iter_types() .collect::>() // Force take ownership .into_iter() - .permutations(generics.type_or_consts.len()); + .permutations(non_default_type_params_len); generic_params .filter_map(|generics| { + // Insert default type params + let mut g = generics.into_iter(); + let generics: Vec<_> = type_params + .iter() + .map(|it| match it.default(db) { + Some(ty) => ty, + None => g.next().expect("Missing type param"), + }) + .collect(); + let enum_ty = parent_enum.ty_with_generics(db, generics.iter().cloned()); + // Allow types with generics only if they take us straight to goal for + // performance reasons if !generics.is_empty() && !enum_ty.could_unify_with_deeply(db, goal) { return None; } + // Ignore types that have something to do with lifetimes + if enum_ty.contains_reference(db) { + return None; + } + // Early exit if some param cannot be filled from lookup let param_trees: Vec> = variant .fields(db) @@ -203,33 +234,64 @@ fn variant_helper( Some(trees) } ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => { - let generics = db.generic_params(it.id.into()); + // Ignore unstable + if it.is_unstable(db) { + return None; + } + + let generics = GenericDef::from(*it); // Ignore enums with const generics - if generics - .type_or_consts - .values() - .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) - { + if !generics.const_params(db).is_empty() { return None; } // We currently do not check lifetime bounds so ignore all types that have something to do // with them - if !generics.lifetimes.is_empty() { + if !generics.lifetime_params(db).is_empty() { return None; } + let type_params = generics.type_params(db); + + // Only account for stable type parameters for now, unstable params can be default + // tho, for example in `Box` + if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) { + return None; + } + + let non_default_type_params_len = + type_params.iter().filter(|it| it.default(db).is_none()).count(); + let generic_params = lookup .iter_types() .collect::>() // Force take ownership .into_iter() - .permutations(generics.type_or_consts.len()); + .permutations(non_default_type_params_len); let trees = generic_params .filter_map(|generics| { + // Insert default type params + let mut g = generics.into_iter(); + let generics: Vec<_> = type_params + .iter() + .map(|it| match it.default(db) { + Some(ty) => ty, + None => g.next().expect("Missing type param"), + }) + .collect(); let struct_ty = it.ty_with_generics(db, generics.iter().cloned()); - if !generics.is_empty() && !struct_ty.could_unify_with_deeply(db, goal) { + + // Allow types with generics only if they take us straight to goal for + // performance reasons + if non_default_type_params_len != 0 + && struct_ty.could_unify_with_deeply(db, goal) + { + return None; + } + + // Ignore types that have something to do with lifetimes + if struct_ty.contains_reference(db) { return None; } let fileds = it.fields(db); @@ -301,20 +363,31 @@ pub(super) fn free_function<'a>( defs.iter() .filter_map(|def| match def { ScopeDef::ModuleDef(ModuleDef::Function(it)) => { - let generics = db.generic_params(it.id.into()); + let generics = GenericDef::from(*it); // Skip functions that require const generics - if generics - .type_or_consts - .values() - .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) - { + if !generics.const_params(db).is_empty() { return None; } - // Ignore bigger number of generics for now as they kill the performance // Ignore lifetimes as we do not check them - if generics.type_or_consts.len() > 0 || !generics.lifetimes.is_empty() { + if !generics.lifetime_params(db).is_empty() { + return None; + } + + let type_params = generics.type_params(db); + + // Only account for stable type parameters for now, unstable params can be default + // tho, for example in `Box` + if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) { + return None; + } + + let non_default_type_params_len = + type_params.iter().filter(|it| it.default(db).is_none()).count(); + + // Ignore bigger number of generics for now as they kill the performance + if non_default_type_params_len > 0 { return None; } @@ -322,16 +395,26 @@ pub(super) fn free_function<'a>( .iter_types() .collect::>() // Force take ownership .into_iter() - .permutations(generics.type_or_consts.len()); + .permutations(non_default_type_params_len); let trees: Vec<_> = generic_params .filter_map(|generics| { + // Insert default type params + let mut g = generics.into_iter(); + let generics: Vec<_> = type_params + .iter() + .map(|it| match it.default(db) { + Some(ty) => ty, + None => g.next().expect("Missing type param"), + }) + .collect(); + let ret_ty = it.ret_type_with_generics(db, generics.iter().cloned()); // Filter out private and unsafe functions if !it.is_visible_from(db, *module) || it.is_unsafe_to_call(db) || it.is_unstable(db) - || ret_ty.is_reference() + || ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { return None; @@ -417,24 +500,17 @@ pub(super) fn impl_method<'a>( _ => None, }) .filter_map(|(imp, ty, it)| { - let fn_generics = db.generic_params(it.id.into()); - let imp_generics = db.generic_params(imp.id.into()); + let fn_generics = GenericDef::from(it); + let imp_generics = GenericDef::from(imp); // Ignore impl if it has const type arguments - if fn_generics - .type_or_consts - .values() - .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) - || imp_generics - .type_or_consts - .values() - .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) + if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty() { return None; } // Ignore all functions that have something to do with lifetimes as we don't check them - if !fn_generics.lifetimes.is_empty() { + if !fn_generics.lifetime_params(db).is_empty() { return None; } @@ -448,8 +524,25 @@ pub(super) fn impl_method<'a>( return None; } + let imp_type_params = imp_generics.type_params(db); + let fn_type_params = fn_generics.type_params(db); + + // Only account for stable type parameters for now, unstable params can be default + // tho, for example in `Box` + if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) + || fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) + { + return None; + } + + let non_default_type_params_len = imp_type_params + .iter() + .chain(fn_type_params.iter()) + .filter(|it| it.default(db).is_none()) + .count(); + // Ignore bigger number of generics for now as they kill the performance - if imp_generics.type_or_consts.len() + fn_generics.type_or_consts.len() > 0 { + if non_default_type_params_len > 0 { return None; } @@ -457,16 +550,27 @@ pub(super) fn impl_method<'a>( .iter_types() .collect::>() // Force take ownership .into_iter() - .permutations(imp_generics.type_or_consts.len() + fn_generics.type_or_consts.len()); + .permutations(non_default_type_params_len); let trees: Vec<_> = generic_params .filter_map(|generics| { + // Insert default type params + let mut g = generics.into_iter(); + let generics: Vec<_> = imp_type_params + .iter() + .chain(fn_type_params.iter()) + .map(|it| match it.default(db) { + Some(ty) => ty, + None => g.next().expect("Missing type param"), + }) + .collect(); + let ret_ty = it.ret_type_with_generics( db, ty.type_arguments().chain(generics.iter().cloned()), ); // Filter out functions that return references - if ret_ty.is_reference() || ret_ty.is_raw_ptr() { + if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { return None; } @@ -590,3 +694,157 @@ pub(super) fn famous_types<'a>( }) .filter(|tt| tt.ty(db).could_unify_with_deeply(db, goal)) } + +/// # Impl static method (without self type) tactic +/// +/// Attempts different functions from impl blocks that take no self parameter. +/// +/// Updates lookup by new types reached and returns iterator that yields +/// elements that unify with `goal`. +/// +/// # Arguments +/// * `db` - HIR database +/// * `module` - Module where the term search target location +/// * `defs` - Set of items in scope at term search target location +/// * `lookup` - Lookup table for types +/// * `goal` - Term search target type +pub(super) fn impl_static_method<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + _defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + lookup + .take_types_wishlist() + .into_iter() + .chain(iter::once(goal.clone())) + .flat_map(|ty| { + Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp)) + }) + .filter(|(_, imp)| !imp.is_unsafe(db)) + .flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item))) + .filter_map(|(imp, ty, it)| match it { + AssocItem::Function(f) => Some((imp, ty, f)), + _ => None, + }) + .filter_map(|(imp, ty, it)| { + let fn_generics = GenericDef::from(it); + let imp_generics = GenericDef::from(imp); + + // Ignore impl if it has const type arguments + if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty() + { + return None; + } + + // Ignore all functions that have something to do with lifetimes as we don't check them + if !fn_generics.lifetime_params(db).is_empty() + || !imp_generics.lifetime_params(db).is_empty() + { + return None; + } + + // Ignore functions with self param + if it.has_self_param(db) { + return None; + } + + // Filter out private and unsafe functions + if !it.is_visible_from(db, *module) || it.is_unsafe_to_call(db) || it.is_unstable(db) { + return None; + } + + let imp_type_params = imp_generics.type_params(db); + let fn_type_params = fn_generics.type_params(db); + + // Only account for stable type parameters for now, unstable params can be default + // tho, for example in `Box` + if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) + || fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) + { + return None; + } + + let non_default_type_params_len = imp_type_params + .iter() + .chain(fn_type_params.iter()) + .filter(|it| it.default(db).is_none()) + .count(); + + // Ignore bigger number of generics for now as they kill the performance + if non_default_type_params_len > 0 { + return None; + } + + let generic_params = lookup + .iter_types() + .collect::>() // Force take ownership + .into_iter() + .permutations(non_default_type_params_len); + + let trees: Vec<_> = generic_params + .filter_map(|generics| { + // Insert default type params + let mut g = generics.into_iter(); + let generics: Vec<_> = imp_type_params + .iter() + .chain(fn_type_params.iter()) + .map(|it| match it.default(db) { + Some(ty) => ty, + None => g.next().expect("Missing type param"), + }) + .collect(); + + let ret_ty = it.ret_type_with_generics( + db, + ty.type_arguments().chain(generics.iter().cloned()), + ); + // Filter out functions that return references + if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { + return None; + } + + // Ignore functions that do not change the type + // if ty.could_unify_with_deeply(db, &ret_ty) { + // return None; + // } + + // Early exit if some param cannot be filled from lookup + let param_trees: Vec> = it + .params_without_self_with_generics( + db, + ty.type_arguments().chain(generics.iter().cloned()), + ) + .into_iter() + .map(|field| lookup.find_autoref(db, &field.ty())) + .collect::>()?; + + // Note that we need special case for 0 param constructors because of multi cartesian + // product + let fn_trees: Vec = if param_trees.is_empty() { + vec![TypeTree::Function { func: it, generics, params: Vec::new() }] + } else { + param_trees + .into_iter() + .multi_cartesian_product() + .take(MAX_VARIATIONS) + .map(|params| TypeTree::Function { + func: it, + generics: generics.clone(), + + params, + }) + .collect() + }; + + lookup.insert(ret_ty.clone(), fn_trees.iter().cloned()); + Some((ret_ty, fn_trees)) + }) + .collect(); + Some(trees) + }) + .flatten() + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .flatten() +} diff --git a/crates/hir/src/term_search/type_tree.rs b/crates/hir/src/term_search/type_tree.rs index 4178ba2d7df..3cb2fcdd64b 100644 --- a/crates/hir/src/term_search/type_tree.rs +++ b/crates/hir/src/term_search/type_tree.rs @@ -1,16 +1,18 @@ //! Type tree for term search use hir_def::find_path::PrefixKind; +use hir_expand::mod_path::ModPath; use hir_ty::{db::HirDatabase, display::HirDisplay}; use itertools::Itertools; use crate::{ - Adt, AsAssocItem, Const, ConstParam, Field, Function, Local, ModuleDef, SemanticsScope, Static, - Struct, StructKind, Trait, Type, Variant, + Adt, AsAssocItem, Const, ConstParam, Field, Function, GenericDef, Local, ModuleDef, + SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant, }; -/// Helper function to prefix items with modules when required -fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String { +/// Helper function to get path to `ModuleDef` +fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option { + let db = sema_scope.db; // Account for locals shadowing items from module let name_hit_count = def.name(db).map(|def_name| { let mut name_hit_count = 0; @@ -23,12 +25,45 @@ fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &Mo }); let m = sema_scope.module(); - let path = match name_hit_count { + match name_hit_count { Some(0..=1) | None => m.find_use_path(db.upcast(), *def, false, true), Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true), - }; + } +} - path.map(|it| it.display(db.upcast()).to_string()).expect("use path error") +/// Helper function to get path to `ModuleDef` as string +fn mod_item_path_str(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String { + let path = mod_item_path(sema_scope, def); + path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap() +} + +/// Helper function to get path to `Type` +fn type_path(sema_scope: &SemanticsScope<'_>, ty: &Type) -> String { + let db = sema_scope.db; + match ty.as_adt() { + Some(adt) => { + let ty_name = ty.display(db).to_string(); + + let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt)).unwrap(); + path.pop_segment(); + let path = path.display(db.upcast()).to_string(); + match path.is_empty() { + true => ty_name, + false => format!("{path}::{ty_name}"), + } + } + None => ty.display(db).to_string(), + } +} + +/// Helper function to filter out generic parameters that are default +fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]) -> Vec { + def.type_params(db) + .into_iter() + .zip(generics) + .filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg)) + .map(|(_, arg)| arg.clone()) + .collect() } /// Type tree shows how can we get from set of types to some type. @@ -85,8 +120,8 @@ impl TypeTree { pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { let db = sema_scope.db; match self { - TypeTree::Const(it) => mod_item_path(db, sema_scope, &ModuleDef::Const(*it)), - TypeTree::Static(it) => mod_item_path(db, sema_scope, &ModuleDef::Static(*it)), + TypeTree::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), + TypeTree::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), TypeTree::Local(it) => return it.name(db).display(db.upcast()).to_string(), TypeTree::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), TypeTree::FamousType { value, .. } => return value.to_string(), @@ -100,7 +135,7 @@ pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { match func.as_assoc_item(db).unwrap().containing_trait_or_trait_impl(db) { Some(trait_) => { let trait_name = - mod_item_path(db, sema_scope, &ModuleDef::Trait(trait_)); + mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_)); let target = match self_param.access(db) { crate::Access::Shared => format!("&{target}"), crate::Access::Exclusive => format!("&mut {target}"), @@ -116,15 +151,51 @@ pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { } else { let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", "); - let fn_name = mod_item_path(db, sema_scope, &ModuleDef::Function(*func)); - format!("{fn_name}({args})",) + match func.as_assoc_item(db).map(|it| it.container(db)) { + Some(container) => { + let container_name = match container { + crate::AssocItemContainer::Trait(trait_) => { + mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_)) + } + crate::AssocItemContainer::Impl(imp) => { + let self_ty = imp.self_ty(db); + // Should it be guaranteed that `mod_item_path` always exists? + match self_ty + .as_adt() + .and_then(|adt| mod_item_path(sema_scope, &adt.into())) + { + Some(path) => { + path.display(sema_scope.db.upcast()).to_string() + } + None => self_ty.display(db).to_string(), + } + } + }; + let fn_name = func.name(db).display(db.upcast()).to_string(); + format!("{container_name}::{fn_name}({args})",) + } + None => { + let fn_name = + mod_item_path_str(sema_scope, &ModuleDef::Function(*func)); + format!("{fn_name}({args})",) + } + } } } TypeTree::Variant { variant, generics, params } => { + let generics = non_default_generics(db, (*variant).into(), generics); + let generics_str = match generics.is_empty() { + true => String::new(), + false => { + let generics = + generics.iter().map(|it| type_path(sema_scope, it)).join(", "); + format!("::<{generics}>") + } + }; let inner = match variant.kind(db) { StructKind::Tuple => { let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", "); - format!("({args})") + format!("{generics_str}({args})") } StructKind::Record => { let fields = variant.fields(db); @@ -139,21 +210,16 @@ pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { ) }) .join(", "); - format!("{{ {args} }}") + format!("{generics_str}{{ {args} }}") } - StructKind::Unit => match generics.is_empty() { - true => String::new(), - false => { - let generics = generics.iter().map(|it| it.display(db)).join(", "); - format!("::<{generics}>") - } - }, + StructKind::Unit => generics_str, }; - let prefix = mod_item_path(db, sema_scope, &ModuleDef::Variant(*variant)); + let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant)); format!("{prefix}{inner}") } TypeTree::Struct { strukt, generics, params } => { + let generics = non_default_generics(db, (*strukt).into(), generics); let inner = match strukt.kind(db) { StructKind::Tuple => { let args = params.iter().map(|a| a.gen_source_code(sema_scope)).join(", "); @@ -177,13 +243,14 @@ pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { StructKind::Unit => match generics.is_empty() { true => String::new(), false => { - let generics = generics.iter().map(|it| it.display(db)).join(", "); + let generics = + generics.iter().map(|it| type_path(sema_scope, it)).join(", "); format!("::<{generics}>") } }, }; - let prefix = mod_item_path(db, sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt))); + let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt))); format!("{prefix}{inner}") } TypeTree::Field { type_tree, field } => { diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 3c4c4eed011..a32e36b7127 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -91,7 +91,7 @@ enum Option { Some(T), None } fn f() { let a: i32 = 1; let b: Option = todo$0!(); }"#, r#"macro_rules! todo { () => (_) }; enum Option { Some(T), None } - fn f() { let a: i32 = 1; let b: Option = Option::None::; }"#, + fn f() { let a: i32 = 1; let b: Option = Option::None; }"#, ) } @@ -108,6 +108,42 @@ enum Option { None, Some(T) } ) } + #[test] + fn test_enum_with_generics3() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + enum Option { None, Some(T) } + fn f() { let a: Option = Option::None; let b: Option> = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + enum Option { None, Some(T) } + fn f() { let a: Option = Option::None; let b: Option> = Option::Some(a); }"#, + ) + } + + #[test] + fn test_enum_with_generics4() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + enum Foo { Foo(T) } + fn f() { let a = 0; let b: Foo = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + enum Foo { Foo(T) } + fn f() { let a = 0; let b: Foo = Foo::Foo(a); }"#, + ); + + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + enum Foo { Foo(T) } + fn f() { let a: Foo = Foo::Foo(0); let b: Foo = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + enum Foo { Foo(T) } + fn f() { let a: Foo = Foo::Foo(0); let b: Foo = a; }"#, + ) + } + #[test] fn test_newtype() { check_assist( diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 3862acc2af4..7e1811b4cac 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -148,7 +148,7 @@ fn build_ctx(&self) -> Ctx<'a> { let mut defaulted_params: Vec = Default::default(); self.generic_def .into_iter() - .flat_map(|it| it.type_params(db)) + .flat_map(|it| it.type_or_const_params(db)) .skip(skip) // The actual list of trait type parameters may be longer than the one // used in the `impl` block due to trailing default type parameters. diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index bca08f91c1e..2ca93b5ca89 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -439,17 +439,18 @@ fn trim(s: &str) -> String { if let Some(mut err_idx) = err.find("error[E") { err_idx += 7; let err_code = &err[err_idx..err_idx + 4]; - // if err_code == "0308" { - println!("{}", err); - println!("{}", generated); - // } + if err_code == "0282" { + continue; // Byproduct of testing method + } + bar.println(err); + bar.println(generated); acc.error_codes .entry(err_code.to_owned()) .and_modify(|n| *n += 1) .or_insert(1); } else { acc.syntax_errors += 1; - bar.println(format!("Syntax error here >>>>\n{}", err)); + bar.println(format!("Syntax error: \n{}", err)); } } }