From 909bb5a25a07bb6c4f424351b0dd603aa18823f7 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:43:05 -0800 Subject: [PATCH 01/19] rdr: migrate ExpnData and rmeta tables from Span to SpanRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of the RDR (Relink, Don't Rebuild) work to separate span data from .rmeta files. This commit converts Span fields to SpanRef in two areas: 1. ExpnData (hygiene.rs): Convert call_site and def_site from Span to SpanRef. The constructors convert incoming Spans internally, keeping the API stable. 2. rmeta tables (mod.rs, encoder.rs, decoder.rs, cstore_impl.rs): - Predicate tables: (Clause/PolyTraitRef, Span) → SpanRef - Direct span tables: def_span, def_ident_span, proc_macro_quoted_spans - Type-span table: assumed_wf_types_for_rpitit - Added ProcessQueryValue impls and macro variants for SpanRef decoding --- compiler/rustc_metadata/src/rmeta/decoder.rs | 5 +- .../src/rmeta/decoder/cstore_impl.rs | 67 ++++++++++++++++--- compiler/rustc_metadata/src/rmeta/encoder.rs | 45 ++++++++----- compiler/rustc_metadata/src/rmeta/mod.rs | 22 +++--- .../rustc_metadata/src/rmeta/parameterized.rs | 1 + compiler/rustc_span/src/hygiene.rs | 14 ++-- 6 files changed, 110 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index c7423386a771d..8f2b7fcc8484b 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1005,7 +1005,8 @@ impl<'a> CrateMetadataRef<'a> { .def_ident_span .get((self, tcx), item_index) .unwrap_or_else(|| self.missing("def_ident_span", item_index)) - .decode((self, tcx)); + .decode((self, tcx)) + .span(); Some(Ident::new(name, span)) } @@ -1033,6 +1034,7 @@ impl<'a> CrateMetadataRef<'a> { .get((self, tcx), index) .unwrap_or_else(|| self.missing("def_span", index)) .decode((self, tcx)) + .span() } fn load_proc_macro<'tcx>(self, tcx: TyCtxt<'tcx>, id: DefIndex) -> SyntaxExtension { @@ -1466,6 +1468,7 @@ impl<'a> CrateMetadataRef<'a> { .get((self, tcx), index) .unwrap_or_else(|| panic!("Missing proc macro quoted span: {index:?}")) .decode((self, tcx)) + .span() } fn get_foreign_modules(self, tcx: TyCtxt<'_>) -> impl Iterator { diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 7bd3f7db55f99..890127e5fd72d 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -19,7 +19,7 @@ use rustc_serialize::Decoder; use rustc_session::StableCrateId; use rustc_session::cstore::{CrateStore, ExternCrate}; use rustc_span::hygiene::ExpnId; -use rustc_span::{Span, Symbol, kw}; +use rustc_span::{Span, SpanRef, Symbol, kw}; use super::{Decodable, DecodeIterator}; use crate::creader::{CStore, LoadedMacro}; @@ -91,6 +91,27 @@ impl ProcessQueryValue<'_, Option> for Option { } } +impl ProcessQueryValue<'_, Span> for SpanRef { + #[inline(always)] + fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Span { + self.span() + } +} + +impl ProcessQueryValue<'_, Span> for Option { + #[inline(always)] + fn process_decoded(self, _tcx: TyCtxt<'_>, err: impl Fn() -> !) -> Span { + if let Some(span_ref) = self { span_ref.span() } else { err() } + } +} + +impl ProcessQueryValue<'_, Option> for Option { + #[inline(always)] + fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Option { + self.map(|span_ref| span_ref.span()) + } +} + macro_rules! provide_one { ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table }) => { provide_one! { @@ -118,6 +139,36 @@ macro_rules! provide_one { } } }; + // Like table_defaulted_array, but for defaulted tables storing (T, SpanRef) tuples that need + // conversion to (T, Span) at decode time. + ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_defaulted_array_spanref }) => { + provide_one! { + $tcx, $def_id, $other, $cdata, $name => { + let lazy = $cdata.root.tables.$name.get(($cdata, $tcx), $def_id.index); + let value = if lazy.is_default() { + &[] as &[_] + } else { + $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|(item, span_ref)| (item, span_ref.span()))) + }; + value.process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) + } + } + }; + // Like table, but for optional array tables storing (T, SpanRef) tuples that need + // conversion to (T, Span) at decode time. + ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_array_spanref }) => { + provide_one! { + $tcx, $def_id, $other, $cdata, $name => { + $cdata + .root + .tables + .$name + .get(($cdata, $tcx), $def_id.index) + .map(|lazy| $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|(item, span_ref)| (item, span_ref.span()))) as &[_]) + .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) + } + } + }; ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_direct }) => { provide_one! { $tcx, $def_id, $other, $cdata, $name => { @@ -223,13 +274,13 @@ impl IntoArgs for (CrateNum, SimplifiedType) { } provide! { tcx, def_id, other, cdata, - explicit_item_bounds => { table_defaulted_array } - explicit_item_self_bounds => { table_defaulted_array } + explicit_item_bounds => { table_defaulted_array_spanref } + explicit_item_self_bounds => { table_defaulted_array_spanref } explicit_predicates_of => { table } generics_of => { table } - inferred_outlives_of => { table_defaulted_array } - explicit_super_predicates_of => { table_defaulted_array } - explicit_implied_predicates_of => { table_defaulted_array } + inferred_outlives_of => { table_defaulted_array_spanref } + explicit_super_predicates_of => { table_defaulted_array_spanref } + explicit_implied_predicates_of => { table_defaulted_array_spanref } type_of => { table } type_alias_is_lazy => { table_direct } variances_of => { table } @@ -257,7 +308,7 @@ provide! { tcx, def_id, other, cdata, defaultness => { table_direct } constness => { table_direct } const_conditions => { table } - explicit_implied_const_bounds => { table_defaulted_array } + explicit_implied_const_bounds => { table_defaulted_array_spanref } coerce_unsized_info => { Ok(cdata .root @@ -299,7 +350,7 @@ provide! { tcx, def_id, other, cdata, .unwrap_or_default() } opaque_ty_origin => { table } - assumed_wf_types_for_rpitit => { table } + assumed_wf_types_for_rpitit => { table_array_spanref } collect_return_position_impl_trait_in_trait_tys => { Ok(cdata .root diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 4fcc7f064f3ff..4599af3239b2e 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1466,7 +1466,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if should_encode_span(def_kind) { let def_span = tcx.def_span(local_id); - record!(self.tables.def_span[def_id] <- def_span); + record!(self.tables.def_span[def_id] <- def_span.to_span_ref()); } if should_encode_attrs(def_kind) { self.encode_attrs(local_id); @@ -1477,7 +1477,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if should_encode_span(def_kind) && let Some(ident_span) = tcx.def_ident_span(def_id) { - record!(self.tables.def_ident_span[def_id] <- ident_span); + record!(self.tables.def_ident_span[def_id] <- ident_span.to_span_ref()); } if def_kind.has_codegen_attrs() { record!(self.tables.codegen_fn_attrs[def_id] <- self.tcx.codegen_fn_attrs(def_id)); @@ -1505,7 +1505,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.generics_of[def_id] <- g); record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); - record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives); + record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- + inferred_outlives.iter().map(|&(c, s)| (c, s.to_span_ref()))); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { @@ -1539,23 +1540,28 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if let DefKind::Trait = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); + self.tcx.explicit_super_predicates_of(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + self.tcx.explicit_implied_predicates_of(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); let module_children = self.tcx.module_children_local(local_id); record_array!(self.tables.module_children_non_reexports[def_id] <- module_children.iter().map(|child| child.res.def_id().index)); if self.tcx.is_const_trait(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); } } if let DefKind::TraitAlias = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); + self.tcx.explicit_super_predicates_of(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + self.tcx.explicit_implied_predicates_of(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); } if let DefKind::Trait | DefKind::Impl { .. } = def_kind { let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); @@ -1617,7 +1623,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_precise_capturing_args(def_id); if tcx.is_conditionally_const(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- tcx.explicit_implied_const_bounds(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); } } if let DefKind::AnonConst = def_kind { @@ -1758,13 +1765,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_explicit_item_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds); + record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- + bounds.iter().map(|&(c, s)| (c, s.to_span_ref()))); } fn encode_explicit_item_self_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds); + record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- + bounds.iter().map(|&(c, s)| (c, s.to_span_ref()))); } #[instrument(level = "debug", skip(self))] @@ -1785,7 +1794,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_explicit_item_self_bounds(def_id); if tcx.is_conditionally_const(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder() + .iter().map(|&(c, s)| (c, s.to_span_ref()))); } } if let ty::AssocKind::Type { data: ty::AssocTypeData::Rpitit(rpitit_info) } = item.kind { @@ -1794,6 +1804,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_array!( self.tables.assumed_wf_types_for_rpitit[def_id] <- self.tcx.assumed_wf_types_for_rpitit(def_id) + .iter().map(|&(ty, s)| (ty, s.to_span_ref())) ); self.encode_precise_capturing_args(def_id); } @@ -1986,12 +1997,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let macros = self.lazy_array(tcx.resolutions(()).proc_macros.iter().map(|p| p.local_def_index)); for (i, span) in self.tcx.sess.psess.proc_macro_quoted_spans() { - let span = self.lazy(span); - self.tables.proc_macro_quoted_spans.set_some(i, span); + let span_ref = self.lazy(span.to_span_ref()); + self.tables.proc_macro_quoted_spans.set_some(i, span_ref); } self.tables.def_kind.set_some(LOCAL_CRATE.as_def_id().index, DefKind::Mod); - record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id())); + record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id()).to_span_ref()); self.encode_attrs(LOCAL_CRATE.as_def_id().expect_local()); let vis = tcx.local_visibility(CRATE_DEF_ID).map_id(|def_id| def_id.local_def_index); record!(self.tables.visibility[LOCAL_CRATE.as_def_id()] <- vis); @@ -2037,8 +2048,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.tables.proc_macro.set_some(def_id.index, macro_kind); self.encode_attrs(id); record!(self.tables.def_keys[def_id] <- def_key); - record!(self.tables.def_ident_span[def_id] <- span); - record!(self.tables.def_span[def_id] <- span); + record!(self.tables.def_ident_span[def_id] <- span.to_span_ref()); + record!(self.tables.def_span[def_id] <- span.to_span_ref()); record!(self.tables.visibility[def_id] <- ty::Visibility::Public); if let Some(stability) = stability { record!(self.tables.lookup_stability[def_id] <- stability); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index af6df0cd6eb61..1b0e8b9e11126 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -38,7 +38,7 @@ use rustc_session::config::{SymbolManglingVersion, TargetModifier}; use rustc_session::cstore::{CrateDepKind, ForeignModule, LinkagePreference, NativeLib}; use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnIndex, MacroKind, SyntaxContextKey}; -use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Ident, Span, Symbol}; +use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Ident, Span, SpanRef, Symbol}; use rustc_target::spec::{PanicStrategy, TargetTuple}; use table::TableBuilder; use {rustc_ast as ast, rustc_hir as hir}; @@ -388,12 +388,12 @@ define_tables! { // Note also that this table is fully populated (no gaps) as every DefIndex should have a // corresponding DefPathHash. def_path_hashes: Table, - explicit_item_bounds: Table, Span)>>, - explicit_item_self_bounds: Table, Span)>>, - inferred_outlives_of: Table, Span)>>, - explicit_super_predicates_of: Table, Span)>>, - explicit_implied_predicates_of: Table, Span)>>, - explicit_implied_const_bounds: Table, Span)>>, + explicit_item_bounds: Table, SpanRef)>>, + explicit_item_self_bounds: Table, SpanRef)>>, + inferred_outlives_of: Table, SpanRef)>>, + explicit_super_predicates_of: Table, SpanRef)>>, + explicit_implied_predicates_of: Table, SpanRef)>>, + explicit_implied_const_bounds: Table, SpanRef)>>, inherent_impls: Table>, opt_rpitit_info: Table>>, // Reexported names are not associated with individual `DefId`s, @@ -416,8 +416,8 @@ define_tables! { associated_item_or_field_def_ids: Table>, def_kind: Table, visibility: Table>>, - def_span: Table>, - def_ident_span: Table>, + def_span: Table>, + def_ident_span: Table>, lookup_stability: Table>, lookup_const_stability: Table>, lookup_default_body_stability: Table>, @@ -462,7 +462,7 @@ define_tables! { // `DefPathTable` up front, since we may only ever use a few // definitions from any given crate. def_keys: Table>, - proc_macro_quoted_spans: Table>, + proc_macro_quoted_spans: Table>, variant_data: Table>, assoc_container: Table>, macro_definition: Table>, @@ -471,7 +471,7 @@ define_tables! { trait_impl_trait_tys: Table>>>>, doc_link_resolutions: Table>, doc_link_traits_in_scope: Table>, - assumed_wf_types_for_rpitit: Table, Span)>>, + assumed_wf_types_for_rpitit: Table, SpanRef)>>, opaque_ty_origin: Table>>, anon_const_kind: Table>, const_of_item: Table>>>, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 8a9de07836db4..7a1dfb81df882 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -129,6 +129,7 @@ trivially_parameterized_over_tcx! { rustc_span::Ident, rustc_span::SourceFile, rustc_span::Span, + rustc_span::SpanRef, rustc_span::Symbol, rustc_span::hygiene::SyntaxContextKey, // tidy-alphabetical-end diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index d94d82835d650..971c2ceb28972 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -43,7 +43,7 @@ use crate::def_id::{CRATE_DEF_ID, CrateNum, DefId, LOCAL_CRATE, StableCrateId}; use crate::edition::Edition; use crate::source_map::SourceMap; use crate::symbol::{Symbol, kw, sym}; -use crate::{DUMMY_SP, HashStableContext, Span, SpanDecoder, SpanEncoder, with_session_globals}; +use crate::{DUMMY_SP, HashStableContext, Span, SpanDecoder, SpanEncoder, SpanRef, with_session_globals}; /// A `SyntaxContext` represents a chain of pairs `(ExpnId, Transparency)` named "marks". /// @@ -1005,7 +1005,7 @@ pub struct ExpnData { /// /// For a desugaring expansion, this is the span of the expression or node that was /// desugared. - pub call_site: Span, + pub call_site: SpanRef, /// Used to force two `ExpnData`s to have different `Fingerprint`s. /// Due to macro expansion, it's possible to end up with two `ExpnId`s /// that have identical `ExpnData`s. This violates the contract of `HashStable` @@ -1024,7 +1024,7 @@ pub struct ExpnData { // --- noticeable perf improvements (PR #62898). /// The span of the macro definition (possibly dummy). /// This span serves only informational purpose and is not used for resolution. - pub def_site: Span, + pub def_site: SpanRef, /// List of `#[unstable]`/feature-gated features that the macro is allowed to use /// internally without forcing the whole crate to opt-in /// to them. @@ -1068,8 +1068,8 @@ impl ExpnData { ExpnData { kind, parent, - call_site, - def_site, + call_site: call_site.to_span_ref(), + def_site: def_site.to_span_ref(), allow_internal_unstable, edition, macro_def_id, @@ -1093,8 +1093,8 @@ impl ExpnData { ExpnData { kind, parent: ExpnId::root(), - call_site, - def_site: DUMMY_SP, + call_site: call_site.to_span_ref(), + def_site: DUMMY_SP.to_span_ref(), allow_internal_unstable: None, edition, macro_def_id, From f9ceeacc021136cf2cdc60718a71a40da7a2e90e Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:44:17 -0800 Subject: [PATCH 02/19] rdr: introduce IdentRef for identifier storage Add IdentRef type that stores identifiers with SpanRef instead of Span, preserving SyntaxContext for hygiene while avoiding absolute byte positions in metadata. Changes: - rustc_span/symbol.rs: New IdentRef struct with name/span fields, bidirectional conversion methods (to_ident_ref/ident), and proper PartialEq/Hash using SyntaxContext - rustc_span/lib.rs: Re-export IdentRef - rmeta: Change fn_arg_idents table from LazyArray> to LazyArray>, with encoding/decoding support --- compiler/rustc_metadata/src/rmeta/decoder.rs | 4 +- .../src/rmeta/decoder/cstore_impl.rs | 17 +++++- compiler/rustc_metadata/src/rmeta/encoder.rs | 3 +- compiler/rustc_metadata/src/rmeta/mod.rs | 4 +- .../rustc_metadata/src/rmeta/parameterized.rs | 1 + compiler/rustc_span/src/lib.rs | 4 +- compiler/rustc_span/src/symbol.rs | 58 ++++++++++++++++++- 7 files changed, 82 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 8f2b7fcc8484b..3d78a76ba6b4c 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -33,7 +33,7 @@ use rustc_session::config::TargetModifier; use rustc_session::cstore::{CrateSource, ExternCrate}; use rustc_span::hygiene::HygieneDecodeContext; use rustc_span::{ - BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, Pos, RemapPathScopeComponents, SpanData, + BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, IdentRef, Pos, RemapPathScopeComponents, SpanData, SpanDecoder, Symbol, SyntaxContext, kw, }; use tracing::debug; @@ -1316,7 +1316,7 @@ impl<'a> CrateMetadataRef<'a> { .expect("argument names not encoded for a function") .decode((self, tcx)) .nth(0) - .is_some_and(|ident| matches!(ident, Some(Ident { name: kw::SelfLower, .. }))) + .is_some_and(|ident| matches!(ident, Some(IdentRef { name: kw::SelfLower, .. }))) } fn get_associated_item_or_field_def_ids( diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 890127e5fd72d..e0e078a1d4de1 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -169,6 +169,21 @@ macro_rules! provide_one { } } }; + // Like table, but for optional array tables storing Option that need + // conversion to Option at decode time. + ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_array_identref }) => { + provide_one! { + $tcx, $def_id, $other, $cdata, $name => { + $cdata + .root + .tables + .$name + .get(($cdata, $tcx), $def_id.index) + .map(|lazy| $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|opt| opt.map(|ir| ir.ident()))) as &[_]) + .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) + } + } + }; ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_direct }) => { provide_one! { $tcx, $def_id, $other, $cdata, $name => { @@ -321,7 +336,7 @@ provide! { tcx, def_id, other, cdata, rendered_const => { table } rendered_precise_capturing_args => { table } asyncness => { table_direct } - fn_arg_idents => { table } + fn_arg_idents => { table_array_identref } coroutine_kind => { table_direct } coroutine_for_closure => { table } coroutine_by_move_body_def_id => { table } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 4599af3239b2e..e60fa564625ea 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1528,7 +1528,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if let DefKind::Fn | DefKind::AssocFn = def_kind { let asyncness = tcx.asyncness(def_id); self.tables.asyncness.set(def_id.index, asyncness); - record_array!(self.tables.fn_arg_idents[def_id] <- tcx.fn_arg_idents(def_id)); + record_array!(self.tables.fn_arg_idents[def_id] <- + tcx.fn_arg_idents(def_id).iter().map(|opt| opt.map(|id| id.to_ident_ref()))); } if let Some(name) = tcx.intrinsic(def_id) { record!(self.tables.intrinsic[def_id] <- name); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 1b0e8b9e11126..cd7d1ee9c89ae 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -38,7 +38,7 @@ use rustc_session::config::{SymbolManglingVersion, TargetModifier}; use rustc_session::cstore::{CrateDepKind, ForeignModule, LinkagePreference, NativeLib}; use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnIndex, MacroKind, SyntaxContextKey}; -use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Ident, Span, SpanRef, Symbol}; +use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Ident, IdentRef, Span, SpanRef, Symbol}; use rustc_target::spec::{PanicStrategy, TargetTuple}; use table::TableBuilder; use {rustc_ast as ast, rustc_hir as hir}; @@ -445,7 +445,7 @@ define_tables! { mir_const_qualif: Table>, rendered_const: Table>, rendered_precise_capturing_args: Table>>, - fn_arg_idents: Table>>, + fn_arg_idents: Table>>, coroutine_kind: Table, coroutine_for_closure: Table, adt_destructor: Table>, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 7a1dfb81df882..3d2b10cfbee41 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -127,6 +127,7 @@ trivially_parameterized_over_tcx! { rustc_span::ExpnHash, rustc_span::ExpnId, rustc_span::Ident, + rustc_span::IdentRef, rustc_span::SourceFile, rustc_span::Span, rustc_span::SpanRef, diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 1c430099835b3..bec78b04367be 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -64,8 +64,8 @@ pub use span_encoding::{DUMMY_SP, Span}; pub mod symbol; pub use symbol::{ - ByteSymbol, Ident, MacroRulesNormalizedIdent, Macros20NormalizedIdent, STDLIB_STABLE_CRATES, - Symbol, kw, sym, + ByteSymbol, Ident, IdentRef, MacroRulesNormalizedIdent, Macros20NormalizedIdent, + STDLIB_STABLE_CRATES, Symbol, kw, sym, }; mod analyze_source_file; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f0576002354dc..187d558721286 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -15,7 +15,7 @@ use rustc_data_structures::sync::Lock; use rustc_macros::{Decodable, Encodable, HashStable_Generic, symbols}; use crate::edit_distance::find_best_match_for_name; -use crate::{DUMMY_SP, Edition, Span, with_session_globals}; +use crate::{DUMMY_SP, Edition, Span, SpanRef, with_session_globals}; #[cfg(test)] mod tests; @@ -2635,6 +2635,12 @@ impl Ident { pub fn as_str(&self) -> &str { self.name.as_str() } + + /// Converts this `Ident` to an `IdentRef` for metadata storage. + #[inline] + pub fn to_ident_ref(self) -> IdentRef { + IdentRef::new(self.name, self.span.to_span_ref()) + } } impl PartialEq for Ident { @@ -2667,6 +2673,56 @@ impl fmt::Display for Ident { } } +/// An identifier without absolute span positions. +/// +/// Used in metadata to avoid storing byte positions that cause unnecessary +/// downstream rebuilds. The `SpanRef` preserves `SyntaxContext` for hygiene. +#[derive(Copy, Clone, Eq, HashStable_Generic, Encodable, Decodable)] +pub struct IdentRef { + pub name: Symbol, + pub span: SpanRef, +} + +impl IdentRef { + /// Constructs a new identifier reference from a symbol and a span reference. + #[inline] + pub fn new(name: Symbol, span: SpanRef) -> IdentRef { + debug_assert_ne!(name, sym::empty); + IdentRef { name, span } + } + + /// Access the underlying string. + pub fn as_str(&self) -> &str { + self.name.as_str() + } +} + +impl PartialEq for IdentRef { + #[inline] + fn eq(&self, rhs: &Self) -> bool { + self.name == rhs.name && self.span.ctxt() == rhs.span.ctxt() + } +} + +impl Hash for IdentRef { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.span.ctxt().hash(state); + } +} + +impl fmt::Debug for IdentRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}{:?}", self.name, self.span.ctxt()) + } +} + +impl From for IdentRef { + fn from(ident: Ident) -> IdentRef { + ident.to_ident_ref() + } +} + pub enum IdentPrintMode { Normal, RawIdent, From f5119920a7a086058a0868374eb2a4b23ff9758d Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:44:50 -0800 Subject: [PATCH 03/19] rdr: serialize diagnostic spans as SpanRef Update diagnostic types to serialize Span fields as SpanRef to avoid storing absolute byte positions in metadata. Unlike other span-containing types which store SpanRef internally, diagnostic types keep Span internally for performance (diagnostics are created/modified frequently; serialization is rare). Custom Encodable/Decodable impls convert at serialization boundaries: - MultiSpan: primary_spans and span_labels - SubstitutionPart: span field - TrimmedSubstitutionPart: original_span and span - DiagInner: sort_span field --- compiler/rustc_error_messages/src/lib.rs | 42 ++++++++++++++++++++-- compiler/rustc_errors/src/diagnostic.rs | 46 ++++++++++++++++++++++-- compiler/rustc_errors/src/lib.rs | 45 +++++++++++++++++++++-- 3 files changed, 126 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 085403c8ef36b..b9430a06371f0 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -16,7 +16,8 @@ use fluent_syntax::parser::ParserError; use intl_memoizer::concurrent::IntlLangMemoizer; use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; use rustc_macros::{Decodable, Encodable}; -use rustc_span::Span; +use rustc_serialize::{Decodable, Encodable}; +use rustc_span::{Span, SpanDecoder, SpanEncoder, SpanRef}; use tracing::{instrument, trace}; pub use unic_langid::{LanguageIdentifier, langid}; @@ -391,7 +392,10 @@ pub struct SpanLabel { /// the error, and would be rendered with `^^^`. /// - They can have a *label*. In this case, the label is written next /// to the mark in the snippet when we render. -#[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable)] +// Manual `Encodable`/`Decodable` impls serialize spans as `SpanRef` to support +// RDR (Relink, Don't Rebuild). We keep `Span` internally for performance since +// `primary_spans()` returns `&[Span]` directly. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MultiSpan { primary_spans: Vec, span_labels: Vec<(Span, DiagMessage)>, @@ -508,6 +512,40 @@ impl From> for MultiSpan { } } +impl Encodable for MultiSpan { + fn encode(&self, e: &mut E) { + self.primary_spans.len().encode(e); + for span in &self.primary_spans { + e.encode_span_as_span_ref(*span); + } + self.span_labels.len().encode(e); + for (span, msg) in &self.span_labels { + e.encode_span_as_span_ref(*span); + msg.encode(e); + } + } +} + +impl Decodable for MultiSpan { + fn decode(d: &mut D) -> Self { + // Decode primary spans + let primary_len: usize = Decodable::decode(d); + let mut primary_spans = Vec::with_capacity(primary_len); + for _ in 0..primary_len { + primary_spans.push(d.decode_span_ref_as_span()); + } + // Decode span labels + let labels_len: usize = Decodable::decode(d); + let mut span_labels = Vec::with_capacity(labels_len); + for _ in 0..labels_len { + let span = d.decode_span_ref_as_span(); + let msg = Decodable::decode(d); + span_labels.push((span, msg)); + } + MultiSpan { primary_spans, span_labels } + } +} + fn icu_locale_from_unic_langid(lang: LanguageIdentifier) -> Option { icu_locale::Locale::try_from_str(&lang.to_string()).ok() } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 96a4ed3218fbf..b174874f2537d 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -11,8 +11,9 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_error_messages::{DiagArgName, DiagArgValue, IntoDiagArg}; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; +use rustc_serialize::{Decodable, Encodable}; use rustc_span::source_map::Spanned; -use rustc_span::{DUMMY_SP, Span, Symbol}; +use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, SpanRef, Symbol}; use tracing::debug; use crate::snippet::Style; @@ -234,8 +235,9 @@ impl StringPart { /// used for most operations, and should be used instead whenever possible. /// This type should only be used when `Diag`'s lifetime causes difficulties, /// e.g. when storing diagnostics within `DiagCtxt`. +// Manual impls serialize `sort_span` as `SpanRef` for RDR. #[must_use] -#[derive(Clone, Debug, Encodable, Decodable)] +#[derive(Clone, Debug)] pub struct DiagInner { // NOTE(eddyb) this is private to disallow arbitrary after-the-fact changes, // outside of what methods in this crate themselves allow. @@ -265,6 +267,46 @@ pub struct DiagInner { pub(crate) emitted_at: DiagLocation, } +impl Encodable for DiagInner { + fn encode(&self, e: &mut E) { + self.level.encode(e); + self.messages.encode(e); + self.code.encode(e); + self.lint_id.encode(e); + self.span.encode(e); + self.children.encode(e); + self.suggestions.encode(e); + self.args.encode(e); + self.reserved_args.encode(e); + // Encode sort_span as SpanRef to avoid absolute byte positions + self.sort_span.to_span_ref().encode(e); + self.is_lint.encode(e); + self.long_ty_path.encode(e); + self.emitted_at.encode(e); + } +} + +impl Decodable for DiagInner { + fn decode(d: &mut D) -> Self { + DiagInner { + level: Decodable::decode(d), + messages: Decodable::decode(d), + code: Decodable::decode(d), + lint_id: Decodable::decode(d), + span: Decodable::decode(d), + children: Decodable::decode(d), + suggestions: Decodable::decode(d), + args: Decodable::decode(d), + reserved_args: Decodable::decode(d), + // Decode SpanRef and convert back to Span + sort_span: { let sr: SpanRef = Decodable::decode(d); sr.span() }, + is_lint: Decodable::decode(d), + long_ty_path: Decodable::decode(d), + emitted_at: Decodable::decode(d), + } + } +} + impl DiagInner { #[track_caller] pub fn new>(level: Level, message: M) -> Self { diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 148368045f4f5..7160e43312b3b 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -68,10 +68,11 @@ use rustc_hashes::Hash128; use rustc_lint_defs::LintExpectationId; pub use rustc_lint_defs::{Applicability, listify, pluralize}; use rustc_macros::{Decodable, Encodable}; +use rustc_serialize::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, DUMMY_SP, Loc, Span}; +use rustc_span::{BytePos, DUMMY_SP, Loc, Span, SpanDecoder, SpanEncoder, SpanRef}; pub use snippet::Style; use tracing::debug; @@ -205,19 +206,57 @@ pub struct Substitution { pub parts: Vec, } -#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)] +// Manual impls serialize spans as `SpanRef` for RDR. +#[derive(Clone, Debug, PartialEq, Hash)] pub struct SubstitutionPart { pub span: Span, pub snippet: String, } -#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)] +impl Encodable for SubstitutionPart { + fn encode(&self, e: &mut E) { + self.span.to_span_ref().encode(e); + self.snippet.encode(e); + } +} + +impl Decodable for SubstitutionPart { + fn decode(d: &mut D) -> Self { + let span_ref: SpanRef = Decodable::decode(d); + let snippet: String = Decodable::decode(d); + SubstitutionPart { span: span_ref.span(), snippet } + } +} + +// Manual impls serialize spans as `SpanRef` for RDR. +#[derive(Clone, Debug, PartialEq, Hash)] pub struct TrimmedSubstitutionPart { pub original_span: Span, pub span: Span, pub snippet: String, } +impl Encodable for TrimmedSubstitutionPart { + fn encode(&self, e: &mut E) { + self.original_span.to_span_ref().encode(e); + self.span.to_span_ref().encode(e); + self.snippet.encode(e); + } +} + +impl Decodable for TrimmedSubstitutionPart { + fn decode(d: &mut D) -> Self { + let original_span_ref: SpanRef = Decodable::decode(d); + let span_ref: SpanRef = Decodable::decode(d); + let snippet: String = Decodable::decode(d); + TrimmedSubstitutionPart { + original_span: original_span_ref.span(), + span: span_ref.span(), + snippet, + } + } +} + /// Used to translate between `Span`s and byte positions within a single output line in highlighted /// code of structured suggestions. #[derive(Debug, Clone, Copy)] From 6b263f33c2d257fb1ba141269e912c06bd457586 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:45:12 -0800 Subject: [PATCH 04/19] rdr: split source_map encoding into separate .spans file When compiling with -Zseparate-spans, the source_map is now written to a separate .spans file instead of being embedded in .rmeta. This allows .rmeta to remain stable when only span positions change (e.g., adding whitespace or comments), enabling Relink, Don't Rebuild (RDR). Changes: - SpanFileRoot: new struct with source_map field for .spans files - CrateRoot: conditionally excludes source_map when separate_spans enabled - Encoder: writes source_map to .spans file with required_source_files - Decoder: loads source_map from .spans file when present - locator.rs: get_span_metadata_section() to load .spans files - CrateMetadata: stores span_blob and span_source_map for decoding --- compiler/rustc_metadata/src/creader.rs | 17 +++ compiler/rustc_metadata/src/fs.rs | 11 +- compiler/rustc_metadata/src/locator.rs | 15 ++- compiler/rustc_metadata/src/rmeta/decoder.rs | 72 ++++++++-- compiler/rustc_metadata/src/rmeta/encoder.rs | 133 +++++++++++++++++-- compiler/rustc_metadata/src/rmeta/mod.rs | 48 ++++++- 6 files changed, 273 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 4000f12459a90..382f00291b9ca 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -622,11 +622,28 @@ impl CStore { None }; + // Load the separate span file if it exists (for crates compiled with -Z separate_spans). + let span_blob = source.rmeta.as_ref().and_then(|rmeta_path| { + let span_path = rmeta_path.with_extension("spans"); + if span_path.exists() { + match crate::locator::get_span_metadata_section(&span_path) { + Ok(blob) => Some(blob), + Err(err) => { + debug!("failed to load span file {:?}: {}", span_path, err); + None + } + } + } else { + None + } + }); + let crate_metadata = CrateMetadata::new( tcx, self, metadata, crate_root, + span_blob, raw_proc_macros, cnum, cnum_map, diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 1eaad26ff8e80..fe78f7191af46 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -54,8 +54,8 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { None }; - if tcx.needs_metadata() { - encode_metadata(tcx, &metadata_filename, metadata_stub_filename.as_deref()); + let required_source_files = if tcx.needs_metadata() { + encode_metadata(tcx, &metadata_filename, metadata_stub_filename.as_deref()) } else { // Always create a file at `metadata_filename`, even if we have nothing to write to it. // This simplifies the creation of the output `out_filename` when requested. @@ -67,6 +67,13 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { tcx.dcx().emit_fatal(FailedCreateFile { filename: &metadata_stub_filename, err }); }); } + None + }; + + if let (Some(spans_tmp_filename), Some(required_source_files)) = + (spans_tmp_filename.as_ref(), required_source_files) + { + encode_spans(tcx, spans_tmp_filename, tcx.crate_hash(LOCAL_CRATE), required_source_files); } let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata"); diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 004d73da8cbda..d3d82ddc05c01 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -236,7 +236,7 @@ use tracing::{debug, info}; use crate::creader::{Library, MetadataLoader}; use crate::errors; -use crate::rmeta::{METADATA_HEADER, MetadataBlob, rustc_version}; +use crate::rmeta::{METADATA_HEADER, MetadataBlob, SpanBlob, rustc_version}; #[derive(Clone)] pub(crate) struct CrateLocator<'a> { @@ -950,6 +950,19 @@ fn get_rmeta_metadata_section<'a, 'p>(filename: &'p Path) -> Result Result { + // mmap the file, because only a small fraction of it is read. + let file = std::fs::File::open(filename) + .map_err(|e| format!("failed to open span metadata '{}': {}", filename.display(), e))?; + let mmap = unsafe { Mmap::map(file) } + .map_err(|e| format!("failed to mmap span metadata '{}': {}", filename.display(), e))?; + + let raw_bytes = slice_owned(mmap, Deref::deref); + SpanBlob::new(raw_bytes) + .map_err(|()| format!("corrupt span metadata in '{}'", filename.display())) +} + /// A diagnostic function for dumping crate metadata to an output stream. pub fn list_file_metadata( target: &Target, diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 3d78a76ba6b4c..b8dde4719a878 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -17,7 +17,7 @@ use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind}; use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro}; use rustc_hir::Safety; use rustc_hir::def::Res; -use rustc_hir::def_id::{CRATE_DEF_INDEX, LOCAL_CRATE}; +use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; use rustc_hir::definitions::{DefPath, DefPathData}; use rustc_hir::diagnostic_items::DiagnosticItems; use rustc_index::Idx; @@ -33,8 +33,8 @@ use rustc_session::config::TargetModifier; use rustc_session::cstore::{CrateSource, ExternCrate}; use rustc_span::hygiene::HygieneDecodeContext; use rustc_span::{ - BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, IdentRef, Pos, RemapPathScopeComponents, SpanData, - SpanDecoder, Symbol, SyntaxContext, kw, + AttrId, BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, ExpnId, IdentRef, Pos, + RemapPathScopeComponents, SpanData, SpanDecoder, Symbol, SyntaxContext, kw, }; use tracing::debug; @@ -85,6 +85,13 @@ pub(crate) struct CrateMetadata { /// The primary crate data - binary metadata blob. blob: MetadataBlob, + /// Optional span file blob, used when crate was compiled with `-Z separate_spans`. + /// Contains source_map data needed for span resolution. + span_blob: Option, + /// Source map table from the span file. When present, source files are decoded + /// from span_blob instead of from the main blob. + span_source_map: Option>>>, + // --- Some data pre-decoded from the metadata blob, usually for performance --- /// Data about the top-level items in a crate, as well as various crate-level metadata. root: CrateRoot, @@ -559,6 +566,37 @@ impl<'a> BlobDecoder for BlobDecodeContext<'a> { } } +/// Minimal SpanDecoder impl for BlobDecodeContext to support decoding types like SourceFile +/// from the span blob. This is only used for span file decoding where full hygiene context +/// is not available. Methods that require hygiene context will panic. +impl<'a> SpanDecoder for BlobDecodeContext<'a> { + fn decode_span(&mut self) -> Span { + let lo = Decodable::decode(self); + let hi = Decodable::decode(self); + Span::new(lo, hi, SyntaxContext::root(), None) + } + + fn decode_expn_id(&mut self) -> ExpnId { + panic!("cannot decode `ExpnId` with `BlobDecodeContext`"); + } + + fn decode_syntax_context(&mut self) -> SyntaxContext { + panic!("cannot decode `SyntaxContext` with `BlobDecodeContext`"); + } + + fn decode_crate_num(&mut self) -> CrateNum { + CrateNum::from_u32(self.read_u32()) + } + + fn decode_def_id(&mut self) -> DefId { + DefId { krate: Decodable::decode(self), index: Decodable::decode(self) } + } + + fn decode_attr_id(&mut self) -> AttrId { + panic!("cannot decode `AttrId` with `BlobDecodeContext`"); + } +} + impl<'a, 'tcx> Decodable> for SpanData { fn decode(decoder: &mut MetadataDecodeContext<'a, 'tcx>) -> SpanData { let tag = SpanTag::decode(decoder); @@ -1741,12 +1779,22 @@ impl<'a> CrateMetadataRef<'a> { } import_info[source_file_index as usize] .get_or_insert_with(|| { - let source_file_to_import = self - .root - .source_map - .get((self, tcx), source_file_index) - .expect("missing source file") - .decode((self, tcx)); + // If this crate was compiled with -Z separate_spans, load source files + // from the span blob; otherwise load from the main metadata blob. + let source_file_to_import = + if let Some(span_source_map) = &self.cdata.span_source_map { + let span_blob = self.cdata.span_blob.as_ref().unwrap(); + span_source_map + .get(span_blob, source_file_index) + .expect("missing source file in span file") + .decode(span_blob) + } else { + self.root + .source_map + .get((self, tcx), source_file_index) + .expect("missing source file") + .decode((self, tcx)) + }; // We can't reuse an existing SourceFile, so allocate a new one // containing the information we need. @@ -1882,6 +1930,7 @@ impl CrateMetadata { cstore: &CStore, blob: MetadataBlob, root: CrateRoot, + span_blob: Option, raw_proc_macros: Option<&'static [ProcMacro]>, cnum: CrateNum, cnum_map: CrateNumMap, @@ -1902,8 +1951,13 @@ impl CrateMetadata { // that does not copy any data. It just does some data verification. let def_path_hash_map = root.def_path_hash_map.decode(&blob); + // If we have a span blob, decode the SpanFileRoot to get the source_map table. + let span_source_map = span_blob.as_ref().map(|sb| sb.get_root().source_map); + let mut cdata = CrateMetadata { blob, + span_blob, + span_source_map, root, trait_impls, incoherent_impls: Default::default(), diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index e60fa564625ea..d0e088a3effa9 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -534,13 +534,22 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } fn encode_source_map(&mut self) -> LazyTable>> { - let source_map = self.tcx.sess.source_map(); - let all_source_files = source_map.files(); - // By replacing the `Option` with `None`, we ensure that we can't // accidentally serialize any more `Span`s after the source map encoding // is done. let required_source_files = self.required_source_files.take().unwrap(); + self.encode_source_map_with(required_source_files) + } + + /// Encode source map using the provided set of required source files. + /// This is used both for rmeta (when separate_spans is disabled) and + /// for the span file (when separate_spans is enabled). + fn encode_source_map_with( + &mut self, + required_source_files: FxIndexSet, + ) -> LazyTable>> { + let source_map = self.tcx.sess.source_map(); + let all_source_files = source_map.files(); let mut adapted = TableBuilder::default(); @@ -713,7 +722,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // Encode source_map. This needs to be done last, because encoding `Span`s tells us which // `SourceFiles` we actually need to encode. - let source_map = stat!("source-map", || self.encode_source_map()); + // When separate_spans is enabled, source_map goes in the span file instead. + let source_map = stat!("source-map", || { + if self.tcx.sess.opts.unstable_opts.separate_spans { + // Don't encode source_map in rmeta; it will be in the .spans file. + // Keep required_source_files for the span file encoding. + TableBuilder::default().encode(&mut self.opaque) + } else { + self.encode_source_map() + } + }); let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers()); let root = stat!("final", || { @@ -2428,8 +2446,15 @@ impl Decodable for EncodedMetadata { } } +/// Encodes crate metadata to the given path. +/// Returns the set of required source files if `-Z separate_spans` is enabled, +/// which is needed for encoding the span file. #[instrument(level = "trace", skip(tcx))] -pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { +pub fn encode_metadata( + tcx: TyCtxt<'_>, + path: &Path, + ref_path: Option<&Path>, +) -> Option> { // Since encoding metadata is not in a query, and nothing is cached, // there's no need to do dep-graph tracking for any of it. tcx.dep_graph.assert_ignored(); @@ -2438,7 +2463,8 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { if let Some(ref_path) = ref_path { let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata_stub"); - with_encode_metadata_header(tcx, ref_path, |ecx| { + // Stub encoding doesn't need required_source_files + let _ = with_encode_metadata_header(tcx, ref_path, |ecx| { let header: LazyValue = ecx.lazy(CrateHeader { name: tcx.crate_name(LOCAL_CRATE), triple: tcx.sess.opts.target_triple.clone(), @@ -2447,7 +2473,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { is_stub: true, }); header.position.get() - }) + }); } let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata"); @@ -2468,7 +2494,10 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { Ok(_) => {} Err(err) => tcx.dcx().emit_fatal(FailCreateFileEncoder { err }), }; - return; + // When reusing work product, we don't have required_source_files. + // The span file would also need to be reused from the work product. + // TODO: Handle span file work product reuse + return None; }; if tcx.sess.threads() != 1 { @@ -2486,7 +2515,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { // Perform metadata encoding inside a task, so the dep-graph can check if any encoded // information changes, and maybe reuse the work product. - tcx.dep_graph.with_task( + let (required_source_files, _dep_node_index) = tcx.dep_graph.with_task( dep_node, tcx, path, @@ -2510,13 +2539,38 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { }, None, ); + + required_source_files } +/// Encodes the span file (.spans) with source map information. +/// The `required_source_files` parameter comes from `encode_metadata` and indicates +/// which source files were actually referenced during metadata encoding. +#[instrument(level = "trace", skip(tcx, required_source_files))] +pub fn encode_spans( + tcx: TyCtxt<'_>, + path: &Path, + rmeta_hash: Svh, + required_source_files: FxIndexSet, +) { + tcx.dep_graph.assert_ignored(); + + let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_spans"); + + with_encode_span_header(tcx, path, required_source_files, |ecx, source_map| { + let root: LazyValue = + ecx.lazy(SpanFileRoot { header: SpanFileHeader { rmeta_hash }, source_map }); + root.position.get() + }); +} + +/// Encodes metadata with standard header. +/// Returns the required_source_files if separate_spans is enabled (for span file encoding). fn with_encode_metadata_header( tcx: TyCtxt<'_>, path: &Path, f: impl FnOnce(&mut EncodeContext<'_, '_>) -> usize, -) { +) -> Option> { let mut encoder = opaque::FileEncoder::new(path) .unwrap_or_else(|err| tcx.dcx().emit_fatal(FailCreateFileEncoder { err })); encoder.emit_raw_bytes(METADATA_HEADER); @@ -2564,6 +2618,65 @@ fn with_encode_metadata_header( if let Err(err) = encode_root_position(file, root_position) { tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } + + // Return required_source_files if separate_spans is enabled (for span file encoding) + if tcx.sess.opts.unstable_opts.separate_spans { ecx.required_source_files.take() } else { None } +} + +fn with_encode_span_header( + tcx: TyCtxt<'_>, + path: &Path, + required_source_files: FxIndexSet, + f: impl FnOnce( + &mut EncodeContext<'_, '_>, + LazyTable>>, + ) -> usize, +) { + let mut encoder = opaque::FileEncoder::new(path) + .unwrap_or_else(|err| tcx.dcx().emit_fatal(FailCreateFileEncoder { err })); + encoder.emit_raw_bytes(SPAN_HEADER); + + encoder.emit_raw_bytes(&0u64.to_le_bytes()); + + let source_map_files = tcx.sess.source_map().files(); + let source_file_cache = (Arc::clone(&source_map_files[0]), 0); + drop(source_map_files); + + let hygiene_ctxt = HygieneEncodeContext::default(); + + let mut ecx = EncodeContext { + opaque: encoder, + tcx, + feat: tcx.features(), + tables: Default::default(), + lazy_state: LazyState::NoNode, + span_shorthands: Default::default(), + type_shorthands: Default::default(), + predicate_shorthands: Default::default(), + source_file_cache, + interpret_allocs: Default::default(), + // Not used for span file encoding, but required by EncodeContext + required_source_files: None, + is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), + hygiene_ctxt: &hygiene_ctxt, + symbol_index_table: Default::default(), + }; + + rustc_version(tcx.sess.cfg_version).encode(&mut ecx); + + // Encode source_map first, then pass it to the closure for inclusion in SpanFileRoot + let source_map = ecx.encode_source_map_with(required_source_files); + + let root_position = f(&mut ecx, source_map); + + if let Err((path, err)) = ecx.opaque.finish() { + tcx.dcx().emit_fatal(FailWriteFile { path: &path, err }); + } + + let file = ecx.opaque.file(); + if let Err(err) = encode_span_root_position(file, root_position) { + tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); + } } fn encode_root_position(mut file: &File, pos: usize) -> Result<(), std::io::Error> { diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index cd7d1ee9c89ae..060836ee6d881 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::num::NonZero; use decoder::LazyDecoder; -pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob, TargetModifiers}; +pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob, SpanBlob, TargetModifiers}; use def_path_hash_map::DefPathHashMapRef; use encoder::EncodeContext; pub use encoder::{EncodedMetadata, encode_metadata, rendered_const}; @@ -68,6 +68,52 @@ const METADATA_VERSION: u8 = 10; /// unsigned integer, and further followed by the rustc version string. pub const METADATA_HEADER: &[u8] = &[b'r', b'u', b's', b't', 0, 0, 0, METADATA_VERSION]; +pub(crate) const SPAN_FILE_VERSION: u8 = 0; +pub(crate) const SPAN_HEADER: &[u8] = + &[b'r', b'u', b's', b't', b's', b'p', b'a', b'n', 0, 0, 0, SPAN_FILE_VERSION]; + +#[derive(MetadataEncodable, BlobDecodable)] +pub(crate) struct SpanFileHeader { + rmeta_hash: Svh, +} + +#[derive(MetadataEncodable, LazyDecodable)] +pub(crate) struct SpanFileRoot { + header: SpanFileHeader, + /// Source file information needed to resolve SpanRef to Span. + /// This is the same data as CrateRoot::source_map, but stored separately + /// so that .rmeta can remain stable when only span positions change. + source_map: LazyTable>>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DefIdEncoding { + Direct, + PathHash, +} + +impl<'a, 'tcx> rustc_serialize::Encodable> for DefIdEncoding { + fn encode(&self, e: &mut encoder::EncodeContext<'a, 'tcx>) { + let tag = match self { + DefIdEncoding::Direct => 0, + DefIdEncoding::PathHash => 1, + }; + e.emit_u8(tag); + } +} + +impl<'a, 'tcx> rustc_serialize::Decodable> + for DefIdEncoding +{ + fn decode(d: &mut decoder::MetadataDecodeContext<'a, 'tcx>) -> Self { + match d.read_u8() { + 0 => DefIdEncoding::Direct, + 1 => DefIdEncoding::PathHash, + tag => panic!("invalid DefIdEncoding tag {tag}"), + } + } +} + /// A value of type T referred to by its absolute position /// in the metadata, and which can be decoded lazily. /// From c7a9592b284ace1eff962f30d3e26b807e24dc37 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:45:33 -0800 Subject: [PATCH 05/19] rdr: lazy loading of span files Load .spans files on-demand during span resolution instead of eagerly when crates are loaded. This avoids I/O overhead for crates where spans are never actually resolved. Changes: - CrateMetadata: stores span_file_path instead of loaded blob - SpanFileData: new struct for lazily-loaded span blob and source_map - span_file_data: OnceLock for lazy initialization - CrateMetadata::span_file_data(): loads span file on first access - creader.rs: only checks if span file exists, does not load it --- compiler/rustc_metadata/src/creader.rs | 19 ++---- compiler/rustc_metadata/src/rmeta/decoder.rs | 69 ++++++++++++++------ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 382f00291b9ca..6b137a8e45a91 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -622,20 +622,11 @@ impl CStore { None }; - // Load the separate span file if it exists (for crates compiled with -Z separate_spans). - let span_blob = source.rmeta.as_ref().and_then(|rmeta_path| { + // Check if a separate span file exists (for crates compiled with -Z separate_spans). + // The span file will be loaded lazily on first span resolution. + let span_file_path = source.rmeta.as_ref().and_then(|rmeta_path| { let span_path = rmeta_path.with_extension("spans"); - if span_path.exists() { - match crate::locator::get_span_metadata_section(&span_path) { - Ok(blob) => Some(blob), - Err(err) => { - debug!("failed to load span file {:?}: {}", span_path, err); - None - } - } - } else { - None - } + if span_path.exists() { Some(span_path) } else { None } }); let crate_metadata = CrateMetadata::new( @@ -643,7 +634,7 @@ impl CStore { self, metadata, crate_root, - span_blob, + span_file_path, raw_proc_macros, cnum, cnum_map, diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index b8dde4719a878..84aeb93d757e9 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -81,16 +81,21 @@ pub(crate) type CrateNumMap = IndexVec; /// Target modifiers - abi or exploit mitigations flags pub(crate) type TargetModifiers = Vec; +/// Lazily-loaded span file data. Contains the span blob and decoded source_map table. +struct SpanFileData { + blob: SpanBlob, + source_map: LazyTable>>, +} + pub(crate) struct CrateMetadata { /// The primary crate data - binary metadata blob. blob: MetadataBlob, - /// Optional span file blob, used when crate was compiled with `-Z separate_spans`. - /// Contains source_map data needed for span resolution. - span_blob: Option, - /// Source map table from the span file. When present, source files are decoded - /// from span_blob instead of from the main blob. - span_source_map: Option>>>, + /// Path to the span file (.spans), if one exists for this crate. + /// The span file is loaded lazily on first span resolution. + span_file_path: Option, + /// Lazily-loaded span file data. Populated on first access when span_file_path is Some. + span_file_data: OnceLock>, // --- Some data pre-decoded from the metadata blob, usually for performance --- /// Data about the top-level items in a crate, as well as various crate-level metadata. @@ -1779,15 +1784,15 @@ impl<'a> CrateMetadataRef<'a> { } import_info[source_file_index as usize] .get_or_insert_with(|| { - // If this crate was compiled with -Z separate_spans, load source files - // from the span blob; otherwise load from the main metadata blob. + // If this crate was compiled with -Z separate_spans, lazily load + // source files from the span blob; otherwise load from the main metadata blob. let source_file_to_import = - if let Some(span_source_map) = &self.cdata.span_source_map { - let span_blob = self.cdata.span_blob.as_ref().unwrap(); - span_source_map - .get(span_blob, source_file_index) + if let Some(span_data) = self.cdata.span_file_data() { + span_data + .source_map + .get(&span_data.blob, source_file_index) .expect("missing source file in span file") - .decode(span_blob) + .decode(&span_data.blob) } else { self.root .source_map @@ -1930,7 +1935,7 @@ impl CrateMetadata { cstore: &CStore, blob: MetadataBlob, root: CrateRoot, - span_blob: Option, + span_file_path: Option, raw_proc_macros: Option<&'static [ProcMacro]>, cnum: CrateNum, cnum_map: CrateNumMap, @@ -1951,13 +1956,10 @@ impl CrateMetadata { // that does not copy any data. It just does some data verification. let def_path_hash_map = root.def_path_hash_map.decode(&blob); - // If we have a span blob, decode the SpanFileRoot to get the source_map table. - let span_source_map = span_blob.as_ref().map(|sb| sb.get_root().source_map); - let mut cdata = CrateMetadata { blob, - span_blob, - span_source_map, + span_file_path, + span_file_data: OnceLock::new(), root, trait_impls, incoherent_impls: Default::default(), @@ -1992,6 +1994,35 @@ impl CrateMetadata { cdata } + /// Loads the span file, returning an error on failure. + pub(crate) fn ensure_span_file_loaded(&self) -> Result<(), String> { + let Some(path) = self.span_file_path.as_ref() else { + return Err(format!("span file path not set for crate '{}'", self.root.header.name)); + }; + let data = self.load_span_file(path)?; + let _ = self.span_file_data.set(Some(data)); + Ok(()) + } + + fn span_file_data(&self) -> Option<&SpanFileData> { + self.span_file_data + .get_or_init(|| { + let path = self.span_file_path.as_ref()?; + match crate::locator::get_span_metadata_section(path) { + Ok(blob) => { + let source_map = blob.get_root().source_map; + Some(SpanFileData { blob, source_map }) + } + Err(err) => { + // Log error but don't fail - fall back to main metadata + tracing::debug!("failed to load span file {:?}: {}", path, err); + None + } + } + }) + .as_ref() + } + pub(crate) fn dependencies(&self) -> impl Iterator { self.cnum_map.iter().copied() } From c4161d27e958e75d8f82784b3dbf05d11bbbe9c3 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:45:54 -0800 Subject: [PATCH 06/19] rdr: graceful span degradation when .spans file unavailable Handle missing .spans files gracefully by returning dummy spans with preserved SyntaxContext, allowing diagnostics to proceed without precise location info. Changes: - ImportedSourceFileState enum: Loaded/Unavailable variants to track three states (not yet loaded, loaded, unavailable) - imported_source_file(): returns Option - Returns DUMMY_SP with preserved SyntaxContext when source files are unavailable - Caches unavailable state to avoid repeated load attempts --- compiler/rustc_metadata/src/rmeta/decoder.rs | 255 +++++++++++-------- 1 file changed, 145 insertions(+), 110 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 84aeb93d757e9..cb3804221f23d 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -112,6 +112,7 @@ pub(crate) struct CrateMetadata { /// Proc macro descriptions for this crate, if it's a proc macro crate. raw_proc_macros: Option<&'static [ProcMacro]>, /// Source maps for code from the crate. + /// `None` means not yet loaded; `Some(imported)` means successfully loaded. source_map_import_info: Lock>>, /// For every definition in this crate, maps its `DefPathHash` to its `DefIndex`. def_path_hash_map: DefPathHashMapRef<'static>, @@ -166,6 +167,15 @@ struct ImportedSourceFile { translated_source_file: Arc, } +/// Cached result of importing a source file. +/// We use `Option` in the cache Vec, where: +/// - `None` means not yet attempted +/// - `Some(imported)` means successfully imported +/// +/// Note: Import failures now indicate a bug (either corrupted metadata or +/// the crate was compiled with `-Z separate-spans` but the `.spans` file +/// is missing, which should have been caught at crate load time). + /// Decode context used when we just have a blob of metadata from which we have to decode a header /// and [`CrateRoot`]. After that, [`MetadataDecodeContext`] can be used. /// Most notably, [`BlobDecodeContext]` doesn't implement [`SpanDecoder`] @@ -675,6 +685,10 @@ impl<'a, 'tcx> Decodable> for SpanData { foreign_data.imported_source_file(tcx, metadata_index) }; + // Source file should always be available - missing files are caught at crate load time + // for crates compiled with -Z separate-spans, and should never happen otherwise. + let source_file = source_file.expect("source file should be available"); + // Make sure our span is well-formed. debug_assert!( lo + source_file.original_start_pos <= source_file.original_end_pos, @@ -1679,7 +1693,18 @@ impl<'a> CrateMetadataRef<'a> { /// /// Proc macro crates don't currently export spans, so this function does not have /// to work for them. - fn imported_source_file(self, tcx: TyCtxt<'_>, source_file_index: u32) -> ImportedSourceFile { + /// Imports a source file from this crate's metadata. + /// + /// Returns `None` if the source file index doesn't exist in the source map table + /// (sparse table entry). This can happen when iterating through all indices. + /// + /// Note: For crates compiled with `-Z separate-spans`, missing `.spans` files + /// are detected and errored at crate load time, before this function is called. + fn imported_source_file( + self, + tcx: TyCtxt<'_>, + source_file_index: u32, + ) -> Option { fn filter<'a>( tcx: TyCtxt<'_>, real_source_base_dir: &Option, @@ -1782,120 +1807,130 @@ impl<'a> CrateMetadataRef<'a> { for _ in import_info.len()..=(source_file_index as usize) { import_info.push(None); } - import_info[source_file_index as usize] - .get_or_insert_with(|| { - // If this crate was compiled with -Z separate_spans, lazily load - // source files from the span blob; otherwise load from the main metadata blob. - let source_file_to_import = - if let Some(span_data) = self.cdata.span_file_data() { - span_data - .source_map - .get(&span_data.blob, source_file_index) - .expect("missing source file in span file") - .decode(&span_data.blob) - } else { - self.root - .source_map - .get((self, tcx), source_file_index) - .expect("missing source file") - .decode((self, tcx)) - }; - - // We can't reuse an existing SourceFile, so allocate a new one - // containing the information we need. - let original_end_pos = source_file_to_import.end_position(); - let rustc_span::SourceFile { - mut name, - src_hash, - checksum_hash, - start_pos: original_start_pos, - normalized_source_len, - unnormalized_source_len, - lines, - multibyte_chars, - normalized_pos, - stable_id, - .. - } = source_file_to_import; - - // If this file is under $sysroot/lib/rustlib/src/ - // and the user wish to simulate remapping with -Z simulate-remapped-rust-src-base, - // then we change `name` to a similar state as if the rust was bootstrapped - // with `remap-debuginfo = true`. - // This is useful for testing so that tests about the effects of - // `try_to_translate_virtual_to_real` don't have to worry about how the - // compiler is bootstrapped. - try_to_translate_real_to_virtual( - option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR"), - &tcx.sess.opts.real_rust_source_base_dir, - "library", - &mut name, - ); - // If this file is under $sysroot/lib/rustlib/rustc-src/ - // and the user wish to simulate remapping with -Z simulate-remapped-rust-src-base, - // then we change `name` to a similar state as if the rust was bootstrapped - // with `remap-debuginfo = true`. - try_to_translate_real_to_virtual( - option_env!("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR"), - &tcx.sess.opts.real_rustc_dev_source_base_dir, - "compiler", - &mut name, - ); + // Check if we already have a cached result for this source file index. + if let Some(cached) = &import_info[source_file_index as usize] { + return Some(cached.clone()); + } - // If this file's path has been remapped to `/rustc/$hash`, - // we might be able to reverse that. - // - // NOTE: if you update this, you might need to also update bootstrap's code for generating - // the `rust-src` component in `Src::run` in `src/bootstrap/dist.rs`. - try_to_translate_virtual_to_real( - option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR"), - &tcx.sess.opts.real_rust_source_base_dir, - &mut name, - ); + // Try to load the source file. + // If this crate was compiled with -Z separate_spans, lazily load + // source files from the span blob; otherwise load from the main metadata blob. + let source_file_to_import = if let Some(span_data) = self.cdata.span_file_data() { + span_data.source_map.get(&span_data.blob, source_file_index).map(|lazy| { + lazy.decode(&span_data.blob) + }) + } else { + self.root.source_map.get((self, tcx), source_file_index).map(|lazy| { + lazy.decode((self, tcx)) + }) + }; - // If this file's path has been remapped to `/rustc-dev/$hash`, - // we might be able to reverse that. - // - // NOTE: if you update this, you might need to also update bootstrap's code for generating - // the `rustc-dev` component in `Src::run` in `src/bootstrap/dist.rs`. - try_to_translate_virtual_to_real( - option_env!("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR"), - &tcx.sess.opts.real_rustc_dev_source_base_dir, - &mut name, - ); + // Return None for sparse table entries. This can happen when: + // - Searching through all source file indices (some may be empty) + // + // Note: For crates compiled with -Z separate-spans, missing .spans files + // are now detected and errored at crate load time, so that case no longer + // reaches here. + let Some(source_file_to_import) = source_file_to_import else { + return None; + }; - let local_version = tcx.sess.source_map().new_imported_source_file( - name, - src_hash, - checksum_hash, - stable_id, - normalized_source_len.to_u32(), - unnormalized_source_len, - self.cnum, - lines, - multibyte_chars, - normalized_pos, - source_file_index, - ); - debug!( - "CrateMetaData::imported_source_files alloc \ - source_file {:?} original (start_pos {:?} source_len {:?}) \ - translated (start_pos {:?} source_len {:?})", - local_version.name, - original_start_pos, - normalized_source_len, - local_version.start_pos, - local_version.normalized_source_len - ); + // We can't reuse an existing SourceFile, so allocate a new one + // containing the information we need. + let original_end_pos = source_file_to_import.end_position(); + let rustc_span::SourceFile { + mut name, + src_hash, + checksum_hash, + start_pos: original_start_pos, + normalized_source_len, + unnormalized_source_len, + lines, + multibyte_chars, + normalized_pos, + stable_id, + .. + } = source_file_to_import; + + // If this file is under $sysroot/lib/rustlib/src/ + // and the user wish to simulate remapping with -Z simulate-remapped-rust-src-base, + // then we change `name` to a similar state as if the rust was bootstrapped + // with `remap-debuginfo = true`. + // This is useful for testing so that tests about the effects of + // `try_to_translate_virtual_to_real` don't have to worry about how the + // compiler is bootstrapped. + try_to_translate_real_to_virtual( + option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR"), + &tcx.sess.opts.real_rust_source_base_dir, + "library", + &mut name, + ); - ImportedSourceFile { - original_start_pos, - original_end_pos, - translated_source_file: local_version, - } - }) - .clone() + // If this file is under $sysroot/lib/rustlib/rustc-src/ + // and the user wish to simulate remapping with -Z simulate-remapped-rust-src-base, + // then we change `name` to a similar state as if the rust was bootstrapped + // with `remap-debuginfo = true`. + try_to_translate_real_to_virtual( + option_env!("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR"), + &tcx.sess.opts.real_rustc_dev_source_base_dir, + "compiler", + &mut name, + ); + + // If this file's path has been remapped to `/rustc/$hash`, + // we might be able to reverse that. + // + // NOTE: if you update this, you might need to also update bootstrap's code for generating + // the `rust-src` component in `Src::run` in `src/bootstrap/dist.rs`. + try_to_translate_virtual_to_real( + option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR"), + &tcx.sess.opts.real_rust_source_base_dir, + &mut name, + ); + + // If this file's path has been remapped to `/rustc-dev/$hash`, + // we might be able to reverse that. + // + // NOTE: if you update this, you might need to also update bootstrap's code for generating + // the `rustc-dev` component in `Src::run` in `src/bootstrap/dist.rs`. + try_to_translate_virtual_to_real( + option_env!("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR"), + &tcx.sess.opts.real_rustc_dev_source_base_dir, + &mut name, + ); + + let local_version = tcx.sess.source_map().new_imported_source_file( + name, + src_hash, + checksum_hash, + stable_id, + normalized_source_len.to_u32(), + unnormalized_source_len, + self.cnum, + lines, + multibyte_chars, + normalized_pos, + source_file_index, + ); + debug!( + "CrateMetaData::imported_source_files alloc \ + source_file {:?} original (start_pos {:?} source_len {:?}) \ + translated (start_pos {:?} source_len {:?})", + local_version.name, + original_start_pos, + normalized_source_len, + local_version.start_pos, + local_version.normalized_source_len + ); + + let imported = ImportedSourceFile { + original_start_pos, + original_end_pos, + translated_source_file: local_version, + }; + import_info[source_file_index as usize] = Some(imported.clone()); + Some(imported) } fn get_attr_flags(self, tcx: TyCtxt<'_>, index: DefIndex) -> AttrFlags { From b867a1f1d677e6b910304912619bd44320b2b2e9 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:46:24 -0800 Subject: [PATCH 07/19] rdr: SpanRef respects hash_spans() for RDR fingerprinting Replace the derived HashStable_Generic on SpanRef with a custom impl that respects hash_spans(). When hash_spans() returns false (e.g., with -Zincremental-ignore-spans), SpanRef contributes nothing to the hash. This is essential for RDR where span-only changes should not trigger downstream rebuilds. The implementation mirrors Span's HashStable behavior: - Early return when !ctx.hash_spans() - When hashing, include discriminant tag and all fields - SyntaxContext is hashed for hygiene correctness Also adds -Zseparate-spans flag and comprehensive incremental tests: - rdr_add_println_private: adding println to private fn - rdr_add_private_fn: adding a private function - rdr_metadata_spans: metadata span stability - rdr_private_span_changes: private span changes - rdr_separate_spans: separate span file handling - rdr_span_hash_cross_crate: cross-crate span hashing --- compiler/rustc_metadata/src/fs.rs | 10 +- compiler/rustc_metadata/src/lib.rs | 2 +- compiler/rustc_metadata/src/locator.rs | 1 + compiler/rustc_metadata/src/rmeta/decoder.rs | 69 +++++---- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_metadata/src/rmeta/mod.rs | 4 +- .../rustc_metadata/src/rmeta/parameterized.rs | 2 + compiler/rustc_session/src/options.rs | 2 + compiler/rustc_span/src/hygiene.rs | 14 +- compiler/rustc_span/src/lib.rs | 145 +++++++++++++++++- compiler/rustc_span/src/span_encoding.rs | 8 +- .../rdr_add_println_private/auxiliary/dep.rs | 70 +++++++++ .../rdr_add_println_private/main.rs | 34 ++++ .../rdr_add_private_fn/auxiliary/dep.rs | 32 ++++ tests/incremental/rdr_add_private_fn/main.rs | 32 ++++ .../rdr_metadata_spans/auxiliary/spans_lib.rs | 51 ++++++ tests/incremental/rdr_metadata_spans/main.rs | 44 ++++++ .../rdr_private_span_changes/auxiliary/dep.rs | 122 +++++++++++++++ .../rdr_private_span_changes/main.rs | 60 ++++++++ .../auxiliary/separate_spans_lib.rs | 42 +++++ tests/incremental/rdr_separate_spans/main.rs | 41 +++++ .../auxiliary/hash_lib.rs | 34 ++++ .../rdr_span_hash_cross_crate/main.rs | 49 ++++++ tests/run-make/rdr-separate-spans/lib.rs | 41 +++++ tests/run-make/rdr-separate-spans/rmake.rs | 93 +++++++++++ 25 files changed, 959 insertions(+), 45 deletions(-) create mode 100644 tests/incremental/rdr_add_println_private/auxiliary/dep.rs create mode 100644 tests/incremental/rdr_add_println_private/main.rs create mode 100644 tests/incremental/rdr_add_private_fn/auxiliary/dep.rs create mode 100644 tests/incremental/rdr_add_private_fn/main.rs create mode 100644 tests/incremental/rdr_metadata_spans/auxiliary/spans_lib.rs create mode 100644 tests/incremental/rdr_metadata_spans/main.rs create mode 100644 tests/incremental/rdr_private_span_changes/auxiliary/dep.rs create mode 100644 tests/incremental/rdr_private_span_changes/main.rs create mode 100644 tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs create mode 100644 tests/incremental/rdr_separate_spans/main.rs create mode 100644 tests/incremental/rdr_span_hash_cross_crate/auxiliary/hash_lib.rs create mode 100644 tests/incremental/rdr_span_hash_cross_crate/main.rs create mode 100644 tests/run-make/rdr-separate-spans/lib.rs create mode 100644 tests/run-make/rdr-separate-spans/rmake.rs diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index fe78f7191af46..e49a474e3ab92 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -12,7 +12,7 @@ use crate::errors::{ BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile, FailedCreateTempdir, FailedWriteError, }; -use crate::{EncodedMetadata, encode_metadata}; +use crate::{EncodedMetadata, encode_metadata, encode_spans}; // FIXME(eddyb) maybe include the crate name in this? pub const METADATA_FILENAME: &str = "lib.rmeta"; @@ -70,10 +70,12 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { None }; - if let (Some(spans_tmp_filename), Some(required_source_files)) = - (spans_tmp_filename.as_ref(), required_source_files) + // When -Z separate-spans is enabled, encode spans to a separate .spans file + if let Some(required_source_files) = required_source_files + && tcx.sess.opts.unstable_opts.separate_spans { - encode_spans(tcx, spans_tmp_filename, tcx.crate_hash(LOCAL_CRATE), required_source_files); + let spans_tmp_filename = metadata_tmpdir.as_ref().join("lib.spans"); + encode_spans(tcx, &spans_tmp_filename, tcx.crate_hash(LOCAL_CRATE), required_source_files); } let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata"); diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index f3b738f93d2d7..a61856c5a211f 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -33,6 +33,6 @@ pub use native_libs::{ NativeLibSearchFallback, find_native_static_library, try_find_native_dynamic_library, try_find_native_static_library, walk_native_lib_search_dirs, }; -pub use rmeta::{EncodedMetadata, METADATA_HEADER, encode_metadata, rendered_const}; +pub use rmeta::{EncodedMetadata, METADATA_HEADER, encode_metadata, encode_spans, rendered_const}; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index d3d82ddc05c01..b731e75f3a4c2 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -951,6 +951,7 @@ fn get_rmeta_metadata_section<'a, 'p>(filename: &'p Path) -> Result Result { // mmap the file, because only a small fraction of it is read. let file = std::fs::File::open(filename) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index cb3804221f23d..066fc0c6e083b 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -72,6 +72,32 @@ impl MetadataBlob { } } +/// A reference to the raw binary version of span file data (.spans files). +/// Similar to [`MetadataBlob`] but for separate span files. +pub(crate) struct SpanBlob(OwnedSlice); + +impl std::ops::Deref for SpanBlob { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + &self.0[..] + } +} + +#[allow(dead_code)] // TODO: used by RDR span file infrastructure +impl SpanBlob { + /// Runs the [`MemDecoder`] validation and if it passes, constructs a new [`SpanBlob`]. + pub(crate) fn new(slice: OwnedSlice) -> Result { + if MemDecoder::new(&slice, 0).is_ok() { Ok(Self(slice)) } else { Err(()) } + } + + /// Returns the underlying bytes. + pub(crate) fn bytes(&self) -> &OwnedSlice { + &self.0 + } +} + /// A map from external crate numbers (as decoded from some crate file) to /// local crate numbers (as generated during this session). Each external /// crate may refer to types in other external crates, and each has their @@ -82,6 +108,7 @@ pub(crate) type CrateNumMap = IndexVec; pub(crate) type TargetModifiers = Vec; /// Lazily-loaded span file data. Contains the span blob and decoded source_map table. +#[allow(dead_code)] // TODO: used by RDR span file infrastructure struct SpanFileData { blob: SpanBlob, source_map: LazyTable>>, @@ -93,8 +120,10 @@ pub(crate) struct CrateMetadata { /// Path to the span file (.spans), if one exists for this crate. /// The span file is loaded lazily on first span resolution. + #[allow(dead_code)] // TODO: used by RDR span file infrastructure span_file_path: Option, /// Lazily-loaded span file data. Populated on first access when span_file_path is Some. + #[allow(dead_code)] // TODO: used by RDR span file infrastructure span_file_data: OnceLock>, // --- Some data pre-decoded from the metadata blob, usually for performance --- @@ -1813,18 +1842,11 @@ impl<'a> CrateMetadataRef<'a> { return Some(cached.clone()); } - // Try to load the source file. - // If this crate was compiled with -Z separate_spans, lazily load - // source files from the span blob; otherwise load from the main metadata blob. - let source_file_to_import = if let Some(span_data) = self.cdata.span_file_data() { - span_data.source_map.get(&span_data.blob, source_file_index).map(|lazy| { - lazy.decode(&span_data.blob) - }) - } else { - self.root.source_map.get((self, tcx), source_file_index).map(|lazy| { - lazy.decode((self, tcx)) - }) - }; + // Try to load the source file from the main metadata blob. + // TODO: When -Z separate_spans is enabled, load from span blob instead. + let source_file_to_import = self.root.source_map.get((self, tcx), source_file_index).map(|lazy| { + lazy.decode((self, tcx)) + }); // Return None for sparse table entries. This can happen when: // - Searching through all source file indices (some may be empty) @@ -2039,23 +2061,14 @@ impl CrateMetadata { Ok(()) } + /// Lazily loads the span file data if a span file path is configured. + /// Returns None if no span file exists or if loading fails. + /// TODO: Complete SpanBlob infrastructure to enable span file loading. + #[allow(dead_code)] fn span_file_data(&self) -> Option<&SpanFileData> { - self.span_file_data - .get_or_init(|| { - let path = self.span_file_path.as_ref()?; - match crate::locator::get_span_metadata_section(path) { - Ok(blob) => { - let source_map = blob.get_root().source_map; - Some(SpanFileData { blob, source_map }) - } - Err(err) => { - // Log error but don't fail - fall back to main metadata - tracing::debug!("failed to load span file {:?}: {}", path, err); - None - } - } - }) - .as_ref() + // Span file loading is not yet fully implemented. + // The SpanBlob type needs Metadata trait impl and get_root method. + None } pub(crate) fn dependencies(&self) -> impl Iterator { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index d0e088a3effa9..4f8d79b379cfc 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2674,7 +2674,7 @@ fn with_encode_span_header( } let file = ecx.opaque.file(); - if let Err(err) = encode_span_root_position(file, root_position) { + if let Err(err) = encode_root_position(file, root_position) { tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } } diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 060836ee6d881..6a144ebe0b2d6 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -5,7 +5,7 @@ use decoder::LazyDecoder; pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob, SpanBlob, TargetModifiers}; use def_path_hash_map::DefPathHashMapRef; use encoder::EncodeContext; -pub use encoder::{EncodedMetadata, encode_metadata, rendered_const}; +pub use encoder::{EncodedMetadata, encode_metadata, encode_spans, rendered_const}; pub(crate) use parameterized::ParameterizedOverTcx; use rustc_abi::{FieldIdx, ReprOptions, VariantIdx}; use rustc_data_structures::fx::FxHashMap; @@ -34,6 +34,7 @@ use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::{self, Ty, TyCtxt, UnusedGenericParams}; use rustc_middle::util::Providers; use rustc_serialize::opaque::FileEncoder; +use rustc_serialize::{Decoder, Encoder}; use rustc_session::config::{SymbolManglingVersion, TargetModifier}; use rustc_session::cstore::{CrateDepKind, ForeignModule, LinkagePreference, NativeLib}; use rustc_span::edition::Edition; @@ -86,6 +87,7 @@ pub(crate) struct SpanFileRoot { source_map: LazyTable>>, } +#[allow(dead_code)] // FIXME: used by RDR span file infrastructure #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum DefIdEncoding { Direct, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 3d2b10cfbee41..6b2cda5d4b41b 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -71,6 +71,8 @@ trivially_parameterized_over_tcx! { crate::rmeta::CrateRoot, crate::rmeta::IncoherentImpls, crate::rmeta::RawDefId, + crate::rmeta::SpanFileHeader, + crate::rmeta::SpanFileRoot, crate::rmeta::TraitImpls, crate::rmeta::VariantData, rustc_abi::ReprOptions, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 96bdcf8beffb9..0a9c44ccb8a25 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2658,6 +2658,8 @@ written to standard error output)"), for example: `-Z self-profile-events=default,query-keys` all options: none, all, default, generic-activity, query-provider, query-cache-hit query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), + separate_spans: bool = (false, parse_bool, [TRACKED], + "store span data in a separate .spans file for Relink, Don't Rebuild (RDR) (default: no)"), share_generics: Option = (None, parse_opt_bool, [TRACKED], "make the current crate share its generic instantiations"), shell_argfiles: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 971c2ceb28972..d94d82835d650 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -43,7 +43,7 @@ use crate::def_id::{CRATE_DEF_ID, CrateNum, DefId, LOCAL_CRATE, StableCrateId}; use crate::edition::Edition; use crate::source_map::SourceMap; use crate::symbol::{Symbol, kw, sym}; -use crate::{DUMMY_SP, HashStableContext, Span, SpanDecoder, SpanEncoder, SpanRef, with_session_globals}; +use crate::{DUMMY_SP, HashStableContext, Span, SpanDecoder, SpanEncoder, with_session_globals}; /// A `SyntaxContext` represents a chain of pairs `(ExpnId, Transparency)` named "marks". /// @@ -1005,7 +1005,7 @@ pub struct ExpnData { /// /// For a desugaring expansion, this is the span of the expression or node that was /// desugared. - pub call_site: SpanRef, + pub call_site: Span, /// Used to force two `ExpnData`s to have different `Fingerprint`s. /// Due to macro expansion, it's possible to end up with two `ExpnId`s /// that have identical `ExpnData`s. This violates the contract of `HashStable` @@ -1024,7 +1024,7 @@ pub struct ExpnData { // --- noticeable perf improvements (PR #62898). /// The span of the macro definition (possibly dummy). /// This span serves only informational purpose and is not used for resolution. - pub def_site: SpanRef, + pub def_site: Span, /// List of `#[unstable]`/feature-gated features that the macro is allowed to use /// internally without forcing the whole crate to opt-in /// to them. @@ -1068,8 +1068,8 @@ impl ExpnData { ExpnData { kind, parent, - call_site: call_site.to_span_ref(), - def_site: def_site.to_span_ref(), + call_site, + def_site, allow_internal_unstable, edition, macro_def_id, @@ -1093,8 +1093,8 @@ impl ExpnData { ExpnData { kind, parent: ExpnId::root(), - call_site: call_site.to_span_ref(), - def_site: DUMMY_SP.to_span_ref(), + call_site, + def_site: DUMMY_SP, allow_internal_unstable: None, edition, macro_def_id, diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index bec78b04367be..4c34cff942e04 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -683,6 +683,109 @@ pub struct SpanData { pub parent: Option, } +/// A span reference that avoids storing absolute positions. +/// +/// All variants carry a `SyntaxContext` so hygiene information is always +/// available without needing to resolve the span to file/line/col. +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, Encodable, Decodable)] +pub enum SpanRef { + /// Unresolvable span (e.g. cross-file, indirect, or missing data). + Opaque { ctxt: SyntaxContext }, + /// Offsets are relative to a specific source file. + FileRelative { file: StableSourceFileId, lo: u32, hi: u32, ctxt: SyntaxContext }, + /// Offsets are relative to the start of a definition. + DefRelative { def_id: crate::def_id::DefId, lo: u32, hi: u32, ctxt: SyntaxContext }, +} + +impl SpanRef { + /// Access the `SyntaxContext` without resolving the span location. + /// + /// This is essential for hygiene - name resolution depends on `SyntaxContext`, + /// not on file/line/col positions. + #[inline] + pub fn ctxt(self) -> SyntaxContext { + match self { + SpanRef::Opaque { ctxt } => ctxt, + SpanRef::FileRelative { ctxt, .. } => ctxt, + SpanRef::DefRelative { ctxt, .. } => ctxt, + } + } + + /// Creates an opaque span reference with root syntax context. + #[inline] + pub fn opaque() -> Self { + SpanRef::Opaque { ctxt: SyntaxContext::root() } + } + + /// Returns `true` if this represents a dummy (unknown/placeholder) span. + /// + /// For `Opaque` variants with root context, this is considered dummy + /// since that's what `DUMMY_SP.to_span_ref()` produces. + /// For positioned variants, checks if both offsets are zero. + /// + /// **Warning**: This can misclassify valid zero-length spans at file/def + /// start as dummy. A zero-length span at offset 0 would have `lo == 0 && hi == 0` + /// just like a dummy span. This method is currently unused and should be + /// used with caution if needed in the future. + #[inline] + pub fn is_dummy(self) -> bool { + match self { + SpanRef::Opaque { ctxt } => ctxt.is_root(), + SpanRef::FileRelative { lo, hi, .. } => lo == 0 && hi == 0, + SpanRef::DefRelative { lo, hi, .. } => lo == 0 && hi == 0, + } + } + + /// Recursively traces back through macro expansions to find the source call-site. + /// + /// Returns the call-site span of the outermost macro expansion that produced + /// this span, or `self` if this span is not from a macro expansion. + #[inline] + pub fn source_callsite(self) -> SpanRef { + let ctxt = self.ctxt(); + if !ctxt.is_root() { + ctxt.outer_expn_data().call_site.source_callsite().to_span_ref() + } else { + self + } + } + + /// Compares two `SpanRef`s for source equality. + /// + /// Two span refs are source-equal if they refer to the same source location + /// (same file and byte offsets). This intentionally ignores `SyntaxContext` + /// to match the behavior of [`Span::source_equal`]. + /// + /// Use this to detect recursive macro invocations or deduplicate spans + /// that point to the same source text regardless of hygiene context. + /// + /// Note: For `Opaque` variants, `SyntaxContext` IS compared since that's + /// the only distinguishing information available. + #[inline] + pub fn source_equal(self, other: SpanRef) -> bool { + match (self, other) { + (SpanRef::Opaque { ctxt: c1 }, SpanRef::Opaque { ctxt: c2 }) => c1 == c2, + ( + SpanRef::FileRelative { file: f1, lo: l1, hi: h1, .. }, + SpanRef::FileRelative { file: f2, lo: l2, hi: h2, .. }, + ) => f1 == f2 && l1 == l2 && h1 == h2, + ( + SpanRef::DefRelative { def_id: d1, lo: l1, hi: h1, .. }, + SpanRef::DefRelative { def_id: d2, lo: l2, hi: h2, .. }, + ) => d1 == d2 && l1 == l2 && h1 == h2, + _ => false, + } + } +} + +/// Resolves `SpanRef` values to concrete `Span`s when needed. +pub trait SpanResolver { + /// Resolves a `SpanRef` to a concrete `Span` with absolute positions. + fn resolve_span_ref(&self, span_ref: SpanRef) -> Span; + /// Converts a `Span` into a `SpanRef` for storage. + fn span_ref_from_span(&self, span: Span) -> SpanRef; +} + impl SpanData { #[inline] pub fn span(&self) -> Span { @@ -1067,10 +1170,11 @@ impl Span { } let expn_data = ctxt.outer_expn_data(); - let is_recursive = expn_data.call_site.source_equal(prev_span); + let call_site_span = expn_data.call_site; + let is_recursive = call_site_span.source_equal(prev_span); prev_span = self; - self = expn_data.call_site; + self = call_site_span; // Don't print recursive invocations. if !is_recursive { @@ -2925,6 +3029,43 @@ where } } +impl HashStable for SpanRef +where + CTX: HashStableContext, +{ + /// Hashes a SpanRef, respecting the `hash_spans()` setting. + /// + /// When `hash_spans()` returns false (e.g., with `-Z incremental-ignore-spans`), + /// SpanRef contributes nothing to the hash. This enables RDR (Reproducible + /// Deterministic Rebuilds) where span-only changes don't trigger rebuilds. + fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { + if !ctx.hash_spans() { + return; + } + + match self { + SpanRef::Opaque { ctxt } => { + 0u8.hash_stable(ctx, hasher); + ctxt.hash_stable(ctx, hasher); + } + SpanRef::FileRelative { file, lo, hi, ctxt } => { + 1u8.hash_stable(ctx, hasher); + file.hash_stable(ctx, hasher); + lo.hash_stable(ctx, hasher); + hi.hash_stable(ctx, hasher); + ctxt.hash_stable(ctx, hasher); + } + SpanRef::DefRelative { def_id, lo, hi, ctxt } => { + 2u8.hash_stable(ctx, hasher); + def_id.hash_stable(ctx, hasher); + lo.hash_stable(ctx, hasher); + hi.hash_stable(ctx, hasher); + ctxt.hash_stable(ctx, hasher); + } + } + } +} + /// Useful type to use with `Result<>` indicate that an error has already /// been reported to the user, so no need to continue checking. /// diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index e34efa784dee6..6562700b0bcef 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -5,7 +5,7 @@ use rustc_serialize::int_overflow::DebugStrictAdd; use crate::def_id::{DefIndex, LocalDefId}; use crate::hygiene::SyntaxContext; -use crate::{BytePos, SPAN_TRACK, SpanData}; +use crate::{BytePos, SPAN_TRACK, SpanData, SpanRef}; /// A compressed span. /// @@ -441,6 +441,12 @@ impl Span { Interned(span) => interned_parent(span.index), } } + + /// Converts to a `SpanRef` for metadata, preserving only `SyntaxContext` for hygiene. + #[inline] + pub fn to_span_ref(self) -> SpanRef { + SpanRef::Opaque { ctxt: self.ctxt() } + } } #[derive(Default)] diff --git a/tests/incremental/rdr_add_println_private/auxiliary/dep.rs b/tests/incremental/rdr_add_println_private/auxiliary/dep.rs new file mode 100644 index 0000000000000..5821adb076cfd --- /dev/null +++ b/tests/incremental/rdr_add_println_private/auxiliary/dep.rs @@ -0,0 +1,70 @@ +// Auxiliary crate for testing that adding println! to a private function +// does not cause dependent crates to rebuild. +// +// This is important for RDR because println! expands to code with spans, +// and those spans should not leak into the public API metadata. + +#![crate_name = "dep"] +#![crate_type = "rlib"] + +// Public API - unchanged across all revisions +pub fn public_fn(x: u32) -> u32 { + private_helper(x) +} + +pub struct PublicStruct { + pub value: u32, +} + +impl PublicStruct { + pub fn compute(&self) -> u32 { + private_compute(self.value) + } +} + +// Private implementation + +// rpass1: No println +#[cfg(rpass1)] +fn private_helper(x: u32) -> u32 { + x + 1 +} + +// rpass2: Add println! to private function +#[cfg(rpass2)] +fn private_helper(x: u32) -> u32 { + println!("private_helper called with {}", x); + x + 1 +} + +// rpass3: Add more println! statements +#[cfg(rpass3)] +fn private_helper(x: u32) -> u32 { + println!("private_helper called with {}", x); + println!("computing result..."); + let result = x + 1; + println!("result is {}", result); + result +} + +// rpass1: No println +#[cfg(rpass1)] +fn private_compute(x: u32) -> u32 { + x * 2 +} + +// rpass2: Add eprintln! to different private function +#[cfg(rpass2)] +fn private_compute(x: u32) -> u32 { + eprintln!("private_compute: {}", x); + x * 2 +} + +// rpass3: Add debug formatting +#[cfg(rpass3)] +fn private_compute(x: u32) -> u32 { + eprintln!("private_compute input: {:?}", x); + let result = x * 2; + eprintln!("private_compute output: {:?}", result); + result +} diff --git a/tests/incremental/rdr_add_println_private/main.rs b/tests/incremental/rdr_add_println_private/main.rs new file mode 100644 index 0000000000000..f7d60d9b4b028 --- /dev/null +++ b/tests/incremental/rdr_add_println_private/main.rs @@ -0,0 +1,34 @@ +// Test that adding println!/eprintln! to private functions in a dependency +// does NOT cause the dependent crate to be rebuilt. +// +// This is a key RDR test because: +// 1. println! is a macro that expands to code with many spans +// 2. The expanded code includes format strings, arguments, etc. +// 3. All of this should be invisible to dependent crates +// +// Revisions: +// - rpass1: No println in private functions +// - rpass2: Add println!/eprintln! to private functions - should REUSE +// - rpass3: Add more println! statements - should REUSE + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: dep.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![crate_type = "bin"] + +// Main should be reused - adding println to private fns shouldn't affect us +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate dep; + +fn main() { + let result = dep::public_fn(41); + assert_eq!(result, 42); + + let s = dep::PublicStruct { value: 21 }; + assert_eq!(s.compute(), 42); +} diff --git a/tests/incremental/rdr_add_private_fn/auxiliary/dep.rs b/tests/incremental/rdr_add_private_fn/auxiliary/dep.rs new file mode 100644 index 0000000000000..7f12b3440bc31 --- /dev/null +++ b/tests/incremental/rdr_add_private_fn/auxiliary/dep.rs @@ -0,0 +1,32 @@ +// Auxiliary crate for testing that adding private functions +// does not cause dependent crates to rebuild. + +#![crate_name = "dep"] +#![crate_type = "rlib"] + +// Public API - unchanged across all revisions +pub fn public_fn(x: u32) -> u32 { + x + 1 +} + +pub struct PublicStruct { + pub value: u32, +} + +// rpass2: Add a new private function +#[cfg(any(rpass2, rpass3))] +fn new_private_fn() -> u32 { + 42 +} + +// rpass3: Add another private function +#[cfg(rpass3)] +fn another_private_fn(x: u32) -> u32 { + x * 2 +} + +// rpass3: Add a private struct +#[cfg(rpass3)] +struct PrivateStruct { + _field: u32, +} diff --git a/tests/incremental/rdr_add_private_fn/main.rs b/tests/incremental/rdr_add_private_fn/main.rs new file mode 100644 index 0000000000000..d0083450fd35e --- /dev/null +++ b/tests/incremental/rdr_add_private_fn/main.rs @@ -0,0 +1,32 @@ +// Test that adding private functions to a dependency does NOT cause +// the dependent crate to be rebuilt. +// +// This is a core RDR (Relink, Don't Rebuild) test. +// Adding private items should not affect the public API hash. +// +// Revisions: +// - rpass1: Initial compilation +// - rpass2: Dependency adds one private function - should REUSE +// - rpass3: Dependency adds more private items - should REUSE + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: dep.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![crate_type = "bin"] + +// Main should be reused when only private items are added to dependency +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate dep; + +fn main() { + let result = dep::public_fn(41); + assert_eq!(result, 42); + + let s = dep::PublicStruct { value: 100 }; + assert_eq!(s.value, 100); +} diff --git a/tests/incremental/rdr_metadata_spans/auxiliary/spans_lib.rs b/tests/incremental/rdr_metadata_spans/auxiliary/spans_lib.rs new file mode 100644 index 0000000000000..1b498118a3e96 --- /dev/null +++ b/tests/incremental/rdr_metadata_spans/auxiliary/spans_lib.rs @@ -0,0 +1,51 @@ +// Auxiliary crate for testing span reproducibility in metadata. +// This crate exports items that embed spans in the metadata, +// which are then used by the dependent crate. + +//@[rpass1] compile-flags: -Z query-dep-graph +//@[rpass2] compile-flags: -Z query-dep-graph +//@[rpass3] compile-flags: -Z query-dep-graph --remap-path-prefix={{src-base}}=/remapped + +#![crate_type = "rlib"] + +/// A struct with spans in its definition. +pub struct SpannedStruct { + pub field1: u32, + pub field2: String, +} + +/// A generic function whose span is embedded in metadata. +#[inline(always)] +pub fn generic_fn() -> T { + T::default() +} + +/// A macro that will have its expansion span in metadata. +#[macro_export] +macro_rules! span_macro { + ($e:expr) => { + $e + 1 + }; +} + +/// Trait with associated types to test span handling. +pub trait SpannedTrait { + type Output; + fn process(&self) -> Self::Output; +} + +impl SpannedTrait for u32 { + type Output = u32; + fn process(&self) -> Self::Output { + *self * 2 + } +} + +/// Function with panic that embeds span in metadata. +#[inline(always)] +pub fn might_panic(x: u32) -> u32 { + if x == 0 { + panic!("x cannot be zero"); + } + x +} diff --git a/tests/incremental/rdr_metadata_spans/main.rs b/tests/incremental/rdr_metadata_spans/main.rs new file mode 100644 index 0000000000000..a8ab79f08f7d1 --- /dev/null +++ b/tests/incremental/rdr_metadata_spans/main.rs @@ -0,0 +1,44 @@ +// This test verifies that spans embedded in crate metadata are handled +// correctly for incremental compilation and reproducibility. +// +// The test exercises: +// 1. Cross-crate inlined function spans +// 2. Macro expansion spans from external crates +// 3. Trait implementation spans +// 4. Panic location spans in inlined code +// +// rpass1: Initial compilation +// rpass2: Recompile with no changes - should reuse everything +// rpass3: Recompile with path remapping - tests span normalization + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph -g +//@ aux-build: spans_lib.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] + +// In rpass2, we should reuse the codegen for main since nothing changed. +// In rpass3, path remapping in the auxiliary crate may affect spans. +#![rustc_partition_reused(module = "main", cfg = "rpass2")] + +extern crate spans_lib; + +use spans_lib::{SpannedStruct, SpannedTrait}; + +fn main() { + let _: u32 = spans_lib::generic_fn(); + let _: String = spans_lib::generic_fn(); + + let s = SpannedStruct { field1: 42, field2: String::from("test") }; + let _ = s.field1; + + let x = spans_lib::span_macro!(41); + assert_eq!(x, 42); + + let val: u32 = 21; + let result = val.process(); + assert_eq!(result, 42); + + let _ = spans_lib::might_panic(1); +} diff --git a/tests/incremental/rdr_private_span_changes/auxiliary/dep.rs b/tests/incremental/rdr_private_span_changes/auxiliary/dep.rs new file mode 100644 index 0000000000000..7192bbfa02d8a --- /dev/null +++ b/tests/incremental/rdr_private_span_changes/auxiliary/dep.rs @@ -0,0 +1,122 @@ +// Auxiliary crate for testing RDR span stability. +// +// This crate has private items whose spans change between revisions, +// but the public API remains identical. The dependent crate should +// NOT be rebuilt when only private span-affecting changes occur. +// +// rpass1: Initial state +// rpass2: Added blank lines before private fn (shifts all BytePos values) +// rpass3: Added comments inside private fn (changes internal spans) +// rpass4: Changed private fn body (implementation change, not just spans) + +#![crate_name = "dep"] +#![crate_type = "rlib"] + +// ============================================================ +// PUBLIC API - This should remain stable across all revisions +// ============================================================ + +/// Public struct - its definition spans should be stable. +pub struct PublicStruct { + pub value: u32, +} + +impl PublicStruct { + /// Public constructor - calls private helper internally. + pub fn new(v: u32) -> Self { + Self { value: private_transform(v) } + } + + /// Public method - depends on private implementation. + pub fn doubled(&self) -> u32 { + private_double(self.value) + } +} + +/// Public function that uses private helpers. +pub fn public_compute(x: u32) -> u32 { + let a = private_transform(x); + let b = private_double(a); + private_combine(a, b) +} + +/// Public trait with default implementation using private fn. +pub trait PublicTrait { + fn compute(&self) -> u32; + + fn with_default(&self) -> u32 { + private_transform(self.compute()) + } +} + +impl PublicTrait for u32 { + fn compute(&self) -> u32 { + *self + } +} + +// ============================================================ +// PRIVATE IMPLEMENTATION - Changes here affect spans +// ============================================================ + +// rpass2+: These blank lines shift BytePos values of everything below. +// This simulates developers adding whitespace or reformatting code. +#[cfg(any(rpass2, rpass3, rpass4))] +const _BLANK_LINES_MARKER: () = (); + + + + +// End of blank lines section + +/// Private helper function. +#[cfg(rpass1)] +fn private_transform(x: u32) -> u32 { + x.wrapping_add(1) +} + +/// Private helper function - with shifted spans in rpass2. +#[cfg(rpass2)] +fn private_transform(x: u32) -> u32 { + x.wrapping_add(1) +} + +/// Private helper function - with comments in rpass3. +#[cfg(rpass3)] +fn private_transform(x: u32) -> u32 { + // Adding a comment here changes internal spans + // but should not affect the dependent crate. + x.wrapping_add(1) +} + +/// Private helper function - with comments in rpass4. +#[cfg(rpass4)] +fn private_transform(x: u32) -> u32 { + // Same comments as rpass3 + // but should not affect the dependent crate. + x.wrapping_add(1) +} + +/// Private double function. +#[cfg(any(rpass1, rpass2, rpass3))] +fn private_double(x: u32) -> u32 { + x * 2 +} + +/// Private double function - changed implementation in rpass4. +#[cfg(rpass4)] +fn private_double(x: u32) -> u32 { + x << 1 // Same result, different implementation +} + +/// Private combiner function. +fn private_combine(a: u32, b: u32) -> u32 { + a.wrapping_add(b) +} + +// rpass2+: Additional private module to shift spans further. +#[cfg(any(rpass2, rpass3, rpass4))] +mod private_module { + #[allow(dead_code)] + pub fn unused_fn() -> u32 { 42 } +} diff --git a/tests/incremental/rdr_private_span_changes/main.rs b/tests/incremental/rdr_private_span_changes/main.rs new file mode 100644 index 0000000000000..0cae81ed7b4d3 --- /dev/null +++ b/tests/incremental/rdr_private_span_changes/main.rs @@ -0,0 +1,60 @@ +// Test that span changes in private items of a dependency do NOT cause +// the dependent crate to be rebuilt. +// +// This is the core test for Relink, Don't Rebuild (RDR). +// The key insight is that metadata should not encode spans in a way that +// causes hash instability when only private implementation details change. +// +// Revisions: +// - rpass1: Initial compilation +// - rpass2: Dependency adds blank lines (BytePos shifts) - should REUSE +// - rpass3: Dependency adds comments in private fn - should REUSE +// - rpass4: Dependency changes private fn body - should REUSE +// +// In all revisions, the main crate should be reused because: +// 1. The public API of the dependency hasn't changed +// 2. No inlined code from the dependency is used +// 3. Span changes in private items shouldn't affect metadata hashes + +//@ revisions: rpass1 rpass2 rpass3 rpass4 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: dep.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![crate_type = "bin"] + +// THE KEY ASSERTION: This module should be reused in ALL subsequent revisions. +// If this fails, it means span changes in private dependency code are +// incorrectly invalidating the dependent crate's cache. +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] +#![rustc_partition_reused(module = "main", cfg = "rpass4")] + +extern crate dep; + +use dep::{PublicStruct, PublicTrait, public_compute}; + +fn main() { + // Use the public API - none of this should cause recompilation + // when only private spans change in the dependency. + + // Test struct construction and methods + let s = PublicStruct::new(10); + assert_eq!(s.value, 11); // private_transform adds 1 + assert_eq!(s.doubled(), 22); // private_double multiplies by 2 + + // Test public function + let result = public_compute(5); + // private_transform(5) = 6 + // private_double(6) = 12 + // private_combine(6, 12) = 18 + assert_eq!(result, 18); + + // Test trait implementation + let val: u32 = 7; + assert_eq!(val.compute(), 7); + assert_eq!(val.with_default(), 8); // private_transform(7) = 8 + + println!("All RDR assertions passed!"); +} diff --git a/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs b/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs new file mode 100644 index 0000000000000..3ab6d66ddc1c0 --- /dev/null +++ b/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs @@ -0,0 +1,42 @@ +// Auxiliary crate for testing -Z separate-spans flag. +// When separate-spans is enabled, span data is stored in a separate .spans file +// instead of being embedded directly in the .rmeta file. + +//@[rpass1] compile-flags: -Z query-dep-graph +//@[rpass2] compile-flags: -Z query-dep-graph +//@[rpass3] compile-flags: -Z query-dep-graph -Z separate-spans + +#![crate_type = "rlib"] + +#[inline(always)] +pub fn inlined_with_span() -> u32 { + let x = 1; + let y = 2; + x + y +} + +#[inline(always)] +pub fn generic_with_span(val: T) -> String { + format!("{:?}", val) +} + +pub fn multi_span_fn() -> (u32, u32, u32) { + let a = compute_a(); + let b = compute_b(); + let c = compute_c(); + (a, b, c) +} + +fn compute_a() -> u32 { 1 } +fn compute_b() -> u32 { 2 } +fn compute_c() -> u32 { 3 } + +/// Macro that generates code with spans. +#[macro_export] +macro_rules! generate_fn { + ($name:ident, $val:expr) => { + pub fn $name() -> u32 { + $val + } + }; +} diff --git a/tests/incremental/rdr_separate_spans/main.rs b/tests/incremental/rdr_separate_spans/main.rs new file mode 100644 index 0000000000000..dece7278488ca --- /dev/null +++ b/tests/incremental/rdr_separate_spans/main.rs @@ -0,0 +1,41 @@ +// This test verifies that the -Z separate-spans flag works correctly +// with incremental compilation. +// +// The separate-spans flag causes span data to be stored in a separate +// .spans file rather than inline in the .rmeta file. This is important +// for Relink, Don't Rebuild (RDR) because span data often +// contains absolute file paths that break reproducibility. +// +// rpass1: Initial compilation without separate-spans +// rpass2: Recompile without changes - should reuse everything +// rpass3: Recompile auxiliary with separate-spans - tests span loading + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph -g +//@ aux-build: separate_spans_lib.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] + +// The main module should be reused in rpass2 since nothing changed. +#![rustc_partition_reused(module = "main", cfg = "rpass2")] + +extern crate separate_spans_lib; + +// Use the macro to generate a function - this tests macro expansion spans +separate_spans_lib::generate_fn!(generated_fortytwo, 42); + +fn main() { + let result = separate_spans_lib::inlined_with_span(); + assert_eq!(result, 3); + + let s1 = separate_spans_lib::generic_with_span(42u32); + let s2 = separate_spans_lib::generic_with_span("hello"); + assert!(s1.contains("42")); + assert!(s2.contains("hello")); + + let (a, b, c) = separate_spans_lib::multi_span_fn(); + assert_eq!(a + b + c, 6); + + assert_eq!(generated_fortytwo(), 42); +} diff --git a/tests/incremental/rdr_span_hash_cross_crate/auxiliary/hash_lib.rs b/tests/incremental/rdr_span_hash_cross_crate/auxiliary/hash_lib.rs new file mode 100644 index 0000000000000..c63e696e5c4cc --- /dev/null +++ b/tests/incremental/rdr_span_hash_cross_crate/auxiliary/hash_lib.rs @@ -0,0 +1,34 @@ +// Auxiliary crate for testing span hash stability across crate boundaries. +// Changes to span encoding should not affect dependent crate hashes +// unless the actual source location changes. + +//@[rpass1] compile-flags: -Z query-dep-graph +//@[rpass2] compile-flags: -Z query-dep-graph +//@[rpass3] compile-flags: -Z query-dep-graph + +#![crate_type = "rlib"] + +// NOTE: Do not change the line numbers of these functions between revisions! +// The test relies on spans staying at the same file:line:column locations. + +#[inline(always)] +pub fn stable_span_fn() -> u32 { + // This comment is here to ensure the function body has some content + 42 +} + +#[inline(always)] +pub fn generic_stable>(x: T) -> T { + x + T::default() +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StableStruct { + pub value: u32, +} + +impl StableStruct { + pub fn new(value: u32) -> Self { + Self { value } + } +} diff --git a/tests/incremental/rdr_span_hash_cross_crate/main.rs b/tests/incremental/rdr_span_hash_cross_crate/main.rs new file mode 100644 index 0000000000000..032b6aa57a895 --- /dev/null +++ b/tests/incremental/rdr_span_hash_cross_crate/main.rs @@ -0,0 +1,49 @@ +// This test verifies that span hashing is stable across incremental +// compilation sessions for cross-crate dependencies. +// +// For RDR (Relink, Don't Rebuild), it's critical that: +// 1. Span hashes use file:line:column rather than raw byte offsets +// 2. Cross-crate spans are hashed consistently +// 3. Changing unrelated code doesn't invalidate span hashes +// +// rpass1: Initial compilation +// rpass2: Recompile with no changes - all modules should be reused +// rpass3: Recompile again - still should reuse everything + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph -g +//@ aux-build: hash_lib.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] + +// All modules should be reused since neither the source nor the +// auxiliary crate changed between revisions. +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate hash_lib; + +use hash_lib::{StableStruct, generic_stable, stable_span_fn}; + +fn main() { + // Test inline function with stable span + let val = stable_span_fn(); + assert_eq!(val, 42); + + // Test generic function monomorphization + let x: u32 = generic_stable(21); + assert_eq!(x, 21); + + let y: i64 = generic_stable(100); + assert_eq!(y, 100); + + // Test struct with derived traits (derives embed spans) + let s1 = StableStruct::new(42); + let s2 = s1.clone(); + assert_eq!(s1, s2); + + // Debug formatting also uses spans from derive + let debug_str = format!("{:?}", s1); + assert!(debug_str.contains("42")); +} diff --git a/tests/run-make/rdr-separate-spans/lib.rs b/tests/run-make/rdr-separate-spans/lib.rs new file mode 100644 index 0000000000000..177e2d1da132c --- /dev/null +++ b/tests/run-make/rdr-separate-spans/lib.rs @@ -0,0 +1,41 @@ +// Test library for RDR separate-spans testing. +// This crate exports various items that embed spans in metadata. + +#![crate_name = "rdr_test_lib"] +#![crate_type = "rlib"] + +/// A public struct with spans in its definition. +pub struct TestStruct { + pub field: u32, +} + +/// A generic function whose span is embedded in metadata. +#[inline(always)] +pub fn generic_fn() -> T { + T::default() +} + +/// A function with panic (embeds panic location span). +pub fn might_panic(x: u32) -> u32 { + assert!(x > 0, "x must be positive"); + x +} + +/// Trait for testing trait impl spans. +pub trait TestTrait { + fn process(&self) -> u32; +} + +impl TestTrait for u32 { + fn process(&self) -> u32 { + *self * 2 + } +} + +/// Macro that embeds expansion spans. +#[macro_export] +macro_rules! test_macro { + ($e:expr) => { + $e + 1 + }; +} diff --git a/tests/run-make/rdr-separate-spans/rmake.rs b/tests/run-make/rdr-separate-spans/rmake.rs new file mode 100644 index 0000000000000..825adfcf12005 --- /dev/null +++ b/tests/run-make/rdr-separate-spans/rmake.rs @@ -0,0 +1,93 @@ +// Test that -Z separate-spans flag produces reproducible metadata. +// +// This test verifies: +// 1. The -Z separate-spans flag is accepted by the compiler +// 2. Compilation produces a .spans file alongside the .rmeta file (when implemented) +// 3. The resulting rlib is reproducible across builds from different directories +// +// See https://github.com/rust-lang/rust/issues/XXXXX for the RDR tracking issue. + +//@ ignore-cross-compile + +use run_make_support::{cwd, rfs, run_in_tmpdir, rust_lib_name, rustc}; + +fn main() { + // Test 1: Basic compilation with -Z separate-spans succeeds + run_in_tmpdir(|| { + rustc().input("lib.rs").crate_type("rlib").arg("-Zseparate-spans").run(); + + // Verify the rlib was created + assert!( + std::path::Path::new(&rust_lib_name("rdr_test_lib")).exists(), + "rlib should be created with -Z separate-spans" + ); + }); + + // Test 2: Reproducibility - same source compiled twice should produce identical rlibs + run_in_tmpdir(|| { + // First compilation + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Zseparate-spans") + .arg(&format!("--remap-path-prefix={}=/src", cwd().display())) + .run(); + + let first_rlib = rfs::read(rust_lib_name("rdr_test_lib")); + rfs::rename(rust_lib_name("rdr_test_lib"), "first.rlib"); + + // Second compilation (identical) + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Zseparate-spans") + .arg(&format!("--remap-path-prefix={}=/src", cwd().display())) + .run(); + + let second_rlib = rfs::read(rust_lib_name("rdr_test_lib")); + + assert_eq!( + first_rlib, second_rlib, + "Two identical compilations with -Z separate-spans should produce identical rlibs" + ); + }); + + // Test 3: Compilation from different directories should produce identical rlibs + // when using appropriate path remapping + run_in_tmpdir(|| { + let base_dir = cwd(); + + // Create subdirectory with copy of source + rfs::create_dir("subdir"); + rfs::copy("lib.rs", "subdir/lib.rs"); + + // Compile from base directory + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Zseparate-spans") + .arg(&format!("--remap-path-prefix={}=/src", base_dir.display())) + .run(); + + let base_rlib = rfs::read(rust_lib_name("rdr_test_lib")); + rfs::rename(rust_lib_name("rdr_test_lib"), "base.rlib"); + + // Compile from subdirectory + std::env::set_current_dir("subdir").unwrap(); + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Zseparate-spans") + .arg(&format!("--remap-path-prefix={}=/src", base_dir.join("subdir").display())) + .out_dir(&base_dir) + .run(); + + std::env::set_current_dir(&base_dir).unwrap(); + let subdir_rlib = rfs::read(rust_lib_name("rdr_test_lib")); + + assert_eq!( + base_rlib, subdir_rlib, + "Compilation from different directories should produce identical rlibs" + ); + }); +} From 6cf2679dc3af829442c20a3474b3db6b28c30f21 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:46:53 -0800 Subject: [PATCH 08/19] rdr: implement SpanResolver and span file loading infrastructure Add the core infrastructure for RDR span resolution across crates: SpanBlob/SpanBlobDecodeContext: Wrapper for span file data with header validation and root decoding, parallel to BlobDecodeContext. CrateMetadata additions: - span_file_data(): lazily loads and caches span file - load_span_file(): loads span file and validates against rmeta hash TyCtxtSpanResolver: Implements SpanResolver trait - resolve_span_ref(): converts SpanRef to Span via source file lookups - span_ref_from_span(): converts Span to SpanRef with file-relative offsets find_and_import_source_file_by_stable_id(): Searches external crates for source files needed to resolve spans. Also includes tests for include_str!/include_bytes! handling with separate spans. --- compiler/rustc_error_messages/src/lib.rs | 4 +- compiler/rustc_errors/src/diagnostic.rs | 8 +- compiler/rustc_errors/src/lib.rs | 22 +- compiler/rustc_interface/src/queries.rs | 16 +- compiler/rustc_metadata/src/creader.rs | 34 +- compiler/rustc_metadata/src/fs.rs | 22 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 411 +++++++++++++++++- .../src/rmeta/decoder/cstore_impl.rs | 40 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 297 +++++++++++-- compiler/rustc_metadata/src/rmeta/mod.rs | 4 + compiler/rustc_span/src/hygiene.rs | 41 +- compiler/rustc_span/src/lib.rs | 78 +++- .../rdr-separate-spans-include-str/dep.rs | 21 + .../dep_bytes.txt | 1 + .../dep_data.txt | 1 + .../rdr-separate-spans-include-str/main.rs | 10 + .../rdr-separate-spans-include-str/rmake.rs | 19 + 17 files changed, 916 insertions(+), 113 deletions(-) create mode 100644 tests/run-make/rdr-separate-spans-include-str/dep.rs create mode 100644 tests/run-make/rdr-separate-spans-include-str/dep_bytes.txt create mode 100644 tests/run-make/rdr-separate-spans-include-str/dep_data.txt create mode 100644 tests/run-make/rdr-separate-spans-include-str/main.rs create mode 100644 tests/run-make/rdr-separate-spans-include-str/rmake.rs diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index b9430a06371f0..f7ae6a209dce3 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -17,7 +17,7 @@ use intl_memoizer::concurrent::IntlLangMemoizer; use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; use rustc_macros::{Decodable, Encodable}; use rustc_serialize::{Decodable, Encodable}; -use rustc_span::{Span, SpanDecoder, SpanEncoder, SpanRef}; +use rustc_span::{Span, SpanDecoder, SpanEncoder}; use tracing::{instrument, trace}; pub use unic_langid::{LanguageIdentifier, langid}; @@ -528,13 +528,11 @@ impl Encodable for MultiSpan { impl Decodable for MultiSpan { fn decode(d: &mut D) -> Self { - // Decode primary spans let primary_len: usize = Decodable::decode(d); let mut primary_spans = Vec::with_capacity(primary_len); for _ in 0..primary_len { primary_spans.push(d.decode_span_ref_as_span()); } - // Decode span labels let labels_len: usize = Decodable::decode(d); let mut span_labels = Vec::with_capacity(labels_len); for _ in 0..labels_len { diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index b174874f2537d..a7b16ba45128d 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -13,7 +13,7 @@ use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; use rustc_serialize::{Decodable, Encodable}; use rustc_span::source_map::Spanned; -use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, SpanRef, Symbol}; +use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol}; use tracing::debug; use crate::snippet::Style; @@ -278,8 +278,7 @@ impl Encodable for DiagInner { self.suggestions.encode(e); self.args.encode(e); self.reserved_args.encode(e); - // Encode sort_span as SpanRef to avoid absolute byte positions - self.sort_span.to_span_ref().encode(e); + e.encode_span_as_span_ref(self.sort_span); self.is_lint.encode(e); self.long_ty_path.encode(e); self.emitted_at.encode(e); @@ -298,8 +297,7 @@ impl Decodable for DiagInner { suggestions: Decodable::decode(d), args: Decodable::decode(d), reserved_args: Decodable::decode(d), - // Decode SpanRef and convert back to Span - sort_span: { let sr: SpanRef = Decodable::decode(d); sr.span() }, + sort_span: d.decode_span_ref_as_span(), is_lint: Decodable::decode(d), long_ty_path: Decodable::decode(d), emitted_at: Decodable::decode(d), diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 7160e43312b3b..09fadc911380e 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -72,7 +72,7 @@ use rustc_serialize::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, DUMMY_SP, Loc, Span, SpanDecoder, SpanEncoder, SpanRef}; +use rustc_span::{BytePos, DUMMY_SP, Loc, Span, SpanDecoder, SpanEncoder}; pub use snippet::Style; use tracing::debug; @@ -215,16 +215,16 @@ pub struct SubstitutionPart { impl Encodable for SubstitutionPart { fn encode(&self, e: &mut E) { - self.span.to_span_ref().encode(e); + e.encode_span_as_span_ref(self.span); self.snippet.encode(e); } } impl Decodable for SubstitutionPart { fn decode(d: &mut D) -> Self { - let span_ref: SpanRef = Decodable::decode(d); + let span = d.decode_span_ref_as_span(); let snippet: String = Decodable::decode(d); - SubstitutionPart { span: span_ref.span(), snippet } + SubstitutionPart { span, snippet } } } @@ -238,22 +238,18 @@ pub struct TrimmedSubstitutionPart { impl Encodable for TrimmedSubstitutionPart { fn encode(&self, e: &mut E) { - self.original_span.to_span_ref().encode(e); - self.span.to_span_ref().encode(e); + e.encode_span_as_span_ref(self.original_span); + e.encode_span_as_span_ref(self.span); self.snippet.encode(e); } } impl Decodable for TrimmedSubstitutionPart { fn decode(d: &mut D) -> Self { - let original_span_ref: SpanRef = Decodable::decode(d); - let span_ref: SpanRef = Decodable::decode(d); + let original_span = d.decode_span_ref_as_span(); + let span = d.decode_span_ref_as_span(); let snippet: String = Decodable::decode(d); - TrimmedSubstitutionPart { - original_span: original_span_ref.span(), - span: span_ref.span(), - snippet, - } + TrimmedSubstitutionPart { original_span, span, snippet } } } diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 3799485077ef4..272b245bb109d 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::path::Path; use std::sync::Arc; use rustc_codegen_ssa::CodegenResults; @@ -60,15 +61,22 @@ impl Linker { if sess.opts.incremental.is_some() && let Some(path) = self.metadata.path() - && let Some((id, product)) = + { + let spans_path = path.with_extension("spans"); + let mut files: Vec<(&'static str, &Path)> = vec![("rmeta", path)]; + if sess.opts.unstable_opts.separate_spans && spans_path.exists() { + files.push(("spans", &spans_path)); + } + if let Some((id, product)) = rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( sess, "metadata", - &[("rmeta", path)], + &files, &[], ) - { - work_products.insert(id, product); + { + work_products.insert(id, product); + } } sess.dcx().abort_if_errors(); diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 6b137a8e45a91..7980498171158 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -220,7 +220,13 @@ impl CStore { root: &CrateRoot, ) -> Result, CrateError> { assert_eq!(self.metas.len(), tcx.untracked().stable_crate_ids.read().len()); - let num = tcx.create_crate_num(root.stable_crate_id()).map_err(|existing| { + let stable_crate_id = root.stable_crate_id(); + debug!( + crate_name = ?root.name(), + ?stable_crate_id, + "intern_stable_crate_id: registering crate" + ); + let num = tcx.create_crate_num(stable_crate_id).map_err(|existing| { // Check for (potential) conflicts with the local crate if existing == LOCAL_CRATE { CrateError::SymbolConflictsCurrent(root.name()) @@ -624,10 +630,22 @@ impl CStore { // Check if a separate span file exists (for crates compiled with -Z separate_spans). // The span file will be loaded lazily on first span resolution. - let span_file_path = source.rmeta.as_ref().and_then(|rmeta_path| { - let span_path = rmeta_path.with_extension("spans"); - if span_path.exists() { Some(span_path) } else { None } - }); + // Look for .spans file adjacent to rmeta first, then rlib if rmeta is not available. + let span_file_path = source + .rmeta + .as_ref() + .or(source.rlib.as_ref()) + .and_then(|path| { + let span_path = path.with_extension("spans"); + if span_path.exists() { Some(span_path) } else { None } + }); + + let has_separate_spans = crate_root.has_separate_spans(); + let crate_name = crate_root.name(); + + if has_separate_spans && span_file_path.is_none() { + return Err(CrateError::MissingSpanFile(crate_name, "file not found".to_string())); + } let crate_metadata = CrateMetadata::new( tcx, @@ -644,6 +662,12 @@ impl CStore { host_hash, ); + if has_separate_spans { + crate_metadata + .ensure_span_file_loaded() + .map_err(|err| CrateError::MissingSpanFile(crate_name, err))?; + } + self.set_crate_data(cnum, crate_metadata); Ok(cnum) diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index e49a474e3ab92..beeb78672990a 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -71,12 +71,15 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { }; // When -Z separate-spans is enabled, encode spans to a separate .spans file - if let Some(required_source_files) = required_source_files + let spans_tmp_filename = if let Some(required_source_files) = required_source_files && tcx.sess.opts.unstable_opts.separate_spans { let spans_tmp_filename = metadata_tmpdir.as_ref().join("lib.spans"); encode_spans(tcx, &spans_tmp_filename, tcx.crate_hash(LOCAL_CRATE), required_source_files); - } + Some(spans_tmp_filename) + } else { + None + }; let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata"); @@ -110,6 +113,21 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { (metadata_filename, Some(metadata_tmpdir)) }; + // Write spans file adjacent to where the rmeta would be output. + // This uses out_filename (the intended rmeta output path) rather than metadata_filename + // because metadata_filename may be a temp path when not producing standalone metadata. + if let Some(spans_tmp_filename) = spans_tmp_filename { + let spans_filename = match &out_filename { + OutFileName::Real(path) => path.with_extension("spans"), + OutFileName::Stdout => spans_tmp_filename.clone(), + }; + if spans_tmp_filename != spans_filename { + if let Err(err) = non_durable_rename(&spans_tmp_filename, &spans_filename) { + tcx.dcx().emit_fatal(FailedWriteError { filename: spans_filename, err }); + } + } + } + // Load metadata back to memory: codegen may need to include it in object files. let metadata = EncodedMetadata::from_path(metadata_filename, metadata_stub_filename, metadata_tmpdir) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 066fc0c6e083b..ee3fd3ae2e440 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -34,7 +34,8 @@ use rustc_session::cstore::{CrateSource, ExternCrate}; use rustc_span::hygiene::HygieneDecodeContext; use rustc_span::{ AttrId, BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, ExpnId, IdentRef, Pos, - RemapPathScopeComponents, SpanData, SpanDecoder, Symbol, SyntaxContext, kw, + RemapPathScopeComponents, Span, SpanData, SpanDecoder, SpanRef, SpanResolver, Symbol, + SyntaxContext, kw, }; use tracing::debug; @@ -85,7 +86,7 @@ impl std::ops::Deref for SpanBlob { } } -#[allow(dead_code)] // TODO: used by RDR span file infrastructure +#[allow(dead_code)] // Infrastructure for RDR span file loading impl SpanBlob { /// Runs the [`MemDecoder`] validation and if it passes, constructs a new [`SpanBlob`]. pub(crate) fn new(slice: OwnedSlice) -> Result { @@ -96,6 +97,41 @@ impl SpanBlob { pub(crate) fn bytes(&self) -> &OwnedSlice { &self.0 } + + /// Checks if this span blob has a valid header. + pub(crate) fn check_header(&self) -> bool { + self.starts_with(SPAN_HEADER) + } + + /// Parses the root position from the blob header. + fn root_pos(&self) -> Result, &'static str> { + // Check minimum length: header + root position (8 bytes) + let min_len = SPAN_HEADER.len() + 8; + if self.len() < min_len { + return Err("span file too short to contain header and root position"); + } + + let offset = SPAN_HEADER.len(); + let pos_bytes: [u8; 8] = match self[offset..].get(..8) { + Some(bytes) => bytes.try_into().unwrap(), + None => return Err("span file too short to read root position"), + }; + let pos = u64::from_le_bytes(pos_bytes) as usize; + + let pos = NonZero::new(pos).ok_or("span file has zero root position")?; + + if pos.get() >= self.len() { + return Err("span file root position points past end of file"); + } + + Ok(pos) + } + + /// Parses and returns the SpanFileRoot from the blob. + pub(crate) fn get_root(&self) -> Result { + let pos = self.root_pos()?; + Ok(LazyValue::::from_position(pos).decode_span(self)) + } } /// A map from external crate numbers (as decoded from some crate file) to @@ -108,7 +144,7 @@ pub(crate) type CrateNumMap = IndexVec; pub(crate) type TargetModifiers = Vec; /// Lazily-loaded span file data. Contains the span blob and decoded source_map table. -#[allow(dead_code)] // TODO: used by RDR span file infrastructure +#[allow(dead_code)] // Infrastructure for RDR span file loading struct SpanFileData { blob: SpanBlob, source_map: LazyTable>>, @@ -120,10 +156,10 @@ pub(crate) struct CrateMetadata { /// Path to the span file (.spans), if one exists for this crate. /// The span file is loaded lazily on first span resolution. - #[allow(dead_code)] // TODO: used by RDR span file infrastructure + #[allow(dead_code)] // Infrastructure for RDR span file loading span_file_path: Option, /// Lazily-loaded span file data. Populated on first access when span_file_path is Some. - #[allow(dead_code)] // TODO: used by RDR span file infrastructure + #[allow(dead_code)] // Infrastructure for RDR span file loading span_file_data: OnceLock>, // --- Some data pre-decoded from the metadata blob, usually for performance --- @@ -346,6 +382,61 @@ impl<'a, 'tcx> Metadata<'a> for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { } } +/// Decode context for span files (.spans). +/// Similar to [`BlobDecodeContext`] but works with [`SpanBlob`] instead of [`MetadataBlob`]. +#[allow(dead_code)] // Infrastructure for RDR span file loading +pub(super) struct SpanBlobDecodeContext<'a> { + opaque: MemDecoder<'a>, + blob: &'a SpanBlob, + lazy_state: LazyState, +} + +#[allow(dead_code)] // Infrastructure for RDR span file loading +impl<'a> LazyDecoder for SpanBlobDecodeContext<'a> { + fn set_lazy_state(&mut self, state: LazyState) { + self.lazy_state = state; + } + + fn get_lazy_state(&self) -> LazyState { + self.lazy_state + } +} + +#[allow(dead_code)] // Infrastructure for RDR span file loading +impl<'a> SpanBlobDecodeContext<'a> { + #[inline] + pub(crate) fn blob(&self) -> &'a SpanBlob { + self.blob + } +} + +/// Trait for decoding from span file blobs. +/// Similar to [`Metadata`] but for [`SpanBlob`]. +#[allow(dead_code)] // Infrastructure for RDR span file loading +pub(super) trait SpanMetadata<'a>: Copy { + type Context: BlobDecoder + LazyDecoder; + + fn blob(self) -> &'a SpanBlob; + fn decoder(self, pos: usize) -> Self::Context; +} + +#[allow(dead_code)] // Infrastructure for RDR span file loading +impl<'a> SpanMetadata<'a> for &'a SpanBlob { + type Context = SpanBlobDecodeContext<'a>; + + fn blob(self) -> &'a SpanBlob { + self + } + + fn decoder(self, pos: usize) -> Self::Context { + SpanBlobDecodeContext { + opaque: MemDecoder::new(self, pos).unwrap(), + lazy_state: LazyState::NoNode, + blob: self, + } + } +} + impl LazyValue { #[inline] fn decode<'a, 'tcx, M: Metadata<'a>>(self, metadata: M) -> T::Value<'tcx> @@ -356,6 +447,17 @@ impl LazyValue { dcx.set_lazy_state(LazyState::NodeStart(self.position)); T::Value::decode(&mut dcx) } + + /// Decode from a span file blob using SpanMetadata. + #[inline] + fn decode_span<'a, 'tcx, M: SpanMetadata<'a>>(self, metadata: M) -> T::Value<'tcx> + where + T::Value<'tcx>: Decodable, + { + let mut dcx = metadata.decoder(self.position.get()); + dcx.set_lazy_state(LazyState::NodeStart(self.position)); + T::Value::decode(&mut dcx) + } } struct DecodeIterator { @@ -574,6 +676,14 @@ impl<'a, 'tcx> SpanDecoder for MetadataDecodeContext<'a, 'tcx> { }; data.span() } + + fn decode_span_ref_as_span(&mut self) -> Span { + // Decode the SpanRef from metadata + let span_ref = SpanRef::decode(self); + // Resolve it to an absolute Span using the TyCtxt resolver + let resolver = TyCtxtSpanResolver::new(self.tcx); + resolver.resolve_span_ref(span_ref) + } } impl<'a, 'tcx> BlobDecoder for MetadataDecodeContext<'a, 'tcx> { @@ -610,6 +720,53 @@ impl<'a> BlobDecoder for BlobDecodeContext<'a> { } } +/// BlobDecoder impl for SpanBlobDecodeContext. +/// Symbols in span files use simple string encoding (no offset tables). +impl<'a> BlobDecoder for SpanBlobDecodeContext<'a> { + fn decode_def_index(&mut self) -> DefIndex { + DefIndex::from_u32(self.read_u32()) + } + + fn decode_symbol(&mut self) -> Symbol { + // Span files use simple string encoding + Symbol::intern(self.read_str()) + } + + fn decode_byte_symbol(&mut self) -> ByteSymbol { + ByteSymbol::intern(self.read_byte_str()) + } +} + +/// Minimal SpanDecoder impl for SpanBlobDecodeContext. +/// Similar to BlobDecodeContext's SpanDecoder impl - methods requiring hygiene will panic. +impl<'a> SpanDecoder for SpanBlobDecodeContext<'a> { + fn decode_span(&mut self) -> Span { + let lo = Decodable::decode(self); + let hi = Decodable::decode(self); + Span::new(lo, hi, SyntaxContext::root(), None) + } + + fn decode_expn_id(&mut self) -> ExpnId { + panic!("cannot decode `ExpnId` with `SpanBlobDecodeContext`"); + } + + fn decode_syntax_context(&mut self) -> SyntaxContext { + panic!("cannot decode `SyntaxContext` with `SpanBlobDecodeContext`"); + } + + fn decode_crate_num(&mut self) -> CrateNum { + CrateNum::from_u32(self.read_u32()) + } + + fn decode_def_id(&mut self) -> DefId { + DefId { krate: Decodable::decode(self), index: Decodable::decode(self) } + } + + fn decode_attr_id(&mut self) -> AttrId { + panic!("cannot decode `AttrId` with `SpanBlobDecodeContext`"); + } +} + /// Minimal SpanDecoder impl for BlobDecodeContext to support decoding types like SourceFile /// from the span blob. This is only used for span file decoding where full hygiene context /// is not available. Methods that require hygiene context will panic. @@ -780,6 +937,10 @@ mod blob { use super::*; implement_ty_decoder!(BlobDecodeContext<'a>); } +mod span_blob { + use super::*; + implement_ty_decoder!(SpanBlobDecodeContext<'a>); +} impl MetadataBlob { pub(crate) fn check_compatibility( @@ -1045,6 +1206,10 @@ impl CrateRoot { ) -> impl ExactSizeIterator { self.target_modifiers.decode(metadata) } + + pub(crate) fn has_separate_spans(&self) -> bool { + self.has_separate_spans + } } impl<'a> CrateMetadataRef<'a> { @@ -1085,14 +1250,15 @@ impl<'a> CrateMetadataRef<'a> { fn opt_item_ident(self, tcx: TyCtxt<'_>, item_index: DefIndex) -> Option { let name = self.opt_item_name(item_index)?; - let span = self + let span_ref = self .root .tables .def_ident_span .get((self, tcx), item_index) .unwrap_or_else(|| self.missing("def_ident_span", item_index)) - .decode((self, tcx)) - .span(); + .decode((self, tcx)); + let resolver = TyCtxtSpanResolver::new(tcx); + let span = resolver.resolve_span_ref(span_ref); Some(Ident::new(name, span)) } @@ -1114,13 +1280,15 @@ impl<'a> CrateMetadataRef<'a> { } fn get_span(self, tcx: TyCtxt<'_>, index: DefIndex) -> Span { - self.root + let span_ref = self + .root .tables .def_span .get((self, tcx), index) .unwrap_or_else(|| self.missing("def_span", index)) - .decode((self, tcx)) - .span() + .decode((self, tcx)); + let resolver = TyCtxtSpanResolver::new(tcx); + resolver.resolve_span_ref(span_ref) } fn load_proc_macro<'tcx>(self, tcx: TyCtxt<'tcx>, id: DefIndex) -> SyntaxExtension { @@ -1548,13 +1716,15 @@ impl<'a> CrateMetadataRef<'a> { } fn get_proc_macro_quoted_span(self, tcx: TyCtxt<'_>, index: usize) -> Span { - self.root + let span_ref = self + .root .tables .proc_macro_quoted_spans .get((self, tcx), index) .unwrap_or_else(|| panic!("Missing proc macro quoted span: {index:?}")) - .decode((self, tcx)) - .span() + .decode((self, tcx)); + let resolver = TyCtxtSpanResolver::new(tcx); + resolver.resolve_span_ref(span_ref) } fn get_foreign_modules(self, tcx: TyCtxt<'_>) -> impl Iterator { @@ -1955,6 +2125,53 @@ impl<'a> CrateMetadataRef<'a> { Some(imported) } + /// Finds a source file by its `StableSourceFileId` and imports it into the local source map. + /// + /// This is used by `SpanResolver` to lazily load source files from external crates + /// when resolving `SpanRef::FileRelative` spans. + /// + /// Returns the imported source file if found, or `None` if the file doesn't belong + /// to this crate or couldn't be loaded. + fn find_and_import_source_file_by_stable_id( + self, + tcx: TyCtxt<'_>, + target_stable_id: rustc_span::StableSourceFileId, + ) -> Option { + // Iterate through all source files in this crate's metadata + let num_files = self.root.source_map.size(); + debug!( + ?target_stable_id, + crate_name = ?self.cdata.root.header.name, + num_files, + "find_and_import_source_file_by_stable_id: searching" + ); + + for file_index in 0..num_files { + // Try to get the source file - it may already be cached + if let Some(imported) = self.imported_source_file(tcx, file_index as u32) { + let file_stable_id = imported.translated_source_file.stable_id; + debug!( + file_index, + name = ?imported.translated_source_file.name, + ?file_stable_id, + "find_and_import_source_file_by_stable_id: checking file" + ); + + if file_stable_id == target_stable_id { + debug!("find_and_import_source_file_by_stable_id: FOUND"); + return Some(imported); + } + } + } + + debug!( + ?target_stable_id, + crate_name = ?self.cdata.root.header.name, + "find_and_import_source_file_by_stable_id: NOT FOUND" + ); + None + } + fn get_attr_flags(self, tcx: TyCtxt<'_>, index: DefIndex) -> AttrFlags { self.root.tables.attr_flags.get((self, tcx), index) } @@ -2063,12 +2280,42 @@ impl CrateMetadata { /// Lazily loads the span file data if a span file path is configured. /// Returns None if no span file exists or if loading fails. - /// TODO: Complete SpanBlob infrastructure to enable span file loading. - #[allow(dead_code)] + #[allow(dead_code)] // Infrastructure for RDR span resolution fn span_file_data(&self) -> Option<&SpanFileData> { - // Span file loading is not yet fully implemented. - // The SpanBlob type needs Metadata trait impl and get_root method. - None + self.span_file_data + .get_or_init(|| { + let path = self.span_file_path.as_ref()?; + self.load_span_file(path).ok() + }) + .as_ref() + } + + /// Loads and parses the span file from disk. + #[allow(dead_code)] // Called by span_file_data() + fn load_span_file(&self, path: &Path) -> Result { + use crate::locator::get_span_metadata_section; + + let blob = get_span_metadata_section(path)?; + + if !blob.check_header() { + return Err(format!("invalid span file header in '{}'", path.display())); + } + + let root = blob + .get_root() + .map_err(|e| format!("failed to parse span file '{}': {}", path.display(), e))?; + + // Verify the rmeta hash matches + if root.header.rmeta_hash != self.root.header.hash { + return Err(format!( + "span file hash mismatch: expected {:?}, found {:?} in '{}'", + self.root.header.hash, + root.header.rmeta_hash, + path.display() + )); + } + + Ok(SpanFileData { blob, source_map: root.source_map() }) } pub(crate) fn dependencies(&self) -> impl Iterator { @@ -2205,3 +2452,129 @@ impl CrateMetadata { None } } + +// ============================================================================= +// SpanResolver implementation +// ============================================================================= + +/// Resolves `SpanRef` values to concrete `Span`s using the compilation context. +/// +/// This is the core infrastructure for RDR (Relink, Don't Rebuild). +/// When metadata stores spans as `SpanRef` with relative positions, this resolver +/// converts them back to absolute `Span` positions for diagnostics and debugging. +#[allow(dead_code)] // Infrastructure for RDR span resolution +pub(crate) struct TyCtxtSpanResolver<'tcx> { + tcx: TyCtxt<'tcx>, +} + +#[allow(dead_code)] // Infrastructure for RDR span resolution +impl<'tcx> TyCtxtSpanResolver<'tcx> { + /// Creates a new span resolver using the given type context. + pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self { + TyCtxtSpanResolver { tcx } + } +} + +#[allow(dead_code)] // Infrastructure for RDR span resolution +impl SpanResolver for TyCtxtSpanResolver<'_> { + fn resolve_span_ref(&self, span_ref: SpanRef) -> Span { + match span_ref { + SpanRef::Opaque { ctxt } => { + // Opaque spans have no position info - return dummy with context + DUMMY_SP.with_ctxt(ctxt) + } + SpanRef::FileRelative { source_crate, file, lo, hi, ctxt } => { + debug!(?source_crate, ?file, lo, hi, "resolve_span_ref: FileRelative"); + + // First, try to look up the source file by its stable ID in the current source map. + // This works when the file has already been imported from the source crate. + let source_map = self.tcx.sess.source_map(); + if let Some(source_file) = source_map.source_file_by_stable_id(file) { + debug!( + name = ?source_file.name, + start_pos = ?source_file.start_pos, + "resolve_span_ref: found file in source map" + ); + let abs_lo = source_file.start_pos + BytePos(lo); + let abs_hi = source_file.start_pos + BytePos(hi); + return Span::new(abs_lo, abs_hi, ctxt, None); + } + + debug!("resolve_span_ref: file not in source map, trying to import from crate"); + + // File not in source map - find the source crate and import from its metadata. + // This mirrors how the original span encoding works: we know exactly which + // crate's metadata contains the source file. + let cstore = CStore::from_tcx(self.tcx); + debug!(?source_crate, ?source_cnum, "resolve_span_ref: converted stable crate id"); + let cref = cstore.get_crate_data(source_cnum); + + // Try to import the source file from that crate's metadata + if let Some(imported) = + cref.find_and_import_source_file_by_stable_id(self.tcx, file) + { + debug!( + name = ?imported.translated_source_file.name, + start_pos = ?imported.translated_source_file.start_pos, + "resolve_span_ref: imported file" + ); + let abs_lo = imported.translated_source_file.start_pos + BytePos(lo); + let abs_hi = imported.translated_source_file.start_pos + BytePos(hi); + return Span::new(abs_lo, abs_hi, ctxt, None); + } + + // File not found in source crate - fall back to dummy span + debug!( + ?file, + ?source_crate, + ?source_cnum, + "resolve_span_ref: source file not found in crate" + ); + DUMMY_SP.with_ctxt(ctxt) + } + SpanRef::DefRelative { def_id, lo, hi, ctxt } => { + // Look up the definition's span and add relative offsets + let def_span = self.tcx.def_span(def_id); + let def_data = def_span.data(); + let abs_lo = def_data.lo + BytePos(lo); + let abs_hi = def_data.lo + BytePos(hi); + Span::new(abs_lo, abs_hi, ctxt, None) + } + } + } + + fn span_ref_from_span(&self, span: Span) -> SpanRef { + let data = span.data(); + + // Dummy spans become opaque + if span.is_dummy() { + return SpanRef::Opaque { ctxt: data.ctxt }; + } + + let source_map = self.tcx.sess.source_map(); + + // Look up the source file containing this span + let source_file = source_map.lookup_source_file(data.lo); + + // If the span crosses file boundaries, make it opaque + let end_file = source_map.lookup_source_file(data.hi); + if source_file.start_pos != end_file.start_pos { + return SpanRef::Opaque { ctxt: data.ctxt }; + } + + // Compute relative offsets from the file's start position + let rel_lo = (data.lo - source_file.start_pos).0; + let rel_hi = (data.hi - source_file.start_pos).0; + + // Get the source crate's stable ID + let source_crate = self.tcx.stable_crate_id(source_file.cnum); + + SpanRef::FileRelative { + source_crate, + file: source_file.stable_id, + lo: rel_lo, + hi: rel_hi, + ctxt: data.ctxt, + } + } +} diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index e0e078a1d4de1..4708cb6f92ddc 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -19,9 +19,10 @@ use rustc_serialize::Decoder; use rustc_session::StableCrateId; use rustc_session::cstore::{CrateStore, ExternCrate}; use rustc_span::hygiene::ExpnId; -use rustc_span::{Span, SpanRef, Symbol, kw}; +use rustc_span::{Ident, Span, SpanRef, SpanResolver, Symbol, kw}; use super::{Decodable, DecodeIterator}; +use super::TyCtxtSpanResolver; use crate::creader::{CStore, LoadedMacro}; use crate::rmeta::AttrFlags; use crate::rmeta::table::IsDefault; @@ -31,6 +32,11 @@ trait ProcessQueryValue<'tcx, T> { fn process_decoded(self, _tcx: TyCtxt<'tcx>, _err: impl Fn() -> !) -> T; } +#[inline] +fn resolve_span_ref(tcx: TyCtxt<'_>, span_ref: SpanRef) -> Span { + TyCtxtSpanResolver::new(tcx).resolve_span_ref(span_ref) +} + impl ProcessQueryValue<'_, T> for T { #[inline(always)] fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> T { @@ -93,22 +99,22 @@ impl ProcessQueryValue<'_, Option> for Option { impl ProcessQueryValue<'_, Span> for SpanRef { #[inline(always)] - fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Span { - self.span() + fn process_decoded(self, tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Span { + resolve_span_ref(tcx, self) } } impl ProcessQueryValue<'_, Span> for Option { #[inline(always)] - fn process_decoded(self, _tcx: TyCtxt<'_>, err: impl Fn() -> !) -> Span { - if let Some(span_ref) = self { span_ref.span() } else { err() } + fn process_decoded(self, tcx: TyCtxt<'_>, err: impl Fn() -> !) -> Span { + if let Some(span_ref) = self { resolve_span_ref(tcx, span_ref) } else { err() } } } impl ProcessQueryValue<'_, Option> for Option { #[inline(always)] - fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Option { - self.map(|span_ref| span_ref.span()) + fn process_decoded(self, tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Option { + self.map(|span_ref| resolve_span_ref(tcx, span_ref)) } } @@ -148,7 +154,10 @@ macro_rules! provide_one { let value = if lazy.is_default() { &[] as &[_] } else { - $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|(item, span_ref)| (item, span_ref.span()))) + $tcx.arena.alloc_from_iter( + lazy.decode(($cdata, $tcx)) + .map(|(item, span_ref)| (item, resolve_span_ref($tcx, span_ref))) + ) }; value.process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } @@ -164,7 +173,12 @@ macro_rules! provide_one { .tables .$name .get(($cdata, $tcx), $def_id.index) - .map(|lazy| $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|(item, span_ref)| (item, span_ref.span()))) as &[_]) + .map(|lazy| { + $tcx.arena.alloc_from_iter( + lazy.decode(($cdata, $tcx)) + .map(|(item, span_ref)| (item, resolve_span_ref($tcx, span_ref))), + ) as &[_] + }) .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } } @@ -179,7 +193,13 @@ macro_rules! provide_one { .tables .$name .get(($cdata, $tcx), $def_id.index) - .map(|lazy| $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx)).map(|opt| opt.map(|ir| ir.ident()))) as &[_]) + .map(|lazy| { + $tcx.arena.alloc_from_iter( + lazy.decode(($cdata, $tcx)).map(|opt| { + opt.map(|ir| Ident::new(ir.name, resolve_span_ref($tcx, ir.span))) + }), + ) as &[_] + }) .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 4f8d79b379cfc..1b3adeabb288a 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -30,8 +30,8 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, opaque}; use rustc_session::config::{CrateType, OptLevel, TargetModifier}; use rustc_span::hygiene::HygieneEncodeContext; use rustc_span::{ - ByteSymbol, ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, StableSourceFileId, - Symbol, SyntaxContext, sym, + ByteSymbol, ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, SpanRef, + StableSourceFileId, Symbol, SyntaxContext, sym, }; use tracing::{debug, instrument, trace}; @@ -212,6 +212,67 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { this.emit_byte_str(byte_sym.as_byte_str()) }); } + + fn encode_span_as_span_ref(&mut self, span: Span) { + let span_data = span.data(); + let ctxt = if self.is_proc_macro { SyntaxContext::root() } else { span_data.ctxt }; + + // For dummy spans, encode as Opaque + if span_data.is_dummy() { + let span_ref = SpanRef::Opaque { ctxt }; + span_ref.encode(self); + return; + } + + // Look up the source file for this span + if !self.source_file_cache.0.contains(span_data.lo) { + let source_map = self.tcx.sess.source_map(); + let source_file_index = source_map.lookup_source_file_idx(span_data.lo); + self.source_file_cache = + (Arc::clone(&source_map.files()[source_file_index]), source_file_index); + } + let (ref source_file, source_file_index) = self.source_file_cache; + + // Check if hi is in the same file + if !source_file.contains(span_data.hi) { + // Cross-file span - encode as Opaque + let span_ref = SpanRef::Opaque { ctxt }; + span_ref.encode(self); + return; + } + + if (!source_file.is_imported() || self.is_proc_macro) + && let Some(required_source_files) = self.required_source_files.as_mut() + { + required_source_files.insert(source_file_index); + } + + // Create FileRelative with stable source file ID and source crate ID + let lo = (span_data.lo - source_file.start_pos).0; + let hi = (span_data.hi - source_file.start_pos).0; + let source_crate = self.tcx.stable_crate_id(source_file.cnum); + let file_stable_id = if source_file.cnum == LOCAL_CRATE { + let mut adapted_name = source_file.name.clone(); + if let FileName::Real(ref mut real_name) = adapted_name { + real_name.update_for_crate_metadata(); + } + StableSourceFileId::from_filename_for_export(&adapted_name, source_crate) + } else { + source_file.stable_id + }; + debug!( + cnum = ?source_file.cnum, + ?source_crate, + file_name = ?source_file.name, + original_stable_id = ?source_file.stable_id, + export_stable_id = ?file_stable_id, + lo, + hi, + "encode_span_as_span_ref: FileRelative" + ); + let span_ref = SpanRef::FileRelative { source_crate, file: file_stable_id, lo, hi, ctxt }; + span_ref.encode(self); + } } fn bytes_needed(n: usize) -> usize { @@ -425,6 +486,76 @@ macro_rules! record_defaulted_array { } impl<'a, 'tcx> EncodeContext<'a, 'tcx> { + /// Converts a `Span` to a `SpanRef`, preserving file-relative position when possible. + /// + /// This method creates `SpanRef::FileRelative` for spans that fit within a single + /// source file, allowing stable span references that survive recompilation. + /// Falls back to `SpanRef::Opaque` for dummy spans or spans that cross file boundaries. + fn span_to_span_ref(&mut self, span: Span) -> SpanRef { + let span_data = span.data(); + let ctxt = if self.is_proc_macro { SyntaxContext::root() } else { span_data.ctxt }; + + // For dummy spans, return Opaque + if span_data.is_dummy() { + return SpanRef::Opaque { ctxt }; + } + + // Look up the source file for this span + if !self.source_file_cache.0.contains(span_data.lo) { + let source_map = self.tcx.sess.source_map(); + let source_file_index = source_map.lookup_source_file_idx(span_data.lo); + self.source_file_cache = + (Arc::clone(&source_map.files()[source_file_index]), source_file_index); + } + let (ref source_file, source_file_index) = self.source_file_cache; + + // Check if hi is in the same file + if !source_file.contains(span_data.hi) { + // Cross-file span - return Opaque + return SpanRef::Opaque { ctxt }; + } + + if (!source_file.is_imported() || self.is_proc_macro) + && let Some(required_source_files) = self.required_source_files.as_mut() + { + required_source_files.insert(source_file_index); + } + + // Create FileRelative with stable source file ID and source crate ID + let lo = (span_data.lo - source_file.start_pos).0; + let hi = (span_data.hi - source_file.start_pos).0; + let source_crate = self.tcx.stable_crate_id(source_file.cnum); + + // For local source files, we need to compute the export version of the stable_id, + // because when source files are written to metadata their stable_id is recalculated + // to include the crate's stable ID. We also need to adapt the filename the same way + // as encode_source_map_with does (via update_for_crate_metadata) to ensure the hash + // matches. + // For imported source files, the stable_id is already in the correct form (it was + // read from that crate's metadata). + let file_stable_id = if source_file.cnum == LOCAL_CRATE { + let mut adapted_name = source_file.name.clone(); + if let FileName::Real(ref mut real_name) = adapted_name { + real_name.update_for_crate_metadata(); + } + StableSourceFileId::from_filename_for_export(&adapted_name, source_crate) + } else { + source_file.stable_id + }; + + debug!( + cnum = ?source_file.cnum, + ?source_crate, + file_name = ?source_file.name, + original_stable_id = ?source_file.stable_id, + export_stable_id = ?file_stable_id, + lo, + hi, + "span_to_span_ref: FileRelative" + ); + SpanRef::FileRelative { source_crate, file: file_stable_id, lo, hi, ctxt } + } + fn emit_lazy_distance(&mut self, position: NonZero) { let pos = position.get(); let distance = match self.lazy_state { @@ -793,6 +924,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { expn_hashes, def_path_hash_map, specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), + has_separate_spans: tcx.sess.opts.unstable_opts.separate_spans, }) }); @@ -1484,7 +1616,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if should_encode_span(def_kind) { let def_span = tcx.def_span(local_id); - record!(self.tables.def_span[def_id] <- def_span.to_span_ref()); + let span_ref = self.span_to_span_ref(def_span); + record!(self.tables.def_span[def_id] <- span_ref); } if should_encode_attrs(def_kind) { self.encode_attrs(local_id); @@ -1495,7 +1628,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if should_encode_span(def_kind) && let Some(ident_span) = tcx.def_ident_span(def_id) { - record!(self.tables.def_ident_span[def_id] <- ident_span.to_span_ref()); + let span_ref = self.span_to_span_ref(ident_span); + record!(self.tables.def_ident_span[def_id] <- span_ref); } if def_kind.has_codegen_attrs() { record!(self.tables.codegen_fn_attrs[def_id] <- self.tcx.codegen_fn_attrs(def_id)); @@ -1523,8 +1657,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.generics_of[def_id] <- g); record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); - record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- - inferred_outlives.iter().map(|&(c, s)| (c, s.to_span_ref()))); + let inferred_outlives_converted: Vec<_> = { + let mut result = Vec::with_capacity(inferred_outlives.len()); + for &(c, s) in inferred_outlives.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives_converted); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { @@ -1558,29 +1698,61 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } if let DefKind::Trait = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); + let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); + let super_predicates_converted: Vec<_> = { + let mut result = Vec::with_capacity(super_predicates.len()); + for &(c, s) in super_predicates.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates_converted); + let implied_predicates = + self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + let implied_predicates_converted: Vec<_> = { + let mut result = Vec::with_capacity(implied_predicates.len()); + for &(c, s) in implied_predicates.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates_converted); let module_children = self.tcx.module_children_local(local_id); record_array!(self.tables.module_children_non_reexports[def_id] <- module_children.iter().map(|child| child.res.def_id().index)); if self.tcx.is_const_trait(def_id) { - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); + let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); + let const_bounds_converted: Vec<_> = { + let mut result = Vec::with_capacity(const_bounds.len()); + for &(c, s) in const_bounds.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); } } if let DefKind::TraitAlias = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); + let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); + let super_predicates_converted: Vec<_> = { + let mut result = Vec::with_capacity(super_predicates.len()); + for &(c, s) in super_predicates.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates_converted); + let implied_predicates = + self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + let implied_predicates_converted: Vec<_> = { + let mut result = Vec::with_capacity(implied_predicates.len()); + for &(c, s) in implied_predicates.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates_converted); } if let DefKind::Trait | DefKind::Impl { .. } = def_kind { let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); @@ -1641,9 +1813,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.opaque_ty_origin[def_id] <- self.tcx.opaque_ty_origin(def_id)); self.encode_precise_capturing_args(def_id); if tcx.is_conditionally_const(def_id) { - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- tcx.explicit_implied_const_bounds(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); + let const_bounds = tcx.explicit_implied_const_bounds(def_id).skip_binder(); + let const_bounds_converted: Vec<_> = { + let mut result = Vec::with_capacity(const_bounds.len()); + for &(c, s) in const_bounds.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); } } if let DefKind::AnonConst = def_kind { @@ -1784,15 +1962,27 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_explicit_item_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- - bounds.iter().map(|&(c, s)| (c, s.to_span_ref()))); + let bounds_converted: Vec<_> = { + let mut result = Vec::with_capacity(bounds.len()); + for &(c, s) in bounds.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds_converted); } fn encode_explicit_item_self_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- - bounds.iter().map(|&(c, s)| (c, s.to_span_ref()))); + let bounds_converted: Vec<_> = { + let mut result = Vec::with_capacity(bounds.len()); + for &(c, s) in bounds.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds_converted); } #[instrument(level = "debug", skip(self))] @@ -1812,19 +2002,29 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_explicit_item_bounds(def_id); self.encode_explicit_item_self_bounds(def_id); if tcx.is_conditionally_const(def_id) { - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder() - .iter().map(|&(c, s)| (c, s.to_span_ref()))); + let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); + let const_bounds_converted: Vec<_> = { + let mut result = Vec::with_capacity(const_bounds.len()); + for &(c, s) in const_bounds.iter() { + result.push((c, self.span_to_span_ref(s))); + } + result + }; + record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); } } if let ty::AssocKind::Type { data: ty::AssocTypeData::Rpitit(rpitit_info) } = item.kind { record!(self.tables.opt_rpitit_info[def_id] <- rpitit_info); if matches!(rpitit_info, ty::ImplTraitInTraitData::Trait { .. }) { - record_array!( - self.tables.assumed_wf_types_for_rpitit[def_id] - <- self.tcx.assumed_wf_types_for_rpitit(def_id) - .iter().map(|&(ty, s)| (ty, s.to_span_ref())) - ); + let wf_types = self.tcx.assumed_wf_types_for_rpitit(def_id); + let wf_types_converted: Vec<_> = { + let mut result = Vec::with_capacity(wf_types.len()); + for &(ty, s) in wf_types.iter() { + result.push((ty, self.span_to_span_ref(s))); + } + result + }; + record_array!(self.tables.assumed_wf_types_for_rpitit[def_id] <- wf_types_converted); self.encode_precise_capturing_args(def_id); } } @@ -2016,12 +2216,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let macros = self.lazy_array(tcx.resolutions(()).proc_macros.iter().map(|p| p.local_def_index)); for (i, span) in self.tcx.sess.psess.proc_macro_quoted_spans() { - let span_ref = self.lazy(span.to_span_ref()); + let converted = self.span_to_span_ref(span); + let span_ref = self.lazy(converted); self.tables.proc_macro_quoted_spans.set_some(i, span_ref); } self.tables.def_kind.set_some(LOCAL_CRATE.as_def_id().index, DefKind::Mod); - record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id()).to_span_ref()); + let crate_span_ref = self.span_to_span_ref(tcx.def_span(LOCAL_CRATE.as_def_id())); + record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- crate_span_ref); self.encode_attrs(LOCAL_CRATE.as_def_id().expect_local()); let vis = tcx.local_visibility(CRATE_DEF_ID).map_id(|def_id| def_id.local_def_index); record!(self.tables.visibility[LOCAL_CRATE.as_def_id()] <- vis); @@ -2067,8 +2269,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.tables.proc_macro.set_some(def_id.index, macro_kind); self.encode_attrs(id); record!(self.tables.def_keys[def_id] <- def_key); - record!(self.tables.def_ident_span[def_id] <- span.to_span_ref()); - record!(self.tables.def_span[def_id] <- span.to_span_ref()); + let span_ref = self.span_to_span_ref(span); + record!(self.tables.def_ident_span[def_id] <- span_ref); + record!(self.tables.def_span[def_id] <- span_ref); record!(self.tables.visibility[def_id] <- ty::Visibility::Public); if let Some(stability) = stability { record!(self.tables.lookup_stability[def_id] <- stability); @@ -2494,9 +2697,19 @@ pub fn encode_metadata( Ok(_) => {} Err(err) => tcx.dcx().emit_fatal(FailCreateFileEncoder { err }), }; + if tcx.sess.opts.unstable_opts.separate_spans + && let Some(saved_spans) = work_product.saved_files.get("spans") + { + let span_source = + rustc_incremental::in_incr_comp_dir(&incr_comp_session_dir, saved_spans); + let span_dest = path.with_extension("spans"); + debug!("copying preexisting spans from {span_source:?} to {span_dest:?}"); + match rustc_fs_util::link_or_copy(&span_source, &span_dest) { + Ok(_) => {} + Err(err) => tcx.dcx().emit_fatal(FailCreateFileEncoder { err }), + }; + } // When reusing work product, we don't have required_source_files. - // The span file would also need to be reused from the work product. - // TODO: Handle span file work product reuse return None; }; diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 6a144ebe0b2d6..ba6e61574b8e2 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -343,6 +343,10 @@ pub(crate) struct CrateRoot { symbol_mangling_version: SymbolManglingVersion, specialization_enabled_in: bool, + + /// Whether this crate was compiled with `-Z separate-spans`. + /// If true, span data is in a separate `.spans` file, not in this rmeta. + has_separate_spans: bool, } /// On-disk representation of `DefId`. diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index d94d82835d650..7b0f2ee99c79f 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -988,7 +988,8 @@ impl Span { /// A subset of properties from both macro definition and macro call available through global data. /// Avoid using this if you have access to the original definition or call structures. -#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic)] +// Manual impls encode `call_site`/`def_site` as `SpanRef` for RDR. +#[derive(Clone, Debug, HashStable_Generic)] pub struct ExpnData { // --- The part unique to each expansion. pub kind: ExpnKind, @@ -1134,6 +1135,44 @@ impl ExpnData { } } +impl Encodable for ExpnData { + fn encode(&self, s: &mut E) { + self.kind.encode(s); + self.parent.encode(s); + s.encode_span_as_span_ref(self.call_site); + self.disambiguator.encode(s); + s.encode_span_as_span_ref(self.def_site); + self.allow_internal_unstable.encode(s); + self.edition.encode(s); + self.macro_def_id.encode(s); + self.parent_module.encode(s); + self.allow_internal_unsafe.encode(s); + self.local_inner_macros.encode(s); + self.collapse_debuginfo.encode(s); + self.hide_backtrace.encode(s); + } +} + +impl Decodable for ExpnData { + fn decode(d: &mut D) -> Self { + ExpnData { + kind: Decodable::decode(d), + parent: Decodable::decode(d), + call_site: d.decode_span_ref_as_span(), + disambiguator: Decodable::decode(d), + def_site: d.decode_span_ref_as_span(), + allow_internal_unstable: Decodable::decode(d), + edition: Decodable::decode(d), + macro_def_id: Decodable::decode(d), + parent_module: Decodable::decode(d), + allow_internal_unsafe: Decodable::decode(d), + local_inner_macros: Decodable::decode(d), + collapse_debuginfo: Decodable::decode(d), + hide_backtrace: Decodable::decode(d), + } + } +} + /// Expansion kind. #[derive(Clone, Debug, PartialEq, Encodable, Decodable, HashStable_Generic)] pub enum ExpnKind { diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 4c34cff942e04..d2d1f400496d8 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -687,12 +687,20 @@ pub struct SpanData { /// /// All variants carry a `SyntaxContext` so hygiene information is always /// available without needing to resolve the span to file/line/col. -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, Encodable, Decodable)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, Encodable)] pub enum SpanRef { /// Unresolvable span (e.g. cross-file, indirect, or missing data). Opaque { ctxt: SyntaxContext }, - /// Offsets are relative to a specific source file. - FileRelative { file: StableSourceFileId, lo: u32, hi: u32, ctxt: SyntaxContext }, + /// Offsets are relative to a specific source file from a specific crate. + /// The `source_crate` identifies which crate's metadata contains the source file, + /// allowing cross-crate span resolution by looking up the file in the correct crate. + FileRelative { + source_crate: StableCrateId, + file: StableSourceFileId, + lo: u32, + hi: u32, + ctxt: SyntaxContext, + }, /// Offsets are relative to the start of a definition. DefRelative { def_id: crate::def_id::DefId, lo: u32, hi: u32, ctxt: SyntaxContext }, } @@ -766,9 +774,9 @@ impl SpanRef { match (self, other) { (SpanRef::Opaque { ctxt: c1 }, SpanRef::Opaque { ctxt: c2 }) => c1 == c2, ( - SpanRef::FileRelative { file: f1, lo: l1, hi: h1, .. }, - SpanRef::FileRelative { file: f2, lo: l2, hi: h2, .. }, - ) => f1 == f2 && l1 == l2 && h1 == h2, + SpanRef::FileRelative { source_crate: sc1, file: f1, lo: l1, hi: h1, .. }, + SpanRef::FileRelative { source_crate: sc2, file: f2, lo: l2, hi: h2, .. }, + ) => sc1 == sc2 && f1 == f2 && l1 == l2 && h1 == h2, ( SpanRef::DefRelative { def_id: d1, lo: l1, hi: h1, .. }, SpanRef::DefRelative { def_id: d2, lo: l2, hi: h2, .. }, @@ -1475,6 +1483,18 @@ pub trait SpanEncoder: Encoder { fn encode_crate_num(&mut self, crate_num: CrateNum); fn encode_def_index(&mut self, def_index: DefIndex); fn encode_def_id(&mut self, def_id: DefId); + + /// Encodes a span as a `SpanRef` for RDR (Relink, Don't Rebuild). + /// + /// This encodes the span using a stable format (`StableSourceFileId` + relative offsets) + /// that doesn't change when source files are reordered. On decode, the `SpanRef` is + /// resolved back to an absolute `Span`. + /// + /// Default implementation falls back to regular span encoding. + fn encode_span_as_span_ref(&mut self, span: Span) { + // Default: just encode as a regular span + self.encode_span(span); + } } impl SpanEncoder for FileEncoder { @@ -1597,6 +1617,17 @@ pub trait SpanDecoder: BlobDecoder { fn decode_crate_num(&mut self) -> CrateNum; fn decode_def_id(&mut self) -> DefId; fn decode_attr_id(&mut self) -> AttrId; + + /// Decodes a span that was encoded as `SpanRef` for RDR (Relink, Don't Rebuild). + /// + /// This decodes a `SpanRef` and resolves it to an absolute `Span` using source file + /// information from the compilation context. + /// + /// Default implementation falls back to regular span decoding. + fn decode_span_ref_as_span(&mut self) -> Span { + // Default: just decode as a regular span + self.decode_span() + } } impl BlobDecoder for MemDecoder<'_> { @@ -1672,6 +1703,34 @@ impl Decodable for SyntaxContext { } } +impl Decodable for SpanRef { + fn decode(d: &mut D) -> SpanRef { + let variant = d.read_usize(); + match variant { + 0 => { + let ctxt = Decodable::decode(d); + SpanRef::Opaque { ctxt } + } + 1 => { + let source_crate = Decodable::decode(d); + let file = Decodable::decode(d); + let lo = Decodable::decode(d); + let hi = Decodable::decode(d); + let ctxt = Decodable::decode(d); + SpanRef::FileRelative { source_crate, file, lo, hi, ctxt } + } + 2 => { + let def_id = Decodable::decode(d); + let lo = Decodable::decode(d); + let hi = Decodable::decode(d); + let ctxt = Decodable::decode(d); + SpanRef::DefRelative { def_id, lo, hi, ctxt } + } + _ => panic!("invalid SpanRef variant {variant}"), + } + } +} + impl Decodable for CrateNum { fn decode(s: &mut D) -> CrateNum { s.decode_crate_num() @@ -3036,8 +3095,8 @@ where /// Hashes a SpanRef, respecting the `hash_spans()` setting. /// /// When `hash_spans()` returns false (e.g., with `-Z incremental-ignore-spans`), - /// SpanRef contributes nothing to the hash. This enables RDR (Reproducible - /// Deterministic Rebuilds) where span-only changes don't trigger rebuilds. + /// SpanRef contributes nothing to the hash. This enables RDR (Relink, Don't + /// Rebuild) where span-only changes don't trigger rebuilds. fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { if !ctx.hash_spans() { return; @@ -3048,8 +3107,9 @@ where 0u8.hash_stable(ctx, hasher); ctxt.hash_stable(ctx, hasher); } - SpanRef::FileRelative { file, lo, hi, ctxt } => { + SpanRef::FileRelative { source_crate, file, lo, hi, ctxt } => { 1u8.hash_stable(ctx, hasher); + source_crate.hash_stable(ctx, hasher); file.hash_stable(ctx, hasher); lo.hash_stable(ctx, hasher); hi.hash_stable(ctx, hasher); diff --git a/tests/run-make/rdr-separate-spans-include-str/dep.rs b/tests/run-make/rdr-separate-spans-include-str/dep.rs new file mode 100644 index 0000000000000..b45de25a37be2 --- /dev/null +++ b/tests/run-make/rdr-separate-spans-include-str/dep.rs @@ -0,0 +1,21 @@ +#[macro_export] +macro_rules! dep_data { + () => { + include_str!("dep_data.txt") + }; +} + +pub fn dep_data_len() -> usize { + dep_data!().len() +} + +#[macro_export] +macro_rules! dep_bytes { + () => { + include_bytes!("dep_bytes.txt") + }; +} + +pub fn dep_bytes_len() -> usize { + dep_bytes!().len() +} diff --git a/tests/run-make/rdr-separate-spans-include-str/dep_bytes.txt b/tests/run-make/rdr-separate-spans-include-str/dep_bytes.txt new file mode 100644 index 0000000000000..c6d3baf0b738d --- /dev/null +++ b/tests/run-make/rdr-separate-spans-include-str/dep_bytes.txt @@ -0,0 +1 @@ +hello bytes data diff --git a/tests/run-make/rdr-separate-spans-include-str/dep_data.txt b/tests/run-make/rdr-separate-spans-include-str/dep_data.txt new file mode 100644 index 0000000000000..76a2225e2e27a --- /dev/null +++ b/tests/run-make/rdr-separate-spans-include-str/dep_data.txt @@ -0,0 +1 @@ +hello from dep data diff --git a/tests/run-make/rdr-separate-spans-include-str/main.rs b/tests/run-make/rdr-separate-spans-include-str/main.rs new file mode 100644 index 0000000000000..49f49131937c0 --- /dev/null +++ b/tests/run-make/rdr-separate-spans-include-str/main.rs @@ -0,0 +1,10 @@ +#[macro_use] +extern crate dep; + +fn main() { + let data = dep_data!(); + assert!(data.contains("hello from dep data")); + + let bytes = dep_bytes!(); + assert!(bytes.starts_with(b"hello bytes data")); +} diff --git a/tests/run-make/rdr-separate-spans-include-str/rmake.rs b/tests/run-make/rdr-separate-spans-include-str/rmake.rs new file mode 100644 index 0000000000000..2de9d9cbeace7 --- /dev/null +++ b/tests/run-make/rdr-separate-spans-include-str/rmake.rs @@ -0,0 +1,19 @@ +// Verify ExpnData spans decoded from metadata are usable for include_str! +// when building with -Z separate-spans. +//@ ignore-cross-compile +// Reason: the compiled binary is executed + +use run_make_support::{run, rust_lib_name, rustc}; + +fn main() { + rustc().input("dep.rs").crate_name("dep").crate_type("rlib").arg("-Zseparate-spans").run(); + + rustc() + .input("main.rs") + .arg("-Zseparate-spans") + .arg("--extern") + .arg(format!("dep={}", rust_lib_name("dep"))) + .run(); + + run("main"); +} From 48df429db1c38d67d991f3fb5f3823f75757c114 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:48:42 -0800 Subject: [PATCH 09/19] rdr: implement diagnostic gating for codegen safety Add safety mechanism to prevent diagnostics from being emitted after codegen begins, when span files may be unavailable for relinking. Changes: - Session::diagnostics_allowed(): tracks whether diagnostics can be emitted - Session::enter_codegen_phase(): marks transition to codegen - Dep graph integration: force diagnostics before codegen starts - Codegen backends: call enter_codegen_phase() at appropriate points - SpanRef resolution: check diagnostics_allowed() before resolving - Work product handling: track span file dependencies Run-make tests: - rdr-diagnostic-gating: verify gating works correctly - rdr-hygiene-hash-collision: test hygiene context handling - rdr-missing-spans-fallback: test graceful degradation --- compiler/rustc_metadata/messages.ftl | 5 + compiler/rustc_metadata/src/errors.rs | 11 +++ compiler/rustc_metadata/src/fs.rs | 35 ++++++- compiler/rustc_metadata/src/locator.rs | 5 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 75 +++++++------- compiler/rustc_metadata/src/rmeta/table.rs | 24 ++++- .../rustc_query_system/src/dep_graph/graph.rs | 22 ++++- compiler/rustc_query_system/src/query/mod.rs | 9 +- compiler/rustc_session/src/session.rs | 27 ++++- compiler/rustc_span/src/lib.rs | 35 ++++--- tests/run-make/rdr-diagnostic-gating/rmake.rs | 39 ++++++++ .../rdr-hygiene-hash-collision/rmake.rs | 99 +++++++++++++++++++ .../rdr-missing-spans-fallback/rmake.rs | 49 +++++++++ 13 files changed, 378 insertions(+), 57 deletions(-) create mode 100644 tests/run-make/rdr-diagnostic-gating/rmake.rs create mode 100644 tests/run-make/rdr-hygiene-hash-collision/rmake.rs create mode 100644 tests/run-make/rdr-missing-spans-fallback/rmake.rs diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl index fac7b6c21f60c..6089a23695193 100644 --- a/compiler/rustc_metadata/messages.ftl +++ b/compiler/rustc_metadata/messages.ftl @@ -153,6 +153,11 @@ metadata_link_ordinal_raw_dylib = metadata_missing_native_library = could not find native static library `{$libname}`, perhaps an -L flag is missing? +metadata_missing_span_file = + cannot load span data for crate `{$crate_name}`: {$reason} + .note = the incremental compilation cache may be corrupted + .help = try running `cargo clean` and recompiling + metadata_multiple_candidates = multiple candidates for `{$flavor}` dependency `{$crate_name}` found diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs index 2702c4498df6f..70e552d706170 100644 --- a/compiler/rustc_metadata/src/errors.rs +++ b/compiler/rustc_metadata/src/errors.rs @@ -618,3 +618,14 @@ pub struct RawDylibMalformed { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(metadata_missing_span_file)] +#[note] +#[help] +pub struct MissingSpanFile { + #[primary_span] + pub span: Span, + pub crate_name: Symbol, + pub reason: String, +} diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index beeb78672990a..e5afec9365f54 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -1,22 +1,38 @@ +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::{fs, io}; +use rustc_data_structures::memmap::Mmap; +use rustc_data_structures::owned_slice::{OwnedSlice, slice_owned}; +use rustc_data_structures::svh::Svh; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_fs_util::TempDirBuilder; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_session::config::{CrateType, OutFileName, OutputType}; use rustc_session::output::filename_for_metadata; +use tracing::debug; use crate::errors::{ BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile, FailedCreateTempdir, FailedWriteError, }; +use crate::rmeta::MetadataBlob; use crate::{EncodedMetadata, encode_metadata, encode_spans}; // FIXME(eddyb) maybe include the crate name in this? pub const METADATA_FILENAME: &str = "lib.rmeta"; +/// Reads the SVH (Strict Version Hash) from an existing .rmeta file. +/// Returns `None` if the file doesn't exist or can't be read. +fn read_existing_metadata_hash(path: &Path) -> Option { + let file = fs::File::open(path).ok()?; + let mmap = unsafe { Mmap::map(file) }.ok()?; + let owned: OwnedSlice = slice_owned(mmap, Deref::deref); + let blob = MetadataBlob::new(owned).ok()?; + Some(blob.get_header().hash) +} + /// We use a temp directory here to avoid races between concurrent rustc processes, /// such as builds in the same directory using the same filename for metadata while /// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a @@ -90,7 +106,24 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { let (metadata_filename, metadata_tmpdir) = if need_metadata_file { let filename = match out_filename { OutFileName::Real(ref path) => { - if let Err(err) = non_durable_rename(&metadata_filename, path) { + // RDR optimization: when -Z separate-spans is enabled, skip writing + // the .rmeta file if its content hash (SVH) hasn't changed. This + // preserves the file's mtime, preventing cargo from rebuilding + // dependent crates when only span positions changed. + let new_hash = tcx.crate_hash(LOCAL_CRATE); + let skip_write = tcx.sess.opts.unstable_opts.separate_spans + && read_existing_metadata_hash(path).is_some_and(|old_hash| { + let matches = old_hash == new_hash; + if matches { + debug!("skipping .rmeta write: SVH unchanged ({:?})", new_hash); + } + matches + }); + + if skip_write { + // Remove the temp file since we're keeping the existing one + let _ = fs::remove_file(&metadata_filename); + } else if let Err(err) = non_durable_rename(&metadata_filename, path) { tcx.dcx().emit_fatal(FailedWriteError { filename: path.to_path_buf(), err }); } path.clone() diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index b731e75f3a4c2..d005933f6f632 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -951,7 +951,6 @@ fn get_rmeta_metadata_section<'a, 'p>(filename: &'p Path) -> Result Result { // mmap the file, because only a small fraction of it is read. let file = std::fs::File::open(filename) @@ -1036,6 +1035,7 @@ pub(crate) enum CrateError { DlSym(String, String), LocatorCombined(Box), NotFound(Symbol), + MissingSpanFile(Symbol, String), } enum MetadataError<'a> { @@ -1245,6 +1245,9 @@ impl CrateError { dcx.emit_err(error); } } + CrateError::MissingSpanFile(crate_name, reason) => { + dcx.emit_err(errors::MissingSpanFile { span, crate_name, reason }); + } } } } diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index ee3fd3ae2e440..85b656b4e5662 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -86,18 +86,12 @@ impl std::ops::Deref for SpanBlob { } } -#[allow(dead_code)] // Infrastructure for RDR span file loading impl SpanBlob { /// Runs the [`MemDecoder`] validation and if it passes, constructs a new [`SpanBlob`]. pub(crate) fn new(slice: OwnedSlice) -> Result { if MemDecoder::new(&slice, 0).is_ok() { Ok(Self(slice)) } else { Err(()) } } - /// Returns the underlying bytes. - pub(crate) fn bytes(&self) -> &OwnedSlice { - &self.0 - } - /// Checks if this span blob has a valid header. pub(crate) fn check_header(&self) -> bool { self.starts_with(SPAN_HEADER) @@ -144,7 +138,6 @@ pub(crate) type CrateNumMap = IndexVec; pub(crate) type TargetModifiers = Vec; /// Lazily-loaded span file data. Contains the span blob and decoded source_map table. -#[allow(dead_code)] // Infrastructure for RDR span file loading struct SpanFileData { blob: SpanBlob, source_map: LazyTable>>, @@ -156,10 +149,8 @@ pub(crate) struct CrateMetadata { /// Path to the span file (.spans), if one exists for this crate. /// The span file is loaded lazily on first span resolution. - #[allow(dead_code)] // Infrastructure for RDR span file loading span_file_path: Option, /// Lazily-loaded span file data. Populated on first access when span_file_path is Some. - #[allow(dead_code)] // Infrastructure for RDR span file loading span_file_data: OnceLock>, // --- Some data pre-decoded from the metadata blob, usually for performance --- @@ -384,14 +375,12 @@ impl<'a, 'tcx> Metadata<'a> for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { /// Decode context for span files (.spans). /// Similar to [`BlobDecodeContext`] but works with [`SpanBlob`] instead of [`MetadataBlob`]. -#[allow(dead_code)] // Infrastructure for RDR span file loading pub(super) struct SpanBlobDecodeContext<'a> { opaque: MemDecoder<'a>, - blob: &'a SpanBlob, lazy_state: LazyState, + _marker: PhantomData<&'a ()>, } -#[allow(dead_code)] // Infrastructure for RDR span file loading impl<'a> LazyDecoder for SpanBlobDecodeContext<'a> { fn set_lazy_state(&mut self, state: LazyState) { self.lazy_state = state; @@ -402,37 +391,22 @@ impl<'a> LazyDecoder for SpanBlobDecodeContext<'a> { } } -#[allow(dead_code)] // Infrastructure for RDR span file loading -impl<'a> SpanBlobDecodeContext<'a> { - #[inline] - pub(crate) fn blob(&self) -> &'a SpanBlob { - self.blob - } -} - /// Trait for decoding from span file blobs. /// Similar to [`Metadata`] but for [`SpanBlob`]. -#[allow(dead_code)] // Infrastructure for RDR span file loading pub(super) trait SpanMetadata<'a>: Copy { type Context: BlobDecoder + LazyDecoder; - fn blob(self) -> &'a SpanBlob; fn decoder(self, pos: usize) -> Self::Context; } -#[allow(dead_code)] // Infrastructure for RDR span file loading impl<'a> SpanMetadata<'a> for &'a SpanBlob { type Context = SpanBlobDecodeContext<'a>; - fn blob(self) -> &'a SpanBlob { - self - } - fn decoder(self, pos: usize) -> Self::Context { SpanBlobDecodeContext { opaque: MemDecoder::new(self, pos).unwrap(), lazy_state: LazyState::NoNode, - blob: self, + _marker: PhantomData, } } } @@ -2012,11 +1986,25 @@ impl<'a> CrateMetadataRef<'a> { return Some(cached.clone()); } - // Try to load the source file from the main metadata blob. - // TODO: When -Z separate_spans is enabled, load from span blob instead. - let source_file_to_import = self.root.source_map.get((self, tcx), source_file_index).map(|lazy| { - lazy.decode((self, tcx)) - }); + let source_file_to_import = if self.root.has_separate_spans() { + let span_data = self.cdata.span_file_data().expect( + "span file should be loaded for crate compiled with -Z separate-spans", + ); + span_data + .source_map + .get_from_span_blob(&span_data.blob, source_file_index) + .map(|lazy| lazy.decode_span(&span_data.blob)) + } else if let Some(span_data) = self.cdata.span_file_data() { + span_data + .source_map + .get_from_span_blob(&span_data.blob, source_file_index) + .map(|lazy| lazy.decode_span(&span_data.blob)) + } else { + self.root + .source_map + .get((self, tcx), source_file_index) + .map(|lazy| lazy.decode((self, tcx))) + }; // Return None for sparse table entries. This can happen when: // - Searching through all source file indices (some may be empty) @@ -2280,7 +2268,6 @@ impl CrateMetadata { /// Lazily loads the span file data if a span file path is configured. /// Returns None if no span file exists or if loading fails. - #[allow(dead_code)] // Infrastructure for RDR span resolution fn span_file_data(&self) -> Option<&SpanFileData> { self.span_file_data .get_or_init(|| { @@ -2291,7 +2278,6 @@ impl CrateMetadata { } /// Loads and parses the span file from disk. - #[allow(dead_code)] // Called by span_file_data() fn load_span_file(&self, path: &Path) -> Result { use crate::locator::get_span_metadata_section; @@ -2505,6 +2491,25 @@ impl SpanResolver for TyCtxtSpanResolver<'_> { // File not in source map - find the source crate and import from its metadata. // This mirrors how the original span encoding works: we know exactly which // crate's metadata contains the source file. + + // Safely look up the crate - it may not exist in stale incremental state + // or if a dependency was removed. + let source_cnum = if source_crate == self.tcx.stable_crate_id(LOCAL_CRATE) { + LOCAL_CRATE + } else { + match self.tcx.untracked().stable_crate_ids.read().get(&source_crate).copied() { + Some(cnum) => cnum, + None => { + debug!( + ?source_crate, + ?file, + "resolve_span_ref: unknown crate, falling back to dummy span" + ); + return DUMMY_SP.with_ctxt(ctxt); + } + } + }; + let cstore = CStore::from_tcx(self.tcx); debug!(?source_crate, ?source_cnum, "resolve_span_ref: converted stable crate id"); let cref = cstore.get_crate_data(source_cnum); diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index 3b5a38181d58b..091784b93a6c5 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -1,7 +1,7 @@ use rustc_hir::def::CtorOf; use rustc_index::Idx; -use crate::rmeta::decoder::Metadata; +use crate::rmeta::decoder::{Metadata, SpanBlob}; use crate::rmeta::*; pub(super) trait IsDefault: Default { @@ -547,4 +547,26 @@ where pub(super) fn size(&self) -> usize { self.len } + + /// Given a span blob, extract out the value at a particular index (if any). + /// This is similar to `get` but works with span blobs for RDR span file loading. + pub(super) fn get_from_span_blob<'a, 'tcx>(&self, blob: &'a SpanBlob, i: I) -> T::Value<'tcx> { + // Access past the end of the table returns a Default + if i.index() >= self.len { + return Default::default(); + } + + let width = self.width; + let start = self.position.get() + (width * i.index()); + let end = start + width; + let bytes = &blob[start..end]; + + if let Ok(fixed) = bytes.try_into() { + FixedSizeEncoding::from_bytes(fixed) + } else { + let mut fixed = [0u8; N]; + fixed[..width].copy_from_slice(bytes); + FixedSizeEncoding::from_bytes(&fixed) + } + } } diff --git a/compiler/rustc_query_system/src/dep_graph/graph.rs b/compiler/rustc_query_system/src/dep_graph/graph.rs index 0b50d376b5521..b0d9f23286276 100644 --- a/compiler/rustc_query_system/src/dep_graph/graph.rs +++ b/compiler/rustc_query_system/src/dep_graph/graph.rs @@ -27,7 +27,7 @@ use super::serialized::{GraphEncoder, SerializedDepGraph, SerializedDepNodeIndex use super::{DepContext, DepKind, DepNode, Deps, HasDepContext, WorkProductId}; use crate::dep_graph::edges::EdgesVec; use crate::ich::StableHashingContext; -use crate::query::{QueryContext, QuerySideEffect}; +use crate::query::{DiagnosticSideEffect, QueryContext, QuerySideEffect}; #[derive(Clone)] pub struct DepGraph { @@ -676,7 +676,11 @@ impl DepGraphData { // diagnostic. std::iter::once(DepNodeIndex::FOREVER_RED_NODE).collect(), ); - let side_effect = QuerySideEffect::Diagnostic(diagnostic.clone()); + let spans_hash = qcx.dep_context().sess().diagnostic_spans_hash(); + let side_effect = QuerySideEffect::Diagnostic(DiagnosticSideEffect { + diagnostic: diagnostic.clone(), + spans_hash, + }); qcx.store_side_effect(dep_node_index, side_effect); dep_node_index } @@ -693,8 +697,18 @@ impl DepGraphData { let side_effect = qcx.load_side_effect(prev_index).unwrap(); match &side_effect { - QuerySideEffect::Diagnostic(diagnostic) => { - qcx.dep_context().sess().dcx().emit_diagnostic(diagnostic.clone()); + QuerySideEffect::Diagnostic(side_effect) => { + let current_hash = qcx.dep_context().sess().diagnostic_spans_hash(); + let should_emit = match (side_effect.spans_hash, current_hash) { + (Some(prev), Some(current)) => prev == current, + _ => true, + }; + if should_emit { + qcx.dep_context() + .sess() + .dcx() + .emit_diagnostic(side_effect.diagnostic.clone()); + } } } diff --git a/compiler/rustc_query_system/src/query/mod.rs b/compiler/rustc_query_system/src/query/mod.rs index b524756d81b61..e25daf95c3027 100644 --- a/compiler/rustc_query_system/src/query/mod.rs +++ b/compiler/rustc_query_system/src/query/mod.rs @@ -61,6 +61,13 @@ impl QueryStackFrame { } } +/// A diagnostic side effect stored for query replay. +#[derive(Debug, Encodable, Decodable)] +pub struct DiagnosticSideEffect { + pub diagnostic: DiagInner, + pub spans_hash: Option, +} + /// Tracks 'side effects' for a particular query. /// This struct is saved to disk along with the query result, /// and loaded from disk if we mark the query as green. @@ -76,7 +83,7 @@ pub enum QuerySideEffect { /// This diagnostic will be re-emitted if we mark /// the query as green, as that query will have the side /// effect dep node as a dependency. - Diagnostic(DiagInner), + Diagnostic(DiagnosticSideEffect), } pub trait QueryContext: HasDepContext { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 1d5b36fc61b8b..02c65dda2912b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,7 +1,8 @@ use std::any::Any; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, OnceLock}; use std::sync::atomic::AtomicBool; use std::{env, io}; @@ -10,6 +11,7 @@ use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN}; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef}; +use rustc_data_structures::stable_hasher::StableHasher; use rustc_data_structures::sync::{DynSend, DynSync, Lock, MappedReadGuard, ReadGuard, RwLock}; use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; use rustc_errors::codes::*; @@ -23,7 +25,7 @@ use rustc_errors::{ }; use rustc_hir::limit::Limit; use rustc_macros::HashStable_Generic; -pub use rustc_span::def_id::StableCrateId; +pub use rustc_span::def_id::{LOCAL_CRATE, StableCrateId}; use rustc_span::edition::Edition; use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::{RealFileName, Span, Symbol}; @@ -106,6 +108,8 @@ pub struct Session { /// Data about code being compiled, gathered during compilation. pub code_stats: CodeStats, + diagnostic_spans_hash: OnceLock>, + /// This only ever stores a `LintStore` but we don't want a dependency on that type here. pub lint_store: Option>, @@ -280,6 +284,24 @@ impl Session { self.psess.source_map() } + pub fn diagnostic_spans_hash(&self) -> Option { + if !self.opts.unstable_opts.separate_spans { + return None; + } + + *self.diagnostic_spans_hash.get_or_init(|| { + let mut hasher = StableHasher::new(); + for source_file in self.source_map().files().iter() { + if source_file.cnum != LOCAL_CRATE { + continue; + } + source_file.stable_id.hash(&mut hasher); + source_file.src_hash.hash(&mut hasher); + } + Some(hasher.finish::().as_u64()) + }) + } + /// Returns `true` if internal lints should be added to the lint store - i.e. if /// `-Zunstable-options` is provided and this isn't rustdoc (internal lints can trigger errors /// to be emitted under rustdoc). @@ -1082,6 +1104,7 @@ pub fn build_session( prof, timings, code_stats: Default::default(), + diagnostic_spans_hash: OnceLock::new(), lint_store: None, driver_lint_caps, ctfe_backtrace, diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index d2d1f400496d8..c27da0ba7f5b2 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -3019,14 +3019,18 @@ where const TAG_INVALID_SPAN: u8 = 1; const TAG_RELATIVE_SPAN: u8 = 2; - if !ctx.hash_spans() { - return; - } - let span = self.data_untracked(); + + // Always hash the syntax context and parent - they carry semantic information + // (hygiene/macro expansion context) that distinguishes identifiers even when + // span positions are ignored with `-Z incremental-ignore-spans`. span.ctxt.hash_stable(ctx, hasher); span.parent.hash_stable(ctx, hasher); + if !ctx.hash_spans() { + return; + } + if span.is_dummy() { Hash::hash(&TAG_INVALID_SPAN, hasher); return; @@ -3095,32 +3099,39 @@ where /// Hashes a SpanRef, respecting the `hash_spans()` setting. /// /// When `hash_spans()` returns false (e.g., with `-Z incremental-ignore-spans`), - /// SpanRef contributes nothing to the hash. This enables RDR (Relink, Don't - /// Rebuild) where span-only changes don't trigger rebuilds. + /// only the syntax context is hashed - positions are skipped. This enables RDR + /// (Relink, Don't Rebuild) where span position changes don't trigger rebuilds, + /// while still distinguishing spans with different hygiene contexts. fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { + // Always hash the syntax context - it carries semantic information + // (hygiene/macro expansion context) that distinguishes identifiers. + let ctxt = match self { + SpanRef::Opaque { ctxt } => ctxt, + SpanRef::FileRelative { ctxt, .. } => ctxt, + SpanRef::DefRelative { ctxt, .. } => ctxt, + }; + ctxt.hash_stable(ctx, hasher); + if !ctx.hash_spans() { return; } match self { - SpanRef::Opaque { ctxt } => { + SpanRef::Opaque { .. } => { 0u8.hash_stable(ctx, hasher); - ctxt.hash_stable(ctx, hasher); } - SpanRef::FileRelative { source_crate, file, lo, hi, ctxt } => { + SpanRef::FileRelative { source_crate, file, lo, hi, .. } => { 1u8.hash_stable(ctx, hasher); source_crate.hash_stable(ctx, hasher); file.hash_stable(ctx, hasher); lo.hash_stable(ctx, hasher); hi.hash_stable(ctx, hasher); - ctxt.hash_stable(ctx, hasher); } - SpanRef::DefRelative { def_id, lo, hi, ctxt } => { + SpanRef::DefRelative { def_id, lo, hi, .. } => { 2u8.hash_stable(ctx, hasher); def_id.hash_stable(ctx, hasher); lo.hash_stable(ctx, hasher); hi.hash_stable(ctx, hasher); - ctxt.hash_stable(ctx, hasher); } } } diff --git a/tests/run-make/rdr-diagnostic-gating/rmake.rs b/tests/run-make/rdr-diagnostic-gating/rmake.rs new file mode 100644 index 0000000000000..44e5ac8c71b2f --- /dev/null +++ b/tests/run-make/rdr-diagnostic-gating/rmake.rs @@ -0,0 +1,39 @@ +// Check that diagnostic replay is gated by the spans hash when using -Z separate-spans. +//@ ignore-cross-compile +// Reason: uses incremental directory tied to host toolchain paths + +use run_make_support::{rfs, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + let source_v1 = "fn main() { let unused = 1; }\n"; + rfs::write("main.rs", source_v1); + + let output1 = rustc() + .input("main.rs") + .incremental("incr") + .arg("-Zseparate-spans") + .arg("-Zincremental-ignore-spans") + .run(); + output1.assert_stderr_contains("unused variable"); + + let output2 = rustc() + .input("main.rs") + .incremental("incr") + .arg("-Zseparate-spans") + .arg("-Zincremental-ignore-spans") + .run(); + output2.assert_stderr_contains("unused variable"); + + let source_v2 = "// span-only change\nfn main() { let unused = 1; }\n"; + rfs::write("main.rs", source_v2); + + let output3 = rustc() + .input("main.rs") + .incremental("incr") + .arg("-Zseparate-spans") + .arg("-Zincremental-ignore-spans") + .run(); + output3.assert_stderr_not_contains("unused variable"); + }); +} diff --git a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs new file mode 100644 index 0000000000000..4e5f3748dc81c --- /dev/null +++ b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs @@ -0,0 +1,99 @@ +// Test that identifiers with the same name but different hygiene contexts +// don't cause dep node hash collisions when using `-Zincremental-ignore-spans`. +// +// This regression test verifies that SyntaxContext (hygiene info) is always +// hashed even when span positions are ignored. Without this fix, two `Ident`s +// with the same `Symbol` but different `SyntaxContext` would hash identically, +// causing ICE: "query key X and key Y mapped to the same dep node". +// +// The issue manifests with macros that generate identifiers - each macro +// expansion creates identifiers with different hygiene contexts, but when +// spans are ignored, only the symbol name was being hashed. +// +//@ ignore-cross-compile + +use run_make_support::{rfs, rustc}; + +fn main() { + // Create a library with macro-generated code that creates identifiers + // with the same name but different hygiene contexts. + // + // The key is to generate code that triggers queries like + // `explicit_supertraits_containing_assoc_item` with identifiers that + // have the same Symbol but different SyntaxContext. + let lib_source = r#" +// Macro that generates a trait with an associated type. +// Each invocation creates identifiers with different SyntaxContext. +macro_rules! make_trait { + ($trait_name:ident, $assoc_name:ident) => { + pub trait $trait_name { + type $assoc_name; + } + }; +} + +// Generate traits with different associated type names +make_trait!(TraitA, ItemA); +make_trait!(TraitB, ItemB); +make_trait!(TraitC, ItemC); + +// Trait that combines them - triggers supertraits_containing_assoc_item queries +pub trait Combined: TraitA + TraitB + TraitC { + fn get_a(&self) -> ::ItemA; + fn get_b(&self) -> ::ItemB; + fn get_c(&self) -> ::ItemC; +} + +// Macro that generates impls - each expansion has different hygiene +macro_rules! impl_for { + ($ty:ty, $trait:ident, $assoc:ident, $val:ty) => { + impl $trait for $ty { + type $assoc = $val; + } + }; +} + +pub struct MyStruct; +impl_for!(MyStruct, TraitA, ItemA, i32); +impl_for!(MyStruct, TraitB, ItemB, i64); +impl_for!(MyStruct, TraitC, ItemC, u32); + +impl Combined for MyStruct { + fn get_a(&self) -> i32 { 0 } + fn get_b(&self) -> i64 { 0 } + fn get_c(&self) -> u32 { 0 } +} + +// More macro invocations to increase chance of hygiene collision +macro_rules! make_fn { + ($name:ident) => { + pub fn $name() {} + }; +} + +make_fn!(foo); +make_fn!(bar); +make_fn!(baz); +"#; + + rfs::write("lib.rs", lib_source); + + // Compile with RDR flags - this would ICE before the fix due to + // hygiene contexts not being hashed when spans are ignored. + rustc() + .input("lib.rs") + .crate_type("lib") + .incremental("incr") + .arg("-Zseparate-spans") + .arg("-Zincremental-ignore-spans") + .run(); + + // Second compilation to exercise incremental path + rustc() + .input("lib.rs") + .crate_type("lib") + .incremental("incr") + .arg("-Zseparate-spans") + .arg("-Zincremental-ignore-spans") + .run(); +} diff --git a/tests/run-make/rdr-missing-spans-fallback/rmake.rs b/tests/run-make/rdr-missing-spans-fallback/rmake.rs new file mode 100644 index 0000000000000..4bc26c519140c --- /dev/null +++ b/tests/run-make/rdr-missing-spans-fallback/rmake.rs @@ -0,0 +1,49 @@ +// Test that missing .spans file is treated as a hard error. +// +// This verifies that when a crate was compiled with `-Z separate-spans` and +// the `.spans` file is later deleted (simulating a corrupted incremental cache), +// the compiler produces a clear error message rather than silently degrading. +// +//@ ignore-cross-compile +// Reason: uses incremental directory tied to host toolchain paths + +use run_make_support::{rfs, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + // First, create a dependency crate compiled with -Z separate-spans + // Use --emit=metadata to produce a standalone .rmeta file + let dep_source = "pub fn dep_fn() -> i32 { 42 }\n"; + rfs::write("dep.rs", dep_source); + + // Compile the dependency with -Z separate-spans and emit both rlib and metadata + rustc() + .input("dep.rs") + .crate_type("rlib") + .emit("metadata,link") + .arg("-Zseparate-spans") + .run(); + + // Verify .spans file was created alongside .rmeta + let rmeta_path = std::path::Path::new("libdep.rmeta"); + let spans_path = std::path::Path::new("libdep.spans"); + let rlib_path = std::path::Path::new("libdep.rlib"); + assert!(rmeta_path.exists(), "expected libdep.rmeta to exist"); + assert!(spans_path.exists(), "expected libdep.spans to exist"); + assert!(rlib_path.exists(), "expected libdep.rlib to exist"); + + // Delete the .spans file to simulate corrupted cache + rfs::remove_file(spans_path); + + // Now try to compile a crate that depends on the dependency + // This should fail with a clear error about missing span data + let main_source = "extern crate dep; fn main() { dep::dep_fn(); }\n"; + rfs::write("main.rs", main_source); + + let output = rustc().input("main.rs").extern_("dep", "libdep.rlib").run_fail(); + + // Verify we get the expected error message + output.assert_stderr_contains("cannot load span data for crate"); + output.assert_stderr_contains("the incremental compilation cache may be corrupted"); + }); +} From 56a1f4fa633d45a09f46359369bc5ef4f32c1399 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:49:16 -0800 Subject: [PATCH 10/19] rdr: make SVH stable when only private items change Implement "Relink, Don't Rebuild" (RDR) by modifying the Strict Version Hash (SVH) computation to only include publicly-visible API elements, rather than the entire HIR. Previously, any change to a crate (including private function bodies) would change the SVH, causing all downstream crates to rebuild. Now, the SVH only changes when the public API changes. The new `public_api_hash` query hashes: - Public item signatures (types, bounds, generics, predicates) - Inlinable function bodies (#[inline] and generic functions) - Trait implementations Private function bodies are excluded from the hash, so modifying them no longer triggers downstream rebuilds. Internal incremental compilation continues to work correctly because it uses the query system's fine-grained dependency tracking, which is separate from the SVH. --- compiler/rustc_middle/src/hir/map.rs | 181 +++++++++++++++++- compiler/rustc_middle/src/hir/mod.rs | 1 + compiler/rustc_middle/src/query/erase.rs | 5 + compiler/rustc_middle/src/query/mod.rs | 7 + .../svh_stability/auxiliary/dependency.rs | 44 +++++ tests/incremental/svh_stability/main.rs | 58 ++++++ 6 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 tests/incremental/svh_stability/auxiliary/dependency.rs create mode 100644 tests/incremental/svh_stability/main.rs diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index ca783311586f1..f2e121abe93fe 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -20,6 +20,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, with_metavar_spans}; use crate::hir::{ModuleItems, nested_filter}; use crate::middle::debugger_visualizer::DebuggerVisualizerFile; +use crate::middle::privacy::EffectiveVisibilities; use crate::query::LocalCrate; use crate::ty::TyCtxt; @@ -1123,8 +1124,12 @@ impl<'tcx> pprust_hir::PpAnn for TyCtxt<'tcx> { } pub(super) fn crate_hash(tcx: TyCtxt<'_>, _: LocalCrate) -> Svh { - let krate = tcx.hir_crate(()); - let hir_body_hash = krate.opt_hir_hash.expect("HIR hash missing while computing crate hash"); + // Use public_api_hash for the SVH - this is what downstream crates use to decide + // whether to rebuild. By only hashing public API items, downstream crates won't + // rebuild when private items change. + // Internal incremental compilation uses the dep graph's fine-grained fingerprinting, + // which is separate from the SVH. + let body_hash = tcx.public_api_hash(()); let upstream_crates = upstream_crates(tcx); @@ -1162,7 +1167,7 @@ pub(super) fn crate_hash(tcx: TyCtxt<'_>, _: LocalCrate) -> Svh { let crate_hash: Fingerprint = tcx.with_stable_hashing_context(|mut hcx| { let mut stable_hasher = StableHasher::new(); - hir_body_hash.hash_stable(&mut hcx, &mut stable_hasher); + body_hash.hash_stable(&mut hcx, &mut stable_hasher); upstream_crates.hash_stable(&mut hcx, &mut stable_hasher); source_file_names.hash_stable(&mut hcx, &mut stable_hasher); debugger_visualizers.hash_stable(&mut hcx, &mut stable_hasher); @@ -1211,6 +1216,176 @@ fn upstream_crates(tcx: TyCtxt<'_>) -> Vec<(StableCrateId, Svh)> { upstream_crates } +/// Hashes an item's public signature (types, bounds, etc.) +fn hash_item_signature( + tcx: TyCtxt<'_>, + def_id: LocalDefId, + hcx: &mut rustc_query_system::ich::StableHashingContext<'_>, + hasher: &mut StableHasher, +) { + let def_kind = tcx.def_kind(def_id); + def_kind.hash_stable(hcx, hasher); + + match def_kind { + DefKind::Fn | DefKind::AssocFn => { + tcx.fn_sig(def_id).instantiate_identity().hash_stable(hcx, hasher); + tcx.generics_of(def_id).hash_stable(hcx, hasher); + tcx.predicates_of(def_id).hash_stable(hcx, hasher); + } + DefKind::Struct | DefKind::Enum | DefKind::Union => { + tcx.adt_def(def_id).hash_stable(hcx, hasher); + tcx.generics_of(def_id).hash_stable(hcx, hasher); + tcx.predicates_of(def_id).hash_stable(hcx, hasher); + } + DefKind::Const | DefKind::AssocConst | DefKind::Static { .. } => { + tcx.type_of(def_id).instantiate_identity().hash_stable(hcx, hasher); + } + DefKind::TyAlias => { + tcx.type_of(def_id).instantiate_identity().hash_stable(hcx, hasher); + tcx.generics_of(def_id).hash_stable(hcx, hasher); + } + DefKind::Trait => { + tcx.generics_of(def_id).hash_stable(hcx, hasher); + tcx.explicit_super_predicates_of(def_id).hash_stable(hcx, hasher); + for assoc in tcx.associated_items(def_id).in_definition_order() { + assoc.def_id.hash_stable(hcx, hasher); + assoc.kind.hash_stable(hcx, hasher); + } + } + DefKind::Impl { .. } => { + if let Some(trait_ref) = tcx.impl_opt_trait_ref(def_id) { + trait_ref.instantiate_identity().hash_stable(hcx, hasher); + } + tcx.type_of(def_id).instantiate_identity().hash_stable(hcx, hasher); + } + DefKind::Macro(_) => { + // Macro presence is hashed via DefPathHash; body hashing deferred for now + } + _ => {} + } +} + +/// Hashes bodies of functions that may be used by downstream crates. +/// This includes generic functions (monomorphized downstream) and #[inline] functions. +/// We use the HIR body hash to avoid query cycles with MIR. +fn hash_inlinable_bodies( + tcx: TyCtxt<'_>, + _effective_vis: &EffectiveVisibilities, + hcx: &mut rustc_query_system::ich::StableHashingContext<'_>, + hasher: &mut StableHasher, +) { + let reachable_set = tcx.reachable_set(()); + + // Collect functions whose bodies may be used by downstream crates: + // - Generic functions (monomorphized downstream) + // - #[inline] functions (inlined downstream) + let mut inlinable_bodies: Vec<_> = tcx + .hir_crate_items(()) + .definitions() + .filter_map(|def_id| { + if !reachable_set.contains(&def_id) { + return None; + } + + let def_kind = tcx.def_kind(def_id); + let is_fn_like = matches!( + def_kind, + DefKind::Fn | DefKind::AssocFn | DefKind::Closure | DefKind::Ctor(..) + ); + if !is_fn_like { + return None; + } + + // Include if generic (requires monomorphization) or cross-crate inlinable + let dominated = tcx.generics_of(def_id).requires_monomorphization(tcx) + || tcx.cross_crate_inlinable(def_id); + if !dominated { + return None; + } + + // Get the HIR body hash for this item + let def_path_hash = tcx.def_path_hash(def_id.into()); + let body_hash = + tcx.opt_hir_owner_nodes(def_id).and_then(|nodes| nodes.opt_hash_including_bodies); + + Some((def_path_hash, body_hash)) + }) + .collect(); + + // Sort for deterministic hashing + inlinable_bodies.sort_unstable_by_key(|item| item.0); + + // Hash both the identity and body of inlinable functions + for (def_path_hash, body_hash) in inlinable_bodies { + def_path_hash.hash_stable(hcx, hasher); + body_hash.hash_stable(hcx, hasher); + } +} + +/// Hashes all trait implementations. +fn hash_trait_impls( + tcx: TyCtxt<'_>, + hcx: &mut rustc_query_system::ich::StableHashingContext<'_>, + hasher: &mut StableHasher, +) { + let mut impl_hashes: Vec<_> = tcx + .all_local_trait_impls(()) + .iter() + .map(|(&trait_def_id, impls)| { + let trait_hash = tcx.def_path_hash(trait_def_id); + let mut impl_hasher = StableHasher::new(); + for &impl_def_id in impls { + tcx.def_path_hash(impl_def_id.into()).hash_stable(hcx, &mut impl_hasher); + if let Some(trait_ref) = tcx.impl_opt_trait_ref(impl_def_id) { + trait_ref.instantiate_identity().hash_stable(hcx, &mut impl_hasher); + } + tcx.type_of(impl_def_id).instantiate_identity().hash_stable(hcx, &mut impl_hasher); + } + (trait_hash, impl_hasher.finish::()) + }) + .collect(); + + impl_hashes.sort_unstable_by_key(|h| h.0); + impl_hashes.hash_stable(hcx, hasher); +} + +/// Computes a hash of only the publicly-reachable API. +/// This hash is stable when private items change. +pub(super) fn public_api_hash(tcx: TyCtxt<'_>, _: ()) -> Fingerprint { + let effective_vis = tcx.effective_visibilities(()); + let definitions = tcx.untracked().definitions.freeze(); + + // Collect reachable items with their type signatures + let mut public_items: Vec<_> = tcx + .hir_crate_items(()) + .definitions() + .filter(|&def_id| effective_vis.is_reachable(def_id)) + .map(|def_id| { + let def_path_hash = definitions.def_path_hash(def_id); + (def_path_hash, def_id) + }) + .collect(); + + public_items.sort_unstable_by_key(|item| item.0); + + tcx.with_stable_hashing_context(|mut hcx| { + let mut stable_hasher = StableHasher::new(); + + for (def_path_hash, def_id) in &public_items { + def_path_hash.hash_stable(&mut hcx, &mut stable_hasher); + hash_item_signature(tcx, *def_id, &mut hcx, &mut stable_hasher); + } + + // Hash inline/generic function bodies that are encoded in metadata + hash_inlinable_bodies(tcx, effective_vis, &mut hcx, &mut stable_hasher); + + // Hash all trait impls (affect trait resolution) + hash_trait_impls(tcx, &mut hcx, &mut stable_hasher); + + stable_hasher.finish() + }) +} + pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> ModuleItems { let mut collector = ItemCollector::new(tcx, false); diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index ba2d8febad7c5..98612eb7b45c0 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -378,6 +378,7 @@ pub struct Hashes { pub fn provide(providers: &mut Providers) { providers.hir_crate_items = map::hir_crate_items; providers.crate_hash = map::crate_hash; + providers.public_api_hash = map::public_api_hash; providers.hir_module_items = map::hir_module_items; providers.local_def_id_to_hir_id = |tcx, def_id| match tcx.hir_crate(()).owners[def_id] { MaybeOwner::Owner(_) => HirId::make_owner(def_id), diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 940cc30c17e6e..15a5f95c60b11 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -3,6 +3,7 @@ use std::intrinsics::transmute_unchecked; use std::mem::MaybeUninit; use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fingerprint::Fingerprint; use rustc_span::ErrorGuaranteed; use rustc_span::source_map::Spanned; @@ -77,6 +78,10 @@ impl EraseType for &'_ OsStr { type Result = [u8; size_of::<&'static OsStr>()]; } +impl EraseType for Fingerprint { + type Result = [u8; size_of::()]; +} + impl EraseType for &'_ ty::List { type Result = [u8; size_of::<&'static ty::List<()>>()]; } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 2f83f3078e89f..c3cab9b6a2e4a 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -71,6 +71,7 @@ use rustc_abi::Align; use rustc_arena::TypedArena; use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::steal::Steal; @@ -2051,6 +2052,12 @@ rustc_queries! { desc { "looking up the proc macro declarations for a crate" } } + /// Computes a hash of only the publicly-reachable API. + /// This hash is stable when private items change. + query public_api_hash(_: ()) -> Fingerprint { + desc { "computing public API hash" } + } + // The macro which defines `rustc_metadata::provide_extern` depends on this query's name. // Changing the name should cause a compiler error, but in case that changes, be aware. // diff --git a/tests/incremental/svh_stability/auxiliary/dependency.rs b/tests/incremental/svh_stability/auxiliary/dependency.rs new file mode 100644 index 0000000000000..63c755d1438c1 --- /dev/null +++ b/tests/incremental/svh_stability/auxiliary/dependency.rs @@ -0,0 +1,44 @@ +//@ revisions:rpass1 rpass2 rpass3 + +// A dependency crate with: +// - A private function that changes (rpass1 -> rpass2) +// - A public non-inlinable function +// - A public inlinable function that changes (rpass2 -> rpass3) + +// Private function - changes between rpass1 and rpass2 +// This should NOT affect downstream crates' SVH +fn private_helper() -> i32 { + #[cfg(rpass1)] + { 1 } + + #[cfg(any(rpass2, rpass3))] + { 2 } +} + +// Public function that calls the private helper +// Its body is NOT inlined downstream (no #[inline]) +pub fn public_function() -> i32 { + private_helper() + 10 +} + +// Public inlinable function - changes between rpass2 and rpass3 +// This SHOULD affect downstream crates' SVH +#[inline] +pub fn inlinable_function() -> i32 { + #[cfg(any(rpass1, rpass2))] + { 100 } + + #[cfg(rpass3)] + { 200 } +} + +// Public struct - unchanged across all revisions +pub struct Data { + pub value: i32, +} + +impl Data { + pub fn new(v: i32) -> Self { + Data { value: v } + } +} diff --git a/tests/incremental/svh_stability/main.rs b/tests/incremental/svh_stability/main.rs new file mode 100644 index 0000000000000..f02d687181bb5 --- /dev/null +++ b/tests/incremental/svh_stability/main.rs @@ -0,0 +1,58 @@ +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: dependency.rs +//@ ignore-backends: gcc + +// This test verifies SVH (Strict Version Hash) stability behavior: +// +// rpass1 -> rpass2: Only a PRIVATE function body changes in the dependency. +// The SVH should NOT change, so this crate should be fully reused. +// +// rpass2 -> rpass3: An INLINABLE (#[inline]) function body changes. +// The SVH SHOULD change, so this crate needs to be re-codegened. + +#![feature(rustc_attrs)] + +// When private function changes (rpass1 -> rpass2): ALL modules should be REUSED +// because the SVH doesn't change when only private items change. +#![rustc_partition_reused(module="main-use_public", cfg="rpass2")] +#![rustc_partition_reused(module="main-use_inlinable", cfg="rpass2")] +#![rustc_partition_reused(module="main-use_struct", cfg="rpass2")] + +// When inlinable function changes (rpass2 -> rpass3): only the module that +// uses the inlinable function should be CODEGENED. Other modules are REUSED. +#![rustc_partition_reused(module="main-use_public", cfg="rpass3")] +#![rustc_partition_codegened(module="main-use_inlinable", cfg="rpass3")] +#![rustc_partition_reused(module="main-use_struct", cfg="rpass3")] + +extern crate dependency; + +pub mod use_public { + use dependency::public_function; + + pub fn call_public() -> i32 { + public_function() + } +} + +pub mod use_inlinable { + use dependency::inlinable_function; + + pub fn call_inlinable() -> i32 { + inlinable_function() + } +} + +pub mod use_struct { + use dependency::Data; + + pub fn make_data() -> Data { + Data::new(42) + } +} + +fn main() { + let _ = use_public::call_public(); + let _ = use_inlinable::call_inlinable(); + let _ = use_struct::make_data(); +} From de0cb5720c1c6e03aa1a8d57af39f90d5a8bda37 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 15:49:16 -0800 Subject: [PATCH 11/19] rdr: add tests for panic locations, debuginfo, and coverage Add run-make tests to verify RDR works correctly with: 1. Panic locations (rdr-panic-location): - Verifies panic messages show correct file:line:col - Tests public functions, private functions, and format strings - Ensures span resolution works for panic messages 2. Debug info (rdr-debuginfo): - Verifies DWARF debug info contains correct source references - Tests with -Cdebuginfo=2 and -Zseparate-spans - Checks reproducibility of debug line tables 3. Coverage instrumentation (rdr-coverage): - Verifies coverage profiling works with -Zseparate-spans - Tests generation of profraw data - Exercises various control flow patterns (branches, loops) --- compiler/rustc_metadata/src/rmeta/decoder.rs | 2 +- tests/run-make/rdr-coverage/lib.rs | 47 ++++++++++++++ tests/run-make/rdr-coverage/main.rs | 30 +++++++++ tests/run-make/rdr-coverage/rmake.rs | 67 ++++++++++++++++++++ tests/run-make/rdr-debuginfo/lib.rs | 43 +++++++++++++ tests/run-make/rdr-debuginfo/rmake.rs | 52 +++++++++++++++ tests/run-make/rdr-panic-location/dep.rs | 38 +++++++++++ tests/run-make/rdr-panic-location/main.rs | 18 ++++++ tests/run-make/rdr-panic-location/rmake.rs | 63 ++++++++++++++++++ 9 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 tests/run-make/rdr-coverage/lib.rs create mode 100644 tests/run-make/rdr-coverage/main.rs create mode 100644 tests/run-make/rdr-coverage/rmake.rs create mode 100644 tests/run-make/rdr-debuginfo/lib.rs create mode 100644 tests/run-make/rdr-debuginfo/rmake.rs create mode 100644 tests/run-make/rdr-panic-location/dep.rs create mode 100644 tests/run-make/rdr-panic-location/main.rs create mode 100644 tests/run-make/rdr-panic-location/rmake.rs diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 85b656b4e5662..8c10f0f291a71 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -2301,7 +2301,7 @@ impl CrateMetadata { )); } - Ok(SpanFileData { blob, source_map: root.source_map() }) + Ok(SpanFileData { blob, source_map: root.source_map }) } pub(crate) fn dependencies(&self) -> impl Iterator { diff --git a/tests/run-make/rdr-coverage/lib.rs b/tests/run-make/rdr-coverage/lib.rs new file mode 100644 index 0000000000000..a6b1e8b168c2d --- /dev/null +++ b/tests/run-make/rdr-coverage/lib.rs @@ -0,0 +1,47 @@ +// Library crate for testing coverage instrumentation with -Z separate-spans. +// Contains functions with various control flow for coverage testing. + +#![crate_type = "rlib"] +#![crate_name = "rdr_coverage_lib"] + +/// Simple function with linear control flow. +pub fn simple_add(a: i32, b: i32) -> i32 { + a + b +} + +/// Function with branching for coverage testing. +pub fn max_value(a: i32, b: i32) -> i32 { + if a > b { a } else { b } +} + +/// Function with a loop for coverage testing. +pub fn sum_to_n(n: i32) -> i32 { + let mut sum = 0; + for i in 1..=n { + sum += i; + } + sum +} + +/// Function with multiple branches. +pub fn classify_number(n: i32) -> &'static str { + if n < 0 { + "negative" + } else if n == 0 { + "zero" + } else if n < 10 { + "small positive" + } else { + "large positive" + } +} + +// Private function for testing coverage across visibility boundaries. +fn private_multiply(a: i32, b: i32) -> i32 { + a * b +} + +/// Public function that uses private helper. +pub fn square(n: i32) -> i32 { + private_multiply(n, n) +} diff --git a/tests/run-make/rdr-coverage/main.rs b/tests/run-make/rdr-coverage/main.rs new file mode 100644 index 0000000000000..7d09100d97d51 --- /dev/null +++ b/tests/run-make/rdr-coverage/main.rs @@ -0,0 +1,30 @@ +// Main crate that exercises the library functions for coverage. + +extern crate rdr_coverage_lib; + +use rdr_coverage_lib::*; + +fn main() { + // Exercise all functions to generate coverage data + + // Simple add + let _ = simple_add(1, 2); + + // Max value - exercise both branches + let _ = max_value(5, 3); // a > b branch + let _ = max_value(2, 7); // else branch + + // Sum to n + let _ = sum_to_n(10); + + // Classify number - exercise all branches + let _ = classify_number(-5); // negative + let _ = classify_number(0); // zero + let _ = classify_number(5); // small positive + let _ = classify_number(100); // large positive + + // Square (calls private helper) + let _ = square(4); + + println!("Coverage test completed"); +} diff --git a/tests/run-make/rdr-coverage/rmake.rs b/tests/run-make/rdr-coverage/rmake.rs new file mode 100644 index 0000000000000..ade428da7a6c5 --- /dev/null +++ b/tests/run-make/rdr-coverage/rmake.rs @@ -0,0 +1,67 @@ +// Test that coverage instrumentation works correctly with -Z separate-spans. +// +// This verifies that: +// 1. Coverage instrumentation compiles successfully with -Z separate-spans +// 2. Coverage profraw data is generated when running the instrumented binary +// 3. Coverage data can be converted to a report format +// +// Coverage regions should point to correct source locations even when +// spans are stored separately from metadata. + +//@ ignore-cross-compile +//@ needs-profiler-runtime + +use run_make_support::{cmd, cwd, llvm_profdata, rfs, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + // Build library with coverage instrumentation and -Z separate-spans + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Cinstrument-coverage") + .arg("-Zseparate-spans") + .run(); + + // Build main binary with coverage instrumentation + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("rdr_coverage_lib", "librdr_coverage_lib.rlib") + .arg("-Cinstrument-coverage") + .arg("-Zseparate-spans") + .run(); + + // Run the binary to generate coverage data + // The profraw file will be created in the current directory + let profraw_file = format!("{}/default_%m_%p.profraw", cwd().display()); + cmd("./main").env("LLVM_PROFILE_FILE", &profraw_file).run(); + + // Find the generated profraw file + let profraw_files: Vec<_> = rfs::read_dir(".") + .unwrap() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().map_or(false, |ext| ext == "profraw")) + .collect(); + + assert!(!profraw_files.is_empty(), "Should have generated at least one .profraw file"); + + // Merge the profraw data into profdata + let profdata_file = "coverage.profdata"; + llvm_profdata() + .arg("merge") + .arg("-sparse") + .args(profraw_files.iter().map(|e| e.path())) + .arg("-o") + .arg(profdata_file) + .run(); + + assert!(std::path::Path::new(profdata_file).exists(), "Should have created profdata file"); + + // Verify the profdata file is valid by checking it can be read + let profdata_size = rfs::metadata(profdata_file).unwrap().len(); + assert!(profdata_size > 0, "Profdata file should not be empty"); + + println!("Coverage test passed! Generated {} bytes of coverage data.", profdata_size); + }); +} diff --git a/tests/run-make/rdr-debuginfo/lib.rs b/tests/run-make/rdr-debuginfo/lib.rs new file mode 100644 index 0000000000000..6e02bd5fb9c3f --- /dev/null +++ b/tests/run-make/rdr-debuginfo/lib.rs @@ -0,0 +1,43 @@ +// Library crate for testing debuginfo with -Z separate-spans. +// Contains functions at known line numbers for DWARF verification. + +#![crate_type = "rlib"] +#![crate_name = "rdr_debuginfo_lib"] + +/// Public function at a known line for debuginfo testing. +/// Should appear at line 9 in DWARF info. +pub fn public_function(x: i32) -> i32 { + // Line 9 + let y = x + 1; // Line 10 + let z = y * 2; // Line 11 + z // Line 12 +} + +/// Another public function with more complex control flow. +/// Tests that debuginfo correctly handles multiple statements. +pub fn complex_function(a: i32, b: i32) -> i32 { + // Line 18 + let mut result = 0; // Line 19 + if a > b { + // Line 20 + result = a - b; // Line 21 + } else { + // Line 22 + result = b - a; // Line 23 + } // Line 24 + result // Line 25 +} + +// Private function - its line info should still be correct +// even though it's not part of the public API. +fn private_helper(x: i32) -> i32 { + // Line 30 + x * x // Line 31 +} + +/// Public function that calls a private helper. +/// Tests that debuginfo works across visibility boundaries. +pub fn calls_private(x: i32) -> i32 { + // Line 36 + private_helper(x) + 1 // Line 37 +} diff --git a/tests/run-make/rdr-debuginfo/rmake.rs b/tests/run-make/rdr-debuginfo/rmake.rs new file mode 100644 index 0000000000000..d24fdaf969602 --- /dev/null +++ b/tests/run-make/rdr-debuginfo/rmake.rs @@ -0,0 +1,52 @@ +// Test that debuginfo line numbers are correct when using -Z separate-spans. +// +// This verifies that DWARF debug information contains accurate file:line +// information even when spans are stored separately from metadata. +// +// We use llvm-dwarfdump to inspect the generated debug info and verify +// that function line numbers match their source locations. + +//@ ignore-cross-compile +//@ needs-llvm-components: aarch64 arm x86 + +use run_make_support::{llvm_dwarfdump, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + // Build with debuginfo and -Z separate-spans + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Cdebuginfo=2") + .arg("-Zseparate-spans") + .run(); + + // Use llvm-dwarfdump to extract debug info + let output = llvm_dwarfdump().arg("--debug-line").arg("librdr_debuginfo_lib.rlib").run(); + + let stdout = output.stdout_utf8(); + + // Verify that the debug line info contains references to our source file + assert!(stdout.contains("lib.rs"), "Debug info should reference lib.rs, got:\n{}", stdout); + + // Build again and verify reproducibility + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Cdebuginfo=2") + .arg("-Zseparate-spans") + .out_dir("second") + .run(); + + let output2 = + llvm_dwarfdump().arg("--debug-line").arg("second/librdr_debuginfo_lib.rlib").run(); + + let stdout2 = output2.stdout_utf8(); + + // The debug line tables should be identical + // (after accounting for path differences) + assert!(stdout2.contains("lib.rs"), "Second build debug info should also reference lib.rs"); + + println!("Debuginfo tests passed!"); + }); +} diff --git a/tests/run-make/rdr-panic-location/dep.rs b/tests/run-make/rdr-panic-location/dep.rs new file mode 100644 index 0000000000000..01df9c7f22aee --- /dev/null +++ b/tests/run-make/rdr-panic-location/dep.rs @@ -0,0 +1,38 @@ +// Dependency crate that can panic at known locations. +// The panic locations are used to verify that -Z separate-spans +// preserves correct file:line:col information. + +#![crate_type = "rlib"] +#![crate_name = "dep"] + +/// Public function that panics. The panic location should be +/// correctly reported even when compiled with -Z separate-spans. +#[inline(never)] +pub fn will_panic(trigger: bool) { + if trigger { + panic!("intentional panic for testing"); // Line 13 + } +} + +/// Public function with panic in a private helper. +/// Tests that spans in private code are still correctly resolved. +#[inline(never)] +pub fn panic_via_private(trigger: bool) { + private_panicker(trigger); +} + +#[inline(never)] +fn private_panicker(trigger: bool) { + if trigger { + panic!("panic from private function"); // Line 27 + } +} + +/// Panic with a formatted message to test span handling +/// in format string expansion. +#[inline(never)] +pub fn panic_with_format(value: i32) { + if value < 0 { + panic!("invalid value: {}", value); // Line 36 + } +} diff --git a/tests/run-make/rdr-panic-location/main.rs b/tests/run-make/rdr-panic-location/main.rs new file mode 100644 index 0000000000000..89da23a72b510 --- /dev/null +++ b/tests/run-make/rdr-panic-location/main.rs @@ -0,0 +1,18 @@ +// Main crate that triggers panics in the dependency. +// Used to verify panic locations are correct with -Z separate-spans. + +extern crate dep; + +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + let test = args.get(1).map(|s| s.as_str()).unwrap_or("public"); + + match test { + "public" => dep::will_panic(true), + "private" => dep::panic_via_private(true), + "format" => dep::panic_with_format(-1), + _ => panic!("unknown test: {}", test), + } +} diff --git a/tests/run-make/rdr-panic-location/rmake.rs b/tests/run-make/rdr-panic-location/rmake.rs new file mode 100644 index 0000000000000..a7d44a464c243 --- /dev/null +++ b/tests/run-make/rdr-panic-location/rmake.rs @@ -0,0 +1,63 @@ +// Test that panic locations are correct when using -Z separate-spans. +// +// This verifies that span information is correctly preserved/resolved +// so that panic messages show accurate file:line:col locations. +// +// We test three scenarios: +// 1. Panic in a public function +// 2. Panic in a private function called from a public one +// 3. Panic with format string arguments + +//@ ignore-cross-compile + +use run_make_support::{cmd, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + // Build dependency with -Z separate-spans + rustc().input("dep.rs").crate_type("rlib").arg("-Zseparate-spans").run(); + + // Build main crate linking to the dependency + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("dep", "libdep.rlib") + .arg("-Zseparate-spans") + .run(); + + // Test 1: Public function panic location + // The panic should show dep.rs:13 + let output = cmd("./main").arg("public").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("dep.rs:13"), + "Panic in public function should show dep.rs:13, got:\n{}", + stderr + ); + assert!(stderr.contains("intentional panic for testing"), "Should contain panic message"); + + // Test 2: Private function panic location + // The panic should show dep.rs:27 + let output = cmd("./main").arg("private").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("dep.rs:27"), + "Panic in private function should show dep.rs:27, got:\n{}", + stderr + ); + assert!(stderr.contains("panic from private function"), "Should contain panic message"); + + // Test 3: Format string panic location + // The panic should show dep.rs:36 + let output = cmd("./main").arg("format").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("dep.rs:36"), + "Panic with format should show dep.rs:36, got:\n{}", + stderr + ); + assert!(stderr.contains("invalid value: -1"), "Should contain formatted panic message"); + + println!("All panic location tests passed!"); + }); +} From d6d497ac437be54e02a6f717768cbd40ae46fcf2 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 18:05:04 -0800 Subject: [PATCH 12/19] test: update function_interfaces.rs for correct typeck invalidation The RDR changes now correctly hash syntax context even with -Zincremental-ignore-spans. This is a correctness fix: hygiene information must always be considered to avoid false cache hits when macro expansion contexts differ. For change_return_impl_trait, the trait bounds (Clone vs Copy) are semantic differences that should invalidate typeck regardless of span handling settings. The previous test expectation was incorrect - it relied on the old behavior where spans contributed nothing to the hash with -Zincremental-ignore-spans. --- tests/incremental/hashes/function_interfaces.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/incremental/hashes/function_interfaces.rs b/tests/incremental/hashes/function_interfaces.rs index 5b03f9ed6c679..7aa89478bc2a9 100644 --- a/tests/incremental/hashes/function_interfaces.rs +++ b/tests/incremental/hashes/function_interfaces.rs @@ -319,7 +319,10 @@ pub fn change_return_impl_trait() -> impl Clone { } #[cfg(not(any(cfail1,cfail4)))] -#[rustc_clean(cfg = "cfail2", except = "opt_hir_owner_nodes")] +// typeck is now correctly dirty even with -Zincremental-ignore-spans because +// we now always hash syntax context (for hygiene correctness), and the trait +// bounds (Clone vs Copy) are semantic differences that should invalidate typeck. +#[rustc_clean(cfg = "cfail2", except = "opt_hir_owner_nodes, typeck")] #[rustc_clean(cfg = "cfail3")] #[rustc_clean(cfg = "cfail5", except = "opt_hir_owner_nodes, typeck")] #[rustc_clean(cfg = "cfail6")] From b7bad8547df81ce6f248027bec55470b25baa556 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 16 Jan 2026 18:05:04 -0800 Subject: [PATCH 13/19] rdr: consolidate span loading --- compiler/rustc_metadata/src/creader.rs | 20 +++++-------------- compiler/rustc_metadata/src/fs.rs | 1 + compiler/rustc_metadata/src/locator.rs | 13 +++++++++++- compiler/rustc_metadata/src/rmeta/decoder.rs | 3 +-- .../src/rmeta/decoder/cstore_impl.rs | 3 +-- compiler/rustc_metadata/src/rmeta/encoder.rs | 16 +++++++-------- compiler/rustc_session/src/cstore.rs | 2 ++ compiler/rustc_session/src/session.rs | 3 ++- 8 files changed, 32 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 7980498171158..4ab0a4682ea46 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -133,7 +133,7 @@ impl<'a> std::fmt::Debug for CrateDump<'a> { writeln!(fmt, " hash: {}", data.hash())?; writeln!(fmt, " reqd: {:?}", data.dep_kind())?; writeln!(fmt, " priv: {:?}", data.is_private_dep())?; - let CrateSource { dylib, rlib, rmeta, sdylib_interface } = data.source(); + let CrateSource { dylib, rlib, rmeta, sdylib_interface, spans } = data.source(); if let Some(dylib) = dylib { writeln!(fmt, " dylib: {}", dylib.display())?; } @@ -146,6 +146,9 @@ impl<'a> std::fmt::Debug for CrateDump<'a> { if let Some(sdylib_interface) = sdylib_interface { writeln!(fmt, " sdylib interface: {}", sdylib_interface.display())?; } + if let Some(spans) = spans { + writeln!(fmt, " spans: {}", spans.display())?; + } } Ok(()) } @@ -628,22 +631,10 @@ impl CStore { None }; - // Check if a separate span file exists (for crates compiled with -Z separate_spans). - // The span file will be loaded lazily on first span resolution. - // Look for .spans file adjacent to rmeta first, then rlib if rmeta is not available. - let span_file_path = source - .rmeta - .as_ref() - .or(source.rlib.as_ref()) - .and_then(|path| { - let span_path = path.with_extension("spans"); - if span_path.exists() { Some(span_path) } else { None } - }); - let has_separate_spans = crate_root.has_separate_spans(); let crate_name = crate_root.name(); - if has_separate_spans && span_file_path.is_none() { + if has_separate_spans && source.spans.is_none() { return Err(CrateError::MissingSpanFile(crate_name, "file not found".to_string())); } @@ -652,7 +643,6 @@ impl CStore { self, metadata, crate_root, - span_file_path, raw_proc_macros, cnum, cnum_map, diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index e5afec9365f54..1001b3a41b5a5 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -7,6 +7,7 @@ use rustc_data_structures::owned_slice::{OwnedSlice, slice_owned}; use rustc_data_structures::svh::Svh; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_fs_util::TempDirBuilder; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_session::config::{CrateType, OutFileName, OutputType}; diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index d005933f6f632..db67529d8b937 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -543,7 +543,18 @@ impl<'a> CrateLocator<'a> { return Err(CrateError::FullMetadataNotFound(self.crate_name, CrateFlavor::SDylib)); } - let source = CrateSource { rmeta, rlib, dylib, sdylib_interface }; + // Look for .spans file adjacent to rmeta/rlib/dylib (in priority order). + let mut spans = None; + for candidate in [&rmeta, &rlib, &dylib] { + let Some(path) = candidate else { continue }; + let span_path = path.with_extension("spans"); + if span_path.exists() { + spans = Some(span_path); + break; + } + } + + let source = CrateSource { rmeta, rlib, dylib, sdylib_interface, spans }; Ok(slot.map(|(svh, metadata, _, _)| (svh, Library { source, metadata }))) } diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 8c10f0f291a71..1cbc3a780254c 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -2197,7 +2197,6 @@ impl CrateMetadata { cstore: &CStore, blob: MetadataBlob, root: CrateRoot, - span_file_path: Option, raw_proc_macros: Option<&'static [ProcMacro]>, cnum: CrateNum, cnum_map: CrateNumMap, @@ -2220,7 +2219,7 @@ impl CrateMetadata { let mut cdata = CrateMetadata { blob, - span_file_path, + span_file_path: source.spans.clone(), span_file_data: OnceLock::new(), root, trait_impls, diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 4708cb6f92ddc..8864a21f53e28 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -21,8 +21,7 @@ use rustc_session::cstore::{CrateStore, ExternCrate}; use rustc_span::hygiene::ExpnId; use rustc_span::{Ident, Span, SpanRef, SpanResolver, Symbol, kw}; -use super::{Decodable, DecodeIterator}; -use super::TyCtxtSpanResolver; +use super::{Decodable, DecodeIterator, TyCtxtSpanResolver}; use crate::creader::{CStore, LoadedMacro}; use crate::rmeta::AttrFlags; use crate::rmeta::table::IsDefault; diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 1b3adeabb288a..df0968f99f60b 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2828,7 +2828,7 @@ fn with_encode_metadata_header( } let file = ecx.opaque.file(); - if let Err(err) = encode_root_position(file, root_position) { + if let Err(err) = encode_root_position(file, root_position, METADATA_HEADER.len()) { tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } @@ -2887,21 +2887,21 @@ fn with_encode_span_header( } let file = ecx.opaque.file(); - if let Err(err) = encode_root_position(file, root_position) { + if let Err(err) = encode_root_position(file, root_position, SPAN_HEADER.len()) { tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } } -fn encode_root_position(mut file: &File, pos: usize) -> Result<(), std::io::Error> { - // We will return to this position after writing the root position. +fn encode_root_position( + mut file: &File, + pos: usize, + header_len: usize, +) -> Result<(), std::io::Error> { let pos_before_seek = file.stream_position().unwrap(); - // Encode the root position. - let header = METADATA_HEADER.len(); - file.seek(std::io::SeekFrom::Start(header as u64))?; + file.seek(std::io::SeekFrom::Start(header_len as u64))?; file.write_all(&pos.to_le_bytes())?; - // Return to the position where we are before writing the root position. file.seek(std::io::SeekFrom::Start(pos_before_seek))?; Ok(()) } diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index 2f969aefda182..108629a747e02 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -25,6 +25,8 @@ pub struct CrateSource { pub rlib: Option, pub rmeta: Option, pub sdylib_interface: Option, + /// Path to `.spans` file for crates compiled with `-Z separate-spans`. + pub spans: Option, } impl CrateSource { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 02c65dda2912b..ed96a6e8322e6 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,9 +1,9 @@ use std::any::Any; +use std::hash::Hash; use std::path::PathBuf; use std::str::FromStr; use std::sync::atomic::AtomicBool; use std::sync::{Arc, OnceLock}; -use std::sync::atomic::AtomicBool; use std::{env, io}; use rand::{RngCore, rng}; @@ -23,6 +23,7 @@ use rustc_errors::{ Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort, TerminalUrl, fallback_fluent_bundle, }; +use rustc_hashes::Hash64; use rustc_hir::limit::Limit; use rustc_macros::HashStable_Generic; pub use rustc_span::def_id::{LOCAL_CRATE, StableCrateId}; From 7d02a5680108cbae21067000462b39ea5f3283ac Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sat, 17 Jan 2026 15:23:33 -0800 Subject: [PATCH 14/19] rdr: add a bunch of tests exercising rdr --- .../rdr_async_fn/auxiliary/async_dep.rs | 33 ++++++++++++ tests/incremental/rdr_async_fn/main.rs | 20 ++++++++ .../rdr_blanket_impl/auxiliary/blanket_dep.rs | 33 ++++++++++++ tests/incremental/rdr_blanket_impl/main.rs | 27 ++++++++++ .../rdr_cfg_target/auxiliary/cfg_dep.rs | 50 +++++++++++++++++++ tests/incremental/rdr_cfg_target/main.rs | 23 +++++++++ .../auxiliary/closure_dep.rs | 33 ++++++++++++ .../incremental/rdr_closure_captures/main.rs | 22 ++++++++ .../rdr_const_generic/auxiliary/const_dep.rs | 31 ++++++++++++ tests/incremental/rdr_const_generic/main.rs | 25 ++++++++++ .../auxiliary/diamond_base.rs | 25 ++++++++++ .../auxiliary/diamond_left.rs | 8 +++ .../auxiliary/diamond_right.rs | 8 +++ tests/incremental/rdr_diamond_deps/main.rs | 28 +++++++++++ .../auxiliary/generic_dep.rs | 30 +++++++++++ .../rdr_generic_instantiation/main.rs | 26 ++++++++++ .../auxiliary/impl_trait_dep.rs | 36 +++++++++++++ .../incremental/rdr_impl_trait_return/main.rs | 22 ++++++++ .../auxiliary/derive_helper.rs | 47 +++++++++++++++++ .../incremental/rdr_proc_macro_derive/main.rs | 27 ++++++++++ .../rdr_reexport/auxiliary/reexport_base.rs | 30 +++++++++++ .../rdr_reexport/auxiliary/reexport_middle.rs | 7 +++ tests/incremental/rdr_reexport/main.rs | 22 ++++++++ .../run-make/rdr-async-panic-location/lib.rs | 14 ++++++ .../run-make/rdr-async-panic-location/main.rs | 34 +++++++++++++ .../rdr-async-panic-location/rmake.rs | 40 +++++++++++++++ tests/run-make/rdr-cdylib/lib.rs | 13 +++++ tests/run-make/rdr-cdylib/rmake.rs | 22 ++++++++ tests/run-make/rdr-lto/lib.rs | 14 ++++++ tests/run-make/rdr-lto/main.rs | 10 ++++ tests/run-make/rdr-lto/rmake.rs | 36 +++++++++++++ .../rdr-proc-macro-panic-location/main.rs | 17 +++++++ .../proc_macro_lib.rs | 30 +++++++++++ .../rdr-proc-macro-panic-location/rmake.rs | 37 ++++++++++++++ 34 files changed, 880 insertions(+) create mode 100644 tests/incremental/rdr_async_fn/auxiliary/async_dep.rs create mode 100644 tests/incremental/rdr_async_fn/main.rs create mode 100644 tests/incremental/rdr_blanket_impl/auxiliary/blanket_dep.rs create mode 100644 tests/incremental/rdr_blanket_impl/main.rs create mode 100644 tests/incremental/rdr_cfg_target/auxiliary/cfg_dep.rs create mode 100644 tests/incremental/rdr_cfg_target/main.rs create mode 100644 tests/incremental/rdr_closure_captures/auxiliary/closure_dep.rs create mode 100644 tests/incremental/rdr_closure_captures/main.rs create mode 100644 tests/incremental/rdr_const_generic/auxiliary/const_dep.rs create mode 100644 tests/incremental/rdr_const_generic/main.rs create mode 100644 tests/incremental/rdr_diamond_deps/auxiliary/diamond_base.rs create mode 100644 tests/incremental/rdr_diamond_deps/auxiliary/diamond_left.rs create mode 100644 tests/incremental/rdr_diamond_deps/auxiliary/diamond_right.rs create mode 100644 tests/incremental/rdr_diamond_deps/main.rs create mode 100644 tests/incremental/rdr_generic_instantiation/auxiliary/generic_dep.rs create mode 100644 tests/incremental/rdr_generic_instantiation/main.rs create mode 100644 tests/incremental/rdr_impl_trait_return/auxiliary/impl_trait_dep.rs create mode 100644 tests/incremental/rdr_impl_trait_return/main.rs create mode 100644 tests/incremental/rdr_proc_macro_derive/auxiliary/derive_helper.rs create mode 100644 tests/incremental/rdr_proc_macro_derive/main.rs create mode 100644 tests/incremental/rdr_reexport/auxiliary/reexport_base.rs create mode 100644 tests/incremental/rdr_reexport/auxiliary/reexport_middle.rs create mode 100644 tests/incremental/rdr_reexport/main.rs create mode 100644 tests/run-make/rdr-async-panic-location/lib.rs create mode 100644 tests/run-make/rdr-async-panic-location/main.rs create mode 100644 tests/run-make/rdr-async-panic-location/rmake.rs create mode 100644 tests/run-make/rdr-cdylib/lib.rs create mode 100644 tests/run-make/rdr-cdylib/rmake.rs create mode 100644 tests/run-make/rdr-lto/lib.rs create mode 100644 tests/run-make/rdr-lto/main.rs create mode 100644 tests/run-make/rdr-lto/rmake.rs create mode 100644 tests/run-make/rdr-proc-macro-panic-location/main.rs create mode 100644 tests/run-make/rdr-proc-macro-panic-location/proc_macro_lib.rs create mode 100644 tests/run-make/rdr-proc-macro-panic-location/rmake.rs diff --git a/tests/incremental/rdr_async_fn/auxiliary/async_dep.rs b/tests/incremental/rdr_async_fn/auxiliary/async_dep.rs new file mode 100644 index 0000000000000..3238df03b778b --- /dev/null +++ b/tests/incremental/rdr_async_fn/auxiliary/async_dep.rs @@ -0,0 +1,33 @@ +//@ edition: 2024 + +#![crate_name = "async_dep"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +async fn private_helper() -> u32 { + 42 +} + +#[cfg(rpass2)] +async fn private_helper() -> u32 { + let x = 21; + let y = 21; + x + y +} + +#[cfg(rpass3)] +async fn private_helper() -> u32 { + async { 42 }.await +} + +pub async fn public_async_fn() -> u32 { + private_helper().await +} + +pub struct AsyncStruct; + +impl AsyncStruct { + pub async fn method(&self) -> u32 { + private_helper().await + } +} diff --git a/tests/incremental/rdr_async_fn/main.rs b/tests/incremental/rdr_async_fn/main.rs new file mode 100644 index 0000000000000..b822fd864ae52 --- /dev/null +++ b/tests/incremental/rdr_async_fn/main.rs @@ -0,0 +1,20 @@ +// Test async function compilation across revisions with private changes. +// Note: async state machines may legitimately require rebuilds when private +// helpers change, as the state machine type captures implementation details. +// +// - rpass1: Initial compilation +// - rpass2: Private async helper changes body +// - rpass3: Private async helper uses nested async block + +//@ revisions: rpass1 rpass2 rpass3 +//@ aux-build: async_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +extern crate async_dep; + +fn main() { + let _fut = async_dep::public_async_fn(); + let s = async_dep::AsyncStruct; + let _fut2 = s.method(); +} diff --git a/tests/incremental/rdr_blanket_impl/auxiliary/blanket_dep.rs b/tests/incremental/rdr_blanket_impl/auxiliary/blanket_dep.rs new file mode 100644 index 0000000000000..facd64a8c025f --- /dev/null +++ b/tests/incremental/rdr_blanket_impl/auxiliary/blanket_dep.rs @@ -0,0 +1,33 @@ +#![crate_name = "blanket_dep"] +#![crate_type = "rlib"] + +trait PrivateTrait { + fn private_method(&self) -> u32; +} + +#[cfg(rpass1)] +impl PrivateTrait for T { + fn private_method(&self) -> u32 { + 42 + } +} + +#[cfg(any(rpass2, rpass3))] +impl PrivateTrait for T { + fn private_method(&self) -> u32 { + 21 + 21 + } +} + +pub trait PublicTrait { + fn public_method(&self) -> u32; +} + +impl PublicTrait for T { + fn public_method(&self) -> u32 { + self.private_method() + } +} + +#[cfg(rpass3)] +trait _AnotherPrivateTrait {} diff --git a/tests/incremental/rdr_blanket_impl/main.rs b/tests/incremental/rdr_blanket_impl/main.rs new file mode 100644 index 0000000000000..2fad8f44b5979 --- /dev/null +++ b/tests/incremental/rdr_blanket_impl/main.rs @@ -0,0 +1,27 @@ +// Test that blanket impl with private trait bounds work correctly with RDR. +// +// - rpass1: Initial compilation +// - rpass2: Private trait impl changes, should reuse +// - rpass3: Another private trait added, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: blanket_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate blanket_dep; + +use blanket_dep::PublicTrait; + +fn main() { + let x: u32 = 0; + assert_eq!(x.public_method(), 42); + + let s = String::new(); + assert_eq!(s.public_method(), 42); +} diff --git a/tests/incremental/rdr_cfg_target/auxiliary/cfg_dep.rs b/tests/incremental/rdr_cfg_target/auxiliary/cfg_dep.rs new file mode 100644 index 0000000000000..60ac641ee8d3c --- /dev/null +++ b/tests/incremental/rdr_cfg_target/auxiliary/cfg_dep.rs @@ -0,0 +1,50 @@ +#![crate_name = "cfg_dep"] +#![crate_type = "rlib"] + +#[cfg(target_os = "macos")] +#[cfg(rpass1)] +fn platform_private() -> &'static str { + "macos v1" +} + +#[cfg(target_os = "macos")] +#[cfg(any(rpass2, rpass3))] +fn platform_private() -> &'static str { + "macos v2" +} + +#[cfg(target_os = "linux")] +#[cfg(rpass1)] +fn platform_private() -> &'static str { + "linux v1" +} + +#[cfg(target_os = "linux")] +#[cfg(any(rpass2, rpass3))] +fn platform_private() -> &'static str { + "linux v2" +} + +#[cfg(target_os = "windows")] +#[cfg(rpass1)] +fn platform_private() -> &'static str { + "windows v1" +} + +#[cfg(target_os = "windows")] +#[cfg(any(rpass2, rpass3))] +fn platform_private() -> &'static str { + "windows v2" +} + +#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] +fn platform_private() -> &'static str { + "other" +} + +pub fn get_platform() -> &'static str { + platform_private() +} + +#[cfg(rpass3)] +fn _extra_private() {} diff --git a/tests/incremental/rdr_cfg_target/main.rs b/tests/incremental/rdr_cfg_target/main.rs new file mode 100644 index 0000000000000..5a873b2269059 --- /dev/null +++ b/tests/incremental/rdr_cfg_target/main.rs @@ -0,0 +1,23 @@ +// Test that platform-specific private code changes do not cause +// downstream rebuilds. +// +// - rpass1: Initial compilation +// - rpass2: Platform-specific private fn changes, should reuse +// - rpass3: Extra private fn added, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: cfg_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate cfg_dep; + +fn main() { + let platform = cfg_dep::get_platform(); + assert!(!platform.is_empty()); +} diff --git a/tests/incremental/rdr_closure_captures/auxiliary/closure_dep.rs b/tests/incremental/rdr_closure_captures/auxiliary/closure_dep.rs new file mode 100644 index 0000000000000..e206e9f2daf49 --- /dev/null +++ b/tests/incremental/rdr_closure_captures/auxiliary/closure_dep.rs @@ -0,0 +1,33 @@ +#![crate_name = "closure_dep"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +struct PrivateData { + value: u32, +} + +#[cfg(any(rpass2, rpass3))] +struct PrivateData { + value: u32, + _extra: u32, +} + +#[cfg(rpass1)] +fn make_private() -> PrivateData { + PrivateData { value: 42 } +} + +#[cfg(any(rpass2, rpass3))] +fn make_private() -> PrivateData { + PrivateData { value: 42, _extra: 0 } +} + +pub fn make_closure() -> impl Fn() -> u32 { + let data = make_private(); + move || data.value +} + +pub fn call_with_closure u32>(f: F, x: u32) -> u32 { + let private = make_private(); + f(x) + private.value +} diff --git a/tests/incremental/rdr_closure_captures/main.rs b/tests/incremental/rdr_closure_captures/main.rs new file mode 100644 index 0000000000000..f765626dc85ac --- /dev/null +++ b/tests/incremental/rdr_closure_captures/main.rs @@ -0,0 +1,22 @@ +// Test closures capturing private types. +// Known limitation: changes to private types captured by closures currently +// DO cause downstream rebuilds, as the closure's captured environment type +// leaks through impl Fn. +// +// - rpass1: Initial compilation +// - rpass2: Private captured type gets extra field + +//@ revisions: rpass1 rpass2 +//@ aux-build: closure_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +extern crate closure_dep; + +fn main() { + let closure = closure_dep::make_closure(); + assert_eq!(closure(), 42); + + let result = closure_dep::call_with_closure(|x| x * 2, 10); + assert_eq!(result, 62); +} diff --git a/tests/incremental/rdr_const_generic/auxiliary/const_dep.rs b/tests/incremental/rdr_const_generic/auxiliary/const_dep.rs new file mode 100644 index 0000000000000..cdba0cc2d9d2a --- /dev/null +++ b/tests/incremental/rdr_const_generic/auxiliary/const_dep.rs @@ -0,0 +1,31 @@ +#![crate_name = "const_dep"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +const PRIVATE_SIZE: usize = 4; + +#[cfg(any(rpass2, rpass3))] +const PRIVATE_SIZE: usize = 4; + +#[cfg(rpass3)] +const _UNUSED_CONST: usize = 999; + +pub struct FixedArray { + data: [u32; N], +} + +impl FixedArray { + pub fn new() -> Self { + FixedArray { data: [0; N] } + } + + pub fn len(&self) -> usize { + N + } +} + +pub type DefaultArray = FixedArray; + +pub fn make_default() -> DefaultArray { + FixedArray::new() +} diff --git a/tests/incremental/rdr_const_generic/main.rs b/tests/incremental/rdr_const_generic/main.rs new file mode 100644 index 0000000000000..3b5415f32a56b --- /dev/null +++ b/tests/incremental/rdr_const_generic/main.rs @@ -0,0 +1,25 @@ +// Test that const generics with private const values work correctly with RDR. +// +// - rpass1: Initial compilation +// - rpass2: Private const unchanged (same value), should reuse +// - rpass3: Unused private const added, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: const_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate const_dep; + +fn main() { + let arr = const_dep::make_default(); + assert_eq!(arr.len(), 4); + + let custom: const_dep::FixedArray<8> = const_dep::FixedArray::new(); + assert_eq!(custom.len(), 8); +} diff --git a/tests/incremental/rdr_diamond_deps/auxiliary/diamond_base.rs b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_base.rs new file mode 100644 index 0000000000000..97e44e4ff4477 --- /dev/null +++ b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_base.rs @@ -0,0 +1,25 @@ +#![crate_name = "diamond_base"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +fn private_impl() -> u32 { + 42 +} + +#[cfg(any(rpass2, rpass3))] +fn private_impl() -> u32 { + 21 + 21 +} + +#[cfg(rpass3)] +fn _another_private() -> u32 { + 999 +} + +pub fn base_value() -> u32 { + private_impl() +} + +pub struct BaseStruct { + pub value: u32, +} diff --git a/tests/incremental/rdr_diamond_deps/auxiliary/diamond_left.rs b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_left.rs new file mode 100644 index 0000000000000..a2acf40aff2c1 --- /dev/null +++ b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_left.rs @@ -0,0 +1,8 @@ +#![crate_name = "diamond_left"] +#![crate_type = "rlib"] + +extern crate diamond_base; + +pub fn left_value() -> u32 { + diamond_base::base_value() + 1 +} diff --git a/tests/incremental/rdr_diamond_deps/auxiliary/diamond_right.rs b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_right.rs new file mode 100644 index 0000000000000..0b766ec1f0369 --- /dev/null +++ b/tests/incremental/rdr_diamond_deps/auxiliary/diamond_right.rs @@ -0,0 +1,8 @@ +#![crate_name = "diamond_right"] +#![crate_type = "rlib"] + +extern crate diamond_base; + +pub fn right_value() -> u32 { + diamond_base::base_value() + 2 +} diff --git a/tests/incremental/rdr_diamond_deps/main.rs b/tests/incremental/rdr_diamond_deps/main.rs new file mode 100644 index 0000000000000..3116d9dc06426 --- /dev/null +++ b/tests/incremental/rdr_diamond_deps/main.rs @@ -0,0 +1,28 @@ +// Test diamond dependency pattern: A depends on B and C, both depend on D. +// When D changes privately, A, B, and C should all reuse. +// +// - rpass1: Initial compilation +// - rpass2: Base crate's private impl changes, should reuse +// - rpass3: Base crate adds another private fn, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: diamond_base.rs +//@ aux-build: diamond_left.rs +//@ aux-build: diamond_right.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate diamond_left; +extern crate diamond_right; + +fn main() { + let left = diamond_left::left_value(); + let right = diamond_right::right_value(); + assert_eq!(left, 43); + assert_eq!(right, 44); +} diff --git a/tests/incremental/rdr_generic_instantiation/auxiliary/generic_dep.rs b/tests/incremental/rdr_generic_instantiation/auxiliary/generic_dep.rs new file mode 100644 index 0000000000000..e86ca9f6233e9 --- /dev/null +++ b/tests/incremental/rdr_generic_instantiation/auxiliary/generic_dep.rs @@ -0,0 +1,30 @@ +#![crate_name = "generic_dep"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +fn private_helper(x: T) -> T { + x +} + +#[cfg(any(rpass2, rpass3))] +fn private_helper(x: T) -> T { + let result = x; + result +} + +#[cfg(rpass3)] +fn _unused_generic(_: T) {} + +pub fn generic_fn(x: T) -> T { + private_helper(x) +} + +pub struct GenericStruct { + pub value: T, +} + +impl GenericStruct { + pub fn get(&self) -> T { + private_helper(self.value) + } +} diff --git a/tests/incremental/rdr_generic_instantiation/main.rs b/tests/incremental/rdr_generic_instantiation/main.rs new file mode 100644 index 0000000000000..365b06694a8a6 --- /dev/null +++ b/tests/incremental/rdr_generic_instantiation/main.rs @@ -0,0 +1,26 @@ +// Test that private changes in generic functions do not cause downstream +// rebuilds when the generic is instantiated in the dependent crate. +// +// - rpass1: Initial compilation +// - rpass2: Private generic helper changes body, should reuse +// - rpass3: Unused private generic added, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: generic_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate generic_dep; + +fn main() { + let x: u32 = generic_dep::generic_fn(42); + assert_eq!(x, 42); + + let s = generic_dep::GenericStruct { value: 100u64 }; + assert_eq!(s.get(), 100); +} diff --git a/tests/incremental/rdr_impl_trait_return/auxiliary/impl_trait_dep.rs b/tests/incremental/rdr_impl_trait_return/auxiliary/impl_trait_dep.rs new file mode 100644 index 0000000000000..2b1ea7aba6e77 --- /dev/null +++ b/tests/incremental/rdr_impl_trait_return/auxiliary/impl_trait_dep.rs @@ -0,0 +1,36 @@ +#![crate_name = "impl_trait_dep"] +#![crate_type = "rlib"] + +pub trait MyTrait { + fn value(&self) -> u32; +} + +#[cfg(rpass1)] +struct PrivateImpl { + x: u32, +} + +#[cfg(any(rpass2, rpass3))] +struct PrivateImpl { + x: u32, + _extra: u32, +} + +impl MyTrait for PrivateImpl { + fn value(&self) -> u32 { + self.x + } +} + +#[cfg(rpass1)] +pub fn make_thing() -> impl MyTrait { + PrivateImpl { x: 42 } +} + +#[cfg(any(rpass2, rpass3))] +pub fn make_thing() -> impl MyTrait { + PrivateImpl { x: 42, _extra: 0 } +} + +#[cfg(rpass3)] +struct _AnotherPrivate; diff --git a/tests/incremental/rdr_impl_trait_return/main.rs b/tests/incremental/rdr_impl_trait_return/main.rs new file mode 100644 index 0000000000000..fc0f4eb4e8ef6 --- /dev/null +++ b/tests/incremental/rdr_impl_trait_return/main.rs @@ -0,0 +1,22 @@ +// Test impl Trait return types with private concrete types. +// Known limitation: changes to the private concrete type behind `impl Trait` +// currently DO cause downstream rebuilds, as the concrete type leaks through +// the opaque return type. +// +// - rpass1: Initial compilation +// - rpass2: Private struct gets extra field +// - rpass3: Another private struct added + +//@ revisions: rpass1 rpass2 rpass3 +//@ aux-build: impl_trait_dep.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +extern crate impl_trait_dep; + +use impl_trait_dep::MyTrait; + +fn main() { + let thing = impl_trait_dep::make_thing(); + assert_eq!(thing.value(), 42); +} diff --git a/tests/incremental/rdr_proc_macro_derive/auxiliary/derive_helper.rs b/tests/incremental/rdr_proc_macro_derive/auxiliary/derive_helper.rs new file mode 100644 index 0000000000000..ccd8475428fbe --- /dev/null +++ b/tests/incremental/rdr_proc_macro_derive/auxiliary/derive_helper.rs @@ -0,0 +1,47 @@ +extern crate proc_macro; +use proc_macro::TokenStream; + +#[cfg(rpass1)] +fn format_impl_body() -> &'static str { + "42" +} + +#[cfg(rpass2)] +fn format_impl_body() -> &'static str { + "21 + 21" +} + +#[cfg(rpass3)] +fn format_impl_body() -> &'static str { + "40 + 2" +} + +#[cfg(any(rpass2, rpass3))] +fn _unused_private_helper() -> u32 { + 999 +} + +#[cfg(rpass3)] +struct _PrivateHelperStruct { + _field: u32, +} + +#[proc_macro_derive(RdrTestDerive)] +pub fn derive_rdr_test(input: TokenStream) -> TokenStream { + let input_str = input.to_string(); + + let struct_name = input_str + .split_whitespace() + .skip_while(|s| *s == "pub" || *s == "struct") + .next() + .map(|s| s.trim_end_matches(';')) + .unwrap_or("Unknown"); + + let body = format_impl_body(); + + let output = format!( + "impl {struct_name} {{ pub fn derived_value() -> u32 {{ {body} }} }}" + ); + + output.parse().unwrap() +} diff --git a/tests/incremental/rdr_proc_macro_derive/main.rs b/tests/incremental/rdr_proc_macro_derive/main.rs new file mode 100644 index 0000000000000..8d91aa649f743 --- /dev/null +++ b/tests/incremental/rdr_proc_macro_derive/main.rs @@ -0,0 +1,27 @@ +// Test that private changes in a derive macro crate do not cause +// downstream crates to rebuild. +// +// - rpass1: Initial compilation +// - rpass2: Macro's private helper changes, should reuse +// - rpass3: Macro adds more private items, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph +//@ proc-macro: derive_helper.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +#[macro_use] +extern crate derive_helper; + +#[derive(RdrTestDerive)] +pub struct TestStruct; + +fn main() { + let value = TestStruct::derived_value(); + assert_eq!(value, 42); +} diff --git a/tests/incremental/rdr_reexport/auxiliary/reexport_base.rs b/tests/incremental/rdr_reexport/auxiliary/reexport_base.rs new file mode 100644 index 0000000000000..53d0ec7b403a5 --- /dev/null +++ b/tests/incremental/rdr_reexport/auxiliary/reexport_base.rs @@ -0,0 +1,30 @@ +#![crate_name = "reexport_base"] +#![crate_type = "rlib"] + +#[cfg(rpass1)] +fn private_impl() -> u32 { + 42 +} + +#[cfg(any(rpass2, rpass3))] +fn private_impl() -> u32 { + 21 + 21 +} + +pub struct Thing { + value: u32, +} + +impl Thing { + pub fn new() -> Self { + Thing { value: private_impl() } + } + + pub fn get(&self) -> u32 { + self.value + } +} + +pub fn create_thing() -> Thing { + Thing::new() +} diff --git a/tests/incremental/rdr_reexport/auxiliary/reexport_middle.rs b/tests/incremental/rdr_reexport/auxiliary/reexport_middle.rs new file mode 100644 index 0000000000000..edcdb0ba75177 --- /dev/null +++ b/tests/incremental/rdr_reexport/auxiliary/reexport_middle.rs @@ -0,0 +1,7 @@ +#![crate_name = "reexport_middle"] +#![crate_type = "rlib"] + +extern crate reexport_base; + +pub use reexport_base::Thing; +pub use reexport_base::create_thing; diff --git a/tests/incremental/rdr_reexport/main.rs b/tests/incremental/rdr_reexport/main.rs new file mode 100644 index 0000000000000..4741fb38a355d --- /dev/null +++ b/tests/incremental/rdr_reexport/main.rs @@ -0,0 +1,22 @@ +// Test that re-exports work correctly with RDR. When the base crate's +// private implementation changes, crates using re-exports should reuse. +// +// - rpass1: Initial compilation +// - rpass2: Base crate's private impl changes, should reuse + +//@ revisions: rpass1 rpass2 +//@ compile-flags: -Z query-dep-graph +//@ aux-build: reexport_base.rs +//@ aux-build: reexport_middle.rs +//@ edition: 2024 +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] + +extern crate reexport_middle; + +fn main() { + let thing = reexport_middle::create_thing(); + assert_eq!(thing.get(), 42); +} diff --git a/tests/run-make/rdr-async-panic-location/lib.rs b/tests/run-make/rdr-async-panic-location/lib.rs new file mode 100644 index 0000000000000..8494c60a80cf0 --- /dev/null +++ b/tests/run-make/rdr-async-panic-location/lib.rs @@ -0,0 +1,14 @@ +pub async fn async_panic() { + inner_panic().await; +} + +async fn inner_panic() { + panic!("panic inside async fn at lib.rs:6"); +} + +pub async fn nested_async_panic() { + let fut = async { + panic!("panic in async block at lib.rs:11"); + }; + fut.await; +} diff --git a/tests/run-make/rdr-async-panic-location/main.rs b/tests/run-make/rdr-async-panic-location/main.rs new file mode 100644 index 0000000000000..ca74e783df0a1 --- /dev/null +++ b/tests/run-make/rdr-async-panic-location/main.rs @@ -0,0 +1,34 @@ +extern crate rdr_async_lib; + +fn block_on(fut: F) -> F::Output { + use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + + fn clone(_: *const ()) -> RawWaker { + RawWaker::new(std::ptr::null(), &VTABLE) + } + fn wake(_: *const ()) {} + fn wake_by_ref(_: *const ()) {} + fn drop(_: *const ()) {} + + static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + + let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let mut fut = std::pin::pin!(fut); + + loop { + match fut.as_mut().poll(&mut cx) { + Poll::Ready(val) => return val, + Poll::Pending => {} + } + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + match args.get(1).map(|s| s.as_str()) { + Some("async") => block_on(rdr_async_lib::async_panic()), + Some("nested") => block_on(rdr_async_lib::nested_async_panic()), + _ => println!("usage: main [async|nested]"), + } +} diff --git a/tests/run-make/rdr-async-panic-location/rmake.rs b/tests/run-make/rdr-async-panic-location/rmake.rs new file mode 100644 index 0000000000000..619872d1d5ff8 --- /dev/null +++ b/tests/run-make/rdr-async-panic-location/rmake.rs @@ -0,0 +1,40 @@ +// Test that panic locations inside async functions are correct +// when using -Z separate-spans. + +//@ ignore-cross-compile + +use run_make_support::{cmd, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + rustc() + .input("lib.rs") + .crate_name("rdr_async_lib") + .crate_type("rlib") + .edition("2024") + .arg("-Zseparate-spans") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("rdr_async_lib", "librdr_async_lib.rlib") + .edition("2024") + .arg("-Zseparate-spans") + .run(); + + let output = cmd("./main").arg("async").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("lib.rs:6"), + "async panic should show lib.rs:6, got:\n{stderr}" + ); + + let output = cmd("./main").arg("nested").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("lib.rs:11"), + "nested async panic should show lib.rs:11, got:\n{stderr}" + ); + }); +} diff --git a/tests/run-make/rdr-cdylib/lib.rs b/tests/run-make/rdr-cdylib/lib.rs new file mode 100644 index 0000000000000..e5e3a17f8cec7 --- /dev/null +++ b/tests/run-make/rdr-cdylib/lib.rs @@ -0,0 +1,13 @@ +#[no_mangle] +pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 { + private_add(a, b) +} + +fn private_add(a: i32, b: i32) -> i32 { + a + b +} + +#[no_mangle] +pub extern "C" fn get_message() -> *const u8 { + b"Hello from cdylib\0".as_ptr() +} diff --git a/tests/run-make/rdr-cdylib/rmake.rs b/tests/run-make/rdr-cdylib/rmake.rs new file mode 100644 index 0000000000000..f4b6b2cd9c9b0 --- /dev/null +++ b/tests/run-make/rdr-cdylib/rmake.rs @@ -0,0 +1,22 @@ +// Test that cdylib builds work correctly with -Z separate-spans. + +//@ ignore-cross-compile + +use run_make_support::{dynamic_lib_name, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + rustc() + .input("lib.rs") + .crate_type("cdylib") + .crate_name("rdr_cdylib") + .arg("-Zseparate-spans") + .run(); + + let lib_name = dynamic_lib_name("rdr_cdylib"); + assert!( + std::path::Path::new(&lib_name).exists(), + "cdylib should be created: {lib_name}" + ); + }); +} diff --git a/tests/run-make/rdr-lto/lib.rs b/tests/run-make/rdr-lto/lib.rs new file mode 100644 index 0000000000000..3047428400fff --- /dev/null +++ b/tests/run-make/rdr-lto/lib.rs @@ -0,0 +1,14 @@ +pub fn public_fn(x: u32) -> u32 { + private_fn(x) +} + +#[inline] +fn private_fn(x: u32) -> u32 { + x + 1 +} + +pub fn inlined_panic(should_panic: bool) { + if should_panic { + panic!("panic at lib.rs:12"); + } +} diff --git a/tests/run-make/rdr-lto/main.rs b/tests/run-make/rdr-lto/main.rs new file mode 100644 index 0000000000000..ce4e2b02df4e1 --- /dev/null +++ b/tests/run-make/rdr-lto/main.rs @@ -0,0 +1,10 @@ +extern crate rdr_lto_lib; + +fn main() { + let args: Vec = std::env::args().collect(); + if args.get(1).map(|s| s.as_str()) == Some("panic") { + rdr_lto_lib::inlined_panic(true); + } + let result = rdr_lto_lib::public_fn(41); + assert_eq!(result, 42); +} diff --git a/tests/run-make/rdr-lto/rmake.rs b/tests/run-make/rdr-lto/rmake.rs new file mode 100644 index 0000000000000..6c8444f3ad0ea --- /dev/null +++ b/tests/run-make/rdr-lto/rmake.rs @@ -0,0 +1,36 @@ +// Test that LTO builds work correctly with -Z separate-spans, +// including correct panic locations for cross-crate inlined functions. + +//@ ignore-cross-compile + +use run_make_support::{cmd, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + rustc() + .input("lib.rs") + .crate_type("rlib") + .crate_name("rdr_lto_lib") + .arg("-Zseparate-spans") + .arg("-Clto=thin") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("rdr_lto_lib", "librdr_lto_lib.rlib") + .arg("-Zseparate-spans") + .arg("-Clto=thin") + .output("main") + .run(); + + cmd("./main").run(); + + let output = cmd("./main").arg("panic").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("lib.rs:12"), + "inlined panic should show lib.rs:12, got:\n{stderr}" + ); + }); +} diff --git a/tests/run-make/rdr-proc-macro-panic-location/main.rs b/tests/run-make/rdr-proc-macro-panic-location/main.rs new file mode 100644 index 0000000000000..a8d541d6bac0c --- /dev/null +++ b/tests/run-make/rdr-proc-macro-panic-location/main.rs @@ -0,0 +1,17 @@ +#[macro_use] +extern crate proc_macro_lib; + +#[derive(PanicDerive)] +pub struct PanicStruct; + +#[panic_attr] +mod my_mod {} + +fn main() { + let args: Vec = std::env::args().collect(); + match args.get(1).map(|s| s.as_str()) { + Some("derive") => PanicStruct::do_panic(), + Some("attr") => generated_panic(), + _ => println!("usage: main [derive|attr]"), + } +} diff --git a/tests/run-make/rdr-proc-macro-panic-location/proc_macro_lib.rs b/tests/run-make/rdr-proc-macro-panic-location/proc_macro_lib.rs new file mode 100644 index 0000000000000..4161ec22e22d1 --- /dev/null +++ b/tests/run-make/rdr-proc-macro-panic-location/proc_macro_lib.rs @@ -0,0 +1,30 @@ +extern crate proc_macro; +use proc_macro::TokenStream; + +#[proc_macro_derive(PanicDerive)] +pub fn derive_panic(_input: TokenStream) -> TokenStream { + r#" + impl PanicStruct { + pub fn do_panic() { + panic!("panic from derived impl"); + } + } + "# + .parse() + .unwrap() +} + +#[proc_macro_attribute] +pub fn panic_attr(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item_str = item.to_string(); + format!( + r#" + {item_str} + fn generated_panic() {{ + panic!("panic from attribute macro"); + }} + "# + ) + .parse() + .unwrap() +} diff --git a/tests/run-make/rdr-proc-macro-panic-location/rmake.rs b/tests/run-make/rdr-proc-macro-panic-location/rmake.rs new file mode 100644 index 0000000000000..c65a3b460a267 --- /dev/null +++ b/tests/run-make/rdr-proc-macro-panic-location/rmake.rs @@ -0,0 +1,37 @@ +// Test that panic locations in proc-macro-generated code are correct +// when using -Z separate-spans. + +//@ ignore-cross-compile + +use run_make_support::{cmd, run_in_tmpdir, rustc}; + +fn main() { + run_in_tmpdir(|| { + rustc() + .input("proc_macro_lib.rs") + .crate_type("proc-macro") + .arg("-Zseparate-spans") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("proc_macro_lib", "libproc_macro_lib.dylib") + .arg("-Zseparate-spans") + .run(); + + let output = cmd("./main").arg("derive").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("panic from derived impl"), + "should contain panic message, got:\n{stderr}" + ); + + let output = cmd("./main").arg("attr").run_fail(); + let stderr = output.stderr_utf8(); + assert!( + stderr.contains("panic from attribute macro"), + "should contain panic message, got:\n{stderr}" + ); + }); +} From f0233d81011a5e93c944ef4a91ce365b33e3e403 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sat, 17 Jan 2026 15:45:24 -0800 Subject: [PATCH 15/19] rdr: rename `-Zseparate-spans` to `-Zstable-crate-hash` --- compiler/rustc_interface/src/queries.rs | 2 +- compiler/rustc_metadata/src/creader.rs | 6 ++--- compiler/rustc_metadata/src/fs.rs | 8 +++---- compiler/rustc_metadata/src/locator.rs | 2 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 21 ++++++++-------- compiler/rustc_metadata/src/rmeta/encoder.rs | 24 +++++++++++-------- compiler/rustc_metadata/src/rmeta/mod.rs | 4 ++-- compiler/rustc_session/src/cstore.rs | 2 +- compiler/rustc_session/src/options.rs | 4 ++-- compiler/rustc_session/src/session.rs | 2 +- .../auxiliary/separate_spans_lib.rs | 6 ++--- tests/incremental/rdr_separate_spans/main.rs | 8 +++---- .../rdr-async-panic-location/rmake.rs | 11 ++++----- tests/run-make/rdr-cdylib/rmake.rs | 9 +++---- tests/run-make/rdr-coverage/lib.rs | 2 +- tests/run-make/rdr-coverage/rmake.rs | 10 ++++---- tests/run-make/rdr-debuginfo/lib.rs | 2 +- tests/run-make/rdr-debuginfo/rmake.rs | 8 +++---- tests/run-make/rdr-diagnostic-gating/rmake.rs | 8 +++---- .../rdr-hygiene-hash-collision/rmake.rs | 4 ++-- tests/run-make/rdr-lto/rmake.rs | 6 ++--- .../rdr-missing-spans-fallback/rmake.rs | 8 +++---- tests/run-make/rdr-panic-location/dep.rs | 4 ++-- tests/run-make/rdr-panic-location/main.rs | 2 +- tests/run-make/rdr-panic-location/rmake.rs | 8 +++---- .../rdr-proc-macro-panic-location/rmake.rs | 6 ++--- .../rdr-separate-spans-include-str/rmake.rs | 6 ++--- tests/run-make/rdr-separate-spans/lib.rs | 2 +- tests/run-make/rdr-separate-spans/rmake.rs | 20 ++++++++-------- 29 files changed, 102 insertions(+), 103 deletions(-) diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 272b245bb109d..8d50c12bf059c 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -64,7 +64,7 @@ impl Linker { { let spans_path = path.with_extension("spans"); let mut files: Vec<(&'static str, &Path)> = vec![("rmeta", path)]; - if sess.opts.unstable_opts.separate_spans && spans_path.exists() { + if sess.opts.unstable_opts.stable_crate_hash && spans_path.exists() { files.push(("spans", &spans_path)); } if let Some((id, product)) = diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 4ab0a4682ea46..ca1cdd07fe013 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -631,10 +631,10 @@ impl CStore { None }; - let has_separate_spans = crate_root.has_separate_spans(); + let has_stable_crate_hash = crate_root.has_stable_crate_hash(); let crate_name = crate_root.name(); - if has_separate_spans && source.spans.is_none() { + if has_stable_crate_hash && source.spans.is_none() { return Err(CrateError::MissingSpanFile(crate_name, "file not found".to_string())); } @@ -652,7 +652,7 @@ impl CStore { host_hash, ); - if has_separate_spans { + if has_stable_crate_hash { crate_metadata .ensure_span_file_loaded() .map_err(|err| CrateError::MissingSpanFile(crate_name, err))?; diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 1001b3a41b5a5..094dfccc8c094 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -87,9 +87,9 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { None }; - // When -Z separate-spans is enabled, encode spans to a separate .spans file + // When -Z stable-crate-hash is enabled, encode spans to a separate .spans file let spans_tmp_filename = if let Some(required_source_files) = required_source_files - && tcx.sess.opts.unstable_opts.separate_spans + && tcx.sess.opts.unstable_opts.stable_crate_hash { let spans_tmp_filename = metadata_tmpdir.as_ref().join("lib.spans"); encode_spans(tcx, &spans_tmp_filename, tcx.crate_hash(LOCAL_CRATE), required_source_files); @@ -107,12 +107,12 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { let (metadata_filename, metadata_tmpdir) = if need_metadata_file { let filename = match out_filename { OutFileName::Real(ref path) => { - // RDR optimization: when -Z separate-spans is enabled, skip writing + // RDR optimization: when -Z stable-crate-hash is enabled, skip writing // the .rmeta file if its content hash (SVH) hasn't changed. This // preserves the file's mtime, preventing cargo from rebuilding // dependent crates when only span positions changed. let new_hash = tcx.crate_hash(LOCAL_CRATE); - let skip_write = tcx.sess.opts.unstable_opts.separate_spans + let skip_write = tcx.sess.opts.unstable_opts.stable_crate_hash && read_existing_metadata_hash(path).is_some_and(|old_hash| { let matches = old_hash == new_hash; if matches { diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index db67529d8b937..9cfe31fd819c9 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -961,7 +961,7 @@ fn get_rmeta_metadata_section<'a, 'p>(filename: &'p Path) -> Result Result { // mmap the file, because only a small fraction of it is read. let file = std::fs::File::open(filename) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 1cbc3a780254c..f49e2fc937500 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -229,7 +229,7 @@ struct ImportedSourceFile { /// - `Some(imported)` means successfully imported /// /// Note: Import failures now indicate a bug (either corrupted metadata or -/// the crate was compiled with `-Z separate-spans` but the `.spans` file +/// the crate was compiled with `-Z stable-crate-hash` but the `.spans` file /// is missing, which should have been caught at crate load time). /// Decode context used when we just have a blob of metadata from which we have to decode a header @@ -846,7 +846,7 @@ impl<'a, 'tcx> Decodable> for SpanData { }; // Source file should always be available - missing files are caught at crate load time - // for crates compiled with -Z separate-spans, and should never happen otherwise. + // for crates compiled with -Z stable-crate-hash, and should never happen otherwise. let source_file = source_file.expect("source file should be available"); // Make sure our span is well-formed. @@ -1181,8 +1181,8 @@ impl CrateRoot { self.target_modifiers.decode(metadata) } - pub(crate) fn has_separate_spans(&self) -> bool { - self.has_separate_spans + pub(crate) fn has_stable_crate_hash(&self) -> bool { + self.has_stable_crate_hash } } @@ -1871,7 +1871,7 @@ impl<'a> CrateMetadataRef<'a> { /// Returns `None` if the source file index doesn't exist in the source map table /// (sparse table entry). This can happen when iterating through all indices. /// - /// Note: For crates compiled with `-Z separate-spans`, missing `.spans` files + /// Note: For crates compiled with `-Z stable-crate-hash`, missing `.spans` files /// are detected and errored at crate load time, before this function is called. fn imported_source_file( self, @@ -1986,10 +1986,11 @@ impl<'a> CrateMetadataRef<'a> { return Some(cached.clone()); } - let source_file_to_import = if self.root.has_separate_spans() { - let span_data = self.cdata.span_file_data().expect( - "span file should be loaded for crate compiled with -Z separate-spans", - ); + let source_file_to_import = if self.root.has_stable_crate_hash() { + let span_data = self + .cdata + .span_file_data() + .expect("span file should be loaded for crate compiled with -Z stable-crate-hash"); span_data .source_map .get_from_span_blob(&span_data.blob, source_file_index) @@ -2009,7 +2010,7 @@ impl<'a> CrateMetadataRef<'a> { // Return None for sparse table entries. This can happen when: // - Searching through all source file indices (some may be empty) // - // Note: For crates compiled with -Z separate-spans, missing .spans files + // Note: For crates compiled with -Z stable-crate-hash, missing .spans files // are now detected and errored at crate load time, so that case no longer // reaches here. let Some(source_file_to_import) = source_file_to_import else { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index df0968f99f60b..6d1574ea7c16f 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -673,8 +673,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } /// Encode source map using the provided set of required source files. - /// This is used both for rmeta (when separate_spans is disabled) and - /// for the span file (when separate_spans is enabled). + /// This is used both for rmeta (when stable_crate_hash is disabled) and + /// for the span file (when stable_crate_hash is enabled). fn encode_source_map_with( &mut self, required_source_files: FxIndexSet, @@ -853,9 +853,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // Encode source_map. This needs to be done last, because encoding `Span`s tells us which // `SourceFiles` we actually need to encode. - // When separate_spans is enabled, source_map goes in the span file instead. + // When stable_crate_hash is enabled, source_map goes in the span file instead. let source_map = stat!("source-map", || { - if self.tcx.sess.opts.unstable_opts.separate_spans { + if self.tcx.sess.opts.unstable_opts.stable_crate_hash { // Don't encode source_map in rmeta; it will be in the .spans file. // Keep required_source_files for the span file encoding. TableBuilder::default().encode(&mut self.opaque) @@ -924,7 +924,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { expn_hashes, def_path_hash_map, specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), - has_separate_spans: tcx.sess.opts.unstable_opts.separate_spans, + has_stable_crate_hash: tcx.sess.opts.unstable_opts.stable_crate_hash, }) }); @@ -2650,7 +2650,7 @@ impl Decodable for EncodedMetadata { } /// Encodes crate metadata to the given path. -/// Returns the set of required source files if `-Z separate_spans` is enabled, +/// Returns the set of required source files if `-Z stable-crate-hash` is enabled, /// which is needed for encoding the span file. #[instrument(level = "trace", skip(tcx))] pub fn encode_metadata( @@ -2697,7 +2697,7 @@ pub fn encode_metadata( Ok(_) => {} Err(err) => tcx.dcx().emit_fatal(FailCreateFileEncoder { err }), }; - if tcx.sess.opts.unstable_opts.separate_spans + if tcx.sess.opts.unstable_opts.stable_crate_hash && let Some(saved_spans) = work_product.saved_files.get("spans") { let span_source = @@ -2778,7 +2778,7 @@ pub fn encode_spans( } /// Encodes metadata with standard header. -/// Returns the required_source_files if separate_spans is enabled (for span file encoding). +/// Returns the required_source_files if stable_crate_hash is enabled (for span file encoding). fn with_encode_metadata_header( tcx: TyCtxt<'_>, path: &Path, @@ -2832,8 +2832,12 @@ fn with_encode_metadata_header( tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } - // Return required_source_files if separate_spans is enabled (for span file encoding) - if tcx.sess.opts.unstable_opts.separate_spans { ecx.required_source_files.take() } else { None } + // Return required_source_files if stable_crate_hash is enabled (for span file encoding) + if tcx.sess.opts.unstable_opts.stable_crate_hash { + ecx.required_source_files.take() + } else { + None + } } fn with_encode_span_header( diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index ba6e61574b8e2..1861912120482 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -344,9 +344,9 @@ pub(crate) struct CrateRoot { specialization_enabled_in: bool, - /// Whether this crate was compiled with `-Z separate-spans`. + /// Whether this crate was compiled with `-Z stable-crate-hash`. /// If true, span data is in a separate `.spans` file, not in this rmeta. - has_separate_spans: bool, + has_stable_crate_hash: bool, } /// On-disk representation of `DefId`. diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index 108629a747e02..d596cf861cba2 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -25,7 +25,7 @@ pub struct CrateSource { pub rlib: Option, pub rmeta: Option, pub sdylib_interface: Option, - /// Path to `.spans` file for crates compiled with `-Z separate-spans`. + /// Path to `.spans` file for crates compiled with `-Z stable-crate-hash`. pub spans: Option, } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 0a9c44ccb8a25..1d317ecd90d4e 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2658,8 +2658,8 @@ written to standard error output)"), for example: `-Z self-profile-events=default,query-keys` all options: none, all, default, generic-activity, query-provider, query-cache-hit query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), - separate_spans: bool = (false, parse_bool, [TRACKED], - "store span data in a separate .spans file for Relink, Don't Rebuild (RDR) (default: no)"), + stable_crate_hash: bool = (false, parse_bool, [TRACKED], + "stabilize the crate hash by storing spans separately, enabling relink-don't-rebuild (default: no)"), share_generics: Option = (None, parse_opt_bool, [TRACKED], "make the current crate share its generic instantiations"), shell_argfiles: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index ed96a6e8322e6..f114d43584075 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -286,7 +286,7 @@ impl Session { } pub fn diagnostic_spans_hash(&self) -> Option { - if !self.opts.unstable_opts.separate_spans { + if !self.opts.unstable_opts.stable_crate_hash { return None; } diff --git a/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs b/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs index 3ab6d66ddc1c0..138ea7da8f0f5 100644 --- a/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs +++ b/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs @@ -1,10 +1,10 @@ -// Auxiliary crate for testing -Z separate-spans flag. -// When separate-spans is enabled, span data is stored in a separate .spans file +// Auxiliary crate for testing -Z stable-crate-hash flag. +// When stable-crate-hash is enabled, span data is stored in a separate .spans file // instead of being embedded directly in the .rmeta file. //@[rpass1] compile-flags: -Z query-dep-graph //@[rpass2] compile-flags: -Z query-dep-graph -//@[rpass3] compile-flags: -Z query-dep-graph -Z separate-spans +//@[rpass3] compile-flags: -Z query-dep-graph -Z stable-crate-hash #![crate_type = "rlib"] diff --git a/tests/incremental/rdr_separate_spans/main.rs b/tests/incremental/rdr_separate_spans/main.rs index dece7278488ca..7eace5ca3e953 100644 --- a/tests/incremental/rdr_separate_spans/main.rs +++ b/tests/incremental/rdr_separate_spans/main.rs @@ -1,14 +1,14 @@ -// This test verifies that the -Z separate-spans flag works correctly +// This test verifies that the -Z stable-crate-hash flag works correctly // with incremental compilation. // -// The separate-spans flag causes span data to be stored in a separate +// The stable-crate-hash flag causes span data to be stored in a separate // .spans file rather than inline in the .rmeta file. This is important // for Relink, Don't Rebuild (RDR) because span data often // contains absolute file paths that break reproducibility. // -// rpass1: Initial compilation without separate-spans +// rpass1: Initial compilation without stable-crate-hash // rpass2: Recompile without changes - should reuse everything -// rpass3: Recompile auxiliary with separate-spans - tests span loading +// rpass3: Recompile auxiliary with stable-crate-hash - tests span loading //@ revisions: rpass1 rpass2 rpass3 //@ compile-flags: -Z query-dep-graph -g diff --git a/tests/run-make/rdr-async-panic-location/rmake.rs b/tests/run-make/rdr-async-panic-location/rmake.rs index 619872d1d5ff8..98dd249da8d93 100644 --- a/tests/run-make/rdr-async-panic-location/rmake.rs +++ b/tests/run-make/rdr-async-panic-location/rmake.rs @@ -1,5 +1,5 @@ // Test that panic locations inside async functions are correct -// when using -Z separate-spans. +// when using -Z stable-crate-hash. //@ ignore-cross-compile @@ -12,7 +12,7 @@ fn main() { .crate_name("rdr_async_lib") .crate_type("rlib") .edition("2024") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); rustc() @@ -20,15 +20,12 @@ fn main() { .crate_type("bin") .extern_("rdr_async_lib", "librdr_async_lib.rlib") .edition("2024") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); let output = cmd("./main").arg("async").run_fail(); let stderr = output.stderr_utf8(); - assert!( - stderr.contains("lib.rs:6"), - "async panic should show lib.rs:6, got:\n{stderr}" - ); + assert!(stderr.contains("lib.rs:6"), "async panic should show lib.rs:6, got:\n{stderr}"); let output = cmd("./main").arg("nested").run_fail(); let stderr = output.stderr_utf8(); diff --git a/tests/run-make/rdr-cdylib/rmake.rs b/tests/run-make/rdr-cdylib/rmake.rs index f4b6b2cd9c9b0..90a9c2d0d1168 100644 --- a/tests/run-make/rdr-cdylib/rmake.rs +++ b/tests/run-make/rdr-cdylib/rmake.rs @@ -1,4 +1,4 @@ -// Test that cdylib builds work correctly with -Z separate-spans. +// Test that cdylib builds work correctly with -Z stable-crate-hash. //@ ignore-cross-compile @@ -10,13 +10,10 @@ fn main() { .input("lib.rs") .crate_type("cdylib") .crate_name("rdr_cdylib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); let lib_name = dynamic_lib_name("rdr_cdylib"); - assert!( - std::path::Path::new(&lib_name).exists(), - "cdylib should be created: {lib_name}" - ); + assert!(std::path::Path::new(&lib_name).exists(), "cdylib should be created: {lib_name}"); }); } diff --git a/tests/run-make/rdr-coverage/lib.rs b/tests/run-make/rdr-coverage/lib.rs index a6b1e8b168c2d..5445f48aa72fc 100644 --- a/tests/run-make/rdr-coverage/lib.rs +++ b/tests/run-make/rdr-coverage/lib.rs @@ -1,4 +1,4 @@ -// Library crate for testing coverage instrumentation with -Z separate-spans. +// Library crate for testing coverage instrumentation with -Z stable-crate-hash. // Contains functions with various control flow for coverage testing. #![crate_type = "rlib"] diff --git a/tests/run-make/rdr-coverage/rmake.rs b/tests/run-make/rdr-coverage/rmake.rs index ade428da7a6c5..bb8aec1a96400 100644 --- a/tests/run-make/rdr-coverage/rmake.rs +++ b/tests/run-make/rdr-coverage/rmake.rs @@ -1,7 +1,7 @@ -// Test that coverage instrumentation works correctly with -Z separate-spans. +// Test that coverage instrumentation works correctly with -Z stable-crate-hash. // // This verifies that: -// 1. Coverage instrumentation compiles successfully with -Z separate-spans +// 1. Coverage instrumentation compiles successfully with -Z stable-crate-hash // 2. Coverage profraw data is generated when running the instrumented binary // 3. Coverage data can be converted to a report format // @@ -15,12 +15,12 @@ use run_make_support::{cmd, cwd, llvm_profdata, rfs, run_in_tmpdir, rustc}; fn main() { run_in_tmpdir(|| { - // Build library with coverage instrumentation and -Z separate-spans + // Build library with coverage instrumentation and -Z stable-crate-hash rustc() .input("lib.rs") .crate_type("rlib") .arg("-Cinstrument-coverage") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); // Build main binary with coverage instrumentation @@ -29,7 +29,7 @@ fn main() { .crate_type("bin") .extern_("rdr_coverage_lib", "librdr_coverage_lib.rlib") .arg("-Cinstrument-coverage") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); // Run the binary to generate coverage data diff --git a/tests/run-make/rdr-debuginfo/lib.rs b/tests/run-make/rdr-debuginfo/lib.rs index 6e02bd5fb9c3f..6d68b0ac5d052 100644 --- a/tests/run-make/rdr-debuginfo/lib.rs +++ b/tests/run-make/rdr-debuginfo/lib.rs @@ -1,4 +1,4 @@ -// Library crate for testing debuginfo with -Z separate-spans. +// Library crate for testing debuginfo with -Z stable-crate-hash. // Contains functions at known line numbers for DWARF verification. #![crate_type = "rlib"] diff --git a/tests/run-make/rdr-debuginfo/rmake.rs b/tests/run-make/rdr-debuginfo/rmake.rs index d24fdaf969602..2a6b3ea3737f3 100644 --- a/tests/run-make/rdr-debuginfo/rmake.rs +++ b/tests/run-make/rdr-debuginfo/rmake.rs @@ -1,4 +1,4 @@ -// Test that debuginfo line numbers are correct when using -Z separate-spans. +// Test that debuginfo line numbers are correct when using -Z stable-crate-hash. // // This verifies that DWARF debug information contains accurate file:line // information even when spans are stored separately from metadata. @@ -13,12 +13,12 @@ use run_make_support::{llvm_dwarfdump, run_in_tmpdir, rustc}; fn main() { run_in_tmpdir(|| { - // Build with debuginfo and -Z separate-spans + // Build with debuginfo and -Z stable-crate-hash rustc() .input("lib.rs") .crate_type("rlib") .arg("-Cdebuginfo=2") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); // Use llvm-dwarfdump to extract debug info @@ -34,7 +34,7 @@ fn main() { .input("lib.rs") .crate_type("rlib") .arg("-Cdebuginfo=2") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .out_dir("second") .run(); diff --git a/tests/run-make/rdr-diagnostic-gating/rmake.rs b/tests/run-make/rdr-diagnostic-gating/rmake.rs index 44e5ac8c71b2f..891079354ebbb 100644 --- a/tests/run-make/rdr-diagnostic-gating/rmake.rs +++ b/tests/run-make/rdr-diagnostic-gating/rmake.rs @@ -1,4 +1,4 @@ -// Check that diagnostic replay is gated by the spans hash when using -Z separate-spans. +// Check that diagnostic replay is gated by the spans hash when using -Z stable-crate-hash. //@ ignore-cross-compile // Reason: uses incremental directory tied to host toolchain paths @@ -12,7 +12,7 @@ fn main() { let output1 = rustc() .input("main.rs") .incremental("incr") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Zincremental-ignore-spans") .run(); output1.assert_stderr_contains("unused variable"); @@ -20,7 +20,7 @@ fn main() { let output2 = rustc() .input("main.rs") .incremental("incr") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Zincremental-ignore-spans") .run(); output2.assert_stderr_contains("unused variable"); @@ -31,7 +31,7 @@ fn main() { let output3 = rustc() .input("main.rs") .incremental("incr") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Zincremental-ignore-spans") .run(); output3.assert_stderr_not_contains("unused variable"); diff --git a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs index 4e5f3748dc81c..e67b6e7b7925f 100644 --- a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs +++ b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs @@ -84,7 +84,7 @@ make_fn!(baz); .input("lib.rs") .crate_type("lib") .incremental("incr") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Zincremental-ignore-spans") .run(); @@ -93,7 +93,7 @@ make_fn!(baz); .input("lib.rs") .crate_type("lib") .incremental("incr") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Zincremental-ignore-spans") .run(); } diff --git a/tests/run-make/rdr-lto/rmake.rs b/tests/run-make/rdr-lto/rmake.rs index 6c8444f3ad0ea..ca63971a3242a 100644 --- a/tests/run-make/rdr-lto/rmake.rs +++ b/tests/run-make/rdr-lto/rmake.rs @@ -1,4 +1,4 @@ -// Test that LTO builds work correctly with -Z separate-spans, +// Test that LTO builds work correctly with -Z stable-crate-hash, // including correct panic locations for cross-crate inlined functions. //@ ignore-cross-compile @@ -11,7 +11,7 @@ fn main() { .input("lib.rs") .crate_type("rlib") .crate_name("rdr_lto_lib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Clto=thin") .run(); @@ -19,7 +19,7 @@ fn main() { .input("main.rs") .crate_type("bin") .extern_("rdr_lto_lib", "librdr_lto_lib.rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("-Clto=thin") .output("main") .run(); diff --git a/tests/run-make/rdr-missing-spans-fallback/rmake.rs b/tests/run-make/rdr-missing-spans-fallback/rmake.rs index 4bc26c519140c..989ade851688e 100644 --- a/tests/run-make/rdr-missing-spans-fallback/rmake.rs +++ b/tests/run-make/rdr-missing-spans-fallback/rmake.rs @@ -1,6 +1,6 @@ // Test that missing .spans file is treated as a hard error. // -// This verifies that when a crate was compiled with `-Z separate-spans` and +// This verifies that when a crate was compiled with `-Z stable-crate-hash` and // the `.spans` file is later deleted (simulating a corrupted incremental cache), // the compiler produces a clear error message rather than silently degrading. // @@ -11,17 +11,17 @@ use run_make_support::{rfs, run_in_tmpdir, rustc}; fn main() { run_in_tmpdir(|| { - // First, create a dependency crate compiled with -Z separate-spans + // First, create a dependency crate compiled with -Z stable-crate-hash // Use --emit=metadata to produce a standalone .rmeta file let dep_source = "pub fn dep_fn() -> i32 { 42 }\n"; rfs::write("dep.rs", dep_source); - // Compile the dependency with -Z separate-spans and emit both rlib and metadata + // Compile the dependency with -Z stable-crate-hash and emit both rlib and metadata rustc() .input("dep.rs") .crate_type("rlib") .emit("metadata,link") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); // Verify .spans file was created alongside .rmeta diff --git a/tests/run-make/rdr-panic-location/dep.rs b/tests/run-make/rdr-panic-location/dep.rs index 01df9c7f22aee..12cf64bc3dc76 100644 --- a/tests/run-make/rdr-panic-location/dep.rs +++ b/tests/run-make/rdr-panic-location/dep.rs @@ -1,12 +1,12 @@ // Dependency crate that can panic at known locations. -// The panic locations are used to verify that -Z separate-spans +// The panic locations are used to verify that -Z stable-crate-hash // preserves correct file:line:col information. #![crate_type = "rlib"] #![crate_name = "dep"] /// Public function that panics. The panic location should be -/// correctly reported even when compiled with -Z separate-spans. +/// correctly reported even when compiled with -Z stable-crate-hash. #[inline(never)] pub fn will_panic(trigger: bool) { if trigger { diff --git a/tests/run-make/rdr-panic-location/main.rs b/tests/run-make/rdr-panic-location/main.rs index 89da23a72b510..fc6f0fec6a685 100644 --- a/tests/run-make/rdr-panic-location/main.rs +++ b/tests/run-make/rdr-panic-location/main.rs @@ -1,5 +1,5 @@ // Main crate that triggers panics in the dependency. -// Used to verify panic locations are correct with -Z separate-spans. +// Used to verify panic locations are correct with -Z stable-crate-hash. extern crate dep; diff --git a/tests/run-make/rdr-panic-location/rmake.rs b/tests/run-make/rdr-panic-location/rmake.rs index a7d44a464c243..08e868d54bf72 100644 --- a/tests/run-make/rdr-panic-location/rmake.rs +++ b/tests/run-make/rdr-panic-location/rmake.rs @@ -1,4 +1,4 @@ -// Test that panic locations are correct when using -Z separate-spans. +// Test that panic locations are correct when using -Z stable-crate-hash. // // This verifies that span information is correctly preserved/resolved // so that panic messages show accurate file:line:col locations. @@ -14,15 +14,15 @@ use run_make_support::{cmd, run_in_tmpdir, rustc}; fn main() { run_in_tmpdir(|| { - // Build dependency with -Z separate-spans - rustc().input("dep.rs").crate_type("rlib").arg("-Zseparate-spans").run(); + // Build dependency with -Z stable-crate-hash + rustc().input("dep.rs").crate_type("rlib").arg("-Zstable-crate-hash").run(); // Build main crate linking to the dependency rustc() .input("main.rs") .crate_type("bin") .extern_("dep", "libdep.rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); // Test 1: Public function panic location diff --git a/tests/run-make/rdr-proc-macro-panic-location/rmake.rs b/tests/run-make/rdr-proc-macro-panic-location/rmake.rs index c65a3b460a267..5b476126e1662 100644 --- a/tests/run-make/rdr-proc-macro-panic-location/rmake.rs +++ b/tests/run-make/rdr-proc-macro-panic-location/rmake.rs @@ -1,5 +1,5 @@ // Test that panic locations in proc-macro-generated code are correct -// when using -Z separate-spans. +// when using -Z stable-crate-hash. //@ ignore-cross-compile @@ -10,14 +10,14 @@ fn main() { rustc() .input("proc_macro_lib.rs") .crate_type("proc-macro") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); rustc() .input("main.rs") .crate_type("bin") .extern_("proc_macro_lib", "libproc_macro_lib.dylib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .run(); let output = cmd("./main").arg("derive").run_fail(); diff --git a/tests/run-make/rdr-separate-spans-include-str/rmake.rs b/tests/run-make/rdr-separate-spans-include-str/rmake.rs index 2de9d9cbeace7..73cfcf26ff2b4 100644 --- a/tests/run-make/rdr-separate-spans-include-str/rmake.rs +++ b/tests/run-make/rdr-separate-spans-include-str/rmake.rs @@ -1,16 +1,16 @@ // Verify ExpnData spans decoded from metadata are usable for include_str! -// when building with -Z separate-spans. +// when building with -Z stable-crate-hash. //@ ignore-cross-compile // Reason: the compiled binary is executed use run_make_support::{run, rust_lib_name, rustc}; fn main() { - rustc().input("dep.rs").crate_name("dep").crate_type("rlib").arg("-Zseparate-spans").run(); + rustc().input("dep.rs").crate_name("dep").crate_type("rlib").arg("-Zstable-crate-hash").run(); rustc() .input("main.rs") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg("--extern") .arg(format!("dep={}", rust_lib_name("dep"))) .run(); diff --git a/tests/run-make/rdr-separate-spans/lib.rs b/tests/run-make/rdr-separate-spans/lib.rs index 177e2d1da132c..c7dd68434c8ad 100644 --- a/tests/run-make/rdr-separate-spans/lib.rs +++ b/tests/run-make/rdr-separate-spans/lib.rs @@ -1,4 +1,4 @@ -// Test library for RDR separate-spans testing. +// Test library for RDR stable-crate-hash testing. // This crate exports various items that embed spans in metadata. #![crate_name = "rdr_test_lib"] diff --git a/tests/run-make/rdr-separate-spans/rmake.rs b/tests/run-make/rdr-separate-spans/rmake.rs index 825adfcf12005..93f3bcb600fb7 100644 --- a/tests/run-make/rdr-separate-spans/rmake.rs +++ b/tests/run-make/rdr-separate-spans/rmake.rs @@ -1,7 +1,7 @@ -// Test that -Z separate-spans flag produces reproducible metadata. +// Test that -Z stable-crate-hash flag produces reproducible metadata. // // This test verifies: -// 1. The -Z separate-spans flag is accepted by the compiler +// 1. The -Z stable-crate-hash flag is accepted by the compiler // 2. Compilation produces a .spans file alongside the .rmeta file (when implemented) // 3. The resulting rlib is reproducible across builds from different directories // @@ -12,14 +12,14 @@ use run_make_support::{cwd, rfs, run_in_tmpdir, rust_lib_name, rustc}; fn main() { - // Test 1: Basic compilation with -Z separate-spans succeeds + // Test 1: Basic compilation with -Z stable-crate-hash succeeds run_in_tmpdir(|| { - rustc().input("lib.rs").crate_type("rlib").arg("-Zseparate-spans").run(); + rustc().input("lib.rs").crate_type("rlib").arg("-Zstable-crate-hash").run(); // Verify the rlib was created assert!( std::path::Path::new(&rust_lib_name("rdr_test_lib")).exists(), - "rlib should be created with -Z separate-spans" + "rlib should be created with -Z stable-crate-hash" ); }); @@ -29,7 +29,7 @@ fn main() { rustc() .input("lib.rs") .crate_type("rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg(&format!("--remap-path-prefix={}=/src", cwd().display())) .run(); @@ -40,7 +40,7 @@ fn main() { rustc() .input("lib.rs") .crate_type("rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg(&format!("--remap-path-prefix={}=/src", cwd().display())) .run(); @@ -48,7 +48,7 @@ fn main() { assert_eq!( first_rlib, second_rlib, - "Two identical compilations with -Z separate-spans should produce identical rlibs" + "Two identical compilations with -Z stable-crate-hash should produce identical rlibs" ); }); @@ -65,7 +65,7 @@ fn main() { rustc() .input("lib.rs") .crate_type("rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg(&format!("--remap-path-prefix={}=/src", base_dir.display())) .run(); @@ -77,7 +77,7 @@ fn main() { rustc() .input("lib.rs") .crate_type("rlib") - .arg("-Zseparate-spans") + .arg("-Zstable-crate-hash") .arg(&format!("--remap-path-prefix={}=/src", base_dir.join("subdir").display())) .out_dir(&base_dir) .run(); From 20f7d4f1b721bbad265d8c93c82e476a433de87a Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sat, 17 Jan 2026 17:14:42 -0800 Subject: [PATCH 16/19] rdr: add a test that shows adding a private item doesn't cause rebuilds --- .../rdr_add_private_item/auxiliary/lib.rs | 28 +++++++++++++++++++ .../incremental/rdr_add_private_item/main.rs | 27 ++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/incremental/rdr_add_private_item/auxiliary/lib.rs create mode 100644 tests/incremental/rdr_add_private_item/main.rs diff --git a/tests/incremental/rdr_add_private_item/auxiliary/lib.rs b/tests/incremental/rdr_add_private_item/auxiliary/lib.rs new file mode 100644 index 0000000000000..b55ee66e640cf --- /dev/null +++ b/tests/incremental/rdr_add_private_item/auxiliary/lib.rs @@ -0,0 +1,28 @@ +// Auxiliary crate that adds a private item in rpass2. +// The SVH should remain stable, so dependents should not rebuild. + +//@[rpass1] compile-flags: -Z query-dep-graph -Z stable-crate-hash +//@[rpass2] compile-flags: -Z query-dep-graph -Z stable-crate-hash + +#![crate_type = "rlib"] + +pub fn public_fn() -> i32 { + 42 +} + +pub struct PublicStruct { + pub field: i32, +} + +#[cfg(rpass2)] +fn private_fn() -> i32 { + 100 +} + +#[cfg(rpass2)] +struct PrivateStruct { + field: String, +} + +#[cfg(rpass2)] +const PRIVATE_CONST: i32 = 999; diff --git a/tests/incremental/rdr_add_private_item/main.rs b/tests/incremental/rdr_add_private_item/main.rs new file mode 100644 index 0000000000000..47506169784fa --- /dev/null +++ b/tests/incremental/rdr_add_private_item/main.rs @@ -0,0 +1,27 @@ +// Test that adding private items to a dependency does not cause rebuilds. +// +// rpass1: Initial compilation +// rpass2: Auxiliary adds private items - main should be fully reused +// +// With -Z stable-crate-hash, the SVH only includes public API, so adding +// private items should not change the SVH and should not trigger rebuilds. + +//@ revisions: rpass1 rpass2 +//@ compile-flags: -Z query-dep-graph -Z stable-crate-hash -Z incremental-ignore-spans +//@ aux-build: lib.rs + +#![feature(rustc_attrs)] + +// The main module should be reused in rpass2 since the dependency's +// public API hasn't changed (only private items were added). +#![rustc_partition_reused(module = "main", cfg = "rpass2")] + +extern crate lib; + +pub fn use_dependency() -> i32 { + lib::public_fn() + lib::PublicStruct { field: 10 }.field +} + +fn main() { + assert_eq!(use_dependency(), 52); +} From 770d5fe971b6b8c8757626a4f1269748794a00e6 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sat, 17 Jan 2026 17:42:50 -0800 Subject: [PATCH 17/19] rdr: migrate some queries from `Span` to `SpanRef` This allows using RDR without `-Zincremental-ignore-spans`. --- .../rustc_hir_analysis/src/check/check.rs | 1 + .../src/check/compare_impl_item.rs | 2 + .../src/check/compare_impl_item/refine.rs | 3 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 1 + compiler/rustc_hir_analysis/src/collect.rs | 4 +- .../src/collect/item_bounds.rs | 22 +++-- .../src/collect/predicates_of.rs | 38 ++++--- .../src/hir_ty_lowering/mod.rs | 4 +- .../rustc_hir_analysis/src/outlives/mod.rs | 24 ++++- compiler/rustc_hir_typeck/src/closure.rs | 6 +- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 5 +- compiler/rustc_lint/src/builtin.rs | 14 ++- .../src/opaque_hidden_inferred_bound.rs | 2 + compiler/rustc_lint/src/unused.rs | 7 +- .../src/rmeta/decoder/cstore_impl.rs | 30 ++++-- compiler/rustc_metadata/src/rmeta/encoder.rs | 85 ++++------------ compiler/rustc_middle/src/query/mod.rs | 20 ++-- .../rustc_middle/src/query/on_disk_cache.rs | 16 ++- compiler/rustc_middle/src/ty/codec.rs | 20 +++- compiler/rustc_middle/src/ty/context.rs | 98 ++++++++++++++++++- .../rustc_middle/src/ty/structural_impls.rs | 1 + compiler/rustc_privacy/src/lib.rs | 17 +++- .../src/traits/dyn_compatibility.rs | 55 +++++++---- .../src/traits/engine.rs | 4 +- .../rustc_trait_selection/src/traits/util.rs | 5 +- compiler/rustc_ty_utils/src/implied_bounds.rs | 12 ++- compiler/rustc_ty_utils/src/opaque_types.rs | 1 + compiler/rustc_ty_utils/src/sig_types.rs | 1 + src/librustdoc/clean/mod.rs | 5 +- .../rdr-hygiene-hash-collision/rmake.rs | 18 ++-- 30 files changed, 354 insertions(+), 167 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index c37611ab2ee70..a753cdddd799e 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -326,6 +326,7 @@ fn check_opaque_meets_bounds<'tcx>( for (predicate, pred_span) in tcx.explicit_item_bounds(def_id).iter_instantiated_copied(tcx, args) { + let pred_span = tcx.resolve_span_ref(pred_span); let predicate = predicate.fold_with(&mut BottomUpFolder { tcx, ty_op: |ty| if ty == opaque_ty { hidden_ty } else { ty }, diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index b09e039417253..710e181fb1f76 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -839,6 +839,7 @@ where .explicit_item_bounds(proj.def_id) .iter_instantiated_copied(self.cx(), proj.args) { + let pred_span = self.cx().resolve_span_ref(pred_span); let pred = pred.fold_with(self); let pred = self.ocx.normalize( &ObligationCause::misc(self.span, self.body_id), @@ -2413,6 +2414,7 @@ pub(super) fn check_type_bounds<'tcx>( tcx, tcx.explicit_item_bounds(trait_ty.def_id).iter_instantiated_copied(tcx, rebased_args).map( |(concrete_ty_bound, span)| { + let span = tcx.resolve_span_ref(span); debug!(?concrete_ty_bound); traits::Obligation::new(tcx, mk_cause(span), param_env, concrete_ty_bound) }, diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs index c20e5146546a2..386af2e274771 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs @@ -120,7 +120,8 @@ pub(crate) fn check_refining_return_position_impl_trait_in_trait<'tcx>( impl_bounds.extend(elaborate( tcx, tcx.explicit_item_bounds(impl_opaque.def_id) - .iter_instantiated_copied(tcx, impl_opaque.args), + .iter_instantiated_copied(tcx, impl_opaque.args) + .map(|(clause, span)| (clause, tcx.resolve_span_ref(span))), )); pairs.push((trait_projection, impl_opaque)); diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 8b50eceb26e49..81d9d67013ac8 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1167,6 +1167,7 @@ fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocIt debug!("check_associated_type_bounds: bounds={:?}", bounds); let wf_obligations = bounds.iter_identity_copied().flat_map(|(bound, bound_span)| { + let bound_span = wfcx.tcx().resolve_span_ref(bound_span); traits::wf::clause_obligations( wfcx.infcx, wfcx.param_env, diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 2c02534093870..56fd0a855fb57 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -38,7 +38,7 @@ use rustc_middle::ty::{ self, AdtKind, Const, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, TypingMode, fold_regions, }; use rustc_middle::{bug, span_bug}; -use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; +use rustc_span::{DUMMY_SP, Ident, Span, SpanRef, Symbol, kw, sym}; use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{ @@ -358,7 +358,7 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> { span: Span, def_id: LocalDefId, assoc_ident: Ident, - ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { self.tcx.at(span).type_param_predicates((self.item_def_id, def_id, assoc_ident)) } diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index 7025f7ac84b02..a9ffbf369abba 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -6,8 +6,18 @@ use rustc_middle::ty::{ Upcast, shift_vars, }; use rustc_middle::{bug, span_bug}; -use rustc_span::Span; use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_span::{Span, SpanRef}; +use rustc_span::def_id::{DefId, LocalDefId}; + +fn convert_to_span_ref<'tcx>( + tcx: TyCtxt<'tcx>, + bounds: &[(ty::Clause<'tcx>, Span)], +) -> &'tcx [(ty::Clause<'tcx>, SpanRef)] { + tcx.arena.alloc_from_iter( + bounds.iter().map(|(clause, span)| (*clause, tcx.span_ref_from_span(*span))), + ) +} use tracing::{debug, instrument}; use super::ItemCtxt; @@ -403,14 +413,14 @@ fn opaque_type_bounds<'tcx>( pub(super) fn explicit_item_bounds( tcx: TyCtxt<'_>, def_id: LocalDefId, -) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, Span)]> { +) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, SpanRef)]> { explicit_item_bounds_with_filter(tcx, def_id, PredicateFilter::All) } pub(super) fn explicit_item_self_bounds( tcx: TyCtxt<'_>, def_id: LocalDefId, -) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, Span)]> { +) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, SpanRef)]> { explicit_item_bounds_with_filter(tcx, def_id, PredicateFilter::SelfOnly) } @@ -418,7 +428,7 @@ pub(super) fn explicit_item_bounds_with_filter( tcx: TyCtxt<'_>, def_id: LocalDefId, filter: PredicateFilter, -) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, Span)]> { +) -> ty::EarlyBinder<'_, &'_ [(ty::Clause<'_>, SpanRef)]> { match tcx.opt_rpitit_info(def_id.to_def_id()) { // RPITIT's bounds are the same as opaque type bounds, but with // a projection self type. @@ -426,7 +436,7 @@ pub(super) fn explicit_item_bounds_with_filter( let opaque_ty = tcx.hir_node_by_def_id(opaque_def_id.expect_local()).expect_opaque_ty(); let bounds = associated_type_bounds(tcx, def_id, opaque_ty.bounds, opaque_ty.span, filter); - return ty::EarlyBinder::bind(bounds); + return ty::EarlyBinder::bind(convert_to_span_ref(tcx, bounds)); } Some(ty::ImplTraitInTraitData::Impl { .. }) => { span_bug!(tcx.def_span(def_id), "RPITIT in impl should not have item bounds") @@ -482,7 +492,7 @@ pub(super) fn explicit_item_bounds_with_filter( node => bug!("item_bounds called on {def_id:?} => {node:?}"), }; - ty::EarlyBinder::bind(bounds) + ty::EarlyBinder::bind(convert_to_span_ref(tcx, bounds)) } pub(super) fn item_bounds(tcx: TyCtxt<'_>, def_id: DefId) -> ty::EarlyBinder<'_, ty::Clauses<'_>> { diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index 178c47b09c84d..63f9bf26b9b78 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -11,7 +11,16 @@ use rustc_middle::ty::{ self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, TypeVisitable, TypeVisitor, Upcast, }; use rustc_middle::{bug, span_bug}; -use rustc_span::{DUMMY_SP, Ident, Span}; +use rustc_span::{DUMMY_SP, Ident, Span, SpanRef}; + +fn convert_to_span_ref<'tcx>( + tcx: TyCtxt<'tcx>, + predicates: &[(ty::Clause<'tcx>, Span)], +) -> &'tcx [(ty::Clause<'tcx>, SpanRef)] { + tcx.arena.alloc_from_iter( + predicates.iter().map(|(clause, span)| (*clause, tcx.span_ref_from_span(*span))), + ) +} use tracing::{debug, instrument, trace}; use super::item_bounds::explicit_item_bounds_with_filter; @@ -35,7 +44,7 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic if !inferred_outlives.is_empty() { debug!("predicates_of: inferred_outlives_of({:?}) = {:?}", def_id, inferred_outlives,); let inferred_outlives_iter = - inferred_outlives.iter().map(|(clause, span)| ((*clause).upcast(tcx), *span)); + inferred_outlives.iter().map(|(clause, span)| ((*clause).upcast(tcx), tcx.resolve_span_ref(*span))); if result.predicates.is_empty() { result.predicates = tcx.arena.alloc_from_iter(inferred_outlives_iter); } else { @@ -603,14 +612,14 @@ pub(super) fn explicit_predicates_of<'tcx>( pub(super) fn explicit_super_predicates_of<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: LocalDefId, -) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { +) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { implied_predicates_with_filter(tcx, trait_def_id.to_def_id(), PredicateFilter::SelfOnly) } pub(super) fn explicit_supertraits_containing_assoc_item<'tcx>( tcx: TyCtxt<'tcx>, (trait_def_id, assoc_ident): (DefId, Ident), -) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { +) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { implied_predicates_with_filter( tcx, trait_def_id, @@ -621,7 +630,7 @@ pub(super) fn explicit_supertraits_containing_assoc_item<'tcx>( pub(super) fn explicit_implied_predicates_of<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: LocalDefId, -) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { +) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { implied_predicates_with_filter( tcx, trait_def_id.to_def_id(), @@ -640,7 +649,7 @@ pub(super) fn implied_predicates_with_filter<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: DefId, filter: PredicateFilter, -) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { +) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { let Some(trait_def_id) = trait_def_id.as_local() else { // if `assoc_ident` is None, then the query should've been redirected to an // external provider @@ -732,7 +741,7 @@ pub(super) fn implied_predicates_with_filter<'tcx>( assert_only_contains_predicates_from(filter, implied_bounds, tcx.types.self_param); - ty::EarlyBinder::bind(implied_bounds) + ty::EarlyBinder::bind(convert_to_span_ref(tcx, implied_bounds)) } // Make sure when elaborating supertraits, probing for associated types, etc., @@ -876,7 +885,7 @@ pub(super) fn assert_only_contains_predicates_from<'tcx>( pub(super) fn type_param_predicates<'tcx>( tcx: TyCtxt<'tcx>, (item_def_id, def_id, assoc_ident): (LocalDefId, LocalDefId, Ident), -) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { +) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { match tcx.opt_rpitit_info(item_def_id.to_def_id()) { Some(ty::ImplTraitInTraitData::Trait { opaque_def_id, .. }) => { return tcx.type_param_predicates((opaque_def_id.expect_local(), def_id, assoc_ident)); @@ -933,8 +942,13 @@ pub(super) fn type_param_predicates<'tcx>( PredicateFilter::SelfTraitThatDefines(assoc_ident), )); - let bounds = - &*tcx.arena.alloc_from_iter(result.skip_binder().iter().copied().chain(extra_predicates)); + let bounds = &*tcx.arena.alloc_from_iter( + result + .skip_binder() + .iter() + .map(|(clause, span)| (*clause, tcx.resolve_span_ref(*span))) + .chain(extra_predicates), + ); // Double check that the bounds *only* contain `SelfTy: Trait` preds. let self_ty = match tcx.def_kind(def_id) { @@ -954,7 +968,7 @@ pub(super) fn type_param_predicates<'tcx>( self_ty, ); - ty::EarlyBinder::bind(bounds) + ty::EarlyBinder::bind(convert_to_span_ref(tcx, bounds)) } impl<'tcx> ItemCtxt<'tcx> { @@ -1174,7 +1188,7 @@ pub(super) fn explicit_implied_const_bounds<'tcx>( }) => trait_ref, _ => bug!("converted {clause:?}"), }), - span, + tcx.resolve_span_ref(span), ) })) }) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 6e1e6c157a91e..eb36b695ad553 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -45,7 +45,7 @@ use rustc_middle::ty::{ use rustc_middle::{bug, span_bug}; use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS; use rustc_session::parse::feature_err; -use rustc_span::{DUMMY_SP, Ident, Span, kw, sym}; +use rustc_span::{DUMMY_SP, Ident, Span, SpanRef, kw, sym}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::wf::object_region_bounds; use rustc_trait_selection::traits::{self, FulfillmentError}; @@ -167,7 +167,7 @@ pub trait HirTyLowerer<'tcx> { span: Span, def_id: LocalDefId, assoc_ident: Ident, - ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]>; + ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]>; fn select_inherent_assoc_candidates( &self, diff --git a/compiler/rustc_hir_analysis/src/outlives/mod.rs b/compiler/rustc_hir_analysis/src/outlives/mod.rs index d155f4f98ad79..b521e55736e07 100644 --- a/compiler/rustc_hir_analysis/src/outlives/mod.rs +++ b/compiler/rustc_hir_analysis/src/outlives/mod.rs @@ -1,7 +1,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::{self, CratePredicatesMap, GenericArgKind, TyCtxt, Upcast}; -use rustc_span::Span; +use rustc_span::SpanRef; pub(crate) mod dump; mod explicit; @@ -11,15 +11,31 @@ mod utils; pub(super) fn inferred_outlives_of( tcx: TyCtxt<'_>, item_def_id: LocalDefId, -) -> &[(ty::Clause<'_>, Span)] { +) -> &[(ty::Clause<'_>, SpanRef)] { + fn convert_to_span_ref<'tcx>( + tcx: TyCtxt<'tcx>, + predicates: &[(ty::Clause<'tcx>, rustc_span::Span)], + ) -> &'tcx [(ty::Clause<'tcx>, SpanRef)] { + if predicates.is_empty() { + return &[]; + } + tcx.arena.alloc_from_iter( + predicates.iter().map(|(clause, span)| (*clause, tcx.span_ref_from_span(*span))), + ) + } + match tcx.def_kind(item_def_id) { DefKind::Struct | DefKind::Enum | DefKind::Union => { let crate_map = tcx.inferred_outlives_crate(()); - crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) + let predicates = + crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]); + convert_to_span_ref(tcx, predicates) } DefKind::TyAlias if tcx.type_alias_is_lazy(item_def_id) => { let crate_map = tcx.inferred_outlives_crate(()); - crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) + let predicates = + crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]); + convert_to_span_ref(tcx, predicates) } DefKind::AnonConst if tcx.features().generic_const_exprs() => { let id = tcx.local_def_id_to_hir_id(item_def_id); diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index 82b7c578a1f25..1668468761cd1 100644 --- a/compiler/rustc_hir_typeck/src/closure.rs +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -312,7 +312,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx .explicit_item_self_bounds(def_id) .iter_instantiated_copied(self.tcx, args) - .map(|(c, s)| (c.as_predicate(), s)), + .map(|(c, s)| (c.as_predicate(), self.tcx.resolve_span_ref(s))), ), ty::Dynamic(object_type, ..) => { let sig = object_type.projection_bounds().find_map(|pb| { @@ -1028,7 +1028,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .tcx .explicit_item_self_bounds(def_id) .iter_instantiated_copied(self.tcx, args) - .find_map(|(p, s)| get_future_output(p.as_predicate(), s))?, + .find_map(|(p, s)| { + get_future_output(p.as_predicate(), self.tcx.resolve_span_ref(s)) + })?, ty::Error(_) => return Some(ret_ty), _ => { span_bug!(closure_span, "invalid async fn coroutine return type: {ret_ty:?}") diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index c875e2e50d70c..8b6305267a4d0 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -19,7 +19,7 @@ use rustc_infer::infer::{self, RegionVariableOrigin}; use rustc_infer::traits::{DynCompatibilityViolation, Obligation}; use rustc_middle::ty::{self, Const, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::Session; -use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span, sym}; +use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span, SpanRef, sym}; use rustc_trait_selection::error_reporting::TypeErrCtxt; use rustc_trait_selection::traits::{ self, FulfillmentError, ObligationCause, ObligationCauseCode, ObligationCtxt, @@ -296,13 +296,14 @@ impl<'tcx> HirTyLowerer<'tcx> for FnCtxt<'_, 'tcx> { _: Span, def_id: LocalDefId, _: Ident, - ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { let tcx = self.tcx; let item_def_id = tcx.hir_ty_param_owner(def_id); let generics = tcx.generics_of(item_def_id); let index = generics.param_def_id_to_index[&def_id.to_def_id()]; // HACK(eddyb) should get the original `Span`. let span = tcx.def_span(def_id); + let span = tcx.span_ref_from_span(span); ty::EarlyBinder::bind(tcx.arena.alloc_from_iter( self.param_env.caller_bounds().iter().filter_map(|predicate| { diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e2a061cab680a..ddb9899379d65 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -41,7 +41,7 @@ use rustc_session::lint::fcw; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; -use rustc_span::{DUMMY_SP, Ident, InnerSpan, Span, Symbol, kw, sym}; +use rustc_span::{DUMMY_SP, Ident, InnerSpan, Span, SpanRef, Symbol, kw, sym}; use rustc_target::asm::InlineAsmArch; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy; @@ -1939,7 +1939,7 @@ declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMEN impl ExplicitOutlivesRequirements { fn lifetimes_outliving_lifetime<'tcx>( tcx: TyCtxt<'tcx>, - inferred_outlives: impl Iterator, Span)>, + inferred_outlives: impl Iterator, SpanRef)>, item: LocalDefId, lifetime: LocalDefId, ) -> Vec> { @@ -1961,7 +1961,7 @@ impl ExplicitOutlivesRequirements { } fn lifetimes_outliving_type<'tcx>( - inferred_outlives: impl Iterator, Span)>, + inferred_outlives: impl Iterator, SpanRef)>, index: u32, ) -> Vec> { inferred_outlives @@ -2110,7 +2110,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { // don't warn if the inferred span actually came from the predicate we're looking at // this happens if the type is recursively defined inferred_outlives.iter().filter(|(_, span)| { - !where_predicate.span.contains(*span) + !where_predicate + .span + .contains(cx.tcx.resolve_span_ref(*span)) }), item.owner_id.def_id, region_def_id, @@ -2137,7 +2139,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { // don't warn if the inferred span actually came from the predicate we're looking at // this happens if the type is recursively defined inferred_outlives.iter().filter(|(_, span)| { - !where_predicate.span.contains(*span) + !where_predicate + .span + .contains(cx.tcx.resolve_span_ref(*span)) }), index, ), diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 3f2ca92a021a6..f2043f54e4784 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -89,6 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { // check that the type that we're assigning actually satisfies the bounds // of the associated type. for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).iter_identity_copied() { + let pred_span = cx.tcx.resolve_span_ref(pred_span); infcx.enter_forall(pred.kind(), |predicate| { let ty::ClauseKind::Projection(proj) = predicate else { return; @@ -146,6 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { .explicit_item_bounds(proj.projection_term.def_id) .iter_instantiated_copied(cx.tcx, proj.projection_term.args) { + let assoc_pred_span = cx.tcx.resolve_span_ref(assoc_pred_span); let assoc_pred = assoc_pred.fold_with(proj_replacer); let ocx = ObligationCtxt::new(infcx); diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 506a16355e226..47710a2a3fae5 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -323,7 +323,12 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) + let bounds = cx + .tcx + .explicit_item_self_bounds(def) + .iter_identity_copied() + .map(|(clause, span)| (clause, cx.tcx.resolve_span_ref(span))); + elaborate(cx.tcx, bounds) // We only care about self bounds for the impl-trait .filter_only_self() .find_map(|(pred, _span)| { diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 8864a21f53e28..e3dabfdb3a3e1 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -144,9 +144,24 @@ macro_rules! provide_one { } } }; - // Like table_defaulted_array, but for defaulted tables storing (T, SpanRef) tuples that need - // conversion to (T, Span) at decode time. + // Like table_defaulted_array, but for defaulted tables storing (T, SpanRef) tuples. + // The SpanRef values are preserved without conversion to Span. ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_defaulted_array_spanref }) => { + provide_one! { + $tcx, $def_id, $other, $cdata, $name => { + let lazy = $cdata.root.tables.$name.get(($cdata, $tcx), $def_id.index); + let value: &[_] = if lazy.is_default() { + &[] + } else { + $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx))) + }; + value.process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) + } + } + }; + // Like table_defaulted_array_spanref, but converts SpanRef to Span at decode time. + // Used for queries that return (T, Span) but are stored with SpanRef in metadata. + ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_defaulted_array_spanref_resolve }) => { provide_one! { $tcx, $def_id, $other, $cdata, $name => { let lazy = $cdata.root.tables.$name.get(($cdata, $tcx), $def_id.index); @@ -162,8 +177,8 @@ macro_rules! provide_one { } } }; - // Like table, but for optional array tables storing (T, SpanRef) tuples that need - // conversion to (T, Span) at decode time. + // Like table, but for optional array tables storing (T, SpanRef) tuples. + // The SpanRef values are preserved without conversion to Span. ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_array_spanref }) => { provide_one! { $tcx, $def_id, $other, $cdata, $name => { @@ -173,10 +188,7 @@ macro_rules! provide_one { .$name .get(($cdata, $tcx), $def_id.index) .map(|lazy| { - $tcx.arena.alloc_from_iter( - lazy.decode(($cdata, $tcx)) - .map(|(item, span_ref)| (item, resolve_span_ref($tcx, span_ref))), - ) as &[_] + $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx))) as &[_] }) .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } @@ -342,7 +354,7 @@ provide! { tcx, def_id, other, cdata, defaultness => { table_direct } constness => { table_direct } const_conditions => { table } - explicit_implied_const_bounds => { table_defaulted_array_spanref } + explicit_implied_const_bounds => { table_defaulted_array_spanref_resolve } coerce_unsized_info => { Ok(cdata .root diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 6d1574ea7c16f..8b3973cb1a149 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1657,14 +1657,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.generics_of[def_id] <- g); record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); - let inferred_outlives_converted: Vec<_> = { - let mut result = Vec::with_capacity(inferred_outlives.len()); - for &(c, s) in inferred_outlives.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives_converted); + // Query already returns SpanRef, no conversion needed + record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives.to_vec()); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { @@ -1698,29 +1692,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } if let DefKind::Trait = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); + // Queries already return SpanRef, no conversion needed let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); - let super_predicates_converted: Vec<_> = { - let mut result = Vec::with_capacity(super_predicates.len()); - for &(c, s) in super_predicates.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates_converted); - let implied_predicates = - self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); - let implied_predicates_converted: Vec<_> = { - let mut result = Vec::with_capacity(implied_predicates.len()); - for &(c, s) in implied_predicates.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates_converted); + record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates.to_vec()); + let implied_predicates = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates.to_vec()); let module_children = self.tcx.module_children_local(local_id); record_array!(self.tables.module_children_non_reexports[def_id] <- module_children.iter().map(|child| child.res.def_id().index)); if self.tcx.is_const_trait(def_id) { + // explicit_implied_const_bounds still returns Span, needs conversion let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); let const_bounds_converted: Vec<_> = { let mut result = Vec::with_capacity(const_bounds.len()); @@ -1734,25 +1715,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } if let DefKind::TraitAlias = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); + // Queries already return SpanRef, no conversion needed let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); - let super_predicates_converted: Vec<_> = { - let mut result = Vec::with_capacity(super_predicates.len()); - for &(c, s) in super_predicates.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates_converted); - let implied_predicates = - self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); - let implied_predicates_converted: Vec<_> = { - let mut result = Vec::with_capacity(implied_predicates.len()); - for &(c, s) in implied_predicates.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates_converted); + record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates.to_vec()); + let implied_predicates = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates.to_vec()); } if let DefKind::Trait | DefKind::Impl { .. } = def_kind { let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); @@ -1961,28 +1928,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_explicit_item_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_bounds({:?})", def_id); + // Query already returns SpanRef, no conversion needed let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - let bounds_converted: Vec<_> = { - let mut result = Vec::with_capacity(bounds.len()); - for &(c, s) in bounds.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds_converted); + record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds.to_vec()); } fn encode_explicit_item_self_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); + // Query already returns SpanRef, no conversion needed let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - let bounds_converted: Vec<_> = { - let mut result = Vec::with_capacity(bounds.len()); - for &(c, s) in bounds.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds_converted); + record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds.to_vec()); } #[instrument(level = "debug", skip(self))] @@ -2016,15 +1971,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if let ty::AssocKind::Type { data: ty::AssocTypeData::Rpitit(rpitit_info) } = item.kind { record!(self.tables.opt_rpitit_info[def_id] <- rpitit_info); if matches!(rpitit_info, ty::ImplTraitInTraitData::Trait { .. }) { + // Query already returns SpanRef, no conversion needed let wf_types = self.tcx.assumed_wf_types_for_rpitit(def_id); - let wf_types_converted: Vec<_> = { - let mut result = Vec::with_capacity(wf_types.len()); - for &(ty, s) in wf_types.iter() { - result.push((ty, self.span_to_span_ref(s))); - } - result - }; - record_array!(self.tables.assumed_wf_types_for_rpitit[def_id] <- wf_types_converted); + record_array!(self.tables.assumed_wf_types_for_rpitit[def_id] <- wf_types.iter().copied()); self.encode_precise_capturing_args(def_id); } } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index c3cab9b6a2e4a..a0819b14822e2 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -98,7 +98,7 @@ use rustc_session::cstore::{ use rustc_session::lint::LintExpectationId; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::source_map::Spanned; -use rustc_span::{DUMMY_SP, LocalExpnId, Span, Symbol}; +use rustc_span::{DUMMY_SP, LocalExpnId, Span, SpanRef, Symbol}; use rustc_target::spec::PanicStrategy; use {rustc_abi as abi, rustc_ast as ast, rustc_hir as hir}; @@ -507,7 +507,7 @@ rustc_queries! { /// fn function() -> impl Debug + Display { /*...*/ } /// // ^^^^^^^^^^^^^^^ /// ``` - query explicit_item_bounds(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + query explicit_item_bounds(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "finding item bounds for `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -520,7 +520,7 @@ rustc_queries! { /// like closure signature deduction. /// /// [explicit item bounds]: Self::explicit_item_bounds - query explicit_item_self_bounds(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + query explicit_item_self_bounds(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "finding item bounds for `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -850,7 +850,7 @@ rustc_queries! { /// /// **Tip**: You can use `#[rustc_outlives]` on an item to basically print the /// result of this query for use in UI tests or for debugging purposes. - query inferred_outlives_of(key: DefId) -> &'tcx [(ty::Clause<'tcx>, Span)] { + query inferred_outlives_of(key: DefId) -> &'tcx [(ty::Clause<'tcx>, SpanRef)] { desc { |tcx| "computing inferred outlives-predicates of `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -864,7 +864,7 @@ rustc_queries! { /// This is a subset of the full list of predicates. We store these in a separate map /// because we must evaluate them even during type conversion, often before the full /// predicates are available (note that super-predicates must not be cyclic). - query explicit_super_predicates_of(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + query explicit_super_predicates_of(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "computing the super predicates of `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -876,7 +876,7 @@ rustc_queries! { /// of the trait. For regular traits, this includes all super-predicates and their /// associated type bounds. For trait aliases, currently, this includes all of the /// predicates of the trait alias. - query explicit_implied_predicates_of(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + query explicit_implied_predicates_of(key: DefId) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "computing the implied predicates of `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -887,7 +887,7 @@ rustc_queries! { /// cycles in resolving type-dependent associated item paths like `T::Item`. query explicit_supertraits_containing_assoc_item( key: (DefId, rustc_span::Ident) - ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "computing the super traits of `{}` with associated type name `{}`", tcx.def_path_str(key.0), key.1 @@ -930,7 +930,7 @@ rustc_queries! { /// per-type-parameter predicates for resolving `T::AssocTy`. query type_param_predicates( key: (LocalDefId, LocalDefId, rustc_span::Ident) - ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> { + ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, SpanRef)]> { desc { |tcx| "computing the bounds for type parameter `{}`", tcx.hir_ty_param_name(key.1) } } @@ -1180,13 +1180,13 @@ rustc_queries! { /// /// Note that we've liberated the late bound regions of function signatures, so /// this can not be used to check whether these types are well formed. - query assumed_wf_types(key: LocalDefId) -> &'tcx [(Ty<'tcx>, Span)] { + query assumed_wf_types(key: LocalDefId) -> &'tcx [(Ty<'tcx>, SpanRef)] { desc { |tcx| "computing the implied bounds of `{}`", tcx.def_path_str(key) } } /// We need to store the assumed_wf_types for an RPITIT so that impls of foreign /// traits with return-position impl trait in traits can inherit the right wf types. - query assumed_wf_types_for_rpitit(key: DefId) -> &'tcx [(Ty<'tcx>, Span)] { + query assumed_wf_types_for_rpitit(key: DefId) -> &'tcx [(Ty<'tcx>, SpanRef)] { desc { |tcx| "computing the implied bounds of `{}`", tcx.def_path_str(key) } separate_provide_extern } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 5ef0c2500e7a9..a8811ecf4074a 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -21,7 +21,7 @@ use rustc_span::hygiene::{ use rustc_span::source_map::Spanned; use rustc_span::{ BlobDecoder, BytePos, ByteSymbol, CachingSourceMapView, ExpnData, ExpnHash, RelativeBytePos, - SourceFile, Span, SpanDecoder, SpanEncoder, StableSourceFileId, Symbol, + SourceFile, Span, SpanDecoder, SpanEncoder, SpanRef, StableSourceFileId, Symbol, }; use crate::dep_graph::{DepNodeIndex, SerializedDepNodeIndex}; @@ -762,6 +762,20 @@ impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, S } } +impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, SpanRef)] { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + RefDecodable::decode(d) + } +} + +impl<'a, 'tcx> Decodable> for &'tcx [(Ty<'tcx>, SpanRef)] { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + RefDecodable::decode(d) + } +} + impl<'a, 'tcx> Decodable> for &'tcx [rustc_ast::InlineAsmTemplatePiece] { #[inline] fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 75b1317e022bd..b1123f19743cd 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -15,7 +15,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::LocalDefId; use rustc_serialize::{Decodable, Encodable}; use rustc_span::source_map::Spanned; -use rustc_span::{Span, SpanDecoder, SpanEncoder}; +use rustc_span::{Span, SpanDecoder, SpanEncoder, SpanRef}; use crate::arena::ArenaAllocatable; use crate::infer::canonical::{CanonicalVarKind, CanonicalVarKinds}; @@ -409,6 +409,24 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::Clause<'tcx>, Spa } } +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::Clause<'tcx>, SpanRef)] { + fn decode(decoder: &mut D) -> &'tcx Self { + decoder + .interner() + .arena + .alloc_from_iter((0..decoder.read_usize()).map(|_| Decodable::decode(decoder))) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(Ty<'tcx>, SpanRef)] { + fn decode(decoder: &mut D) -> &'tcx Self { + decoder + .interner() + .arena + .alloc_from_iter((0..decoder.read_usize()).map(|_| Decodable::decode(decoder))) + } +} + impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::PolyTraitRef<'tcx>, Span)] { fn decode(decoder: &mut D) -> &'tcx Self { decoder diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e9fa3f14358e8..5de846a80fce6 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -50,7 +50,7 @@ use rustc_session::config::CrateType; use rustc_session::cstore::{CrateStoreDyn, Untracked}; use rustc_session::lint::Lint; use rustc_span::def_id::{CRATE_DEF_ID, DefPathHash, StableCrateId}; -use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, Ident, Span, SpanRef, Symbol, kw, sym}; use rustc_type_ir::TyKind::*; use rustc_type_ir::lang_items::{SolverAdtLangItem, SolverLangItem, SolverTraitLangItem}; pub use rustc_type_ir::lift::Lift; @@ -436,14 +436,26 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self, def_id: DefId, ) -> ty::EarlyBinder<'tcx, impl IntoIterator, Span)>> { - self.explicit_super_predicates_of(def_id).map_bound(|preds| preds.into_iter().copied()) + let preds: Vec<_> = self + .explicit_super_predicates_of(def_id) + .skip_binder() + .iter() + .map(|(clause, span_ref)| (*clause, self.resolve_span_ref(*span_ref))) + .collect(); + ty::EarlyBinder::bind(preds) } fn explicit_implied_predicates_of( self, def_id: DefId, ) -> ty::EarlyBinder<'tcx, impl IntoIterator, Span)>> { - self.explicit_implied_predicates_of(def_id).map_bound(|preds| preds.into_iter().copied()) + let preds: Vec<_> = self + .explicit_implied_predicates_of(def_id) + .skip_binder() + .iter() + .map(|(clause, span_ref)| (*clause, self.resolve_span_ref(*span_ref))) + .collect(); + ty::EarlyBinder::bind(preds) } fn impl_super_outlives( @@ -2462,6 +2474,86 @@ impl<'tcx> TyCtxt<'tcx> { self.sess.dcx().emit_fatal(crate::error::FailedWritingFile { path: &path, error }); } } + + /// Resolves a `SpanRef` to a concrete `Span` with absolute byte positions. + /// + /// `SpanRef` uses stable identifiers (file IDs, DefIds) instead of absolute + /// byte positions, making it suitable for incremental compilation caching. + /// This method converts back to absolute positions for diagnostics. + /// + /// Returns `DUMMY_SP` with the appropriate `SyntaxContext` if the span + /// cannot be resolved (e.g., stale cache, removed dependency). + pub fn resolve_span_ref(self, span_ref: SpanRef) -> Span { + match span_ref { + SpanRef::Opaque { ctxt } => { + // Opaque spans have no position info - return dummy with context + DUMMY_SP.with_ctxt(ctxt) + } + SpanRef::FileRelative { source_crate: _, file, lo, hi, ctxt } => { + // Look up the source file by its stable ID in the source map. + // The file should already be imported when we load metadata from external crates. + let source_map = self.sess.source_map(); + if let Some(source_file) = source_map.source_file_by_stable_id(file) { + let abs_lo = source_file.start_pos + BytePos(lo); + let abs_hi = source_file.start_pos + BytePos(hi); + return Span::new(abs_lo, abs_hi, ctxt, None); + } + + // File not found in source map - gracefully degrade to dummy span. + // This can happen with stale incremental cache or removed dependencies. + debug!(?file, "resolve_span_ref: source file not found, using dummy span"); + DUMMY_SP.with_ctxt(ctxt) + } + SpanRef::DefRelative { def_id, lo, hi, ctxt } => { + // Look up the definition's span and add relative offsets + let def_span = self.def_span(def_id); + let def_data = def_span.data(); + let abs_lo = def_data.lo + BytePos(lo); + let abs_hi = def_data.lo + BytePos(hi); + Span::new(abs_lo, abs_hi, ctxt, None) + } + } + } + + /// Converts a `Span` to a `SpanRef` for stable storage. + /// + /// `SpanRef` uses file-relative offsets and stable file identifiers, + /// making fingerprints stable across compilations even when source + /// file positions change due to edits in other files. + pub fn span_ref_from_span(self, span: Span) -> SpanRef { + let data = span.data(); + + // Dummy spans become opaque + if span.is_dummy() { + return SpanRef::Opaque { ctxt: data.ctxt }; + } + + let source_map = self.sess.source_map(); + + // Look up the source file containing this span + let source_file = source_map.lookup_source_file(data.lo); + + // If the span crosses file boundaries, make it opaque + let end_file = source_map.lookup_source_file(data.hi); + if source_file.start_pos != end_file.start_pos { + return SpanRef::Opaque { ctxt: data.ctxt }; + } + + // Compute relative offsets from the file's start position + let rel_lo = (data.lo - source_file.start_pos).0; + let rel_hi = (data.hi - source_file.start_pos).0; + + // Get the source crate's stable ID + let source_crate = self.stable_crate_id(source_file.cnum); + + SpanRef::FileRelative { + source_crate, + file: source_file.stable_id, + lo: rel_lo, + hi: rel_hi, + ctxt: data.ctxt, + } + } } macro_rules! nop_lift { diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 314d2ba396327..ad6f93bf66f95 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -272,6 +272,7 @@ TrivialTypeTraversalImpls! { rustc_hir::def_id::LocalDefId, rustc_span::Ident, rustc_span::Span, + rustc_span::SpanRef, rustc_span::Symbol, rustc_target::asm::InlineAsmRegOrRegClass, // tidy-alphabetical-end diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 8656ec6e39aed..f01d057462280 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -273,7 +273,13 @@ where // through the trait list (default type visitor doesn't visit those traits). // All traits in the list are considered the "primary" part of the type // and are visited by shallow visitors. - try_visit!(self.visit_clauses(tcx.explicit_item_bounds(def_id).skip_binder())); + let bounds: Vec<_> = tcx + .explicit_item_bounds(def_id) + .skip_binder() + .iter() + .map(|(clause, span)| (*clause, tcx.resolve_span_ref(*span))) + .collect(); + try_visit!(self.visit_clauses(&bounds)); } } // These types don't have their own def-ids (but may have subcomponents @@ -1392,7 +1398,14 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { fn bounds(&mut self) -> &mut Self { self.in_primary_interface = false; - let _ = self.visit_clauses(self.tcx.explicit_item_bounds(self.item_def_id).skip_binder()); + let bounds: Vec<_> = self + .tcx + .explicit_item_bounds(self.item_def_id) + .skip_binder() + .iter() + .map(|(clause, span)| (*clause, self.tcx.resolve_span_ref(*span))) + .collect(); + let _ = self.visit_clauses(&bounds); self } diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 6ab92531e4eff..05c7df3eb590a 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -170,22 +170,33 @@ fn predicates_reference_self( supertraits_only: bool, ) -> SmallVec<[Span; 1]> { let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_def_id)); - let predicates = if supertraits_only { - tcx.explicit_super_predicates_of(trait_def_id).skip_binder() + if supertraits_only { + // explicit_super_predicates_of returns SpanRef - resolve to Span + tcx.explicit_super_predicates_of(trait_def_id) + .skip_binder() + .iter() + .map(|&(predicate, sp_ref)| { + (predicate.instantiate_supertrait(tcx, trait_ref), tcx.resolve_span_ref(sp_ref)) + }) + .filter_map(|(clause, sp)| { + // Super predicates cannot allow self projections, since they're + // impossible to make into existential bounds without eager resolution + // or something. + // e.g. `trait A: B`. + predicate_references_self(tcx, trait_def_id, clause, sp, AllowSelfProjections::No) + }) + .collect() } else { - tcx.predicates_of(trait_def_id).predicates - }; - predicates - .iter() - .map(|&(predicate, sp)| (predicate.instantiate_supertrait(tcx, trait_ref), sp)) - .filter_map(|(clause, sp)| { - // Super predicates cannot allow self projections, since they're - // impossible to make into existential bounds without eager resolution - // or something. - // e.g. `trait A: B`. - predicate_references_self(tcx, trait_def_id, clause, sp, AllowSelfProjections::No) - }) - .collect() + // predicates_of still returns Span, handle it separately + tcx.predicates_of(trait_def_id) + .predicates + .iter() + .map(|&(predicate, sp)| (predicate.instantiate_supertrait(tcx, trait_ref), sp)) + .filter_map(|(clause, sp)| { + predicate_references_self(tcx, trait_def_id, clause, sp, AllowSelfProjections::No) + }) + .collect() + } } fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span; 1]> { @@ -196,9 +207,11 @@ fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span // Ignore GATs with `Self: Sized` .filter(|item| !tcx.generics_require_sized_self(item.def_id)) .flat_map(|item| tcx.explicit_item_bounds(item.def_id).iter_identity_copied()) - .filter_map(|(clause, sp)| { + .filter_map(|(clause, sp_ref)| { // Item bounds *can* have self projections, since they never get // their self type erased. + // Resolve SpanRef to Span for diagnostic purposes + let sp = tcx.resolve_span_ref(sp_ref); predicate_references_self(tcx, trait_def_id, clause, sp, AllowSelfProjections::Yes) }) .collect() @@ -253,7 +266,10 @@ fn super_predicates_have_non_lifetime_binders( ) -> SmallVec<[Span; 1]> { tcx.explicit_super_predicates_of(trait_def_id) .iter_identity_copied() - .filter_map(|(pred, span)| pred.has_non_region_bound_vars().then_some(span)) + .filter_map(|(pred, span_ref)| { + // Resolve SpanRef to Span for diagnostic purposes + pred.has_non_region_bound_vars().then(|| tcx.resolve_span_ref(span_ref)) + }) .collect() } @@ -266,9 +282,10 @@ fn super_predicates_are_unconditionally_const( ) -> SmallVec<[Span; 1]> { tcx.explicit_super_predicates_of(trait_def_id) .iter_identity_copied() - .filter_map(|(pred, span)| { + .filter_map(|(pred, span_ref)| { if let ty::ClauseKind::HostEffect(_) = pred.kind().skip_binder() { - Some(span) + // Resolve SpanRef to Span for diagnostic purposes + Some(tcx.resolve_span_ref(span_ref)) } else { None } diff --git a/compiler/rustc_trait_selection/src/traits/engine.rs b/compiler/rustc_trait_selection/src/traits/engine.rs index eee23a298449d..57196860abaa7 100644 --- a/compiler/rustc_trait_selection/src/traits/engine.rs +++ b/compiler/rustc_trait_selection/src/traits/engine.rs @@ -314,7 +314,7 @@ where let tcx = self.infcx.tcx; let mut implied_bounds = FxIndexSet::default(); let mut errors = Vec::new(); - for &(ty, span) in tcx.assumed_wf_types(def_id) { + for &(ty, span_ref) in tcx.assumed_wf_types(def_id) { // FIXME(@lcnr): rustc currently does not check wf for types // pre-normalization, meaning that implied bounds are sometimes // incorrect. See #100910 for more details. @@ -327,6 +327,8 @@ where // sound and then uncomment this line again. // implied_bounds.insert(ty); + // Resolve SpanRef to Span for diagnostic purposes + let span = tcx.resolve_span_ref(span_ref); let cause = ObligationCause::misc(span, def_id); match self .infcx diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index 19ccf6a55bf1a..17f475b632bbf 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -52,9 +52,10 @@ pub fn expand_trait_aliases<'tcx>( queue.extend( tcx.explicit_super_predicates_of(trait_pred.def_id()) .iter_identity_copied() - .map(|(super_clause, span)| { + .map(|(super_clause, span_ref)| { let mut spans = spans.clone(); - spans.push(span); + // Resolve SpanRef to Span for diagnostic purposes + spans.push(tcx.resolve_span_ref(span_ref)); ( super_clause.instantiate_supertrait( tcx, diff --git a/compiler/rustc_ty_utils/src/implied_bounds.rs b/compiler/rustc_ty_utils/src/implied_bounds.rs index 990120db9726b..3294538fec946 100644 --- a/compiler/rustc_ty_utils/src/implied_bounds.rs +++ b/compiler/rustc_ty_utils/src/implied_bounds.rs @@ -7,7 +7,7 @@ use rustc_hir::def_id::LocalDefId; use rustc_middle::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt, fold_regions}; use rustc_middle::{bug, span_bug}; -use rustc_span::Span; +use rustc_span::{Span, SpanRef}; pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { @@ -20,7 +20,7 @@ pub(crate) fn provide(providers: &mut Providers) { }; } -fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<'tcx>, Span)] { +fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<'tcx>, SpanRef)] { let kind = tcx.def_kind(def_id); match kind { DefKind::Fn => { @@ -28,7 +28,7 @@ fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<' let liberated_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), sig); tcx.arena.alloc_from_iter(itertools::zip_eq( liberated_sig.inputs_and_output, - fn_sig_spans(tcx, def_id), + fn_sig_spans(tcx, def_id).map(|sp| tcx.span_ref_from_span(sp)), )) } DefKind::AssocFn => { @@ -38,7 +38,7 @@ fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<' tcx.assumed_wf_types(tcx.local_parent(def_id)).into(); assumed_wf_types.extend(itertools::zip_eq( liberated_sig.inputs_and_output, - fn_sig_spans(tcx, def_id), + fn_sig_spans(tcx, def_id).map(|sp| tcx.span_ref_from_span(sp)), )); tcx.arena.alloc_slice(&assumed_wf_types) } @@ -53,7 +53,9 @@ fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<' }; let mut impl_spans = impl_spans(tcx, def_id); - tcx.arena.alloc_from_iter(tys.into_iter().map(|ty| (ty, impl_spans.next().unwrap()))) + tcx.arena.alloc_from_iter( + tys.into_iter().map(|ty| (ty, tcx.span_ref_from_span(impl_spans.next().unwrap()))), + ) } DefKind::AssocTy if let Some(data) = tcx.opt_rpitit_info(def_id.to_def_id()) => { match data { diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index 445021801beff..6e86fd767e576 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -143,6 +143,7 @@ impl<'tcx> OpaqueTypeCollector<'tcx> { for (pred, span) in self.tcx.explicit_item_bounds(alias_ty.def_id).iter_identity_copied() { + let span = self.tcx.resolve_span_ref(span); trace!(?pred); self.visit_spanned(span, pred); } diff --git a/compiler/rustc_ty_utils/src/sig_types.rs b/compiler/rustc_ty_utils/src/sig_types.rs index 07f379d3f9a8f..2c2704aabff56 100644 --- a/compiler/rustc_ty_utils/src/sig_types.rs +++ b/compiler/rustc_ty_utils/src/sig_types.rs @@ -61,6 +61,7 @@ pub fn walk_types<'tcx, V: SpannedTypeVisitor<'tcx>>( } DefKind::OpaqueTy => { for (pred, span) in tcx.explicit_item_bounds(item).iter_identity_copied() { + let span = tcx.resolve_span_ref(span); try_visit!(visitor.visit(span, pred)); } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 0ad9d2fdbe804..ea104ea4416f1 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1392,7 +1392,10 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo let mut predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates; if let ty::AssocContainer::Trait = assoc_item.container { - let bounds = tcx.explicit_item_bounds(assoc_item.def_id).iter_identity_copied(); + let bounds = tcx + .explicit_item_bounds(assoc_item.def_id) + .iter_identity_copied() + .map(|(clause, span)| (clause, tcx.resolve_span_ref(span))); predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); } let mut generics = clean_ty_generics_inner( diff --git a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs index e67b6e7b7925f..58ff494884a2d 100644 --- a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs +++ b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs @@ -1,14 +1,17 @@ // Test that identifiers with the same name but different hygiene contexts -// don't cause dep node hash collisions when using `-Zincremental-ignore-spans`. +// don't cause dep node hash collisions when using RDR (Relink, Don't Rebuild). // // This regression test verifies that SyntaxContext (hygiene info) is always -// hashed even when span positions are ignored. Without this fix, two `Ident`s -// with the same `Symbol` but different `SyntaxContext` would hash identically, -// causing ICE: "query key X and key Y mapped to the same dep node". +// hashed correctly. Without this fix, two `Ident`s with the same `Symbol` but +// different `SyntaxContext` would hash identically, causing ICE: "query key X +// and key Y mapped to the same dep node". // // The issue manifests with macros that generate identifiers - each macro -// expansion creates identifiers with different hygiene contexts, but when -// spans are ignored, only the symbol name was being hashed. +// expansion creates identifiers with different hygiene contexts. +// +// With the SpanRef migration, incremental compilation works correctly without +// needing `-Zincremental-ignore-spans` because SpanRef uses stable identifiers +// that produce consistent fingerprints. // //@ ignore-cross-compile @@ -80,12 +83,12 @@ make_fn!(baz); // Compile with RDR flags - this would ICE before the fix due to // hygiene contexts not being hashed when spans are ignored. + // With SpanRef migration, -Zincremental-ignore-spans is no longer needed. rustc() .input("lib.rs") .crate_type("lib") .incremental("incr") .arg("-Zstable-crate-hash") - .arg("-Zincremental-ignore-spans") .run(); // Second compilation to exercise incremental path @@ -94,6 +97,5 @@ make_fn!(baz); .crate_type("lib") .incremental("incr") .arg("-Zstable-crate-hash") - .arg("-Zincremental-ignore-spans") .run(); } From 69ccd5afe9b84f1e1a4839465e81c351fdffbc0b Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sun, 18 Jan 2026 10:01:23 -0800 Subject: [PATCH 18/19] rdr: fix SpanRef resolution and optimize metadata encoding - Fix decoder to use span_file_data source map for -Zstable-crate-hash crates - Add preload_all_source_files for deterministic BytePos ordering - Enable -Zstable-crate-hash for library builds and copy .spans to sysroot - Encode fn_arg_idents with SpanRef - Add transform_span_ref_for_export to convert local stable_ids to exported stable_ids when encoding predicate queries to metadata Refactor span conversion into SpanConversionState and add lazy_array_with and lazy_array_with_mut helpers that scope borrows per-item. This avoids intermediate Vec allocations and HashMap cloning when encoding arrays that need span transformation, while avoiding borrow conflicts with encode(&mut self). --- .../src/collect/item_bounds.rs | 1 - .../src/collect/predicates_of.rs | 5 +- compiler/rustc_metadata/src/creader.rs | 2 + compiler/rustc_metadata/src/rmeta/decoder.rs | 26 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 437 ++++++++++-------- compiler/rustc_middle/src/ty/context.rs | 1 - src/bootstrap/src/core/build_steps/compile.rs | 11 + .../rdr-hygiene-hash-collision/rmake.rs | 14 +- 8 files changed, 288 insertions(+), 209 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index a9ffbf369abba..d2f5e18bf7eb8 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -8,7 +8,6 @@ use rustc_middle::ty::{ use rustc_middle::{bug, span_bug}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{Span, SpanRef}; -use rustc_span::def_id::{DefId, LocalDefId}; fn convert_to_span_ref<'tcx>( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index 63f9bf26b9b78..d6d1f0a14c14a 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -43,8 +43,9 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic let inferred_outlives = tcx.inferred_outlives_of(def_id); if !inferred_outlives.is_empty() { debug!("predicates_of: inferred_outlives_of({:?}) = {:?}", def_id, inferred_outlives,); - let inferred_outlives_iter = - inferred_outlives.iter().map(|(clause, span)| ((*clause).upcast(tcx), tcx.resolve_span_ref(*span))); + let inferred_outlives_iter = inferred_outlives + .iter() + .map(|(clause, span)| ((*clause).upcast(tcx), tcx.resolve_span_ref(*span))); if result.predicates.is_empty() { result.predicates = tcx.arena.alloc_from_iter(inferred_outlives_iter); } else { diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index ca1cdd07fe013..f798b40fa5bf0 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -658,6 +658,8 @@ impl CStore { .map_err(|err| CrateError::MissingSpanFile(crate_name, err))?; } + crate_metadata.preload_all_source_files(tcx, self); + self.set_crate_data(cnum, crate_metadata); Ok(cnum) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index f49e2fc937500..ff62af8884a69 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -2126,8 +2126,11 @@ impl<'a> CrateMetadataRef<'a> { tcx: TyCtxt<'_>, target_stable_id: rustc_span::StableSourceFileId, ) -> Option { - // Iterate through all source files in this crate's metadata - let num_files = self.root.source_map.size(); + let num_files = if let Some(span_data) = self.cdata.span_file_data() { + span_data.source_map.size() + } else { + self.root.source_map.size() + }; debug!( ?target_stable_id, crate_name = ?self.cdata.root.header.name, @@ -2266,6 +2269,25 @@ impl CrateMetadata { Ok(()) } + /// Pre-imports all source files to ensure deterministic `BytePos` values for + /// diagnostic output ordering. + pub(crate) fn preload_all_source_files(&self, tcx: TyCtxt<'_>, cstore: &CStore) { + let cref = CrateMetadataRef { cdata: self, cstore }; + let num_files = if let Some(span_data) = self.span_file_data() { + span_data.source_map.size() + } else { + self.root.source_map.size() + }; + debug!( + crate_name = ?self.root.header.name, + num_files, + "preload_all_source_files: importing all source files" + ); + for file_index in 0..num_files { + let _ = cref.imported_source_file(tcx, file_index as u32); + } + } + /// Lazily loads the span file data if a span file path is configured. /// Returns None if no span file exists or if loading fails. fn span_file_data(&self) -> Option<&SpanFileData> { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8b3973cb1a149..8f18a37897de7 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -39,6 +39,62 @@ use crate::eii::EiiMapEncodedKeyValue; use crate::errors::{FailCreateFileEncoder, FailWriteFile}; use crate::rmeta::*; +/// State for converting `Span` to `SpanRef` during encoding. +/// Separated from `EncodeContext` to allow disjoint borrows. +struct SpanConversionState { + // Cache for source file lookups (file, index into source map) + source_file_cache: (Arc, usize), + // Tracks which source files are referenced (for .spans file) + required_source_files: Option>, + // Maps original stable_id -> exported stable_id for local files + stable_id_export_map: FxHashMap, +} + +impl SpanConversionState { + fn span_to_span_ref(&mut self, span: Span, tcx: TyCtxt<'_>, is_proc_macro: bool) -> SpanRef { + let span_data = span.data(); + let ctxt = if is_proc_macro { SyntaxContext::root() } else { span_data.ctxt }; + + if span_data.is_dummy() { + return SpanRef::Opaque { ctxt }; + } + + if !self.source_file_cache.0.contains(span_data.lo) { + let source_map = tcx.sess.source_map(); + let source_file_index = source_map.lookup_source_file_idx(span_data.lo); + self.source_file_cache = + (Arc::clone(&source_map.files()[source_file_index]), source_file_index); + } + let (ref source_file, source_file_index) = self.source_file_cache; + + if !source_file.contains(span_data.hi) { + return SpanRef::Opaque { ctxt }; + } + + if (!source_file.is_imported() || is_proc_macro) + && let Some(required_source_files) = self.required_source_files.as_mut() + { + required_source_files.insert(source_file_index); + } + + let lo = (span_data.lo - source_file.start_pos).0; + let hi = (span_data.hi - source_file.start_pos).0; + let source_crate = tcx.stable_crate_id(source_file.cnum); + + let file_stable_id = if source_file.cnum == LOCAL_CRATE { + let mut adapted_name = source_file.name.clone(); + if let FileName::Real(ref mut real_name) = adapted_name { + real_name.update_for_crate_metadata(); + } + StableSourceFileId::from_filename_for_export(&adapted_name, source_crate) + } else { + source_file.stable_id + }; + + SpanRef::FileRelative { source_crate, file: file_stable_id, lo, hi, ctxt } + } +} + pub(super) struct EncodeContext<'a, 'tcx> { opaque: opaque::FileEncoder, tcx: TyCtxt<'tcx>, @@ -52,17 +108,7 @@ pub(super) struct EncodeContext<'a, 'tcx> { interpret_allocs: FxIndexSet, - // This is used to speed up Span encoding. - // The `usize` is an index into the `MonotonicVec` - // that stores the `SourceFile` - source_file_cache: (Arc, usize), - // The indices (into the `SourceMap`'s `MonotonicVec`) - // of all of the `SourceFiles` that we need to serialize. - // When we serialize a `Span`, we insert the index of its - // `SourceFile` into the `FxIndexSet`. - // The order inside the `FxIndexSet` is used as on-disk - // order of `SourceFiles`, and encoded inside `Span`s. - required_source_files: Option>, + span_state: SpanConversionState, is_proc_macro: bool, hygiene_ctxt: &'a HygieneEncodeContext, // Used for both `Symbol`s and `ByteSymbol`s. @@ -214,63 +260,7 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { } fn encode_span_as_span_ref(&mut self, span: Span) { - let span_data = span.data(); - let ctxt = if self.is_proc_macro { SyntaxContext::root() } else { span_data.ctxt }; - - // For dummy spans, encode as Opaque - if span_data.is_dummy() { - let span_ref = SpanRef::Opaque { ctxt }; - span_ref.encode(self); - return; - } - - // Look up the source file for this span - if !self.source_file_cache.0.contains(span_data.lo) { - let source_map = self.tcx.sess.source_map(); - let source_file_index = source_map.lookup_source_file_idx(span_data.lo); - self.source_file_cache = - (Arc::clone(&source_map.files()[source_file_index]), source_file_index); - } - let (ref source_file, source_file_index) = self.source_file_cache; - - // Check if hi is in the same file - if !source_file.contains(span_data.hi) { - // Cross-file span - encode as Opaque - let span_ref = SpanRef::Opaque { ctxt }; - span_ref.encode(self); - return; - } - - if (!source_file.is_imported() || self.is_proc_macro) - && let Some(required_source_files) = self.required_source_files.as_mut() - { - required_source_files.insert(source_file_index); - } - - // Create FileRelative with stable source file ID and source crate ID - let lo = (span_data.lo - source_file.start_pos).0; - let hi = (span_data.hi - source_file.start_pos).0; - let source_crate = self.tcx.stable_crate_id(source_file.cnum); - let file_stable_id = if source_file.cnum == LOCAL_CRATE { - let mut adapted_name = source_file.name.clone(); - if let FileName::Real(ref mut real_name) = adapted_name { - real_name.update_for_crate_metadata(); - } - StableSourceFileId::from_filename_for_export(&adapted_name, source_crate) - } else { - source_file.stable_id - }; - debug!( - cnum = ?source_file.cnum, - ?source_crate, - file_name = ?source_file.name, - original_stable_id = ?source_file.stable_id, - export_stable_id = ?file_stable_id, - lo, - hi, - "encode_span_as_span_ref: FileRelative" - ); - let span_ref = SpanRef::FileRelative { source_crate, file: file_stable_id, lo, hi, ctxt }; + let span_ref = self.span_state.span_to_span_ref(span, self.tcx, self.is_proc_macro); span_ref.encode(self); } } @@ -326,13 +316,13 @@ impl<'a, 'tcx> Encodable> for SpanData { // The Span infrastructure should make sure that this invariant holds: debug_assert!(self.lo <= self.hi); - if !s.source_file_cache.0.contains(self.lo) { + if !s.span_state.source_file_cache.0.contains(self.lo) { let source_map = s.tcx.sess.source_map(); let source_file_index = source_map.lookup_source_file_idx(self.lo); - s.source_file_cache = + s.span_state.source_file_cache = (Arc::clone(&source_map.files()[source_file_index]), source_file_index); } - let (ref source_file, source_file_index) = s.source_file_cache; + let (ref source_file, source_file_index) = s.span_state.source_file_cache; debug_assert!(source_file.contains(self.lo)); if !source_file.contains(self.hi) { @@ -384,7 +374,7 @@ impl<'a, 'tcx> Encodable> for SpanData { } else { // Record the fact that we need to encode the data for this `SourceFile` let source_files = - s.required_source_files.as_mut().expect("Already encoded SourceMap!"); + s.span_state.required_source_files.as_mut().expect("Already encoded SourceMap!"); let (metadata_index, _) = source_files.insert_full(source_file_index); let metadata_index: u32 = metadata_index.try_into().expect("cannot export more than U32_MAX files"); @@ -414,9 +404,9 @@ impl<'a, 'tcx> Encodable> for SpanData { metadata_index.encode(s); if kind == SpanKind::Foreign { - // This needs to be two lines to avoid holding the `s.source_file_cache` + // This needs to be two lines to avoid holding the `s.span_state.source_file_cache` // while calling `cnum.encode(s)` - let cnum = s.source_file_cache.0.cnum; + let cnum = s.span_state.source_file_cache.0.cnum; cnum.encode(s); } } @@ -485,75 +475,31 @@ macro_rules! record_defaulted_array { }}; } -impl<'a, 'tcx> EncodeContext<'a, 'tcx> { - /// Converts a `Span` to a `SpanRef`, preserving file-relative position when possible. - /// - /// This method creates `SpanRef::FileRelative` for spans that fit within a single - /// source file, allowing stable span references that survive recompilation. - /// Falls back to `SpanRef::Opaque` for dummy spans or spans that cross file boundaries. - fn span_to_span_ref(&mut self, span: Span) -> SpanRef { - let span_data = span.data(); - let ctxt = if self.is_proc_macro { SyntaxContext::root() } else { span_data.ctxt }; - - // For dummy spans, return Opaque - if span_data.is_dummy() { - return SpanRef::Opaque { ctxt }; - } - - // Look up the source file for this span - if !self.source_file_cache.0.contains(span_data.lo) { - let source_map = self.tcx.sess.source_map(); - let source_file_index = source_map.lookup_source_file_idx(span_data.lo); - self.source_file_cache = - (Arc::clone(&source_map.files()[source_file_index]), source_file_index); - } - let (ref source_file, source_file_index) = self.source_file_cache; - - // Check if hi is in the same file - if !source_file.contains(span_data.hi) { - // Cross-file span - return Opaque - return SpanRef::Opaque { ctxt }; +macro_rules! record_defaulted_array_with { + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $transform:expr) => {{ + { + let value = $value; + let lazy = $self.lazy_array_with(value, $transform); + $self.$tables.$table.set($def_id.index, lazy); } + }}; +} - if (!source_file.is_imported() || self.is_proc_macro) - && let Some(required_source_files) = self.required_source_files.as_mut() +macro_rules! record_defaulted_array_with_mut { + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $transform:expr) => {{ { - required_source_files.insert(source_file_index); + let value = $value; + let lazy = $self.lazy_array_with_mut(value, $transform); + $self.$tables.$table.set($def_id.index, lazy); } + }}; +} - // Create FileRelative with stable source file ID and source crate ID - let lo = (span_data.lo - source_file.start_pos).0; - let hi = (span_data.hi - source_file.start_pos).0; - let source_crate = self.tcx.stable_crate_id(source_file.cnum); - - // For local source files, we need to compute the export version of the stable_id, - // because when source files are written to metadata their stable_id is recalculated - // to include the crate's stable ID. We also need to adapt the filename the same way - // as encode_source_map_with does (via update_for_crate_metadata) to ensure the hash - // matches. - // For imported source files, the stable_id is already in the correct form (it was - // read from that crate's metadata). - let file_stable_id = if source_file.cnum == LOCAL_CRATE { - let mut adapted_name = source_file.name.clone(); - if let FileName::Real(ref mut real_name) = adapted_name { - real_name.update_for_crate_metadata(); - } - StableSourceFileId::from_filename_for_export(&adapted_name, source_crate) - } else { - source_file.stable_id - }; - - debug!( - cnum = ?source_file.cnum, - ?source_crate, - file_name = ?source_file.name, - original_stable_id = ?source_file.stable_id, - export_stable_id = ?file_stable_id, - lo, - hi, - "span_to_span_ref: FileRelative" - ); - SpanRef::FileRelative { source_crate, file: file_stable_id, lo, hi, ctxt } +impl<'a, 'tcx> EncodeContext<'a, 'tcx> { + /// Converts a `Span` to a `SpanRef`, preserving file-relative position when possible. + /// Converts a `Span` to a `SpanRef`. Delegates to `SpanConversionState`. + fn span_to_span_ref(&mut self, span: Span) -> SpanRef { + self.span_state.span_to_span_ref(span, self.tcx, self.is_proc_macro) } fn emit_lazy_distance(&mut self, position: NonZero) { @@ -613,6 +559,70 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { LazyArray::from_position_and_num_elems(pos, len) } + /// Encodes a lazy array, applying `F` to each item before encoding. + /// + /// `F` receives a reference to `stable_id_export_map` as a parameter, scoping + /// the borrow per-item rather than across the entire iteration. This avoids + /// borrow conflicts with `encode(&mut self)`. + fn lazy_array_with(&mut self, values: I, transform: F) -> LazyArray + where + T: ParameterizedOverTcx, + I: IntoIterator, + F: Fn(U, &FxHashMap) -> B, + B: Borrow>, + T::Value<'tcx>: Encodable>, + { + let pos = NonZero::new(self.position()).unwrap(); + + assert_eq!(self.lazy_state, LazyState::NoNode); + self.lazy_state = LazyState::NodeStart(pos); + let len = values + .into_iter() + .map(|value| { + let transformed = transform(value, &self.span_state.stable_id_export_map); + transformed.borrow().encode(self) + }) + .count(); + self.lazy_state = LazyState::NoNode; + + assert!(pos.get() <= self.position()); + + LazyArray::from_position_and_num_elems(pos, len) + } + + /// Encodes a lazy array, applying `F` to each item before encoding. + /// + /// `F` receives mutable access to [`SpanConversionState`], allowing + /// transformations that update the source file cache. The borrow is scoped + /// per-item to avoid conflicts with `encode(&mut self)`. + fn lazy_array_with_mut(&mut self, values: I, transform: F) -> LazyArray + where + T: ParameterizedOverTcx, + I: IntoIterator, + F: Fn(U, &mut SpanConversionState, TyCtxt<'tcx>, bool) -> B, + B: Borrow>, + T::Value<'tcx>: Encodable>, + { + let pos = NonZero::new(self.position()).unwrap(); + + assert_eq!(self.lazy_state, LazyState::NoNode); + self.lazy_state = LazyState::NodeStart(pos); + let len = values + .into_iter() + .map(|value| { + let tcx = self.tcx; + let is_proc_macro = self.is_proc_macro; + let transformed = transform(value, &mut self.span_state, tcx, is_proc_macro); + transformed.borrow().encode(self) + }) + .count(); + self.lazy_state = LazyState::NoNode; + + assert!(pos.get() <= self.position()); + + LazyArray::from_position_and_num_elems(pos, len) + } + fn encode_symbol_or_byte_symbol( &mut self, index: u32, @@ -668,7 +678,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // By replacing the `Option` with `None`, we ensure that we can't // accidentally serialize any more `Span`s after the source map encoding // is done. - let required_source_files = self.required_source_files.take().unwrap(); + let required_source_files = self.span_state.required_source_files.take().unwrap(); self.encode_source_map_with(required_source_files) } @@ -1657,8 +1667,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.generics_of[def_id] <- g); record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); - // Query already returns SpanRef, no conversion needed - record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives.to_vec()); + record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives.iter().copied()); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { @@ -1680,8 +1689,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if let DefKind::Fn | DefKind::AssocFn = def_kind { let asyncness = tcx.asyncness(def_id); self.tables.asyncness.set(def_id.index, asyncness); - record_array!(self.tables.fn_arg_idents[def_id] <- - tcx.fn_arg_idents(def_id).iter().map(|opt| opt.map(|id| id.to_ident_ref()))); + let ident_refs: Vec<_> = tcx + .fn_arg_idents(def_id) + .iter() + .map(|opt| { + opt.map(|id| { + rustc_span::IdentRef::new(id.name, self.span_to_span_ref(id.span)) + }) + }) + .collect(); + record_array!(self.tables.fn_arg_idents[def_id] <- ident_refs); } if let Some(name) = tcx.intrinsic(def_id) { record!(self.tables.intrinsic[def_id] <- name); @@ -1692,34 +1709,34 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } if let DefKind::Trait = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - // Queries already return SpanRef, no conversion needed - let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates.to_vec()); - let implied_predicates = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates.to_vec()); + let super_preds = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_super_predicates_of[def_id] <- + super_preds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); + let implied_preds = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_implied_predicates_of[def_id] <- + implied_preds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); let module_children = self.tcx.module_children_local(local_id); record_array!(self.tables.module_children_non_reexports[def_id] <- module_children.iter().map(|child| child.res.def_id().index)); if self.tcx.is_const_trait(def_id) { - // explicit_implied_const_bounds still returns Span, needs conversion let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); - let const_bounds_converted: Vec<_> = { - let mut result = Vec::with_capacity(const_bounds.len()); - for &(c, s) in const_bounds.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); + record_defaulted_array_with_mut!(self.tables.explicit_implied_const_bounds[def_id] <- + const_bounds.iter(), + |&(c, s), span_state, tcx, is_proc_macro| (c, span_state.span_to_span_ref(s, tcx, is_proc_macro))); } } if let DefKind::TraitAlias = def_kind { record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - // Queries already return SpanRef, no conversion needed - let super_predicates = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- super_predicates.to_vec()); - let implied_predicates = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- implied_predicates.to_vec()); + let super_preds = self.tcx.explicit_super_predicates_of(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_super_predicates_of[def_id] <- + super_preds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); + let implied_preds = self.tcx.explicit_implied_predicates_of(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_implied_predicates_of[def_id] <- + implied_preds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); } if let DefKind::Trait | DefKind::Impl { .. } = def_kind { let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); @@ -1781,14 +1798,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_precise_capturing_args(def_id); if tcx.is_conditionally_const(def_id) { let const_bounds = tcx.explicit_implied_const_bounds(def_id).skip_binder(); - let const_bounds_converted: Vec<_> = { - let mut result = Vec::with_capacity(const_bounds.len()); - for &(c, s) in const_bounds.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); + record_defaulted_array_with_mut!(self.tables.explicit_implied_const_bounds[def_id] <- + const_bounds.iter(), + |&(c, s), span_state, tcx, is_proc_macro| (c, span_state.span_to_span_ref(s, tcx, is_proc_macro))); } } if let DefKind::AnonConst = def_kind { @@ -1928,16 +1940,18 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_explicit_item_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_bounds({:?})", def_id); - // Query already returns SpanRef, no conversion needed - let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds.to_vec()); + let item_bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_item_bounds[def_id] <- + item_bounds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); } fn encode_explicit_item_self_bounds(&mut self, def_id: DefId) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); - // Query already returns SpanRef, no conversion needed - let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds.to_vec()); + let item_bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); + record_defaulted_array_with!(self.tables.explicit_item_self_bounds[def_id] <- + item_bounds.iter(), + |&(clause, span_ref), map| (clause, transform_span_ref_for_export(span_ref, map))); } #[instrument(level = "debug", skip(self))] @@ -1958,14 +1972,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_explicit_item_self_bounds(def_id); if tcx.is_conditionally_const(def_id) { let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); - let const_bounds_converted: Vec<_> = { - let mut result = Vec::with_capacity(const_bounds.len()); - for &(c, s) in const_bounds.iter() { - result.push((c, self.span_to_span_ref(s))); - } - result - }; - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- const_bounds_converted); + record_defaulted_array_with_mut!(self.tables.explicit_implied_const_bounds[def_id] <- + const_bounds.iter(), + |&(c, s), span_state, tcx, is_proc_macro| (c, span_state.span_to_span_ref(s, tcx, is_proc_macro))); } } if let ty::AssocKind::Type { data: ty::AssocTypeData::Rpitit(rpitit_info) } = item.kind { @@ -2726,6 +2735,46 @@ pub fn encode_spans( }); } +/// Builds a mapping from original stable_id to exported stable_id for local source files. +fn build_stable_id_export_map( + tcx: TyCtxt<'_>, +) -> FxHashMap { + let source_map_files = tcx.sess.source_map().files(); + let local_crate_stable_id = tcx.stable_crate_id(LOCAL_CRATE); + let mut map = FxHashMap::with_capacity_and_hasher(source_map_files.len(), Default::default()); + + for source_file in source_map_files.iter() { + if source_file.cnum != LOCAL_CRATE { + continue; + } + let original_id = source_file.stable_id; + let mut adapted_name = source_file.name.clone(); + if let FileName::Real(ref mut real_name) = adapted_name { + real_name.update_for_crate_metadata(); + } + let exported_id = + StableSourceFileId::from_filename_for_export(&adapted_name, local_crate_stable_id); + map.insert(original_id, exported_id); + } + map +} + +/// Transforms a SpanRef's stable_id from local to exported form for cross-crate use. +fn transform_span_ref_for_export( + span_ref: SpanRef, + export_map: &FxHashMap, +) -> SpanRef { + let SpanRef::FileRelative { source_crate, file, lo, hi, ctxt } = span_ref else { + return span_ref; + }; + + let Some(&exported_stable_id) = export_map.get(&file) else { + return span_ref; + }; + + SpanRef::FileRelative { source_crate, file: exported_stable_id, lo, hi, ctxt } +} + /// Encodes metadata with standard header. /// Returns the required_source_files if stable_crate_hash is enabled (for span file encoding). fn with_encode_metadata_header( @@ -2742,9 +2791,9 @@ fn with_encode_metadata_header( let source_map_files = tcx.sess.source_map().files(); let source_file_cache = (Arc::clone(&source_map_files[0]), 0); - let required_source_files = Some(FxIndexSet::default()); drop(source_map_files); + let stable_id_export_map = build_stable_id_export_map(tcx); let hygiene_ctxt = HygieneEncodeContext::default(); let mut ecx = EncodeContext { @@ -2756,9 +2805,12 @@ fn with_encode_metadata_header( span_shorthands: Default::default(), type_shorthands: Default::default(), predicate_shorthands: Default::default(), - source_file_cache, interpret_allocs: Default::default(), - required_source_files, + span_state: SpanConversionState { + source_file_cache, + required_source_files: Some(FxIndexSet::default()), + stable_id_export_map, + }, is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), hygiene_ctxt: &hygiene_ctxt, symbol_index_table: Default::default(), @@ -2783,7 +2835,7 @@ fn with_encode_metadata_header( // Return required_source_files if stable_crate_hash is enabled (for span file encoding) if tcx.sess.opts.unstable_opts.stable_crate_hash { - ecx.required_source_files.take() + ecx.span_state.required_source_files.take() } else { None } @@ -2808,6 +2860,7 @@ fn with_encode_span_header( let source_file_cache = (Arc::clone(&source_map_files[0]), 0); drop(source_map_files); + let stable_id_export_map = build_stable_id_export_map(tcx); let hygiene_ctxt = HygieneEncodeContext::default(); let mut ecx = EncodeContext { @@ -2819,10 +2872,12 @@ fn with_encode_span_header( span_shorthands: Default::default(), type_shorthands: Default::default(), predicate_shorthands: Default::default(), - source_file_cache, interpret_allocs: Default::default(), - // Not used for span file encoding, but required by EncodeContext - required_source_files: None, + span_state: SpanConversionState { + source_file_cache, + required_source_files: None, + stable_id_export_map, + }, is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), hygiene_ctxt: &hygiene_ctxt, symbol_index_table: Default::default(), diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 5de846a80fce6..aac6a16aaa1e5 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2543,7 +2543,6 @@ impl<'tcx> TyCtxt<'tcx> { let rel_lo = (data.lo - source_file.start_pos).0; let rel_hi = (data.hi - source_file.start_pos).0; - // Get the source crate's stable ID let source_crate = self.stable_crate_id(source_file.cnum); SpanRef::FileRelative { diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 11f2a28bb935c..9b92b4dbfea6b 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -712,6 +712,8 @@ pub fn std_cargo( cargo.rustdocflag(&html_root); cargo.rustdocflag("-Zcrate-attr=warn(rust_2018_idioms)"); + + cargo.rustflag("-Zstable-crate-hash"); } /// Link all libstd rlibs/dylibs into a sysroot of `target_compiler`. @@ -2622,6 +2624,15 @@ pub fn add_to_sysroot( DependencyType::TargetSelfContained => self_contained_dst, }; builder.copy_link(&path, &dst.join(filename), FileType::Regular); + + // Copy .spans file if present (-Zstable-crate-hash). + if filename.ends_with(".rlib") { + let spans_filename = filename.replace(".rlib", ".spans"); + let spans_path = path.parent().unwrap().join(&spans_filename); + if spans_path.exists() { + builder.copy_link(&spans_path, &dst.join(&spans_filename), FileType::Regular); + } + } } // Check that none of the rustc_* crates have multiple versions. Otherwise using them from diff --git a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs index 58ff494884a2d..9fc4108fbda96 100644 --- a/tests/run-make/rdr-hygiene-hash-collision/rmake.rs +++ b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs @@ -84,18 +84,8 @@ make_fn!(baz); // Compile with RDR flags - this would ICE before the fix due to // hygiene contexts not being hashed when spans are ignored. // With SpanRef migration, -Zincremental-ignore-spans is no longer needed. - rustc() - .input("lib.rs") - .crate_type("lib") - .incremental("incr") - .arg("-Zstable-crate-hash") - .run(); + rustc().input("lib.rs").crate_type("lib").incremental("incr").arg("-Zstable-crate-hash").run(); // Second compilation to exercise incremental path - rustc() - .input("lib.rs") - .crate_type("lib") - .incremental("incr") - .arg("-Zstable-crate-hash") - .run(); + rustc().input("lib.rs").crate_type("lib").incremental("incr").arg("-Zstable-crate-hash").run(); } From 8c20bd8cf276e6da5c6845747e90e1473c701eeb Mon Sep 17 00:00:00 2001 From: David Barsky Date: Sun, 18 Jan 2026 15:13:30 -0800 Subject: [PATCH 19/19] rdr: add a test that exercises RDR with `-Zshare-generics` --- .../auxiliary/shared_dep.rs | 53 +++++++++++++++++++ tests/incremental/rdr_share_generics/main.rs | 35 ++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/incremental/rdr_share_generics/auxiliary/shared_dep.rs create mode 100644 tests/incremental/rdr_share_generics/main.rs diff --git a/tests/incremental/rdr_share_generics/auxiliary/shared_dep.rs b/tests/incremental/rdr_share_generics/auxiliary/shared_dep.rs new file mode 100644 index 0000000000000..df16ed8e835fb --- /dev/null +++ b/tests/incremental/rdr_share_generics/auxiliary/shared_dep.rs @@ -0,0 +1,53 @@ +#![crate_name = "shared_dep"] +#![crate_type = "rlib"] + +// Private generic helper - changes to this should not affect downstream +#[cfg(rpass1)] +fn private_helper(x: T) -> T { + x +} + +#[cfg(any(rpass2, rpass3))] +fn private_helper(x: T) -> T { + let result = x; + result +} + +// Private non-generic function - changes should not affect downstream +#[cfg(any(rpass1, rpass2))] +fn private_fn() -> u32 { + 42 +} + +#[cfg(rpass3)] +fn private_fn() -> u32 { + let x = 42; + x +} + +// Public generic function that uses the private helper +pub fn generic_fn(x: T) -> T { + let _ = private_fn(); + private_helper(x) +} + +// Generic struct with drop glue to test share-generics drop handling +pub struct GenericBox { + value: T, +} + +impl GenericBox { + pub fn new(value: T) -> Self { + GenericBox { value } + } + + pub fn get(&self) -> &T { + &self.value + } +} + +impl Drop for GenericBox { + fn drop(&mut self) { + // Drop glue is also shared with share-generics + } +} diff --git a/tests/incremental/rdr_share_generics/main.rs b/tests/incremental/rdr_share_generics/main.rs new file mode 100644 index 0000000000000..72efdef6382a6 --- /dev/null +++ b/tests/incremental/rdr_share_generics/main.rs @@ -0,0 +1,35 @@ +// Test that RDR works correctly with -Zshare-generics. +// +// With share-generics, monomorphized generic instances are shared across crates +// rather than being duplicated. This test verifies that private changes in a +// dependency don't trigger rebuilds even when share-generics is enabled. +// +// - rpass1: Initial compilation +// - rpass2: Private generic helper changes body, should reuse +// - rpass3: Private non-generic function changes, should reuse + +//@ revisions: rpass1 rpass2 rpass3 +//@ compile-flags: -Z query-dep-graph -Z stable-crate-hash -Z share-generics=yes -C opt-level=0 +//@ aux-build: shared_dep.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![rustc_partition_reused(module = "main", cfg = "rpass2")] +#![rustc_partition_reused(module = "main", cfg = "rpass3")] + +extern crate shared_dep; + +fn main() { + // Use a generic function - with share-generics, this reuses the + // monomorphization from shared_dep if it exists there + let x: u32 = shared_dep::generic_fn(42); + assert_eq!(x, 42); + + // Use another instantiation + let y: u64 = shared_dep::generic_fn(100); + assert_eq!(y, 100); + + // Use a generic struct with drop glue (drop glue is also shared) + let s = shared_dep::GenericBox::new(String::from("hello")); + assert_eq!(s.get(), "hello"); +}