aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/early_return.rs2
-rw-r--r--crates/assists/src/handlers/move_guard.rs1
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs277
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/assists/src/lib.rs1
-rw-r--r--crates/assists/src/tests/generated.rs28
-rw-r--r--crates/hir_def/src/docs.rs22
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/syntax/src/ast/make.rs13
9 files changed, 330 insertions, 18 deletions
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
index 7fd78e9d4..7bcc318a9 100644
--- a/crates/assists/src/handlers/early_return.rs
+++ b/crates/assists/src/handlers/early_return.rs
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
112 let then_branch = 112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 let cond = invert_boolean_expression(cond_expr); 114 let cond = invert_boolean_expression(cond_expr);
115 make::expr_if(make::condition(cond, None), then_branch) 115 make::expr_if(make::condition(cond, None), then_branch, None)
116 .indent(if_indent_level) 116 .indent(if_indent_level)
117 }; 117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
diff --git a/crates/assists/src/handlers/move_guard.rs b/crates/assists/src/handlers/move_guard.rs
index e1855b63d..eaffd80ce 100644
--- a/crates/assists/src/handlers/move_guard.rs
+++ b/crates/assists/src/handlers/move_guard.rs
@@ -42,6 +42,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
42 let if_expr = make::expr_if( 42 let if_expr = make::expr_if(
43 make::condition(guard_condition, None), 43 make::condition(guard_condition, None),
44 make::block_expr(None, Some(arm_expr.clone())), 44 make::block_expr(None, Some(arm_expr.clone())),
45 None,
45 ) 46 )
46 .indent(arm_expr.indent_level()); 47 .indent(arm_expr.indent_level());
47 48
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
index 9a49c48c1..4a355c66f 100644
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -1,3 +1,6 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
1use syntax::{ 4use syntax::{
2 ast::{ 5 ast::{
3 self, 6 self,
@@ -8,7 +11,6 @@ use syntax::{
8}; 11};
9 12
10use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; 13use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11use ide_db::ty_filter::TryEnum;
12 14
13// Assist: replace_if_let_with_match 15// Assist: replace_if_let_with_match
14// 16//
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
79 ) 81 )
80} 82}
81 83
84// Assist: replace_match_with_if_let
85//
86// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87//
88// ```
89// enum Action { Move { distance: u32 }, Stop }
90//
91// fn handle(action: Action) {
92// <|>match action {
93// Action::Move { distance } => foo(distance),
94// _ => bar(),
95// }
96// }
97// ```
98// ->
99// ```
100// enum Action { Move { distance: u32 }, Stop }
101//
102// fn handle(action: Action) {
103// if let Action::Move { distance } = action {
104// foo(distance)
105// } else {
106// bar()
107// }
108// }
109// ```
110pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
111 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
112 let mut arms = match_expr.match_arm_list()?.arms();
113 let first_arm = arms.next()?;
114 let second_arm = arms.next()?;
115 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
116 return None;
117 }
118 let condition_expr = match_expr.expr()?;
119 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
120 {
121 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
122 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
123 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
124 } else {
125 return None;
126 };
127
128 let target = match_expr.syntax().text_range();
129 acc.add(
130 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
131 "Replace with if let",
132 target,
133 move |edit| {
134 let condition = make::condition(condition_expr, Some(if_let_pat));
135 let then_block = match then_expr.reset_indent() {
136 ast::Expr::BlockExpr(block) => block,
137 expr => make::block_expr(iter::empty(), Some(expr)),
138 };
139 let else_expr = match else_expr {
140 ast::Expr::BlockExpr(block)
141 if block.statements().count() == 0 && block.expr().is_none() =>
142 {
143 None
144 }
145 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
146 expr => Some(expr),
147 };
148 let if_let_expr = make::expr_if(
149 condition,
150 then_block,
151 else_expr.map(|else_expr| {
152 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
153 }),
154 )
155 .indent(IndentLevel::from_node(match_expr.syntax()));
156
157 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
158 },
159 )
160}
161
162fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
163 sema.type_of_pat(&pat)
164 .and_then(|ty| TryEnum::from_ty(sema, &ty))
165 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
166 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
167}
168
82#[cfg(test)] 169#[cfg(test)]
83mod tests { 170mod tests {
84 use super::*; 171 use super::*;
@@ -252,4 +339,192 @@ fn main() {
252"#, 339"#,
253 ) 340 )
254 } 341 }
342
343 #[test]
344 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
345 check_assist(
346 replace_match_with_if_let,
347 r#"
348impl VariantData {
349 pub fn is_struct(&self) -> bool {
350 <|>match *self {
351 VariantData::Struct(..) => true,
352 _ => false,
353 }
354 }
355} "#,
356 r#"
357impl VariantData {
358 pub fn is_struct(&self) -> bool {
359 if let VariantData::Struct(..) = *self {
360 true
361 } else {
362 false
363 }
364 }
365} "#,
366 )
367 }
368
369 #[test]
370 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
371 check_assist(
372 replace_match_with_if_let,
373 r#"
374fn foo() {
375 <|>match a {
376 VariantData::Struct(..) => {
377 bar(
378 123
379 )
380 }
381 _ => false,
382 }
383} "#,
384 r#"
385fn foo() {
386 if let VariantData::Struct(..) = a {
387 bar(
388 123
389 )
390 } else {
391 false
392 }
393} "#,
394 )
395 }
396
397 #[test]
398 fn replace_match_with_if_let_target() {
399 check_assist_target(
400 replace_match_with_if_let,
401 r#"
402impl VariantData {
403 pub fn is_struct(&self) -> bool {
404 <|>match *self {
405 VariantData::Struct(..) => true,
406 _ => false,
407 }
408 }
409} "#,
410 r#"match *self {
411 VariantData::Struct(..) => true,
412 _ => false,
413 }"#,
414 );
415 }
416
417 #[test]
418 fn special_case_option_match_to_if_let() {
419 check_assist(
420 replace_match_with_if_let,
421 r#"
422enum Option<T> { Some(T), None }
423use Option::*;
424
425fn foo(x: Option<i32>) {
426 <|>match x {
427 Some(x) => println!("{}", x),
428 None => println!("none"),
429 }
430}
431 "#,
432 r#"
433enum Option<T> { Some(T), None }
434use Option::*;
435
436fn foo(x: Option<i32>) {
437 if let Some(x) = x {
438 println!("{}", x)
439 } else {
440 println!("none")
441 }
442}
443 "#,
444 );
445 }
446
447 #[test]
448 fn special_case_result_match_to_if_let() {
449 check_assist(
450 replace_match_with_if_let,
451 r#"
452enum Result<T, E> { Ok(T), Err(E) }
453use Result::*;
454
455fn foo(x: Result<i32, ()>) {
456 <|>match x {
457 Ok(x) => println!("{}", x),
458 Err(_) => println!("none"),
459 }
460}
461 "#,
462 r#"
463enum Result<T, E> { Ok(T), Err(E) }
464use Result::*;
465
466fn foo(x: Result<i32, ()>) {
467 if let Ok(x) = x {
468 println!("{}", x)
469 } else {
470 println!("none")
471 }
472}
473 "#,
474 );
475 }
476
477 #[test]
478 fn nested_indent_match_to_if_let() {
479 check_assist(
480 replace_match_with_if_let,
481 r#"
482fn main() {
483 if true {
484 <|>match path.strip_prefix(root_path) {
485 Ok(rel_path) => {
486 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487 Some((*id, rel_path))
488 }
489 _ => None,
490 }
491 }
492}
493"#,
494 r#"
495fn main() {
496 if true {
497 if let Ok(rel_path) = path.strip_prefix(root_path) {
498 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499 Some((*id, rel_path))
500 } else {
501 None
502 }
503 }
504}
505"#,
506 )
507 }
508
509 #[test]
510 fn replace_match_with_if_let_empty_wildcard_expr() {
511 check_assist(
512 replace_match_with_if_let,
513 r#"
514fn main() {
515 <|>match path.strip_prefix(root_path) {
516 Ok(rel_path) => println!("{}", rel_path),
517 _ => (),
518 }
519}
520"#,
521 r#"
522fn main() {
523 if let Ok(rel_path) = path.strip_prefix(root_path) {
524 println!("{}", rel_path)
525 }
526}
527"#,
528 )
529 }
255} 530}
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index 69d3b08d3..5970e283c 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
60 }; 60 };
61 let block = 61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); 62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
64 let stmt = make::expr_stmt(if_); 64 let stmt = make::expr_stmt(if_);
65 65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap(); 66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index dfe6c2729..b8ce7418d 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -209,6 +209,7 @@ mod handlers {
209 reorder_fields::reorder_fields, 209 reorder_fields::reorder_fields,
210 replace_derive_with_manual_impl::replace_derive_with_manual_impl, 210 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
211 replace_if_let_with_match::replace_if_let_with_match, 211 replace_if_let_with_match::replace_if_let_with_match,
212 replace_if_let_with_match::replace_match_with_if_let,
212 replace_impl_trait_with_generic::replace_impl_trait_with_generic, 213 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
213 replace_let_with_if_let::replace_let_with_if_let, 214 replace_let_with_if_let::replace_let_with_if_let,
214 replace_qualified_name_with_use::replace_qualified_name_with_use, 215 replace_qualified_name_with_use::replace_qualified_name_with_use,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 8d50c8791..853bde09c 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -890,6 +890,34 @@ fn compute() -> Option<i32> { None }
890} 890}
891 891
892#[test] 892#[test]
893fn doctest_replace_match_with_if_let() {
894 check_doc_test(
895 "replace_match_with_if_let",
896 r#####"
897enum Action { Move { distance: u32 }, Stop }
898
899fn handle(action: Action) {
900 <|>match action {
901 Action::Move { distance } => foo(distance),
902 _ => bar(),
903 }
904}
905"#####,
906 r#####"
907enum Action { Move { distance: u32 }, Stop }
908
909fn handle(action: Action) {
910 if let Action::Move { distance } = action {
911 foo(distance)
912 } else {
913 bar()
914 }
915}
916"#####,
917 )
918}
919
920#[test]
893fn doctest_replace_qualified_name_with_use() { 921fn doctest_replace_qualified_name_with_use() {
894 check_doc_test( 922 check_doc_test(
895 "replace_qualified_name_with_use", 923 "replace_qualified_name_with_use",
diff --git a/crates/hir_def/src/docs.rs b/crates/hir_def/src/docs.rs
index e9a02b11b..3e59a8f47 100644
--- a/crates/hir_def/src/docs.rs
+++ b/crates/hir_def/src/docs.rs
@@ -6,7 +6,8 @@
6use std::sync::Arc; 6use std::sync::Arc;
7 7
8use either::Either; 8use either::Either;
9use syntax::ast; 9use itertools::Itertools;
10use syntax::{ast, SmolStr};
10 11
11use crate::{ 12use crate::{
12 db::DefDatabase, 13 db::DefDatabase,
@@ -93,7 +94,7 @@ fn merge_doc_comments_and_attrs(
93) -> Option<String> { 94) -> Option<String> {
94 match (doc_comment_text, doc_attr_text) { 95 match (doc_comment_text, doc_attr_text) {
95 (Some(mut comment_text), Some(attr_text)) => { 96 (Some(mut comment_text), Some(attr_text)) => {
96 comment_text.push_str("\n\n"); 97 comment_text.push_str("\n");
97 comment_text.push_str(&attr_text); 98 comment_text.push_str(&attr_text);
98 Some(comment_text) 99 Some(comment_text)
99 } 100 }
@@ -105,17 +106,16 @@ fn merge_doc_comments_and_attrs(
105 106
106fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> { 107fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
107 let mut docs = String::new(); 108 let mut docs = String::new();
108 for attr in owner.attrs() { 109 owner
109 if let Some(("doc", value)) = 110 .attrs()
110 attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) 111 .filter_map(|attr| attr.as_simple_key_value().filter(|(key, _)| key == "doc"))
111 { 112 .map(|(_, value)| value)
112 docs.push_str(value); 113 .intersperse(SmolStr::new_inline("\n"))
113 docs.push_str("\n\n"); 114 // No FromIterator<SmolStr> for String
114 } 115 .for_each(|s| docs.push_str(s.as_str()));
115 }
116 if docs.is_empty() { 116 if docs.is_empty() {
117 None 117 None
118 } else { 118 } else {
119 Some(docs.trim_end_matches("\n\n").to_owned()) 119 Some(docs)
120 } 120 }
121} 121}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 462f5c2b8..dc9621f46 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1525,9 +1525,7 @@ fn foo() { let bar = Ba<|>r; }
1525 --- 1525 ---
1526 1526
1527 bar docs 0 1527 bar docs 0
1528
1529 bar docs 1 1528 bar docs 1
1530
1531 bar docs 2 1529 bar docs 2
1532 "#]], 1530 "#]],
1533 ); 1531 );
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 876659a2b..cc09b77a5 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -171,8 +171,17 @@ pub fn expr_return() -> ast::Expr {
171pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { 171pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
172 expr_from_text(&format!("match {} {}", expr, match_arm_list)) 172 expr_from_text(&format!("match {} {}", expr, match_arm_list))
173} 173}
174pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr { 174pub fn expr_if(
175 expr_from_text(&format!("if {} {}", condition, then_branch)) 175 condition: ast::Condition,
176 then_branch: ast::BlockExpr,
177 else_branch: Option<ast::ElseBranch>,
178) -> ast::Expr {
179 let else_branch = match else_branch {
180 Some(ast::ElseBranch::Block(block)) => format!("else {}", block),
181 Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr),
182 None => String::new(),
183 };
184 expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
176} 185}
177pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { 186pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
178 let token = token(op); 187 let token = token(op);