From bb3c7cff60a95e1befad87bbd0f1f6bc47d818ba Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Mon, 11 Dec 2023 17:04:20 +0200 Subject: [PATCH 1/8] Introduce term search to rust-analyzer --- crates/hir-ty/src/infer.rs | 2 +- crates/hir-ty/src/infer/unify.rs | 75 +++ crates/hir-ty/src/lib.rs | 4 +- crates/hir-ty/src/mir/borrowck.rs | 162 +++++ crates/hir-ty/src/mir/lower.rs | 2 +- crates/hir/src/lib.rs | 174 +++++- crates/hir/src/term_search/mod.rs | 162 +++++ crates/hir/src/term_search/tactics.rs | 553 ++++++++++++++++++ crates/hir/src/term_search/type_tree.rs | 242 ++++++++ .../ide-assists/src/handlers/term_search.rs | 181 ++++++ crates/ide-assists/src/lib.rs | 2 + .../src/handlers/typed_hole.rs | 238 ++++++-- crates/ide-diagnostics/src/tests.rs | 85 +++ .../rust-analyzer/src/cli/analysis_stats.rs | 200 ++++++- crates/rust-analyzer/src/cli/flags.rs | 6 + 15 files changed, 2022 insertions(+), 66 deletions(-) create mode 100644 crates/hir/src/term_search/mod.rs create mode 100644 crates/hir/src/term_search/tactics.rs create mode 100644 crates/hir/src/term_search/type_tree.rs create mode 100644 crates/ide-assists/src/handlers/term_search.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 71c3f89716d8..b0ae437ee3c1 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -68,7 +68,7 @@ use crate::{ #[allow(unreachable_pub)] pub use coerce::could_coerce; #[allow(unreachable_pub)] -pub use unify::could_unify; +pub use unify::{could_unify, could_unify_deeply}; use cast::CastCheck; pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index de23ca34990b..a6c2dfad550c 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -82,6 +82,37 @@ pub fn could_unify( unify(db, env, tys).is_some() } +pub fn could_unify_deeply( + db: &dyn HirDatabase, + env: Arc, + tys: &Canonical<(Ty, Ty)>, +) -> bool { + let mut table = InferenceTable::new(db, env); + let vars = Substitution::from_iter( + Interner, + tys.binders.iter(Interner).map(|it| match &it.kind { + chalk_ir::VariableKind::Ty(_) => { + GenericArgData::Ty(table.new_type_var()).intern(Interner) + } + chalk_ir::VariableKind::Lifetime => { + GenericArgData::Ty(table.new_type_var()).intern(Interner) + } // FIXME: maybe wrong? + chalk_ir::VariableKind::Const(ty) => { + GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner) + } + }), + ); + let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); + let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); + let ty1_with_vars = table.normalize_associated_types_in(ty1_with_vars); + let ty2_with_vars = table.normalize_associated_types_in(ty2_with_vars); + // table.resolve_obligations_as_possible(); + // table.propagate_diverging_flag(); + // let ty1_with_vars = table.resolve_completely(ty1_with_vars); + // let ty2_with_vars = table.resolve_completely(ty2_with_vars); + table.unify_deeply(&ty1_with_vars, &ty2_with_vars) +} + pub(crate) fn unify( db: &dyn HirDatabase, env: Arc, @@ -431,6 +462,18 @@ impl<'a> InferenceTable<'a> { true } + /// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that. + pub(crate) fn unify_deeply>(&mut self, ty1: &T, ty2: &T) -> bool { + let result = match self.try_unify(ty1, ty2) { + Ok(r) => r, + Err(_) => return false, + }; + result.goals.iter().all(|goal| { + let canonicalized = self.canonicalize(goal.clone()); + self.try_fulfill_obligation(&canonicalized) + }) + } + /// Unify two relatable values (e.g. `Ty`) and return new trait goals arising from it, so the /// caller needs to deal with them. pub(crate) fn try_unify>( @@ -661,6 +704,38 @@ impl<'a> InferenceTable<'a> { } } + fn try_fulfill_obligation( + &mut self, + canonicalized: &Canonicalized>, + ) -> bool { + let solution = self.db.trait_solve( + self.trait_env.krate, + self.trait_env.block, + canonicalized.value.clone(), + ); + + // FIXME: Does just returning `solution.is_some()` work? + match solution { + Some(Solution::Unique(canonical_subst)) => { + canonicalized.apply_solution( + self, + Canonical { + binders: canonical_subst.binders, + // FIXME: handle constraints + value: canonical_subst.value.subst, + }, + ); + true + } + Some(Solution::Ambig(Guidance::Definite(substs))) => { + canonicalized.apply_solution(self, substs); + true + } + Some(_) => true, + None => false, + } + } + pub(crate) fn callable_sig( &mut self, ty: &Ty, diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 70138633341c..ec97bdc2c434 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -79,8 +79,8 @@ pub use builder::{ParamKind, TyBuilder}; pub use chalk_ext::*; pub use infer::{ closure::{CaptureKind, CapturedItem}, - could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic, - InferenceResult, OverloadedDeref, PointerCast, + could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode, + InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, }; pub use interner::Interner; pub use lower::{ diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs index 9089c11c5d9b..8d3200098127 100644 --- a/crates/hir-ty/src/mir/borrowck.rs +++ b/crates/hir-ty/src/mir/borrowck.rs @@ -7,6 +7,7 @@ use std::iter; use hir_def::{DefWithBodyId, HasModule}; use la_arena::ArenaMap; +use rustc_hash::FxHashMap; use stdx::never; use triomphe::Arc; @@ -36,11 +37,27 @@ pub struct MovedOutOfRef { pub span: MirSpan, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PartiallyMoved { + pub ty: Ty, + pub span: MirSpan, + pub local: LocalId, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BorrowRegion { + pub local: LocalId, + pub kind: BorrowKind, + pub places: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct BorrowckResult { pub mir_body: Arc, pub mutability_of_locals: ArenaMap, pub moved_out_of_ref: Vec, + pub partially_moved: Vec, + pub borrow_regions: Vec, } fn all_mir_bodies( @@ -80,6 +97,8 @@ pub fn borrowck_query( res.push(BorrowckResult { mutability_of_locals: mutability_of_locals(db, &body), moved_out_of_ref: moved_out_of_ref(db, &body), + partially_moved: partially_moved(db, &body), + borrow_regions: borrow_regions(db, &body), mir_body: body, }); })?; @@ -188,6 +207,149 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec result } +fn partially_moved(db: &dyn HirDatabase, body: &MirBody) -> Vec { + let mut result = vec![]; + let mut for_operand = |op: &Operand, span: MirSpan| match op { + Operand::Copy(p) | Operand::Move(p) => { + let mut ty: Ty = body.locals[p.local].ty.clone(); + for proj in p.projection.lookup(&body.projection_store) { + ty = proj.projected_ty( + ty, + db, + |c, subst, f| { + let (def, _) = db.lookup_intern_closure(c.into()); + let infer = db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures + .get(f) + .expect("broken closure field") + .ty + .clone() + .substitute(Interner, parent_subst) + }, + body.owner.module(db.upcast()).krate(), + ); + } + if !ty.clone().is_copy(db, body.owner) + && !ty.data(Interner).flags.intersects(TypeFlags::HAS_ERROR) + { + result.push(PartiallyMoved { span, ty, local: p.local }); + } + } + Operand::Constant(_) | Operand::Static(_) => (), + }; + for (_, block) in body.basic_blocks.iter() { + db.unwind_if_cancelled(); + for statement in &block.statements { + match &statement.kind { + StatementKind::Assign(_, r) => match r { + Rvalue::ShallowInitBoxWithAlloc(_) => (), + Rvalue::ShallowInitBox(o, _) + | Rvalue::UnaryOp(_, o) + | Rvalue::Cast(_, o, _) + | Rvalue::Repeat(o, _) + | Rvalue::Use(o) => for_operand(o, statement.span), + Rvalue::CopyForDeref(_) + | Rvalue::Discriminant(_) + | Rvalue::Len(_) + | Rvalue::Ref(_, _) => (), + Rvalue::CheckedBinaryOp(_, o1, o2) => { + for_operand(o1, statement.span); + for_operand(o2, statement.span); + } + Rvalue::Aggregate(_, ops) => { + for op in ops.iter() { + for_operand(op, statement.span); + } + } + }, + StatementKind::FakeRead(_) + | StatementKind::Deinit(_) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Nop => (), + } + } + match &block.terminator { + Some(terminator) => match &terminator.kind { + TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, terminator.span), + TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::CoroutineDrop + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } => (), + TerminatorKind::DropAndReplace { value, .. } => { + for_operand(value, terminator.span); + } + TerminatorKind::Call { func, args, .. } => { + for_operand(func, terminator.span); + args.iter().for_each(|it| for_operand(it, terminator.span)); + } + TerminatorKind::Assert { cond, .. } => { + for_operand(cond, terminator.span); + } + TerminatorKind::Yield { value, .. } => { + for_operand(value, terminator.span); + } + }, + None => (), + } + } + result.shrink_to_fit(); + result +} + +fn borrow_regions(db: &dyn HirDatabase, body: &MirBody) -> Vec { + let mut borrows = FxHashMap::default(); + for (_, block) in body.basic_blocks.iter() { + db.unwind_if_cancelled(); + for statement in &block.statements { + match &statement.kind { + StatementKind::Assign(_, r) => match r { + Rvalue::Ref(kind, p) => { + borrows + .entry(p.local) + .and_modify(|it: &mut BorrowRegion| { + it.places.push(statement.span); + }) + .or_insert_with(|| BorrowRegion { + local: p.local, + kind: *kind, + places: vec![statement.span], + }); + } + _ => (), + }, + _ => (), + } + } + match &block.terminator { + Some(terminator) => match &terminator.kind { + TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::CoroutineDrop + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } => (), + TerminatorKind::DropAndReplace { .. } => {} + TerminatorKind::Call { .. } => {} + _ => (), + }, + None => (), + } + } + + borrows.into_values().collect() +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ProjectionCase { /// Projection is a local diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 1572a6d497c5..0ba8a17103e0 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1246,7 +1246,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.push_assignment(current, place, op.into(), expr_id.into()); Ok(Some(current)) } - Expr::Underscore => not_supported!("underscore"), + Expr::Underscore => Ok(Some(current)), } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 32abbc80c6af..24323284e9b8 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -31,6 +31,7 @@ mod has_source; pub mod db; pub mod diagnostics; pub mod symbols; +pub mod term_search; mod display; @@ -1084,6 +1085,26 @@ impl Field { Type::new(db, var_id, ty) } + pub fn ty_with_generics( + &self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Type { + let var_id = self.parent.into(); + let def_id: AdtId = match self.parent { + VariantDef::Struct(it) => it.id.into(), + VariantDef::Union(it) => it.id.into(), + VariantDef::Variant(it) => it.parent_enum(db).id.into(), + }; + let substs = TyBuilder::subst_for_def(db, def_id, None) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs); + Type::new(db, var_id, ty) + } + pub fn layout(&self, db: &dyn HirDatabase) -> Result { db.layout_of_ty( self.ty(db).ty, @@ -1137,6 +1158,20 @@ impl Struct { Type::from_def(db, self.id) } + pub fn ty_with_generics( + self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Type { + let substs = TyBuilder::subst_for_def(db, self.id, None) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let ty = db.ty(self.id.into()).substitute(Interner, &substs); + Type::new(db, self.id, ty) + } + pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type { Type::from_value_def(db, self.id) } @@ -1228,6 +1263,20 @@ impl Enum { Type::from_def(db, self.id) } + pub fn ty_with_generics( + &self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Type { + let substs = TyBuilder::subst_for_def(db, self.id, None) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let ty = db.ty(self.id.into()).substitute(Interner, &substs); + Type::new(db, self.id, ty) + } + /// The type of the enum variant bodies. pub fn variant_body_ty(self, db: &dyn HirDatabase) -> Type { Type::new_for_crate( @@ -1789,6 +1838,39 @@ impl Function { Type::new_with_resolver_inner(db, &resolver, ty) } + pub fn ret_type_with_generics( + self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Type { + let resolver = self.id.resolver(db.upcast()); + let parent_id: Option = match self.id.lookup(db.upcast()).container { + ItemContainerId::ImplId(it) => Some(it.into()), + ItemContainerId::TraitId(it) => Some(it.into()), + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None, + }; + let parent_substs = parent_id.map(|id| { + TyBuilder::subst_for_def(db, id, None) + .fill(|_| { + GenericArg::new( + Interner, + GenericArgData::Ty(generics.next().unwrap().ty.clone()), + ) + }) + .build() + }); + + let substs = TyBuilder::subst_for_def(db, self.id, parent_substs) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + + let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs); + let ty = callable_sig.ret().clone(); + Type::new_with_resolver_inner(db, &resolver, ty) + } + pub fn async_ret_type(self, db: &dyn HirDatabase) -> Option { if !self.is_async(db) { return None; @@ -1855,6 +1937,47 @@ impl Function { .collect() } + pub fn params_without_self_with_generics( + self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Vec { + let environment = db.trait_environment(self.id.into()); + let parent_id: Option = match self.id.lookup(db.upcast()).container { + ItemContainerId::ImplId(it) => Some(it.into()), + ItemContainerId::TraitId(it) => Some(it.into()), + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None, + }; + let parent_substs = parent_id.map(|id| { + TyBuilder::subst_for_def(db, id, None) + .fill(|_| { + GenericArg::new( + Interner, + GenericArgData::Ty(generics.next().unwrap().ty.clone()), + ) + }) + .build() + }); + + let substs = TyBuilder::subst_for_def(db, self.id, parent_substs) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs); + let skip = if db.function_data(self.id).has_self_param() { 1 } else { 0 }; + callable_sig + .params() + .iter() + .enumerate() + .skip(skip) + .map(|(idx, ty)| { + let ty = Type { env: environment.clone(), ty: ty.clone() }; + Param { func: self, ty, idx } + }) + .collect() + } + pub fn is_const(self, db: &dyn HirDatabase) -> bool { db.function_data(self.id).has_const_kw() } @@ -1889,6 +2012,11 @@ impl Function { db.function_data(self.id).attrs.is_bench() } + /// Is this function marked as unstable with `#[feature]` attribute? + pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { + db.function_data(self.id).attrs.is_unstable() + } + pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool { hir_ty::is_fn_unsafe_to_call(db, self.id) } @@ -2052,6 +2180,36 @@ impl SelfParam { let ty = callable_sig.params()[0].clone(); Type { env: environment, ty } } + + pub fn ty_with_generics( + &self, + db: &dyn HirDatabase, + mut generics: impl Iterator, + ) -> Type { + let parent_id: GenericDefId = match self.func.lookup(db.upcast()).container { + ItemContainerId::ImplId(it) => it.into(), + ItemContainerId::TraitId(it) => it.into(), + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { + panic!("Never get here") + } + }; + + let parent_substs = TyBuilder::subst_for_def(db, parent_id, None) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let substs = TyBuilder::subst_for_def(db, self.func, Some(parent_substs)) + .fill(|_| { + GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + }) + .build(); + let callable_sig = + db.callable_item_signature(self.func.into()).substitute(Interner, &substs); + let environment = db.trait_environment(self.func.into()); + let ty = callable_sig.params()[0].clone(); + Type { env: environment, ty } + } } impl HasVisibility for Function { @@ -3285,13 +3443,8 @@ impl Impl { .filter(filter), ) }); - for id in def_crates - .iter() - .flat_map(|&id| Crate { id }.transitive_reverse_dependencies(db)) - .map(|Crate { id }| id) - .chain(def_crates.iter().copied()) - .unique() - { + + for Crate { id } in Crate::all(db) { all.extend( db.trait_impls_in_crate(id) .for_self_ty_without_blanket_impls(fp) @@ -3520,7 +3673,7 @@ pub enum CaptureKind { Move, } -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct Type { env: Arc, ty: Ty, @@ -4374,6 +4527,11 @@ impl Type { hir_ty::could_unify(db, self.env.clone(), &tys) } + pub fn could_unify_with_deeply(&self, db: &dyn HirDatabase, other: &Type) -> bool { + let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone())); + hir_ty::could_unify_deeply(db, self.env.clone(), &tys) + } + pub fn could_coerce_to(&self, db: &dyn HirDatabase, to: &Type) -> bool { let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), to.ty.clone())); hir_ty::could_coerce(db, self.env.clone(), &tys) diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs new file mode 100644 index 000000000000..b1e616e004cd --- /dev/null +++ b/crates/hir/src/term_search/mod.rs @@ -0,0 +1,162 @@ +//! Term search + +use hir_def::type_ref::Mutability; +use hir_ty::db::HirDatabase; +use itertools::Itertools; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type}; + +pub mod type_tree; +pub use type_tree::TypeTree; + +mod tactics; + +const MAX_VARIATIONS: usize = 10; + +#[derive(Debug, Hash, PartialEq, Eq)] +enum NewTypesKey { + ImplMethod, + StructProjection, +} + +/// Lookup table for term search +#[derive(Default, Debug)] +struct LookupTable { + data: FxHashMap>, + new_types: FxHashMap>, + exhausted_scopedefs: FxHashSet, + round_scopedef_hits: FxHashSet, + scopedef_hits: FxHashMap, +} + +impl LookupTable { + fn new() -> Self { + let mut res: Self = Default::default(); + res.new_types.insert(NewTypesKey::ImplMethod, Vec::new()); + res.new_types.insert(NewTypesKey::StructProjection, Vec::new()); + res + } + + fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { + self.data + .iter() + .find(|(t, _)| t.could_unify_with_deeply(db, ty)) + .map(|(_, tts)| tts.iter().cloned().collect()) + } + + fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { + self.data + .iter() + .find(|(t, _)| t.could_unify_with_deeply(db, ty)) + .map(|(_, tts)| tts.iter().cloned().collect()) + .or_else(|| { + self.data + .iter() + .find(|(t, _)| { + Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty) + }) + .map(|(_, tts)| { + tts.iter().map(|tt| TypeTree::Reference(Box::new(tt.clone()))).collect() + }) + }) + } + + fn insert(&mut self, ty: Type, trees: impl Iterator) { + match self.data.get_mut(&ty) { + Some(it) => it.extend(trees.take(MAX_VARIATIONS)), + None => { + self.data.insert(ty.clone(), trees.take(MAX_VARIATIONS).collect()); + for it in self.new_types.values_mut() { + it.push(ty.clone()); + } + } + } + } + + fn iter_types(&self) -> impl Iterator + '_ { + self.data.keys().cloned() + } + + fn new_types(&mut self, key: NewTypesKey) -> Vec { + match self.new_types.get_mut(&key) { + Some(it) => std::mem::take(it), + None => Vec::new(), + } + } + + fn mark_exhausted(&mut self, def: ScopeDef) { + self.exhausted_scopedefs.insert(def); + } + + fn mark_fulfilled(&mut self, def: ScopeDef) { + self.round_scopedef_hits.insert(def); + } + + fn new_round(&mut self) { + for def in &self.round_scopedef_hits { + let hits = self.scopedef_hits.entry(*def).and_modify(|n| *n += 1).or_insert(0); + const MAX_ROUNDS_AFTER_HIT: u32 = 2; + if *hits > MAX_ROUNDS_AFTER_HIT { + self.exhausted_scopedefs.insert(*def); + } + } + self.round_scopedef_hits.clear(); + } + + fn exhausted_scopedefs(&self) -> &FxHashSet { + &self.exhausted_scopedefs + } +} + +/// # Term search +/// +/// Search for terms (expressions) that unify with the `goal` type. +/// +/// # Arguments +/// * `sema` - Semantics for the program +/// * `scope` - Semantic scope, captures context for the term search +/// * `goal` - Target / expected output type +pub fn term_search( + sema: &Semantics<'_, DB>, + scope: &SemanticsScope<'_>, + goal: &Type, +) -> Vec { + let mut defs = FxHashSet::default(); + defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(scope.module()))); + + scope.process_all_names(&mut |_, def| { + defs.insert(def); + }); + let module = scope.module(); + + let mut lookup = LookupTable::new(); + + // Try trivial tactic first, also populates lookup table + let mut solutions: Vec = + tactics::trivial(sema.db, &defs, &mut lookup, goal).collect(); + solutions.extend(tactics::famous_types(sema.db, &module, &defs, &mut lookup, goal)); + + let mut solution_found = !solutions.is_empty(); + + for _ in 0..5 { + lookup.new_round(); + + solutions.extend(tactics::type_constructor(sema.db, &module, &defs, &mut lookup, goal)); + 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)); + + if solution_found { + break; + } + + solution_found = !solutions.is_empty(); + + for def in lookup.exhausted_scopedefs() { + defs.remove(def); + } + } + + solutions.into_iter().unique().collect() +} diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs new file mode 100644 index 000000000000..87261617370c --- /dev/null +++ b/crates/hir/src/term_search/tactics.rs @@ -0,0 +1,553 @@ +//! Tactics for term search + +use hir_def::generics::TypeOrConstParamData; +use hir_ty::db::HirDatabase; +use hir_ty::mir::BorrowKind; +use hir_ty::TyBuilder; +use itertools::Itertools; +use rustc_hash::FxHashSet; + +use crate::{ + Adt, AssocItem, Enum, GenericParam, HasVisibility, Impl, Module, ModuleDef, ScopeDef, Type, + Variant, +}; + +use crate::term_search::TypeTree; + +use super::{LookupTable, NewTypesKey, MAX_VARIATIONS}; + +/// Trivial tactic +/// +/// Attempts to fulfill the goal by trying items in scope +/// Also works as a starting point to move all items in scope to lookup table +pub(super) fn trivial<'a>( + db: &'a dyn HirDatabase, + defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + defs.iter().filter_map(|def| { + let tt = match def { + ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(TypeTree::Const(*it)), + ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(TypeTree::Static(*it)), + ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(TypeTree::ConstParam(*it)), + ScopeDef::Local(it) => { + let borrowck = db.borrowck(it.parent).ok()?; + + let invalid = borrowck.iter().any(|b| { + b.partially_moved.iter().any(|moved| { + Some(&moved.local) == b.mir_body.binding_locals.get(it.binding_id) + }) || b.borrow_regions.iter().any(|region| { + // Shared borrows are fine + Some(®ion.local) == b.mir_body.binding_locals.get(it.binding_id) + && region.kind != BorrowKind::Shared + }) + }); + + if invalid { + return None; + } + + Some(TypeTree::Local(*it)) + } + _ => None, + }?; + + lookup.mark_exhausted(*def); + + let ty = tt.ty(db); + 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() { + return None; + } + + ty.could_unify_with_deeply(db, goal).then(|| tt) + }) +} + +/// Type constructor tactic +/// +/// Attempts different type constructors for enums and structs in scope +/// +/// # 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 type_constructor<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + fn variant_helper( + db: &dyn HirDatabase, + lookup: &mut LookupTable, + parent_enum: Enum, + variant: Variant, + goal: &Type, + ) -> Vec<(Type, Vec)> { + let generics = db.generic_params(variant.parent_enum(db).id.into()); + + // Ignore enums with const generics + if generics + .type_or_consts + .values() + .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) + { + 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() { + return Vec::new(); + } + + let generic_params = lookup + .iter_types() + .collect::>() // Force take ownership + .into_iter() + .permutations(generics.type_or_consts.len()); + + generic_params + .filter_map(|generics| { + let enum_ty = parent_enum.ty_with_generics(db, generics.iter().cloned()); + + if !generics.is_empty() && !enum_ty.could_unify_with_deeply(db, goal) { + return None; + } + + // Early exit if some param cannot be filled from lookup + let param_trees: Vec> = variant + .fields(db) + .into_iter() + .map(|field| { + lookup.find(db, &field.ty_with_generics(db, generics.iter().cloned())) + }) + .collect::>()?; + + // Note that we need special case for 0 param constructors because of multi cartesian + // product + let variant_trees: Vec = if param_trees.is_empty() { + vec![TypeTree::Variant { + variant, + generics: generics.clone(), + params: Vec::new(), + }] + } else { + param_trees + .into_iter() + .multi_cartesian_product() + .take(MAX_VARIATIONS) + .map(|params| TypeTree::Variant { + variant, + generics: generics.clone(), + params, + }) + .collect() + }; + lookup.insert(enum_ty.clone(), variant_trees.iter().cloned()); + + Some((enum_ty, variant_trees)) + }) + .collect() + } + defs.iter() + .filter_map(|def| match def { + ScopeDef::ModuleDef(ModuleDef::Variant(it)) => { + let variant_trees = variant_helper(db, lookup, it.parent_enum(db), *it, goal); + if variant_trees.is_empty() { + return None; + } + lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it))); + Some(variant_trees) + } + ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => { + let trees: Vec<(Type, Vec)> = enum_ + .variants(db) + .into_iter() + .flat_map(|it| variant_helper(db, lookup, enum_.clone(), it, goal)) + .collect(); + + if !trees.is_empty() { + lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(*enum_)))); + } + + Some(trees) + } + ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => { + let generics = db.generic_params(it.id.into()); + + // Ignore enums with const generics + if generics + .type_or_consts + .values() + .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) + { + 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() { + return None; + } + + let generic_params = lookup + .iter_types() + .collect::>() // Force take ownership + .into_iter() + .permutations(generics.type_or_consts.len()); + + let trees = generic_params + .filter_map(|generics| { + let struct_ty = it.ty_with_generics(db, generics.iter().cloned()); + if !generics.is_empty() && !struct_ty.could_unify_with_deeply(db, goal) { + return None; + } + let fileds = it.fields(db); + // Check if all fields are visible, otherwise we cannot fill them + if fileds.iter().any(|it| !it.is_visible_from(db, *module)) { + return None; + } + + // Early exit if some param cannot be filled from lookup + let param_trees: Vec> = fileds + .into_iter() + .map(|field| lookup.find(db, &field.ty(db))) + .collect::>()?; + + // Note that we need special case for 0 param constructors because of multi cartesian + // product + let struct_trees: Vec = if param_trees.is_empty() { + vec![TypeTree::Struct { strukt: *it, generics, params: Vec::new() }] + } else { + param_trees + .into_iter() + .multi_cartesian_product() + .take(MAX_VARIATIONS) + .map(|params| TypeTree::Struct { + strukt: *it, + generics: generics.clone(), + params, + }) + .collect() + }; + + lookup + .mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it)))); + lookup.insert(struct_ty.clone(), struct_trees.iter().cloned()); + + Some((struct_ty, struct_trees)) + }) + .collect(); + Some(trees) + } + _ => None, + }) + .flatten() + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .flatten() +} + +/// Free function tactic +/// +/// Attempts to call different functions in scope with parameters from lookup table +/// +/// # 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 free_function<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + defs.iter() + .filter_map(|def| match def { + ScopeDef::ModuleDef(ModuleDef::Function(it)) => { + let generics = db.generic_params(it.id.into()); + + // Skip functions that require const generics + if generics + .type_or_consts + .values() + .any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_))) + { + 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() { + return None; + } + + let generic_params = lookup + .iter_types() + .collect::>() // Force take ownership + .into_iter() + .permutations(generics.type_or_consts.len()); + + let trees: Vec<_> = generic_params + .filter_map(|generics| { + 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.is_raw_ptr() + { + return None; + } + + // Early exit if some param cannot be filled from lookup + let param_trees: Vec> = it + .params_without_self_with_generics(db, generics.iter().cloned()) + .into_iter() + .map(|field| { + let ty = field.ty(); + match ty.is_mutable_reference() { + true => None, + false => lookup.find_autoref(db, &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.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Function(*it))); + lookup.insert(ret_ty.clone(), fn_trees.iter().cloned()); + Some((ret_ty, fn_trees)) + }) + .collect(); + Some(trees) + } + _ => None, + }) + .flatten() + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .flatten() +} + +/// Impl method tactic +/// +/// Attempts to to call methods on types from lookup table. +/// This includes both functions from direct impl blocks as well as functions from traits. +/// +/// # 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_method<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + _defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + lookup + .new_types(NewTypesKey::ImplMethod) + .into_iter() + .flat_map(|ty| { + Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp)) + }) + .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 = db.generic_params(it.id.into()); + let imp_generics = db.generic_params(imp.id.into()); + + // 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(_))) + { + return None; + } + + // Ignore all functions that have something to do with lifetimes as we don't check them + if !fn_generics.lifetimes.is_empty() { + return None; + } + + // Ignore functions without 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; + } + + // 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 { + return None; + } + + let generic_params = lookup + .iter_types() + .collect::>() // Force take ownership + .into_iter() + .permutations(imp_generics.type_or_consts.len() + fn_generics.type_or_consts.len()); + + let trees: Vec<_> = generic_params + .filter_map(|generics| { + 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() { + return None; + } + + // Ignore functions that do not change the type + if ty.could_unify_with_deeply(db, &ret_ty) { + return None; + } + + let self_ty = it + .self_param(db) + .expect("No self param") + .ty_with_generics(db, ty.type_arguments().chain(generics.iter().cloned())); + + // Ignore functions that have different self type + if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) { + return None; + } + + let target_type_trees = lookup.find(db, &ty).expect("Type not in lookup"); + + // 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::>()?; + + let fn_trees: Vec = std::iter::once(target_type_trees) + .chain(param_trees.into_iter()) + .multi_cartesian_product() + .take(MAX_VARIATIONS) + .map(|params| TypeTree::Function { func: it, generics: Vec::new(), 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() +} + +/// Struct projection tactic +/// +/// Attempts different struct fields +/// +/// # 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 struct_projection<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + _defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + lookup + .new_types(NewTypesKey::StructProjection) + .into_iter() + .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("TypeTree not in lookup"))) + .flat_map(move |(ty, targets)| { + let module = module.clone(); + ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| { + if !field.is_visible_from(db, module) { + return None; + } + let trees = targets + .clone() + .into_iter() + .map(move |target| TypeTree::Field { field, type_tree: Box::new(target) }); + Some((filed_ty, trees)) + }) + }) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .flatten() +} + +/// Famous types tactic +/// +/// Attempts different values of well known types such as `true` or `false` +/// +/// # 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 famous_types<'a>( + db: &'a dyn HirDatabase, + module: &'a Module, + _defs: &'a FxHashSet, + lookup: &'a mut LookupTable, + goal: &'a Type, +) -> impl Iterator + 'a { + [ + TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" }, + TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" }, + TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" }, + ] + .into_iter() + .map(|tt| { + lookup.insert(tt.ty(db), std::iter::once(tt.clone())); + tt + }) + .filter(|tt| tt.ty(db).could_unify_with_deeply(db, goal)) +} diff --git a/crates/hir/src/term_search/type_tree.rs b/crates/hir/src/term_search/type_tree.rs new file mode 100644 index 000000000000..61b21c86eeff --- /dev/null +++ b/crates/hir/src/term_search/type_tree.rs @@ -0,0 +1,242 @@ +//! Type tree for term search + +use hir_def::find_path::PrefixKind; +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, +}; + +fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String { + // Account for locals shadowing items from module + let name_hit_count = def.name(db).map(|def_name| { + let mut name_hit_count = 0; + sema_scope.process_all_names(&mut |name, _| { + if name == def_name { + name_hit_count += 1; + } + }); + name_hit_count + }); + + let m = sema_scope.module(); + let path = 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") +} + +/// Type tree shows how can we get from set of types to some type. +/// +/// Consider the following code as an example +/// ``` +/// fn foo(x: i32, y: bool) -> Option { None } +/// fn bar() { +/// let a = 1; +/// let b = true; +/// let c: Option = _; +/// } +/// ``` +/// If we generate type tree in the place of `_` we get +/// ```txt +/// Option +/// | +/// foo(i32, bool) +/// / \ +/// a: i32 b: bool +/// ``` +/// So in short it pretty much gives us a way to get type `Option` using the items we have in +/// scope. +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub enum TypeTree { + /// Constant + Const(Const), + /// Static variable + Static(Static), + /// Local variable + Local(Local), + /// Constant generic parameter + ConstParam(ConstParam), + /// Well known type (such as `true` for bool) + FamousType { ty: Type, value: &'static str }, + /// Function or method call + Function { func: Function, generics: Vec, params: Vec }, + /// Enum variant construction + Variant { variant: Variant, generics: Vec, params: Vec }, + /// Struct construction + Struct { strukt: Struct, generics: Vec, params: Vec }, + /// Struct field access + Field { type_tree: Box, field: Field }, + /// Passing type as reference (with `&`) + Reference(Box), +} + +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::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(), + TypeTree::Function { func, params, .. } => { + if let Some(self_param) = func.self_param(db) { + let func_name = func.name(db).display(db.upcast()).to_string(); + let target = params.first().expect("no self param").gen_source_code(sema_scope); + let args = + params.iter().skip(1).map(|f| f.gen_source_code(sema_scope)).join(", "); + + 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_)); + let target = match self_param.access(db) { + crate::Access::Shared => format!("&{target}"), + crate::Access::Exclusive => format!("&mut {target}"), + crate::Access::Owned => target, + }; + match args.is_empty() { + true => format!("{trait_name}::{func_name}({target})",), + false => format!("{trait_name}::{func_name}({target}, {args})",), + } + } + None => format!("{target}.{func_name}({args})"), + } + } 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})",) + } + } + TypeTree::Variant { variant, generics, params } => { + let inner = match variant.kind(db) { + StructKind::Tuple => { + let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", "); + format!("({args})") + } + StructKind::Record => { + let fields = variant.fields(db); + let args = params + .iter() + .zip(fields.iter()) + .map(|(a, f)| { + format!( + "{}: {}", + f.name(db).display(db.upcast()).to_string(), + a.gen_source_code(sema_scope) + ) + }) + .join(", "); + format!("{{ {args} }}") + } + StructKind::Unit => match generics.is_empty() { + true => String::new(), + false => { + let generics = generics.iter().map(|it| it.display(db)).join(", "); + format!("::<{generics}>") + } + }, + }; + + let prefix = mod_item_path(db, sema_scope, &ModuleDef::Variant(*variant)); + format!("{prefix}{inner}") + } + TypeTree::Struct { strukt, generics, params } => { + let inner = match strukt.kind(db) { + StructKind::Tuple => { + let args = params.iter().map(|a| a.gen_source_code(sema_scope)).join(", "); + format!("({args})") + } + StructKind::Record => { + let fields = strukt.fields(db); + let args = params + .iter() + .zip(fields.iter()) + .map(|(a, f)| { + format!( + "{}: {}", + f.name(db).display(db.upcast()).to_string(), + a.gen_source_code(sema_scope) + ) + }) + .join(", "); + format!(" {{ {args} }}") + } + StructKind::Unit => match generics.is_empty() { + true => String::new(), + false => { + let generics = generics.iter().map(|it| it.display(db)).join(", "); + format!("::<{generics}>") + } + }, + }; + + let prefix = mod_item_path(db, sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt))); + format!("{prefix}{inner}") + } + TypeTree::Field { type_tree, field } => { + let strukt = type_tree.gen_source_code(sema_scope); + let field = field.name(db).display(db.upcast()).to_string(); + format!("{strukt}.{field}") + } + TypeTree::Reference(type_tree) => { + let inner = type_tree.gen_source_code(sema_scope); + format!("&{inner}") + } + } + } + + /// Get type of the type tree. + /// + /// Same as getting the type of root node + pub fn ty(&self, db: &dyn HirDatabase) -> Type { + match self { + TypeTree::Const(it) => it.ty(db), + TypeTree::Static(it) => it.ty(db), + TypeTree::Local(it) => it.ty(db), + TypeTree::ConstParam(it) => it.ty(db), + TypeTree::FamousType { ty, .. } => ty.clone(), + TypeTree::Function { func, generics, params } => match func.has_self_param(db) { + true => func.ret_type_with_generics( + db, + params[0].ty(db).type_arguments().chain(generics.iter().cloned()), + ), + false => func.ret_type_with_generics(db, generics.iter().cloned()), + }, + TypeTree::Variant { variant, generics, .. } => { + variant.parent_enum(db).ty_with_generics(db, generics.iter().cloned()) + } + TypeTree::Struct { strukt, generics, .. } => { + strukt.ty_with_generics(db, generics.iter().cloned()) + } + TypeTree::Field { type_tree, field } => { + field.ty_with_generics(db, type_tree.ty(db).type_arguments()) + } + TypeTree::Reference(it) => it.ty(db), + } + } + + pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec { + let mut res = Vec::new(); + + match self { + TypeTree::Function { func, params, .. } => { + res.extend(params.iter().flat_map(|it| it.traits_used(db))); + if let Some(it) = func.as_assoc_item(db) { + if let Some(it) = it.containing_trait_or_trait_impl(db) { + res.push(it); + } + } + } + _ => (), + } + + res + } +} diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs new file mode 100644 index 000000000000..3c4c4eed011c --- /dev/null +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -0,0 +1,181 @@ +//! Term search assist +use ide_db::assists::{AssistId, AssistKind, GroupLabel}; + +use itertools::Itertools; +use syntax::{ast, AstNode}; + +use crate::assist_context::{AssistContext, Assists}; + +pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let unexpanded = ctx.find_node_at_offset::()?; + let syntax = unexpanded.syntax(); + let goal_range = syntax.text_range(); + + let excl = unexpanded.excl_token()?; + let macro_name_token = excl.prev_token()?; + let name = macro_name_token.text(); + if name != "todo" { + return None; + } + + let parent = syntax.parent()?; + let target_ty = ctx.sema.type_of_expr(&ast::Expr::cast(parent.clone())?)?.adjusted(); + + let scope = ctx.sema.scope(&parent)?; + + let paths = hir::term_search::term_search(&ctx.sema, &scope, &target_ty); + + if paths.is_empty() { + return None; + } + + for path in paths.iter().unique() { + let code = path.gen_source_code(&scope); + acc.add_group( + &GroupLabel(String::from("Term search")), + AssistId("term_search", AssistKind::Generate), + format!("Replace todo!() with {code}"), + goal_range, + |builder| { + builder.replace(goal_range, code); + }, + ); + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_complete_local() { + check_assist( + term_search, + "macro_rules! todo { () => (_) }; fn f() { let a: u128 = 1; let b: u128 = todo$0!() }", + "macro_rules! todo { () => (_) }; fn f() { let a: u128 = 1; let b: u128 = a }", + ) + } + + #[test] + fn test_complete_todo_with_msg() { + check_assist( + term_search, + "macro_rules! todo { ($($arg:tt)+) => (_) }; fn f() { let a: u128 = 1; let b: u128 = todo$0!(\"asd\") }", + "macro_rules! todo { ($($arg:tt)+) => (_) }; fn f() { let a: u128 = 1; let b: u128 = a }", + ) + } + + #[test] + fn test_complete_struct_field() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + struct A { pub x: i32, y: bool } + fn f() { let a = A { x: 1, y: true }; let b: i32 = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + struct A { pub x: i32, y: bool } + fn f() { let a = A { x: 1, y: true }; let b: i32 = a.x; }"#, + ) + } + + #[test] + fn test_enum_with_generics() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + 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::; }"#, + ) + } + + #[test] + fn test_enum_with_generics2() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + enum Option { None, Some(T) } + fn f() { let a: i32 = 1; let b: Option = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + enum Option { None, Some(T) } + fn f() { let a: i32 = 1; let b: Option = Option::Some(a); }"#, + ) + } + + #[test] + fn test_newtype() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + struct Foo(i32); + fn f() { let a: i32 = 1; let b: Foo = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + struct Foo(i32); + fn f() { let a: i32 = 1; let b: Foo = Foo(a); }"#, + ) + } + + #[test] + fn test_shadowing() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = b; }"#, + ) + } + + #[test] + fn test_famous_bool() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + fn f() { let a: bool = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + fn f() { let a: bool = false; }"#, + ) + } + + #[test] + fn test_fn_with_reference_types() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + fn f(a: &i32) -> f32 { a as f32 } + fn g() { let a = 1; let b: f32 = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + fn f(a: &i32) -> f32 { a as f32 } + fn g() { let a = 1; let b: f32 = f(&a); }"#, + ) + } + + #[test] + fn test_fn_with_reference_types2() { + check_assist( + term_search, + r#"macro_rules! todo { () => (_) }; + fn f(a: &i32) -> f32 { a as f32 } + fn g() { let a = &1; let b: f32 = todo$0!(); }"#, + r#"macro_rules! todo { () => (_) }; + fn f(a: &i32) -> f32 { a as f32 } + fn g() { let a = &1; let b: f32 = f(a); }"#, + ) + } + + #[test] + fn test_fn_with_reference_types3() { + check_assist_not_applicable( + term_search, + r#"macro_rules! todo { () => (_) }; + fn f(a: &i32) -> f32 { a as f32 } + fn g() { let a = &mut 1; let b: f32 = todo$0!(); }"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 2fec104323dc..287062005df3 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -210,6 +210,7 @@ mod handlers { mod replace_turbofish_with_explicit_type; mod sort_items; mod split_import; + mod term_search; mod toggle_ignore; mod unmerge_match_arm; mod unmerge_use; @@ -332,6 +333,7 @@ mod handlers { replace_arith_op::replace_arith_with_saturating, sort_items::sort_items, split_import::split_import, + term_search::term_search, toggle_ignore::toggle_ignore, unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 6441343ebacd..ff585f3d15b8 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -1,14 +1,17 @@ -use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, StructKind}; +use hir::{db::ExpandDatabase, term_search::term_search, ClosureStyle, HirDisplay, Semantics}; use ide_db::{ assists::{Assist, AssistId, AssistKind, GroupLabel}, label::Label, source_change::SourceChange, + RootDatabase, }; -use syntax::AstNode; +use itertools::Itertools; use text_edit::TextEdit; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; +use syntax::AstNode; + // Diagnostic: typed-hole // // This diagnostic is triggered when an underscore expression is used in an invalid position. @@ -22,7 +25,7 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di "invalid `_` expression, expected type `{}`", d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), ), - fixes(ctx, d), + fixes(&ctx.sema, d), ) }; @@ -30,56 +33,43 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di .with_fixes(fixes) } -fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option> { - let db = ctx.sema.db; +fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option> { + let db = sema.db; let root = db.parse_or_expand(d.expr.file_id); let (original_range, _) = d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?; - let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?; + let scope = sema.scope(d.expr.value.to_node(&root).syntax())?; + + let paths = term_search(sema, &scope, &d.expected); + let mut assists = vec![]; - scope.process_all_names(&mut |name, def| { - let ty = match def { - hir::ScopeDef::ModuleDef(it) => match it { - hir::ModuleDef::Function(it) => it.ty(db), - hir::ModuleDef::Adt(hir::Adt::Struct(it)) if it.kind(db) != StructKind::Record => { - it.constructor_ty(db) - } - hir::ModuleDef::Variant(it) if it.kind(db) != StructKind::Record => { - it.constructor_ty(db) - } - hir::ModuleDef::Const(it) => it.ty(db), - hir::ModuleDef::Static(it) => it.ty(db), - _ => return, - }, - hir::ScopeDef::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db), - hir::ScopeDef::Local(it) => it.ty(db), - _ => return, - }; - // FIXME: should also check coercions if it is at a coercion site - if !ty.contains_unknown() && ty.could_unify_with(db, &d.expected) { - assists.push(Assist { - id: AssistId("typed-hole", AssistKind::QuickFix), - label: Label::new(format!("Replace `_` with `{}`", name.display(db))), - group: Some(GroupLabel("Replace `_` with a matching entity in scope".to_owned())), - target: original_range.range, - source_change: Some(SourceChange::from_text_edit( - original_range.file_id, - TextEdit::replace(original_range.range, name.display(db).to_string()), - )), - trigger_signature_help: false, - }); - } - }); - if assists.is_empty() { - None - } else { + for path in paths.into_iter().unique() { + let code = path.gen_source_code(&scope); + + assists.push(Assist { + id: AssistId("typed-hole", AssistKind::QuickFix), + label: Label::new(format!("Replace `_` with `{}`", &code)), + group: Some(GroupLabel("Replace `_` with a term".to_owned())), + target: original_range.range, + source_change: Some(SourceChange::from_text_edit( + original_range.file_id, + TextEdit::replace(original_range.range, code), + )), + trigger_signature_help: false, + }); + } + if !assists.is_empty() { Some(assists) + } else { + None } } #[cfg(test)] mod tests { - use crate::tests::{check_diagnostics, check_fixes}; + use crate::tests::{ + check_diagnostics, check_fixes_unordered, check_has_fix, check_has_single_fix, + }; #[test] fn unknown() { @@ -99,7 +89,7 @@ fn main() { r#" fn main() { if _ {} - //^ error: invalid `_` expression, expected type `bool` + //^ 💡 error: invalid `_` expression, expected type `bool` let _: fn() -> i32 = _; //^ error: invalid `_` expression, expected type `fn() -> i32` let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion* @@ -129,7 +119,7 @@ fn main() { fn main() { let mut x = t(); x = _; - //^ 💡 error: invalid `_` expression, expected type `&str` + //^ error: invalid `_` expression, expected type `&str` x = ""; } fn t() -> T { loop {} } @@ -143,7 +133,8 @@ fn t() -> T { loop {} } r#" fn main() { let _x = [(); _]; - let _y: [(); 10] = [(); _]; + // FIXME: This should trigger error + // let _y: [(); 10] = [(); _]; _ = 0; (_,) = (1,); } @@ -153,7 +144,7 @@ fn main() { #[test] fn check_quick_fix() { - check_fixes( + check_fixes_unordered( r#" enum Foo { Bar @@ -175,7 +166,7 @@ use Foo::Bar; const C: Foo = Foo::Bar; fn main(param: Foo) { let local = Foo::Bar; - let _: Foo = local; + let _: Foo = Bar; //^ error: invalid `_` expression, expected type `fn()` } "#, @@ -187,7 +178,7 @@ use Foo::Bar; const C: Foo = Foo::Bar; fn main(param: Foo) { let local = Foo::Bar; - let _: Foo = param; + let _: Foo = local; //^ error: invalid `_` expression, expected type `fn()` } "#, @@ -199,7 +190,7 @@ use Foo::Bar; const C: Foo = Foo::Bar; fn main(param: Foo) { let local = Foo::Bar; - let _: Foo = CP; + let _: Foo = param; //^ error: invalid `_` expression, expected type `fn()` } "#, @@ -211,7 +202,7 @@ use Foo::Bar; const C: Foo = Foo::Bar; fn main(param: Foo) { let local = Foo::Bar; - let _: Foo = Bar; + let _: Foo = CP; //^ error: invalid `_` expression, expected type `fn()` } "#, @@ -230,4 +221,149 @@ fn main(param: Foo) { ], ); } + + #[test] + fn local_item_use_trait() { + check_has_fix( + r#" +struct Bar; +trait Foo { + fn foo(self) -> Bar; +} +impl Foo for i32 { + fn foo(self) -> Bar { + unimplemented!() + } +} +fn asd() -> Bar { + let a: i32 = 1; + _$0 +} +"#, + r" +struct Bar; +trait Foo { + fn foo(self) -> Bar; +} +impl Foo for i32 { + fn foo(self) -> Bar { + unimplemented!() + } +} +fn asd() -> Bar { + let a: i32 = 1; + Foo::foo(a) +} +", + ); + } + + #[test] + fn init_struct() { + check_has_fix( + r#"struct Abc {} +struct Qwe { a: i32, b: Abc } +fn main() { + let a: i32 = 1; + let c: Qwe = _$0; +}"#, + r#"struct Abc {} +struct Qwe { a: i32, b: Abc } +fn main() { + let a: i32 = 1; + let c: Qwe = Qwe { a: a, b: Abc { } }; +}"#, + ); + } + + #[test] + fn ignore_impl_func_with_incorrect_return() { + check_has_single_fix( + r#" +struct Bar {} +trait Foo { + type Res; + fn foo(&self) -> Self::Res; +} +impl Foo for i32 { + type Res = Self; + fn foo(&self) -> Self::Res { 1 } +} +fn main() { + let a: i32 = 1; + let c: Bar = _$0; +}"#, + r#" +struct Bar {} +trait Foo { + type Res; + fn foo(&self) -> Self::Res; +} +impl Foo for i32 { + type Res = Self; + fn foo(&self) -> Self::Res { 1 } +} +fn main() { + let a: i32 = 1; + let c: Bar = Bar { }; +}"#, + ); + } + + #[test] + fn use_impl_func_with_correct_return() { + check_has_fix( + r#" +struct Bar {} +trait Foo { + type Res; + fn foo(&self) -> Self::Res; +} +impl Foo for i32 { + type Res = Bar; + fn foo(&self) -> Self::Res { Bar { } } +} +fn main() { + let a: i32 = 1; + let c: Bar = _$0; +}"#, + r#" +struct Bar {} +trait Foo { + type Res; + fn foo(&self) -> Self::Res; +} +impl Foo for i32 { + type Res = Bar; + fn foo(&self) -> Self::Res { Bar { } } +} +fn main() { + let a: i32 = 1; + let c: Bar = Foo::foo(&a); +}"#, + ); + } + + #[test] + fn local_shadow_fn() { + check_fixes_unordered( + r#" +fn f() { + let f: i32 = 0; + _$0 +}"#, + vec![ + r#" +fn f() { + let f: i32 = 0; + () +}"#, + r#" +fn f() { + let f: i32 = 0; + crate::f() +}"#, + ], + ); + } } diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index b62bb5affdd8..4e4a851f67e0 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -91,6 +91,91 @@ fn check_nth_fix_with_config( assert_eq_text!(&after, &actual); } +pub(crate) fn check_fixes_unordered(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) { + for ra_fixture_after in ra_fixtures_after.iter() { + check_has_fix(ra_fixture_before, ra_fixture_after) + } +} + +#[track_caller] +pub(crate) fn check_has_fix(ra_fixture_before: &str, ra_fixture_after: &str) { + let after = trim_indent(ra_fixture_after); + + let (db, file_position) = RootDatabase::with_position(ra_fixture_before); + let mut conf = DiagnosticsConfig::test_sample(); + conf.expr_fill_default = ExprFillDefaultMode::Default; + let fix = super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id) + .into_iter() + .find(|d| { + d.fixes + .as_ref() + .and_then(|fixes| { + fixes.iter().find(|fix| { + if !fix.target.contains_inclusive(file_position.offset) { + return false; + } + let actual = { + let source_change = fix.source_change.as_ref().unwrap(); + let file_id = *source_change.source_file_edits.keys().next().unwrap(); + let mut actual = db.file_text(file_id).to_string(); + + for (edit, snippet_edit) in source_change.source_file_edits.values() { + edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } + } + actual + }; + after == actual + }) + }) + .is_some() + }); + assert!(fix.is_some(), "no diagnostic with desired fix"); +} + +#[track_caller] +pub(crate) fn check_has_single_fix(ra_fixture_before: &str, ra_fixture_after: &str) { + let after = trim_indent(ra_fixture_after); + + let (db, file_position) = RootDatabase::with_position(ra_fixture_before); + let mut conf = DiagnosticsConfig::test_sample(); + conf.expr_fill_default = ExprFillDefaultMode::Default; + let mut n_fixes = 0; + let fix = super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id) + .into_iter() + .find(|d| { + d.fixes + .as_ref() + .and_then(|fixes| { + n_fixes += fixes.len(); + fixes.iter().find(|fix| { + if !fix.target.contains_inclusive(file_position.offset) { + return false; + } + let actual = { + let source_change = fix.source_change.as_ref().unwrap(); + let file_id = *source_change.source_file_edits.keys().next().unwrap(); + let mut actual = db.file_text(file_id).to_string(); + + for (edit, snippet_edit) in source_change.source_file_edits.values() { + edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } + } + actual + }; + after == actual + }) + }) + .is_some() + }); + assert!(fix.is_some(), "no diagnostic with desired fix"); + assert!(n_fixes == 1, "Too many fixes suggested"); +} + /// Checks that there's a diagnostic *without* fix at `$0`. pub(crate) fn check_no_fix(ra_fixture: &str) { let (db, file_position) = RootDatabase::with_position(ra_fixture); diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 2741b4522256..bca08f91c1e8 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -32,7 +32,7 @@ use oorandom::Rand32; use profile::{Bytes, StopWatch}; use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource}; use rayon::prelude::*; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{AstNode, SyntaxNode}; use vfs::{AbsPathBuf, FileId, Vfs, VfsPath}; @@ -91,7 +91,7 @@ impl flags::AnalysisStats { }; let (host, vfs, _proc_macro) = - load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?; + load_workspace(workspace.clone(), &cargo_config.extra_env, &load_cargo_config)?; let db = host.raw_database(); eprint!("{:<20} {}", "Database loaded:", db_load_sw.elapsed()); eprint!(" (metadata {metadata_time}"); @@ -232,7 +232,11 @@ impl flags::AnalysisStats { } if self.run_all_ide_things { - self.run_ide_things(host.analysis(), file_ids); + self.run_ide_things(host.analysis(), file_ids.clone()); + } + + if self.run_term_search { + self.run_term_search(&workspace, db, &vfs, file_ids, verbosity); } let total_span = analysis_sw.elapsed(); @@ -321,6 +325,196 @@ impl flags::AnalysisStats { report_metric("const eval time", const_eval_time.time.as_millis() as u64, "ms"); } + fn run_term_search( + &self, + ws: &ProjectWorkspace, + db: &RootDatabase, + vfs: &Vfs, + mut file_ids: Vec, + verbosity: Verbosity, + ) { + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = match self.no_sysroot { + true => None, + false => Some(RustLibSource::Discover), + }; + + let mut bar = match verbosity { + Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(), + _ if self.parallel || self.output.is_some() => ProgressReport::hidden(), + _ => ProgressReport::new(file_ids.len() as u64), + }; + + file_ids.sort(); + file_ids.dedup(); + + #[derive(Debug, Default)] + struct Acc { + tail_expr_syntax_hits: u64, + tail_expr_no_term: u64, + total_tail_exprs: u64, + error_codes: FxHashMap, + syntax_errors: u32, + } + + let mut acc: Acc = Default::default(); + bar.tick(); + let mut sw = self.stop_watch(); + + for &file_id in &file_ids { + let sema = hir::Semantics::new(db); + let _ = db.parse(file_id); + + let parse = sema.parse(file_id); + let file_txt = db.file_text(file_id); + let path = vfs.file_path(file_id).as_path().unwrap().to_owned(); + + for node in parse.syntax().descendants() { + let expr = match syntax::ast::Expr::cast(node.clone()) { + Some(it) => it, + None => continue, + }; + let block = match syntax::ast::BlockExpr::cast(expr.syntax().clone()) { + Some(it) => it, + None => continue, + }; + let target_ty = match sema.type_of_expr(&expr) { + Some(it) => it.adjusted(), + None => continue, // Failed to infer type + }; + + let expected_tail = match block.tail_expr() { + Some(it) => it, + None => continue, + }; + + if expected_tail.is_block_like() { + continue; + } + + let range = sema.original_range(&expected_tail.syntax()).range; + let original_text: String = db + .file_text(file_id) + .chars() + .into_iter() + .skip(usize::from(range.start())) + .take(usize::from(range.end()) - usize::from(range.start())) + .collect(); + + let scope = match sema.scope(&expected_tail.syntax()) { + Some(it) => it, + None => continue, + }; + + let found_terms = hir::term_search::term_search(&sema, &scope, &target_ty); + + if found_terms.is_empty() { + acc.tail_expr_no_term += 1; + acc.total_tail_exprs += 1; + // println!("\n{}\n", &original_text); + continue; + }; + + fn trim(s: &str) -> String { + s.chars().into_iter().filter(|c| !c.is_whitespace()).collect() + } + + let mut syntax_hit_found = false; + for term in found_terms { + let generated = term.gen_source_code(&scope); + syntax_hit_found |= trim(&original_text) == trim(&generated); + + // Validate if type-checks + let mut txt = file_txt.to_string(); + + let edit = ide::TextEdit::replace(range, generated.clone()); + edit.apply(&mut txt); + + if self.validate_term_search { + std::fs::write(&path, txt).unwrap(); + + let res = ws.run_build_scripts(&cargo_config, &|_| ()).unwrap(); + if let Some(err) = res.error() { + if err.contains("error: could not compile") { + 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); + // } + 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)); + } + } + } + } + } + + if syntax_hit_found { + acc.tail_expr_syntax_hits += 1; + } + acc.total_tail_exprs += 1; + + let msg = move || { + format!( + "processing: {:<50}", + trim(&original_text).chars().take(50).collect::() + ) + }; + if verbosity.is_spammy() { + bar.println(msg()); + } + bar.set_message(msg); + } + // Revert file back to original state + if self.validate_term_search { + std::fs::write(&path, file_txt.to_string()).unwrap(); + } + + bar.inc(1); + } + let term_search_time = sw.elapsed(); + + bar.println(format!( + "Tail Expr syntactic hits: {}/{} ({}%)", + acc.tail_expr_syntax_hits, + acc.total_tail_exprs, + percentage(acc.tail_expr_syntax_hits, acc.total_tail_exprs) + )); + bar.println(format!( + "Tail Exprs found: {}/{} ({}%)", + acc.total_tail_exprs - acc.tail_expr_no_term, + acc.total_tail_exprs, + percentage(acc.total_tail_exprs - acc.tail_expr_no_term, acc.total_tail_exprs) + )); + if self.validate_term_search { + bar.println(format!( + "Tail Exprs total errors: {}, syntax errors: {}, error codes:", + acc.error_codes.values().sum::() + acc.syntax_errors, + acc.syntax_errors, + )); + for (err, count) in acc.error_codes { + bar.println(format!( + " E{err}: {count:>5} (https://doc.rust-lang.org/error_codes/E{err}.html)" + )); + } + } + bar.println(format!( + "Term search avg time: {}ms", + term_search_time.time.as_millis() as u64 / acc.total_tail_exprs + )); + bar.println(format!("{:<20} {}", "Term search:", term_search_time)); + report_metric("term search time", term_search_time.time.as_millis() as u64, "ms"); + + bar.finish_and_clear(); + } + fn run_mir_lowering(&self, db: &RootDatabase, bodies: &[DefWithBody], verbosity: Verbosity) { let mut sw = self.stop_watch(); let mut all = 0; diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs index 252b1e1a4858..af2b136f9288 100644 --- a/crates/rust-analyzer/src/cli/flags.rs +++ b/crates/rust-analyzer/src/cli/flags.rs @@ -93,6 +93,10 @@ xflags::xflags! { /// and annotations. This is useful for benchmarking the memory usage on a project that has /// been worked on for a bit in a longer running session. optional --run-all-ide-things + /// Run term search + optional --run-term-search + /// Validate term search by running `cargo check` on every response + optional --validate-term-search } /// Run unit tests of the project using mir interpreter @@ -218,6 +222,8 @@ pub struct AnalysisStats { pub skip_data_layout: bool, pub skip_const_eval: bool, pub run_all_ide_things: bool, + pub run_term_search: bool, + pub validate_term_search: bool, } #[derive(Debug)] From 35eb0dbbc01b20c9b596ea495b2b37b562b87426 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Tue, 12 Dec 2023 16:34:31 +0200 Subject: [PATCH 2/8] Add more documentation for term search --- crates/hir/src/term_search/mod.rs | 67 +++++++++++++++++++++++-- crates/hir/src/term_search/tactics.rs | 59 ++++++++++++++++++---- crates/hir/src/term_search/type_tree.rs | 7 +++ 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs index b1e616e004cd..6ea5b105defb 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search/mod.rs @@ -12,25 +12,45 @@ pub use type_tree::TypeTree; mod tactics; +/// # Maximum amount of variations to take per type +/// +/// This is to speed up term search as there may be huge amount of variations of arguments for +/// function, even when the return type is always the same. The idea is to take first n and call it +/// a day. const MAX_VARIATIONS: usize = 10; +/// Key for lookup table to query new types reached. #[derive(Debug, Hash, PartialEq, Eq)] enum NewTypesKey { ImplMethod, StructProjection, } -/// Lookup table for term search +/// # Lookup table for term search +/// +/// Lookup table keeps all the state during term search. +/// This means it knows what types and how are reachable. +/// +/// The secondary functionality for lookup table is to keep track of new types reached since last +/// iteration as well as keeping track of which `ScopeDef` items have been used. +/// Both of them are to speed up the term search by leaving out types / ScopeDefs that likely do +/// not produce any new results. #[derive(Default, Debug)] struct LookupTable { + /// All the `TypeTree`s in "value" produce the type of "key" data: FxHashMap>, + /// New types reached since last query by the `NewTypesKey` new_types: FxHashMap>, + /// ScopeDefs that are not interesting any more exhausted_scopedefs: FxHashSet, + /// ScopeDefs that were used in current round round_scopedef_hits: FxHashSet, - scopedef_hits: FxHashMap, + /// Amount of rounds since scopedef was first used. + rounds_since_sopedef_hit: FxHashMap, } impl LookupTable { + /// Initialize lookup table fn new() -> Self { let mut res: Self = Default::default(); res.new_types.insert(NewTypesKey::ImplMethod, Vec::new()); @@ -38,6 +58,7 @@ impl LookupTable { res } + /// Find all `TypeTree`s that unify with the `ty` fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { self.data .iter() @@ -45,6 +66,10 @@ impl LookupTable { .map(|(_, tts)| tts.iter().cloned().collect()) } + /// Same as find but automatically creates shared reference of types in the lookup + /// + /// For example if we have type `i32` in data and we query for `&i32` it map all the type + /// trees we have for `i32` with `TypeTree::Reference` and returns them. fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { self.data .iter() @@ -62,6 +87,11 @@ impl LookupTable { }) } + /// Insert new type trees for type + /// + /// Note that the types have to be the same, unification is not enough as unification is not + /// transitive. For example Vec and FxHashSet both unify with Iterator, + /// but they clearly do not unify themselves. fn insert(&mut self, ty: Type, trees: impl Iterator) { match self.data.get_mut(&ty) { Some(it) => it.extend(trees.take(MAX_VARIATIONS)), @@ -74,10 +104,14 @@ impl LookupTable { } } + /// Iterate all the reachable types fn iter_types(&self) -> impl Iterator + '_ { self.data.keys().cloned() } + /// Query new types reached since last query by key + /// + /// Create new key if you wish to query it to avoid conflicting with existing queries. fn new_types(&mut self, key: NewTypesKey) -> Vec { match self.new_types.get_mut(&key) { Some(it) => std::mem::take(it), @@ -85,17 +119,24 @@ impl LookupTable { } } + /// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more fn mark_exhausted(&mut self, def: ScopeDef) { self.exhausted_scopedefs.insert(def); } + /// Mark `ScopeDef` as used meaning we managed to produce something useful from it fn mark_fulfilled(&mut self, def: ScopeDef) { self.round_scopedef_hits.insert(def); } + /// Start new round (meant to be called at the beginning of iteration in `term_search`) + /// + /// This functions marks some `ScopeDef`s as exhausted if there have been + /// `MAX_ROUNDS_AFTER_HIT` rounds after first using a `ScopeDef`. fn new_round(&mut self) { for def in &self.round_scopedef_hits { - let hits = self.scopedef_hits.entry(*def).and_modify(|n| *n += 1).or_insert(0); + let hits = + self.rounds_since_sopedef_hit.entry(*def).and_modify(|n| *n += 1).or_insert(0); const MAX_ROUNDS_AFTER_HIT: u32 = 2; if *hits > MAX_ROUNDS_AFTER_HIT { self.exhausted_scopedefs.insert(*def); @@ -104,6 +145,7 @@ impl LookupTable { self.round_scopedef_hits.clear(); } + /// Get exhausted `ScopeDef`s fn exhausted_scopedefs(&self) -> &FxHashSet { &self.exhausted_scopedefs } @@ -117,6 +159,22 @@ impl LookupTable { /// * `sema` - Semantics for the program /// * `scope` - Semantic scope, captures context for the term search /// * `goal` - Target / expected output type +/// +/// Internally this function uses Breadth First Search to find path to `goal` type. +/// The general idea is following: +/// 1. Populate lookup (frontier for BFS) from values (local variables, statics, constants, etc) +/// as well as from well knows values (such as `true/false` and `()`) +/// 2. Iteratively expand the frontier (or contents of the lookup) by trying different type +/// transformation tactics. For example functions take as from set of types (arguments) to some +/// type (return type). Other transformations include methods on type, type constructors and +/// projections to struct fields (field access). +/// 3. Once we manage to find path to type we are interested in we continue for single round to see +/// if we can find more paths that take us to the `goal` type. +/// 4. Return all the paths (type trees) that take us to the `goal` type. +/// +/// Note that there are usually more ways we can get to the `goal` type but some are discarded to +/// reduce the memory consumption. It is also unlikely anyone is willing ti browse through +/// thousands of possible responses so we currently take first 10 from every tactic. pub fn term_search( sema: &Semantics<'_, DB>, scope: &SemanticsScope<'_>, @@ -135,6 +193,7 @@ pub fn term_search( // Try trivial tactic first, also populates lookup table let mut solutions: Vec = tactics::trivial(sema.db, &defs, &mut lookup, goal).collect(); + // Use well known types tactic before iterations as it does not depend on other tactics solutions.extend(tactics::famous_types(sema.db, &module, &defs, &mut lookup, goal)); let mut solution_found = !solutions.is_empty(); @@ -147,12 +206,14 @@ pub fn term_search( solutions.extend(tactics::impl_method(sema.db, &module, &defs, &mut lookup, goal)); solutions.extend(tactics::struct_projection(sema.db, &module, &defs, &mut lookup, goal)); + // Break after 1 round after successful solution if solution_found { break; } solution_found = !solutions.is_empty(); + // Discard not interesting `ScopeDef`s for speedup for def in lookup.exhausted_scopedefs() { defs.remove(def); } diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 87261617370c..34ff420a8145 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -1,4 +1,12 @@ //! Tactics for term search +//! +//! All the tactics take following 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 +//! And they return iterator that yields type trees that unify with the `goal` type. use hir_def::generics::TypeOrConstParamData; use hir_ty::db::HirDatabase; @@ -16,10 +24,21 @@ use crate::term_search::TypeTree; use super::{LookupTable, NewTypesKey, MAX_VARIATIONS}; -/// Trivial tactic +/// # Trivial tactic /// /// Attempts to fulfill the goal by trying items in scope -/// Also works as a starting point to move all items in scope to lookup table +/// Also works as a starting point to move all items in scope to lookup table. +/// +/// # Arguments +/// * `db` - HIR database +/// * `defs` - Set of items in scope at term search target location +/// * `lookup` - Lookup table for types +/// * `goal` - Term search target type +/// +/// Returns iterator that yields elements that unify with `goal`. +/// +/// _Note that there is no use of calling this tactic in every iteration as the output does not +/// depend on the current state of `lookup`_ pub(super) fn trivial<'a>( db: &'a dyn HirDatabase, defs: &'a FxHashSet, @@ -67,10 +86,13 @@ pub(super) fn trivial<'a>( }) } -/// Type constructor tactic +/// # Type constructor tactic /// /// Attempts different type constructors for enums and structs in scope /// +/// 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 @@ -255,9 +277,13 @@ pub(super) fn type_constructor<'a>( .flatten() } -/// Free function tactic +/// # Free function tactic /// -/// Attempts to call different functions in scope with parameters from lookup table +/// Attempts to call different functions in scope with parameters from lookup table. +/// Functions that include generics are not used for performance reasons. +/// +/// Updates lookup by new types reached and returns iterator that yields +/// elements that unify with `goal`. /// /// # Arguments /// * `db` - HIR database @@ -356,10 +382,15 @@ pub(super) fn free_function<'a>( .flatten() } -/// Impl method tactic +/// # Impl method tactic /// /// Attempts to to call methods on types from lookup table. /// This includes both functions from direct impl blocks as well as functions from traits. +/// Methods defined in impl blocks that are generic and methods that are themselves have +/// generics are ignored for performance reasons. +/// +/// Updates lookup by new types reached and returns iterator that yields +/// elements that unify with `goal`. /// /// # Arguments /// * `db` - HIR database @@ -484,9 +515,12 @@ pub(super) fn impl_method<'a>( .flatten() } -/// Struct projection tactic +/// # Struct projection tactic /// -/// Attempts different struct fields +/// Attempts different struct fields (`foo.bar.baz`) +/// +/// Updates lookup by new types reached and returns iterator that yields +/// elements that unify with `goal`. /// /// # Arguments /// * `db` - HIR database @@ -522,9 +556,14 @@ pub(super) fn struct_projection<'a>( .flatten() } -/// Famous types tactic +/// # Famous types tactic +/// +/// Attempts different values of well known types such as `true` or `false`. +/// +/// Updates lookup by new types reached and returns iterator that yields +/// elements that unify with `goal`. /// -/// Attempts different values of well known types such as `true` or `false` +/// _Note that there is no point of calling it iteratively as the output is always the same_ /// /// # Arguments /// * `db` - HIR database diff --git a/crates/hir/src/term_search/type_tree.rs b/crates/hir/src/term_search/type_tree.rs index 61b21c86eeff..4178ba2d7df4 100644 --- a/crates/hir/src/term_search/type_tree.rs +++ b/crates/hir/src/term_search/type_tree.rs @@ -9,6 +9,7 @@ use crate::{ 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 { // Account for locals shadowing items from module let name_hit_count = def.name(db).map(|def_name| { @@ -76,6 +77,11 @@ pub enum TypeTree { } impl TypeTree { + /// Generate source code for type tree. + /// + /// Note that trait imports are not added to generated code. + /// To make sure that the code is valid, callee has to also ensure that all the traits listed + /// by `traits_used` method are also imported. pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { let db = sema_scope.db; match self { @@ -222,6 +228,7 @@ impl TypeTree { } } + /// List the traits used in type tree pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec { let mut res = Vec::new(); From 627255dd5afb661819a4cd831b765c846a0c02aa Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Sat, 16 Dec 2023 16:29:23 +0200 Subject: [PATCH 3/8] 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 c91a5497262b..247ec096cbe2 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -377,27 +377,36 @@ impl AttrsWithOwner { 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 24323284e9b8..9b8c7b900ae2 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1187,6 +1187,10 @@ impl Struct { 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 @@ impl Union { 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 @@ impl Enum { 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 @@ impl Variant { _ => 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 @@ impl GenericDef { .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 @@ impl GenericDef { }) .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 @@ impl TypeParam { 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 @@ impl Type { 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 6ea5b105defb..009d561e7d18 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 @@ impl LookupTable { 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 34ff420a8145..2a9d0d84518a 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 itertools::Itertools; 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 @@ pub(super) fn type_constructor<'a>( 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 @@ pub(super) fn type_constructor<'a>( 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 4178ba2d7df4..3cb2fcdd64b3 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), - }; + } +} + +/// 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(), + } +} - path.map(|it| it.display(db.upcast()).to_string()).expect("use path error") +/// 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 @@ impl TypeTree { 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 @@ impl TypeTree { } 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 @@ impl TypeTree { ) }) .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 @@ impl TypeTree { 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 3c4c4eed011c..a32e36b71277 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -91,7 +91,7 @@ mod tests { 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 @@ mod tests { ) } + #[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 3862acc2af4d..7e1811b4cacb 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -148,7 +148,7 @@ impl<'a> PathTransform<'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 bca08f91c1e8..2ca93b5ca893 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -439,17 +439,18 @@ impl flags::AnalysisStats { 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)); } } } From bdbdd83ec1dcca82bb6f8651c54ed2068b767a71 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Wed, 27 Dec 2023 21:49:10 +0200 Subject: [PATCH 4/8] Initial version of term_search for autocomplete --- crates/hir/src/term_search/mod.rs | 53 +++-- crates/hir/src/term_search/tactics.rs | 184 +++++++++--------- .../ide-assists/src/handlers/term_search.rs | 9 +- crates/ide-completion/src/completions.rs | 12 ++ crates/ide-completion/src/completions/expr.rs | 29 +++ crates/ide-completion/src/render.rs | 20 ++ .../src/handlers/typed_hole.rs | 9 +- .../rust-analyzer/src/cli/analysis_stats.rs | 10 +- 8 files changed, 211 insertions(+), 115 deletions(-) diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs index 009d561e7d18..457165296ad7 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search/mod.rs @@ -152,11 +152,37 @@ impl LookupTable { &self.exhausted_scopedefs } + /// Types queried but not found fn take_types_wishlist(&mut self) -> FxHashSet { std::mem::take(&mut self.types_wishlist) } } +/// Context for the `term_search` function +pub struct TermSearchCtx<'a, DB: HirDatabase> { + /// Semantics for the program + pub sema: &'a Semantics<'a, DB>, + /// Semantic scope, captures context for the term search + pub scope: &'a SemanticsScope<'a>, + /// Target / expected output type + pub goal: Type, + /// Configuration for term search + pub config: TermSearchConfig, +} + +/// Configuration options for the term search +#[derive(Debug, Clone, Copy)] +pub struct TermSearchConfig { + /// Enable borrow checking, this guarantees the outputs of the `term_search` to borrow-check + pub enable_borrowcheck: bool, +} + +impl Default for TermSearchConfig { + fn default() -> Self { + Self { enable_borrowcheck: true } + } +} + /// # Term search /// /// Search for terms (expressions) that unify with the `goal` type. @@ -181,37 +207,32 @@ impl LookupTable { /// Note that there are usually more ways we can get to the `goal` type but some are discarded to /// reduce the memory consumption. It is also unlikely anyone is willing ti browse through /// thousands of possible responses so we currently take first 10 from every tactic. -pub fn term_search( - sema: &Semantics<'_, DB>, - scope: &SemanticsScope<'_>, - goal: &Type, -) -> Vec { +pub fn term_search(ctx: TermSearchCtx<'_, DB>) -> Vec { + let module = ctx.scope.module(); let mut defs = FxHashSet::default(); - defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(scope.module()))); + defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module))); - scope.process_all_names(&mut |_, def| { + ctx.scope.process_all_names(&mut |_, def| { defs.insert(def); }); - let module = scope.module(); let mut lookup = LookupTable::new(); // Try trivial tactic first, also populates lookup table - let mut solutions: Vec = - tactics::trivial(sema.db, &defs, &mut lookup, goal).collect(); + let mut solutions: Vec = tactics::trivial(&ctx, &defs, &mut lookup).collect(); // Use well known types tactic before iterations as it does not depend on other tactics - solutions.extend(tactics::famous_types(sema.db, &module, &defs, &mut lookup, goal)); + solutions.extend(tactics::famous_types(&ctx, &defs, &mut lookup)); let mut solution_found = !solutions.is_empty(); for _ in 0..5 { lookup.new_round(); - solutions.extend(tactics::type_constructor(sema.db, &module, &defs, &mut lookup, goal)); - 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)); + solutions.extend(tactics::type_constructor(&ctx, &defs, &mut lookup)); + solutions.extend(tactics::free_function(&ctx, &defs, &mut lookup)); + solutions.extend(tactics::impl_method(&ctx, &defs, &mut lookup)); + solutions.extend(tactics::struct_projection(&ctx, &defs, &mut lookup)); + solutions.extend(tactics::impl_static_method(&ctx, &defs, &mut lookup)); // 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 2a9d0d84518a..da0ffd59def7 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -1,11 +1,9 @@ //! Tactics for term search //! //! All the tactics take following arguments -//! * `db` - HIR database -//! * `module` - Module where the term search target location +//! * `ctx` - Context for the term search //! * `defs` - Set of items in scope at term search target location //! * `lookup` - Lookup table for types -//! * `goal` - Term search target type //! And they return iterator that yields type trees that unify with the `goal` type. use std::iter; @@ -17,13 +15,13 @@ use itertools::Itertools; use rustc_hash::FxHashSet; use crate::{ - Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, Module, ModuleDef, - ScopeDef, Type, Variant, + Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, ModuleDef, ScopeDef, Type, + Variant, }; -use crate::term_search::TypeTree; +use crate::term_search::{TermSearchConfig, TypeTree}; -use super::{LookupTable, NewTypesKey, MAX_VARIATIONS}; +use super::{LookupTable, NewTypesKey, TermSearchCtx, MAX_VARIATIONS}; /// # Trivial tactic /// @@ -31,41 +29,42 @@ use super::{LookupTable, NewTypesKey, MAX_VARIATIONS}; /// Also works as a starting point to move all items in scope to lookup table. /// /// # Arguments -/// * `db` - HIR database +/// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types -/// * `goal` - Term search target type /// /// Returns iterator that yields elements that unify with `goal`. /// /// _Note that there is no use of calling this tactic in every iteration as the output does not /// depend on the current state of `lookup`_ -pub(super) fn trivial<'a>( - db: &'a dyn HirDatabase, +pub(super) fn trivial<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; defs.iter().filter_map(|def| { let tt = match def { ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(TypeTree::Const(*it)), ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(TypeTree::Static(*it)), ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(TypeTree::ConstParam(*it)), ScopeDef::Local(it) => { - let borrowck = db.borrowck(it.parent).ok()?; - - let invalid = borrowck.iter().any(|b| { - b.partially_moved.iter().any(|moved| { - Some(&moved.local) == b.mir_body.binding_locals.get(it.binding_id) - }) || b.borrow_regions.iter().any(|region| { - // Shared borrows are fine - Some(®ion.local) == b.mir_body.binding_locals.get(it.binding_id) - && region.kind != BorrowKind::Shared - }) - }); + if ctx.config.enable_borrowcheck { + let borrowck = db.borrowck(it.parent).ok()?; + + let invalid = borrowck.iter().any(|b| { + b.partially_moved.iter().any(|moved| { + Some(&moved.local) == b.mir_body.binding_locals.get(it.binding_id) + }) || b.borrow_regions.iter().any(|region| { + // Shared borrows are fine + Some(®ion.local) == b.mir_body.binding_locals.get(it.binding_id) + && region.kind != BorrowKind::Shared + }) + }); - if invalid { - return None; + if invalid { + return None; + } } Some(TypeTree::Local(*it)) @@ -83,7 +82,7 @@ pub(super) fn trivial<'a>( return None; } - ty.could_unify_with_deeply(db, goal).then(|| tt) + ty.could_unify_with_deeply(db, &ctx.goal).then(|| tt) }) } @@ -95,24 +94,23 @@ pub(super) fn trivial<'a>( /// elements that unify with `goal`. /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types -/// * `goal` - Term search target type -pub(super) fn type_constructor<'a>( - db: &'a dyn HirDatabase, - module: &'a Module, +pub(super) fn type_constructor<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); fn variant_helper( db: &dyn HirDatabase, lookup: &mut LookupTable, parent_enum: Enum, variant: Variant, goal: &Type, + config: &TermSearchConfig, ) -> Vec<(Type, Vec)> { let generics = GenericDef::from(variant.parent_enum(db)); @@ -151,7 +149,7 @@ pub(super) fn type_constructor<'a>( .permutations(non_default_type_params_len); generic_params - .filter_map(|generics| { + .filter_map(move |generics| { // Insert default type params let mut g = generics.into_iter(); let generics: Vec<_> = type_params @@ -171,7 +169,7 @@ pub(super) fn type_constructor<'a>( } // Ignore types that have something to do with lifetimes - if enum_ty.contains_reference(db) { + if config.enable_borrowcheck && enum_ty.contains_reference(db) { return None; } @@ -211,9 +209,10 @@ pub(super) fn type_constructor<'a>( .collect() } defs.iter() - .filter_map(|def| match def { + .filter_map(move |def| match def { ScopeDef::ModuleDef(ModuleDef::Variant(it)) => { - let variant_trees = variant_helper(db, lookup, it.parent_enum(db), *it, goal); + let variant_trees = + variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.goal, &ctx.config); if variant_trees.is_empty() { return None; } @@ -224,7 +223,9 @@ pub(super) fn type_constructor<'a>( let trees: Vec<(Type, Vec)> = enum_ .variants(db) .into_iter() - .flat_map(|it| variant_helper(db, lookup, enum_.clone(), it, goal)) + .flat_map(|it| { + variant_helper(db, lookup, enum_.clone(), it, &ctx.goal, &ctx.config) + }) .collect(); if !trees.is_empty() { @@ -234,8 +235,8 @@ pub(super) fn type_constructor<'a>( Some(trees) } ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => { - // Ignore unstable - if it.is_unstable(db) { + // Ignore unstable and not visible + if it.is_unstable(db) || !it.is_visible_from(db, module) { return None; } @@ -285,18 +286,18 @@ pub(super) fn type_constructor<'a>( // 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) + && struct_ty.could_unify_with_deeply(db, &ctx.goal) { return None; } // Ignore types that have something to do with lifetimes - if struct_ty.contains_reference(db) { + if ctx.config.enable_borrowcheck && struct_ty.contains_reference(db) { return None; } let fileds = it.fields(db); // Check if all fields are visible, otherwise we cannot fill them - if fileds.iter().any(|it| !it.is_visible_from(db, *module)) { + if fileds.iter().any(|it| !it.is_visible_from(db, module)) { return None; } @@ -335,7 +336,7 @@ pub(super) fn type_constructor<'a>( _ => None, }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) .flatten() } @@ -348,20 +349,18 @@ pub(super) fn type_constructor<'a>( /// elements that unify with `goal`. /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types -/// * `goal` - Term search target type -pub(super) fn free_function<'a>( - db: &'a dyn HirDatabase, - module: &'a Module, +pub(super) fn free_function<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); defs.iter() - .filter_map(|def| match def { + .filter_map(move |def| match def { ScopeDef::ModuleDef(ModuleDef::Function(it)) => { let generics = GenericDef::from(*it); @@ -411,10 +410,10 @@ pub(super) fn free_function<'a>( 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) + if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) || it.is_unstable(db) - || ret_ty.contains_reference(db) + || ctx.config.enable_borrowcheck && ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { return None; @@ -461,7 +460,7 @@ pub(super) fn free_function<'a>( _ => None, }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) .flatten() } @@ -476,18 +475,16 @@ pub(super) fn free_function<'a>( /// elements that unify with `goal`. /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `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_method<'a>( - db: &'a dyn HirDatabase, - module: &'a Module, +pub(super) fn impl_method<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); lookup .new_types(NewTypesKey::ImplMethod) .into_iter() @@ -499,7 +496,7 @@ pub(super) fn impl_method<'a>( AssocItem::Function(f) => Some((imp, ty, f)), _ => None, }) - .filter_map(|(imp, ty, it)| { + .filter_map(move |(imp, ty, it)| { let fn_generics = GenericDef::from(it); let imp_generics = GenericDef::from(imp); @@ -520,7 +517,7 @@ pub(super) fn impl_method<'a>( } // Filter out private and unsafe functions - if !it.is_visible_from(db, *module) || it.is_unsafe_to_call(db) || it.is_unstable(db) { + if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) || it.is_unstable(db) { return None; } @@ -570,7 +567,9 @@ pub(super) fn impl_method<'a>( ty.type_arguments().chain(generics.iter().cloned()), ); // Filter out functions that return references - if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { + if ctx.config.enable_borrowcheck && ret_ty.contains_reference(db) + || ret_ty.is_raw_ptr() + { return None; } @@ -615,7 +614,7 @@ pub(super) fn impl_method<'a>( Some(trees) }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) .flatten() } @@ -627,24 +626,21 @@ pub(super) fn impl_method<'a>( /// elements that unify with `goal`. /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types -/// * `goal` - Term search target type -pub(super) fn struct_projection<'a>( - db: &'a dyn HirDatabase, - module: &'a Module, +pub(super) fn struct_projection<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); lookup .new_types(NewTypesKey::StructProjection) .into_iter() .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("TypeTree not in lookup"))) .flat_map(move |(ty, targets)| { - let module = module.clone(); ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| { if !field.is_visible_from(db, module) { return None; @@ -656,7 +652,7 @@ pub(super) fn struct_projection<'a>( Some((filed_ty, trees)) }) }) - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) .flatten() } @@ -670,18 +666,16 @@ pub(super) fn struct_projection<'a>( /// _Note that there is no point of calling it iteratively as the output is always the same_ /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types -/// * `goal` - Term search target type -pub(super) fn famous_types<'a>( - db: &'a dyn HirDatabase, - module: &'a Module, +pub(super) fn famous_types<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); [ TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" }, TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" }, @@ -692,7 +686,7 @@ pub(super) fn famous_types<'a>( lookup.insert(tt.ty(db), std::iter::once(tt.clone())); tt }) - .filter(|tt| tt.ty(db).could_unify_with_deeply(db, goal)) + .filter(|tt| tt.ty(db).could_unify_with_deeply(db, &ctx.goal)) } /// # Impl static method (without self type) tactic @@ -703,22 +697,20 @@ pub(super) fn famous_types<'a>( /// elements that unify with `goal`. /// /// # Arguments -/// * `db` - HIR database -/// * `module` - Module where the term search target location +/// * `ctx` - Context for the term search /// * `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, +pub(super) fn impl_static_method<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, - goal: &'a Type, ) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); lookup .take_types_wishlist() .into_iter() - .chain(iter::once(goal.clone())) + .chain(iter::once(ctx.goal.clone())) .flat_map(|ty| { Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp)) }) @@ -728,7 +720,7 @@ pub(super) fn impl_static_method<'a>( AssocItem::Function(f) => Some((imp, ty, f)), _ => None, }) - .filter_map(|(imp, ty, it)| { + .filter_map(move |(imp, ty, it)| { let fn_generics = GenericDef::from(it); let imp_generics = GenericDef::from(imp); @@ -751,7 +743,7 @@ pub(super) fn impl_static_method<'a>( } // Filter out private and unsafe functions - if !it.is_visible_from(db, *module) || it.is_unsafe_to_call(db) || it.is_unstable(db) { + if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) || it.is_unstable(db) { return None; } @@ -801,7 +793,9 @@ pub(super) fn impl_static_method<'a>( ty.type_arguments().chain(generics.iter().cloned()), ); // Filter out functions that return references - if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { + if ctx.config.enable_borrowcheck && ret_ty.contains_reference(db) + || ret_ty.is_raw_ptr() + { return None; } @@ -845,6 +839,6 @@ pub(super) fn impl_static_method<'a>( Some(trees) }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees)) + .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) .flatten() } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index a32e36b71277..85d7add4a0b5 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -1,4 +1,5 @@ //! Term search assist +use hir::term_search::TermSearchCtx; use ide_db::assists::{AssistId, AssistKind, GroupLabel}; use itertools::Itertools; @@ -23,7 +24,13 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let scope = ctx.sema.scope(&parent)?; - let paths = hir::term_search::term_search(&ctx.sema, &scope, &target_ty); + let term_search_ctx = TermSearchCtx { + sema: &ctx.sema, + scope: &scope, + goal: target_ty, + config: Default::default(), + }; + let paths = hir::term_search::term_search(term_search_ctx); if paths.is_empty() { return None; diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index ba3c0cf3fd60..920db07e06f9 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -41,6 +41,7 @@ use crate::{ macro_::render_macro, pattern::{render_struct_pat, render_variant_pat}, render_field, render_path_resolution, render_pattern_resolution, render_tuple_field, + render_type_tree, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, RenderContext, @@ -157,6 +158,16 @@ impl Completions { item.add_to(self, ctx.db); } + pub(crate) fn add_expr( + &mut self, + ctx: &CompletionContext<'_>, + expr: &hir::term_search::TypeTree, + path_ctx: &PathCompletionCtx, + ) { + let item = render_type_tree(ctx, expr, path_ctx); + item.add_to(self, ctx.db); + } + pub(crate) fn add_crate_roots( &mut self, ctx: &CompletionContext<'_>, @@ -690,6 +701,7 @@ pub(super) fn complete_name_ref( match kind { NameRefKind::Path(path_ctx) => { flyimport::import_on_the_fly_path(acc, ctx, path_ctx); + expr::complete_expr(acc, ctx, path_ctx); match &path_ctx.kind { PathKind::Expr { expr_ctx } => { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 77fd5dd98b8d..b8ed429cb24e 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -328,3 +328,32 @@ pub(crate) fn complete_expr_path( } } } + +pub(crate) fn complete_expr( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx, +) { + let _p = profile::span("complete_expr"); + if !ctx.qualifier_ctx.none() { + return; + } + + if let Some(ty) = &ctx.expected_type { + // Ignore unit types as they are not very interesting + if ty.is_unit() { + return; + } + + let term_search_ctx = hir::term_search::TermSearchCtx { + sema: &ctx.sema, + scope: &ctx.scope, + goal: ty.clone(), + config: hir::term_search::TermSearchConfig { enable_borrowcheck: false }, + }; + let exprs = hir::term_search::term_search(term_search_ctx); + for expr in exprs { + acc.add_expr(ctx, &expr, path_ctx); + } + } +} diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 2ed080a83479..6d91b379162d 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -272,6 +272,26 @@ pub(crate) fn render_resolution_with_import_pat( Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution)) } +pub(crate) fn render_type_tree( + ctx: &CompletionContext<'_>, + expr: &hir::term_search::TypeTree, + path_ctx: &PathCompletionCtx, +) -> Builder { + let mut item = CompletionItem::new( + CompletionItemKind::Snippet, + ctx.source_range(), + expr.gen_source_code(&ctx.scope), + ); + item.set_relevance(crate::CompletionRelevance { + type_match: Some(crate::item::CompletionRelevanceTypeMatch::CouldUnify), + ..Default::default() + }); + + path_ref_match(ctx, path_ctx, &expr.ty(ctx.sema.db), &mut item); + + item +} + fn scope_def_to_name( resolution: ScopeDef, ctx: &RenderContext<'_>, diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index ff585f3d15b8..62e20f48b801 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -1,4 +1,8 @@ -use hir::{db::ExpandDatabase, term_search::term_search, ClosureStyle, HirDisplay, Semantics}; +use hir::{ + db::ExpandDatabase, + term_search::{term_search, TermSearchCtx}, + ClosureStyle, HirDisplay, Semantics, +}; use ide_db::{ assists::{Assist, AssistId, AssistKind, GroupLabel}, label::Label, @@ -40,7 +44,8 @@ fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option continue, }; - let found_terms = hir::term_search::term_search(&sema, &scope, &target_ty); + let ctx = hir::term_search::TermSearchCtx { + sema: &sema, + scope: &scope, + goal: target_ty, + config: hir::term_search::TermSearchConfig { + enable_borrowcheck: true, + }, + }; + let found_terms = hir::term_search::term_search(ctx); if found_terms.is_empty() { acc.tail_expr_no_term += 1; From a946970e2db9c86d2ed5e834e9853349e68b509d Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Wed, 3 Jan 2024 22:11:08 +0200 Subject: [PATCH 5/8] Add quantified trees to reduce autocomplete options --- Cargo.toml | 2 +- crates/hir/src/term_search/mod.rs | 81 ++++++++++--- crates/hir/src/term_search/tactics.rs | 7 +- crates/hir/src/term_search/type_tree.rs | 45 +++++-- .../ide-assists/src/handlers/term_search.rs | 3 +- crates/ide-completion/src/completions.rs | 9 +- crates/ide-completion/src/completions/expr.rs | 9 +- crates/ide-completion/src/render.rs | 51 ++++++-- crates/ide-completion/src/tests/expression.rs | 11 +- crates/ide-completion/src/tests/pattern.rs | 24 +++- crates/ide-completion/src/tests/record.rs | 2 + crates/ide-completion/src/tests/special.rs | 8 ++ crates/ide-completion/src/tests/type_pos.rs | 112 ++++++++++++------ .../src/handlers/typed_hole.rs | 6 +- .../rust-analyzer/src/cli/analysis_stats.rs | 4 +- crates/syntax/src/ast/node_ext.rs | 20 ++++ 16 files changed, 300 insertions(+), 94 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b81f7b11b23..e6f7c1009040 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ authors = ["rust-analyzer team"] [profile.dev] # Disabling debug info speeds up builds a bunch, # and we don't rely on it for debugging that much. -debug = 0 +debug = 2 [profile.dev.package] # These speed up local tests. diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs index 457165296ad7..a519324cdad3 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search/mod.rs @@ -12,13 +12,6 @@ pub use type_tree::TypeTree; mod tactics; -/// # Maximum amount of variations to take per type -/// -/// This is to speed up term search as there may be huge amount of variations of arguments for -/// function, even when the return type is always the same. The idea is to take first n and call it -/// a day. -const MAX_VARIATIONS: usize = 10; - /// Key for lookup table to query new types reached. #[derive(Debug, Hash, PartialEq, Eq)] enum NewTypesKey { @@ -26,6 +19,52 @@ enum NewTypesKey { StructProjection, } +#[derive(Debug)] +enum AlternativeTrees { + Few(FxHashSet), + Many(Type), +} + +impl AlternativeTrees { + pub fn new( + threshold: usize, + ty: Type, + trees: impl Iterator, + ) -> AlternativeTrees { + let mut it = AlternativeTrees::Few(Default::default()); + it.extend_with_threshold(threshold, ty, trees); + it + } + + pub fn trees(&self) -> Vec { + match self { + AlternativeTrees::Few(trees) => trees.iter().cloned().collect(), + AlternativeTrees::Many(ty) => vec![TypeTree::Many(ty.clone())], + } + } + + pub fn extend_with_threshold( + &mut self, + threshold: usize, + ty: Type, + mut trees: impl Iterator, + ) { + match self { + AlternativeTrees::Few(tts) => { + while let Some(it) = trees.next() { + if tts.len() > threshold { + *self = AlternativeTrees::Many(ty); + break; + } + + tts.insert(it); + } + } + AlternativeTrees::Many(_) => (), + } + } +} + /// # Lookup table for term search /// /// Lookup table keeps all the state during term search. @@ -38,7 +77,7 @@ enum NewTypesKey { #[derive(Default, Debug)] struct LookupTable { /// All the `TypeTree`s in "value" produce the type of "key" - data: FxHashMap>, + data: FxHashMap, /// New types reached since last query by the `NewTypesKey` new_types: FxHashMap>, /// ScopeDefs that are not interesting any more @@ -49,6 +88,8 @@ struct LookupTable { rounds_since_sopedef_hit: FxHashMap, /// Types queried but not present types_wishlist: FxHashSet, + /// Threshold to squash trees to `Many` + many_threshold: usize, } impl LookupTable { @@ -65,7 +106,7 @@ impl LookupTable { self.data .iter() .find(|(t, _)| t.could_unify_with_deeply(db, ty)) - .map(|(_, tts)| tts.iter().cloned().collect()) + .map(|(_, tts)| tts.trees()) } /// Same as find but automatically creates shared reference of types in the lookup @@ -76,7 +117,7 @@ impl LookupTable { self.data .iter() .find(|(t, _)| t.could_unify_with_deeply(db, ty)) - .map(|(_, tts)| tts.iter().cloned().collect()) + .map(|(_, tts)| tts.trees()) .or_else(|| { self.data .iter() @@ -84,7 +125,10 @@ impl LookupTable { Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty) }) .map(|(_, tts)| { - tts.iter().map(|tt| TypeTree::Reference(Box::new(tt.clone()))).collect() + tts.trees() + .into_iter() + .map(|tt| TypeTree::Reference(Box::new(tt))) + .collect() }) }) } @@ -96,9 +140,12 @@ impl LookupTable { /// but they clearly do not unify themselves. fn insert(&mut self, ty: Type, trees: impl Iterator) { match self.data.get_mut(&ty) { - Some(it) => it.extend(trees.take(MAX_VARIATIONS)), + Some(it) => it.extend_with_threshold(self.many_threshold, ty, trees), None => { - self.data.insert(ty.clone(), trees.take(MAX_VARIATIONS).collect()); + self.data.insert( + ty.clone(), + AlternativeTrees::new(self.many_threshold, ty.clone(), trees), + ); for it in self.new_types.values_mut() { it.push(ty.clone()); } @@ -175,11 +222,15 @@ pub struct TermSearchCtx<'a, DB: HirDatabase> { pub struct TermSearchConfig { /// Enable borrow checking, this guarantees the outputs of the `term_search` to borrow-check pub enable_borrowcheck: bool, + /// Indicate when to squash multiple trees to `Many` as there are too many to keep track + pub many_alternatives_threshold: usize, + /// Depth of the search eg. number of cycles to run + pub depth: usize, } impl Default for TermSearchConfig { fn default() -> Self { - Self { enable_borrowcheck: true } + Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 5 } } } @@ -225,7 +276,7 @@ pub fn term_search(ctx: TermSearchCtx<'_, DB>) -> Vec let mut solution_found = !solutions.is_empty(); - for _ in 0..5 { + for _ in 0..ctx.config.depth { lookup.new_round(); solutions.extend(tactics::type_constructor(&ctx, &defs, &mut lookup)); diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index da0ffd59def7..e0b6c1291e42 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -21,7 +21,7 @@ use crate::{ use crate::term_search::{TermSearchConfig, TypeTree}; -use super::{LookupTable, NewTypesKey, TermSearchCtx, MAX_VARIATIONS}; +use super::{LookupTable, NewTypesKey, TermSearchCtx}; /// # Trivial tactic /// @@ -194,7 +194,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( param_trees .into_iter() .multi_cartesian_product() - .take(MAX_VARIATIONS) .map(|params| TypeTree::Variant { variant, generics: generics.clone(), @@ -315,7 +314,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( param_trees .into_iter() .multi_cartesian_product() - .take(MAX_VARIATIONS) .map(|params| TypeTree::Struct { strukt: *it, generics: generics.clone(), @@ -440,7 +438,6 @@ pub(super) fn free_function<'a, DB: HirDatabase>( param_trees .into_iter() .multi_cartesian_product() - .take(MAX_VARIATIONS) .map(|params| TypeTree::Function { func: *it, generics: generics.clone(), @@ -603,7 +600,6 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( let fn_trees: Vec = std::iter::once(target_type_trees) .chain(param_trees.into_iter()) .multi_cartesian_product() - .take(MAX_VARIATIONS) .map(|params| TypeTree::Function { func: it, generics: Vec::new(), params }) .collect(); @@ -822,7 +818,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( param_trees .into_iter() .multi_cartesian_product() - .take(MAX_VARIATIONS) .map(|params| TypeTree::Function { func: it, generics: generics.clone(), diff --git a/crates/hir/src/term_search/type_tree.rs b/crates/hir/src/term_search/type_tree.rs index 3cb2fcdd64b3..72a1cbe90968 100644 --- a/crates/hir/src/term_search/type_tree.rs +++ b/crates/hir/src/term_search/type_tree.rs @@ -109,6 +109,8 @@ pub enum TypeTree { Field { type_tree: Box, field: Field }, /// Passing type as reference (with `&`) Reference(Box), + /// Indicates possibility of many different options that all evaluate to `ty` + Many(Type), } impl TypeTree { @@ -117,7 +119,11 @@ impl TypeTree { /// Note that trait imports are not added to generated code. /// To make sure that the code is valid, callee has to also ensure that all the traits listed /// by `traits_used` method are also imported. - pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String { + pub fn gen_source_code( + &self, + sema_scope: &SemanticsScope<'_>, + many_formatter: &mut dyn FnMut(&Type) -> String, + ) -> String { let db = sema_scope.db; match self { TypeTree::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), @@ -128,9 +134,15 @@ impl TypeTree { TypeTree::Function { func, params, .. } => { if let Some(self_param) = func.self_param(db) { let func_name = func.name(db).display(db.upcast()).to_string(); - let target = params.first().expect("no self param").gen_source_code(sema_scope); - let args = - params.iter().skip(1).map(|f| f.gen_source_code(sema_scope)).join(", "); + let target = params + .first() + .expect("no self param") + .gen_source_code(sema_scope, many_formatter); + let args = params + .iter() + .skip(1) + .map(|f| f.gen_source_code(sema_scope, many_formatter)) + .join(", "); match func.as_assoc_item(db).unwrap().containing_trait_or_trait_impl(db) { Some(trait_) => { @@ -149,7 +161,10 @@ impl TypeTree { None => format!("{target}.{func_name}({args})"), } } else { - let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", "); + let args = params + .iter() + .map(|f| f.gen_source_code(sema_scope, many_formatter)) + .join(", "); match func.as_assoc_item(db).map(|it| it.container(db)) { Some(container) => { @@ -194,7 +209,10 @@ impl TypeTree { }; let inner = match variant.kind(db) { StructKind::Tuple => { - let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", "); + let args = params + .iter() + .map(|f| f.gen_source_code(sema_scope, many_formatter)) + .join(", "); format!("{generics_str}({args})") } StructKind::Record => { @@ -206,7 +224,7 @@ impl TypeTree { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope) + a.gen_source_code(sema_scope, many_formatter) ) }) .join(", "); @@ -222,7 +240,10 @@ impl TypeTree { 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(", "); + let args = params + .iter() + .map(|a| a.gen_source_code(sema_scope, many_formatter)) + .join(", "); format!("({args})") } StructKind::Record => { @@ -234,7 +255,7 @@ impl TypeTree { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope) + a.gen_source_code(sema_scope, many_formatter) ) }) .join(", "); @@ -254,14 +275,15 @@ impl TypeTree { format!("{prefix}{inner}") } TypeTree::Field { type_tree, field } => { - let strukt = type_tree.gen_source_code(sema_scope); + let strukt = type_tree.gen_source_code(sema_scope, many_formatter); let field = field.name(db).display(db.upcast()).to_string(); format!("{strukt}.{field}") } TypeTree::Reference(type_tree) => { - let inner = type_tree.gen_source_code(sema_scope); + let inner = type_tree.gen_source_code(sema_scope, many_formatter); format!("&{inner}") } + TypeTree::Many(ty) => many_formatter(ty), } } @@ -292,6 +314,7 @@ impl TypeTree { field.ty_with_generics(db, type_tree.ty(db).type_arguments()) } TypeTree::Reference(it) => it.ty(db), + TypeTree::Many(ty) => ty.clone(), } } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 85d7add4a0b5..3451a65493bb 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -36,8 +36,9 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< return None; } + let mut formatter = |_: &hir::Type| String::from("todo!()"); for path in paths.iter().unique() { - let code = path.gen_source_code(&scope); + let code = path.gen_source_code(&scope, &mut formatter); acc.add_group( &GroupLabel(String::from("Term search")), AssistId("term_search", AssistKind::Generate), diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 920db07e06f9..b696933c2727 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -162,10 +162,11 @@ impl Completions { &mut self, ctx: &CompletionContext<'_>, expr: &hir::term_search::TypeTree, - path_ctx: &PathCompletionCtx, ) { - let item = render_type_tree(ctx, expr, path_ctx); - item.add_to(self, ctx.db); + match render_type_tree(ctx, expr) { + Some(item) => item.add_to(self, ctx.db), + None => (), + } } pub(crate) fn add_crate_roots( @@ -698,10 +699,10 @@ pub(super) fn complete_name_ref( ctx: &CompletionContext<'_>, NameRefContext { nameref, kind }: &NameRefContext, ) { + expr::complete_expr(acc, ctx); match kind { NameRefKind::Path(path_ctx) => { flyimport::import_on_the_fly_path(acc, ctx, path_ctx); - expr::complete_expr(acc, ctx, path_ctx); match &path_ctx.kind { PathKind::Expr { expr_ctx } => { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index b8ed429cb24e..891717285dac 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -332,7 +332,6 @@ pub(crate) fn complete_expr_path( pub(crate) fn complete_expr( acc: &mut Completions, ctx: &CompletionContext<'_>, - path_ctx: &PathCompletionCtx, ) { let _p = profile::span("complete_expr"); if !ctx.qualifier_ctx.none() { @@ -349,11 +348,15 @@ pub(crate) fn complete_expr( sema: &ctx.sema, scope: &ctx.scope, goal: ty.clone(), - config: hir::term_search::TermSearchConfig { enable_borrowcheck: false }, + config: hir::term_search::TermSearchConfig { + enable_borrowcheck: false, + many_alternatives_threshold: 1, + depth: 2, + }, }; let exprs = hir::term_search::term_search(term_search_ctx); for expr in exprs { - acc.add_expr(ctx, &expr, path_ctx); + acc.add_expr(ctx, &expr); } } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 6d91b379162d..0a9ea1ac849f 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -17,7 +17,7 @@ use ide_db::{ imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; -use syntax::{format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange}; +use syntax::{ast, AstNode, SmolStr, SyntaxKind, TextRange}; use text_edit::TextEdit; use crate::{ @@ -275,21 +275,50 @@ pub(crate) fn render_resolution_with_import_pat( pub(crate) fn render_type_tree( ctx: &CompletionContext<'_>, expr: &hir::term_search::TypeTree, - path_ctx: &PathCompletionCtx, -) -> Builder { - let mut item = CompletionItem::new( - CompletionItemKind::Snippet, - ctx.source_range(), - expr.gen_source_code(&ctx.scope), - ); +) -> Option { + let mut i = 1; + let mut snippet_formatter = |ty: &hir::Type| { + let arg_name = ty + .as_adt() + .and_then(|adt| adt.name(ctx.db).as_text()) + .map(|s| stdx::to_lower_snake_case(s.as_str())) + .unwrap_or_else(|| String::from("_")); + let res = format!("${{{i}:{arg_name}}}"); + i += 1; + res + }; + + let mut label_formatter = |ty: &hir::Type| { + ty.as_adt() + .and_then(|adt| adt.name(ctx.db).as_text()) + .map(|s| stdx::to_lower_snake_case(s.as_str())) + .unwrap_or_else(|| String::from("_")) + }; + + let label = expr.gen_source_code(&ctx.scope, &mut label_formatter); + + let source_range = match &ctx.expected_name { + Some(name_or_ref) => name_or_ref.syntax().text_range(), + None => match ctx.original_token.parent() { + Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { + Some(path) => path.syntax().text_range(), + None => node.text_range(), + }, + None => ctx.source_range(), + }, + }; + + let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label); + + let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter)); + let edit = TextEdit::replace(source_range, snippet); + item.snippet_edit(ctx.config.snippet_cap?, edit); item.set_relevance(crate::CompletionRelevance { type_match: Some(crate::item::CompletionRelevanceTypeMatch::CouldUnify), ..Default::default() }); - path_ref_match(ctx, path_ctx, &expr.ty(ctx.sema.db), &mut item); - - item + Some(item) } fn scope_def_to_name( diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 78907a2896c4..b835820260a9 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -97,6 +97,11 @@ fn func(param0 @ (param1, param2): (i32, i32)) { kw unsafe kw while kw while let + sn ifletlocal + sn letlocal + sn matcharm + sn param1 + sn param2 "#]], ); } @@ -238,9 +243,11 @@ fn complete_in_block() { kw use kw while kw while let + sn false sn macro_rules sn pd sn ppd + sn true "#]], ) } @@ -682,7 +689,9 @@ fn main() { } "#, expect![[r#" - fn test() fn() -> Zulu + fn test() fn() -> Zulu + sn Zulu + sn Zulu::test() "#]], ); } diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs index 67cf551fce84..5363e33b8553 100644 --- a/crates/ide-completion/src/tests/pattern.rs +++ b/crates/ide-completion/src/tests/pattern.rs @@ -316,6 +316,15 @@ fn func() { bn RecordV {…} RecordV { field$1 }$0 bn TupleV(…) TupleV($1)$0 bn UnitV UnitV$0 + sn () + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn func() + sn function() + sn true "#]], ); } @@ -558,10 +567,12 @@ fn foo() { } "#, expect![[r#" - bn A A$0 - bn B {…} B { r#type$1 }$0 - bn struct {…} r#struct { r#type$1 }$0 - bn type r#type$0 + bn A A$0 + bn B {…} B { r#type$1 }$0 + bn struct {…} r#struct { r#type$1 }$0 + bn type r#type$0 + sn Enum::A + sn Enum::r#type "#]], ); } @@ -586,6 +597,7 @@ fn f(t: Ty) { "#, expect![[r#" ct ABC const ABC: Self + sn t "#]], ); @@ -608,6 +620,7 @@ fn f(e: MyEnum) { expect![[r#" ct A pub const A: i32 ct B pub const B: i32 + sn e "#]], ); @@ -633,6 +646,7 @@ fn f(u: U) { expect![[r#" ct C pub const C: i32 ct D pub const D: i32 + sn u "#]], ); @@ -652,6 +666,7 @@ fn f(v: u32) { "#, expect![[r#" ct MIN pub const MIN: Self + sn v "#]], ); } @@ -763,6 +778,7 @@ fn f(x: EnumAlias) { expect![[r#" bn Tuple(…) Tuple($1)$0 bn Unit Unit$0 + sn x "#]], ); } diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs index 18afde1b7cef..c137de3e52d6 100644 --- a/crates/ide-completion/src/tests/record.rs +++ b/crates/ide-completion/src/tests/record.rs @@ -192,6 +192,8 @@ fn main() { bt u32 u32 kw crate:: kw self:: + sn Foo::default() + sn foo "#]], ); check( diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index a87d16c789fa..68a6c678501f 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -1230,6 +1230,10 @@ fn here_we_go() { "#, expect![[r#" st Bar (alias Qux) Bar + sn () + sn false + sn here_we_go() + sn true "#]], ); } @@ -1284,6 +1288,10 @@ fn here_we_go() { kw unsafe kw while kw while let + sn () + sn false + sn here_we_go() + sn true "#]], ); } diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index c7161f82ce74..8b383d499523 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -70,18 +70,27 @@ fn fn_return_type() { fn x<'lt, T, const C: usize>() -> $0 "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait tp T - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 kw crate:: kw self:: + sn () + sn C + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn function() + sn true "#]], ); } @@ -100,18 +109,27 @@ fn foo() -> B$0 { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 it () kw crate:: kw self:: + sn () + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn foo() + sn function() + sn true "#]], ) } @@ -204,18 +222,26 @@ fn f2(x: u64) -> $0 { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 it u64 kw crate:: kw self:: + sn () + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn function() + sn true "#]], ); } @@ -319,18 +345,27 @@ fn foo<'lt, T, const C: usize>() { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait tp T - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 kw crate:: kw self:: + sn () + sn C + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn function() + sn true "#]], ); check( @@ -341,14 +376,23 @@ fn foo<'lt, T, const C: usize>() { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union + un Union Union + sn () + sn C + sn CONST + sn Enum::UnitV + sn STATIC + sn Unit + sn false + sn function() + sn true "#]], ); } diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 62e20f48b801..fa4734b1a9c7 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -44,12 +44,14 @@ fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option bool { + matches!(kind, SyntaxKind::NAME | SyntaxKind::NAME_REF) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SyntaxKind::NAME => NameOrNameRef::Name(ast::Name { syntax }), + SyntaxKind::NAME_REF => NameOrNameRef::NameRef(ast::NameRef { syntax }), + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + NameOrNameRef::NameRef(it) => it.syntax(), + NameOrNameRef::Name(it) => it.syntax(), + } + } +} + impl NameOrNameRef { pub fn text(&self) -> TokenText<'_> { match self { From 0b838e3e2381ebad5de3e7f58f38ac9883dca941 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Sat, 6 Jan 2024 15:17:16 +0200 Subject: [PATCH 6/8] Expand target for autocompletion --- Cargo.toml | 2 +- crates/hir-ty/src/infer/unify.rs | 4 - crates/hir/src/lib.rs | 104 ++++---- .../src/term_search/{type_tree.rs => expr.rs} | 222 ++++++++++-------- crates/hir/src/term_search/mod.rs | 132 +++++------ crates/hir/src/term_search/tactics.rs | 206 ++++++++-------- .../ide-assists/src/handlers/term_search.rs | 2 +- crates/ide-completion/src/completions.rs | 15 +- crates/ide-completion/src/completions/expr.rs | 35 ++- crates/ide-completion/src/render.rs | 41 ++-- crates/ide-completion/src/tests/pattern.rs | 24 +- crates/ide-completion/src/tests/type_pos.rs | 112 +++------ .../src/handlers/typed_hole.rs | 2 +- .../rust-analyzer/src/cli/analysis_stats.rs | 11 +- 14 files changed, 448 insertions(+), 464 deletions(-) rename crates/hir/src/term_search/{type_tree.rs => expr.rs} (58%) diff --git a/Cargo.toml b/Cargo.toml index e6f7c1009040..2b81f7b11b23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ authors = ["rust-analyzer team"] [profile.dev] # Disabling debug info speeds up builds a bunch, # and we don't rely on it for debugging that much. -debug = 2 +debug = 0 [profile.dev.package] # These speed up local tests. diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index a6c2dfad550c..25dde2d15934 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -106,10 +106,6 @@ pub fn could_unify_deeply( let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); let ty1_with_vars = table.normalize_associated_types_in(ty1_with_vars); let ty2_with_vars = table.normalize_associated_types_in(ty2_with_vars); - // table.resolve_obligations_as_possible(); - // table.propagate_diverging_flag(); - // let ty1_with_vars = table.resolve_completely(ty1_with_vars); - // let ty2_with_vars = table.resolve_completely(ty2_with_vars); table.unify_deeply(&ty1_with_vars, &ty2_with_vars) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 9b8c7b900ae2..283461f2199f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1085,20 +1085,21 @@ impl Field { Type::new(db, var_id, ty) } - pub fn ty_with_generics( - &self, - db: &dyn HirDatabase, - mut generics: impl Iterator, - ) -> Type { + pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { let var_id = self.parent.into(); let def_id: AdtId = match self.parent { VariantDef::Struct(it) => it.id.into(), VariantDef::Union(it) => it.id.into(), VariantDef::Variant(it) => it.parent_enum(db).id.into(), }; + let mut generics = generics.map(|it| it.ty.clone()); let substs = TyBuilder::subst_for_def(db, def_id, None) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + .fill(|x| { + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => ty.cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } }) .build(); let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs); @@ -1158,14 +1159,15 @@ impl Struct { Type::from_def(db, self.id) } - pub fn ty_with_generics( - self, - db: &dyn HirDatabase, - mut generics: impl Iterator, - ) -> Type { + pub fn ty_with_args(self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { + let mut generics = generics.map(|it| it.ty.clone()); let substs = TyBuilder::subst_for_def(db, self.id, None) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + .fill(|x| { + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => ty.cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } }) .build(); let ty = db.ty(self.id.into()).substitute(Interner, &substs); @@ -1271,16 +1273,18 @@ impl Enum { Type::from_def(db, self.id) } - pub fn ty_with_generics( - &self, - db: &dyn HirDatabase, - mut generics: impl Iterator, - ) -> Type { + pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { + let mut generics = generics.map(|it| it.ty.clone()); let substs = TyBuilder::subst_for_def(db, self.id, None) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + .fill(|x| { + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => ty.cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } }) .build(); + let ty = db.ty(self.id.into()).substitute(Interner, &substs); Type::new(db, self.id, ty) } @@ -1854,10 +1858,10 @@ impl Function { Type::new_with_resolver_inner(db, &resolver, ty) } - pub fn ret_type_with_generics( + pub fn ret_type_with_args( self, db: &dyn HirDatabase, - mut generics: impl Iterator, + generics: impl Iterator, ) -> Type { let resolver = self.id.resolver(db.upcast()); let parent_id: Option = match self.id.lookup(db.upcast()).container { @@ -1865,22 +1869,18 @@ impl Function { ItemContainerId::TraitId(it) => Some(it.into()), ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None, }; - let parent_substs = parent_id.map(|id| { - TyBuilder::subst_for_def(db, id, None) - .fill(|_| { - GenericArg::new( - Interner, - GenericArgData::Ty(generics.next().unwrap().ty.clone()), - ) - }) - .build() - }); + let mut generics = generics.map(|it| it.ty.clone()); + let mut filler = |x: &_| { + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => ty.cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } + }; - let substs = TyBuilder::subst_for_def(db, self.id, parent_substs) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) - }) - .build(); + let parent_substs = + parent_id.map(|id| TyBuilder::subst_for_def(db, id, None).fill(&mut filler).build()); + let substs = TyBuilder::subst_for_def(db, self.id, parent_substs).fill(&mut filler).build(); let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs); let ty = callable_sig.ret().clone(); @@ -2197,11 +2197,7 @@ impl SelfParam { Type { env: environment, ty } } - pub fn ty_with_generics( - &self, - db: &dyn HirDatabase, - mut generics: impl Iterator, - ) -> Type { + pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { let parent_id: GenericDefId = match self.func.lookup(db.upcast()).container { ItemContainerId::ImplId(it) => it.into(), ItemContainerId::TraitId(it) => it.into(), @@ -2210,16 +2206,18 @@ impl SelfParam { } }; - let parent_substs = TyBuilder::subst_for_def(db, parent_id, None) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) - }) - .build(); - let substs = TyBuilder::subst_for_def(db, self.func, Some(parent_substs)) - .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) - }) - .build(); + let mut generics = generics.map(|it| it.ty.clone()); + let mut filler = |x: &_| { + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => ty.cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } + }; + + let parent_substs = TyBuilder::subst_for_def(db, parent_id, None).fill(&mut filler).build(); + let substs = + TyBuilder::subst_for_def(db, self.func, Some(parent_substs)).fill(&mut filler).build(); let callable_sig = db.callable_item_signature(self.func.into()).substitute(Interner, &substs); let environment = db.trait_environment(self.func.into()); diff --git a/crates/hir/src/term_search/type_tree.rs b/crates/hir/src/term_search/expr.rs similarity index 58% rename from crates/hir/src/term_search/type_tree.rs rename to crates/hir/src/term_search/expr.rs index 72a1cbe90968..b43f10528db6 100644 --- a/crates/hir/src/term_search/type_tree.rs +++ b/crates/hir/src/term_search/expr.rs @@ -88,7 +88,7 @@ fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type] /// So in short it pretty much gives us a way to get type `Option` using the items we have in /// scope. #[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub enum TypeTree { +pub enum Expr { /// Constant Const(Const), /// Static variable @@ -99,21 +99,23 @@ pub enum TypeTree { ConstParam(ConstParam), /// Well known type (such as `true` for bool) FamousType { ty: Type, value: &'static str }, - /// Function or method call - Function { func: Function, generics: Vec, params: Vec }, + /// Function call (does not take self param) + Function { func: Function, generics: Vec, params: Vec }, + /// Method call (has self param) + Method { func: Function, generics: Vec, target: Box, params: Vec }, /// Enum variant construction - Variant { variant: Variant, generics: Vec, params: Vec }, + Variant { variant: Variant, generics: Vec, params: Vec }, /// Struct construction - Struct { strukt: Struct, generics: Vec, params: Vec }, + Struct { strukt: Struct, generics: Vec, params: Vec }, /// Struct field access - Field { type_tree: Box, field: Field }, + Field { expr: Box, field: Field }, /// Passing type as reference (with `&`) - Reference(Box), + Reference(Box), /// Indicates possibility of many different options that all evaluate to `ty` Many(Type), } -impl TypeTree { +impl Expr { /// Generate source code for type tree. /// /// Note that trait imports are not added to generated code. @@ -126,78 +128,70 @@ impl TypeTree { ) -> String { let db = sema_scope.db; match self { - 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(), - TypeTree::Function { func, params, .. } => { - if let Some(self_param) = func.self_param(db) { - let func_name = func.name(db).display(db.upcast()).to_string(); - let target = params - .first() - .expect("no self param") - .gen_source_code(sema_scope, many_formatter); - let args = params - .iter() - .skip(1) - .map(|f| f.gen_source_code(sema_scope, many_formatter)) - .join(", "); + Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), + Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), + Expr::Local(it) => return it.name(db).display(db.upcast()).to_string(), + Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), + Expr::FamousType { value, .. } => return value.to_string(), + Expr::Function { func, params, .. } => { + let args = + params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); - match func.as_assoc_item(db).unwrap().containing_trait_or_trait_impl(db) { - Some(trait_) => { - let trait_name = - 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}"), - crate::Access::Owned => target, - }; - match args.is_empty() { - true => format!("{trait_name}::{func_name}({target})",), - false => format!("{trait_name}::{func_name}({target}, {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_)) } - } - None => format!("{target}.{func_name}({args})"), + 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})",) } - } else { - let args = params - .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter)) - .join(", "); + None => { + let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func)); + format!("{fn_name}({args})",) + } + } + } + Expr::Method { func, target, params, .. } => { + if target.contains_many_in_illegal_pos() { + return many_formatter(&target.ty(db)); + } - 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})",) + let func_name = func.name(db).display(db.upcast()).to_string(); + let self_param = func.self_param(db).unwrap(); + let target = target.gen_source_code(sema_scope, many_formatter); + let args = + params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); + + match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) { + Some(trait_) => { + let trait_name = 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}"), + crate::Access::Owned => target, + }; + match args.is_empty() { + true => format!("{trait_name}::{func_name}({target})",), + false => format!("{trait_name}::{func_name}({target}, {args})",), } } + None => format!("{target}.{func_name}({args})"), } } - TypeTree::Variant { variant, generics, params } => { + Expr::Variant { variant, generics, params } => { let generics = non_default_generics(db, (*variant).into(), generics); let generics_str = match generics.is_empty() { true => String::new(), @@ -236,7 +230,7 @@ impl TypeTree { let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant)); format!("{prefix}{inner}") } - TypeTree::Struct { strukt, generics, params } => { + Expr::Struct { strukt, generics, params } => { let generics = non_default_generics(db, (*strukt).into(), generics); let inner = match strukt.kind(db) { StructKind::Tuple => { @@ -274,16 +268,24 @@ impl TypeTree { let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt))); format!("{prefix}{inner}") } - TypeTree::Field { type_tree, field } => { - let strukt = type_tree.gen_source_code(sema_scope, many_formatter); + Expr::Field { expr, field } => { + if expr.contains_many_in_illegal_pos() { + return many_formatter(&expr.ty(db)); + } + + let strukt = expr.gen_source_code(sema_scope, many_formatter); let field = field.name(db).display(db.upcast()).to_string(); format!("{strukt}.{field}") } - TypeTree::Reference(type_tree) => { - let inner = type_tree.gen_source_code(sema_scope, many_formatter); + Expr::Reference(expr) => { + if expr.contains_many_in_illegal_pos() { + return many_formatter(&expr.ty(db)); + } + + let inner = expr.gen_source_code(sema_scope, many_formatter); format!("&{inner}") } - TypeTree::Many(ty) => many_formatter(ty), + Expr::Many(ty) => many_formatter(ty), } } @@ -292,29 +294,27 @@ impl TypeTree { /// Same as getting the type of root node pub fn ty(&self, db: &dyn HirDatabase) -> Type { match self { - TypeTree::Const(it) => it.ty(db), - TypeTree::Static(it) => it.ty(db), - TypeTree::Local(it) => it.ty(db), - TypeTree::ConstParam(it) => it.ty(db), - TypeTree::FamousType { ty, .. } => ty.clone(), - TypeTree::Function { func, generics, params } => match func.has_self_param(db) { - true => func.ret_type_with_generics( - db, - params[0].ty(db).type_arguments().chain(generics.iter().cloned()), - ), - false => func.ret_type_with_generics(db, generics.iter().cloned()), - }, - TypeTree::Variant { variant, generics, .. } => { - variant.parent_enum(db).ty_with_generics(db, generics.iter().cloned()) + Expr::Const(it) => it.ty(db), + Expr::Static(it) => it.ty(db), + Expr::Local(it) => it.ty(db), + Expr::ConstParam(it) => it.ty(db), + Expr::FamousType { ty, .. } => ty.clone(), + Expr::Function { func, generics, .. } => { + func.ret_type_with_args(db, generics.iter().cloned()) } - TypeTree::Struct { strukt, generics, .. } => { - strukt.ty_with_generics(db, generics.iter().cloned()) + Expr::Method { func, generics, target, .. } => func.ret_type_with_args( + db, + target.ty(db).type_arguments().chain(generics.iter().cloned()), + ), + Expr::Variant { variant, generics, .. } => { + variant.parent_enum(db).ty_with_args(db, generics.iter().cloned()) } - TypeTree::Field { type_tree, field } => { - field.ty_with_generics(db, type_tree.ty(db).type_arguments()) + Expr::Struct { strukt, generics, .. } => { + strukt.ty_with_args(db, generics.iter().cloned()) } - TypeTree::Reference(it) => it.ty(db), - TypeTree::Many(ty) => ty.clone(), + Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()), + Expr::Reference(it) => it.ty(db), + Expr::Many(ty) => ty.clone(), } } @@ -323,7 +323,7 @@ impl TypeTree { let mut res = Vec::new(); match self { - TypeTree::Function { func, params, .. } => { + Expr::Method { func, params, .. } => { res.extend(params.iter().flat_map(|it| it.traits_used(db))); if let Some(it) = func.as_assoc_item(db) { if let Some(it) = it.containing_trait_or_trait_impl(db) { @@ -336,4 +336,28 @@ impl TypeTree { res } + + /// Check in the tree contains `Expr::Many` variant in illegal place to insert `todo`, + /// `unimplemented` or similar macro + /// + /// Some examples are following + /// ```no_compile + /// macro!().foo + /// macro!().bar() + /// ¯o!() + /// ``` + fn contains_many_in_illegal_pos(&self) -> bool { + match self { + Expr::Method { target, .. } => target.contains_many_in_illegal_pos(), + Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(), + Expr::Reference(target) => target.is_many(), + Expr::Many(_) => true, + _ => false, + } + } + + /// Helper function to check if outermost type tree is `Expr::Many` variant + pub fn is_many(&self) -> bool { + matches!(self, Expr::Many(_)) + } } diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs index a519324cdad3..f0c3fdb4d094 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search/mod.rs @@ -7,8 +7,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type}; -pub mod type_tree; -pub use type_tree::TypeTree; +mod expr; +pub use expr::Expr; mod tactics; @@ -19,48 +19,57 @@ enum NewTypesKey { StructProjection, } +/// Helper enum to squash big number of alternative trees into `Many` variant as there is too many +/// to take into account. #[derive(Debug)] -enum AlternativeTrees { - Few(FxHashSet), - Many(Type), +enum AlternativeExprs { + /// There are few trees, so we keep track of them all + Few(FxHashSet), + /// There are too many trees to keep track of + Many, } -impl AlternativeTrees { - pub fn new( - threshold: usize, - ty: Type, - trees: impl Iterator, - ) -> AlternativeTrees { - let mut it = AlternativeTrees::Few(Default::default()); - it.extend_with_threshold(threshold, ty, trees); +impl AlternativeExprs { + /// Construct alternative trees + /// + /// # Arguments + /// `threshold` - threshold value for many trees (more than that is many) + /// `exprs` - expressions iterator + fn new(threshold: usize, exprs: impl Iterator) -> AlternativeExprs { + let mut it = AlternativeExprs::Few(Default::default()); + it.extend_with_threshold(threshold, exprs); it } - pub fn trees(&self) -> Vec { + /// Get type trees stored in alternative trees (or `Expr::Many` in case of many) + /// + /// # Arguments + /// `ty` - Type of expressions queried (this is used to give type to `Expr::Many`) + fn exprs(&self, ty: &Type) -> Vec { match self { - AlternativeTrees::Few(trees) => trees.iter().cloned().collect(), - AlternativeTrees::Many(ty) => vec![TypeTree::Many(ty.clone())], + AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(), + AlternativeExprs::Many => vec![Expr::Many(ty.clone())], } } - pub fn extend_with_threshold( - &mut self, - threshold: usize, - ty: Type, - mut trees: impl Iterator, - ) { + /// Extend alternative expressions + /// + /// # Arguments + /// `threshold` - threshold value for many trees (more than that is many) + /// `exprs` - expressions iterator + fn extend_with_threshold(&mut self, threshold: usize, mut exprs: impl Iterator) { match self { - AlternativeTrees::Few(tts) => { - while let Some(it) = trees.next() { + AlternativeExprs::Few(tts) => { + while let Some(it) = exprs.next() { if tts.len() > threshold { - *self = AlternativeTrees::Many(ty); + *self = AlternativeExprs::Many; break; } tts.insert(it); } } - AlternativeTrees::Many(_) => (), + AlternativeExprs::Many => (), } } } @@ -76,8 +85,8 @@ impl AlternativeTrees { /// not produce any new results. #[derive(Default, Debug)] struct LookupTable { - /// All the `TypeTree`s in "value" produce the type of "key" - data: FxHashMap, + /// All the `Expr`s in "value" produce the type of "key" + data: FxHashMap, /// New types reached since last query by the `NewTypesKey` new_types: FxHashMap>, /// ScopeDefs that are not interesting any more @@ -94,40 +103,40 @@ struct LookupTable { impl LookupTable { /// Initialize lookup table - fn new() -> Self { - let mut res: Self = Default::default(); + fn new(many_threshold: usize) -> Self { + let mut res = Self { many_threshold, ..Default::default() }; res.new_types.insert(NewTypesKey::ImplMethod, Vec::new()); res.new_types.insert(NewTypesKey::StructProjection, Vec::new()); res } - /// Find all `TypeTree`s that unify with the `ty` - fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { + /// Find all `Expr`s that unify with the `ty` + fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { self.data .iter() .find(|(t, _)| t.could_unify_with_deeply(db, ty)) - .map(|(_, tts)| tts.trees()) + .map(|(t, tts)| tts.exprs(t)) } /// Same as find but automatically creates shared reference of types in the lookup /// /// For example if we have type `i32` in data and we query for `&i32` it map all the type - /// trees we have for `i32` with `TypeTree::Reference` and returns them. - fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { + /// trees we have for `i32` with `Expr::Reference` and returns them. + fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option> { self.data .iter() .find(|(t, _)| t.could_unify_with_deeply(db, ty)) - .map(|(_, tts)| tts.trees()) + .map(|(t, it)| it.exprs(t)) .or_else(|| { self.data .iter() .find(|(t, _)| { Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty) }) - .map(|(_, tts)| { - tts.trees() + .map(|(t, it)| { + it.exprs(t) .into_iter() - .map(|tt| TypeTree::Reference(Box::new(tt))) + .map(|expr| Expr::Reference(Box::new(expr))) .collect() }) }) @@ -138,14 +147,11 @@ impl LookupTable { /// Note that the types have to be the same, unification is not enough as unification is not /// transitive. For example Vec and FxHashSet both unify with Iterator, /// but they clearly do not unify themselves. - fn insert(&mut self, ty: Type, trees: impl Iterator) { + fn insert(&mut self, ty: Type, exprs: impl Iterator) { match self.data.get_mut(&ty) { - Some(it) => it.extend_with_threshold(self.many_threshold, ty, trees), + Some(it) => it.extend_with_threshold(self.many_threshold, exprs), None => { - self.data.insert( - ty.clone(), - AlternativeTrees::new(self.many_threshold, ty.clone(), trees), - ); + self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs)); for it in self.new_types.values_mut() { it.push(ty.clone()); } @@ -206,6 +212,7 @@ impl LookupTable { } /// Context for the `term_search` function +#[derive(Debug)] pub struct TermSearchCtx<'a, DB: HirDatabase> { /// Semantics for the program pub sema: &'a Semantics<'a, DB>, @@ -230,7 +237,7 @@ pub struct TermSearchConfig { impl Default for TermSearchConfig { fn default() -> Self { - Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 5 } + Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 } } } @@ -239,9 +246,7 @@ impl Default for TermSearchConfig { /// Search for terms (expressions) that unify with the `goal` type. /// /// # Arguments -/// * `sema` - Semantics for the program -/// * `scope` - Semantic scope, captures context for the term search -/// * `goal` - Target / expected output type +/// * `ctx` - Context for term search /// /// Internally this function uses Breadth First Search to find path to `goal` type. /// The general idea is following: @@ -258,7 +263,7 @@ impl Default for TermSearchConfig { /// Note that there are usually more ways we can get to the `goal` type but some are discarded to /// reduce the memory consumption. It is also unlikely anyone is willing ti browse through /// thousands of possible responses so we currently take first 10 from every tactic. -pub fn term_search(ctx: TermSearchCtx<'_, DB>) -> Vec { +pub fn term_search(ctx: &TermSearchCtx<'_, DB>) -> Vec { let module = ctx.scope.module(); let mut defs = FxHashSet::default(); defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module))); @@ -267,30 +272,21 @@ pub fn term_search(ctx: TermSearchCtx<'_, DB>) -> Vec defs.insert(def); }); - let mut lookup = LookupTable::new(); + let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold); // Try trivial tactic first, also populates lookup table - let mut solutions: Vec = tactics::trivial(&ctx, &defs, &mut lookup).collect(); + let mut solutions: Vec = tactics::trivial(ctx, &defs, &mut lookup).collect(); // Use well known types tactic before iterations as it does not depend on other tactics - solutions.extend(tactics::famous_types(&ctx, &defs, &mut lookup)); - - let mut solution_found = !solutions.is_empty(); + solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup)); for _ in 0..ctx.config.depth { lookup.new_round(); - solutions.extend(tactics::type_constructor(&ctx, &defs, &mut lookup)); - solutions.extend(tactics::free_function(&ctx, &defs, &mut lookup)); - solutions.extend(tactics::impl_method(&ctx, &defs, &mut lookup)); - solutions.extend(tactics::struct_projection(&ctx, &defs, &mut lookup)); - solutions.extend(tactics::impl_static_method(&ctx, &defs, &mut lookup)); - - // Break after 1 round after successful solution - if solution_found { - break; - } - - solution_found = !solutions.is_empty(); + solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup)); + solutions.extend(tactics::free_function(ctx, &defs, &mut lookup)); + solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup)); + solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup)); + solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup)); // Discard not interesting `ScopeDef`s for speedup for def in lookup.exhausted_scopedefs() { @@ -298,5 +294,5 @@ pub fn term_search(ctx: TermSearchCtx<'_, DB>) -> Vec } } - solutions.into_iter().unique().collect() + solutions.into_iter().filter(|it| !it.is_many()).unique().collect() } diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index e0b6c1291e42..a8282359cec7 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -19,7 +19,7 @@ use crate::{ Variant, }; -use crate::term_search::{TermSearchConfig, TypeTree}; +use crate::term_search::{Expr, TermSearchConfig}; use super::{LookupTable, NewTypesKey, TermSearchCtx}; @@ -41,13 +41,13 @@ pub(super) fn trivial<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; defs.iter().filter_map(|def| { - let tt = match def { - ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(TypeTree::Const(*it)), - ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(TypeTree::Static(*it)), - ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(TypeTree::ConstParam(*it)), + let expr = match def { + ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(Expr::Const(*it)), + ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(Expr::Static(*it)), + ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(Expr::ConstParam(*it)), ScopeDef::Local(it) => { if ctx.config.enable_borrowcheck { let borrowck = db.borrowck(it.parent).ok()?; @@ -67,22 +67,22 @@ pub(super) fn trivial<'a, DB: HirDatabase>( } } - Some(TypeTree::Local(*it)) + Some(Expr::Local(*it)) } _ => None, }?; lookup.mark_exhausted(*def); - let ty = tt.ty(db); - lookup.insert(ty.clone(), std::iter::once(tt.clone())); + let ty = expr.ty(db); + lookup.insert(ty.clone(), std::iter::once(expr.clone())); // Don't suggest local references as they are not valid for return - if matches!(tt, TypeTree::Local(_)) && ty.contains_reference(db) { + if matches!(expr, Expr::Local(_)) && ty.contains_reference(db) { return None; } - ty.could_unify_with_deeply(db, &ctx.goal).then(|| tt) + ty.could_unify_with_deeply(db, &ctx.goal).then(|| expr) }) } @@ -101,7 +101,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); fn variant_helper( @@ -111,14 +111,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( variant: Variant, goal: &Type, config: &TermSearchConfig, - ) -> Vec<(Type, Vec)> { - let generics = GenericDef::from(variant.parent_enum(db)); - - // Ignore unstable variants + ) -> Vec<(Type, Vec)> { + // Ignore unstable if variant.is_unstable(db) { return Vec::new(); } + let generics = GenericDef::from(variant.parent_enum(db)); + // Ignore enums with const generics if !generics.const_params(db).is_empty() { return Vec::new(); @@ -160,7 +160,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( }) .collect(); - let enum_ty = parent_enum.ty_with_generics(db, generics.iter().cloned()); + let enum_ty = parent_enum.ty_with_args(db, generics.iter().cloned()); // Allow types with generics only if they take us straight to goal for // performance reasons @@ -174,52 +174,42 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( } // Early exit if some param cannot be filled from lookup - let param_trees: Vec> = variant + let param_exprs: Vec> = variant .fields(db) .into_iter() - .map(|field| { - lookup.find(db, &field.ty_with_generics(db, generics.iter().cloned())) - }) + .map(|field| lookup.find(db, &field.ty_with_args(db, generics.iter().cloned()))) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product - let variant_trees: Vec = if param_trees.is_empty() { - vec![TypeTree::Variant { - variant, - generics: generics.clone(), - params: Vec::new(), - }] + let variant_exprs: Vec = if param_exprs.is_empty() { + vec![Expr::Variant { variant, generics: generics.clone(), params: Vec::new() }] } else { - param_trees + param_exprs .into_iter() .multi_cartesian_product() - .map(|params| TypeTree::Variant { - variant, - generics: generics.clone(), - params, - }) + .map(|params| Expr::Variant { variant, generics: generics.clone(), params }) .collect() }; - lookup.insert(enum_ty.clone(), variant_trees.iter().cloned()); + lookup.insert(enum_ty.clone(), variant_exprs.iter().cloned()); - Some((enum_ty, variant_trees)) + Some((enum_ty, variant_exprs)) }) .collect() } defs.iter() .filter_map(move |def| match def { ScopeDef::ModuleDef(ModuleDef::Variant(it)) => { - let variant_trees = + let variant_exprs = variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.goal, &ctx.config); - if variant_trees.is_empty() { + if variant_exprs.is_empty() { return None; } lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it))); - Some(variant_trees) + Some(variant_exprs) } ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => { - let trees: Vec<(Type, Vec)> = enum_ + let exprs: Vec<(Type, Vec)> = enum_ .variants(db) .into_iter() .flat_map(|it| { @@ -227,11 +217,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( }) .collect(); - if !trees.is_empty() { + if !exprs.is_empty() { lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(*enum_)))); } - Some(trees) + Some(exprs) } ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => { // Ignore unstable and not visible @@ -269,7 +259,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( .into_iter() .permutations(non_default_type_params_len); - let trees = generic_params + let exprs = generic_params .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -280,7 +270,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( None => g.next().expect("Missing type param"), }) .collect(); - let struct_ty = it.ty_with_generics(db, generics.iter().cloned()); + let struct_ty = it.ty_with_args(db, generics.iter().cloned()); // Allow types with generics only if they take us straight to goal for // performance reasons @@ -301,20 +291,20 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( } // Early exit if some param cannot be filled from lookup - let param_trees: Vec> = fileds + let param_exprs: Vec> = fileds .into_iter() .map(|field| lookup.find(db, &field.ty(db))) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product - let struct_trees: Vec = if param_trees.is_empty() { - vec![TypeTree::Struct { strukt: *it, generics, params: Vec::new() }] + let struct_exprs: Vec = if param_exprs.is_empty() { + vec![Expr::Struct { strukt: *it, generics, params: Vec::new() }] } else { - param_trees + param_exprs .into_iter() .multi_cartesian_product() - .map(|params| TypeTree::Struct { + .map(|params| Expr::Struct { strukt: *it, generics: generics.clone(), params, @@ -324,17 +314,17 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( lookup .mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it)))); - lookup.insert(struct_ty.clone(), struct_trees.iter().cloned()); + lookup.insert(struct_ty.clone(), struct_exprs.iter().cloned()); - Some((struct_ty, struct_trees)) + Some((struct_ty, struct_exprs)) }) .collect(); - Some(trees) + Some(exprs) } _ => None, }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) .flatten() } @@ -354,7 +344,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); defs.iter() @@ -394,7 +384,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( .into_iter() .permutations(non_default_type_params_len); - let trees: Vec<_> = generic_params + let exprs: Vec<_> = generic_params .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -406,7 +396,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( }) .collect(); - let ret_ty = it.ret_type_with_generics(db, generics.iter().cloned()); + let ret_ty = it.ret_type_with_args(db, generics.iter().cloned()); // Filter out private and unsafe functions if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) @@ -418,7 +408,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( } // Early exit if some param cannot be filled from lookup - let param_trees: Vec> = it + let param_exprs: Vec> = it .params_without_self_with_generics(db, generics.iter().cloned()) .into_iter() .map(|field| { @@ -432,13 +422,13 @@ pub(super) fn free_function<'a, DB: HirDatabase>( // 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() }] + let fn_exprs: Vec = if param_exprs.is_empty() { + vec![Expr::Function { func: *it, generics, params: Vec::new() }] } else { - param_trees + param_exprs .into_iter() .multi_cartesian_product() - .map(|params| TypeTree::Function { + .map(|params| Expr::Function { func: *it, generics: generics.clone(), @@ -448,16 +438,16 @@ pub(super) fn free_function<'a, DB: HirDatabase>( }; lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Function(*it))); - lookup.insert(ret_ty.clone(), fn_trees.iter().cloned()); - Some((ret_ty, fn_trees)) + lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned()); + Some((ret_ty, fn_exprs)) }) .collect(); - Some(trees) + Some(exprs) } _ => None, }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) .flatten() } @@ -479,7 +469,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); lookup @@ -546,7 +536,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( .into_iter() .permutations(non_default_type_params_len); - let trees: Vec<_> = generic_params + let exprs: Vec<_> = generic_params .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -559,7 +549,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( }) .collect(); - let ret_ty = it.ret_type_with_generics( + let ret_ty = it.ret_type_with_args( db, ty.type_arguments().chain(generics.iter().cloned()), ); @@ -578,17 +568,17 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( let self_ty = it .self_param(db) .expect("No self param") - .ty_with_generics(db, ty.type_arguments().chain(generics.iter().cloned())); + .ty_with_args(db, ty.type_arguments().chain(generics.iter().cloned())); // Ignore functions that have different self type if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) { return None; } - let target_type_trees = lookup.find(db, &ty).expect("Type not in lookup"); + let target_type_exprs = lookup.find(db, &ty).expect("Type not in lookup"); // Early exit if some param cannot be filled from lookup - let param_trees: Vec> = it + let param_exprs: Vec> = it .params_without_self_with_generics( db, ty.type_arguments().chain(generics.iter().cloned()), @@ -597,20 +587,29 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( .map(|field| lookup.find_autoref(db, &field.ty())) .collect::>()?; - let fn_trees: Vec = std::iter::once(target_type_trees) - .chain(param_trees.into_iter()) + let fn_exprs: Vec = std::iter::once(target_type_exprs) + .chain(param_exprs.into_iter()) .multi_cartesian_product() - .map(|params| TypeTree::Function { func: it, generics: Vec::new(), params }) + .map(|params| { + let mut params = params.into_iter(); + let target = Box::new(params.next().unwrap()); + Expr::Method { + func: it, + generics: generics.clone(), + target, + params: params.collect(), + } + }) .collect(); - lookup.insert(ret_ty.clone(), fn_trees.iter().cloned()); - Some((ret_ty, fn_trees)) + lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned()); + Some((ret_ty, fn_exprs)) }) .collect(); - Some(trees) + Some(exprs) }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) .flatten() } @@ -629,26 +628,26 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .new_types(NewTypesKey::StructProjection) .into_iter() - .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("TypeTree not in lookup"))) + .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup"))) .flat_map(move |(ty, targets)| { ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| { if !field.is_visible_from(db, module) { return None; } - let trees = targets + let exprs = targets .clone() .into_iter() - .map(move |target| TypeTree::Field { field, type_tree: Box::new(target) }); - Some((filed_ty, trees)) + .map(move |target| Expr::Field { field, expr: Box::new(target) }); + Some((filed_ty, exprs)) }) }) - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) .flatten() } @@ -669,20 +668,20 @@ pub(super) fn famous_types<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); [ - TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" }, - TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" }, - TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" }, + Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" }, + Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" }, + Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" }, ] .into_iter() - .map(|tt| { - lookup.insert(tt.ty(db), std::iter::once(tt.clone())); - tt + .map(|exprs| { + lookup.insert(exprs.ty(db), std::iter::once(exprs.clone())); + exprs }) - .filter(|tt| tt.ty(db).could_unify_with_deeply(db, &ctx.goal)) + .filter(|expr| expr.ty(db).could_unify_with_deeply(db, &ctx.goal)) } /// # Impl static method (without self type) tactic @@ -700,7 +699,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); lookup @@ -771,7 +770,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .into_iter() .permutations(non_default_type_params_len); - let trees: Vec<_> = generic_params + let exprs: Vec<_> = generic_params .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -784,7 +783,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( }) .collect(); - let ret_ty = it.ret_type_with_generics( + let ret_ty = it.ret_type_with_args( db, ty.type_arguments().chain(generics.iter().cloned()), ); @@ -801,7 +800,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( // } // Early exit if some param cannot be filled from lookup - let param_trees: Vec> = it + let param_exprs: Vec> = it .params_without_self_with_generics( db, ty.type_arguments().chain(generics.iter().cloned()), @@ -812,28 +811,27 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( // 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() }] + let fn_exprs: Vec = if param_exprs.is_empty() { + vec![Expr::Function { func: it, generics, params: Vec::new() }] } else { - param_trees + param_exprs .into_iter() .multi_cartesian_product() - .map(|params| TypeTree::Function { + .map(|params| Expr::Function { func: it, generics: generics.clone(), - params, }) .collect() }; - lookup.insert(ret_ty.clone(), fn_trees.iter().cloned()); - Some((ret_ty, fn_trees)) + lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned()); + Some((ret_ty, fn_exprs)) }) .collect(); - Some(trees) + Some(exprs) }) .flatten() - .filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) .flatten() } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 3451a65493bb..89a7bb974ae3 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -30,7 +30,7 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< goal: target_ty, config: Default::default(), }; - let paths = hir::term_search::term_search(term_search_ctx); + let paths = hir::term_search::term_search(&term_search_ctx); if paths.is_empty() { return None; diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index b696933c2727..87016a605739 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -40,8 +40,8 @@ use crate::{ literal::{render_struct_literal, render_variant_lit}, macro_::render_macro, pattern::{render_struct_pat, render_variant_pat}, - render_field, render_path_resolution, render_pattern_resolution, render_tuple_field, - render_type_tree, + render_expr, render_field, render_path_resolution, render_pattern_resolution, + render_tuple_field, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, RenderContext, @@ -158,12 +158,8 @@ impl Completions { item.add_to(self, ctx.db); } - pub(crate) fn add_expr( - &mut self, - ctx: &CompletionContext<'_>, - expr: &hir::term_search::TypeTree, - ) { - match render_type_tree(ctx, expr) { + pub(crate) fn add_expr(&mut self, ctx: &CompletionContext<'_>, expr: &hir::term_search::Expr) { + match render_expr(ctx, expr) { Some(item) => item.add_to(self, ctx.db), None => (), } @@ -699,7 +695,6 @@ pub(super) fn complete_name_ref( ctx: &CompletionContext<'_>, NameRefContext { nameref, kind }: &NameRefContext, ) { - expr::complete_expr(acc, ctx); match kind { NameRefKind::Path(path_ctx) => { flyimport::import_on_the_fly_path(acc, ctx, path_ctx); @@ -707,6 +702,7 @@ pub(super) fn complete_name_ref( match &path_ctx.kind { PathKind::Expr { expr_ctx } => { expr::complete_expr_path(acc, ctx, path_ctx, expr_ctx); + expr::complete_expr(acc, ctx); dot::complete_undotted_self(acc, ctx, path_ctx, expr_ctx); item_list::complete_item_list_in_expr(acc, ctx, path_ctx, expr_ctx); @@ -763,6 +759,7 @@ pub(super) fn complete_name_ref( flyimport::import_on_the_fly_dot(acc, ctx, dot_access); dot::complete_dot(acc, ctx, dot_access); postfix::complete_postfix(acc, ctx, dot_access); + expr::complete_expr(acc, ctx); } NameRefKind::Keyword(item) => { keyword::complete_for_and_where(acc, ctx, item); diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 891717285dac..9708f9c52669 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -329,11 +329,8 @@ pub(crate) fn complete_expr_path( } } -pub(crate) fn complete_expr( - acc: &mut Completions, - ctx: &CompletionContext<'_>, -) { - let _p = profile::span("complete_expr"); +pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) { + let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered(); if !ctx.qualifier_ctx.none() { return; } @@ -351,12 +348,34 @@ pub(crate) fn complete_expr( config: hir::term_search::TermSearchConfig { enable_borrowcheck: false, many_alternatives_threshold: 1, - depth: 2, + depth: 6, }, }; - let exprs = hir::term_search::term_search(term_search_ctx); + let exprs = hir::term_search::term_search(&term_search_ctx); for expr in exprs { - acc.add_expr(ctx, &expr); + // Expand method calls + match expr { + hir::term_search::Expr::Method { func, generics, target, params } + if target.is_many() => + { + let target_ty = target.ty(ctx.db); + let term_search_ctx = + hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx }; + let target_exprs = hir::term_search::term_search(&term_search_ctx); + + for expr in target_exprs { + let expanded_expr = hir::term_search::Expr::Method { + func, + generics: generics.clone(), + target: Box::new(expr), + params: params.clone(), + }; + + acc.add_expr(ctx, &expanded_expr) + } + } + _ => acc.add_expr(ctx, &expr), + } } } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 0a9ea1ac849f..6b102257ed35 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -17,7 +17,7 @@ use ide_db::{ imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; -use syntax::{ast, AstNode, SmolStr, SyntaxKind, TextRange}; +use syntax::{ast, format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange}; use text_edit::TextEdit; use crate::{ @@ -272,9 +272,9 @@ pub(crate) fn render_resolution_with_import_pat( Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution)) } -pub(crate) fn render_type_tree( +pub(crate) fn render_expr( ctx: &CompletionContext<'_>, - expr: &hir::term_search::TypeTree, + expr: &hir::term_search::Expr, ) -> Option { let mut i = 1; let mut snippet_formatter = |ty: &hir::Type| { @@ -292,31 +292,42 @@ pub(crate) fn render_type_tree( ty.as_adt() .and_then(|adt| adt.name(ctx.db).as_text()) .map(|s| stdx::to_lower_snake_case(s.as_str())) - .unwrap_or_else(|| String::from("_")) + .unwrap_or_else(|| String::from("...")) }; let label = expr.gen_source_code(&ctx.scope, &mut label_formatter); - let source_range = match &ctx.expected_name { - Some(name_or_ref) => name_or_ref.syntax().text_range(), - None => match ctx.original_token.parent() { - Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { - Some(path) => path.syntax().text_range(), - None => node.text_range(), - }, - None => ctx.source_range(), + let source_range = match ctx.original_token.parent() { + Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { + Some(path) => path.syntax().text_range(), + None => node.text_range(), }, + None => ctx.source_range(), }; - let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label); + let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone()); let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter)); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); + item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); item.set_relevance(crate::CompletionRelevance { - type_match: Some(crate::item::CompletionRelevanceTypeMatch::CouldUnify), + type_match: compute_type_match(ctx, &expr.ty(ctx.db)), ..Default::default() }); + for trait_ in expr.traits_used(ctx.db) { + let trait_item = hir::ItemInNs::from(hir::ModuleDef::from(trait_)); + let Some(path) = ctx.module.find_use_path( + ctx.db, + trait_item, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) else { + continue; + }; + + item.add_import(LocatedImport::new(path, trait_item, trait_item)); + } Some(item) } @@ -2243,6 +2254,8 @@ fn main() { &[CompletionItemKind::Snippet, CompletionItemKind::Method], expect![[r#" sn not [snippet] + sn true [type] + sn false [type] me not() (use ops::Not) [type_could_unify+requires_import] sn if [] sn while [] diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs index 5363e33b8553..67cf551fce84 100644 --- a/crates/ide-completion/src/tests/pattern.rs +++ b/crates/ide-completion/src/tests/pattern.rs @@ -316,15 +316,6 @@ fn func() { bn RecordV {…} RecordV { field$1 }$0 bn TupleV(…) TupleV($1)$0 bn UnitV UnitV$0 - sn () - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn func() - sn function() - sn true "#]], ); } @@ -567,12 +558,10 @@ fn foo() { } "#, expect![[r#" - bn A A$0 - bn B {…} B { r#type$1 }$0 - bn struct {…} r#struct { r#type$1 }$0 - bn type r#type$0 - sn Enum::A - sn Enum::r#type + bn A A$0 + bn B {…} B { r#type$1 }$0 + bn struct {…} r#struct { r#type$1 }$0 + bn type r#type$0 "#]], ); } @@ -597,7 +586,6 @@ fn f(t: Ty) { "#, expect![[r#" ct ABC const ABC: Self - sn t "#]], ); @@ -620,7 +608,6 @@ fn f(e: MyEnum) { expect![[r#" ct A pub const A: i32 ct B pub const B: i32 - sn e "#]], ); @@ -646,7 +633,6 @@ fn f(u: U) { expect![[r#" ct C pub const C: i32 ct D pub const D: i32 - sn u "#]], ); @@ -666,7 +652,6 @@ fn f(v: u32) { "#, expect![[r#" ct MIN pub const MIN: Self - sn v "#]], ); } @@ -778,7 +763,6 @@ fn f(x: EnumAlias) { expect![[r#" bn Tuple(…) Tuple($1)$0 bn Unit Unit$0 - sn x "#]], ); } diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index 8b383d499523..c7161f82ce74 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -70,27 +70,18 @@ fn fn_return_type() { fn x<'lt, T, const C: usize>() -> $0 "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait tp T - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 kw crate:: kw self:: - sn () - sn C - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn function() - sn true "#]], ); } @@ -109,27 +100,18 @@ fn foo() -> B$0 { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 it () kw crate:: kw self:: - sn () - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn foo() - sn function() - sn true "#]], ) } @@ -222,26 +204,18 @@ fn f2(x: u64) -> $0 { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 it u64 kw crate:: kw self:: - sn () - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn function() - sn true "#]], ); } @@ -345,27 +319,18 @@ fn foo<'lt, T, const C: usize>() { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait tp T - un Union Union - bt u32 u32 + un Union Union + bt u32 u32 kw crate:: kw self:: - sn () - sn C - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn function() - sn true "#]], ); check( @@ -376,23 +341,14 @@ fn foo<'lt, T, const C: usize>() { } "#, expect![[r#" - en Enum Enum - ma makro!(…) macro_rules! makro + en Enum Enum + ma makro!(…) macro_rules! makro md module - st Record Record - st Tuple Tuple - st Unit Unit + st Record Record + st Tuple Tuple + st Unit Unit tt Trait - un Union Union - sn () - sn C - sn CONST - sn Enum::UnitV - sn STATIC - sn Unit - sn false - sn function() - sn true + un Union Union "#]], ); } diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index fa4734b1a9c7..035bc75adbdf 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -46,7 +46,7 @@ fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option continue, // Byproduct of testing method + "0277" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882 + _ => (), } bar.println(err); bar.println(generated); From 88964c0b6a743f922b28b54db2ecdfe710a13148 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Mon, 22 Jan 2024 20:34:17 +0200 Subject: [PATCH 7/8] Optionally disable term search for autocompletion --- crates/hir/src/term_search/expr.rs | 125 ++++++++++++++---- crates/hir/src/term_search/tactics.rs | 2 +- .../ide-assists/src/handlers/term_search.rs | 7 +- crates/ide-completion/src/completions/expr.rs | 5 + crates/ide-completion/src/config.rs | 1 + crates/ide-completion/src/render.rs | 17 ++- crates/ide-completion/src/tests.rs | 1 + .../src/handlers/typed_hole.rs | 28 ++-- .../rust-analyzer/src/cli/analysis_stats.rs | 2 +- crates/rust-analyzer/src/config.rs | 3 + .../src/integrated_benchmarks.rs | 3 + docs/user/generated_config.adoc | 5 + editors/code/package.json | 5 + 13 files changed, 163 insertions(+), 41 deletions(-) diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index b43f10528db6..29590a0730f9 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -11,7 +11,12 @@ use crate::{ }; /// Helper function to get path to `ModuleDef` -fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option { +fn mod_item_path( + sema_scope: &SemanticsScope<'_>, + def: &ModuleDef, + prefer_no_std: bool, + prefer_prelude: bool, +) -> Option { let db = sema_scope.db; // Account for locals shadowing items from module let name_hit_count = def.name(db).map(|def_name| { @@ -26,25 +31,43 @@ fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option m.find_use_path(db.upcast(), *def, false, true), - Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true), + Some(0..=1) | None => m.find_use_path(db.upcast(), *def, prefer_no_std, prefer_prelude), + Some(_) => m.find_use_path_prefixed( + db.upcast(), + *def, + PrefixKind::ByCrate, + prefer_no_std, + prefer_prelude, + ), } } /// 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); +fn mod_item_path_str( + sema_scope: &SemanticsScope<'_>, + def: &ModuleDef, + prefer_no_std: bool, + prefer_prelude: bool, +) -> String { + let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude); 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 { +fn type_path( + sema_scope: &SemanticsScope<'_>, + ty: &Type, + prefer_no_std: bool, + prefer_prelude: bool, +) -> 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(); + let mut path = + mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude) + .unwrap(); path.pop_segment(); let path = path.display(db.upcast()).to_string(); match path.is_empty() { @@ -125,8 +148,11 @@ impl Expr { &self, sema_scope: &SemanticsScope<'_>, many_formatter: &mut dyn FnMut(&Type) -> String, + prefer_no_std: bool, + prefer_prelude: bool, ) -> String { let db = sema_scope.db; + let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude); match self { Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), @@ -134,8 +160,12 @@ impl Expr { Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), Expr::FamousType { value, .. } => return value.to_string(), Expr::Function { func, params, .. } => { - let args = - params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); + let args = params + .iter() + .map(|f| { + f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) + }) + .join(", "); match func.as_assoc_item(db).map(|it| it.container(db)) { Some(container) => { @@ -146,10 +176,14 @@ impl Expr { 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())) - { + match self_ty.as_adt().and_then(|adt| { + mod_item_path( + sema_scope, + &adt.into(), + prefer_no_std, + prefer_prelude, + ) + }) { Some(path) => path.display(sema_scope.db.upcast()).to_string(), None => self_ty.display(db).to_string(), } @@ -171,9 +205,18 @@ impl Expr { let func_name = func.name(db).display(db.upcast()).to_string(); let self_param = func.self_param(db).unwrap(); - let target = target.gen_source_code(sema_scope, many_formatter); - let args = - params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); + let target = target.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ); + let args = params + .iter() + .map(|f| { + f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) + }) + .join(", "); match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) { Some(trait_) => { @@ -196,8 +239,10 @@ impl Expr { let generics_str = match generics.is_empty() { true => String::new(), false => { - let generics = - generics.iter().map(|it| type_path(sema_scope, it)).join(", "); + let generics = generics + .iter() + .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .join(", "); format!("::<{generics}>") } }; @@ -205,7 +250,14 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter)) + .map(|f| { + f.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ) + }) .join(", "); format!("{generics_str}({args})") } @@ -218,7 +270,12 @@ impl Expr { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope, many_formatter) + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude + ) ) }) .join(", "); @@ -236,7 +293,14 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|a| a.gen_source_code(sema_scope, many_formatter)) + .map(|a| { + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ) + }) .join(", "); format!("({args})") } @@ -249,7 +313,12 @@ impl Expr { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope, many_formatter) + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude + ) ) }) .join(", "); @@ -258,8 +327,10 @@ impl Expr { StructKind::Unit => match generics.is_empty() { true => String::new(), false => { - let generics = - generics.iter().map(|it| type_path(sema_scope, it)).join(", "); + let generics = generics + .iter() + .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .join(", "); format!("::<{generics}>") } }, @@ -273,7 +344,8 @@ impl Expr { return many_formatter(&expr.ty(db)); } - let strukt = expr.gen_source_code(sema_scope, many_formatter); + let strukt = + expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); let field = field.name(db).display(db.upcast()).to_string(); format!("{strukt}.{field}") } @@ -282,7 +354,8 @@ impl Expr { return many_formatter(&expr.ty(db)); } - let inner = expr.gen_source_code(sema_scope, many_formatter); + let inner = + expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); format!("&{inner}") } Expr::Many(ty) => many_formatter(ty), diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index a8282359cec7..012d815394e2 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -760,7 +760,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .count(); // Ignore bigger number of generics for now as they kill the performance - if non_default_type_params_len > 0 { + if non_default_type_params_len > 1 { return None; } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 89a7bb974ae3..6b054790e9c7 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -38,7 +38,12 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let mut formatter = |_: &hir::Type| String::from("todo!()"); for path in paths.iter().unique() { - let code = path.gen_source_code(&scope, &mut formatter); + let code = path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); acc.add_group( &GroupLabel(String::from("Term search")), AssistId("term_search", AssistKind::Generate), diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 9708f9c52669..c37b325ee76a 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -331,6 +331,11 @@ pub(crate) fn complete_expr_path( pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) { let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered(); + + if !ctx.config.enable_term_search { + return; + } + if !ctx.qualifier_ctx.none() { return; } diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index ed5ddde8fbfe..04563fb0f469 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -14,6 +14,7 @@ pub struct CompletionConfig { pub enable_imports_on_the_fly: bool, pub enable_self_on_the_fly: bool, pub enable_private_editable: bool, + pub enable_term_search: bool, pub full_function_signatures: bool, pub callable: Option, pub snippet_cap: Option, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 6b102257ed35..1bac2e30c19c 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -295,7 +295,12 @@ pub(crate) fn render_expr( .unwrap_or_else(|| String::from("...")) }; - let label = expr.gen_source_code(&ctx.scope, &mut label_formatter); + let label = expr.gen_source_code( + &ctx.scope, + &mut label_formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); let source_range = match ctx.original_token.parent() { Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { @@ -307,7 +312,15 @@ pub(crate) fn render_expr( let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone()); - let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter)); + let snippet = format!( + "{}$0", + expr.gen_source_code( + &ctx.scope, + &mut snippet_formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude + ) + ); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 154b69875aea..1f032c7df480 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -65,6 +65,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: false, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 035bc75adbdf..7aeadce8e269 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -1,13 +1,12 @@ use hir::{ db::ExpandDatabase, term_search::{term_search, TermSearchCtx}, - ClosureStyle, HirDisplay, Semantics, + ClosureStyle, HirDisplay, }; use ide_db::{ assists::{Assist, AssistId, AssistKind, GroupLabel}, label::Label, source_change::SourceChange, - RootDatabase, }; use itertools::Itertools; use text_edit::TextEdit; @@ -29,7 +28,7 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di "invalid `_` expression, expected type `{}`", d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), ), - fixes(&ctx.sema, d), + fixes(ctx, d), ) }; @@ -37,21 +36,30 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di .with_fixes(fixes) } -fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option> { - let db = sema.db; +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option> { + let db = ctx.sema.db; let root = db.parse_or_expand(d.expr.file_id); let (original_range, _) = d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?; - let scope = sema.scope(d.expr.value.to_node(&root).syntax())?; + let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?; - let ctx = - TermSearchCtx { sema, scope: &scope, goal: d.expected.clone(), config: Default::default() }; - let paths = term_search(&ctx); + let term_search_ctx = TermSearchCtx { + sema: &ctx.sema, + scope: &scope, + goal: d.expected.clone(), + config: Default::default(), + }; + let paths = term_search(&term_search_ctx); let mut assists = vec![]; let mut formatter = |_: &hir::Type| String::from("_"); for path in paths.into_iter().unique() { - let code = path.gen_source_code(&scope, &mut formatter); + let code = path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); assists.push(Assist { id: AssistId("typed-hole", AssistKind::QuickFix), diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 9d1ef728a43e..efad2ff6d1db 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -432,7 +432,7 @@ impl flags::AnalysisStats { let mut formatter = |_: &hir::Type| todo.clone(); let mut syntax_hit_found = false; for term in found_terms { - let generated = term.gen_source_code(&scope, &mut formatter); + let generated = term.gen_source_code(&scope, &mut formatter, false, true); syntax_hit_found |= trim(&original_text) == trim(&generated); // Validate if type-checks diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 7bdd9ec866a5..298d92468f67 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -286,6 +286,8 @@ config_data! { "scope": "expr" } }"#, + /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. + completion_term_search_enable: bool = "true", /// List of rust-analyzer diagnostics to disable. diagnostics_disabled: FxHashSet = "[]", @@ -1535,6 +1537,7 @@ impl Config { && completion_item_edit_resolve(&self.caps), enable_self_on_the_fly: self.data.completion_autoself_enable, enable_private_editable: self.data.completion_privateEditable_enable, + enable_term_search: self.data.completion_term_search_enable, full_function_signatures: self.data.completion_fullFunctionSignatures_enable, callable: match self.data.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index acc02d6447c6..f0eee77aff59 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -132,6 +132,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), @@ -175,6 +176,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), @@ -216,6 +218,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a86ef709411c..9d54999fa38b 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -343,6 +343,11 @@ Default: ---- Custom completion snippets. +-- +[[rust-analyzer.completion.term.search.enable]]rust-analyzer.completion.term.search.enable (default: `true`):: ++ +-- +Whether to enable term search based snippets like `Some(foo.bar().baz())`. -- [[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`):: + diff --git a/editors/code/package.json b/editors/code/package.json index b474471e5a4b..3352007253e9 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -902,6 +902,11 @@ }, "type": "object" }, + "rust-analyzer.completion.term.search.enable": { + "markdownDescription": "Whether to enable term search based snippets like `Some(foo.bar().baz())`.", + "default": true, + "type": "boolean" + }, "rust-analyzer.diagnostics.disabled": { "markdownDescription": "List of rust-analyzer diagnostics to disable.", "default": [], From 125791386d2bc62a401abb298392de91f86d9a74 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Thu, 1 Feb 2024 11:02:19 +0200 Subject: [PATCH 8/8] Cleanup term search related changes --- crates/hir-def/src/attr.rs | 3 + crates/hir-ty/src/infer/unify.rs | 108 ++++------ crates/hir-ty/src/mir/borrowck.rs | 81 +++----- crates/hir/src/lib.rs | 160 +++++++-------- .../{term_search/mod.rs => term_search.rs} | 6 +- crates/hir/src/term_search/expr.rs | 142 +++++++------ crates/hir/src/term_search/tactics.rs | 162 ++++++++------- .../ide-assists/src/handlers/term_search.rs | 187 ++++++++++-------- crates/ide-completion/src/completions.rs | 6 +- crates/ide-completion/src/completions/expr.rs | 2 +- crates/ide-completion/src/item.rs | 2 + crates/ide-completion/src/render.rs | 42 +++- crates/ide-completion/src/tests/expression.rs | 18 +- crates/ide-completion/src/tests/record.rs | 4 +- crates/ide-completion/src/tests/special.rs | 48 +++-- crates/ide-db/src/famous_defs.rs | 8 + .../src/handlers/type_mismatch.rs | 3 +- .../src/handlers/typed_hole.rs | 47 +++-- crates/ide/src/hover/tests.rs | 8 +- .../rust-analyzer/src/cli/analysis_stats.rs | 20 +- crates/rust-analyzer/src/cli/flags.rs | 5 +- crates/rust-analyzer/src/config.rs | 4 +- crates/rust-analyzer/src/lsp/to_proto.rs | 1 + crates/test-utils/src/minicore.rs | 33 ++++ docs/user/generated_config.adoc | 2 +- editors/code/package.json | 4 +- 26 files changed, 590 insertions(+), 516 deletions(-) rename crates/hir/src/{term_search/mod.rs => term_search.rs} (98%) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 247ec096cbe2..519706c65f29 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -377,6 +377,7 @@ impl AttrsWithOwner { AttrDefId::GenericParamId(it) => match it { GenericParamId::ConstParamId(it) => { let src = it.parent().child_source(db); + // FIXME: We should be never getting `None` here. match src.value.get(it.local_id()) { Some(val) => RawAttrs::from_attrs_owner( db.upcast(), @@ -388,6 +389,7 @@ impl AttrsWithOwner { } GenericParamId::TypeParamId(it) => { let src = it.parent().child_source(db); + // FIXME: We should be never getting `None` here. match src.value.get(it.local_id()) { Some(val) => RawAttrs::from_attrs_owner( db.upcast(), @@ -399,6 +401,7 @@ impl AttrsWithOwner { } GenericParamId::LifetimeParamId(it) => { let src = it.parent.child_source(db); + // FIXME: We should be never getting `None` here. match src.value.get(it.local_id) { Some(val) => RawAttrs::from_attrs_owner( db.upcast(), diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 25dde2d15934..709760b64fd3 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -74,6 +74,12 @@ impl> Canonicalized { } } +/// Check if types unify. +/// +/// Note that we consider placeholder types to unify with everything. +/// This means that there may be some unresolved goals that actually set bounds for the placeholder +/// type for the types to unify. For example `Option` and `Option` unify although there is +/// unresolved goal `T = U`. pub fn could_unify( db: &dyn HirDatabase, env: Arc, @@ -82,30 +88,25 @@ pub fn could_unify( unify(db, env, tys).is_some() } +/// Check if types unify eagerly making sure there are no unresolved goals. +/// +/// This means that placeholder types are not considered to unify if there are any bounds set on +/// them. For example `Option` and `Option` do not unify as we cannot show that `T = U` pub fn could_unify_deeply( db: &dyn HirDatabase, env: Arc, tys: &Canonical<(Ty, Ty)>, ) -> bool { let mut table = InferenceTable::new(db, env); - let vars = Substitution::from_iter( - Interner, - tys.binders.iter(Interner).map(|it| match &it.kind { - chalk_ir::VariableKind::Ty(_) => { - GenericArgData::Ty(table.new_type_var()).intern(Interner) - } - chalk_ir::VariableKind::Lifetime => { - GenericArgData::Ty(table.new_type_var()).intern(Interner) - } // FIXME: maybe wrong? - chalk_ir::VariableKind::Const(ty) => { - GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner) - } - }), - ); + let vars = make_substitutions(tys, &mut table); let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); let ty1_with_vars = table.normalize_associated_types_in(ty1_with_vars); let ty2_with_vars = table.normalize_associated_types_in(ty2_with_vars); + table.resolve_obligations_as_possible(); + table.propagate_diverging_flag(); + let ty1_with_vars = table.resolve_completely(ty1_with_vars); + let ty2_with_vars = table.resolve_completely(ty2_with_vars); table.unify_deeply(&ty1_with_vars, &ty2_with_vars) } @@ -115,15 +116,7 @@ pub(crate) fn unify( tys: &Canonical<(Ty, Ty)>, ) -> Option { let mut table = InferenceTable::new(db, env); - let vars = Substitution::from_iter( - Interner, - tys.binders.iter(Interner).map(|it| match &it.kind { - chalk_ir::VariableKind::Ty(_) => table.new_type_var().cast(Interner), - // FIXME: maybe wrong? - chalk_ir::VariableKind::Lifetime => table.new_type_var().cast(Interner), - chalk_ir::VariableKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner), - }), - ); + let vars = make_substitutions(tys, &mut table); let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); if !table.unify(&ty1_with_vars, &ty2_with_vars) { @@ -152,6 +145,21 @@ pub(crate) fn unify( )) } +fn make_substitutions( + tys: &chalk_ir::Canonical<(chalk_ir::Ty, chalk_ir::Ty)>, + table: &mut InferenceTable<'_>, +) -> chalk_ir::Substitution { + Substitution::from_iter( + Interner, + tys.binders.iter(Interner).map(|it| match &it.kind { + chalk_ir::VariableKind::Ty(_) => table.new_type_var().cast(Interner), + // FIXME: maybe wrong? + chalk_ir::VariableKind::Lifetime => table.new_type_var().cast(Interner), + chalk_ir::VariableKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner), + }), + ) +} + bitflags::bitflags! { #[derive(Default, Clone, Copy)] pub(crate) struct TypeVariableFlags: u8 { @@ -458,7 +466,7 @@ impl<'a> InferenceTable<'a> { true } - /// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that. + /// Unify two relatable values (e.g. `Ty`) and check whether trait goals which arise from that could be fulfilled pub(crate) fn unify_deeply>(&mut self, ty1: &T, ty2: &T) -> bool { let result = match self.try_unify(ty1, ty2) { Ok(r) => r, @@ -466,7 +474,7 @@ impl<'a> InferenceTable<'a> { }; result.goals.iter().all(|goal| { let canonicalized = self.canonicalize(goal.clone()); - self.try_fulfill_obligation(&canonicalized) + self.try_resolve_obligation(&canonicalized).is_some() }) } @@ -540,7 +548,8 @@ impl<'a> InferenceTable<'a> { fn register_obligation_in_env(&mut self, goal: InEnvironment) { let canonicalized = self.canonicalize(goal); - if !self.try_resolve_obligation(&canonicalized) { + let solution = self.try_resolve_obligation(&canonicalized); + if matches!(solution, Some(Solution::Ambig(_))) { self.pending_obligations.push(canonicalized); } } @@ -666,70 +675,35 @@ impl<'a> InferenceTable<'a> { fn try_resolve_obligation( &mut self, canonicalized: &Canonicalized>, - ) -> bool { + ) -> Option> { let solution = self.db.trait_solve( self.trait_env.krate, self.trait_env.block, canonicalized.value.clone(), ); - match solution { + match &solution { Some(Solution::Unique(canonical_subst)) => { canonicalized.apply_solution( self, Canonical { - binders: canonical_subst.binders, + binders: canonical_subst.binders.clone(), // FIXME: handle constraints - value: canonical_subst.value.subst, + value: canonical_subst.value.subst.clone(), }, ); - true } Some(Solution::Ambig(Guidance::Definite(substs))) => { - canonicalized.apply_solution(self, substs); - false + canonicalized.apply_solution(self, substs.clone()); } Some(_) => { // FIXME use this when trying to resolve everything at the end - false } None => { // FIXME obligation cannot be fulfilled => diagnostic - true - } - } - } - - fn try_fulfill_obligation( - &mut self, - canonicalized: &Canonicalized>, - ) -> bool { - let solution = self.db.trait_solve( - self.trait_env.krate, - self.trait_env.block, - canonicalized.value.clone(), - ); - - // FIXME: Does just returning `solution.is_some()` work? - match solution { - Some(Solution::Unique(canonical_subst)) => { - canonicalized.apply_solution( - self, - Canonical { - binders: canonical_subst.binders, - // FIXME: handle constraints - value: canonical_subst.value.subst, - }, - ); - true - } - Some(Solution::Ambig(Guidance::Definite(substs))) => { - canonicalized.apply_solution(self, substs); - true } - Some(_) => true, - None => false, } + solution } pub(crate) fn callable_sig( diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs index 8d3200098127..63fa87ad6628 100644 --- a/crates/hir-ty/src/mir/borrowck.rs +++ b/crates/hir-ty/src/mir/borrowck.rs @@ -15,7 +15,7 @@ use crate::{ db::{HirDatabase, InternedClosure}, mir::Operand, utils::ClosureSubst, - ClosureId, Interner, Ty, TyExt, TypeFlags, + ClosureId, Interner, Substitution, Ty, TyExt, TypeFlags, }; use super::{ @@ -105,6 +105,18 @@ pub fn borrowck_query( Ok(res.into()) } +fn make_fetch_closure_field( + db: &dyn HirDatabase, +) -> impl FnOnce(ClosureId, &Substitution, usize) -> Ty + '_ { + |c: ClosureId, subst: &Substitution, f: usize| { + let InternedClosure(def, _) = db.lookup_intern_closure(c.into()); + let infer = db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures.get(f).expect("broken closure field").ty.clone().substitute(Interner, parent_subst) + } +} + fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec { let mut result = vec![]; let mut for_operand = |op: &Operand, span: MirSpan| match op { @@ -118,18 +130,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec ty = proj.projected_ty( ty, db, - |c, subst, f| { - let InternedClosure(def, _) = db.lookup_intern_closure(c.into()); - let infer = db.infer(def); - let (captures, _) = infer.closure_info(&c); - let parent_subst = ClosureSubst(subst).parent_subst(); - captures - .get(f) - .expect("broken closure field") - .ty - .clone() - .substitute(Interner, parent_subst) - }, + make_fetch_closure_field(db), body.owner.module(db.upcast()).krate(), ); } @@ -216,18 +217,7 @@ fn partially_moved(db: &dyn HirDatabase, body: &MirBody) -> Vec ty = proj.projected_ty( ty, db, - |c, subst, f| { - let (def, _) = db.lookup_intern_closure(c.into()); - let infer = db.infer(def); - let (captures, _) = infer.closure_info(&c); - let parent_subst = ClosureSubst(subst).parent_subst(); - captures - .get(f) - .expect("broken closure field") - .ty - .clone() - .substitute(Interner, parent_subst) - }, + make_fetch_closure_field(db), body.owner.module(db.upcast()).krate(), ); } @@ -309,23 +299,17 @@ fn borrow_regions(db: &dyn HirDatabase, body: &MirBody) -> Vec { for (_, block) in body.basic_blocks.iter() { db.unwind_if_cancelled(); for statement in &block.statements { - match &statement.kind { - StatementKind::Assign(_, r) => match r { - Rvalue::Ref(kind, p) => { - borrows - .entry(p.local) - .and_modify(|it: &mut BorrowRegion| { - it.places.push(statement.span); - }) - .or_insert_with(|| BorrowRegion { - local: p.local, - kind: *kind, - places: vec![statement.span], - }); - } - _ => (), - }, - _ => (), + if let StatementKind::Assign(_, Rvalue::Ref(kind, p)) = &statement.kind { + borrows + .entry(p.local) + .and_modify(|it: &mut BorrowRegion| { + it.places.push(statement.span); + }) + .or_insert_with(|| BorrowRegion { + local: p.local, + kind: *kind, + places: vec![statement.span], + }); } } match &block.terminator { @@ -379,18 +363,7 @@ fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> Projectio ty = proj.projected_ty( ty, db, - |c, subst, f| { - let InternedClosure(def, _) = db.lookup_intern_closure(c.into()); - let infer = db.infer(def); - let (captures, _) = infer.closure_info(&c); - let parent_subst = ClosureSubst(subst).parent_subst(); - captures - .get(f) - .expect("broken closure field") - .ty - .clone() - .substitute(Interner, parent_subst) - }, + make_fetch_closure_field(db), body.owner.module(db.upcast()).krate(), ); } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 283461f2199f..08f7bb14caa3 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1085,6 +1085,7 @@ impl Field { Type::new(db, var_id, ty) } + // FIXME: Find better API to also handle const generics pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { let var_id = self.parent.into(); let def_id: AdtId = match self.parent { @@ -1094,12 +1095,11 @@ impl Field { }; let mut generics = generics.map(|it| it.ty.clone()); let substs = TyBuilder::subst_for_def(db, def_id, None) - .fill(|x| { - let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); - match x { - ParamKind::Type => ty.cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + .fill(|x| match x { + ParamKind::Type => { + generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner) } + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), }) .build(); let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs); @@ -1159,21 +1159,6 @@ impl Struct { Type::from_def(db, self.id) } - pub fn ty_with_args(self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { - let mut generics = generics.map(|it| it.ty.clone()); - let substs = TyBuilder::subst_for_def(db, self.id, None) - .fill(|x| { - let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); - match x { - ParamKind::Type => ty.cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), - } - }) - .build(); - let ty = db.ty(self.id.into()).substitute(Interner, &substs); - Type::new(db, self.id, ty) - } - pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type { Type::from_value_def(db, self.id) } @@ -1273,22 +1258,6 @@ impl Enum { Type::from_def(db, self.id) } - pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { - let mut generics = generics.map(|it| it.ty.clone()); - let substs = TyBuilder::subst_for_def(db, self.id, None) - .fill(|x| { - let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); - match x { - ParamKind::Type => ty.cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), - } - }) - .build(); - - let ty = db.ty(self.id.into()).substitute(Interner, &substs); - Type::new(db, self.id, ty) - } - /// The type of the enum variant bodies. pub fn variant_body_ty(self, db: &dyn HirDatabase) -> Type { Type::new_for_crate( @@ -1463,9 +1432,9 @@ impl Adt { /// Turns this ADT into a type with the given type parameters. This isn't /// the greatest API, FIXME find a better one. - pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type { + pub fn ty_with_args(self, db: &dyn HirDatabase, args: impl Iterator) -> Type { let id = AdtId::from(self); - let mut it = args.iter().map(|t| t.ty.clone()); + let mut it = args.map(|t| t.ty.clone()); let ty = TyBuilder::def_ty(db, id.into(), None) .fill(|x| { let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); @@ -1858,6 +1827,7 @@ impl Function { Type::new_with_resolver_inner(db, &resolver, ty) } + // FIXME: Find better API to also handle const generics pub fn ret_type_with_args( self, db: &dyn HirDatabase, @@ -1870,12 +1840,11 @@ impl Function { ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None, }; let mut generics = generics.map(|it| it.ty.clone()); - let mut filler = |x: &_| { - let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); - match x { - ParamKind::Type => ty.cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + let mut filler = |x: &_| match x { + ParamKind::Type => { + generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner) } + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), }; let parent_substs = @@ -1953,10 +1922,11 @@ impl Function { .collect() } - pub fn params_without_self_with_generics( + // FIXME: Find better API to also handle const generics + pub fn params_without_self_with_args( self, db: &dyn HirDatabase, - mut generics: impl Iterator, + generics: impl Iterator, ) -> Vec { let environment = db.trait_environment(self.id.into()); let parent_id: Option = match self.id.lookup(db.upcast()).container { @@ -1964,20 +1934,23 @@ impl Function { ItemContainerId::TraitId(it) => Some(it.into()), ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None, }; + let mut generics = generics.map(|it| it.ty.clone()); let parent_substs = parent_id.map(|id| { TyBuilder::subst_for_def(db, id, None) - .fill(|_| { - GenericArg::new( - Interner, - GenericArgData::Ty(generics.next().unwrap().ty.clone()), - ) + .fill(|x| match x { + ParamKind::Type => generics + .next() + .unwrap_or_else(|| TyKind::Error.intern(Interner)) + .cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), }) .build() }); let substs = TyBuilder::subst_for_def(db, self.id, parent_substs) .fill(|_| { - GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone())) + let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + GenericArg::new(Interner, GenericArgData::Ty(ty)) }) .build(); let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs); @@ -2197,6 +2170,7 @@ impl SelfParam { Type { env: environment, ty } } + // FIXME: Find better API to also handle const generics pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator) -> Type { let parent_id: GenericDefId = match self.func.lookup(db.upcast()).container { ItemContainerId::ImplId(it) => it.into(), @@ -2207,12 +2181,11 @@ impl SelfParam { }; let mut generics = generics.map(|it| it.ty.clone()); - let mut filler = |x: &_| { - let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); - match x { - ParamKind::Type => ty.cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + let mut filler = |x: &_| match x { + ParamKind::Type => { + generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner) } + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), }; let parent_substs = TyBuilder::subst_for_def(db, parent_id, None).fill(&mut filler).build(); @@ -2936,40 +2909,6 @@ impl GenericDef { }) .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. @@ -3451,6 +3390,26 @@ impl TypeOrConstParam { Either::Right(it) => it.ty(db), } } + + pub fn as_type_param(self, db: &dyn HirDatabase) -> Option { + let params = db.generic_params(self.id.parent); + match ¶ms.type_or_consts[self.id.local_id] { + hir_def::generics::TypeOrConstParamData::TypeParamData(_) => { + Some(TypeParam { id: TypeParamId::from_unchecked(self.id) }) + } + hir_def::generics::TypeOrConstParamData::ConstParamData(_) => None, + } + } + + pub fn as_const_param(self, db: &dyn HirDatabase) -> Option { + let params = db.generic_params(self.id.parent); + match ¶ms.type_or_consts[self.id.local_id] { + hir_def::generics::TypeOrConstParamData::TypeParamData(_) => None, + hir_def::generics::TypeOrConstParamData::ConstParamData(_) => { + Some(ConstParam { id: ConstParamId::from_unchecked(self.id) }) + } + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -3496,7 +3455,11 @@ impl Impl { ) }); - for Crate { id } in Crate::all(db) { + for id in def_crates + .iter() + .flat_map(|&id| Crate { id }.transitive_reverse_dependencies(db)) + .map(|Crate { id }| id) + { all.extend( db.trait_impls_in_crate(id) .for_self_ty_without_blanket_impls(fp) @@ -3976,14 +3939,16 @@ impl Type { ) } + // FIXME: Find better API that also handles const generics pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { let mut it = args.iter().map(|t| t.ty.clone()); let trait_ref = TyBuilder::trait_ref(db, trait_.id) .push(self.ty.clone()) .fill(|x| { - let r = it.next().unwrap(); match x { - ParamKind::Type => r.cast(Interner), + ParamKind::Type => { + it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner) + } ParamKind::Const(ty) => { // FIXME: this code is not covered in tests. unknown_const_as_generic(ty.clone()) @@ -4617,12 +4582,19 @@ impl Type { walk_type(db, self, &mut cb); } - + /// Check if type unifies with another type. + /// + /// Note that we consider placeholder types to unify with everything. + /// For example `Option` and `Option` unify although there is unresolved goal `T = U`. pub fn could_unify_with(&self, db: &dyn HirDatabase, other: &Type) -> bool { let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone())); hir_ty::could_unify(db, self.env.clone(), &tys) } + /// Check if type unifies with another type eagerly making sure there are no unresolved goals. + /// + /// This means that placeholder types are not considered to unify if there are any bounds set on + /// them. For example `Option` and `Option` do not unify as we cannot show that `T = U` pub fn could_unify_with_deeply(&self, db: &dyn HirDatabase, other: &Type) -> bool { let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone())); hir_ty::could_unify_deeply(db, self.env.clone(), &tys) diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search.rs similarity index 98% rename from crates/hir/src/term_search/mod.rs rename to crates/hir/src/term_search.rs index f0c3fdb4d094..72762007dc98 100644 --- a/crates/hir/src/term_search/mod.rs +++ b/crates/hir/src/term_search.rs @@ -57,10 +57,10 @@ impl AlternativeExprs { /// # Arguments /// `threshold` - threshold value for many trees (more than that is many) /// `exprs` - expressions iterator - fn extend_with_threshold(&mut self, threshold: usize, mut exprs: impl Iterator) { + fn extend_with_threshold(&mut self, threshold: usize, exprs: impl Iterator) { match self { AlternativeExprs::Few(tts) => { - while let Some(it) = exprs.next() { + for it in exprs { if tts.len() > threshold { *self = AlternativeExprs::Many; break; @@ -131,7 +131,7 @@ impl LookupTable { self.data .iter() .find(|(t, _)| { - Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty) + Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, ty) }) .map(|(t, it)| { it.exprs(t) diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index 29590a0730f9..254fbe7e2b53 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -2,7 +2,10 @@ use hir_def::find_path::PrefixKind; use hir_expand::mod_path::ModPath; -use hir_ty::{db::HirDatabase, display::HirDisplay}; +use hir_ty::{ + db::HirDatabase, + display::{DisplaySourceCodeError, HirDisplay}, +}; use itertools::Itertools; use crate::{ @@ -48,9 +51,10 @@ fn mod_item_path_str( def: &ModuleDef, prefer_no_std: bool, prefer_prelude: bool, -) -> String { +) -> Result { let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude); - path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap() + path.map(|it| it.display(sema_scope.db.upcast()).to_string()) + .ok_or(DisplaySourceCodeError::PathNotFound) } /// Helper function to get path to `Type` @@ -59,30 +63,34 @@ fn type_path( ty: &Type, prefer_no_std: bool, prefer_prelude: bool, -) -> String { +) -> Result { let db = sema_scope.db; + let m = sema_scope.module(); + match ty.as_adt() { Some(adt) => { - let ty_name = ty.display(db).to_string(); + let ty_name = ty.display_source_code(db, m.id, true)?; let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude) .unwrap(); path.pop_segment(); let path = path.display(db.upcast()).to_string(); - match path.is_empty() { + let res = match path.is_empty() { true => ty_name, false => format!("{path}::{ty_name}"), - } + }; + Ok(res) } - None => ty.display(db).to_string(), + None => ty.display_source_code(db, m.id, true), } } /// 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) + def.type_or_const_params(db) .into_iter() + .filter_map(|it| it.as_type_param(db)) .zip(generics) .filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg)) .map(|(_, arg)| arg.clone()) @@ -150,28 +158,30 @@ impl Expr { many_formatter: &mut dyn FnMut(&Type) -> String, prefer_no_std: bool, prefer_prelude: bool, - ) -> String { + ) -> Result { let db = sema_scope.db; let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude); match self { Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), - Expr::Local(it) => return it.name(db).display(db.upcast()).to_string(), - Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), - Expr::FamousType { value, .. } => return value.to_string(), + Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()), + Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()), + Expr::FamousType { value, .. } => Ok(value.to_string()), Expr::Function { func, params, .. } => { let args = params .iter() .map(|f| { f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); 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_)) + mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))? } crate::AssocItemContainer::Impl(imp) => { let self_ty = imp.self_ty(db); @@ -190,17 +200,17 @@ impl Expr { } }; let fn_name = func.name(db).display(db.upcast()).to_string(); - format!("{container_name}::{fn_name}({args})",) + Ok(format!("{container_name}::{fn_name}({args})")) } None => { - let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func)); - format!("{fn_name}({args})",) + let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func))?; + Ok(format!("{fn_name}({args})")) } } } Expr::Method { func, target, params, .. } => { if target.contains_many_in_illegal_pos() { - return many_formatter(&target.ty(db)); + return Ok(many_formatter(&target.ty(db))); } let func_name = func.name(db).display(db.upcast()).to_string(); @@ -210,28 +220,31 @@ impl Expr { many_formatter, prefer_no_std, prefer_prelude, - ); + )?; let args = params .iter() .map(|f| { f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); - match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) { + match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) { Some(trait_) => { - let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_)); + let trait_name = 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}"), crate::Access::Owned => target, }; - match args.is_empty() { + let res = match args.is_empty() { true => format!("{trait_name}::{func_name}({target})",), false => format!("{trait_name}::{func_name}({target}, {args})",), - } + }; + Ok(res) } - None => format!("{target}.{func_name}({args})"), + None => Ok(format!("{target}.{func_name}({args})")), } } Expr::Variant { variant, generics, params } => { @@ -242,6 +255,8 @@ impl Expr { let generics = generics .iter() .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!("::<{generics}>") } @@ -258,6 +273,8 @@ impl Expr { prefer_prelude, ) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!("{generics_str}({args})") } @@ -267,25 +284,28 @@ impl Expr { .iter() .zip(fields.iter()) .map(|(a, f)| { - format!( + let tmp = format!( "{}: {}", - f.name(db).display(db.upcast()).to_string(), + f.name(db).display(db.upcast()), a.gen_source_code( sema_scope, many_formatter, prefer_no_std, prefer_prelude - ) - ) + )? + ); + Ok(tmp) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!("{generics_str}{{ {args} }}") } StructKind::Unit => generics_str, }; - let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant)); - format!("{prefix}{inner}") + let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant))?; + Ok(format!("{prefix}{inner}")) } Expr::Struct { strukt, generics, params } => { let generics = non_default_generics(db, (*strukt).into(), generics); @@ -301,6 +321,8 @@ impl Expr { prefer_prelude, ) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!("({args})") } @@ -310,17 +332,20 @@ impl Expr { .iter() .zip(fields.iter()) .map(|(a, f)| { - format!( + let tmp = format!( "{}: {}", - f.name(db).display(db.upcast()).to_string(), + f.name(db).display(db.upcast()), a.gen_source_code( sema_scope, many_formatter, prefer_no_std, prefer_prelude - ) - ) + )? + ); + Ok(tmp) }) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!(" {{ {args} }}") } @@ -330,35 +355,45 @@ impl Expr { let generics = generics .iter() .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .collect::, DisplaySourceCodeError>>()? + .into_iter() .join(", "); format!("::<{generics}>") } }, }; - let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt))); - format!("{prefix}{inner}") + let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?; + Ok(format!("{prefix}{inner}")) } Expr::Field { expr, field } => { if expr.contains_many_in_illegal_pos() { - return many_formatter(&expr.ty(db)); + return Ok(many_formatter(&expr.ty(db))); } - let strukt = - expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); + let strukt = expr.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + )?; let field = field.name(db).display(db.upcast()).to_string(); - format!("{strukt}.{field}") + Ok(format!("{strukt}.{field}")) } Expr::Reference(expr) => { if expr.contains_many_in_illegal_pos() { - return many_formatter(&expr.ty(db)); + return Ok(many_formatter(&expr.ty(db))); } - let inner = - expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); - format!("&{inner}") + let inner = expr.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + )?; + Ok(format!("&{inner}")) } - Expr::Many(ty) => many_formatter(ty), + Expr::Many(ty) => Ok(many_formatter(ty)), } } @@ -380,10 +415,10 @@ impl Expr { target.ty(db).type_arguments().chain(generics.iter().cloned()), ), Expr::Variant { variant, generics, .. } => { - variant.parent_enum(db).ty_with_args(db, generics.iter().cloned()) + Adt::from(variant.parent_enum(db)).ty_with_args(db, generics.iter().cloned()) } Expr::Struct { strukt, generics, .. } => { - strukt.ty_with_args(db, generics.iter().cloned()) + Adt::from(*strukt).ty_with_args(db, generics.iter().cloned()) } Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()), Expr::Reference(it) => it.ty(db), @@ -395,16 +430,13 @@ impl Expr { pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec { let mut res = Vec::new(); - match self { - Expr::Method { func, params, .. } => { - res.extend(params.iter().flat_map(|it| it.traits_used(db))); - if let Some(it) = func.as_assoc_item(db) { - if let Some(it) = it.containing_trait_or_trait_impl(db) { - res.push(it); - } + if let Expr::Method { func, params, .. } = self { + res.extend(params.iter().flat_map(|it| it.traits_used(db))); + if let Some(it) = func.as_assoc_item(db) { + if let Some(it) = it.container_or_implemented_trait(db) { + res.push(it); } } - _ => (), } res diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 012d815394e2..666d63ac1558 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashSet; use crate::{ Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, ModuleDef, ScopeDef, Type, - Variant, + TypeParam, Variant, }; use crate::term_search::{Expr, TermSearchConfig}; @@ -82,7 +82,7 @@ pub(super) fn trivial<'a, DB: HirDatabase>( return None; } - ty.could_unify_with_deeply(db, &ctx.goal).then(|| expr) + ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr) }) } @@ -118,11 +118,15 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( } let generics = GenericDef::from(variant.parent_enum(db)); - - // Ignore enums with const generics - if !generics.const_params(db).is_empty() { + let Some(type_params) = generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>() + else { + // Ignore enums with const generics return Vec::new(); - } + }; // We currently do not check lifetime bounds so ignore all types that have something to do // with them @@ -130,9 +134,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( 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()) { @@ -154,13 +155,10 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( 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"), - }) + .map(|it| it.default(db).unwrap_or_else(|| g.next().expect("No generic"))) .collect(); - let enum_ty = parent_enum.ty_with_args(db, generics.iter().cloned()); + let enum_ty = Adt::from(parent_enum).ty_with_args(db, generics.iter().cloned()); // Allow types with generics only if they take us straight to goal for // performance reasons @@ -212,9 +210,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( let exprs: Vec<(Type, Vec)> = enum_ .variants(db) .into_iter() - .flat_map(|it| { - variant_helper(db, lookup, enum_.clone(), it, &ctx.goal, &ctx.config) - }) + .flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.goal, &ctx.config)) .collect(); if !exprs.is_empty() { @@ -231,10 +227,12 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( let generics = GenericDef::from(*it); - // Ignore enums with const generics - if !generics.const_params(db).is_empty() { - return None; - } + // Ignore const params for now + let type_params = generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; // We currently do not check lifetime bounds so ignore all types that have something to do // with them @@ -242,8 +240,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( 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()) { @@ -265,12 +261,13 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( 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"), + .map(|it| { + it.default(db) + .unwrap_or_else(|| g.next().expect("Missing type param")) }) .collect(); - let struct_ty = it.ty_with_args(db, generics.iter().cloned()); + + let struct_ty = Adt::from(*it).ty_with_args(db, generics.iter().cloned()); // Allow types with generics only if they take us straight to goal for // performance reasons @@ -324,7 +321,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( _ => None, }) .flatten() - .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } @@ -352,18 +349,18 @@ pub(super) fn free_function<'a, DB: HirDatabase>( ScopeDef::ModuleDef(ModuleDef::Function(it)) => { let generics = GenericDef::from(*it); - // Skip functions that require const generics - if !generics.const_params(db).is_empty() { - return None; - } + // Ignore const params for now + let type_params = generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; // Ignore lifetimes as we do not check them 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()) { @@ -391,10 +388,14 @@ pub(super) fn free_function<'a, DB: HirDatabase>( let generics: Vec<_> = type_params .iter() .map(|it| match it.default(db) { - Some(ty) => ty, - None => g.next().expect("Missing type param"), + Some(ty) => Some(ty), + None => { + let generic = g.next().expect("Missing type param"); + // Filter out generics that do not unify due to trait bounds + it.ty(db).could_unify_with(db, &generic).then_some(generic) + } }) - .collect(); + .collect::>()?; let ret_ty = it.ret_type_with_args(db, generics.iter().cloned()); // Filter out private and unsafe functions @@ -409,13 +410,13 @@ pub(super) fn free_function<'a, DB: HirDatabase>( // Early exit if some param cannot be filled from lookup let param_exprs: Vec> = it - .params_without_self_with_generics(db, generics.iter().cloned()) + .params_without_self_with_args(db, generics.iter().cloned()) .into_iter() .map(|field| { let ty = field.ty(); match ty.is_mutable_reference() { true => None, - false => lookup.find_autoref(db, &ty), + false => lookup.find_autoref(db, ty), } }) .collect::>()?; @@ -447,7 +448,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( _ => None, }) .flatten() - .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } @@ -487,11 +488,19 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( 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 const params for now + let imp_type_params = imp_generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; + + // Ignore const params for now + let fn_type_params = fn_generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; // Ignore all functions that have something to do with lifetimes as we don't check them if !fn_generics.lifetime_params(db).is_empty() { @@ -508,9 +517,6 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( 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()) @@ -544,10 +550,14 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( .iter() .chain(fn_type_params.iter()) .map(|it| match it.default(db) { - Some(ty) => ty, - None => g.next().expect("Missing type param"), + Some(ty) => Some(ty), + None => { + let generic = g.next().expect("Missing type param"); + // Filter out generics that do not unify due to trait bounds + it.ty(db).could_unify_with(db, &generic).then_some(generic) + } }) - .collect(); + .collect::>()?; let ret_ty = it.ret_type_with_args( db, @@ -579,16 +589,16 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( // Early exit if some param cannot be filled from lookup let param_exprs: Vec> = it - .params_without_self_with_generics( + .params_without_self_with_args( db, ty.type_arguments().chain(generics.iter().cloned()), ) .into_iter() - .map(|field| lookup.find_autoref(db, &field.ty())) + .map(|field| lookup.find_autoref(db, field.ty())) .collect::>()?; let fn_exprs: Vec = std::iter::once(target_type_exprs) - .chain(param_exprs.into_iter()) + .chain(param_exprs) .multi_cartesian_product() .map(|params| { let mut params = params.into_iter(); @@ -609,7 +619,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( Some(exprs) }) .flatten() - .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } @@ -647,7 +657,7 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>( Some((filed_ty, exprs)) }) }) - .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } @@ -719,11 +729,19 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( 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 const params for now + let imp_type_params = imp_generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; + + // Ignore const params for now + let fn_type_params = fn_generics + .type_or_const_params(db) + .into_iter() + .map(|it| it.as_type_param(db)) + .collect::>>()?; // Ignore all functions that have something to do with lifetimes as we don't check them if !fn_generics.lifetime_params(db).is_empty() @@ -742,9 +760,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( 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()) @@ -778,10 +793,17 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .iter() .chain(fn_type_params.iter()) .map(|it| match it.default(db) { - Some(ty) => ty, - None => g.next().expect("Missing type param"), + Some(ty) => Some(ty), + None => { + let generic = g.next().expect("Missing type param"); + it.trait_bounds(db) + .into_iter() + .all(|bound| generic.impls_trait(db, bound, &[])); + // Filter out generics that do not unify due to trait bounds + it.ty(db).could_unify_with(db, &generic).then_some(generic) + } }) - .collect(); + .collect::>()?; let ret_ty = it.ret_type_with_args( db, @@ -801,12 +823,12 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( // Early exit if some param cannot be filled from lookup let param_exprs: Vec> = it - .params_without_self_with_generics( + .params_without_self_with_args( db, ty.type_arguments().chain(generics.iter().cloned()), ) .into_iter() - .map(|field| lookup.find_autoref(db, &field.ty())) + .map(|field| lookup.find_autoref(db, field.ty())) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian @@ -832,6 +854,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( Some(exprs) }) .flatten() - .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs)) + .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 6b054790e9c7..51a1a406f316 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -1,6 +1,9 @@ //! Term search assist use hir::term_search::TermSearchCtx; -use ide_db::assists::{AssistId, AssistKind, GroupLabel}; +use ide_db::{ + assists::{AssistId, AssistKind, GroupLabel}, + famous_defs::FamousDefs, +}; use itertools::Itertools; use syntax::{ast, AstNode}; @@ -12,18 +15,21 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let syntax = unexpanded.syntax(); let goal_range = syntax.text_range(); - let excl = unexpanded.excl_token()?; - let macro_name_token = excl.prev_token()?; - let name = macro_name_token.text(); - if name != "todo" { + let parent = syntax.parent()?; + let scope = ctx.sema.scope(&parent)?; + + let macro_call = ctx.sema.resolve_macro_call(&unexpanded)?; + + let famous_defs = FamousDefs(&ctx.sema, scope.krate()); + let std_todo = famous_defs.core_macros_todo()?; + let std_unimplemented = famous_defs.core_macros_unimplemented()?; + + if macro_call != std_todo && macro_call != std_unimplemented { return None; } - let parent = syntax.parent()?; let target_ty = ctx.sema.type_of_expr(&ast::Expr::cast(parent.clone())?)?.adjusted(); - let scope = ctx.sema.scope(&parent)?; - let term_search_ctx = TermSearchCtx { sema: &ctx.sema, scope: &scope, @@ -37,13 +43,21 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< } let mut formatter = |_: &hir::Type| String::from("todo!()"); - for path in paths.iter().unique() { - let code = path.gen_source_code( - &scope, - &mut formatter, - ctx.config.prefer_no_std, - ctx.config.prefer_prelude, - ); + + let paths = paths + .into_iter() + .filter_map(|path| { + path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) + .ok() + }) + .unique(); + + for code in paths { acc.add_group( &GroupLabel(String::from("Term search")), AssistId("term_search", AssistKind::Generate), @@ -68,8 +82,9 @@ mod tests { fn test_complete_local() { check_assist( term_search, - "macro_rules! todo { () => (_) }; fn f() { let a: u128 = 1; let b: u128 = todo$0!() }", - "macro_rules! todo { () => (_) }; fn f() { let a: u128 = 1; let b: u128 = a }", + r#"//- minicore: todo, unimplemented +fn f() { let a: u128 = 1; let b: u128 = todo$0!() }"#, + r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, ) } @@ -77,8 +92,29 @@ mod tests { fn test_complete_todo_with_msg() { check_assist( term_search, - "macro_rules! todo { ($($arg:tt)+) => (_) }; fn f() { let a: u128 = 1; let b: u128 = todo$0!(\"asd\") }", - "macro_rules! todo { ($($arg:tt)+) => (_) }; fn f() { let a: u128 = 1; let b: u128 = a }", + r#"//- minicore: todo, unimplemented +fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, + r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, + ) + } + + #[test] + fn test_complete_unimplemented_with_msg() { + check_assist( + term_search, + r#"//- minicore: todo, unimplemented +fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, + r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, + ) + } + + #[test] + fn test_complete_unimplemented() { + check_assist( + term_search, + r#"//- minicore: todo, unimplemented +fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, + r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, ) } @@ -86,12 +122,11 @@ mod tests { fn test_complete_struct_field() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - struct A { pub x: i32, y: bool } - fn f() { let a = A { x: 1, y: true }; let b: i32 = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - struct A { pub x: i32, y: bool } - fn f() { let a = A { x: 1, y: true }; let b: i32 = a.x; }"#, + r#"//- minicore: todo, unimplemented +struct A { pub x: i32, y: bool } +fn f() { let a = A { x: 1, y: true }; let b: i32 = todo$0!(); }"#, + r#"struct A { pub x: i32, y: bool } +fn f() { let a = A { x: 1, y: true }; let b: i32 = a.x; }"#, ) } @@ -99,12 +134,9 @@ mod tests { fn test_enum_with_generics() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - 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; }"#, + r#"//- minicore: todo, unimplemented, option +fn f() { let a: i32 = 1; let b: Option = todo$0!(); }"#, + r#"fn f() { let a: i32 = 1; let b: Option = None; }"#, ) } @@ -112,12 +144,11 @@ mod tests { fn test_enum_with_generics2() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - enum Option { None, Some(T) } - fn f() { let a: i32 = 1; let b: Option = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - enum Option { None, Some(T) } - fn f() { let a: i32 = 1; let b: Option = Option::Some(a); }"#, + r#"//- minicore: todo, unimplemented +enum Option { None, Some(T) } +fn f() { let a: i32 = 1; let b: Option = todo$0!(); }"#, + r#"enum Option { None, Some(T) } +fn f() { let a: i32 = 1; let b: Option = Option::Some(a); }"#, ) } @@ -125,12 +156,11 @@ mod tests { 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); }"#, + r#"//- minicore: todo, unimplemented +enum Option { None, Some(T) } +fn f() { let a: Option = Option::None; let b: Option> = todo$0!(); }"#, + r#"enum Option { None, Some(T) } +fn f() { let a: Option = Option::None; let b: Option> = Option::Some(a); }"#, ) } @@ -138,22 +168,20 @@ mod tests { 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); }"#, + r#"//- minicore: todo, unimplemented +enum Foo { Foo(T) } +fn f() { let a = 0; let b: Foo = todo$0!(); }"#, + r#"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; }"#, + r#"//- minicore: todo, unimplemented +enum Foo { Foo(T) } +fn f() { let a: Foo = Foo::Foo(0); let b: Foo = todo$0!(); }"#, + r#"enum Foo { Foo(T) } +fn f() { let a: Foo = Foo::Foo(0); let b: Foo = a; }"#, ) } @@ -161,12 +189,11 @@ mod tests { fn test_newtype() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - struct Foo(i32); - fn f() { let a: i32 = 1; let b: Foo = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - struct Foo(i32); - fn f() { let a: i32 = 1; let b: Foo = Foo(a); }"#, + r#"//- minicore: todo, unimplemented +struct Foo(i32); +fn f() { let a: i32 = 1; let b: Foo = todo$0!(); }"#, + r#"struct Foo(i32); +fn f() { let a: i32 = 1; let b: Foo = Foo(a); }"#, ) } @@ -174,10 +201,9 @@ mod tests { fn test_shadowing() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = b; }"#, + r#"//- minicore: todo, unimplemented +fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = todo$0!(); }"#, + r#"fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = b; }"#, ) } @@ -185,10 +211,9 @@ mod tests { fn test_famous_bool() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - fn f() { let a: bool = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - fn f() { let a: bool = false; }"#, + r#"//- minicore: todo, unimplemented +fn f() { let a: bool = todo$0!(); }"#, + r#"fn f() { let a: bool = false; }"#, ) } @@ -196,12 +221,11 @@ mod tests { fn test_fn_with_reference_types() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - fn f(a: &i32) -> f32 { a as f32 } - fn g() { let a = 1; let b: f32 = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - fn f(a: &i32) -> f32 { a as f32 } - fn g() { let a = 1; let b: f32 = f(&a); }"#, + r#"//- minicore: todo, unimplemented +fn f(a: &i32) -> f32 { a as f32 } +fn g() { let a = 1; let b: f32 = todo$0!(); }"#, + r#"fn f(a: &i32) -> f32 { a as f32 } +fn g() { let a = 1; let b: f32 = f(&a); }"#, ) } @@ -209,12 +233,11 @@ mod tests { fn test_fn_with_reference_types2() { check_assist( term_search, - r#"macro_rules! todo { () => (_) }; - fn f(a: &i32) -> f32 { a as f32 } - fn g() { let a = &1; let b: f32 = todo$0!(); }"#, - r#"macro_rules! todo { () => (_) }; - fn f(a: &i32) -> f32 { a as f32 } - fn g() { let a = &1; let b: f32 = f(a); }"#, + r#"//- minicore: todo, unimplemented +fn f(a: &i32) -> f32 { a as f32 } +fn g() { let a = &1; let b: f32 = todo$0!(); }"#, + r#"fn f(a: &i32) -> f32 { a as f32 } +fn g() { let a = &1; let b: f32 = f(a); }"#, ) } @@ -222,7 +245,7 @@ mod tests { fn test_fn_with_reference_types3() { check_assist_not_applicable( term_search, - r#"macro_rules! todo { () => (_) }; + r#"//- minicore: todo, unimplemented fn f(a: &i32) -> f32 { a as f32 } fn g() { let a = &mut 1; let b: f32 = todo$0!(); }"#, ) diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 87016a605739..1ea7220960d2 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -159,9 +159,8 @@ impl Completions { } pub(crate) fn add_expr(&mut self, ctx: &CompletionContext<'_>, expr: &hir::term_search::Expr) { - match render_expr(ctx, expr) { - Some(item) => item.add_to(self, ctx.db), - None => (), + if let Some(item) = render_expr(ctx, expr) { + item.add_to(self, ctx.db) } } @@ -759,7 +758,6 @@ pub(super) fn complete_name_ref( flyimport::import_on_the_fly_dot(acc, ctx, dot_access); dot::complete_dot(acc, ctx, dot_access); postfix::complete_postfix(acc, ctx, dot_access); - expr::complete_expr(acc, ctx); } NameRefKind::Keyword(item) => { keyword::complete_for_and_where(acc, ctx, item); diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index c37b325ee76a..802e9bc3a807 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -342,7 +342,7 @@ pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) if let Some(ty) = &ctx.expected_type { // Ignore unit types as they are not very interesting - if ty.is_unit() { + if ty.is_unit() || ty.is_unknown() { return; } diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 8552a20392ab..17dfcc08ca1f 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -297,6 +297,7 @@ pub enum CompletionItemKind { Method, Snippet, UnresolvedReference, + Expression, } impl_from!(SymbolKind for CompletionItemKind); @@ -341,6 +342,7 @@ impl CompletionItemKind { CompletionItemKind::Method => "me", CompletionItemKind::Snippet => "sn", CompletionItemKind::UnresolvedReference => "??", + CompletionItemKind::Expression => "ex", } } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 1bac2e30c19c..88dc3b5cbe7c 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -295,22 +295,24 @@ pub(crate) fn render_expr( .unwrap_or_else(|| String::from("...")) }; - let label = expr.gen_source_code( - &ctx.scope, - &mut label_formatter, - ctx.config.prefer_no_std, - ctx.config.prefer_prelude, - ); + let label = expr + .gen_source_code( + &ctx.scope, + &mut label_formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) + .ok()?; let source_range = match ctx.original_token.parent() { - Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { + Some(node) => match node.ancestors().find_map(ast::Path::cast) { Some(path) => path.syntax().text_range(), None => node.text_range(), }, None => ctx.source_range(), }; - let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone()); + let mut item = CompletionItem::new(CompletionItemKind::Expression, source_range, label.clone()); let snippet = format!( "{}$0", @@ -320,6 +322,7 @@ pub(crate) fn render_expr( ctx.config.prefer_no_std, ctx.config.prefer_prelude ) + .ok()? ); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); @@ -1034,6 +1037,7 @@ fn func(input: Struct) { } st Self [type] sp Self [type] st Struct [type] + ex Struct [type] lc self [local] fn func(…) [] me self.test() [] @@ -1058,6 +1062,9 @@ fn main() { "#, expect![[r#" lc input [type+name+local] + ex input [type] + ex true [type] + ex false [type] lc inputbad [local] fn main() [] fn test(…) [] @@ -1738,6 +1745,10 @@ fn f() { A { bar: b$0 }; } expect![[r#" fn bar() [type+name] fn baz() [type] + ex baz() [type] + ex bar() [type] + ex A { bar: baz() }.bar [type] + ex A { bar: bar() }.bar [type] st A [] fn f() [] "#]], @@ -1822,6 +1833,8 @@ fn main() { lc s [type+name+local] st S [type] st S [type] + ex s [type] + ex S [type] fn foo(…) [] fn main() [] "#]], @@ -1839,6 +1852,8 @@ fn main() { lc ssss [type+local] st S [type] st S [type] + ex ssss [type] + ex S [type] fn foo(…) [] fn main() [] "#]], @@ -1871,6 +1886,8 @@ fn main() { } "#, expect![[r#" + ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify] + ex core::ops::Deref::deref(&t) (use core::ops::Deref) [type_could_unify] lc m [local] lc t [local] lc &t [type+local] @@ -1919,6 +1936,8 @@ fn main() { } "#, expect![[r#" + ex core::ops::DerefMut::deref_mut(&mut T(S)) (use core::ops::DerefMut) [type_could_unify] + ex core::ops::DerefMut::deref_mut(&mut t) (use core::ops::DerefMut) [type_could_unify] lc m [local] lc t [local] lc &mut t [type+local] @@ -1967,6 +1986,8 @@ fn bar(t: Foo) {} ev Foo::A [type] ev Foo::B [type] en Foo [type] + ex Foo::A [type] + ex Foo::B [type] fn bar(…) [] fn foo() [] "#]], @@ -2020,6 +2041,8 @@ fn main() { } "#, expect![[r#" + ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify] + ex core::ops::Deref::deref(&bar()) (use core::ops::Deref) [type_could_unify] st S [] st &S [type] st S [] @@ -2233,6 +2256,7 @@ fn foo() { "#, expect![[r#" lc foo [type+local] + ex foo [type] ev Foo::A(…) [type_could_unify] ev Foo::B [type_could_unify] en Foo [type_could_unify] @@ -2267,8 +2291,6 @@ fn main() { &[CompletionItemKind::Snippet, CompletionItemKind::Method], expect![[r#" sn not [snippet] - sn true [type] - sn false [type] me not() (use ops::Not) [type_could_unify+requires_import] sn if [] sn while [] diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index b835820260a9..556d3872d7ed 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -97,11 +97,11 @@ fn func(param0 @ (param1, param2): (i32, i32)) { kw unsafe kw while kw while let - sn ifletlocal - sn letlocal - sn matcharm - sn param1 - sn param2 + ex ifletlocal + ex letlocal + ex matcharm + ex param1 + ex param2 "#]], ); } @@ -243,11 +243,11 @@ fn complete_in_block() { kw use kw while kw while let - sn false sn macro_rules sn pd sn ppd - sn true + ex false + ex true "#]], ) } @@ -690,8 +690,8 @@ fn main() { "#, expect![[r#" fn test() fn() -> Zulu - sn Zulu - sn Zulu::test() + ex Zulu + ex Zulu::test() "#]], ); } diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs index c137de3e52d6..e64ec74c6106 100644 --- a/crates/ide-completion/src/tests/record.rs +++ b/crates/ide-completion/src/tests/record.rs @@ -192,8 +192,8 @@ fn main() { bt u32 u32 kw crate:: kw self:: - sn Foo::default() - sn foo + ex Foo::default() + ex foo "#]], ); check( diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index 68a6c678501f..ff32eccfbff4 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -225,10 +225,10 @@ impl S { fn foo() { let _ = lib::S::$0 } "#, expect![[r#" - ct PUBLIC_CONST pub const PUBLIC_CONST: u32 - fn public_method() fn() - ta PublicType pub type PublicType = u32 - "#]], + ct PUBLIC_CONST pub const PUBLIC_CONST: u32 + fn public_method() fn() + ta PublicType pub type PublicType = u32 + "#]], ); } @@ -242,8 +242,8 @@ impl U { fn m() { } } fn foo() { let _ = U::$0 } "#, expect![[r#" - fn m() fn() - "#]], + fn m() fn() + "#]], ); } @@ -256,8 +256,8 @@ trait Trait { fn m(); } fn foo() { let _ = Trait::$0 } "#, expect![[r#" - fn m() (as Trait) fn() - "#]], + fn m() (as Trait) fn() + "#]], ); } @@ -273,8 +273,8 @@ impl Trait for S {} fn foo() { let _ = S::$0 } "#, expect![[r#" - fn m() (as Trait) fn() - "#]], + fn m() (as Trait) fn() + "#]], ); } @@ -290,8 +290,8 @@ impl Trait for S {} fn foo() { let _ = ::$0 } "#, expect![[r#" - fn m() (as Trait) fn() - "#]], + fn m() (as Trait) fn() + "#]], ); } @@ -396,9 +396,9 @@ macro_rules! foo { () => {} } fn main() { let _ = crate::$0 } "#, expect![[r#" - fn main() fn() - ma foo!(…) macro_rules! foo - "#]], + fn main() fn() + ma foo!(…) macro_rules! foo + "#]], ); } @@ -694,8 +694,10 @@ fn bar() -> Bar { } "#, expect![[r#" - fn foo() (as Foo) fn() -> Self - "#]], + fn foo() (as Foo) fn() -> Self + ex Bar + ex bar() + "#]], ); } @@ -722,6 +724,8 @@ fn bar() -> Bar { expect![[r#" fn bar() fn() fn foo() (as Foo) fn() -> Self + ex Bar + ex bar() "#]], ); } @@ -748,6 +752,8 @@ fn bar() -> Bar { "#, expect![[r#" fn foo() (as Foo) fn() -> Self + ex Bar + ex bar() "#]], ); } @@ -1230,10 +1236,6 @@ fn here_we_go() { "#, expect![[r#" st Bar (alias Qux) Bar - sn () - sn false - sn here_we_go() - sn true "#]], ); } @@ -1288,10 +1290,6 @@ fn here_we_go() { kw unsafe kw while kw while let - sn () - sn false - sn here_we_go() - sn true "#]], ); } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 4edfa37b3290..3106772e63b1 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -114,6 +114,14 @@ impl FamousDefs<'_, '_> { self.find_function("core:mem:drop") } + pub fn core_macros_todo(&self) -> Option { + self.find_macro("core:todo") + } + + pub fn core_macros_unimplemented(&self) -> Option { + self.find_macro("core:unimplemented") + } + pub fn builtin_crates(&self) -> impl Iterator { IntoIterator::into_iter([ self.std(), diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index e93eea8ce29e..8c97281b7832 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -112,7 +112,8 @@ fn add_missing_ok_or_some( let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" }; - let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]); + let wrapped_actual_ty = + expected_adt.ty_with_args(ctx.sema.db, std::iter::once(d.actual.clone())); if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) { return None; diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 7aeadce8e269..56c8181e84ce 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -51,17 +51,21 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option }; let paths = term_search(&term_search_ctx); - let mut assists = vec![]; let mut formatter = |_: &hir::Type| String::from("_"); - for path in paths.into_iter().unique() { - let code = path.gen_source_code( - &scope, - &mut formatter, - ctx.config.prefer_no_std, - ctx.config.prefer_prelude, - ); - assists.push(Assist { + let assists: Vec = paths + .into_iter() + .filter_map(|path| { + path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) + .ok() + }) + .unique() + .map(|code| Assist { id: AssistId("typed-hole", AssistKind::QuickFix), label: Label::new(format!("Replace `_` with `{}`", &code)), group: Some(GroupLabel("Replace `_` with a term".to_owned())), @@ -71,8 +75,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option TextEdit::replace(original_range.range, code), )), trigger_signature_help: false, - }); - } + }) + .collect(); + if !assists.is_empty() { Some(assists) } else { @@ -242,31 +247,33 @@ fn main(param: Foo) { check_has_fix( r#" struct Bar; +struct Baz; trait Foo { fn foo(self) -> Bar; } -impl Foo for i32 { +impl Foo for Baz { fn foo(self) -> Bar { unimplemented!() } } fn asd() -> Bar { - let a: i32 = 1; + let a = Baz; _$0 } "#, r" struct Bar; +struct Baz; trait Foo { fn foo(self) -> Bar; } -impl Foo for i32 { +impl Foo for Baz { fn foo(self) -> Bar { unimplemented!() } } fn asd() -> Bar { - let a: i32 = 1; + let a = Baz; Foo::foo(a) } ", @@ -330,30 +337,32 @@ fn main() { check_has_fix( r#" struct Bar {} +struct A; trait Foo { type Res; fn foo(&self) -> Self::Res; } -impl Foo for i32 { +impl Foo for A { type Res = Bar; fn foo(&self) -> Self::Res { Bar { } } } fn main() { - let a: i32 = 1; + let a = A; let c: Bar = _$0; }"#, r#" struct Bar {} +struct A; trait Foo { type Res; fn foo(&self) -> Self::Res; } -impl Foo for i32 { +impl Foo for A { type Res = Bar; fn foo(&self) -> Self::Res { Bar { } } } fn main() { - let a: i32 = 1; + let a = A; let c: Bar = Foo::foo(&a); }"#, ); diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 30bfe6ee9dc3..69ddc1e45efb 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -7263,8 +7263,8 @@ impl Iterator for S { file_id: FileId( 1, ), - full_range: 6157..6365, - focus_range: 6222..6228, + full_range: 6290..6498, + focus_range: 6355..6361, name: "Future", kind: Trait, container_name: "future", @@ -7277,8 +7277,8 @@ impl Iterator for S { file_id: FileId( 1, ), - full_range: 6995..7461, - focus_range: 7039..7047, + full_range: 7128..7594, + focus_range: 7172..7180, name: "Iterator", kind: Trait, container_name: "iterator", diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index efad2ff6d1db..ce7e3b3cd6a4 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -333,10 +333,12 @@ impl flags::AnalysisStats { mut file_ids: Vec, verbosity: Verbosity, ) { - let mut cargo_config = CargoConfig::default(); - cargo_config.sysroot = match self.no_sysroot { - true => None, - false => Some(RustLibSource::Discover), + let cargo_config = CargoConfig { + sysroot: match self.no_sysroot { + true => None, + false => Some(RustLibSource::Discover), + }, + ..Default::default() }; let mut bar = match verbosity { @@ -392,16 +394,15 @@ impl flags::AnalysisStats { continue; } - let range = sema.original_range(&expected_tail.syntax()).range; + let range = sema.original_range(expected_tail.syntax()).range; let original_text: String = db .file_text(file_id) .chars() - .into_iter() .skip(usize::from(range.start())) .take(usize::from(range.end()) - usize::from(range.start())) .collect(); - let scope = match sema.scope(&expected_tail.syntax()) { + let scope = match sema.scope(expected_tail.syntax()) { Some(it) => it, None => continue, }; @@ -425,14 +426,15 @@ impl flags::AnalysisStats { }; fn trim(s: &str) -> String { - s.chars().into_iter().filter(|c| !c.is_whitespace()).collect() + s.chars().filter(|c| !c.is_whitespace()).collect() } let todo = syntax::ast::make::ext::expr_todo().to_string(); let mut formatter = |_: &hir::Type| todo.clone(); let mut syntax_hit_found = false; for term in found_terms { - let generated = term.gen_source_code(&scope, &mut formatter, false, true); + let generated = + term.gen_source_code(&scope, &mut formatter, false, true).unwrap(); syntax_hit_found |= trim(&original_text) == trim(&generated); // Validate if type-checks diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs index af2b136f9288..493e614dce68 100644 --- a/crates/rust-analyzer/src/cli/flags.rs +++ b/crates/rust-analyzer/src/cli/flags.rs @@ -93,9 +93,10 @@ xflags::xflags! { /// and annotations. This is useful for benchmarking the memory usage on a project that has /// been worked on for a bit in a longer running session. optional --run-all-ide-things - /// Run term search + /// Run term search on all the tail expressions (of functions, block, if statements etc.) optional --run-term-search - /// Validate term search by running `cargo check` on every response + /// Validate term search by running `cargo check` on every response. + /// Note that this also temporarily modifies the files on disk, use with caution! optional --validate-term-search } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 298d92468f67..7a1d2596371f 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -287,7 +287,7 @@ config_data! { } }"#, /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. - completion_term_search_enable: bool = "true", + completion_termSearch_enable: bool = "false", /// List of rust-analyzer diagnostics to disable. diagnostics_disabled: FxHashSet = "[]", @@ -1537,7 +1537,7 @@ impl Config { && completion_item_edit_resolve(&self.caps), enable_self_on_the_fly: self.data.completion_autoself_enable, enable_private_editable: self.data.completion_privateEditable_enable, - enable_term_search: self.data.completion_term_search_enable, + enable_term_search: self.data.completion_termSearch_enable, full_function_signatures: self.data.completion_fullFunctionSignatures_enable, callable: match self.data.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index 64f19f0b32d7..bc4666c1221b 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -123,6 +123,7 @@ pub(crate) fn completion_item_kind( CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD, CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET, CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE, + CompletionItemKind::Expression => lsp_types::CompletionItemKind::SNIPPET, CompletionItemKind::SymbolKind(symbol) => match symbol { SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION, SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT, diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 23a3a7e0afa4..f125792d1258 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -60,6 +60,8 @@ //! try: infallible //! unpin: sized //! unsize: sized +//! todo: panic +//! unimplemented: panic #![rustc_coherence_is_core] @@ -927,6 +929,10 @@ pub mod fmt { use crate::mem::transmute; unsafe { Argument { formatter: transmute(f), value: transmute(x) } } } + + pub fn new_display<'b, T: Display>(x: &'b T) -> Argument<'_> { + Self::new(x, Display::fmt) + } } #[lang = "format_alignment"] @@ -1438,6 +1444,33 @@ mod macros { // endregion:fmt + // region:todo + #[macro_export] + #[allow_internal_unstable(core_panic)] + macro_rules! todo { + () => { + $crate::panicking::panic("not yet implemented") + }; + ($($arg:tt)+) => { + $crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+)) + }; + } + // endregion:todo + + // region:unimplemented + #[macro_export] + #[allow_internal_unstable(core_panic)] + macro_rules! unimplemented { + () => { + $crate::panicking::panic("not implemented") + }; + ($($arg:tt)+) => { + $crate::panic!("not implemented: {}", $crate::format_args!($($arg)+)) + }; + } + // endregion:unimplemented + + // region:derive pub(crate) mod builtin { #[rustc_builtin_macro] diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 9d54999fa38b..5669c8fa6bbe 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -344,7 +344,7 @@ Default: Custom completion snippets. -- -[[rust-analyzer.completion.term.search.enable]]rust-analyzer.completion.term.search.enable (default: `true`):: +[[rust-analyzer.completion.termSearch.enable]]rust-analyzer.completion.termSearch.enable (default: `false`):: + -- Whether to enable term search based snippets like `Some(foo.bar().baz())`. diff --git a/editors/code/package.json b/editors/code/package.json index 3352007253e9..5bb87fa5b718 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -902,9 +902,9 @@ }, "type": "object" }, - "rust-analyzer.completion.term.search.enable": { + "rust-analyzer.completion.termSearch.enable": { "markdownDescription": "Whether to enable term search based snippets like `Some(foo.bar().baz())`.", - "default": true, + "default": false, "type": "boolean" }, "rust-analyzer.diagnostics.disabled": {