Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #17449 - kilpkonn:assoc_const, r=Veykril
Term search: new tactic for associated item constants New tactic to cover some more exotic cases that started bothering me. Associated constants seem to be common in [axum](https://github.com/tokio-rs/axum/blob/806bc26e62afc2e0c83240a9e85c14c96bc2ceb3/examples/readme/src/main.rs#L53).
bors 2024-06-19
parent 87ee18d · parent c87609f · commit 3273724
-rw-r--r--crates/hir/src/term_search.rs1
-rw-r--r--crates/hir/src/term_search/expr.rs55
-rw-r--r--crates/hir/src/term_search/tactics.rs51
-rw-r--r--crates/ide-assists/src/handlers/term_search.rs30
-rw-r--r--crates/ide-completion/src/render.rs1
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<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
let mut solutions: Vec<Expr> = 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<String, DisplaySourceCodeError> {
+ 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<ScopeDef>,
+ lookup: &'a mut LookupTable,
+) -> impl Iterator<Item = Expr> + '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<i32> = todo$0!(); }"#,
fn f() { let a = 1; let b: Foo<i32> = 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 []