Skip to content

Commit 5235caf

Browse files
authored
Merge pull request #19129 from ChayimFriedman2/snippet-macro
fix: Fix postfix completions inside macros
2 parents 3b6f77c + d6f3ff1 commit 5235caf

File tree

1 file changed

+64
-27
lines changed

1 file changed

+64
-27
lines changed

crates/ide-completion/src/completions/postfix.rs

+64-27
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
33
mod format_like;
44

5-
use hir::ItemInNs;
6-
use ide_db::text_edit::TextEdit;
5+
use base_db::SourceDatabase;
6+
use hir::{ItemInNs, Semantics};
77
use ide_db::{
88
documentation::{Documentation, HasDocs},
99
imports::insert_use::ImportScope,
10+
text_edit::TextEdit,
1011
ty_filter::TryEnum,
11-
SnippetCap,
12+
RootDatabase, SnippetCap,
1213
};
1314
use stdx::never;
1415
use syntax::{
15-
ast::{self, make, AstNode, AstToken},
16+
ast::{self, AstNode, AstToken},
1617
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
1718
TextRange, TextSize,
1819
};
@@ -48,7 +49,8 @@ pub(crate) fn complete_postfix(
4849
};
4950
let expr_ctx = &dot_access.ctx;
5051

51-
let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
52+
let receiver_text =
53+
get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
5254

5355
let cap = match ctx.config.snippet_cap {
5456
Some(it) => it,
@@ -172,13 +174,15 @@ pub(crate) fn complete_postfix(
172174
// The rest of the postfix completions create an expression that moves an argument,
173175
// so it's better to consider references now to avoid breaking the compilation
174176

175-
let (dot_receiver, node_to_replace_with) = include_references(dot_receiver);
176-
let receiver_text =
177-
get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
178-
let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
179-
Some(it) => it,
180-
None => return,
181-
};
177+
let (dot_receiver_including_refs, prefix) = include_references(dot_receiver);
178+
let mut receiver_text =
179+
get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
180+
receiver_text.insert_str(0, &prefix);
181+
let postfix_snippet =
182+
match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
183+
Some(it) => it,
184+
None => return,
185+
};
182186

183187
if !ctx.config.snippets.is_empty() {
184188
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
@@ -222,7 +226,7 @@ pub(crate) fn complete_postfix(
222226
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
223227
.add_to(acc, ctx.db);
224228

225-
if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
229+
if let Some(parent) = dot_receiver_including_refs.syntax().parent().and_then(|p| p.parent()) {
226230
if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
227231
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
228232
.add_to(acc, ctx.db);
@@ -231,9 +235,9 @@ pub(crate) fn complete_postfix(
231235
}
232236
}
233237

234-
if let ast::Expr::Literal(literal) = dot_receiver.clone() {
238+
if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone() {
235239
if let Some(literal_text) = ast::String::cast(literal.token()) {
236-
add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
240+
add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
237241
}
238242
}
239243

@@ -260,14 +264,20 @@ pub(crate) fn complete_postfix(
260264
}
261265
}
262266

263-
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
264-
let mut text = if receiver_is_ambiguous_float_literal {
265-
let text = receiver.syntax().text();
266-
let without_dot = ..text.len() - TextSize::of('.');
267-
text.slice(without_dot).to_string()
268-
} else {
269-
receiver.to_string()
267+
fn get_receiver_text(
268+
sema: &Semantics<'_, RootDatabase>,
269+
receiver: &ast::Expr,
270+
receiver_is_ambiguous_float_literal: bool,
271+
) -> String {
272+
// Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.
273+
let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {
274+
return receiver.to_string();
270275
};
276+
if receiver_is_ambiguous_float_literal {
277+
range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))
278+
}
279+
let file_text = sema.db.file_text(range.file_id.file_id());
280+
let mut text = file_text[range.range].to_owned();
271281

272282
// The receiver texts should be interpreted as-is, as they are expected to be
273283
// normal Rust expressions.
@@ -284,15 +294,15 @@ fn escape_snippet_bits(text: &mut String) {
284294
stdx::replace(text, '$', "\\$");
285295
}
286296

287-
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
297+
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
288298
let mut resulting_element = initial_element.clone();
289299

290300
while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
291301
{
292302
resulting_element = ast::Expr::from(field_expr);
293303
}
294304

295-
let mut new_element_opt = initial_element.clone();
305+
let mut prefix = String::new();
296306

297307
while let Some(parent_deref_element) =
298308
resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
@@ -303,7 +313,7 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
303313

304314
resulting_element = ast::Expr::from(parent_deref_element);
305315

306-
new_element_opt = make::expr_prefix(syntax::T![*], new_element_opt).into();
316+
prefix.insert(0, '*');
307317
}
308318

309319
if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
@@ -317,15 +327,15 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
317327
let exclusive = parent_ref_element.mut_token().is_some();
318328
resulting_element = ast::Expr::from(parent_ref_element);
319329

320-
new_element_opt = make::expr_ref(new_element_opt, exclusive);
330+
prefix.insert_str(0, if exclusive { "&mut " } else { "&" });
321331
}
322332
} else {
323333
// If we do not find any ref expressions, restore
324334
// all the progress of tree climbing
325335
resulting_element = initial_element.clone();
326336
}
327337

328-
(resulting_element, new_element_opt)
338+
(resulting_element, prefix)
329339
}
330340

331341
fn build_postfix_snippet_builder<'ctx>(
@@ -901,4 +911,31 @@ fn main() {
901911
"#,
902912
);
903913
}
914+
915+
#[test]
916+
fn inside_macro() {
917+
check_edit(
918+
"box",
919+
r#"
920+
macro_rules! assert {
921+
( $it:expr $(,)? ) => { $it };
922+
}
923+
924+
fn foo() {
925+
let a = true;
926+
assert!(if a == false { true } else { false }.$0);
927+
}
928+
"#,
929+
r#"
930+
macro_rules! assert {
931+
( $it:expr $(,)? ) => { $it };
932+
}
933+
934+
fn foo() {
935+
let a = true;
936+
assert!(Box::new(if a == false { true } else { false }));
937+
}
938+
"#,
939+
);
940+
}
904941
}

0 commit comments

Comments
 (0)