1- use hir:: { db:: ExpandDatabase , HirDisplay } ;
1+ use hir:: { db:: ExpandDatabase , AssocItem , HirDisplay , InFile } ;
22use ide_db:: {
33 assists:: { Assist , AssistId , AssistKind } ,
44 base_db:: FileRange ,
55 label:: Label ,
66 source_change:: SourceChange ,
77} ;
8- use syntax:: { ast, AstNode , TextRange } ;
8+ use syntax:: {
9+ ast:: { self , make, HasArgList } ,
10+ AstNode , SmolStr , TextRange ,
11+ } ;
912use text_edit:: TextEdit ;
1013
1114use crate :: { adjusted_display_range_new, Diagnostic , DiagnosticCode , DiagnosticsContext } ;
@@ -17,15 +20,17 @@ pub(crate) fn unresolved_method(
1720 ctx : & DiagnosticsContext < ' _ > ,
1821 d : & hir:: UnresolvedMethodCall ,
1922) -> Diagnostic {
20- let field_suffix = if d. field_with_same_name . is_some ( ) {
23+ let suffix = if d. field_with_same_name . is_some ( ) {
2124 ", 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"
2227 } else {
2328 ""
2429 } ;
2530 Diagnostic :: new (
2631 DiagnosticCode :: RustcHardError ( "E0599" ) ,
2732 format ! (
28- "no method `{}` on type `{}`{field_suffix }" ,
33+ "no method `{}` on type `{}`{suffix }" ,
2934 d. name. display( ctx. sema. db) ,
3035 d. receiver. display( ctx. sema. db)
3136 ) ,
@@ -46,19 +51,35 @@ pub(crate) fn unresolved_method(
4651}
4752
4853fn 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 {
5055 field_fix ( ctx, d, ty)
5156 } else {
5257 // FIXME: add quickfix
5358 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)
5475 }
5576}
5677
5778fn field_fix (
5879 ctx : & DiagnosticsContext < ' _ > ,
5980 d : & hir:: UnresolvedMethodCall ,
6081 ty : & hir:: Type ,
61- ) -> Option < Vec < Assist > > {
82+ ) -> Option < Assist > {
6283 if !ty. impls_fnonce ( ctx. sema . db ) {
6384 return None ;
6485 }
@@ -78,7 +99,7 @@ fn field_fix(
7899 }
79100 _ => return None ,
80101 } ;
81- Some ( vec ! [ Assist {
102+ Some ( Assist {
82103 id : AssistId ( "expected-method-found-field-fix" , AssistKind :: QuickFix ) ,
83104 label : Label :: new ( "Use parentheses to call the value of the field" . to_string ( ) ) ,
84105 group : None ,
@@ -88,13 +109,180 @@ fn field_fix(
88109 ( file_id, TextEdit :: insert ( range. end ( ) , ")" . to_owned ( ) ) ) ,
89110 ] ) ) ,
90111 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+ }
92201}
93202
94203#[ cfg( test) ]
95204mod tests {
96205 use crate :: tests:: { check_diagnostics, check_fix} ;
97206
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+
98286 #[ test]
99287 fn smoke_test ( ) {
100288 check_diagnostics (
0 commit comments