1- use hir:: { db:: ExpandDatabase , HirDisplay } ;
1+ use hir:: { db:: ExpandDatabase , AssocItem , HirDisplay } ;
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,7 +109,93 @@ 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 = 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+ }
92199}
93200
94201#[ cfg( test) ]
0 commit comments