11879: Suggest infered type in auto complete r=HKalbasi a=HKalbasi

fix #11855

It doesn't work for return types and consts (so their tests are failing) because I can't find their body node in the original file. (Are these original and fake file documented somewhere?)

Also it currently needs to type first character of the type (or manual ctrl+space) to open the auto complete panel, is it possible to open it automatically on typing `:` and `->`?


Co-authored-by: hkalbasi <hamidrezakalbasi@protonmail.com>
This commit is contained in:
bors[bot] 2022-04-03 07:05:41 +00:00 committed by GitHub
commit 4204e35563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 312 additions and 14 deletions

View File

@ -302,9 +302,13 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>>
let end_param = self.next_param_idx(); let end_param = self.next_param_idx();
let params = IdxRange::new(start_param..end_param); let params = IdxRange::new(start_param..end_param);
let ret_type = match func.ret_type().and_then(|rt| rt.ty()) { let ret_type = match func.ret_type() {
Some(rt) => match rt.ty() {
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
_ => TypeRef::unit(), None if rt.thin_arrow_token().is_some() => TypeRef::Error,
None => TypeRef::unit(),
},
None => TypeRef::unit(),
}; };
let (ret_type, async_ret_type) = if func.async_token().is_some() { let (ret_type, async_ret_type) = if func.async_token().is_some() {

View File

@ -21,12 +21,13 @@
use std::iter; use std::iter;
use hir::{db::HirDatabase, known, ScopeDef}; use hir::{db::HirDatabase, known, HirDisplay, ScopeDef};
use ide_db::SymbolKind; use ide_db::SymbolKind;
use crate::{ use crate::{
context::Visible, context::Visible,
item::Builder, item::Builder,
patterns::{ImmediateLocation, TypeAnnotation},
render::{ render::{
const_::render_const, const_::render_const,
function::{render_fn, render_method}, function::{render_fn, render_method},
@ -34,6 +35,7 @@
macro_::render_macro, macro_::render_macro,
pattern::{render_struct_pat, render_variant_pat}, pattern::{render_struct_pat, render_variant_pat},
render_field, render_resolution, render_resolution_simple, render_tuple_field, render_field, render_resolution, render_resolution_simple, render_tuple_field,
render_type_inference,
type_alias::{render_type_alias, render_type_alias_with_eq}, type_alias::{render_type_alias, render_type_alias_with_eq},
union_literal::render_union_literal, union_literal::render_union_literal,
RenderContext, RenderContext,
@ -374,3 +376,19 @@ fn enum_variants_with_paths(
} }
} }
} }
pub(crate) fn inferred_type(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
use TypeAnnotation::*;
let pat = match &ctx.completion_location {
Some(ImmediateLocation::TypeAnnotation(t)) => t,
_ => return None,
};
let x = match pat {
Let(pat) | FnParam(pat) => ctx.sema.type_of_pat(pat.as_ref()?),
Const(exp) | RetType(exp) => ctx.sema.type_of_expr(exp.as_ref()?),
}?
.adjusted();
let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?;
acc.add(render_type_inference(ty_string, ctx));
None
}

View File

@ -138,6 +138,8 @@ pub struct CompletionRelevance {
pub is_private_editable: bool, pub is_private_editable: bool,
/// Set for postfix snippet item completions /// Set for postfix snippet item completions
pub postfix_match: Option<CompletionRelevancePostfixMatch>, pub postfix_match: Option<CompletionRelevancePostfixMatch>,
/// This is setted for type inference results
pub is_definite: bool,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
@ -198,6 +200,7 @@ pub fn score(self) -> u32 {
is_op_method, is_op_method,
is_private_editable, is_private_editable,
postfix_match, postfix_match,
is_definite,
} = self; } = self;
// lower rank private things // lower rank private things
@ -225,7 +228,9 @@ pub fn score(self) -> u32 {
if is_local { if is_local {
score += 1; score += 1;
} }
if is_definite {
score += 10;
}
score score
} }
@ -243,6 +248,7 @@ pub enum CompletionItemKind {
SymbolKind(SymbolKind), SymbolKind(SymbolKind),
Binding, Binding,
BuiltinType, BuiltinType,
InferredType,
Keyword, Keyword,
Method, Method,
Snippet, Snippet,
@ -284,6 +290,7 @@ pub(crate) fn tag(&self) -> &'static str {
}, },
CompletionItemKind::Binding => "bn", CompletionItemKind::Binding => "bn",
CompletionItemKind::BuiltinType => "bt", CompletionItemKind::BuiltinType => "bt",
CompletionItemKind::InferredType => "it",
CompletionItemKind::Keyword => "kw", CompletionItemKind::Keyword => "kw",
CompletionItemKind::Method => "me", CompletionItemKind::Method => "me",
CompletionItemKind::Snippet => "sn", CompletionItemKind::Snippet => "sn",

