ide/
highlight_related.rs

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