From 6a045c70296043ce2d00a8c0d7b0ee9264ff4aba Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 7 May 2022 13:46:43 +0200 Subject: [PATCH 1/4] Introduce NameRefContext --- .../src/completions/attribute.rs | 4 +- .../src/completions/attribute/derive.rs | 9 +- crates/ide-completion/src/completions/dot.rs | 10 +- crates/ide-completion/src/completions/expr.rs | 2 +- .../src/completions/flyimport.rs | 2 +- .../src/completions/item_list.rs | 2 +- .../ide-completion/src/completions/keyword.rs | 4 +- .../ide-completion/src/completions/pattern.rs | 2 +- .../ide-completion/src/completions/postfix.rs | 17 ++- .../ide-completion/src/completions/snippet.rs | 4 +- crates/ide-completion/src/completions/type.rs | 2 +- crates/ide-completion/src/completions/use_.rs | 2 +- crates/ide-completion/src/completions/vis.rs | 2 +- crates/ide-completion/src/context.rs | 139 +++++++++++++----- crates/ide-completion/src/patterns.rs | 31 +--- crates/ide-completion/src/render.rs | 2 +- crates/ide-completion/src/render/function.rs | 9 +- crates/ide-completion/src/render/literal.rs | 2 +- crates/ide-completion/src/render/macro_.rs | 4 +- crates/ide-completion/src/render/pattern.rs | 2 +- 20 files changed, 145 insertions(+), 106 deletions(-) diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index fef177d2dd0..ec95021ec02 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -69,8 +69,8 @@ pub(crate) fn complete_known_attribute_input( } pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { - let (is_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context { - Some(PathCompletionCtx { + let (is_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context() { + Some(&PathCompletionCtx { kind: PathKind::Attr { kind, annotated_item_kind }, is_absolute_path, ref qualifier, diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs index d827d781a32..19414e4b315 100644 --- a/crates/ide-completion/src/completions/attribute/derive.rs +++ b/crates/ide-completion/src/completions/attribute/derive.rs @@ -11,9 +11,12 @@ use crate::{ }; pub(crate) fn complete_derive(acc: &mut Completions, ctx: &CompletionContext) { - let (qualifier, is_absolute_path) = match ctx.path_context { - Some(PathCompletionCtx { - kind: PathKind::Derive, ref qualifier, is_absolute_path, .. + let (qualifier, is_absolute_path) = match ctx.path_context() { + Some(&PathCompletionCtx { + kind: PathKind::Derive, + ref qualifier, + is_absolute_path, + .. }) => (qualifier, is_absolute_path), _ => return, }; diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 33af76c7100..af0f38a3d8d 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -3,8 +3,7 @@ use ide_db::FxHashSet; use crate::{ - context::{CompletionContext, PathCompletionCtx, PathKind}, - patterns::ImmediateLocation, + context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind}, Completions, }; @@ -20,7 +19,10 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { _ => return, }; - if matches!(ctx.completion_location, Some(ImmediateLocation::MethodCall { .. })) { + if matches!( + ctx.nameref_ctx, + Some(NameRefContext { dot_access: Some(DotAccess::Method { .. }), .. }), + ) { cov_mark::hit!(test_no_struct_field_completion_for_method_call); } else { complete_fields( @@ -38,7 +40,7 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { if !ctx.config.enable_self_on_the_fly { return; } - match ctx.path_context { + match ctx.path_context() { Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index b7e2b886574..a2a17d92185 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -14,7 +14,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) return; } - let (&is_absolute_path, qualifier) = match &ctx.path_context { + let (&is_absolute_path, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Expr { .. }, is_absolute_path, diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 7a90126916f..bfa4c06f922 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -119,7 +119,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) return None; } // FIXME: This should be encoded in a different way - if ctx.pattern_ctx.is_none() && ctx.path_context.is_none() && !ctx.has_dot_receiver() { + if ctx.pattern_ctx.is_none() && ctx.path_context().is_none() && !ctx.has_dot_receiver() { // completion inside `ast::Name` of a item declaration return None; } diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index df03120dfe1..ebbc33c2da0 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -12,7 +12,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) return; } - let (&is_absolute_path, qualifier) = match &ctx.path_context { + let (&is_absolute_path, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Item { .. }, is_absolute_path, diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index 557992d14a9..766ab4fcd7d 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -124,8 +124,8 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte add_keyword("mut", "mut "); } - let (can_be_stmt, in_loop_body) = match ctx.path_context { - Some(PathCompletionCtx { + let (can_be_stmt, in_loop_body) = match ctx.path_context() { + Some(&PathCompletionCtx { is_absolute_path: false, kind: PathKind::Expr { in_block_expr, in_loop_body, .. }, .. diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs index 963ee309590..211ca4e531b 100644 --- a/crates/ide-completion/src/completions/pattern.rs +++ b/crates/ide-completion/src/completions/pattern.rs @@ -17,7 +17,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { }; let refutable = patctx.refutability == PatternRefutability::Refutable; - if let Some(path_ctx) = &ctx.path_context { + if let Some(path_ctx) = ctx.path_context() { pattern_path_completion(acc, ctx, path_ctx); return; } diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index cc2a4437000..ef765a345a5 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -13,9 +13,8 @@ use text_edit::TextEdit; use crate::{ completions::postfix::format_like::add_format_like_completions, - context::CompletionContext, + context::{CompletionContext, DotAccess, NameRefContext}, item::{Builder, CompletionRelevancePostfixMatch}, - patterns::ImmediateLocation, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope, }; @@ -24,11 +23,15 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 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, + let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.nameref_ctx { + Some(NameRefContext { + dot_access: Some(DotAccess::Method { receiver: Some(it), .. }), + .. + }) => (it, false), + Some(NameRefContext { + dot_access: + Some(DotAccess::Field { receiver: Some(it), receiver_is_ambiguous_float_literal }), + .. }) => (it, *receiver_is_ambiguous_float_literal), _ => return, }; diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs index 38136db4a9f..2bae19c84fd 100644 --- a/crates/ide-completion/src/completions/snippet.rs +++ b/crates/ide-completion/src/completions/snippet.rs @@ -17,7 +17,7 @@ fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) } pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { - let can_be_stmt = match ctx.path_context { + let &can_be_stmt = match ctx.path_context() { Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, @@ -43,7 +43,7 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte } pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { - let path_kind = match ctx.path_context { + let path_kind = match ctx.path_context() { Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index a8d71fd8688..9381548e5ed 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -17,7 +17,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) return; } - let (&is_absolute_path, qualifier) = match &ctx.path_context { + let (&is_absolute_path, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Type, is_absolute_path, qualifier, .. }) => { (is_absolute_path, qualifier) } diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index a8d301da17f..d52a348eb87 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -11,7 +11,7 @@ use crate::{ }; pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { - let (&is_absolute_path, qualifier) = match &ctx.path_context { + let (&is_absolute_path, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }) => { (is_absolute_path, qualifier) } diff --git a/crates/ide-completion/src/completions/vis.rs b/crates/ide-completion/src/completions/vis.rs index 50560c99923..b5e86b62d1a 100644 --- a/crates/ide-completion/src/completions/vis.rs +++ b/crates/ide-completion/src/completions/vis.rs @@ -8,7 +8,7 @@ use crate::{ }; pub(crate) fn complete_vis(acc: &mut Completions, ctx: &CompletionContext) { - let (&is_absolute_path, qualifier, &has_in_token) = match &ctx.path_context { + let (&is_absolute_path, qualifier, &has_in_token) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Vis { has_in_token }, is_absolute_path, diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 802e48f23a6..e6b41ffbd45 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -14,7 +14,7 @@ use ide_db::{ }; use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, - ast::{self, AttrKind, HasName, NameOrNameRef}, + ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef}, match_ast, AstNode, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextSize, T, @@ -148,6 +148,26 @@ pub(super) enum NameContext { Variant, } +#[derive(Debug)] +pub(super) struct NameRefContext { + pub(super) dot_access: Option, + pub(super) path_ctx: Option, +} + +#[derive(Debug)] +pub(super) enum DotAccess { + Field { + receiver: Option, + /// True if the receiver is an integer and there is no ident in the original file after it yet + /// like `0.$0` + receiver_is_ambiguous_float_literal: bool, + }, + Method { + receiver: Option, + has_parens: bool, + }, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ParamKind { Function(ast::Fn), @@ -195,8 +215,8 @@ pub(crate) struct CompletionContext<'a> { pub(super) name_ctx: Option, pub(super) lifetime_ctx: Option, + pub(super) nameref_ctx: Option, pub(super) pattern_ctx: Option, - pub(super) path_context: Option, pub(super) existing_derives: FxHashSet, @@ -237,21 +257,18 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { - match &self.completion_location { - Some( - ImmediateLocation::MethodCall { receiver, .. } - | ImmediateLocation::FieldAccess { receiver, .. }, - ) => receiver.as_ref(), + match &self.nameref_ctx { + Some(NameRefContext { + dot_access: + Some(DotAccess::Method { receiver, .. } | DotAccess::Field { receiver, .. }), + .. + }) => receiver.as_ref(), _ => None, } } pub(crate) fn has_dot_receiver(&self) -> bool { - matches!( - &self.completion_location, - Some(ImmediateLocation::FieldAccess { receiver, .. } | ImmediateLocation::MethodCall { receiver,.. }) - if receiver.is_some() - ) + self.dot_receiver().is_some() } pub(crate) fn expects_assoc_item(&self) -> bool { @@ -324,21 +341,25 @@ impl<'a> CompletionContext<'a> { || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename)) } + pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { + self.nameref_ctx.as_ref().and_then(|ctx| ctx.path_ctx.as_ref()) + } + pub(crate) fn expects_expression(&self) -> bool { - matches!(self.path_context, Some(PathCompletionCtx { kind: PathKind::Expr { .. }, .. })) + matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Expr { .. }, .. })) } pub(crate) fn expects_type(&self) -> bool { - matches!(self.path_context, Some(PathCompletionCtx { kind: PathKind::Type, .. })) + matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Type, .. })) } pub(crate) fn path_is_call(&self) -> bool { - self.path_context.as_ref().map_or(false, |it| it.has_call_parens) + self.path_context().map_or(false, |it| it.has_call_parens) } pub(crate) fn is_non_trivial_path(&self) -> bool { matches!( - self.path_context, + self.path_context(), Some( PathCompletionCtx { is_absolute_path: true, .. } | PathCompletionCtx { qualifier: Some(_), .. } @@ -347,11 +368,11 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn path_qual(&self) -> Option<&ast::Path> { - self.path_context.as_ref().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) + self.path_context().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) } pub(crate) fn path_kind(&self) -> Option { - self.path_context.as_ref().map(|it| it.kind) + self.path_context().map(|it| it.kind) } pub(crate) fn is_immediately_after_macro_bang(&self) -> bool { @@ -498,17 +519,17 @@ impl<'a> CompletionContext<'a> { function_def: None, impl_def: None, name_syntax: None, - lifetime_ctx: None, - pattern_ctx: None, - name_ctx: None, + incomplete_let: false, completion_location: None, prev_sibling: None, fake_attribute_under_caret: None, previous_token: None, - path_context: None, - locals, - incomplete_let: false, + name_ctx: None, + lifetime_ctx: None, + nameref_ctx: None, + pattern_ctx: None, existing_derives: Default::default(), + locals, }; ctx.expand_and_fill( original_file.syntax().clone(), @@ -843,11 +864,13 @@ impl<'a> CompletionContext<'a> { { self.name_syntax = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - if let Some((path_ctx, _)) = + if let Some((mut nameref_ctx, _)) = Self::classify_name_ref(&self.sema, &original_file, name_ref) { - self.path_context = - Some(PathCompletionCtx { kind: PathKind::Derive, ..path_ctx }); + if let Some(path_ctx) = &mut nameref_ctx.path_ctx { + path_ctx.kind = PathKind::Derive; + } + self.nameref_ctx = Some(nameref_ctx); } } return; @@ -878,10 +901,10 @@ impl<'a> CompletionContext<'a> { self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime); } ast::NameLike::NameRef(name_ref) => { - if let Some((path_ctx, pat_ctx)) = + if let Some((nameref_ctx, pat_ctx)) = Self::classify_name_ref(&self.sema, original_file, name_ref) { - self.path_context = Some(path_ctx); + self.nameref_ctx = Some(nameref_ctx); self.pattern_ctx = pat_ctx; } } @@ -968,11 +991,53 @@ impl<'a> CompletionContext<'a> { sema: &Semantics, original_file: &SyntaxNode, name_ref: ast::NameRef, - ) -> Option<(PathCompletionCtx, Option)> { + ) -> Option<(NameRefContext, Option)> { let parent = name_ref.syntax().parent()?; - let segment = ast::PathSegment::cast(parent)?; - let path = segment.parent_path(); + let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None }; + + fn find_in_original_file( + x: Option, + original_file: &SyntaxNode, + ) -> Option { + fn find_node_with_range( + syntax: &SyntaxNode, + range: TextRange, + ) -> Option { + let range = syntax.text_range().intersect(range)?; + syntax.covering_element(range).ancestors().find_map(N::cast) + } + x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) + } + let segment = match_ast! { + match parent { + ast::PathSegment(segment) => segment, + ast::FieldExpr(field) => { + let receiver = find_in_original_file(field.expr(), original_file); + let receiver_is_ambiguous_float_literal = match &receiver { + Some(ast::Expr::Literal(l)) => matches! { + l.kind(), + ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.kind() == T![.]) + }, + _ => false, + }; + nameref_ctx.dot_access = Some(DotAccess::Field { receiver, receiver_is_ambiguous_float_literal }); + return Some((nameref_ctx, None)); + }, + ast::MethodCallExpr(method) => { + nameref_ctx.dot_access = Some( + DotAccess::Method { + receiver: find_in_original_file(method.receiver(), original_file), + has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) + } + ); + return Some((nameref_ctx, None)); + }, + _ => return None, + } + }; + + let path = segment.parent_path(); let mut path_ctx = PathCompletionCtx { has_call_parens: false, has_macro_bang: false, @@ -1109,17 +1174,13 @@ impl<'a> CompletionContext<'a> { is_infer_qualifier, } }); - return Some((path_ctx, pat_ctx)); - } - - if let Some(segment) = path.segment() { + } else if let Some(segment) = path.segment() { if segment.coloncolon_token().is_some() { path_ctx.is_absolute_path = true; - return Some((path_ctx, pat_ctx)); } } - - Some((path_ctx, pat_ctx)) + nameref_ctx.path_ctx = Some(path_ctx); + Some((nameref_ctx, pat_ctx)) } } diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index c1db8ce4a6c..07b2ac4297f 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -8,7 +8,7 @@ use hir::Semantics; use ide_db::RootDatabase; use syntax::{ algo::non_trivia_sibling, - ast::{self, HasArgList, HasLoopBody, HasName}, + ast::{self, HasLoopBody, HasName}, match_ast, AstNode, Direction, SyntaxElement, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -51,16 +51,6 @@ pub(crate) enum ImmediateLocation { TypeBound, /// Original file ast node TypeAnnotation(TypeAnnotation), - /// Original file ast node - MethodCall { - receiver: Option, - has_parens: bool, - }, - /// Original file ast node - FieldAccess { - receiver: Option, - receiver_is_ambiguous_float_literal: bool, - }, // Only set from a type arg /// Original file ast node GenericArgList(ast::GenericArgList), @@ -226,25 +216,6 @@ pub(crate) fn determine_location( ast::GenericArgList(_) => sema .find_node_at_offset_with_macros(original_file, offset) .map(ImmediateLocation::GenericArgList)?, - ast::FieldExpr(it) => { - 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.to_string().ends_with('.'), - _ => false, - } - } else { - false - }; - ImmediateLocation::FieldAccess { - receiver, - receiver_is_ambiguous_float_literal, - } - }, - ast::MethodCallExpr(it) => ImmediateLocation::MethodCall { - 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; diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index a59c8e8ee2f..01c9bfdb85c 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -272,7 +272,7 @@ fn render_resolution_simple_( // Add `<>` for generic types let type_path_no_ty_args = matches!( - ctx.completion.path_context, + ctx.completion.path_context(), Some(PathCompletionCtx { kind: PathKind::Type, has_type_args: false, .. }) ) && ctx.completion.config.add_call_parenthesis; if type_path_no_ty_args { diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 0117d869ea4..93c64eec6f8 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -7,9 +7,8 @@ use stdx::{format_to, to_lower_snake_case}; use syntax::SmolStr; use crate::{ - context::{CompletionContext, PathCompletionCtx, PathKind}, + context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind}, item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, - patterns::ImmediateLocation, render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, }; @@ -196,7 +195,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { return false; } - match ctx.path_context { + match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Expr { .. }, has_call_parens: true, .. }) => { return false } @@ -208,8 +207,8 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { }; if matches!( - ctx.completion_location, - Some(ImmediateLocation::MethodCall { has_parens: true, .. }) + ctx.nameref_ctx, + Some(NameRefContext { dot_access: Some(DotAccess::Method { has_parens: true, .. }), .. }) ) { return false; } diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index f1773137fed..e6540e6ac53 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -52,7 +52,7 @@ fn render( let db = completion.db; let kind = thing.kind(db); let has_call_parens = - matches!(completion.path_context, Some(PathCompletionCtx { has_call_parens: true, .. })); + matches!(completion.path_context(), Some(PathCompletionCtx { has_call_parens: true, .. })); let fields = thing.fields(completion)?; let (qualified_name, short_qualified_name, qualified) = match path { diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs index 22df7132f0e..9c51a6311a4 100644 --- a/crates/ide-completion/src/render/macro_.rs +++ b/crates/ide-completion/src/render/macro_.rs @@ -33,8 +33,8 @@ fn render( let is_fn_like = macro_.is_fn_like(completion.db); let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") }; - let needs_bang = match completion.path_context { - Some(PathCompletionCtx { kind, has_macro_bang, .. }) => { + let needs_bang = match completion.path_context() { + Some(&PathCompletionCtx { kind, has_macro_bang, .. }) => { is_fn_like && kind != PathKind::Use && !has_macro_bang } _ => is_fn_like, diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs index ff61bf61c54..5b403ae8ccc 100644 --- a/crates/ide-completion/src/render/pattern.rs +++ b/crates/ide-completion/src/render/pattern.rs @@ -78,7 +78,7 @@ fn render_pat( fields_omitted: bool, ) -> Option { let has_call_parens = matches!( - ctx.completion.path_context, + ctx.completion.path_context(), Some(PathCompletionCtx { has_call_parens: true, .. }) ); let mut pat = match kind { From 99fa37d6e312699e8c7887146c6e0a7ef315c1f7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 7 May 2022 14:16:03 +0200 Subject: [PATCH 2/4] Split namelike into the corresponding completion contexts --- crates/ide-completion/src/completions/dot.rs | 16 +- .../src/completions/flyimport.rs | 3 +- .../src/completions/lifetime.rs | 15 +- crates/ide-completion/src/completions/mod_.rs | 18 +- crates/ide-completion/src/completions/use_.rs | 15 +- crates/ide-completion/src/context.rs | 160 +++++++++--------- 6 files changed, 123 insertions(+), 104 deletions(-) diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index af0f38a3d8d..439745ffba6 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -9,8 +9,15 @@ use crate::{ /// 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() { - Some(expr) => expr, + let (dot_access, dot_receiver) = match &ctx.nameref_ctx { + Some(NameRefContext { + dot_access: + Some( + access @ (DotAccess::Method { receiver: Some(receiver), .. } + | DotAccess::Field { receiver: Some(receiver), .. }), + ), + .. + }) => (access, receiver), _ => return complete_undotted_self(acc, ctx), }; @@ -19,10 +26,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { _ => return, }; - if matches!( - ctx.nameref_ctx, - Some(NameRefContext { dot_access: Some(DotAccess::Method { .. }), .. }), - ) { + if let DotAccess::Method { .. } = dot_access { cov_mark::hit!(test_no_struct_field_completion_for_method_call); } else { complete_fields( diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index bfa4c06f922..bbb50cb2657 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -217,10 +217,9 @@ pub(crate) fn position_for_import( ) -> Option { Some( match import_candidate { - Some(ImportCandidate::Path(_)) => ctx.name_syntax.as_ref()?.syntax(), Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual()?.syntax(), Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(), - None => return ctx.original_token.parent(), + Some(ImportCandidate::Path(_)) | None => return ctx.original_token.parent(), } .clone(), ) diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 66f87239120..7c1e77c66e6 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -12,17 +12,20 @@ use syntax::{ast, TokenText}; use crate::{ completions::Completions, - context::{CompletionContext, LifetimeContext}, + context::{CompletionContext, LifetimeContext, LifetimeKind}, }; /// Completes lifetimes. pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) { - let lp = match &ctx.lifetime_ctx { - Some(LifetimeContext::Lifetime) => None, - Some(LifetimeContext::LifetimeParam { is_decl: false, param }) => Some(param), + let (lp, lifetime) = match &ctx.lifetime_ctx { + Some(LifetimeContext { kind: LifetimeKind::Lifetime, lifetime }) => (None, lifetime), + Some(LifetimeContext { + kind: LifetimeKind::LifetimeParam { is_decl: false, param }, + lifetime, + }) => (Some(param), lifetime), _ => return, }; - let param_lifetime = match (ctx.lifetime(), lp.and_then(|lp| lp.lifetime())) { + let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) { (Some(lt), Some(lp)) if lp == lt.clone() => return, (Some(_), Some(lp)) => Some(lp), _ => None, @@ -46,7 +49,7 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) /// Completes labels. pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { - if !matches!(ctx.lifetime_ctx, Some(LifetimeContext::LabelRef)) { + if !matches!(ctx.lifetime_ctx, Some(LifetimeContext { kind: LifetimeKind::LabelRef, .. })) { return; } ctx.process_all_names_raw(&mut |name, res| { diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index 43b0da61a9b..21b108ab1d9 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -3,21 +3,23 @@ use std::iter; use hir::{Module, ModuleSource}; -use ide_db::FxHashSet; use ide_db::{ base_db::{SourceDatabaseExt, VfsPath}, - RootDatabase, SymbolKind, + FxHashSet, RootDatabase, SymbolKind, }; use syntax::{ast, AstNode, SyntaxKind}; -use crate::{context::NameContext, CompletionItem}; +use crate::{ + context::{CompletionContext, NameContext, NameKind}, + CompletionItem, Completions, +}; -use crate::{context::CompletionContext, Completions}; - -/// Complete mod declaration, i.e. `mod $0;` +/// Complete mod declaration, i.e. `mod ;` pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let mod_under_caret = match &ctx.name_ctx { - Some(NameContext::Module(mod_under_caret)) if mod_under_caret.item_list().is_none() => { + Some(NameContext { kind: NameKind::Module(mod_under_caret), .. }) + if mod_under_caret.item_list().is_none() => + { mod_under_caret } _ => return None, @@ -26,7 +28,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op let _p = profile::span("completion::complete_mod"); let mut current_module = ctx.module; - // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're + // For `mod `, `ctx.module` is its parent, but for `mod f`, it's `mod f` itself, but we're // interested in its parent. if ctx.original_token.kind() == SyntaxKind::IDENT { if let Some(module) = ctx.original_token.ancestors().nth(1).and_then(ast::Module::cast) { diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index d52a348eb87..eb9449e7614 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -5,16 +5,19 @@ use ide_db::FxHashSet; use syntax::{ast, AstNode}; use crate::{ - context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx}, item::Builder, CompletionRelevance, Completions, }; pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { - let (&is_absolute_path, qualifier) = match ctx.path_context() { - Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }) => { - (is_absolute_path, qualifier) - } + let (&is_absolute_path, qualifier, name_ref) = match &ctx.nameref_ctx { + Some(NameRefContext { + path_ctx: + Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }), + nameref, + .. + }) => (is_absolute_path, qualifier, nameref), _ => return, }; @@ -55,7 +58,7 @@ pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) let module_scope = module.scope(ctx.db, Some(ctx.module)); let unknown_is_current = |name: &hir::Name| { matches!( - ctx.name_ref(), + name_ref, Some(name_ref) if name_ref.syntax().text() == name.to_smol_str().as_str() ) }; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index e6b41ffbd45..94d920257ff 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -117,16 +117,29 @@ pub(super) struct PatternContext { } #[derive(Debug)] -pub(super) enum LifetimeContext { +pub(super) struct LifetimeContext { + pub(super) lifetime: Option, + pub(super) kind: LifetimeKind, +} + +#[derive(Debug)] +pub enum LifetimeKind { LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, Lifetime, LabelRef, LabelDef, } +#[derive(Debug)] +pub struct NameContext { + #[allow(dead_code)] + pub(super) name: Option, + pub(super) kind: NameKind, +} + #[derive(Debug)] #[allow(dead_code)] -pub(super) enum NameContext { +pub(super) enum NameKind { Const, ConstParam, Enum, @@ -150,6 +163,8 @@ pub(super) enum NameContext { #[derive(Debug)] pub(super) struct NameRefContext { + /// NameRef syntax in the original file + pub(super) nameref: Option, pub(super) dot_access: Option, pub(super) path_ctx: Option, } @@ -203,8 +218,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) function_def: Option, /// The parent impl of the cursor position if it exists. pub(super) impl_def: Option, - /// The NameLike under the cursor in the original file if it exists. - pub(super) name_syntax: Option, /// Are we completing inside a let statement with a missing semicolon? pub(super) incomplete_let: bool, @@ -216,6 +229,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) name_ctx: Option, pub(super) lifetime_ctx: Option, pub(super) nameref_ctx: Option, + pub(super) pattern_ctx: Option, pub(super) existing_derives: FxHashSet, @@ -240,14 +254,6 @@ impl<'a> CompletionContext<'a> { } } - pub(crate) fn name_ref(&self) -> Option<&ast::NameRef> { - self.name_syntax.as_ref().and_then(ast::NameLike::as_name_ref) - } - - pub(crate) fn lifetime(&self) -> Option<&ast::Lifetime> { - self.name_syntax.as_ref().and_then(ast::NameLike::as_lifetime) - } - pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool { self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind) } @@ -276,7 +282,7 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn expects_variant(&self) -> bool { - matches!(self.name_ctx, Some(NameContext::Variant)) + matches!(self.name_ctx, Some(NameContext { kind: NameKind::Variant, .. })) } pub(crate) fn expects_non_trait_assoc_item(&self) -> bool { @@ -301,7 +307,7 @@ impl<'a> CompletionContext<'a> { pub(crate) fn expect_field(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::TupleField)) - || matches!(self.name_ctx, Some(NameContext::RecordField)) + || matches!(self.name_ctx, Some(NameContext { kind: NameKind::RecordField, .. })) } /// Whether the cursor is right after a trait or impl header. @@ -338,7 +344,10 @@ impl<'a> CompletionContext<'a> { self.completion_location, Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_)) ) - || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename)) + || matches!( + self.name_ctx, + Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. }) + ) } pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { @@ -518,7 +527,6 @@ impl<'a> CompletionContext<'a> { expected_type: None, function_def: None, impl_def: None, - name_syntax: None, incomplete_let: false, completion_location: None, prev_sibling: None, @@ -862,11 +870,9 @@ impl<'a> CompletionContext<'a> { if let Some(ast::NameLike::NameRef(name_ref)) = find_node_at_offset(&file_with_fake_ident, offset) { - self.name_syntax = - find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - if let Some((mut nameref_ctx, _)) = - Self::classify_name_ref(&self.sema, &original_file, name_ref) - { + if let Some(parent) = name_ref.syntax().parent() { + let (mut nameref_ctx, _) = + Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); if let Some(path_ctx) = &mut nameref_ctx.path_ctx { path_ctx.kind = PathKind::Derive; } @@ -883,8 +889,6 @@ impl<'a> CompletionContext<'a> { self.completion_location = determine_location(&self.sema, original_file, offset, &name_like); self.prev_sibling = determine_prev_sibling(&name_like); - self.name_syntax = - find_node_at_offset(original_file, name_like.syntax().text_range().start()); self.impl_def = self .sema .token_ancestors_with_macros(self.token.clone()) @@ -901,9 +905,9 @@ impl<'a> CompletionContext<'a> { self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime); } ast::NameLike::NameRef(name_ref) => { - if let Some((nameref_ctx, pat_ctx)) = - Self::classify_name_ref(&self.sema, original_file, name_ref) - { + if let Some(parent) = name_ref.syntax().parent() { + let (nameref_ctx, pat_ctx) = + Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); self.nameref_ctx = Some(nameref_ctx); self.pattern_ctx = pat_ctx; } @@ -921,7 +925,7 @@ impl<'a> CompletionContext<'a> { fn classify_lifetime( _sema: &Semantics, - _original_file: &SyntaxNode, + original_file: &SyntaxNode, lifetime: ast::Lifetime, ) -> Option { let parent = lifetime.syntax().parent()?; @@ -929,18 +933,21 @@ impl<'a> CompletionContext<'a> { return None; } - Some(match_ast! { + let kind = match_ast! { match parent { - ast::LifetimeParam(param) => LifetimeContext::LifetimeParam { + ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { is_decl: param.lifetime().as_ref() == Some(&lifetime), param }, - ast::BreakExpr(_) => LifetimeContext::LabelRef, - ast::ContinueExpr(_) => LifetimeContext::LabelRef, - ast::Label(_) => LifetimeContext::LabelDef, - _ => LifetimeContext::Lifetime, + ast::BreakExpr(_) => LifetimeKind::LabelRef, + ast::ContinueExpr(_) => LifetimeKind::LabelRef, + ast::Label(_) => LifetimeKind::LabelDef, + _ => LifetimeKind::Lifetime, } - }) + }; + let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start()); + + Some(LifetimeContext { lifetime, kind }) } fn classify_name( @@ -950,12 +957,12 @@ impl<'a> CompletionContext<'a> { ) -> Option<(NameContext, Option)> { let parent = name.syntax().parent()?; let mut pat_ctx = None; - let name_ctx = match_ast! { + let kind = match_ast! { match parent { - ast::Const(_) => NameContext::Const, - ast::ConstParam(_) => NameContext::ConstParam, - ast::Enum(_) => NameContext::Enum, - ast::Fn(_) => NameContext::Function, + ast::Const(_) => NameKind::Const, + ast::ConstParam(_) => NameKind::ConstParam, + ast::Enum(_) => NameKind::Enum, + ast::Fn(_) => NameKind::Function, ast::IdentPat(bind_pat) => { let is_name_in_field_pat = bind_pat .syntax() @@ -966,49 +973,38 @@ impl<'a> CompletionContext<'a> { pat_ctx = Some(pattern_context_for(original_file, bind_pat.into())); } - NameContext::IdentPat + NameKind::IdentPat }, - ast::MacroDef(_) => NameContext::MacroDef, - ast::MacroRules(_) => NameContext::MacroRules, - ast::Module(module) => NameContext::Module(module), - ast::RecordField(_) => NameContext::RecordField, - ast::Rename(_) => NameContext::Rename, - ast::SelfParam(_) => NameContext::SelfParam, - ast::Static(_) => NameContext::Static, - ast::Struct(_) => NameContext::Struct, - ast::Trait(_) => NameContext::Trait, - ast::TypeAlias(_) => NameContext::TypeAlias, - ast::TypeParam(_) => NameContext::TypeParam, - ast::Union(_) => NameContext::Union, - ast::Variant(_) => NameContext::Variant, + ast::MacroDef(_) => NameKind::MacroDef, + ast::MacroRules(_) => NameKind::MacroRules, + ast::Module(module) => NameKind::Module(module), + ast::RecordField(_) => NameKind::RecordField, + ast::Rename(_) => NameKind::Rename, + ast::SelfParam(_) => NameKind::SelfParam, + ast::Static(_) => NameKind::Static, + ast::Struct(_) => NameKind::Struct, + ast::Trait(_) => NameKind::Trait, + ast::TypeAlias(_) => NameKind::TypeAlias, + ast::TypeParam(_) => NameKind::TypeParam, + ast::Union(_) => NameKind::Union, + ast::Variant(_) => NameKind::Variant, _ => return None, } }; - Some((name_ctx, pat_ctx)) + let name = find_node_at_offset(&original_file, name.syntax().text_range().start()); + Some((NameContext { name, kind }, pat_ctx)) } fn classify_name_ref( sema: &Semantics, original_file: &SyntaxNode, name_ref: ast::NameRef, - ) -> Option<(NameRefContext, Option)> { - let parent = name_ref.syntax().parent()?; + parent: SyntaxNode, + ) -> (NameRefContext, Option) { + let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None }; + let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None, nameref }; - fn find_in_original_file( - x: Option, - original_file: &SyntaxNode, - ) -> Option { - fn find_node_with_range( - syntax: &SyntaxNode, - range: TextRange, - ) -> Option { - let range = syntax.text_range().intersect(range)?; - syntax.covering_element(range).ancestors().find_map(N::cast) - } - x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) - } let segment = match_ast! { match parent { ast::PathSegment(segment) => segment, @@ -1022,7 +1018,7 @@ impl<'a> CompletionContext<'a> { _ => false, }; nameref_ctx.dot_access = Some(DotAccess::Field { receiver, receiver_is_ambiguous_float_literal }); - return Some((nameref_ctx, None)); + return (nameref_ctx, None); }, ast::MethodCallExpr(method) => { nameref_ctx.dot_access = Some( @@ -1031,9 +1027,9 @@ impl<'a> CompletionContext<'a> { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) } ); - return Some((nameref_ctx, None)); + return (nameref_ctx, None); }, - _ => return None, + _ => return (nameref_ctx, None), } }; @@ -1057,7 +1053,7 @@ impl<'a> CompletionContext<'a> { .unwrap_or(false) }; - path_ctx.kind = path.syntax().ancestors().find_map(|it| { + let kind = path.syntax().ancestors().find_map(|it| { // using Option> as extra controlflow let kind = match_ast! { match it { @@ -1138,7 +1134,11 @@ impl<'a> CompletionContext<'a> { } }; Some(kind) - }).flatten()?; + }).flatten(); + match kind { + Some(kind) => path_ctx.kind = kind, + None => return (nameref_ctx, pat_ctx), + } path_ctx.has_type_args = segment.generic_arg_list().is_some(); if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) { @@ -1180,7 +1180,7 @@ impl<'a> CompletionContext<'a> { } } nameref_ctx.path_ctx = Some(path_ctx); - Some((nameref_ctx, pat_ctx)) + (nameref_ctx, pat_ctx) } } @@ -1235,6 +1235,14 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont } } +fn find_in_original_file(x: Option, original_file: &SyntaxNode) -> Option { + fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { + let range = syntax.text_range().intersect(range)?; + syntax.covering_element(range).ancestors().find_map(N::cast) + } + x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) +} + /// Attempts to find `node` inside `syntax` via `node`'s text range. fn find_node_in_file(syntax: &SyntaxNode, node: &N) -> Option { let syntax_range = syntax.text_range(); From 44c3cc100b826afcfd0afef4d16bd47195a62f94 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 7 May 2022 15:05:43 +0200 Subject: [PATCH 3/4] Merge the different identifier contexts into one enum --- .../src/completions/attribute.rs | 7 +- crates/ide-completion/src/completions/dot.rs | 2 +- .../src/completions/extern_abi.rs | 16 +- .../src/completions/format_string.rs | 17 +- .../ide-completion/src/completions/keyword.rs | 10 +- .../src/completions/lifetime.rs | 4 +- crates/ide-completion/src/completions/mod_.rs | 2 +- .../ide-completion/src/completions/postfix.rs | 2 +- crates/ide-completion/src/completions/use_.rs | 2 +- crates/ide-completion/src/context.rs | 145 +++++++++++++----- crates/ide-completion/src/render/function.rs | 2 +- crates/ide-completion/src/tests.rs | 1 - crates/ide-completion/src/tests/attribute.rs | 19 --- 13 files changed, 138 insertions(+), 91 deletions(-) diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index ec95021ec02..0b7479fd0e8 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -18,7 +18,7 @@ use syntax::{ use crate::{ completions::module_or_attr, - context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + context::{CompletionContext, IdentContext, PathCompletionCtx, PathKind, PathQualifierCtx}, item::CompletionItem, Completions, }; @@ -35,7 +35,10 @@ pub(crate) fn complete_known_attribute_input( acc: &mut Completions, ctx: &CompletionContext, ) -> Option<()> { - let attribute = ctx.fake_attribute_under_caret.as_ref()?; + let attribute = match &ctx.ident_ctx { + IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: Some(it) } => it, + _ => return None, + }; let name_ref = match attribute.path() { Some(p) => Some(p.as_single_name_ref()?), None => None, diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 439745ffba6..6a553eadc19 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -9,7 +9,7 @@ use crate::{ /// Complete dot accesses, i.e. fields or methods. pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { - let (dot_access, dot_receiver) = match &ctx.nameref_ctx { + let (dot_access, dot_receiver) = match ctx.nameref_ctx() { Some(NameRefContext { dot_access: Some( diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs index 87fccec008e..ae8c199f0c4 100644 --- a/crates/ide-completion/src/completions/extern_abi.rs +++ b/crates/ide-completion/src/completions/extern_abi.rs @@ -5,7 +5,9 @@ use syntax::{ }; use crate::{ - completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind, + completions::Completions, + context::{CompletionContext, IdentContext}, + CompletionItem, CompletionItemKind, }; // Most of these are feature gated, we should filter/add feature gate completions once we have them. @@ -41,10 +43,14 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[ ]; pub(crate) fn complete_extern_abi(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - if ctx.token.parent().and_then(ast::Abi::cast).is_none() { - return None; - } - let abi_str = ast::String::cast(ctx.token.clone())?; + let abi_str = match &ctx.ident_ctx { + IdentContext::String { expanded: Some(expanded), .. } + if expanded.syntax().parent().map_or(false, |it| ast::Abi::can_cast(it.kind())) => + { + expanded + } + _ => return None, + }; let source_range = abi_str.text_range_between_quotes()?; for &abi in SUPPORTED_CALLING_CONVENTIONS { CompletionItem::new(CompletionItemKind::Keyword, source_range, abi).add_to(acc); diff --git a/crates/ide-completion/src/completions/format_string.rs b/crates/ide-completion/src/completions/format_string.rs index f0c994f6b66..132599906af 100644 --- a/crates/ide-completion/src/completions/format_string.rs +++ b/crates/ide-completion/src/completions/format_string.rs @@ -2,16 +2,21 @@ use ide_db::syntax_helpers::format_string::is_format_string; use itertools::Itertools; -use syntax::{ast, AstToken, TextRange, TextSize}; +use syntax::{AstToken, TextRange, TextSize}; -use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions}; +use crate::{ + context::{CompletionContext, IdentContext}, + CompletionItem, CompletionItemKind, Completions, +}; /// Complete identifiers in format strings. pub(crate) fn format_string(acc: &mut Completions, ctx: &CompletionContext) { - let string = match ast::String::cast(ctx.token.clone()) - .zip(ast::String::cast(ctx.original_token.clone())) - { - Some((expanded, original)) if is_format_string(&expanded) => original, + let string = match &ctx.ident_ctx { + IdentContext::String { expanded: Some(expanded), original } + if is_format_string(&expanded) => + { + original + } _ => return, }; let cursor = ctx.position.offset; diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index 766ab4fcd7d..b34545414ec 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -2,7 +2,7 @@ //! - `self`, `super` and `crate`, as these are considered part of path completions. //! - `await`, as this is a postfix completion we handle this in the postfix completions. -use syntax::{SyntaxKind, T}; +use syntax::T; use crate::{ context::{PathCompletionCtx, PathKind}, @@ -11,18 +11,10 @@ use crate::{ }; pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { - if ctx.token.kind() == SyntaxKind::COMMENT { - cov_mark::hit!(no_keyword_completion_in_comments); - return; - } if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) { cov_mark::hit!(no_keyword_completion_in_record_lit); return; } - if ctx.fake_attribute_under_caret.is_some() { - cov_mark::hit!(no_keyword_completion_in_attr_of_expr); - return; - } if ctx.is_non_trivial_path() { cov_mark::hit!(no_keyword_completion_in_non_trivial_path); return; diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 7c1e77c66e6..12fcc8920a1 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -17,7 +17,7 @@ use crate::{ /// Completes lifetimes. pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) { - let (lp, lifetime) = match &ctx.lifetime_ctx { + let (lp, lifetime) = match ctx.lifetime_ctx() { Some(LifetimeContext { kind: LifetimeKind::Lifetime, lifetime }) => (None, lifetime), Some(LifetimeContext { kind: LifetimeKind::LifetimeParam { is_decl: false, param }, @@ -49,7 +49,7 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) /// Completes labels. pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { - if !matches!(ctx.lifetime_ctx, Some(LifetimeContext { kind: LifetimeKind::LabelRef, .. })) { + if !matches!(ctx.lifetime_ctx(), Some(LifetimeContext { kind: LifetimeKind::LabelRef, .. })) { return; } ctx.process_all_names_raw(&mut |name, res| { diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index 21b108ab1d9..3ba663067a0 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -16,7 +16,7 @@ use crate::{ /// Complete mod declaration, i.e. `mod ;` pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - let mod_under_caret = match &ctx.name_ctx { + let mod_under_caret = match ctx.name_ctx() { Some(NameContext { kind: NameKind::Module(mod_under_caret), .. }) if mod_under_caret.item_list().is_none() => { diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index ef765a345a5..be0f6748891 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -23,7 +23,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { return; } - let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.nameref_ctx { + let (dot_receiver, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() { Some(NameRefContext { dot_access: Some(DotAccess::Method { receiver: Some(it), .. }), .. diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index eb9449e7614..f1beeb454c8 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -11,7 +11,7 @@ use crate::{ }; pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { - let (&is_absolute_path, qualifier, name_ref) = match &ctx.nameref_ctx { + let (&is_absolute_path, qualifier, name_ref) = match ctx.nameref_ctx() { Some(NameRefContext { path_ctx: Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }), diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 94d920257ff..1381e87031c 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -15,7 +15,7 @@ use ide_db::{ use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef}, - match_ast, AstNode, NodeOrToken, + match_ast, AstNode, AstToken, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextSize, T, }; @@ -169,6 +169,21 @@ pub(super) struct NameRefContext { pub(super) path_ctx: Option, } +#[derive(Debug)] +pub(super) enum IdentContext { + Name(NameContext), + NameRef(NameRefContext), + Lifetime(LifetimeContext), + /// Original token, fake token + String { + original: ast::String, + expanded: Option, + }, + UnexpandedAttrTT { + fake_attribute_under_caret: Option, + }, +} + #[derive(Debug)] pub(super) enum DotAccess { Field { @@ -223,12 +238,9 @@ pub(crate) struct CompletionContext<'a> { pub(super) completion_location: Option, pub(super) prev_sibling: Option, - pub(super) fake_attribute_under_caret: Option, pub(super) previous_token: Option, - pub(super) name_ctx: Option, - pub(super) lifetime_ctx: Option, - pub(super) nameref_ctx: Option, + pub(super) ident_ctx: IdentContext, pub(super) pattern_ctx: Option, @@ -262,8 +274,29 @@ impl<'a> CompletionContext<'a> { FamousDefs(&self.sema, self.krate) } + pub(super) fn nameref_ctx(&self) -> Option<&NameRefContext> { + match &self.ident_ctx { + IdentContext::NameRef(it) => Some(it), + _ => None, + } + } + + pub(super) fn name_ctx(&self) -> Option<&NameContext> { + match &self.ident_ctx { + IdentContext::Name(it) => Some(it), + _ => None, + } + } + + pub(super) fn lifetime_ctx(&self) -> Option<&LifetimeContext> { + match &self.ident_ctx { + IdentContext::Lifetime(it) => Some(it), + _ => None, + } + } + pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { - match &self.nameref_ctx { + match self.nameref_ctx() { Some(NameRefContext { dot_access: Some(DotAccess::Method { receiver, .. } | DotAccess::Field { receiver, .. }), @@ -282,7 +315,7 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn expects_variant(&self) -> bool { - matches!(self.name_ctx, Some(NameContext { kind: NameKind::Variant, .. })) + matches!(self.name_ctx(), Some(NameContext { kind: NameKind::Variant, .. })) } pub(crate) fn expects_non_trait_assoc_item(&self) -> bool { @@ -307,7 +340,7 @@ impl<'a> CompletionContext<'a> { pub(crate) fn expect_field(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::TupleField)) - || matches!(self.name_ctx, Some(NameContext { kind: NameKind::RecordField, .. })) + || matches!(self.name_ctx(), Some(NameContext { kind: NameKind::RecordField, .. })) } /// Whether the cursor is right after a trait or impl header. @@ -345,13 +378,13 @@ impl<'a> CompletionContext<'a> { Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_)) ) || matches!( - self.name_ctx, + self.name_ctx(), Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. }) ) } pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { - self.nameref_ctx.as_ref().and_then(|ctx| ctx.path_ctx.as_ref()) + self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref()) } pub(crate) fn expects_expression(&self) -> bool { @@ -501,7 +534,9 @@ impl<'a> CompletionContext<'a> { file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?; let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; + dbg!(&original_token); let token = sema.descend_into_macros_single(original_token.clone()); + dbg!(&token); let scope = sema.scope_at_offset(&token.parent()?, offset)?; let krate = scope.krate(); let module = scope.module(); @@ -530,11 +565,9 @@ impl<'a> CompletionContext<'a> { incomplete_let: false, completion_location: None, prev_sibling: None, - fake_attribute_under_caret: None, previous_token: None, - name_ctx: None, - lifetime_ctx: None, - nameref_ctx: None, + // dummy value, will be overwritten + ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None }, pattern_ctx: None, existing_derives: Default::default(), locals, @@ -544,7 +577,7 @@ impl<'a> CompletionContext<'a> { file_with_fake_ident.syntax().clone(), offset, fake_ident_token, - ); + )?; Some(ctx) } @@ -557,7 +590,7 @@ impl<'a> CompletionContext<'a> { mut speculative_file: SyntaxNode, mut offset: TextSize, mut fake_ident_token: SyntaxToken, - ) { + ) -> Option<()> { let _p = profile::span("CompletionContext::expand_and_fill"); let mut derive_ctx = None; @@ -687,7 +720,7 @@ impl<'a> CompletionContext<'a> { break 'expansion; } - self.fill(&original_file, speculative_file, offset, derive_ctx); + self.fill(&original_file, speculative_file, offset, derive_ctx) } /// Calculate the expected type and name of the cursor position. @@ -835,7 +868,7 @@ impl<'a> CompletionContext<'a> { file_with_fake_ident: SyntaxNode, offset: TextSize, derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>, - ) { + ) -> Option<()> { let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); let syntax_element = NodeOrToken::Token(fake_ident_token); if is_in_token_of_for_loop(syntax_element.clone()) { @@ -844,11 +877,10 @@ impl<'a> CompletionContext<'a> { // don't bother populating the context // FIXME: the completion calculations should end up good enough // such that this special case becomes unnecessary - return; + return None; } self.previous_token = previous_token(syntax_element.clone()); - self.fake_attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast); self.incomplete_let = syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { @@ -870,21 +902,49 @@ impl<'a> CompletionContext<'a> { if let Some(ast::NameLike::NameRef(name_ref)) = find_node_at_offset(&file_with_fake_ident, offset) { - if let Some(parent) = name_ref.syntax().parent() { - let (mut nameref_ctx, _) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); - if let Some(path_ctx) = &mut nameref_ctx.path_ctx { - path_ctx.kind = PathKind::Derive; - } - self.nameref_ctx = Some(nameref_ctx); + let parent = name_ref.syntax().parent()?; + let (mut nameref_ctx, _) = + Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); + if let Some(path_ctx) = &mut nameref_ctx.path_ctx { + path_ctx.kind = PathKind::Derive; } + self.ident_ctx = IdentContext::NameRef(nameref_ctx); + return Some(()); } - return; + return None; } let name_like = match find_node_at_offset(&file_with_fake_ident, offset) { Some(it) => it, - None => return, + None => { + if let Some(original) = ast::String::cast(self.original_token.clone()) { + self.ident_ctx = IdentContext::String { + original, + expanded: ast::String::cast(self.token.clone()), + }; + } else { + // Fix up trailing whitespace problem + // #[attr(foo = $0 + let token = if self.token.kind() == SyntaxKind::WHITESPACE { + self.previous_token.as_ref()? + } else { + &self.token + }; + let p = token.parent()?; + if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + { + self.ident_ctx = IdentContext::UnexpandedAttrTT { + fake_attribute_under_caret: syntax_element + .ancestors() + .find_map(ast::Attr::cast), + }; + } else { + return None; + } + } + return Some(()); + } }; self.completion_location = determine_location(&self.sema, original_file, offset, &name_like); @@ -902,25 +962,26 @@ impl<'a> CompletionContext<'a> { match name_like { ast::NameLike::Lifetime(lifetime) => { - self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime); + self.ident_ctx = IdentContext::Lifetime(Self::classify_lifetime( + &self.sema, + original_file, + lifetime, + )?); } ast::NameLike::NameRef(name_ref) => { - if let Some(parent) = name_ref.syntax().parent() { - let (nameref_ctx, pat_ctx) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); - self.nameref_ctx = Some(nameref_ctx); - self.pattern_ctx = pat_ctx; - } + let parent = name_ref.syntax().parent()?; + let (nameref_ctx, pat_ctx) = + Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); + self.ident_ctx = IdentContext::NameRef(nameref_ctx); + self.pattern_ctx = pat_ctx; } ast::NameLike::Name(name) => { - if let Some((name_ctx, pat_ctx)) = - Self::classify_name(&self.sema, original_file, name) - { - self.pattern_ctx = pat_ctx; - self.name_ctx = Some(name_ctx); - } + let (name_ctx, pat_ctx) = Self::classify_name(&self.sema, original_file, name)?; + self.pattern_ctx = pat_ctx; + self.ident_ctx = IdentContext::Name(name_ctx); } } + Some(()) } fn classify_lifetime( diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 93c64eec6f8..1ede314e87b 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -207,7 +207,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { }; if matches!( - ctx.nameref_ctx, + ctx.nameref_ctx(), Some(NameRefContext { dot_access: Some(DotAccess::Method { has_parens: true, .. }), .. }) ) { return false; diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 72b7a5584aa..0430aeea59b 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -257,7 +257,6 @@ fn foo() { #[test] fn no_completions_in_comments() { - cov_mark::check!(no_keyword_completion_in_comments); assert_eq!( completion_list( r#" diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs index 664bd05e2de..52c69f84b61 100644 --- a/crates/ide-completion/src/tests/attribute.rs +++ b/crates/ide-completion/src/tests/attribute.rs @@ -579,25 +579,6 @@ fn attr_on_fn() { ); } -#[test] -fn attr_on_expr() { - cov_mark::check!(no_keyword_completion_in_attr_of_expr); - check( - r#"fn main() { #[$0] foo() }"#, - expect![[r#" - at allow(…) - at cfg(…) - at cfg_attr(…) - at deny(…) - at forbid(…) - at warn(…) - kw crate:: - kw self:: - kw super:: - "#]], - ); -} - #[test] fn attr_in_source_file_end() { check( From a0fc649269bda6c01f1b22d233ebb49cb34925a3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 7 May 2022 15:08:33 +0200 Subject: [PATCH 4/4] fix unreachable pub --- crates/ide-completion/src/completions/mod_.rs | 4 ++-- crates/ide-completion/src/context.rs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index 3ba663067a0..827d9f85efb 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -14,7 +14,7 @@ use crate::{ CompletionItem, Completions, }; -/// Complete mod declaration, i.e. `mod ;` +/// Complete mod declaration, i.e. `mod $0;` pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let mod_under_caret = match ctx.name_ctx() { Some(NameContext { kind: NameKind::Module(mod_under_caret), .. }) @@ -28,7 +28,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op let _p = profile::span("completion::complete_mod"); let mut current_module = ctx.module; - // For `mod `, `ctx.module` is its parent, but for `mod f`, it's `mod f` itself, but we're + // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're // interested in its parent. if ctx.original_token.kind() == SyntaxKind::IDENT { if let Some(module) = ctx.original_token.ancestors().nth(1).and_then(ast::Module::cast) { diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 1381e87031c..5342c481e36 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -123,7 +123,7 @@ pub(super) struct LifetimeContext { } #[derive(Debug)] -pub enum LifetimeKind { +pub(super) enum LifetimeKind { LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, Lifetime, LabelRef, @@ -131,7 +131,7 @@ pub enum LifetimeKind { } #[derive(Debug)] -pub struct NameContext { +pub(super) struct NameContext { #[allow(dead_code)] pub(super) name: Option, pub(super) kind: NameKind, @@ -534,9 +534,7 @@ impl<'a> CompletionContext<'a> { file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?; let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; - dbg!(&original_token); let token = sema.descend_into_macros_single(original_token.clone()); - dbg!(&token); let scope = sema.scope_at_offset(&token.parent()?, offset)?; let krate = scope.krate(); let module = scope.module();