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