about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTavo Annus <tavo.annus@gmail.com>2024-05-25 20:56:39 +0300
committerTavo Annus <tavo.annus@gmail.com>2024-06-18 22:08:44 +0300
commitd3d840b5cc187d9ee6fdc0b9725e1231bd30f226 (patch)
tree48d9c63086cb621842a87540e2a7b3cdbb3fb16f
parent1ad33f905f437e880df279612edc97a44f98f676 (diff)
downloadrust-d3d840b5cc187d9ee6fdc0b9725e1231bd30f226.tar.gz
rust-d3d840b5cc187d9ee6fdc0b9725e1231bd30f226.zip
Add tactic for associated item constants
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/term_search.rs1
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/term_search/expr.rs55
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs51
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs30
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render.rs1
5 files changed, 118 insertions, 20 deletions
diff --git a/src/tools/rust-analyzer/crates/hir/src/term_search.rs b/src/tools/rust-analyzer/crates/hir/src/term_search.rs
index 7b70cdf4599..aa046b02e2e 100644
--- a/src/tools/rust-analyzer/crates/hir/src/term_search.rs
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir/src/term_search/expr.rs b/src/tools/rust-analyzer/crates/hir/src/term_search/expr.rs
index 8173427cd90..bb687f5e73d 100644
--- a/src/tools/rust-analyzer/crates/hir/src/term_search/expr.rs
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs b/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs
index 7ac63562bb4..b738e6af77b 100644
--- a/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs
index 94e0519cba0..8a9229c549f 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
index 905c7eebfab..ebdc813f3d7 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
+++ b/src/tools/rust-analyzer/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 []