Skip to content

Commit 4e43df2

Browse files
Consider references when applying postfix completions
1 parent f9494f1 commit 4e43df2

File tree

1 file changed

+123
-17
lines changed

1 file changed

+123
-17
lines changed

crates/ra_ide/src/completion/complete_postfix.rs

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! FIXME: write short doc here
22
3-
use ra_syntax::{ast::AstNode, TextRange, TextUnit};
3+
use ra_syntax::{
4+
ast::{self, AstNode},
5+
TextRange, TextUnit,
6+
};
47
use ra_text_edit::TextEdit;
58

69
use crate::{
@@ -21,53 +24,97 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
2124
None => return,
2225
};
2326

24-
let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal {
25-
let text = dot_receiver.syntax().text();
26-
let without_dot = ..text.len() - TextUnit::of_char('.');
27-
text.slice(without_dot).to_string()
28-
} else {
29-
dot_receiver.syntax().text().to_string()
30-
};
27+
let receiver_text =
28+
get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
3129

3230
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
3331
Some(it) => it,
3432
None => return,
3533
};
3634

3735
if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38-
postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39-
.add_to(acc);
4036
postfix_snippet(
4137
ctx,
38+
&dot_receiver,
39+
"if",
40+
"if expr {}",
41+
&format!("if {} {{$0}}", receiver_text),
42+
)
43+
.add_to(acc);
44+
postfix_snippet(
45+
ctx,
46+
&dot_receiver,
4247
"while",
4348
"while expr {}",
4449
&format!("while {} {{\n$0\n}}", receiver_text),
4550
)
4651
.add_to(acc);
4752
}
4853

49-
postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
54+
// !&&&42 is a compiler error, ergo process it before considering the references
55+
postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
5056

51-
postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52-
postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
57+
postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
58+
postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59+
.add_to(acc);
60+
61+
// The rest of the postfix completions create an expression that moves an argument,
62+
// so it's better to consider references now to avoid breaking the compilation
63+
let dot_receiver = include_references(dot_receiver);
64+
let receiver_text =
65+
get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
5366

5467
postfix_snippet(
5568
ctx,
69+
&dot_receiver,
5670
"match",
5771
"match expr {}",
5872
&format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
5973
)
6074
.add_to(acc);
6175

62-
postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
76+
postfix_snippet(
77+
ctx,
78+
&dot_receiver,
79+
"box",
80+
"Box::new(expr)",
81+
&format!("Box::new({})", receiver_text),
82+
)
83+
.add_to(acc);
6384

64-
postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text))
85+
postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
6586
.add_to(acc);
6687
}
6788

68-
fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
89+
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
90+
if receiver_is_ambiguous_float_literal {
91+
let text = receiver.syntax().text();
92+
let without_dot = ..text.len() - TextUnit::of_char('.');
93+
text.slice(without_dot).to_string()
94+
} else {
95+
receiver.to_string()
96+
}
97+
}
98+
99+
fn include_references(initial_element: &ast::Expr) -> ast::Expr {
100+
let mut resulting_element = initial_element.clone();
101+
while let Some(parent_ref_element) =
102+
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
103+
{
104+
resulting_element = ast::Expr::from(parent_ref_element);
105+
}
106+
resulting_element
107+
}
108+
109+
fn postfix_snippet(
110+
ctx: &CompletionContext,
111+
receiver: &ast::Expr,
112+
label: &str,
113+
detail: &str,
114+
snippet: &str,
115+
) -> Builder {
69116
let edit = {
70-
let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
117+
let receiver_syntax = receiver.syntax();
71118
let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72119
let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73120
TextEdit::replace(delete_range, snippet.to_string())
@@ -340,4 +387,63 @@ mod tests {
340387
"###
341388
);
342389
}
390+
391+
#[test]
392+
fn postfix_completion_for_references() {
393+
assert_debug_snapshot!(
394+
do_postfix_completion(
395+
r#"
396+
fn main() {
397+
&&&&42.<|>
398+
}
399+
"#,
400+
),
401+
@r###"
402+
[
403+
CompletionItem {
404+
label: "box",
405+
source_range: [56; 56),
406+
delete: [49; 56),
407+
insert: "Box::new(&&&&42)",
408+
detail: "Box::new(expr)",
409+
},
410+
CompletionItem {
411+
label: "dbg",
412+
source_range: [56; 56),
413+
delete: [49; 56),
414+
insert: "dbg!(&&&&42)",
415+
detail: "dbg!(expr)",
416+
},
417+
CompletionItem {
418+
label: "match",
419+
source_range: [56; 56),
420+
delete: [49; 56),
421+
insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
422+
detail: "match expr {}",
423+
},
424+
CompletionItem {
425+
label: "not",
426+
source_range: [56; 56),
427+
delete: [53; 56),
428+
insert: "!42",
429+
detail: "!expr",
430+
},
431+
CompletionItem {
432+
label: "ref",
433+
source_range: [56; 56),
434+
delete: [53; 56),
435+
insert: "&42",
436+
detail: "&expr",
437+
},
438+
CompletionItem {
439+
label: "refm",
440+
source_range: [56; 56),
441+
delete: [53; 56),
442+
insert: "&mut 42",
443+
detail: "&mut expr",
444+
},
445+
]
446+
"###
447+
);
448+
}
343449
}

0 commit comments

Comments
 (0)