Skip to main content

ide_diagnostics/handlers/
unresolved_method.rs

1use hir::{FileRange, HirDisplay, InFile, db::ExpandDatabase};
2use ide_db::text_edit::TextEdit;
3use ide_db::{
4    assists::{Assist, AssistId},
5    label::Label,
6    source_change::SourceChange,
7};
8use syntax::{
9    AstNode, SmolStr, TextRange, ToSmolStr,
10    ast::{self, HasArgList, make},
11    format_smolstr,
12};
13
14use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range};
15
16// Diagnostic: unresolved-method
17//
18// This diagnostic is triggered if a method does not exist on a given type.
19pub(crate) fn unresolved_method(
20    ctx: &DiagnosticsContext<'_, '_>,
21    d: &hir::UnresolvedMethodCall<'_>,
22) -> Diagnostic {
23    let suffix = if d.field_with_same_name.is_some() {
24        ", but a field with a similar name exists"
25    } else if d.assoc_func_with_same_name.is_some() {
26        ", but an associated function with a similar name exists"
27    } else {
28        ""
29    };
30    Diagnostic::new(
31        DiagnosticCode::RustcHardError("E0599"),
32        format!(
33            "no method `{}` on type `{}`{suffix}",
34            d.name.display(ctx.sema.db, ctx.edition),
35            d.receiver.display(ctx.sema.db, ctx.display_target)
36        ),
37        adjusted_display_range(ctx, d.expr, &|expr| {
38            Some(
39                match expr.left()? {
40                    ast::Expr::MethodCallExpr(it) => it.name_ref(),
41                    ast::Expr::FieldExpr(it) => it.name_ref(),
42                    _ => None,
43                }?
44                .syntax()
45                .text_range(),
46            )
47        }),
48    )
49    .with_fixes(fixes(ctx, d))
50}
51
52fn fixes(
53    ctx: &DiagnosticsContext<'_, '_>,
54    d: &hir::UnresolvedMethodCall<'_>,
55) -> Option<Vec<Assist>> {
56    let field_fix = if let Some(ty) = &d.field_with_same_name {
57        field_fix(ctx, d, ty)
58    } else {
59        // FIXME: add quickfix
60        None
61    };
62
63    let assoc_func_fix = assoc_func_fix(ctx, d);
64
65    let mut fixes = vec![];
66    if let Some(field_fix) = field_fix {
67        fixes.push(field_fix);
68    }
69    if let Some(assoc_func_fix) = assoc_func_fix {
70        fixes.push(assoc_func_fix);
71    }
72
73    if fixes.is_empty() { None } else { Some(fixes) }
74}
75
76fn field_fix(
77    ctx: &DiagnosticsContext<'_, '_>,
78    d: &hir::UnresolvedMethodCall<'_>,
79    ty: &hir::Type<'_>,
80) -> Option<Assist> {
81    if !ty.impls_fnonce(ctx.sema.db) {
82        return None;
83    }
84    let expr_ptr = &d.expr;
85    let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
86    let expr = expr_ptr.value.to_node(&root);
87    let (file_id, range) = match expr.left()? {
88        ast::Expr::MethodCallExpr(mcall) => {
89            let FileRange { range, file_id } =
90                ctx.sema.original_range_opt(mcall.receiver()?.syntax())?;
91            let FileRange { range: range2, file_id: file_id2 } =
92                ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?;
93            if file_id != file_id2 {
94                return None;
95            }
96            (file_id, TextRange::new(range.start(), range2.end()))
97        }
98        _ => return None,
99    };
100    Some(Assist {
101        id: AssistId::quick_fix("expected-method-found-field-fix"),
102        label: Label::new("Use parentheses to call the value of the field".to_owned()),
103        group: None,
104        target: range,
105        source_change: Some(SourceChange::from_iter([
106            (file_id.file_id(ctx.sema.db), TextEdit::insert(range.start(), "(".to_owned())),
107            (file_id.file_id(ctx.sema.db), TextEdit::insert(range.end(), ")".to_owned())),
108        ])),
109        command: None,
110    })
111}
112
113fn assoc_func_fix(
114    ctx: &DiagnosticsContext<'_, '_>,
115    d: &hir::UnresolvedMethodCall<'_>,
116) -> Option<Assist> {
117    if let Some(f) = d.assoc_func_with_same_name {
118        let db = ctx.sema.db;
119
120        let expr_ptr = &d.expr;
121        let root = db.parse_or_expand(expr_ptr.file_id);
122        let expr: ast::Expr = expr_ptr.value.to_node(&root).left()?;
123
124        let call = ast::MethodCallExpr::cast(expr.syntax().clone())?;
125        let range = InFile::new(expr_ptr.file_id, call.syntax().text_range())
126            .original_node_file_range_rooted_opt(db)?;
127
128        let receiver = call.receiver()?;
129        let receiver_type = &ctx.sema.type_of_expr(&receiver)?.original;
130
131        let assoc_fn_params = f.assoc_fn_params(db);
132        let need_to_take_receiver_as_first_arg = assoc_fn_params.first().is_some_and(|first_arg| {
133            // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
134            // type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
135            // However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
136            // so `first_arg.ty() == receiver_type` evaluate to `false` here.
137            // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
138            // apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
139
140            // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
141            first_arg.ty() == receiver_type || first_arg.ty().as_adt() == receiver_type.as_adt()
142        });
143
144        let mut receiver_type_adt_name =
145            receiver_type.as_adt()?.name(db).display_no_db(ctx.edition).to_smolstr();
146
147        let generic_parameters: Vec<SmolStr> =
148            receiver_type.generic_parameters(db, ctx.display_target).collect();
149        // if receiver should be pass as first arg in the assoc func,
150        // we could omit generic parameters cause compiler can deduce it automatically
151        if !need_to_take_receiver_as_first_arg && !generic_parameters.is_empty() {
152            let generic_parameters = generic_parameters.join(", ");
153            receiver_type_adt_name =
154                format_smolstr!("{receiver_type_adt_name}::<{generic_parameters}>");
155        }
156
157        let method_name = call.name_ref()?;
158        let assoc_func_path = format!("{receiver_type_adt_name}::{method_name}");
159
160        let assoc_func_path = make::expr_path(make::path_from_text(&assoc_func_path));
161
162        let args: Vec<_> = if need_to_take_receiver_as_first_arg {
163            std::iter::once(receiver).chain(call.arg_list()?.args()).collect()
164        } else {
165            call.arg_list()?.args().collect()
166        };
167        let args = make::arg_list(args);
168
169        let assoc_func_call_expr_string = make::expr_call(assoc_func_path, args).to_string();
170
171        Some(Assist {
172            id: AssistId::quick_fix("method_call_to_assoc_func_call_fix"),
173            label: Label::new(format!(
174                "Use associated func call instead: `{assoc_func_call_expr_string}`"
175            )),
176            group: None,
177            target: range.range,
178            source_change: Some(SourceChange::from_text_edit(
179                range.file_id.file_id(ctx.sema.db),
180                TextEdit::replace(range.range, assoc_func_call_expr_string),
181            )),
182            command: None,
183        })
184    } else {
185        None
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::tests::{check_diagnostics, check_fix};
192
193    #[test]
194    fn test_assoc_func_fix() {
195        check_fix(
196            r#"
197struct A {}
198
199impl A {
200    fn hello() {}
201}
202fn main() {
203    let a = A{};
204    a.hello$0();
205}
206"#,
207            r#"
208struct A {}
209
210impl A {
211    fn hello() {}
212}
213fn main() {
214    let a = A{};
215    A::hello();
216}
217"#,
218        );
219    }
220
221    #[test]
222    fn test_assoc_func_diagnostic() {
223        check_diagnostics(
224            r#"
225struct A {}
226impl A {
227    fn hello() {}
228}
229fn main() {
230    let a = A{};
231    a.hello();
232   // ^^^^^ 💡 error: no method `hello` on type `A`, but an associated function with a similar name exists
233}
234"#,
235        );
236    }
237
238    #[test]
239    fn test_assoc_func_fix_with_generic() {
240        check_fix(
241            r#"
242struct A<T, U> {
243    a: T,
244    b: U
245}
246
247impl<T, U> A<T, U> {
248    fn foo() {}
249}
250fn main() {
251    let a = A {a: 0, b: ""};
252    a.foo()$0;
253}
254"#,
255            r#"
256struct A<T, U> {
257    a: T,
258    b: U
259}
260
261impl<T, U> A<T, U> {
262    fn foo() {}
263}
264fn main() {
265    let a = A {a: 0, b: ""};
266    A::<i32, &str>::foo();
267}
268"#,
269        );
270    }
271
272    #[test]
273    fn smoke_test() {
274        check_diagnostics(
275            r#"
276fn main() {
277    ().foo();
278    // ^^^ error: no method `foo` on type `()`
279}
280"#,
281        );
282    }
283
284    #[test]
285    fn smoke_test_in_macro_def_site() {
286        check_diagnostics(
287            r#"
288macro_rules! m {
289    ($rcv:expr) => {
290        $rcv.foo()
291    }
292}
293fn main() {
294    m!(());
295 // ^^ error: no method `foo` on type `()`
296}
297"#,
298        );
299    }
300
301    #[test]
302    fn smoke_test_in_macro_call_site() {
303        check_diagnostics(
304            r#"
305macro_rules! m {
306    ($ident:ident) => {
307        ().$ident()
308    }
309}
310fn main() {
311    m!(foo);
312    // ^^^ error: no method `foo` on type `()`
313}
314"#,
315        );
316    }
317
318    #[test]
319    fn field() {
320        check_diagnostics(
321            r#"
322struct Foo { bar: i32 }
323fn foo() {
324    Foo { bar: 0 }.bar();
325                // ^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists
326}
327"#,
328        );
329    }
330
331    #[test]
332    fn callable_field() {
333        check_fix(
334            r#"
335//- minicore: fn
336struct Foo { bar: fn() }
337fn foo() {
338    Foo { bar: foo }.b$0ar();
339}
340"#,
341            r#"
342struct Foo { bar: fn() }
343fn foo() {
344    (Foo { bar: foo }.bar)();
345}
346"#,
347        );
348    }
349
350    #[test]
351    fn iter_collect() {
352        check_diagnostics(
353            r#"
354//- minicore: unsize, coerce_unsized, iterator, iterators, sized
355struct Map<K, V>(K, V);
356impl<K, V> FromIterator<(K, V)> for Map<K, V> {
357    fn from_iter<T: IntoIterator<Item = (K, V)>>(_iter: T) -> Self {
358        loop {}
359    }
360}
361
362fn foo() -> Map<i32, &'static [&'static str]> {
363    [
364        (123, &["abc", "def"] as _),
365        (456, &["ghi"] as _),
366    ].into_iter().collect()
367}
368        "#,
369        );
370    }
371}