Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/completions/postfix.rs')
| -rw-r--r-- | crates/ide-completion/src/completions/postfix.rs | 177 |
1 files changed, 153 insertions, 24 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index d355fdbe07..4dd84daf06 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -11,12 +11,13 @@ use ide_db::{ text_edit::TextEdit, ty_filter::TryEnum, }; -use itertools::Either; +use itertools::Itertools; use stdx::never; use syntax::{ - SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR}, - TextRange, TextSize, + SyntaxKind::{EXPR_STMT, STMT_LIST}, + T, TextRange, TextSize, ToSmolStr, ast::{self, AstNode, AstToken}, + match_ast, }; use crate::{ @@ -43,7 +44,7 @@ pub(crate) fn complete_postfix( DotAccessKind::Field { receiver_is_ambiguous_float_literal } => { receiver_is_ambiguous_float_literal } - DotAccessKind::Method { .. } => false, + DotAccessKind::Method => false, }, ), _ => return, @@ -113,12 +114,8 @@ pub(crate) fn complete_postfix( if let Some(parent) = dot_receiver_including_refs.syntax().parent() && let Some(second_ancestor) = parent.parent() { - let sec_ancestor_kind = second_ancestor.kind(); - if let Some(expr) = <Either<ast::IfExpr, ast::WhileExpr>>::cast(second_ancestor) { - is_in_cond = match expr { - Either::Left(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent), - Either::Right(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent), - } + if let Some(parent_expr) = ast::Expr::cast(parent) { + is_in_cond = is_in_condition(&parent_expr); } match &try_enum { Some(try_enum) if is_in_cond => match try_enum { @@ -147,7 +144,7 @@ pub(crate) fn complete_postfix( .add_to(acc, ctx.db); } }, - _ if matches!(sec_ancestor_kind, STMT_LIST | EXPR_STMT) => { + _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => { postfix_snippet("let", "let", &format!("let $0 = {receiver_text};")) .add_to(acc, ctx.db); postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) @@ -257,18 +254,15 @@ pub(crate) fn complete_postfix( } } - let mut block_should_be_wrapped = true; - if dot_receiver.syntax().kind() == BLOCK_EXPR { - block_should_be_wrapped = false; - if let Some(parent) = dot_receiver.syntax().parent() - && matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) - { - block_should_be_wrapped = true; - } + let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver { + block.modifier().is_some() || !block.is_standalone() + } else { + true }; { let (open_brace, close_brace) = if block_should_be_wrapped { ("{ ", " }") } else { ("", "") }; + // FIXME: Why add parentheses let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") }; let unsafe_completion_string = format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}"); @@ -295,7 +289,7 @@ pub(crate) fn complete_postfix( ) .add_to(acc, ctx.db); - if let BreakableKind::Block | BreakableKind::Loop = expr_ctx.in_breakable { + if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable { postfix_snippet( "break", "break expr", @@ -367,10 +361,18 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { resulting_element.syntax().parent().and_then(ast::RefExpr::cast) { found_ref_or_deref = true; - let exclusive = parent_ref_element.mut_token().is_some(); + let last_child_or_token = parent_ref_element.syntax().last_child_or_token(); + prefix.insert_str( + 0, + parent_ref_element + .syntax() + .children_with_tokens() + .filter(|it| Some(it) != last_child_or_token.as_ref()) + .format("") + .to_smolstr() + .as_str(), + ); resulting_element = ast::Expr::from(parent_ref_element); - - prefix.insert_str(0, if exclusive { "&mut " } else { "&" }); } if !found_ref_or_deref { @@ -444,7 +446,7 @@ fn add_custom_postfix_completions( let body = snippet.postfix_snippet(receiver_text); let mut builder = postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body); - builder.documentation(Documentation::new(format!("```rust\n{body}\n```"))); + builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```"))); for import in imports.into_iter() { builder.add_import(import); } @@ -454,6 +456,24 @@ fn add_custom_postfix_completions( None } +pub(crate) fn is_in_condition(it: &ast::Expr) -> bool { + it.syntax() + .parent() + .and_then(|parent| { + Some(match_ast! { match parent { + ast::IfExpr(expr) => expr.condition()? == *it, + ast::WhileExpr(expr) => expr.condition()? == *it, + ast::MatchGuard(guard) => guard.condition()? == *it, + ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&]) + .then(|| is_in_condition(&bin_expr.into()))?, + ast::Expr(expr) => (expr.syntax().text_range().start() == it.syntax().text_range().start()) + .then(|| is_in_condition(&expr))?, + _ => return None, + } }) + }) + .unwrap_or(false) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -650,6 +670,38 @@ fn main() { } "#, ); + check_edit( + "let", + r#" +//- minicore: option +fn main() { + let bar = Some(true); + if true && bar.$0 +} +"#, + r#" +fn main() { + let bar = Some(true); + if true && let Some($0) = bar +} +"#, + ); + check_edit( + "let", + r#" +//- minicore: option +fn main() { + let bar = Some(true); + if true && true && bar.$0 +} +"#, + r#" +fn main() { + let bar = Some(true); + if true && true && let Some($0) = bar +} +"#, + ); } #[test] @@ -796,6 +848,20 @@ fn main() { &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"), ); + if kind == "const" { + check_edit( + kind, + r#"fn main() { unsafe {1}.$0 }"#, + &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"), + ); + } else { + check_edit( + kind, + r#"fn main() { const {1}.$0 }"#, + &format!("fn main() {{ {kind} {{ const {{1}} }} }}"), + ); + } + // completion will not be triggered check_edit( kind, @@ -949,6 +1015,20 @@ fn main() { ); check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "ok", + r#"fn main() { &raw mut 42.$0 }"#, + r#"fn main() { Ok(&raw mut 42) }"#, + ); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "ok", + r#"fn main() { &raw const 42.$0 }"#, + r#"fn main() { Ok(&raw const 42) }"#, + ); + + check_edit_with_config( CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG }, "ok", r#" @@ -975,6 +1055,55 @@ fn main() { } #[test] + fn postfix_custom_snippets_completion_for_reference_expr() { + // https://github.com/rust-lang/rust-analyzer/issues/21035 + let snippet = Snippet::new( + &[], + &["group".into()], + &["(${receiver})".into()], + "", + &[], + crate::SnippetScope::Expr, + ) + .unwrap(); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "group", + r#"fn main() { &[1, 2, 3].g$0 }"#, + r#"fn main() { (&[1, 2, 3]) }"#, + ); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "group", + r#"fn main() { &&foo(a, b, 1+1).$0 }"#, + r#"fn main() { (&&foo(a, b, 1+1)) }"#, + ); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "group", + r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#, + r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#, + ); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "group", + r#"fn main() { &raw mut Foo::new().$0 }"#, + r#"fn main() { (&raw mut Foo::new()) }"#, + ); + + check_edit_with_config( + CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG }, + "group", + r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#, + r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#, + ); + } + + #[test] fn no_postfix_completions_in_if_block_that_has_an_else() { check( r#" |