Skip to content

Commit d77655e

Browse files
committed
Refine tail exit point highlighting to highlight inner tails
1 parent d049783 commit d77655e

File tree

1 file changed

+178
-51
lines changed

1 file changed

+178
-51
lines changed

crates/ide/src/highlight_related.rs

Lines changed: 178 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use ide_db::{
66
search::{FileReference, ReferenceAccess, SearchScope},
77
RootDatabase,
88
};
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+
};
1013

1114
use crate::{display::TryToNav, references, NavigationTarget};
1215

@@ -83,43 +86,35 @@ fn highlight_exit_points(
8386
) -> Option<Vec<HighlightedRange>> {
8487
let mut highlights = Vec::new();
8588
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() });
9993
}
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() });
10798
}
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() });
110104
}
111-
ast::Expr::ClosureExpr(_) => return true,
112-
_ => (),
113105
}
114-
false
106+
_ => (),
115107
});
116108
let tail = match body {
117109
ast::Expr::BlockExpr(b) => b.tail_expr(),
118110
e => Some(e),
119111
};
120112

121113
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+
});
123118
}
124119
Some(highlights)
125120
}
@@ -149,24 +144,12 @@ fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
149144
highlights.push(HighlightedRange { access: None, range: async_token?.text_range() });
150145
if let Some(body) = body {
151146
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() });
165151
}
166-
ast::Expr::ClosureExpr(__) => return true,
167-
_ => (),
168152
}
169-
false
170153
});
171154
}
172155
Some(highlights)
@@ -184,33 +167,139 @@ fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
184167
None
185168
}
186169

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)) {
189172
let mut preorder = expr.syntax().preorder();
190173
while let Some(event) = preorder.next() {
191174
let node = match event {
192175
WalkEvent::Enter(node) => node,
193176
WalkEvent::Leave(_) => continue,
194177
};
195178
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
196181
Some(ast::Stmt::LetStmt(l)) => {
197182
if let Some(expr) = l.initializer() {
198183
walk(&expr, cb);
199184
}
185+
preorder.skip_subtree();
200186
}
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(_)) => (),
203189
// skip inner items which might have their own expressions
204-
Some(ast::Stmt::Item(_)) => (),
190+
Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
205191
None => {
206192
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();
209206
}
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);
210227
}
211228
}
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+
}
212302
}
213-
preorder.skip_subtree();
214303
}
215304
}
216305

@@ -453,6 +542,44 @@ fn foo() ->$0 u32 {
453542
0
454543
// ^
455544
}
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+
}
456583
"#,
457584
);
458585
}

0 commit comments

Comments
 (0)