@@ -5,7 +5,7 @@ use ide_db::RootDatabase;
55use syntax:: {
66 ast,
77 ast:: { make, ArgListOwner } ,
8- AstNode , TextRange ,
8+ AstNode ,
99} ;
1010use test_utils:: mark;
1111
@@ -16,8 +16,6 @@ use crate::{
1616 AssistId , AssistKind , GroupLabel ,
1717} ;
1818
19- const ASSIST_ID : AssistId = AssistId ( "qualify_path" , AssistKind :: QuickFix ) ;
20-
2119// Assist: qualify_path
2220//
2321// If the name is unresolved, provides all possible qualified paths for it.
@@ -51,162 +49,151 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
5149 return None ;
5250 }
5351
52+ let candidate = import_assets. import_candidate ( ) ;
5453 let range = ctx. sema . original_range ( import_assets. syntax_under_caret ( ) ) . range ;
55- match import_assets. import_candidate ( ) {
56- ImportCandidate :: QualifierStart ( candidate) => {
54+
55+ let qualify_candidate = match candidate {
56+ ImportCandidate :: QualifierStart ( _) => {
57+ mark:: hit!( qualify_path_qualifier_start) ;
5758 let path = ast:: Path :: cast ( import_assets. syntax_under_caret ( ) . clone ( ) ) ?;
5859 let segment = path. segment ( ) ?;
59- qualify_path_qualifier_start ( acc , proposed_imports , range , segment, & candidate . name )
60+ QualifyCandidate :: QualifierStart ( segment)
6061 }
61- ImportCandidate :: UnqualifiedName ( candidate) => {
62- qualify_path_unqualified_name ( acc, proposed_imports, range, & candidate. name )
62+ ImportCandidate :: UnqualifiedName ( _) => {
63+ mark:: hit!( qualify_path_unqualified_name) ;
64+ QualifyCandidate :: UnqualifiedName
6365 }
6466 ImportCandidate :: TraitAssocItem ( _) => {
67+ mark:: hit!( qualify_path_trait_assoc_item) ;
6568 let path = ast:: Path :: cast ( import_assets. syntax_under_caret ( ) . clone ( ) ) ?;
6669 let ( qualifier, segment) = ( path. qualifier ( ) ?, path. segment ( ) ?) ;
67- qualify_path_trait_assoc_item ( acc , proposed_imports , range , qualifier, segment)
70+ QualifyCandidate :: TraitAssocItem ( qualifier, segment)
6871 }
6972 ImportCandidate :: TraitMethod ( _) => {
73+ mark:: hit!( qualify_path_trait_method) ;
7074 let mcall_expr = ast:: MethodCallExpr :: cast ( import_assets. syntax_under_caret ( ) . clone ( ) ) ?;
71- qualify_path_trait_method ( acc , ctx. sema . db , proposed_imports , range , mcall_expr) ? ;
75+ QualifyCandidate :: TraitMethod ( ctx. sema . db , mcall_expr)
7276 }
7377 } ;
74- Some ( ( ) )
75- }
7678
77- // a test that covers this -> `associated_struct_const`
78- fn qualify_path_qualifier_start (
79- acc : & mut Assists ,
80- proposed_imports : Vec < ( hir:: ModPath , hir:: ItemInNs ) > ,
81- range : TextRange ,
82- segment : ast:: PathSegment ,
83- qualifier_start : & ast:: NameRef ,
84- ) {
85- mark:: hit!( qualify_path_qualifier_start) ;
86- let group_label = GroupLabel ( format ! ( "Qualify {}" , qualifier_start) ) ;
87- for ( import, _) in proposed_imports {
79+ let group_label = group_label ( candidate) ;
80+ for ( import, item) in proposed_imports {
8881 acc. add_group (
8982 & group_label,
90- ASSIST_ID ,
91- format ! ( "Qualify with `{}`" , & import) ,
83+ AssistId ( "qualify_path" , AssistKind :: QuickFix ) ,
84+ label ( candidate , & import) ,
9285 range,
9386 |builder| {
94- let import = mod_path_to_ast ( & import) ;
95- builder. replace ( range, format ! ( "{}::{}" , import, segment) ) ;
87+ qualify_candidate. qualify (
88+ |replace_with : String | builder. replace ( range, replace_with) ,
89+ import,
90+ item,
91+ )
9692 } ,
9793 ) ;
9894 }
95+ Some ( ( ) )
9996}
10097
101- // a test that covers this -> `applicable_when_found_an_import_partial`
102- fn qualify_path_unqualified_name (
103- acc : & mut Assists ,
104- proposed_imports : Vec < ( hir:: ModPath , hir:: ItemInNs ) > ,
105- range : TextRange ,
106- name : & ast:: NameRef ,
107- ) {
108- mark:: hit!( qualify_path_unqualified_name) ;
109- let group_label = GroupLabel ( format ! ( "Qualify {}" , name) ) ;
110- for ( import, _) in proposed_imports {
111- acc. add_group (
112- & group_label,
113- ASSIST_ID ,
114- format ! ( "Qualify as `{}`" , & import) ,
115- range,
116- |builder| builder. replace ( range, mod_path_to_ast ( & import) . to_string ( ) ) ,
117- ) ;
118- }
98+ enum QualifyCandidate < ' db > {
99+ QualifierStart ( ast:: PathSegment ) ,
100+ UnqualifiedName ,
101+ TraitAssocItem ( ast:: Path , ast:: PathSegment ) ,
102+ TraitMethod ( & ' db RootDatabase , ast:: MethodCallExpr ) ,
119103}
120104
121- // a test that covers this -> `associated_trait_const`
122- fn qualify_path_trait_assoc_item (
123- acc : & mut Assists ,
124- proposed_imports : Vec < ( hir:: ModPath , hir:: ItemInNs ) > ,
125- range : TextRange ,
126- qualifier : ast:: Path ,
127- segment : ast:: PathSegment ,
128- ) {
129- mark:: hit!( qualify_path_trait_assoc_item) ;
130- let group_label = GroupLabel ( format ! ( "Qualify {}" , & segment) ) ;
131- for ( import, _) in proposed_imports {
132- acc. add_group (
133- & group_label,
134- ASSIST_ID ,
135- format ! ( "Qualify with cast as `{}`" , & import) ,
136- range,
137- |builder| {
105+ impl QualifyCandidate < ' _ > {
106+ fn qualify ( & self , mut replacer : impl FnMut ( String ) , import : hir:: ModPath , item : hir:: ItemInNs ) {
107+ match self {
108+ QualifyCandidate :: QualifierStart ( segment) => {
138109 let import = mod_path_to_ast ( & import) ;
139- builder. replace ( range, format ! ( "<{} as {}>::{}" , qualifier, import, segment) ) ;
140- } ,
141- ) ;
110+ replacer ( format ! ( "{}::{}" , import, segment) ) ;
111+ }
112+ QualifyCandidate :: UnqualifiedName => replacer ( mod_path_to_ast ( & import) . to_string ( ) ) ,
113+ QualifyCandidate :: TraitAssocItem ( qualifier, segment) => {
114+ let import = mod_path_to_ast ( & import) ;
115+ replacer ( format ! ( "<{} as {}>::{}" , qualifier, import, segment) ) ;
116+ }
117+ & QualifyCandidate :: TraitMethod ( db, ref mcall_expr) => {
118+ Self :: qualify_trait_method ( db, mcall_expr, replacer, import, item) ;
119+ }
120+ }
121+ }
122+
123+ fn qualify_trait_method (
124+ db : & RootDatabase ,
125+ mcall_expr : & ast:: MethodCallExpr ,
126+ mut replacer : impl FnMut ( String ) ,
127+ import : hir:: ModPath ,
128+ item : hir:: ItemInNs ,
129+ ) -> Option < ( ) > {
130+ let receiver = mcall_expr. receiver ( ) ?;
131+ let trait_method_name = mcall_expr. name_ref ( ) ?;
132+ let arg_list = mcall_expr. arg_list ( ) . map ( |arg_list| arg_list. args ( ) ) ;
133+ let trait_ = item_as_trait ( item) ?;
134+ let method = find_trait_method ( db, trait_, & trait_method_name) ?;
135+ if let Some ( self_access) = method. self_param ( db) . map ( |sp| sp. access ( db) ) {
136+ let import = mod_path_to_ast ( & import) ;
137+ let receiver = match self_access {
138+ hir:: Access :: Shared => make:: expr_ref ( receiver, false ) ,
139+ hir:: Access :: Exclusive => make:: expr_ref ( receiver, true ) ,
140+ hir:: Access :: Owned => receiver,
141+ } ;
142+ replacer ( format ! (
143+ "{}::{}{}" ,
144+ import,
145+ trait_method_name,
146+ match arg_list. clone( ) {
147+ Some ( args) => make:: arg_list( iter:: once( receiver) . chain( args) ) ,
148+ None => make:: arg_list( iter:: once( receiver) ) ,
149+ }
150+ ) ) ;
151+ }
152+ Some ( ( ) )
142153 }
143154}
144155
145- // a test that covers this -> `trait_method`
146- fn qualify_path_trait_method (
147- acc : & mut Assists ,
156+ fn find_trait_method (
148157 db : & RootDatabase ,
149- proposed_imports : Vec < ( hir:: ModPath , hir:: ItemInNs ) > ,
150- range : TextRange ,
151- mcall_expr : ast:: MethodCallExpr ,
152- ) -> Option < ( ) > {
153- mark:: hit!( qualify_path_trait_method) ;
154-
155- let receiver = mcall_expr. receiver ( ) ?;
156- let trait_method_name = mcall_expr. name_ref ( ) ?;
157- let arg_list = mcall_expr. arg_list ( ) . map ( |arg_list| arg_list. args ( ) ) ;
158- let group_label = GroupLabel ( format ! ( "Qualify {}" , trait_method_name) ) ;
159- let find_method = |item : & hir:: AssocItem | {
160- item. name ( db) . map ( |name| name == trait_method_name. as_name ( ) ) . unwrap_or ( false )
161- } ;
162- for ( import, trait_) in proposed_imports. into_iter ( ) . filter_map ( filter_trait) {
163- acc. add_group (
164- & group_label,
165- ASSIST_ID ,
166- format ! ( "Qualify `{}`" , & import) ,
167- range,
168- |builder| {
169- let import = mod_path_to_ast ( & import) ;
170- if let Some ( hir:: AssocItem :: Function ( method) ) =
171- trait_. items ( db) . into_iter ( ) . find ( find_method)
172- {
173- if let Some ( self_access) = method. self_param ( db) . map ( |sp| sp. access ( db) ) {
174- let receiver = receiver. clone ( ) ;
175- let receiver = match self_access {
176- hir:: Access :: Shared => make:: expr_ref ( receiver, false ) ,
177- hir:: Access :: Exclusive => make:: expr_ref ( receiver, true ) ,
178- hir:: Access :: Owned => receiver,
179- } ;
180- builder. replace (
181- range,
182- format ! (
183- "{}::{}{}" ,
184- import,
185- trait_method_name,
186- match arg_list. clone( ) {
187- Some ( args) => make:: arg_list( iter:: once( receiver) . chain( args) ) ,
188- None => make:: arg_list( iter:: once( receiver) ) ,
189- }
190- ) ,
191- ) ;
192- }
193- }
194- } ,
195- ) ;
158+ trait_ : hir:: Trait ,
159+ trait_method_name : & ast:: NameRef ,
160+ ) -> Option < hir:: Function > {
161+ if let Some ( hir:: AssocItem :: Function ( method) ) =
162+ trait_. items ( db) . into_iter ( ) . find ( |item : & hir:: AssocItem | {
163+ item. name ( db) . map ( |name| name == trait_method_name. as_name ( ) ) . unwrap_or ( false )
164+ } )
165+ {
166+ Some ( method)
167+ } else {
168+ None
196169 }
197- Some ( ( ) )
198170}
199171
200- fn filter_trait (
201- ( import, trait_) : ( hir:: ModPath , hir:: ItemInNs ) ,
202- ) -> Option < ( hir:: ModPath , hir:: Trait ) > {
203- if let hir:: ModuleDef :: Trait ( trait_) = hir:: ModuleDef :: from ( trait_. as_module_def_id ( ) ?) {
204- Some ( ( import, trait_) )
172+ fn item_as_trait ( item : hir:: ItemInNs ) -> Option < hir:: Trait > {
173+ if let hir:: ModuleDef :: Trait ( trait_) = hir:: ModuleDef :: from ( item. as_module_def_id ( ) ?) {
174+ Some ( trait_)
205175 } else {
206176 None
207177 }
208178}
209179
180+ fn group_label ( candidate : & ImportCandidate ) -> GroupLabel {
181+ let name = match candidate {
182+ ImportCandidate :: UnqualifiedName ( it) | ImportCandidate :: QualifierStart ( it) => & it. name ,
183+ ImportCandidate :: TraitAssocItem ( it) | ImportCandidate :: TraitMethod ( it) => & it. name ,
184+ } ;
185+ GroupLabel ( format ! ( "Qualify {}" , name) )
186+ }
187+
188+ fn label ( candidate : & ImportCandidate , import : & hir:: ModPath ) -> String {
189+ match candidate {
190+ ImportCandidate :: UnqualifiedName ( _) => format ! ( "Qualify as `{}`" , & import) ,
191+ ImportCandidate :: QualifierStart ( _) => format ! ( "Qualify with `{}`" , & import) ,
192+ ImportCandidate :: TraitAssocItem ( _) => format ! ( "Qualify `{}`" , & import) ,
193+ ImportCandidate :: TraitMethod ( _) => format ! ( "Qualify with cast as `{}`" , & import) ,
194+ }
195+ }
196+
210197#[ cfg( test) ]
211198mod tests {
212199 use crate :: tests:: { check_assist, check_assist_not_applicable, check_assist_target} ;
0 commit comments