Skip to content

Commit 5f2cf7e

Browse files
authored
Merge pull request #20023 from Veykril/push-vkqlnyttnqzl
Improve completions in if / while expression conditions
2 parents e0c5fcb + 7ba853c commit 5f2cf7e

File tree

3 files changed

+256
-154
lines changed

3 files changed

+256
-154
lines changed

src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs

Lines changed: 215 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ide_db::{
1111
text_edit::TextEdit,
1212
ty_filter::TryEnum,
1313
};
14+
use itertools::Either;
1415
use stdx::never;
1516
use syntax::{
1617
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
@@ -86,98 +87,10 @@ pub(crate) fn complete_postfix(
8687
}
8788
}
8889

89-
let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
90-
if let Some(try_enum) = &try_enum {
91-
match try_enum {
92-
TryEnum::Result => {
93-
postfix_snippet(
94-
"ifl",
95-
"if let Ok {}",
96-
&format!("if let Ok($1) = {receiver_text} {{\n $0\n}}"),
97-
)
98-
.add_to(acc, ctx.db);
99-
100-
postfix_snippet(
101-
"lete",
102-
"let Ok else {}",
103-
&format!("let Ok($1) = {receiver_text} else {{\n $2\n}};\n$0"),
104-
)
105-
.add_to(acc, ctx.db);
106-
107-
postfix_snippet(
108-
"while",
109-
"while let Ok {}",
110-
&format!("while let Ok($1) = {receiver_text} {{\n $0\n}}"),
111-
)
112-
.add_to(acc, ctx.db);
113-
}
114-
TryEnum::Option => {
115-
postfix_snippet(
116-
"ifl",
117-
"if let Some {}",
118-
&format!("if let Some($1) = {receiver_text} {{\n $0\n}}"),
119-
)
120-
.add_to(acc, ctx.db);
121-
122-
postfix_snippet(
123-
"lete",
124-
"let Some else {}",
125-
&format!("let Some($1) = {receiver_text} else {{\n $2\n}};\n$0"),
126-
)
127-
.add_to(acc, ctx.db);
128-
129-
postfix_snippet(
130-
"while",
131-
"while let Some {}",
132-
&format!("while let Some($1) = {receiver_text} {{\n $0\n}}"),
133-
)
134-
.add_to(acc, ctx.db);
135-
}
136-
}
137-
} else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
138-
postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n $0\n}}"))
139-
.add_to(acc, ctx.db);
140-
postfix_snippet("while", "while expr {}", &format!("while {receiver_text} {{\n $0\n}}"))
141-
.add_to(acc, ctx.db);
142-
postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
143-
} else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
144-
if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
145-
postfix_snippet(
146-
"for",
147-
"for ele in expr {}",
148-
&format!("for ele in {receiver_text} {{\n $0\n}}"),
149-
)
150-
.add_to(acc, ctx.db);
151-
}
152-
}
153-
15490
postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc, ctx.db);
15591
postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc, ctx.db);
15692
postfix_snippet("deref", "*expr", &format!("*{receiver_text}")).add_to(acc, ctx.db);
15793

158-
let mut block_should_be_wrapped = true;
159-
if dot_receiver.syntax().kind() == BLOCK_EXPR {
160-
block_should_be_wrapped = false;
161-
if let Some(parent) = dot_receiver.syntax().parent() {
162-
if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
163-
block_should_be_wrapped = true;
164-
}
165-
}
166-
};
167-
let unsafe_completion_string = if block_should_be_wrapped {
168-
format!("unsafe {{ {receiver_text} }}")
169-
} else {
170-
format!("unsafe {receiver_text}")
171-
};
172-
postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
173-
174-
let const_completion_string = if block_should_be_wrapped {
175-
format!("const {{ {receiver_text} }}")
176-
} else {
177-
format!("const {receiver_text}")
178-
};
179-
postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
180-
18194
// The rest of the postfix completions create an expression that moves an argument,
18295
// so it's better to consider references now to avoid breaking the compilation
18396

@@ -195,51 +108,192 @@ pub(crate) fn complete_postfix(
195108
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
196109
}
197110

