]>
Commit | Line | Data |
---|---|---|
353b0b11 FG |
1 | use hir::{db::ExpandDatabase, HirDisplay}; |
2 | use ide_db::{ | |
3 | assists::{Assist, AssistId, AssistKind}, | |
4 | base_db::FileRange, | |
5 | label::Label, | |
6 | source_change::SourceChange, | |
7 | }; | |
8 | use syntax::{ast, AstNode, TextRange}; | |
9 | use text_edit::TextEdit; | |
10 | ||
4b012472 | 11 | use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext}; |
353b0b11 FG |
12 | |
13 | // Diagnostic: unresolved-method | |
14 | // | |
15 | // This diagnostic is triggered if a method does not exist on a given type. | |
16 | pub(crate) fn unresolved_method( | |
17 | ctx: &DiagnosticsContext<'_>, | |
18 | d: &hir::UnresolvedMethodCall, | |
19 | ) -> Diagnostic { | |
20 | let field_suffix = if d.field_with_same_name.is_some() { | |
21 | ", but a field with a similar name exists" | |
22 | } else { | |
23 | "" | |
24 | }; | |
4b012472 | 25 | Diagnostic::new( |
add651ee | 26 | DiagnosticCode::RustcHardError("E0599"), |
353b0b11 FG |
27 | format!( |
28 | "no method `{}` on type `{}`{field_suffix}", | |
fe692bf9 | 29 | d.name.display(ctx.sema.db), |
353b0b11 FG |
30 | d.receiver.display(ctx.sema.db) |
31 | ), | |
4b012472 FG |
32 | adjusted_display_range_new(ctx, d.expr, &|expr| { |
33 | Some( | |
34 | match expr { | |
35 | ast::Expr::MethodCallExpr(it) => it.name_ref(), | |
36 | ast::Expr::FieldExpr(it) => it.name_ref(), | |
37 | _ => None, | |
38 | }? | |
39 | .syntax() | |
40 | .text_range(), | |
41 | ) | |
42 | }), | |
353b0b11 FG |
43 | ) |
44 | .with_fixes(fixes(ctx, d)) | |
45 | .experimental() | |
46 | } | |
47 | ||
48 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> { | |
49 | if let Some(ty) = &d.field_with_same_name { | |
50 | field_fix(ctx, d, ty) | |
51 | } else { | |
52 | // FIXME: add quickfix | |
53 | None | |
54 | } | |
55 | } | |
56 | ||
57 | fn field_fix( | |
58 | ctx: &DiagnosticsContext<'_>, | |
59 | d: &hir::UnresolvedMethodCall, | |
60 | ty: &hir::Type, | |
61 | ) -> Option<Vec<Assist>> { | |
62 | if !ty.impls_fnonce(ctx.sema.db) { | |
63 | return None; | |
64 | } | |
65 | let expr_ptr = &d.expr; | |
fe692bf9 | 66 | let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); |
353b0b11 FG |
67 | let expr = expr_ptr.value.to_node(&root); |
68 | let (file_id, range) = match expr { | |
69 | ast::Expr::MethodCallExpr(mcall) => { | |
70 | let FileRange { range, file_id } = | |
71 | ctx.sema.original_range_opt(mcall.receiver()?.syntax())?; | |
72 | let FileRange { range: range2, file_id: file_id2 } = | |
73 | ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?; | |
74 | if file_id != file_id2 { | |
75 | return None; | |
76 | } | |
77 | (file_id, TextRange::new(range.start(), range2.end())) | |
78 | } | |
79 | _ => return None, | |
80 | }; | |
81 | Some(vec![Assist { | |
82 | id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix), | |
83 | label: Label::new("Use parentheses to call the value of the field".to_string()), | |
84 | group: None, | |
85 | target: range, | |
86 | source_change: Some(SourceChange::from_iter([ | |
87 | (file_id, TextEdit::insert(range.start(), "(".to_owned())), | |
88 | (file_id, TextEdit::insert(range.end(), ")".to_owned())), | |
89 | ])), | |
90 | trigger_signature_help: false, | |
91 | }]) | |
92 | } | |
93 | ||
94 | #[cfg(test)] | |
95 | mod tests { | |
96 | use crate::tests::{check_diagnostics, check_fix}; | |
97 | ||
98 | #[test] | |
99 | fn smoke_test() { | |
100 | check_diagnostics( | |
101 | r#" | |
102 | fn main() { | |
103 | ().foo(); | |
4b012472 FG |
104 | // ^^^ error: no method `foo` on type `()` |
105 | } | |
106 | "#, | |
107 | ); | |
108 | } | |
109 | ||
110 | #[test] | |
111 | fn smoke_test_in_macro_def_site() { | |
112 | check_diagnostics( | |
113 | r#" | |
114 | macro_rules! m { | |
115 | ($rcv:expr) => { | |
116 | $rcv.foo() | |
117 | } | |
118 | } | |
119 | fn main() { | |
120 | m!(()); | |
121 | // ^^^^^^ error: no method `foo` on type `()` | |
122 | } | |
123 | "#, | |
124 | ); | |
125 | } | |
126 | ||
127 | #[test] | |
128 | fn smoke_test_in_macro_call_site() { | |
129 | check_diagnostics( | |
130 | r#" | |
131 | macro_rules! m { | |
132 | ($ident:ident) => { | |
133 | ().$ident() | |
134 | } | |
135 | } | |
136 | fn main() { | |
137 | m!(foo); | |
138 | // ^^^ error: no method `foo` on type `()` | |
353b0b11 FG |
139 | } |
140 | "#, | |
141 | ); | |
142 | } | |
143 | ||
144 | #[test] | |
145 | fn field() { | |
146 | check_diagnostics( | |
147 | r#" | |
148 | struct Foo { bar: i32 } | |
149 | fn foo() { | |
150 | Foo { bar: i32 }.bar(); | |
4b012472 | 151 | // ^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists |
353b0b11 FG |
152 | } |
153 | "#, | |
154 | ); | |
155 | } | |
156 | ||
157 | #[test] | |
158 | fn callable_field() { | |
159 | check_fix( | |
160 | r#" | |
161 | //- minicore: fn | |
162 | struct Foo { bar: fn() } | |
163 | fn foo() { | |
164 | Foo { bar: foo }.b$0ar(); | |
165 | } | |
166 | "#, | |
167 | r#" | |
168 | struct Foo { bar: fn() } | |
169 | fn foo() { | |
170 | (Foo { bar: foo }.bar)(); | |
171 | } | |
172 | "#, | |
173 | ); | |
174 | } | |
175 | } |