diff options
Diffstat (limited to 'crates/ide_assists')
3 files changed, 429 insertions, 89 deletions
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index a30c4d04e..be927cc1c 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs | |||
@@ -53,7 +53,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
53 | .iter() | 53 | .iter() |
54 | .filter_map(ast::MatchArm::pat) | 54 | .filter_map(ast::MatchArm::pat) |
55 | .flat_map(|pat| match pat { | 55 | .flat_map(|pat| match pat { |
56 | // Special casee OrPat as separate top-level pats | 56 | // Special case OrPat as separate top-level pats |
57 | Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), | 57 | Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), |
58 | _ => Either::Right(iter::once(pat)), | 58 | _ => Either::Right(iter::once(pat)), |
59 | }) | 59 | }) |
@@ -72,7 +72,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) | 72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
73 | .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())) |
74 | .collect::<Vec<_>>(); | 74 | .collect::<Vec<_>>(); |
75 | 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 | { | ||
76 | // Match `Some` variant first. | 80 | // Match `Some` variant first. |
77 | cov_mark::hit!(option_order); | 81 | cov_mark::hit!(option_order); |
78 | variants.reverse() | 82 | variants.reverse() |
@@ -151,49 +155,99 @@ fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { | |||
151 | } | 155 | } |
152 | } | 156 | } |
153 | 157 | ||
154 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { | 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) | ||
173 | } | ||
174 | |||
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> { | ||
155 | 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() { |
156 | Some(Adt::Enum(e)) => Some(e), | 190 | Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), |
157 | _ => None, | 191 | _ => { |
192 | if ty.is_bool() { | ||
193 | Some(ExtendedEnum::Bool) | ||
194 | } else { | ||
195 | None | ||
196 | } | ||
197 | } | ||
158 | }) | 198 | }) |
159 | } | 199 | } |
160 | 200 | ||
161 | fn resolve_tuple_of_enum_def( | 201 | fn resolve_tuple_of_enum_def( |
162 | sema: &Semantics<RootDatabase>, | 202 | sema: &Semantics<RootDatabase>, |
163 | expr: &ast::Expr, | 203 | expr: &ast::Expr, |
164 | ) -> Option<Vec<hir::Enum>> { | 204 | ) -> Option<Vec<ExtendedEnum>> { |
165 | sema.type_of_expr(&expr)? | 205 | sema.type_of_expr(&expr)? |
166 | .tuple_fields(sema.db) | 206 | .tuple_fields(sema.db) |
167 | .iter() | 207 | .iter() |
168 | .map(|ty| { | 208 | .map(|ty| { |
169 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | 209 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { |
170 | Some(Adt::Enum(e)) => Some(e), | 210 | Some(Adt::Enum(e)) => Some(lift_enum(e)), |
171 | // 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 |
172 | // 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 |
173 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. | 213 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. |
174 | _ => None, | 214 | _ => { |
215 | if ty.is_bool() { | ||
216 | Some(ExtendedEnum::Bool) | ||
217 | } else { | ||
218 | None | ||
219 | } | ||
220 | } | ||
175 | }) | 221 | }) |
176 | }) | 222 | }) |
177 | .collect() | 223 | .collect() |
178 | } | 224 | } |
179 | 225 | ||
180 | 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> { |
181 | 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 | }; | ||
182 | 245 | ||
183 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | 246 | Some(pat) |
184 | let pat: ast::Pat = match var.source(db)?.value.kind() { | ||
185 | ast::StructKind::Tuple(field_list) => { | ||
186 | let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); | ||
187 | make::tuple_struct_pat(path, pats).into() | ||
188 | } | ||
189 | ast::StructKind::Record(field_list) => { | ||
190 | let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); | ||
191 | make::record_pat(path, pats).into() | ||
192 | } | 247 | } |
193 | ast::StructKind::Unit => make::path_pat(path), | 248 | ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), |
194 | }; | 249 | ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), |
195 | 250 | } | |
196 | Some(pat) | ||
197 | } | 251 | } |
198 | 252 | ||
199 | #[cfg(test)] | 253 | #[cfg(test)] |
@@ -226,6 +280,21 @@ mod tests { | |||
226 | } | 280 | } |
227 | 281 | ||
228 | #[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] | ||
229 | fn tuple_of_non_enum() { | 298 | fn tuple_of_non_enum() { |
230 | // 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 |
231 | // in the future | 300 | // in the future |
@@ -241,6 +310,113 @@ mod tests { | |||
241 | } | 310 | } |
242 | 311 | ||
243 | #[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] | ||
244 | fn partial_fill_record_tuple() { | 420 | fn partial_fill_record_tuple() { |
245 | check_assist( | 421 | check_assist( |
246 | fill_match_arms, | 422 | fill_match_arms, |
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/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index 870a8d4ff..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 | |||
@@ -47,6 +47,11 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
47 | return None; | 47 | return None; |
48 | } | 48 | } |
49 | 49 | ||
50 | if !args.syntax().text_range().contains(ctx.offset()) { | ||
51 | cov_mark::hit!(outside_of_attr_args); | ||
52 | return None; | ||
53 | } | ||
54 | |||
50 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; | 55 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; |
51 | let trait_name = trait_token.text(); | 56 | let trait_name = trait_token.text(); |
52 | 57 | ||
@@ -207,7 +212,7 @@ mod tests { | |||
207 | fn add_custom_impl_debug() { | 212 | fn add_custom_impl_debug() { |
208 | check_assist( | 213 | check_assist( |
209 | replace_derive_with_manual_impl, | 214 | replace_derive_with_manual_impl, |
210 | " | 215 | r#" |
211 | mod fmt { | 216 | mod fmt { |
212 | pub struct Error; | 217 | pub struct Error; |
213 | pub type Result = Result<(), Error>; | 218 | pub type Result = Result<(), Error>; |
@@ -221,8 +226,8 @@ mod fmt { | |||
221 | struct Foo { | 226 | struct Foo { |
222 | bar: String, | 227 | bar: String, |
223 | } | 228 | } |
224 | ", | 229 | "#, |
225 | " | 230 | r#" |
226 | mod fmt { | 231 | mod fmt { |
227 | pub struct Error; | 232 | pub struct Error; |
228 | pub type Result = Result<(), Error>; | 233 | pub type Result = Result<(), Error>; |
@@ -241,14 +246,14 @@ impl fmt::Debug for Foo { | |||
241 | ${0:todo!()} | 246 | ${0:todo!()} |
242 | } | 247 | } |
243 | } | 248 | } |
244 | ", | 249 | "#, |
245 | ) | 250 | ) |
246 | } | 251 | } |
247 | #[test] | 252 | #[test] |
248 | fn add_custom_impl_all() { | 253 | fn add_custom_impl_all() { |
249 | check_assist( | 254 | check_assist( |
250 | replace_derive_with_manual_impl, | 255 | replace_derive_with_manual_impl, |
251 | " | 256 | r#" |
252 | mod foo { | 257 | mod foo { |
253 | pub trait Bar { | 258 | pub trait Bar { |
254 | type Qux; | 259 | type Qux; |
@@ -263,8 +268,8 @@ mod foo { | |||
263 | struct Foo { | 268 | struct Foo { |
264 | bar: String, | 269 | bar: String, |
265 | } | 270 | } |
266 | ", | 271 | "#, |
267 | " | 272 | r#" |
268 | mod foo { | 273 | mod foo { |
269 | pub trait Bar { | 274 | pub trait Bar { |
270 | type Qux; | 275 | type Qux; |
@@ -290,20 +295,20 @@ impl foo::Bar for Foo { | |||
290 | todo!() | 295 | todo!() |
291 | } | 296 | } |
292 | } | 297 | } |
293 | ", | 298 | "#, |
294 | ) | 299 | ) |
295 | } | 300 | } |
296 | #[test] | 301 | #[test] |
297 | fn add_custom_impl_for_unique_input() { | 302 | fn add_custom_impl_for_unique_input() { |
298 | check_assist( | 303 | check_assist( |
299 | replace_derive_with_manual_impl, | 304 | replace_derive_with_manual_impl, |
300 | " | 305 | r#" |
301 | #[derive(Debu$0g)] | 306 | #[derive(Debu$0g)] |
302 | struct Foo { | 307 | struct Foo { |
303 | bar: String, | 308 | bar: String, |
304 | } | 309 | } |
305 | ", | 310 | "#, |
306 | " | 311 | r#" |
307 | struct Foo { | 312 | struct Foo { |
308 | bar: String, | 313 | bar: String, |
309 | } | 314 | } |
@@ -311,7 +316,7 @@ struct Foo { | |||
311 | impl Debug for Foo { | 316 | impl Debug for Foo { |
312 | $0 | 317 | $0 |
313 | } | 318 | } |
314 | ", | 319 | "#, |
315 | ) | 320 | ) |
316 | } | 321 | } |
317 | 322 | ||
@@ -319,13 +324,13 @@ impl Debug for Foo { | |||
319 | fn add_custom_impl_for_with_visibility_modifier() { | 324 | fn add_custom_impl_for_with_visibility_modifier() { |
320 | check_assist( | 325 | check_assist( |
321 | replace_derive_with_manual_impl, | 326 | replace_derive_with_manual_impl, |
322 | " | 327 | r#" |
323 | #[derive(Debug$0)] | 328 | #[derive(Debug$0)] |
324 | pub struct Foo { | 329 | pub struct Foo { |
325 | bar: String, | 330 | bar: String, |
326 | } | 331 | } |
327 | ", | 332 | "#, |
328 | " | 333 | r#" |
329 | pub struct Foo { | 334 | pub struct Foo { |
330 | bar: String, | 335 | bar: String, |
331 | } | 336 | } |
@@ -333,7 +338,7 @@ pub struct Foo { | |||
333 | impl Debug for Foo { | 338 | impl Debug for Foo { |
334 | $0 | 339 | $0 |
335 | } | 340 | } |
336 | ", | 341 | "#, |
337 | ) | 342 | ) |
338 | } | 343 | } |
339 | 344 | ||
@@ -341,18 +346,18 @@ impl Debug for Foo { | |||
341 | fn add_custom_impl_when_multiple_inputs() { | 346 | fn add_custom_impl_when_multiple_inputs() { |
342 | check_assist( | 347 | check_assist( |
343 | replace_derive_with_manual_impl, | 348 | replace_derive_with_manual_impl, |
344 | " | 349 | r#" |
345 | #[derive(Display, Debug$0, Serialize)] | 350 | #[derive(Display, Debug$0, Serialize)] |
346 | struct Foo {} | 351 | struct Foo {} |
347 | ", | 352 | "#, |
348 | " | 353 | r#" |
349 | #[derive(Display, Serialize)] | 354 | #[derive(Display, Serialize)] |
350 | struct Foo {} | 355 | struct Foo {} |
351 | 356 | ||
352 | impl Debug for Foo { | 357 | impl Debug for Foo { |
353 | $0 | 358 | $0 |
354 | } | 359 | } |
355 | ", | 360 | "#, |
356 | ) | 361 | ) |
357 | } | 362 | } |
358 | 363 | ||
@@ -360,10 +365,10 @@ impl Debug for Foo { | |||
360 | fn test_ignore_derive_macro_without_input() { | 365 | fn test_ignore_derive_macro_without_input() { |
361 | check_assist_not_applicable( | 366 | check_assist_not_applicable( |
362 | replace_derive_with_manual_impl, | 367 | replace_derive_with_manual_impl, |
363 | " | 368 | r#" |
364 | #[derive($0)] | 369 | #[derive($0)] |
365 | struct Foo {} | 370 | struct Foo {} |
366 | ", | 371 | "#, |
367 | ) | 372 | ) |
368 | } | 373 | } |
369 | 374 | ||
@@ -371,18 +376,18 @@ struct Foo {} | |||
371 | fn test_ignore_if_cursor_on_param() { | 376 | fn test_ignore_if_cursor_on_param() { |
372 | check_assist_not_applicable( | 377 | check_assist_not_applicable( |
373 | replace_derive_with_manual_impl, | 378 | replace_derive_with_manual_impl, |
374 | " | 379 | r#" |
375 | #[derive$0(Debug)] | 380 | #[derive$0(Debug)] |
376 | struct Foo {} | 381 | struct Foo {} |
377 | ", | 382 | "#, |
378 | ); | 383 | ); |
379 | 384 | ||
380 | check_assist_not_applicable( | 385 | check_assist_not_applicable( |
381 | replace_derive_with_manual_impl, | 386 | replace_derive_with_manual_impl, |
382 | " | 387 | r#" |
383 | #[derive(Debug)$0] | 388 | #[derive(Debug)$0] |
384 | struct Foo {} | 389 | struct Foo {} |
385 | ", | 390 | "#, |
386 | ) | 391 | ) |
387 | } | 392 | } |
388 | 393 | ||
@@ -390,10 +395,22 @@ struct Foo {} | |||
390 | fn test_ignore_if_not_derive() { | 395 | fn test_ignore_if_not_derive() { |
391 | check_assist_not_applicable( | 396 | check_assist_not_applicable( |
392 | replace_derive_with_manual_impl, | 397 | replace_derive_with_manual_impl, |
393 | " | 398 | r#" |
394 | #[allow(non_camel_$0case_types)] | 399 | #[allow(non_camel_$0case_types)] |
395 | struct Foo {} | 400 | struct Foo {} |
396 | ", | 401 | "#, |
397 | ) | 402 | ) |
398 | } | 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 | } | ||
399 | } | 416 | } |