ide_assists/handlers/
generate_is_empty_from_len.rs

1use hir::{HasSource, Name, sym};
2use syntax::{
3    AstNode,
4    ast::{self, HasName},
5};
6
7use crate::{
8    AssistId,
9    assist_context::{AssistContext, Assists},
10};
11
12// Assist: generate_is_empty_from_len
13//
14// Generates is_empty implementation from the len method.
15//
16// ```
17// struct MyStruct { data: Vec<String> }
18//
19// impl MyStruct {
20//     #[must_use]
21//     p$0ub fn len(&self) -> usize {
22//         self.data.len()
23//     }
24// }
25// ```
26// ->
27// ```
28// struct MyStruct { data: Vec<String> }
29//
30// impl MyStruct {
31//     #[must_use]
32//     pub fn len(&self) -> usize {
33//         self.data.len()
34//     }
35//
36//     #[must_use]
37//     pub fn is_empty(&self) -> bool {
38//         self.len() == 0
39//     }
40// }
41// ```
42pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43    let fn_node = ctx.find_node_at_offset::<ast::Fn>()?;
44    let fn_name = fn_node.name()?;
45
46    if fn_name.text() != "len" {
47        cov_mark::hit!(len_function_not_present);
48        return None;
49    }
50
51    if fn_node.param_list()?.params().next().is_some() {
52        cov_mark::hit!(len_function_with_parameters);
53        return None;
54    }
55
56    let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?;
57    let len_fn = get_impl_method(ctx, &impl_, &Name::new_symbol_root(sym::len))?;
58    if !len_fn.ret_type(ctx.sema.db).is_usize() {
59        cov_mark::hit!(len_fn_different_return_type);
60        return None;
61    }
62
63    if get_impl_method(ctx, &impl_, &Name::new_symbol_root(sym::is_empty)).is_some() {
64        cov_mark::hit!(is_empty_already_implemented);
65        return None;
66    }
67
68    let node = len_fn.source(ctx.sema.db)?;
69    let range = node.syntax().value.text_range();
70
71    acc.add(
72        AssistId::generate("generate_is_empty_from_len"),
73        "Generate a is_empty impl from a len function",
74        range,
75        |builder| {
76            let code = r#"
77
78    #[must_use]
79    pub fn is_empty(&self) -> bool {
80        self.len() == 0
81    }"#
82            .to_owned();
83            builder.insert(range.end(), code)
84        },
85    )
86}
87
88fn get_impl_method(
89    ctx: &AssistContext<'_>,
90    impl_: &ast::Impl,
91    fn_name: &Name,
92) -> Option<hir::Function> {
93    let db = ctx.sema.db;
94    let impl_def: hir::Impl = ctx.sema.to_def(impl_)?;
95
96    let scope = ctx.sema.scope(impl_.syntax())?;
97    let ty = impl_def.self_ty(db);
98    ty.iterate_method_candidates(db, &scope, Some(fn_name), Some)
99}
100
101#[cfg(test)]
102mod tests {
103    use crate::tests::{check_assist, check_assist_not_applicable};
104
105    use super::*;
106
107    #[test]
108    fn len_function_not_present() {
109        cov_mark::check!(len_function_not_present);
110        check_assist_not_applicable(
111            generate_is_empty_from_len,
112            r#"
113struct MyStruct { data: Vec<String> }
114
115impl MyStruct {
116    p$0ub fn test(&self) -> usize {
117            self.data.len()
118        }
119    }
120"#,
121        );
122    }
123
124    #[test]
125    fn len_function_with_parameters() {
126        cov_mark::check!(len_function_with_parameters);
127        check_assist_not_applicable(
128            generate_is_empty_from_len,
129            r#"
130struct MyStruct { data: Vec<String> }
131
132impl MyStruct {
133    #[must_use]
134    p$0ub fn len(&self, _i: bool) -> usize {
135        self.data.len()
136    }
137}
138"#,
139        );
140    }
141
142    #[test]
143    fn is_empty_already_implemented() {
144        cov_mark::check!(is_empty_already_implemented);
145        check_assist_not_applicable(
146            generate_is_empty_from_len,
147            r#"
148struct MyStruct { data: Vec<String> }
149
150impl MyStruct {
151    #[must_use]
152    p$0ub fn len(&self) -> usize {
153        self.data.len()
154    }
155
156    #[must_use]
157    pub fn is_empty(&self) -> bool {
158        self.len() == 0
159    }
160}
161"#,
162        );
163    }
164
165    #[test]
166    fn len_fn_different_return_type() {
167        cov_mark::check!(len_fn_different_return_type);
168        check_assist_not_applicable(
169            generate_is_empty_from_len,
170            r#"
171struct MyStruct { data: Vec<String> }
172
173impl MyStruct {
174    #[must_use]
175    p$0ub fn len(&self) -> u32 {
176        self.data.len()
177    }
178}
179"#,
180        );
181    }
182
183    #[test]
184    fn generate_is_empty() {
185        check_assist(
186            generate_is_empty_from_len,
187            r#"
188struct MyStruct { data: Vec<String> }
189
190impl MyStruct {
191    #[must_use]
192    p$0ub fn len(&self) -> usize {
193        self.data.len()
194    }
195}
196"#,
197            r#"
198struct MyStruct { data: Vec<String> }
199
200impl MyStruct {
201    #[must_use]
202    pub fn len(&self) -> usize {
203        self.data.len()
204    }
205
206    #[must_use]
207    pub fn is_empty(&self) -> bool {
208        self.len() == 0
209    }
210}
211"#,
212        );
213    }
214
215    #[test]
216    fn multiple_functions_in_impl() {
217        check_assist(
218            generate_is_empty_from_len,
219            r#"
220struct MyStruct { data: Vec<String> }
221
222impl MyStruct {
223    #[must_use]
224    pub fn new() -> Self {
225        Self { data: 0 }
226    }
227
228    #[must_use]
229    p$0ub fn len(&self) -> usize {
230        self.data.len()
231    }
232
233    pub fn work(&self) -> Option<usize> {
234
235    }
236}
237"#,
238            r#"
239struct MyStruct { data: Vec<String> }
240
241impl MyStruct {
242    #[must_use]
243    pub fn new() -> Self {
244        Self { data: 0 }
245    }
246
247    #[must_use]
248    pub fn len(&self) -> usize {
249        self.data.len()
250    }
251
252    #[must_use]
253    pub fn is_empty(&self) -> bool {
254        self.len() == 0
255    }
256
257    pub fn work(&self) -> Option<usize> {
258
259    }
260}
261"#,
262        );
263    }
264
265    #[test]
266    fn multiple_impls() {
267        check_assist_not_applicable(
268            generate_is_empty_from_len,
269            r#"
270struct MyStruct { data: Vec<String> }
271
272impl MyStruct {
273    #[must_use]
274    p$0ub fn len(&self) -> usize {
275        self.data.len()
276    }
277}
278
279impl MyStruct {
280    #[must_use]
281    pub fn is_empty(&self) -> bool {
282        self.len() == 0
283    }
284}
285"#,
286        );
287    }
288}