From 9271941a950026836511bd1c85e15e26a480b824 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Jun 2021 15:21:18 +0200 Subject: [PATCH 1/2] Add MethodCall and FieldAccess variants to ImmediateLocation --- crates/ide_completion/src/completions/dot.rs | 7 ++- .../src/completions/flyimport.rs | 6 +-- .../ide_completion/src/completions/keyword.rs | 2 +- .../ide_completion/src/completions/postfix.rs | 17 ++++--- crates/ide_completion/src/context.rs | 51 +++++++------------ crates/ide_completion/src/patterns.rs | 45 ++++++++++++++-- crates/ide_completion/src/render/function.rs | 2 +- 7 files changed, 81 insertions(+), 49 deletions(-) diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index 302c9ccbd36..e0a7021fd3e 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs @@ -8,7 +8,7 @@ /// Complete dot accesses, i.e. fields or methods. pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { - let dot_receiver = match &ctx.dot_receiver { + let dot_receiver = match ctx.dot_receiver() { Some(expr) => expr, _ => return complete_undotted_self(acc, ctx), }; @@ -30,7 +30,10 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { } fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path || !ctx.config.enable_self_on_the_fly { + if !ctx.config.enable_self_on_the_fly { + return; + } + if !ctx.is_trivial_path || ctx.is_path_disallowed() { return; } ctx.scope.process_all_names(&mut |name, def| { diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index df27e7a848a..d72bf13d31a 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs @@ -162,19 +162,19 @@ pub(crate) fn position_for_import<'a>( Some(match import_candidate { Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(), Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(), - Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(), + Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(), None => ctx .name_ref_syntax .as_ref() .map(|name_ref| name_ref.syntax()) .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax())) - .or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?, + .or_else(|| ctx.dot_receiver().map(|expr| expr.syntax()))?, }) } fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option { let current_module = ctx.scope.module()?; - if let Some(dot_receiver) = &ctx.dot_receiver { + if let Some(dot_receiver) = ctx.dot_receiver() { ImportAssets::for_fuzzy_method_call( current_module, ctx.sema.type_of_expr(dot_receiver)?, diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 0d035c61151..1a7a484a4cd 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs @@ -31,7 +31,7 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC } // Suggest .await syntax for types that implement Future trait - if let Some(receiver) = &ctx.dot_receiver { + if let Some(receiver) = ctx.dot_receiver() { if let Some(ty) = ctx.sema.type_of_expr(receiver) { if ty.impls_future(ctx.db) { let mut item = kw_completion("await"); diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs index 962aaf0df36..86bbb58e266 100644 --- a/crates/ide_completion/src/completions/postfix.rs +++ b/crates/ide_completion/src/completions/postfix.rs @@ -14,6 +14,7 @@ completions::postfix::format_like::add_format_like_completions, context::CompletionContext, item::{Builder, CompletionKind}, + patterns::ImmediateLocation, CompletionItem, CompletionItemKind, Completions, }; @@ -22,13 +23,16 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { return; } - let dot_receiver = match &ctx.dot_receiver { - Some(it) => it, - None => return, + let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location { + Some(ImmediateLocation::MethodCall { receiver: Some(it) }) => (it, false), + Some(ImmediateLocation::FieldAccess { + receiver: Some(it), + receiver_is_ambiguous_float_literal, + }) => (it, *receiver_is_ambiguous_float_literal), + _ => return, }; - let receiver_text = - get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); + let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal); let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { Some(it) => it, @@ -123,8 +127,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { // The rest of the postfix completions create an expression that moves an argument, // so it's better to consider references now to avoid breaking the compilation let dot_receiver = include_references(dot_receiver); - let receiver_text = - get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); + let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal); match try_enum { Some(try_enum) => match try_enum { diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 7c46c815d2b..eeb4333f851 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -80,9 +80,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_expr: bool, /// Something is typed at the "top" level, in module or impl/trait. pub(super) is_new_item: bool, - /// The receiver if this is a field or method access, i.e. writing something.$0 - pub(super) dot_receiver: Option, - pub(super) dot_receiver_is_ambiguous_float_literal: bool, /// If this is a call (method or function) in particular, i.e. the () are already there. pub(super) is_call: bool, /// Like `is_call`, but for tuple patterns. @@ -159,8 +156,6 @@ pub(super) fn new( can_be_stmt: false, is_expr: false, is_new_item: false, - dot_receiver: None, - dot_receiver_is_ambiguous_float_literal: false, is_call: false, is_pattern_call: false, is_macro_call: false, @@ -255,6 +250,22 @@ pub(crate) fn expects_assoc_item(&self) -> bool { ) } + pub(crate) fn has_dot_receiver(&self) -> bool { + matches!( + &self.completion_location, + Some(ImmediateLocation::FieldAccess { receiver, .. }) | Some(ImmediateLocation::MethodCall { receiver }) + if receiver.is_some() + ) + } + + pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { + match &self.completion_location { + Some(ImmediateLocation::MethodCall { receiver }) + | Some(ImmediateLocation::FieldAccess { receiver, .. }) => receiver.as_ref(), + _ => None, + } + } + pub(crate) fn expects_use_tree(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::Use)) } @@ -267,6 +278,7 @@ pub(crate) fn expects_item(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::ItemList)) } + // fn expects_value(&self) -> bool { pub(crate) fn expects_expression(&self) -> bool { self.is_expr } @@ -623,33 +635,8 @@ fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameR .unwrap_or(false); self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); } - - if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { - // The receiver comes before the point of insertion of the fake - // ident, so it should have the same range in the non-modified file - self.dot_receiver = field_expr - .expr() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file, r)); - self.dot_receiver_is_ambiguous_float_literal = - if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { - match l.kind() { - ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), - _ => false, - } - } else { - false - }; - } - - if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { - // As above - self.dot_receiver = method_call_expr - .receiver() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file, r)); - self.is_call = true; - } + self.is_call |= + matches!(self.completion_location, Some(ImmediateLocation::MethodCall { .. })); } } diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index 26516046bcf..bf3a3f61ebb 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -7,7 +7,7 @@ ast::{self, LoopBodyOwner}, match_ast, AstNode, Direction, SyntaxElement, SyntaxKind::*, - SyntaxNode, SyntaxToken, TextSize, T, + SyntaxNode, SyntaxToken, TextRange, TextSize, T, }; #[cfg(test)] @@ -37,6 +37,15 @@ pub(crate) enum ImmediateLocation { // Fake file ast node ModDeclaration(ast::Module), // Original file ast node + MethodCall { + receiver: Option, + }, + // Original file ast node + FieldAccess { + receiver: Option, + receiver_is_ambiguous_float_literal: bool, + }, + // Original file ast node /// The record expr of the field name we are completing RecordExpr(ast::RecordExpr), // Original file ast node @@ -164,12 +173,38 @@ pub(crate) fn determine_location( Some(TRAIT) => ImmediateLocation::Trait, _ => return None, }, - ast::Module(it) => if it.item_list().is_none() { + ast::Module(it) => { + if it.item_list().is_none() { ImmediateLocation::ModDeclaration(it) } else { - return None + return None; + } }, ast::Attr(it) => ImmediateLocation::Attribute(it), + 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_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver { + match l.kind() { + ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), + _ => false, + } + } else { + false + }; + ImmediateLocation::FieldAccess { + receiver, + receiver_is_ambiguous_float_literal, + } + }, + ast::MethodCallExpr(it) => ImmediateLocation::MethodCall { + receiver: it + .receiver() + .map(|e| e.syntax().text_range()) + .and_then(|r| find_node_with_range(original_file, r)), + }, _ => return None, } }; @@ -194,6 +229,10 @@ fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { name_ref.syntax().clone() } +fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { + syntax.covering_element(range).ancestors().find_map(N::cast) +} + pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`, // where we only check the first parent with different text range. diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index 3ec77ca0f4a..1abeed96d7d 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs @@ -154,7 +154,7 @@ fn params(&self) -> Params { }; let mut params_pats = Vec::new(); - let params_ty = if self.ctx.completion.dot_receiver.is_some() || self.receiver.is_some() { + let params_ty = if self.ctx.completion.has_dot_receiver() || self.receiver.is_some() { self.func.method_params(self.ctx.db()).unwrap_or_default() } else { if let Some(s) = ast_params.self_param() { From 76fd1b316f38b59991316d5b97582c0203728738 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Jun 2021 15:25:02 +0200 Subject: [PATCH 2/2] Remove obsolete is_new_item field on CompletionContext --- .../src/completions/macro_in_item_position.rs | 2 +- crates/ide_completion/src/completions/snippet.rs | 2 +- crates/ide_completion/src/context.rs | 14 +------------- crates/ide_completion/src/patterns.rs | 4 ++-- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/ide_completion/src/completions/macro_in_item_position.rs b/crates/ide_completion/src/completions/macro_in_item_position.rs index 202e71215a4..781b96ff185 100644 --- a/crates/ide_completion/src/completions/macro_in_item_position.rs +++ b/crates/ide_completion/src/completions/macro_in_item_position.rs @@ -5,7 +5,7 @@ // Ideally this should be removed and moved into `(un)qualified_path` respectively pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { // Show only macros in top level. - if !ctx.is_new_item { + if !ctx.expects_item() { return; } diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs index defc25b0089..6e6a6eb92ef 100644 --- a/crates/ide_completion/src/completions/snippet.rs +++ b/crates/ide_completion/src/completions/snippet.rs @@ -29,7 +29,7 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte } pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_new_item { + if !ctx.expects_item() { return; } let cap = match ctx.config.snippet_cap { diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index eeb4333f851..6f685c02f38 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -78,8 +78,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) can_be_stmt: bool, /// `true` if we expect an expression at the cursor position. pub(super) is_expr: bool, - /// Something is typed at the "top" level, in module or impl/trait. - pub(super) is_new_item: bool, /// If this is a call (method or function) in particular, i.e. the () are already there. pub(super) is_call: bool, /// Like `is_call`, but for tuple patterns. @@ -155,7 +153,6 @@ pub(super) fn new( path_qual: None, can_be_stmt: false, is_expr: false, - is_new_item: false, is_call: false, is_pattern_call: false, is_macro_call: false, @@ -552,16 +549,7 @@ fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameR self.name_ref_syntax = find_node_at_offset(original_file, name_ref.syntax().text_range().start()); - let name_range = name_ref.syntax().text_range(); - let top_node = name_ref - .syntax() - .ancestors() - .take_while(|it| it.text_range() == name_range) - .last() - .unwrap(); - - if matches!(top_node.parent().map(|it| it.kind()), Some(SOURCE_FILE) | Some(ITEM_LIST)) { - self.is_new_item = true; + if matches!(self.completion_location, Some(ImmediateLocation::ItemList)) { return; } diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index bf3a3f61ebb..080898aef00 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -13,7 +13,7 @@ #[cfg(test)] use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; -/// Direct parent container of the cursor position +/// Immediate previous node to what we are completing. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediatePrevSibling { IfExpr, @@ -21,7 +21,7 @@ pub(crate) enum ImmediatePrevSibling { ImplDefType, } -/// Direct parent container of the cursor position +/// Direct parent "thing" of what we are currently completing. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediateLocation { Use,