1
- use hir:: { db:: ExpandDatabase , HirDisplay } ;
1
+ use hir:: { db:: ExpandDatabase , AssocItem , HirDisplay } ;
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,7 +109,93 @@ 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 = call. syntax ( ) . text_range ( ) ;
125
+
126
+ let receiver = call. receiver ( ) ?;
127
+ let receiver_type = & ctx. sema . type_of_expr ( & receiver) ?. original ;
128
+
129
+ let need_to_take_receiver_as_first_arg = match hir:: AssocItem :: from ( assoc_item_id) {
130
+ AssocItem :: Function ( f) => {
131
+ let assoc_fn_params = f. assoc_fn_params ( db) ;
132
+ if assoc_fn_params. is_empty ( ) {
133
+ false
134
+ } else {
135
+ assoc_fn_params
136
+ . first ( )
137
+ . map ( |first_arg| {
138
+ // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
139
+ // type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
140
+ // However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
141
+ // so `first_arg.ty() == receiver_type` evaluate to `false` here.
142
+ // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
143
+ // apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
144
+
145
+ // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
146
+ first_arg. ty ( ) == receiver_type
147
+ || first_arg. ty ( ) . as_adt ( ) == receiver_type. as_adt ( )
148
+ } )
149
+ . unwrap_or ( false )
150
+ }
151
+ }
152
+ _ => false ,
153
+ } ;
154
+
155
+ let mut receiver_type_adt_name = receiver_type. as_adt ( ) ?. name ( db) . to_smol_str ( ) . to_string ( ) ;
156
+
157
+ let generic_parameters: Vec < SmolStr > = receiver_type. generic_parameters ( db) . collect ( ) ;
158
+ // if receiver should be pass as first arg in the assoc func,
159
+ // we could omit generic parameters cause compiler can deduce it automatically
160
+ if !need_to_take_receiver_as_first_arg && !generic_parameters. is_empty ( ) {
161
+ let generic_parameters = generic_parameters. join ( ", " ) . to_string ( ) ;
162
+ receiver_type_adt_name =
163
+ format ! ( "{}::<{}>" , receiver_type_adt_name, generic_parameters) ;
164
+ }
165
+
166
+ let method_name = call. name_ref ( ) ?;
167
+ let assoc_func_call = format ! ( "{}::{}()" , receiver_type_adt_name, method_name) ;
168
+
169
+ let assoc_func_call = make:: expr_path ( make:: path_from_text ( & assoc_func_call) ) ;
170
+
171
+ let args: Vec < _ > = if need_to_take_receiver_as_first_arg {
172
+ std:: iter:: once ( receiver) . chain ( call. arg_list ( ) ?. args ( ) ) . collect ( )
173
+ } else {
174
+ call. arg_list ( ) ?. args ( ) . collect ( )
175
+ } ;
176
+ let args = make:: arg_list ( args) ;
177
+
178
+ let assoc_func_call_expr_string = make:: expr_call ( assoc_func_call, args) . to_string ( ) ;
179
+
180
+ let file_id = ctx. sema . original_range_opt ( call. receiver ( ) ?. syntax ( ) ) ?. file_id ;
181
+
182
+ Some ( Assist {
183
+ id : AssistId ( "method_call_to_assoc_func_call_fix" , AssistKind :: QuickFix ) ,
184
+ label : Label :: new ( format ! (
185
+ "Use associated func call instead: `{}`" ,
186
+ assoc_func_call_expr_string
187
+ ) ) ,
188
+ group : None ,
189
+ target : range,
190
+ source_change : Some ( SourceChange :: from_text_edit (
191
+ file_id,
192
+ TextEdit :: replace ( range, assoc_func_call_expr_string) ,
193
+ ) ) ,
194
+ trigger_signature_help : false ,
195
+ } )
196
+ } else {
197
+ None
198
+ }
92
199
}
93
200
94
201
#[ cfg( test) ]
0 commit comments