11use std:: path:: { Path , PathBuf } ;
22
33use rustc_data_structures:: fx:: { FxHashMap , FxIndexMap } ;
4+ use rustc_hir as hir;
45use rustc_hir:: def:: { DefKind , Res } ;
5- use rustc_hir:: def_id:: { DefId , LOCAL_CRATE , LocalDefId } ;
6+ use rustc_hir:: def_id:: { DefId , LOCAL_CRATE } ;
67use rustc_hir:: intravisit:: { self , Visitor , VisitorExt } ;
78use rustc_hir:: { ExprKind , HirId , Item , ItemKind , Mod , Node , QPath } ;
89use rustc_middle:: hir:: nested_filter;
9- use rustc_middle:: ty:: TyCtxt ;
10+ use rustc_middle:: ty:: { self , TyCtxt } ;
1011use rustc_span:: { BytePos , ExpnKind } ;
1112
1213use crate :: clean:: { self , PrimitiveType , rustc_span} ;
@@ -82,7 +83,8 @@ pub(crate) fn collect_spans_and_sources(
8283 generate_link_to_definition : bool ,
8384) -> ( FxIndexMap < PathBuf , String > , FxHashMap < Span , LinkFromSrc > ) {
8485 if include_sources {
85- let mut visitor = SpanMapVisitor { tcx, matches : FxHashMap :: default ( ) } ;
86+ let mut visitor =
87+ SpanMapVisitor { tcx, maybe_typeck_results : None , matches : FxHashMap :: default ( ) } ;
8688
8789 if generate_link_to_definition {
8890 tcx. hir_walk_toplevel_module ( & mut visitor) ;
@@ -96,12 +98,34 @@ pub(crate) fn collect_spans_and_sources(
9698
9799struct SpanMapVisitor < ' tcx > {
98100 pub ( crate ) tcx : TyCtxt < ' tcx > ,
101+ pub ( crate ) maybe_typeck_results : Option < LazyTypeckResults < ' tcx > > ,
99102 pub ( crate ) matches : FxHashMap < Span , LinkFromSrc > ,
100103}
101104
102- impl SpanMapVisitor < ' _ > {
105+ impl < ' tcx > SpanMapVisitor < ' tcx > {
106+ /// Returns the typeck results of the current body if we're in one.
107+ ///
108+ /// This will typeck the body if it hasn't been already. Since rustdoc intentionally doesn't run
109+ /// all semantic analysis passes on function bodies at the time of writing, this can lead to us
110+ /// "suddenly" rejecting the user's code under `--generate-link-to-definition` while accepting
111+ /// it if that flag isn't passed! So use this method sparingly and think about the consequences
112+ /// including performance!
113+ ///
114+ /// This behavior is documented in the rustdoc book. Ideally, it wouldn't be that way but no
115+ /// good solution has been found so far. Don't think about adding some sort of flag to rustc to
116+ /// suppress diagnostic emission that would be unsound wrt. `ErrorGuaranteed`[^1] and generally
117+ /// be quite hacky!
118+ ///
119+ /// [^1]: Historical context:
120+ /// <https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352>.
121+ fn maybe_typeck_results ( & mut self ) -> Option < & ' tcx ty:: TypeckResults < ' tcx > > {
122+ let results = self . maybe_typeck_results . as_mut ( ) ?;
123+ let results = results. cache . get_or_insert_with ( || self . tcx . typeck_body ( results. body_id ) ) ;
124+ Some ( * results)
125+ }
126+
103127 /// This function is where we handle `hir::Path` elements and add them into the "span map".
104- fn handle_path ( & mut self , path : & rustc_hir :: Path < ' _ > , only_use_last_segment : bool ) {
128+ fn handle_path ( & mut self , path : & hir :: Path < ' _ > , only_use_last_segment : bool ) {
105129 match path. res {
106130 // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
107131 // Would be nice to support them too alongside the other `DefKind`
@@ -218,18 +242,13 @@ impl SpanMapVisitor<'_> {
218242 }
219243
220244 fn infer_id ( & mut self , hir_id : HirId , expr_hir_id : Option < HirId > , span : Span ) {
221- let tcx = self . tcx ;
222- let body_id = tcx. hir_enclosing_body_owner ( hir_id) ;
223- // FIXME: this is showing error messages for parts of the code that are not
224- // compiled (because of cfg)!
225- //
226- // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
227- let typeck_results = tcx. typeck_body ( tcx. hir_body_owned_by ( body_id) . id ( ) ) ;
245+ let Some ( typeck_results) = self . maybe_typeck_results ( ) else { return } ;
246+
228247 // Interestingly enough, for method calls, we need the whole expression whereas for static
229248 // method/function calls, we need the call expression specifically.
230249 if let Some ( def_id) = typeck_results. type_dependent_def_id ( expr_hir_id. unwrap_or ( hir_id) ) {
231250 let link = if def_id. as_local ( ) . is_some ( ) {
232- LinkFromSrc :: Local ( rustc_span ( def_id, tcx) )
251+ LinkFromSrc :: Local ( rustc_span ( def_id, self . tcx ) )
233252 } else {
234253 LinkFromSrc :: External ( def_id)
235254 } ;
@@ -238,31 +257,21 @@ impl SpanMapVisitor<'_> {
238257 }
239258}
240259
241- // This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
242- // panicking.
243- fn hir_enclosing_body_owner ( tcx : TyCtxt < ' _ > , hir_id : HirId ) -> Option < LocalDefId > {
244- for ( _, node) in tcx. hir_parent_iter ( hir_id) {
245- // FIXME: associated type impl items don't have an associated body, so we don't handle
246- // them currently.
247- if let Node :: ImplItem ( impl_item) = node
248- && matches ! ( impl_item. kind, rustc_hir:: ImplItemKind :: Type ( _) )
249- {
250- return None ;
251- } else if let Some ( ( def_id, _) ) = node. associated_body ( ) {
252- return Some ( def_id) ;
253- }
254- }
255- None
256- }
257-
258260impl < ' tcx > Visitor < ' tcx > for SpanMapVisitor < ' tcx > {
259261 type NestedFilter = nested_filter:: All ;
260262
261263 fn maybe_tcx ( & mut self ) -> Self :: MaybeTyCtxt {
262264 self . tcx
263265 }
264266
265- fn visit_path ( & mut self , path : & rustc_hir:: Path < ' tcx > , _id : HirId ) {
267+ fn visit_nested_body ( & mut self , body_id : hir:: BodyId ) -> Self :: Result {
268+ let maybe_typeck_results =
269+ self . maybe_typeck_results . replace ( LazyTypeckResults { body_id, cache : None } ) ;
270+ self . visit_body ( self . tcx . hir_body ( body_id) ) ;
271+ self . maybe_typeck_results = maybe_typeck_results;
272+ }
273+
274+ fn visit_path ( & mut self , path : & hir:: Path < ' tcx > , _id : HirId ) {
266275 if self . handle_macro ( path. span ) {
267276 return ;
268277 }
@@ -272,25 +281,37 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
272281
273282 fn visit_qpath ( & mut self , qpath : & QPath < ' tcx > , id : HirId , _span : rustc_span:: Span ) {
274283 match * qpath {
275- QPath :: TypeRelative ( qself, path) => {
276- if matches ! ( path. res, Res :: Err ) {
277- let tcx = self . tcx ;
278- if let Some ( body_id) = hir_enclosing_body_owner ( tcx, id) {
279- let typeck_results = tcx. typeck_body ( tcx. hir_body_owned_by ( body_id) . id ( ) ) ;
280- let path = rustc_hir:: Path {
284+ QPath :: TypeRelative ( qself, segment) => {
285+ if let Res :: Err = segment. res {
286+ // FIXME: This doesn't work for paths in *types* since HIR ty lowering currently
287+ // doesn't write back the resolution of type-relative paths. Updating it
288+ // to do so should be a simple fix.
289+ // FIXME: This obviously doesn't support item signatures / non-bodies. Sadly,
290+ // rustc currently doesn't keep around that information & thus can't
291+ // provide an API for it.
292+ // `ItemCtxt`s would need a place to write back the resolution of type-
293+ // dependent definitions. Ideally there was some sort of query keyed on
294+ // the `LocalDefId` of the owning item that returns some table with which
295+ // we can map the `HirId` to a `DefId`.
296+ // Of course, we could re-HIR-ty-lower such paths *here* if we were to
297+ // extend the public API of HIR analysis. However, I strongly advise
298+ // against it as it would be too much of a hack.
299+ if let Some ( typeck_results) = self . maybe_typeck_results ( ) {
300+ let path = hir:: Path {
281301 // We change the span to not include parens.
282- span : path . ident . span ,
302+ span : segment . ident . span ,
283303 res : typeck_results. qpath_res ( qpath, id) ,
304+ // FIXME(fmease): Don't create a path with zero segments!
284305 segments : & [ ] ,
285306 } ;
286307 self . handle_path ( & path, false ) ;
287308 }
288309 } else {
289- self . infer_id ( path . hir_id , Some ( id) , path . ident . span . into ( ) ) ;
310+ self . infer_id ( segment . hir_id , Some ( id) , segment . ident . span . into ( ) ) ;
290311 }
291312
292313 rustc_ast:: visit:: try_visit!( self . visit_ty_unambig( qself) ) ;
293- self . visit_path_segment ( path ) ;
314+ self . visit_path_segment ( segment ) ;
294315 }
295316 QPath :: Resolved ( maybe_qself, path) => {
296317 self . handle_path ( path, true ) ;
@@ -323,11 +344,17 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
323344 intravisit:: walk_mod ( self , m) ;
324345 }
325346
326- fn visit_expr ( & mut self , expr : & ' tcx rustc_hir :: Expr < ' tcx > ) {
347+ fn visit_expr ( & mut self , expr : & ' tcx hir :: Expr < ' tcx > ) {
327348 match expr. kind {
328349 ExprKind :: MethodCall ( segment, ..) => {
329350 self . infer_id ( segment. hir_id , Some ( expr. hir_id ) , segment. ident . span . into ( ) )
330351 }
352+ // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't
353+ // type-relative. In the majority of cases, it's just gonna be a
354+ // `Resolved` path meaning we can end up unnecessarily
355+ // `typeck`'ing the body which is super costly!
356+ // Moreover, if it actually is a type-relative path, we end up
357+ // "resolving" it twice (with slightly different spans).
331358 ExprKind :: Call ( call, ..) => self . infer_id ( call. hir_id , None , call. span . into ( ) ) ,
332359 _ => {
333360 if self . handle_macro ( expr. span ) {
@@ -340,6 +367,10 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
340367 }
341368
342369 fn visit_item ( & mut self , item : & ' tcx Item < ' tcx > ) {
370+ // We're no longer in a body since we've crossed an item boundary.
371+ // Temporarily take away the typeck results which are only valid in bodies.
372+ let maybe_typeck_results = self . maybe_typeck_results . take ( ) ;
373+
343374 match item. kind {
344375 ItemKind :: Static ( ..)
345376 | ItemKind :: Const ( ..)
@@ -359,6 +390,15 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
359390 // We already have "visit_mod" above so no need to check it here.
360391 | ItemKind :: Mod ( ..) => { }
361392 }
393+
362394 intravisit:: walk_item ( self , item) ;
395+
396+ self . maybe_typeck_results = maybe_typeck_results;
363397 }
364398}
399+
400+ /// Lazily computed & cached [`ty::TypeckResults`].
401+ struct LazyTypeckResults < ' tcx > {
402+ body_id : hir:: BodyId ,
403+ cache : Option < & ' tcx ty:: TypeckResults < ' tcx > > ,
404+ }
0 commit comments