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.rs296
1 files changed, 244 insertions, 52 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 82baf885dd..0cb39dd108 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -12,7 +12,7 @@ use ide_db::{
text_edit::TextEdit,
ty_filter::TryEnum,
};
-use itertools::Itertools;
+use itertools::{Either, Itertools};
use stdx::never;
use syntax::{
SmolStr,
@@ -31,7 +31,7 @@ use crate::{
pub(crate) fn complete_postfix(
acc: &mut Completions,
- ctx: &CompletionContext<'_>,
+ ctx: &CompletionContext<'_, '_>,
dot_access: &DotAccess<'_>,
) {
if !ctx.config.enable_postfix_completions {
@@ -84,15 +84,15 @@ pub(crate) fn complete_postfix(
let mut item = postfix_snippet(
"drop",
"fn drop(&mut self)",
- &format!("{path}($0{receiver_text})", path = path.display(ctx.db, ctx.edition)),
+ format!("{path}($0{receiver_text})", path = path.display(ctx.db, ctx.edition)),
);
item.set_documentation(drop_fn.docs(ctx.db));
item.add_to(acc, ctx.db);
}
- postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc, ctx.db);
- postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc, ctx.db);
- postfix_snippet("deref", "*expr", &format!("*{receiver_text}")).add_to(acc, ctx.db);
+ postfix_snippet("ref", "&expr", format!("&{receiver_text}")).add_to(acc, ctx.db);
+ postfix_snippet("refm", "&mut expr", format!("&mut {receiver_text}")).add_to(acc, ctx.db);
+ postfix_snippet("deref", "*expr", format!("*{receiver_text}")).add_to(acc, ctx.db);
// 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
@@ -110,15 +110,47 @@ pub(crate) fn complete_postfix(
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
}
- postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
+ postfix_snippet("box", "Box::new(expr)", format!("Box::new({receiver_text})"))
.add_to(acc, ctx.db);
- postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
- postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
- postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
+ postfix_snippet("dbg", "dbg!(expr)", format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
+ postfix_snippet("dbgr", "dbg!(&expr)", format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
+ postfix_snippet("call", "function(expr)", format!("${{1}}({receiver_text})"))
.add_to(acc, ctx.db);
+ if let Some(expected_ty) = ctx.expected_type.as_ref()
+ && let Some(adt) = expected_ty.as_adt()
+ {
+ let is_valid_new = expected_ty
+ .iterate_assoc_items(ctx.db, |item| {
+ if let hir::AssocItem::Function(func) = item
+ && func.name(ctx.db) == hir::sym::new
+ && !func.has_self_param(ctx.db)
+ {
+ let params = func.params_without_self(ctx.db);
+ if params.len() == 1 {
+ return Some(());
+ }
+ }
+ None
+ })
+ .is_some();
+
+ let adt = hir::ModuleDef::from(adt);
+ if is_valid_new && let Some(path) = ctx.module.find_path(ctx.db, adt, cfg) {
+ let ty_name = path.display(ctx.db, ctx.display_target.edition).to_smolstr();
+
+ postfix_snippet(
+ "new",
+ &format_smolstr!("{}::new(expr)", ty_name),
+ format!("{}::new({}$0)", ty_name, receiver_text),
+ )
+ .add_to(acc, ctx.db);
+ }
+ }
+
let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);
let is_in_cond = is_in_condition(&dot_receiver_including_refs);
+ let is_in_value = is_in_value(&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 {
@@ -127,13 +159,13 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"let",
"let Ok(_)",
- &format!("let Ok({placeholder}) = {receiver_text}"),
+ format!("let Ok({placeholder}) = {receiver_text}"),
)
.add_to(acc, ctx.db);
postfix_snippet(
"letm",
"let Ok(mut _)",
- &format!("let Ok(mut {placeholder}) = {receiver_text}"),
+ format!("let Ok(mut {placeholder}) = {receiver_text}"),
)
.add_to(acc, ctx.db);
}
@@ -141,38 +173,38 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"let",
"let Some(_)",
- &format!("let Some({placeholder}) = {receiver_text}"),
+ format!("let Some({placeholder}) = {receiver_text}"),
)
.add_to(acc, ctx.db);
postfix_snippet(
"letm",
"let Some(mut _)",
- &format!("let Some(mut {placeholder}) = {receiver_text}"),
+ format!("let Some(mut {placeholder}) = {receiver_text}"),
)
.add_to(acc, ctx.db);
}
},
_ if is_in_cond => {
- postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))
+ postfix_snippet("let", "let", format!("let $1 = {receiver_text}"))
.add_to(acc, ctx.db);
}
_ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {
- postfix_snippet("let", "let", &format!("let $0 = {receiver_text}{semi}"))
+ 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}"))
+ 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}}"),
+ 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}}"),
+ format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),
)
.add_to(acc, ctx.db);
}
@@ -187,7 +219,7 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"match",
"match expr {}",
- &format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),
+ format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),
)
.add_to(acc, ctx.db);
}
@@ -195,7 +227,7 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"match",
"match expr {}",
- &format!(
+ format!(
"match {receiver_text} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}"
),
)
@@ -206,35 +238,35 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"match",
"match expr {}",
- &format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
+ format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
)
.add_to(acc, ctx.db);
}
}
if let Some(try_enum) = &try_enum {
let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema);
+ let if_then_snip =
+ if is_in_value { "{\n $2\n} else {\n $0\n}" } else { "{\n $0\n}" };
match try_enum {
TryEnum::Result => {
postfix_snippet(
"ifl",
"if let Ok {}",
- &format!("if let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),
+ format!("if let Ok({placeholder}) = {receiver_text} {if_then_snip}"),
)
.add_to(acc, ctx.db);
postfix_snippet(
"lete",
"let Ok else {}",
- &format!(
- "let Ok({placeholder}) = {receiver_text} else {{\n $2\n}};\n$0"
- ),
+ format!("let Ok({placeholder}) = {receiver_text} else {{\n $2\n}};\n$0"),
)
.add_to(acc, ctx.db);
postfix_snippet(
"while",
"while let Ok {}",
- &format!("while let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),
+ format!("while let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),
)
.add_to(acc, ctx.db);
}
@@ -242,14 +274,14 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"ifl",
"if let Some {}",
- &format!("if let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),
+ format!("if let Some({placeholder}) = {receiver_text} {if_then_snip}"),
)
.add_to(acc, ctx.db);
postfix_snippet(
"lete",
"let Some else {}",
- &format!(
+ format!(
"let Some({placeholder}) = {receiver_text} else {{\n $2\n}};\n$0"
),
)
@@ -258,18 +290,20 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"while",
"while let Some {}",
- &format!("while let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),
+ format!("while let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),
)
.add_to(acc, ctx.db);
}
}
} else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
- postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n $0\n}}"))
+ let if_then_snip =
+ if is_in_value { "{\n $1\n} else {\n $0\n}" } else { "{\n $0\n}" };
+ postfix_snippet("if", "if expr {}", format!("if {receiver_text} {if_then_snip}"))
.add_to(acc, ctx.db);
postfix_snippet(
"while",
"while expr {}",
- &format!("while {receiver_text} {{\n $0\n}}"),
+ format!("while {receiver_text} {{\n $0\n}}"),
)
.add_to(acc, ctx.db);
} else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()
@@ -278,14 +312,14 @@ pub(crate) fn complete_postfix(
postfix_snippet(
"for",
"for ele in expr {}",
- &format!("for ele in {receiver_text} {{\n $0\n}}"),
+ format!("for ele in {receiver_text} {{\n $0\n}}"),
)
.add_to(acc, ctx.db);
}
}
if receiver_ty.is_bool() || receiver_ty.is_unknown() {
- postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
+ 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 {
@@ -300,11 +334,11 @@ pub(crate) fn complete_postfix(
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}");
- postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
+ postfix_snippet("unsafe", "unsafe {}", unsafe_completion_string).add_to(acc, ctx.db);
let const_completion_string =
format!("{open_paren}const {open_brace}{receiver_text}{close_brace}{close_paren}");
- postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
+ postfix_snippet("const", "const {}", const_completion_string).add_to(acc, ctx.db);
}
if let ast::Expr::Literal(literal) = dot_receiver.clone()
@@ -313,11 +347,11 @@ pub(crate) fn complete_postfix(
add_format_like_completions(acc, ctx, dot_receiver, cap, &literal_text, semi);
}
- postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}"))
+ 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}"))
+ postfix_snippet("break", "break expr", format!("break {receiver_text}{semi}"))
.add_to(acc, ctx.db);
}
}
@@ -364,7 +398,7 @@ fn get_receiver_text(
}
let file_text = sema.db.file_text(range.file_id.file_id(sema.db));
let text = file_text.text(sema.db);
- let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]);
+ let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.end())]);
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
@@ -434,6 +468,11 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
.syntax()
.children_with_tokens()
.filter(|it| Some(it) != last_child_or_token.as_ref())
+ .flat_map(|it| {
+ let has_ws = it.next_sibling_or_token().is_some_and(|it| it.kind().is_trivia());
+ let need_ws = !has_ws && it.kind().is_any_identifier();
+ itertools::chain([Either::Left(it)], need_ws.then_some(Either::Right(" ")))
+ })
.format("")
.to_smolstr()
.as_str(),
@@ -445,10 +484,10 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
}
fn build_postfix_snippet_builder<'ctx>(
- ctx: &'ctx CompletionContext<'_>,
+ ctx: &'ctx CompletionContext<'_, '_>,
cap: SnippetCap,
receiver: &'ctx ast::Expr,
-) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
+) -> Option<impl Fn(&str, &str, String) -> Builder + 'ctx> {
let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
if ctx.source_range().end() < receiver_range.start() {
// This shouldn't happen, yet it does. I assume this might be due to an incorrect token
@@ -461,12 +500,12 @@ fn build_postfix_snippet_builder<'ctx>(
// Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
// can't be annotated for the closure, hence fix it by constructing it without the Option first
fn build<'ctx>(
- ctx: &'ctx CompletionContext<'_>,
+ ctx: &'ctx CompletionContext<'_, '_>,
cap: SnippetCap,
delete_range: TextRange,
- ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
+ ) -> impl Fn(&str, &str, String) -> Builder + 'ctx {
move |label, detail, snippet| {
- let edit = TextEdit::replace(delete_range, snippet.to_owned());
+ let edit = TextEdit::replace(delete_range, snippet);
let mut item = CompletionItem::new(
CompletionItemKind::Snippet,
ctx.source_range(),
@@ -491,8 +530,8 @@ fn build_postfix_snippet_builder<'ctx>(
fn add_custom_postfix_completions(
acc: &mut Completions,
- ctx: &CompletionContext<'_>,
- postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
+ ctx: &CompletionContext<'_, '_>,
+ postfix_snippet: impl Fn(&str, &str, String) -> Builder,
receiver_text: &str,
) -> Option<()> {
ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
@@ -503,9 +542,10 @@ fn add_custom_postfix_completions(
None => return,
};
let body = snippet.postfix_snippet(receiver_text);
+ let document = Documentation::new_owned(format!("```rust\n{body}\n```"));
let mut builder =
- postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
- builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```")));
+ postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), body);
+ builder.documentation(document);
for import in imports.into_iter() {
builder.add_import(import);
}
@@ -533,6 +573,22 @@ pub(crate) fn is_in_condition(it: &ast::Expr) -> bool {
.unwrap_or(false)
}
+pub(crate) fn is_in_value(it: &ast::Expr) -> bool {
+ let Some(node) = it.syntax().parent() else { return false };
+ let kind = node.kind();
+ ast::LetStmt::can_cast(kind)
+ || ast::ArgList::can_cast(kind)
+ || ast::ArrayExpr::can_cast(kind)
+ || ast::ParenExpr::can_cast(kind)
+ || ast::BreakExpr::can_cast(kind)
+ || ast::ReturnExpr::can_cast(kind)
+ || ast::PrefixExpr::can_cast(kind)
+ || ast::FormatArgsArg::can_cast(kind)
+ || ast::RecordExprField::can_cast(kind)
+ || ast::BinExpr::cast(node.clone()).is_some_and(|expr| expr.rhs().as_ref() == Some(it))
+ || ast::IndexExpr::cast(node).is_some_and(|expr| expr.index().as_ref() == Some(it))
+}
+
#[cfg(test)]
mod tests {
use expect_test::expect;
@@ -1148,6 +1204,66 @@ fn main() {
}
#[test]
+ fn postfix_completion_if_else_in_value() {
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ let s = cond.is_some().$0;
+}
+"#,
+ r#"
+fn main() {
+ let s = if cond.is_some() {
+ $1
+} else {
+ $0
+};
+}
+"#,
+ );
+
+ check_edit(
+ "ifl",
+ r#"
+//- minicore: option
+fn main() {
+ let cond = Some("x");
+ let s = cond.$0;
+}
+"#,
+ r#"
+fn main() {
+ let cond = Some("x");
+ let s = if let Some(${1:cond}) = cond {
+ $2
+} else {
+ $0
+};
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ 2 + true.$0;
+}
+"#,
+ r#"
+fn main() {
+ 2 + if true {
+ $1
+} else {
+ $0
+};
+}
+"#,
+ );
+ }
+
+ #[test]
fn postfix_completion_for_unsafe() {
postfix_completion_for_block("unsafe");
}
@@ -1186,7 +1302,9 @@ fn main() {
);
check_edit(
kind,
- r#"fn main() { for i in 0..10 {}.$0 }"#,
+ r#"
+//- minicore: iterator
+fn main() { for i in 0..10 {}.$0 }"#,
&format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),
);
check_edit(
@@ -1456,6 +1574,15 @@ fn main() {
r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,
r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,
);
+
+ check_edit_with_config(
+ CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
+ "group",
+ r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }
+fn main() { id!(&raw const Foo::bar::SOME_CONST.$0) }"#,
+ r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }
+fn main() { id!((&raw const Foo::bar::SOME_CONST)) }"#,
+ );
}
#[test]
@@ -1545,7 +1672,9 @@ fn foo(x: Option<i32>, y: Option<i32>) {
let _f = || {
x
.and(y)
- .map(|it| it+2)
+ .map(|it| {
+ it+2
+ })
.$0
};
}
@@ -1554,11 +1683,74 @@ fn foo(x: Option<i32>, y: Option<i32>) {
fn foo(x: Option<i32>, y: Option<i32>) {
let _f = || {
let $0 = x
- .and(y)
- .map(|it| it+2);
+.and(y)
+.map(|it| {
+ it+2
+});
};
}
"#,
);
}
+
+ #[test]
+ fn postfix_new() {
+ check_edit(
+ "new",
+ r#"
+struct OtherThing;
+struct RefCell<T>(T);
+impl<T> RefCell<T> {
+ fn new(t: T) -> Self { RefCell(t) }
+}
+
+fn main() {
+ let other_thing = OtherThing;
+ let thing: RefCell<OtherThing> = other_thing.$0;
+}
+"#,
+ r#"
+struct OtherThing;
+struct RefCell<T>(T);
+impl<T> RefCell<T> {
+ fn new(t: T) -> Self { RefCell(t) }
+}
+
+fn main() {
+ let other_thing = OtherThing;
+ let thing: RefCell<OtherThing> = RefCell::new(other_thing$0);
+}
+"#,
+ );
+
+ check_edit(
+ "new",
+ r#"
+mod foo {
+ pub struct OtherThing;
+ pub struct RefCell<T>(T);
+ impl<T> RefCell<T> {
+ pub fn new(t: T) -> Self { RefCell(t) }
+ }
+}
+
+fn main() {
+ let thing: foo::RefCell<foo::OtherThing> = foo::OtherThing.$0;
+}
+"#,
+ r#"
+mod foo {
+ pub struct OtherThing;
+ pub struct RefCell<T>(T);
+ impl<T> RefCell<T> {
+ pub fn new(t: T) -> Self { RefCell(t) }
+ }
+}
+
+fn main() {
+ let thing: foo::RefCell<foo::OtherThing> = foo::RefCell::new(foo::OtherThing$0);
+}
+"#,
+ );
+ }
}