2 use ide_db::syntax_helpers::node_ext::walk_ty;
4 ast::{self, edit::IndentLevel, make, AstNode, HasGenericParams, HasName},
8 use crate::{AssistContext, AssistId, AssistKind, Assists};
10 // Assist: extract_type_alias
12 // Extracts the selected type as a type alias.
16 // field: $0(u8, u8, u8)$0,
21 // type $0Type = (u8, u8, u8);
27 pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28 if ctx.has_empty_selection() {
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| {
37 ast::Trait(tr) => Some(Either::Left(tr)),
38 ast::Impl(impl_) => Some(Either::Right(impl_)),
43 let node = assoc_owner.as_ref().map_or_else(
45 |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax),
47 let insert_pos = node.text_range().start();
48 let target = ty.syntax().text_range();
51 AssistId("extract_type_alias", AssistKind::RefactorExtract),
52 "Extract type as type alias",
55 let mut known_generics = match item.generic_param_list() {
56 Some(it) => it.generic_params().collect(),
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(),
63 known_generics.extend(it.generic_params());
65 let generics = collect_used_generics(&ty, &known_generics);
67 generics.map(|it| make::generic_param_list(it.into_iter().cloned()));
69 let ty_args = generic_params
71 .map_or(String::new(), |it| it.to_generic_args().to_string());
72 let replacement = format!("Type{ty_args}");
73 builder.replace(target, replacement);
75 let indent = IndentLevel::from_node(node);
76 let generic_params = generic_params.map_or(String::new(), |it| it.to_string());
77 match ctx.config.snippet_cap {
79 builder.insert_snippet(
82 format!("type $0Type{generic_params} = {ty};\n\n{indent}"),
88 format!("type Type{generic_params} = {ty};\n\n{indent}"),
96 fn collect_used_generics<'gp>(
98 known_generics: &'gp [ast::GenericParam],
99 ) -> Option<Vec<&'gp ast::GenericParam>> {
100 // can't use a closure -> closure here cause lifetime inference fails for that
101 fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ {
102 move |gp: &&ast::GenericParam| match gp {
103 ast::GenericParam::LifetimeParam(lp) => {
104 lp.lifetime().map_or(false, |lt| lt.text() == text)
110 let mut generics = Vec::new();
111 walk_ty(ty, &mut |ty| match ty {
112 ast::Type::PathType(ty) => {
113 if let Some(path) = ty.path() {
114 if let Some(name_ref) = path.as_single_name_ref() {
115 if let Some(param) = known_generics.iter().find(|gp| {
117 ast::GenericParam::ConstParam(cp) => cp.name(),
118 ast::GenericParam::TypeParam(tp) => tp.name(),
121 .map_or(false, |n| n.text() == name_ref.text())
123 generics.push(param);
128 .filter_map(|seg| seg.generic_arg_list())
129 .flat_map(|it| it.generic_args())
130 .filter_map(|it| match it {
131 ast::GenericArg::LifetimeArg(lt) => {
132 let lt = lt.lifetime()?;
133 known_generics.iter().find(find_lifetime(<.text()))
140 ast::Type::ImplTraitType(impl_ty) => {
141 if let Some(it) = impl_ty.type_bound_list() {
144 .filter_map(|it| it.lifetime())
145 .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))),
149 ast::Type::DynTraitType(dyn_ty) => {
150 if let Some(it) = dyn_ty.type_bound_list() {
153 .filter_map(|it| it.lifetime())
154 .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))),
158 ast::Type::RefType(ref_) => generics.extend(
159 ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))),
161 ast::Type::ArrayType(ar) => {
162 if let Some(expr) = ar.expr() {
163 if let ast::Expr::PathExpr(p) = expr {
164 if let Some(path) = p.path() {
165 if let Some(name_ref) = path.as_single_name_ref() {
166 if let Some(param) = known_generics.iter().find(|gp| {
167 if let ast::GenericParam::ConstParam(cp) = gp {
168 cp.name().map_or(false, |n| n.text() == name_ref.text())
173 generics.push(param);
182 // stable resort to lifetime, type, const
183 generics.sort_by_key(|gp| match gp {
184 ast::GenericParam::ConstParam(_) => 2,
185 ast::GenericParam::LifetimeParam(_) => 0,
186 ast::GenericParam::TypeParam(_) => 1,
189 Some(generics).filter(|it| it.len() > 0)
194 use crate::tests::{check_assist, check_assist_not_applicable};
199 fn test_not_applicable_without_selection() {
200 check_assist_not_applicable(
204 field: $0(u8, u8, u8),
211 fn test_simple_types() {
230 fn test_generic_type_arg() {
253 fn test_inner_type_arg() {
259 v: Vec<Vec<$0Vec<u8>$0>>,
264 type $0Type = Vec<u8>;
274 fn test_extract_inner_type() {
293 fn extract_from_impl_or_trait() {
294 // When invoked in an impl/trait, extracted type alias should be placed next to the
295 // impl/trait, not inside.
300 fn f() -> $0(u8, u8)$0 {}
304 type $0Type = (u8, u8);
315 fn f() -> $0(u8, u8)$0 {}
319 type $0Type = (u8, u8);
352 struct Struct<const C: usize>;
353 impl<'outer, Outer, const OUTER: usize> () {
354 fn func<'inner, Inner, const INNER: usize>(_: $0&(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ())$0) {}
358 struct Struct<const C: usize>;
359 type $0Type<'inner, 'outer, Outer, Inner, const INNER: usize, const OUTER: usize> = &(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ());
361 impl<'outer, Outer, const OUTER: usize> () {
362 fn func<'inner, Inner, const INNER: usize>(_: Type<'inner, 'outer, Outer, Inner, INNER, OUTER>) {}
373 struct Foo<T, const N: usize>
381 type $0Type<T, const N: usize> = [T; N];
383 struct Foo<T, const N: usize>