diff options
Diffstat (limited to 'crates/assists')
-rw-r--r-- | crates/assists/src/handlers/extract_function.rs | 2159 | ||||
-rw-r--r-- | crates/assists/src/handlers/extract_struct_from_enum_variant.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_enum_match_method.rs | 221 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_new.rs | 64 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 55 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 75 |
7 files changed, 2516 insertions, 66 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs new file mode 100644 index 000000000..d876eabca --- /dev/null +++ b/crates/assists/src/handlers/extract_function.rs | |||
@@ -0,0 +1,2159 @@ | |||
1 | use either::Either; | ||
2 | use hir::{HirDisplay, Local}; | ||
3 | use ide_db::{ | ||
4 | defs::{Definition, NameRefClass}, | ||
5 | search::{FileReference, ReferenceAccess, SearchScope}, | ||
6 | }; | ||
7 | use itertools::Itertools; | ||
8 | use stdx::format_to; | ||
9 | use syntax::{ | ||
10 | algo::SyntaxRewriter, | ||
11 | ast::{ | ||
12 | self, | ||
13 | edit::{AstNodeEdit, IndentLevel}, | ||
14 | AstNode, | ||
15 | }, | ||
16 | AstToken, Direction, SyntaxElement, | ||
17 | SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, | ||
18 | SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T, | ||
19 | }; | ||
20 | use test_utils::mark; | ||
21 | |||
22 | use crate::{ | ||
23 | assist_context::{AssistContext, Assists}, | ||
24 | AssistId, | ||
25 | }; | ||
26 | |||
27 | // Assist: extract_function | ||
28 | // | ||
29 | // Extracts selected statements into new function. | ||
30 | // | ||
31 | // ``` | ||
32 | // fn main() { | ||
33 | // let n = 1; | ||
34 | // $0let m = n + 2; | ||
35 | // let k = m + n;$0 | ||
36 | // let g = 3; | ||
37 | // } | ||
38 | // ``` | ||
39 | // -> | ||
40 | // ``` | ||
41 | // fn main() { | ||
42 | // let n = 1; | ||
43 | // fun_name(n); | ||
44 | // let g = 3; | ||
45 | // } | ||
46 | // | ||
47 | // fn $0fun_name(n: i32) { | ||
48 | // let m = n + 2; | ||
49 | // let k = m + n; | ||
50 | // } | ||
51 | // ``` | ||
52 | pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
53 | if ctx.frange.range.is_empty() { | ||
54 | return None; | ||
55 | } | ||
56 | |||
57 | let node = ctx.covering_element(); | ||
58 | if node.kind() == COMMENT { | ||
59 | mark::hit!(extract_function_in_comment_is_not_applicable); | ||
60 | return None; | ||
61 | } | ||
62 | |||
63 | let node = element_to_node(node); | ||
64 | |||
65 | let body = extraction_target(&node, ctx.frange.range)?; | ||
66 | |||
67 | let vars_used_in_body = vars_used_in_body(ctx, &body); | ||
68 | let self_param = self_param_from_usages(ctx, &body, &vars_used_in_body); | ||
69 | |||
70 | let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding }; | ||
71 | let insert_after = scope_for_fn_insertion(&body, anchor)?; | ||
72 | let module = ctx.sema.scope(&insert_after).module()?; | ||
73 | |||
74 | let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); | ||
75 | let ret_ty = body_return_ty(ctx, &body)?; | ||
76 | |||
77 | // FIXME: we compute variables that outlive here just to check `never!` condition | ||
78 | // this requires traversing whole `body` (cheap) and finding all references (expensive) | ||
79 | // maybe we can move this check to `edit` closure somehow? | ||
80 | if stdx::never!(!vars_defined_in_body_and_outlive.is_empty() && !ret_ty.is_unit()) { | ||
81 | // We should not have variables that outlive body if we have expression block | ||
82 | return None; | ||
83 | } | ||
84 | |||
85 | let target_range = match &body { | ||
86 | FunctionBody::Expr(expr) => expr.syntax().text_range(), | ||
87 | FunctionBody::Span { .. } => ctx.frange.range, | ||
88 | }; | ||
89 | |||
90 | acc.add( | ||
91 | AssistId("extract_function", crate::AssistKind::RefactorExtract), | ||
92 | "Extract into function", | ||
93 | target_range, | ||
94 | move |builder| { | ||
95 | let params = extracted_function_params(ctx, &body, &vars_used_in_body); | ||
96 | |||
97 | let fun = Function { | ||
98 | name: "fun_name".to_string(), | ||
99 | self_param: self_param.map(|(_, pat)| pat), | ||
100 | params, | ||
101 | ret_ty, | ||
102 | body, | ||
103 | vars_defined_in_body_and_outlive, | ||
104 | }; | ||
105 | |||
106 | builder.replace(target_range, format_replacement(ctx, &fun)); | ||
107 | |||
108 | let new_indent = IndentLevel::from_node(&insert_after); | ||
109 | let old_indent = fun.body.indent_level(); | ||
110 | |||
111 | let fn_def = format_function(ctx, module, &fun, old_indent, new_indent); | ||
112 | let insert_offset = insert_after.text_range().end(); | ||
113 | builder.insert(insert_offset, fn_def); | ||
114 | }, | ||
115 | ) | ||
116 | } | ||
117 | |||
118 | #[derive(Debug)] | ||
119 | struct Function { | ||
120 | name: String, | ||
121 | self_param: Option<ast::SelfParam>, | ||
122 | params: Vec<Param>, | ||
123 | ret_ty: RetType, | ||
124 | body: FunctionBody, | ||
125 | vars_defined_in_body_and_outlive: Vec<Local>, | ||
126 | } | ||
127 | |||
128 | #[derive(Debug)] | ||
129 | struct Param { | ||
130 | var: Local, | ||
131 | ty: hir::Type, | ||
132 | has_usages_afterwards: bool, | ||
133 | has_mut_inside_body: bool, | ||
134 | is_copy: bool, | ||
135 | } | ||
136 | |||
137 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
138 | enum ParamKind { | ||
139 | Value, | ||
140 | MutValue, | ||
141 | SharedRef, | ||
142 | MutRef, | ||
143 | } | ||
144 | |||
145 | impl ParamKind { | ||
146 | fn is_ref(&self) -> bool { | ||
147 | matches!(self, ParamKind::SharedRef | ParamKind::MutRef) | ||
148 | } | ||
149 | } | ||
150 | |||
151 | impl Param { | ||
152 | fn kind(&self) -> ParamKind { | ||
153 | match (self.has_usages_afterwards, self.has_mut_inside_body, self.is_copy) { | ||
154 | (true, true, _) => ParamKind::MutRef, | ||
155 | (true, false, false) => ParamKind::SharedRef, | ||
156 | (false, true, _) => ParamKind::MutValue, | ||
157 | (true, false, true) | (false, false, _) => ParamKind::Value, | ||
158 | } | ||
159 | } | ||
160 | |||
161 | fn value_prefix(&self) -> &'static str { | ||
162 | match self.kind() { | ||
163 | ParamKind::Value | ParamKind::MutValue => "", | ||
164 | ParamKind::SharedRef => "&", | ||
165 | ParamKind::MutRef => "&mut ", | ||
166 | } | ||
167 | } | ||
168 | |||
169 | fn type_prefix(&self) -> &'static str { | ||
170 | match self.kind() { | ||
171 | ParamKind::Value | ParamKind::MutValue => "", | ||
172 | ParamKind::SharedRef => "&", | ||
173 | ParamKind::MutRef => "&mut ", | ||
174 | } | ||
175 | } | ||
176 | |||
177 | fn mut_pattern(&self) -> &'static str { | ||
178 | match self.kind() { | ||
179 | ParamKind::MutValue => "mut ", | ||
180 | _ => "", | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | #[derive(Debug)] | ||
186 | enum RetType { | ||
187 | Expr(hir::Type), | ||
188 | Stmt, | ||
189 | } | ||
190 | |||
191 | impl RetType { | ||
192 | fn is_unit(&self) -> bool { | ||
193 | match self { | ||
194 | RetType::Expr(ty) => ty.is_unit(), | ||
195 | RetType::Stmt => true, | ||
196 | } | ||
197 | } | ||
198 | |||
199 | fn as_fn_ret(&self) -> Option<&hir::Type> { | ||
200 | match self { | ||
201 | RetType::Stmt => None, | ||
202 | RetType::Expr(ty) if ty.is_unit() => None, | ||
203 | RetType::Expr(ty) => Some(ty), | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | |||
208 | /// Semantically same as `ast::Expr`, but preserves identity when using only part of the Block | ||
209 | #[derive(Debug)] | ||
210 | enum FunctionBody { | ||
211 | Expr(ast::Expr), | ||
212 | Span { elements: Vec<SyntaxElement>, leading_indent: String }, | ||
213 | } | ||
214 | |||
215 | impl FunctionBody { | ||
216 | fn from_whole_node(node: SyntaxNode) -> Option<Self> { | ||
217 | match node.kind() { | ||
218 | PATH_EXPR => None, | ||
219 | BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()).map(Self::Expr), | ||
220 | RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()).map(Self::Expr), | ||
221 | BLOCK_EXPR => ast::BlockExpr::cast(node) | ||
222 | .filter(|it| it.is_standalone()) | ||
223 | .map(Into::into) | ||
224 | .map(Self::Expr), | ||
225 | _ => ast::Expr::cast(node).map(Self::Expr), | ||
226 | } | ||
227 | } | ||
228 | |||
229 | fn from_range(node: &SyntaxNode, range: TextRange) -> Option<FunctionBody> { | ||
230 | let mut first = node.token_at_offset(range.start()).left_biased()?; | ||
231 | let last = node.token_at_offset(range.end()).right_biased()?; | ||
232 | |||
233 | let mut leading_indent = String::new(); | ||
234 | |||
235 | let leading_trivia = first | ||
236 | .siblings_with_tokens(Direction::Prev) | ||
237 | .skip(1) | ||
238 | .take_while(|e| e.kind() == SyntaxKind::WHITESPACE && e.as_token().is_some()); | ||
239 | |||
240 | for e in leading_trivia { | ||
241 | let token = e.as_token().unwrap(); | ||
242 | let text = token.text(); | ||
243 | match text.rfind('\n') { | ||
244 | Some(pos) => { | ||
245 | leading_indent = text[pos..].to_owned(); | ||
246 | break; | ||
247 | } | ||
248 | None => first = token.clone(), | ||
249 | } | ||
250 | } | ||
251 | |||
252 | let mut elements: Vec<_> = first | ||
253 | .siblings_with_tokens(Direction::Next) | ||
254 | .take_while(|e| e.as_token() != Some(&last)) | ||
255 | .collect(); | ||
256 | |||
257 | if !(last.kind() == SyntaxKind::WHITESPACE && last.text().lines().count() <= 2) { | ||
258 | elements.push(last.into()); | ||
259 | } | ||
260 | |||
261 | Some(FunctionBody::Span { elements, leading_indent }) | ||
262 | } | ||
263 | |||
264 | fn indent_level(&self) -> IndentLevel { | ||
265 | match &self { | ||
266 | FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()), | ||
267 | FunctionBody::Span { elements, .. } => elements | ||
268 | .iter() | ||
269 | .filter_map(SyntaxElement::as_node) | ||
270 | .map(IndentLevel::from_node) | ||
271 | .min_by_key(|level| level.0) | ||
272 | .expect("body must contain at least one node"), | ||
273 | } | ||
274 | } | ||
275 | |||
276 | fn tail_expr(&self) -> Option<ast::Expr> { | ||
277 | match &self { | ||
278 | FunctionBody::Expr(expr) => Some(expr.clone()), | ||
279 | FunctionBody::Span { elements, .. } => { | ||
280 | elements.iter().rev().find_map(|e| e.as_node()).cloned().and_then(ast::Expr::cast) | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | |||
285 | fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ { | ||
286 | match self { | ||
287 | FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()), | ||
288 | FunctionBody::Span { elements, .. } => Either::Left( | ||
289 | elements | ||
290 | .iter() | ||
291 | .filter_map(SyntaxElement::as_node) | ||
292 | .flat_map(SyntaxNode::descendants), | ||
293 | ), | ||
294 | } | ||
295 | } | ||
296 | |||
297 | fn text_range(&self) -> TextRange { | ||
298 | match self { | ||
299 | FunctionBody::Expr(expr) => expr.syntax().text_range(), | ||
300 | FunctionBody::Span { elements, .. } => TextRange::new( | ||
301 | elements.first().unwrap().text_range().start(), | ||
302 | elements.last().unwrap().text_range().end(), | ||
303 | ), | ||
304 | } | ||
305 | } | ||
306 | |||
307 | fn contains_range(&self, range: TextRange) -> bool { | ||
308 | self.text_range().contains_range(range) | ||
309 | } | ||
310 | |||
311 | fn preceedes_range(&self, range: TextRange) -> bool { | ||
312 | self.text_range().end() <= range.start() | ||
313 | } | ||
314 | |||
315 | fn contains_node(&self, node: &SyntaxNode) -> bool { | ||
316 | self.contains_range(node.text_range()) | ||
317 | } | ||
318 | } | ||
319 | |||
320 | impl HasTokenAtOffset for FunctionBody { | ||
321 | fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> { | ||
322 | match self { | ||
323 | FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset), | ||
324 | FunctionBody::Span { elements, .. } => { | ||
325 | stdx::always!(self.text_range().contains(offset)); | ||
326 | let mut iter = elements | ||
327 | .iter() | ||
328 | .filter(|element| element.text_range().contains_inclusive(offset)); | ||
329 | let element1 = iter.next().expect("offset does not fall into body"); | ||
330 | let element2 = iter.next(); | ||
331 | stdx::always!(iter.next().is_none(), "> 2 tokens at offset"); | ||
332 | let t1 = match element1 { | ||
333 | syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), | ||
334 | syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), | ||
335 | }; | ||
336 | let t2 = element2.map(|e| match e { | ||
337 | syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), | ||
338 | syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), | ||
339 | }); | ||
340 | |||
341 | match t2 { | ||
342 | Some(t2) => match (t1.clone().right_biased(), t2.clone().left_biased()) { | ||
343 | (Some(e1), Some(e2)) => TokenAtOffset::Between(e1, e2), | ||
344 | (Some(_), None) => t1, | ||
345 | (None, _) => t2, | ||
346 | }, | ||
347 | None => t1, | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | /// node or token's parent | ||
355 | fn element_to_node(node: SyntaxElement) -> SyntaxNode { | ||
356 | match node { | ||
357 | syntax::NodeOrToken::Node(n) => n, | ||
358 | syntax::NodeOrToken::Token(t) => t.parent(), | ||
359 | } | ||
360 | } | ||
361 | |||
362 | /// Try to guess what user wants to extract | ||
363 | /// | ||
364 | /// We have basically have two cases: | ||
365 | /// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs. | ||
366 | /// Then we can use `ast::Expr` | ||
367 | /// * We want a few statements for a block. E.g. | ||
368 | /// ```rust,no_run | ||
369 | /// fn foo() -> i32 { | ||
370 | /// let m = 1; | ||
371 | /// $0 | ||
372 | /// let n = 2; | ||
373 | /// let k = 3; | ||
374 | /// k + n | ||
375 | /// $0 | ||
376 | /// } | ||
377 | /// ``` | ||
378 | /// | ||
379 | fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> { | ||
380 | // we have selected exactly the expr node | ||
381 | // wrap it before anything else | ||
382 | if node.text_range() == selection_range { | ||
383 | let body = FunctionBody::from_whole_node(node.clone()); | ||
384 | if body.is_some() { | ||
385 | return body; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | // we have selected a few statements in a block | ||
390 | // so covering_element returns the whole block | ||
391 | if node.kind() == BLOCK_EXPR { | ||
392 | let body = FunctionBody::from_range(&node, selection_range); | ||
393 | if body.is_some() { | ||
394 | return body; | ||
395 | } | ||
396 | } | ||
397 | |||
398 | // we have selected single statement | ||
399 | // `from_whole_node` failed because (let) statement is not and expression | ||
400 | // so we try to expand covering_element to parent and repeat the previous | ||
401 | if let Some(parent) = node.parent() { | ||
402 | if parent.kind() == BLOCK_EXPR { | ||
403 | let body = FunctionBody::from_range(&parent, selection_range); | ||
404 | if body.is_some() { | ||
405 | return body; | ||
406 | } | ||
407 | } | ||
408 | } | ||
409 | |||
410 | // select the closest containing expr (both ifs are used) | ||
411 | std::iter::once(node.clone()).chain(node.ancestors()).find_map(FunctionBody::from_whole_node) | ||
412 | } | ||
413 | |||
414 | /// list local variables that are referenced in `body` | ||
415 | fn vars_used_in_body(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | ||
416 | // FIXME: currently usages inside macros are not found | ||
417 | body.descendants() | ||
418 | .filter_map(ast::NameRef::cast) | ||
419 | .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref)) | ||
420 | .map(|name_kind| name_kind.referenced(ctx.db())) | ||
421 | .filter_map(|definition| match definition { | ||
422 | Definition::Local(local) => Some(local), | ||
423 | _ => None, | ||
424 | }) | ||
425 | .unique() | ||
426 | .collect() | ||
427 | } | ||
428 | |||
429 | /// find `self` param, that was not defined inside `body` | ||
430 | /// | ||
431 | /// It should skip `self` params from impls inside `body` | ||
432 | fn self_param_from_usages( | ||
433 | ctx: &AssistContext, | ||
434 | body: &FunctionBody, | ||
435 | vars_used_in_body: &[Local], | ||
436 | ) -> Option<(Local, ast::SelfParam)> { | ||
437 | let mut iter = vars_used_in_body | ||
438 | .iter() | ||
439 | .filter(|var| var.is_self(ctx.db())) | ||
440 | .map(|var| (var, var.source(ctx.db()))) | ||
441 | .filter(|(_, src)| is_defined_before(ctx, body, src)) | ||
442 | .filter_map(|(&node, src)| match src.value { | ||
443 | Either::Right(it) => Some((node, it)), | ||
444 | Either::Left(_) => { | ||
445 | stdx::never!(false, "Local::is_self returned true, but source is IdentPat"); | ||
446 | None | ||
447 | } | ||
448 | }); | ||
449 | |||
450 | let self_param = iter.next(); | ||
451 | stdx::always!( | ||
452 | iter.next().is_none(), | ||
453 | "body references two different self params, both defined outside" | ||
454 | ); | ||
455 | |||
456 | self_param | ||
457 | } | ||
458 | |||
459 | /// find variables that should be extracted as params | ||
460 | /// | ||
461 | /// Computes additional info that affects param type and mutability | ||
462 | fn extracted_function_params( | ||
463 | ctx: &AssistContext, | ||
464 | body: &FunctionBody, | ||
465 | vars_used_in_body: &[Local], | ||
466 | ) -> Vec<Param> { | ||
467 | vars_used_in_body | ||
468 | .iter() | ||
469 | .filter(|var| !var.is_self(ctx.db())) | ||
470 | .map(|node| (node, node.source(ctx.db()))) | ||
471 | .filter(|(_, src)| is_defined_before(ctx, body, src)) | ||
472 | .filter_map(|(&node, src)| { | ||
473 | if src.value.is_left() { | ||
474 | Some(node) | ||
475 | } else { | ||
476 | stdx::never!(false, "Local::is_self returned false, but source is SelfParam"); | ||
477 | None | ||
478 | } | ||
479 | }) | ||
480 | .map(|var| { | ||
481 | let usages = LocalUsages::find(ctx, var); | ||
482 | let ty = var.ty(ctx.db()); | ||
483 | let is_copy = ty.is_copy(ctx.db()); | ||
484 | Param { | ||
485 | var, | ||
486 | ty, | ||
487 | has_usages_afterwards: has_usages_after_body(&usages, body), | ||
488 | has_mut_inside_body: has_exclusive_usages(ctx, &usages, body), | ||
489 | is_copy, | ||
490 | } | ||
491 | }) | ||
492 | .collect() | ||
493 | } | ||
494 | |||
495 | fn has_usages_after_body(usages: &LocalUsages, body: &FunctionBody) -> bool { | ||
496 | usages.iter().any(|reference| body.preceedes_range(reference.range)) | ||
497 | } | ||
498 | |||
499 | /// checks if relevant var is used with `&mut` access inside body | ||
500 | fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &FunctionBody) -> bool { | ||
501 | usages | ||
502 | .iter() | ||
503 | .filter(|reference| body.contains_range(reference.range)) | ||
504 | .any(|reference| reference_is_exclusive(reference, body, ctx)) | ||
505 | } | ||
506 | |||
507 | /// checks if this reference requires `&mut` access inside body | ||
508 | fn reference_is_exclusive( | ||
509 | reference: &FileReference, | ||
510 | body: &FunctionBody, | ||
511 | ctx: &AssistContext, | ||
512 | ) -> bool { | ||
513 | // we directly modify variable with set: `n = 0`, `n += 1` | ||
514 | if reference.access == Some(ReferenceAccess::Write) { | ||
515 | return true; | ||
516 | } | ||
517 | |||
518 | // we take `&mut` reference to variable: `&mut v` | ||
519 | let path = match path_element_of_reference(body, reference) { | ||
520 | Some(path) => path, | ||
521 | None => return false, | ||
522 | }; | ||
523 | |||
524 | expr_require_exclusive_access(ctx, &path).unwrap_or(false) | ||
525 | } | ||
526 | |||
527 | /// checks if this expr requires `&mut` access, recurses on field access | ||
528 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { | ||
529 | let parent = expr.syntax().parent()?; | ||
530 | |||
531 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { | ||
532 | if bin_expr.op_kind()?.is_assignment() { | ||
533 | return Some(bin_expr.lhs()?.syntax() == expr.syntax()); | ||
534 | } | ||
535 | return Some(false); | ||
536 | } | ||
537 | |||
538 | if let Some(ref_expr) = ast::RefExpr::cast(parent.clone()) { | ||
539 | return Some(ref_expr.mut_token().is_some()); | ||
540 | } | ||
541 | |||
542 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { | ||
543 | let func = ctx.sema.resolve_method_call(&method_call)?; | ||
544 | let self_param = func.self_param(ctx.db())?; | ||
545 | let access = self_param.access(ctx.db()); | ||
546 | |||
547 | return Some(matches!(access, hir::Access::Exclusive)); | ||
548 | } | ||
549 | |||
550 | if let Some(field) = ast::FieldExpr::cast(parent) { | ||
551 | return expr_require_exclusive_access(ctx, &field.into()); | ||
552 | } | ||
553 | |||
554 | Some(false) | ||
555 | } | ||
556 | |||
557 | /// Container of local varaible usages | ||
558 | /// | ||
559 | /// Semanticall same as `UsageSearchResult`, but provides more convenient interface | ||
560 | struct LocalUsages(ide_db::search::UsageSearchResult); | ||
561 | |||
562 | impl LocalUsages { | ||
563 | fn find(ctx: &AssistContext, var: Local) -> Self { | ||
564 | Self( | ||
565 | Definition::Local(var) | ||
566 | .usages(&ctx.sema) | ||
567 | .in_scope(SearchScope::single_file(ctx.frange.file_id)) | ||
568 | .all(), | ||
569 | ) | ||
570 | } | ||
571 | |||
572 | fn iter(&self) -> impl Iterator<Item = &FileReference> + '_ { | ||
573 | self.0.iter().flat_map(|(_, rs)| rs.iter()) | ||
574 | } | ||
575 | } | ||
576 | |||
577 | trait HasTokenAtOffset { | ||
578 | fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken>; | ||
579 | } | ||
580 | |||
581 | impl HasTokenAtOffset for SyntaxNode { | ||
582 | fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> { | ||
583 | SyntaxNode::token_at_offset(&self, offset) | ||
584 | } | ||
585 | } | ||
586 | |||
587 | /// find relevant `ast::PathExpr` for reference | ||
588 | /// | ||
589 | /// # Preconditions | ||
590 | /// | ||
591 | /// `node` must cover `reference`, that is `node.text_range().contains_range(reference.range)` | ||
592 | fn path_element_of_reference( | ||
593 | node: &dyn HasTokenAtOffset, | ||
594 | reference: &FileReference, | ||
595 | ) -> Option<ast::Expr> { | ||
596 | let token = node.token_at_offset(reference.range.start()).right_biased().or_else(|| { | ||
597 | stdx::never!(false, "cannot find token at variable usage: {:?}", reference); | ||
598 | None | ||
599 | })?; | ||
600 | let path = token.ancestors().find_map(ast::Expr::cast).or_else(|| { | ||
601 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); | ||
602 | None | ||
603 | })?; | ||
604 | stdx::always!(matches!(path, ast::Expr::PathExpr(_))); | ||
605 | Some(path) | ||
606 | } | ||
607 | |||
608 | /// list local variables defined inside `body` | ||
609 | fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { | ||
610 | // FIXME: this doesn't work well with macros | ||
611 | // see https://github.com/rust-analyzer/rust-analyzer/pull/7535#discussion_r570048550 | ||
612 | body.descendants() | ||
613 | .filter_map(ast::IdentPat::cast) | ||
614 | .filter_map(|let_stmt| ctx.sema.to_def(&let_stmt)) | ||
615 | .unique() | ||
616 | .collect() | ||
617 | } | ||
618 | |||
619 | /// list local variables defined inside `body` that should be returned from extracted function | ||
620 | fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | ||
621 | let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); | ||
622 | vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); | ||
623 | vars_defined_in_body | ||
624 | } | ||
625 | |||
626 | /// checks if the relevant local was defined before(outside of) body | ||
627 | fn is_defined_before( | ||
628 | ctx: &AssistContext, | ||
629 | body: &FunctionBody, | ||
630 | src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>, | ||
631 | ) -> bool { | ||
632 | src.file_id.original_file(ctx.db()) == ctx.frange.file_id | ||
633 | && !body.contains_node(&either_syntax(&src.value)) | ||
634 | } | ||
635 | |||
636 | fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | ||
637 | match value { | ||
638 | Either::Left(pat) => pat.syntax(), | ||
639 | Either::Right(it) => it.syntax(), | ||
640 | } | ||
641 | } | ||
642 | |||
643 | /// checks if local variable is used after(outside of) body | ||
644 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | ||
645 | let usages = Definition::Local(*var) | ||
646 | .usages(&ctx.sema) | ||
647 | .in_scope(SearchScope::single_file(ctx.frange.file_id)) | ||
648 | .all(); | ||
649 | let mut usages = usages.iter().flat_map(|(_, rs)| rs.iter()); | ||
650 | |||
651 | usages.any(|reference| body.preceedes_range(reference.range)) | ||
652 | } | ||
653 | |||
654 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { | ||
655 | match body.tail_expr() { | ||
656 | Some(expr) => { | ||
657 | let ty = ctx.sema.type_of_expr(&expr)?; | ||
658 | Some(RetType::Expr(ty)) | ||
659 | } | ||
660 | None => Some(RetType::Stmt), | ||
661 | } | ||
662 | } | ||
663 | /// Where to put extracted function definition | ||
664 | #[derive(Debug)] | ||
665 | enum Anchor { | ||
666 | /// Extract free function and put right after current top-level function | ||
667 | Freestanding, | ||
668 | /// Extract method and put right after current function in the impl-block | ||
669 | Method, | ||
670 | } | ||
671 | |||
672 | /// find where to put extracted function definition | ||
673 | /// | ||
674 | /// Function should be put right after returned node | ||
675 | fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { | ||
676 | match body { | ||
677 | FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor), | ||
678 | FunctionBody::Span { elements, .. } => { | ||
679 | let node = elements.iter().find_map(|e| e.as_node())?; | ||
680 | scope_for_fn_insertion_node(&node, anchor) | ||
681 | } | ||
682 | } | ||
683 | } | ||
684 | |||
685 | fn scope_for_fn_insertion_node(node: &SyntaxNode, anchor: Anchor) -> Option<SyntaxNode> { | ||
686 | let mut ancestors = node.ancestors().peekable(); | ||
687 | let mut last_ancestor = None; | ||
688 | while let Some(next_ancestor) = ancestors.next() { | ||
689 | match next_ancestor.kind() { | ||
690 | SyntaxKind::SOURCE_FILE => break, | ||
691 | SyntaxKind::ITEM_LIST => { | ||
692 | if !matches!(anchor, Anchor::Freestanding) { | ||
693 | continue; | ||
694 | } | ||
695 | if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) { | ||
696 | break; | ||
697 | } | ||
698 | } | ||
699 | SyntaxKind::ASSOC_ITEM_LIST => { | ||
700 | if !matches!(anchor, Anchor::Method) { | ||
701 | continue; | ||
702 | } | ||
703 | if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::IMPL) { | ||
704 | break; | ||
705 | } | ||
706 | } | ||
707 | _ => {} | ||
708 | } | ||
709 | last_ancestor = Some(next_ancestor); | ||
710 | } | ||
711 | last_ancestor | ||
712 | } | ||
713 | |||
714 | fn format_replacement(ctx: &AssistContext, fun: &Function) -> String { | ||
715 | let mut buf = String::new(); | ||
716 | |||
717 | match fun.vars_defined_in_body_and_outlive.as_slice() { | ||
718 | [] => {} | ||
719 | [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()), | ||
720 | [v0, vs @ ..] => { | ||
721 | buf.push_str("let ("); | ||
722 | format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); | ||
723 | for var in vs { | ||
724 | format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); | ||
725 | } | ||
726 | buf.push_str(") = "); | ||
727 | } | ||
728 | } | ||
729 | |||
730 | if fun.self_param.is_some() { | ||
731 | format_to!(buf, "self."); | ||
732 | } | ||
733 | format_to!(buf, "{}(", fun.name); | ||
734 | format_arg_list_to(&mut buf, fun, ctx); | ||
735 | format_to!(buf, ")"); | ||
736 | |||
737 | if fun.ret_ty.is_unit() { | ||
738 | format_to!(buf, ";"); | ||
739 | } | ||
740 | |||
741 | buf | ||
742 | } | ||
743 | |||
744 | fn format_arg_list_to(buf: &mut String, fun: &Function, ctx: &AssistContext) { | ||
745 | let mut it = fun.params.iter(); | ||
746 | if let Some(param) = it.next() { | ||
747 | format_arg_to(buf, ctx, param); | ||
748 | } | ||
749 | for param in it { | ||
750 | buf.push_str(", "); | ||
751 | format_arg_to(buf, ctx, param); | ||
752 | } | ||
753 | } | ||
754 | |||
755 | fn format_arg_to(buf: &mut String, ctx: &AssistContext, param: &Param) { | ||
756 | format_to!(buf, "{}{}", param.value_prefix(), param.var.name(ctx.db()).unwrap()); | ||
757 | } | ||
758 | |||
759 | fn format_function( | ||
760 | ctx: &AssistContext, | ||
761 | module: hir::Module, | ||
762 | fun: &Function, | ||
763 | old_indent: IndentLevel, | ||
764 | new_indent: IndentLevel, | ||
765 | ) -> String { | ||
766 | let mut fn_def = String::new(); | ||
767 | format_to!(fn_def, "\n\n{}fn $0{}(", new_indent, fun.name); | ||
768 | format_function_param_list_to(&mut fn_def, ctx, module, fun); | ||
769 | fn_def.push(')'); | ||
770 | format_function_ret_to(&mut fn_def, ctx, module, fun); | ||
771 | fn_def.push_str(" {"); | ||
772 | format_function_body_to(&mut fn_def, ctx, old_indent, new_indent, fun); | ||
773 | format_to!(fn_def, "{}}}", new_indent); | ||
774 | |||
775 | fn_def | ||
776 | } | ||
777 | |||
778 | fn format_function_param_list_to( | ||
779 | fn_def: &mut String, | ||
780 | ctx: &AssistContext, | ||
781 | module: hir::Module, | ||
782 | fun: &Function, | ||
783 | ) { | ||
784 | let mut it = fun.params.iter(); | ||
785 | if let Some(self_param) = &fun.self_param { | ||
786 | format_to!(fn_def, "{}", self_param); | ||
787 | } else if let Some(param) = it.next() { | ||
788 | format_param_to(fn_def, ctx, module, param); | ||
789 | } | ||
790 | for param in it { | ||
791 | fn_def.push_str(", "); | ||
792 | format_param_to(fn_def, ctx, module, param); | ||
793 | } | ||
794 | } | ||
795 | |||
796 | fn format_param_to(fn_def: &mut String, ctx: &AssistContext, module: hir::Module, param: &Param) { | ||
797 | format_to!( | ||
798 | fn_def, | ||
799 | "{}{}: {}{}", | ||
800 | param.mut_pattern(), | ||
801 | param.var.name(ctx.db()).unwrap(), | ||
802 | param.type_prefix(), | ||
803 | format_type(¶m.ty, ctx, module) | ||
804 | ); | ||
805 | } | ||
806 | |||
807 | fn format_function_ret_to( | ||
808 | fn_def: &mut String, | ||
809 | ctx: &AssistContext, | ||
810 | module: hir::Module, | ||
811 | fun: &Function, | ||
812 | ) { | ||
813 | if let Some(ty) = fun.ret_ty.as_fn_ret() { | ||
814 | format_to!(fn_def, " -> {}", format_type(ty, ctx, module)); | ||
815 | } else { | ||
816 | match fun.vars_defined_in_body_and_outlive.as_slice() { | ||
817 | [] => {} | ||
818 | [var] => { | ||
819 | format_to!(fn_def, " -> {}", format_type(&var.ty(ctx.db()), ctx, module)); | ||
820 | } | ||
821 | [v0, vs @ ..] => { | ||
822 | format_to!(fn_def, " -> ({}", format_type(&v0.ty(ctx.db()), ctx, module)); | ||
823 | for var in vs { | ||
824 | format_to!(fn_def, ", {}", format_type(&var.ty(ctx.db()), ctx, module)); | ||
825 | } | ||
826 | fn_def.push(')'); | ||
827 | } | ||
828 | } | ||
829 | } | ||
830 | } | ||
831 | |||
832 | fn format_function_body_to( | ||
833 | fn_def: &mut String, | ||
834 | ctx: &AssistContext, | ||
835 | old_indent: IndentLevel, | ||
836 | new_indent: IndentLevel, | ||
837 | fun: &Function, | ||
838 | ) { | ||
839 | match &fun.body { | ||
840 | FunctionBody::Expr(expr) => { | ||
841 | fn_def.push('\n'); | ||
842 | let expr = expr.dedent(old_indent).indent(new_indent + 1); | ||
843 | let expr = fix_param_usages(ctx, &fun.params, expr.syntax()); | ||
844 | format_to!(fn_def, "{}{}", new_indent + 1, expr); | ||
845 | fn_def.push('\n'); | ||
846 | } | ||
847 | FunctionBody::Span { elements, leading_indent } => { | ||
848 | format_to!(fn_def, "{}", leading_indent); | ||
849 | let new_indent_str = format!("\n{}", new_indent + 1); | ||
850 | for mut element in elements { | ||
851 | let new_ws; | ||
852 | if let Some(ws) = element.as_token().cloned().and_then(ast::Whitespace::cast) { | ||
853 | let text = ws.syntax().text(); | ||
854 | if text.contains('\n') { | ||
855 | let new_text = text.replace(&format!("\n{}", old_indent), &new_indent_str); | ||
856 | new_ws = ast::make::tokens::whitespace(&new_text).into(); | ||
857 | element = &new_ws; | ||
858 | } | ||
859 | } | ||
860 | |||
861 | match element { | ||
862 | syntax::NodeOrToken::Node(node) => { | ||
863 | format_to!(fn_def, "{}", fix_param_usages(ctx, &fun.params, node)); | ||
864 | } | ||
865 | syntax::NodeOrToken::Token(token) => { | ||
866 | format_to!(fn_def, "{}", token); | ||
867 | } | ||
868 | } | ||
869 | } | ||
870 | if !fn_def.ends_with('\n') { | ||
871 | fn_def.push('\n'); | ||
872 | } | ||
873 | } | ||
874 | } | ||
875 | |||
876 | match fun.vars_defined_in_body_and_outlive.as_slice() { | ||
877 | [] => {} | ||
878 | [var] => format_to!(fn_def, "{}{}\n", new_indent + 1, var.name(ctx.db()).unwrap()), | ||
879 | [v0, vs @ ..] => { | ||
880 | format_to!(fn_def, "{}({}", new_indent + 1, v0.name(ctx.db()).unwrap()); | ||
881 | for var in vs { | ||
882 | format_to!(fn_def, ", {}", var.name(ctx.db()).unwrap()); | ||
883 | } | ||
884 | fn_def.push_str(")\n"); | ||
885 | } | ||
886 | } | ||
887 | } | ||
888 | |||
889 | fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String { | ||
890 | ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) | ||
891 | } | ||
892 | |||
893 | /// change all usages to account for added `&`/`&mut` for some params | ||
894 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { | ||
895 | let mut rewriter = SyntaxRewriter::default(); | ||
896 | for param in params { | ||
897 | if !param.kind().is_ref() { | ||
898 | continue; | ||
899 | } | ||
900 | |||
901 | let usages = LocalUsages::find(ctx, param.var); | ||
902 | let usages = usages | ||
903 | .iter() | ||
904 | .filter(|reference| syntax.text_range().contains_range(reference.range)) | ||
905 | .filter_map(|reference| path_element_of_reference(syntax, reference)); | ||
906 | for path in usages { | ||
907 | match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { | ||
908 | Some(ast::Expr::MethodCallExpr(_)) | Some(ast::Expr::FieldExpr(_)) => { | ||
909 | // do nothing | ||
910 | } | ||
911 | Some(ast::Expr::RefExpr(node)) | ||
912 | if param.kind() == ParamKind::MutRef && node.mut_token().is_some() => | ||
913 | { | ||
914 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); | ||
915 | } | ||
916 | Some(ast::Expr::RefExpr(node)) | ||
917 | if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() => | ||
918 | { | ||
919 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); | ||
920 | } | ||
921 | Some(_) | None => { | ||
922 | rewriter.replace_ast(&path, &ast::make::expr_prefix(T![*], path.clone())); | ||
923 | } | ||
924 | }; | ||
925 | } | ||
926 | } | ||
927 | |||
928 | rewriter.rewrite(syntax) | ||
929 | } | ||
930 | |||
931 | #[cfg(test)] | ||
932 | mod tests { | ||
933 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
934 | |||
935 | use super::*; | ||
936 | |||
937 | #[test] | ||
938 | fn no_args_from_binary_expr() { | ||
939 | check_assist( | ||
940 | extract_function, | ||
941 | r#" | ||
942 | fn foo() { | ||
943 | foo($01 + 1$0); | ||
944 | }"#, | ||
945 | r#" | ||
946 | fn foo() { | ||
947 | foo(fun_name()); | ||
948 | } | ||
949 | |||
950 | fn $0fun_name() -> i32 { | ||
951 | 1 + 1 | ||
952 | }"#, | ||
953 | ); | ||
954 | } | ||
955 | |||
956 | #[test] | ||
957 | fn no_args_from_binary_expr_in_module() { | ||
958 | check_assist( | ||
959 | extract_function, | ||
960 | r#" | ||
961 | mod bar { | ||
962 | fn foo() { | ||
963 | foo($01 + 1$0); | ||
964 | } | ||
965 | }"#, | ||
966 | r#" | ||
967 | mod bar { | ||
968 | fn foo() { | ||
969 | foo(fun_name()); | ||
970 | } | ||
971 | |||
972 | fn $0fun_name() -> i32 { | ||
973 | 1 + 1 | ||
974 | } | ||
975 | }"#, | ||
976 | ); | ||
977 | } | ||
978 | |||
979 | #[test] | ||
980 | fn no_args_from_binary_expr_indented() { | ||
981 | check_assist( | ||
982 | extract_function, | ||
983 | r#" | ||
984 | fn foo() { | ||
985 | $0{ 1 + 1 }$0; | ||
986 | }"#, | ||
987 | r#" | ||
988 | fn foo() { | ||
989 | fun_name(); | ||
990 | } | ||
991 | |||
992 | fn $0fun_name() -> i32 { | ||
993 | { 1 + 1 } | ||
994 | }"#, | ||
995 | ); | ||
996 | } | ||
997 | |||
998 | #[test] | ||
999 | fn no_args_from_stmt_with_last_expr() { | ||
1000 | check_assist( | ||
1001 | extract_function, | ||
1002 | r#" | ||
1003 | fn foo() -> i32 { | ||
1004 | let k = 1; | ||
1005 | $0let m = 1; | ||
1006 | m + 1$0 | ||
1007 | }"#, | ||
1008 | r#" | ||
1009 | fn foo() -> i32 { | ||
1010 | let k = 1; | ||
1011 | fun_name() | ||
1012 | } | ||
1013 | |||
1014 | fn $0fun_name() -> i32 { | ||
1015 | let m = 1; | ||
1016 | m + 1 | ||
1017 | }"#, | ||
1018 | ); | ||
1019 | } | ||
1020 | |||
1021 | #[test] | ||
1022 | fn no_args_from_stmt_unit() { | ||
1023 | check_assist( | ||
1024 | extract_function, | ||
1025 | r#" | ||
1026 | fn foo() { | ||
1027 | let k = 3; | ||
1028 | $0let m = 1; | ||
1029 | let n = m + 1;$0 | ||
1030 | let g = 5; | ||
1031 | }"#, | ||
1032 | r#" | ||
1033 | fn foo() { | ||
1034 | let k = 3; | ||
1035 | fun_name(); | ||
1036 | let g = 5; | ||
1037 | } | ||
1038 | |||
1039 | fn $0fun_name() { | ||
1040 | let m = 1; | ||
1041 | let n = m + 1; | ||
1042 | }"#, | ||
1043 | ); | ||
1044 | } | ||
1045 | |||
1046 | #[test] | ||
1047 | fn no_args_if() { | ||
1048 | check_assist( | ||
1049 | extract_function, | ||
1050 | r#" | ||
1051 | fn foo() { | ||
1052 | $0if true { }$0 | ||
1053 | }"#, | ||
1054 | r#" | ||
1055 | fn foo() { | ||
1056 | fun_name(); | ||
1057 | } | ||
1058 | |||
1059 | fn $0fun_name() { | ||
1060 | if true { } | ||
1061 | }"#, | ||
1062 | ); | ||
1063 | } | ||
1064 | |||
1065 | #[test] | ||
1066 | fn no_args_if_else() { | ||
1067 | check_assist( | ||
1068 | extract_function, | ||
1069 | r#" | ||
1070 | fn foo() -> i32 { | ||
1071 | $0if true { 1 } else { 2 }$0 | ||
1072 | }"#, | ||
1073 | r#" | ||
1074 | fn foo() -> i32 { | ||
1075 | fun_name() | ||
1076 | } | ||
1077 | |||
1078 | fn $0fun_name() -> i32 { | ||
1079 | if true { 1 } else { 2 } | ||
1080 | }"#, | ||
1081 | ); | ||
1082 | } | ||
1083 | |||
1084 | #[test] | ||
1085 | fn no_args_if_let_else() { | ||
1086 | check_assist( | ||
1087 | extract_function, | ||
1088 | r#" | ||
1089 | fn foo() -> i32 { | ||
1090 | $0if let true = false { 1 } else { 2 }$0 | ||
1091 | }"#, | ||
1092 | r#" | ||
1093 | fn foo() -> i32 { | ||
1094 | fun_name() | ||
1095 | } | ||
1096 | |||
1097 | fn $0fun_name() -> i32 { | ||
1098 | if let true = false { 1 } else { 2 } | ||
1099 | }"#, | ||
1100 | ); | ||
1101 | } | ||
1102 | |||
1103 | #[test] | ||
1104 | fn no_args_match() { | ||
1105 | check_assist( | ||
1106 | extract_function, | ||
1107 | r#" | ||
1108 | fn foo() -> i32 { | ||
1109 | $0match true { | ||
1110 | true => 1, | ||
1111 | false => 2, | ||
1112 | }$0 | ||
1113 | }"#, | ||
1114 | r#" | ||
1115 | fn foo() -> i32 { | ||
1116 | fun_name() | ||
1117 | } | ||
1118 | |||
1119 | fn $0fun_name() -> i32 { | ||
1120 | match true { | ||
1121 | true => 1, | ||
1122 | false => 2, | ||
1123 | } | ||
1124 | }"#, | ||
1125 | ); | ||
1126 | } | ||
1127 | |||
1128 | #[test] | ||
1129 | fn no_args_while() { | ||
1130 | check_assist( | ||
1131 | extract_function, | ||
1132 | r#" | ||
1133 | fn foo() { | ||
1134 | $0while true { }$0 | ||
1135 | }"#, | ||
1136 | r#" | ||
1137 | fn foo() { | ||
1138 | fun_name(); | ||
1139 | } | ||
1140 | |||
1141 | fn $0fun_name() { | ||
1142 | while true { } | ||
1143 | }"#, | ||
1144 | ); | ||
1145 | } | ||
1146 | |||
1147 | #[test] | ||
1148 | fn no_args_for() { | ||
1149 | check_assist( | ||
1150 | extract_function, | ||
1151 | r#" | ||
1152 | fn foo() { | ||
1153 | $0for v in &[0, 1] { }$0 | ||
1154 | }"#, | ||
1155 | r#" | ||
1156 | fn foo() { | ||
1157 | fun_name(); | ||
1158 | } | ||
1159 | |||
1160 | fn $0fun_name() { | ||
1161 | for v in &[0, 1] { } | ||
1162 | }"#, | ||
1163 | ); | ||
1164 | } | ||
1165 | |||
1166 | #[test] | ||
1167 | fn no_args_from_loop_unit() { | ||
1168 | check_assist( | ||
1169 | extract_function, | ||
1170 | r#" | ||
1171 | fn foo() { | ||
1172 | $0loop { | ||
1173 | let m = 1; | ||
1174 | }$0 | ||
1175 | }"#, | ||
1176 | r#" | ||
1177 | fn foo() { | ||
1178 | fun_name() | ||
1179 | } | ||
1180 | |||
1181 | fn $0fun_name() -> ! { | ||
1182 | loop { | ||
1183 | let m = 1; | ||
1184 | } | ||
1185 | }"#, | ||
1186 | ); | ||
1187 | } | ||
1188 | |||
1189 | #[test] | ||
1190 | fn no_args_from_loop_with_return() { | ||
1191 | check_assist( | ||
1192 | extract_function, | ||
1193 | r#" | ||
1194 | fn foo() { | ||
1195 | let v = $0loop { | ||
1196 | let m = 1; | ||
1197 | break m; | ||
1198 | }$0; | ||
1199 | }"#, | ||
1200 | r#" | ||
1201 | fn foo() { | ||
1202 | let v = fun_name(); | ||
1203 | } | ||
1204 | |||
1205 | fn $0fun_name() -> i32 { | ||
1206 | loop { | ||
1207 | let m = 1; | ||
1208 | break m; | ||
1209 | } | ||
1210 | }"#, | ||
1211 | ); | ||
1212 | } | ||
1213 | |||
1214 | #[test] | ||
1215 | fn no_args_from_match() { | ||
1216 | check_assist( | ||
1217 | extract_function, | ||
1218 | r#" | ||
1219 | fn foo() { | ||
1220 | let v: i32 = $0match Some(1) { | ||
1221 | Some(x) => x, | ||
1222 | None => 0, | ||
1223 | }$0; | ||
1224 | }"#, | ||
1225 | r#" | ||
1226 | fn foo() { | ||
1227 | let v: i32 = fun_name(); | ||
1228 | } | ||
1229 | |||
1230 | fn $0fun_name() -> i32 { | ||
1231 | match Some(1) { | ||
1232 | Some(x) => x, | ||
1233 | None => 0, | ||
1234 | } | ||
1235 | }"#, | ||
1236 | ); | ||
1237 | } | ||
1238 | |||
1239 | #[test] | ||
1240 | fn argument_form_expr() { | ||
1241 | check_assist( | ||
1242 | extract_function, | ||
1243 | r" | ||
1244 | fn foo() -> u32 { | ||
1245 | let n = 2; | ||
1246 | $0n+2$0 | ||
1247 | }", | ||
1248 | r" | ||
1249 | fn foo() -> u32 { | ||
1250 | let n = 2; | ||
1251 | fun_name(n) | ||
1252 | } | ||
1253 | |||
1254 | fn $0fun_name(n: u32) -> u32 { | ||
1255 | n+2 | ||
1256 | }", | ||
1257 | ) | ||
1258 | } | ||
1259 | |||
1260 | #[test] | ||
1261 | fn argument_used_twice_form_expr() { | ||
1262 | check_assist( | ||
1263 | extract_function, | ||
1264 | r" | ||
1265 | fn foo() -> u32 { | ||
1266 | let n = 2; | ||
1267 | $0n+n$0 | ||
1268 | }", | ||
1269 | r" | ||
1270 | fn foo() -> u32 { | ||
1271 | let n = 2; | ||
1272 | fun_name(n) | ||
1273 | } | ||
1274 | |||
1275 | fn $0fun_name(n: u32) -> u32 { | ||
1276 | n+n | ||
1277 | }", | ||
1278 | ) | ||
1279 | } | ||
1280 | |||
1281 | #[test] | ||
1282 | fn two_arguments_form_expr() { | ||
1283 | check_assist( | ||
1284 | extract_function, | ||
1285 | r" | ||
1286 | fn foo() -> u32 { | ||
1287 | let n = 2; | ||
1288 | let m = 3; | ||
1289 | $0n+n*m$0 | ||
1290 | }", | ||
1291 | r" | ||
1292 | fn foo() -> u32 { | ||
1293 | let n = 2; | ||
1294 | let m = 3; | ||
1295 | fun_name(n, m) | ||
1296 | } | ||
1297 | |||
1298 | fn $0fun_name(n: u32, m: u32) -> u32 { | ||
1299 | n+n*m | ||
1300 | }", | ||
1301 | ) | ||
1302 | } | ||
1303 | |||
1304 | #[test] | ||
1305 | fn argument_and_locals() { | ||
1306 | check_assist( | ||
1307 | extract_function, | ||
1308 | r" | ||
1309 | fn foo() -> u32 { | ||
1310 | let n = 2; | ||
1311 | $0let m = 1; | ||
1312 | n + m$0 | ||
1313 | }", | ||
1314 | r" | ||
1315 | fn foo() -> u32 { | ||
1316 | let n = 2; | ||
1317 | fun_name(n) | ||
1318 | } | ||
1319 | |||
1320 | fn $0fun_name(n: u32) -> u32 { | ||
1321 | let m = 1; | ||
1322 | n + m | ||
1323 | }", | ||
1324 | ) | ||
1325 | } | ||
1326 | |||
1327 | #[test] | ||
1328 | fn in_comment_is_not_applicable() { | ||
1329 | mark::check!(extract_function_in_comment_is_not_applicable); | ||
1330 | check_assist_not_applicable(extract_function, r"fn main() { 1 + /* $0comment$0 */ 1; }"); | ||
1331 | } | ||
1332 | |||
1333 | #[test] | ||
1334 | fn part_of_expr_stmt() { | ||
1335 | check_assist( | ||
1336 | extract_function, | ||
1337 | " | ||
1338 | fn foo() { | ||
1339 | $01$0 + 1; | ||
1340 | }", | ||
1341 | " | ||
1342 | fn foo() { | ||
1343 | fun_name() + 1; | ||
1344 | } | ||
1345 | |||
1346 | fn $0fun_name() -> i32 { | ||
1347 | 1 | ||
1348 | }", | ||
1349 | ); | ||
1350 | } | ||
1351 | |||
1352 | #[test] | ||
1353 | fn function_expr() { | ||
1354 | check_assist( | ||
1355 | extract_function, | ||
1356 | r#" | ||
1357 | fn foo() { | ||
1358 | $0bar(1 + 1)$0 | ||
1359 | }"#, | ||
1360 | r#" | ||
1361 | fn foo() { | ||
1362 | fun_name(); | ||
1363 | } | ||
1364 | |||
1365 | fn $0fun_name() { | ||
1366 | bar(1 + 1) | ||
1367 | }"#, | ||
1368 | ) | ||
1369 | } | ||
1370 | |||
1371 | #[test] | ||
1372 | fn extract_from_nested() { | ||
1373 | check_assist( | ||
1374 | extract_function, | ||
1375 | r" | ||
1376 | fn main() { | ||
1377 | let x = true; | ||
1378 | let tuple = match x { | ||
1379 | true => ($02 + 2$0, true) | ||
1380 | _ => (0, false) | ||
1381 | }; | ||
1382 | }", | ||
1383 | r" | ||
1384 | fn main() { | ||
1385 | let x = true; | ||
1386 | let tuple = match x { | ||
1387 | true => (fun_name(), true) | ||
1388 | _ => (0, false) | ||
1389 | }; | ||
1390 | } | ||
1391 | |||
1392 | fn $0fun_name() -> i32 { | ||
1393 | 2 + 2 | ||
1394 | }", | ||
1395 | ); | ||
1396 | } | ||
1397 | |||
1398 | #[test] | ||
1399 | fn param_from_closure() { | ||
1400 | check_assist( | ||
1401 | extract_function, | ||
1402 | r" | ||
1403 | fn main() { | ||
1404 | let lambda = |x: u32| $0x * 2$0; | ||
1405 | }", | ||
1406 | r" | ||
1407 | fn main() { | ||
1408 | let lambda = |x: u32| fun_name(x); | ||
1409 | } | ||
1410 | |||
1411 | fn $0fun_name(x: u32) -> u32 { | ||
1412 | x * 2 | ||
1413 | }", | ||
1414 | ); | ||
1415 | } | ||
1416 | |||
1417 | #[test] | ||
1418 | fn extract_return_stmt() { | ||
1419 | check_assist( | ||
1420 | extract_function, | ||
1421 | r" | ||
1422 | fn foo() -> u32 { | ||
1423 | $0return 2 + 2$0; | ||
1424 | }", | ||
1425 | r" | ||
1426 | fn foo() -> u32 { | ||
1427 | return fun_name(); | ||
1428 | } | ||
1429 | |||
1430 | fn $0fun_name() -> u32 { | ||
1431 | 2 + 2 | ||
1432 | }", | ||
1433 | ); | ||
1434 | } | ||
1435 | |||
1436 | #[test] | ||
1437 | fn does_not_add_extra_whitespace() { | ||
1438 | check_assist( | ||
1439 | extract_function, | ||
1440 | r" | ||
1441 | fn foo() -> u32 { | ||
1442 | |||
1443 | |||
1444 | $0return 2 + 2$0; | ||
1445 | }", | ||
1446 | r" | ||
1447 | fn foo() -> u32 { | ||
1448 | |||
1449 | |||
1450 | return fun_name(); | ||
1451 | } | ||
1452 | |||
1453 | fn $0fun_name() -> u32 { | ||
1454 | 2 + 2 | ||
1455 | }", | ||
1456 | ); | ||
1457 | } | ||
1458 | |||
1459 | #[test] | ||
1460 | fn break_stmt() { | ||
1461 | check_assist( | ||
1462 | extract_function, | ||
1463 | r" | ||
1464 | fn main() { | ||
1465 | let result = loop { | ||
1466 | $0break 2 + 2$0; | ||
1467 | }; | ||
1468 | }", | ||
1469 | r" | ||
1470 | fn main() { | ||
1471 | let result = loop { | ||
1472 | break fun_name(); | ||
1473 | }; | ||
1474 | } | ||
1475 | |||
1476 | fn $0fun_name() -> i32 { | ||
1477 | 2 + 2 | ||
1478 | }", | ||
1479 | ); | ||
1480 | } | ||
1481 | |||
1482 | #[test] | ||
1483 | fn extract_cast() { | ||
1484 | check_assist( | ||
1485 | extract_function, | ||
1486 | r" | ||
1487 | fn main() { | ||
1488 | let v = $00f32 as u32$0; | ||
1489 | }", | ||
1490 | r" | ||
1491 | fn main() { | ||
1492 | let v = fun_name(); | ||
1493 | } | ||
1494 | |||
1495 | fn $0fun_name() -> u32 { | ||
1496 | 0f32 as u32 | ||
1497 | }", | ||
1498 | ); | ||
1499 | } | ||
1500 | |||
1501 | #[test] | ||
1502 | fn return_not_applicable() { | ||
1503 | check_assist_not_applicable(extract_function, r"fn foo() { $0return$0; } "); | ||
1504 | } | ||
1505 | |||
1506 | #[test] | ||
1507 | fn method_to_freestanding() { | ||
1508 | check_assist( | ||
1509 | extract_function, | ||
1510 | r" | ||
1511 | struct S; | ||
1512 | |||
1513 | impl S { | ||
1514 | fn foo(&self) -> i32 { | ||
1515 | $01+1$0 | ||
1516 | } | ||
1517 | }", | ||
1518 | r" | ||
1519 | struct S; | ||
1520 | |||
1521 | impl S { | ||
1522 | fn foo(&self) -> i32 { | ||
1523 | fun_name() | ||
1524 | } | ||
1525 | } | ||
1526 | |||
1527 | fn $0fun_name() -> i32 { | ||
1528 | 1+1 | ||
1529 | }", | ||
1530 | ); | ||
1531 | } | ||
1532 | |||
1533 | #[test] | ||
1534 | fn method_with_reference() { | ||
1535 | check_assist( | ||
1536 | extract_function, | ||
1537 | r" | ||
1538 | struct S { f: i32 }; | ||
1539 | |||
1540 | impl S { | ||
1541 | fn foo(&self) -> i32 { | ||
1542 | $01+self.f$0 | ||
1543 | } | ||
1544 | }", | ||
1545 | r" | ||
1546 | struct S { f: i32 }; | ||
1547 | |||
1548 | impl S { | ||
1549 | fn foo(&self) -> i32 { | ||
1550 | self.fun_name() | ||
1551 | } | ||
1552 | |||
1553 | fn $0fun_name(&self) -> i32 { | ||
1554 | 1+self.f | ||
1555 | } | ||
1556 | }", | ||
1557 | ); | ||
1558 | } | ||
1559 | |||
1560 | #[test] | ||
1561 | fn method_with_mut() { | ||
1562 | check_assist( | ||
1563 | extract_function, | ||
1564 | r" | ||
1565 | struct S { f: i32 }; | ||
1566 | |||
1567 | impl S { | ||
1568 | fn foo(&mut self) { | ||
1569 | $0self.f += 1;$0 | ||
1570 | } | ||
1571 | }", | ||
1572 | r" | ||
1573 | struct S { f: i32 }; | ||
1574 | |||
1575 | impl S { | ||
1576 | fn foo(&mut self) { | ||
1577 | self.fun_name(); | ||
1578 | } | ||
1579 | |||
1580 | fn $0fun_name(&mut self) { | ||
1581 | self.f += 1; | ||
1582 | } | ||
1583 | }", | ||
1584 | ); | ||
1585 | } | ||
1586 | |||
1587 | #[test] | ||
1588 | fn variable_defined_inside_and_used_after_no_ret() { | ||
1589 | check_assist( | ||
1590 | extract_function, | ||
1591 | r" | ||
1592 | fn foo() { | ||
1593 | let n = 1; | ||
1594 | $0let k = n * n;$0 | ||
1595 | let m = k + 1; | ||
1596 | }", | ||
1597 | r" | ||
1598 | fn foo() { | ||
1599 | let n = 1; | ||
1600 | let k = fun_name(n); | ||
1601 | let m = k + 1; | ||
1602 | } | ||
1603 | |||
1604 | fn $0fun_name(n: i32) -> i32 { | ||
1605 | let k = n * n; | ||
1606 | k | ||
1607 | }", | ||
1608 | ); | ||
1609 | } | ||
1610 | |||
1611 | #[test] | ||
1612 | fn two_variables_defined_inside_and_used_after_no_ret() { | ||
1613 | check_assist( | ||
1614 | extract_function, | ||
1615 | r" | ||
1616 | fn foo() { | ||
1617 | let n = 1; | ||
1618 | $0let k = n * n; | ||
1619 | let m = k + 2;$0 | ||
1620 | let h = k + m; | ||
1621 | }", | ||
1622 | r" | ||
1623 | fn foo() { | ||
1624 | let n = 1; | ||
1625 | let (k, m) = fun_name(n); | ||
1626 | let h = k + m; | ||
1627 | } | ||
1628 | |||
1629 | fn $0fun_name(n: i32) -> (i32, i32) { | ||
1630 | let k = n * n; | ||
1631 | let m = k + 2; | ||
1632 | (k, m) | ||
1633 | }", | ||
1634 | ); | ||
1635 | } | ||
1636 | |||
1637 | #[test] | ||
1638 | fn nontrivial_patterns_define_variables() { | ||
1639 | check_assist( | ||
1640 | extract_function, | ||
1641 | r" | ||
1642 | struct Counter(i32); | ||
1643 | fn foo() { | ||
1644 | $0let Counter(n) = Counter(0);$0 | ||
1645 | let m = n; | ||
1646 | }", | ||
1647 | r" | ||
1648 | struct Counter(i32); | ||
1649 | fn foo() { | ||
1650 | let n = fun_name(); | ||
1651 | let m = n; | ||
1652 | } | ||
1653 | |||
1654 | fn $0fun_name() -> i32 { | ||
1655 | let Counter(n) = Counter(0); | ||
1656 | n | ||
1657 | }", | ||
1658 | ); | ||
1659 | } | ||
1660 | |||
1661 | #[test] | ||
1662 | fn struct_with_two_fields_pattern_define_variables() { | ||
1663 | check_assist( | ||
1664 | extract_function, | ||
1665 | r" | ||
1666 | struct Counter { n: i32, m: i32 }; | ||
1667 | fn foo() { | ||
1668 | $0let Counter { n, m: k } = Counter { n: 1, m: 2 };$0 | ||
1669 | let h = n + k; | ||
1670 | }", | ||
1671 | r" | ||
1672 | struct Counter { n: i32, m: i32 }; | ||
1673 | fn foo() { | ||
1674 | let (n, k) = fun_name(); | ||
1675 | let h = n + k; | ||
1676 | } | ||
1677 | |||
1678 | fn $0fun_name() -> (i32, i32) { | ||
1679 | let Counter { n, m: k } = Counter { n: 1, m: 2 }; | ||
1680 | (n, k) | ||
1681 | }", | ||
1682 | ); | ||
1683 | } | ||
1684 | |||
1685 | #[test] | ||
1686 | fn mut_var_from_outer_scope() { | ||
1687 | check_assist( | ||
1688 | extract_function, | ||
1689 | r" | ||
1690 | fn foo() { | ||
1691 | let mut n = 1; | ||
1692 | $0n += 1;$0 | ||
1693 | let m = n + 1; | ||
1694 | }", | ||
1695 | r" | ||
1696 | fn foo() { | ||
1697 | let mut n = 1; | ||
1698 | fun_name(&mut n); | ||
1699 | let m = n + 1; | ||
1700 | } | ||
1701 | |||
1702 | fn $0fun_name(n: &mut i32) { | ||
1703 | *n += 1; | ||
1704 | }", | ||
1705 | ); | ||
1706 | } | ||
1707 | |||
1708 | #[test] | ||
1709 | fn mut_field_from_outer_scope() { | ||
1710 | check_assist( | ||
1711 | extract_function, | ||
1712 | r" | ||
1713 | struct C { n: i32 } | ||
1714 | fn foo() { | ||
1715 | let mut c = C { n: 0 }; | ||
1716 | $0c.n += 1;$0 | ||
1717 | let m = c.n + 1; | ||
1718 | }", | ||
1719 | r" | ||
1720 | struct C { n: i32 } | ||
1721 | fn foo() { | ||
1722 | let mut c = C { n: 0 }; | ||
1723 | fun_name(&mut c); | ||
1724 | let m = c.n + 1; | ||
1725 | } | ||
1726 | |||
1727 | fn $0fun_name(c: &mut C) { | ||
1728 | c.n += 1; | ||
1729 | }", | ||
1730 | ); | ||
1731 | } | ||
1732 | |||
1733 | #[test] | ||
1734 | fn mut_nested_field_from_outer_scope() { | ||
1735 | check_assist( | ||
1736 | extract_function, | ||
1737 | r" | ||
1738 | struct P { n: i32} | ||
1739 | struct C { p: P } | ||
1740 | fn foo() { | ||
1741 | let mut c = C { p: P { n: 0 } }; | ||
1742 | let mut v = C { p: P { n: 0 } }; | ||
1743 | let u = C { p: P { n: 0 } }; | ||
1744 | $0c.p.n += u.p.n; | ||
1745 | let r = &mut v.p.n;$0 | ||
1746 | let m = c.p.n + v.p.n + u.p.n; | ||
1747 | }", | ||
1748 | r" | ||
1749 | struct P { n: i32} | ||
1750 | struct C { p: P } | ||
1751 | fn foo() { | ||
1752 | let mut c = C { p: P { n: 0 } }; | ||
1753 | let mut v = C { p: P { n: 0 } }; | ||
1754 | let u = C { p: P { n: 0 } }; | ||
1755 | fun_name(&mut c, &u, &mut v); | ||
1756 | let m = c.p.n + v.p.n + u.p.n; | ||
1757 | } | ||
1758 | |||
1759 | fn $0fun_name(c: &mut C, u: &C, v: &mut C) { | ||
1760 | c.p.n += u.p.n; | ||
1761 | let r = &mut v.p.n; | ||
1762 | }", | ||
1763 | ); | ||
1764 | } | ||
1765 | |||
1766 | #[test] | ||
1767 | fn mut_param_many_usages_stmt() { | ||
1768 | check_assist( | ||
1769 | extract_function, | ||
1770 | r" | ||
1771 | fn bar(k: i32) {} | ||
1772 | trait I: Copy { | ||
1773 | fn succ(&self) -> Self; | ||
1774 | fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v } | ||
1775 | } | ||
1776 | impl I for i32 { | ||
1777 | fn succ(&self) -> Self { *self + 1 } | ||
1778 | } | ||
1779 | fn foo() { | ||
1780 | let mut n = 1; | ||
1781 | $0n += n; | ||
1782 | bar(n); | ||
1783 | bar(n+1); | ||
1784 | bar(n*n); | ||
1785 | bar(&n); | ||
1786 | n.inc(); | ||
1787 | let v = &mut n; | ||
1788 | *v = v.succ(); | ||
1789 | n.succ();$0 | ||
1790 | let m = n + 1; | ||
1791 | }", | ||
1792 | r" | ||
1793 | fn bar(k: i32) {} | ||
1794 | trait I: Copy { | ||
1795 | fn succ(&self) -> Self; | ||
1796 | fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v } | ||
1797 | } | ||
1798 | impl I for i32 { | ||
1799 | fn succ(&self) -> Self { *self + 1 } | ||
1800 | } | ||
1801 | fn foo() { | ||
1802 | let mut n = 1; | ||
1803 | fun_name(&mut n); | ||
1804 | let m = n + 1; | ||
1805 | } | ||
1806 | |||
1807 | fn $0fun_name(n: &mut i32) { | ||
1808 | *n += *n; | ||
1809 | bar(*n); | ||
1810 | bar(*n+1); | ||
1811 | bar(*n**n); | ||
1812 | bar(&*n); | ||
1813 | n.inc(); | ||
1814 | let v = n; | ||
1815 | *v = v.succ(); | ||
1816 | n.succ(); | ||
1817 | }", | ||
1818 | ); | ||
1819 | } | ||
1820 | |||
1821 | #[test] | ||
1822 | fn mut_param_many_usages_expr() { | ||
1823 | check_assist( | ||
1824 | extract_function, | ||
1825 | r" | ||
1826 | fn bar(k: i32) {} | ||
1827 | trait I: Copy { | ||
1828 | fn succ(&self) -> Self; | ||
1829 | fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v } | ||
1830 | } | ||
1831 | impl I for i32 { | ||
1832 | fn succ(&self) -> Self { *self + 1 } | ||
1833 | } | ||
1834 | fn foo() { | ||
1835 | let mut n = 1; | ||
1836 | $0{ | ||
1837 | n += n; | ||
1838 | bar(n); | ||
1839 | bar(n+1); | ||
1840 | bar(n*n); | ||
1841 | bar(&n); | ||
1842 | n.inc(); | ||
1843 | let v = &mut n; | ||
1844 | *v = v.succ(); | ||
1845 | n.succ(); | ||
1846 | }$0 | ||
1847 | let m = n + 1; | ||
1848 | }", | ||
1849 | r" | ||
1850 | fn bar(k: i32) {} | ||
1851 | trait I: Copy { | ||
1852 | fn succ(&self) -> Self; | ||
1853 | fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v } | ||
1854 | } | ||
1855 | impl I for i32 { | ||
1856 | fn succ(&self) -> Self { *self + 1 } | ||
1857 | } | ||
1858 | fn foo() { | ||
1859 | let mut n = 1; | ||
1860 | fun_name(&mut n); | ||
1861 | let m = n + 1; | ||
1862 | } | ||
1863 | |||
1864 | fn $0fun_name(n: &mut i32) { | ||
1865 | { | ||
1866 | *n += *n; | ||
1867 | bar(*n); | ||
1868 | bar(*n+1); | ||
1869 | bar(*n**n); | ||
1870 | bar(&*n); | ||
1871 | n.inc(); | ||
1872 | let v = n; | ||
1873 | *v = v.succ(); | ||
1874 | n.succ(); | ||
1875 | } | ||
1876 | }", | ||
1877 | ); | ||
1878 | } | ||
1879 | |||
1880 | #[test] | ||
1881 | fn mut_param_by_value() { | ||
1882 | check_assist( | ||
1883 | extract_function, | ||
1884 | r" | ||
1885 | fn foo() { | ||
1886 | let mut n = 1; | ||
1887 | $0n += 1;$0 | ||
1888 | }", | ||
1889 | r" | ||
1890 | fn foo() { | ||
1891 | let mut n = 1; | ||
1892 | fun_name(n); | ||
1893 | } | ||
1894 | |||
1895 | fn $0fun_name(mut n: i32) { | ||
1896 | n += 1; | ||
1897 | }", | ||
1898 | ); | ||
1899 | } | ||
1900 | |||
1901 | #[test] | ||
1902 | fn mut_param_because_of_mut_ref() { | ||
1903 | check_assist( | ||
1904 | extract_function, | ||
1905 | r" | ||
1906 | fn foo() { | ||
1907 | let mut n = 1; | ||
1908 | $0let v = &mut n; | ||
1909 | *v += 1;$0 | ||
1910 | let k = n; | ||
1911 | }", | ||
1912 | r" | ||
1913 | fn foo() { | ||
1914 | let mut n = 1; | ||
1915 | fun_name(&mut n); | ||
1916 | let k = n; | ||
1917 | } | ||
1918 | |||
1919 | fn $0fun_name(n: &mut i32) { | ||
1920 | let v = n; | ||
1921 | *v += 1; | ||
1922 | }", | ||
1923 | ); | ||
1924 | } | ||
1925 | |||
1926 | #[test] | ||
1927 | fn mut_param_by_value_because_of_mut_ref() { | ||
1928 | check_assist( | ||
1929 | extract_function, | ||
1930 | r" | ||
1931 | fn foo() { | ||
1932 | let mut n = 1; | ||
1933 | $0let v = &mut n; | ||
1934 | *v += 1;$0 | ||
1935 | }", | ||
1936 | r" | ||
1937 | fn foo() { | ||
1938 | let mut n = 1; | ||
1939 | fun_name(n); | ||
1940 | } | ||
1941 | |||
1942 | fn $0fun_name(mut n: i32) { | ||
1943 | let v = &mut n; | ||
1944 | *v += 1; | ||
1945 | }", | ||
1946 | ); | ||
1947 | } | ||
1948 | |||
1949 | #[test] | ||
1950 | fn mut_method_call() { | ||
1951 | check_assist( | ||
1952 | extract_function, | ||
1953 | r" | ||
1954 | trait I { | ||
1955 | fn inc(&mut self); | ||
1956 | } | ||
1957 | impl I for i32 { | ||
1958 | fn inc(&mut self) { *self += 1 } | ||
1959 | } | ||
1960 | fn foo() { | ||
1961 | let mut n = 1; | ||
1962 | $0n.inc();$0 | ||
1963 | }", | ||
1964 | r" | ||
1965 | trait I { | ||
1966 | fn inc(&mut self); | ||
1967 | } | ||
1968 | impl I for i32 { | ||
1969 | fn inc(&mut self) { *self += 1 } | ||
1970 | } | ||
1971 | fn foo() { | ||
1972 | let mut n = 1; | ||
1973 | fun_name(n); | ||
1974 | } | ||
1975 | |||
1976 | fn $0fun_name(mut n: i32) { | ||
1977 | n.inc(); | ||
1978 | }", | ||
1979 | ); | ||
1980 | } | ||
1981 | |||
1982 | #[test] | ||
1983 | fn shared_method_call() { | ||
1984 | check_assist( | ||
1985 | extract_function, | ||
1986 | r" | ||
1987 | trait I { | ||
1988 | fn succ(&self); | ||
1989 | } | ||
1990 | impl I for i32 { | ||
1991 | fn succ(&self) { *self + 1 } | ||
1992 | } | ||
1993 | fn foo() { | ||
1994 | let mut n = 1; | ||
1995 | $0n.succ();$0 | ||
1996 | }", | ||
1997 | r" | ||
1998 | trait I { | ||
1999 | fn succ(&self); | ||
2000 | } | ||
2001 | impl I for i32 { | ||
2002 | fn succ(&self) { *self + 1 } | ||
2003 | } | ||
2004 | fn foo() { | ||
2005 | let mut n = 1; | ||
2006 | fun_name(n); | ||
2007 | } | ||
2008 | |||
2009 | fn $0fun_name(n: i32) { | ||
2010 | n.succ(); | ||
2011 | }", | ||
2012 | ); | ||
2013 | } | ||
2014 | |||
2015 | #[test] | ||
2016 | fn mut_method_call_with_other_receiver() { | ||
2017 | check_assist( | ||
2018 | extract_function, | ||
2019 | r" | ||
2020 | trait I { | ||
2021 | fn inc(&mut self, n: i32); | ||
2022 | } | ||
2023 | impl I for i32 { | ||
2024 | fn inc(&mut self, n: i32) { *self += n } | ||
2025 | } | ||
2026 | fn foo() { | ||
2027 | let mut n = 1; | ||
2028 | $0let mut m = 2; | ||
2029 | m.inc(n);$0 | ||
2030 | }", | ||
2031 | r" | ||
2032 | trait I { | ||
2033 | fn inc(&mut self, n: i32); | ||
2034 | } | ||
2035 | impl I for i32 { | ||
2036 | fn inc(&mut self, n: i32) { *self += n } | ||
2037 | } | ||
2038 | fn foo() { | ||
2039 | let mut n = 1; | ||
2040 | fun_name(n); | ||
2041 | } | ||
2042 | |||
2043 | fn $0fun_name(n: i32) { | ||
2044 | let mut m = 2; | ||
2045 | m.inc(n); | ||
2046 | }", | ||
2047 | ); | ||
2048 | } | ||
2049 | |||
2050 | #[test] | ||
2051 | fn non_copy_without_usages_after() { | ||
2052 | check_assist( | ||
2053 | extract_function, | ||
2054 | r" | ||
2055 | struct Counter(i32); | ||
2056 | fn foo() { | ||
2057 | let c = Counter(0); | ||
2058 | $0let n = c.0;$0 | ||
2059 | }", | ||
2060 | r" | ||
2061 | struct Counter(i32); | ||
2062 | fn foo() { | ||
2063 | let c = Counter(0); | ||
2064 | fun_name(c); | ||
2065 | } | ||
2066 | |||
2067 | fn $0fun_name(c: Counter) { | ||
2068 | let n = c.0; | ||
2069 | }", | ||
2070 | ); | ||
2071 | } | ||
2072 | |||
2073 | #[test] | ||
2074 | fn non_copy_used_after() { | ||
2075 | check_assist( | ||
2076 | extract_function, | ||
2077 | r" | ||
2078 | struct Counter(i32); | ||
2079 | fn foo() { | ||
2080 | let c = Counter(0); | ||
2081 | $0let n = c.0;$0 | ||
2082 | let m = c.0; | ||
2083 | }", | ||
2084 | r" | ||
2085 | struct Counter(i32); | ||
2086 | fn foo() { | ||
2087 | let c = Counter(0); | ||
2088 | fun_name(&c); | ||
2089 | let m = c.0; | ||
2090 | } | ||
2091 | |||
2092 | fn $0fun_name(c: &Counter) { | ||
2093 | let n = c.0; | ||
2094 | }", | ||
2095 | ); | ||
2096 | } | ||
2097 | |||
2098 | #[test] | ||
2099 | fn indented_stmts() { | ||
2100 | check_assist( | ||
2101 | extract_function, | ||
2102 | r" | ||
2103 | fn foo() { | ||
2104 | if true { | ||
2105 | loop { | ||
2106 | $0let n = 1; | ||
2107 | let m = 2;$0 | ||
2108 | } | ||
2109 | } | ||
2110 | }", | ||
2111 | r" | ||
2112 | fn foo() { | ||
2113 | if true { | ||
2114 | loop { | ||
2115 | fun_name(); | ||
2116 | } | ||
2117 | } | ||
2118 | } | ||
2119 | |||
2120 | fn $0fun_name() { | ||
2121 | let n = 1; | ||
2122 | let m = 2; | ||
2123 | }", | ||
2124 | ); | ||
2125 | } | ||
2126 | |||
2127 | #[test] | ||
2128 | fn indented_stmts_inside_mod() { | ||
2129 | check_assist( | ||
2130 | extract_function, | ||
2131 | r" | ||
2132 | mod bar { | ||
2133 | fn foo() { | ||
2134 | if true { | ||
2135 | loop { | ||
2136 | $0let n = 1; | ||
2137 | let m = 2;$0 | ||
2138 | } | ||
2139 | } | ||
2140 | } | ||
2141 | }", | ||
2142 | r" | ||
2143 | mod bar { | ||
2144 | fn foo() { | ||
2145 | if true { | ||
2146 | loop { | ||
2147 | fun_name(); | ||
2148 | } | ||
2149 | } | ||
2150 | } | ||
2151 | |||
2152 | fn $0fun_name() { | ||
2153 | let n = 1; | ||
2154 | let m = 2; | ||
2155 | } | ||
2156 | }", | ||
2157 | ); | ||
2158 | } | ||
2159 | } | ||
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index e3ef04932..5c7678b53 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -151,8 +151,8 @@ fn insert_import( | |||
151 | ctx.config.insert_use.prefix_kind, | 151 | ctx.config.insert_use.prefix_kind, |
152 | ); | 152 | ); |
153 | if let Some(mut mod_path) = mod_path { | 153 | if let Some(mut mod_path) = mod_path { |
154 | mod_path.segments.pop(); | 154 | mod_path.pop_segment(); |
155 | mod_path.segments.push(variant_hir_name.clone()); | 155 | mod_path.push_segment(variant_hir_name.clone()); |
156 | let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?; | 156 | let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?; |
157 | *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); | 157 | *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); |
158 | } | 158 | } |
diff --git a/crates/assists/src/handlers/generate_enum_match_method.rs b/crates/assists/src/handlers/generate_enum_match_method.rs new file mode 100644 index 000000000..ee89d4208 --- /dev/null +++ b/crates/assists/src/handlers/generate_enum_match_method.rs | |||
@@ -0,0 +1,221 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::ast::{self, AstNode, NameOwner}; | ||
3 | use syntax::{ast::VisibilityOwner, T}; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; | ||
7 | |||
8 | // Assist: generate_enum_match_method | ||
9 | // | ||
10 | // Generate an `is_` method for an enum variant. | ||
11 | // | ||
12 | // ``` | ||
13 | // enum Version { | ||
14 | // Undefined, | ||
15 | // Minor$0, | ||
16 | // Major, | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // enum Version { | ||
22 | // Undefined, | ||
23 | // Minor, | ||
24 | // Major, | ||
25 | // } | ||
26 | // | ||
27 | // impl Version { | ||
28 | // /// Returns `true` if the version is [`Minor`]. | ||
29 | // fn is_minor(&self) -> bool { | ||
30 | // matches!(self, Self::Minor) | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
35 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | ||
36 | let variant_name = variant.name()?; | ||
37 | let parent_enum = variant.parent_enum(); | ||
38 | if !matches!(variant.kind(), ast::StructKind::Unit) { | ||
39 | mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented); | ||
40 | return None; | ||
41 | } | ||
42 | |||
43 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | ||
44 | let fn_name = to_lower_snake_case(&variant_name.to_string()); | ||
45 | |||
46 | // Return early if we've found an existing new fn | ||
47 | let impl_def = find_struct_impl( | ||
48 | &ctx, | ||
49 | &ast::AdtDef::Enum(parent_enum.clone()), | ||
50 | format!("is_{}", fn_name).as_str(), | ||
51 | )?; | ||
52 | |||
53 | let target = variant.syntax().text_range(); | ||
54 | acc.add( | ||
55 | AssistId("generate_enum_match_method", AssistKind::Generate), | ||
56 | "Generate an `is_` method for an enum variant", | ||
57 | target, | ||
58 | |builder| { | ||
59 | let mut buf = String::with_capacity(512); | ||
60 | |||
61 | if impl_def.is_some() { | ||
62 | buf.push('\n'); | ||
63 | } | ||
64 | |||
65 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
66 | |||
67 | format_to!( | ||
68 | buf, | ||
69 | " /// Returns `true` if the {} is [`{}`]. | ||
70 | {}fn is_{}(&self) -> bool {{ | ||
71 | matches!(self, Self::{}) | ||
72 | }}", | ||
73 | enum_lowercase_name, | ||
74 | variant_name, | ||
75 | vis, | ||
76 | fn_name, | ||
77 | variant_name | ||
78 | ); | ||
79 | |||
80 | let start_offset = impl_def | ||
81 | .and_then(|impl_def| { | ||
82 | buf.push('\n'); | ||
83 | let start = impl_def | ||
84 | .syntax() | ||
85 | .descendants_with_tokens() | ||
86 | .find(|t| t.kind() == T!['{'])? | ||
87 | .text_range() | ||
88 | .end(); | ||
89 | |||
90 | Some(start) | ||
91 | }) | ||
92 | .unwrap_or_else(|| { | ||
93 | buf = generate_impl_text(&parent_enum, &buf); | ||
94 | parent_enum.syntax().text_range().end() | ||
95 | }); | ||
96 | |||
97 | builder.insert(start_offset, buf); | ||
98 | }, | ||
99 | ) | ||
100 | } | ||
101 | |||
102 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
103 | // parameters | ||
104 | fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { | ||
105 | let mut buf = String::with_capacity(code.len()); | ||
106 | buf.push_str("\n\nimpl "); | ||
107 | buf.push_str(strukt.name().unwrap().text()); | ||
108 | format_to!(buf, " {{\n{}\n}}", code); | ||
109 | buf | ||
110 | } | ||
111 | |||
112 | #[cfg(test)] | ||
113 | mod tests { | ||
114 | use test_utils::mark; | ||
115 | |||
116 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
117 | |||
118 | use super::*; | ||
119 | |||
120 | fn check_not_applicable(ra_fixture: &str) { | ||
121 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_generate_enum_match_from_variant() { | ||
126 | check_assist( | ||
127 | generate_enum_match_method, | ||
128 | r#" | ||
129 | enum Variant { | ||
130 | Undefined, | ||
131 | Minor$0, | ||
132 | Major, | ||
133 | }"#, | ||
134 | r#"enum Variant { | ||
135 | Undefined, | ||
136 | Minor, | ||
137 | Major, | ||
138 | } | ||
139 | |||
140 | impl Variant { | ||
141 | /// Returns `true` if the variant is [`Minor`]. | ||
142 | fn is_minor(&self) -> bool { | ||
143 | matches!(self, Self::Minor) | ||
144 | } | ||
145 | }"#, | ||
146 | ); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn test_generate_enum_match_already_implemented() { | ||
151 | check_not_applicable( | ||
152 | r#" | ||
153 | enum Variant { | ||
154 | Undefined, | ||
155 | Minor$0, | ||
156 | Major, | ||
157 | } | ||
158 | |||
159 | impl Variant { | ||
160 | fn is_minor(&self) -> bool { | ||
161 | matches!(self, Self::Minor) | ||
162 | } | ||
163 | }"#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_add_from_impl_no_element() { | ||
169 | mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented); | ||
170 | check_not_applicable( | ||
171 | r#" | ||
172 | enum Variant { | ||
173 | Undefined, | ||
174 | Minor(u32)$0, | ||
175 | Major, | ||
176 | }"#, | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn test_generate_enum_match_from_variant_with_one_variant() { | ||
182 | check_assist( | ||
183 | generate_enum_match_method, | ||
184 | r#"enum Variant { Undefi$0ned }"#, | ||
185 | r#" | ||
186 | enum Variant { Undefined } | ||
187 | |||
188 | impl Variant { | ||
189 | /// Returns `true` if the variant is [`Undefined`]. | ||
190 | fn is_undefined(&self) -> bool { | ||
191 | matches!(self, Self::Undefined) | ||
192 | } | ||
193 | }"#, | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn test_generate_enum_match_from_variant_with_visibility_marker() { | ||
199 | check_assist( | ||
200 | generate_enum_match_method, | ||
201 | r#" | ||
202 | pub(crate) enum Variant { | ||
203 | Undefined, | ||
204 | Minor$0, | ||
205 | Major, | ||
206 | }"#, | ||
207 | r#"pub(crate) enum Variant { | ||
208 | Undefined, | ||
209 | Minor, | ||
210 | Major, | ||
211 | } | ||
212 | |||
213 | impl Variant { | ||
214 | /// Returns `true` if the variant is [`Minor`]. | ||
215 | pub(crate) fn is_minor(&self) -> bool { | ||
216 | matches!(self, Self::Minor) | ||
217 | } | ||
218 | }"#, | ||
219 | ); | ||
220 | } | ||
221 | } | ||
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs index b7390855a..84832273f 100644 --- a/crates/assists/src/handlers/generate_new.rs +++ b/crates/assists/src/handlers/generate_new.rs | |||
@@ -1,4 +1,3 @@ | |||
1 | use hir::Adt; | ||
2 | use itertools::Itertools; | 1 | use itertools::Itertools; |
3 | use stdx::format_to; | 2 | use stdx::format_to; |
4 | use syntax::{ | 3 | use syntax::{ |
@@ -6,7 +5,7 @@ use syntax::{ | |||
6 | SmolStr, T, | 5 | SmolStr, T, |
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; |
10 | 9 | ||
11 | // Assist: generate_new | 10 | // Assist: generate_new |
12 | // | 11 | // |
@@ -38,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
38 | }; | 37 | }; |
39 | 38 | ||
40 | // Return early if we've found an existing new fn | 39 | // Return early if we've found an existing new fn |
41 | let impl_def = find_struct_impl(&ctx, &strukt)?; | 40 | let impl_def = find_struct_impl(&ctx, &ast::AdtDef::Struct(strukt.clone()), "new")?; |
42 | 41 | ||
43 | let target = strukt.syntax().text_range(); | 42 | let target = strukt.syntax().text_range(); |
44 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { | 43 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { |
@@ -111,65 +110,6 @@ fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String { | |||
111 | buf | 110 | buf |
112 | } | 111 | } |
113 | 112 | ||
114 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
115 | // exist within the module/file | ||
116 | // | ||
117 | // Returns `None` if we've found an existing `new` fn | ||
118 | // | ||
119 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
120 | // viable (e.g. we process proc macros, etc) | ||
121 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> { | ||
122 | let db = ctx.db(); | ||
123 | let module = strukt.syntax().ancestors().find(|node| { | ||
124 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
125 | })?; | ||
126 | |||
127 | let struct_def = ctx.sema.to_def(strukt)?; | ||
128 | |||
129 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
130 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
131 | |||
132 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
133 | // (we currently use the wrong type parameter) | ||
134 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
135 | let same_ty = match blk.target_ty(db).as_adt() { | ||
136 | Some(def) => def == Adt::Struct(struct_def), | ||
137 | None => false, | ||
138 | }; | ||
139 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
140 | |||
141 | if !(same_ty && not_trait_impl) { | ||
142 | None | ||
143 | } else { | ||
144 | Some(impl_blk) | ||
145 | } | ||
146 | }); | ||
147 | |||
148 | if let Some(ref impl_blk) = block { | ||
149 | if has_new_fn(impl_blk) { | ||
150 | return None; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | Some(block) | ||
155 | } | ||
156 | |||
157 | fn has_new_fn(imp: &ast::Impl) -> bool { | ||
158 | if let Some(il) = imp.assoc_item_list() { | ||
159 | for item in il.assoc_items() { | ||
160 | if let ast::AssocItem::Fn(f) = item { | ||
161 | if let Some(name) = f.name() { | ||
162 | if name.text().eq_ignore_ascii_case("new") { | ||
163 | return true; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | false | ||
171 | } | ||
172 | |||
173 | #[cfg(test)] | 113 | #[cfg(test)] |
174 | mod tests { | 114 | mod tests { |
175 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 115 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 559b9651e..83fbf6986 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -117,6 +117,7 @@ mod handlers { | |||
117 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
118 | mod early_return; | 118 | mod early_return; |
119 | mod expand_glob_import; | 119 | mod expand_glob_import; |
120 | mod extract_function; | ||
120 | mod extract_struct_from_enum_variant; | 121 | mod extract_struct_from_enum_variant; |
121 | mod extract_variable; | 122 | mod extract_variable; |
122 | mod fill_match_arms; | 123 | mod fill_match_arms; |
@@ -126,6 +127,7 @@ mod handlers { | |||
126 | mod flip_trait_bound; | 127 | mod flip_trait_bound; |
127 | mod generate_default_from_enum_variant; | 128 | mod generate_default_from_enum_variant; |
128 | mod generate_derive; | 129 | mod generate_derive; |
130 | mod generate_enum_match_method; | ||
129 | mod generate_from_impl_for_enum; | 131 | mod generate_from_impl_for_enum; |
130 | mod generate_function; | 132 | mod generate_function; |
131 | mod generate_impl; | 133 | mod generate_impl; |
@@ -174,6 +176,7 @@ mod handlers { | |||
174 | early_return::convert_to_guarded_return, | 176 | early_return::convert_to_guarded_return, |
175 | expand_glob_import::expand_glob_import, | 177 | expand_glob_import::expand_glob_import, |
176 | move_module_to_file::move_module_to_file, | 178 | move_module_to_file::move_module_to_file, |
179 | extract_function::extract_function, | ||
177 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 180 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
178 | extract_variable::extract_variable, | 181 | extract_variable::extract_variable, |
179 | fill_match_arms::fill_match_arms, | 182 | fill_match_arms::fill_match_arms, |
@@ -183,6 +186,7 @@ mod handlers { | |||
183 | flip_trait_bound::flip_trait_bound, | 186 | flip_trait_bound::flip_trait_bound, |
184 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 187 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
185 | generate_derive::generate_derive, | 188 | generate_derive::generate_derive, |
189 | generate_enum_match_method::generate_enum_match_method, | ||
186 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 190 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
187 | generate_function::generate_function, | 191 | generate_function::generate_function, |
188 | generate_impl::generate_impl, | 192 | generate_impl::generate_impl, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 9aa807f10..0dbb05f2a 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -257,6 +257,33 @@ fn qux(bar: Bar, baz: Baz) {} | |||
257 | } | 257 | } |
258 | 258 | ||
259 | #[test] | 259 | #[test] |
260 | fn doctest_extract_function() { | ||
261 | check_doc_test( | ||
262 | "extract_function", | ||
263 | r#####" | ||
264 | fn main() { | ||
265 | let n = 1; | ||
266 | $0let m = n + 2; | ||
267 | let k = m + n;$0 | ||
268 | let g = 3; | ||
269 | } | ||
270 | "#####, | ||
271 | r#####" | ||
272 | fn main() { | ||
273 | let n = 1; | ||
274 | fun_name(n); | ||
275 | let g = 3; | ||
276 | } | ||
277 | |||
278 | fn $0fun_name(n: i32) { | ||
279 | let m = n + 2; | ||
280 | let k = m + n; | ||
281 | } | ||
282 | "#####, | ||
283 | ) | ||
284 | } | ||
285 | |||
286 | #[test] | ||
260 | fn doctest_extract_struct_from_enum_variant() { | 287 | fn doctest_extract_struct_from_enum_variant() { |
261 | check_doc_test( | 288 | check_doc_test( |
262 | "extract_struct_from_enum_variant", | 289 | "extract_struct_from_enum_variant", |
@@ -433,6 +460,34 @@ struct Point { | |||
433 | } | 460 | } |
434 | 461 | ||
435 | #[test] | 462 | #[test] |
463 | fn doctest_generate_enum_match_method() { | ||
464 | check_doc_test( | ||
465 | "generate_enum_match_method", | ||
466 | r#####" | ||
467 | enum Version { | ||
468 | Undefined, | ||
469 | Minor$0, | ||
470 | Major, | ||
471 | } | ||
472 | "#####, | ||
473 | r#####" | ||
474 | enum Version { | ||
475 | Undefined, | ||
476 | Minor, | ||
477 | Major, | ||
478 | } | ||
479 | |||
480 | impl Version { | ||
481 | /// Returns `true` if the version is [`Minor`]. | ||
482 | fn is_minor(&self) -> bool { | ||
483 | matches!(self, Self::Minor) | ||
484 | } | ||
485 | } | ||
486 | "#####, | ||
487 | ) | ||
488 | } | ||
489 | |||
490 | #[test] | ||
436 | fn doctest_generate_from_impl_for_enum() { | 491 | fn doctest_generate_from_impl_for_enum() { |
437 | check_doc_test( | 492 | check_doc_test( |
438 | "generate_from_impl_for_enum", | 493 | "generate_from_impl_for_enum", |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 4e762e18b..3842558d8 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use hir::HasSource; | 5 | use hir::{Adt, HasSource}; |
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 6 | use ide_db::{helpers::SnippetCap, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
@@ -15,7 +15,10 @@ use syntax::{ | |||
15 | SyntaxNode, TextSize, T, | 15 | SyntaxNode, TextSize, T, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}; | 18 | use crate::{ |
19 | assist_context::AssistContext, | ||
20 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
21 | }; | ||
19 | 22 | ||
20 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | 23 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { |
21 | extract_trivial_expression(&block) | 24 | extract_trivial_expression(&block) |
@@ -267,3 +270,71 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool { | |||
267 | 270 | ||
268 | pat_head == var_head | 271 | pat_head == var_head |
269 | } | 272 | } |
273 | |||
274 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
275 | // exist within the module/file | ||
276 | // | ||
277 | // Returns `None` if we've found an existing `new` fn | ||
278 | // | ||
279 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
280 | // viable (e.g. we process proc macros, etc) | ||
281 | pub(crate) fn find_struct_impl( | ||
282 | ctx: &AssistContext, | ||
283 | strukt: &ast::AdtDef, | ||
284 | name: &str, | ||
285 | ) -> Option<Option<ast::Impl>> { | ||
286 | let db = ctx.db(); | ||
287 | let module = strukt.syntax().ancestors().find(|node| { | ||
288 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
289 | })?; | ||
290 | |||
291 | let struct_def = match strukt { | ||
292 | ast::AdtDef::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?), | ||
293 | ast::AdtDef::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?), | ||
294 | ast::AdtDef::Union(u) => Adt::Union(ctx.sema.to_def(u)?), | ||
295 | }; | ||
296 | |||
297 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
298 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
299 | |||
300 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
301 | // (we currently use the wrong type parameter) | ||
302 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
303 | |||
304 | let same_ty = match blk.target_ty(db).as_adt() { | ||
305 | Some(def) => def == struct_def, | ||
306 | None => false, | ||
307 | }; | ||
308 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
309 | |||
310 | if !(same_ty && not_trait_impl) { | ||
311 | None | ||
312 | } else { | ||
313 | Some(impl_blk) | ||
314 | } | ||
315 | }); | ||
316 | |||
317 | if let Some(ref impl_blk) = block { | ||
318 | if has_fn(impl_blk, name) { | ||
319 | return None; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | Some(block) | ||
324 | } | ||
325 | |||
326 | fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool { | ||
327 | if let Some(il) = imp.assoc_item_list() { | ||
328 | for item in il.assoc_items() { | ||
329 | if let ast::AssocItem::Fn(f) = item { | ||
330 | if let Some(name) = f.name() { | ||
331 | if name.text().eq_ignore_ascii_case(rhs_name) { | ||
332 | return true; | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | false | ||
340 | } | ||