View File

@ -155,6 +155,7 @@ pub fn completions(
completions::flyimport::import_on_the_fly(&mut acc, &ctx); completions::flyimport::import_on_the_fly(&mut acc, &ctx);
completions::fn_param::complete_fn_param(&mut acc, &ctx); completions::fn_param::complete_fn_param(&mut acc, &ctx);
completions::format_string::format_string(&mut acc, &ctx); completions::format_string::format_string(&mut acc, &ctx);
completions::inferred_type(&mut acc, &ctx);
completions::keyword::complete_expr_keyword(&mut acc, &ctx); completions::keyword::complete_expr_keyword(&mut acc, &ctx);
completions::lifetime::complete_label(&mut acc, &ctx); completions::lifetime::complete_label(&mut acc, &ctx);
completions::lifetime::complete_lifetime(&mut acc, &ctx); completions::lifetime::complete_lifetime(&mut acc, &ctx);

View File

@ -8,7 +8,7 @@
use ide_db::RootDatabase; use ide_db::RootDatabase;
use syntax::{ use syntax::{
algo::non_trivia_sibling, algo::non_trivia_sibling,
ast::{self, HasArgList, HasLoopBody}, ast::{self, HasArgList, HasLoopBody, HasName},
match_ast, AstNode, Direction, SyntaxElement, match_ast, AstNode, Direction, SyntaxElement,
SyntaxKind::*, SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize, SyntaxNode, SyntaxToken, TextRange, TextSize,
@ -27,6 +27,14 @@ pub(crate) enum ImmediatePrevSibling {
Attribute, Attribute,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeAnnotation {
Let(Option<ast::Pat>),
FnParam(Option<ast::Pat>),
RetType(Option<ast::Expr>),
Const(Option<ast::Expr>),
}
/// Direct parent "thing" of what we are currently completing. /// Direct parent "thing" of what we are currently completing.
/// ///
/// This may contain nodes of the fake file as well as the original, comments on the variants specify /// This may contain nodes of the fake file as well as the original, comments on the variants specify
@ -44,6 +52,8 @@ pub(crate) enum ImmediateLocation {
ItemList, ItemList,
TypeBound, TypeBound,
Variant, Variant,
/// Original file ast node
TypeAnnotation(TypeAnnotation),
/// Fake file ast node /// Fake file ast node
ModDeclaration(ast::Module), ModDeclaration(ast::Module),
/// Original file ast node /// Original file ast node
@ -235,10 +245,7 @@ pub(crate) fn determine_location(
} }
}, },
ast::FieldExpr(it) => { ast::FieldExpr(it) => {
let receiver = it let receiver = find_in_original_file(it.expr(), original_file);
.expr()
.map(|e| e.syntax().text_range())
.and_then(|r| find_node_with_range(original_file, r));
let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver { let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver {
match l.kind() { match l.kind() {
ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
@ -253,15 +260,65 @@ pub(crate) fn determine_location(
} }
}, },
ast::MethodCallExpr(it) => ImmediateLocation::MethodCall { ast::MethodCallExpr(it) => ImmediateLocation::MethodCall {
receiver: it receiver: find_in_original_file(it.receiver(), original_file),
.receiver()
.map(|e| e.syntax().text_range())
.and_then(|r| find_node_with_range(original_file, r)),
has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some()) has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some())
}, },
ast::Const(it) => {
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
return None;
}
let name = find_in_original_file(it.name(), original_file)?;
let original = ast::Const::cast(name.syntax().parent()?)?;
ImmediateLocation::TypeAnnotation(TypeAnnotation::Const(original.body()))
},
ast::RetType(it) => {
if it.thin_arrow_token().is_none() {
return None;
}
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
return None;
}
let parent = match ast::Fn::cast(parent.parent()?) {
Some(x) => x.param_list(),
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
};
let parent = find_in_original_file(parent, original_file)?.syntax().parent()?;
ImmediateLocation::TypeAnnotation(TypeAnnotation::RetType(match_ast! {
match parent {
ast::ClosureExpr(it) => {
it.body()
},
ast::Fn(it) => {
it.body().map(ast::Expr::BlockExpr)
},
_ => return None,
}
}))
},
ast::Param(it) => {
if it.colon_token().is_none() {
return None;
}
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
return None;
}
ImmediateLocation::TypeAnnotation(TypeAnnotation::FnParam(find_in_original_file(it.pat(), original_file)))
},
ast::LetStmt(it) => {
if it.colon_token().is_none() {
return None;
}
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
return None;
}
ImmediateLocation::TypeAnnotation(TypeAnnotation::Let(find_in_original_file(it.pat(), original_file)))
},
_ => return None, _ => return None,
} }
}; };
fn find_in_original_file<N: AstNode>(x: Option<N>, original_file: &SyntaxNode) -> Option<N> {
x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r))
}
Some(res) Some(res)
} }

