]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/inline_call.rs
Merge #9765
[rust.git] / crates / ide_assists / src / handlers / inline_call.rs
1 use ast::make;
2 use hir::{HasSource, PathResolution, TypeInfo};
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             None,
89             assoc_fn_params.next()?,
90         ));
91     }
92     for param in param_list.params() {
93         params.push((param.pat()?, param.ty(), assoc_fn_params.next()?));
94     }
95
96     if arg_list.len() != params.len() {
97         // Can't inline the function because they've passed the wrong number of
98         // arguments to this function
99         cov_mark::hit!(inline_call_incorrect_number_of_arguments);
100         return None;
101     }
102
103     let body = function_source.body()?;
104
105     acc.add(
106         AssistId("inline_call", AssistKind::RefactorInline),
107         label,
108         expr.syntax().text_range(),
109         |builder| {
110             let body = body.clone_for_update();
111
112             let file_id = file_id.original_file(ctx.sema.db);
113             let usages_for_locals = |local| {
114                 Definition::Local(local)
115                     .usages(&ctx.sema)
116                     .all()
117                     .references
118                     .remove(&file_id)
119                     .unwrap_or_default()
120                     .into_iter()
121             };
122             // Contains the nodes of usages of parameters.
123             // If the inner Vec for a parameter is empty it either means there are no usages or that the parameter
124             // has a pattern that does not allow inlining
125             let param_use_nodes: Vec<Vec<_>> = params
126                 .iter()
127                 .map(|(pat, _, param)| {
128                     if !matches!(pat, ast::Pat::IdentPat(pat) if pat.is_simple_ident()) {
129                         return Vec::new();
130                     }
131                     usages_for_locals(param.as_local(ctx.sema.db))
132                         .map(|FileReference { name, range, .. }| match name {
133                             ast::NameLike::NameRef(_) => body
134                                 .syntax()
135                                 .covering_element(range)
136                                 .ancestors()
137                                 .nth(3)
138                                 .and_then(ast::PathExpr::cast),
139                             _ => None,
140                         })
141                         .collect::<Option<Vec<_>>>()
142                         .unwrap_or_default()
143                 })
144                 .collect();
145
146             // Rewrite `self` to `this`
147             if param_list.self_param().is_some() {
148                 let this = || make::name_ref("this").syntax().clone_for_update();
149                 usages_for_locals(params[0].2.as_local(ctx.sema.db))
150                     .flat_map(|FileReference { name, range, .. }| match name {
151                         ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
152                         _ => None,
153                     })
154                     .for_each(|it| {
155                         ted::replace(it, &this());
156                     })
157             }
158
159             // Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
160             for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arg_list).rev()
161             {
162                 let expr_is_name_ref = matches!(&expr,
163                     ast::Expr::PathExpr(expr)
164                         if expr.path().and_then(|path| path.as_single_name_ref()).is_some()
165                 );
166                 match &*usages {
167                     // inline single use closure arguments
168                     [usage]
169                         if matches!(expr, ast::Expr::ClosureExpr(_))
170                             && usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
171                     {
172                         cov_mark::hit!(inline_call_inline_closure);
173                         let expr = make::expr_paren(expr);
174                         ted::replace(usage.syntax(), expr.syntax().clone_for_update());
175                     }
176                     // inline single use literals
177                     [usage] if matches!(expr, ast::Expr::Literal(_)) => {
178                         cov_mark::hit!(inline_call_inline_literal);
179                         ted::replace(usage.syntax(), expr.syntax().clone_for_update());
180                     }
181                     // inline direct local arguments
182                     [_, ..] if expr_is_name_ref => {
183                         cov_mark::hit!(inline_call_inline_locals);
184                         usages.into_iter().for_each(|usage| {
185                             ted::replace(usage.syntax(), &expr.syntax().clone_for_update());
186                         });
187                     }
188                     // cant inline, emit a let statement
189                     _ => {
190                         let ty = ctx
191                             .sema
192                             .type_of_expr(&expr)
193                             .filter(TypeInfo::has_adjustment)
194                             .and_then(|_| param_ty);
195                         body.push_front(
196                             make::let_stmt(pat, ty, Some(expr)).clone_for_update().into(),
197                         )
198                     }
199                 }
200             }
201
202             let original_indentation = expr.indent_level();
203             let replacement = body.reset_indent().indent(original_indentation);
204
205             let replacement = match replacement.tail_expr() {
206                 Some(expr) if replacement.statements().next().is_none() => expr,
207                 _ => ast::Expr::BlockExpr(replacement),
208             };
209             builder.replace_ast(expr, replacement);
210         },
211     )
212 }
213
214 #[cfg(test)]
215 mod tests {
216     use crate::tests::{check_assist, check_assist_not_applicable};
217
218     use super::*;
219
220     #[test]
221     fn no_args_or_return_value_gets_inlined_without_block() {
222         check_assist(
223             inline_call,
224             r#"
225 fn foo() { println!("Hello, World!"); }
226 fn main() {
227     fo$0o();
228 }
229 "#,
230             r#"
231 fn foo() { println!("Hello, World!"); }
232 fn main() {
233     { println!("Hello, World!"); };
234 }
235 "#,
236         );
237     }
238
239     #[test]
240     fn not_applicable_when_incorrect_number_of_parameters_are_provided() {
241         cov_mark::check!(inline_call_incorrect_number_of_arguments);
242         check_assist_not_applicable(
243             inline_call,
244             r#"
245 fn add(a: u32, b: u32) -> u32 { a + b }
246 fn main() { let x = add$0(42); }
247 "#,
248         );
249     }
250
251     #[test]
252     fn args_with_side_effects() {
253         check_assist(
254             inline_call,
255             r#"
256 fn foo(name: String) {
257     println!("Hello, {}!", name);
258 }
259 fn main() {
260     foo$0(String::from("Michael"));
261 }
262 "#,
263             r#"
264 fn foo(name: String) {
265     println!("Hello, {}!", name);
266 }
267 fn main() {
268     {
269         let name = String::from("Michael");
270         println!("Hello, {}!", name);
271     };
272 }
273 "#,
274         );
275     }
276
277     #[test]
278     fn function_with_multiple_statements() {
279         check_assist(
280             inline_call,
281             r#"
282 fn foo(a: u32, b: u32) -> u32 {
283     let x = a + b;
284     let y = x - b;
285     x * y
286 }
287
288 fn main() {
289     let x = foo$0(1, 2);
290 }
291 "#,
292             r#"
293 fn foo(a: u32, b: u32) -> u32 {
294     let x = a + b;
295     let y = x - b;
296     x * y
297 }
298
299 fn main() {
300     let x = {
301         let b = 2;
302         let x = 1 + b;
303         let y = x - b;
304         x * y
305     };
306 }
307 "#,
308         );
309     }
310
311     #[test]
312     fn function_with_self_param() {
313         check_assist(
314             inline_call,
315             r#"
316 struct Foo(u32);
317
318 impl Foo {
319     fn add(self, a: u32) -> Self {
320         Foo(self.0 + a)
321     }
322 }
323
324 fn main() {
325     let x = Foo::add$0(Foo(3), 2);
326 }
327 "#,
328             r#"
329 struct Foo(u32);
330
331 impl Foo {
332     fn add(self, a: u32) -> Self {
333         Foo(self.0 + a)
334     }
335 }
336
337 fn main() {
338     let x = {
339         let this = Foo(3);
340         Foo(this.0 + 2)
341     };
342 }
343 "#,
344         );
345     }
346
347     #[test]
348     fn method_by_val() {
349         check_assist(
350             inline_call,
351             r#"
352 struct Foo(u32);
353
354 impl Foo {
355     fn add(self, a: u32) -> Self {
356         Foo(self.0 + a)
357     }
358 }
359
360 fn main() {
361     let x = Foo(3).add$0(2);
362 }
363 "#,
364             r#"
365 struct Foo(u32);
366
367 impl Foo {
368     fn add(self, a: u32) -> Self {
369         Foo(self.0 + a)
370     }
371 }
372
373 fn main() {
374     let x = {
375         let this = Foo(3);
376         Foo(this.0 + 2)
377     };
378 }
379 "#,
380         );
381     }
382
383     #[test]
384     fn method_by_ref() {
385         check_assist(
386             inline_call,
387             r#"
388 struct Foo(u32);
389
390 impl Foo {
391     fn add(&self, a: u32) -> Self {
392         Foo(self.0 + a)
393     }
394 }
395
396 fn main() {
397     let x = Foo(3).add$0(2);
398 }
399 "#,
400             r#"
401 struct Foo(u32);
402
403 impl Foo {
404     fn add(&self, a: u32) -> Self {
405         Foo(self.0 + a)
406     }
407 }
408
409 fn main() {
410     let x = {
411         let ref this = Foo(3);
412         Foo(this.0 + 2)
413     };
414 }
415 "#,
416         );
417     }
418
419     #[test]
420     fn method_by_ref_mut() {
421         check_assist(
422             inline_call,
423             r#"
424 struct Foo(u32);
425
426 impl Foo {
427     fn clear(&mut self) {
428         self.0 = 0;
429     }
430 }
431
432 fn main() {
433     let mut foo = Foo(3);
434     foo.clear$0();
435 }
436 "#,
437             r#"
438 struct Foo(u32);
439
440 impl Foo {
441     fn clear(&mut self) {
442         self.0 = 0;
443     }
444 }
445
446 fn main() {
447     let mut foo = Foo(3);
448     {
449         let ref mut this = foo;
450         this.0 = 0;
451     };
452 }
453 "#,
454         );
455     }
456
457     #[test]
458     fn function_multi_use_expr_in_param() {
459         check_assist(
460             inline_call,
461             r#"
462 fn square(x: u32) -> u32 {
463     x * x
464 }
465 fn main() {
466     let x = 51;
467     let y = square$0(10 + x);
468 }
469 "#,
470             r#"
471 fn square(x: u32) -> u32 {
472     x * x
473 }
474 fn main() {
475     let x = 51;
476     let y = {
477         let x = 10 + x;
478         x * x
479     };
480 }
481 "#,
482         );
483     }
484
485     #[test]
486     fn function_use_local_in_param() {
487         cov_mark::check!(inline_call_inline_locals);
488         check_assist(
489             inline_call,
490             r#"
491 fn square(x: u32) -> u32 {
492     x * x
493 }
494 fn main() {
495     let local = 51;
496     let y = square$0(local);
497 }
498 "#,
499             r#"
500 fn square(x: u32) -> u32 {
501     x * x
502 }
503 fn main() {
504     let local = 51;
505     let y = local * local;
506 }
507 "#,
508         );
509     }
510
511     #[test]
512     fn method_in_impl() {
513         check_assist(
514             inline_call,
515             r#"
516 struct Foo;
517 impl Foo {
518     fn foo(&self) {
519         self;
520         self;
521     }
522     fn bar(&self) {
523         self.foo$0();
524     }
525 }
526 "#,
527             r#"
528 struct Foo;
529 impl Foo {
530     fn foo(&self) {
531         self;
532         self;
533     }
534     fn bar(&self) {
535         {
536             let ref this = self;
537             this;
538             this;
539         };
540     }
541 }
542 "#,
543         );
544     }
545
546     #[test]
547     fn wraps_closure_in_paren() {
548         cov_mark::check!(inline_call_inline_closure);
549         check_assist(
550             inline_call,
551             r#"
552 fn foo(x: fn()) {
553     x();
554 }
555
556 fn main() {
557     foo$0(|| {})
558 }
559 "#,
560             r#"
561 fn foo(x: fn()) {
562     x();
563 }
564
565 fn main() {
566     {
567         (|| {})();
568     }
569 }
570 "#,
571         );
572         check_assist(
573             inline_call,
574             r#"
575 fn foo(x: fn()) {
576     x();
577 }
578
579 fn main() {
580     foo$0(main)
581 }
582 "#,
583             r#"
584 fn foo(x: fn()) {
585     x();
586 }
587
588 fn main() {
589     {
590         main();
591     }
592 }
593 "#,
594         );
595     }
596
597     #[test]
598     fn inline_single_literal_expr() {
599         cov_mark::check!(inline_call_inline_literal);
600         check_assist(
601             inline_call,
602             r#"
603 fn foo(x: u32) -> u32{
604     x
605 }
606
607 fn main() {
608     foo$0(222);
609 }
610 "#,
611             r#"
612 fn foo(x: u32) -> u32{
613     x
614 }
615
616 fn main() {
617     222;
618 }
619 "#,
620         );
621     }
622
623     #[test]
624     fn inline_emits_type_for_coercion() {
625         check_assist(
626             inline_call,
627             r#"
628 fn foo(x: *const u32) -> u32 {
629     x as u32
630 }
631
632 fn main() {
633     foo$0(&222);
634 }
635 "#,
636             r#"
637 fn foo(x: *const u32) -> u32 {
638     x as u32
639 }
640
641 fn main() {
642     {
643         let x: *const u32 = &222;
644         x as u32
645     };
646 }
647 "#,
648         );
649     }
650 }