Skip to content

Commit 80f5220

Browse files
bors[bot]Veykril
andauthored
Merge #9790
9790: fix: extract_type_alias extracts generics correctly r=Veykril a=Veykril Fixes #8335 bors r+ Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents 950efff + b6d5746 commit 80f5220

File tree

2 files changed

+175
-17
lines changed

2 files changed

+175
-17
lines changed

crates/ide_assists/src/handlers/extract_type_alias.rs

Lines changed: 156 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use either::Either;
2+
use itertools::Itertools;
13
use syntax::{
2-
ast::{self, edit::IndentLevel, AstNode},
4+
ast::{self, edit::IndentLevel, AstNode, GenericParamsOwner, NameOwner},
35
match_ast,
46
};
57

@@ -27,41 +29,158 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Opti
2729
return None;
2830
}
2931

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+
}
3641
}
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();
4149

4250
acc.add(
4351
AssistId("extract_type_alias", AssistKind::RefactorExtract),
4452
"Extract type as type alias",
4553
target,
4654
|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+
};
4989
match ctx.config.snippet_cap {
5090
Some(cap) => {
5191
builder.insert_snippet(
5292
cap,
53-
insert,
54-
format!("type $0Type = {};\n\n{}", node, indent),
93+
insert_pos,
94+
format!("type $0Type{} = {};\n\n{}", generics, ty, indent),
5595
);
5696
}
5797
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+
);
59102
}
60103
}
61104
},
62105
)
63106
}
64107

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(&lt.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(&lt.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(&lt.text()))),
167+
);
168+
}
169+
}
170+
ast::Type::RefType(ref_) => generics.extend(
171+
ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(&lt.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+
65184
#[cfg(test)]
66185
mod tests {
67186
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -216,4 +335,25 @@ mod m {
216335
"#,
217336
);
218337
}
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+
}
219359
}

crates/syntax/src/ast/node_ext.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use parser::SyntaxKind;
88
use rowan::{GreenNodeData, GreenTokenData, WalkEvent};
99

1010
use crate::{
11-
ast::{self, support, AstChildren, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode},
11+
ast::{
12+
self, support, AstChildren, AstNode, AstToken, AttrsOwner, GenericParamsOwner, NameOwner,
13+
SyntaxNode,
14+
},
1215
NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
1316
};
1417

@@ -593,6 +596,21 @@ impl ast::Variant {
593596
}
594597
}
595598

599+
impl ast::Item {
600+
pub fn generic_param_list(&self) -> Option<ast::GenericParamList> {
601+
match self {
602+
ast::Item::Enum(it) => it.generic_param_list(),
603+
ast::Item::Fn(it) => it.generic_param_list(),
604+
ast::Item::Impl(it) => it.generic_param_list(),
605+
ast::Item::Struct(it) => it.generic_param_list(),
606+
ast::Item::Trait(it) => it.generic_param_list(),
607+
ast::Item::TypeAlias(it) => it.generic_param_list(),
608+
ast::Item::Union(it) => it.generic_param_list(),
609+
_ => None,
610+
}
611+
}
612+
}
613+
596614
#[derive(Debug, Clone, PartialEq, Eq)]
597615
pub enum FieldKind {
598616
Name(ast::NameRef),

0 commit comments

Comments
 (0)