Add assist: add lifetime to type #7200
Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
parent
cd532e615a
commit
557cf513fa
217
crates/assists/src/handlers/add_lifetime_to_type.rs
Normal file
217
crates/assists/src/handlers/add_lifetime_to_type.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
use ast::FieldList;
|
||||||
|
use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner, RefType, Type};
|
||||||
|
|
||||||
|
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||||
|
|
||||||
|
// Assist: add_lifetime_to_type
|
||||||
|
//
|
||||||
|
// Adds a new lifetime to a struct, enum or union.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// struct Point$0 {
|
||||||
|
// x: &u32,
|
||||||
|
// y: u32,
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// ->
|
||||||
|
// ```
|
||||||
|
// struct Point<'a> {
|
||||||
|
// x: &'a u32,
|
||||||
|
// y: u32,
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||||
|
let node = ctx.find_node_at_offset::<ast::AdtDef>()?;
|
||||||
|
let has_lifetime = node
|
||||||
|
.generic_param_list()
|
||||||
|
.map(|gen_list| gen_list.lifetime_params().count() > 0)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if has_lifetime {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ref_types = fetch_borrowed_types(&node)?;
|
||||||
|
let target = node.syntax().text_range();
|
||||||
|
|
||||||
|
acc.add(
|
||||||
|
AssistId("add_lifetime_to_type", AssistKind::Generate),
|
||||||
|
"Add lifetime`",
|
||||||
|
target,
|
||||||
|
|builder| {
|
||||||
|
match node.generic_param_list() {
|
||||||
|
Some(gen_param) => {
|
||||||
|
if let Some(left_angle) = gen_param.l_angle_token() {
|
||||||
|
builder.insert(left_angle.text_range().end(), "'a, ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if let Some(name) = node.name() {
|
||||||
|
builder.insert(name.syntax().text_range().end(), "<'a>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ref_type in ref_types {
|
||||||
|
if let Some(amp_token) = ref_type.amp_token() {
|
||||||
|
builder.insert(amp_token.text_range().end(), "'a ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_borrowed_types(node: &ast::AdtDef) -> Option<Vec<RefType>> {
|
||||||
|
let ref_types: Vec<RefType> = match node {
|
||||||
|
ast::AdtDef::Enum(enum_) => {
|
||||||
|
let variant_list = enum_.variant_list()?;
|
||||||
|
variant_list
|
||||||
|
.variants()
|
||||||
|
.filter_map(|variant| {
|
||||||
|
let field_list = variant.field_list()?;
|
||||||
|
|
||||||
|
find_ref_types_from_field_list(&field_list)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
ast::AdtDef::Struct(strukt) => {
|
||||||
|
let field_list = strukt.field_list()?;
|
||||||
|
find_ref_types_from_field_list(&field_list)?
|
||||||
|
}
|
||||||
|
ast::AdtDef::Union(un) => {
|
||||||
|
let record_field_list = un.record_field_list()?;
|
||||||
|
record_field_list
|
||||||
|
.fields()
|
||||||
|
.filter_map(|r_field| {
|
||||||
|
if let Type::RefType(ref_type) = r_field.ty()? {
|
||||||
|
if ref_type.lifetime().is_none() {
|
||||||
|
return Some(ref_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ref_types.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ref_types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType>> {
|
||||||
|
let ref_types: Vec<RefType> = match field_list {
|
||||||
|
ast::FieldList::RecordFieldList(record_list) => record_list
|
||||||
|
.fields()
|
||||||
|
.filter_map(|f| {
|
||||||
|
if let Type::RefType(ref_type) = f.ty()? {
|
||||||
|
if ref_type.lifetime().is_none() {
|
||||||
|
return Some(ref_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
|
||||||
|
.fields()
|
||||||
|
.filter_map(|f| {
|
||||||
|
if let Type::RefType(ref_type) = f.ty()? {
|
||||||
|
if ref_type.lifetime().is_none() {
|
||||||
|
return Some(ref_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if ref_types.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ref_types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_lifetime_to_struct() {
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"struct Foo$0 { a: &i32 }",
|
||||||
|
"struct Foo<'a> { a: &'a i32 }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"struct Foo$0 { a: &i32, b: &usize }",
|
||||||
|
"struct Foo<'a> { a: &'a i32, b: &'a usize }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"struct Foo<T>$0 { a: &T, b: usize }",
|
||||||
|
"struct Foo<'a, T> { a: &'a T, b: usize }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a>$0 { a: &'a i32 }");
|
||||||
|
check_assist_not_applicable(add_lifetime_to_type, "struct Foo$0 { a: &'a i32 }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_lifetime_to_enum() {
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"enum Foo$0 { Bar { a: i32 }, Other, Tuple(u32, &u32)}",
|
||||||
|
"enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"enum Foo$0 { Bar { a: &i32 }}",
|
||||||
|
"enum Foo<'a> { Bar { a: &'a i32 }}",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"enum Foo<T>$0 { Bar { a: &i32, b: &T }}",
|
||||||
|
"enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist_not_applicable(add_lifetime_to_type, "enum Foo<'a>$0 { Bar { a: &'a i32 }}");
|
||||||
|
check_assist_not_applicable(add_lifetime_to_type, "enum Foo$0 { Bar, Misc }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_lifetime_to_union() {
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"union Foo$0 { a: &i32 }",
|
||||||
|
"union Foo<'a> { a: &'a i32 }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"union Foo$0 { a: &i32, b: &usize }",
|
||||||
|
"union Foo<'a> { a: &'a i32, b: &'a usize }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
add_lifetime_to_type,
|
||||||
|
"union Foo<T>$0 { a: &T, b: usize }",
|
||||||
|
"union Foo<'a, T> { a: &'a T, b: usize }",
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a>$0 { a: &'a i32 }");
|
||||||
|
}
|
||||||
|
}
|
@ -108,6 +108,7 @@ mod handlers {
|
|||||||
pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
|
pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
|
||||||
|
|
||||||
mod add_explicit_type;
|
mod add_explicit_type;
|
||||||
|
mod add_lifetime_to_type;
|
||||||
mod add_missing_impl_members;
|
mod add_missing_impl_members;
|
||||||
mod add_turbo_fish;
|
mod add_turbo_fish;
|
||||||
mod apply_demorgan;
|
mod apply_demorgan;
|
||||||
@ -164,6 +165,7 @@ pub(crate) fn all() -> &'static [Handler] {
|
|||||||
&[
|
&[
|
||||||
// These are alphabetic for the foolish consistency
|
// These are alphabetic for the foolish consistency
|
||||||
add_explicit_type::add_explicit_type,
|
add_explicit_type::add_explicit_type,
|
||||||
|
add_lifetime_to_type::add_lifetime_to_type,
|
||||||
add_turbo_fish::add_turbo_fish,
|
add_turbo_fish::add_turbo_fish,
|
||||||
apply_demorgan::apply_demorgan,
|
apply_demorgan::apply_demorgan,
|
||||||
auto_import::auto_import,
|
auto_import::auto_import,
|
||||||
|
@ -103,6 +103,25 @@ fn foo(&self) -> u32 {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn doctest_add_lifetime_to_type() {
|
||||||
|
check_doc_test(
|
||||||
|
"add_lifetime_to_type",
|
||||||
|
r#####"
|
||||||
|
struct Point$0 {
|
||||||
|
x: &u32,
|
||||||
|
y: u32,
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
r#####"
|
||||||
|
struct Point<'a> {
|
||||||
|
x: &'a u32,
|
||||||
|
y: u32,
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doctest_add_turbo_fish() {
|
fn doctest_add_turbo_fish() {
|
||||||
check_doc_test(
|
check_doc_test(
|
||||||
|
Loading…
Reference in New Issue
Block a user