aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs971
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/tests/generated.rs13
-rw-r--r--crates/ra_db/src/fixture.rs46
-rw-r--r--crates/ra_hir_ty/src/_match.rs29
-rw-r--r--crates/test_utils/src/lib.rs104
-rw-r--r--docs/user/assists.md12
-rw-r--r--xtask/tests/tidy.rs1
8 files changed, 1161 insertions, 17 deletions
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
new file mode 100644
index 000000000..1e8d986cd
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -0,0 +1,971 @@
1use ra_syntax::{
2 ast, AstNode,
3 SyntaxKind::{COMMENT, WHITESPACE},
4 SyntaxNode, TextSize,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8use ast::{BlockExpr, Expr, LoopBodyOwner};
9
10// Assist: change_return_type_to_result
11//
12// Change the function's return type to Result.
13//
14// ```
15// fn foo() -> i32<|> { 42i32 }
16// ```
17// ->
18// ```
19// fn foo() -> Result<i32, > { Ok(42i32) }
20// ```
21pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> {
22 let fn_def = ctx.find_node_at_offset::<ast::FnDef>();
23 let fn_def = &mut fn_def?;
24 let ret_type = &fn_def.ret_type()?.type_ref()?;
25 if ret_type.syntax().text().to_string().starts_with("Result<") {
26 return None;
27 }
28
29 let block_expr = &fn_def.body()?;
30 let cursor_in_ret_type =
31 fn_def.ret_type()?.syntax().text_range().contains_range(ctx.frange.range);
32 if !cursor_in_ret_type {
33 return None;
34 }
35
36 ctx.add_assist(
37 AssistId("change_return_type_to_result"),
38 "Change return type to Result",
39 ret_type.syntax().text_range(),
40 |edit| {
41 let mut tail_return_expr_collector = TailReturnCollector::new();
42 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
43 tail_return_expr_collector.collect_tail_exprs(block_expr);
44
45 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
46 edit.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
47 }
48 edit.replace_node_and_indent(ret_type.syntax(), format!("Result<{}, >", ret_type));
49
50 if let Some(node_start) = result_insertion_offset(&ret_type) {
51 edit.set_cursor(node_start + TextSize::of(&format!("Result<{}, ", ret_type)));
52 }
53 },
54 )
55}
56
57struct TailReturnCollector {
58 exprs_to_wrap: Vec<SyntaxNode>,
59}
60
61impl TailReturnCollector {
62 fn new() -> Self {
63 Self { exprs_to_wrap: vec![] }
64 }
65 /// Collect all`return` expression
66 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
67 let statements = block_expr.statements();
68 for stmt in statements {
69 let expr = match &stmt {
70 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
71 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
72 };
73 if let Some(expr) = &expr {
74 self.handle_exprs(expr, collect_break);
75 }
76 }
77
78 // Browse tail expressions for each block
79 if let Some(expr) = block_expr.expr() {
80 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
81 for last_expr in last_exprs {
82 let last_expr = match last_expr {
83 NodeType::Node(expr) | NodeType::Leaf(expr) => expr,
84 };
85
86 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
87 self.handle_exprs(&last_expr, collect_break);
88 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
89 let expr_stmt = match &expr_stmt {
90 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
91 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
92 };
93 if let Some(expr) = &expr_stmt {
94 self.handle_exprs(expr, collect_break);
95 }
96 }
97 }
98 }
99 }
100 }
101
102 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
103 match expr {
104 Expr::BlockExpr(block_expr) => {
105 self.collect_jump_exprs(&block_expr, collect_break);
106 }
107 Expr::ReturnExpr(ret_expr) => {
108 if let Some(ret_expr_arg) = &ret_expr.expr() {
109 self.exprs_to_wrap.push(ret_expr_arg.syntax().clone());
110 }
111 }
112 Expr::BreakExpr(break_expr) if collect_break => {
113 if let Some(break_expr_arg) = &break_expr.expr() {
114 self.exprs_to_wrap.push(break_expr_arg.syntax().clone());
115 }
116 }
117 Expr::IfExpr(if_expr) => {
118 for block in if_expr.blocks() {
119 self.collect_jump_exprs(&block, collect_break);
120 }
121 }
122 Expr::LoopExpr(loop_expr) => {
123 if let Some(block_expr) = loop_expr.loop_body() {
124 self.collect_jump_exprs(&block_expr, collect_break);
125 }
126 }
127 Expr::ForExpr(for_expr) => {
128 if let Some(block_expr) = for_expr.loop_body() {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 }
132 Expr::WhileExpr(while_expr) => {
133 if let Some(block_expr) = while_expr.loop_body() {
134 self.collect_jump_exprs(&block_expr, collect_break);
135 }
136 }
137 Expr::MatchExpr(match_expr) => {
138 if let Some(arm_list) = match_expr.match_arm_list() {
139 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
140 self.handle_exprs(&expr, collect_break);
141 });
142 }
143 }
144 _ => {}
145 }
146 }
147
148 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
149 if let Some(expr) = block.expr() {
150 self.handle_exprs(&expr, true);
151 self.fetch_tail_exprs(&expr);
152 }
153 }
154
155 fn fetch_tail_exprs(&mut self, expr: &Expr) {
156 if let Some(exprs) = get_tail_expr_from_block(expr) {
157 for node_type in &exprs {
158 match node_type {
159 NodeType::Leaf(expr) => {
160 self.exprs_to_wrap.push(expr.clone());
161 }
162 NodeType::Node(expr) => match &Expr::cast(expr.clone()) {
163 Some(last_expr) => {
164 self.fetch_tail_exprs(last_expr);
165 }
166 None => {
167 self.exprs_to_wrap.push(expr.clone());
168 }
169 },
170 }
171 }
172 }
173 }
174}
175
176#[derive(Debug)]
177enum NodeType {
178 Leaf(SyntaxNode),
179 Node(SyntaxNode),
180}
181
182/// Get a tail expression inside a block
183fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
184 match expr {
185 Expr::IfExpr(if_expr) => {
186 let mut nodes = vec![];
187 for block in if_expr.blocks() {
188 if let Some(block_expr) = block.expr() {
189 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
190 nodes.extend(tail_exprs);
191 }
192 } else if let Some(last_expr) = block.syntax().last_child() {
193 nodes.push(NodeType::Node(last_expr));
194 } else {
195 nodes.push(NodeType::Node(block.syntax().clone()));
196 }
197 }
198 Some(nodes)
199 }
200 Expr::LoopExpr(loop_expr) => {
201 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
202 }
203 Expr::ForExpr(for_expr) => {
204 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
205 }
206 Expr::WhileExpr(while_expr) => {
207 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
208 }
209 Expr::BlockExpr(block_expr) => {
210 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
211 }
212 Expr::MatchExpr(match_expr) => {
213 let arm_list = match_expr.match_arm_list()?;
214 let arms: Vec<NodeType> = arm_list
215 .arms()
216 .filter_map(|match_arm| match_arm.expr())
217 .map(|expr| match expr {
218 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
219 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
220 _ => match expr.syntax().last_child() {
221 Some(last_expr) => NodeType::Node(last_expr),
222 None => NodeType::Node(expr.syntax().clone()),
223 },
224 })
225 .collect();
226
227 Some(arms)
228 }
229 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]),
230 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
231 Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]),
232 Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]),
233 Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
234 Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
235 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
236 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
237 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
238 Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
239 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
240 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
247 Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
248 Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
249 _ => None,
250 }
251}
252
253fn result_insertion_offset(ret_type: &ast::TypeRef) -> Option<TextSize> {
254 let non_ws_child = ret_type
255 .syntax()
256 .children_with_tokens()
257 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
258 Some(non_ws_child.text_range().start())
259}
260
261#[cfg(test)]
262mod tests {
263
264 use crate::tests::{check_assist, check_assist_not_applicable};
265
266 use super::*;
267
268 #[test]
269 fn change_return_type_to_result_simple() {
270 check_assist(
271 change_return_type_to_result,
272 r#"fn foo() -> i3<|>2 {
273 let test = "test";
274 return 42i32;
275 }"#,
276 r#"fn foo() -> Result<i32, <|>> {
277 let test = "test";
278 return Ok(42i32);
279 }"#,
280 );
281 }
282
283 #[test]
284 fn change_return_type_to_result_simple_return_type() {
285 check_assist(
286 change_return_type_to_result,
287 r#"fn foo() -> i32<|> {
288 let test = "test";
289 return 42i32;
290 }"#,
291 r#"fn foo() -> Result<i32, <|>> {
292 let test = "test";
293 return Ok(42i32);
294 }"#,
295 );
296 }
297
298 #[test]
299 fn change_return_type_to_result_simple_return_type_bad_cursor() {
300 check_assist_not_applicable(
301 change_return_type_to_result,
302 r#"fn foo() -> i32 {
303 let test = "test";<|>
304 return 42i32;
305 }"#,
306 );
307 }
308
309 #[test]
310 fn change_return_type_to_result_simple_with_cursor() {
311 check_assist(
312 change_return_type_to_result,
313 r#"fn foo() -> <|>i32 {
314 let test = "test";
315 return 42i32;
316 }"#,
317 r#"fn foo() -> Result<i32, <|>> {
318 let test = "test";
319 return Ok(42i32);
320 }"#,
321 );
322 }
323
324 #[test]
325 fn change_return_type_to_result_simple_with_tail() {
326 check_assist(
327 change_return_type_to_result,
328 r#"fn foo() -><|> i32 {
329 let test = "test";
330 42i32
331 }"#,
332 r#"fn foo() -> Result<i32, <|>> {
333 let test = "test";
334 Ok(42i32)
335 }"#,
336 );
337 }
338
339 #[test]
340 fn change_return_type_to_result_simple_with_tail_only() {
341 check_assist(
342 change_return_type_to_result,
343 r#"fn foo() -> i32<|> {
344 42i32
345 }"#,
346 r#"fn foo() -> Result<i32, <|>> {
347 Ok(42i32)
348 }"#,
349 );
350 }
351 #[test]
352 fn change_return_type_to_result_simple_with_tail_block_like() {
353 check_assist(
354 change_return_type_to_result,
355 r#"fn foo() -> i32<|> {
356 if true {
357 42i32
358 } else {
359 24i32
360 }
361 }"#,
362 r#"fn foo() -> Result<i32, <|>> {
363 if true {
364 Ok(42i32)
365 } else {
366 Ok(24i32)
367 }
368 }"#,
369 );
370 }
371
372 #[test]
373 fn change_return_type_to_result_simple_with_nested_if() {
374 check_assist(
375 change_return_type_to_result,
376 r#"fn foo() -> i32<|> {
377 if true {
378 if false {
379 1
380 } else {
381 2
382 }
383 } else {
384 24i32
385 }
386 }"#,
387 r#"fn foo() -> Result<i32, <|>> {
388 if true {
389 if false {
390 Ok(1)
391 } else {
392 Ok(2)
393 }
394 } else {
395 Ok(24i32)
396 }
397 }"#,
398 );
399 }
400
401 #[test]
402 fn change_return_type_to_result_simple_with_await() {
403 check_assist(
404 change_return_type_to_result,
405 r#"async fn foo() -> i<|>32 {
406 if true {
407 if false {
408 1.await
409 } else {
410 2.await
411 }
412 } else {
413 24i32.await
414 }
415 }"#,
416 r#"async fn foo() -> Result<i32, <|>> {
417 if true {
418 if false {
419 Ok(1.await)
420 } else {
421 Ok(2.await)
422 }
423 } else {
424 Ok(24i32.await)
425 }
426 }"#,
427 );
428 }
429
430 #[test]
431 fn change_return_type_to_result_simple_with_array() {
432 check_assist(
433 change_return_type_to_result,
434 r#"fn foo() -> [i32;<|> 3] {
435 [1, 2, 3]
436 }"#,
437 r#"fn foo() -> Result<[i32; 3], <|>> {
438 Ok([1, 2, 3])
439 }"#,
440 );
441 }
442
443 #[test]
444 fn change_return_type_to_result_simple_with_cast() {
445 check_assist(
446 change_return_type_to_result,
447 r#"fn foo() -<|>> i32 {
448 if true {
449 if false {
450 1 as i32
451 } else {
452 2 as i32
453 }
454 } else {
455 24 as i32
456 }
457 }"#,
458 r#"fn foo() -> Result<i32, <|>> {
459 if true {
460 if false {
461 Ok(1 as i32)
462 } else {
463 Ok(2 as i32)
464 }
465 } else {
466 Ok(24 as i32)
467 }
468 }"#,
469 );
470 }
471
472 #[test]
473 fn change_return_type_to_result_simple_with_tail_block_like_match() {
474 check_assist(
475 change_return_type_to_result,
476 r#"fn foo() -> i32<|> {
477 let my_var = 5;
478 match my_var {
479 5 => 42i32,
480 _ => 24i32,
481 }
482 }"#,
483 r#"fn foo() -> Result<i32, <|>> {
484 let my_var = 5;
485 match my_var {
486 5 => Ok(42i32),
487 _ => Ok(24i32),
488 }
489 }"#,
490 );
491 }
492
493 #[test]
494 fn change_return_type_to_result_simple_with_loop_with_tail() {
495 check_assist(
496 change_return_type_to_result,
497 r#"fn foo() -> i32<|> {
498 let my_var = 5;
499 loop {
500 println!("test");
501 5
502 }
503
504 my_var
505 }"#,
506 r#"fn foo() -> Result<i32, <|>> {
507 let my_var = 5;
508 loop {
509 println!("test");
510 5
511 }
512
513 Ok(my_var)
514 }"#,
515 );
516 }
517
518 #[test]
519 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
520 check_assist(
521 change_return_type_to_result,
522 r#"fn foo() -> i32<|> {
523 let my_var = let x = loop {
524 break 1;
525 };
526
527 my_var
528 }"#,
529 r#"fn foo() -> Result<i32, <|>> {
530 let my_var = let x = loop {
531 break 1;
532 };
533
534 Ok(my_var)
535 }"#,
536 );
537 }
538
539 #[test]
540 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
541 check_assist(
542 change_return_type_to_result,
543 r#"fn foo() -> i32<|> {
544 let my_var = 5;
545 let res = match my_var {
546 5 => 42i32,
547 _ => return 24i32,
548 };
549
550 res
551 }"#,
552 r#"fn foo() -> Result<i32, <|>> {
553 let my_var = 5;
554 let res = match my_var {
555 5 => 42i32,
556 _ => return Ok(24i32),
557 };
558
559 Ok(res)
560 }"#,
561 );
562
563 check_assist(
564 change_return_type_to_result,
565 r#"fn foo() -> i32<|> {
566 let my_var = 5;
567 let res = if my_var == 5 {
568 42i32
569 } else {
570 return 24i32;
571 };
572
573 res
574 }"#,
575 r#"fn foo() -> Result<i32, <|>> {
576 let my_var = 5;
577 let res = if my_var == 5 {
578 42i32
579 } else {
580 return Ok(24i32);
581 };
582
583 Ok(res)
584 }"#,
585 );
586 }
587
588 #[test]
589 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
590 check_assist(
591 change_return_type_to_result,
592 r#"fn foo() -> i32<|> {
593 let my_var = 5;
594 match my_var {
595 5 => {
596 if true {
597 42i32
598 } else {
599 25i32
600 }
601 },
602 _ => {
603 let test = "test";
604 if test == "test" {
605 return bar();
606 }
607 53i32
608 },
609 }
610 }"#,
611 r#"fn foo() -> Result<i32, <|>> {
612 let my_var = 5;
613 match my_var {
614 5 => {
615 if true {
616 Ok(42i32)
617 } else {
618 Ok(25i32)
619 }
620 },
621 _ => {
622 let test = "test";
623 if test == "test" {
624 return Ok(bar());
625 }
626 Ok(53i32)
627 },
628 }
629 }"#,
630 );
631 }
632
633 #[test]
634 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
635 check_assist(
636 change_return_type_to_result,
637 r#"fn foo() -> i<|>32 {
638 let test = "test";
639 if test == "test" {
640 return 24i32;
641 }
642 53i32
643 }"#,
644 r#"fn foo() -> Result<i32, <|>> {
645 let test = "test";
646 if test == "test" {
647 return Ok(24i32);
648 }
649 Ok(53i32)
650 }"#,
651 );
652 }
653
654 #[test]
655 fn change_return_type_to_result_simple_with_closure() {
656 check_assist(
657 change_return_type_to_result,
658 r#"fn foo(the_field: u32) -><|> u32 {
659 let true_closure = || {
660 return true;
661 };
662 if the_field < 5 {
663 let mut i = 0;
664
665
666 if true_closure() {
667 return 99;
668 } else {
669 return 0;
670 }
671 }
672
673 the_field
674 }"#,
675 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
676 let true_closure = || {
677 return true;
678 };
679 if the_field < 5 {
680 let mut i = 0;
681
682
683 if true_closure() {
684 return Ok(99);
685 } else {
686 return Ok(0);
687 }
688 }
689
690 Ok(the_field)
691 }"#,
692 );
693
694 check_assist(
695 change_return_type_to_result,
696 r#"fn foo(the_field: u32) -> u32<|> {
697 let true_closure = || {
698 return true;
699 };
700 if the_field < 5 {
701 let mut i = 0;
702
703
704 if true_closure() {
705 return 99;
706 } else {
707 return 0;
708 }
709 }
710 let t = None;
711
712 t.unwrap_or_else(|| the_field)
713 }"#,
714 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
715 let true_closure = || {
716 return true;
717 };
718 if the_field < 5 {
719 let mut i = 0;
720
721
722 if true_closure() {
723 return Ok(99);
724 } else {
725 return Ok(0);
726 }
727 }
728 let t = None;
729
730 Ok(t.unwrap_or_else(|| the_field))
731 }"#,
732 );
733 }
734
735 #[test]
736 fn change_return_type_to_result_simple_with_weird_forms() {
737 check_assist(
738 change_return_type_to_result,
739 r#"fn foo() -> i32<|> {
740 let test = "test";
741 if test == "test" {
742 return 24i32;
743 }
744 let mut i = 0;
745 loop {
746 if i == 1 {
747 break 55;
748 }
749 i += 1;
750 }
751 }"#,
752 r#"fn foo() -> Result<i32, <|>> {
753 let test = "test";
754 if test == "test" {
755 return Ok(24i32);
756 }
757 let mut i = 0;
758 loop {
759 if i == 1 {
760 break Ok(55);
761 }
762 i += 1;
763 }
764 }"#,
765 );
766
767 check_assist(
768 change_return_type_to_result,
769 r#"fn foo() -> i32<|> {
770 let test = "test";
771 if test == "test" {
772 return 24i32;
773 }
774 let mut i = 0;
775 loop {
776 loop {
777 if i == 1 {
778 break 55;
779 }
780 i += 1;
781 }
782 }
783 }"#,
784 r#"fn foo() -> Result<i32, <|>> {
785 let test = "test";
786 if test == "test" {
787 return Ok(24i32);
788 }
789 let mut i = 0;
790 loop {
791 loop {
792 if i == 1 {
793 break Ok(55);
794 }
795 i += 1;
796 }
797 }
798 }"#,
799 );
800
801 check_assist(
802 change_return_type_to_result,
803 r#"fn foo() -> i3<|>2 {
804 let test = "test";
805 let other = 5;
806 if test == "test" {
807 let res = match other {
808 5 => 43,
809 _ => return 56,
810 };
811 }
812 let mut i = 0;
813 loop {
814 loop {
815 if i == 1 {
816 break 55;
817 }
818 i += 1;
819 }
820 }
821 }"#,
822 r#"fn foo() -> Result<i32, <|>> {
823 let test = "test";
824 let other = 5;
825 if test == "test" {
826 let res = match other {
827 5 => 43,
828 _ => return Ok(56),
829 };
830 }
831 let mut i = 0;
832 loop {
833 loop {
834 if i == 1 {
835 break Ok(55);
836 }
837 i += 1;
838 }
839 }
840 }"#,
841 );
842
843 check_assist(
844 change_return_type_to_result,
845 r#"fn foo(the_field: u32) -> u32<|> {
846 if the_field < 5 {
847 let mut i = 0;
848 loop {
849 if i > 5 {
850 return 55u32;
851 }
852 i += 3;
853 }
854
855 match i {
856 5 => return 99,
857 _ => return 0,
858 };
859 }
860
861 the_field
862 }"#,
863 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
864 if the_field < 5 {
865 let mut i = 0;
866 loop {
867 if i > 5 {
868 return Ok(55u32);
869 }
870 i += 3;
871 }
872
873 match i {
874 5 => return Ok(99),
875 _ => return Ok(0),
876 };
877 }
878
879 Ok(the_field)
880 }"#,
881 );
882
883 check_assist(
884 change_return_type_to_result,
885 r#"fn foo(the_field: u32) -> u3<|>2 {
886 if the_field < 5 {
887 let mut i = 0;
888
889 match i {
890 5 => return 99,
891 _ => return 0,
892 }
893 }
894
895 the_field
896 }"#,
897 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
898 if the_field < 5 {
899 let mut i = 0;
900
901 match i {
902 5 => return Ok(99),
903 _ => return Ok(0),
904 }
905 }
906
907 Ok(the_field)
908 }"#,
909 );
910
911 check_assist(
912 change_return_type_to_result,
913 r#"fn foo(the_field: u32) -> u32<|> {
914 if the_field < 5 {
915 let mut i = 0;
916
917 if i == 5 {
918 return 99
919 } else {
920 return 0
921 }
922 }
923
924 the_field
925 }"#,
926 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
927 if the_field < 5 {
928 let mut i = 0;
929
930 if i == 5 {
931 return Ok(99)
932 } else {
933 return Ok(0)
934 }
935 }
936
937 Ok(the_field)
938 }"#,
939 );
940
941 check_assist(
942 change_return_type_to_result,
943 r#"fn foo(the_field: u32) -> <|>u32 {
944 if the_field < 5 {
945 let mut i = 0;
946
947 if i == 5 {
948 return 99;
949 } else {
950 return 0;
951 }
952 }
953
954 the_field
955 }"#,
956 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
957 if the_field < 5 {
958 let mut i = 0;
959
960 if i == 5 {
961 return Ok(99);
962 } else {
963 return Ok(0);
964 }
965 }
966
967 Ok(the_field)
968 }"#,
969 );
970 }
971}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 13ea45ec7..0473fd8c2 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -129,6 +129,7 @@ mod handlers {
129 mod replace_qualified_name_with_use; 129 mod replace_qualified_name_with_use;
130 mod replace_unwrap_with_match; 130 mod replace_unwrap_with_match;
131 mod split_import; 131 mod split_import;
132 mod change_return_type_to_result;
132 mod add_from_impl_for_enum; 133 mod add_from_impl_for_enum;
133 mod reorder_fields; 134 mod reorder_fields;
134 mod unwrap_block; 135 mod unwrap_block;
@@ -145,6 +146,7 @@ mod handlers {
145 add_new::add_new, 146 add_new::add_new,
146 apply_demorgan::apply_demorgan, 147 apply_demorgan::apply_demorgan,
147 auto_import::auto_import, 148 auto_import::auto_import,
149 change_return_type_to_result::change_return_type_to_result,
148 change_visibility::change_visibility, 150 change_visibility::change_visibility,
149 early_return::convert_to_guarded_return, 151 early_return::convert_to_guarded_return,
150 fill_match_arms::fill_match_arms, 152 fill_match_arms::fill_match_arms,
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 7d35fa284..972dbd251 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -250,6 +250,19 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
250} 250}
251 251
252#[test] 252#[test]
253fn doctest_change_return_type_to_result() {
254 check_doc_test(
255 "change_return_type_to_result",
256 r#####"
257fn foo() -> i32<|> { 42i32 }
258"#####,
259 r#####"
260fn foo() -> Result<i32, > { Ok(42i32) }
261"#####,
262 )
263}
264
265#[test]
253fn doctest_change_visibility() { 266fn doctest_change_visibility() {
254 check_doc_test( 267 check_doc_test(
255 "change_visibility", 268 "change_visibility",
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 8248684ee..51d4c493e 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -1,4 +1,48 @@
1//! FIXME: write short doc here 1//! Fixtures are strings containing rust source code with optional metadata.
2//! A fixture without metadata is parsed into a single source file.
3//! Use this to test functionality local to one file.
4//!
5//! Example:
6//! ```
7//! r#"
8//! fn main() {
9//! println!("Hello World")
10//! }
11//! "#
12//! ```
13//!
14//! Metadata can be added to a fixture after a `//-` comment.
15//! The basic form is specifying filenames,
16//! which is also how to define multiple files in a single test fixture
17//!
18//! Example:
19//! ```
20//! "
21//! //- /main.rs
22//! mod foo;
23//! fn main() {
24//! foo::bar();
25//! }
26//!
27//! //- /foo.rs
28//! pub fn bar() {}
29//! "
30//! ```
31//!
32//! Metadata allows specifying all settings and variables
33//! that are available in a real rust project:
34//! - crate names via `crate:cratename`
35//! - dependencies via `deps:dep1,dep2`
36//! - configuration settings via `cfg:dbg=false,opt_level=2`
37//! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
38//!
39//! Example:
40//! ```
41//! "
42//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
43//! fn insert_source_code_here() {}
44//! "
45//! ```
2 46
3use std::str::FromStr; 47use std::str::FromStr;
4use std::sync::Arc; 48use std::sync::Arc;
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs
index 779e78574..149f65042 100644
--- a/crates/ra_hir_ty/src/_match.rs
+++ b/crates/ra_hir_ty/src/_match.rs
@@ -573,14 +573,20 @@ pub(crate) fn is_useful(
573 matrix: &Matrix, 573 matrix: &Matrix,
574 v: &PatStack, 574 v: &PatStack,
575) -> MatchCheckResult<Usefulness> { 575) -> MatchCheckResult<Usefulness> {
576 // Handle the special case of enums with no variants. In that case, no match 576 // Handle two special cases:
577 // arm is useful. 577 // - enum with no variants
578 if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) = 578 // - `!` type
579 cx.infer[cx.match_expr].strip_references() 579 // In those cases, no match arm is useful.
580 { 580 match cx.infer[cx.match_expr].strip_references() {
581 if cx.db.enum_data(*enum_id).variants.is_empty() { 581 Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) => {
582 if cx.db.enum_data(*enum_id).variants.is_empty() {
583 return Ok(Usefulness::NotUseful);
584 }
585 }
586 Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => {
582 return Ok(Usefulness::NotUseful); 587 return Ok(Usefulness::NotUseful);
583 } 588 }
589 _ => (),
584 } 590 }
585 591
586 if v.is_empty() { 592 if v.is_empty() {
@@ -1918,6 +1924,17 @@ mod tests {
1918 } 1924 }
1919 1925
1920 #[test] 1926 #[test]
1927 fn type_never() {
1928 let content = r"
1929 fn test_fn(never: !) {
1930 match never {}
1931 }
1932 ";
1933
1934 check_no_diagnostic(content);
1935 }
1936
1937 #[test]
1921 fn enum_never_ref() { 1938 fn enum_never_ref() {
1922 let content = r" 1939 let content = r"
1923 enum Never {} 1940 enum Never {}
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b1365444a..b13e13af2 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -155,7 +155,7 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String {
155 res 155 res
156} 156}
157 157
158#[derive(Debug)] 158#[derive(Debug, Eq, PartialEq)]
159pub struct FixtureEntry { 159pub struct FixtureEntry {
160 pub meta: String, 160 pub meta: String,
161 pub text: String, 161 pub text: String,
@@ -170,19 +170,26 @@ pub struct FixtureEntry {
170/// // - other meta 170/// // - other meta
171/// ``` 171/// ```
172pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { 172pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
173 let margin = fixture 173 let fixture = indent_first_line(fixture);
174 .lines() 174 let margin = fixture_margin(&fixture);
175 .filter(|it| it.trim_start().starts_with("//-"))
176 .map(|it| it.len() - it.trim_start().len())
177 .next()
178 .expect("empty fixture");
179 175
180 let mut lines = fixture 176 let mut lines = fixture
181 .split('\n') // don't use `.lines` to not drop `\r\n` 177 .split('\n') // don't use `.lines` to not drop `\r\n`
182 .filter_map(|line| { 178 .enumerate()
179 .filter_map(|(ix, line)| {
183 if line.len() >= margin { 180 if line.len() >= margin {
184 assert!(line[..margin].trim().is_empty()); 181 assert!(line[..margin].trim().is_empty());
185 Some(&line[margin..]) 182 let line_content = &line[margin..];
183 if !line_content.starts_with("//-") {
184 assert!(
185 !line_content.contains("//-"),
186 r#"Metadata line {} has invalid indentation. All metadata lines need to have the same indentation.
187The offending line: {:?}"#,
188 ix,
189 line
190 );
191 }
192 Some(line_content)
186 } else { 193 } else {
187 assert!(line.trim().is_empty()); 194 assert!(line.trim().is_empty());
188 None 195 None
@@ -202,6 +209,85 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
202 res 209 res
203} 210}
204 211
212/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines.
213/// This allows fixtures to start off in a different indentation, e.g. to align the first line with
214/// the other lines visually:
215/// ```
216/// let fixture = "//- /lib.rs
217/// mod foo;
218/// //- /foo.rs
219/// fn bar() {}
220/// ";
221/// assert_eq!(fixture_margin(fixture),
222/// " //- /lib.rs
223/// mod foo;
224/// //- /foo.rs
225/// fn bar() {}
226/// ")
227/// ```
228fn indent_first_line(fixture: &str) -> String {
229 if fixture.is_empty() {
230 return String::new();
231 }
232 let mut lines = fixture.lines();
233 let first_line = lines.next().unwrap();
234 if first_line.contains("//-") {
235 let rest = lines.collect::<Vec<_>>().join("\n");
236 let fixed_margin = fixture_margin(&rest);
237 let fixed_indent = fixed_margin - indent_len(first_line);
238 format!("\n{}{}\n{}", " ".repeat(fixed_indent), first_line, rest)
239 } else {
240 fixture.to_owned()
241 }
242}
243
244fn fixture_margin(fixture: &str) -> usize {
245 fixture
246 .lines()
247 .filter(|it| it.trim_start().starts_with("//-"))
248 .map(indent_len)
249 .next()
250 .expect("empty fixture")
251}
252
253fn indent_len(s: &str) -> usize {
254 s.len() - s.trim_start().len()
255}
256
257#[test]
258#[should_panic]
259fn parse_fixture_checks_further_indented_metadata() {
260 parse_fixture(
261 r"
262 //- /lib.rs
263 mod bar;
264
265 fn foo() {}
266 //- /bar.rs
267 pub fn baz() {}
268 ",
269 );
270}
271
272#[test]
273fn parse_fixture_can_handle_unindented_first_line() {
274 let fixture = "//- /lib.rs
275 mod foo;
276 //- /foo.rs
277 struct Bar;
278";
279 assert_eq!(
280 parse_fixture(fixture),
281 parse_fixture(
282 "//- /lib.rs
283mod foo;
284//- /foo.rs
285struct Bar;
286"
287 )
288 )
289}
290
205/// Same as `parse_fixture`, except it allow empty fixture 291/// Same as `parse_fixture`, except it allow empty fixture
206pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { 292pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> {
207 if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { 293 if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
diff --git a/docs/user/assists.md b/docs/user/assists.md
index ee515949e..692fd4f52 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -241,6 +241,18 @@ fn main() {
241} 241}
242``` 242```
243 243
244## `change_return_type_to_result`
245
246Change the function's return type to Result.
247
248```rust
249// BEFORE
250fn foo() -> i32┃ { 42i32 }
251
252// AFTER
253fn foo() -> Result<i32, > { Ok(42i32) }
254```
255
244## `change_visibility` 256## `change_visibility`
245 257
246Adds or changes existing visibility specifier. 258Adds or changes existing visibility specifier.
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index c4eac1bc4..b8e8860ba 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -136,7 +136,6 @@ impl TidyDocs {
136 } 136 }
137 137
138 let whitelist = [ 138 let whitelist = [
139 "ra_db",
140 "ra_hir", 139 "ra_hir",
141 "ra_hir_expand", 140 "ra_hir_expand",
142 "ra_ide", 141 "ra_ide",