From 003a6b74e469713e99f23d8cff58af95c10b29e0 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Sat, 2 Apr 2022 16:19:36 +0430 Subject: [PATCH] suggest infered type in auto complete --- crates/hir_def/src/item_tree/lower.rs | 10 +- crates/ide_completion/src/completions.rs | 20 +- crates/ide_completion/src/item.rs | 9 +- crates/ide_completion/src/lib.rs | 1 + crates/ide_completion/src/patterns.rs | 75 +++++++- crates/ide_completion/src/render.rs | 10 + crates/ide_completion/src/tests/type_pos.rs | 200 ++++++++++++++++++++ crates/rust-analyzer/src/to_proto.rs | 1 + 8 files changed, 312 insertions(+), 14 deletions(-) diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index 379e03504b2..b576815f877 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs @@ -302,9 +302,13 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option> let end_param = self.next_param_idx(); let params = IdxRange::new(start_param..end_param); - let ret_type = match func.ret_type().and_then(|rt| rt.ty()) { - Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), - _ => TypeRef::unit(), + let ret_type = match func.ret_type() { + Some(rt) => match rt.ty() { + Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), + 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() { diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index ab442fa64b1..7dbc5113954 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -21,12 +21,13 @@ use std::iter; -use hir::{db::HirDatabase, known, ScopeDef}; +use hir::{db::HirDatabase, known, HirDisplay, ScopeDef}; use ide_db::SymbolKind; use crate::{ context::Visible, item::Builder, + patterns::{ImmediateLocation, TypeAnnotation}, render::{ const_::render_const, function::{render_fn, render_method}, @@ -34,6 +35,7 @@ macro_::render_macro, pattern::{render_struct_pat, render_variant_pat}, render_field, render_resolution, render_resolution_simple, render_tuple_field, + render_type_inference, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, 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 +} diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 3c2be32f4e9..8c73bcaab2d 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs @@ -138,6 +138,8 @@ pub struct CompletionRelevance { pub is_private_editable: bool, /// Set for postfix snippet item completions pub postfix_match: Option, + /// This is setted for type inference results + pub is_definite: bool, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -198,6 +200,7 @@ pub fn score(self) -> u32 { is_op_method, is_private_editable, postfix_match, + is_definite, } = self; // lower rank private things @@ -225,7 +228,9 @@ pub fn score(self) -> u32 { if is_local { score += 1; } - + if is_definite { + score += 10; + } score } @@ -243,6 +248,7 @@ pub enum CompletionItemKind { SymbolKind(SymbolKind), Binding, BuiltinType, + InferredType, Keyword, Method, Snippet, @@ -284,6 +290,7 @@ pub(crate) fn tag(&self) -> &'static str { }, CompletionItemKind::Binding => "bn", CompletionItemKind::BuiltinType => "bt", + CompletionItemKind::InferredType => "it", CompletionItemKind::Keyword => "kw", CompletionItemKind::Method => "me", CompletionItemKind::Snippet => "sn", diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 8eb7615f8f2..6924ba1db6d 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -155,6 +155,7 @@ pub fn completions( completions::flyimport::import_on_the_fly(&mut acc, &ctx); completions::fn_param::complete_fn_param(&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::lifetime::complete_label(&mut acc, &ctx); completions::lifetime::complete_lifetime(&mut acc, &ctx); diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index 4536e3e6ee5..5fd9602b46f 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -8,7 +8,7 @@ use ide_db::RootDatabase; use syntax::{ algo::non_trivia_sibling, - ast::{self, HasArgList, HasLoopBody}, + ast::{self, HasArgList, HasLoopBody, HasName}, match_ast, AstNode, Direction, SyntaxElement, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -27,6 +27,14 @@ pub(crate) enum ImmediatePrevSibling { Attribute, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum TypeAnnotation { + Let(Option), + FnParam(Option), + RetType(Option), + Const(Option), +} + /// 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 @@ -44,6 +52,8 @@ pub(crate) enum ImmediateLocation { ItemList, TypeBound, Variant, + /// Original file ast node + TypeAnnotation(TypeAnnotation), /// Fake file ast node ModDeclaration(ast::Module), /// Original file ast node @@ -235,10 +245,7 @@ pub(crate) fn determine_location( } }, ast::FieldExpr(it) => { - let receiver = it - .expr() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file, r)); + let receiver = find_in_original_file(it.expr(), original_file); let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver { match l.kind() { ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), @@ -253,15 +260,65 @@ pub(crate) fn determine_location( } }, ast::MethodCallExpr(it) => ImmediateLocation::MethodCall { - receiver: it - .receiver() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file, r)), + receiver: find_in_original_file(it.receiver(), original_file), 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, } }; + fn find_in_original_file(x: Option, original_file: &SyntaxNode) -> Option { + x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) + } Some(res) } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index e5ec2732125..65375494569 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -170,6 +170,13 @@ pub(crate) fn render_resolution_with_import( 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_( ctx: RenderContext<'_>, local_name: hir::Name, @@ -620,6 +627,7 @@ fn main() { let _: m::Spam = S$0 } is_op_method: false, is_private_editable: false, postfix_match: None, + is_definite: false, }, }, CompletionItem { @@ -641,6 +649,7 @@ fn main() { let _: m::Spam = S$0 } is_op_method: false, is_private_editable: false, postfix_match: None, + is_definite: false, }, }, ] @@ -728,6 +737,7 @@ fn foo() { A { the$0 } } is_op_method: false, is_private_editable: false, postfix_match: None, + is_definite: false, }, }, ] diff --git a/crates/ide_completion/src/tests/type_pos.rs b/crates/ide_completion/src/tests/type_pos.rs index c8260f6e23c..e3ffb92f786 100644 --- a/crates/ide_completion/src/tests/type_pos.rs +++ b/crates/ide_completion/src/tests/type_pos.rs @@ -89,6 +89,206 @@ fn x<'lt, T, const C: usize>() -> $0 ); } +#[test] +fn inferred_type_const() { + check( + r#" +struct Foo(T); +const FOO: $0 = Foo(2); +"#, + expect![[r#" + it Foo + 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); + pub fn x() -> Foo> { + Foo(Foo(2)) + } +} +fn foo<'lt, T, const C: usize>() { + let local = (); + let foo: $0 = a::x(); +} +"#, + expect![[r#" + it a::Foo> + 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); +fn foo<'lt, T, const C: usize>() { + let local = (); + let foo: $0 = Foo(2); +} +"#, + expect![[r#" + it Foo + 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] fn body_type_pos() { check( diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 9b377f742b3..e52505d8a17 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -107,6 +107,7 @@ pub(crate) fn completion_item_kind( match completion_item_kind { CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE, CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT, + CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET, CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD, CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD, CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,