Skip to main content

ide/
highlight_related.rs

1use std::iter;
2
3use hir::{EditionedFileId, FilePosition, FileRange, HirFileId, InFile, Semantics};
4use ide_db::{
5    FxHashMap, FxHashSet, RootDatabase,
6    base_db::SourceDatabase,
7    defs::{Definition, IdentClass},
8    helpers::pick_best_token,
9    search::{FileReference, ReferenceCategory, SearchScope},
10    syntax_helpers::node_ext::{
11        eq_label_lt, find_loops, for_each_tail_expr, full_path_of_name_ref,
12        is_closure_or_blk_with_modif, preorder_expr_with_ctx_checker,
13    },
14};
15use syntax::{
16    AstNode,
17    SyntaxKind::{self, IDENT, INT_NUMBER},
18    SyntaxToken, T, TextRange, WalkEvent,
19    ast::{self, HasLoopBody},
20    match_ast,
21};
22
23use crate::{NavigationTarget, TryToNav, goto_definition, navigation_target::ToNav};
24
25#[derive(PartialEq, Eq, Hash)]
26pub struct HighlightedRange {
27    pub range: TextRange,
28    // FIXME: This needs to be more precise. Reference category makes sense only
29    // for references, but we also have defs. And things like exit points are
30    // neither.
31    pub category: ReferenceCategory,
32}
33
34#[derive(Default, Clone)]
35pub struct HighlightRelatedConfig {
36    pub references: bool,
37    pub exit_points: bool,
38    pub break_points: bool,
39    pub closure_captures: bool,
40    pub yield_points: bool,
41    pub branch_exit_points: bool,
42}
43
44type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;
45
46// Feature: Highlight Related
47//
48// Highlights constructs related to the thing under the cursor:
49//
50// 1. if on an identifier, highlights all references to that identifier in the current file
51//      * additionally, if the identifier is a trait in a where clause, type parameter trait bound or use item, highlights all references to that trait's assoc items in the corresponding scope
52// 1. if on an `async` or `await` token, highlights all yield points for that async context
53// 1. if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
54// 1. if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
55// 1. if on a `move` or `|` token that belongs to a closure, highlights all captures of the closure.
56//
57// Note: `?`, `|` and `->` do not currently trigger this behavior in the VSCode editor.
58pub(crate) fn highlight_related(
59    sema: &Semantics<'_, RootDatabase>,
60    config: HighlightRelatedConfig,
61    ide_db::FilePosition { offset, file_id }: ide_db::FilePosition,
62) -> Option<Vec<HighlightedRange>> {
63    let _p = tracing::info_span!("highlight_related").entered();
64    let file_id = sema.attach_first_edition(file_id);
65    let syntax = sema.parse(file_id).syntax().clone();
66
67    let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
68        T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
69        T![->] | T![=>] => 4,
70        kind if kind.is_keyword(file_id.edition(sema.db)) => 3,
71        IDENT | INT_NUMBER => 2,
72        T![|] => 1,
73        _ => 0,
74    })?;
75    // most if not all of these should be re-implemented with information seeded from hir
76    match token.kind() {
77        T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
78            highlight_exit_points(sema, token).remove(&file_id)
79        }
80        T![fn] | T![return] | T![->] if config.exit_points => {
81            highlight_exit_points(sema, token).remove(&file_id)
82        }
83        T![match] | T![=>] | T![if] if config.branch_exit_points => {
84            highlight_branch_exit_points(sema, token).remove(&file_id)
85        }
86        T![await] | T![async] if config.yield_points => {
87            highlight_yield_points(sema, token).remove(&file_id)
88        }
89        T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
90            highlight_break_points(sema, token).remove(&file_id)
91        }
92        T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
93            highlight_break_points(sema, token).remove(&file_id)
94        }
95        T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {
96            highlight_unsafe_points(sema, token).remove(&file_id)
97        }
98        T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
99        T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
100        _ if config.references => {
101            highlight_references(sema, token, FilePosition { file_id, offset })
102        }
103        _ => None,
104    }
105}
106
107fn highlight_closure_captures(
108    sema: &Semantics<'_, RootDatabase>,
109    token: SyntaxToken,
110    file_id: EditionedFileId,
111) -> Option<Vec<HighlightedRange>> {
112    let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
113    let search_range = closure.body()?.syntax().text_range();
114    let ty = &sema.type_of_expr(&closure.into())?.original;
115    let c = ty.as_closure()?;
116    Some(
117        c.captured_items(sema.db)
118            .into_iter()
119            .map(|capture| capture.local())
120            .flat_map(|local| {
121                let usages = Definition::Local(local)
122                    .usages(sema)
123                    .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))
124                    .include_self_refs()
125                    .all()
126                    .references
127                    .remove(&file_id)
128                    .into_iter()
129                    .flatten()
130                    .map(|FileReference { category, range, .. }| HighlightedRange {
131                        range,
132                        category,
133                    });
134                let category = if local.is_mut(sema.db) {
135                    ReferenceCategory::WRITE
136                } else {
137                    ReferenceCategory::empty()
138                };
139                local
140                    .sources(sema.db)
141                    .into_iter()
142                    .flat_map(|x| x.to_nav(sema.db))
143                    .filter(|decl| decl.file_id == file_id.file_id(sema.db))
144                    .filter_map(|decl| decl.focus_range)
145                    .map(move |range| HighlightedRange { range, category })
146                    .chain(usages)
147            })
148            .collect(),
149    )
150}
151
152fn highlight_references(
153    sema: &Semantics<'_, RootDatabase>,
154    token: SyntaxToken,
155    FilePosition { file_id, offset }: FilePosition,
156) -> Option<Vec<HighlightedRange>> {
157    let defs = if let Some((range, _, _, resolution)) =
158        sema.check_for_format_args_template(token.clone(), offset)
159    {
160        match resolution.map(Definition::from) {
161            Some(def) => iter::once(def).collect(),
162            None => {
163                return Some(vec![HighlightedRange {
164                    range,
165                    category: ReferenceCategory::empty(),
166                }]);
167            }
168        }
169    } else {
170        find_defs(sema, token.clone())
171    };
172    let usages = defs
173        .iter()
174        .filter_map(|&d| {
175            d.usages(sema)
176                .in_scope(&SearchScope::single_file(file_id))
177                .include_self_refs()
178                .all()
179                .references
180                .remove(&file_id)
181        })
182        .flatten()
183        .map(|FileReference { category, range, .. }| HighlightedRange { range, category });
184    let mut res = FxHashSet::default();
185    for &def in &defs {
186        // highlight trait usages
187        if let Definition::Trait(t) = def {
188            let trait_item_use_scope = (|| {
189                let name_ref = token.parent().and_then(ast::NameRef::cast)?;
190                let path = full_path_of_name_ref(&name_ref)?;
191                let parent = path.syntax().parent()?;
192                match_ast! {
193                    match parent {
194                        ast::UseTree(it) => it.syntax().ancestors().find(|it| {
195                            ast::SourceFile::can_cast(it.kind()) || ast::Module::can_cast(it.kind())
196                        }).zip(Some(true)),
197                        ast::PathType(it) => it
198                            .syntax()
199                            .ancestors()
200                            .nth(2)
201                            .and_then(ast::TypeBoundList::cast)?
202                            .syntax()
203                            .parent()
204                            .filter(|it| ast::WhereClause::can_cast(it.kind()) || ast::TypeParam::can_cast(it.kind()))?
205                            .ancestors()
206                            .find(|it| {
207                                ast::Item::can_cast(it.kind())
208                            }).zip(Some(false)),
209                        _ => None,
210                    }
211                }
212            })();
213            if let Some((trait_item_use_scope, use_tree)) = trait_item_use_scope {
214                res.extend(
215                    if use_tree { t.items(sema.db) } else { t.items_with_supertraits(sema.db) }
216                        .into_iter()
217                        .filter_map(|item| {
218                            Definition::from(item)
219                                .usages(sema)
220                                .set_scope(Some(&SearchScope::file_range(FileRange {
221                                    file_id,
222                                    range: trait_item_use_scope.text_range(),
223                                })))
224                                .include_self_refs()
225                                .all()
226                                .references
227                                .remove(&file_id)
228                        })
229                        .flatten()
230                        .map(|FileReference { category, range, .. }| HighlightedRange {
231                            range,
232                            category,
233                        }),
234                );
235            }
236        }
237
238        // highlight the tail expr of the labelled block
239        if matches!(def, Definition::Label(_)) {
240            let label = token.parent_ancestors().nth(1).and_then(ast::Label::cast);
241            if let Some(block) =
242                label.and_then(|label| label.syntax().parent()).and_then(ast::BlockExpr::cast)
243            {
244                for_each_tail_expr(&block.into(), &mut |tail| {
245                    if !matches!(tail, ast::Expr::BreakExpr(_)) {
246                        res.insert(HighlightedRange {
247                            range: tail.syntax().text_range(),
248                            category: ReferenceCategory::empty(),
249                        });
250                    }
251                });
252            }
253        }
254
255        // highlight the defs themselves
256        match def {
257            Definition::Local(local) => {
258                let category = if local.is_mut(sema.db) {
259                    ReferenceCategory::WRITE
260                } else {
261                    ReferenceCategory::empty()
262                };
263                local
264                    .sources(sema.db)
265                    .into_iter()
266                    .flat_map(|x| x.to_nav(sema.db))
267                    .filter(|decl| decl.file_id == file_id.file_id(sema.db))
268                    .filter_map(|decl| decl.focus_range)
269                    .map(|range| HighlightedRange { range, category })
270                    .for_each(|x| {
271                        res.insert(x);
272                    });
273            }
274            def => {
275                let navs = match def {
276                    Definition::Module(module) => {
277                        NavigationTarget::from_module_to_decl(sema.db, module)
278                    }
279                    def => match def.try_to_nav(sema) {
280                        Some(it) => it,
281                        None => continue,
282                    },
283                };
284                for nav in navs {
285                    if nav.file_id != file_id.file_id(sema.db) {
286                        continue;
287                    }
288                    let hl_range = nav.focus_range.map(|range| {
289                        let category = if matches!(def, Definition::Local(l) if l.is_mut(sema.db)) {
290                            ReferenceCategory::WRITE
291                        } else {
292                            ReferenceCategory::empty()
293                        };
294                        HighlightedRange { range, category }
295                    });
296                    if let Some(hl_range) = hl_range {
297                        res.insert(hl_range);
298                    }
299                }
300            }
301        }
302    }
303
304    res.extend(usages);
305    if res.is_empty() { None } else { Some(res.into_iter().collect()) }
306}
307
308pub(crate) fn highlight_branch_exit_points(
309    sema: &Semantics<'_, RootDatabase>,
310    token: SyntaxToken,
311) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
312    let mut highlights: HighlightMap = FxHashMap::default();
313
314    let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {
315        if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
316            let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
317            highlights.entry(file_id).or_default().insert(hrange);
318        }
319    };
320
321    let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {
322        let Some(tail) = tail else {
323            return;
324        };
325
326        for_each_tail_expr(&tail, &mut |tail| {
327            let file_id = sema.hir_file_for(tail.syntax());
328            let range = tail.syntax().text_range();
329            push_to_highlights(file_id, Some(range), highlights);
330        });
331    };
332
333    let nodes = goto_definition::find_branch_root(sema, &token).into_iter();
334    match token.kind() {
335        T![match] => {
336            for match_expr in nodes.filter_map(ast::MatchExpr::cast) {
337                let file_id = sema.hir_file_for(match_expr.syntax());
338                let range = match_expr.match_token().map(|token| token.text_range());
339                push_to_highlights(file_id, range, &mut highlights);
340
341                let Some(arm_list) = match_expr.match_arm_list() else {
342                    continue;
343                };
344                for arm in arm_list.arms() {
345                    push_tail_expr(arm.expr(), &mut highlights);
346                }
347            }
348        }
349        T![=>] => {
350            for arm in nodes.filter_map(ast::MatchArm::cast) {
351                let file_id = sema.hir_file_for(arm.syntax());
352                let range = arm.fat_arrow_token().map(|token| token.text_range());
353                push_to_highlights(file_id, range, &mut highlights);
354
355                push_tail_expr(arm.expr(), &mut highlights);
356            }
357        }
358        T![if] => {
359            for mut if_to_process in nodes.map(ast::IfExpr::cast) {
360                while let Some(cur_if) = if_to_process.take() {
361                    let file_id = sema.hir_file_for(cur_if.syntax());
362
363                    let if_kw_range = cur_if.if_token().map(|token| token.text_range());
364                    push_to_highlights(file_id, if_kw_range, &mut highlights);
365
366                    if let Some(then_block) = cur_if.then_branch() {
367                        push_tail_expr(Some(then_block.into()), &mut highlights);
368                    }
369
370                    match cur_if.else_branch() {
371                        Some(ast::ElseBranch::Block(else_block)) => {
372                            push_tail_expr(Some(else_block.into()), &mut highlights);
373                            if_to_process = None;
374                        }
375                        Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),
376                        None => if_to_process = None,
377                    }
378                }
379            }
380        }
381        _ => {}
382    }
383
384    highlights
385        .into_iter()
386        .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))
387        .collect()
388}
389
390fn hl_exit_points(
391    sema: &Semantics<'_, RootDatabase>,
392    def_token: Option<SyntaxToken>,
393    body: ast::Expr,
394) -> Option<HighlightMap> {
395    let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
396
397    let mut push_to_highlights = |file_id, range| {
398        if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
399            let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
400            highlights.entry(file_id).or_default().insert(hrange);
401        }
402    };
403
404    if let Some(tok) = def_token {
405        let file_id = sema.hir_file_for(&tok.parent()?);
406        let range = Some(tok.text_range());
407        push_to_highlights(file_id, range);
408    }
409
410    WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {
411        let file_id = sema.hir_file_for(expr.syntax());
412
413        let range = match &expr {
414            ast::Expr::TryExpr(try_) => try_.question_mark_token().map(|token| token.text_range()),
415            ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_)
416                if sema.type_of_expr(&expr).is_some_and(|ty| ty.original.is_never()) =>
417            {
418                Some(expr.syntax().text_range())
419            }
420            _ => None,
421        };
422
423        push_to_highlights(file_id, range);
424    });
425
426    // We should handle `return` separately, because when it is used in a `try` block,
427    // it will exit the outside function instead of the block itself.
428    WalkExpandedExprCtx::new(sema)
429        .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)
430        .walk(&body, &mut |_, expr| {
431            let file_id = sema.hir_file_for(expr.syntax());
432
433            let range = match &expr {
434                ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()),
435                _ => None,
436            };
437
438            push_to_highlights(file_id, range);
439        });
440
441    let tail = match body {
442        ast::Expr::BlockExpr(b) => b.tail_expr(),
443        e => Some(e),
444    };
445
446    if let Some(tail) = tail {
447        for_each_tail_expr(&tail, &mut |tail| {
448            let file_id = sema.hir_file_for(tail.syntax());
449            let range = match tail {
450                ast::Expr::BreakExpr(b) => b
451                    .break_token()
452                    .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
453                _ => tail.syntax().text_range(),
454            };
455            push_to_highlights(file_id, Some(range));
456        });
457    }
458    Some(highlights)
459}
460
461// If `file_id` is None,
462pub(crate) fn highlight_exit_points(
463    sema: &Semantics<'_, RootDatabase>,
464    token: SyntaxToken,
465) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
466    let mut res = FxHashMap::default();
467    for def in goto_definition::find_fn_or_blocks(sema, &token) {
468        let new_map = match_ast! {
469            match def {
470                ast::Fn(fn_) => fn_.body().and_then(|body| hl_exit_points(sema, fn_.fn_token(), body.into())),
471                ast::ClosureExpr(closure) => {
472                    let pipe_tok = closure.param_list().and_then(|p| p.pipe_token());
473                    closure.body().and_then(|body| hl_exit_points(sema, pipe_tok, body))
474                },
475                ast::BlockExpr(blk) => match blk.modifier() {
476                    Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()),
477                    Some(ast::BlockModifier::Try { try_token: t, .. }) if token.kind() != T![return] => {
478                        hl_exit_points(sema, Some(t), blk.into())
479                    },
480                    _ => continue,
481                },
482                _ => continue,
483            }
484        };
485        merge_map(&mut res, new_map);
486    }
487
488    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
489}
490
491pub(crate) fn highlight_break_points(
492    sema: &Semantics<'_, RootDatabase>,
493    token: SyntaxToken,
494) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
495    pub(crate) fn hl(
496        sema: &Semantics<'_, RootDatabase>,
497        cursor_token_kind: SyntaxKind,
498        loop_token: Option<SyntaxToken>,
499        label: Option<ast::Label>,
500        expr: ast::Expr,
501    ) -> Option<HighlightMap> {
502        let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
503
504        let mut push_to_highlights = |file_id, range| {
505            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
506                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
507                highlights.entry(file_id).or_default().insert(hrange);
508            }
509        };
510
511        let label_lt = label.as_ref().and_then(|it| it.lifetime());
512
513        if let Some(range) = cover_range(
514            loop_token.as_ref().map(|tok| tok.text_range()),
515            label.as_ref().map(|it| it.syntax().text_range()),
516        ) {
517            let file_id = loop_token
518                .and_then(|tok| Some(sema.hir_file_for(&tok.parent()?)))
519                .unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax()));
520            push_to_highlights(file_id, Some(range));
521        }
522
523        WalkExpandedExprCtx::new(sema)
524            .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)
525            .walk(&expr, &mut |depth, expr| {
526                let file_id = sema.hir_file_for(expr.syntax());
527
528                // Only highlight the `break`s for `break` and `continue`s for `continue`
529                let (token, token_lt) = match expr {
530                    ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => {
531                        (b.break_token(), b.lifetime())
532                    }
533                    ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => {
534                        (c.continue_token(), c.lifetime())
535                    }
536                    _ => return,
537                };
538
539                if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) {
540                    return;
541                }
542
543                let text_range = cover_range(
544                    token.map(|it| it.text_range()),
545                    token_lt.map(|it| it.syntax().text_range()),
546                );
547
548                push_to_highlights(file_id, text_range);
549            });
550
551        if matches!(expr, ast::Expr::BlockExpr(_)) {
552            for_each_tail_expr(&expr, &mut |tail| {
553                if matches!(tail, ast::Expr::BreakExpr(_)) {
554                    return;
555                }
556
557                let file_id = sema.hir_file_for(tail.syntax());
558                let range = tail.syntax().text_range();
559                push_to_highlights(file_id, Some(range));
560            });
561        }
562
563        Some(highlights)
564    }
565
566    let Some(loops) = find_loops(sema, &token) else {
567        return FxHashMap::default();
568    };
569
570    let mut res = FxHashMap::default();
571    let token_kind = token.kind();
572    for expr in loops {
573        let new_map = match &expr {
574            ast::Expr::LoopExpr(l) => hl(sema, token_kind, l.loop_token(), l.label(), expr),
575            ast::Expr::ForExpr(f) => hl(sema, token_kind, f.for_token(), f.label(), expr),
576            ast::Expr::WhileExpr(w) => hl(sema, token_kind, w.while_token(), w.label(), expr),
577            ast::Expr::BlockExpr(e) => hl(sema, token_kind, None, e.label(), expr),
578            _ => continue,
579        };
580        merge_map(&mut res, new_map);
581    }
582
583    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
584}
585
586pub(crate) fn highlight_yield_points(
587    sema: &Semantics<'_, RootDatabase>,
588    token: SyntaxToken,
589) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
590    fn hl(
591        sema: &Semantics<'_, RootDatabase>,
592        async_token: Option<SyntaxToken>,
593        body: Option<ast::Expr>,
594    ) -> Option<HighlightMap> {
595        let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
596
597        let mut push_to_highlights = |file_id, range| {
598            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
599                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
600                highlights.entry(file_id).or_default().insert(hrange);
601            }
602        };
603
604        let async_token = async_token?;
605        let async_tok_file_id = sema.hir_file_for(&async_token.parent()?);
606        push_to_highlights(async_tok_file_id, Some(async_token.text_range()));
607
608        let Some(body) = body else {
609            return Some(highlights);
610        };
611
612        WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {
613            let file_id = sema.hir_file_for(expr.syntax());
614
615            let text_range = match expr {
616                ast::Expr::AwaitExpr(expr) => expr.await_token(),
617                ast::Expr::ReturnExpr(expr) => expr.return_token(),
618                _ => None,
619            }
620            .map(|it| it.text_range());
621
622            push_to_highlights(file_id, text_range);
623        });
624
625        Some(highlights)
626    }
627
628    let mut res = FxHashMap::default();
629    for anc in goto_definition::find_fn_or_blocks(sema, &token) {
630        let new_map = match_ast! {
631            match anc {
632                ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
633                ast::BlockExpr(block_expr) => {
634                    let Some(async_token) = block_expr.async_token() else {
635                        continue;
636                    };
637
638                    // Async blocks act similar to closures. So we want to
639                    // highlight their exit points too, but only if we are on
640                    // the async token.
641                    if async_token == token {
642                        let exit_points = hl_exit_points(
643                            sema,
644                            Some(async_token.clone()),
645                            block_expr.clone().into(),
646                        );
647                        merge_map(&mut res, exit_points);
648                    }
649
650                    hl(sema, Some(async_token), Some(block_expr.into()))
651                },
652                ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()),
653                _ => continue,
654            }
655        };
656        merge_map(&mut res, new_map);
657    }
658
659    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
660}
661
662fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
663    match (r0, r1) {
664        (Some(r0), Some(r1)) => Some(r0.cover(r1)),
665        (Some(range), None) => Some(range),
666        (None, Some(range)) => Some(range),
667        (None, None) => None,
668    }
669}
670
671fn find_defs<'db>(
672    sema: &Semantics<'db, RootDatabase>,
673    token: SyntaxToken,
674) -> FxHashSet<Definition<'db>> {
675    sema.descend_into_macros_exact(token)
676        .into_iter()
677        .filter_map(|token| IdentClass::classify_token(sema, &token))
678        .flat_map(IdentClass::definitions_no_ops)
679        .collect()
680}
681
682fn original_frange(
683    db: &dyn SourceDatabase,
684    file_id: HirFileId,
685    text_range: Option<TextRange>,
686) -> Option<FileRange> {
687    InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)
688}
689
690fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {
691    let Some(new) = new else {
692        return;
693    };
694    new.into_iter().for_each(|(file_id, ranges)| {
695        res.entry(file_id).or_default().extend(ranges);
696    });
697}
698
699/// Preorder walk all the expression's child expressions.
700/// For macro calls, the callback will be called on the expanded expressions after
701/// visiting the macro call itself.
702struct WalkExpandedExprCtx<'a, 'db> {
703    sema: &'a Semantics<'db, RootDatabase>,
704    depth: usize,
705    check_ctx: &'static dyn Fn(&ast::Expr) -> bool,
706}
707
708impl<'a, 'db> WalkExpandedExprCtx<'a, 'db> {
709    fn new(sema: &'a Semantics<'db, RootDatabase>) -> Self {
710        Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif }
711    }
712
713    fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self {
714        Self { check_ctx, ..*self }
715    }
716
717    fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) {
718        preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent<ast::Expr>| {
719            match ev {
720                syntax::WalkEvent::Enter(expr) => {
721                    cb(self.depth, expr.clone());
722
723                    if Self::should_change_depth(&expr) {
724                        self.depth += 1;
725                    }
726
727                    if let ast::Expr::MacroExpr(expr) = expr
728                        && let Some(expanded) =
729                            expr.macro_call().and_then(|call| self.sema.expand_macro_call(&call))
730                    {
731                        match_ast! {
732                            match (expanded.value) {
733                                ast::MacroStmts(it) => {
734                                    self.handle_expanded(it, cb);
735                                },
736                                ast::Expr(it) => {
737                                    self.walk(&it, cb);
738                                },
739                                _ => {}
740                            }
741                        }
742                    }
743                }
744                syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => {
745                    self.depth -= 1;
746                }
747                _ => {}
748            }
749            false
750        })
751    }
752
753    fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) {
754        if let Some(expr) = expanded.expr() {
755            self.walk(&expr, cb);
756        }
757
758        for stmt in expanded.statements() {
759            if let ast::Stmt::ExprStmt(stmt) = stmt
760                && let Some(expr) = stmt.expr()
761            {
762                self.walk(&expr, cb);
763            }
764        }
765    }
766
767    fn should_change_depth(expr: &ast::Expr) -> bool {
768        match expr {
769            ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true,
770            ast::Expr::BlockExpr(blk) if blk.label().is_some() => true,
771            _ => false,
772        }
773    }
774
775    fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool {
776        match expr {
777            ast::Expr::BlockExpr(b) => matches!(
778                b.modifier(),
779                Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_))
780            ),
781            ast::Expr::ClosureExpr(_) => true,
782            _ => false,
783        }
784    }
785}
786
787pub(crate) fn highlight_unsafe_points(
788    sema: &Semantics<'_, RootDatabase>,
789    token: SyntaxToken,
790) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
791    fn hl(
792        sema: &Semantics<'_, RootDatabase>,
793        unsafe_token: &SyntaxToken,
794        block_expr: Option<ast::BlockExpr>,
795    ) -> Option<FxHashMap<EditionedFileId, Vec<HighlightedRange>>> {
796        let mut highlights: FxHashMap<EditionedFileId, Vec<_>> = FxHashMap::default();
797
798        let mut push_to_highlights = |file_id, range| {
799            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
800                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
801                highlights.entry(file_id).or_default().push(hrange);
802            }
803        };
804
805        // highlight unsafe keyword itself
806        let unsafe_token_file_id = sema.hir_file_for(&unsafe_token.parent()?);
807        push_to_highlights(unsafe_token_file_id, Some(unsafe_token.text_range()));
808
809        // highlight unsafe operations
810        if let Some(block) = block_expr {
811            let unsafe_ops = sema.get_unsafe_ops_for_unsafe_block(block);
812            for unsafe_op in unsafe_ops {
813                push_to_highlights(unsafe_op.file_id, Some(unsafe_op.value.text_range()));
814            }
815        }
816
817        Some(highlights)
818    }
819
820    hl(sema, &token, token.parent().and_then(ast::BlockExpr::cast)).unwrap_or_default()
821}
822
823#[cfg(test)]
824mod tests {
825    use itertools::Itertools;
826
827    use crate::fixture;
828
829    use super::*;
830
831    const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
832        break_points: true,
833        exit_points: true,
834        references: true,
835        closure_captures: true,
836        yield_points: true,
837        branch_exit_points: true,
838    };
839
840    #[track_caller]
841    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
842        check_with_config(ra_fixture, ENABLED_CONFIG);
843    }
844
845    #[track_caller]
846    fn check_with_config(
847        #[rust_analyzer::rust_fixture] ra_fixture: &str,
848        config: HighlightRelatedConfig,
849    ) {
850        let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
851
852        let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
853
854        let mut expected =
855            annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();
856
857        let mut actual: Vec<(TextRange, String)> = hls
858            .into_iter()
859            .map(|hl| {
860                (
861                    hl.range,
862                    hl.category.iter_names().map(|(name, _flag)| name.to_lowercase()).join(","),
863                )
864            })
865            .collect();
866        actual.sort_by_key(|(range, _)| range.start());
867        expected.sort_by_key(|(range, _)| range.start());
868
869        assert_eq!(expected, actual);
870    }
871
872    #[test]
873    fn test_hl_unsafe_block() {
874        check(
875            r#"
876fn foo() {
877    unsafe fn this_is_unsafe_function() {}
878
879    unsa$0fe {
880  //^^^^^^
881        let raw_ptr = &42 as *const i32;
882        let val = *raw_ptr;
883                //^^^^^^^^
884
885        let mut_ptr = &mut 5 as *mut i32;
886        *mut_ptr = 10;
887      //^^^^^^^^
888
889        this_is_unsafe_function();
890      //^^^^^^^^^^^^^^^^^^^^^^^^^
891    }
892
893}
894"#,
895        );
896    }
897
898    #[test]
899    fn test_hl_tuple_fields() {
900        check(
901            r#"
902struct Tuple(u32, u32);
903
904fn foo(t: Tuple) {
905    t.0$0;
906   // ^ read
907    t.0;
908   // ^ read
909}
910"#,
911        );
912    }
913
914    #[test]
915    fn test_hl_module() {
916        check(
917            r#"
918//- /lib.rs
919mod foo$0;
920 // ^^^
921//- /foo.rs
922struct Foo;
923"#,
924        );
925    }
926
927    #[test]
928    fn test_hl_self_in_crate_root() {
929        check(
930            r#"
931use crate$0;
932  //^^^^^ import
933use self;
934  //^^^^ import
935mod __ {
936    use super;
937      //^^^^^ import
938}
939"#,
940        );
941        check(
942            r#"
943//- /main.rs crate:main deps:lib
944use lib$0;
945  //^^^ import
946//- /lib.rs crate:lib
947"#,
948        );
949    }
950
951    #[test]
952    fn test_hl_self_in_module() {
953        check(
954            r#"
955//- /lib.rs
956mod foo;
957//- /foo.rs
958use self$0;
959 // ^^^^ import
960"#,
961        );
962    }
963
964    #[test]
965    fn test_hl_local() {
966        check(
967            r#"
968fn foo() {
969    let mut bar = 3;
970         // ^^^ write
971    bar$0;
972 // ^^^ read
973}
974"#,
975        );
976    }
977
978    #[test]
979    fn test_hl_local_in_attr() {
980        check(
981            r#"
982//- proc_macros: identity
983#[proc_macros::identity]
984fn foo() {
985    let mut bar = 3;
986         // ^^^ write
987    bar$0;
988 // ^^^ read
989}
990"#,
991        );
992    }
993
994    #[test]
995    fn test_multi_macro_usage() {
996        check(
997            r#"
998macro_rules! foo {
999    ($ident:ident) => {
1000        fn $ident() -> $ident { loop {} }
1001        struct $ident;
1002    }
1003}
1004
1005foo!(bar$0);
1006  // ^^^
1007fn foo() {
1008    let bar: bar = bar();
1009          // ^^^
1010                // ^^^
1011}
1012"#,
1013        );
1014        check(
1015            r#"
1016macro_rules! foo {
1017    ($ident:ident) => {
1018        fn $ident() -> $ident { loop {} }
1019        struct $ident;
1020    }
1021}
1022
1023foo!(bar);
1024  // ^^^
1025fn foo() {
1026    let bar: bar$0 = bar();
1027          // ^^^
1028}
1029"#,
1030        );
1031    }
1032
1033    #[test]
1034    fn test_hl_yield_points() {
1035        check(
1036            r#"
1037pub async fn foo() {
1038 // ^^^^^
1039    let x = foo()
1040        .await$0
1041      // ^^^^^
1042        .await;
1043      // ^^^^^
1044    || { 0.await };
1045    (async { 0.await }).await
1046                     // ^^^^^
1047}
1048"#,
1049        );
1050    }
1051
1052    #[test]
1053    fn test_hl_yield_points2() {
1054        check(
1055            r#"
1056pub async$0 fn foo() {
1057 // ^^^^^
1058    let x = foo()
1059        .await
1060      // ^^^^^
1061        .await;
1062      // ^^^^^
1063    || { 0.await };
1064    (async { 0.await }).await
1065                     // ^^^^^
1066}
1067"#,
1068        );
1069    }
1070
1071    #[test]
1072    fn test_hl_exit_points_of_async_blocks() {
1073        check(
1074            r#"
1075pub fn foo() {
1076    let x = async$0 {
1077         // ^^^^^
1078        0.await;
1079       // ^^^^^
1080       0?;
1081     // ^
1082       return 0;
1083    // ^^^^^^
1084       0
1085    // ^
1086    };
1087}
1088"#,
1089        );
1090    }
1091
1092    #[test]
1093    fn test_hl_let_else_yield_points() {
1094        check(
1095            r#"
1096pub async fn foo() {
1097 // ^^^^^
1098    let x = foo()
1099        .await$0
1100      // ^^^^^
1101        .await;
1102      // ^^^^^
1103    || { 0.await };
1104    let Some(_) = None else {
1105        foo().await
1106           // ^^^^^
1107    };
1108    (async { 0.await }).await
1109                     // ^^^^^
1110}
1111"#,
1112        );
1113    }
1114
1115    #[test]
1116    fn test_hl_yield_nested_fn() {
1117        check(
1118            r#"
1119async fn foo() {
1120    async fn foo2() {
1121 // ^^^^^
1122        async fn foo3() {
1123            0.await
1124        }
1125        0.await$0
1126       // ^^^^^
1127    }
1128    0.await
1129}
1130"#,
1131        );
1132    }
1133
1134    #[test]
1135    fn test_hl_yield_nested_async_blocks() {
1136        check(
1137            r#"
1138async fn foo() {
1139    (async {
1140  // ^^^^^
1141        (async { 0.await }).await$0
1142                         // ^^^^^
1143    }).await;
1144}
1145"#,
1146        );
1147    }
1148
1149    #[test]
1150    fn test_hl_exit_points() {
1151        check(
1152            r#"
1153  fn foo() -> u32 {
1154//^^
1155    if true {
1156        return$0 0;
1157     // ^^^^^^
1158    }
1159
1160    0?;
1161  // ^
1162    0xDEAD_BEEF
1163 // ^^^^^^^^^^^
1164}
1165"#,
1166        );
1167    }
1168
1169    #[test]
1170    fn test_hl_exit_points2() {
1171        check(
1172            r#"
1173  fn foo() ->$0 u32 {
1174//^^
1175    if true {
1176        return 0;
1177     // ^^^^^^
1178    }
1179
1180    0?;
1181  // ^
1182    0xDEAD_BEEF
1183 // ^^^^^^^^^^^
1184}
1185"#,
1186        );
1187    }
1188
1189    #[test]
1190    fn test_hl_exit_points3() {
1191        check(
1192            r#"
1193  fn$0 foo() -> u32 {
1194//^^
1195    if true {
1196        return 0;
1197     // ^^^^^^
1198    }
1199
1200    0?;
1201  // ^
1202    0xDEAD_BEEF
1203 // ^^^^^^^^^^^
1204}
1205"#,
1206        );
1207    }
1208
1209    #[test]
1210    fn test_hl_let_else_exit_points() {
1211        check(
1212            r#"
1213  fn$0 foo() -> u32 {
1214//^^
1215    let Some(bar) = None else {
1216        return 0;
1217     // ^^^^^^
1218    };
1219
1220    0?;
1221  // ^
1222    0xDEAD_BEEF
1223 // ^^^^^^^^^^^
1224}
1225"#,
1226        );
1227    }
1228
1229    #[test]
1230    fn test_hl_prefer_ref_over_tail_exit() {
1231        check(
1232            r#"
1233fn foo() -> u32 {
1234// ^^^
1235    if true {
1236        return 0;
1237    }
1238
1239    0?;
1240
1241    foo$0()
1242 // ^^^
1243}
1244"#,
1245        );
1246    }
1247
1248    #[test]
1249    fn test_hl_never_call_is_exit_point() {
1250        check(
1251            r#"
1252struct Never;
1253impl Never {
1254    fn never(self) -> ! { loop {} }
1255}
1256macro_rules! never {
1257    () => { never() }
1258         // ^^^^^^^
1259}
1260fn never() -> ! { loop {} }
1261  fn foo() ->$0 u32 {
1262//^^
1263    never();
1264 // ^^^^^^^
1265    never!();
1266 // ^^^^^^^^
1267
1268    Never.never();
1269 // ^^^^^^^^^^^^^
1270
1271    0
1272 // ^
1273}
1274"#,
1275        );
1276    }
1277
1278    #[test]
1279    fn test_hl_inner_tail_exit_points() {
1280        check(
1281            r#"
1282  fn foo() ->$0 u32 {
1283//^^
1284    if true {
1285        unsafe {
1286            return 5;
1287         // ^^^^^^
1288            5
1289         // ^
1290        }
1291    } else if false {
1292        0
1293     // ^
1294    } else {
1295        match 5 {
1296            6 => 100,
1297              // ^^^
1298            7 => loop {
1299                break 5;
1300             // ^^^^^
1301            }
1302            8 => 'a: loop {
1303                'b: loop {
1304                    break 'a 5;
1305                 // ^^^^^
1306                    break 'b 5;
1307                    break 5;
1308                };
1309            }
1310            //
1311            _ => 500,
1312              // ^^^
1313        }
1314    }
1315}
1316"#,
1317        );
1318    }
1319
1320    #[test]
1321    fn test_hl_inner_tail_exit_points_labeled_block() {
1322        check(
1323            r#"
1324  fn foo() ->$0 u32 {
1325//^^
1326    'foo: {
1327        break 'foo 0;
1328     // ^^^^^
1329        loop {
1330            break;
1331            break 'foo 0;
1332         // ^^^^^
1333        }
1334        0
1335     // ^
1336    }
1337}
1338"#,
1339        );
1340    }
1341
1342    #[test]
1343    fn test_hl_inner_tail_exit_points_loops() {
1344        check(
1345            r#"
1346  fn foo() ->$0 u32 {
1347//^^
1348    'foo: while { return 0; true } {
1349               // ^^^^^^
1350        break 'foo 0;
1351     // ^^^^^
1352        return 0;
1353     // ^^^^^^
1354    }
1355}
1356"#,
1357        );
1358    }
1359
1360    #[test]
1361    fn test_hl_break_loop() {
1362        check(
1363            r#"
1364fn foo() {
1365    'outer: loop {
1366 // ^^^^^^^^^^^^
1367         break;
1368      // ^^^^^
1369         'inner: loop {
1370            break;
1371            'innermost: loop {
1372                break 'outer;
1373             // ^^^^^^^^^^^^
1374                break 'inner;
1375            }
1376            break$0 'outer;
1377         // ^^^^^^^^^^^^
1378            break;
1379        }
1380        break;
1381     // ^^^^^
1382    }
1383}
1384"#,
1385        );
1386    }
1387
1388    #[test]
1389    fn test_hl_break_loop2() {
1390        check(
1391            r#"
1392fn foo() {
1393    'outer: loop {
1394        break;
1395        'inner: loop {
1396     // ^^^^^^^^^^^^
1397            break;
1398         // ^^^^^
1399            'innermost: loop {
1400                break 'outer;
1401                break 'inner;
1402             // ^^^^^^^^^^^^
1403            }
1404            break 'outer;
1405            break$0;
1406         // ^^^^^
1407        }
1408        break;
1409    }
1410}
1411"#,
1412        );
1413    }
1414
1415    #[test]
1416    fn test_hl_break_for() {
1417        check(
1418            r#"
1419fn foo() {
1420    'outer: for _ in () {
1421 // ^^^^^^^^^^^
1422         break;
1423      // ^^^^^
1424         'inner: for _ in () {
1425            break;
1426            'innermost: for _ in () {
1427                break 'outer;
1428             // ^^^^^^^^^^^^
1429                break 'inner;
1430            }
1431            break$0 'outer;
1432         // ^^^^^^^^^^^^
1433            break;
1434        }
1435        break;
1436     // ^^^^^
1437    }
1438}
1439"#,
1440        );
1441    }
1442
1443    #[test]
1444    fn test_hl_break_for_but_not_continue() {
1445        check(
1446            r#"
1447fn foo() {
1448    'outer: for _ in () {
1449 // ^^^^^^^^^^^
1450        break;
1451     // ^^^^^
1452        continue;
1453        'inner: for _ in () {
1454            break;
1455            continue;
1456            'innermost: for _ in () {
1457                continue 'outer;
1458                break 'outer;
1459             // ^^^^^^^^^^^^
1460                continue 'inner;
1461                break 'inner;
1462            }
1463            break$0 'outer;
1464         // ^^^^^^^^^^^^
1465            continue 'outer;
1466            break;
1467            continue;
1468        }
1469        break;
1470     // ^^^^^
1471        continue;
1472    }
1473}
1474"#,
1475        );
1476    }
1477
1478    #[test]
1479    fn test_hl_continue_for_but_not_break() {
1480        check(
1481            r#"
1482fn foo() {
1483    'outer: for _ in () {
1484 // ^^^^^^^^^^^
1485        break;
1486        continue;
1487     // ^^^^^^^^
1488        'inner: for _ in () {
1489            break;
1490            continue;
1491            'innermost: for _ in () {
1492                continue 'outer;
1493             // ^^^^^^^^^^^^^^^
1494                break 'outer;
1495                continue 'inner;
1496                break 'inner;
1497            }
1498            break 'outer;
1499            continue$0 'outer;
1500         // ^^^^^^^^^^^^^^^
1501            break;
1502            continue;
1503        }
1504        break;
1505        continue;
1506     // ^^^^^^^^
1507    }
1508}
1509"#,
1510        );
1511    }
1512
1513    #[test]
1514    fn test_hl_break_and_continue() {
1515        check(
1516            r#"
1517fn foo() {
1518    'outer: fo$0r _ in () {
1519 // ^^^^^^^^^^^
1520        break;
1521     // ^^^^^
1522        continue;
1523     // ^^^^^^^^
1524        'inner: for _ in () {
1525            break;
1526            continue;
1527            'innermost: for _ in () {
1528                continue 'outer;
1529             // ^^^^^^^^^^^^^^^
1530                break 'outer;
1531             // ^^^^^^^^^^^^
1532                continue 'inner;
1533                break 'inner;
1534            }
1535            break 'outer;
1536         // ^^^^^^^^^^^^
1537            continue 'outer;
1538         // ^^^^^^^^^^^^^^^
1539            break;
1540            continue;
1541        }
1542        break;
1543     // ^^^^^
1544        continue;
1545     // ^^^^^^^^
1546    }
1547}
1548"#,
1549        );
1550    }
1551
1552    #[test]
1553    fn test_hl_break_while() {
1554        check(
1555            r#"
1556fn foo() {
1557    'outer: while true {
1558 // ^^^^^^^^^^^^^
1559         break;
1560      // ^^^^^
1561         'inner: while true {
1562            break;
1563            'innermost: while true {
1564                break 'outer;
1565             // ^^^^^^^^^^^^
1566                break 'inner;
1567            }
1568            break$0 'outer;
1569         // ^^^^^^^^^^^^
1570            break;
1571        }
1572        break;
1573     // ^^^^^
1574    }
1575}
1576"#,
1577        );
1578    }
1579
1580    #[test]
1581    fn test_hl_break_labeled_block() {
1582        check(
1583            r#"
1584fn foo() {
1585    'outer: {
1586 // ^^^^^^^
1587         break;
1588      // ^^^^^
1589         'inner: {
1590            break;
1591            'innermost: {
1592                break 'outer;
1593             // ^^^^^^^^^^^^
1594                break 'inner;
1595            }
1596            break$0 'outer;
1597         // ^^^^^^^^^^^^
1598            break;
1599        }
1600        break;
1601     // ^^^^^
1602    }
1603}
1604"#,
1605        );
1606    }
1607
1608    #[test]
1609    fn test_hl_break_unlabeled_loop() {
1610        check(
1611            r#"
1612fn foo() {
1613    loop {
1614 // ^^^^
1615        break$0;
1616     // ^^^^^
1617    }
1618}
1619"#,
1620        );
1621    }
1622
1623    #[test]
1624    fn test_hl_break_unlabeled_block_in_loop() {
1625        check(
1626            r#"
1627fn foo() {
1628    loop {
1629 // ^^^^
1630        {
1631            break$0;
1632         // ^^^^^
1633        }
1634    }
1635}
1636"#,
1637        );
1638    }
1639
1640    #[test]
1641    fn test_hl_field_shorthand() {
1642        check(
1643            r#"
1644struct Struct { field: u32 }
1645              //^^^^^
1646fn function(field: u32) {
1647          //^^^^^
1648    Struct { field$0 }
1649           //^^^^^ read
1650}
1651"#,
1652        );
1653    }
1654
1655    #[test]
1656    fn test_hl_disabled_ref_local() {
1657        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1658
1659        check_with_config(
1660            r#"
1661fn foo() {
1662    let x$0 = 5;
1663    let y = x * 2;
1664}
1665"#,
1666            config,
1667        );
1668    }
1669
1670    #[test]
1671    fn test_hl_disabled_ref_local_preserved_break() {
1672        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1673
1674        check_with_config(
1675            r#"
1676fn foo() {
1677    let x$0 = 5;
1678    let y = x * 2;
1679
1680    loop {
1681        break;
1682    }
1683}
1684"#,
1685            config.clone(),
1686        );
1687
1688        check_with_config(
1689            r#"
1690fn foo() {
1691    let x = 5;
1692    let y = x * 2;
1693
1694    loop$0 {
1695//  ^^^^
1696        break;
1697//      ^^^^^
1698    }
1699}
1700"#,
1701            config,
1702        );
1703    }
1704
1705    #[test]
1706    fn test_hl_disabled_ref_local_preserved_yield() {
1707        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1708
1709        check_with_config(
1710            r#"
1711async fn foo() {
1712    let x$0 = 5;
1713    let y = x * 2;
1714
1715    0.await;
1716}
1717"#,
1718            config.clone(),
1719        );
1720
1721        check_with_config(
1722            r#"
1723    async fn foo() {
1724//  ^^^^^
1725        let x = 5;
1726        let y = x * 2;
1727
1728        0.await$0;
1729//        ^^^^^
1730}
1731"#,
1732            config,
1733        );
1734    }
1735
1736    #[test]
1737    fn test_hl_disabled_ref_local_preserved_exit() {
1738        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1739
1740        check_with_config(
1741            r#"
1742fn foo() -> i32 {
1743    let x$0 = 5;
1744    let y = x * 2;
1745
1746    if true {
1747        return y;
1748    }
1749
1750    0?
1751}
1752"#,
1753            config.clone(),
1754        );
1755
1756        check_with_config(
1757            r#"
1758  fn foo() ->$0 i32 {
1759//^^
1760    let x = 5;
1761    let y = x * 2;
1762
1763    if true {
1764        return y;
1765//      ^^^^^^
1766    }
1767
1768    0?
1769//   ^
1770"#,
1771            config,
1772        );
1773    }
1774
1775    #[test]
1776    fn test_hl_disabled_break() {
1777        let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
1778
1779        check_with_config(
1780            r#"
1781fn foo() {
1782    loop {
1783        break$0;
1784    }
1785}
1786"#,
1787            config,
1788        );
1789    }
1790
1791    #[test]
1792    fn test_hl_disabled_yield() {
1793        let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
1794
1795        check_with_config(
1796            r#"
1797async$0 fn foo() {
1798    0.await;
1799}
1800"#,
1801            config,
1802        );
1803    }
1804
1805    #[test]
1806    fn test_hl_disabled_exit() {
1807        let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
1808
1809        check_with_config(
1810            r#"
1811fn foo() ->$0 i32 {
1812    if true {
1813        return -1;
1814    }
1815
1816    42
1817}"#,
1818            config,
1819        );
1820    }
1821
1822    #[test]
1823    fn test_hl_multi_local() {
1824        check(
1825            r#"
1826fn foo((
1827    foo$0
1828  //^^^
1829    | foo
1830    //^^^
1831    | foo
1832    //^^^
1833): ()) {
1834    foo;
1835  //^^^read
1836    let foo;
1837}
1838"#,
1839        );
1840        check(
1841            r#"
1842fn foo((
1843    foo
1844  //^^^
1845    | foo$0
1846    //^^^
1847    | foo
1848    //^^^
1849): ()) {
1850    foo;
1851  //^^^read
1852    let foo;
1853}
1854"#,
1855        );
1856        check(
1857            r#"
1858fn foo((
1859    foo
1860  //^^^
1861    | foo
1862    //^^^
1863    | foo
1864    //^^^
1865): ()) {
1866    foo$0;
1867  //^^^read
1868    let foo;
1869}
1870"#,
1871        );
1872    }
1873
1874    #[test]
1875    fn test_hl_trait_impl_methods() {
1876        check(
1877            r#"
1878trait Trait {
1879    fn func$0(self) {}
1880     //^^^^
1881}
1882
1883impl Trait for () {
1884    fn func(self) {}
1885     //^^^^
1886}
1887
1888fn main() {
1889    <()>::func(());
1890        //^^^^
1891    ().func();
1892     //^^^^
1893}
1894"#,
1895        );
1896        check(
1897            r#"
1898trait Trait {
1899    fn func(self) {}
1900}
1901
1902impl Trait for () {
1903    fn func$0(self) {}
1904     //^^^^
1905}
1906
1907fn main() {
1908    <()>::func(());
1909        //^^^^
1910    ().func();
1911     //^^^^
1912}
1913"#,
1914        );
1915        check(
1916            r#"
1917trait Trait {
1918    fn func(self) {}
1919}
1920
1921impl Trait for () {
1922    fn func(self) {}
1923     //^^^^
1924}
1925
1926fn main() {
1927    <()>::func(());
1928        //^^^^
1929    ().func$0();
1930     //^^^^
1931}
1932"#,
1933        );
1934    }
1935
1936    #[test]
1937    fn test_assoc_type_highlighting() {
1938        check(
1939            r#"
1940trait Trait {
1941    type Output;
1942      // ^^^^^^
1943}
1944impl Trait for () {
1945    type Output$0 = ();
1946      // ^^^^^^
1947}
1948"#,
1949        );
1950    }
1951
1952    #[test]
1953    fn test_closure_capture_pipe() {
1954        check(
1955            r#"
1956fn f() {
1957    let x = 1;
1958    //  ^
1959    let c = $0|y| x + y;
1960    //          ^ read
1961}
1962"#,
1963        );
1964    }
1965
1966    #[test]
1967    fn test_closure_capture_move() {
1968        check(
1969            r#"
1970fn f() {
1971    let x = 1;
1972    //  ^
1973    let c = move$0 |y| x + y;
1974    //               ^ read
1975}
1976"#,
1977        );
1978    }
1979
1980    #[test]
1981    fn test_trait_highlights_assoc_item_uses() {
1982        check(
1983            r#"
1984trait Super {
1985    type SuperT;
1986}
1987trait Foo: Super {
1988    //^^^
1989    type T;
1990    const C: usize;
1991    fn f() {}
1992    fn m(&self) {}
1993}
1994impl Foo for i32 {
1995   //^^^
1996    type T = i32;
1997    const C: usize = 0;
1998    fn f() {}
1999    fn m(&self) {}
2000}
2001fn f<T: Foo$0>(t: T) {
2002      //^^^
2003    let _: T::SuperT;
2004            //^^^^^^
2005    let _: T::T;
2006            //^
2007    t.m();
2008    //^
2009    T::C;
2010     //^
2011    T::f();
2012     //^
2013}
2014
2015fn f2<T: Foo>(t: T) {
2016       //^^^
2017    let _: T::T;
2018    t.m();
2019    T::C;
2020    T::f();
2021}
2022"#,
2023        );
2024    }
2025
2026    #[test]
2027    fn test_trait_highlights_assoc_item_uses_use_tree() {
2028        check(
2029            r#"
2030use Foo$0;
2031 // ^^^ import
2032trait Super {
2033    type SuperT;
2034}
2035trait Foo: Super {
2036    //^^^
2037    type T;
2038    const C: usize;
2039    fn f() {}
2040    fn m(&self) {}
2041}
2042impl Foo for i32 {
2043   //^^^
2044    type T = i32;
2045      // ^
2046    const C: usize = 0;
2047       // ^
2048    fn f() {}
2049    // ^
2050    fn m(&self) {}
2051    // ^
2052}
2053fn f<T: Foo>(t: T) {
2054      //^^^
2055    let _: T::SuperT;
2056    let _: T::T;
2057            //^
2058    t.m();
2059    //^
2060    T::C;
2061     //^
2062    T::f();
2063     //^
2064}
2065"#,
2066        );
2067    }
2068
2069    #[test]
2070    fn implicit_format_args() {
2071        check(
2072            r#"
2073//- minicore: fmt
2074fn test() {
2075    let a = "foo";
2076     // ^
2077    format_args!("hello {a} {a$0} {}", a);
2078                      // ^read
2079                          // ^read
2080                                  // ^read
2081}
2082"#,
2083        );
2084    }
2085
2086    #[test]
2087    fn return_in_macros() {
2088        check(
2089            r#"
2090//- minicore: fn
2091macro_rules! N {
2092    ($i:ident, $x:expr, $blk:expr) => {
2093        for $i in 0..$x {
2094            $blk
2095        }
2096    };
2097}
2098
2099fn main() {
2100    fn f() {
2101 // ^^
2102        N!(i, 5, {
2103            println!("{}", i);
2104            return$0;
2105         // ^^^^^^
2106        });
2107
2108        for i in 1..5 {
2109            return;
2110         // ^^^^^^
2111        }
2112       (|| {
2113            return;
2114        })();
2115    }
2116}
2117"#,
2118        )
2119    }
2120
2121    #[test]
2122    fn return_in_closure() {
2123        check(
2124            r#"
2125macro_rules! N {
2126    ($i:ident, $x:expr, $blk:expr) => {
2127        for $i in 0..$x {
2128            $blk
2129        }
2130    };
2131}
2132
2133fn main() {
2134    fn f() {
2135        N!(i, 5, {
2136            println!("{}", i);
2137            return;
2138        });
2139
2140        for i in 1..5 {
2141            return;
2142        }
2143       (|| {
2144     // ^
2145            return$0;
2146         // ^^^^^^
2147        })();
2148    }
2149}
2150"#,
2151        )
2152    }
2153
2154    #[test]
2155    fn return_in_try() {
2156        check(
2157            r#"
2158fn main() {
2159    fn f() {
2160 // ^^
2161        try {
2162            return$0;
2163         // ^^^^^^
2164        }
2165
2166        return;
2167     // ^^^^^^
2168    }
2169}
2170"#,
2171        )
2172    }
2173
2174    #[test]
2175    fn break_in_try() {
2176        check(
2177            r#"
2178fn main() {
2179    for i in 1..100 {
2180 // ^^^
2181        let x: Result<(), ()> = try {
2182            break$0;
2183         // ^^^^^
2184        };
2185    }
2186}
2187"#,
2188        )
2189    }
2190
2191    #[test]
2192    fn no_highlight_on_return_in_macro_call() {
2193        check(
2194            r#"
2195//- minicore:include
2196//- /lib.rs
2197macro_rules! M {
2198    ($blk:expr) => {
2199        $blk
2200    };
2201}
2202
2203fn main() {
2204    fn f() {
2205 // ^^
2206        M!({ return$0; });
2207          // ^^^^^^
2208     // ^^^^^^^^^^^^^^^
2209
2210        include!("a.rs")
2211     // ^^^^^^^^^^^^^^^^
2212    }
2213}
2214
2215//- /a.rs
2216{
2217    return;
2218}
2219"#,
2220        )
2221    }
2222
2223    #[test]
2224    fn nested_match() {
2225        check(
2226            r#"
2227fn main() {
2228    match$0 0 {
2229 // ^^^^^
2230        0 => match 1 {
2231            1 => 2,
2232              // ^
2233            _ => 3,
2234              // ^
2235        },
2236        _ => 4,
2237          // ^
2238    }
2239}
2240"#,
2241        )
2242    }
2243
2244    #[test]
2245    fn single_arm_highlight() {
2246        check(
2247            r#"
2248fn main() {
2249    match 0 {
2250        0 =>$0 {
2251       // ^^
2252            let x = 1;
2253            x
2254         // ^
2255        }
2256        _ => 2,
2257    }
2258}
2259"#,
2260        )
2261    }
2262
2263    #[test]
2264    fn no_branches_when_disabled() {
2265        let config = HighlightRelatedConfig { branch_exit_points: false, ..ENABLED_CONFIG };
2266        check_with_config(
2267            r#"
2268fn main() {
2269    match$0 0 {
2270        0 => 1,
2271        _ => 2,
2272    }
2273}
2274"#,
2275            config,
2276        );
2277    }
2278
2279    #[test]
2280    fn asm() {
2281        check(
2282            r#"
2283//- minicore: asm
2284#[inline]
2285pub unsafe fn bootstrap() -> ! {
2286    builtin#asm(
2287        "blabla",
2288        "mrs {tmp}, CONTROL",
2289           // ^^^ read
2290        "blabla",
2291        "bics {tmp}, {spsel}",
2292            // ^^^ read
2293        "blabla",
2294        "msr CONTROL, {tmp}",
2295                    // ^^^ read
2296        "blabla",
2297        tmp$0 = inout(reg) 0,
2298     // ^^^
2299        aaa = in(reg) 2,
2300        aaa = in(reg) msp,
2301        aaa = in(reg) rv,
2302        options(noreturn, nomem, nostack),
2303    );
2304}
2305"#,
2306        )
2307    }
2308
2309    #[test]
2310    fn complex_arms_highlight() {
2311        check(
2312            r#"
2313fn calculate(n: i32) -> i32 { n * 2 }
2314
2315fn main() {
2316    match$0 Some(1) {
2317 // ^^^^^
2318        Some(x) => match x {
2319            0 => { let y = x; y },
2320                           // ^
2321            1 => calculate(x),
2322               //^^^^^^^^^^^^
2323            _ => (|| 6)(),
2324              // ^^^^^^^^
2325        },
2326        None => loop {
2327            break 5;
2328         // ^^^^^^^
2329        },
2330    }
2331}
2332"#,
2333        )
2334    }
2335
2336    #[test]
2337    fn match_in_macro_highlight() {
2338        check(
2339            r#"
2340macro_rules! M {
2341    ($e:expr) => { $e };
2342}
2343
2344fn main() {
2345    M!{
2346        match$0 Some(1) {
2347     // ^^^^^
2348            Some(x) => x,
2349                    // ^
2350            None => 0,
2351                 // ^
2352        }
2353    }
2354}
2355"#,
2356        )
2357    }
2358
2359    #[test]
2360    fn match_in_macro_highlight_2() {
2361        check(
2362            r#"
2363macro_rules! match_ast {
2364    (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
2365
2366    (match ($node:expr) {
2367        $( $( $path:ident )::+ ($it:pat) => $res:expr, )*
2368        _ => $catch_all:expr $(,)?
2369    }) => {{
2370        $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
2371        { $catch_all }
2372    }};
2373}
2374
2375fn main() {
2376    match_ast! {
2377        match$0 Some(1) {
2378            Some(x) => x,
2379        }
2380    }
2381}
2382            "#,
2383        );
2384    }
2385
2386    #[test]
2387    fn nested_if_else() {
2388        check(
2389            r#"
2390fn main() {
2391    if$0 true {
2392 // ^^
2393        if false {
2394            1
2395         // ^
2396        } else {
2397            2
2398         // ^
2399        }
2400    } else {
2401        3
2402     // ^
2403    }
2404}
2405"#,
2406        )
2407    }
2408
2409    #[test]
2410    fn if_else_if_highlight() {
2411        check(
2412            r#"
2413fn main() {
2414    if$0 true {
2415 // ^^
2416        1
2417     // ^
2418    } else if false {
2419        // ^^
2420        2
2421     // ^
2422    } else {
2423        3
2424     // ^
2425    }
2426}
2427"#,
2428        )
2429    }
2430
2431    #[test]
2432    fn complex_if_branches() {
2433        check(
2434            r#"
2435fn calculate(n: i32) -> i32 { n * 2 }
2436
2437fn main() {
2438    if$0 true {
2439 // ^^
2440        let x = 5;
2441        calculate(x)
2442     // ^^^^^^^^^^^^
2443    } else if false {
2444        // ^^
2445        (|| 10)()
2446     // ^^^^^^^^^
2447    } else {
2448        loop {
2449            break 15;
2450         // ^^^^^^^^
2451        }
2452    }
2453}
2454"#,
2455        )
2456    }
2457
2458    #[test]
2459    fn if_in_macro_highlight() {
2460        check(
2461            r#"
2462macro_rules! M {
2463    ($e:expr) => { $e };
2464}
2465
2466fn main() {
2467    M!{
2468        if$0 true {
2469     // ^^
2470            5
2471         // ^
2472        } else {
2473            10
2474         // ^^
2475        }
2476    }
2477}
2478"#,
2479        )
2480    }
2481
2482    #[test]
2483    fn match_in_macro() {
2484        // We should not highlight the outer `match` expression.
2485        check(
2486            r#"
2487macro_rules! M {
2488    (match) => { 1 };
2489}
2490
2491fn main() {
2492    match Some(1) {
2493        Some(x) => x,
2494        None => {
2495            M!(match$0)
2496        }
2497    }
2498}
2499            "#,
2500        )
2501    }
2502
2503    #[test]
2504    fn labeled_block_tail_expr() {
2505        check(
2506            r#"
2507fn foo() {
2508    'a: {
2509 // ^^^
2510        if true { break$0 'a 0; }
2511               // ^^^^^^^^
2512        5
2513     // ^
2514    }
2515}
2516"#,
2517        );
2518    }
2519
2520    #[test]
2521    fn labeled_block_tail_expr_2() {
2522        check(
2523            r#"
2524fn foo() {
2525    let _ = 'b$0lk: {
2526         // ^^^^
2527        let x = 1;
2528        if true { break 'blk 42; }
2529                     // ^^^^
2530        if false { break 'blk 24; }
2531                      // ^^^^
2532        100
2533     // ^^^
2534    };
2535}
2536"#,
2537        );
2538    }
2539
2540    #[test]
2541    fn different_unsafe_block() {
2542        check(
2543            r#"
2544fn main() {
2545    unsafe$0 {
2546 // ^^^^^^
2547        *(0 as *const u8)
2548     // ^^^^^^^^^^^^^^^^^
2549    };
2550    unsafe { *(1 as *const u8) };
2551    unsafe { *(2 as *const u8) };
2552}
2553        "#,
2554        );
2555    }
2556
2557    #[test]
2558    fn async_fn_param() {
2559        check(
2560            r#"
2561async fn get_double_async(num$0: u32) -> u32 {
2562                       // ^^^
2563    num
2564 // ^^^ read
2565}
2566        "#,
2567        );
2568        check(
2569            r#"
2570async fn get_double_async((num$0,): (u32,)) -> u32 {
2571                        // ^^^
2572    num
2573 // ^^^ read
2574}
2575        "#,
2576        );
2577    }
2578}