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