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