src/tools/rust-analyzer/crates/ide/src/highlight_related.rs RUST 2,579 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,579.
1use std::iter;23use hir::{EditionedFileId, FilePosition, FileRange, HirFileId, InFile, Semantics};4use ide_db::{5    FxHashMap, FxHashSet, RootDatabase,6    base_db::SourceDatabase,7    defs::{Definition, IdentClass},8    helpers::pick_best_token,9    search::{FileReference, ReferenceCategory, SearchScope},10    syntax_helpers::node_ext::{11        eq_label_lt, find_loops, for_each_tail_expr, full_path_of_name_ref,12        is_closure_or_blk_with_modif, preorder_expr_with_ctx_checker,13    },14};15use syntax::{16    AstNode,17    SyntaxKind::{self, IDENT, INT_NUMBER},18    SyntaxToken, T, TextRange, WalkEvent,19    ast::{self, HasLoopBody},20    match_ast,21};2223use crate::{NavigationTarget, TryToNav, goto_definition, navigation_target::ToNav};2425#[derive(PartialEq, Eq, Hash)]26pub struct HighlightedRange {27    pub range: TextRange,28    // FIXME: This needs to be more precise. Reference category makes sense only29    // for references, but we also have defs. And things like exit points are30    // neither.31    pub category: ReferenceCategory,32}3334#[derive(Default, Clone)]35pub struct HighlightRelatedConfig {36    pub references: bool,37    pub exit_points: bool,38    pub break_points: bool,39    pub closure_captures: bool,40    pub yield_points: bool,41    pub branch_exit_points: bool,42}4344type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;4546// Feature: Highlight Related47//48// Highlights constructs related to the thing under the cursor:49//50// 1. if on an identifier, highlights all references to that identifier in the current file51//      * 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 scope52// 1. if on an `async` or `await` token, highlights all yield points for that async context53// 1. if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context54// 1. if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context55// 1. if on a `move` or `|` token that belongs to a closure, highlights all captures of the closure.56//57// Note: `?`, `|` and `->` do not currently trigger this behavior in the VSCode editor.58pub(crate) fn highlight_related(59    sema: &Semantics<'_, RootDatabase>,60    config: HighlightRelatedConfig,61    ide_db::FilePosition { offset, file_id }: ide_db::FilePosition,62) -> Option<Vec<HighlightedRange>> {63    let _p = tracing::info_span!("highlight_related").entered();64    let file_id = sema.attach_first_edition(file_id);65    let syntax = sema.parse(file_id).syntax().clone();6667    let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {68        T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`69        T![->] | T![=>] => 4,70        kind if kind.is_keyword(file_id.edition(sema.db)) => 3,71        IDENT | INT_NUMBER => 2,72        T![|] => 1,73        _ => 0,74    })?;75    // most if not all of these should be re-implemented with information seeded from hir76    match token.kind() {77        T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {78            highlight_exit_points(sema, token).remove(&file_id)79        }80        T![fn] | T![return] | T![->] if config.exit_points => {81            highlight_exit_points(sema, token).remove(&file_id)82        }83        T![match] | T![=>] | T![if] if config.branch_exit_points => {84            highlight_branch_exit_points(sema, token).remove(&file_id)85        }86        T![await] | T![async] if config.yield_points => {87            highlight_yield_points(sema, token).remove(&file_id)88        }89        T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {90            highlight_break_points(sema, token).remove(&file_id)91        }92        T![break] | T![loop] | T![while] | T![continue] if config.break_points => {93            highlight_break_points(sema, token).remove(&file_id)94        }95        T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {96            highlight_unsafe_points(sema, token).remove(&file_id)97        }98        T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),99        T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),100        _ if config.references => {101            highlight_references(sema, token, FilePosition { file_id, offset })102        }103        _ => None,104    }105}106107fn highlight_closure_captures(108    sema: &Semantics<'_, RootDatabase>,109    token: SyntaxToken,110    file_id: EditionedFileId,111) -> Option<Vec<HighlightedRange>> {112    let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;113    let search_range = closure.body()?.syntax().text_range();114    let ty = &sema.type_of_expr(&closure.into())?.original;115    let c = ty.as_closure()?;116    Some(117        c.captured_items(sema.db)118            .into_iter()119            .map(|capture| capture.local())120            .flat_map(|local| {121                let usages = Definition::Local(local)122                    .usages(sema)123                    .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))124                    .include_self_refs()125                    .all()126                    .references127                    .remove(&file_id)128                    .into_iter()129                    .flatten()130                    .map(|FileReference { category, range, .. }| HighlightedRange {131                        range,132                        category,133                    });134                let category = if local.is_mut(sema.db) {135                    ReferenceCategory::WRITE136                } else {137                    ReferenceCategory::empty()138                };139                local140                    .sources(sema.db)141                    .into_iter()142                    .flat_map(|x| x.to_nav(sema.db))143                    .filter(|decl| decl.file_id == file_id.file_id(sema.db))144                    .filter_map(|decl| decl.focus_range)145                    .map(move |range| HighlightedRange { range, category })146                    .chain(usages)147            })148            .collect(),149    )150}151152fn highlight_references(153    sema: &Semantics<'_, RootDatabase>,154    token: SyntaxToken,155    FilePosition { file_id, offset }: FilePosition,156) -> Option<Vec<HighlightedRange>> {157    let defs = if let Some((range, _, _, resolution)) =158        sema.check_for_format_args_template(token.clone(), offset)159    {160        match resolution.map(Definition::from) {161            Some(def) => iter::once(def).collect(),162            None => {163                return Some(vec![HighlightedRange {164                    range,165                    category: ReferenceCategory::empty(),166                }]);167            }168        }169    } else {170        find_defs(sema, token.clone())171    };172    let usages = defs173        .iter()174        .filter_map(|&d| {175            d.usages(sema)176                .in_scope(&SearchScope::single_file(file_id))177                .include_self_refs()178                .all()179                .references180                .remove(&file_id)181        })182        .flatten()183        .map(|FileReference { category, range, .. }| HighlightedRange { range, category });184    let mut res = FxHashSet::default();185    for &def in &defs {186        // highlight trait usages187        if let Definition::Trait(t) = def {188            let trait_item_use_scope = (|| {189                let name_ref = token.parent().and_then(ast::NameRef::cast)?;190                let path = full_path_of_name_ref(&name_ref)?;191                let parent = path.syntax().parent()?;192                match_ast! {193                    match parent {194                        ast::UseTree(it) => it.syntax().ancestors().find(|it| {195                            ast::SourceFile::can_cast(it.kind()) || ast::Module::can_cast(it.kind())196                        }).zip(Some(true)),197                        ast::PathType(it) => it198                            .syntax()199                            .ancestors()200                            .nth(2)201                            .and_then(ast::TypeBoundList::cast)?202                            .syntax()203                            .parent()204                            .filter(|it| ast::WhereClause::can_cast(it.kind()) || ast::TypeParam::can_cast(it.kind()))?205                            .ancestors()206                            .find(|it| {207                                ast::Item::can_cast(it.kind())208                            }).zip(Some(false)),209                        _ => None,210                    }211                }212            })();213            if let Some((trait_item_use_scope, use_tree)) = trait_item_use_scope {214                res.extend(215                    if use_tree { t.items(sema.db) } else { t.items_with_supertraits(sema.db) }216                        .into_iter()217                        .filter_map(|item| {218                            Definition::from(item)219                                .usages(sema)220                                .set_scope(Some(&SearchScope::file_range(FileRange {221                                    file_id,222                                    range: trait_item_use_scope.text_range(),223                                })))224                                .include_self_refs()225                                .all()226                                .references227                                .remove(&file_id)228                        })229                        .flatten()230                        .map(|FileReference { category, range, .. }| HighlightedRange {231                            range,232                            category,233                        }),234                );235            }236        }237238        // highlight the tail expr of the labelled block239        if matches!(def, Definition::Label(_)) {240            let label = token.parent_ancestors().nth(1).and_then(ast::Label::cast);241            if let Some(block) =242                label.and_then(|label| label.syntax().parent()).and_then(ast::BlockExpr::cast)243            {244                for_each_tail_expr(&block.into(), &mut |tail| {245                    if !matches!(tail, ast::Expr::BreakExpr(_)) {246                        res.insert(HighlightedRange {247                            range: tail.syntax().text_range(),248                            category: ReferenceCategory::empty(),249                        });250                    }251                });252            }253        }254255        // highlight the defs themselves256        match def {257            Definition::Local(local) => {258                let category = if local.is_mut(sema.db) {259                    ReferenceCategory::WRITE260                } else {261                    ReferenceCategory::empty()262                };263                local264                    .sources(sema.db)265                    .into_iter()266                    .flat_map(|x| x.to_nav(sema.db))267                    .filter(|decl| decl.file_id == file_id.file_id(sema.db))268                    .filter_map(|decl| decl.focus_range)269                    .map(|range| HighlightedRange { range, category })270                    .for_each(|x| {271                        res.insert(x);272                    });273            }274            def => {275                let navs = match def {276                    Definition::Module(module) => {277                        NavigationTarget::from_module_to_decl(sema.db, module)278                    }279                    def => match def.try_to_nav(sema) {280                        Some(it) => it,281                        None => continue,282                    },283                };284                for nav in navs {285                    if nav.file_id != file_id.file_id(sema.db) {286                        continue;287                    }288                    let hl_range = nav.focus_range.map(|range| {289                        let category = if matches!(def, Definition::Local(l) if l.is_mut(sema.db)) {290                            ReferenceCategory::WRITE291                        } else {292                            ReferenceCategory::empty()293                        };294                        HighlightedRange { range, category }295                    });296                    if let Some(hl_range) = hl_range {297                        res.insert(hl_range);298                    }299                }300            }301        }302    }303304    res.extend(usages);305    if res.is_empty() { None } else { Some(res.into_iter().collect()) }306}307308pub(crate) fn highlight_branch_exit_points(309    sema: &Semantics<'_, RootDatabase>,310    token: SyntaxToken,311) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {312    let mut highlights: HighlightMap = FxHashMap::default();313314    let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {315        if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {316            let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };317            highlights.entry(file_id).or_default().insert(hrange);318        }319    };320321    let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {322        let Some(tail) = tail else {323            return;324        };325326        for_each_tail_expr(&tail, &mut |tail| {327            let file_id = sema.hir_file_for(tail.syntax());328            let range = tail.syntax().text_range();329            push_to_highlights(file_id, Some(range), highlights);330        });331    };332333    let nodes = goto_definition::find_branch_root(sema, &token).into_iter();334    match token.kind() {335        T![match] => {336            for match_expr in nodes.filter_map(ast::MatchExpr::cast) {337                let file_id = sema.hir_file_for(match_expr.syntax());338                let range = match_expr.match_token().map(|token| token.text_range());339                push_to_highlights(file_id, range, &mut highlights);340341                let Some(arm_list) = match_expr.match_arm_list() else {342                    continue;343                };344                for arm in arm_list.arms() {345                    push_tail_expr(arm.expr(), &mut highlights);346                }347            }348        }349        T![=>] => {350            for arm in nodes.filter_map(ast::MatchArm::cast) {351                let file_id = sema.hir_file_for(arm.syntax());352                let range = arm.fat_arrow_token().map(|token| token.text_range());353                push_to_highlights(file_id, range, &mut highlights);354355                push_tail_expr(arm.expr(), &mut highlights);356            }357        }358        T![if] => {359            for mut if_to_process in nodes.map(ast::IfExpr::cast) {360                while let Some(cur_if) = if_to_process.take() {361                    let file_id = sema.hir_file_for(cur_if.syntax());362363                    let if_kw_range = cur_if.if_token().map(|token| token.text_range());364                    push_to_highlights(file_id, if_kw_range, &mut highlights);365366                    if let Some(then_block) = cur_if.then_branch() {367                        push_tail_expr(Some(then_block.into()), &mut highlights);368                    }369370                    match cur_if.else_branch() {371                        Some(ast::ElseBranch::Block(else_block)) => {372                            push_tail_expr(Some(else_block.into()), &mut highlights);373                            if_to_process = None;374                        }375                        Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),376                        None => if_to_process = None,377                    }378                }379            }380        }381        _ => {}382    }383384    highlights385        .into_iter()386        .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))387        .collect()388}389390fn hl_exit_points(391    sema: &Semantics<'_, RootDatabase>,392    def_token: Option<SyntaxToken>,393    body: ast::Expr,394) -> Option<HighlightMap> {395    let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();396397    let mut push_to_highlights = |file_id, range| {398        if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {399            let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };400            highlights.entry(file_id).or_default().insert(hrange);401        }402    };403404    if let Some(tok) = def_token {405        let file_id = sema.hir_file_for(&tok.parent()?);406        let range = Some(tok.text_range());407        push_to_highlights(file_id, range);408    }409410    WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {411        let file_id = sema.hir_file_for(expr.syntax());412413        let range = match &expr {414            ast::Expr::TryExpr(try_) => try_.question_mark_token().map(|token| token.text_range()),415            ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_)416                if sema.type_of_expr(&expr).is_some_and(|ty| ty.original.is_never()) =>417            {418                Some(expr.syntax().text_range())419            }420            _ => None,421        };422423        push_to_highlights(file_id, range);424    });425426    // We should handle `return` separately, because when it is used in a `try` block,427    // it will exit the outside function instead of the block itself.428    WalkExpandedExprCtx::new(sema)429        .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)430        .walk(&body, &mut |_, expr| {431            let file_id = sema.hir_file_for(expr.syntax());432433            let range = match &expr {434                ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()),435                _ => None,436            };437438            push_to_highlights(file_id, range);439        });440441    let tail = match body {442        ast::Expr::BlockExpr(b) => b.tail_expr(),443        e => Some(e),444    };445446    if let Some(tail) = tail {447        for_each_tail_expr(&tail, &mut |tail| {448            let file_id = sema.hir_file_for(tail.syntax());449            let range = match tail {450                ast::Expr::BreakExpr(b) => b451                    .break_token()452                    .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),453                _ => tail.syntax().text_range(),454            };455            push_to_highlights(file_id, Some(range));456        });457    }458    Some(highlights)459}460461// If `file_id` is None,462pub(crate) fn highlight_exit_points(463    sema: &Semantics<'_, RootDatabase>,464    token: SyntaxToken,465) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {466    let mut res = FxHashMap::default();467    for def in goto_definition::find_fn_or_blocks(sema, &token) {468        let new_map = match_ast! {469            match def {470                ast::Fn(fn_) => fn_.body().and_then(|body| hl_exit_points(sema, fn_.fn_token(), body.into())),471                ast::ClosureExpr(closure) => {472                    let pipe_tok = closure.param_list().and_then(|p| p.pipe_token());473                    closure.body().and_then(|body| hl_exit_points(sema, pipe_tok, body))474                },475                ast::BlockExpr(blk) => match blk.modifier() {476                    Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()),477                    Some(ast::BlockModifier::Try { try_token: t, .. }) if token.kind() != T![return] => {478                        hl_exit_points(sema, Some(t), blk.into())479                    },480                    _ => continue,481                },482                _ => continue,483            }484        };485        merge_map(&mut res, new_map);486    }487488    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()489}490491pub(crate) fn highlight_break_points(492    sema: &Semantics<'_, RootDatabase>,493    token: SyntaxToken,494) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {495    pub(crate) fn hl(496        sema: &Semantics<'_, RootDatabase>,497        cursor_token_kind: SyntaxKind,498        loop_token: Option<SyntaxToken>,499        label: Option<ast::Label>,500        expr: ast::Expr,501    ) -> Option<HighlightMap> {502        let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();503504        let mut push_to_highlights = |file_id, range| {505            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {506                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };507                highlights.entry(file_id).or_default().insert(hrange);508            }509        };510511        let label_lt = label.as_ref().and_then(|it| it.lifetime());512513        if let Some(range) = cover_range(514            loop_token.as_ref().map(|tok| tok.text_range()),515            label.as_ref().map(|it| it.syntax().text_range()),516        ) {517            let file_id = loop_token518                .and_then(|tok| Some(sema.hir_file_for(&tok.parent()?)))519                .unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax()));520            push_to_highlights(file_id, Some(range));521        }522523        WalkExpandedExprCtx::new(sema)524            .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)525            .walk(&expr, &mut |depth, expr| {526                let file_id = sema.hir_file_for(expr.syntax());527528                // Only highlight the `break`s for `break` and `continue`s for `continue`529                let (token, token_lt) = match expr {530                    ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => {531                        (b.break_token(), b.lifetime())532                    }533                    ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => {534                        (c.continue_token(), c.lifetime())535                    }536                    _ => return,537                };538539                if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) {540                    return;541                }542543                let text_range = cover_range(544                    token.map(|it| it.text_range()),545                    token_lt.map(|it| it.syntax().text_range()),546                );547548                push_to_highlights(file_id, text_range);549            });550551        if matches!(expr, ast::Expr::BlockExpr(_)) {552            for_each_tail_expr(&expr, &mut |tail| {553                if matches!(tail, ast::Expr::BreakExpr(_)) {554                    return;555                }556557                let file_id = sema.hir_file_for(tail.syntax());558                let range = tail.syntax().text_range();559                push_to_highlights(file_id, Some(range));560            });561        }562563        Some(highlights)564    }565566    let Some(loops) = find_loops(sema, &token) else {567        return FxHashMap::default();568    };569570    let mut res = FxHashMap::default();571    let token_kind = token.kind();572    for expr in loops {573        let new_map = match &expr {574            ast::Expr::LoopExpr(l) => hl(sema, token_kind, l.loop_token(), l.label(), expr),575            ast::Expr::ForExpr(f) => hl(sema, token_kind, f.for_token(), f.label(), expr),576            ast::Expr::WhileExpr(w) => hl(sema, token_kind, w.while_token(), w.label(), expr),577            ast::Expr::BlockExpr(e) => hl(sema, token_kind, None, e.label(), expr),578            _ => continue,579        };580        merge_map(&mut res, new_map);581    }582583    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()584}585586pub(crate) fn highlight_yield_points(587    sema: &Semantics<'_, RootDatabase>,588    token: SyntaxToken,589) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {590    fn hl(591        sema: &Semantics<'_, RootDatabase>,592        async_token: Option<SyntaxToken>,593        body: Option<ast::Expr>,594    ) -> Option<HighlightMap> {595        let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();596597        let mut push_to_highlights = |file_id, range| {598            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {599                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };600                highlights.entry(file_id).or_default().insert(hrange);601            }602        };603604        let async_token = async_token?;605        let async_tok_file_id = sema.hir_file_for(&async_token.parent()?);606        push_to_highlights(async_tok_file_id, Some(async_token.text_range()));607608        let Some(body) = body else {609            return Some(highlights);610        };611612        WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {613            let file_id = sema.hir_file_for(expr.syntax());614615            let text_range = match expr {616                ast::Expr::AwaitExpr(expr) => expr.await_token(),617                ast::Expr::ReturnExpr(expr) => expr.return_token(),618                _ => None,619            }620            .map(|it| it.text_range());621622            push_to_highlights(file_id, text_range);623        });624625        Some(highlights)626    }627628    let mut res = FxHashMap::default();629    for anc in goto_definition::find_fn_or_blocks(sema, &token) {630        let new_map = match_ast! {631            match anc {632                ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),633                ast::BlockExpr(block_expr) => {634                    let Some(async_token) = block_expr.async_token() else {635                        continue;636                    };637638                    // Async blocks act similar to closures. So we want to639                    // highlight their exit points too, but only if we are on640                    // the async token.641                    if async_token == token {642                        let exit_points = hl_exit_points(643                            sema,644                            Some(async_token.clone()),645                            block_expr.clone().into(),646                        );647                        merge_map(&mut res, exit_points);648                    }649650                    hl(sema, Some(async_token), Some(block_expr.into()))651                },652                ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()),653                _ => continue,654            }655        };656        merge_map(&mut res, new_map);657    }658659    res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()660}661662fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {663    match (r0, r1) {664        (Some(r0), Some(r1)) => Some(r0.cover(r1)),665        (Some(range), None) => Some(range),666        (None, Some(range)) => Some(range),667        (None, None) => None,668    }669}670671fn find_defs<'db>(672    sema: &Semantics<'db, RootDatabase>,673    token: SyntaxToken,674) -> FxHashSet<Definition<'db>> {675    sema.descend_into_macros_exact(token)676        .into_iter()677        .filter_map(|token| IdentClass::classify_token(sema, &token))678        .flat_map(IdentClass::definitions_no_ops)679        .collect()680}681682fn original_frange(683    db: &dyn SourceDatabase,684    file_id: HirFileId,685    text_range: Option<TextRange>,686) -> Option<FileRange> {687    InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)688}689690fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {691    let Some(new) = new else {692        return;693    };694    new.into_iter().for_each(|(file_id, ranges)| {695        res.entry(file_id).or_default().extend(ranges);696    });697}698699/// Preorder walk all the expression's child expressions.700/// For macro calls, the callback will be called on the expanded expressions after701/// visiting the macro call itself.702struct WalkExpandedExprCtx<'a, 'db> {703    sema: &'a Semantics<'db, RootDatabase>,704    depth: usize,705    check_ctx: &'static dyn Fn(&ast::Expr) -> bool,706}707708impl<'a, 'db> WalkExpandedExprCtx<'a, 'db> {709    fn new(sema: &'a Semantics<'db, RootDatabase>) -> Self {710        Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif }711    }712713    fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self {714        Self { check_ctx, ..*self }715    }716717    fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) {718        preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent<ast::Expr>| {719            match ev {720                syntax::WalkEvent::Enter(expr) => {721                    cb(self.depth, expr.clone());722723                    if Self::should_change_depth(&expr) {724                        self.depth += 1;725                    }726727                    if let ast::Expr::MacroExpr(expr) = expr728                        && let Some(expanded) =729                            expr.macro_call().and_then(|call| self.sema.expand_macro_call(&call))730                    {731                        match_ast! {732                            match (expanded.value) {733                                ast::MacroStmts(it) => {734                                    self.handle_expanded(it, cb);735                                },736                                ast::Expr(it) => {737                                    self.walk(&it, cb);738                                },739                                _ => {}740                            }741                        }742                    }743                }744                syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => {745                    self.depth -= 1;746                }747                _ => {}748            }749            false750        })751    }752753    fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) {754        if let Some(expr) = expanded.expr() {755            self.walk(&expr, cb);756        }757758        for stmt in expanded.statements() {759            if let ast::Stmt::ExprStmt(stmt) = stmt760                && let Some(expr) = stmt.expr()761            {762                self.walk(&expr, cb);763            }764        }765    }766767    fn should_change_depth(expr: &ast::Expr) -> bool {768        match expr {769            ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true,770            ast::Expr::BlockExpr(blk) if blk.label().is_some() => true,771            _ => false,772        }773    }774775    fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool {776        match expr {777            ast::Expr::BlockExpr(b) => matches!(778                b.modifier(),779                Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_))780            ),781            ast::Expr::ClosureExpr(_) => true,782            _ => false,783        }784    }785}786787pub(crate) fn highlight_unsafe_points(788    sema: &Semantics<'_, RootDatabase>,789    token: SyntaxToken,790) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {791    fn hl(792        sema: &Semantics<'_, RootDatabase>,793        unsafe_token: &SyntaxToken,794        block_expr: Option<ast::BlockExpr>,795    ) -> Option<FxHashMap<EditionedFileId, Vec<HighlightedRange>>> {796        let mut highlights: FxHashMap<EditionedFileId, Vec<_>> = FxHashMap::default();797798        let mut push_to_highlights = |file_id, range| {799            if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {800                let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };801                highlights.entry(file_id).or_default().push(hrange);802            }803        };804805        // highlight unsafe keyword itself806        let unsafe_token_file_id = sema.hir_file_for(&unsafe_token.parent()?);807        push_to_highlights(unsafe_token_file_id, Some(unsafe_token.text_range()));808809        // highlight unsafe operations810        if let Some(block) = block_expr {811            let unsafe_ops = sema.get_unsafe_ops_for_unsafe_block(block);812            for unsafe_op in unsafe_ops {813                push_to_highlights(unsafe_op.file_id, Some(unsafe_op.value.text_range()));814            }815        }816817        Some(highlights)818    }819820    hl(sema, &token, token.parent().and_then(ast::BlockExpr::cast)).unwrap_or_default()821}822823#[cfg(test)]824mod tests {825    use itertools::Itertools;826827    use crate::fixture;828829    use super::*;830831    const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {832        break_points: true,833        exit_points: true,834        references: true,835        closure_captures: true,836        yield_points: true,837        branch_exit_points: true,838    };839840    #[track_caller]841    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {842        check_with_config(ra_fixture, ENABLED_CONFIG);843    }844845    #[track_caller]846    fn check_with_config(847        #[rust_analyzer::rust_fixture] ra_fixture: &str,848        config: HighlightRelatedConfig,849    ) {850        let (analysis, pos, annotations) = fixture::annotations(ra_fixture);851852        let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();853854        let mut expected =855            annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();856857        let mut actual: Vec<(TextRange, String)> = hls858            .into_iter()859            .map(|hl| {860                (861                    hl.range,862                    hl.category.iter_names().map(|(name, _flag)| name.to_lowercase()).join(","),863                )864            })865            .collect();866        actual.sort_by_key(|(range, _)| range.start());867        expected.sort_by_key(|(range, _)| range.start());868869        assert_eq!(expected, actual);870    }871872    #[test]873    fn test_hl_unsafe_block() {874        check(875            r#"876fn foo() {877    unsafe fn this_is_unsafe_function() {}878879    unsa$0fe {880  //^^^^^^881        let raw_ptr = &42 as *const i32;882        let val = *raw_ptr;883                //^^^^^^^^884885        let mut_ptr = &mut 5 as *mut i32;886        *mut_ptr = 10;887      //^^^^^^^^888889        this_is_unsafe_function();890      //^^^^^^^^^^^^^^^^^^^^^^^^^891    }892893}894"#,895        );896    }897898    #[test]899    fn test_hl_tuple_fields() {900        check(901            r#"902struct Tuple(u32, u32);903904fn foo(t: Tuple) {905    t.0$0;906   // ^ read907    t.0;908   // ^ read909}910"#,911        );912    }913914    #[test]915    fn test_hl_module() {916        check(917            r#"918//- /lib.rs919mod foo$0;920 // ^^^921//- /foo.rs922struct Foo;923"#,924        );925    }926927    #[test]928    fn test_hl_self_in_crate_root() {929        check(930            r#"931use crate$0;932  //^^^^^ import933use self;934  //^^^^ import935mod __ {936    use super;937      //^^^^^ import938}939"#,940        );941        check(942            r#"943//- /main.rs crate:main deps:lib944use lib$0;945  //^^^ import946//- /lib.rs crate:lib947"#,948        );949    }950951    #[test]952    fn test_hl_self_in_module() {953        check(954            r#"955//- /lib.rs956mod foo;957//- /foo.rs958use self$0;959 // ^^^^ import960"#,961        );962    }963964    #[test]965    fn test_hl_local() {966        check(967            r#"968fn foo() {969    let mut bar = 3;970         // ^^^ write971    bar$0;972 // ^^^ read973}974"#,975        );976    }977978    #[test]979    fn test_hl_local_in_attr() {980        check(981            r#"982//- proc_macros: identity983#[proc_macros::identity]984fn foo() {985    let mut bar = 3;986         // ^^^ write987    bar$0;988 // ^^^ read989}990"#,991        );992    }993994    #[test]995    fn test_multi_macro_usage() {996        check(997            r#"998macro_rules! foo {999    ($ident:ident) => {1000        fn $ident() -> $ident { loop {} }1001        struct $ident;1002    }1003}10041005foo!(bar$0);1006  // ^^^1007fn foo() {1008    let bar: bar = bar();1009          // ^^^1010                // ^^^1011}1012"#,1013        );1014        check(1015            r#"1016macro_rules! foo {1017    ($ident:ident) => {1018        fn $ident() -> $ident { loop {} }1019        struct $ident;1020    }1021}10221023foo!(bar);1024  // ^^^1025fn foo() {1026    let bar: bar$0 = bar();1027          // ^^^1028}1029"#,1030        );1031    }10321033    #[test]1034    fn test_hl_yield_points() {1035        check(1036            r#"1037pub async fn foo() {1038 // ^^^^^1039    let x = foo()1040        .await$01041      // ^^^^^1042        .await;1043      // ^^^^^1044    || { 0.await };1045    (async { 0.await }).await1046                     // ^^^^^1047}1048"#,1049        );1050    }10511052    #[test]1053    fn test_hl_yield_points2() {1054        check(1055            r#"1056pub async$0 fn foo() {1057 // ^^^^^1058    let x = foo()1059        .await1060      // ^^^^^1061        .await;1062      // ^^^^^1063    || { 0.await };1064    (async { 0.await }).await1065                     // ^^^^^1066}1067"#,1068        );1069    }10701071    #[test]1072    fn test_hl_exit_points_of_async_blocks() {1073        check(1074            r#"1075pub fn foo() {1076    let x = async$0 {1077         // ^^^^^1078        0.await;1079       // ^^^^^1080       0?;1081     // ^1082       return 0;1083    // ^^^^^^1084       01085    // ^1086    };1087}1088"#,1089        );1090    }10911092    #[test]1093    fn test_hl_let_else_yield_points() {1094        check(1095            r#"1096pub async fn foo() {1097 // ^^^^^1098    let x = foo()1099        .await$01100      // ^^^^^1101        .await;1102      // ^^^^^1103    || { 0.await };1104    let Some(_) = None else {1105        foo().await1106           // ^^^^^1107    };1108    (async { 0.await }).await1109                     // ^^^^^1110}1111"#,1112        );1113    }11141115    #[test]1116    fn test_hl_yield_nested_fn() {1117        check(1118            r#"1119async fn foo() {1120    async fn foo2() {1121 // ^^^^^1122        async fn foo3() {1123            0.await1124        }1125        0.await$01126       // ^^^^^1127    }1128    0.await1129}1130"#,1131        );1132    }11331134    #[test]1135    fn test_hl_yield_nested_async_blocks() {1136        check(1137            r#"1138async fn foo() {1139    (async {1140  // ^^^^^1141        (async { 0.await }).await$01142                         // ^^^^^1143    }).await;1144}1145"#,1146        );1147    }11481149    #[test]1150    fn test_hl_exit_points() {1151        check(1152            r#"1153  fn foo() -> u32 {1154//^^1155    if true {1156        return$0 0;1157     // ^^^^^^1158    }11591160    0?;1161  // ^1162    0xDEAD_BEEF1163 // ^^^^^^^^^^^1164}1165"#,1166        );1167    }11681169    #[test]1170    fn test_hl_exit_points2() {1171        check(1172            r#"1173  fn foo() ->$0 u32 {1174//^^1175    if true {1176        return 0;1177     // ^^^^^^1178    }11791180    0?;1181  // ^1182    0xDEAD_BEEF1183 // ^^^^^^^^^^^1184}1185"#,1186        );1187    }11881189    #[test]1190    fn test_hl_exit_points3() {1191        check(1192            r#"1193  fn$0 foo() -> u32 {1194//^^1195    if true {1196        return 0;1197     // ^^^^^^1198    }11991200    0?;1201  // ^1202    0xDEAD_BEEF1203 // ^^^^^^^^^^^1204}1205"#,1206        );1207    }12081209    #[test]1210    fn test_hl_let_else_exit_points() {1211        check(1212            r#"1213  fn$0 foo() -> u32 {1214//^^1215    let Some(bar) = None else {1216        return 0;1217     // ^^^^^^1218    };12191220    0?;1221  // ^1222    0xDEAD_BEEF1223 // ^^^^^^^^^^^1224}1225"#,1226        );1227    }12281229    #[test]1230    fn test_hl_prefer_ref_over_tail_exit() {1231        check(1232            r#"1233fn foo() -> u32 {1234// ^^^1235    if true {1236        return 0;1237    }12381239    0?;12401241    foo$0()1242 // ^^^1243}1244"#,1245        );1246    }12471248    #[test]1249    fn test_hl_never_call_is_exit_point() {1250        check(1251            r#"1252struct Never;1253impl Never {1254    fn never(self) -> ! { loop {} }1255}1256macro_rules! never {1257    () => { never() }1258         // ^^^^^^^1259}1260fn never() -> ! { loop {} }1261  fn foo() ->$0 u32 {1262//^^1263    never();1264 // ^^^^^^^1265    never!();1266 // ^^^^^^^^12671268    Never.never();1269 // ^^^^^^^^^^^^^12701271    01272 // ^1273}1274"#,1275        );1276    }12771278    #[test]1279    fn test_hl_inner_tail_exit_points() {1280        check(1281            r#"1282  fn foo() ->$0 u32 {1283//^^1284    if true {1285        unsafe {1286            return 5;1287         // ^^^^^^1288            51289         // ^1290        }1291    } else if false {1292        01293     // ^1294    } else {1295        match 5 {1296            6 => 100,1297              // ^^^1298            7 => loop {1299                break 5;1300             // ^^^^^1301            }1302            8 => 'a: loop {1303                'b: loop {1304                    break 'a 5;1305                 // ^^^^^1306                    break 'b 5;1307                    break 5;1308                };1309            }1310            //1311            _ => 500,1312              // ^^^1313        }1314    }1315}1316"#,1317        );1318    }13191320    #[test]1321    fn test_hl_inner_tail_exit_points_labeled_block() {1322        check(1323            r#"1324  fn foo() ->$0 u32 {1325//^^1326    'foo: {1327        break 'foo 0;1328     // ^^^^^1329        loop {1330            break;1331            break 'foo 0;1332         // ^^^^^1333        }1334        01335     // ^1336    }1337}1338"#,1339        );1340    }13411342    #[test]1343    fn test_hl_inner_tail_exit_points_loops() {1344        check(1345            r#"1346  fn foo() ->$0 u32 {1347//^^1348    'foo: while { return 0; true } {1349               // ^^^^^^1350        break 'foo 0;1351     // ^^^^^1352        return 0;1353     // ^^^^^^1354    }1355}1356"#,1357        );1358    }13591360    #[test]1361    fn test_hl_break_loop() {1362        check(1363            r#"1364fn foo() {1365    'outer: loop {1366 // ^^^^^^^^^^^^1367         break;1368      // ^^^^^1369         'inner: loop {1370            break;1371            'innermost: loop {1372                break 'outer;1373             // ^^^^^^^^^^^^1374                break 'inner;1375            }1376            break$0 'outer;1377         // ^^^^^^^^^^^^1378            break;1379        }1380        break;1381     // ^^^^^1382    }1383}1384"#,1385        );1386    }13871388    #[test]1389    fn test_hl_break_loop2() {1390        check(1391            r#"1392fn foo() {1393    'outer: loop {1394        break;1395        'inner: loop {1396     // ^^^^^^^^^^^^1397            break;1398         // ^^^^^1399            'innermost: loop {1400                break 'outer;1401                break 'inner;1402             // ^^^^^^^^^^^^1403            }1404            break 'outer;1405            break$0;1406         // ^^^^^1407        }1408        break;1409    }1410}1411"#,1412        );1413    }14141415    #[test]1416    fn test_hl_break_for() {1417        check(1418            r#"1419fn foo() {1420    'outer: for _ in () {1421 // ^^^^^^^^^^^1422         break;1423      // ^^^^^1424         'inner: for _ in () {1425            break;1426            'innermost: for _ in () {1427                break 'outer;1428             // ^^^^^^^^^^^^1429                break 'inner;1430            }1431            break$0 'outer;1432         // ^^^^^^^^^^^^1433            break;1434        }1435        break;1436     // ^^^^^1437    }1438}1439"#,1440        );1441    }14421443    #[test]1444    fn test_hl_break_for_but_not_continue() {1445        check(1446            r#"1447fn foo() {1448    'outer: for _ in () {1449 // ^^^^^^^^^^^1450        break;1451     // ^^^^^1452        continue;1453        'inner: for _ in () {1454            break;1455            continue;1456            'innermost: for _ in () {1457                continue 'outer;1458                break 'outer;1459             // ^^^^^^^^^^^^1460                continue 'inner;1461                break 'inner;1462            }1463            break$0 'outer;1464         // ^^^^^^^^^^^^1465            continue 'outer;1466            break;1467            continue;1468        }1469        break;1470     // ^^^^^1471        continue;1472    }1473}1474"#,1475        );1476    }14771478    #[test]1479    fn test_hl_continue_for_but_not_break() {1480        check(1481            r#"1482fn foo() {1483    'outer: for _ in () {1484 // ^^^^^^^^^^^1485        break;1486        continue;1487     // ^^^^^^^^1488        'inner: for _ in () {1489            break;1490            continue;1491            'innermost: for _ in () {1492                continue 'outer;1493             // ^^^^^^^^^^^^^^^1494                break 'outer;1495                continue 'inner;1496                break 'inner;1497            }1498            break 'outer;1499            continue$0 'outer;1500         // ^^^^^^^^^^^^^^^1501            break;1502            continue;1503        }1504        break;1505        continue;1506     // ^^^^^^^^1507    }1508}1509"#,1510        );1511    }15121513    #[test]1514    fn test_hl_break_and_continue() {1515        check(1516            r#"1517fn foo() {1518    'outer: fo$0r _ in () {1519 // ^^^^^^^^^^^1520        break;1521     // ^^^^^1522        continue;1523     // ^^^^^^^^1524        'inner: for _ in () {1525            break;1526            continue;1527            'innermost: for _ in () {1528                continue 'outer;1529             // ^^^^^^^^^^^^^^^1530                break 'outer;1531             // ^^^^^^^^^^^^1532                continue 'inner;1533                break 'inner;1534            }1535            break 'outer;1536         // ^^^^^^^^^^^^1537            continue 'outer;1538         // ^^^^^^^^^^^^^^^1539            break;1540            continue;1541        }1542        break;1543     // ^^^^^1544        continue;1545     // ^^^^^^^^1546    }1547}1548"#,1549        );1550    }15511552    #[test]1553    fn test_hl_break_while() {1554        check(1555            r#"1556fn foo() {1557    'outer: while true {1558 // ^^^^^^^^^^^^^1559         break;1560      // ^^^^^1561         'inner: while true {1562            break;1563            'innermost: while true {1564                break 'outer;1565             // ^^^^^^^^^^^^1566                break 'inner;1567            }1568            break$0 'outer;1569         // ^^^^^^^^^^^^1570            break;1571        }1572        break;1573     // ^^^^^1574    }1575}1576"#,1577        );1578    }15791580    #[test]1581    fn test_hl_break_labeled_block() {1582        check(1583            r#"1584fn foo() {1585    'outer: {1586 // ^^^^^^^1587         break;1588      // ^^^^^1589         'inner: {1590            break;1591            'innermost: {1592                break 'outer;1593             // ^^^^^^^^^^^^1594                break 'inner;1595            }1596            break$0 'outer;1597         // ^^^^^^^^^^^^1598            break;1599        }1600        break;1601     // ^^^^^1602    }1603}1604"#,1605        );1606    }16071608    #[test]1609    fn test_hl_break_unlabeled_loop() {1610        check(1611            r#"1612fn foo() {1613    loop {1614 // ^^^^1615        break$0;1616     // ^^^^^1617    }1618}1619"#,1620        );1621    }16221623    #[test]1624    fn test_hl_break_unlabeled_block_in_loop() {1625        check(1626            r#"1627fn foo() {1628    loop {1629 // ^^^^1630        {1631            break$0;1632         // ^^^^^1633        }1634    }1635}1636"#,1637        );1638    }16391640    #[test]1641    fn test_hl_field_shorthand() {1642        check(1643            r#"1644struct Struct { field: u32 }1645              //^^^^^1646fn function(field: u32) {1647          //^^^^^1648    Struct { field$0 }1649           //^^^^^ read1650}1651"#,1652        );1653    }16541655    #[test]1656    fn test_hl_disabled_ref_local() {1657        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16581659        check_with_config(1660            r#"1661fn foo() {1662    let x$0 = 5;1663    let y = x * 2;1664}1665"#,1666            config,1667        );1668    }16691670    #[test]1671    fn test_hl_disabled_ref_local_preserved_break() {1672        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16731674        check_with_config(1675            r#"1676fn foo() {1677    let x$0 = 5;1678    let y = x * 2;16791680    loop {1681        break;1682    }1683}1684"#,1685            config.clone(),1686        );16871688        check_with_config(1689            r#"1690fn foo() {1691    let x = 5;1692    let y = x * 2;16931694    loop$0 {1695//  ^^^^1696        break;1697//      ^^^^^1698    }1699}1700"#,1701            config,1702        );1703    }17041705    #[test]1706    fn test_hl_disabled_ref_local_preserved_yield() {1707        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17081709        check_with_config(1710            r#"1711async fn foo() {1712    let x$0 = 5;1713    let y = x * 2;17141715    0.await;1716}1717"#,1718            config.clone(),1719        );17201721        check_with_config(1722            r#"1723    async fn foo() {1724//  ^^^^^1725        let x = 5;1726        let y = x * 2;17271728        0.await$0;1729//        ^^^^^1730}1731"#,1732            config,1733        );1734    }17351736    #[test]1737    fn test_hl_disabled_ref_local_preserved_exit() {1738        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17391740        check_with_config(1741            r#"1742fn foo() -> i32 {1743    let x$0 = 5;1744    let y = x * 2;17451746    if true {1747        return y;1748    }17491750    0?1751}1752"#,1753            config.clone(),1754        );17551756        check_with_config(1757            r#"1758  fn foo() ->$0 i32 {1759//^^1760    let x = 5;1761    let y = x * 2;17621763    if true {1764        return y;1765//      ^^^^^^1766    }17671768    0?1769//   ^1770"#,1771            config,1772        );1773    }17741775    #[test]1776    fn test_hl_disabled_break() {1777        let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };17781779        check_with_config(1780            r#"1781fn foo() {1782    loop {1783        break$0;1784    }1785}1786"#,1787            config,1788        );1789    }17901791    #[test]1792    fn test_hl_disabled_yield() {1793        let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };17941795        check_with_config(1796            r#"1797async$0 fn foo() {1798    0.await;1799}1800"#,1801            config,1802        );1803    }18041805    #[test]1806    fn test_hl_disabled_exit() {1807        let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };18081809        check_with_config(1810            r#"1811fn foo() ->$0 i32 {1812    if true {1813        return -1;1814    }18151816    421817}"#,1818            config,1819        );1820    }18211822    #[test]1823    fn test_hl_multi_local() {1824        check(1825            r#"1826fn foo((1827    foo$01828  //^^^1829    | foo1830    //^^^1831    | foo1832    //^^^1833): ()) {1834    foo;1835  //^^^read1836    let foo;1837}1838"#,1839        );1840        check(1841            r#"1842fn foo((1843    foo1844  //^^^1845    | foo$01846    //^^^1847    | foo1848    //^^^1849): ()) {1850    foo;1851  //^^^read1852    let foo;1853}1854"#,1855        );1856        check(1857            r#"1858fn foo((1859    foo1860  //^^^1861    | foo1862    //^^^1863    | foo1864    //^^^1865): ()) {1866    foo$0;1867  //^^^read1868    let foo;1869}1870"#,1871        );1872    }18731874    #[test]1875    fn test_hl_trait_impl_methods() {1876        check(1877            r#"1878trait Trait {1879    fn func$0(self) {}1880     //^^^^1881}18821883impl Trait for () {1884    fn func(self) {}1885     //^^^^1886}18871888fn main() {1889    <()>::func(());1890        //^^^^1891    ().func();1892     //^^^^1893}1894"#,1895        );1896        check(1897            r#"1898trait Trait {1899    fn func(self) {}1900}19011902impl Trait for () {1903    fn func$0(self) {}1904     //^^^^1905}19061907fn main() {1908    <()>::func(());1909        //^^^^1910    ().func();1911     //^^^^1912}1913"#,1914        );1915        check(1916            r#"1917trait Trait {1918    fn func(self) {}1919}19201921impl Trait for () {1922    fn func(self) {}1923     //^^^^1924}19251926fn main() {1927    <()>::func(());1928        //^^^^1929    ().func$0();1930     //^^^^1931}1932"#,1933        );1934    }19351936    #[test]1937    fn test_assoc_type_highlighting() {1938        check(1939            r#"1940trait Trait {1941    type Output;1942      // ^^^^^^1943}1944impl Trait for () {1945    type Output$0 = ();1946      // ^^^^^^1947}1948"#,1949        );1950    }19511952    #[test]1953    fn test_closure_capture_pipe() {1954        check(1955            r#"1956fn f() {1957    let x = 1;1958    //  ^1959    let c = $0|y| x + y;1960    //          ^ read1961}1962"#,1963        );1964    }19651966    #[test]1967    fn test_closure_capture_move() {1968        check(1969            r#"1970fn f() {1971    let x = 1;1972    //  ^1973    let c = move$0 |y| x + y;1974    //               ^ read1975}1976"#,1977        );1978    }19791980    #[test]1981    fn test_trait_highlights_assoc_item_uses() {1982        check(1983            r#"1984trait Super {1985    type SuperT;1986}1987trait Foo: Super {1988    //^^^1989    type T;1990    const C: usize;1991    fn f() {}1992    fn m(&self) {}1993}1994impl Foo for i32 {1995   //^^^1996    type T = i32;1997    const C: usize = 0;1998    fn f() {}1999    fn m(&self) {}2000}

Code quality findings 31

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();
Use of 'unsafe' bypasses Rust's safety guarantees; keep it minimal in scope and justify it (FFI, a measured optimisation) with a SAFETY comment
warning safety unsafe-block
unsafe fn this_is_unsafe_function() {}
Use of 'unsafe' bypasses Rust's safety guarantees; keep it minimal in scope and justify it (FFI, a measured optimisation) with a SAFETY comment
warning safety unsafe-block
unsafe {
Use of 'unsafe' bypasses Rust's safety guarantees; keep it minimal in scope and justify it (FFI, a measured optimisation) with a SAFETY comment
warning safety unsafe-block
pub unsafe fn bootstrap() -> ! {
Use of 'unsafe' bypasses Rust's safety guarantees; keep it minimal in scope and justify it (FFI, a measured optimisation) with a SAFETY comment
warning safety unsafe-block
unsafe { *(1 as *const u8) };
Use of 'unsafe' bypasses Rust's safety guarantees; keep it minimal in scope and justify it (FFI, a measured optimisation) with a SAFETY comment
warning safety unsafe-block
unsafe { *(2 as *const u8) };
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.

Generated by searchcode.com · powered by code_get_file + code_get_findings