Skip to content

Commit 850c4ef

Browse files
committed
Trigger on non generic param path
1 parent ea9b42d commit 850c4ef

1 file changed

Lines changed: 53 additions & 45 deletions

File tree

crates/ide-assists/src/handlers/add_lifetime_to_type.rs

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use syntax::{
2-
SyntaxKind, SyntaxNode, SyntaxToken,
2+
SyntaxKind, SyntaxNode,
33
ast::{self, AstNode, HasGenericParams, HasName},
4+
match_ast,
45
};
56

67
use crate::{AssistContext, AssistId, Assists};
@@ -23,9 +24,8 @@ use crate::{AssistContext, AssistId, Assists};
2324
// }
2425
// ```
2526
pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
26-
if !trigger_assist(ctx) {
27-
return None;
28-
}
27+
// XXX: Maybe delete this and allow it to be triggered conveniently on ADT
28+
let _trigger = Missing::from_node(ctx.covering_element().ancestors().next()?, ctx)?;
2929
let node = ctx.find_node_at_offset::<ast::Adt>()?;
3030
let has_lifetime = node
3131
.generic_param_list()
@@ -35,7 +35,7 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_, '_
3535
return None;
3636
}
3737

38-
let changes = fetch_borrowed_types(&node)?;
38+
let changes = fetch_borrowed_types(&node, ctx)?;
3939
let target = node.syntax().text_range();
4040

