diff options
53 files changed, 3332 insertions, 557 deletions
diff --git a/Cargo.lock b/Cargo.lock index fc39269cb..1ceae965f 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -17,9 +17,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" | |||
17 | 17 | ||
18 | [[package]] | 18 | [[package]] |
19 | name = "always-assert" | 19 | name = "always-assert" |
20 | version = "0.1.1" | 20 | version = "0.1.2" |
21 | source = "registry+https://github.com/rust-lang/crates.io-index" | 21 | source = "registry+https://github.com/rust-lang/crates.io-index" |
22 | checksum = "727786f78c5bc0cda8011831616589f72084cb16b7df4213a997b78749b55a60" | 22 | checksum = "fbf688625d06217d5b1bb0ea9d9c44a1635fd0ee3534466388d18203174f4d11" |
23 | dependencies = [ | 23 | dependencies = [ |
24 | "log", | 24 | "log", |
25 | ] | 25 | ] |
@@ -1661,9 +1661,9 @@ dependencies = [ | |||
1661 | 1661 | ||
1662 | [[package]] | 1662 | [[package]] |
1663 | name = "thread_local" | 1663 | name = "thread_local" |
1664 | version = "1.1.2" | 1664 | version = "1.1.3" |
1665 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1666 | checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" | 1666 | checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" |
1667 | dependencies = [ | 1667 | dependencies = [ |
1668 | "once_cell", | 1668 | "once_cell", |
1669 | ] | 1669 | ] |
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 | } | ||
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs index 9c6a5a40c..c9f928483 100644 --- a/crates/completion/src/completions/flyimport.rs +++ b/crates/completion/src/completions/flyimport.rs | |||
@@ -175,7 +175,7 @@ fn compute_fuzzy_completion_order_key( | |||
175 | user_input_lowercased: &str, | 175 | user_input_lowercased: &str, |
176 | ) -> usize { | 176 | ) -> usize { |
177 | mark::hit!(certain_fuzzy_order_test); | 177 | mark::hit!(certain_fuzzy_order_test); |
178 | let proposed_import_name = match proposed_mod_path.segments.last() { | 178 | let proposed_import_name = match proposed_mod_path.segments().last() { |
179 | Some(name) => name.to_string().to_lowercase(), | 179 | Some(name) => name.to_string().to_lowercase(), |
180 | None => return usize::MAX, | 180 | None => return usize::MAX, |
181 | }; | 181 | }; |
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 5d62fab97..e2482f959 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs | |||
@@ -63,7 +63,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T | |||
63 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { | 63 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { |
64 | // Variants with trivial paths are already added by the existing completion logic, | 64 | // Variants with trivial paths are already added by the existing completion logic, |
65 | // so we should avoid adding these twice | 65 | // so we should avoid adding these twice |
66 | if path.segments.len() > 1 { | 66 | if path.segments().len() > 1 { |
67 | acc.add_qualified_enum_variant(ctx, variant, path); | 67 | acc.add_qualified_enum_variant(ctx, variant, path); |
68 | } | 68 | } |
69 | } | 69 | } |
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 8ec4ac65e..884711f11 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs | |||
@@ -332,9 +332,9 @@ impl Builder { | |||
332 | label = format!("{} ({})", label, import_to_add.import_path); | 332 | label = format!("{} ({})", label, import_to_add.import_path); |
333 | } else { | 333 | } else { |
334 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); | 334 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); |
335 | let _ = import_path_without_last_segment.segments.pop(); | 335 | let _ = import_path_without_last_segment.pop_segment(); |
336 | 336 | ||
337 | if !import_path_without_last_segment.segments.is_empty() { | 337 | if !import_path_without_last_segment.segments().is_empty() { |
338 | lookup = lookup.or_else(|| Some(label.clone())); | 338 | lookup = lookup.or_else(|| Some(label.clone())); |
339 | insert_text = insert_text.or_else(|| Some(label.clone())); | 339 | insert_text = insert_text.or_else(|| Some(label.clone())); |
340 | label = format!("{}::{}", import_path_without_last_segment, label); | 340 | label = format!("{}::{}", import_path_without_last_segment, label); |
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index e11b881ca..eddaaa6f3 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs | |||
@@ -57,7 +57,7 @@ pub(crate) fn render_resolution_with_import<'a>( | |||
57 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), | 57 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), |
58 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), | 58 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), |
59 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), | 59 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), |
60 | _ => import_edit.import_path.segments.last()?.to_string(), | 60 | _ => import_edit.import_path.segments().last()?.to_string(), |
61 | }; | 61 | }; |
62 | Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { | 62 | Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { |
63 | item.completion_kind = CompletionKind::Magic; | 63 | item.completion_kind = CompletionKind::Magic; |
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index adcddebd1..9214193b4 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs | |||
@@ -45,8 +45,8 @@ impl<'a> EnumRender<'a> { | |||
45 | let (qualified_name, short_qualified_name) = match &path { | 45 | let (qualified_name, short_qualified_name) = match &path { |
46 | Some(path) => { | 46 | Some(path) => { |
47 | let full = path.to_string(); | 47 | let full = path.to_string(); |
48 | let short = | 48 | let segments = path.segments(); |
49 | path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); | 49 | let short = segments[segments.len().saturating_sub(2)..].iter().join("::"); |
50 | (full, short) | 50 | (full, short) |
51 | } | 51 | } |
52 | None => (name.to_string(), name.to_string()), | 52 | None => (name.to_string(), name.to_string()), |
diff --git a/crates/hir_def/src/adt.rs b/crates/hir_def/src/adt.rs index 06f0b9b18..ed36c3109 100644 --- a/crates/hir_def/src/adt.rs +++ b/crates/hir_def/src/adt.rs | |||
@@ -351,7 +351,7 @@ fn lower_field( | |||
351 | ) -> FieldData { | 351 | ) -> FieldData { |
352 | FieldData { | 352 | FieldData { |
353 | name: field.name.clone(), | 353 | name: field.name.clone(), |
354 | type_ref: field.type_ref.clone(), | 354 | type_ref: item_tree[field.type_ref].clone(), |
355 | visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(), | 355 | visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(), |
356 | } | 356 | } |
357 | } | 357 | } |
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs index b9ecf22fa..9a432f7d1 100644 --- a/crates/hir_def/src/body.rs +++ b/crates/hir_def/src/body.rs | |||
@@ -33,7 +33,7 @@ use crate::{ | |||
33 | nameres::DefMap, | 33 | nameres::DefMap, |
34 | path::{ModPath, Path}, | 34 | path::{ModPath, Path}, |
35 | src::HasSource, | 35 | src::HasSource, |
36 | AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, | 36 | AsMacroCall, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleId, |
37 | }; | 37 | }; |
38 | 38 | ||
39 | /// A subset of Expander that only deals with cfg attributes. We only need it to | 39 | /// A subset of Expander that only deals with cfg attributes. We only need it to |
@@ -46,10 +46,10 @@ pub(crate) struct CfgExpander { | |||
46 | 46 | ||
47 | pub(crate) struct Expander { | 47 | pub(crate) struct Expander { |
48 | cfg_expander: CfgExpander, | 48 | cfg_expander: CfgExpander, |
49 | crate_def_map: Arc<DefMap>, | 49 | def_map: Arc<DefMap>, |
50 | current_file_id: HirFileId, | 50 | current_file_id: HirFileId, |
51 | ast_id_map: Arc<AstIdMap>, | 51 | ast_id_map: Arc<AstIdMap>, |
52 | module: ModuleId, | 52 | module: LocalModuleId, |
53 | recursion_limit: usize, | 53 | recursion_limit: usize, |
54 | } | 54 | } |
55 | 55 | ||
@@ -91,10 +91,10 @@ impl Expander { | |||
91 | let ast_id_map = db.ast_id_map(current_file_id); | 91 | let ast_id_map = db.ast_id_map(current_file_id); |
92 | Expander { | 92 | Expander { |
93 | cfg_expander, | 93 | cfg_expander, |
94 | crate_def_map, | 94 | def_map: crate_def_map, |
95 | current_file_id, | 95 | current_file_id, |
96 | ast_id_map, | 96 | ast_id_map, |
97 | module, | 97 | module: module.local_id, |
98 | recursion_limit: 0, | 98 | recursion_limit: 0, |
99 | } | 99 | } |
100 | } | 100 | } |
@@ -102,7 +102,6 @@ impl Expander { | |||
102 | pub(crate) fn enter_expand<T: ast::AstNode>( | 102 | pub(crate) fn enter_expand<T: ast::AstNode>( |
103 | &mut self, | 103 | &mut self, |
104 | db: &dyn DefDatabase, | 104 | db: &dyn DefDatabase, |
105 | local_scope: Option<&ItemScope>, | ||
106 | macro_call: ast::MacroCall, | 105 | macro_call: ast::MacroCall, |
107 | ) -> ExpandResult<Option<(Mark, T)>> { | 106 | ) -> ExpandResult<Option<(Mark, T)>> { |
108 | if self.recursion_limit + 1 > EXPANSION_RECURSION_LIMIT { | 107 | if self.recursion_limit + 1 > EXPANSION_RECURSION_LIMIT { |
@@ -112,18 +111,12 @@ impl Expander { | |||
112 | 111 | ||
113 | let macro_call = InFile::new(self.current_file_id, ¯o_call); | 112 | let macro_call = InFile::new(self.current_file_id, ¯o_call); |
114 | 113 | ||
115 | let resolver = |path: ModPath| -> Option<MacroDefId> { | 114 | let resolver = |
116 | if let Some(local_scope) = local_scope { | 115 | |path: ModPath| -> Option<MacroDefId> { self.resolve_path_as_macro(db, &path) }; |
117 | if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) { | ||
118 | return Some(def); | ||
119 | } | ||
120 | } | ||
121 | self.resolve_path_as_macro(db, &path) | ||
122 | }; | ||
123 | 116 | ||
124 | let mut err = None; | 117 | let mut err = None; |
125 | let call_id = | 118 | let call_id = |
126 | macro_call.as_call_id_with_errors(db, self.crate_def_map.krate(), resolver, &mut |e| { | 119 | macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| { |
127 | err.get_or_insert(e); | 120 | err.get_or_insert(e); |
128 | }); | 121 | }); |
129 | let call_id = match call_id { | 122 | let call_id = match call_id { |
@@ -204,10 +197,7 @@ impl Expander { | |||
204 | } | 197 | } |
205 | 198 | ||
206 | fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroDefId> { | 199 | fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroDefId> { |
207 | self.crate_def_map | 200 | self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros() |
208 | .resolve_path(db, self.module.local_id, path, BuiltinShadowMode::Other) | ||
209 | .0 | ||
210 | .take_macros() | ||
211 | } | 201 | } |
212 | 202 | ||
213 | fn ast_id<N: AstNode>(&self, item: &N) -> AstId<N> { | 203 | fn ast_id<N: AstNode>(&self, item: &N) -> AstId<N> { |
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs index 209965fca..28b11cdde 100644 --- a/crates/hir_def/src/body/lower.rs +++ b/crates/hir_def/src/body/lower.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr` | 1 | //! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr` |
2 | //! representation. | 2 | //! representation. |
3 | 3 | ||
4 | use std::{any::type_name, sync::Arc}; | 4 | use std::{any::type_name, mem, sync::Arc}; |
5 | 5 | ||
6 | use either::Either; | 6 | use either::Either; |
7 | use hir_expand::{ | 7 | use hir_expand::{ |
@@ -36,8 +36,8 @@ use crate::{ | |||
36 | item_tree::{ItemTree, ItemTreeId, ItemTreeNode}, | 36 | item_tree::{ItemTree, ItemTreeId, ItemTreeNode}, |
37 | path::{GenericArgs, Path}, | 37 | path::{GenericArgs, Path}, |
38 | type_ref::{Mutability, Rawness, TypeRef}, | 38 | type_ref::{Mutability, Rawness, TypeRef}, |
39 | AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId, | 39 | AdtId, BlockLoc, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, |
40 | StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, | 40 | ModuleDefId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, |
41 | }; | 41 | }; |
42 | 42 | ||
43 | use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; | 43 | use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; |
@@ -152,8 +152,8 @@ impl ExprCollector<'_> { | |||
152 | fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { | 152 | fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { |
153 | self.make_expr(expr, Err(SyntheticSyntax)) | 153 | self.make_expr(expr, Err(SyntheticSyntax)) |
154 | } | 154 | } |
155 | fn empty_block(&mut self) -> ExprId { | 155 | fn unit(&mut self) -> ExprId { |
156 | self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None }) | 156 | self.alloc_expr_desugared(Expr::Tuple { exprs: Vec::new() }) |
157 | } | 157 | } |
158 | fn missing_expr(&mut self) -> ExprId { | 158 | fn missing_expr(&mut self) -> ExprId { |
159 | self.alloc_expr_desugared(Expr::Missing) | 159 | self.alloc_expr_desugared(Expr::Missing) |
@@ -222,7 +222,7 @@ impl ExprCollector<'_> { | |||
222 | MatchArm { pat, expr: then_branch, guard: None }, | 222 | MatchArm { pat, expr: then_branch, guard: None }, |
223 | MatchArm { | 223 | MatchArm { |
224 | pat: placeholder_pat, | 224 | pat: placeholder_pat, |
225 | expr: else_branch.unwrap_or_else(|| self.empty_block()), | 225 | expr: else_branch.unwrap_or_else(|| self.unit()), |
226 | guard: None, | 226 | guard: None, |
227 | }, | 227 | }, |
228 | ]; | 228 | ]; |
@@ -561,7 +561,7 @@ impl ExprCollector<'_> { | |||
561 | let outer_file = self.expander.current_file_id; | 561 | let outer_file = self.expander.current_file_id; |
562 | 562 | ||
563 | let macro_call = self.expander.to_source(AstPtr::new(&e)); | 563 | let macro_call = self.expander.to_source(AstPtr::new(&e)); |
564 | let res = self.expander.enter_expand(self.db, Some(&self.body.item_scope), e); | 564 | let res = self.expander.enter_expand(self.db, e); |
565 | 565 | ||
566 | match &res.err { | 566 | match &res.err { |
567 | Some(ExpandError::UnresolvedProcMacro) => { | 567 | Some(ExpandError::UnresolvedProcMacro) => { |
@@ -697,12 +697,30 @@ impl ExprCollector<'_> { | |||
697 | } | 697 | } |
698 | 698 | ||
699 | fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { | 699 | fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { |
700 | let syntax_node_ptr = AstPtr::new(&block.clone().into()); | 700 | let ast_id = self.expander.ast_id(&block); |
701 | let block_loc = | ||
702 | BlockLoc { ast_id, module: self.expander.def_map.module_id(self.expander.module) }; | ||
703 | let block_id = self.db.intern_block(block_loc); | ||
704 | let opt_def_map = self.db.block_def_map(block_id); | ||
705 | let has_def_map = opt_def_map.is_some(); | ||
706 | let def_map = opt_def_map.unwrap_or_else(|| self.expander.def_map.clone()); | ||
707 | let module = if has_def_map { def_map.root() } else { self.expander.module }; | ||
708 | let prev_def_map = mem::replace(&mut self.expander.def_map, def_map); | ||
709 | let prev_local_module = mem::replace(&mut self.expander.module, module); | ||
710 | |||
701 | self.collect_stmts_items(block.statements()); | 711 | self.collect_stmts_items(block.statements()); |
702 | let statements = | 712 | let statements = |
703 | block.statements().filter_map(|s| self.collect_stmt(s)).flatten().collect(); | 713 | block.statements().filter_map(|s| self.collect_stmt(s)).flatten().collect(); |
704 | let tail = block.tail_expr().map(|e| self.collect_expr(e)); | 714 | let tail = block.tail_expr().map(|e| self.collect_expr(e)); |
705 | self.alloc_expr(Expr::Block { statements, tail, label: None }, syntax_node_ptr) | 715 | let syntax_node_ptr = AstPtr::new(&block.clone().into()); |
716 | let expr_id = self.alloc_expr( | ||
717 | Expr::Block { id: block_id, statements, tail, label: None }, | ||
718 | syntax_node_ptr, | ||
719 | ); | ||
720 | |||
721 | self.expander.def_map = prev_def_map; | ||
722 | self.expander.module = prev_local_module; | ||
723 | expr_id | ||
706 | } | 724 | } |
707 | 725 | ||
708 | fn collect_stmts_items(&mut self, stmts: ast::AstChildren<ast::Stmt>) { | 726 | fn collect_stmts_items(&mut self, stmts: ast::AstChildren<ast::Stmt>) { |
@@ -794,7 +812,7 @@ impl ExprCollector<'_> { | |||
794 | } | 812 | } |
795 | Either::Right(e) => { | 813 | Either::Right(e) => { |
796 | let mac = MacroDefId { | 814 | let mac = MacroDefId { |
797 | krate: self.expander.module.krate, | 815 | krate: self.expander.def_map.krate(), |
798 | ast_id: Some(self.expander.ast_id(&e)), | 816 | ast_id: Some(self.expander.ast_id(&e)), |
799 | kind: MacroDefKind::Declarative, | 817 | kind: MacroDefKind::Declarative, |
800 | local_inner: false, | 818 | local_inner: false, |
@@ -832,9 +850,9 @@ impl ExprCollector<'_> { | |||
832 | if annotation == BindingAnnotation::Unannotated && subpat.is_none() { | 850 | if annotation == BindingAnnotation::Unannotated && subpat.is_none() { |
833 | // This could also be a single-segment path pattern. To | 851 | // This could also be a single-segment path pattern. To |
834 | // decide that, we need to try resolving the name. | 852 | // decide that, we need to try resolving the name. |
835 | let (resolved, _) = self.expander.crate_def_map.resolve_path( | 853 | let (resolved, _) = self.expander.def_map.resolve_path( |
836 | self.db, | 854 | self.db, |
837 | self.expander.module.local_id, | 855 | self.expander.module, |
838 | &name.clone().into(), | 856 | &name.clone().into(), |
839 | BuiltinShadowMode::Other, | 857 | BuiltinShadowMode::Other, |
840 | ); | 858 | ); |
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs index 2e5d0a01e..a92134ba7 100644 --- a/crates/hir_def/src/body/tests.rs +++ b/crates/hir_def/src/body/tests.rs | |||
@@ -1,7 +1,10 @@ | |||
1 | use base_db::{fixture::WithFixture, SourceDatabase}; | 1 | mod block; |
2 | |||
3 | use base_db::{fixture::WithFixture, FilePosition, SourceDatabase}; | ||
4 | use expect_test::Expect; | ||
2 | use test_utils::mark; | 5 | use test_utils::mark; |
3 | 6 | ||
4 | use crate::{test_db::TestDB, ModuleDefId}; | 7 | use crate::{test_db::TestDB, BlockId, ModuleDefId}; |
5 | 8 | ||
6 | use super::*; | 9 | use super::*; |
7 | 10 | ||
@@ -31,6 +34,114 @@ fn check_diagnostics(ra_fixture: &str) { | |||
31 | db.check_diagnostics(); | 34 | db.check_diagnostics(); |
32 | } | 35 | } |
33 | 36 | ||
37 | fn block_def_map_at(ra_fixture: &str) -> String { | ||
38 | let (db, position) = crate::test_db::TestDB::with_position(ra_fixture); | ||
39 | |||
40 | let krate = db.crate_graph().iter().next().unwrap(); | ||
41 | let def_map = db.crate_def_map(krate); | ||
42 | |||
43 | let mut block = | ||
44 | block_at_pos(&db, &def_map, position).expect("couldn't find enclosing function or block"); | ||
45 | loop { | ||
46 | let def_map = db.block_def_map(block).unwrap_or_else(|| def_map.clone()); | ||
47 | let new_block = block_at_pos(&db, &def_map, position); | ||
48 | match new_block { | ||
49 | Some(new_block) => { | ||
50 | assert_ne!(block, new_block); | ||
51 | block = new_block; | ||
52 | } | ||
53 | None => { | ||
54 | return def_map.dump(&db); | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | fn block_at_pos(db: &dyn DefDatabase, def_map: &DefMap, position: FilePosition) -> Option<BlockId> { | ||
61 | // Find the smallest (innermost) function containing the cursor. | ||
62 | let mut size = None; | ||
63 | let mut fn_def = None; | ||
64 | for (_, module) in def_map.modules() { | ||
65 | let file_id = module.definition_source(db).file_id; | ||
66 | if file_id != position.file_id.into() { | ||
67 | continue; | ||
68 | } | ||
69 | let root = db.parse_or_expand(file_id).unwrap(); | ||
70 | let ast_map = db.ast_id_map(file_id); | ||
71 | let item_tree = db.item_tree(file_id); | ||
72 | for decl in module.scope.declarations() { | ||
73 | if let ModuleDefId::FunctionId(it) = decl { | ||
74 | let ast = ast_map.get(item_tree[it.lookup(db).id.value].ast_id).to_node(&root); | ||
75 | let range = ast.syntax().text_range(); | ||
76 | |||
77 | if !range.contains(position.offset) { | ||
78 | continue; | ||
79 | } | ||
80 | |||
81 | let new_size = match size { | ||
82 | None => range.len(), | ||
83 | Some(size) => { | ||
84 | if range.len() < size { | ||
85 | range.len() | ||
86 | } else { | ||
87 | size | ||
88 | } | ||
89 | } | ||
90 | }; | ||
91 | if size != Some(new_size) { | ||
92 | size = Some(new_size); | ||
93 | fn_def = Some(it); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | let (body, source_map) = db.body_with_source_map(fn_def?.into()); | ||
100 | |||
101 | // Now find the smallest encompassing block expression in the function body. | ||
102 | let mut size = None; | ||
103 | let mut block_id = None; | ||
104 | for (expr_id, expr) in body.exprs.iter() { | ||
105 | if let Expr::Block { id, .. } = expr { | ||
106 | if let Ok(ast) = source_map.expr_syntax(expr_id) { | ||
107 | if ast.file_id != position.file_id.into() { | ||
108 | continue; | ||
109 | } | ||
110 | |||
111 | let root = db.parse_or_expand(ast.file_id).unwrap(); | ||
112 | let ast = ast.value.to_node(&root); | ||
113 | let range = ast.syntax().text_range(); | ||
114 | |||
115 | if !range.contains(position.offset) { | ||
116 | continue; | ||
117 | } | ||
118 | |||
119 | let new_size = match size { | ||
120 | None => range.len(), | ||
121 | Some(size) => { | ||
122 | if range.len() < size { | ||
123 | range.len() | ||
124 | } else { | ||
125 | size | ||
126 | } | ||
127 | } | ||
128 | }; | ||
129 | if size != Some(new_size) { | ||
130 | size = Some(new_size); | ||
131 | block_id = Some(*id); | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | |||
137 | Some(block_id.expect("can't find block containing cursor")) | ||
138 | } | ||
139 | |||
140 | fn check_at(ra_fixture: &str, expect: Expect) { | ||
141 | let actual = block_def_map_at(ra_fixture); | ||
142 | expect.assert_eq(&actual); | ||
143 | } | ||
144 | |||
34 | #[test] | 145 | #[test] |
35 | fn your_stack_belongs_to_me() { | 146 | fn your_stack_belongs_to_me() { |
36 | mark::check!(your_stack_belongs_to_me); | 147 | mark::check!(your_stack_belongs_to_me); |
diff --git a/crates/hir_def/src/nameres/tests/block.rs b/crates/hir_def/src/body/tests/block.rs index 6cc659513..062560a70 100644 --- a/crates/hir_def/src/nameres/tests/block.rs +++ b/crates/hir_def/src/body/tests/block.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | use expect_test::expect; | ||
2 | 3 | ||
3 | #[test] | 4 | #[test] |
4 | fn inner_item_smoke() { | 5 | fn inner_item_smoke() { |
@@ -13,6 +14,7 @@ fn outer() { | |||
13 | expect![[r#" | 14 | expect![[r#" |
14 | block scope | 15 | block scope |
15 | inner: v | 16 | inner: v |
17 | |||
16 | crate | 18 | crate |
17 | inner: t | 19 | inner: t |
18 | outer: v | 20 | outer: v |
@@ -37,6 +39,7 @@ fn outer() { | |||
37 | CrateStruct: t v | 39 | CrateStruct: t v |
38 | SelfStruct: t v | 40 | SelfStruct: t v |
39 | Struct: t v | 41 | Struct: t v |
42 | |||
40 | crate | 43 | crate |
41 | Struct: t v | 44 | Struct: t v |
42 | outer: v | 45 | outer: v |
@@ -61,6 +64,7 @@ fn outer() { | |||
61 | block scope | 64 | block scope |
62 | imported: t v | 65 | imported: t v |
63 | name: v | 66 | name: v |
67 | |||
64 | crate | 68 | crate |
65 | name: t | 69 | name: t |
66 | outer: v | 70 | outer: v |
@@ -87,9 +91,11 @@ fn outer() { | |||
87 | inner1: t | 91 | inner1: t |
88 | inner2: v | 92 | inner2: v |
89 | outer: v | 93 | outer: v |
94 | |||
90 | block scope | 95 | block scope |
91 | inner: v | 96 | inner: v |
92 | inner1: t | 97 | inner1: t |
98 | |||
93 | crate | 99 | crate |
94 | outer: v | 100 | outer: v |
95 | "#]], | 101 | "#]], |
@@ -112,6 +118,7 @@ struct Struct {} | |||
112 | expect![[r#" | 118 | expect![[r#" |
113 | block scope | 119 | block scope |
114 | Struct: t | 120 | Struct: t |
121 | |||
115 | crate | 122 | crate |
116 | Struct: t | 123 | Struct: t |
117 | module: t | 124 | module: t |
@@ -142,6 +149,7 @@ fn f() { | |||
142 | expect![[r#" | 149 | expect![[r#" |
143 | block scope | 150 | block scope |
144 | Hit: t | 151 | Hit: t |
152 | |||
145 | crate | 153 | crate |
146 | f: v | 154 | f: v |
147 | "#]], | 155 | "#]], |
@@ -176,11 +184,47 @@ pub mod mark { | |||
176 | expect![[r#" | 184 | expect![[r#" |
177 | block scope | 185 | block scope |
178 | Hit: t | 186 | Hit: t |
187 | |||
179 | block scope | 188 | block scope |
180 | nested: v | 189 | nested: v |
190 | |||
181 | crate | 191 | crate |
182 | f: v | 192 | f: v |
183 | mark: t | 193 | mark: t |
184 | "#]], | 194 | "#]], |
185 | ); | 195 | ); |
186 | } | 196 | } |
197 | |||
198 | #[test] | ||
199 | fn macro_resolve_legacy() { | ||
200 | check_at( | ||
201 | r#" | ||
202 | //- /lib.rs | ||
203 | mod module; | ||
204 | |||
205 | //- /module.rs | ||
206 | macro_rules! m { | ||
207 | () => { | ||
208 | struct Def {} | ||
209 | }; | ||
210 | } | ||
211 | |||
212 | fn f() { | ||
213 | { | ||
214 | m!(); | ||
215 | $0 | ||
216 | } | ||
217 | } | ||
218 | "#, | ||
219 | expect![[r#" | ||
220 | block scope | ||
221 | Def: t | ||
222 | |||
223 | crate | ||
224 | module: t | ||
225 | |||
226 | crate::module | ||
227 | f: v | ||
228 | "#]], | ||
229 | ) | ||
230 | } | ||
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs index e7b7724f7..42fcca386 100644 --- a/crates/hir_def/src/data.rs +++ b/crates/hir_def/src/data.rs | |||
@@ -41,8 +41,8 @@ impl FunctionData { | |||
41 | 41 | ||
42 | Arc::new(FunctionData { | 42 | Arc::new(FunctionData { |
43 | name: func.name.clone(), | 43 | name: func.name.clone(), |
44 | params: func.params.to_vec(), | 44 | params: func.params.iter().map(|id| item_tree[*id].clone()).collect(), |
45 | ret_type: func.ret_type.clone(), | 45 | ret_type: item_tree[func.ret_type].clone(), |
46 | attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()).clone(), | 46 | attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()).clone(), |
47 | has_self_param: func.has_self_param, | 47 | has_self_param: func.has_self_param, |
48 | has_body: func.has_body, | 48 | has_body: func.has_body, |
@@ -75,7 +75,7 @@ impl TypeAliasData { | |||
75 | 75 | ||
76 | Arc::new(TypeAliasData { | 76 | Arc::new(TypeAliasData { |
77 | name: typ.name.clone(), | 77 | name: typ.name.clone(), |
78 | type_ref: typ.type_ref.clone(), | 78 | type_ref: typ.type_ref.map(|id| item_tree[id].clone()), |
79 | visibility: item_tree[typ.visibility].clone(), | 79 | visibility: item_tree[typ.visibility].clone(), |
80 | is_extern: typ.is_extern, | 80 | is_extern: typ.is_extern, |
81 | bounds: typ.bounds.to_vec(), | 81 | bounds: typ.bounds.to_vec(), |
@@ -144,8 +144,8 @@ impl ImplData { | |||
144 | 144 | ||
145 | let item_tree = db.item_tree(impl_loc.id.file_id); | 145 | let item_tree = db.item_tree(impl_loc.id.file_id); |
146 | let impl_def = &item_tree[impl_loc.id.value]; | 146 | let impl_def = &item_tree[impl_loc.id.value]; |
147 | let target_trait = impl_def.target_trait.clone(); | 147 | let target_trait = impl_def.target_trait.map(|id| item_tree[id].clone()); |
148 | let target_type = impl_def.target_type.clone(); | 148 | let target_type = item_tree[impl_def.target_type].clone(); |
149 | let is_negative = impl_def.is_negative; | 149 | let is_negative = impl_def.is_negative; |
150 | let module_id = impl_loc.container.module(db); | 150 | let module_id = impl_loc.container.module(db); |
151 | let container = AssocContainerId::ImplId(id); | 151 | let container = AssocContainerId::ImplId(id); |
@@ -182,7 +182,7 @@ impl ConstData { | |||
182 | 182 | ||
183 | Arc::new(ConstData { | 183 | Arc::new(ConstData { |
184 | name: konst.name.clone(), | 184 | name: konst.name.clone(), |
185 | type_ref: konst.type_ref.clone(), | 185 | type_ref: item_tree[konst.type_ref].clone(), |
186 | visibility: item_tree[konst.visibility].clone(), | 186 | visibility: item_tree[konst.visibility].clone(), |
187 | }) | 187 | }) |
188 | } | 188 | } |
@@ -205,7 +205,7 @@ impl StaticData { | |||
205 | 205 | ||
206 | Arc::new(StaticData { | 206 | Arc::new(StaticData { |
207 | name: Some(statik.name.clone()), | 207 | name: Some(statik.name.clone()), |
208 | type_ref: statik.type_ref.clone(), | 208 | type_ref: item_tree[statik.type_ref].clone(), |
209 | visibility: item_tree[statik.visibility].clone(), | 209 | visibility: item_tree[statik.visibility].clone(), |
210 | mutable: statik.mutable, | 210 | mutable: statik.mutable, |
211 | is_extern: statik.is_extern, | 211 | is_extern: statik.is_extern, |
@@ -262,7 +262,7 @@ fn collect_items( | |||
262 | let root = db.parse_or_expand(file_id).unwrap(); | 262 | let root = db.parse_or_expand(file_id).unwrap(); |
263 | let call = ast_id_map.get(call.ast_id).to_node(&root); | 263 | let call = ast_id_map.get(call.ast_id).to_node(&root); |
264 | 264 | ||
265 | if let Some((mark, mac)) = expander.enter_expand(db, None, call).value { | 265 | if let Some((mark, mac)) = expander.enter_expand(db, call).value { |
266 | let src: InFile<ast::MacroItems> = expander.to_source(mac); | 266 | let src: InFile<ast::MacroItems> = expander.to_source(mac); |
267 | let item_tree = db.item_tree(src.file_id); | 267 | let item_tree = db.item_tree(src.file_id); |
268 | let iter = | 268 | let iter = |
diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs index aef7e1f6c..6c01f1ed0 100644 --- a/crates/hir_def/src/db.rs +++ b/crates/hir_def/src/db.rs | |||
@@ -58,8 +58,23 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { | |||
58 | #[salsa::invoke(DefMap::crate_def_map_query)] | 58 | #[salsa::invoke(DefMap::crate_def_map_query)] |
59 | fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>; | 59 | fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>; |
60 | 60 | ||
61 | /// Computes the block-level `DefMap`, returning `None` when `block` doesn't contain any inner | ||
62 | /// items directly. | ||
63 | /// | ||
64 | /// For example: | ||
65 | /// | ||
66 | /// ``` | ||
67 | /// fn f() { // (0) | ||
68 | /// { // (1) | ||
69 | /// fn inner() {} | ||
70 | /// } | ||
71 | /// } | ||
72 | /// ``` | ||
73 | /// | ||
74 | /// The `block_def_map` for block 0 would return `None`, while `block_def_map` of block 1 would | ||
75 | /// return a `DefMap` containing `inner`. | ||
61 | #[salsa::invoke(DefMap::block_def_map_query)] | 76 | #[salsa::invoke(DefMap::block_def_map_query)] |
62 | fn block_def_map(&self, block: BlockId) -> Arc<DefMap>; | 77 | fn block_def_map(&self, block: BlockId) -> Option<Arc<DefMap>>; |
63 | 78 | ||
64 | #[salsa::invoke(StructData::struct_data_query)] | 79 | #[salsa::invoke(StructData::struct_data_query)] |
65 | fn struct_data(&self, id: StructId) -> Arc<StructData>; | 80 | fn struct_data(&self, id: StructId) -> Arc<StructData>; |
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs index 5be838f4a..4d72eaeaf 100644 --- a/crates/hir_def/src/expr.rs +++ b/crates/hir_def/src/expr.rs | |||
@@ -20,6 +20,7 @@ use crate::{ | |||
20 | builtin_type::{BuiltinFloat, BuiltinInt}, | 20 | builtin_type::{BuiltinFloat, BuiltinInt}, |
21 | path::{GenericArgs, Path}, | 21 | path::{GenericArgs, Path}, |
22 | type_ref::{Mutability, Rawness, TypeRef}, | 22 | type_ref::{Mutability, Rawness, TypeRef}, |
23 | BlockId, | ||
23 | }; | 24 | }; |
24 | 25 | ||
25 | pub type ExprId = Idx<Expr>; | 26 | pub type ExprId = Idx<Expr>; |
@@ -56,6 +57,7 @@ pub enum Expr { | |||
56 | else_branch: Option<ExprId>, | 57 | else_branch: Option<ExprId>, |
57 | }, | 58 | }, |
58 | Block { | 59 | Block { |
60 | id: BlockId, | ||
59 | statements: Vec<Statement>, | 61 | statements: Vec<Statement>, |
60 | tail: Option<ExprId>, | 62 | tail: Option<ExprId>, |
61 | label: Option<LabelId>, | 63 | label: Option<LabelId>, |
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs index 94a1d567d..aa2c6e04e 100644 --- a/crates/hir_def/src/find_path.rs +++ b/crates/hir_def/src/find_path.rs | |||
@@ -36,13 +36,13 @@ const MAX_PATH_LEN: usize = 15; | |||
36 | 36 | ||
37 | impl ModPath { | 37 | impl ModPath { |
38 | fn starts_with_std(&self) -> bool { | 38 | fn starts_with_std(&self) -> bool { |
39 | self.segments.first() == Some(&known::std) | 39 | self.segments().first() == Some(&known::std) |
40 | } | 40 | } |
41 | 41 | ||
42 | // When std library is present, paths starting with `std::` | 42 | // When std library is present, paths starting with `std::` |
43 | // should be preferred over paths starting with `core::` and `alloc::` | 43 | // should be preferred over paths starting with `core::` and `alloc::` |
44 | fn can_start_with_std(&self) -> bool { | 44 | fn can_start_with_std(&self) -> bool { |
45 | let first_segment = self.segments.first(); | 45 | let first_segment = self.segments().first(); |
46 | first_segment == Some(&known::alloc) || first_segment == Some(&known::core) | 46 | first_segment == Some(&known::alloc) || first_segment == Some(&known::core) |
47 | } | 47 | } |
48 | } | 48 | } |
@@ -157,7 +157,7 @@ fn find_path_inner( | |||
157 | if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { | 157 | if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { |
158 | if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) { | 158 | if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) { |
159 | let data = db.enum_data(variant.parent); | 159 | let data = db.enum_data(variant.parent); |
160 | path.segments.push(data.variants[variant.local_id].name.clone()); | 160 | path.push_segment(data.variants[variant.local_id].name.clone()); |
161 | return Some(path); | 161 | return Some(path); |
162 | } | 162 | } |
163 | // If this doesn't work, it seems we have no way of referring to the | 163 | // If this doesn't work, it seems we have no way of referring to the |
@@ -186,7 +186,7 @@ fn find_path_inner( | |||
186 | best_path_len - 1, | 186 | best_path_len - 1, |
187 | prefixed, | 187 | prefixed, |
188 | ) { | 188 | ) { |
189 | path.segments.push(name); | 189 | path.push_segment(name); |
190 | 190 | ||
191 | let new_path = if let Some(best_path) = best_path { | 191 | let new_path = if let Some(best_path) = best_path { |
192 | select_best_path(best_path, path, prefer_no_std) | 192 | select_best_path(best_path, path, prefer_no_std) |
@@ -215,7 +215,7 @@ fn find_path_inner( | |||
215 | prefixed, | 215 | prefixed, |
216 | )?; | 216 | )?; |
217 | mark::hit!(partially_imported); | 217 | mark::hit!(partially_imported); |
218 | path.segments.push(info.path.segments.last().unwrap().clone()); | 218 | path.push_segment(info.path.segments.last().unwrap().clone()); |
219 | Some(path) | 219 | Some(path) |
220 | }) | 220 | }) |
221 | }); | 221 | }); |
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index 2750e1c91..ee46c3330 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs | |||
@@ -8,6 +8,7 @@ use hir_expand::name::Name; | |||
8 | use hir_expand::MacroDefKind; | 8 | use hir_expand::MacroDefKind; |
9 | use once_cell::sync::Lazy; | 9 | use once_cell::sync::Lazy; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 10 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | use stdx::format_to; | ||
11 | use test_utils::mark; | 12 | use test_utils::mark; |
12 | 13 | ||
13 | use crate::{ | 14 | use crate::{ |
@@ -292,6 +293,30 @@ impl ItemScope { | |||
292 | *vis = Visibility::Module(this_module); | 293 | *vis = Visibility::Module(this_module); |
293 | } | 294 | } |
294 | } | 295 | } |
296 | |||
297 | pub(crate) fn dump(&self, buf: &mut String) { | ||
298 | let mut entries: Vec<_> = self.resolutions().collect(); | ||
299 | entries.sort_by_key(|(name, _)| name.clone()); | ||
300 | |||
301 | for (name, def) in entries { | ||
302 | format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string())); | ||
303 | |||
304 | if def.types.is_some() { | ||
305 | buf.push_str(" t"); | ||
306 | } | ||
307 | if def.values.is_some() { | ||
308 | buf.push_str(" v"); | ||
309 | } | ||
310 | if def.macros.is_some() { | ||
311 | buf.push_str(" m"); | ||
312 | } | ||
313 | if def.is_none() { | ||
314 | buf.push_str(" _"); | ||
315 | } | ||
316 | |||
317 | buf.push('\n'); | ||
318 | } | ||
319 | } | ||
295 | } | 320 | } |
296 | 321 | ||
297 | impl PerNs { | 322 | impl PerNs { |
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 42d9f0947..3233b1957 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs | |||
@@ -24,7 +24,7 @@ use la_arena::{Arena, Idx, RawIdx}; | |||
24 | use profile::Count; | 24 | use profile::Count; |
25 | use rustc_hash::FxHashMap; | 25 | use rustc_hash::FxHashMap; |
26 | use smallvec::SmallVec; | 26 | use smallvec::SmallVec; |
27 | use syntax::{ast, match_ast}; | 27 | use syntax::{ast, match_ast, SyntaxKind}; |
28 | use test_utils::mark; | 28 | use test_utils::mark; |
29 | 29 | ||
30 | use crate::{ | 30 | use crate::{ |
@@ -80,6 +80,10 @@ impl ItemTree { | |||
80 | pub(crate) fn item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> { | 80 | pub(crate) fn item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> { |
81 | let _p = profile::span("item_tree_query").detail(|| format!("{:?}", file_id)); | 81 | let _p = profile::span("item_tree_query").detail(|| format!("{:?}", file_id)); |
82 | let syntax = if let Some(node) = db.parse_or_expand(file_id) { | 82 | let syntax = if let Some(node) = db.parse_or_expand(file_id) { |
83 | if node.kind() == SyntaxKind::ERROR { | ||
84 | // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic | ||
85 | return Default::default(); | ||
86 | } | ||
83 | node | 87 | node |
84 | } else { | 88 | } else { |
85 | return Default::default(); | 89 | return Default::default(); |
@@ -142,6 +146,7 @@ impl ItemTree { | |||
142 | macro_defs, | 146 | macro_defs, |
143 | vis, | 147 | vis, |
144 | generics, | 148 | generics, |
149 | type_refs, | ||
145 | inner_items, | 150 | inner_items, |
146 | } = &mut **data; | 151 | } = &mut **data; |
147 | 152 | ||
@@ -165,6 +170,8 @@ impl ItemTree { | |||
165 | 170 | ||
166 | vis.arena.shrink_to_fit(); | 171 | vis.arena.shrink_to_fit(); |
167 | generics.arena.shrink_to_fit(); | 172 | generics.arena.shrink_to_fit(); |
173 | type_refs.arena.shrink_to_fit(); | ||
174 | type_refs.map.shrink_to_fit(); | ||
168 | 175 | ||
169 | inner_items.shrink_to_fit(); | 176 | inner_items.shrink_to_fit(); |
170 | } | 177 | } |
@@ -233,7 +240,7 @@ impl ItemVisibilities { | |||
233 | fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId { | 240 | fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId { |
234 | match &vis { | 241 | match &vis { |
235 | RawVisibility::Public => RawVisibilityId::PUB, | 242 | RawVisibility::Public => RawVisibilityId::PUB, |
236 | RawVisibility::Module(path) if path.segments.is_empty() => match &path.kind { | 243 | RawVisibility::Module(path) if path.segments().is_empty() => match &path.kind { |
237 | PathKind::Super(0) => RawVisibilityId::PRIV, | 244 | PathKind::Super(0) => RawVisibilityId::PRIV, |
238 | PathKind::Crate => RawVisibilityId::PUB_CRATE, | 245 | PathKind::Crate => RawVisibilityId::PUB_CRATE, |
239 | _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()), | 246 | _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()), |
@@ -244,10 +251,8 @@ impl ItemVisibilities { | |||
244 | } | 251 | } |
245 | 252 | ||
246 | static VIS_PUB: RawVisibility = RawVisibility::Public; | 253 | static VIS_PUB: RawVisibility = RawVisibility::Public; |
247 | static VIS_PRIV: RawVisibility = | 254 | static VIS_PRIV: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Super(0))); |
248 | RawVisibility::Module(ModPath { kind: PathKind::Super(0), segments: Vec::new() }); | 255 | static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Crate)); |
249 | static VIS_PUB_CRATE: RawVisibility = | ||
250 | RawVisibility::Module(ModPath { kind: PathKind::Crate, segments: Vec::new() }); | ||
251 | 256 | ||
252 | #[derive(Default, Debug, Eq, PartialEq)] | 257 | #[derive(Default, Debug, Eq, PartialEq)] |
253 | struct GenericParamsStorage { | 258 | struct GenericParamsStorage { |
@@ -275,6 +280,32 @@ static EMPTY_GENERICS: GenericParams = GenericParams { | |||
275 | where_predicates: Vec::new(), | 280 | where_predicates: Vec::new(), |
276 | }; | 281 | }; |
277 | 282 | ||
283 | /// `TypeRef` interner. | ||
284 | #[derive(Default, Debug, Eq, PartialEq)] | ||
285 | struct TypeRefStorage { | ||
286 | arena: Arena<Arc<TypeRef>>, | ||
287 | map: FxHashMap<Arc<TypeRef>, Idx<Arc<TypeRef>>>, | ||
288 | } | ||
289 | |||
290 | impl TypeRefStorage { | ||
291 | // Note: We lie about the `Idx<TypeRef>` to hide the interner details. | ||
292 | |||
293 | fn intern(&mut self, ty: TypeRef) -> Idx<TypeRef> { | ||
294 | if let Some(id) = self.map.get(&ty) { | ||
295 | return Idx::from_raw(id.into_raw()); | ||
296 | } | ||
297 | |||
298 | let ty = Arc::new(ty); | ||
299 | let idx = self.arena.alloc(ty.clone()); | ||
300 | self.map.insert(ty, idx); | ||
301 | Idx::from_raw(idx.into_raw()) | ||
302 | } | ||
303 | |||
304 | fn lookup(&self, id: Idx<TypeRef>) -> &TypeRef { | ||
305 | &self.arena[Idx::from_raw(id.into_raw())] | ||
306 | } | ||
307 | } | ||
308 | |||
278 | #[derive(Default, Debug, Eq, PartialEq)] | 309 | #[derive(Default, Debug, Eq, PartialEq)] |
279 | struct ItemTreeData { | 310 | struct ItemTreeData { |
280 | imports: Arena<Import>, | 311 | imports: Arena<Import>, |
@@ -297,6 +328,7 @@ struct ItemTreeData { | |||
297 | 328 | ||
298 | vis: ItemVisibilities, | 329 | vis: ItemVisibilities, |
299 | generics: GenericParamsStorage, | 330 | generics: GenericParamsStorage, |
331 | type_refs: TypeRefStorage, | ||
300 | 332 | ||
301 | inner_items: FxHashMap<FileAstId<ast::BlockExpr>, SmallVec<[ModItem; 1]>>, | 333 | inner_items: FxHashMap<FileAstId<ast::BlockExpr>, SmallVec<[ModItem; 1]>>, |
302 | } | 334 | } |
@@ -485,6 +517,14 @@ impl Index<GenericParamsId> for ItemTree { | |||
485 | } | 517 | } |
486 | } | 518 | } |
487 | 519 | ||
520 | impl Index<Idx<TypeRef>> for ItemTree { | ||
521 | type Output = TypeRef; | ||
522 | |||
523 | fn index(&self, id: Idx<TypeRef>) -> &Self::Output { | ||
524 | self.data().type_refs.lookup(id) | ||
525 | } | ||
526 | } | ||
527 | |||
488 | impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree { | 528 | impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree { |
489 | type Output = N; | 529 | type Output = N; |
490 | fn index(&self, id: FileItemTreeId<N>) -> &N { | 530 | fn index(&self, id: FileItemTreeId<N>) -> &N { |
@@ -528,9 +568,9 @@ pub struct Function { | |||
528 | /// Whether the function is located in an `extern` block (*not* whether it is an | 568 | /// Whether the function is located in an `extern` block (*not* whether it is an |
529 | /// `extern "abi" fn`). | 569 | /// `extern "abi" fn`). |
530 | pub is_extern: bool, | 570 | pub is_extern: bool, |
531 | pub params: Box<[TypeRef]>, | 571 | pub params: Box<[Idx<TypeRef>]>, |
532 | pub is_varargs: bool, | 572 | pub is_varargs: bool, |
533 | pub ret_type: TypeRef, | 573 | pub ret_type: Idx<TypeRef>, |
534 | pub ast_id: FileAstId<ast::Fn>, | 574 | pub ast_id: FileAstId<ast::Fn>, |
535 | } | 575 | } |
536 | 576 | ||
@@ -577,7 +617,7 @@ pub struct Const { | |||
577 | /// const _: () = (); | 617 | /// const _: () = (); |
578 | pub name: Option<Name>, | 618 | pub name: Option<Name>, |
579 | pub visibility: RawVisibilityId, | 619 | pub visibility: RawVisibilityId, |
580 | pub type_ref: TypeRef, | 620 | pub type_ref: Idx<TypeRef>, |
581 | pub ast_id: FileAstId<ast::Const>, | 621 | pub ast_id: FileAstId<ast::Const>, |
582 | } | 622 | } |
583 | 623 | ||
@@ -588,7 +628,7 @@ pub struct Static { | |||
588 | pub mutable: bool, | 628 | pub mutable: bool, |
589 | /// Whether the static is in an `extern` block. | 629 | /// Whether the static is in an `extern` block. |
590 | pub is_extern: bool, | 630 | pub is_extern: bool, |
591 | pub type_ref: TypeRef, | 631 | pub type_ref: Idx<TypeRef>, |
592 | pub ast_id: FileAstId<ast::Static>, | 632 | pub ast_id: FileAstId<ast::Static>, |
593 | } | 633 | } |
594 | 634 | ||
@@ -605,8 +645,8 @@ pub struct Trait { | |||
605 | #[derive(Debug, Clone, Eq, PartialEq)] | 645 | #[derive(Debug, Clone, Eq, PartialEq)] |
606 | pub struct Impl { | 646 | pub struct Impl { |
607 | pub generic_params: GenericParamsId, | 647 | pub generic_params: GenericParamsId, |
608 | pub target_trait: Option<TypeRef>, | 648 | pub target_trait: Option<Idx<TypeRef>>, |
609 | pub target_type: TypeRef, | 649 | pub target_type: Idx<TypeRef>, |
610 | pub is_negative: bool, | 650 | pub is_negative: bool, |
611 | pub items: Box<[AssocItem]>, | 651 | pub items: Box<[AssocItem]>, |
612 | pub ast_id: FileAstId<ast::Impl>, | 652 | pub ast_id: FileAstId<ast::Impl>, |
@@ -619,7 +659,7 @@ pub struct TypeAlias { | |||
619 | /// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`. | 659 | /// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`. |
620 | pub bounds: Box<[TypeBound]>, | 660 | pub bounds: Box<[TypeBound]>, |
621 | pub generic_params: GenericParamsId, | 661 | pub generic_params: GenericParamsId, |
622 | pub type_ref: Option<TypeRef>, | 662 | pub type_ref: Option<Idx<TypeRef>>, |
623 | pub is_extern: bool, | 663 | pub is_extern: bool, |
624 | pub ast_id: FileAstId<ast::TypeAlias>, | 664 | pub ast_id: FileAstId<ast::TypeAlias>, |
625 | } | 665 | } |
@@ -802,6 +842,6 @@ pub enum Fields { | |||
802 | #[derive(Debug, Clone, PartialEq, Eq)] | 842 | #[derive(Debug, Clone, PartialEq, Eq)] |
803 | pub struct Field { | 843 | pub struct Field { |
804 | pub name: Name, | 844 | pub name: Name, |
805 | pub type_ref: TypeRef, | 845 | pub type_ref: Idx<TypeRef>, |
806 | pub visibility: RawVisibilityId, | 846 | pub visibility: RawVisibilityId, |
807 | } | 847 | } |
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index acc001add..8f2f0b340 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs | |||
@@ -183,6 +183,7 @@ impl Ctx { | |||
183 | block_stack.push(self.source_ast_id_map.ast_id(&block)); | 183 | block_stack.push(self.source_ast_id_map.ast_id(&block)); |
184 | }, | 184 | }, |
185 | ast::Item(item) => { | 185 | ast::Item(item) => { |
186 | // FIXME: This triggers for macro calls in expression position | ||
186 | let mod_items = self.lower_mod_item(&item, true); | 187 | let mod_items = self.lower_mod_item(&item, true); |
187 | let current_block = block_stack.last(); | 188 | let current_block = block_stack.last(); |
188 | if let (Some(mod_items), Some(block)) = (mod_items, current_block) { | 189 | if let (Some(mod_items), Some(block)) = (mod_items, current_block) { |
@@ -363,6 +364,7 @@ impl Ctx { | |||
363 | params.push(type_ref); | 364 | params.push(type_ref); |
364 | } | 365 | } |
365 | } | 366 | } |
367 | let params = params.into_iter().map(|param| self.data().type_refs.intern(param)).collect(); | ||
366 | 368 | ||
367 | let mut is_varargs = false; | 369 | let mut is_varargs = false; |
368 | if let Some(params) = func.param_list() { | 370 | if let Some(params) = func.param_list() { |
@@ -384,6 +386,8 @@ impl Ctx { | |||
384 | ret_type | 386 | ret_type |
385 | }; | 387 | }; |
386 | 388 | ||
389 | let ret_type = self.data().type_refs.intern(ret_type); | ||
390 | |||
387 | let has_body = func.body().is_some(); | 391 | let has_body = func.body().is_some(); |
388 | 392 | ||
389 | let ast_id = self.source_ast_id_map.ast_id(func); | 393 | let ast_id = self.source_ast_id_map.ast_id(func); |
@@ -395,7 +399,7 @@ impl Ctx { | |||
395 | has_body, | 399 | has_body, |
396 | is_unsafe: func.unsafe_token().is_some(), | 400 | is_unsafe: func.unsafe_token().is_some(), |
397 | is_extern: false, | 401 | is_extern: false, |
398 | params: params.into_boxed_slice(), | 402 | params, |
399 | is_varargs, | 403 | is_varargs, |
400 | ret_type, | 404 | ret_type, |
401 | ast_id, | 405 | ast_id, |
@@ -656,6 +660,7 @@ impl Ctx { | |||
656 | generics.fill(&self.body_ctx, sm, node); | 660 | generics.fill(&self.body_ctx, sm, node); |
657 | // lower `impl Trait` in arguments | 661 | // lower `impl Trait` in arguments |
658 | for param in &*func.params { | 662 | for param in &*func.params { |
663 | let param = self.data().type_refs.lookup(*param); | ||
659 | generics.fill_implicit_impl_trait_args(param); | 664 | generics.fill_implicit_impl_trait_args(param); |
660 | } | 665 | } |
661 | } | 666 | } |
@@ -708,11 +713,15 @@ impl Ctx { | |||
708 | self.data().vis.alloc(vis) | 713 | self.data().vis.alloc(vis) |
709 | } | 714 | } |
710 | 715 | ||
711 | fn lower_type_ref(&self, type_ref: &ast::Type) -> TypeRef { | 716 | fn lower_type_ref(&mut self, type_ref: &ast::Type) -> Idx<TypeRef> { |
712 | TypeRef::from_ast(&self.body_ctx, type_ref.clone()) | 717 | let tyref = TypeRef::from_ast(&self.body_ctx, type_ref.clone()); |
718 | self.data().type_refs.intern(tyref) | ||
713 | } | 719 | } |
714 | fn lower_type_ref_opt(&self, type_ref: Option<ast::Type>) -> TypeRef { | 720 | fn lower_type_ref_opt(&mut self, type_ref: Option<ast::Type>) -> Idx<TypeRef> { |
715 | type_ref.map(|ty| self.lower_type_ref(&ty)).unwrap_or(TypeRef::Error) | 721 | match type_ref.map(|ty| self.lower_type_ref(&ty)) { |
722 | Some(it) => it, | ||
723 | None => self.data().type_refs.intern(TypeRef::Error), | ||
724 | } | ||
716 | } | 725 | } |
717 | 726 | ||
718 | /// Forces the visibility `vis` to be used for all items lowered during execution of `f`. | 727 | /// Forces the visibility `vis` to be used for all items lowered during execution of `f`. |
@@ -741,7 +750,8 @@ impl Ctx { | |||
741 | 750 | ||
742 | fn desugar_future_path(orig: TypeRef) -> Path { | 751 | fn desugar_future_path(orig: TypeRef) -> Path { |
743 | let path = path![core::future::Future]; | 752 | let path = path![core::future::Future]; |
744 | let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect(); | 753 | let mut generic_args: Vec<_> = |
754 | std::iter::repeat(None).take(path.segments().len() - 1).collect(); | ||
745 | let mut last = GenericArgs::empty(); | 755 | let mut last = GenericArgs::empty(); |
746 | let binding = | 756 | let binding = |
747 | AssociatedTypeBinding { name: name![Output], type_ref: Some(orig), bounds: Vec::new() }; | 757 | AssociatedTypeBinding { name: name![Output], type_ref: Some(orig), bounds: Vec::new() }; |
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index 42b50b5b7..b50923747 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs | |||
@@ -81,7 +81,13 @@ pub struct ModuleId { | |||
81 | impl ModuleId { | 81 | impl ModuleId { |
82 | pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc<DefMap> { | 82 | pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc<DefMap> { |
83 | match self.block { |