11//! Renderer for function calls.
22
3- use hir:: { AsAssocItem , HirDisplay , db:: HirDatabase } ;
4- use ide_db:: { SnippetCap , SymbolKind } ;
3+ use hir:: { AsAssocItem , AssocItemContainer , HirDisplay , PathResolution , db:: HirDatabase } ;
4+ use ide_db:: { SnippetCap , SymbolKind , text_edit :: TextEdit } ;
55use itertools:: Itertools ;
6+ use std:: borrow:: Cow ;
67use stdx:: { format_to, to_lower_snake_case} ;
7- use syntax:: { AstNode , SmolStr , ToSmolStr , format_smolstr} ;
8+ use syntax:: { AstNode , SmolStr , TextRange , ToSmolStr , format_smolstr} ;
89
910use crate :: {
1011 CallableSnippets ,
1112 context:: {
12- CompleteSemicolon , CompletionContext , DotAccess , DotAccessKind , PathCompletionCtx , PathKind ,
13+ CompleteSemicolon , CompletionContext , DotAccess , DotAccessKind , PathCompletionCtx ,
14+ PathKind , Qualified ,
1315 } ,
1416 item:: {
1517 Builder , CompletionItem , CompletionItemKind , CompletionRelevance , CompletionRelevanceFn ,
@@ -26,6 +28,17 @@ enum FuncKind<'ctx> {
2628 Method ( & ' ctx DotAccess < ' ctx > , Option < SmolStr > ) ,
2729}
2830
31+ struct UfcsData {
32+ prefix : String ,
33+ replacement_range : TextRange ,
34+ }
35+
36+ #[ derive( Clone , Copy ) ]
37+ pub ( super ) struct CallSnippetRewrite < ' a > {
38+ prefix : & ' a str ,
39+ replace_range : TextRange ,
40+ }
41+
2942pub ( crate ) fn render_fn (
3043 ctx : RenderContext < ' _ > ,
3144 path_ctx : & PathCompletionCtx < ' _ > ,
@@ -87,6 +100,18 @@ fn render(
87100 }
88101 } ) ;
89102
103+ let trait_container = assoc_item. and_then ( |assoc_item| match assoc_item. container ( db) {
104+ AssocItemContainer :: Trait ( trait_) => Some ( trait_) ,
105+ _ => None ,
106+ } ) ;
107+
108+ let ufcs_data = match ( & func_kind, trait_container) {
109+ ( FuncKind :: Function ( path_ctx) , Some ( trait_) ) => {
110+ trait_method_ufcs_data ( ctx. completion , path_ctx, trait_)
111+ }
112+ _ => None ,
113+ } ;
114+
90115 let ( has_dot_receiver, has_call_parens, cap) = match func_kind {
91116 FuncKind :: Function ( & PathCompletionCtx {
92117 kind : PathKind :: Expr { .. } ,
@@ -151,7 +176,16 @@ fn render(
151176 . detail ( detail)
152177 . lookup_by ( name. as_str ( ) . to_smolstr ( ) ) ;
153178
179+ if let Some ( data) = & ufcs_data {
180+ let insert_text = format ! ( "{}{}" , data. prefix, escaped_call) ;
181+ item. text_edit ( TextEdit :: replace ( data. replacement_range , insert_text) ) ;
182+ }
183+
154184 if let Some ( ( cap, ( self_param, params) ) ) = complete_call_parens {
185+ let rewrite = ufcs_data. as_ref ( ) . map ( |data| CallSnippetRewrite {
186+ replace_range : data. replacement_range ,
187+ prefix : & data. prefix ,
188+ } ) ;
155189 add_call_parens (
156190 & mut item,
157191 completion,
@@ -161,6 +195,7 @@ fn render(
161195 self_param,
162196 params,
163197 & ret_type,
198+ rewrite,
164199 ) ;
165200 }
166201
@@ -217,11 +252,19 @@ pub(super) fn add_call_parens<'b>(
217252 self_param : Option < hir:: SelfParam > ,
218253 params : Vec < hir:: Param < ' _ > > ,
219254 ret_type : & hir:: Type < ' _ > ,
255+ mut rewrite : Option < CallSnippetRewrite < ' _ > > ,
220256) -> & ' b mut Builder {
221257 cov_mark:: hit!( inserts_parens_for_function_calls) ;
222258
259+ let call_head = if let Some ( rewrite_data) = rewrite. as_ref ( ) {
260+ Cow :: Owned ( format ! ( "{}{}" , rewrite_data. prefix, escaped_name) )
261+ } else {
262+ Cow :: Borrowed ( escaped_name. as_str ( ) )
263+ } ;
264+ let call_head = call_head. as_ref ( ) ;
265+
223266 let ( mut snippet, label_suffix) = if self_param. is_none ( ) && params. is_empty ( ) {
224- ( format ! ( "{escaped_name }()$0" ) , "()" )
267+ ( format ! ( "{call_head }()$0" ) , "()" )
225268 } else {
226269 builder. trigger_call_info ( ) ;
227270 let snippet = if let Some ( CallableSnippets :: FillArguments ) = ctx. config . callable {
@@ -247,20 +290,19 @@ pub(super) fn add_call_parens<'b>(
247290 match self_param {
248291 Some ( self_param) => {
249292 format ! (
250- "{}(${{1:{}}}{}{})$0" ,
251- escaped_name,
293+ "{call_head}(${{1:{}}}{}{})$0" ,
252294 self_param. display( ctx. db, ctx. display_target) ,
253295 if params. is_empty( ) { "" } else { ", " } ,
254296 function_params_snippet
255297 )
256298 }
257299 None => {
258- format ! ( "{escaped_name }({function_params_snippet})$0" )
300+ format ! ( "{call_head }({function_params_snippet})$0" )
259301 }
260302 }
261303 } else {
262304 cov_mark:: hit!( suppress_arg_snippets) ;
263- format ! ( "{escaped_name }($0)" )
305+ format ! ( "{call_head }($0)" )
264306 } ;
265307
266308 ( snippet, "(…)" )
@@ -283,7 +325,12 @@ pub(super) fn add_call_parens<'b>(
283325 }
284326 }
285327 }
286- builder. label ( SmolStr :: from_iter ( [ & name, label_suffix] ) ) . insert_snippet ( cap, snippet)
328+ let builder = builder. label ( SmolStr :: from_iter ( [ & name, label_suffix] ) ) ;
329+ if let Some ( rewrite_data) = rewrite. take ( ) {
330+ builder. snippet_edit ( cap, TextEdit :: replace ( rewrite_data. replace_range , snippet) )
331+ } else {
332+ builder. insert_snippet ( cap, snippet)
333+ }
287334}
288335
289336fn ref_of_param ( ctx : & CompletionContext < ' _ > , arg : & str , ty : & hir:: Type < ' _ > ) -> & ' static str {
@@ -393,6 +440,68 @@ fn params<'db>(
393440 Some ( ( self_param, func. params_without_self ( ctx. db ) ) )
394441}
395442
443+ fn trait_method_ufcs_data (
444+ completion : & CompletionContext < ' _ > ,
445+ path_ctx : & PathCompletionCtx < ' _ > ,
446+ trait_ : hir:: Trait ,
447+ ) -> Option < UfcsData > {
448+ let needs_ufcs = match & path_ctx. qualified {
449+ Qualified :: With { resolution : Some ( resolution) , .. } => resolution_targets_type ( resolution) ,
450+ Qualified :: TypeAnchor { ty, trait_ : None } => ty. is_some ( ) ,
451+ _ => false ,
452+ } ;
453+
454+ if !needs_ufcs {
455+ return None ;
456+ }
457+
458+ let ( qualifier_text, replacement_range) = qualifier_text_and_range ( completion, path_ctx) ?;
459+ let trait_path = trait_path_string ( completion, trait_) ;
460+ let prefix = format ! ( "<{qualifier_text} as {trait_path}>::" ) ;
461+ Some ( UfcsData { prefix, replacement_range } )
462+ }
463+
464+ fn qualifier_text_and_range (
465+ completion : & CompletionContext < ' _ > ,
466+ path_ctx : & PathCompletionCtx < ' _ > ,
467+ ) -> Option < ( String , TextRange ) > {
468+ let qualifier = path_ctx
469+ . original_path
470+ . as_ref ( )
471+ . and_then ( |path| path. qualifier ( ) )
472+ . or_else ( || path_ctx. path . qualifier ( ) ) ?;
473+ let qualifier_syntax = qualifier. syntax ( ) . clone ( ) ;
474+ let text = qualifier_syntax. text ( ) . to_string ( ) ;
475+ if text. is_empty ( ) {
476+ return None ;
477+ }
478+ let start = qualifier_syntax. text_range ( ) . start ( ) ;
479+ let end = completion. position . offset ;
480+ if start >= end {
481+ return None ;
482+ }
483+ Some ( ( text, TextRange :: new ( start, end) ) )
484+ }
485+
486+ fn trait_path_string ( ctx : & CompletionContext < ' _ > , trait_ : hir:: Trait ) -> String {
487+ let cfg = ctx. config . find_path_config ( ctx. is_nightly ) ;
488+ ctx. module
489+ . find_path ( ctx. db , hir:: ModuleDef :: Trait ( trait_) , cfg)
490+ . map ( |path| path. display ( ctx. db , ctx. edition ) . to_string ( ) )
491+ . unwrap_or_else ( || trait_. name ( ctx. db ) . display_no_db ( ctx. edition ) . to_string ( ) )
492+ }
493+
494+ fn resolution_targets_type ( resolution : & PathResolution ) -> bool {
495+ match resolution {
496+ PathResolution :: Def ( def) => matches ! (
497+ def,
498+ hir:: ModuleDef :: Adt ( _) | hir:: ModuleDef :: TypeAlias ( _) | hir:: ModuleDef :: BuiltinType ( _)
499+ ) ,
500+ PathResolution :: TypeParam ( _) | PathResolution :: SelfType ( _) => true ,
501+ _ => false ,
502+ }
503+ }
504+
396505#[ cfg( test) ]
397506mod tests {
398507 use crate :: {
0 commit comments