From 5660408f0a5b62bcc31258678e65078378109c94 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 30 May 2021 21:23:42 +0200 Subject: Move more fields to `ImmediateLocation` --- crates/ide_completion/src/completions/fn_param.rs | 15 +++ crates/ide_completion/src/completions/keyword.rs | 18 ++- crates/ide_completion/src/completions/mod_.rs | 6 +- crates/ide_completion/src/completions/record.rs | 20 +-- crates/ide_completion/src/context.rs | 99 +++++--------- crates/ide_completion/src/patterns.rs | 151 +++++++++++++--------- 6 files changed, 163 insertions(+), 146 deletions(-) (limited to 'crates') diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs index 0ea558489..cb90e8a3e 100644 --- a/crates/ide_completion/src/completions/fn_param.rs +++ b/crates/ide_completion/src/completions/fn_param.rs @@ -128,4 +128,19 @@ fn outer(text: String) { "#]], ) } + + #[test] + fn completes_non_ident_pat_param() { + check( + r#" +struct Bar { bar: u32 } + +fn foo(Bar { bar }: Bar) {} +fn foo2($0) {} +"#, + expect![[r#" + bn Bar { bar }: Bar + "#]], + ) + } } diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index e71a04b6e..0d035c611 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs @@ -4,7 +4,10 @@ use std::iter; use syntax::{SyntaxKind, T}; -use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; +use crate::{ + patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind, + CompletionKind, Completions, +}; pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { // complete keyword "crate" in use stmt @@ -44,7 +47,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte cov_mark::hit!(no_keyword_completion_in_comments); return; } - if ctx.record_lit_syntax.is_some() { + if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) { cov_mark::hit!(no_keyword_completion_in_record_lit); return; } @@ -55,7 +58,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte let expects_item = ctx.expects_item(); if ctx.has_impl_or_trait_prev_sibling() { - // FIXME this also incorrectly shows up after a complete trait/impl add_keyword("where", "where "); return; } @@ -77,11 +79,8 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte add_keyword("pub", "pub "); } - if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm { - add_keyword("unsafe", "unsafe "); - } - if expects_item || expects_assoc_item || has_block_expr_parent { + add_keyword("unsafe", "unsafe "); add_keyword("fn", "fn $1($2) {\n $0\n}"); add_keyword("const", "const $0"); add_keyword("type", "type $0"); @@ -103,6 +102,9 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte } if ctx.expects_expression() { + if !has_block_expr_parent { + add_keyword("unsafe", "unsafe {\n $0\n}"); + } add_keyword("match", "match $1 {\n $0\n}"); add_keyword("while", "while $1 {\n $0\n}"); add_keyword("while let", "while let $1 = $2 {\n $0\n}"); @@ -574,6 +576,7 @@ pub mod future { check( r#"fn main() { let _ = $0 }"#, expect![[r#" + kw unsafe kw match kw while kw while let @@ -634,6 +637,7 @@ fn foo() { } "#, expect![[r#" + kw unsafe kw match kw while kw while let diff --git a/crates/ide_completion/src/completions/mod_.rs b/crates/ide_completion/src/completions/mod_.rs index 4f9415736..6a5746fb9 100644 --- a/crates/ide_completion/src/completions/mod_.rs +++ b/crates/ide_completion/src/completions/mod_.rs @@ -9,14 +9,14 @@ use ide_db::{ }; use rustc_hash::FxHashSet; -use crate::CompletionItem; +use crate::{patterns::ImmediateLocation, CompletionItem}; use crate::{context::CompletionContext, item::CompletionKind, Completions}; /// Complete mod declaration, i.e. `mod $0 ;` pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - let mod_under_caret = match &ctx.mod_declaration_under_caret { - Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret, + let mod_under_caret = match &ctx.completion_location { + Some(ImmediateLocation::ModDeclaration(mod_under_caret)) => mod_under_caret, _ => return None, }; diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs index e1526b70b..227c08d01 100644 --- a/crates/ide_completion/src/completions/record.rs +++ b/crates/ide_completion/src/completions/record.rs @@ -2,21 +2,21 @@ use ide_db::{helpers::FamousDefs, SymbolKind}; use syntax::ast::Expr; -use crate::{item::CompletionKind, CompletionContext, CompletionItem, Completions}; +use crate::{ + item::CompletionKind, patterns::ImmediateLocation, CompletionContext, CompletionItem, + Completions, +}; pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { - (None, None) => return None, - (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), - (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), - (_, Some(record_lit)) => { - let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone())); + let missing_fields = match &ctx.completion_location { + Some(ImmediateLocation::RecordExpr(record_expr)) => { + let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone())); let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); let impl_default_trait = default_trait .zip(ty) .map_or(false, |(default_trait, ty)| ty.impls_trait(ctx.db, default_trait, &[])); - let missing_fields = ctx.sema.record_literal_missing_fields(record_lit); + let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); if impl_default_trait && !missing_fields.is_empty() { let completion_text = "..Default::default()"; let mut item = CompletionItem::new( @@ -32,6 +32,10 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> missing_fields } + Some(ImmediateLocation::RecordPat(record_pat)) => { + ctx.sema.record_pattern_missing_fields(record_pat) + } + _ => return None, }; for (field, ty) in missing_fields { diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 8d6440cb2..7c46c815d 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -18,7 +18,7 @@ use text_edit::Indel; use crate::{ patterns::{ determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, - is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling, + is_in_loop_body, previous_token, ImmediateLocation, ImmediatePrevSibling, }, CompletionConfig, }; @@ -54,11 +54,6 @@ pub(crate) struct CompletionContext<'a> { /// The parent impl of the cursor position if it exists. pub(super) impl_def: Option, - /// RecordExpr the token is a field of - pub(super) record_lit_syntax: Option, - /// RecordPat the token is a field of - pub(super) record_pat_syntax: Option, - // potentially set if we are completing a lifetime pub(super) lifetime_syntax: Option, pub(super) lifetime_param_syntax: Option, @@ -71,6 +66,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) completion_location: Option, pub(super) prev_sibling: Option, + pub(super) attribute_under_caret: Option, /// FIXME: `ActiveParameter` is string-based, which is very very wrong pub(super) active_parameter: Option, @@ -95,14 +91,10 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_macro_call: bool, pub(super) is_path_type: bool, pub(super) has_type_args: bool, - pub(super) attribute_under_caret: Option, - pub(super) mod_declaration_under_caret: Option, pub(super) locals: Vec<(String, Local)>, - // keyword patterns pub(super) previous_token: Option, pub(super) in_loop_body: bool, - pub(super) is_match_arm: bool, pub(super) incomplete_let: bool, no_completion_required: bool, @@ -157,8 +149,6 @@ impl<'a> CompletionContext<'a> { lifetime_param_syntax: None, function_def: None, use_item_syntax: None, - record_lit_syntax: None, - record_pat_syntax: None, impl_def: None, active_parameter: ActiveParameter::at(db, position), is_label_ref: false, @@ -176,15 +166,13 @@ impl<'a> CompletionContext<'a> { is_macro_call: false, is_path_type: false, has_type_args: false, - attribute_under_caret: None, - mod_declaration_under_caret: None, previous_token: None, in_loop_body: false, completion_location: None, prev_sibling: None, - is_match_arm: false, no_completion_required: false, incomplete_let: false, + attribute_under_caret: None, locals, }; @@ -227,7 +215,6 @@ impl<'a> CompletionContext<'a> { break; } } - ctx.fill_keyword_patterns(&speculative_file, offset); ctx.fill(&original_file, speculative_file, offset); Some(ctx) } @@ -311,31 +298,13 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn is_path_disallowed(&self) -> bool { - self.record_lit_syntax.is_some() - || self.record_pat_syntax.is_some() - || self.attribute_under_caret.is_some() - || self.mod_declaration_under_caret.is_some() - } - - fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { - let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); - let syntax_element = NodeOrToken::Token(fake_ident_token); - self.previous_token = previous_token(syntax_element.clone()); - self.in_loop_body = is_in_loop_body(syntax_element.clone()); - self.is_match_arm = is_match_arm(syntax_element.clone()); - - self.mod_declaration_under_caret = - find_node_at_offset::(&file_with_fake_ident, offset) - .filter(|module| module.item_list().is_none()); - self.incomplete_let = - syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { - it.syntax().text_range().end() == syntax_element.text_range().end() - }); - - let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); - let fn_is_prev = self.previous_token_is(T![fn]); - let for_is_prev2 = for_is_prev2(syntax_element.clone()); - self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2; + matches!( + self.completion_location, + Some(ImmediateLocation::Attribute(_)) + | Some(ImmediateLocation::ModDeclaration(_)) + | Some(ImmediateLocation::RecordPat(_)) + | Some(ImmediateLocation::RecordExpr(_)) + ) || self.attribute_under_caret.is_some() } fn fill_impl_def(&mut self) { @@ -453,25 +422,43 @@ impl<'a> CompletionContext<'a> { file_with_fake_ident: SyntaxNode, offset: TextSize, ) { + let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); + let syntax_element = NodeOrToken::Token(fake_ident_token); + self.previous_token = previous_token(syntax_element.clone()); + self.attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast); + self.no_completion_required = { + let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); + let fn_is_prev = self.previous_token_is(T![fn]); + let for_is_prev2 = for_is_prev2(syntax_element.clone()); + (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 + }; + self.in_loop_body = is_in_loop_body(syntax_element.clone()); + + self.incomplete_let = + syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { + it.syntax().text_range().end() == syntax_element.text_range().end() + }); + let (expected_type, expected_name) = self.expected_type_and_name(); self.expected_type = expected_type; self.expected_name = expected_name; - self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); + let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) { Some(it) => it, None => return, }; - self.completion_location = determine_location(&name_like); + self.completion_location = + determine_location(&self.sema, original_file, offset, &name_like); self.prev_sibling = determine_prev_sibling(&name_like); match name_like { ast::NameLike::Lifetime(lifetime) => { self.classify_lifetime(original_file, lifetime, offset); } ast::NameLike::NameRef(name_ref) => { - self.classify_name_ref(original_file, name_ref, offset); + self.classify_name_ref(original_file, name_ref); } ast::NameLike::Name(name) => { - self.classify_name(original_file, name, offset); + self.classify_name(name); } } } @@ -505,7 +492,7 @@ impl<'a> CompletionContext<'a> { } } - fn classify_name(&mut self, original_file: &SyntaxNode, name: ast::Name, offset: TextSize) { + fn classify_name(&mut self, name: ast::Name) { if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { self.is_pat_or_const = Some(PatternRefutability::Refutable); // if any of these is here our bind pat can't be a const pat anymore @@ -543,28 +530,12 @@ impl<'a> CompletionContext<'a> { self.fill_impl_def(); } + self.is_param |= is_node::(name.syntax()); - if ast::RecordPatField::for_field_name(&name).is_some() { - self.record_pat_syntax = - self.sema.find_node_at_offset_with_macros(&original_file, offset); - } } - fn classify_name_ref( - &mut self, - original_file: &SyntaxNode, - name_ref: ast::NameRef, - offset: TextSize, - ) { + fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { self.fill_impl_def(); - if ast::RecordExprField::for_field_name(&name_ref).is_some() { - self.record_lit_syntax = - self.sema.find_node_at_offset_with_macros(original_file, offset); - } - if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() { - self.record_pat_syntax = - self.sema.find_node_at_offset_with_macros(&original_file, offset); - } self.name_ref_syntax = find_node_at_offset(original_file, name_ref.syntax().text_range().start()); diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index caf0ef39f..26516046b 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -1,15 +1,18 @@ //! Patterns telling us certain facts about current syntax element, they are used in completion context +use hir::Semantics; +use ide_db::RootDatabase; use syntax::{ algo::non_trivia_sibling, ast::{self, LoopBodyOwner}, - match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, + match_ast, AstNode, Direction, SyntaxElement, SyntaxKind::*, - SyntaxNode, SyntaxToken, T, + SyntaxNode, SyntaxToken, TextSize, T, }; #[cfg(test)] use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; + /// Direct parent container of the cursor position #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediatePrevSibling { @@ -19,7 +22,7 @@ pub(crate) enum ImmediatePrevSibling { } /// Direct parent container of the cursor position -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediateLocation { Use, Impl, @@ -29,10 +32,24 @@ pub(crate) enum ImmediateLocation { IdentPat, BlockExpr, ItemList, + // Fake file ast node + Attribute(ast::Attr), + // Fake file ast node + ModDeclaration(ast::Module), + // Original file ast node + /// The record expr of the field name we are completing + RecordExpr(ast::RecordExpr), + // Original file ast node + /// The record pat of the field name we are completing + RecordPat(ast::RecordPat), } pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option { - let node = maximize_name_ref(name_like)?; + let node = match name_like { + ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref), + ast::NameLike::Name(n) => n.syntax().clone(), + ast::NameLike::Lifetime(lt) => lt.syntax().clone(), + }; let node = match node.parent().and_then(ast::MacroCall::cast) { // When a path is being typed after the name of a trait/type of an impl it is being // parsed as a macro, so when the trait/impl has a block following it an we are between the @@ -77,8 +94,37 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option Option { - let node = maximize_name_ref(name_like)?; +pub(crate) fn determine_location( + sema: &Semantics, + original_file: &SyntaxNode, + offset: TextSize, + name_like: &ast::NameLike, +) -> Option { + let node = match name_like { + ast::NameLike::NameRef(name_ref) => { + if ast::RecordExprField::for_field_name(&name_ref).is_some() { + return sema + .find_node_at_offset_with_macros(original_file, offset) + .map(ImmediateLocation::RecordExpr); + } + if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() { + return sema + .find_node_at_offset_with_macros(original_file, offset) + .map(ImmediateLocation::RecordPat); + } + maximize_name_ref(name_ref) + } + ast::NameLike::Name(name) => { + if ast::RecordPatField::for_field_name(&name).is_some() { + return sema + .find_node_at_offset_with_macros(original_file, offset) + .map(ImmediateLocation::RecordPat); + } + name.syntax().clone() + } + ast::NameLike::Lifetime(lt) => lt.syntax().clone(), + }; + let parent = match node.parent() { Some(parent) => match ast::MacroCall::cast(parent.clone()) { // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. @@ -103,6 +149,7 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option ImmediateLocation::IdentPat, @@ -117,36 +164,34 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option ImmediateLocation::Trait, _ => return None, }, + ast::Module(it) => if it.item_list().is_none() { + ImmediateLocation::ModDeclaration(it) + } else { + return None + }, + ast::Attr(it) => ImmediateLocation::Attribute(it), _ => return None, } }; Some(res) } -fn maximize_name_ref(name_like: &ast::NameLike) -> Option { - // First walk the element we are completing up to its highest node that has the same text range - // as the element so that we can check in what context it immediately lies. We only do this for - // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. - // We only wanna do this if the NameRef is the last segment of the path. - let node = match name_like { - ast::NameLike::NameRef(name_ref) => { - if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { - let p = segment.parent_path(); - if p.parent_path().is_none() { - p.syntax() - .ancestors() - .take_while(|it| it.text_range() == p.syntax().text_range()) - .last()? - } else { - return None; - } - } else { - return None; +fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { + // Maximize a nameref to its enclosing path if its the last segment of said path + if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { + let p = segment.parent_path(); + if p.parent_path().is_none() { + if let Some(it) = p + .syntax() + .ancestors() + .take_while(|it| it.text_range() == p.syntax().text_range()) + .last() + { + return it; } } - it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), - }; - Some(node) + } + name_ref.syntax().clone() } pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { @@ -167,18 +212,6 @@ fn test_inside_impl_trait_block() { check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); } -pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { - not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() - && previous_sibling_or_ancestor_sibling(element) - .and_then(|it| it.into_token()) - .filter(|it| it.kind() == FAT_ARROW) - .is_some() -} -#[test] -fn test_is_match_arm() { - check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm); -} - pub(crate) fn previous_token(element: SyntaxElement) -> Option { element.into_token().and_then(|it| previous_non_trivia_token(it)) } @@ -216,10 +249,6 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { .is_some() } -pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option { - element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next() -} - fn previous_non_trivia_token(token: SyntaxToken) -> Option { let mut token = token.prev_token(); while let Some(inner) = token.clone() { @@ -232,31 +261,25 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option { None } -fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option { - let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); - if let Some(sibling) = token_sibling { - Some(sibling) - } else { - // if not trying to find first ancestor which has such a sibling - let range = element.text_range(); - let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; - let prev_sibling_node = top_node.ancestors().find(|it| { - non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() - })?; - non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) - } -} - #[cfg(test)] mod tests { + use syntax::algo::find_node_at_offset; + + use crate::test_utils::position; + use super::*; fn check_location(code: &str, loc: impl Into>) { - check_pattern_is_applicable(code, |e| { - let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); - assert_eq!(determine_location(name), loc.into()); - true - }); + let (db, pos) = position(code); + + let sema = Semantics::new(&db); + let original_file = sema.parse(pos.file_id); + + let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap(); + assert_eq!( + determine_location(&sema, original_file.syntax(), pos.offset, &name_like), + loc.into() + ); } fn check_prev_sibling(code: &str, sibling: impl Into>) { -- cgit v1.2.3