diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 64 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/fill_match_arms.rs | 348 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_deref.rs | 227 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/inline_local_variable.rs | 223 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/introduce_named_lifetime.rs | 112 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 108 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 1 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 27 |
10 files changed, 969 insertions, 176 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 5ccd7f7a2..49aa70f74 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -934,4 +934,37 @@ fn main() { | |||
934 | ", | 934 | ", |
935 | ); | 935 | ); |
936 | } | 936 | } |
937 | |||
938 | #[test] | ||
939 | fn inner_items() { | ||
940 | check_assist( | ||
941 | auto_import, | ||
942 | r#" | ||
943 | mod baz { | ||
944 | pub struct Foo {} | ||
945 | } | ||
946 | |||
947 | mod bar { | ||
948 | fn bar() { | ||
949 | Foo$0; | ||
950 | println!("Hallo"); | ||
951 | } | ||
952 | } | ||
953 | "#, | ||
954 | r#" | ||
955 | mod baz { | ||
956 | pub struct Foo {} | ||
957 | } | ||
958 | |||
959 | mod bar { | ||
960 | use crate::baz::Foo; | ||
961 | |||
962 | fn bar() { | ||
963 | Foo; | ||
964 | println!("Hallo"); | ||
965 | } | ||
966 | } | ||
967 | "#, | ||
968 | ); | ||
969 | } | ||
937 | } | 970 | } |
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index 059414274..78a57fbdc 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -599,7 +599,12 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
599 | // we have selected a few statements in a block | 599 | // we have selected a few statements in a block |
600 | // so covering_element returns the whole block | 600 | // so covering_element returns the whole block |
601 | if node.kind() == BLOCK_EXPR { | 601 | if node.kind() == BLOCK_EXPR { |
602 | let body = FunctionBody::from_range(node.clone(), selection_range); | 602 | // Extract the full statements. |
603 | let statements_range = node | ||
604 | .children() | ||
605 | .filter(|c| selection_range.intersect(c.text_range()).is_some()) | ||
606 | .fold(selection_range, |acc, c| acc.cover(c.text_range())); | ||
607 | let body = FunctionBody::from_range(node.clone(), statements_range); | ||
603 | if body.is_some() { | 608 | if body.is_some() { |
604 | return body; | 609 | return body; |
605 | } | 610 | } |
@@ -610,7 +615,8 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
610 | // so we try to expand covering_element to parent and repeat the previous | 615 | // so we try to expand covering_element to parent and repeat the previous |
611 | if let Some(parent) = node.parent() { | 616 | if let Some(parent) = node.parent() { |
612 | if parent.kind() == BLOCK_EXPR { | 617 | if parent.kind() == BLOCK_EXPR { |
613 | let body = FunctionBody::from_range(parent, selection_range); | 618 | // Extract the full statement. |
619 | let body = FunctionBody::from_range(parent, node.text_range()); | ||
614 | if body.is_some() { | 620 | if body.is_some() { |
615 | return body; | 621 | return body; |
616 | } | 622 | } |
@@ -1785,6 +1791,60 @@ fn $0fun_name() -> i32 { | |||
1785 | } | 1791 | } |
1786 | 1792 | ||
1787 | #[test] | 1793 | #[test] |
1794 | fn extract_partial_block_single_line() { | ||
1795 | check_assist( | ||
1796 | extract_function, | ||
1797 | r#" | ||
1798 | fn foo() { | ||
1799 | let n = 1; | ||
1800 | let mut v = $0n * n;$0 | ||
1801 | v += 1; | ||
1802 | }"#, | ||
1803 | r#" | ||
1804 | fn foo() { | ||
1805 | let n = 1; | ||
1806 | let mut v = fun_name(n); | ||
1807 | v += 1; | ||
1808 | } | ||
1809 | |||
1810 | fn $0fun_name(n: i32) -> i32 { | ||
1811 | let mut v = n * n; | ||
1812 | v | ||
1813 | }"#, | ||
1814 | ); | ||
1815 | } | ||
1816 | |||
1817 | #[test] | ||
1818 | fn extract_partial_block() { | ||
1819 | check_assist( | ||
1820 | extract_function, | ||
1821 | r#" | ||
1822 | fn foo() { | ||
1823 | let m = 2; | ||
1824 | let n = 1; | ||
1825 | let mut v = m $0* n; | ||
1826 | let mut w = 3;$0 | ||
1827 | v += 1; | ||
1828 | w += 1; | ||
1829 | }"#, | ||
1830 | r#" | ||
1831 | fn foo() { | ||
1832 | let m = 2; | ||
1833 | let n = 1; | ||
1834 | let (mut v, mut w) = fun_name(m, n); | ||
1835 | v += 1; | ||
1836 | w += 1; | ||
1837 | } | ||
1838 | |||
1839 | fn $0fun_name(m: i32, n: i32) -> (i32, i32) { | ||
1840 | let mut v = m * n; | ||
1841 | let mut w = 3; | ||
1842 | (v, w) | ||
1843 | }"#, | ||
1844 | ); | ||
1845 | } | ||
1846 | |||
1847 | #[test] | ||
1788 | fn argument_form_expr() { | 1848 | fn argument_form_expr() { |
1789 | check_assist( | 1849 | check_assist( |
1790 | extract_function, | 1850 | extract_function, |
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index 80bd1b7e8..be927cc1c 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use either::Either; | ||
3 | use hir::{Adt, HasSource, ModuleDef, Semantics}; | 4 | use hir::{Adt, HasSource, ModuleDef, Semantics}; |
4 | use ide_db::helpers::{mod_path_to_ast, FamousDefs}; | 5 | use ide_db::helpers::{mod_path_to_ast, FamousDefs}; |
5 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
@@ -7,7 +8,7 @@ use itertools::Itertools; | |||
7 | use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | 8 | use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; |
8 | 9 | ||
9 | use crate::{ | 10 | use crate::{ |
10 | utils::{does_pat_match_variant, render_snippet, Cursor}, | 11 | utils::{self, render_snippet, Cursor}, |
11 | AssistContext, AssistId, AssistKind, Assists, | 12 | AssistContext, AssistId, AssistKind, Assists, |
12 | }; | 13 | }; |
13 | 14 | ||
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
48 | } | 49 | } |
49 | } | 50 | } |
50 | 51 | ||
52 | let top_lvl_pats: Vec<_> = arms | ||
53 | .iter() | ||
54 | .filter_map(ast::MatchArm::pat) | ||
55 | .flat_map(|pat| match pat { | ||
56 | // Special case OrPat as separate top-level pats | ||
57 | Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), | ||
58 | _ => Either::Right(iter::once(pat)), | ||
59 | }) | ||
60 | // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129. | ||
61 | .filter(|pat| !matches!(pat, Pat::WildcardPat(_))) | ||
62 | .collect(); | ||
63 | |||
51 | let module = ctx.sema.scope(expr.syntax()).module()?; | 64 | let module = ctx.sema.scope(expr.syntax()).module()?; |
52 | 65 | ||
53 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { | 66 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { |
@@ -56,21 +69,20 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
56 | let mut variants = variants | 69 | let mut variants = variants |
57 | .into_iter() | 70 | .into_iter() |
58 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) | 71 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) |
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 73 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
61 | .collect::<Vec<_>>(); | 74 | .collect::<Vec<_>>(); |
62 | if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { | 75 | if Some(enum_def) |
76 | == FamousDefs(&ctx.sema, Some(module.krate())) | ||
77 | .core_option_Option() | ||
78 | .map(|x| lift_enum(x)) | ||
79 | { | ||
63 | // Match `Some` variant first. | 80 | // Match `Some` variant first. |
64 | cov_mark::hit!(option_order); | 81 | cov_mark::hit!(option_order); |
65 | variants.reverse() | 82 | variants.reverse() |
66 | } | 83 | } |
67 | variants | 84 | variants |
68 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { | 85 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { |
69 | // Partial fill not currently supported for tuple of enums. | ||
70 | if !arms.is_empty() { | ||
71 | return None; | ||
72 | } | ||
73 | |||
74 | // When calculating the match arms for a tuple of enums, we want | 86 | // When calculating the match arms for a tuple of enums, we want |
75 | // to create a match arm for each possible combination of enum | 87 | // to create a match arm for each possible combination of enum |
76 | // values. The `multi_cartesian_product` method transforms | 88 | // values. The `multi_cartesian_product` method transforms |
@@ -85,7 +97,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
85 | variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); | 97 | variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); |
86 | ast::Pat::from(make::tuple_pat(patterns)) | 98 | ast::Pat::from(make::tuple_pat(patterns)) |
87 | }) | 99 | }) |
88 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 100 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
89 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 101 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
90 | .collect() | 102 | .collect() |
91 | } else { | 103 | } else { |
@@ -128,61 +140,114 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
128 | ) | 140 | ) |
129 | } | 141 | } |
130 | 142 | ||
131 | fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { | 143 | fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool { |
132 | existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { | 144 | !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var)) |
133 | // Special casee OrPat as separate top-level pats | 145 | } |
134 | let top_level_pats: Vec<Pat> = match pat { | ||
135 | Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(), | ||
136 | _ => vec![pat], | ||
137 | }; | ||
138 | 146 | ||
139 | !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) | 147 | // Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check? |
140 | }) | 148 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { |
149 | match (pat, var) { | ||
150 | (Pat::WildcardPat(_), _) => true, | ||
151 | (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => { | ||
152 | tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v)) | ||
153 | } | ||
154 | _ => utils::does_pat_match_variant(pat, var), | ||
155 | } | ||
156 | } | ||
157 | |||
158 | #[derive(Eq, PartialEq, Clone)] | ||
159 | enum ExtendedEnum { | ||
160 | Bool, | ||
161 | Enum(hir::Enum), | ||
162 | } | ||
163 | |||
164 | #[derive(Eq, PartialEq, Clone)] | ||
165 | enum ExtendedVariant { | ||
166 | True, | ||
167 | False, | ||
168 | Variant(hir::Variant), | ||
169 | } | ||
170 | |||
171 | fn lift_enum(e: hir::Enum) -> ExtendedEnum { | ||
172 | ExtendedEnum::Enum(e) | ||
141 | } | 173 | } |
142 | 174 | ||
143 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { | 175 | impl ExtendedEnum { |
176 | fn variants(&self, db: &RootDatabase) -> Vec<ExtendedVariant> { | ||
177 | match self { | ||
178 | ExtendedEnum::Enum(e) => { | ||
179 | e.variants(db).into_iter().map(|x| ExtendedVariant::Variant(x)).collect::<Vec<_>>() | ||
180 | } | ||
181 | ExtendedEnum::Bool => { | ||
182 | Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False]) | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | |||
188 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> { | ||
144 | sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | 189 | sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { |
145 | Some(Adt::Enum(e)) => Some(e), | 190 | Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), |
146 | _ => None, | 191 | _ => { |
192 | if ty.is_bool() { | ||
193 | Some(ExtendedEnum::Bool) | ||
194 | } else { | ||
195 | None | ||
196 | } | ||
197 | } | ||
147 | }) | 198 | }) |
148 | } | 199 | } |
149 | 200 | ||
150 | fn resolve_tuple_of_enum_def( | 201 | fn resolve_tuple_of_enum_def( |
151 | sema: &Semantics<RootDatabase>, | 202 | sema: &Semantics<RootDatabase>, |
152 | expr: &ast::Expr, | 203 | expr: &ast::Expr, |
153 | ) -> Option<Vec<hir::Enum>> { | 204 | ) -> Option<Vec<ExtendedEnum>> { |
154 | sema.type_of_expr(&expr)? | 205 | sema.type_of_expr(&expr)? |
155 | .tuple_fields(sema.db) | 206 | .tuple_fields(sema.db) |
156 | .iter() | 207 | .iter() |
157 | .map(|ty| { | 208 | .map(|ty| { |
158 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | 209 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { |
159 | Some(Adt::Enum(e)) => Some(e), | 210 | Some(Adt::Enum(e)) => Some(lift_enum(e)), |
160 | // For now we only handle expansion for a tuple of enums. Here | 211 | // For now we only handle expansion for a tuple of enums. Here |
161 | // we map non-enum items to None and rely on `collect` to | 212 | // we map non-enum items to None and rely on `collect` to |
162 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. | 213 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. |
163 | _ => None, | 214 | _ => { |
215 | if ty.is_bool() { | ||
216 | Some(ExtendedEnum::Bool) | ||
217 | } else { | ||
218 | None | ||
219 | } | ||
220 | } | ||
164 | }) | 221 | }) |
165 | }) | 222 | }) |
166 | .collect() | 223 | .collect() |
167 | } | 224 | } |
168 | 225 | ||
169 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> { | 226 | fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> { |
170 | let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); | 227 | match var { |
228 | ExtendedVariant::Variant(var) => { | ||
229 | let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); | ||
230 | |||
231 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | ||
232 | let pat: ast::Pat = match var.source(db)?.value.kind() { | ||
233 | ast::StructKind::Tuple(field_list) => { | ||
234 | let pats = | ||
235 | iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); | ||
236 | make::tuple_struct_pat(path, pats).into() | ||
237 | } | ||
238 | ast::StructKind::Record(field_list) => { | ||
239 | let pats = | ||
240 | field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); | ||
241 | make::record_pat(path, pats).into() | ||
242 | } | ||
243 | ast::StructKind::Unit => make::path_pat(path), | ||
244 | }; | ||
171 | 245 | ||
172 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | 246 | Some(pat) |
173 | let pat: ast::Pat = match var.source(db)?.value.kind() { | ||
174 | ast::StructKind::Tuple(field_list) => { | ||
175 | let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); | ||
176 | make::tuple_struct_pat(path, pats).into() | ||
177 | } | ||
178 | ast::StructKind::Record(field_list) => { | ||
179 | let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); | ||
180 | make::record_pat(path, pats).into() | ||
181 | } | 247 | } |
182 | ast::StructKind::Unit => make::path_pat(path), | 248 | ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), |
183 | }; | 249 | ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), |
184 | 250 | } | |
185 | Some(pat) | ||
186 | } | 251 | } |
187 | 252 | ||
188 | #[cfg(test)] | 253 | #[cfg(test)] |
@@ -215,6 +280,21 @@ mod tests { | |||
215 | } | 280 | } |
216 | 281 | ||
217 | #[test] | 282 | #[test] |
283 | fn all_boolean_match_arms_provided() { | ||
284 | check_assist_not_applicable( | ||
285 | fill_match_arms, | ||
286 | r#" | ||
287 | fn foo(a: bool) { | ||
288 | match a$0 { | ||
289 | true => {} | ||
290 | false => {} | ||
291 | } | ||
292 | } | ||
293 | "#, | ||
294 | ) | ||
295 | } | ||
296 | |||
297 | #[test] | ||
218 | fn tuple_of_non_enum() { | 298 | fn tuple_of_non_enum() { |
219 | // for now this case is not handled, although it potentially could be | 299 | // for now this case is not handled, although it potentially could be |
220 | // in the future | 300 | // in the future |
@@ -230,6 +310,113 @@ mod tests { | |||
230 | } | 310 | } |
231 | 311 | ||
232 | #[test] | 312 | #[test] |
313 | fn fill_match_arms_boolean() { | ||
314 | check_assist( | ||
315 | fill_match_arms, | ||
316 | r#" | ||
317 | fn foo(a: bool) { | ||
318 | match a$0 { | ||
319 | } | ||
320 | } | ||
321 | "#, | ||
322 | r#" | ||
323 | fn foo(a: bool) { | ||
324 | match a { | ||
325 | $0true => {} | ||
326 | false => {} | ||
327 | } | ||
328 | } | ||
329 | "#, | ||
330 | ) | ||
331 | } | ||
332 | |||
333 | #[test] | ||
334 | fn partial_fill_boolean() { | ||
335 | check_assist( | ||
336 | fill_match_arms, | ||
337 | r#" | ||
338 | fn foo(a: bool) { | ||
339 | match a$0 { | ||
340 | true => {} | ||
341 | } | ||
342 | } | ||
343 | "#, | ||
344 | r#" | ||
345 | fn foo(a: bool) { | ||
346 | match a { | ||
347 | true => {} | ||
348 | $0false => {} | ||
349 | } | ||
350 | } | ||
351 | "#, | ||
352 | ) | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn all_boolean_tuple_arms_provided() { | ||
357 | check_assist_not_applicable( | ||
358 | fill_match_arms, | ||
359 | r#" | ||
360 | fn foo(a: bool) { | ||
361 | match (a, a)$0 { | ||
362 | (true, true) => {} | ||
363 | (true, false) => {} | ||
364 | (false, true) => {} | ||
365 | (false, false) => {} | ||
366 | } | ||
367 | } | ||
368 | "#, | ||
369 | ) | ||
370 | } | ||
371 | |||
372 | #[test] | ||
373 | fn fill_boolean_tuple() { | ||
374 | check_assist( | ||
375 | fill_match_arms, | ||
376 | r#" | ||
377 | fn foo(a: bool) { | ||
378 | match (a, a)$0 { | ||
379 | } | ||
380 | } | ||
381 | "#, | ||
382 | r#" | ||
383 | fn foo(a: bool) { | ||
384 | match (a, a) { | ||
385 | $0(true, true) => {} | ||
386 | (true, false) => {} | ||
387 | (false, true) => {} | ||
388 | (false, false) => {} | ||
389 | } | ||
390 | } | ||
391 | "#, | ||
392 | ) | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn partial_fill_boolean_tuple() { | ||
397 | check_assist( | ||
398 | fill_match_arms, | ||
399 | r#" | ||
400 | fn foo(a: bool) { | ||
401 | match (a, a)$0 { | ||
402 | (false, true) => {} | ||
403 | } | ||
404 | } | ||
405 | "#, | ||
406 | r#" | ||
407 | fn foo(a: bool) { | ||
408 | match (a, a) { | ||
409 | (false, true) => {} | ||
410 | $0(true, true) => {} | ||
411 | (true, false) => {} | ||
412 | (false, false) => {} | ||
413 | } | ||
414 | } | ||
415 | "#, | ||
416 | ) | ||
417 | } | ||
418 | |||
419 | #[test] | ||
233 | fn partial_fill_record_tuple() { | 420 | fn partial_fill_record_tuple() { |
234 | check_assist( | 421 | check_assist( |
235 | fill_match_arms, | 422 | fill_match_arms, |
@@ -467,20 +654,81 @@ fn main() { | |||
467 | 654 | ||
468 | #[test] | 655 | #[test] |
469 | fn fill_match_arms_tuple_of_enum_partial() { | 656 | fn fill_match_arms_tuple_of_enum_partial() { |
470 | check_assist_not_applicable( | 657 | check_assist( |
471 | fill_match_arms, | 658 | fill_match_arms, |
472 | r#" | 659 | r#" |
473 | enum A { One, Two } | 660 | enum A { One, Two } |
474 | enum B { One, Two } | 661 | enum B { One, Two } |
475 | 662 | ||
476 | fn main() { | 663 | fn main() { |
477 | let a = A::One; | 664 | let a = A::One; |
478 | let b = B::One; | 665 | let b = B::One; |
479 | match (a$0, b) { | 666 | match (a$0, b) { |
480 | (A::Two, B::One) => {} | 667 | (A::Two, B::One) => {} |
481 | } | 668 | } |
482 | } | 669 | } |
483 | "#, | 670 | "#, |
671 | r#" | ||
672 | enum A { One, Two } | ||
673 | enum B { One, Two } | ||
674 | |||
675 | fn main() { | ||
676 | let a = A::One; | ||
677 | let b = B::One; | ||
678 | match (a, b) { | ||
679 | (A::Two, B::One) => {} | ||
680 | $0(A::One, B::One) => {} | ||
681 | (A::One, B::Two) => {} | ||
682 | (A::Two, B::Two) => {} | ||
683 | } | ||
684 | } | ||
685 | "#, | ||
686 | ); | ||
687 | } | ||
688 | |||
689 | #[test] | ||
690 | fn fill_match_arms_tuple_of_enum_partial_with_wildcards() { | ||
691 | let ra_fixture = r#" | ||
692 | fn main() { | ||
693 | let a = Some(1); | ||
694 | let b = Some(()); | ||
695 | match (a$0, b) { | ||
696 | (Some(_), _) => {} | ||
697 | (None, Some(_)) => {} | ||
698 | } | ||
699 | } | ||
700 | "#; | ||
701 | check_assist( | ||
702 | fill_match_arms, | ||
703 | &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE), | ||
704 | r#" | ||
705 | fn main() { | ||
706 | let a = Some(1); | ||
707 | let b = Some(()); | ||
708 | match (a, b) { | ||
709 | (Some(_), _) => {} | ||
710 | (None, Some(_)) => {} | ||
711 | $0(None, None) => {} | ||
712 | } | ||
713 | } | ||
714 | "#, | ||
715 | ); | ||
716 | } | ||
717 | |||
718 | #[test] | ||
719 | fn fill_match_arms_partial_with_deep_pattern() { | ||
720 | // Fixme: cannot handle deep patterns | ||
721 | let ra_fixture = r#" | ||
722 | fn main() { | ||
723 | match $0Some(true) { | ||
724 | Some(true) => {} | ||
725 | None => {} | ||
726 | } | ||
727 | } | ||
728 | "#; | ||
729 | check_assist_not_applicable( | ||
730 | fill_match_arms, | ||
731 | &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE), | ||
484 | ); | 732 | ); |
485 | } | 733 | } |
486 | 734 | ||
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs new file mode 100644 index 000000000..4998ff7a4 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_deref.rs | |||
@@ -0,0 +1,227 @@ | |||
1 | use std::fmt::Display; | ||
2 | |||
3 | use ide_db::{helpers::FamousDefs, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, NameOwner}, | ||
6 | AstNode, SyntaxNode, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
11 | utils::generate_trait_impl_text, | ||
12 | AssistId, AssistKind, | ||
13 | }; | ||
14 | |||
15 | // Assist: generate_deref | ||
16 | // | ||
17 | // Generate `Deref` impl using the given struct field. | ||
18 | // | ||
19 | // ``` | ||
20 | // struct A; | ||
21 | // struct B { | ||
22 | // $0a: A | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // struct A; | ||
28 | // struct B { | ||
29 | // a: A | ||
30 | // } | ||
31 | // | ||
32 | // impl std::ops::Deref for B { | ||
33 | // type Target = A; | ||
34 | // | ||
35 | // fn deref(&self) -> &Self::Target { | ||
36 | // &self.a | ||
37 | // } | ||
38 | // } | ||
39 | // ``` | ||
40 | pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
41 | generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx)) | ||
42 | } | ||
43 | |||
44 | fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
45 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
46 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
47 | |||
48 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | ||
49 | cov_mark::hit!(test_add_record_deref_impl_already_exists); | ||
50 | return None; | ||
51 | } | ||
52 | |||
53 | let field_type = field.ty()?; | ||
54 | let field_name = field.name()?; | ||
55 | let target = field.syntax().text_range(); | ||
56 | acc.add( | ||
57 | AssistId("generate_deref", AssistKind::Generate), | ||
58 | format!("Generate `Deref` impl using `{}`", field_name), | ||
59 | target, | ||
60 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()), | ||
61 | ) | ||
62 | } | ||
63 | |||
64 | fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
65 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
66 | let field = ctx.find_node_at_offset::<ast::TupleField>()?; | ||
67 | let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?; | ||
68 | let field_list_index = | ||
69 | field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; | ||
70 | |||
71 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | ||
72 | cov_mark::hit!(test_add_field_deref_impl_already_exists); | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | let field_type = field.ty()?; | ||
77 | let target = field.syntax().text_range(); | ||
78 | acc.add( | ||
79 | AssistId("generate_deref", AssistKind::Generate), | ||
80 | format!("Generate `Deref` impl using `{}`", field.syntax()), | ||
81 | target, | ||
82 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index), | ||
83 | ) | ||
84 | } | ||
85 | |||
86 | fn generate_edit( | ||
87 | edit: &mut AssistBuilder, | ||
88 | strukt: ast::Struct, | ||
89 | field_type_syntax: &SyntaxNode, | ||
90 | field_name: impl Display, | ||
91 | ) { | ||
92 | let start_offset = strukt.syntax().text_range().end(); | ||
93 | let impl_code = format!( | ||
94 | r#" type Target = {0}; | ||
95 | |||
96 | fn deref(&self) -> &Self::Target {{ | ||
97 | &self.{1} | ||
98 | }}"#, | ||
99 | field_type_syntax, field_name | ||
100 | ); | ||
101 | let strukt_adt = ast::Adt::Struct(strukt); | ||
102 | let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code); | ||
103 | edit.insert(start_offset, deref_impl); | ||
104 | } | ||
105 | |||
106 | fn existing_deref_impl( | ||
107 | sema: &'_ hir::Semantics<'_, RootDatabase>, | ||
108 | strukt: &ast::Struct, | ||
109 | ) -> Option<()> { | ||
110 | let strukt = sema.to_def(strukt)?; | ||
111 | let krate = strukt.module(sema.db).krate(); | ||
112 | |||
113 | let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?; | ||
114 | let strukt_type = strukt.ty(sema.db); | ||
115 | |||
116 | if strukt_type.impls_trait(sema.db, deref_trait, &[]) { | ||
117 | Some(()) | ||
118 | } else { | ||
119 | None | ||
120 | } | ||
121 | } | ||
122 | |||
123 | #[cfg(test)] | ||
124 | mod tests { | ||
125 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
126 | |||
127 | use super::*; | ||
128 | |||
129 | #[test] | ||
130 | fn test_generate_record_deref() { | ||
131 | check_assist( | ||
132 | generate_deref, | ||
133 | r#"struct A { } | ||
134 | struct B { $0a: A }"#, | ||
135 | r#"struct A { } | ||
136 | struct B { a: A } | ||
137 | |||
138 | impl std::ops::Deref for B { | ||
139 | type Target = A; | ||
140 | |||
141 | fn deref(&self) -> &Self::Target { | ||
142 | &self.a | ||
143 | } | ||
144 | }"#, | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn test_generate_field_deref_idx_0() { | ||
150 | check_assist( | ||
151 | generate_deref, | ||
152 | r#"struct A { } | ||
153 | struct B($0A);"#, | ||
154 | r#"struct A { } | ||
155 | struct B(A); | ||
156 | |||
157 | impl std::ops::Deref for B { | ||
158 | type Target = A; | ||
159 | |||
160 | fn deref(&self) -> &Self::Target { | ||
161 | &self.0 | ||
162 | } | ||
163 | }"#, | ||
164 | ); | ||
165 | } | ||
166 | #[test] | ||
167 | fn test_generate_field_deref_idx_1() { | ||
168 | check_assist( | ||
169 | generate_deref, | ||
170 | r#"struct A { } | ||
171 | struct B(u8, $0A);"#, | ||
172 | r#"struct A { } | ||
173 | struct B(u8, A); | ||
174 | |||
175 | impl std::ops::Deref for B { | ||
176 | type Target = A; | ||
177 | |||
178 | fn deref(&self) -> &Self::Target { | ||
179 | &self.1 | ||
180 | } | ||
181 | }"#, | ||
182 | ); | ||
183 | } | ||
184 | |||
185 | fn check_not_applicable(ra_fixture: &str) { | ||
186 | let fixture = format!( | ||
187 | "//- /main.rs crate:main deps:core,std\n{}\n{}", | ||
188 | ra_fixture, | ||
189 | FamousDefs::FIXTURE | ||
190 | ); | ||
191 | check_assist_not_applicable(generate_deref, &fixture) | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn test_generate_record_deref_not_applicable_if_already_impl() { | ||
196 | cov_mark::check!(test_add_record_deref_impl_already_exists); | ||
197 | check_not_applicable( | ||
198 | r#"struct A { } | ||
199 | struct B { $0a: A } | ||
200 | |||
201 | impl std::ops::Deref for B { | ||
202 | type Target = A; | ||
203 | |||
204 | fn deref(&self) -> &Self::Target { | ||
205 | &self.a | ||
206 | } | ||
207 | }"#, | ||
208 | ) | ||
209 | } | ||
210 | |||
211 | #[test] | ||
212 | fn test_generate_field_deref_not_applicable_if_already_impl() { | ||
213 | cov_mark::check!(test_add_field_deref_impl_already_exists); | ||
214 | check_not_applicable( | ||
215 | r#"struct A { } | ||
216 | struct B($0A) | ||
217 | |||
218 | impl std::ops::Deref for B { | ||
219 | type Target = A; | ||
220 | |||
221 | fn deref(&self) -> &Self::Target { | ||
222 | &self.0 | ||
223 | } | ||
224 | }"#, | ||
225 | ) | ||
226 | } | ||
227 | } | ||
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs index ea1466dc8..f5dafc8cb 100644 --- a/crates/ide_assists/src/handlers/inline_local_variable.rs +++ b/crates/ide_assists/src/handlers/inline_local_variable.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | use ide_db::{defs::Definition, search::FileReference}; | 1 | use either::Either; |
2 | use hir::PathResolution; | ||
3 | use ide_db::{base_db::FileId, defs::Definition, search::FileReference}; | ||
2 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
3 | use syntax::{ | 5 | use syntax::{ |
4 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken, NameOwner}, |
5 | TextRange, | 7 | TextRange, |
6 | }; | 8 | }; |
7 | 9 | ||
@@ -27,44 +29,28 @@ use crate::{ | |||
27 | // } | 29 | // } |
28 | // ``` | 30 | // ``` |
29 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
30 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | 32 | let InlineData { let_stmt, delete_let, replace_usages, target } = |
31 | let bind_pat = match let_stmt.pat()? { | 33 | inline_let(ctx).or_else(|| inline_usage(ctx))?; |
32 | ast::Pat::IdentPat(pat) => pat, | ||
33 | _ => return None, | ||
34 | }; | ||
35 | if bind_pat.mut_token().is_some() { | ||
36 | cov_mark::hit!(test_not_inline_mut_variable); | ||
37 | return None; | ||
38 | } | ||
39 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
40 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
41 | return None; | ||
42 | } | ||
43 | let initializer_expr = let_stmt.initializer()?; | 34 | let initializer_expr = let_stmt.initializer()?; |
44 | 35 | ||
45 | let def = ctx.sema.to_def(&bind_pat)?; | 36 | let delete_range = if delete_let { |
46 | let def = Definition::Local(def); | 37 | if let Some(whitespace) = let_stmt |
47 | let usages = def.usages(&ctx.sema).all(); | 38 | .syntax() |
48 | if usages.is_empty() { | 39 | .next_sibling_or_token() |
49 | cov_mark::hit!(test_not_applicable_if_variable_unused); | 40 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) |
50 | return None; | 41 | { |
51 | }; | 42 | Some(TextRange::new( |
52 | 43 | let_stmt.syntax().text_range().start(), | |
53 | let delete_range = if let Some(whitespace) = let_stmt | 44 | whitespace.syntax().text_range().end(), |
54 | .syntax() | 45 | )) |
55 | .next_sibling_or_token() | 46 | } else { |
56 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) | 47 | Some(let_stmt.syntax().text_range()) |
57 | { | 48 | } |
58 | TextRange::new( | ||
59 | let_stmt.syntax().text_range().start(), | ||
60 | whitespace.syntax().text_range().end(), | ||
61 | ) | ||
62 | } else { | 49 | } else { |
63 | let_stmt.syntax().text_range() | 50 | None |
64 | }; | 51 | }; |
65 | 52 | ||
66 | let wrap_in_parens = usages | 53 | let wrap_in_parens = replace_usages |
67 | .references | ||
68 | .iter() | 54 | .iter() |
69 | .map(|(&file_id, refs)| { | 55 | .map(|(&file_id, refs)| { |
70 | refs.iter() | 56 | refs.iter() |
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
114 | let init_str = initializer_expr.syntax().text().to_string(); | 100 | let init_str = initializer_expr.syntax().text().to_string(); |
115 | let init_in_paren = format!("({})", &init_str); | 101 | let init_in_paren = format!("({})", &init_str); |
116 | 102 | ||
117 | let target = bind_pat.syntax().text_range(); | 103 | let target = match target { |
104 | ast::NameOrNameRef::Name(it) => it.syntax().text_range(), | ||
105 | ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(), | ||
106 | }; | ||
107 | |||
118 | acc.add( | 108 | acc.add( |
119 | AssistId("inline_local_variable", AssistKind::RefactorInline), | 109 | AssistId("inline_local_variable", AssistKind::RefactorInline), |
120 | "Inline variable", | 110 | "Inline variable", |
121 | target, | 111 | target, |
122 | move |builder| { | 112 | move |builder| { |
123 | builder.delete(delete_range); | 113 | if let Some(range) = delete_range { |
124 | for (file_id, references) in usages.references { | 114 | builder.delete(range); |
115 | } | ||
116 | for (file_id, references) in replace_usages { | ||
125 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { | 117 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { |
126 | let replacement = | 118 | let replacement = |
127 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 119 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
140 | ) | 132 | ) |
141 | } | 133 | } |
142 | 134 | ||
135 | struct InlineData { | ||
136 | let_stmt: ast::LetStmt, | ||
137 | delete_let: bool, | ||
138 | target: ast::NameOrNameRef, | ||
139 | replace_usages: FxHashMap<FileId, Vec<FileReference>>, | ||
140 | } | ||
141 | |||
142 | fn inline_let(ctx: &AssistContext) -> Option<InlineData> { | ||
143 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | ||
144 | let bind_pat = match let_stmt.pat()? { | ||
145 | ast::Pat::IdentPat(pat) => pat, | ||
146 | _ => return None, | ||
147 | }; | ||
148 | if bind_pat.mut_token().is_some() { | ||
149 | cov_mark::hit!(test_not_inline_mut_variable); | ||
150 | return None; | ||
151 | } | ||
152 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
153 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
154 | return None; | ||
155 | } | ||
156 | |||
157 | let def = ctx.sema.to_def(&bind_pat)?; | ||
158 | let def = Definition::Local(def); | ||
159 | let usages = def.usages(&ctx.sema).all(); | ||
160 | if usages.is_empty() { | ||
161 | cov_mark::hit!(test_not_applicable_if_variable_unused); | ||
162 | return None; | ||
163 | }; | ||
164 | |||
165 | Some(InlineData { | ||
166 | let_stmt, | ||
167 | delete_let: true, | ||
168 | target: ast::NameOrNameRef::Name(bind_pat.name()?), | ||
169 | replace_usages: usages.references, | ||
170 | }) | ||
171 | } | ||
172 | |||
173 | fn inline_usage(ctx: &AssistContext) -> Option<InlineData> { | ||
174 | let path_expr = ctx.find_node_at_offset::<ast::PathExpr>()?; | ||
175 | let path = path_expr.path()?; | ||
176 | let name = match path.as_single_segment()?.kind()? { | ||
177 | ast::PathSegmentKind::Name(name) => name, | ||
178 | _ => return None, | ||
179 | }; | ||
180 | |||
181 | let local = match ctx.sema.resolve_path(&path)? { | ||
182 | PathResolution::Local(local) => local, | ||
183 | _ => return None, | ||
184 | }; | ||
185 | |||
186 | let bind_pat = match local.source(ctx.db()).value { | ||
187 | Either::Left(ident) => ident, | ||
188 | _ => return None, | ||
189 | }; | ||
190 | |||
191 | let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?; | ||
192 | |||
193 | let def = Definition::Local(local); | ||
194 | let mut usages = def.usages(&ctx.sema).all(); | ||
195 | |||
196 | let delete_let = usages.references.values().map(|v| v.len()).sum::<usize>() == 1; | ||
197 | |||
198 | for references in usages.references.values_mut() { | ||
199 | references.retain(|reference| reference.name.as_name_ref() == Some(&name)); | ||
200 | } | ||
201 | |||
202 | Some(InlineData { | ||
203 | let_stmt, | ||
204 | delete_let, | ||
205 | target: ast::NameOrNameRef::NameRef(name), | ||
206 | replace_usages: usages.references, | ||
207 | }) | ||
208 | } | ||
209 | |||
143 | #[cfg(test)] | 210 | #[cfg(test)] |
144 | mod tests { | 211 | mod tests { |
145 | use crate::tests::{check_assist, check_assist_not_applicable}; | 212 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -726,4 +793,84 @@ fn main() { | |||
726 | ", | 793 | ", |
727 | ) | 794 | ) |
728 | } | 795 | } |
796 | |||
797 | #[test] | ||
798 | fn works_on_local_usage() { | ||
799 | check_assist( | ||
800 | inline_local_variable, | ||
801 | r#" | ||
802 | fn f() { | ||
803 | let xyz = 0; | ||
804 | xyz$0; | ||
805 | } | ||
806 | "#, | ||
807 | r#" | ||
808 | fn f() { | ||
809 | 0; | ||
810 | } | ||
811 | "#, | ||
812 | ); | ||
813 | } | ||
814 | |||
815 | #[test] | ||
816 | fn does_not_remove_let_when_multiple_usages() { | ||
817 | check_assist( | ||
818 | inline_local_variable, | ||
819 | r#" | ||
820 | fn f() { | ||
821 | let xyz = 0; | ||
822 | xyz$0; | ||
823 | xyz; | ||
824 | } | ||
825 | "#, | ||
826 | r#" | ||
827 | fn f() { | ||
828 | let xyz = 0; | ||
829 | 0; | ||
830 | xyz; | ||
831 | } | ||
832 | "#, | ||
833 | ); | ||
834 | } | ||
835 | |||
836 | #[test] | ||
837 | fn not_applicable_with_non_ident_pattern() { | ||
838 | check_assist_not_applicable( | ||
839 | inline_local_variable, | ||
840 | r#" | ||
841 | fn main() { | ||
842 | let (x, y) = (0, 1); | ||
843 | x$0; | ||
844 | } | ||
845 | "#, | ||
846 | ); | ||
847 | } | ||
848 | |||
849 | #[test] | ||
850 | fn not_applicable_on_local_usage_in_macro() { | ||
851 | check_assist_not_applicable( | ||
852 | inline_local_variable, | ||
853 | r#" | ||
854 | macro_rules! m { | ||
855 | ($i:ident) => { $i } | ||
856 | } | ||
857 | fn f() { | ||
858 | let xyz = 0; | ||
859 | m!(xyz$0); // replacing it would break the macro | ||
860 | } | ||
861 | "#, | ||
862 | ); | ||
863 | check_assist_not_applicable( | ||
864 | inline_local_variable, | ||
865 | r#" | ||
866 | macro_rules! m { | ||
867 | ($i:ident) => { $i } | ||
868 | } | ||
869 | fn f() { | ||
870 | let xyz$0 = 0; | ||
871 | m!(xyz); // replacing it would break the macro | ||
872 | } | ||
873 | "#, | ||
874 | ); | ||
875 | } | ||
729 | } | 876 | } |
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs index 02782eb6d..9f4f71d6c 100644 --- a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs +++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs | |||
@@ -1,7 +1,8 @@ | |||
1 | use rustc_hash::FxHashSet; | 1 | use rustc_hash::FxHashSet; |
2 | use syntax::{ | 2 | use syntax::{ |
3 | ast::{self, GenericParamsOwner, NameOwner}, | 3 | ast::{self, edit_in_place::GenericParamsOwnerEdit, make, GenericParamsOwner}, |
4 | AstNode, TextRange, TextSize, | 4 | ted::{self, Position}, |
5 | AstNode, TextRange, | ||
5 | }; | 6 | }; |
6 | 7 | ||
7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; |
@@ -37,10 +38,12 @@ static ASSIST_LABEL: &str = "Introduce named lifetime"; | |||
37 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 38 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let lifetime = | 39 | let lifetime = |
39 | ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; | 40 | ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; |
41 | let lifetime_loc = lifetime.lifetime_ident_token()?.text_range(); | ||
42 | |||
40 | if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { | 43 | if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { |
41 | generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range()) | 44 | generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime) |
42 | } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { | 45 | } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { |
43 | generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range()) | 46 | generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime) |
44 | } else { | 47 | } else { |
45 | None | 48 | None |
46 | } | 49 | } |
@@ -49,26 +52,26 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) - | |||
49 | /// Generate the assist for the fn def case | 52 | /// Generate the assist for the fn def case |
50 | fn generate_fn_def_assist( | 53 | fn generate_fn_def_assist( |
51 | acc: &mut Assists, | 54 | acc: &mut Assists, |
52 | fn_def: &ast::Fn, | 55 | fn_def: ast::Fn, |
53 | lifetime_loc: TextRange, | 56 | lifetime_loc: TextRange, |
57 | lifetime: ast::Lifetime, | ||
54 | ) -> Option<()> { | 58 | ) -> Option<()> { |
55 | let param_list: ast::ParamList = fn_def.param_list()?; | 59 | let param_list: ast::ParamList = fn_def.param_list()?; |
56 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?; | 60 | let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?; |
57 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); | ||
58 | let self_param = | 61 | let self_param = |
59 | // use the self if it's a reference and has no explicit lifetime | 62 | // use the self if it's a reference and has no explicit lifetime |
60 | param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); | 63 | param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); |
61 | // compute the location which implicitly has the same lifetime as the anonymous lifetime | 64 | // compute the location which implicitly has the same lifetime as the anonymous lifetime |
62 | let loc_needing_lifetime = if let Some(self_param) = self_param { | 65 | let loc_needing_lifetime = if let Some(self_param) = self_param { |
63 | // if we have a self reference, use that | 66 | // if we have a self reference, use that |
64 | Some(self_param.name()?.syntax().text_range().start()) | 67 | Some(NeedsLifetime::SelfParam(self_param)) |
65 | } else { | 68 | } else { |
66 | // otherwise, if there's a single reference parameter without a named liftime, use that | 69 | // otherwise, if there's a single reference parameter without a named liftime, use that |
67 | let fn_params_without_lifetime: Vec<_> = param_list | 70 | let fn_params_without_lifetime: Vec<_> = param_list |
68 | .params() | 71 | .params() |
69 | .filter_map(|param| match param.ty() { | 72 | .filter_map(|param| match param.ty() { |
70 | Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { | 73 | Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { |
71 | Some(ascribed_type.amp_token()?.text_range().end()) | 74 | Some(NeedsLifetime::RefType(ascribed_type)) |
72 | } | 75 | } |
73 | _ => None, | 76 | _ => None, |
74 | }) | 77 | }) |
@@ -81,30 +84,46 @@ fn generate_fn_def_assist( | |||
81 | } | 84 | } |
82 | }; | 85 | }; |
83 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { | 86 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
84 | add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); | 87 | let fn_def = builder.make_ast_mut(fn_def); |
85 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 88 | let lifetime = builder.make_ast_mut(lifetime); |
86 | loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); | 89 | let loc_needing_lifetime = |
90 | loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position()); | ||
91 | |||
92 | add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param); | ||
93 | ted::replace( | ||
94 | lifetime.syntax(), | ||
95 | make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(), | ||
96 | ); | ||
97 | loc_needing_lifetime.map(|position| { | ||
98 | ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax()) | ||
99 | }); | ||
87 | }) | 100 | }) |
88 | } | 101 | } |
89 | 102 | ||
90 | /// Generate the assist for the impl def case | 103 | /// Generate the assist for the impl def case |
91 | fn generate_impl_def_assist( | 104 | fn generate_impl_def_assist( |
92 | acc: &mut Assists, | 105 | acc: &mut Assists, |
93 | impl_def: &ast::Impl, | 106 | impl_def: ast::Impl, |
94 | lifetime_loc: TextRange, | 107 | lifetime_loc: TextRange, |
108 | lifetime: ast::Lifetime, | ||
95 | ) -> Option<()> { | 109 | ) -> Option<()> { |
96 | let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?; | 110 | let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; |
97 | let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); | ||
98 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { | 111 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
99 | add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); | 112 | let impl_def = builder.make_ast_mut(impl_def); |
100 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 113 | let lifetime = builder.make_ast_mut(lifetime); |
114 | |||
115 | add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param); | ||
116 | ted::replace( | ||
117 | lifetime.syntax(), | ||
118 | make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(), | ||
119 | ); | ||
101 | }) | 120 | }) |
102 | } | 121 | } |
103 | 122 | ||
104 | /// Given a type parameter list, generate a unique lifetime parameter name | 123 | /// Given a type parameter list, generate a unique lifetime parameter name |
105 | /// which is not in the list | 124 | /// which is not in the list |
106 | fn generate_unique_lifetime_param_name( | 125 | fn generate_unique_lifetime_param_name( |
107 | existing_type_param_list: &Option<ast::GenericParamList>, | 126 | existing_type_param_list: Option<ast::GenericParamList>, |
108 | ) -> Option<char> { | 127 | ) -> Option<char> { |
109 | match existing_type_param_list { | 128 | match existing_type_param_list { |
110 | Some(type_params) => { | 129 | Some(type_params) => { |
@@ -118,25 +137,37 @@ fn generate_unique_lifetime_param_name( | |||
118 | } | 137 | } |
119 | } | 138 | } |
120 | 139 | ||
121 | /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise | 140 | fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) { |
122 | /// add new type params brackets with the lifetime parameter at `new_type_params_loc`. | 141 | let generic_param = |
123 | fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>( | 142 | make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update(); |
124 | type_params_owner: &TypeParamsOwner, | 143 | type_params.add_generic_param(generic_param); |
125 | builder: &mut AssistBuilder, | 144 | } |
126 | new_type_params_loc: TextSize, | 145 | |
127 | new_lifetime_param: char, | 146 | fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime { |
128 | ) { | 147 | make::generic_param(format!("'{}", new_lifetime_param), None) |
129 | match type_params_owner.generic_param_list() { | 148 | .syntax() |
130 | // add the new lifetime parameter to an existing type param list | 149 | .descendants() |
131 | Some(type_params) => { | 150 | .find_map(ast::Lifetime::cast) |
132 | builder.insert( | 151 | .unwrap() |
133 | (u32::from(type_params.syntax().text_range().end()) - 1).into(), | 152 | } |
134 | format!(", '{}", new_lifetime_param), | 153 | |
135 | ); | 154 | enum NeedsLifetime { |
155 | SelfParam(ast::SelfParam), | ||
156 | RefType(ast::RefType), | ||
157 | } | ||
158 | |||
159 | impl NeedsLifetime { | ||
160 | fn make_mut(self, builder: &mut AssistBuilder) -> Self { | ||
161 | match self { | ||
162 | Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)), | ||
163 | Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)), | ||
136 | } | 164 | } |
137 | // create a new type param list containing only the new lifetime parameter | 165 | } |
138 | None => { | 166 | |
139 | builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); | 167 | fn to_position(self) -> Option<Position> { |
168 | match self { | ||
169 | Self::SelfParam(it) => Some(Position::after(it.amp_token()?)), | ||
170 | Self::RefType(it) => Some(Position::after(it.amp_token()?)), | ||
140 | } | 171 | } |
141 | } | 172 | } |
142 | } | 173 | } |
@@ -312,4 +343,13 @@ mod tests { | |||
312 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 343 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
313 | ); | 344 | ); |
314 | } | 345 | } |
346 | |||
347 | #[test] | ||
348 | fn test_function_add_lifetime_to_self_ref_mut() { | ||
349 | check_assist( | ||
350 | introduce_named_lifetime, | ||
351 | r#"fn foo(&mut self) -> &'_$0 ()"#, | ||
352 | r#"fn foo<'a>(&'a mut self) -> &'a ()"#, | ||
353 | ); | ||
354 | } | ||
315 | } | 355 | } |
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index f872d20c8..694d897d1 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -5,7 +5,6 @@ use itertools::Itertools; | |||
5 | use syntax::{ | 5 | use syntax::{ |
6 | ast::{self, make, AstNode, NameOwner}, | 6 | ast::{self, make, AstNode, NameOwner}, |
7 | SyntaxKind::{IDENT, WHITESPACE}, | 7 | SyntaxKind::{IDENT, WHITESPACE}, |
8 | TextSize, | ||
9 | }; | 8 | }; |
10 | 9 | ||
11 | use crate::{ | 10 | use crate::{ |
@@ -43,32 +42,28 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
43 | ctx: &AssistContext, | 42 | ctx: &AssistContext, |
44 | ) -> Option<()> { | 43 | ) -> Option<()> { |
45 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; | 44 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; |
45 | let (name, args) = attr.as_simple_call()?; | ||
46 | if name != "derive" { | ||
47 | return None; | ||
48 | } | ||
46 | 49 | ||
47 | let has_derive = attr | 50 | if !args.syntax().text_range().contains(ctx.offset()) { |
48 | .syntax() | 51 | cov_mark::hit!(outside_of_attr_args); |
49 | .descendants_with_tokens() | ||
50 | .filter(|t| t.kind() == IDENT) | ||
51 | .find_map(syntax::NodeOrToken::into_token) | ||
52 | .filter(|t| t.text() == "derive") | ||
53 | .is_some(); | ||
54 | if !has_derive { | ||
55 | return None; | 52 | return None; |
56 | } | 53 | } |
57 | 54 | ||
58 | let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; | 55 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; |
59 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | 56 | let trait_name = trait_token.text(); |
60 | 57 | ||
61 | let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; | 58 | let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; |
62 | let annotated_name = adt.name()?; | ||
63 | let insert_pos = adt.syntax().text_range().end(); | ||
64 | 59 | ||
65 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | 60 | let current_module = ctx.sema.scope(adt.syntax()).module()?; |
66 | let current_crate = current_module.krate(); | 61 | let current_crate = current_module.krate(); |
67 | 62 | ||
68 | let found_traits = items_locator::items_with_name( | 63 | let found_traits = items_locator::items_with_name( |
69 | &ctx.sema, | 64 | &ctx.sema, |
70 | current_crate, | 65 | current_crate, |
71 | NameToImport::Exact(trait_token.text().to_string()), | 66 | NameToImport::Exact(trait_name.to_string()), |
72 | items_locator::AssocItemSearch::Exclude, | 67 | items_locator::AssocItemSearch::Exclude, |
73 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), | 68 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), |
74 | ) | 69 | ) |
@@ -86,10 +81,11 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
86 | 81 | ||
87 | let mut no_traits_found = true; | 82 | let mut no_traits_found = true; |
88 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | 83 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { |
89 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?; | 84 | add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?; |
90 | } | 85 | } |
91 | if no_traits_found { | 86 | if no_traits_found { |
92 | add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?; | 87 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_name))); |
88 | add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?; | ||
93 | } | 89 | } |
94 | Some(()) | 90 | Some(()) |
95 | } | 91 | } |
@@ -98,15 +94,14 @@ fn add_assist( | |||
98 | acc: &mut Assists, | 94 | acc: &mut Assists, |
99 | ctx: &AssistContext, | 95 | ctx: &AssistContext, |
100 | attr: &ast::Attr, | 96 | attr: &ast::Attr, |
97 | input: &ast::TokenTree, | ||
101 | trait_path: &ast::Path, | 98 | trait_path: &ast::Path, |
102 | trait_: Option<hir::Trait>, | 99 | trait_: Option<hir::Trait>, |
103 | adt: &ast::Adt, | 100 | adt: &ast::Adt, |
104 | annotated_name: &ast::Name, | ||
105 | insert_pos: TextSize, | ||
106 | ) -> Option<()> { | 101 | ) -> Option<()> { |
107 | let target = attr.syntax().text_range(); | 102 | let target = attr.syntax().text_range(); |
108 | let input = attr.token_tree()?; | 103 | let annotated_name = adt.name()?; |
109 | let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); | 104 | let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); |
110 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; | 105 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; |
111 | 106 | ||
112 | acc.add( | 107 | acc.add( |
@@ -114,8 +109,9 @@ fn add_assist( | |||
114 | label, | 109 | label, |
115 | target, | 110 | target, |
116 | |builder| { | 111 | |builder| { |
112 | let insert_pos = adt.syntax().text_range().end(); | ||
117 | let impl_def_with_items = | 113 | let impl_def_with_items = |
118 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); | 114 | impl_def_from_trait(&ctx.sema, &annotated_name, trait_, trait_path); |
119 | update_attribute(builder, &input, &trait_name, &attr); | 115 | update_attribute(builder, &input, &trait_name, &attr); |
120 | let trait_path = format!("{}", trait_path); | 116 | let trait_path = format!("{}", trait_path); |
121 | match (ctx.config.snippet_cap, impl_def_with_items) { | 117 | match (ctx.config.snippet_cap, impl_def_with_items) { |
@@ -216,7 +212,7 @@ mod tests { | |||
216 | fn add_custom_impl_debug() { | 212 | fn add_custom_impl_debug() { |
217 | check_assist( | 213 | check_assist( |
218 | replace_derive_with_manual_impl, | 214 | replace_derive_with_manual_impl, |
219 | " | 215 | r#" |
220 | mod fmt { | 216 | mod fmt { |
221 | pub struct Error; | 217 | pub struct Error; |
222 | pub type Result = Result<(), Error>; | 218 | pub type Result = Result<(), Error>; |
@@ -230,8 +226,8 @@ mod fmt { | |||
230 | struct Foo { | 226 | struct Foo { |
231 | bar: String, | 227 | bar: String, |
232 | } | 228 | } |
233 | ", | 229 | "#, |
234 | " | 230 | r#" |
235 | mod fmt { | 231 | mod fmt { |
236 | pub struct Error; | 232 | pub struct Error; |
237 | pub type Result = Result<(), Error>; | 233 | pub type Result = Result<(), Error>; |
@@ -250,14 +246,14 @@ impl fmt::Debug for Foo { | |||
250 | ${0:todo!()} | 246 | ${0:todo!()} |
251 | } | 247 | } |
252 | } | 248 | } |
253 | ", | 249 | "#, |
254 | ) | 250 | ) |
255 | } | 251 | } |
256 | #[test] | 252 | #[test] |
257 | fn add_custom_impl_all() { | 253 | fn add_custom_impl_all() { |
258 | check_assist( | 254 | check_assist( |
259 | replace_derive_with_manual_impl, | 255 | replace_derive_with_manual_impl, |
260 | " | 256 | r#" |
261 | mod foo { | 257 | mod foo { |
262 | pub trait Bar { | 258 | pub trait Bar { |
263 | type Qux; | 259 | type Qux; |
@@ -272,8 +268,8 @@ mod foo { | |||
272 | struct Foo { | 268 | struct Foo { |
273 | bar: String, | 269 | bar: String, |
274 | } | 270 | } |
275 | ", | 271 | "#, |
276 | " | 272 | r#" |
277 | mod foo { | 273 | mod foo { |
278 | pub trait Bar { | 274 | pub trait Bar { |
279 | type Qux; | 275 | type Qux; |
@@ -299,20 +295,20 @@ impl foo::Bar for Foo { | |||
299 | todo!() | 295 | todo!() |
300 | } | 296 | } |
301 | } | 297 | } |
302 | ", | 298 | "#, |
303 | ) | 299 | ) |
304 | } | 300 | } |
305 | #[test] | 301 | #[test] |
306 | fn add_custom_impl_for_unique_input() { | 302 | fn add_custom_impl_for_unique_input() { |
307 | check_assist( | 303 | check_assist( |
308 | replace_derive_with_manual_impl, | 304 | replace_derive_with_manual_impl, |
309 | " | 305 | r#" |
310 | #[derive(Debu$0g)] | 306 | #[derive(Debu$0g)] |
311 | struct Foo { | 307 | struct Foo { |
312 | bar: String, | 308 | bar: String, |
313 | } | 309 | } |
314 | ", | 310 | "#, |
315 | " | 311 | r#" |
316 | struct Foo { | 312 | struct Foo { |
317 | bar: String, | 313 | bar: String, |
318 | } | 314 | } |
@@ -320,7 +316,7 @@ struct Foo { | |||
320 | impl Debug for Foo { | 316 | impl Debug for Foo { |
321 | $0 | 317 | $0 |
322 | } | 318 | } |
323 | ", | 319 | "#, |
324 | ) | 320 | ) |
325 | } | 321 | } |
326 | 322 | ||
@@ -328,13 +324,13 @@ impl Debug for Foo { | |||
328 | fn add_custom_impl_for_with_visibility_modifier() { | 324 | fn add_custom_impl_for_with_visibility_modifier() { |
329 | check_assist( | 325 | check_assist( |
330 | replace_derive_with_manual_impl, | 326 | replace_derive_with_manual_impl, |
331 | " | 327 | r#" |
332 | #[derive(Debug$0)] | 328 | #[derive(Debug$0)] |
333 | pub struct Foo { | 329 | pub struct Foo { |
334 | bar: String, | 330 | bar: String, |
335 | } | 331 | } |
336 | ", | 332 | "#, |
337 | " | 333 | r#" |
338 | pub struct Foo { | 334 | pub struct Foo { |
339 | bar: String, | 335 | bar: String, |
340 | } | 336 | } |
@@ -342,7 +338,7 @@ pub struct Foo { | |||
342 | impl Debug for Foo { | 338 | impl Debug for Foo { |
343 | $0 | 339 | $0 |
344 | } | 340 | } |
345 | ", | 341 | "#, |
346 | ) | 342 | ) |
347 | } | 343 | } |
348 | 344 | ||
@@ -350,18 +346,18 @@ impl Debug for Foo { | |||
350 | fn add_custom_impl_when_multiple_inputs() { | 346 | fn add_custom_impl_when_multiple_inputs() { |
351 | check_assist( | 347 | check_assist( |
352 | replace_derive_with_manual_impl, | 348 | replace_derive_with_manual_impl, |
353 | " | 349 | r#" |
354 | #[derive(Display, Debug$0, Serialize)] | 350 | #[derive(Display, Debug$0, Serialize)] |
355 | struct Foo {} | 351 | struct Foo {} |
356 | ", | 352 | "#, |
357 | " | 353 | r#" |
358 | #[derive(Display, Serialize)] | 354 | #[derive(Display, Serialize)] |
359 | struct Foo {} | 355 | struct Foo {} |
360 | 356 | ||
361 | impl Debug for Foo { | 357 | impl Debug for Foo { |
362 | $0 | 358 | $0 |
363 | } | 359 | } |
364 | ", | 360 | "#, |
365 | ) | 361 | ) |
366 | } | 362 | } |
367 | 363 | ||
@@ -369,10 +365,10 @@ impl Debug for Foo { | |||
369 | fn test_ignore_derive_macro_without_input() { | 365 | fn test_ignore_derive_macro_without_input() { |
370 | check_assist_not_applicable( | 366 | check_assist_not_applicable( |
371 | replace_derive_with_manual_impl, | 367 | replace_derive_with_manual_impl, |
372 | " | 368 | r#" |
373 | #[derive($0)] | 369 | #[derive($0)] |
374 | struct Foo {} | 370 | struct Foo {} |
375 | ", | 371 | "#, |
376 | ) | 372 | ) |
377 | } | 373 | } |
378 | 374 | ||
@@ -380,18 +376,18 @@ struct Foo {} | |||
380 | fn test_ignore_if_cursor_on_param() { | 376 | fn test_ignore_if_cursor_on_param() { |
381 | check_assist_not_applicable( | 377 | check_assist_not_applicable( |
382 | replace_derive_with_manual_impl, | 378 | replace_derive_with_manual_impl, |
383 | " | 379 | r#" |
384 | #[derive$0(Debug)] | 380 | #[derive$0(Debug)] |
385 | struct Foo {} | 381 | struct Foo {} |
386 | ", | 382 | "#, |
387 | ); | 383 | ); |
388 | 384 | ||
389 | check_assist_not_applicable( | 385 | check_assist_not_applicable( |
390 | replace_derive_with_manual_impl, | 386 | replace_derive_with_manual_impl, |
391 | " | 387 | r#" |
392 | #[derive(Debug)$0] | 388 | #[derive(Debug)$0] |
393 | struct Foo {} | 389 | struct Foo {} |
394 | ", | 390 | "#, |
395 | ) | 391 | ) |
396 | } | 392 | } |
397 | 393 | ||
@@ -399,10 +395,22 @@ struct Foo {} | |||
399 | fn test_ignore_if_not_derive() { | 395 | fn test_ignore_if_not_derive() { |
400 | check_assist_not_applicable( | 396 | check_assist_not_applicable( |
401 | replace_derive_with_manual_impl, | 397 | replace_derive_with_manual_impl, |
402 | " | 398 | r#" |
403 | #[allow(non_camel_$0case_types)] | 399 | #[allow(non_camel_$0case_types)] |
404 | struct Foo {} | 400 | struct Foo {} |
405 | ", | 401 | "#, |
406 | ) | 402 | ) |
407 | } | 403 | } |
404 | |||
405 | #[test] | ||
406 | fn works_at_start_of_file() { | ||
407 | cov_mark::check!(outside_of_attr_args); | ||
408 | check_assist_not_applicable( | ||
409 | replace_derive_with_manual_impl, | ||
410 | r#" | ||
411 | $0#[derive(Debug)] | ||
412 | struct S; | ||
413 | "#, | ||
414 | ); | ||
415 | } | ||
408 | } | 416 | } |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3694f468f..8996c1b61 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -134,6 +134,7 @@ mod handlers { | |||
134 | mod generate_default_from_enum_variant; | 134 | mod generate_default_from_enum_variant; |
135 | mod generate_default_from_new; | 135 | mod generate_default_from_new; |
136 | mod generate_is_empty_from_len; | 136 | mod generate_is_empty_from_len; |
137 | mod generate_deref; | ||
137 | mod generate_derive; | 138 | mod generate_derive; |
138 | mod generate_enum_is_method; | 139 | mod generate_enum_is_method; |
139 | mod generate_enum_projection_method; | 140 | mod generate_enum_projection_method; |
@@ -201,6 +202,7 @@ mod handlers { | |||
201 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 202 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
202 | generate_default_from_new::generate_default_from_new, | 203 | generate_default_from_new::generate_default_from_new, |
203 | generate_is_empty_from_len::generate_is_empty_from_len, | 204 | generate_is_empty_from_len::generate_is_empty_from_len, |
205 | generate_deref::generate_deref, | ||
204 | generate_derive::generate_derive, | 206 | generate_derive::generate_derive, |
205 | generate_enum_is_method::generate_enum_is_method, | 207 | generate_enum_is_method::generate_enum_is_method, |
206 | generate_enum_projection_method::generate_enum_as_method, | 208 | generate_enum_projection_method::generate_enum_as_method, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 60e3a1474..49533e7d2 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -193,6 +193,7 @@ fn assist_order_field_struct() { | |||
193 | let mut assists = assists.iter(); | 193 | let mut assists = assists.iter(); |
194 | 194 | ||
195 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
196 | assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`"); | ||
196 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); | 197 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); |
197 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | 198 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); |
198 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); | 199 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 27a22ca10..41559b43a 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -552,6 +552,33 @@ impl Default for Example { | |||
552 | } | 552 | } |
553 | 553 | ||
554 | #[test] | 554 | #[test] |
555 | fn doctest_generate_deref() { | ||
556 | check_doc_test( | ||
557 | "generate_deref", | ||
558 | r#####" | ||
559 | struct A; | ||
560 | struct B { | ||
561 | $0a: A | ||
562 | } | ||
563 | "#####, | ||
564 | r#####" | ||
565 | struct A; | ||
566 | struct B { | ||
567 | a: A | ||
568 | } | ||
569 | |||
570 | impl std::ops::Deref for B { | ||
571 | type Target = A; | ||
572 | |||
573 | fn deref(&self) -> &Self::Target { | ||
574 | &self.a | ||
575 | } | ||
576 | } | ||
577 | "#####, | ||
578 | ) | ||
579 | } | ||
580 | |||
581 | #[test] | ||
555 | fn doctest_generate_derive() { | 582 | fn doctest_generate_derive() { |
556 | check_doc_test( | 583 | check_doc_test( |
557 | "generate_derive", | 584 | "generate_derive", |