From c87609fef1eebe3044cf7196915959436b7dd594 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Sat, 25 May 2024 20:56:39 +0300 Subject: [PATCH] Add tactic for associated item constants --- crates/hir/src/term_search.rs | 1 + crates/hir/src/term_search/expr.rs | 55 ++++++++++++------- crates/hir/src/term_search/tactics.rs | 51 ++++++++++++++++- .../ide-assists/src/handlers/term_search.rs | 30 ++++++++++ crates/ide-completion/src/render.rs | 1 + 5 files changed, 118 insertions(+), 20 deletions(-) diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs index 7b70cdf459..aa046b02e2 100644 --- a/crates/hir/src/term_search.rs +++ b/crates/hir/src/term_search.rs @@ -325,6 +325,7 @@ pub fn term_search(ctx: &TermSearchCtx<'_, DB>) -> Vec { 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)); + solutions.extend(tactics::assoc_const(ctx, &defs, &mut lookup)); while should_continue() { lookup.new_round(); diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index 8173427cd9..bb687f5e73 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -9,8 +9,8 @@ use hir_ty::{ use itertools::Itertools; use crate::{ - Adt, AsAssocItem, Const, ConstParam, Field, Function, GenericDef, Local, ModuleDef, - SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant, + Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, GenericDef, Local, + ModuleDef, SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant, }; /// Helper function to get path to `ModuleDef` @@ -138,7 +138,17 @@ impl Expr { let db = sema_scope.db; let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg); match self { - Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), + Expr::Const(it) => match it.as_assoc_item(db).map(|it| it.container(db)) { + Some(container) => { + let container_name = container_name(container, sema_scope, cfg)?; + let const_name = it + .name(db) + .map(|c| c.display(db.upcast()).to_string()) + .unwrap_or(String::new()); + Ok(format!("{container_name}::{const_name}")) + } + None => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), + }, Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()), Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()), @@ -153,22 +163,7 @@ impl Expr { 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(), cfg)) - { - Some(path) => path.display(sema_scope.db.upcast()).to_string(), - None => self_ty.display(db).to_string(), - } - } - }; + let container_name = container_name(container, sema_scope, cfg)?; let fn_name = func.name(db).display(db.upcast()).to_string(); Ok(format!("{container_name}::{fn_name}({args})")) } @@ -414,3 +409,25 @@ impl Expr { matches!(self, Expr::Many(_)) } } + +/// Helper function to find name of container +fn container_name( + container: AssocItemContainer, + sema_scope: &SemanticsScope<'_>, + cfg: ImportPathConfig, +) -> Result { + let container_name = match container { + crate::AssocItemContainer::Trait(trait_) => { + mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg)? + } + crate::AssocItemContainer::Impl(imp) => { + let self_ty = imp.self_ty(sema_scope.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(), cfg)) { + Some(path) => path.display(sema_scope.db.upcast()).to_string(), + None => self_ty.display(sema_scope.db).to_string(), + } + } + }; + Ok(container_name) +} diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 7ac63562bb..b738e6af77 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -80,7 +80,10 @@ pub(super) fn trivial<'a, DB: HirDatabase>( lookup.insert(ty.clone(), std::iter::once(expr.clone())); // Don't suggest local references as they are not valid for return - if matches!(expr, Expr::Local(_)) && ty.contains_reference(db) { + if matches!(expr, Expr::Local(_)) + && ty.contains_reference(db) + && ctx.config.enable_borrowcheck + { return None; } @@ -88,6 +91,52 @@ pub(super) fn trivial<'a, DB: HirDatabase>( }) } +/// # Associated constant tactic +/// +/// Attempts to fulfill the goal by trying constants defined as associated items. +/// Only considers them on types that are in scope. +/// +/// # Arguments +/// * `ctx` - Context for the term search +/// * `defs` - Set of items in scope at term search target location +/// * `lookup` - Lookup table for types +/// +/// 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 assoc_const<'a, DB: HirDatabase>( + ctx: &'a TermSearchCtx<'a, DB>, + defs: &'a FxHashSet, + lookup: &'a mut LookupTable, +) -> impl Iterator + 'a { + let db = ctx.sema.db; + let module = ctx.scope.module(); + + defs.iter() + .filter_map(|def| match def { + ScopeDef::ModuleDef(ModuleDef::Adt(it)) => Some(it), + _ => None, + }) + .flat_map(|it| Impl::all_for_type(db, it.ty(db))) + .filter(|it| !it.is_unsafe(db)) + .flat_map(|it| it.items(db)) + .filter(move |it| it.is_visible_from(db, module)) + .filter_map(AssocItem::as_const) + .filter_map(|it| { + let expr = Expr::Const(it); + let ty = it.ty(db); + + if ty.contains_unknown() { + return None; + } + + lookup.insert(ty.clone(), std::iter::once(expr.clone())); + + ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr) + }) +} + /// # Data constructor tactic /// /// Attempts different data constructors for enums and structs in scope diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 94e0519cba..8a9229c549 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -290,4 +290,34 @@ fn f() { let a = 1; let b: Foo = todo$0!(); }"#, fn f() { let a = 1; let b: Foo = Foo(a); }"#, ) } + + #[test] + fn test_struct_assoc_item() { + check_assist( + term_search, + r#"//- minicore: todo, unimplemented +struct Foo; +impl Foo { const FOO: i32 = 0; } +fn f() { let a: i32 = todo$0!(); }"#, + r#"struct Foo; +impl Foo { const FOO: i32 = 0; } +fn f() { let a: i32 = Foo::FOO; }"#, + ) + } + + #[test] + fn test_trait_assoc_item() { + check_assist( + term_search, + r#"//- minicore: todo, unimplemented +struct Foo; +trait Bar { const BAR: i32; } +impl Bar for Foo { const BAR: i32 = 0; } +fn f() { let a: i32 = todo$0!(); }"#, + r#"struct Foo; +trait Bar { const BAR: i32; } +impl Bar for Foo { const BAR: i32 = 0; } +fn f() { let a: i32 = Foo::BAR; }"#, + ) + } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 905c7eebfa..ebdc813f3d 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -1799,6 +1799,7 @@ fn go(world: &WorldSnapshot) { go(w$0) } "#, expect![[r#" lc world [type+name+local] + ex world [type] st WorldSnapshot {…} [] st &WorldSnapshot {…} [type] st WorldSnapshot []