Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #20023 from Veykril/push-vkqlnyttnqzl
Improve completions in if / while expression conditions
Lukas Wirth 10 months ago
parent 630628f · parent 7447db8 · commit 3bf8a77
-rw-r--r--crates/ide-completion/src/completions/postfix.rs328
-rw-r--r--crates/ide-completion/src/render.rs80
-rw-r--r--crates/ide-db/src/ty_filter.rs2
3 files changed, 256 insertions, 154 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index b79a97025b..2e2a3e83cc 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -11,6 +11,7 @@ use ide_db::{
text_edit::TextEdit,
ty_filter::TryEnum,
};
+use itertools::Either;
use stdx::never;
use syntax::{
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
@@ -86,98 +87,10 @@ pub(crate) fn complete_postfix(
}
}
- let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
- if let Some(try_enum) = &try_enum {
- match try_enum {
- TryEnum::Result => {
- postfix_snippet(
- "ifl",
- "if let Ok {}",
- &format!("if let Ok($1) = {receiver_text} {{\n $0\n}}"),
- )
- .add_to(acc, ctx.db);
-
- postfix_snippet(
- "lete",
- "let Ok else {}",
- &format!("let Ok($1) = {receiver_text} else {{\n $2\n}};\n$0"),
- )
- .add_to(acc, ctx.db);
-
- postfix_snippet(
- "while",
- "while let Ok {}",
- &format!("while let Ok($1) = {receiver_text} {{\n $0\n}}"),
- )
- .add_to(acc, ctx.db);
- }
- TryEnum::Option => {
- postfix_snippet(
- "ifl",
- "if let Some {}",
- &format!("if let Some($1) = {receiver_text} {{\n $0\n}}"),
- )
- .add_to(acc, ctx.db);
-
- postfix_snippet(
- "lete",
- "let Some else {}",
- &format!("let Some($1) = {receiver_text} else {{\n $2\n}};\n$0"),
- )
- .add_to(acc, ctx.db);
-
- postfix_snippet(
- "while",
- "while let Some {}",
- &format!("while let Some($1) = {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}}"))
- .add_to(acc, ctx.db);
- postfix_snippet("while", "while expr {}", &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() {
- if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
- postfix_snippet(
- "for",
- "for ele in expr {}",
- &format!("for ele in {receiver_text} {{\n $0\n}}"),
- )
- .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);
- let mut block_should_be_wrapped = true;
- if dot_receiver.syntax().kind() == BLOCK_EXPR {
- block_should_be_wrapped = false;
- if let Some(parent) = dot_receiver.syntax().parent() {
- if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
- block_should_be_wrapped = true;
- }
- }
- };
- let unsafe_completion_string = if block_should_be_wrapped {
- format!("unsafe {{ {receiver_text} }}")
- } else {
- format!("unsafe {receiver_text}")
- };
- postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
-
- let const_completion_string = if block_should_be_wrapped {
- format!("const {{ {receiver_text} }}")
- } else {
- format!("const {receiver_text}")
- };
- postfix_snippet("const", "const {}", &const_completion_string).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
@@ -195,18 +108,81 @@ pub(crate) fn complete_postfix(
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
}
- match try_enum {
- Some(try_enum) => match try_enum {
- TryEnum::Result => {
- postfix_snippet(
+ 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})"))
+ .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() {
+ if let Some(second_ancestor) = parent.parent() {
+ let sec_ancestor_kind = second_ancestor.kind();
+ if let Some(expr) = <Either<ast::IfExpr, ast::WhileExpr>>::cast(second_ancestor) {
+ is_in_cond = match expr {
+ Either::Left(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent),
+ Either::Right(it) => {
+ it.condition().is_some_and(|cond| *cond.syntax() == parent)
+ }
+ }
+ }
+ match &try_enum {
+ Some(try_enum) if is_in_cond => match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "let",
+ "let Ok(_)",
+ &format!("let Ok($0) = {receiver_text}"),
+ )
+ .add_to(acc, ctx.db);
+ postfix_snippet(
+ "letm",
+ "let Ok(mut _)",
+ &format!("let Ok(mut $0) = {receiver_text}"),
+ )
+ .add_to(acc, ctx.db);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "let",
+ "let Some(_)",
+ &format!("let Some($0) = {receiver_text}"),
+ )
+ .add_to(acc, ctx.db);
+ postfix_snippet(
+ "letm",
+ "let Some(mut _)",
+ &format!("let Some(mut $0) = {receiver_text}"),
+ )
+ .add_to(acc, ctx.db);
+ }
+ },
+ _ if matches!(sec_ancestor_kind, STMT_LIST | EXPR_STMT) => {
+ postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
+ .add_to(acc, ctx.db);
+ postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
+ .add_to(acc, ctx.db);
+ }
+ _ => (),
+ }
+ }
+ }
+
+ if !is_in_cond {
+ match try_enum {
+ Some(try_enum) => match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
"match",
"match expr {}",
&format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),
)
.add_to(acc, ctx.db);
- }
- TryEnum::Option => {
- postfix_snippet(
+ }
+ TryEnum::Option => {
+ postfix_snippet(
"match",
"match expr {}",
&format!(
@@ -214,32 +190,110 @@ pub(crate) fn complete_postfix(
),
)
.add_to(acc, ctx.db);
+ }
+ },
+ None => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
+ )
+ .add_to(acc, ctx.db);
}
- },
- None => {
+ }
+ if let Some(try_enum) = &try_enum {
+ match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "ifl",
+ "if let Ok {}",
+ &format!("if let Ok($1) = {receiver_text} {{\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+
+ postfix_snippet(
+ "lete",
+ "let Ok else {}",
+ &format!("let Ok($1) = {receiver_text} else {{\n $2\n}};\n$0"),
+ )
+ .add_to(acc, ctx.db);
+
+ postfix_snippet(
+ "while",
+ "while let Ok {}",
+ &format!("while let Ok($1) = {receiver_text} {{\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "ifl",
+ "if let Some {}",
+ &format!("if let Some($1) = {receiver_text} {{\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+
+ postfix_snippet(
+ "lete",
+ "let Some else {}",
+ &format!("let Some($1) = {receiver_text} else {{\n $2\n}};\n$0"),
+ )
+ .add_to(acc, ctx.db);
+
+ postfix_snippet(
+ "while",
+ "while let Some {}",
+ &format!("while let Some($1) = {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}}"))
+ .add_to(acc, ctx.db);
postfix_snippet(
- "match",
- "match expr {}",
- &format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
+ "while",
+ "while expr {}",
+ &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() {
+ if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
+ postfix_snippet(
+ "for",
+ "for ele in expr {}",
+ &format!("for ele in {receiver_text} {{\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ }
}
}
- 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})"))
- .add_to(acc, ctx.db);
-
- if let Some(parent) = dot_receiver_including_refs.syntax().parent().and_then(|p| p.parent()) {
- if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
- postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
- .add_to(acc, ctx.db);
- postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
- .add_to(acc, ctx.db);
+ let mut block_should_be_wrapped = true;
+ if dot_receiver.syntax().kind() == BLOCK_EXPR {
+ block_should_be_wrapped = false;
+ if let Some(parent) = dot_receiver.syntax().parent() {
+ if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
+ block_should_be_wrapped = true;
+ }
}
+ };
+ {
+ let (open_brace, close_brace) =
+ if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };
+ let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };
+ let unsafe_completion_string = format!(
+ "{}unsafe {}{receiver_text}{}{}",
+ open_paren, open_brace, close_brace, close_paren
+ );
+ postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
+
+ let const_completion_string = format!(
+ "{}const {}{receiver_text}{}{}",
+ open_paren, open_brace, close_brace, close_paren
+ );
+ postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
}
if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone() {
@@ -568,6 +622,54 @@ fn main() {
}
#[test]
+ fn option_iflet_cond() {
+ check(
+ r#"
+//- minicore: option
+fn main() {
+ let bar = Some(true);
+ if bar.$0
+}
+"#,
+ expect![[r#"
+ me and(…) fn(self, Option<U>) -> Option<U>
+ me as_ref() const fn(&self) -> Option<&T>
+ me ok_or(…) const fn(self, E) -> Result<T, E>
+ me unwrap() const fn(self) -> T
+ me unwrap_or(…) fn(self, T) -> T
+ 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 Some(_)
+ sn letm let Some(mut _)
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check_edit(
+ "let",
+ r#"
+//- minicore: option
+fn main() {
+ let bar = Some(true);
+ if bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = Some(true);
+ if let Some($0) = bar
+}
+"#,
+ );
+ }
+
+ #[test]
fn option_letelse() {
check_edit(
"lete",
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index d6e30fe0c6..c6b8af3c79 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -733,7 +733,7 @@ mod tests {
) {
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
actual.retain(|it| kinds.contains(&it.kind));
- actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
+ actual.sort_by_key(|it| (cmp::Reverse(it.relevance.score()), it.label.primary.clone()));
check_relevance_(actual, expect);
}
@@ -743,7 +743,7 @@ mod tests {
actual.retain(|it| it.kind != CompletionItemKind::Snippet);
actual.retain(|it| it.kind != CompletionItemKind::Keyword);
actual.retain(|it| it.kind != CompletionItemKind::BuiltinType);
- actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
+ actual.sort_by_key(|it| (cmp::Reverse(it.relevance.score()), it.label.primary.clone()));
check_relevance_(actual, expect);
}
@@ -824,9 +824,9 @@ fn main() {
st dep::test_mod_b::Struct {…} dep::test_mod_b::Struct { } [type_could_unify]
ex dep::test_mod_b::Struct { } [type_could_unify]
st Struct Struct [type_could_unify+requires_import]
+ md dep []
fn main() fn() []
fn test(…) fn(Struct) []
- md dep []
st Struct Struct [requires_import]
"#]],
);
@@ -862,9 +862,9 @@ fn main() {
"#,
expect![[r#"
un Union Union [type_could_unify+requires_import]
+ md dep []
fn main() fn() []
fn test(…) fn(Union) []
- md dep []
en Union Union [requires_import]
"#]],
);
@@ -900,9 +900,9 @@ fn main() {
ev dep::test_mod_b::Enum::variant dep::test_mod_b::Enum::variant [type_could_unify]
ex dep::test_mod_b::Enum::variant [type_could_unify]
en Enum Enum [type_could_unify+requires_import]
+ md dep []
fn main() fn() []
fn test(…) fn(Enum) []
- md dep []
en Enum Enum [requires_import]
"#]],
);
@@ -937,9 +937,9 @@ fn main() {
expect![[r#"
ev dep::test_mod_b::Enum::Variant dep::test_mod_b::Enum::Variant [type_could_unify]
ex dep::test_mod_b::Enum::Variant [type_could_unify]
+ md dep []
fn main() fn() []
fn test(…) fn(Enum) []
- md dep []
"#]],
);
}
@@ -967,9 +967,9 @@ fn main() {
}
"#,
expect![[r#"
+ md dep []
fn main() fn() []
fn test(…) fn(fn(usize) -> i32) []
- md dep []
fn function fn(usize) -> i32 [requires_import]
fn function(…) fn(isize) -> i32 [requires_import]
"#]],
@@ -1000,9 +1000,9 @@ fn main() {
"#,
expect![[r#"
ct CONST i32 [type_could_unify+requires_import]
+ md dep []
fn main() fn() []
fn test(…) fn(i32) []
- md dep []
ct CONST i64 [requires_import]
"#]],
);
@@ -1032,9 +1032,9 @@ fn main() {
"#,
expect![[r#"
sc STATIC i32 [type_could_unify+requires_import]
+ md dep []
fn main() fn() []
fn test(…) fn(i32) []
- md dep []
sc STATIC i64 [requires_import]
"#]],
);
@@ -1090,8 +1090,8 @@ fn func(input: Struct) { }
"#,
expect![[r#"
- st Struct Struct [type]
st Self Self [type]
+ st Struct Struct [type]
sp Self Struct [type]
st Struct Struct [type]
ex Struct [type]
@@ -1119,9 +1119,9 @@ fn main() {
"#,
expect![[r#"
lc input bool [type+name+local]
+ ex false [type]
ex input [type]
ex true [type]
- ex false [type]
lc inputbad i32 [local]
fn main() fn() []
fn test(…) fn(bool) []
@@ -2088,9 +2088,9 @@ fn f() { A { bar: b$0 }; }
"#,
expect![[r#"
fn bar() fn() -> u8 [type+name]
+ ex bar() [type]
fn baz() fn() -> u8 [type]
ex baz() [type]
- ex bar() [type]
st A A []
fn f() fn() []
"#]],
@@ -2199,8 +2199,8 @@ fn main() {
lc s S [type+name+local]
st S S [type]
st S S [type]
- ex s [type]
ex S [type]
+ ex s [type]
fn foo(…) fn(&mut S) []
fn main() fn() []
"#]],
@@ -2218,8 +2218,8 @@ fn main() {
st S S [type]
lc ssss S [type+local]
st S S [type]
- ex ssss [type]
ex S [type]
+ ex ssss [type]
fn foo(…) fn(&mut S) []
fn main() fn() []
"#]],
@@ -2252,11 +2252,11 @@ fn main() {
ex Foo [type]
lc foo &Foo [local]
lc *foo [type+local]
- fn bar(…) fn(Foo) []
- fn main() fn() []
- md core []
tt Clone []
tt Copy []
+ fn bar(…) fn(Foo) []
+ md core []
+ fn main() fn() []
"#]],
);
}
@@ -2297,9 +2297,9 @@ fn main() {
st &S [type]
st T T []
st &T [type]
+ md core []
fn foo(…) fn(&S) []
fn main() fn() []
- md core []
"#]],
)
}
@@ -2346,9 +2346,9 @@ fn main() {
st &mut S [type]
st T T []
st &mut T [type]
+ md core []
fn foo(…) fn(&mut S) []
fn main() fn() []
- md core []
"#]],
)
}
@@ -2364,8 +2364,8 @@ fn foo(bar: u32) {
}
"#,
expect![[r#"
- lc baz i32 [local]
lc bar u32 [local]
+ lc baz i32 [local]
fn foo(…) fn(u32) []
"#]],
);
@@ -2449,9 +2449,9 @@ fn main() {
st &T [type]
fn bar() fn() -> T []
fn &bar() [type]
+ md core []
fn foo(…) fn(&S) []
fn main() fn() []
- md core []
"#]],
)
}
@@ -2702,8 +2702,8 @@ fn test() {
fn fn_builder() fn() -> FooBuilder [type_could_unify]
fn fn_ctr_wrapped() fn() -> Option<Foo<T>> [type_could_unify]
fn fn_ctr_wrapped_2() fn() -> Result<Foo<T>, u32> [type_could_unify]
- me fn_returns_unit(…) fn(&self) [type_could_unify]
fn fn_other() fn() -> Option<u32> [type_could_unify]
+ me fn_returns_unit(…) fn(&self) [type_could_unify]
"#]],
);
}
@@ -2965,12 +2965,12 @@ fn foo() {
ev Foo::B Foo::B [type_could_unify]
ev Foo::A(…) Foo::A(T) [type_could_unify]
lc foo Foo<u32> [type+local]
- ex foo [type]
ex Foo::B [type]
+ ex foo [type]
en Foo Foo<{unknown}> [type_could_unify]
- fn foo() fn() []
fn bar() fn() -> Foo<u8> []
fn baz() fn() -> Foo<T> []
+ fn foo() fn() []
"#]],
);
}
@@ -3000,19 +3000,19 @@ fn main() {
expect![[r#"
sn not !expr [snippet]
me not() fn(self) -> <Self as Not>::Output [type_could_unify+requires_import]
- sn if if expr {} []
- sn while while expr {} []
- sn ref &expr []
- sn refm &mut expr []
- sn deref *expr []
- sn unsafe unsafe {} []
- sn const const {} []
- sn match match expr {} []
sn box Box::new(expr) []
+ sn call function(expr) []
+ sn const const {} []
sn dbg dbg!(expr) []
sn dbgr dbg!(&expr) []
- sn call function(expr) []
+ sn deref *expr []
+ sn if if expr {} []
+ sn match match expr {} []
+ sn ref &expr []
+ sn refm &mut expr []
sn return return expr []
+ sn unsafe unsafe {} []
+ sn while while expr {} []
"#]],
);
}
@@ -3033,19 +3033,19 @@ fn main() {
&[CompletionItemKind::Snippet, CompletionItemKind::SymbolKind(SymbolKind::Method)],
expect![[r#"
me f() fn(&self) []
- sn ref &expr []
- sn refm &mut expr []
- sn deref *expr []
- sn unsafe unsafe {} []
- sn const const {} []
- sn match match expr {} []
sn box Box::new(expr) []
+ sn call function(expr) []
+ sn const const {} []
sn dbg dbg!(expr) []
sn dbgr dbg!(&expr) []
- sn call function(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 {} []
"#]],
);
}
diff --git a/crates/ide-db/src/ty_filter.rs b/crates/ide-db/src/ty_filter.rs
index e8f2fbc9eb..095256d829 100644
--- a/crates/ide-db/src/ty_filter.rs
+++ b/crates/ide-db/src/ty_filter.rs
@@ -10,7 +10,7 @@ use syntax::ast::{self, Pat, make};
use crate::RootDatabase;
/// Enum types that implement `std::ops::Try` trait.
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
pub enum TryEnum {
Result,
Option,