1
- use ide_db:: { defs:: Definition , search:: FileReference } ;
1
+ use either:: Either ;
2
+ use hir:: PathResolution ;
3
+ use ide_db:: { base_db:: FileId , defs:: Definition , search:: FileReference } ;
2
4
use rustc_hash:: FxHashMap ;
3
5
use syntax:: {
4
- ast:: { self , AstNode , AstToken } ,
6
+ ast:: { self , AstNode , AstToken , NameOwner } ,
5
7
TextRange ,
6
8
} ;
7
9
@@ -27,44 +29,28 @@ use crate::{
27
29
// }
28
30
// ```
29
31
pub ( crate ) fn inline_local_variable ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
30
- let let_stmt = ctx. find_node_at_offset :: < ast:: LetStmt > ( ) ?;
31
- let bind_pat = match let_stmt. pat ( ) ? {
32
- ast:: Pat :: IdentPat ( pat) => pat,
33
- _ => return None ,
34
- } ;
35
- if bind_pat. mut_token ( ) . is_some ( ) {
36
- cov_mark:: hit!( test_not_inline_mut_variable) ;
37
- return None ;
38
- }
39
- if !bind_pat. syntax ( ) . text_range ( ) . contains_inclusive ( ctx. offset ( ) ) {
40
- cov_mark:: hit!( not_applicable_outside_of_bind_pat) ;
41
- return None ;
42
- }
32
+ let InlineData { let_stmt, delete_let, replace_usages, target } =
33
+ inline_let ( ctx) . or_else ( || inline_usage ( ctx) ) ?;
43
34
let initializer_expr = let_stmt. initializer ( ) ?;
44
35
45
- let def = ctx. sema . to_def ( & bind_pat) ?;
46
- let def = Definition :: Local ( def) ;
47
- let usages = def. usages ( & ctx. sema ) . all ( ) ;
48
- if usages. is_empty ( ) {
49
- cov_mark:: hit!( test_not_applicable_if_variable_unused) ;
50
- return None ;
51
- } ;
52
-
53
- let delete_range = if let Some ( whitespace) = let_stmt
54
- . syntax ( )
55
- . next_sibling_or_token ( )
56
- . and_then ( |it| ast:: Whitespace :: cast ( it. as_token ( ) ?. clone ( ) ) )
57
- {
58
- TextRange :: new (
59
- let_stmt. syntax ( ) . text_range ( ) . start ( ) ,
60
- whitespace. syntax ( ) . text_range ( ) . end ( ) ,
61
- )
36
+ let delete_range = if delete_let {
37
+ if let Some ( whitespace) = let_stmt
38
+ . syntax ( )
39
+ . next_sibling_or_token ( )
40
+ . and_then ( |it| ast:: Whitespace :: cast ( it. as_token ( ) ?. clone ( ) ) )
41
+ {
42
+ Some ( TextRange :: new (
43
+ let_stmt. syntax ( ) . text_range ( ) . start ( ) ,
44
+ whitespace. syntax ( ) . text_range ( ) . end ( ) ,
45
+ ) )
46
+ } else {
47
+ Some ( let_stmt. syntax ( ) . text_range ( ) )
48
+ }
62
49
} else {
63
- let_stmt . syntax ( ) . text_range ( )
50
+ None
64
51
} ;
65
52
66
- let wrap_in_parens = usages
67
- . references
53
+ let wrap_in_parens = replace_usages
68
54
. iter ( )
69
55
. map ( |( & file_id, refs) | {
70
56
refs. iter ( )
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
114
100
let init_str = initializer_expr. syntax ( ) . text ( ) . to_string ( ) ;
115
101
let init_in_paren = format ! ( "({})" , & init_str) ;
116
102
117
- let target = bind_pat. syntax ( ) . text_range ( ) ;
103
+ let target = match target {
104
+ ast:: NameOrNameRef :: Name ( it) => it. syntax ( ) . text_range ( ) ,
105
+ ast:: NameOrNameRef :: NameRef ( it) => it. syntax ( ) . text_range ( ) ,
106
+ } ;
107
+
118
108
acc. add (
119
109
AssistId ( "inline_local_variable" , AssistKind :: RefactorInline ) ,
120
110
"Inline variable" ,
121
111
target,
122
112
move |builder| {
123
- builder. delete ( delete_range) ;
124
- for ( file_id, references) in usages. references {
113
+ if let Some ( range) = delete_range {
114
+ builder. delete ( range) ;
115
+ }
116
+ for ( file_id, references) in replace_usages {
125
117
for ( & should_wrap, reference) in wrap_in_parens[ & file_id] . iter ( ) . zip ( references) {
126
118
let replacement =
127
119
if should_wrap { init_in_paren. clone ( ) } else { init_str. clone ( ) } ;
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
140
132
)
141
133
}
142
134
135
+ struct InlineData {
136
+ let_stmt : ast:: LetStmt ,
137
+ delete_let : bool ,
138
+ target : ast:: NameOrNameRef ,
139
+ replace_usages : FxHashMap < FileId , Vec < FileReference > > ,
140
+ }
141
+
142
+ fn inline_let ( ctx : & AssistContext ) -> Option < InlineData > {
143
+ let let_stmt = ctx. find_node_at_offset :: < ast:: LetStmt > ( ) ?;
144
+ let bind_pat = match let_stmt. pat ( ) ? {
145
+ ast:: Pat :: IdentPat ( pat) => pat,
146
+ _ => return None ,
147
+ } ;
148
+ if bind_pat. mut_token ( ) . is_some ( ) {
149
+ cov_mark:: hit!( test_not_inline_mut_variable) ;
150
+ return None ;
151
+ }
152
+ if !bind_pat. syntax ( ) . text_range ( ) . contains_inclusive ( ctx. offset ( ) ) {
153
+ cov_mark:: hit!( not_applicable_outside_of_bind_pat) ;
154
+ return None ;
155
+ }
156
+
157
+ let def = ctx. sema . to_def ( & bind_pat) ?;
158
+ let def = Definition :: Local ( def) ;
159
+ let usages = def. usages ( & ctx. sema ) . all ( ) ;
160
+ if usages. is_empty ( ) {
161
+ cov_mark:: hit!( test_not_applicable_if_variable_unused) ;
162
+ return None ;
163
+ } ;
164
+
165
+ Some ( InlineData {
166
+ let_stmt,
167
+ delete_let : true ,
168
+ target : ast:: NameOrNameRef :: Name ( bind_pat. name ( ) ?) ,
169
+ replace_usages : usages. references ,
170
+ } )
171
+ }
172
+
173
+ fn inline_usage ( ctx : & AssistContext ) -> Option < InlineData > {
174
+ let path_expr = ctx. find_node_at_offset :: < ast:: PathExpr > ( ) ?;
175
+ let path = path_expr. path ( ) ?;
176
+ let name = match path. as_single_segment ( ) ?. kind ( ) ? {
177
+ ast:: PathSegmentKind :: Name ( name) => name,
178
+ _ => return None ,
179
+ } ;
180
+
181
+ let local = match ctx. sema . resolve_path ( & path) ? {
182
+ PathResolution :: Local ( local) => local,
183
+ _ => return None ,
184
+ } ;
185
+
186
+ let bind_pat = match local. source ( ctx. db ( ) ) . value {
187
+ Either :: Left ( ident) => ident,
188
+ _ => return None ,
189
+ } ;
190
+
191
+ let let_stmt = ast:: LetStmt :: cast ( bind_pat. syntax ( ) . parent ( ) ?) ?;
192
+
193
+ let def = Definition :: Local ( local) ;
194
+ let mut usages = def. usages ( & ctx. sema ) . all ( ) ;
195
+
196
+ let delete_let = usages. references . values ( ) . map ( |v| v. len ( ) ) . sum :: < usize > ( ) == 1 ;
197
+
198
+ for references in usages. references . values_mut ( ) {
199
+ references. retain ( |reference| reference. name . as_name_ref ( ) == Some ( & name) ) ;
200
+ }
201
+
202
+ Some ( InlineData {
203
+ let_stmt,
204
+ delete_let,
205
+ target : ast:: NameOrNameRef :: NameRef ( name) ,
206
+ replace_usages : usages. references ,
207
+ } )
208
+ }
209
+
143
210
#[ cfg( test) ]
144
211
mod tests {
145
212
use crate :: tests:: { check_assist, check_assist_not_applicable} ;
@@ -726,4 +793,84 @@ fn main() {
726
793
" ,
727
794
)
728
795
}
796
+
797
+ #[ test]
798
+ fn works_on_local_usage ( ) {
799
+ check_assist (
800
+ inline_local_variable,
801
+ r#"
802
+ fn f() {
803
+ let xyz = 0;
804
+ xyz$0;
805
+ }
806
+ "# ,
807
+ r#"
808
+ fn f() {
809
+ 0;
810
+ }
811
+ "# ,
812
+ ) ;
813
+ }
814
+
815
+ #[ test]
816
+ fn does_not_remove_let_when_multiple_usages ( ) {
817
+ check_assist (
818
+ inline_local_variable,
819
+ r#"
820
+ fn f() {
821
+ let xyz = 0;
822
+ xyz$0;
823
+ xyz;
824
+ }
825
+ "# ,
826
+ r#"
827
+ fn f() {
828
+ let xyz = 0;
829
+ 0;
830
+ xyz;
831
+ }
832
+ "# ,
833
+ ) ;
834
+ }
835
+
836
+ #[ test]
837
+ fn not_applicable_with_non_ident_pattern ( ) {
838
+ check_assist_not_applicable (
839
+ inline_local_variable,
840
+ r#"
841
+ fn main() {
842
+ let (x, y) = (0, 1);
843
+ x$0;
844
+ }
845
+ "# ,
846
+ ) ;
847
+ }
848
+
849
+ #[ test]
850
+ fn not_applicable_on_local_usage_in_macro ( ) {
851
+ check_assist_not_applicable (
852
+ inline_local_variable,
853
+ r#"
854
+ macro_rules! m {
855
+ ($i:ident) => { $i }
856
+ }
857
+ fn f() {
858
+ let xyz = 0;
859
+ m!(xyz$0); // replacing it would break the macro
860
+ }
861
+ "# ,
862
+ ) ;
863
+ check_assist_not_applicable (
864
+ inline_local_variable,
865
+ r#"
866
+ macro_rules! m {
867
+ ($i:ident) => { $i }
868
+ }
869
+ fn f() {
870
+ let xyz$0 = 0;
871
+ m!(xyz); // replacing it would break the macro
872
+ }
873
+ "# ,
874
+ ) ;
875
+ }
729
876
}
0 commit comments