src/tools/rust-analyzer/crates/ide/src/highlight_related.rs RUST 2,553 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,553.
1use std::iter;23use 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, find_loops, for_each_tail_expr, full_path_of_name_ref,11        is_closure_or_blk_with_modif, 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};2122use crate::{NavigationTarget, TryToNav, goto_definition, navigation_target::ToNav};2324#[derive(PartialEq, Eq, Hash)]25pub struct HighlightedRange {26    pub range: TextRange,27    // FIXME: This needs to be more precise. Reference category makes sense only28    // for references, but we also have defs. And things like exit points are29    // neither.30    pub category: ReferenceCategory,31}3233#[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}4243type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;4445// Feature: Highlight Related46//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 file50//      * 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 scope51// 1. if on an `async` or `await` token, highlights all yield points for that async context52// 1. if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context53// 1. if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context54// 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();6566    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 hir75    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}105106fn 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                    .references126                    .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::WRITE135                } else {136                    ReferenceCategory::empty()137                };138                local139                    .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}150151fn 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 = defs172        .iter()173        .filter_map(|&d| {174            d.usages(sema)175                .in_scope(&SearchScope::single_file(file_id))176                .include_self_refs()177                .all()178                .references179                .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 usages186        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) => it197                            .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                                .references226                                .remove(&file_id)227                        })228                        .flatten()229                        .map(|FileReference { category, range, .. }| HighlightedRange {230                            range,231                            category,232                        }),233                );234            }235        }236237        // highlight the tail expr of the labelled block238        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        }253254        // highlight the defs themselves255        match def {256            Definition::Local(local) => {257                let category = if local.is_mut(sema.db) {258                    ReferenceCategory::WRITE259                } else {260                    ReferenceCategory::empty()261                };262                local263                    .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::WRITE290                        } 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    }302303    res.extend(usages);304    if res.is_empty() { None } else { Some(res.into_iter().collect()) }305}306307pub(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();312313    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    };319320    let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {321        let Some(tail) = tail else {322            return;323        };324325        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    };331332    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);339340                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);353354                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());361362                    let if_kw_range = cur_if.if_token().map(|token| token.text_range());363                    push_to_highlights(file_id, if_kw_range, &mut highlights);364365                    if let Some(then_block) = cur_if.then_branch() {366                        push_tail_expr(Some(then_block.into()), &mut highlights);367                    }368369                    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    }382383    highlights384        .into_iter()385        .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))386        .collect()387}388389fn 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();395396    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    };402403    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    }408409    WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {410        let file_id = sema.hir_file_for(expr.syntax());411412        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        };421422        push_to_highlights(file_id, range);423    });424425    // 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());431432            let range = match &expr {433                ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()),434                _ => None,435            };436437            push_to_highlights(file_id, range);438        });439440    let tail = match body {441        ast::Expr::BlockExpr(b) => b.tail_expr(),442        e => Some(e),443    };444445    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) => b450                    .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}459460// 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 { try_token: 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    }486487    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()488}489490pub(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();502503        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        };509510        let label_lt = label.as_ref().and_then(|it| it.lifetime());511512        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_token517                .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        }521522        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());526527                // 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                };537538                if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) {539                    return;540                }541542                let text_range = cover_range(543                    token.map(|it| it.text_range()),544                    token_lt.map(|it| it.syntax().text_range()),545                );546547                push_to_highlights(file_id, text_range);548            });549550        if matches!(expr, ast::Expr::BlockExpr(_)) {551            for_each_tail_expr(&expr, &mut |tail| {552                if matches!(tail, ast::Expr::BreakExpr(_)) {553                    return;554                }555556                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        }561562        Some(highlights)563    }564565    let Some(loops) = find_loops(sema, &token) else {566        return FxHashMap::default();567    };568569    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    }581582    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()583}584585pub(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();595596        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        };602603        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()));606607        let Some(body) = body else {608            return Some(highlights);609        };610611        WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {612            let file_id = sema.hir_file_for(expr.syntax());613614            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());620621            push_to_highlights(file_id, text_range);622        });623624        Some(highlights)625    }626627    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                    };636637                    // Async blocks act similar to closures. So we want to638                    // highlight their exit points too, but only if we are on639                    // 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                    }648649                    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    }657658    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()659}660661fn 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}669670fn 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}677678fn 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}685686fn 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}694695/// Preorder walk all the expression's child expressions.696/// For macro calls, the callback will be called on the expanded expressions after697/// 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}703704impl<'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    }708709    fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self {710        Self { check_ctx, ..*self }711    }712713    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());718719                    if Self::should_change_depth(&expr) {720                        self.depth += 1;721                    }722723                    if let ast::Expr::MacroExpr(expr) = expr724                        && 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            false746        })747    }748749    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        }753754        for stmt in expanded.statements() {755            if let ast::Stmt::ExprStmt(stmt) = stmt756                && let Some(expr) = stmt.expr()757            {758                self.walk(&expr, cb);759            }760        }761    }762763    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    }770771    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}782783pub(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();793794        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        };800801        // highlight unsafe keyword itself802        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()));804805        // highlight unsafe operations806        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        }812813        Some(highlights)814    }815816    hl(sema, &token, token.parent().and_then(ast::BlockExpr::cast)).unwrap_or_default()817}818819#[cfg(test)]820mod tests {821    use itertools::Itertools;822823    use crate::fixture;824825    use super::*;826827    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    };835836    #[track_caller]837    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {838        check_with_config(ra_fixture, ENABLED_CONFIG);839    }840841    #[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);847848        let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();849850        let mut expected =851            annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();852853        let mut actual: Vec<(TextRange, String)> = hls854            .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());864865        assert_eq!(expected, actual);866    }867868    #[test]869    fn test_hl_unsafe_block() {870        check(871            r#"872fn foo() {873    unsafe fn this_is_unsafe_function() {}874875    unsa$0fe {876  //^^^^^^877        let raw_ptr = &42 as *const i32;878        let val = *raw_ptr;879                //^^^^^^^^880881        let mut_ptr = &mut 5 as *mut i32;882        *mut_ptr = 10;883      //^^^^^^^^884885        this_is_unsafe_function();886      //^^^^^^^^^^^^^^^^^^^^^^^^^887    }888889}890"#,891        );892    }893894    #[test]895    fn test_hl_tuple_fields() {896        check(897            r#"898struct Tuple(u32, u32);899900fn foo(t: Tuple) {901    t.0$0;902   // ^ read903    t.0;904   // ^ read905}906"#,907        );908    }909910    #[test]911    fn test_hl_module() {912        check(913            r#"914//- /lib.rs915mod foo$0;916 // ^^^917//- /foo.rs918struct Foo;919"#,920        );921    }922923    #[test]924    fn test_hl_self_in_crate_root() {925        check(926            r#"927use crate$0;928  //^^^^^ import929use self;930  //^^^^ import931mod __ {932    use super;933      //^^^^^ import934}935"#,936        );937        check(938            r#"939//- /main.rs crate:main deps:lib940use lib$0;941  //^^^ import942//- /lib.rs crate:lib943"#,944        );945    }946947    #[test]948    fn test_hl_self_in_module() {949        check(950            r#"951//- /lib.rs952mod foo;953//- /foo.rs954use self$0;955 // ^^^^ import956"#,957        );958    }959960    #[test]961    fn test_hl_local() {962        check(963            r#"964fn foo() {965    let mut bar = 3;966         // ^^^ write967    bar$0;968 // ^^^ read969}970"#,971        );972    }973974    #[test]975    fn test_hl_local_in_attr() {976        check(977            r#"978//- proc_macros: identity979#[proc_macros::identity]980fn foo() {981    let mut bar = 3;982         // ^^^ write983    bar$0;984 // ^^^ read985}986"#,987        );988    }989990    #[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}10001001foo!(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}10181019foo!(bar);1020  // ^^^1021fn foo() {1022    let bar: bar$0 = bar();1023          // ^^^1024}1025"#,1026        );1027    }10281029    #[test]1030    fn test_hl_yield_points() {1031        check(1032            r#"1033pub async fn foo() {1034 // ^^^^^1035    let x = foo()1036        .await$01037      // ^^^^^1038        .await;1039      // ^^^^^1040    || { 0.await };1041    (async { 0.await }).await1042                     // ^^^^^1043}1044"#,1045        );1046    }10471048    #[test]1049    fn test_hl_yield_points2() {1050        check(1051            r#"1052pub async$0 fn foo() {1053 // ^^^^^1054    let x = foo()1055        .await1056      // ^^^^^1057        .await;1058      // ^^^^^1059    || { 0.await };1060    (async { 0.await }).await1061                     // ^^^^^1062}1063"#,1064        );1065    }10661067    #[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       01081    // ^1082    };1083}1084"#,1085        );1086    }10871088    #[test]1089    fn test_hl_let_else_yield_points() {1090        check(1091            r#"1092pub async fn foo() {1093 // ^^^^^1094    let x = foo()1095        .await$01096      // ^^^^^1097        .await;1098      // ^^^^^1099    || { 0.await };1100    let Some(_) = None else {1101        foo().await1102           // ^^^^^1103    };1104    (async { 0.await }).await1105                     // ^^^^^1106}1107"#,1108        );1109    }11101111    #[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.await1120        }1121        0.await$01122       // ^^^^^1123    }1124    0.await1125}1126"#,1127        );1128    }11291130    #[test]1131    fn test_hl_yield_nested_async_blocks() {1132        check(1133            r#"1134async fn foo() {1135    (async {1136  // ^^^^^1137        (async { 0.await }).await$01138                         // ^^^^^1139    }).await;1140}1141"#,1142        );1143    }11441145    #[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    }11551156    0?;1157  // ^1158    0xDEAD_BEEF1159 // ^^^^^^^^^^^1160}1161"#,1162        );1163    }11641165    #[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    }11751176    0?;1177  // ^1178    0xDEAD_BEEF1179 // ^^^^^^^^^^^1180}1181"#,1182        );1183    }11841185    #[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    }11951196    0?;1197  // ^1198    0xDEAD_BEEF1199 // ^^^^^^^^^^^1200}1201"#,1202        );1203    }12041205    #[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    };12151216    0?;1217  // ^1218    0xDEAD_BEEF1219 // ^^^^^^^^^^^1220}1221"#,1222        );1223    }12241225    #[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    }12341235    0?;12361237    foo$0()1238 // ^^^1239}1240"#,1241        );1242    }12431244    #[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 // ^^^^^^^^12631264    Never.never();1265 // ^^^^^^^^^^^^^12661267    01268 // ^1269}1270"#,1271        );1272    }12731274    #[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            51285         // ^1286        }1287    } else if false {1288        01289     // ^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    }13151316    #[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        01331     // ^1332    }1333}1334"#,1335        );1336    }13371338    #[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    }13551356    #[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    }13831384    #[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    }14101411    #[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    }14381439    #[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    }14731474    #[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    }15081509    #[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    }15471548    #[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    }15751576    #[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    }16031604    #[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    }16181619    #[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    }16351636    #[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           //^^^^^ read1646}1647"#,1648        );1649    }16501651    #[test]1652    fn test_hl_disabled_ref_local() {1653        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16541655        check_with_config(1656            r#"1657fn foo() {1658    let x$0 = 5;1659    let y = x * 2;1660}1661"#,1662            config,1663        );1664    }16651666    #[test]1667    fn test_hl_disabled_ref_local_preserved_break() {1668        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16691670        check_with_config(1671            r#"1672fn foo() {1673    let x$0 = 5;1674    let y = x * 2;16751676    loop {1677        break;1678    }1679}1680"#,1681            config.clone(),1682        );16831684        check_with_config(1685            r#"1686fn foo() {1687    let x = 5;1688    let y = x * 2;16891690    loop$0 {1691//  ^^^^1692        break;1693//      ^^^^^1694    }1695}1696"#,1697            config,1698        );1699    }17001701    #[test]1702    fn test_hl_disabled_ref_local_preserved_yield() {1703        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17041705        check_with_config(1706            r#"1707async fn foo() {1708    let x$0 = 5;1709    let y = x * 2;17101711    0.await;1712}1713"#,1714            config.clone(),1715        );17161717        check_with_config(1718            r#"1719    async fn foo() {1720//  ^^^^^1721        let x = 5;1722        let y = x * 2;17231724        0.await$0;1725//        ^^^^^1726}1727"#,1728            config,1729        );1730    }17311732    #[test]1733    fn test_hl_disabled_ref_local_preserved_exit() {1734        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17351736        check_with_config(1737            r#"1738fn foo() -> i32 {1739    let x$0 = 5;1740    let y = x * 2;17411742    if true {1743        return y;1744    }17451746    0?1747}1748"#,1749            config.clone(),1750        );17511752        check_with_config(1753            r#"1754  fn foo() ->$0 i32 {1755//^^1756    let x = 5;1757    let y = x * 2;17581759    if true {1760        return y;1761//      ^^^^^^1762    }17631764    0?1765//   ^1766"#,1767            config,1768        );1769    }17701771    #[test]1772    fn test_hl_disabled_break() {1773        let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };17741775        check_with_config(1776            r#"1777fn foo() {1778    loop {1779        break$0;1780    }1781}1782"#,1783            config,1784        );1785    }17861787    #[test]1788    fn test_hl_disabled_yield() {1789        let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };17901791        check_with_config(1792            r#"1793async$0 fn foo() {1794    0.await;1795}1796"#,1797            config,1798        );1799    }18001801    #[test]1802    fn test_hl_disabled_exit() {1803        let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };18041805        check_with_config(1806            r#"1807fn foo() ->$0 i32 {1808    if true {1809        return -1;1810    }18111812    421813}"#,1814            config,1815        );1816    }18171818    #[test]1819    fn test_hl_multi_local() {1820        check(1821            r#"1822fn foo((1823    foo$01824  //^^^1825    | foo1826    //^^^1827    | foo1828    //^^^1829): ()) {1830    foo;1831  //^^^read1832    let foo;1833}1834"#,1835        );1836        check(1837            r#"1838fn foo((1839    foo1840  //^^^1841    | foo$01842    //^^^1843    | foo1844    //^^^1845): ()) {1846    foo;1847  //^^^read1848    let foo;1849}1850"#,1851        );1852        check(1853            r#"1854fn foo((1855    foo1856  //^^^1857    | foo1858    //^^^1859    | foo1860    //^^^1861): ()) {1862    foo$0;1863  //^^^read1864    let foo;1865}1866"#,1867        );1868    }18691870    #[test]1871    fn test_hl_trait_impl_methods() {1872        check(1873            r#"1874trait Trait {1875    fn func$0(self) {}1876     //^^^^1877}18781879impl Trait for () {1880    fn func(self) {}1881     //^^^^1882}18831884fn main() {1885    <()>::func(());1886        //^^^^1887    ().func();1888     //^^^^1889}1890"#,1891        );1892        check(1893            r#"1894trait Trait {1895    fn func(self) {}1896}18971898impl Trait for () {1899    fn func$0(self) {}1900     //^^^^1901}19021903fn main() {1904    <()>::func(());1905        //^^^^1906    ().func();1907     //^^^^1908}1909"#,1910        );1911        check(1912            r#"1913trait Trait {1914    fn func(self) {}1915}19161917impl Trait for () {1918    fn func(self) {}1919     //^^^^1920}19211922fn main() {1923    <()>::func(());1924        //^^^^1925    ().func$0();1926     //^^^^1927}1928"#,1929        );1930    }19311932    #[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    }19471948    #[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    //          ^ read1957}1958"#,1959        );1960    }19611962    #[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    //               ^ read1971}1972"#,1973        );1974    }19751976    #[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            //^^^^^^

Code quality findings 33

Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// highlight unsafe keyword itself
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// highlight unsafe operations
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
unsafe fn this_is_unsafe_function() {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
unsafe {
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
pub unsafe fn bootstrap() -> ! {
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
unsafe { *(1 as *const u8) };
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
unsafe { *(2 as *const u8) };
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax()));
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match token.kind() {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let range = match &expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let range = match &expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let tail = match body {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
ast::BlockExpr(blk) => match blk.modifier() {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (token, token_lt) = match expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let new_map = match &expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let text_range = match expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match (expanded.value) {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match expr {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match expr {
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use super::*;
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info safety raw-pointer
let raw_ptr = &42 as *const i32;
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info safety raw-pointer
let mut_ptr = &mut 5 as *mut i32;
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
config.clone(),
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
println!("{}", i);
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
println!("{}", i);
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
0 => match 1 {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match 0 {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
Some(x) => match x {
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info safety raw-pointer
*(0 as *const u8)
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info safety raw-pointer
unsafe { *(1 as *const u8) };
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info safety raw-pointer
unsafe { *(2 as *const u8) };

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.