diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-01-05 11:04:58 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-05 11:04:58 +0000 |
commit | 5c10f2f705d6757b9821387a5be759789b7ee480 (patch) | |
tree | 961570d2245c2e62ce7f20c2437fe5d8abb92166 | |
parent | 4bc1ed7d592819bf2a29f7be376e0c09f190c345 (diff) | |
parent | 7b4b4ef02681053299dda5111c0d4b0113e29224 (diff) |
Merge #7131
7131: Created an assist for inlining a function's body into its caller r=matklad a=Michael-F-Bryan
This introduces an `inline_function` assist which will convert code like this:
```rust
fn add(a: u32, b: u32) -> u32 { a + b }
fn main() {
let x = add<|>(1, 2);
}
```
Into something like this:
```rust
fn add(a: u32, b: u32) -> u32 { a + b }
fn main() {
let x = {
let a = 1;
let b = 2;
a + b
};
}
```
Fixes #6863.
Co-authored-by: Michael-F-Bryan <[email protected]>
-rw-r--r-- | crates/assists/src/handlers/inline_function.rs | 202 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 23 |
3 files changed, 227 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/inline_function.rs b/crates/assists/src/handlers/inline_function.rs new file mode 100644 index 000000000..2edf252b7 --- /dev/null +++ b/crates/assists/src/handlers/inline_function.rs | |||
@@ -0,0 +1,202 @@ | |||
1 | use ast::make; | ||
2 | use hir::{HasSource, PathResolution}; | ||
3 | use syntax::{ | ||
4 | ast::{self, edit::AstNodeEdit, ArgListOwner}, | ||
5 | AstNode, | ||
6 | }; | ||
7 | use test_utils::mark; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_context::{AssistContext, Assists}, | ||
11 | AssistId, AssistKind, | ||
12 | }; | ||
13 | |||
14 | // Assist: inline_function | ||
15 | // | ||
16 | // Inlines a function body. | ||
17 | // | ||
18 | // ``` | ||
19 | // fn add(a: u32, b: u32) -> u32 { a + b } | ||
20 | // fn main() { | ||
21 | // let x = add<|>(1, 2); | ||
22 | // } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // fn add(a: u32, b: u32) -> u32 { a + b } | ||
27 | // fn main() { | ||
28 | // let x = { | ||
29 | // let a = 1; | ||
30 | // let b = 2; | ||
31 | // a + b | ||
32 | // }; | ||
33 | // } | ||
34 | // ``` | ||
35 | pub(crate) fn inline_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
36 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; | ||
37 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | ||
38 | let path = path_expr.path()?; | ||
39 | |||
40 | let function = match ctx.sema.resolve_path(&path)? { | ||
41 | PathResolution::Def(hir::ModuleDef::Function(f)) => f, | ||
42 | _ => return None, | ||
43 | }; | ||
44 | |||
45 | let function_source = function.source(ctx.db())?; | ||
46 | let arguments: Vec<_> = call.arg_list()?.args().collect(); | ||
47 | let parameters = function_parameter_patterns(&function_source.value)?; | ||
48 | |||
49 | if arguments.len() != parameters.len() { | ||
50 | // Can't inline the function because they've passed the wrong number of | ||
51 | // arguments to this function | ||
52 | mark::hit!(inline_function_incorrect_number_of_arguments); | ||
53 | return None; | ||
54 | } | ||
55 | |||
56 | let new_bindings = parameters.into_iter().zip(arguments); | ||
57 | |||
58 | let body = function_source.value.body()?; | ||
59 | |||
60 | acc.add( | ||
61 | AssistId("inline_function", AssistKind::RefactorInline), | ||
62 | format!("Inline `{}`", path), | ||
63 | call.syntax().text_range(), | ||
64 | |builder| { | ||
65 | let mut statements: Vec<ast::Stmt> = Vec::new(); | ||
66 | |||
67 | for (pattern, value) in new_bindings { | ||
68 | statements.push(make::let_stmt(pattern, Some(value)).into()); | ||
69 | } | ||
70 | |||
71 | statements.extend(body.statements()); | ||
72 | |||
73 | let original_indentation = call.indent_level(); | ||
74 | let replacement = make::block_expr(statements, body.expr()) | ||
75 | .reset_indent() | ||
76 | .indent(original_indentation); | ||
77 | |||
78 | builder.replace_ast(ast::Expr::CallExpr(call), ast::Expr::BlockExpr(replacement)); | ||
79 | }, | ||
80 | ) | ||
81 | } | ||
82 | |||
83 | fn function_parameter_patterns(value: &ast::Fn) -> Option<Vec<ast::Pat>> { | ||
84 | let mut patterns = Vec::new(); | ||
85 | |||
86 | for param in value.param_list()?.params() { | ||
87 | let pattern = param.pat()?; | ||
88 | patterns.push(pattern); | ||
89 | } | ||
90 | |||
91 | Some(patterns) | ||
92 | } | ||
93 | |||
94 | #[cfg(test)] | ||
95 | mod tests { | ||
96 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
97 | |||
98 | use super::*; | ||
99 | |||
100 | #[test] | ||
101 | fn no_args_or_return_value_gets_inlined_without_block() { | ||
102 | check_assist( | ||
103 | inline_function, | ||
104 | r#" | ||
105 | fn foo() { println!("Hello, World!"); } | ||
106 | fn main() { | ||
107 | fo<|>o(); | ||
108 | } | ||
109 | "#, | ||
110 | r#" | ||
111 | fn foo() { println!("Hello, World!"); } | ||
112 | fn main() { | ||
113 | { | ||
114 | println!("Hello, World!"); | ||
115 | }; | ||
116 | } | ||
117 | "#, | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | #[test] | ||
122 | fn args_with_side_effects() { | ||
123 | check_assist( | ||
124 | inline_function, | ||
125 | r#" | ||
126 | fn foo(name: String) { println!("Hello, {}!", name); } | ||
127 | fn main() { | ||
128 | foo<|>(String::from("Michael")); | ||
129 | } | ||
130 | "#, | ||
131 | r#" | ||
132 | fn foo(name: String) { println!("Hello, {}!", name); } | ||
133 | fn main() { | ||
134 | { | ||
135 | let name = String::from("Michael"); | ||
136 | println!("Hello, {}!", name); | ||
137 | }; | ||
138 | } | ||
139 | "#, | ||
140 | ); | ||
141 | } | ||
142 | |||
143 | #[test] | ||
144 | fn method_inlining_isnt_supported() { | ||
145 | check_assist_not_applicable( | ||
146 | inline_function, | ||
147 | r" | ||
148 | struct Foo; | ||
149 | impl Foo { fn bar(&self) {} } | ||
150 | |||
151 | fn main() { Foo.bar<|>(); } | ||
152 | ", | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn not_applicable_when_incorrect_number_of_parameters_are_provided() { | ||
158 | mark::check!(inline_function_incorrect_number_of_arguments); | ||
159 | check_assist_not_applicable( | ||
160 | inline_function, | ||
161 | r#" | ||
162 | fn add(a: u32, b: u32) -> u32 { a + b } | ||
163 | fn main() { let x = add<|>(42); } | ||
164 | "#, | ||
165 | ); | ||
166 | } | ||
167 | |||
168 | #[test] | ||
169 | fn function_with_multiple_statements() { | ||
170 | check_assist( | ||
171 | inline_function, | ||
172 | r#" | ||
173 | fn foo(a: u32, b: u32) -> u32 { | ||
174 | let x = a + b; | ||
175 | let y = x - b; | ||
176 | x * y | ||
177 | } | ||
178 | |||
179 | fn main() { | ||
180 | let x = foo<|>(1, 2); | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | fn foo(a: u32, b: u32) -> u32 { | ||
185 | let x = a + b; | ||
186 | let y = x - b; | ||
187 | x * y | ||
188 | } | ||
189 | |||
190 | fn main() { | ||
191 | let x = { | ||
192 | let a = 1; | ||
193 | let b = 2; | ||
194 | let x = a + b; | ||
195 | let y = x - b; | ||
196 | x * y | ||
197 | }; | ||
198 | } | ||
199 | "#, | ||
200 | ); | ||
201 | } | ||
202 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 01baa65fe..9c2a95735 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -131,6 +131,7 @@ mod handlers { | |||
131 | mod generate_impl; | 131 | mod generate_impl; |
132 | mod generate_new; | 132 | mod generate_new; |
133 | mod infer_function_return_type; | 133 | mod infer_function_return_type; |
134 | mod inline_function; | ||
134 | mod inline_local_variable; | 135 | mod inline_local_variable; |
135 | mod introduce_named_lifetime; | 136 | mod introduce_named_lifetime; |
136 | mod invert_if; | 137 | mod invert_if; |
@@ -183,6 +184,7 @@ mod handlers { | |||
183 | generate_impl::generate_impl, | 184 | generate_impl::generate_impl, |
184 | generate_new::generate_new, | 185 | generate_new::generate_new, |
185 | infer_function_return_type::infer_function_return_type, | 186 | infer_function_return_type::infer_function_return_type, |
187 | inline_function::inline_function, | ||
186 | inline_local_variable::inline_local_variable, | 188 | inline_local_variable::inline_local_variable, |
187 | introduce_named_lifetime::introduce_named_lifetime, | 189 | introduce_named_lifetime::introduce_named_lifetime, |
188 | invert_if::invert_if, | 190 | invert_if::invert_if, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 85e3c6742..b15352cf3 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -531,6 +531,29 @@ fn foo() -> i32 { 42i32 } | |||
531 | } | 531 | } |
532 | 532 | ||
533 | #[test] | 533 | #[test] |
534 | fn doctest_inline_function() { | ||
535 | check_doc_test( | ||
536 | "inline_function", | ||
537 | r#####" | ||
538 | fn add(a: u32, b: u32) -> u32 { a + b } | ||
539 | fn main() { | ||
540 | let x = add<|>(1, 2); | ||
541 | } | ||
542 | "#####, | ||
543 | r#####" | ||
544 | fn add(a: u32, b: u32) -> u32 { a + b } | ||
545 | fn main() { | ||
546 | let x = { | ||
547 | let a = 1; | ||
548 | let b = 2; | ||
549 | a + b | ||
550 | }; | ||
551 | } | ||
552 | "#####, | ||
553 | ) | ||
554 | } | ||
555 | |||
556 | #[test] | ||
534 | fn doctest_inline_local_variable() { | 557 | fn doctest_inline_local_variable() { |
535 | check_doc_test( | 558 | check_doc_test( |
536 | "inline_local_variable", | 559 | "inline_local_variable", |