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>
This commit is contained in:
commit
80f522091a
@ -1,5 +1,7 @@
|
|||||||
|
use either::Either;
|
||||||
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, edit::IndentLevel, AstNode},
|
ast::{self, edit::IndentLevel, AstNode, GenericParamsOwner, NameOwner},
|
||||||
match_ast,
|
match_ast,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,41 +29,158 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Opti
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = ctx.find_node_at_range::<ast::Type>()?;
|
let ty = ctx.find_node_at_range::<ast::Type>()?;
|
||||||
let item = ctx.find_node_at_offset::<ast::Item>()?;
|
let item = ty.syntax().ancestors().find_map(ast::Item::cast)?;
|
||||||
let insert = match_ast! {
|
let assoc_owner = item.syntax().ancestors().nth(2).and_then(|it| {
|
||||||
match (item.syntax().parent()?) {
|
match_ast! {
|
||||||
ast::AssocItemList(it) => it.syntax().parent()?,
|
match it {
|
||||||
_ => item.syntax().clone(),
|
ast::Trait(tr) => Some(Either::Left(tr)),
|
||||||
|
ast::Impl(impl_) => Some(Either::Right(impl_)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
let indent = IndentLevel::from_node(&insert);
|
let node = assoc_owner.as_ref().map_or_else(
|
||||||
let insert = insert.text_range().start();
|
|| item.syntax(),
|
||||||
let target = node.syntax().text_range();
|
|impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax),
|
||||||
|
);
|
||||||
|
let insert_pos = node.text_range().start();
|
||||||
|
let target = ty.syntax().text_range();
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("extract_type_alias", AssistKind::RefactorExtract),
|
AssistId("extract_type_alias", AssistKind::RefactorExtract),
|
||||||
"Extract type as type alias",
|
"Extract type as type alias",
|
||||||
target,
|
target,
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.edit_file(ctx.frange.file_id);
|
let mut known_generics = match item.generic_param_list() {
|
||||||
builder.replace(target, "Type");
|
Some(it) => it.generic_params().collect(),
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
if let Some(it) = assoc_owner.as_ref().and_then(|it| match it {
|
||||||
|
Either::Left(it) => it.generic_param_list(),
|
||||||
|
Either::Right(it) => it.generic_param_list(),
|
||||||
|
}) {
|
||||||
|
known_generics.extend(it.generic_params());
|
||||||
|
}
|
||||||
|
let generics = collect_used_generics(&ty, &known_generics);
|
||||||
|
|
||||||
|
let replacement = if !generics.is_empty() {
|
||||||
|
format!(
|
||||||
|
"Type<{}>",
|
||||||
|
generics.iter().format_with(", ", |generic, f| {
|
||||||
|
match generic {
|
||||||
|
ast::GenericParam::ConstParam(cp) => f(&cp.name().unwrap()),
|
||||||
|
ast::GenericParam::LifetimeParam(lp) => f(&lp.lifetime().unwrap()),
|
||||||
|
ast::GenericParam::TypeParam(tp) => f(&tp.name().unwrap()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::from("Type")
|
||||||
|
};
|
||||||
|
builder.replace(target, replacement);
|
||||||
|
|
||||||
|
let indent = IndentLevel::from_node(node);
|
||||||
|
let generics = if !generics.is_empty() {
|
||||||
|
format!("<{}>", generics.iter().format(", "))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
builder.insert_snippet(
|
builder.insert_snippet(
|
||||||
cap,
|
cap,
|
||||||
insert,
|
insert_pos,
|
||||||
format!("type $0Type = {};\n\n{}", node, indent),
|
format!("type $0Type{} = {};\n\n{}", generics, ty, indent),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
builder.insert(insert, format!("type Type = {};\n\n{}", node, indent));
|
builder.insert(
|
||||||
|
insert_pos,
|
||||||
|
format!("type Type{} = {};\n\n{}", generics, ty, indent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_used_generics<'gp>(
|
||||||
|
ty: &ast::Type,
|
||||||
|
known_generics: &'gp [ast::GenericParam],
|
||||||
|
) -> Vec<&'gp ast::GenericParam> {
|
||||||
|
// can't use a closure -> closure here cause lifetime inference fails for that
|
||||||
|
fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ {
|
||||||
|
move |gp: &&ast::GenericParam| match gp {
|
||||||
|
ast::GenericParam::LifetimeParam(lp) => {
|
||||||
|
lp.lifetime().map_or(false, |lt| lt.text() == text)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut generics = Vec::new();
|
||||||
|
ty.walk(&mut |ty| match ty {
|
||||||
|
ast::Type::PathType(ty) => {
|
||||||
|
if let Some(path) = ty.path() {
|
||||||
|
if let Some(name_ref) = path.as_single_name_ref() {
|
||||||
|
if let Some(param) = known_generics.iter().find(|gp| {
|
||||||
|
match gp {
|
||||||
|
ast::GenericParam::ConstParam(cp) => cp.name(),
|
||||||
|
ast::GenericParam::TypeParam(tp) => tp.name(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.map_or(false, |n| n.text() == name_ref.text())
|
||||||
|
}) {
|
||||||
|
generics.push(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generics.extend(
|
||||||
|
path.segments()
|
||||||
|
.filter_map(|seg| seg.generic_arg_list())
|
||||||
|
.flat_map(|it| it.generic_args())
|
||||||
|
.filter_map(|it| match it {
|
||||||
|
ast::GenericArg::LifetimeArg(lt) => {
|
||||||
|
let lt = lt.lifetime()?;
|
||||||
|
known_generics.iter().find(find_lifetime(<.text()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Type::ImplTraitType(impl_ty) => {
|
||||||
|
if let Some(it) = impl_ty.type_bound_list() {
|
||||||
|
generics.extend(
|
||||||
|
it.bounds()
|
||||||
|
.filter_map(|it| it.lifetime())
|
||||||
|
.filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Type::DynTraitType(dyn_ty) => {
|
||||||
|
if let Some(it) = dyn_ty.type_bound_list() {
|
||||||
|
generics.extend(
|
||||||
|
it.bounds()
|
||||||
|
.filter_map(|it| it.lifetime())
|
||||||
|
.filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Type::RefType(ref_) => generics.extend(
|
||||||
|
ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))),
|
||||||
|
),
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
// stable resort to lifetime, type, const
|
||||||
|
generics.sort_by_key(|gp| match gp {
|
||||||
|
ast::GenericParam::ConstParam(_) => 2,
|
||||||
|
ast::GenericParam::LifetimeParam(_) => 0,
|
||||||
|
ast::GenericParam::TypeParam(_) => 1,
|
||||||
|
});
|
||||||
|
generics
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||||
@ -216,4 +335,25 @@ fn f() -> Type {}
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generics() {
|
||||||
|
check_assist(
|
||||||
|
extract_type_alias,
|
||||||
|
r#"
|
||||||
|
struct Struct<const C: usize>;
|
||||||
|
impl<'outer, Outer, const OUTER: usize> () {
|
||||||
|
fn func<'inner, Inner, const INNER: usize>(_: $0&(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ())$0) {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Struct<const C: usize>;
|
||||||
|
type $0Type<'inner, 'outer, Outer, Inner, const INNER: usize, const OUTER: usize> = &(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ());
|
||||||
|
|
||||||
|
impl<'outer, Outer, const OUTER: usize> () {
|
||||||
|
fn func<'inner, Inner, const INNER: usize>(_: Type<'inner, 'outer, Outer, Inner, INNER, OUTER>) {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,10 @@
|
|||||||
use rowan::{GreenNodeData, GreenTokenData, WalkEvent};
|
use rowan::{GreenNodeData, GreenTokenData, WalkEvent};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{self, support, AstChildren, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode},
|
ast::{
|
||||||
|
self, support, AstChildren, AstNode, AstToken, AttrsOwner, GenericParamsOwner, NameOwner,
|
||||||
|
SyntaxNode,
|
||||||
|
},
|
||||||
NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
|
NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -593,6 +596,21 @@ pub fn kind(&self) -> StructKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ast::Item {
|
||||||
|
pub fn generic_param_list(&self) -> Option<ast::GenericParamList> {
|
||||||
|
match self {
|
||||||
|
ast::Item::Enum(it) => it.generic_param_list(),
|
||||||
|
ast::Item::Fn(it) => it.generic_param_list(),
|
||||||
|
ast::Item::Impl(it) => it.generic_param_list(),
|
||||||
|
ast::Item::Struct(it) => it.generic_param_list(),
|
||||||
|
ast::Item::Trait(it) => it.generic_param_list(),
|
||||||
|
ast::Item::TypeAlias(it) => it.generic_param_list(),
|
||||||
|
ast::Item::Union(it) => it.generic_param_list(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum FieldKind {
|
pub enum FieldKind {
|
||||||
Name(ast::NameRef),
|
Name(ast::NameRef),
|
||||||
|
Loading…
Reference in New Issue
Block a user