198-
match try_enum {
199-
Some(try_enum) => match try_enum {
200-
TryEnum::Result => {
201-
postfix_snippet(
111+
postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
112+
.add_to(acc, ctx.db);
113+
postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
114+
postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
115+
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
116+
.add_to(acc, ctx.db);
117+
118+
let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
119+
let mut is_in_cond = false;
120+
if let Some(parent) = dot_receiver_including_refs.syntax().parent() {
121+
if let Some(second_ancestor) = parent.parent() {
122+
let sec_ancestor_kind = second_ancestor.kind();
123+
if let Some(expr) = <Either<ast::IfExpr, ast::WhileExpr>>::cast(second_ancestor) {
124+
is_in_cond = match expr {
125+
Either::Left(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent),
126+
Either::Right(it) => {
127+
it.condition().is_some_and(|cond| *cond.syntax() == parent)
128+
}
129+
}
130+
}
131+
match &try_enum {
132+
Some(try_enum) if is_in_cond => match try_enum {
133+
TryEnum::Result => {
134+
postfix_snippet(
135+
"let",
136+
"let Ok(_)",
137+
&format!("let Ok($0) = {receiver_text}"),
138+
)
139+
.add_to(acc, ctx.db);
140+
postfix_snippet(
141+
"letm",
142+
"let Ok(mut _)",
143+
&format!("let Ok(mut $0) = {receiver_text}"),
144+
)
145+
.add_to(acc, ctx.db);
146+
}
147+
TryEnum::Option => {
148+
postfix_snippet(
149+
"let",
150+
"let Some(_)",
151+
&format!("let Some($0) = {receiver_text}"),
152+
)
153+
.add_to(acc, ctx.db);
154+
postfix_snippet(
155+
"letm",
156+
"let Some(mut _)",
157+
&format!("let Some(mut $0) = {receiver_text}"),
158+
)
159+
.add_to(acc, ctx.db);
160+
}
161+
},
162+
_ if matches!(sec_ancestor_kind, STMT_LIST | EXPR_STMT) => {
163+
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
164+
.add_to(acc, ctx.db);
165+
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
166+
.add_to(acc, ctx.db);
167+
}
168+
_ => (),
169+
}
170+
}
171+
}
172+
173+
if !is_in_cond {
174+
match try_enum {
175+
Some(try_enum) => match try_enum {
176+
TryEnum::Result => {
177+
postfix_snippet(
202178
"match",
203179
"match expr {}",
204180
&format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),
205181
)
206182
.add_to(acc, ctx.db);
207-
}
208-
TryEnum::Option => {
209-
postfix_snippet(
183+
}
184+
TryEnum::Option => {
185+
postfix_snippet(
210186
"match",
211187
"match expr {}",
212188
&format!(
213189
"match {receiver_text} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}"
214190
),
215191
)
216192
.add_to(acc, ctx.db);
193+
}
194+
},
195+
None => {
196+
postfix_snippet(
197+
"match",
198+
"match expr {}",
199+
&format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
200+
)
201+
.add_to(acc, ctx.db);
217202
}
218-
},
219-
None => {
203+
}
204+
if let Some(try_enum) = &try_enum {
205+
match try_enum {
206+
TryEnum::Result => {
207+
postfix_snippet(
208+
"ifl",
209+
"if let Ok {}",
210+
&format!("if let Ok($1) = {receiver_text} {{\n $0\n}}"),
211+
)
212+
.add_to(acc, ctx.db);
213+
214+
postfix_snippet(
215+
"lete",
216+
"let Ok else {}",
217+
&format!("let Ok($1) = {receiver_text} else {{\n $2\n}};\n$0"),
218+
)
219+
.add_to(acc, ctx.db);
220+
221+
postfix_snippet(
222+
"while",
223+
"while let Ok {}",
224+
&format!("while let Ok($1) = {receiver_text} {{\n $0\n}}"),
225+
)
226+
.add_to(acc, ctx.db);
227+
}
228+
TryEnum::Option => {
229+
postfix_snippet(
230+
"ifl",
231+
"if let Some {}",
232+
&format!("if let Some($1) = {receiver_text} {{\n $0\n}}"),
233+
)
234+
.add_to(acc, ctx.db);
235+
236+
postfix_snippet(
237+
"lete",
238+
"let Some else {}",
239+
&format!("let Some($1) = {receiver_text} else {{\n $2\n}};\n$0"),
240+
)
241+
.add_to(acc, ctx.db);
242+
243+
postfix_snippet(
244+
"while",
245+
"while let Some {}",
246+
&format!("while let Some($1) = {receiver_text} {{\n $0\n}}"),
247+
)
248+
.add_to(acc, ctx.db);
249+
}
250+
}
251+
} else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
252+
postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n $0\n}}"))
253+
.add_to(acc, ctx.db);
220254
postfix_snippet(
221-
"match",
222-
"match expr {}",
223-
&format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
255+
"while",
256+
"while expr {}",
257+
&format!("while {receiver_text} {{\n $0\n}}"),
224258
)
225259
.add_to(acc, ctx.db);
260+
postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
261+
} else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
262+
if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
263+
postfix_snippet(
264+
"for",
265+
"for ele in expr {}",
266+
&format!("for ele in {receiver_text} {{\n $0\n}}"),
267+
)
268+
.add_to(acc, ctx.db);
269+
}
226270
}
227271
}
228272