View File

@ -170,6 +170,13 @@ pub(crate) fn render_resolution_with_import(
Some(render_resolution_(ctx, local_name, Some(import_edit), resolution)) Some(render_resolution_(ctx, local_name, Some(import_edit), resolution))
} }
pub(crate) fn render_type_inference(ty_string: String, ctx: &CompletionContext) -> CompletionItem {
let mut builder =
CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string);
builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() });
builder.build()
}
fn render_resolution_( fn render_resolution_(
ctx: RenderContext<'_>, ctx: RenderContext<'_>,
local_name: hir::Name, local_name: hir::Name,
@ -620,6 +627,7 @@ fn main() { let _: m::Spam = S$0 }
is_op_method: false, is_op_method: false,
is_private_editable: false, is_private_editable: false,
postfix_match: None, postfix_match: None,
is_definite: false,
}, },
}, },
CompletionItem { CompletionItem {
@ -641,6 +649,7 @@ fn main() { let _: m::Spam = S$0 }
is_op_method: false, is_op_method: false,
is_private_editable: false, is_private_editable: false,
postfix_match: None, postfix_match: None,
is_definite: false,
}, },
}, },
] ]
@ -728,6 +737,7 @@ fn foo() { A { the$0 } }
is_op_method: false, is_op_method: false,
is_private_editable: false, is_private_editable: false,
postfix_match: None, postfix_match: None,
is_definite: false,
}, },
}, },
] ]

View File

@ -89,6 +89,206 @@ fn x<'lt, T, const C: usize>() -> $0
); );
} }
#[test]
fn inferred_type_const() {
check(
r#"
struct Foo<T>(T);
const FOO: $0 = Foo(2);
"#,
expect![[r#"
it Foo<i32>
kw self
kw super
kw crate
tt Trait
en Enum
st Record
st Tuple
md module
st Foo<>
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test]
fn inferred_type_closure_param() {
check(
r#"
fn f1(f: fn(i32) -> i32) {}
fn f2() {
f1(|x: $0);
}
"#,
expect![[r#"
it i32
kw self
kw super
kw crate
tt Trait
en Enum
st Record
st Tuple
md module
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test]
fn inferred_type_closure_return() {
check(
r#"
fn f1(f: fn(u64) -> u64) {}
fn f2() {
f1(|x| -> $0 {
x + 5
});
}
"#,
expect![[r#"
it u64
kw self
kw super
kw crate
tt Trait
en Enum
st Record
st Tuple
md module
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test]
fn inferred_type_fn_return() {
check(
r#"
fn f2(x: u64) -> $0 {
x + 5
}
"#,
expect![[r#"
it u64
kw self
kw super
kw crate
tt Trait
en Enum
st Record
st Tuple
md module
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test]
fn inferred_type_fn_param() {
check(
r#"
fn f1(x: i32) {}
fn f2(x: $0) {
f1(x);
}
"#,
expect![[r#"
it i32
kw self
kw super
kw crate
tt Trait
en Enum
st Record
st Tuple
md module
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test]
fn inferred_type_not_in_the_scope() {
check(
r#"
mod a {
pub struct Foo<T>(T);
pub fn x() -> Foo<Foo<i32>> {
Foo(Foo(2))
}
}
fn foo<'lt, T, const C: usize>() {
let local = ();
let foo: $0 = a::x();
}
"#,
expect![[r#"
it a::Foo<a::Foo<i32>>
kw self
kw super
kw crate
tp T
tt Trait
en Enum
st Record
st Tuple
md module
st Unit
ma makro!() macro_rules! makro
un Union
md a
bt u32
"#]],
);
}
#[test]
fn inferred_type_let() {
check(
r#"
struct Foo<T>(T);
fn foo<'lt, T, const C: usize>() {
let local = ();
let foo: $0 = Foo(2);
}
"#,
expect![[r#"
it Foo<i32>
kw self
kw super
kw crate
tp T
tt Trait
en Enum
st Record
st Tuple
md module
st Foo<>
st Unit
ma makro!() macro_rules! makro
un Union
bt u32
"#]],
);
}
#[test] #[test]
fn body_type_pos() { fn body_type_pos() {
check( check(

View File

@ -107,6 +107,7 @@ pub(crate) fn completion_item_kind(
match completion_item_kind { match completion_item_kind {
CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE, CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT, CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD, CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD, CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD,
CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET, CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,