4141
acc.add(AssistId::quick_fix("add_lifetime_to_type"), "Add lifetime", target, |builder| {
@@ -54,28 +54,23 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_, '_
5454

5555
for change in changes {
5656
match change {
57-
Change::Replace(it) => {
58-
builder.replace(it.text_range(), "'a");
57+
Missing::Lifetime(lt) => {
58+
builder.replace(lt.syntax().text_range(), "'a");
59+
}
60+
Missing::RefType(ref_type) => {
61+
if let Some(amp_token) = ref_type.amp_token() {
62+
builder.insert(amp_token.text_range().end(), "'a ");
63+
}
5964
}
60-
Change::Insert(it) => {
61-
builder.insert(it.text_range().end(), "'a ");
65+
Missing::PathType(path_type) => {
66+
builder.insert(path_type.syntax().text_range().end(), "<'a>");
6267
}
6368
}
6469
}
6570
})
6671
}
6772

68-
fn trigger_assist(ctx: &AssistContext<'_, '_>) -> bool {
69-
ctx.find_node_at_offset::<ast::RefType>()
70-
.is_some_and(|it| it.lifetime().is_none_or(|it| it.text() == "'_"))
71-
|| ctx
72-
.find_node_at_offset::<ast::PathType>()
73-
.map(ast::Type::from)
74-
.and_then(|it| it.generic_arg_list()?.lifetime_args().next()?.lifetime())
75-
.is_some_and(|it| it.text() == "'_")
76-
}
77-
78-
fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<Change>> {
73+
fn fetch_borrowed_types(node: &ast::Adt, ctx: &AssistContext<'_, '_>) -> Option<Vec<Missing>> {
7974
let ref_types: Vec<_> = match node {
8075
ast::Adt::Enum(enum_) => {
8176
let variant_list = enum_.variant_list()?;
@@ -84,58 +79,71 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<Change>> {
8479
.filter_map(|variant| {
8580
let field_list = variant.field_list()?;
8681

87-
find_ref_types_from_field_list(&field_list)
82+
find_ref_types_from_field_list(&field_list, ctx)
8883
})
8984
.flatten()
9085
.collect()
9186
}
9287
ast::Adt::Struct(strukt) => {
9388
let field_list = strukt.field_list()?;
94-
find_ref_types_from_field_list(&field_list)?
89+
find_ref_types_from_field_list(&field_list, ctx)?
9590
}
9691
ast::Adt::Union(un) => {
9792
let record_field_list = un.record_field_list()?;
98-
find_ref_types_from_field_list(&record_field_list.into())?
93+
find_ref_types_from_field_list(&record_field_list.into(), ctx)?
9994
}
10095
};
10196

10297
if ref_types.is_empty() { None } else { Some(ref_types) }
10398
}
10499

105-
fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option<Vec<Change>> {
100+
fn find_ref_types_from_field_list(
101+
field_list: &ast::FieldList,
102+
ctx: &AssistContext<'_, '_>,
103+
) -> Option<Vec<Missing>> {
106104
let ref_types: Vec<_> = match field_list {
107105
ast::FieldList::RecordFieldList(record_list) => {
108-
record_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect()
106+
record_list.fields().flat_map(|f| infer_lifetimes(f.syntax(), ctx)).collect()
109107
}
110108
ast::FieldList::TupleFieldList(tuple_field_list) => {
111-
tuple_field_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect()
109+
tuple_field_list.fields().flat_map(|f| infer_lifetimes(f.syntax(), ctx)).collect()
112110
}
113111
};
114112

115113
if ref_types.is_empty() { None } else { Some(ref_types) }
116114
}
117115

118-
enum Change {
119-
Replace(SyntaxToken),
120-
Insert(SyntaxToken),
116+
enum Missing {
117+
RefType(ast::RefType),
118+
PathType(ast::PathType),
119+
Lifetime(ast::Lifetime),
120+
}
121+
122+
impl Missing {
123+
fn from_node(node: SyntaxNode, ctx: &AssistContext<'_, '_>) -> Option<Self> {
124+
match_ast! {
125+
match node {
126+
ast::Lifetime(it) => (it.syntax().text() == "'_").then_some(Missing::Lifetime(it)),
127+
ast::RefType(it) => (it.lifetime().is_none() && it.amp_token().is_some()).then_some(Missing::RefType(it)),
128+
ast::PathType(it) => {
129+
let has_lifetime = match ctx.sema.resolve_path(&it.path()?)? {
130+
hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) => adt.lifetime(ctx.db()).is_some(),
131+
// FIXME: check TypeAlias and Trait lifetime params
132+
_ => false,
133+
};
134+
(has_lifetime && ast::Type::from(it.clone()).generic_arg_list().is_some())
135+
.then_some(Missing::PathType(it))
136+
},
137+
_ => None,
138+
}
139+
}
140+
}
121141
}
122142

123-
fn infer_lifetimes(node: &SyntaxNode) -> Vec<Change> {
143+
fn infer_lifetimes(node: &SyntaxNode, ctx: &AssistContext<'_, '_>) -> Vec<Missing> {
124144
node.children()
125145
.filter(|it| !matches!(it.kind(), SyntaxKind::FN_PTR_TYPE | SyntaxKind::TYPE_BOUND_LIST))
126-
.flat_map(|it| {
127-
infer_lifetimes(&it)
128-
.into_iter()
129-
.chain(ast::Lifetime::cast(it.clone()).and_then(|lt| {
130-
lt.lifetime_ident_token().filter(|lt| lt.text() == "'_").map(Change::Replace)
131-
}))
132-
.chain(
133-
ast::RefType::cast(it)
134-
.filter(|ty| ty.lifetime().is_none())
135-
.and_then(|ty| ty.amp_token())
136-
.map(Change::Insert),
137-
)
138-
})
146+
.flat_map(|it| infer_lifetimes(&it, ctx).into_iter().chain(Missing::from_node(it, ctx)))
139147
.collect()
140148
}
141149

@@ -188,13 +196,13 @@ mod tests {
188196
fn add_lifetime_to_explicit_infer_lifetime() {
189197
check_assist(
190198
add_lifetime_to_type,
191-
r#"struct Foo { a: &'_ $0i32, b: &'_ (&'_ i32, fn(&str) -> &str) }"#,
199+
r#"struct Foo { a: &'_$0 i32, b: &'_ (&'_ i32, fn(&str) -> &str) }"#,
192200
r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#,
193201
);
194202

195203
check_assist(
196204
add_lifetime_to_type,
197-
r#"struct Foo { a: &'_ $0i32, b: Foo<'_> }"#,
205+
r#"struct Foo { a: &'_$0 i32, b: Foo<'_> }"#,
198206
r#"struct Foo<'a> { a: &'a i32, b: Foo<'a> }"#,
199207
);
200208

0 commit comments

Comments
 (0)