diff options
-rw-r--r-- | crates/ra_assists/src/handlers/change_return_type_to_result.rs | 971 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/tests/generated.rs | 13 | ||||
-rw-r--r-- | crates/ra_db/src/fixture.rs | 46 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/_match.rs | 29 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 104 | ||||
-rw-r--r-- | docs/user/assists.md | 12 | ||||
-rw-r--r-- | xtask/tests/tidy.rs | 1 |
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 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast, AstNode, | ||
3 | SyntaxKind::{COMMENT, WHITESPACE}, | ||
4 | SyntaxNode, TextSize, | ||
5 | }; | ||
6 | |||
7 | use crate::{Assist, AssistCtx, AssistId}; | ||
8 | use 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 | // ``` | ||
21 | pub(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 | |||
57 | struct TailReturnCollector { | ||
58 | exprs_to_wrap: Vec<SyntaxNode>, | ||
59 | } | ||
60 | |||
61 | impl 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)] | ||
177 | enum NodeType { | ||
178 | Leaf(SyntaxNode), | ||
179 | Node(SyntaxNode), | ||
180 | } | ||
181 | |||
182 | /// Get a tail expression inside a block | ||
183 | fn 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 | |||
253 | fn 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)] | ||
262 | mod 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] |
253 | fn doctest_change_return_type_to_result() { | ||
254 | check_doc_test( | ||
255 | "change_return_type_to_result", | ||
256 | r#####" | ||
257 | fn foo() -> i32<|> { 42i32 } | ||
258 | "#####, | ||
259 | r#####" | ||
260 | fn foo() -> Result<i32, > { Ok(42i32) } | ||
261 | "#####, | ||
262 | ) | ||
263 | } | ||
264 | |||
265 | #[test] | ||
253 | fn doctest_change_visibility() { | 266 | fn 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 | ||
3 | use std::str::FromStr; | 47 | use std::str::FromStr; |
4 | use std::sync::Arc; | 48 | use 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)] |
159 | pub struct FixtureEntry { | 159 | pub 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 | /// ``` |
172 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | 172 | pub 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. | ||
187 | The 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 | /// ``` | ||
228 | fn 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 | |||
244 | fn 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 | |||
253 | fn indent_len(s: &str) -> usize { | ||
254 | s.len() - s.trim_start().len() | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | #[should_panic] | ||
259 | fn 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] | ||
273 | fn 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 | ||
283 | mod foo; | ||
284 | //- /foo.rs | ||
285 | struct 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 |
206 | pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { | 292 | pub 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 | |||
246 | Change the function's return type to Result. | ||
247 | |||
248 | ```rust | ||
249 | // BEFORE | ||
250 | fn foo() -> i32┃ { 42i32 } | ||
251 | |||
252 | // AFTER | ||
253 | fn foo() -> Result<i32, > { Ok(42i32) } | ||
254 | ``` | ||
255 | |||
244 | ## `change_visibility` | 256 | ## `change_visibility` |
245 | 257 | ||
246 | Adds or changes existing visibility specifier. | 258 | Adds 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", |