Implement function type matching
This commit is contained in:
parent
14a7a614c1
commit
6abba17a5b
@ -4567,8 +4567,8 @@ pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
|
||||
// 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,
|
||||
|
@ -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<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,
|
||||
@ -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<CompletionRelevanceTypeMatch> {
|
||||
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 []
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -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::<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()
|
||||
|
Loading…
Reference in New Issue
Block a user