]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/inline_call.rs
Merge #9500
[rust.git] / crates / ide_assists / src / handlers / inline_call.rs
1 use ast::make;
2 use hir::{HasSource, PathResolution};
3 use ide_db::{defs::Definition, search::FileReference};
4 use itertools::izip;
5 use syntax::{
6     ast::{self, edit::AstNodeEdit, ArgListOwner},
7     ted, AstNode,
8 };
9
10 use crate::{
11     assist_context::{AssistContext, Assists},
12     AssistId, AssistKind,
13 };
14
15 // Assist: inline_call
16 //
17 // Inlines a function or method body creating a `let` statement per parameter unless the parameter
18 // can be inlined. The parameter will be inlined either if it the supplied argument is a simple local
19 // or if the parameter is only accessed inside the function body once.
20 //
21 // ```
22 // # //- minicore: option
23 // fn foo(name: Option<&str>) {
24 //     let name = name.unwrap$0();
25 // }
26 // ```
27 // ->
28 // ```
29 // fn foo(name: Option<&str>) {
30 //     let name = match name {
31 //             Some(val) => val,
32 //             None => panic!("called `Option::unwrap()` on a `None` value"),
33 //         };
34 // }
35 // ```
36 pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37     let (label, function, arguments, expr) =
38         if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
39             let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
40             let path = path_expr.path()?;
41
42             let function = match ctx.sema.resolve_path(&path)? {
43                 PathResolution::Def(hir::ModuleDef::Function(f))
44                 | PathResolution::AssocItem(hir::AssocItem::Function(f)) => f,
45                 _ => return None,
46             };
47             (
48                 format!("Inline `{}`", path),
49                 function,
50                 call.arg_list()?.args().collect(),
51                 ast::Expr::CallExpr(call),
52             )
53         } else {
54             let name_ref: ast::NameRef = ctx.find_node_at_offset()?;
55             let call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
56             let receiver = call.receiver()?;
57             let function = ctx.sema.resolve_method_call(&call)?;
58             let mut arguments = vec![receiver];
59             arguments.extend(call.arg_list()?.args());
60             (format!("Inline `{}`", name_ref), function, arguments, ast::Expr::MethodCallExpr(call))
61         };
62
63     inline_(acc, ctx, label, function, arguments, expr)
64 }
65
66 pub(crate) fn inline_(
67     acc: &mut Assists,
68     ctx: &AssistContext,
69     label: String,
70     function: hir::Function,
71     arg_list: Vec<ast::Expr>,
72     expr: ast::Expr,
73 ) -> Option<()> {
74     let hir::InFile { value: function_source, file_id } = function.source(ctx.db())?;
75     let param_list = function_source.param_list()?;
76     let mut assoc_fn_params = function.assoc_fn_params(ctx.sema.db).into_iter();
77
78     let mut params = Vec::new();
79     if let Some(self_param) = param_list.self_param() {
80         // FIXME this should depend on the receiver as well as the self_param
81         params.push((
82             make::ident_pat(
83                 self_param.amp_token().is_some(),
84                 self_param.mut_token().is_some(),
85                 make::name("this"),
86             )
87             .into(),
88             assoc_fn_params.next()?,
89         ));
90     }
91     for param in param_list.params() {
92         params.push((param.pat()?, assoc_fn_params.next()?));
93     }
94
95     if arg_list.len() != params.len() {
96         // Can't inline the function because they've passed the wrong number of
97         // arguments to this function
98         cov_mark::hit!(inline_call_incorrect_number_of_arguments);
99         return None;
100     }
101
102     let body = function_source.body()?;
103
104     acc.add(
105         AssistId("inline_call", AssistKind::RefactorInline),
106         label,
107         expr.syntax().text_range(),
108         |builder| {
109             let body = body.clone_for_update();
110
111             let file_id = file_id.original_file(ctx.sema.db);
112             let usages_for_locals = |local| {
113                 Definition::Local(local)
114                     .usages(&ctx.sema)
115                     .all()
116                     .references
117                     .remove(&file_id)
118                     .unwrap_or_default()
119                     .into_iter()
120             };
121             // Contains the nodes of usages of parameters.
122             // If the inner Vec for a parameter is empty it either means there are no usages or that the parameter
123             // has a pattern that does not allow inlining
124             let param_use_nodes: Vec<Vec<_>> = params
125                 .iter()
126                 .map(|(pat, param)| {
127                     if !matches!(pat, ast::Pat::IdentPat(pat) if pat.is_simple_ident()) {
128                         return Vec::new();
129                     }
130                     usages_for_locals(param.as_local(ctx.sema.db))
131                         .map(|FileReference { name, range, .. }| match name {
132                             ast::NameLike::NameRef(_) => body
133                                 .syntax()
134                                 .covering_element(range)
135                                 .ancestors()
136                                 .nth(3)
137                                 .and_then(ast::PathExpr::cast),
138                             _ => None,
139                         })
140                         .collect::<Option<Vec<_>>>()
141                         .unwrap_or_default()
142                 })
143                 .collect();
144
145             // Rewrite `self` to `this`
146             if param_list.self_param().is_some() {
147                 let this = || make::name_ref("this").syntax().clone_for_update();
148                 usages_for_locals(params[0].1.as_local(ctx.sema.db))
149                     .flat_map(|FileReference { name, range, .. }| match name {
150                         ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
151                         _ => None,
152                     })
153                     .for_each(|it| {
154                         ted::replace(it, &this());
155                     })
156             }
157
158             // Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
159             for ((pat, _), usages, expr) in izip!(params, param_use_nodes, arg_list).rev() {
160                 let expr_is_name_ref = matches!(&expr,
161                     ast::Expr::PathExpr(expr)
162                         if expr.path().and_then(|path| path.as_single_name_ref()).is_some()
163                 );
164                 match &*usages {
165                     // inline single use closure arguments
166                     [usage]
167                         if matches!(expr, ast::Expr::ClosureExpr(_))
168                             && usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
169                     {
170                         cov_mark::hit!(inline_call_inline_closure);
171                         let expr = make::expr_paren(expr);
172                         ted::replace(usage.syntax(), expr.syntax().clone_for_update());
173                     }
174                     // inline single use literals
175                     [usage] if matches!(expr, ast::Expr::Literal(_)) => {
176                         cov_mark::hit!(inline_call_inline_literal);
177                         ted::replace(usage.syntax(), expr.syntax().clone_for_update());
178                     }
179                     // inline direct local arguments
180                     [_, ..] if expr_is_name_ref => {
181                         cov_mark::hit!(inline_call_inline_locals);
182                         usages.into_iter().for_each(|usage| {
183                             ted::replace(usage.syntax(), &expr.syntax().clone_for_update());
184                         });
185                     }
186                     // cant inline, emit a let statement
187                     // FIXME: emit type ascriptions when a coercion happens?
188                     _ => body.push_front(make::let_stmt(pat, Some(expr)).clone_for_update().into()),
189                 }
190             }
191
192             let original_indentation = expr.indent_level();
193             let replacement = body.reset_indent().indent(original_indentation);
194
195             let replacement = match replacement.tail_expr() {
196                 Some(expr) if replacement.statements().next().is_none() => expr,
197                 _ => ast::Expr::BlockExpr(replacement),
198             };
199             builder.replace_ast(expr, replacement);
200         },
201     )
202 }
203
204 #[cfg(test)]
205 mod tests {
206     use crate::tests::{check_assist, check_assist_not_applicable};
207
208     use super::*;
209
210     #[test]
211     fn no_args_or_return_value_gets_inlined_without_block() {
212         check_assist(
213             inline_call,
214             r#"
215 fn foo() { println!("Hello, World!"); }
216 fn main() {
217     fo$0o();
218 }
219 "#,
220             r#"
221 fn foo() { println!("Hello, World!"); }
222 fn main() {
223     { println!("Hello, World!"); };
224 }
225 "#,
226         );
227     }
228
229     #[test]
230     fn not_applicable_when_incorrect_number_of_parameters_are_provided() {
231         cov_mark::check!(inline_call_incorrect_number_of_arguments);
232         check_assist_not_applicable(
233             inline_call,
234             r#"
235 fn add(a: u32, b: u32) -> u32 { a + b }
236 fn main() { let x = add$0(42); }
237 "#,
238         );
239     }
240
241     #[test]
242     fn args_with_side_effects() {
243         check_assist(
244             inline_call,
245             r#"
246 fn foo(name: String) {
247     println!("Hello, {}!", name);
248 }
249 fn main() {
250     foo$0(String::from("Michael"));
251 }
252 "#,
253             r#"
254 fn foo(name: String) {
255     println!("Hello, {}!", name);
256 }
257 fn main() {
258     {
259         let name = String::from("Michael");
260         println!("Hello, {}!", name);
261     };
262 }
263 "#,
264         );
265     }
266
267     #[test]
268     fn function_with_multiple_statements() {
269         check_assist(
270             inline_call,
271             r#"
272 fn foo(a: u32, b: u32) -> u32 {
273     let x = a + b;
274     let y = x - b;
275     x * y
276 }
277
278 fn main() {
279     let x = foo$0(1, 2);
280 }
281 "#,
282             r#"
283 fn foo(a: u32, b: u32) -> u32 {
284     let x = a + b;
285     let y = x - b;
286     x * y
287 }
288
289 fn main() {
290     let x = {
291         let b = 2;
292         let x = 1 + b;
293         let y = x - b;
294         x * y
295     };
296 }
297 "#,
298         );
299     }
300
301     #[test]
302     fn function_with_self_param() {
303         check_assist(
304             inline_call,
305             r#"
306 struct Foo(u32);
307
308 impl Foo {
309     fn add(self, a: u32) -> Self {
310         Foo(self.0 + a)
311     }
312 }
313
314 fn main() {
315     let x = Foo::add$0(Foo(3), 2);
316 }
317 "#,
318             r#"
319 struct Foo(u32);
320
321 impl Foo {
322     fn add(self, a: u32) -> Self {
323         Foo(self.0 + a)
324     }
325 }
326
327 fn main() {
328     let x = {
329         let this = Foo(3);
330         Foo(this.0 + 2)
331     };
332 }
333 "#,
334         );
335     }
336
337     #[test]
338     fn method_by_val() {
339         check_assist(
340             inline_call,
341             r#"
342 struct Foo(u32);
343
344 impl Foo {
345     fn add(self, a: u32) -> Self {
346         Foo(self.0 + a)
347     }
348 }
349
350 fn main() {
351     let x = Foo(3).add$0(2);
352 }
353 "#,
354             r#"
355 struct Foo(u32);
356
357 impl Foo {
358     fn add(self, a: u32) -> Self {
359         Foo(self.0 + a)
360     }
361 }
362
363 fn main() {
364     let x = {
365         let this = Foo(3);
366         Foo(this.0 + 2)
367     };
368 }
369 "#,
370         );
371     }
372
373     #[test]
374     fn method_by_ref() {
375         check_assist(
376             inline_call,
377             r#"
378 struct Foo(u32);
379
380 impl Foo {
381     fn add(&self, a: u32) -> Self {
382         Foo(self.0 + a)
383     }
384 }
385
386 fn main() {
387     let x = Foo(3).add$0(2);
388 }
389 "#,
390             r#"
391 struct Foo(u32);
392
393 impl Foo {
394     fn add(&self, a: u32) -> Self {
395         Foo(self.0 + a)
396     }
397 }
398
399 fn main() {
400     let x = {
401         let ref this = Foo(3);
402         Foo(this.0 + 2)
403     };
404 }
405 "#,
406         );
407     }
408
409     #[test]
410     fn method_by_ref_mut() {
411         check_assist(
412             inline_call,
413             r#"
414 struct Foo(u32);
415
416 impl Foo {
417     fn clear(&mut self) {
418         self.0 = 0;
419     }
420 }
421
422 fn main() {
423     let mut foo = Foo(3);
424     foo.clear$0();
425 }
426 "#,
427             r#"
428 struct Foo(u32);
429
430 impl Foo {
431     fn clear(&mut self) {
432         self.0 = 0;
433     }
434 }
435
436 fn main() {
437     let mut foo = Foo(3);
438     {
439         let ref mut this = foo;
440         this.0 = 0;
441     };
442 }
443 "#,
444         );
445     }
446
447     #[test]
448     fn function_multi_use_expr_in_param() {
449         check_assist(
450             inline_call,
451             r#"
452 fn square(x: u32) -> u32 {
453     x * x
454 }
455 fn main() {
456     let x = 51;
457     let y = square$0(10 + x);
458 }
459 "#,
460             r#"
461 fn square(x: u32) -> u32 {
462     x * x
463 }
464 fn main() {
465     let x = 51;
466     let y = {
467         let x = 10 + x;
468         x * x
469     };
470 }
471 "#,
472         );
473     }
474
475     #[test]
476     fn function_use_local_in_param() {
477         cov_mark::check!(inline_call_inline_locals);
478         check_assist(
479             inline_call,
480             r#"
481 fn square(x: u32) -> u32 {
482     x * x
483 }
484 fn main() {
485     let local = 51;
486     let y = square$0(local);
487 }
488 "#,
489             r#"
490 fn square(x: u32) -> u32 {
491     x * x
492 }
493 fn main() {
494     let local = 51;
495     let y = local * local;
496 }
497 "#,
498         );
499     }
500
501     #[test]
502     fn method_in_impl() {
503         check_assist(
504             inline_call,
505             r#"
506 struct Foo;
507 impl Foo {
508     fn foo(&self) {
509         self;
510         self;
511     }
512     fn bar(&self) {
513         self.foo$0();
514     }
515 }
516 "#,
517             r#"
518 struct Foo;
519 impl Foo {
520     fn foo(&self) {
521         self;
522         self;
523     }
524     fn bar(&self) {
525         {
526             let ref this = self;
527             this;
528             this;
529         };
530     }
531 }
532 "#,
533         );
534     }
535
536     #[test]
537     fn wraps_closure_in_paren() {
538         cov_mark::check!(inline_call_inline_closure);
539         check_assist(
540             inline_call,
541             r#"
542 fn foo(x: fn()) {
543     x();
544 }
545
546 fn main() {
547     foo$0(|| {})
548 }
549 "#,
550             r#"
551 fn foo(x: fn()) {
552     x();
553 }
554
555 fn main() {
556     {
557         (|| {})();
558     }
559 }
560 "#,
561         );
562         check_assist(
563             inline_call,
564             r#"
565 fn foo(x: fn()) {
566     x();
567 }
568
569 fn main() {
570     foo$0(main)
571 }
572 "#,
573             r#"
574 fn foo(x: fn()) {
575     x();
576 }
577
578 fn main() {
579     {
580         main();
581     }
582 }
583 "#,
584         );
585     }
586
587     #[test]
588     fn inline_single_literal_expr() {
589         cov_mark::check!(inline_call_inline_literal);
590         check_assist(
591             inline_call,
592             r#"
593 fn foo(x: u32) -> u32{
594     x
595 }
596
597 fn main() {
598     foo$0(222);
599 }
600 "#,
601             r#"
602 fn foo(x: u32) -> u32{
603     x
604 }
605
606 fn main() {
607     222;
608 }
609 "#,
610         );
611     }
612 }