1
- use hir:: { db:: ExpandDatabase , HirDisplay } ;
1
+ use hir:: { db:: ExpandDatabase , AssocItem , HirDisplay , InFile } ;
2
2
use ide_db:: {
3
3
assists:: { Assist , AssistId , AssistKind } ,
4
4
base_db:: FileRange ,
5
5
label:: Label ,
6
6
source_change:: SourceChange ,
7
7
} ;
8
- use syntax:: { ast, AstNode , TextRange } ;
8
+ use syntax:: {
9
+ ast:: { self , make, HasArgList } ,
10
+ AstNode , SmolStr , TextRange ,
11
+ } ;
9
12
use text_edit:: TextEdit ;
10
13
11
14
use crate :: { adjusted_display_range_new, Diagnostic , DiagnosticCode , DiagnosticsContext } ;
@@ -17,15 +20,17 @@ pub(crate) fn unresolved_method(
17
20
ctx : & DiagnosticsContext < ' _ > ,
18
21
d : & hir:: UnresolvedMethodCall ,
19
22
) -> Diagnostic {
20
- let field_suffix = if d. field_with_same_name . is_some ( ) {
23
+ let suffix = if d. field_with_same_name . is_some ( ) {
21
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"
22
27
} else {
23
28
""
24
29
} ;
25
30
Diagnostic :: new (
26
31
DiagnosticCode :: RustcHardError ( "E0599" ) ,
27
32
format ! (
28
- "no method `{}` on type `{}`{field_suffix }" ,
33
+ "no method `{}` on type `{}`{suffix }" ,
29
34
d. name. display( ctx. sema. db) ,
30
35
d. receiver. display( ctx. sema. db)
31
36
) ,
@@ -46,19 +51,35 @@ pub(crate) fn unresolved_method(
46
51
}
47
52
48
53
fn fixes ( ctx : & DiagnosticsContext < ' _ > , d : & hir:: UnresolvedMethodCall ) -> Option < Vec < Assist > > {
49
- if let Some ( ty) = & d. field_with_same_name {
54
+ let field_fix = if let Some ( ty) = & d. field_with_same_name {
50
55
field_fix ( ctx, d, ty)
51
56
} else {
52
57
// FIXME: add quickfix
53
58
None
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)
54
75
}
55
76
}
56
77
57
78
fn field_fix (
58
79
ctx : & DiagnosticsContext < ' _ > ,
59
80
d : & hir:: UnresolvedMethodCall ,
60
81
ty : & hir:: Type ,
61
- ) -> Option < Vec < Assist > > {
82
+ ) -> Option < Assist > {
62
83
if !ty. impls_fnonce ( ctx. sema . db ) {
63
84
return None ;
64
85
}
@@ -78,7 +99,7 @@ fn field_fix(
78
99
}
79
100
_ => return None ,
80
101
} ;
81
- Some ( vec ! [ Assist {
102
+ Some ( Assist {
82
103
id : AssistId ( "expected-method-found-field-fix" , AssistKind :: QuickFix ) ,
83
104
label : Label :: new ( "Use parentheses to call the value of the field" . to_string ( ) ) ,
84
105
group : None ,
@@ -88,13 +109,180 @@ fn field_fix(
88
109
( file_id, TextEdit :: insert ( range. end ( ) , ")" . to_owned ( ) ) ) ,
89
110
] ) ) ,
90
111
trigger_signature_help : false ,
91
- } ] )
112
+ } )
113
+ }
114
+
115
+ fn 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 ( ", " ) . to_string ( ) ;
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
+ }
92
201
}
93
202
94
203
#[ cfg( test) ]
95
204
mod tests {
96
205
use crate :: tests:: { check_diagnostics, check_fix} ;
97
206
207
+ #[ test]
208
+ fn test_assoc_func_fix ( ) {
209
+ check_fix (
210
+ r#"
211
+ struct A {}
212
+
213
+ impl A {
214
+ fn hello() {}
215
+ }
216
+ fn main() {
217
+ let a = A{};
218
+ a.hello$0();
219
+ }
220
+ "# ,
221
+ r#"
222
+ struct A {}
223
+
224
+ impl A {
225
+ fn hello() {}
226
+ }
227
+ fn 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#"
239
+ struct A {}
240
+ impl A {
241
+ fn hello() {}
242
+ }
243
+ fn 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#"
256
+ struct A<T, U> {
257
+ a: T,
258
+ b: U
259
+ }
260
+
261
+ impl<T, U> A<T, U> {
262
+ fn foo() {}
263
+ }
264
+ fn main() {
265
+ let a = A {a: 0, b: ""};
266
+ a.foo()$0;
267
+ }
268
+ "# ,
269
+ r#"
270
+ struct A<T, U> {
271
+ a: T,
272
+ b: U
273
+ }
274
+
275
+ impl<T, U> A<T, U> {
276
+ fn foo() {}
277
+ }
278
+ fn main() {
279
+ let a = A {a: 0, b: ""};
280
+ A::<i32, &str>::foo();
281
+ }
282
+ "# ,
283
+ ) ;
284
+ }
285
+
98
286
#[ test]
99
287
fn smoke_test ( ) {
100
288
check_diagnostics (
0 commit comments