aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2021-01-17 14:15:23 +0000
committerBenjamin Coenen <[email protected]>2021-01-17 14:15:23 +0000
commit557cf513fa8126a71775cc559d5242cf4feac625 (patch)
treede36d2910d54360ededf9351000f7bd47812bf40
parentcd532e615abdac766ce9110e02e197f674fce375 (diff)
Add assist: add lifetime to type #7200
Signed-off-by: Benjamin Coenen <[email protected]>
-rw-r--r--crates/assists/src/handlers/add_lifetime_to_type.rs217
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs19
3 files changed, 238 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}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 14178a651..559b9651e 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -108,6 +108,7 @@ mod handlers {
108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; 108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
109 109
110 mod add_explicit_type; 110 mod add_explicit_type;
111 mod add_lifetime_to_type;
111 mod add_missing_impl_members; 112 mod add_missing_impl_members;
112 mod add_turbo_fish; 113 mod add_turbo_fish;
113 mod apply_demorgan; 114 mod apply_demorgan;
@@ -164,6 +165,7 @@ mod handlers {
164 &[ 165 &[
165 // These are alphabetic for the foolish consistency 166 // These are alphabetic for the foolish consistency
166 add_explicit_type::add_explicit_type, 167 add_explicit_type::add_explicit_type,
168 add_lifetime_to_type::add_lifetime_to_type,
167 add_turbo_fish::add_turbo_fish, 169 add_turbo_fish::add_turbo_fish,
168 apply_demorgan::apply_demorgan, 170 apply_demorgan::apply_demorgan,
169 auto_import::auto_import, 171 auto_import::auto_import,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index d48d063b4..4b254d463 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -104,6 +104,25 @@ impl Trait<u32> for () {
104} 104}
105 105
106#[test] 106#[test]
107fn doctest_add_lifetime_to_type() {
108 check_doc_test(
109 "add_lifetime_to_type",
110 r#####"
111struct Point$0 {
112 x: &u32,
113 y: u32,
114}
115"#####,
116 r#####"
117struct Point<'a> {
118 x: &'a u32,
119 y: u32,
120}
121"#####,
122 )
123}
124
125#[test]
107fn doctest_add_turbo_fish() { 126fn doctest_add_turbo_fish() {
108 check_doc_test( 127 check_doc_test(
109 "add_turbo_fish", 128 "add_turbo_fish",