Skip to main content

ide_diagnostics/handlers/
explicit_drop_method_use.rs

1use either::Either;
2use hir::InFile;
3use ide_db::assists::Assist;
4use ide_db::source_change::{SourceChange, SourceChangeBuilder};
5use ide_db::text_edit::TextEdit;
6use itertools::Itertools;
7use syntax::{
8    AstNode, AstPtr,
9    ast::{self, HasArgList},
10};
11
12use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range, fix};
13
14// Diagnostic: explicit-drop-method-use
15//
16// This diagnostic is triggered when the `Drop::drop` method is called (or named) explicitly.
17pub(crate) fn explicit_drop_method_use(
18    ctx: &DiagnosticsContext<'_, '_>,
19    d: &hir::ExplicitDropMethodUse,
20) -> Diagnostic {
21    match d.expr_or_path {
22        Either::Left(expr) => {
23            let display_range = adjusted_display_range(ctx, expr, &|node| {
24                Some(node.name_ref()?.syntax().text_range())
25            });
26            Diagnostic::new(
27                DiagnosticCode::RustcHardError("E0040"),
28                "explicit use of destructor method",
29                display_range,
30            )
31            .stable()
32            .with_main_node(expr.map(Into::into))
33            .with_fixes(fix_method_call(ctx, expr))
34        }
35        Either::Right(path) => Diagnostic::new_with_syntax_node_ptr(
36            ctx,
37            DiagnosticCode::RustcHardError("E0040"),
38            "explicit use of destructor method",
39            path.map(Into::into),
40        )
41        .stable()
42        .with_fixes(fix_path(ctx, path)),
43    }
44}
45
46fn fix_method_call(
47    ctx: &DiagnosticsContext<'_, '_>,
48    mcall_ptr: InFile<AstPtr<ast::MethodCallExpr>>,
49) -> Option<Vec<Assist>> {
50    if mcall_ptr.file_id.is_macro() {
51        // TODO: handle macro calls. Rough plan:
52        // 1. upmap the range of the receiver and the range of the whole call
53        // 2. delete everything outside the receiver and replace it with `drop(...)`, using range edits only.
54        return None;
55    }
56
57    let db = ctx.db();
58
59    let file_id = mcall_ptr.file_id;
60    let mcall = mcall_ptr.to_node(db);
61    let range = mcall.syntax().text_range();
62
63    // `mcall` is `foo.drop()` -- extract the receiver, and wrap it in `drop()`
64    // NOTE: it could theoretically be `(&mut foo).drop()` instead, in which case the fix
65    // below would be incorrect, as it'd result in `drop((&mut foo))` instead of `drop(foo)`
66    // -- but we don't bother to deal with that case.
67    let recv = mcall.receiver()?;
68
69    let mut builder = SourceChangeBuilder::new(file_id.original_file(db).file_id(db));
70    let editor = builder.make_editor(mcall.syntax());
71    let make = editor.make();
72    let new_call =
73        make.expr_call(make.expr_path(make.path_from_text("drop")), make.arg_list([recv]));
74    builder.replace_ast(ast::Expr::MethodCallExpr(mcall), ast::Expr::CallExpr(new_call));
75    let source_change = builder.finish();
76    Some(vec![fix("use-drop-function", "Use `drop` function", source_change, range)])
77}
78
79fn fix_path(
80    ctx: &DiagnosticsContext<'_, '_>,
81    path_ptr: InFile<AstPtr<ast::Path>>,
82) -> Option<Vec<Assist>> {
83    let db = ctx.db();
84
85    let file_id = path_ptr.file_id;
86    let path = path_ptr.to_node(db);
87
88    if let Some(call) =
89        path.syntax().parent().and_then(|it| it.parent()).and_then(ast::CallExpr::cast)
90    {
91        if file_id.is_macro() {
92            // TODO: make this work in macros? Might not be worth it, as this is a niche way to trigger this
93            // already niche error
94            return None;
95        }
96
97        // `call` is `Drop::drop(&mut foo)` -- extract the arg, and wrap it in `drop()`
98        let arg_list = call.arg_list()?;
99        let ref_recv = arg_list.args().exactly_one().ok()?;
100        let ast::Expr::RefExpr(ref_recv) = ref_recv else {
101            return None;
102        };
103        let recv = ref_recv.expr()?;
104
105        let range = call.syntax().text_range();
106
107        let mut builder = SourceChangeBuilder::new(file_id.original_file(db).file_id(db));
108        let editor = builder.make_editor(call.syntax());
109        let make = editor.make();
110        let new_call =
111            make.expr_call(make.expr_path(make.path_from_text("drop")), make.arg_list([recv]));
112        builder.replace_ast(call, new_call);
113        let source_change = builder.finish();
114        Some(vec![fix("use-drop-function", "Use `drop` function", source_change, range)])
115    } else {
116        // `path` could be the `Foo::drop` in `let d = Foo::drop;`
117        // -- replace the path with `drop`
118
119        let range = InFile::new(file_id, path.syntax().text_range())
120            .original_node_file_range_rooted_opt(db)?;
121
122        let edit = TextEdit::replace(range.range, "drop".to_owned());
123        let source_change = SourceChange::from_text_edit(range.file_id.file_id(db), edit);
124        Some(vec![fix("use-drop-function", "Use `drop` function", source_change, range.range)])
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use crate::tests::{
131        check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
132    };
133
134    #[test]
135    fn method_call_diagnostic() {
136        check_diagnostics(
137            r#"
138//- minicore: drop
139struct A;
140impl Drop for A { fn drop(&mut self) {} }
141
142fn main(mut a: A) {
143    a.drop();
144   // ^^^^ 💡 error: explicit use of destructor method
145}
146"#,
147        );
148    }
149
150    #[test]
151    fn method_call_fix() {
152        check_fix(
153            r#"
154//- minicore: drop
155struct A;
156impl Drop for A { fn drop(&mut self) {} }
157
158fn main(mut a: A) {
159    a.drop$0();
160}
161"#,
162            r#"
163struct A;
164impl Drop for A { fn drop(&mut self) {} }
165
166fn main(mut a: A) {
167    drop(a);
168}
169"#,
170        );
171    }
172
173    #[test]
174    fn qualified_call_1_diagnostic() {
175        check_diagnostics(
176            r#"
177//- minicore: drop
178struct A;
179impl Drop for A { fn drop(&mut self) {} }
180
181fn main(mut a: A) {
182    A::drop(&mut a);
183 // ^^^^^^^ 💡 error: explicit use of destructor method
184}
185"#,
186        );
187    }
188
189    #[test]
190    fn qualified_call_1_fix() {
191        check_fix(
192            r#"
193//- minicore: drop
194struct A;
195impl Drop for A { fn drop(&mut self) {} }
196
197fn main(mut a: A) {
198    A::drop(&mut a$0);
199}
200"#,
201            r#"
202struct A;
203impl Drop for A { fn drop(&mut self) {} }
204
205fn main(mut a: A) {
206    drop(a);
207}
208"#,
209        )
210    }
211
212    #[test]
213    fn qualified_call_2_diagnostic() {
214        check_diagnostics(
215            r#"
216//- minicore: drop
217struct A;
218impl Drop for A { fn drop(&mut self) {} }
219
220fn main(mut a: A) {
221    Drop::drop(&mut a);
222 // ^^^^^^^^^^ 💡 error: explicit use of destructor method
223}
224"#,
225        );
226    }
227
228    #[test]
229    fn qualified_call_2_fix() {
230        check_fix(
231            r#"
232//- minicore: drop
233struct A;
234impl Drop for A { fn drop(&mut self) {} }
235
236fn main(mut a: A) {
237    Drop::drop(&mut a$0);
238}
239"#,
240            r#"
241struct A;
242impl Drop for A { fn drop(&mut self) {} }
243
244fn main(mut a: A) {
245    drop(a);
246}
247"#,
248        )
249    }
250
251    #[test]
252    fn fully_qualified_call_diagnostic() {
253        check_diagnostics(
254            r#"
255//- minicore: drop
256struct A;
257impl Drop for A { fn drop(&mut self) {} }
258
259fn main(mut a: A) {
260    <A as Drop>::drop(&mut a);
261 // ^^^^^^^^^^^^^^^^^ 💡 error: explicit use of destructor method
262}
263"#,
264        );
265    }
266
267    #[test]
268    fn fully_qualified_call_fix() {
269        check_fix(
270            r#"
271//- minicore: drop
272struct A;
273impl Drop for A { fn drop(&mut self) {} }
274
275fn main(mut a: A) {
276    <A as Drop>::drop(&mut a$0);
277}
278"#,
279            r#"
280struct A;
281impl Drop for A { fn drop(&mut self) {} }
282
283fn main(mut a: A) {
284    drop(a);
285}
286"#,
287        )
288    }
289
290    #[test]
291    fn path_diagnostic() {
292        check_diagnostics_with_disabled(
293            r#"
294//- minicore: drop
295struct A;
296impl Drop for A { fn drop(&mut self) {} }
297
298fn main(mut a: A) {
299    let d = A::drop;
300         // ^^^^^^^ 💡 error: explicit use of destructor method
301    d(&mut a);
302}
303"#,
304            // Because of the error, the code isn't analyzed further (?), and so `d` is warned on as unused.
305            // Arguably a bug in r-a (rustc doesn't emit a warning in this case)
306            // FIXME: remove this once r-a no longer warns
307            &["unused_variables"],
308        );
309    }
310
311    #[test]
312    // NOTE: Here, the fix is not completely correct, as it doesn't replace `d(&mut a)` with `d(a)`.
313    // Oh well, rustc doesn't either
314    fn path_fix() {
315        check_fix_with_disabled(
316            r#"
317//- minicore: drop
318struct A;
319impl Drop for A { fn drop(&mut self) {} }
320
321fn main(mut a: A) {
322    let d = A::drop$0;
323    d(&mut a);
324}
325"#,
326            r#"
327struct A;
328impl Drop for A { fn drop(&mut self) {} }
329
330fn main(mut a: A) {
331    let d = drop;
332    d(&mut a);
333}
334"#,
335            // Because of the error, the code isn't analyzed further (?), and so `d` is warned on as unused.
336            // Arguably a bug in r-a (rustc doesn't emit a warning in this case)
337            // FIXME: remove this once r-a no longer warns
338            &["unused_variables"],
339        );
340    }
341
342    #[test]
343    // NOTE: Here, the fix is not completely correct, as it doesn't replace `d(&mut a)` with `d(a)`.
344    // Oh well, rustc doesn't either
345    fn path_fix_in_macro() {
346        check_fix(
347            r#"
348//- minicore: drop
349struct A;
350impl Drop for A { fn drop(&mut self) {} }
351
352macro_rules! main {
353    ($e:expr) => {
354        fn main() { $e }
355    }
356}
357
358main!{{
359    let mut a = A;
360    let d = A::drop$0;
361    d(&mut a);
362}};
363"#,
364            r#"
365struct A;
366impl Drop for A { fn drop(&mut self) {} }
367
368macro_rules! main {
369    ($e:expr) => {
370        fn main() { $e }
371    }
372}
373
374main!{{
375    let mut a = A;
376    let d = drop;
377    d(&mut a);
378}};
379"#,
380        );
381    }
382
383    #[test]
384    fn std_mem_drop() {
385        check_diagnostics(
386            r#"
387//- minicore: drop
388struct A;
389impl Drop for A { fn drop(&mut self) {} }
390
391fn main(a: A) {
392    drop(a);
393}
394"#,
395        );
396    }
397
398    #[test]
399    fn inherent_drop_method() {
400        check_diagnostics(
401            r#"
402struct A;
403impl A { fn drop(&mut self) {} }
404
405fn main(mut a: A) {
406    a.drop();
407}
408"#,
409        );
410    }
411
412    #[test]
413    fn custom_trait_drop_method() {
414        check_diagnostics(
415            r#"
416struct A;
417trait MyDrop { fn drop(&mut self); }
418impl MyDrop for A { fn drop(&mut self) {} }
419
420fn main(mut a: A) {
421    a.drop();
422}
423"#,
424        );
425    }
426}