Skip to main content

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