Skip to content

Commit

Permalink
Implement function type matching
Browse files Browse the repository at this point in the history
  • Loading branch information
jmintb committed Nov 4, 2023
1 parent d2f4e0e commit 636fe55
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 102 deletions.
4 changes: 2 additions & 2 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4414,8 +4414,8 @@ impl Type {
// 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,
Expand Down
155 changes: 102 additions & 53 deletions crates/ide-completion/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ pub(crate) mod variant;
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,
Expand Down Expand Up @@ -359,17 +357,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(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
Expand Down Expand Up @@ -466,6 +461,20 @@ fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> boo
}
}

fn match_types(
ctx: &CompletionContext<'_>,
ty1: &hir::Type,
ty2: &hir::Type,
) -> Option<CompletionRelevanceTypeMatch> {
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,
Expand All @@ -478,35 +487,54 @@ 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<CompletionRelevanceTypeMatch> {
let expected_type = completion_ty1;

// 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() {
// 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.

// Note that the self parameter is included here if present.
let expected_callable = ctx.expected_type.as_ref()?.as_callable(ctx.db)?;
let mut expected_types: Vec<Type> =
expected_callable.params(ctx.db).into_iter().map(|param| param.1).collect();
expected_types.push(expected_callable.return_type());

// It is important that we include the self parameter as it will be included in the expected_types
// vec also the type matching would be incomplete without.
let mut actual_types: Vec<Type> = func
.ty(ctx.db)
.as_callable(ctx.db)?
.params(ctx.db)
.into_iter()
.map(|param| param.1)
.collect();
actual_types.push(func.ret_type(ctx.db));

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
.iter()
.zip(actual_types.iter())
.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 {
Expand Down Expand Up @@ -756,7 +784,7 @@ fn main() {
);
}

// TODO: does this test even make sense?
// TODO: How dowe test ModuleDef::Variant(Variant?)
#[test]
fn set_enum_variant_type_completion_info() {
check_relevance(
Expand All @@ -780,7 +808,7 @@ pub mod test_mod_a {
fn test(input: dep::test_mod_b::Enum) { }
fn main() {
test(Enum$0);
test(Enum::Variant$0);
}
"#,
expect![[r#"
Expand Down Expand Up @@ -819,7 +847,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 []
Expand All @@ -828,7 +856,6 @@ fn main() {
);
}

// TODO This test does not trigger the const case
#[test]
fn set_const_type_completion_info() {
check_relevance(
Expand Down Expand Up @@ -893,8 +920,38 @@ fn main() {
);
}

// 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(
Expand Down Expand Up @@ -924,34 +981,26 @@ fn func(input: Struct) { }
);
}

// TODO: how do we actually test builtins?

#[test]
fn set_builtin_type_completion_info() {
check_relevance(
r#"
//- /lib.rs crate:dep
pub mod test_mod_b {
static STATIC: i32 = 5;
}
//- /main.rs crate:main
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 []
"#]],
);
}
Expand Down
51 changes: 4 additions & 47 deletions crates/ide-completion/src/render/function.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
//! 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};
use syntax::{AstNode, SmolStr};

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,
};
Expand Down Expand Up @@ -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::<Vec<Type>>(),
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::<Vec<Type>>();
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()
Expand Down

0 comments on commit 636fe55

Please sign in to comment.