Skip to content

Commit 36fb399

Browse files
committed
implement assist to add missing lifetime to function
1 parent 83ba420 commit 36fb399

File tree

3 files changed

+382
-0
lines changed

3 files changed

+382
-0
lines changed
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
use ide_db::FxHashSet;
2+
use itertools::Itertools;
3+
use syntax::ast::{
4+
self, AstNode, GenericArgList, GenericParamList, HasGenericParams, HasName, HasTypeBounds,
5+
ParamList, TypeBoundList, WhereClause,
6+
};
7+
8+
use crate::{AssistContext, AssistId, AssistKind, Assists};
9+
10+
// Assist: add_lifetime_to_function
11+
//
12+
// Adds a new lifetime to a struct, enum or union.
13+
//
14+
// ```
15+
// fn print(s: &$0'a str) {
16+
// println!("{s}");
17+
// }
18+
// ```
19+
// ->
20+
// ```
21+
// fn print<'a>(s: &'a str) {
22+
// println!("{s}");
23+
// }
24+
// ```
25+
pub(crate) fn add_lifetime_to_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26+
let function = ctx.find_node_at_offset::<ast::Fn>()?;
27+
let target = function.syntax().text_range();
28+
29+
let lifetimes_used_in_signature = function
30+
.param_list()
31+
.into_iter()
32+
.flat_map(lifetimes_from_param_list)
33+
.chain(
34+
function.ret_type().into_iter().filter_map(|ty| ty.ty()).flat_map(lifetimes_from_type),
35+
)
36+
.chain(function.where_clause().into_iter().flat_map(lifetimes_from_where_clause));
37+
let lifetimes_in_generic_list: FxHashSet<_> = function
38+
.generic_param_list()
39+
.iter()
40+
.flat_map(|gparams| gparams.lifetime_params())
41+
.filter_map(|lifetime_param| lifetime_param.lifetime())
42+
.map(|lifetime| lifetime.to_string())
43+
.collect();
44+
45+
let mut lifetimes_to_add = lifetimes_used_in_signature
46+
.filter(|life| life != "'static")
47+
.filter(|life| !lifetimes_in_generic_list.contains(life))
48+
.peekable();
49+
lifetimes_to_add.peek()?;
50+
51+
acc.add(
52+
AssistId("add_lifetime_to_function", AssistKind::QuickFix),
53+
"Add lifetime to function",
54+
target,
55+
|edit| {
56+
let mut lifetimes_to_add: Vec<_> = lifetimes_to_add.collect();
57+
lifetimes_to_add.sort();
58+
lifetimes_to_add.dedup();
59+
let lifetime_list = lifetimes_to_add.into_iter().join(", ");
60+
match function.generic_param_list() {
61+
Some(gen_param) => {
62+
if let Some(lifetime_end) = gen_param.lifetime_params().last() {
63+
edit.insert(
64+
lifetime_end.syntax().text_range().end(),
65+
format!(", {lifetime_list}"),
66+
);
67+
} else if let Some(generic_start) = gen_param.generic_params().next() {
68+
edit.insert(
69+
generic_start.syntax().text_range().start(),
70+
format!("{lifetime_list}, "),
71+
);
72+
}
73+
}
74+
None => {
75+
if let Some(name) = function.name() {
76+
edit.insert(name.syntax().text_range().end(), format!("<{lifetime_list}>"));
77+
}
78+
}
79+
}
80+
},
81+
)
82+
}
83+
84+
fn lifetimes_from_type(ty: ast::Type) -> Vec<String> {
85+
match ty {
86+
ast::Type::ArrayType(arr_ty) => arr_ty.ty().map(lifetimes_from_type).unwrap_or_default(),
87+
ast::Type::DynTraitType(dynt_ty) => dynt_ty
88+
// dyn Trait<'a, &'b Struct<'c>>
89+
// ^^^^^^^^^^^^^^^^^^^^
90+
.type_bound_list()
91+
.iter()
92+
// dyn Trait<'a, &'b Struct<'c>>
93+
// ^^, ^^^^^^^^^^^^^^
94+
.flat_map(|list| list.bounds())
95+
.flat_map(|bound| {
96+
bound
97+
// dyn Trait<'a, &'b Struct<'c>>
98+
// ^^^^^^^^^^^^^^
99+
.ty()
100+
.into_iter()
101+
// dyn Trait<'a, &'b Struct<'c>>
102+
// ^^ ^^
103+
.flat_map(lifetimes_from_type)
104+
// dyn Trait<'a, &'b Struct<'c>>
105+
// ^^
106+
.chain(bound.lifetime().map(|life| life.to_string()))
107+
})
108+
.collect(),
109+
ast::Type::FnPtrType(fn_ptr_ty) => fn_ptr_ty
110+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
111+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
112+
.param_list()
113+
.into_iter()
114+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
115+
// ^^^^^^^^^^^^^^^, ^^^^^^^^^^^^^^^^^^^^^^^^^
116+
.flat_map(|param_list| param_list.params())
117+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
118+
// ^^^^^^^, ^^^^^^^^^^^^^^^^^
119+
.filter_map(|param| param.ty())
120+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
121+
// ^^, ^^ ^^
122+
.flat_map(lifetimes_from_type)
123+
.chain(
124+
fn_ptr_ty
125+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
126+
// ^^^^^^^
127+
.ret_type()
128+
.and_then(|ty| ty.ty())
129+
.into_iter()
130+
// fn(param1: &'a str, param2: &'b dyn Trait<'c>) -> &'d str
131+
// ^^
132+
.flat_map(lifetimes_from_type),
133+
)
134+
.collect(),
135+
// Note: we collect the lifetimes from where bounds that have an `for` restriction on them.
136+
// We could also add any lifetimes we find inside ForTypes to the `generic_param_list` of
137+
// the `for` bound, but we instead add them to the function; it's less confusing.
138+
ast::Type::ForType(for_ty) => {
139+
let for_lifetimes: FxHashSet<_> = for_ty
140+
// for<'life> T: Trait<'life, 'a>
141+
// ^^^^^^^
142+
.generic_param_list()
143+
.into_iter()
144+
// for<'life> T: Trait<'life, 'a>
145+
// ^^^^^
146+
.flat_map(lifetimes_from_generic_params)
147+
.collect();
148+
for_ty
149+
// for<'life> T: Trait<'life, 'a>
150+
// ^^^^^^^^^^^^^^^^
151+
.ty()
152+
.into_iter()
153+
// for<'life> T: Trait<'life, 'a>
154+
// ^^^^^, ^^
155+
.flat_map(lifetimes_from_type)
156+
// If a type was declared on the `for<'a>` bound, then we should not report it as
157+
// missing.
158+
// for<'life> T: Trait<'life, 'a>
159+
// ^^
160+
.filter(|life| !for_lifetimes.contains(life))
161+
.collect()
162+
}
163+
ast::Type::ImplTraitType(impl_trait_ty) => impl_trait_ty
164+
.type_bound_list()
165+
.into_iter()
166+
.flat_map(lifetimes_from_type_bounds)
167+
.collect(),
168+
// _ doesn't have any lifetimes
169+
ast::Type::InferType(_) => vec![],
170+
// macro calls also don't have lifetimes
171+
ast::Type::MacroType(_) => vec![],
172+
// ! is equally blessed with no lifetimes
173+
ast::Type::NeverType(_) => vec![],
174+
ast::Type::ParenType(paren_ty) => {
175+
paren_ty.ty().map(lifetimes_from_type).unwrap_or_default()
176+
}
177+
ast::Type::PathType(path_ty) => path_ty
178+
.path()
179+
.unwrap()
180+
.segments()
181+
.flat_map(|seg| {
182+
seg.generic_arg_list()
183+
.into_iter()
184+
.flat_map(lifetimes_from_generic_args)
185+
.chain(seg.param_list().into_iter().flat_map(lifetimes_from_param_list))
186+
.chain(
187+
seg.ret_type()
188+
.and_then(|ty| ty.ty())
189+
.into_iter()
190+
.flat_map(lifetimes_from_type),
191+
)
192+
})
193+
.collect(),
194+
ast::Type::PtrType(ptr_ty) => ptr_ty.ty().map(lifetimes_from_type).unwrap_or_default(),
195+
ast::Type::RefType(ref_ty) => ref_ty
196+
.lifetime()
197+
.iter()
198+
.map(|life| life.to_string())
199+
.chain(ref_ty.ty().map(lifetimes_from_type).unwrap_or_default())
200+
.collect(),
201+
ast::Type::SliceType(slice_ty) => {
202+
slice_ty.ty().map(lifetimes_from_type).unwrap_or_default()
203+
}
204+
ast::Type::TupleType(tpl_ty) => tpl_ty.fields().flat_map(lifetimes_from_type).collect(),
205+
}
206+
}
207+
208+
fn lifetimes_from_generic_params(generic_params: GenericParamList) -> impl Iterator<Item = String> {
209+
generic_params
210+
.lifetime_params()
211+
.flat_map(|lifetime_param| lifetime_param.lifetime())
212+
.map(|lifetime| lifetime.to_string())
213+
}
214+
215+
fn lifetimes_from_type_bounds(type_bounds: TypeBoundList) -> impl Iterator<Item = String> {
216+
type_bounds.bounds().flat_map(|bound| {
217+
bound
218+
.ty()
219+
.into_iter()
220+
.flat_map(lifetimes_from_type)
221+
.chain(bound.lifetime().map(|life| life.to_string()))
222+
})
223+
}
224+
225+
fn lifetimes_from_param_list(param_list: ParamList) -> impl Iterator<Item = String> {
226+
param_list
227+
.params()
228+
.filter_map(|param| param.ty())
229+
.flat_map(lifetimes_from_type)
230+
.map(|life| life.to_string())
231+
}
232+
233+
fn lifetimes_from_generic_args(generic_args: GenericArgList) -> Vec<String> {
234+
generic_args
235+
.generic_args()
236+
.flat_map(|arg| match arg {
237+
ast::GenericArg::AssocTypeArg(_assoc_ty_arg) => vec![],
238+
ast::GenericArg::ConstArg(_) => vec![],
239+
ast::GenericArg::LifetimeArg(lifetime_arg) => vec![lifetime_arg.to_string()],
240+
ast::GenericArg::TypeArg(ty_arg) => {
241+
ty_arg.ty().map(lifetimes_from_type).unwrap_or_default()
242+
}
243+
})
244+
.collect()
245+
}
246+
247+
fn lifetimes_from_where_clause(clause: WhereClause) -> impl Iterator<Item = String> {
248+
clause.predicates().flat_map(|pred| {
249+
let declared_params: FxHashSet<_> =
250+
pred.generic_param_list().into_iter().flat_map(lifetimes_from_generic_params).collect();
251+
pred.ty()
252+
// for<'b> T: std::fmt::Display + 'a + 'b
253+
// ^
254+
.into_iter()
255+
// for<'b> T: std::fmt::Display + 'a + 'b
256+
//
257+
.flat_map(lifetimes_from_type)
258+
// for<'b> T: std::fmt::Display + 'a + 'b
259+
// ^^, ^^
260+
.chain(pred.lifetime().map(|life| life.to_string()))
261+
.chain(pred.type_bound_list().into_iter().flat_map(lifetimes_from_type_bounds))
262+
.filter(move |life| !declared_params.contains(life))
263+
})
264+
}
265+
266+
#[cfg(test)]
267+
mod tests {
268+
use crate::tests::{check_assist, check_assist_not_applicable};
269+
270+
use super::*;
271+
272+
#[test]
273+
fn test_add_lifetime_to_function() {
274+
check_assist(
275+
add_lifetime_to_function,
276+
r#"fn f(s: &$0'a str) {}"#,
277+
r#"fn f<'a>(s: &'a str) {}"#,
278+
);
279+
}
280+
281+
#[test]
282+
fn test_dont_add_lifetime_to_good_function() {
283+
check_assist_not_applicable(add_lifetime_to_function, r#"fn f<'a>(s: &$0'a str) {}"#);
284+
}
285+
286+
#[test]
287+
fn test_add_lifetime_to_generic_function() {
288+
check_assist(
289+
add_lifetime_to_function,
290+
r#"fn f<T>(s: &$0'a str, t: T) {}"#,
291+
r#"fn f<'a, T>(s: &'a str, t: T) {}"#,
292+
);
293+
}
294+
295+
#[test]
296+
fn test_add_lifetime_to_lifetime_function() {
297+
check_assist(
298+
add_lifetime_to_function,
299+
r#"fn f<'b>(s: &$0'a str, s2: &'b str) {}"#,
300+
r#"fn f<'b, 'a>(s: &'a str, s2: &'b str) {}"#,
301+
);
302+
}
303+
304+
#[test]
305+
fn test_add_lifetime_to_lifetime_generic_function() {
306+
check_assist(
307+
add_lifetime_to_function,
308+
r#"fn f<'a, T>(s: &'a str, s2: &$0'b T) {}"#,
309+
r#"fn f<'a, 'b, T>(s: &'a str, s2: &'b T) {}"#,
310+
);
311+
}
312+
313+
#[test]
314+
fn test_add_position_2_lifetime_to_lifetime_function() {
315+
check_assist(
316+
add_lifetime_to_function,
317+
r#"fn f<'a>(s: &'a str, s2: &$0'b str) {}"#,
318+
r#"fn f<'a, 'b>(s: &'a str, s2: &'b str) {}"#,
319+
);
320+
}
321+
322+
#[test]
323+
fn test_add_2_lifetimes_to_function() {
324+
check_assist(
325+
add_lifetime_to_function,
326+
r#"fn f(s: &'a str, s2: &$0'b str) {}"#,
327+
r#"fn f<'a, 'b>(s: &'a str, s2: &'b str) {}"#,
328+
);
329+
}
330+
331+
#[test]
332+
fn test_add_lifetime_to_function_for_bound() {
333+
check_assist(
334+
add_lifetime_to_function,
335+
r#"fn f<T>(s: &str) where for<'b> &'b S<'a, T>: std::fmt::Display + '$0a + 'b {}"#,
336+
r#"fn f<'a, T>(s: &str) where for<'b> &'b S<'a, T>: std::fmt::Display + 'a + 'b {}"#,
337+
);
338+
check_assist(
339+
add_lifetime_to_function,
340+
r#"fn g<T: 'static>(s: &str) where T: 'static, S<'a, T>: for<'b> Trait<'a$0, &'b T> { }"#,
341+
r#"fn g<'a, T: 'static>(s: &str) where T: 'static, S<'a, T>: for<'b> Trait<'a, &'b T> { }"#,
342+
);
343+
}
344+
345+
#[test]
346+
fn test_add_lifetime_to_complicated_function() {
347+
check_assist(
348+
add_lifetime_to_function,
349+
r#"fn f<T>(id: T) where T: Displa$0y + 'a {}"#,
350+
r#"fn f<'a, T>(id: T) where T: Display + 'a {}"#,
351+
);
352+
check_assist(
353+
add_lifetime_to_function,
354+
r#"fn f(things: &[Thing<'$0a>]) {}"#,
355+
r#"fn f<'a>(things: &[Thing<'a>]) {}"#,
356+
);
357+
check_assist(
358+
add_lifetime_to_function,
359+
r#"fn f<T, ID>(id: ID) where ID: for<'db> Lookup<Database<'db> = dyn DefDatabase + 'db, Data = T> + '$0a {}"#,
360+
r#"fn f<'a, T, ID>(id: ID) where ID: for<'db> Lookup<Database<'db> = dyn DefDatabase + 'db, Data = T> + 'a {}"#,
361+
);
362+
}
363+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ mod handlers {
104104
mod add_braces;
105105
mod add_explicit_type;
106106
mod add_label_to_loop;
107+
mod add_lifetime_to_function;
107108
mod add_lifetime_to_type;
108109
mod add_missing_impl_members;
109110
mod add_missing_match_arms;
@@ -227,6 +228,7 @@ mod handlers {
227228
add_explicit_type::add_explicit_type,
228229
add_label_to_loop::add_label_to_loop,
229230
add_missing_match_arms::add_missing_match_arms,
231+
add_lifetime_to_function::add_lifetime_to_function,
230232
add_lifetime_to_type::add_lifetime_to_type,
231233
add_return_type::add_return_type,
232234
add_turbo_fish::add_turbo_fish,

0 commit comments

Comments
 (0)