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 ;
3
8
4
9
use crate :: { Diagnostic , DiagnosticCode , DiagnosticsContext } ;
5
10
@@ -10,47 +15,195 @@ pub(crate) fn trait_impl_redundant_assoc_item(
10
15
ctx : & DiagnosticsContext < ' _ > ,
11
16
d : & hir:: TraitImplRedundantAssocItems ,
12
17
) -> Diagnostic {
18
+ let db = ctx. sema . db ;
13
19
let name = d. assoc_item . 0 . clone ( ) ;
20
+ let redundant_assoc_item_name = name. display ( db) ;
14
21
let assoc_item = d. assoc_item . 1 ;
15
- let db = ctx. sema . db ;
16
22
17
23
let default_range = d. impl_ . syntax_node_ptr ( ) . text_range ( ) ;
18
24
let trait_name = d. trait_ . name ( db) . to_smol_str ( ) ;
19
25
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
+ }
42
60
} ;
43
61
44
62
Diagnostic :: new (
45
63
DiagnosticCode :: RustcHardError ( "E0407" ) ,
46
64
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 ) ,
48
66
)
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
+ } ] )
49
111
}
50
112
51
113
#[ cfg( test) ]
52
114
mod 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
+ }
54
207
55
208
#[ test]
56
209
fn trait_with_default_value ( ) {
@@ -64,12 +217,12 @@ trait Marker {
64
217
struct Foo;
65
218
impl Marker for Foo {
66
219
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`
68
221
69
222
const FLAG: bool = true;
70
223
71
224
fn bar() {}
72
- //^^^^^^^^^^^ error: `fn bar` is not a member of trait `Marker`
225
+ //^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
73
226
74
227
fn boo() {}
75
228
}
0 commit comments