compiler/rustc_ast_lowering/src/path.rs RUST 608 lines View on github.com → Search inside
1use std::sync::Arc;23use rustc_ast::{self as ast, *};4use rustc_errors::StashKey;5use rustc_hir::def::{DefKind, PartialRes, PerNS, Res};6use rustc_hir::def_id::DefId;7use rustc_hir::{self as hir, GenericArg};8use rustc_middle::{span_bug, ty};9use rustc_session::errors::add_feature_diagnostics;10use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Ident, Span, Symbol, sym};11use smallvec::smallvec;12use tracing::{debug, instrument};1314use super::errors::{15    AsyncBoundNotOnTrait, AsyncBoundOnlyForFnTraits, BadReturnTypeNotation,16    GenericTypeWithParentheses, RTNSuggestion, UseAngleBrackets,17};18use super::{19    AllowReturnTypeNotation, GenericArgsCtor, GenericArgsMode, ImplTraitContext, ImplTraitPosition,20    LifetimeRes, LoweringContext, ParamMode, ResolverAstLoweringExt,21};2223impl<'hir> LoweringContext<'_, 'hir> {24    #[instrument(level = "trace", skip(self))]25    pub(crate) fn lower_qpath(26        &mut self,27        id: NodeId,28        qself: &Option<Box<QSelf>>,29        p: &Path,30        param_mode: ParamMode,31        allow_return_type_notation: AllowReturnTypeNotation,32        itctx: ImplTraitContext,33        // modifiers of the impl/bound if this is a trait path34        modifiers: Option<ast::TraitBoundModifiers>,35    ) -> hir::QPath<'hir> {36        let qself_position = qself.as_ref().map(|q| q.position);37        let qself = qself38            .as_ref()39            // Reject cases like `<impl Trait>::Assoc` and `<impl Trait as Trait>::Assoc`.40            .map(|q| {41                self.lower_ty_alloc(&q.ty, ImplTraitContext::Disallowed(ImplTraitPosition::Path))42            });4344        let partial_res = self.get_partial_res(id).unwrap_or_else(|| PartialRes::new(Res::Err));45        let base_res = partial_res.base_res();46        let unresolved_segments = partial_res.unresolved_segments();4748        let mut res = self.lower_res(base_res);4950        // When we have an `async` kw on a bound, map the trait it resolves to.51        if let Some(TraitBoundModifiers { asyncness: BoundAsyncness::Async(_), .. }) = modifiers {52            match res {53                Res::Def(DefKind::Trait, def_id) => {54                    if let Some(async_def_id) = self.map_trait_to_async_trait(def_id) {55                        res = Res::Def(DefKind::Trait, async_def_id);56                    } else {57                        self.dcx().emit_err(AsyncBoundOnlyForFnTraits { span: p.span });58                    }59                }60                Res::Err => {61                    // No additional error.62                }63                _ => {64                    // This error isn't actually emitted AFAICT, but it's best to keep65                    // it around in case the resolver doesn't always check the defkind66                    // of an item or something.67                    self.dcx().emit_err(AsyncBoundNotOnTrait { span: p.span, descr: res.descr() });68                }69            }70        }7172        // Ungate the `async_fn_traits` feature in the path if the trait is73        // named via either `async Fn*()` or `AsyncFn*()`.74        let bound_modifier_allowed_features = if let Res::Def(DefKind::Trait, async_def_id) = res75            && self.tcx.async_fn_trait_kind_from_def_id(async_def_id).is_some()76        {77            Some(Arc::clone(&self.allow_async_fn_traits))78        } else {79            None80        };8182        // Only permit `impl Trait` in the final segment. E.g., we permit `Option<impl Trait>`,83        // `option::Option<T>::Xyz<impl Trait>` and reject `option::Option<impl Trait>::Xyz`.84        let itctx = |i| {85            if i + 1 == p.segments.len() {86                itctx87            } else {88                ImplTraitContext::Disallowed(ImplTraitPosition::Path)89            }90        };9192        let path_span_lo = p.span.shrink_to_lo();93        let proj_start = p.segments.len() - unresolved_segments;94        let path = self.arena.alloc(hir::Path {95            res,96            segments: self.arena.alloc_from_iter(p.segments[..proj_start].iter().enumerate().map(97                |(i, segment)| {98                    let param_mode = match (qself_position, param_mode) {99                        (Some(j), ParamMode::Optional) if i < j => {100                            // This segment is part of the trait path in a101                            // qualified path - one of `a`, `b` or `Trait`102                            // in `<X as a::b::Trait>::T::U::method`.103                            ParamMode::Explicit104                        }105                        _ => param_mode,106                    };107108                    let generic_args_mode = match base_res {109                        // `a::b::Trait(Args)`110                        Res::Def(DefKind::Trait, _) if i + 1 == proj_start => {111                            GenericArgsMode::ParenSugar112                        }113                        // `a::b::Trait(Args)::TraitItem`114                        Res::Def(DefKind::AssocFn, _)115                        | Res::Def(DefKind::AssocConst { .. }, _)116                        | Res::Def(DefKind::AssocTy, _)117                            if i + 2 == proj_start =>118                        {119                            GenericArgsMode::ParenSugar120                        }121                        Res::Def(DefKind::AssocFn, _) if i + 1 == proj_start => {122                            match allow_return_type_notation {123                                AllowReturnTypeNotation::Yes => GenericArgsMode::ReturnTypeNotation,124                                AllowReturnTypeNotation::No => GenericArgsMode::Err,125                            }126                        }127                        // Avoid duplicated errors.128                        Res::Err => GenericArgsMode::Silence,129                        // An error130                        _ => GenericArgsMode::Err,131                    };132133                    self.lower_path_segment(134                        p.span,135                        segment,136                        param_mode,137                        generic_args_mode,138                        itctx(i),139                        bound_modifier_allowed_features.clone(),140                    )141                },142            )),143            span: self.lower_span(144                p.segments[..proj_start]145                    .last()146                    .map_or(path_span_lo, |segment| path_span_lo.to(segment.span())),147            ),148        });149150        if let Some(bound_modifier_allowed_features) = bound_modifier_allowed_features {151            path.span = self.mark_span_with_reason(152                DesugaringKind::BoundModifier,153                path.span,154                Some(bound_modifier_allowed_features),155            );156        }157158        // Simple case, either no projections, or only fully-qualified.159        // E.g., `std::mem::size_of` or `<I as Iterator>::Item`.160        if unresolved_segments == 0 {161            return hir::QPath::Resolved(qself, path);162        }163164        // Create the innermost type that we're projecting from.165        let mut ty = if path.segments.is_empty() {166            // If the base path is empty that means there exists a167            // syntactical `Self`, e.g., `&i32` in `<&i32>::clone`.168            qself.expect("missing QSelf for <T>::...")169        } else {170            // Otherwise, the base path is an implicit `Self` type path,171            // e.g., `Vec` in `Vec::new` or `<I as Iterator>::Item` in172            // `<I as Iterator>::Item::default`.173            let new_id = self.next_id();174            self.arena.alloc(self.ty_path(new_id, path.span, hir::QPath::Resolved(qself, path)))175        };176177        // Anything after the base path are associated "extensions",178        // out of which all but the last one are associated types,179        // e.g., for `std::vec::Vec::<T>::IntoIter::Item::clone`:180        // * base path is `std::vec::Vec<T>`181        // * "extensions" are `IntoIter`, `Item` and `clone`182        // * type nodes are:183        //   1. `std::vec::Vec<T>` (created above)184        //   2. `<std::vec::Vec<T>>::IntoIter`185        //   3. `<<std::vec::Vec<T>>::IntoIter>::Item`186        // * final path is `<<<std::vec::Vec<T>>::IntoIter>::Item>::clone`187        for (i, segment) in p.segments.iter().enumerate().skip(proj_start) {188            // If this is a type-dependent `T::method(..)`.189            let generic_args_mode = if i + 1 == p.segments.len()190                && matches!(allow_return_type_notation, AllowReturnTypeNotation::Yes)191            {192                GenericArgsMode::ReturnTypeNotation193            } else {194                GenericArgsMode::Err195            };196197            let hir_segment = self.arena.alloc(self.lower_path_segment(198                p.span,199                segment,200                param_mode,201                generic_args_mode,202                itctx(i),203                None,204            ));205            let qpath = hir::QPath::TypeRelative(ty, hir_segment);206207            // It's finished, return the extension of the right node type.208            if i == p.segments.len() - 1 {209                return qpath;210            }211212            // Wrap the associated extension in another type node.213            let new_id = self.next_id();214            ty = self.arena.alloc(self.ty_path(new_id, path_span_lo.to(segment.span()), qpath));215        }216217        // We should've returned in the for loop above.218219        self.dcx().span_bug(220            p.span,221            format!(222                "lower_qpath: no final extension segment in {}..{}",223                proj_start,224                p.segments.len()225            ),226        );227    }228229    pub(crate) fn lower_use_path(230        &mut self,231        res: PerNS<Option<Res>>,232        p: &Path,233        param_mode: ParamMode,234    ) -> &'hir hir::UsePath<'hir> {235        assert!(!res.is_empty());236        self.arena.alloc(hir::UsePath {237            res,238            segments: self.arena.alloc_from_iter(p.segments.iter().map(|segment| {239                self.lower_path_segment(240                    p.span,241                    segment,242                    param_mode,243                    GenericArgsMode::Err,244                    ImplTraitContext::Disallowed(ImplTraitPosition::Path),245                    None,246                )247            })),248            span: self.lower_span(p.span),249        })250    }251252    pub(crate) fn lower_path_segment(253        &mut self,254        path_span: Span,255        segment: &PathSegment,256        param_mode: ParamMode,257        generic_args_mode: GenericArgsMode,258        itctx: ImplTraitContext,259        // Additional features ungated with a bound modifier like `async`.260        // This is passed down to the implicit associated type binding in261        // parenthesized bounds.262        bound_modifier_allowed_features: Option<Arc<[Symbol]>>,263    ) -> hir::PathSegment<'hir> {264        debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment);265        let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args.as_deref() {266            match generic_args {267                GenericArgs::AngleBracketed(data) => {268                    self.lower_angle_bracketed_parameter_data(data, param_mode, itctx)269                }270                GenericArgs::Parenthesized(data) => match generic_args_mode {271                    GenericArgsMode::ReturnTypeNotation => {272                        let err = match (&data.inputs[..], &data.output) {273                            ([_, ..], FnRetTy::Default(_)) => {274                                BadReturnTypeNotation::Inputs { span: data.inputs_span }275                            }276                            ([], FnRetTy::Default(_)) => {277                                BadReturnTypeNotation::NeedsDots { span: data.inputs_span }278                            }279                            // The case `T: Trait<method(..) -> Ret>` is handled in the parser.280                            (_, FnRetTy::Ty(ty)) => {281                                let span = data.inputs_span.shrink_to_hi().to(ty.span);282                                BadReturnTypeNotation::Output {283                                    span,284                                    suggestion: RTNSuggestion {285                                        output: span,286                                        input: data.inputs_span,287                                    },288                                }289                            }290                        };291                        let mut err = self.dcx().create_err(err);292                        if !self.tcx.features().return_type_notation()293                            && self.tcx.sess.is_nightly_build()294                        {295                            add_feature_diagnostics(296                                &mut err,297                                &self.tcx.sess,298                                sym::return_type_notation,299                            );300                        }301                        err.stash(path_span, StashKey::ReturnTypeNotation);302                        (303                            GenericArgsCtor {304                                args: Default::default(),305                                constraints: &[],306                                parenthesized: hir::GenericArgsParentheses::ReturnTypeNotation,307                                span: path_span,308                            },309                            false,310                        )311                    }312                    GenericArgsMode::ParenSugar | GenericArgsMode::Silence => self313                        .lower_parenthesized_parameter_data(314                            data,315                            itctx,316                            bound_modifier_allowed_features,317                        ),318                    GenericArgsMode::Err => {319                        // Suggest replacing parentheses with angle brackets `Trait(params...)` to `Trait<params...>`320                        let sub = if !data.inputs.is_empty() {321                            // Start of the span to the 1st character of 1st argument322                            let open_param = data.inputs_span.shrink_to_lo().to(data323                                .inputs324                                .first()325                                .unwrap()326                                .span327                                .shrink_to_lo());328                            // Last character position of last argument to the end of the span329                            let close_param = data330                                .inputs331                                .last()332                                .unwrap()333                                .span334                                .shrink_to_hi()335                                .to(data.inputs_span.shrink_to_hi());336337                            Some(UseAngleBrackets { open_param, close_param })338                        } else {339                            None340                        };341                        self.dcx().emit_err(GenericTypeWithParentheses { span: data.span, sub });342                        (343                            self.lower_angle_bracketed_parameter_data(344                                &data.as_angle_bracketed_args(),345                                param_mode,346                                itctx,347                            )348                            .0,349                            false,350                        )351                    }352                },353                GenericArgs::ParenthesizedElided(span) => {354                    match generic_args_mode {355                        GenericArgsMode::ReturnTypeNotation | GenericArgsMode::Silence => {356                            // Ok357                        }358                        GenericArgsMode::ParenSugar | GenericArgsMode::Err => {359                            self.dcx().emit_err(BadReturnTypeNotation::Position { span: *span });360                        }361                    }362                    (363                        GenericArgsCtor {364                            args: Default::default(),365                            constraints: &[],366                            parenthesized: hir::GenericArgsParentheses::ReturnTypeNotation,367                            span: *span,368                        },369                        false,370                    )371                }372            }373        } else {374            (375                GenericArgsCtor {376                    args: Default::default(),377                    constraints: &[],378                    parenthesized: hir::GenericArgsParentheses::No,379                    span: path_span.shrink_to_hi(),380                },381                param_mode == ParamMode::Optional,382            )383        };384385        let has_lifetimes =386            generic_args.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)));387388        // FIXME(return_type_notation): Is this correct? I think so.389        if generic_args.parenthesized != hir::GenericArgsParentheses::ParenSugar && !has_lifetimes {390            self.maybe_insert_elided_lifetimes_in_path(391                path_span,392                segment.id,393                segment.ident.span,394                &mut generic_args,395            );396        }397398        let res = self.expect_full_res(segment.id);399        let hir_id = self.lower_node_id(segment.id);400        debug!(401            "lower_path_segment: ident={:?} original-id={:?} new-id={:?}",402            segment.ident, segment.id, hir_id,403        );404405        hir::PathSegment {406            ident: self.lower_ident(segment.ident),407            hir_id,408            res: self.lower_res(res),409            infer_args,410            args: if generic_args.is_empty() && generic_args.span.is_empty() {411                None412            } else {413                Some(generic_args.into_generic_args(self))414            },415        }416    }417418    fn maybe_insert_elided_lifetimes_in_path(419        &mut self,420        path_span: Span,421        segment_id: NodeId,422        segment_ident_span: Span,423        generic_args: &mut GenericArgsCtor<'hir>,424    ) {425        let (start, end) = match self.resolver.get_lifetime_res(segment_id) {426            Some(LifetimeRes::ElidedAnchor { start, end }) => (start, end),427            None => return,428            Some(res) => {429                span_bug!(path_span, "expected an elided lifetime to insert. found {res:?}")430            }431        };432        let expected_lifetimes = end.as_usize() - start.as_usize();433        debug!(expected_lifetimes);434435        // Note: these spans are used for diagnostics when they can't be inferred.436        // See rustc_resolve::late::lifetimes::LifetimeContext::add_missing_lifetime_specifiers_label437        let (elided_lifetime_span, angle_brackets) = if generic_args.span.is_empty() {438            // No brackets, e.g. `Path`: use an empty span just past the end of the identifier.439            // HACK: we use find_ancestor_inside to properly suggest elided spans in paths440            // originating from macros, since the segment's span might be from a macro arg.441            (442                segment_ident_span.find_ancestor_inside(path_span).unwrap_or(path_span),443                hir::AngleBrackets::Missing,444            )445        } else {446            // Brackets, e.g. `Path<>` or `Path<T>`: use an empty span just after the `<`.447            (448                generic_args.span.with_lo(generic_args.span.lo() + BytePos(1)).shrink_to_lo(),449                if generic_args.is_empty() {450                    hir::AngleBrackets::Empty451                } else {452                    hir::AngleBrackets::Full453                },454            )455        };456457        generic_args.args.insert_many(458            0,459            (start..end).map(|id| {460                let l =461                    self.lower_lifetime_hidden_in_path(id, elided_lifetime_span, angle_brackets);462                GenericArg::Lifetime(l)463            }),464        );465    }466467    pub(crate) fn lower_angle_bracketed_parameter_data(468        &mut self,469        data: &AngleBracketedArgs,470        param_mode: ParamMode,471        itctx: ImplTraitContext,472    ) -> (GenericArgsCtor<'hir>, bool) {473        let has_non_lt_args = data.args.iter().any(|arg| match arg {474            AngleBracketedArg::Arg(ast::GenericArg::Lifetime(_))475            | AngleBracketedArg::Constraint(_) => false,476            AngleBracketedArg::Arg(ast::GenericArg::Type(_) | ast::GenericArg::Const(_)) => true,477        });478        let args = data479            .args480            .iter()481            .filter_map(|arg| match arg {482                AngleBracketedArg::Arg(arg) => Some(self.lower_generic_arg(arg, itctx)),483                AngleBracketedArg::Constraint(_) => None,484            })485            .collect();486        let constraints =487            self.arena.alloc_from_iter(data.args.iter().filter_map(|arg| match arg {488                AngleBracketedArg::Constraint(c) => {489                    Some(self.lower_assoc_item_constraint(c, itctx))490                }491                AngleBracketedArg::Arg(_) => None,492            }));493        let ctor = GenericArgsCtor {494            args,495            constraints,496            parenthesized: hir::GenericArgsParentheses::No,497            span: data.span,498        };499        (ctor, !has_non_lt_args && param_mode == ParamMode::Optional)500    }501502    fn lower_parenthesized_parameter_data(503        &mut self,504        data: &ParenthesizedArgs,505        itctx: ImplTraitContext,506        bound_modifier_allowed_features: Option<Arc<[Symbol]>>,507    ) -> (GenericArgsCtor<'hir>, bool) {508        // Switch to `PassThrough` mode for anonymous lifetimes; this509        // means that we permit things like `&Ref<T>`, where `Ref` has510        // a hidden lifetime parameter. This is needed for backwards511        // compatibility, even in contexts like an impl header where512        // we generally don't permit such things (see #51008).513        let ParenthesizedArgs { span, inputs, inputs_span, output } = data;514        let inputs = self.arena.alloc_from_iter(inputs.iter().map(|ty| {515            self.lower_ty(ty, ImplTraitContext::Disallowed(ImplTraitPosition::FnTraitParam))516        }));517        let output_ty = match output {518            // Only allow `impl Trait` in return position. i.e.:519            // ```rust520            // fn f(_: impl Fn() -> impl Debug) -> impl Fn() -> impl Debug521            // //      disallowed --^^^^^^^^^^        allowed --^^^^^^^^^^522            // ```523            FnRetTy::Ty(ty) if matches!(itctx, ImplTraitContext::OpaqueTy { .. }) => {524                if self.tcx.features().impl_trait_in_fn_trait_return() {525                    self.lower_ty_alloc(ty, itctx)526                } else {527                    self.lower_ty_alloc(528                        ty,529                        ImplTraitContext::FeatureGated(530                            ImplTraitPosition::FnTraitReturn,531                            sym::impl_trait_in_fn_trait_return,532                        ),533                    )534                }535            }536            FnRetTy::Ty(ty) => self537                .lower_ty_alloc(ty, ImplTraitContext::Disallowed(ImplTraitPosition::FnTraitReturn)),538            FnRetTy::Default(_) => self.arena.alloc(self.ty_tup(*span, &[])),539        };540        let args = smallvec![GenericArg::Type(541            self.arena.alloc(self.ty_tup(*inputs_span, inputs)).try_as_ambig_ty().unwrap()542        )];543544        // If we have a bound like `async Fn() -> T`, make sure that we mark the545        // `Output = T` associated type bound with the right feature gates.546        let mut output_span = output_ty.span;547        if let Some(bound_modifier_allowed_features) = bound_modifier_allowed_features {548            output_span = self.mark_span_with_reason(549                DesugaringKind::BoundModifier,550                output_span,551                Some(bound_modifier_allowed_features),552            );553        }554        let constraint = self.assoc_ty_binding(sym::Output, output_span, output_ty);555556        (557            GenericArgsCtor {558                args,559                constraints: arena_vec![self; constraint],560                parenthesized: hir::GenericArgsParentheses::ParenSugar,561                span: data.inputs_span,562            },563            false,564        )565    }566567    /// An associated type binding (i.e., associated type equality constraint).568    pub(crate) fn assoc_ty_binding(569        &mut self,570        assoc_ty_name: rustc_span::Symbol,571        span: Span,572        ty: &'hir hir::Ty<'hir>,573    ) -> hir::AssocItemConstraint<'hir> {574        let ident = Ident::with_dummy_span(assoc_ty_name);575        let kind = hir::AssocItemConstraintKind::Equality { term: ty.into() };576        let args = arena_vec![self;];577        let constraints = arena_vec![self;];578        let gen_args = self.arena.alloc(hir::GenericArgs {579            args,580            constraints,581            parenthesized: hir::GenericArgsParentheses::No,582            span_ext: DUMMY_SP,583        });584        hir::AssocItemConstraint {585            hir_id: self.next_id(),586            gen_args,587            span: self.lower_span(span),588            ident,589            kind,590        }591    }592593    /// When a bound is annotated with `async`, it signals to lowering that the trait594    /// that the bound refers to should be mapped to the "async" flavor of the trait.595    ///596    /// This only needs to be done until we unify `AsyncFn` and `Fn` traits into one597    /// that is generic over `async`ness, if that's ever possible, or modify the598    /// lowering of `async Fn()` bounds to desugar to another trait like `LendingFn`.599    fn map_trait_to_async_trait(&self, def_id: DefId) -> Option<DefId> {600        let lang_items = self.tcx.lang_items();601        match self.tcx.fn_trait_kind_from_def_id(def_id)? {602            ty::ClosureKind::Fn => lang_items.async_fn_trait(),603            ty::ClosureKind::FnMut => lang_items.async_fn_mut_trait(),604            ty::ClosureKind::FnOnce => lang_items.async_fn_once_trait(),605        }606    }607}

Code quality findings 9

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
segments: self.arena.alloc_from_iter(p.segments[..proj_start].iter().enumerate().map(
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
p.segments[..proj_start]
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
qself.expect("missing QSelf for <T>::...")
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let err = match (&data.inputs[..], &data.output) {
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()
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()
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
self.arena.alloc(self.ty_tup(*inputs_span, inputs)).try_as_ambig_ty().unwrap()
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 param_mode = match (qself_position, param_mode) {
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 generic_args_mode = match base_res {

Get this view in your editor

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