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