229-
postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
230-
.add_to(acc, ctx.db);
231-
postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
232-
postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
233-
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
234-
.add_to(acc, ctx.db);
235-
236-
if let Some(parent) = dot_receiver_including_refs.syntax().parent().and_then(|p| p.parent()) {
237-
if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
238-
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
239-
.add_to(acc, ctx.db);
240-
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
241-
.add_to(acc, ctx.db);
273+
let mut block_should_be_wrapped = true;
274+
if dot_receiver.syntax().kind() == BLOCK_EXPR {
275+
block_should_be_wrapped = false;
276+
if let Some(parent) = dot_receiver.syntax().parent() {
277+
if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
278+
block_should_be_wrapped = true;
279+
}
242280
}
281+
};
282+
{
283+
let (open_brace, close_brace) =
284+
if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };
285+
let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };
286+
let unsafe_completion_string = format!(
287+
"{}unsafe {}{receiver_text}{}{}",
288+
open_paren, open_brace, close_brace, close_paren
289+
);
290+
postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
291+
292+
let const_completion_string = format!(
293+
"{}const {}{receiver_text}{}{}",
294+
open_paren, open_brace, close_brace, close_paren
295+
);
296+
postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
243297
}
244298

245299
if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone() {
@@ -567,6 +621,54 @@ fn main() {
567621
);
568622
}
569623

624+
#[test]
625+
fn option_iflet_cond() {
626+
check(
627+
r#"
628+
//- minicore: option
629+
fn main() {
630+
let bar = Some(true);
631+
if bar.$0
632+
}
633+
"#,
634+
expect![[r#"
635+
me and(…) fn(self, Option<U>) -> Option<U>
636+
me as_ref() const fn(&self) -> Option<&T>
637+
me ok_or(…) const fn(self, E) -> Result<T, E>
638+
me unwrap() const fn(self) -> T
639+
me unwrap_or(…) fn(self, T) -> T
640+
sn box Box::new(expr)
641+
sn call function(expr)
642+
sn const const {}
643+
sn dbg dbg!(expr)
644+
sn dbgr dbg!(&expr)
645+
sn deref *expr
646+
sn let let Some(_)
647+
sn letm let Some(mut _)
648+
sn ref &expr
649+
sn refm &mut expr
650+
sn return return expr
651+
sn unsafe unsafe {}
652+
"#]],
653+
);
654+
check_edit(
655+
"let",
656+
r#"
657+
//- minicore: option
658+
fn main() {
659+
let bar = Some(true);
660+
if bar.$0
661+
}
662+
"#,
663+
r#"
664+
fn main() {
665+
let bar = Some(true);
666+
if let Some($0) = bar
667+
}
668+
"#,
669+
);
670+
}
671+
570672
#[test]
571673
fn option_letelse() {
572674
check_edit(

0 commit comments

Comments
 (0)