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(