@@ -6,7 +6,10 @@ use ide_db::{
6
6
search:: { FileReference , ReferenceAccess , SearchScope } ,
7
7
RootDatabase ,
8
8
} ;
9
- use syntax:: { ast, match_ast, AstNode , SyntaxNode , SyntaxToken , TextRange , WalkEvent , T } ;
9
+ use syntax:: {
10
+ ast:: { self , LoopBodyOwner } ,
11
+ match_ast, AstNode , SyntaxNode , SyntaxToken , TextRange , WalkEvent , T ,
12
+ } ;
10
13
11
14
use crate :: { display:: TryToNav , references, NavigationTarget } ;
12
15
@@ -83,43 +86,35 @@ fn highlight_exit_points(
83
86
) -> Option < Vec < HighlightedRange > > {
84
87
let mut highlights = Vec :: new ( ) ;
85
88
let body = body?;
86
- walk ( & body, & mut |expr| {
87
- match expr {
88
- ast:: Expr :: ReturnExpr ( expr) => {
89
- if let Some ( token) = expr. return_token ( ) {
90
- highlights
91
- . push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
92
- }
93
- }
94
- ast:: Expr :: TryExpr ( try_) => {
95
- if let Some ( token) = try_. question_mark_token ( ) {
96
- highlights
97
- . push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
98
- }
89
+ walk ( & body, & mut |expr| match expr {
90
+ ast:: Expr :: ReturnExpr ( expr) => {
91
+ if let Some ( token) = expr. return_token ( ) {
92
+ highlights. push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
99
93
}
100
- ast:: Expr :: MethodCallExpr ( _) | ast:: Expr :: CallExpr ( _) | ast:: Expr :: MacroCall ( _) => {
101
- if sema. type_of_expr ( & expr) . map_or ( false , |ty| ty. is_never ( ) ) {
102
- highlights. push ( HighlightedRange {
103
- access : None ,
104
- range : expr. syntax ( ) . text_range ( ) ,
105
- } ) ;
106
- }
94
+ }
95
+ ast:: Expr :: TryExpr ( try_) => {
96
+ if let Some ( token) = try_. question_mark_token ( ) {
97
+ highlights. push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
107
98
}
108
- ast:: Expr :: EffectExpr ( effect) => {
109
- return effect. async_token ( ) . is_some ( ) || effect. try_token ( ) . is_some ( )
99
+ }
100
+ ast:: Expr :: MethodCallExpr ( _) | ast:: Expr :: CallExpr ( _) | ast:: Expr :: MacroCall ( _) => {
101
+ if sema. type_of_expr ( & expr) . map_or ( false , |ty| ty. is_never ( ) ) {
102
+ highlights
103
+ . push ( HighlightedRange { access : None , range : expr. syntax ( ) . text_range ( ) } ) ;
110
104
}
111
- ast:: Expr :: ClosureExpr ( _) => return true ,
112
- _ => ( ) ,
113
105
}
114
- false
106
+ _ => ( ) ,
115
107
} ) ;
116
108
let tail = match body {
117
109
ast:: Expr :: BlockExpr ( b) => b. tail_expr ( ) ,
118
110
e => Some ( e) ,
119
111
} ;
120
112
121
113
if let Some ( tail) = tail {
122
- highlights. push ( HighlightedRange { access : None , range : tail. syntax ( ) . text_range ( ) } )
114
+ for_each_inner_tail ( & tail, & mut |tail| {
115
+ highlights
116
+ . push ( HighlightedRange { access : None , range : tail. syntax ( ) . text_range ( ) } )
117
+ } ) ;
123
118
}
124
119
Some ( highlights)
125
120
}
@@ -149,24 +144,12 @@ fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
149
144
highlights. push ( HighlightedRange { access : None , range : async_token?. text_range ( ) } ) ;
150
145
if let Some ( body) = body {
151
146
walk ( & body, & mut |expr| {
152
- match expr {
153
- ast:: Expr :: AwaitExpr ( expr) => {
154
- if let Some ( token) = expr. await_token ( ) {
155
- highlights
156
- . push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
157
- }
158
- }
159
- // All the following are different contexts so skip them
160
- ast:: Expr :: EffectExpr ( effect) => {
161
- return matches ! (
162
- effect. effect( ) ,
163
- ast:: Effect :: Async ( _) | ast:: Effect :: Try ( _) | ast:: Effect :: Const ( _)
164
- )
147
+ if let ast:: Expr :: AwaitExpr ( expr) = expr {
148
+ if let Some ( token) = expr. await_token ( ) {
149
+ highlights
150
+ . push ( HighlightedRange { access : None , range : token. text_range ( ) } ) ;
165
151
}
166
- ast:: Expr :: ClosureExpr ( __) => return true ,
167
- _ => ( ) ,
168
152
}
169
- false
170
153
} ) ;
171
154
}
172
155
Some ( highlights)
@@ -184,33 +167,139 @@ fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
184
167
None
185
168
}
186
169
187
- /// Preorder walk the expression node skipping a node 's subtrees if the callback returns `true` for the node.
188
- fn walk ( expr : & ast:: Expr , cb : & mut dyn FnMut ( ast:: Expr ) -> bool ) {
170
+ /// Preorder walk all the expression's child expressions
171
+ fn walk ( expr : & ast:: Expr , cb : & mut dyn FnMut ( ast:: Expr ) ) {
189
172
let mut preorder = expr. syntax ( ) . preorder ( ) ;
190
173
while let Some ( event) = preorder. next ( ) {
191
174
let node = match event {
192
175
WalkEvent :: Enter ( node) => node,
193
176
WalkEvent :: Leave ( _) => continue ,
194
177
} ;
195
178
match ast:: Stmt :: cast ( node. clone ( ) ) {
179
+ // recursively walk the initializer, skipping potential const pat expressions
180
+ // lets statements aren't usually nested too deeply so this is fine to recurse on
196
181
Some ( ast:: Stmt :: LetStmt ( l) ) => {
197
182
if let Some ( expr) = l. initializer ( ) {
198
183
walk ( & expr, cb) ;
199
184
}
185
+ preorder. skip_subtree ( ) ;
200
186
}
201
- // Don't skip subtree since we want to process the expression behind this next
202
- Some ( ast:: Stmt :: ExprStmt ( _) ) => continue ,
187
+ // Don't skip subtree since we want to process the expression child next
188
+ Some ( ast:: Stmt :: ExprStmt ( _) ) => ( ) ,
203
189
// skip inner items which might have their own expressions
204
- Some ( ast:: Stmt :: Item ( _) ) => ( ) ,
190
+ Some ( ast:: Stmt :: Item ( _) ) => preorder . skip_subtree ( ) ,
205
191
None => {
206
192
if let Some ( expr) = ast:: Expr :: cast ( node) {
207
- if !cb ( expr) {
208
- continue ;
193
+ let is_different_context = match & expr {
194
+ ast:: Expr :: EffectExpr ( effect) => {
195
+ matches ! (
196
+ effect. effect( ) ,
197
+ ast:: Effect :: Async ( _) | ast:: Effect :: Try ( _) | ast:: Effect :: Const ( _)
198
+ )
199
+ }
200
+ ast:: Expr :: ClosureExpr ( __) => true ,
201
+ _ => false ,
202
+ } ;
203
+ cb ( expr) ;
204
+ if is_different_context {
205
+ preorder. skip_subtree ( ) ;
209
206
}
207
+ } else {
208
+ preorder. skip_subtree ( ) ;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ // FIXME: doesn't account for labeled breaks in labeled blocks
216
+ fn for_each_inner_tail ( expr : & ast:: Expr , cb : & mut dyn FnMut ( & ast:: Expr ) ) {
217
+ match expr {
218
+ ast:: Expr :: BlockExpr ( b) => {
219
+ if let Some ( e) = b. tail_expr ( ) {
220
+ for_each_inner_tail ( & e, cb) ;
221
+ }
222
+ }
223
+ ast:: Expr :: EffectExpr ( e) => match e. effect ( ) {
224
+ ast:: Effect :: Label ( _) | ast:: Effect :: Unsafe ( _) => {
225
+ if let Some ( e) = e. block_expr ( ) . and_then ( |b| b. tail_expr ( ) ) {
226
+ for_each_inner_tail ( & e, cb) ;
210
227
}
211
228
}
229
+ ast:: Effect :: Async ( _) | ast:: Effect :: Try ( _) | ast:: Effect :: Const ( _) => cb ( expr) ,
230
+ } ,
231
+ ast:: Expr :: IfExpr ( if_) => {
232
+ if_. blocks ( ) . for_each ( |block| for_each_inner_tail ( & ast:: Expr :: BlockExpr ( block) , cb) )
233
+ }
234
+ ast:: Expr :: LoopExpr ( l) => for_each_break ( l, cb) ,
235
+ ast:: Expr :: MatchExpr ( m) => {
236
+ if let Some ( arms) = m. match_arm_list ( ) {
237
+ arms. arms ( ) . filter_map ( |arm| arm. expr ( ) ) . for_each ( |e| for_each_inner_tail ( & e, cb) ) ;
238
+ }
239
+ }
240
+ ast:: Expr :: ArrayExpr ( _)
241
+ | ast:: Expr :: AwaitExpr ( _)
242
+ | ast:: Expr :: BinExpr ( _)
243
+ | ast:: Expr :: BoxExpr ( _)
244
+ | ast:: Expr :: BreakExpr ( _)
245
+ | ast:: Expr :: CallExpr ( _)
246
+ | ast:: Expr :: CastExpr ( _)
247
+ | ast:: Expr :: ClosureExpr ( _)
248
+ | ast:: Expr :: ContinueExpr ( _)
249
+ | ast:: Expr :: FieldExpr ( _)
250
+ | ast:: Expr :: ForExpr ( _)
251
+ | ast:: Expr :: IndexExpr ( _)
252
+ | ast:: Expr :: Literal ( _)
253
+ | ast:: Expr :: MacroCall ( _)
254
+ | ast:: Expr :: MacroStmts ( _)
255
+ | ast:: Expr :: MethodCallExpr ( _)
256
+ | ast:: Expr :: ParenExpr ( _)
257
+ | ast:: Expr :: PathExpr ( _)
258
+ | ast:: Expr :: PrefixExpr ( _)
259
+ | ast:: Expr :: RangeExpr ( _)
260
+ | ast:: Expr :: RecordExpr ( _)
261
+ | ast:: Expr :: RefExpr ( _)
262
+ | ast:: Expr :: ReturnExpr ( _)
263
+ | ast:: Expr :: TryExpr ( _)
264
+ | ast:: Expr :: TupleExpr ( _)
265
+ | ast:: Expr :: WhileExpr ( _)
266
+ | ast:: Expr :: YieldExpr ( _) => cb ( expr) ,
267
+ }
268
+ }
269
+
270
+ fn for_each_break ( l : & ast:: LoopExpr , cb : & mut dyn FnMut ( & ast:: Expr ) ) {
271
+ let label = l. label ( ) . and_then ( |lbl| lbl. lifetime ( ) ) ;
272
+ let mut depth = 0 ;
273
+ if let Some ( b) = l. loop_body ( ) {
274
+ let preorder = & mut b. syntax ( ) . preorder ( ) ;
275
+ let ev_as_expr = |ev| match ev {
276
+ WalkEvent :: Enter ( it) => Some ( WalkEvent :: Enter ( ast:: Expr :: cast ( it) ?) ) ,
277
+ WalkEvent :: Leave ( it) => Some ( WalkEvent :: Leave ( ast:: Expr :: cast ( it) ?) ) ,
278
+ } ;
279
+ let eq_label = |lt : Option < ast:: Lifetime > | {
280
+ lt. zip ( label. as_ref ( ) ) . map_or ( false , |( lt, lbl) | lt. text ( ) == lbl. text ( ) )
281
+ } ;
282
+ while let Some ( node) = preorder. find_map ( ev_as_expr) {
283
+ match node {
284
+ WalkEvent :: Enter ( expr) => match & expr {
285
+ ast:: Expr :: LoopExpr ( _) | ast:: Expr :: WhileExpr ( _) | ast:: Expr :: ForExpr ( _) => {
286
+ depth += 1
287
+ }
288
+ ast:: Expr :: EffectExpr ( e) if e. label ( ) . is_some ( ) => depth += 1 ,
289
+ ast:: Expr :: BreakExpr ( b) if depth == 0 || eq_label ( b. lifetime ( ) ) => {
290
+ cb ( & expr) ;
291
+ }
292
+ _ => ( ) ,
293
+ } ,
294
+ WalkEvent :: Leave ( expr) => match expr {
295
+ ast:: Expr :: LoopExpr ( _) | ast:: Expr :: WhileExpr ( _) | ast:: Expr :: ForExpr ( _) => {
296
+ depth -= 1
297
+ }
298
+ ast:: Expr :: EffectExpr ( e) if e. label ( ) . is_some ( ) => depth -= 1 ,
299
+ _ => ( ) ,
300
+ } ,
301
+ }
212
302
}
213
- preorder. skip_subtree ( ) ;
214
303
}
215
304
}
216
305
@@ -453,6 +542,44 @@ fn foo() ->$0 u32 {
453
542
0
454
543
// ^
455
544
}
545
+ "# ,
546
+ ) ;
547
+ }
548
+
549
+ #[ test]
550
+ fn test_hl_inner_tail_exit_points ( ) {
551
+ check (
552
+ r#"
553
+ fn foo() ->$0 u32 {
554
+ if true {
555
+ unsafe {
556
+ return 5;
557
+ // ^^^^^^
558
+ 5
559
+ // ^
560
+ }
561
+ } else {
562
+ match 5 {
563
+ 6 => 100,
564
+ // ^^^
565
+ 7 => loop {
566
+ break 5;
567
+ // ^^^^^^^
568
+ }
569
+ 8 => 'a: loop {
570
+ 'b: loop {
571
+ break 'a 5;
572
+ // ^^^^^^^^^^
573
+ break 'b 5;
574
+ break 5;
575
+ };
576
+ }
577
+ //
578
+ _ => 500,
579
+ // ^^^
580
+ }
581
+ }
582
+ }
456
583
"# ,
457
584
) ;
458
585
}
0 commit comments