aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/add_lifetime_to_type.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/add_lifetime_to_type.rs')
-rw-r--r--crates/assists/src/handlers/add_lifetime_to_type.rs217
1 files changed, 217 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/add_lifetime_to_type.rs b/crates/assists/src/handlers/add_lifetime_to_type.rs
new file mode 100644
index 000000000..c7af84704
--- /dev/null
+++ b/crates/assists/src/handlers/add_lifetime_to_type.rs
@@ -0,0 +1,217 @@
1use ast::FieldList;
2use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner, RefType, Type};
3
4use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6// Assist: add_lifetime_to_type
7//
8// Adds a new lifetime to a struct, enum or union.
9//
10// ```
11// struct Point$0 {
12// x: &u32,
13// y: u32,
14// }
15// ```
16// ->
17// ```
18// struct Point<'a> {
19// x: &'a u32,
20// y: u32,
21// }
22// ```
23pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let node = ctx.find_node_at_offset::<ast::AdtDef>()?;
25 let has_lifetime = node
26 .generic_param_list()
27 .map(|gen_list| gen_list.lifetime_params().count() > 0)
28 .unwrap_or_default();
29
30 if has_lifetime {
31 return None;
32 }
33
34 let ref_types = fetch_borrowed_types(&node)?;
35 let target = node.syntax().text_range();
36
37 acc.add(
38 AssistId("add_lifetime_to_type", AssistKind::Generate),
39 "Add lifetime`",
40 target,
41 |builder| {
42 match node.generic_param_list() {
43 Some(gen_param) => {
44 if let Some(left_angle) = gen_param.l_angle_token() {
45 builder.insert(left_angle.text_range().end(), "'a, ");
46 }
47 }
48 None => {
49 if let Some(name) = node.name() {
50 builder.insert(name.syntax().text_range().end(), "<'a>");
51 }
52 }
53 }
54
55 for ref_type in ref_types {
56 if let Some(amp_token) = ref_type.amp_token() {
57 builder.insert(amp_token.text_range().end(), "'a ");
58 }
59 }
60 },
61 )
62}
63
64fn fetch_borrowed_types(node: &ast::AdtDef) -> Option<Vec<RefType>> {
65 let ref_types: Vec<RefType> = match node {
66 ast::AdtDef::Enum(enum_) => {
67 let variant_list = enum_.variant_list()?;
68 variant_list
69 .variants()
70 .filter_map(|variant| {
71 let field_list = variant.field_list()?;
72
73 find_ref_types_from_field_list(&field_list)
74 })
75 .flatten()
76 .collect()
77 }
78 ast::AdtDef::Struct(strukt) => {
79 let field_list = strukt.field_list()?;
80 find_ref_types_from_field_list(&field_list)?
81 }
82 ast::AdtDef::Union(un) => {
83 let record_field_list = un.record_field_list()?;
84 record_field_list
85 .fields()
86 .filter_map(|r_field| {
87 if let Type::RefType(ref_type) = r_field.ty()? {
88 if ref_type.lifetime().is_none() {
89 return Some(ref_type);
90 }
91 }
92
93 None
94 })
95 .collect()
96 }
97 };
98
99 if ref_types.is_empty() {
100 None
101 } else {
102 Some(ref_types)
103 }
104}
105
106fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType>> {
107 let ref_types: Vec<RefType> = match field_list {
108 ast::FieldList::RecordFieldList(record_list) => record_list
109 .fields()
110 .filter_map(|f| {
111 if let Type::RefType(ref_type) = f.ty()? {
112 if ref_type.lifetime().is_none() {
113 return Some(ref_type);
114 }
115 }
116
117 None
118 })
119 .collect(),
120 ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
121 .fields()
122 .filter_map(|f| {
123 if let Type::RefType(ref_type) = f.ty()? {
124 if ref_type.lifetime().is_none() {
125 return Some(ref_type);
126 }
127 }
128
129 None
130 })
131 .collect(),
132 };
133
134 if ref_types.is_empty() {
135 None
136 } else {
137 Some(ref_types)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use crate::tests::{check_assist, check_assist_not_applicable};
144
145 use super::*;
146
147 #[test]
148 fn add_lifetime_to_struct() {
149 check_assist(
150 add_lifetime_to_type,
151 "struct Foo$0 { a: &i32 }",
152 "struct Foo<'a> { a: &'a i32 }",
153 );
154
155 check_assist(
156 add_lifetime_to_type,
157 "struct Foo$0 { a: &i32, b: &usize }",
158 "struct Foo<'a> { a: &'a i32, b: &'a usize }",
159 );
160
161 check_assist(
162 add_lifetime_to_type,
163 "struct Foo<T>$0 { a: &T, b: usize }",
164 "struct Foo<'a, T> { a: &'a T, b: usize }",
165 );
166
167 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a>$0 { a: &'a i32 }");
168 check_assist_not_applicable(add_lifetime_to_type, "struct Foo$0 { a: &'a i32 }");
169 }
170
171 #[test]
172 fn add_lifetime_to_enum() {
173 check_assist(
174 add_lifetime_to_type,
175 "enum Foo$0 { Bar { a: i32 }, Other, Tuple(u32, &u32)}",
176 "enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}",
177 );
178
179 check_assist(
180 add_lifetime_to_type,
181 "enum Foo$0 { Bar { a: &i32 }}",
182 "enum Foo<'a> { Bar { a: &'a i32 }}",
183 );
184
185 check_assist(
186 add_lifetime_to_type,
187 "enum Foo<T>$0 { Bar { a: &i32, b: &T }}",
188 "enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}",
189 );
190
191 check_assist_not_applicable(add_lifetime_to_type, "enum Foo<'a>$0 { Bar { a: &'a i32 }}");
192 check_assist_not_applicable(add_lifetime_to_type, "enum Foo$0 { Bar, Misc }");
193 }
194
195 #[test]
196 fn add_lifetime_to_union() {
197 check_assist(
198 add_lifetime_to_type,
199 "union Foo$0 { a: &i32 }",
200 "union Foo<'a> { a: &'a i32 }",
201 );
202
203 check_assist(
204 add_lifetime_to_type,
205 "union Foo$0 { a: &i32, b: &usize }",
206 "union Foo<'a> { a: &'a i32, b: &'a usize }",
207 );
208
209 check_assist(
210 add_lifetime_to_type,
211 "union Foo<T>$0 { a: &T, b: usize }",
212 "union Foo<'a, T> { a: &'a T, b: usize }",
213 );
214
215 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a>$0 { a: &'a i32 }");
216 }
217}