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.rs419
1 files changed, 369 insertions, 50 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 7f67ef848e..5b91e7c456 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -16,7 +16,7 @@ use itertools::Itertools;
use stdx::never;
use syntax::{
SmolStr,
- SyntaxKind::{EXPR_STMT, STMT_LIST},
+ SyntaxKind::{CLOSURE_EXPR, EXPR_STMT, MATCH_ARM, STMT_LIST},
T, TextRange, TextSize, ToSmolStr,
ast::{self, AstNode, AstToken},
format_smolstr, match_ast,
@@ -52,6 +52,7 @@ pub(crate) fn complete_postfix(
_ => return,
};
let expr_ctx = &dot_access.ctx;
+ let receiver_accessor = receiver_accessor(dot_receiver);
let receiver_text =
get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
@@ -65,6 +66,12 @@ pub(crate) fn complete_postfix(
Some(it) => it,
None => return,
};
+ let semi =
+ if expr_ctx.in_block_expr && ctx.token.next_token().is_none_or(|it| it.kind() != T![;]) {
+ ";"
+ } else {
+ ""
+ };
let cfg = ctx.config.find_path_config(ctx.is_nightly);
@@ -90,9 +97,8 @@ pub(crate) fn complete_postfix(
// The rest of the postfix completions create an expression that moves an argument,
// so it's better to consider references now to avoid breaking the compilation
- let (dot_receiver_including_refs, prefix) = include_references(dot_receiver);
- let mut receiver_text =
- get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
+ let (dot_receiver_including_refs, prefix) = include_references(&receiver_accessor);
+ let mut receiver_text = receiver_text;
receiver_text.insert_str(0, &prefix);
let postfix_snippet =
match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
@@ -111,14 +117,9 @@ pub(crate) fn complete_postfix(
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
.add_to(acc, ctx.db);
- let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
- let mut is_in_cond = false;
- if let Some(parent) = dot_receiver_including_refs.syntax().parent()
- && let Some(second_ancestor) = parent.parent()
- {
- if let Some(parent_expr) = ast::Expr::cast(parent) {
- is_in_cond = is_in_condition(&parent_expr);
- }
+ let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);
+ let is_in_cond = is_in_condition(&dot_receiver_including_refs);
+ if let Some(parent) = dot_receiver_including_refs.syntax().parent() {
let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);
match &try_enum {
Some(try_enum) if is_in_cond => match try_enum {
@@ -151,12 +152,30 @@ pub(crate) fn complete_postfix(
.add_to(acc, ctx.db);
}
},
- _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => {
- postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
+ _ if is_in_cond => {
+ postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))
.add_to(acc, ctx.db);
- postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
+ }
+ _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {
+ postfix_snippet("let", "let", &format!("let $0 = {receiver_text}{semi}"))
+ .add_to(acc, ctx.db);
+ postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text}{semi}"))
.add_to(acc, ctx.db);
}
+ _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => {
+ postfix_snippet(
+ "let",
+ "let",
+ &format!("{{\n let $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ postfix_snippet(
+ "letm",
+ "let mut",
+ &format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ }
_ => (),
}
}
@@ -253,7 +272,6 @@ pub(crate) fn complete_postfix(
&format!("while {receiver_text} {{\n $0\n}}"),
)
.add_to(acc, ctx.db);
- postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
} else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()
&& receiver_ty.impls_trait(ctx.db, trait_, &[])
{
@@ -266,6 +284,10 @@ pub(crate) fn complete_postfix(
}
}
+ if receiver_ty.is_bool() || receiver_ty.is_unknown() {
+ postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
+ }
+
let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver {
block.modifier().is_some() || !block.is_standalone()
} else {
@@ -285,32 +307,18 @@ pub(crate) fn complete_postfix(
postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
}
- if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone()
+ if let ast::Expr::Literal(literal) = dot_receiver.clone()
&& let Some(literal_text) = ast::String::cast(literal.token())
{
add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
}
- postfix_snippet(
- "return",
- "return expr",
- &format!(
- "return {receiver_text}{semi}",
- semi = if expr_ctx.in_block_expr { ";" } else { "" }
- ),
- )
- .add_to(acc, ctx.db);
+ postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}"))
+ .add_to(acc, ctx.db);
if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {
- postfix_snippet(
- "break",
- "break expr",
- &format!(
- "break {receiver_text}{semi}",
- semi = if expr_ctx.in_block_expr { ";" } else { "" }
- ),
- )
- .add_to(acc, ctx.db);
+ postfix_snippet("break", "break expr", &format!("break {receiver_text}{semi}"))
+ .add_to(acc, ctx.db);
}
}
@@ -355,12 +363,20 @@ fn get_receiver_text(
range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))
}
let file_text = sema.db.file_text(range.file_id.file_id(sema.db));
- let mut text = file_text.text(sema.db)[range.range].to_owned();
+ let text = file_text.text(sema.db);
+ let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]);
+ let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);
// The receiver texts should be interpreted as-is, as they are expected to be
// normal Rust expressions.
escape_snippet_bits(&mut text);
- text
+ return text;
+
+ fn indent_of_tail_line(text: &str) -> usize {
+ let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s);
+ let trimmed = tail_line.trim_start_matches(' ');
+ tail_line.len() - trimmed.len()
+ }
}
/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
@@ -372,25 +388,34 @@ fn escape_snippet_bits(text: &mut String) {
stdx::replace(text, '$', "\\$");
}
+fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {
+ receiver
+ .syntax()
+ .parent()
+ .and_then(ast::Expr::cast)
+ .filter(|it| {
+ matches!(
+ it,
+ ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_)
+ )
+ })
+ .unwrap_or_else(|| receiver.clone())
+}
+
+/// Given an `initial_element`, tries to expand it to include deref(s), and then references.
+/// Returns the expanded expressions, and the added prefix as a string
+///
+/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`.
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
let mut resulting_element = initial_element.clone();
-
- while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
- {
- resulting_element = ast::Expr::from(field_expr);
- }
-
let mut prefix = String::new();
let mut found_ref_or_deref = false;
while let Some(parent_deref_element) =
resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
+ && parent_deref_element.op_kind() == Some(ast::UnaryOp::Deref)
{
- if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) {
- break;
- }
-
found_ref_or_deref = true;
resulting_element = ast::Expr::from(parent_deref_element);
@@ -586,6 +611,31 @@ fn main() {
}
#[test]
+ fn postfix_completion_works_in_if_condition() {
+ check(
+ r#"
+fn foo(cond: bool) {
+ if cond.$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ }
+
+ #[test]
fn postfix_type_filtering() {
check(
r#"
@@ -614,6 +664,22 @@ fn main() {
#[test]
fn let_middle_block() {
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ baz.l$0
+ res
+}
+"#,
+ r#"
+fn main() {
+ let $0 = baz;
+ res
+}
+"#,
+ );
+
check(
r#"
fn main() {
@@ -640,6 +706,118 @@ fn main() {
sn while while expr {}
"#]],
);
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+ res
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn let_tail_block() {
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ baz.l$0
+}
+"#,
+ r#"
+fn main() {
+ let $0 = baz;
+}
+"#,
+ );
+
+ check(
+ r#"
+fn main() {
+ baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn let_before_semicolon() {
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ baz.l$0;
+}
+"#,
+ r#"
+fn main() {
+ let $0 = baz;
+}
+"#,
+ );
}
#[test]
@@ -745,6 +923,119 @@ fn main() {
}
#[test]
+ fn iflet_fallback_cond() {
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ let bar = 2;
+ if bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = 2;
+ if let $1 = bar
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn match_arm_let_block() {
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => &bar.l$0
+ }
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 2 {
+ bar => {
+ let $1 = bar;
+ $0
+}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_let_block() {
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ let bar = 2;
+ let f = || bar.$0;
+}
+"#,
+ r#"
+fn main() {
+ let bar = 2;
+ let f = || {
+ let $1 = bar;
+ $0
+};
+}
+"#,
+ );
+ }
+
+ #[test]
fn option_letelse() {
check_edit(
"lete",
@@ -819,6 +1110,7 @@ fn main() {
#[test]
fn postfix_completion_for_references() {
check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
+ check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);
check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
check_edit(
"ifl",
@@ -977,9 +1269,9 @@ use core::ops::ControlFlow;
fn main() {
ControlFlow::Break(match true {
- true => "\${1:placeholder}",
- false => "\\\$",
- })
+ true => "\${1:placeholder}",
+ false => "\\\$",
+})
}
"#,
);
@@ -1219,4 +1511,31 @@ fn foo() {
"#,
);
}
+
+ #[test]
+ fn snippet_dedent() {
+ check_edit(
+ "let",
+ r#"
+//- minicore: option
+fn foo(x: Option<i32>, y: Option<i32>) {
+ let _f = || {
+ x
+ .and(y)
+ .map(|it| it+2)
+ .$0
+ };
+}
+"#,
+ r#"
+fn foo(x: Option<i32>, y: Option<i32>) {
+ let _f = || {
+ let $0 = x
+ .and(y)
+ .map(|it| it+2);
+ };
+}
+"#,
+ );
+ }
}