diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2ec382be6e3..5137bff055a 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -4567,8 +4567,8 @@ pub fn layout(&self, db: &dyn HirDatabase) -> Result { // FIXME: Document this #[derive(Debug)] pub struct Callable { - pub ty: Type, - pub sig: CallableSig, + ty: Type, + sig: CallableSig, callee: Callee, /// Whether this is a method that was called with method call syntax. pub(crate) is_bound_method: bool, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 6f5d2111884..309c1937492 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -10,9 +10,7 @@ pub(crate) mod union_literal; pub(crate) mod literal; -use core::panic; - -use hir::{AsAssocItem, HasAttrs, HirDisplay, ModuleDef, ScopeDef, Type}; +use hir::{AsAssocItem, Function, HasAttrs, HirDisplay, ModuleDef, ScopeDef, Type}; use ide_db::{ documentation::{Documentation, HasDocs}, helpers::item_name, @@ -395,17 +393,14 @@ fn render_resolution_path( ScopeDef::ModuleDef(ModuleDef::Adt(adt)) | ScopeDef::AdtSelfType(adt) => { set_item_relevance(adt.ty(db)) } - ScopeDef::ModuleDef(ModuleDef::Function(func)) => { - set_item_relevance(func.ty(db).as_callable(db).unwrap().ty) - } - ScopeDef::ModuleDef(ModuleDef::Variant(variant)) => { - set_item_relevance(variant.parent_enum(db).ty(db)) - } + // Functions are handled at the start of the function. + ScopeDef::ModuleDef(ModuleDef::Function(_)) => (), // TODO: Should merge with the match case earlier in the function? + // Enum variants are handled at the start of the function. + ScopeDef::ModuleDef(ModuleDef::Variant(_)) => (), ScopeDef::ModuleDef(ModuleDef::Const(konst)) => set_item_relevance(konst.ty(db)), ScopeDef::ModuleDef(ModuleDef::Static(stat)) => set_item_relevance(stat.ty(db)), ScopeDef::ModuleDef(ModuleDef::BuiltinType(bt)) => set_item_relevance(bt.ty(db)), ScopeDef::ImplSelfType(imp) => set_item_relevance(imp.self_ty(db)), - ScopeDef::GenericParam(_) | ScopeDef::Label(_) | ScopeDef::Unknown @@ -502,6 +497,20 @@ fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> boo } } +fn match_types( + ctx: &CompletionContext<'_>, + ty1: &hir::Type, + ty2: &hir::Type, +) -> Option { + if ty1 == ty2 { + Some(CompletionRelevanceTypeMatch::Exact) + } else if ty1.could_unify_with(ctx.db, ty2) { + Some(CompletionRelevanceTypeMatch::CouldUnify) + } else { + None + } +} + fn compute_type_match( ctx: &CompletionContext<'_>, completion_ty: &hir::Type, @@ -514,35 +523,42 @@ fn compute_type_match( return None; } - if completion_ty == expected_type { - Some(CompletionRelevanceTypeMatch::Exact) - } else if expected_type.could_unify_with(ctx.db, completion_ty) { - Some(CompletionRelevanceTypeMatch::CouldUnify) - } else { - None - } + match_types(ctx, expected_type, completion_ty) } -fn compute_type_match2( +fn compute_function_type_match( ctx: &CompletionContext<'_>, - completion_ty1: &hir::Type, - completion_ty2: &hir::Type, + func: &Function, ) -> Option { - let expected_type = completion_ty1; + // We compute a vec of function parameters + the return type for the expected + // type as well as the function we are matching with. Doing this allows for + // matching all of the types in one iterator. - // We don't ever consider unit type to be an exact type match, since - // nearly always this is not meaningful to the user. - if expected_type.is_unit() { + let expected_callable = ctx.expected_type.as_ref()?.as_callable(ctx.db)?; + let expected_types = expected_callable.params(ctx.db).into_iter().map(|param| param.1); + let actual_types = + func.ty(ctx.db).as_callable(ctx.db)?.params(ctx.db).into_iter().map(|param| param.1); + + if expected_types.len() != actual_types.len() { return None; } - if completion_ty2 == expected_type { - Some(CompletionRelevanceTypeMatch::Exact) - } else if expected_type.could_unify_with(ctx.db, completion_ty2) { - Some(CompletionRelevanceTypeMatch::CouldUnify) - } else { - None + let mut matches = expected_types + .zip(actual_types) + .chain([(expected_callable.return_type(), func.ret_type(ctx.db))]) + .map(|(expected_type, actual_type)| match_types(ctx, &expected_type, &actual_type)); + + // Any missing type match indicates that these types can not be unified. + if matches.any(|type_match| type_match.is_none()) { + return None; } + + // If any of the types are unifiable but not exact we consider the function types as a whole + // to be unifiable. Otherwise if every pair of types is an exact match the functions are an + // exact type match. + matches + .find(|type_match| matches!(type_match, Some(CompletionRelevanceTypeMatch::CouldUnify))) + .unwrap_or(Some(CompletionRelevanceTypeMatch::Exact)) } fn compute_exact_name_match(ctx: &CompletionContext<'_>, completion_name: &str) -> bool { @@ -796,7 +812,7 @@ fn test(…) [] ); } - // TODO: does this test even make sense? + // TODO: How dowe test ModuleDef::Variant(Variant?) #[test] fn set_enum_variant_type_completion_info() { check_relevance( @@ -820,7 +836,7 @@ pub enum Enum { fn test(input: dep::test_mod_b::Enum) { } fn main() { - test(Enum$0); + test(Enum::Variant$0); } "#, expect![[r#" @@ -859,7 +875,7 @@ fn main() { } "#, expect![[r#" - fn Function (use dep::test_mod_a::Function) [type_could_unify+requires_import] + fn Function (use dep::test_mod_a::Function) [type+requires_import] fn main [] fn test [] md dep [] @@ -868,7 +884,6 @@ fn Function (use dep::test_mod_b::Function) [requires_import] ); } - // TODO This test does not trigger the const case #[test] fn set_const_type_completion_info() { check_relevance( @@ -933,8 +948,38 @@ fn test(…) [] ); } - // TODO: seems like something is going wrong here. Exapt type match has no effect - // EDIT: maybe it is actually working + #[test] + fn set_self_type_completion_info_with_params() { + check_relevance( + r#" +//- /lib.rs crate:dep +pub struct Struct; + +impl Struct { + pub fn Function(&self, input: i32) -> bool { + false + } +} + + +//- /main.rs crate:main deps:dep + +use dep::Struct; + + +fn test(input: fn(&dep::Struct, i32) -> bool) { } + +fn main() { + test(Struct::Function$0); +} + +"#, + expect![[r#" + me Function [type] + "#]], + ); + } + #[test] fn set_self_type_completion_info() { check_relevance( @@ -964,34 +1009,26 @@ fn func(…) [] ); } - // TODO: how do we actually test builtins? - #[test] fn set_builtin_type_completion_info() { check_relevance( r#" -//- /lib.rs crate:dep +//- /main.rs crate:main -pub mod test_mod_b { - static STATIC: i32 = 5; -} - - pub mod test_mod_a { - static STATIC: &str = "test"; -} - -//- /main.rs crate:main deps:dep - -fn test(input: i32) { } +fn test(input: bool) { } + pub Input: bool = false; fn main() { - test(STATIC$0); + let input = false; + let inputbad = 3; + test(inp$0); } "#, expect![[r#" + lc input [type+name+local] + lc inputbad [local] fn main() [] fn test(…) [] - md dep [] "#]], ); } diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index c2e06c926f1..ff84aa8742e 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -1,6 +1,6 @@ //! Renderer for function calls. -use hir::{db::HirDatabase, AsAssocItem, Callable, HirDisplay, Type}; +use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; use ide_db::{SnippetCap, SymbolKind}; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; @@ -8,13 +8,9 @@ use crate::{ context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind}, - item::{ - Builder, CompletionItem, CompletionItemKind, CompletionRelevance, - CompletionRelevanceTypeMatch, - }, + item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, render::{ - compute_exact_name_match, compute_ref_match, compute_type_match, compute_type_match2, - RenderContext, + compute_exact_name_match, compute_function_type_match, compute_ref_match, RenderContext, }, CallableSnippets, }; @@ -85,47 +81,8 @@ fn render( .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db())) .map_or(false, |trait_| completion.is_ops_trait(trait_)); - // TODO next step figure out how to unify function typesk, we need to convert fndef to actual callable type - - let type_match = if let Some(ref t) = completion.expected_type { - if let Some(t) = t.as_callable(db) { - let (mut param_types_exp, ret_type_exp) = ( - t.params(db).into_iter().map(|(_, ty)| ty).collect::>(), - t.return_type(), - ); - - param_types_exp.push(ret_type_exp); - - let mut param_types = func - .ty(db) - .as_callable(db) - .unwrap() - .params(db) - .into_iter() - .map(|(_, ty)| ty) - .collect::>(); - param_types.push(ret_type.clone()); - - if param_types.len() != param_types_exp.len() { - None - } else { - if param_types_exp.iter().zip(param_types).all(|(expected_type, item_type)| { - compute_type_match2(completion, &expected_type, &item_type).is_some() - }) { - Some(CompletionRelevanceTypeMatch::CouldUnify) - } else { - None - } - } - } else { - None - } - } else { - None - }; - item.set_relevance(CompletionRelevance { - type_match, + type_match: compute_function_type_match(completion, &func), exact_name_match: compute_exact_name_match(completion, &call), is_op_method, ..ctx.completion_relevance()