|
| 1 | +use either::Either; |
| 2 | +use itertools::Itertools; |
1 | 3 | use syntax::{
|
2 |
| - ast::{self, edit::IndentLevel, AstNode}, |
| 4 | + ast::{self, edit::IndentLevel, AstNode, GenericParamsOwner, NameOwner}, |
3 | 5 | match_ast,
|
4 | 6 | };
|
5 | 7 |
|
@@ -27,41 +29,158 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Opti
|
27 | 29 | return None;
|
28 | 30 | }
|
29 | 31 |
|
30 |
| - let node = ctx.find_node_at_range::<ast::Type>()?; |
31 |
| - let item = ctx.find_node_at_offset::<ast::Item>()?; |
32 |
| - let insert = match_ast! { |
33 |
| - match (item.syntax().parent()?) { |
34 |
| - ast::AssocItemList(it) => it.syntax().parent()?, |
35 |
| - _ => item.syntax().clone(), |
| 32 | + let ty = ctx.find_node_at_range::<ast::Type>()?; |
| 33 | + let item = ty.syntax().ancestors().find_map(ast::Item::cast)?; |
| 34 | + let assoc_owner = item.syntax().ancestors().nth(2).and_then(|it| { |
| 35 | + match_ast! { |
| 36 | + match it { |
| 37 | + ast::Trait(tr) => Some(Either::Left(tr)), |
| 38 | + ast::Impl(impl_) => Some(Either::Right(impl_)), |
| 39 | + _ => None, |
| 40 | + } |
36 | 41 | }
|
37 |
| - }; |
38 |
| - let indent = IndentLevel::from_node(&insert); |
39 |
| - let insert = insert.text_range().start(); |
40 |
| - let target = node.syntax().text_range(); |
| 42 | + }); |
| 43 | + let node = assoc_owner.as_ref().map_or_else( |
| 44 | + || item.syntax(), |
| 45 | + |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax), |
| 46 | + ); |
| 47 | + let insert_pos = node.text_range().start(); |
| 48 | + let target = ty.syntax().text_range(); |
41 | 49 |
|
42 | 50 | acc.add(
|
43 | 51 | AssistId("extract_type_alias", AssistKind::RefactorExtract),
|
44 | 52 | "Extract type as type alias",
|
45 | 53 | target,
|
46 | 54 | |builder| {
|
47 |
| - builder.edit_file(ctx.frange.file_id); |
48 |
| - builder.replace(target, "Type"); |
| 55 | + let mut known_generics = match item.generic_param_list() { |
| 56 | + Some(it) => it.generic_params().collect(), |
| 57 | + None => Vec::new(), |
| 58 | + }; |
| 59 | + if let Some(it) = assoc_owner.as_ref().and_then(|it| match it { |
| 60 | + Either::Left(it) => it.generic_param_list(), |
| 61 | + Either::Right(it) => it.generic_param_list(), |
| 62 | + }) { |
| 63 | + known_generics.extend(it.generic_params()); |
| 64 | + } |
| 65 | + let generics = collect_used_generics(&ty, &known_generics); |
| 66 | + |
| 67 | + let replacement = if !generics.is_empty() { |
| 68 | + format!( |
| 69 | + "Type<{}>", |
| 70 | + generics.iter().format_with(", ", |generic, f| { |
| 71 | + match generic { |
| 72 | + ast::GenericParam::ConstParam(cp) => f(&cp.name().unwrap()), |
| 73 | + ast::GenericParam::LifetimeParam(lp) => f(&lp.lifetime().unwrap()), |
| 74 | + ast::GenericParam::TypeParam(tp) => f(&tp.name().unwrap()), |
| 75 | + } |
| 76 | + }) |
| 77 | + ) |
| 78 | + } else { |
| 79 | + String::from("Type") |
| 80 | + }; |
| 81 | + builder.replace(target, replacement); |
| 82 | + |
| 83 | + let indent = IndentLevel::from_node(node); |
| 84 | + let generics = if !generics.is_empty() { |
| 85 | + format!("<{}>", generics.iter().format(", ")) |
| 86 | + } else { |
| 87 | + String::new() |
| 88 | + }; |
49 | 89 | match ctx.config.snippet_cap {
|
50 | 90 | Some(cap) => {
|
51 | 91 | builder.insert_snippet(
|
52 | 92 | cap,
|
53 |
| - insert, |
54 |
| - format!("type $0Type = {};\n\n{}", node, indent), |
| 93 | + insert_pos, |
| 94 | + format!("type $0Type{} = {};\n\n{}", generics, ty, indent), |
55 | 95 | );
|
56 | 96 | }
|
57 | 97 | None => {
|
58 |
| - builder.insert(insert, format!("type Type = {};\n\n{}", node, indent)); |
| 98 | + builder.insert( |
| 99 | + insert_pos, |
| 100 | + format!("type Type{} = {};\n\n{}", generics, ty, indent), |
| 101 | + ); |
59 | 102 | }
|
60 | 103 | }
|
61 | 104 | },
|
62 | 105 | )
|
63 | 106 | }
|
64 | 107 |
|
| 108 | +fn collect_used_generics<'gp>( |
| 109 | + ty: &ast::Type, |
| 110 | + known_generics: &'gp [ast::GenericParam], |
| 111 | +) -> Vec<&'gp ast::GenericParam> { |
| 112 | + // can't use a closure -> closure here cause lifetime inference fails for that |
| 113 | + fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ { |
| 114 | + move |gp: &&ast::GenericParam| match gp { |
| 115 | + ast::GenericParam::LifetimeParam(lp) => { |
| 116 | + lp.lifetime().map_or(false, |lt| lt.text() == text) |
| 117 | + } |
| 118 | + _ => false, |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + let mut generics = Vec::new(); |
| 123 | + ty.walk(&mut |ty| match ty { |
| 124 | + ast::Type::PathType(ty) => { |
| 125 | + if let Some(path) = ty.path() { |
| 126 | + if let Some(name_ref) = path.as_single_name_ref() { |
| 127 | + if let Some(param) = known_generics.iter().find(|gp| { |
| 128 | + match gp { |
| 129 | + ast::GenericParam::ConstParam(cp) => cp.name(), |
| 130 | + ast::GenericParam::TypeParam(tp) => tp.name(), |
| 131 | + _ => None, |
| 132 | + } |
| 133 | + .map_or(false, |n| n.text() == name_ref.text()) |
| 134 | + }) { |
| 135 | + generics.push(param); |
| 136 | + } |
| 137 | + } |
| 138 | + generics.extend( |
| 139 | + path.segments() |
| 140 | + .filter_map(|seg| seg.generic_arg_list()) |
| 141 | + .flat_map(|it| it.generic_args()) |
| 142 | + .filter_map(|it| match it { |
| 143 | + ast::GenericArg::LifetimeArg(lt) => { |
| 144 | + let lt = lt.lifetime()?; |
| 145 | + known_generics.iter().find(find_lifetime(<.text())) |
| 146 | + } |
| 147 | + _ => None, |
| 148 | + }), |
| 149 | + ); |
| 150 | + } |
| 151 | + } |
| 152 | + ast::Type::ImplTraitType(impl_ty) => { |
| 153 | + if let Some(it) = impl_ty.type_bound_list() { |
| 154 | + generics.extend( |
| 155 | + it.bounds() |
| 156 | + .filter_map(|it| it.lifetime()) |
| 157 | + .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), |
| 158 | + ); |
| 159 | + } |
| 160 | + } |
| 161 | + ast::Type::DynTraitType(dyn_ty) => { |
| 162 | + if let Some(it) = dyn_ty.type_bound_list() { |
| 163 | + generics.extend( |
| 164 | + it.bounds() |
| 165 | + .filter_map(|it| it.lifetime()) |
| 166 | + .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), |
| 167 | + ); |
| 168 | + } |
| 169 | + } |
| 170 | + ast::Type::RefType(ref_) => generics.extend( |
| 171 | + ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), |
| 172 | + ), |
| 173 | + _ => (), |
| 174 | + }); |
| 175 | + // stable resort to lifetime, type, const |
| 176 | + generics.sort_by_key(|gp| match gp { |
| 177 | + ast::GenericParam::ConstParam(_) => 2, |
| 178 | + ast::GenericParam::LifetimeParam(_) => 0, |
| 179 | + ast::GenericParam::TypeParam(_) => 1, |
| 180 | + }); |
| 181 | + generics |
| 182 | +} |
| 183 | + |
65 | 184 | #[cfg(test)]
|
66 | 185 | mod tests {
|
67 | 186 | use crate::tests::{check_assist, check_assist_not_applicable};
|
@@ -216,4 +335,25 @@ mod m {
|
216 | 335 | "#,
|
217 | 336 | );
|
218 | 337 | }
|
| 338 | + |
| 339 | + #[test] |
| 340 | + fn generics() { |
| 341 | + check_assist( |
| 342 | + extract_type_alias, |
| 343 | + r#" |
| 344 | +struct Struct<const C: usize>; |
| 345 | +impl<'outer, Outer, const OUTER: usize> () { |
| 346 | + fn func<'inner, Inner, const INNER: usize>(_: $0&(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ())$0) {} |
| 347 | +} |
| 348 | +"#, |
| 349 | + r#" |
| 350 | +struct Struct<const C: usize>; |
| 351 | +type $0Type<'inner, 'outer, Outer, Inner, const INNER: usize, const OUTER: usize> = &(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ()); |
| 352 | +
|
| 353 | +impl<'outer, Outer, const OUTER: usize> () { |
| 354 | + fn func<'inner, Inner, const INNER: usize>(_: Type<'inner, 'outer, Outer, Inner, INNER, OUTER>) {} |
| 355 | +} |
| 356 | +"#, |
| 357 | + ); |
| 358 | + } |
219 | 359 | }
|
0 commit comments