Add tactic for associated item constants

This commit is contained in:
Tavo Annus 2024-05-25 20:56:39 +03:00
parent 021ae0101c
commit c87609fef1
5 changed files with 118 additions and 20 deletions

View file

@ -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();

View file

@ -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)
}

View file

@ -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

View file

@ -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; }"#,
)
}
}

View file

@ -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 []