From 0a0a0b1d8d70c4afaaca9649e63fdc60a5a4e760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 15:59:37 +0200 Subject: [PATCH 1/8] rustdoc: `SpanMapVisitor`: Cache `TypeckResults` --- src/librustdoc/html/render/span_map.rs | 85 +++++++++++++------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ff214bad59f1d..99668853b922d 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -1,12 +1,13 @@ use std::path::{Path, PathBuf}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, Visitor, VisitorExt}; use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::{BytePos, ExpnKind}; use crate::clean::{self, PrimitiveType, rustc_span}; @@ -82,7 +83,8 @@ pub(crate) fn collect_spans_and_sources( generate_link_to_definition: bool, ) -> (FxIndexMap, FxHashMap) { if include_sources { - let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; + let mut visitor = + SpanMapVisitor { tcx, cached_typeck_results: None, matches: FxHashMap::default() }; if generate_link_to_definition { tcx.hir_walk_toplevel_module(&mut visitor); @@ -96,12 +98,24 @@ pub(crate) fn collect_spans_and_sources( struct SpanMapVisitor<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, + pub(crate) cached_typeck_results: Option<(hir::BodyId, Option<&'tcx ty::TypeckResults<'tcx>>)>, pub(crate) matches: FxHashMap, } -impl SpanMapVisitor<'_> { +impl<'tcx> SpanMapVisitor<'tcx> { + fn typeck_results_for(&mut self, owner: hir::OwnerId) -> Option<&'tcx ty::TypeckResults<'tcx>> { + let (body_id, cached_typeck_results) = self.cached_typeck_results.as_mut()?; + + // FIXME(fmease): Talk about the consequences of typeck'ing here. + // And maybe add back this link but meh: + // . + Some(*cached_typeck_results.get_or_insert_with(|| self.tcx.typeck_body(*body_id))) + // FIXME(fmease): Explainer. + .filter(|results| results.hir_owner == owner) + } + /// This function is where we handle `hir::Path` elements and add them into the "span map". - fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) { + fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. // Would be nice to support them too alongside the other `DefKind` @@ -218,18 +232,13 @@ impl SpanMapVisitor<'_> { } fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, span: Span) { - let tcx = self.tcx; - let body_id = tcx.hir_enclosing_body_owner(hir_id); - // FIXME: this is showing error messages for parts of the code that are not - // compiled (because of cfg)! - // - // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352 - let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); + let Some(typeck_results) = self.typeck_results_for(hir_id.owner) else { return }; + // Interestingly enough, for method calls, we need the whole expression whereas for static // method/function calls, we need the call expression specifically. if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) { let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, tcx)) + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) } else { LinkFromSrc::External(def_id) }; @@ -238,23 +247,6 @@ impl SpanMapVisitor<'_> { } } -// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without -// panicking. -fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option { - for (_, node) in tcx.hir_parent_iter(hir_id) { - // FIXME: associated type impl items don't have an associated body, so we don't handle - // them currently. - if let Node::ImplItem(impl_item) = node - && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_)) - { - return None; - } else if let Some((def_id, _)) = node.associated_body() { - return Some(def_id); - } - } - None -} - impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { type NestedFilter = nested_filter::All; @@ -262,7 +254,13 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { self.tcx } - fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) { + fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result { + let cached_typeck_results = self.cached_typeck_results.replace((body_id, None)); + self.visit_body(self.tcx.hir_body(body_id)); + self.cached_typeck_results = cached_typeck_results; + } + + fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { if self.handle_macro(path.span) { return; } @@ -272,25 +270,24 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { - QPath::TypeRelative(qself, path) => { - if matches!(path.res, Res::Err) { - let tcx = self.tcx; - if let Some(body_id) = hir_enclosing_body_owner(tcx, id) { - let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); - let path = rustc_hir::Path { + QPath::TypeRelative(qself, segment) => { + if let Res::Err = segment.res { + if let Some(typeck_results) = self.typeck_results_for(id.owner) { + let path = hir::Path { // We change the span to not include parens. - span: path.ident.span, + span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), + // FIXME(fmease): Don't create a path with zero segments! segments: &[], }; self.handle_path(&path, false); } } else { - self.infer_id(path.hir_id, Some(id), path.ident.span.into()); + self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); } rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); - self.visit_path_segment(path); + self.visit_path_segment(segment); } QPath::Resolved(maybe_qself, path) => { self.handle_path(path, true); @@ -323,11 +320,17 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { intravisit::walk_mod(self, m); } - fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into()) } + // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't + // type-relative. In the majority of cases, it's just gonna be a + // `Resolved` path meaning we can end up unnecessarily + // `typeck`'ing the body which is super costly! + // Moreover, if it actually is a type-relative path, we end up + // "resolving" it twice (with slightly different spans). ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), _ => { if self.handle_macro(expr.span) { From b4cf75a638d5301fbbc39fd11eda530c57d71c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:26:46 +0200 Subject: [PATCH 2/8] Don't check if the resolution of `TypeRelative` paths is `Err` because it always is `rustc_resolve` doesn't resolve type-relative paths since that's the job of HIR ty lowering and HIR typeck. `segment.res` comes from `rustc_resolve` and is thus always `Res::Err`. So just try to obtain the `TypeckResults` immediately since they contain the actual resolution as deduced by HIR typeck. --- src/librustdoc/html/render/span_map.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 99668853b922d..8db09e6973ea5 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -271,19 +271,15 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { QPath::TypeRelative(qself, segment) => { - if let Res::Err = segment.res { - if let Some(typeck_results) = self.typeck_results_for(id.owner) { - let path = hir::Path { - // We change the span to not include parens. - span: segment.ident.span, - res: typeck_results.qpath_res(qpath, id), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], - }; - self.handle_path(&path, false); - } - } else { - self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); + if let Some(typeck_results) = self.typeck_results_for(id.owner) { + let path = hir::Path { + // We change the span to not include parens. + span: segment.ident.span, + res: typeck_results.qpath_res(qpath, id), + // FIXME(fmease): Don't create a path with zero segments! + segments: &[], + }; + self.handle_path(&path, false); } rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); From 330459c4dd87609f6262a55b235e4762c8a96b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:49:39 +0200 Subject: [PATCH 3/8] Slightly refactor the way we handle type-dependent defs --- src/librustdoc/html/render/span_map.rs | 53 +++++++++++--------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 8db09e6973ea5..f97606d0e7d8d 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -114,6 +114,14 @@ impl<'tcx> SpanMapVisitor<'tcx> { .filter(|results| results.hir_owner == owner) } + fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { + if def_id.is_local() { + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) + } else { + LinkFromSrc::External(def_id) + } + } + /// This function is where we handle `hir::Path` elements and add them into the "span map". fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { @@ -121,11 +129,7 @@ impl<'tcx> SpanMapVisitor<'tcx> { // Would be nice to support them too alongside the other `DefKind` // (such as primitive types!). Res::Def(kind, def_id) if kind != DefKind::TyParam => { - let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, self.tcx)) - } else { - LinkFromSrc::External(def_id) - }; + let last_segment = path.segments.last().unwrap(); // In case the path ends with generics, we remove them from the span. let span = if only_use_last_segment && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) @@ -146,7 +150,7 @@ impl<'tcx> SpanMapVisitor<'tcx> { }) .unwrap_or(path.span) }; - self.matches.insert(span.into(), link); + self.matches.insert(span.into(), self.link_for_def(def_id)); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { let path_span = if only_use_last_segment @@ -230,21 +234,6 @@ impl<'tcx> SpanMapVisitor<'tcx> { self.matches.insert(new_span.into(), link_from_src); true } - - fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, span: Span) { - let Some(typeck_results) = self.typeck_results_for(hir_id.owner) else { return }; - - // Interestingly enough, for method calls, we need the whole expression whereas for static - // method/function calls, we need the call expression specifically. - if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) { - let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, self.tcx)) - } else { - LinkFromSrc::External(def_id) - }; - self.matches.insert(span, link); - } - } } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -317,23 +306,25 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - match expr.kind { - ExprKind::MethodCall(segment, ..) => { - self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into()) + let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { + let typeck_results = self.typeck_results_for(hir_id.owner).unwrap(); + if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { + self.matches.insert(span.into(), self.link_for_def(def_id)); } + }; + + match expr.kind { + ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't // type-relative. In the majority of cases, it's just gonna be a // `Resolved` path meaning we can end up unnecessarily // `typeck`'ing the body which is super costly! // Moreover, if it actually is a type-relative path, we end up // "resolving" it twice (with slightly different spans). - ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), - _ => { - if self.handle_macro(expr.span) { - // We don't want to go deeper into the macro. - return; - } - } + ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + // We don't want to go deeper into the macro. + _ if self.handle_macro(expr.span) => return, + _ => {} } intravisit::walk_expr(self, expr); } From 7fc1c7363967d0e3f00561584921ebf33619b782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 18:23:50 +0200 Subject: [PATCH 4/8] Don't create a path with zero segments --- src/librustdoc/html/render/span_map.rs | 45 +++++++++----------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index f97606d0e7d8d..dd140447d797e 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -125,38 +125,27 @@ impl<'tcx> SpanMapVisitor<'tcx> { /// This function is where we handle `hir::Path` elements and add them into the "span map". fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { - // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. - // Would be nice to support them too alongside the other `DefKind` - // (such as primitive types!). - Res::Def(kind, def_id) if kind != DefKind::TyParam => { - let last_segment = path.segments.last().unwrap(); + // FIXME: Properly support type parameters. Note they resolve just fine. The issue is + // that our highligher would then also linkify their *definition site* for some reason + // linking them to themselves. Const parameters don't exhibit this issue. + Res::Def(DefKind::TyParam, _) => {} + Res::Def(_, def_id) => { + // The segments can be empty for `use *;` in a non-crate-root scope in Rust 2015. + let span = path.segments.last().map_or(path.span, |seg| seg.ident.span); // In case the path ends with generics, we remove them from the span. - let span = if only_use_last_segment - && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) - { - path_span + let span = if only_use_last_segment { + span } else { - path.segments - .last() - .map(|last| { - // In `use` statements, the included item is not in the path segments. - // However, it doesn't matter because you can't have generics on `use` - // statements. - if path.span.contains(last.ident.span) { - path.span.with_hi(last.ident.span.hi()) - } else { - path.span - } - }) - .unwrap_or(path.span) + // In `use` statements, the included item is not in the path segments. However, + // it doesn't matter because you can't have generics on `use` statements. + if path.span.contains(span) { path.span.with_hi(span.hi()) } else { path.span } }; + self.matches.insert(span.into(), self.link_for_def(def_id)); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { - let path_span = if only_use_last_segment - && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) - { - path_span + let path_span = if only_use_last_segment { + path.segments.last().unwrap().ident.span } else { path.span }; @@ -167,7 +156,6 @@ impl<'tcx> SpanMapVisitor<'tcx> { self.matches .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); } - Res::Err => {} _ => {} } } @@ -265,8 +253,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { // We change the span to not include parens. span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], + segments: std::slice::from_ref(segment), }; self.handle_path(&path, false); } From 53d119ecf28c581f09957575bf23450f146111ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 19:55:04 +0200 Subject: [PATCH 5/8] Don't special-case `ExprKind::Call` Don't unnecessarily try to obtain the type-dependent definition of callees in `visit_expr`, just let `visit_qpath` handle callees. This means that for callees that are * `Resolved` paths (the majority of callees) we don't try to `typeck` the enclosing body which should improve perf if the body doesn't contain any type-dependent definitions. * actually `TypeRelative` paths we don't resolve them twice (with slightly different spans) --- src/librustdoc/html/render/span_map.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index dd140447d797e..d126b4de4f92a 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -293,22 +293,13 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { - let typeck_results = self.typeck_results_for(hir_id.owner).unwrap(); - if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { - self.matches.insert(span.into(), self.link_for_def(def_id)); - } - }; - match expr.kind { - ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), - // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't - // type-relative. In the majority of cases, it's just gonna be a - // `Resolved` path meaning we can end up unnecessarily - // `typeck`'ing the body which is super costly! - // Moreover, if it actually is a type-relative path, we end up - // "resolving" it twice (with slightly different spans). - ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + ExprKind::MethodCall(segment, ..) => { + let typeck_results = self.typeck_results_for(expr.hir_id.owner).unwrap(); + if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { + self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); + } + } // We don't want to go deeper into the macro. _ if self.handle_macro(expr.span) => return, _ => {} From c696ac92778b14f0c28da4dc344775ed53ca361d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 20:54:35 +0200 Subject: [PATCH 6/8] [perf-only] rustdoc: Unconditionally enable link-to-definition --- src/librustdoc/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index d726c612acf68..b7a4886581231 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -824,7 +824,7 @@ impl Options { let generate_redirect_map = matches.opt_present("generate-redirect-map"); let show_type_layout = matches.opt_present("show-type-layout"); let no_capture = matches.opt_present("no-capture"); - let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); + let generate_link_to_definition = true; let generate_macro_expansion = matches.opt_present("generate-macro-expansion"); let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); From ddf589aa27673f15cc9ae68274e6421b0d9807b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 11 May 2026 18:20:04 +0200 Subject: [PATCH 7/8] Unset `cached_typeck_results` when crossing item boundary --- src/librustdoc/html/render/span_map.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index d126b4de4f92a..144ff7d8d72d7 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -103,15 +103,13 @@ struct SpanMapVisitor<'tcx> { } impl<'tcx> SpanMapVisitor<'tcx> { - fn typeck_results_for(&mut self, owner: hir::OwnerId) -> Option<&'tcx ty::TypeckResults<'tcx>> { + fn typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { let (body_id, cached_typeck_results) = self.cached_typeck_results.as_mut()?; // FIXME(fmease): Talk about the consequences of typeck'ing here. // And maybe add back this link but meh: // . Some(*cached_typeck_results.get_or_insert_with(|| self.tcx.typeck_body(*body_id))) - // FIXME(fmease): Explainer. - .filter(|results| results.hir_owner == owner) } fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { @@ -248,7 +246,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { QPath::TypeRelative(qself, segment) => { - if let Some(typeck_results) = self.typeck_results_for(id.owner) { + if let Some(typeck_results) = self.typeck_results() { let path = hir::Path { // We change the span to not include parens. span: segment.ident.span, @@ -295,7 +293,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { - let typeck_results = self.typeck_results_for(expr.hir_id.owner).unwrap(); + let typeck_results = self.typeck_results().unwrap(); if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); } @@ -308,6 +306,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } fn visit_item(&mut self, item: &'tcx Item<'tcx>) { + // FIXME(fmease): Explainer. + let cached_typeck_results = self.cached_typeck_results.take(); + match item.kind { ItemKind::Static(..) | ItemKind::Const(..) @@ -327,6 +328,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { // We already have "visit_mod" above so no need to check it here. | ItemKind::Mod(..) => {} } + intravisit::walk_item(self, item); + + self.cached_typeck_results = cached_typeck_results; } } From 9694a8d4e0eae111bde0df083217a5b8ca096d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 11 May 2026 18:29:54 +0200 Subject: [PATCH 8/8] Introduce type `LazyTypeckResults` --- src/librustdoc/html/render/span_map.rs | 28 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 144ff7d8d72d7..fc1963afde66a 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -84,7 +84,7 @@ pub(crate) fn collect_spans_and_sources( ) -> (FxIndexMap, FxHashMap) { if include_sources { let mut visitor = - SpanMapVisitor { tcx, cached_typeck_results: None, matches: FxHashMap::default() }; + SpanMapVisitor { tcx, maybe_typeck_results: None, matches: FxHashMap::default() }; if generate_link_to_definition { tcx.hir_walk_toplevel_module(&mut visitor); @@ -98,18 +98,18 @@ pub(crate) fn collect_spans_and_sources( struct SpanMapVisitor<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, - pub(crate) cached_typeck_results: Option<(hir::BodyId, Option<&'tcx ty::TypeckResults<'tcx>>)>, + pub(crate) maybe_typeck_results: Option>, pub(crate) matches: FxHashMap, } impl<'tcx> SpanMapVisitor<'tcx> { - fn typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { - let (body_id, cached_typeck_results) = self.cached_typeck_results.as_mut()?; + fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { + let LazyTypeckResults { of: body_id, cache } = self.maybe_typeck_results.as_mut()?; // FIXME(fmease): Talk about the consequences of typeck'ing here. // And maybe add back this link but meh: // . - Some(*cached_typeck_results.get_or_insert_with(|| self.tcx.typeck_body(*body_id))) + Some(*cache.get_or_insert_with(|| self.tcx.typeck_body(*body_id))) } fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { @@ -230,9 +230,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result { - let cached_typeck_results = self.cached_typeck_results.replace((body_id, None)); + let maybe_typeck_results = self.maybe_typeck_results.replace(LazyTypeckResults { of: body_id, cache: None }); self.visit_body(self.tcx.hir_body(body_id)); - self.cached_typeck_results = cached_typeck_results; + self.maybe_typeck_results = maybe_typeck_results; } fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { @@ -246,7 +246,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { QPath::TypeRelative(qself, segment) => { - if let Some(typeck_results) = self.typeck_results() { + if let Some(typeck_results) = self.maybe_typeck_results() { let path = hir::Path { // We change the span to not include parens. span: segment.ident.span, @@ -293,7 +293,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { - let typeck_results = self.typeck_results().unwrap(); + let typeck_results = self.maybe_typeck_results().unwrap(); if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); } @@ -307,7 +307,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_item(&mut self, item: &'tcx Item<'tcx>) { // FIXME(fmease): Explainer. - let cached_typeck_results = self.cached_typeck_results.take(); + let maybe_typeck_results = self.maybe_typeck_results.take(); match item.kind { ItemKind::Static(..) @@ -331,6 +331,12 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { intravisit::walk_item(self, item); - self.cached_typeck_results = cached_typeck_results; + self.maybe_typeck_results = maybe_typeck_results; } } + +/// Lazily computed & cached [`ty::TypeckResults`]. +struct LazyTypeckResults<'tcx> { + of: hir::BodyId, + cache: Option<&'tcx ty::TypeckResults<'tcx>>, +}