1- use hir:: { Const , Function , HasSource , TypeAlias } ;
2- use ide_db:: base_db:: FileRange ;
1+ use hir:: { db:: ExpandDatabase , Const , Function , HasSource , HirDisplay , TypeAlias } ;
2+ use ide_db:: {
3+ assists:: { Assist , AssistId , AssistKind } ,
4+ label:: Label ,
5+ source_change:: SourceChangeBuilder ,
6+ } ;
7+ use text_edit:: TextRange ;
38
49use crate :: { Diagnostic , DiagnosticCode , DiagnosticsContext } ;
510
@@ -10,47 +15,195 @@ pub(crate) fn trait_impl_redundant_assoc_item(
1015 ctx : & DiagnosticsContext < ' _ > ,
1116 d : & hir:: TraitImplRedundantAssocItems ,
1217) -> Diagnostic {
18+ let db = ctx. sema . db ;
1319 let name = d. assoc_item . 0 . clone ( ) ;
20+ let redundant_assoc_item_name = name. display ( db) ;
1421 let assoc_item = d. assoc_item . 1 ;
15- let db = ctx. sema . db ;
1622
1723 let default_range = d. impl_ . syntax_node_ptr ( ) . text_range ( ) ;
1824 let trait_name = d. trait_ . name ( db) . to_smol_str ( ) ;
1925
20- let ( redundant_item_name, diagnostic_range) = match assoc_item {
21- hir:: AssocItem :: Function ( id) => (
22- format ! ( "`fn {}`" , name. display( db) ) ,
23- Function :: from ( id)
24- . source ( db)
25- . map ( |it| it. syntax ( ) . value . text_range ( ) )
26- . unwrap_or ( default_range) ,
27- ) ,
28- hir:: AssocItem :: Const ( id) => (
29- format ! ( "`const {}`" , name. display( db) ) ,
30- Const :: from ( id)
31- . source ( db)
32- . map ( |it| it. syntax ( ) . value . text_range ( ) )
33- . unwrap_or ( default_range) ,
34- ) ,
35- hir:: AssocItem :: TypeAlias ( id) => (
36- format ! ( "`type {}`" , name. display( db) ) ,
37- TypeAlias :: from ( id)
38- . source ( db)
39- . map ( |it| it. syntax ( ) . value . text_range ( ) )
40- . unwrap_or ( default_range) ,
41- ) ,
26+ let ( redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
27+ hir:: AssocItem :: Function ( id) => {
28+ let function = Function :: from ( id) ;
29+ (
30+ format ! ( "`fn {}`" , redundant_assoc_item_name) ,
31+ function
32+ . source ( db)
33+ . map ( |it| it. syntax ( ) . value . text_range ( ) )
34+ . unwrap_or ( default_range) ,
35+ format ! ( "\n {};" , function. display( db) . to_string( ) ) ,
36+ )
37+ }
38+ hir:: AssocItem :: Const ( id) => {
39+ let constant = Const :: from ( id) ;
40+ (
41+ format ! ( "`const {}`" , redundant_assoc_item_name) ,
42+ constant
43+ . source ( db)
44+ . map ( |it| it. syntax ( ) . value . text_range ( ) )
45+ . unwrap_or ( default_range) ,
46+ format ! ( "\n {};" , constant. display( db) . to_string( ) ) ,
47+ )
48+ }
49+ hir:: AssocItem :: TypeAlias ( id) => {
50+ let type_alias = TypeAlias :: from ( id) ;
51+ (
52+ format ! ( "`type {}`" , redundant_assoc_item_name) ,
53+ type_alias
54+ . source ( db)
55+ . map ( |it| it. syntax ( ) . value . text_range ( ) )
56+ . unwrap_or ( default_range) ,
57+ format ! ( "\n type {};" , type_alias. name( ctx. sema. db) . to_smol_str( ) ) ,
58+ )
59+ }
4260 } ;
4361
4462 Diagnostic :: new (
4563 DiagnosticCode :: RustcHardError ( "E0407" ) ,
4664 format ! ( "{redundant_item_name} is not a member of trait `{trait_name}`" ) ,
47- FileRange { file_id : d. file_id . file_id ( ) . unwrap ( ) , range : diagnostic_range } ,
65+ hir :: InFile :: new ( d. file_id , diagnostic_range ) . original_node_file_range_rooted ( db ) ,
4866 )
67+ . with_fixes ( quickfix_for_redundant_assoc_item (
68+ ctx,
69+ d,
70+ redundant_item_def,
71+ diagnostic_range,
72+ ) )
73+ }
74+
75+ /// add assoc item into the trait def body
76+ fn quickfix_for_redundant_assoc_item (
77+ ctx : & DiagnosticsContext < ' _ > ,
78+ d : & hir:: TraitImplRedundantAssocItems ,
79+ redundant_item_def : String ,
80+ range : TextRange ,
81+ ) -> Option < Vec < Assist > > {
82+ let add_assoc_item_def = |builder : & mut SourceChangeBuilder | -> Option < ( ) > {
83+ let db = ctx. sema . db ;
84+ let root = db. parse_or_expand ( d. file_id ) ;
85+ // don't modify trait def in outer crate
86+ let current_crate = ctx. sema . scope ( & d. impl_ . syntax_node_ptr ( ) . to_node ( & root) ) ?. krate ( ) ;
87+ let trait_def_crate = d. trait_ . module ( db) . krate ( ) ;
88+ if trait_def_crate != current_crate {
89+ return None ;
90+ }
91+
92+ let trait_def = d. trait_ . source ( db) ?. value ;
93+ let l_curly = trait_def. assoc_item_list ( ) ?. l_curly_token ( ) ?. text_range ( ) ;
94+ let where_to_insert =
95+ hir:: InFile :: new ( d. file_id , l_curly) . original_node_file_range_rooted ( db) . range ;
96+
97+ Some ( builder. insert ( where_to_insert. end ( ) , redundant_item_def) )
98+ } ;
99+ let file_id = d. file_id . file_id ( ) ?;
100+ let mut source_change_builder = SourceChangeBuilder :: new ( file_id) ;
101+ add_assoc_item_def ( & mut source_change_builder) ?;
102+
103+ Some ( vec ! [ Assist {
104+ id: AssistId ( "add assoc item def into trait def" , AssistKind :: QuickFix ) ,
105+ label: Label :: new( "Add assoc item def into trait def" . to_string( ) ) ,
106+ group: None ,
107+ target: range,
108+ source_change: Some ( source_change_builder. finish( ) ) ,
109+ trigger_signature_help: false ,
110+ } ] )
49111}
50112
51113#[ cfg( test) ]
52114mod tests {
53- use crate :: tests:: check_diagnostics;
115+ use crate :: tests:: { check_diagnostics, check_fix, check_no_fix} ;
116+
117+ #[ test]
118+ fn quickfix_for_assoc_func ( ) {
119+ check_fix (
120+ r#"
121+ trait Marker {
122+ fn boo();
123+ }
124+ struct Foo;
125+ impl Marker for Foo {
126+ fn$0 bar(_a: i32, _b: String) -> String {}
127+ fn boo() {}
128+ }
129+ "# ,
130+ r#"
131+ trait Marker {
132+ fn bar(_a: i32, _b: String) -> String;
133+ fn boo();
134+ }
135+ struct Foo;
136+ impl Marker for Foo {
137+ fn bar(_a: i32, _b: String) -> String {}
138+ fn boo() {}
139+ }
140+ "# ,
141+ )
142+ }
143+
144+ #[ test]
145+ fn quickfix_for_assoc_const ( ) {
146+ check_fix (
147+ r#"
148+ trait Marker {
149+ fn foo () {}
150+ }
151+ struct Foo;
152+ impl Marker for Foo {
153+ const FLAG: bool$0 = false;
154+ }
155+ "# ,
156+ r#"
157+ trait Marker {
158+ const FLAG: bool;
159+ fn foo () {}
160+ }
161+ struct Foo;
162+ impl Marker for Foo {
163+ const FLAG: bool = false;
164+ }
165+ "# ,
166+ )
167+ }
168+
169+ #[ test]
170+ fn quickfix_for_assoc_type ( ) {
171+ check_fix (
172+ r#"
173+ trait Marker {
174+ }
175+ struct Foo;
176+ impl Marker for Foo {
177+ type T = i32;$0
178+ }
179+ "# ,
180+ r#"
181+ trait Marker {
182+ type T;
183+ }
184+ struct Foo;
185+ impl Marker for Foo {
186+ type T = i32;
187+ }
188+ "# ,
189+ )
190+ }
191+
192+ #[ test]
193+ fn quickfix_dont_work ( ) {
194+ check_no_fix (
195+ r#"
196+ //- /dep.rs crate:dep
197+ trait Marker {
198+ }
199+ //- /main.rs crate:main deps:dep
200+ struct Foo;
201+ impl dep::Marker for Foo {
202+ type T = i32;$0
203+ }
204+ "# ,
205+ )
206+ }
54207
55208 #[ test]
56209 fn trait_with_default_value ( ) {
@@ -64,12 +217,12 @@ trait Marker {
64217struct Foo;
65218impl Marker for Foo {
66219 type T = i32;
67- //^^^^^^^^^^^^^ error: `type T` is not a member of trait `Marker`
220+ //^^^^^^^^^^^^^ 💡 error: `type T` is not a member of trait `Marker`
68221
69222 const FLAG: bool = true;
70223
71224 fn bar() {}
72- //^^^^^^^^^^^ error: `fn bar` is not a member of trait `Marker`
225+ //^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
73226
74227 fn boo() {}
75228}
0 commit comments