diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 085403c8ef36b..f7ae6a209dce3 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}; 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,38 @@ 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 { + 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()); + } + 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..a7b16ba45128d 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, 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,44 @@ 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); + 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); + } +} + +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), + sort_span: d.decode_span_ref_as_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..09fadc911380e 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}; pub use snippet::Style; use tracing::debug; @@ -205,19 +206,53 @@ 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) { + e.encode_span_as_span_ref(self.span); + self.snippet.encode(e); + } +} + +impl Decodable for SubstitutionPart { + fn decode(d: &mut D) -> Self { + let span = d.decode_span_ref_as_span(); + let snippet: String = Decodable::decode(d); + SubstitutionPart { 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) { + 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 = d.decode_span_ref_as_span(); + let span = d.decode_span_ref_as_span(); + let snippet: String = Decodable::decode(d); + TrimmedSubstitutionPart { original_span, 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)] 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..d2f5e18bf7eb8 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -6,8 +6,17 @@ 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}; + +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 +412,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 +427,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 +435,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 +491,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..d6d1f0a14c14a 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; @@ -34,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), *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 { @@ -603,14 +613,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 +631,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 +650,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 +742,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 +886,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 +943,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 +969,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 +1189,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_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 3799485077ef4..8d50c12bf059c 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.stable_crate_hash && 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_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/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/creader.rs b/compiler/rustc_metadata/src/creader.rs index 4000f12459a90..f798b40fa5bf0 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(()) } @@ -220,7 +223,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()) @@ -622,6 +631,13 @@ impl CStore { None }; + let has_stable_crate_hash = crate_root.has_stable_crate_hash(); + let crate_name = crate_root.name(); + + if has_stable_crate_hash && source.spans.is_none() { + return Err(CrateError::MissingSpanFile(crate_name, "file not found".to_string())); + } + let crate_metadata = CrateMetadata::new( tcx, self, @@ -636,6 +652,14 @@ impl CStore { host_hash, ); + if has_stable_crate_hash { + crate_metadata + .ensure_span_file_loaded() + .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/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 1eaad26ff8e80..094dfccc8c094 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -1,22 +1,39 @@ +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_hir::def_id::LOCAL_CRATE; 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::{EncodedMetadata, encode_metadata}; +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 @@ -54,8 +71,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,7 +84,19 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> EncodedMetadata { tcx.dcx().emit_fatal(FailedCreateFile { filename: &metadata_stub_filename, err }); }); } - } + None + }; + + // 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.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); + Some(spans_tmp_filename) + } else { + None + }; let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata"); @@ -78,7 +107,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 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.stable_crate_hash + && 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() @@ -101,6 +147,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/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 004d73da8cbda..9cfe31fd819c9 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> { @@ -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 }))) } @@ -950,6 +961,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, @@ -1022,6 +1046,7 @@ pub(crate) enum CrateError { DlSym(String, String), LocatorCombined(Box), NotFound(Symbol), + MissingSpanFile(Symbol, String), } enum MetadataError<'a> { @@ -1231,6 +1256,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 c7423386a771d..ff62af8884a69 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,9 @@ 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, - SpanDecoder, Symbol, SyntaxContext, kw, + AttrId, BlobDecoder, BytePos, ByteSymbol, DUMMY_SP, ExpnId, IdentRef, Pos, + RemapPathScopeComponents, Span, SpanData, SpanDecoder, SpanRef, SpanResolver, Symbol, + SyntaxContext, kw, }; use tracing::debug; @@ -72,6 +73,61 @@ 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[..] + } +} + +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(()) } + } + + /// 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 /// local crate numbers (as generated during this session). Each external /// crate may refer to types in other external crates, and each has their @@ -81,10 +137,22 @@ 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, + /// 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. root: CrateRoot, @@ -100,6 +168,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>, @@ -154,6 +223,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 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 /// and [`CrateRoot`]. After that, [`MetadataDecodeContext`] can be used. /// Most notably, [`BlobDecodeContext]` doesn't implement [`SpanDecoder`] @@ -295,6 +373,44 @@ 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`]. +pub(super) struct SpanBlobDecodeContext<'a> { + opaque: MemDecoder<'a>, + lazy_state: LazyState, + _marker: PhantomData<&'a ()>, +} + +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 + } +} + +/// Trait for decoding from span file blobs. +/// Similar to [`Metadata`] but for [`SpanBlob`]. +pub(super) trait SpanMetadata<'a>: Copy { + type Context: BlobDecoder + LazyDecoder; + + fn decoder(self, pos: usize) -> Self::Context; +} + +impl<'a> SpanMetadata<'a> for &'a SpanBlob { + type Context = SpanBlobDecodeContext<'a>; + + fn decoder(self, pos: usize) -> Self::Context { + SpanBlobDecodeContext { + opaque: MemDecoder::new(self, pos).unwrap(), + lazy_state: LazyState::NoNode, + _marker: PhantomData, + } + } +} + impl LazyValue { #[inline] fn decode<'a, 'tcx, M: Metadata<'a>>(self, metadata: M) -> T::Value<'tcx> @@ -305,6 +421,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 { @@ -523,6 +650,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> { @@ -559,6 +694,84 @@ 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. +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); @@ -632,6 +845,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 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. debug_assert!( lo + source_file.original_start_pos <= source_file.original_end_pos, @@ -694,6 +911,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( @@ -959,6 +1180,10 @@ impl CrateRoot { ) -> impl ExactSizeIterator { self.target_modifiers.decode(metadata) } + + pub(crate) fn has_stable_crate_hash(&self) -> bool { + self.has_stable_crate_hash + } } impl<'a> CrateMetadataRef<'a> { @@ -999,13 +1224,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)); + let resolver = TyCtxtSpanResolver::new(tcx); + let span = resolver.resolve_span_ref(span_ref); Some(Ident::new(name, span)) } @@ -1027,12 +1254,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)) + .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 { @@ -1314,7 +1544,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( @@ -1460,12 +1690,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)) + .decode((self, tcx)); + let resolver = TyCtxtSpanResolver::new(tcx); + resolver.resolve_span_ref(span_ref) } fn get_foreign_modules(self, tcx: TyCtxt<'_>) -> impl Iterator { @@ -1633,7 +1866,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 stable-crate-hash`, 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, @@ -1736,110 +1980,188 @@ 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(|| { - let source_file_to_import = 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, - ); + // 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 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, - ); + 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) + .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))) + }; - // 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, - ); + // 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 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 { + return None; + }; - // 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, - ); + // 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, + ); - 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, - ); + // 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) + } + + /// 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 { + 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, + 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!( - "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 + file_index, + name = ?imported.translated_source_file.name, + ?file_stable_id, + "find_and_import_source_file_by_stable_id: checking file" ); - ImportedSourceFile { - original_start_pos, - original_end_pos, - translated_source_file: local_version, + if file_stable_id == target_stable_id { + debug!("find_and_import_source_file_by_stable_id: FOUND"); + return Some(imported); } - }) - .clone() + } + } + + 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 { @@ -1901,6 +2223,8 @@ impl CrateMetadata { let mut cdata = CrateMetadata { blob, + span_file_path: source.spans.clone(), + span_file_data: OnceLock::new(), root, trait_impls, incoherent_impls: Default::default(), @@ -1935,6 +2259,73 @@ 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(()) + } + + /// 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> { + 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. + 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 { self.cnum_map.iter().copied() } @@ -2069,3 +2460,148 @@ 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. + + // 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); + + // 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 7bd3f7db55f99..e3dabfdb3a3e1 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -19,9 +19,9 @@ 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::{Ident, Span, SpanRef, SpanResolver, Symbol, kw}; -use super::{Decodable, DecodeIterator}; +use super::{Decodable, DecodeIterator, TyCtxtSpanResolver}; use crate::creader::{CStore, LoadedMacro}; use crate::rmeta::AttrFlags; use crate::rmeta::table::IsDefault; @@ -31,6 +31,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 { @@ -91,6 +96,27 @@ impl ProcessQueryValue<'_, Option> for Option { } } +impl ProcessQueryValue<'_, Span> for SpanRef { + #[inline(always)] + 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 { 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| resolve_span_ref(tcx, span_ref)) + } +} + macro_rules! provide_one { ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table }) => { provide_one! { @@ -118,6 +144,77 @@ macro_rules! provide_one { } } }; + // 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); + let value = if lazy.is_default() { + &[] as &[_] + } else { + $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))) + } + } + }; + // 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 => { + $cdata + .root + .tables + .$name + .get(($cdata, $tcx), $def_id.index) + .map(|lazy| { + $tcx.arena.alloc_from_iter(lazy.decode(($cdata, $tcx))) as &[_] + }) + .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) + } + } + }; + // 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| Ident::new(ir.name, resolve_span_ref($tcx, ir.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 +320,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 +354,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_resolve } coerce_unsized_info => { Ok(cdata .root @@ -270,7 +367,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 } @@ -299,7 +396,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..8f18a37897de7 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}; @@ -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. @@ -212,6 +258,11 @@ 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_ref = self.span_state.span_to_span_ref(span, self.tcx, self.is_proc_macro); + span_ref.encode(self); + } } fn bytes_needed(n: usize) -> usize { @@ -265,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) { @@ -323,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"); @@ -353,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); } } @@ -424,7 +475,33 @@ macro_rules! record_defaulted_array { }}; } +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); + } + }}; +} + +macro_rules! record_defaulted_array_with_mut { + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $transform:expr) => {{ + { + let value = $value; + let lazy = $self.lazy_array_with_mut(value, $transform); + $self.$tables.$table.set($def_id.index, lazy); + } + }}; +} + 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) { let pos = position.get(); let distance = match self.lazy_state { @@ -482,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, @@ -534,13 +675,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(); + let required_source_files = self.span_state.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 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, + ) -> LazyTable>> { + let source_map = self.tcx.sess.source_map(); + let all_source_files = source_map.files(); let mut adapted = TableBuilder::default(); @@ -713,7 +863,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 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.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) + } else { + self.encode_source_map() + } + }); let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers()); let root = stat!("final", || { @@ -775,6 +934,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { expn_hashes, def_path_hash_map, specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), + has_stable_crate_hash: tcx.sess.opts.unstable_opts.stable_crate_hash, }) }); @@ -1466,7 +1626,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); + 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); @@ -1477,7 +1638,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); + 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)); @@ -1505,7 +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); - 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().copied()); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { @@ -1527,7 +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)); + 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); @@ -1538,24 +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)); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + 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) { - record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder()); + let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); + 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)); - record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); - record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + 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); @@ -1616,8 +1797,10 @@ 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()); + let const_bounds = tcx.explicit_implied_const_bounds(def_id).skip_binder(); + 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 { @@ -1757,14 +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); - let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds); + 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); - let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds); + 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))] @@ -1784,17 +1971,18 @@ 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()); + let const_bounds = self.tcx.explicit_implied_const_bounds(def_id).skip_binder(); + 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 { 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) - ); + // Query already returns SpanRef, no conversion needed + let wf_types = self.tcx.assumed_wf_types_for_rpitit(def_id); + record_array!(self.tables.assumed_wf_types_for_rpitit[def_id] <- wf_types.iter().copied()); self.encode_precise_capturing_args(def_id); } } @@ -1986,12 +2174,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 = self.lazy(span); - self.tables.proc_macro_quoted_spans.set_some(i, span); + 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())); + 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); @@ -2037,8 +2227,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); - record!(self.tables.def_span[def_id] <- span); + 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); @@ -2416,8 +2607,15 @@ impl Decodable for EncodedMetadata { } } +/// Encodes crate metadata to the given path. +/// 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(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(); @@ -2426,7 +2624,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(), @@ -2435,7 +2634,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"); @@ -2456,7 +2655,20 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { Ok(_) => {} Err(err) => tcx.dcx().emit_fatal(FailCreateFileEncoder { err }), }; - return; + if tcx.sess.opts.unstable_opts.stable_crate_hash + && 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. + return None; }; if tcx.sess.threads() != 1 { @@ -2474,7 +2686,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, @@ -2498,13 +2710,78 @@ 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() + }); } +/// 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( 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); @@ -2514,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 { @@ -2528,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(), @@ -2549,21 +2829,87 @@ 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 }); + } + + // Return required_source_files if stable_crate_hash is enabled (for span file encoding) + if tcx.sess.opts.unstable_opts.stable_crate_hash { + ecx.span_state.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 stable_id_export_map = build_stable_id_export_map(tcx); + 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(), + interpret_allocs: Default::default(), + 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(), + }; + + 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_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_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index af6df0cd6eb61..1861912120482 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -2,10 +2,10 @@ 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}; +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,11 +34,12 @@ 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; 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, IdentRef, Span, SpanRef, Symbol}; use rustc_target::spec::{PanicStrategy, TargetTuple}; use table::TableBuilder; use {rustc_ast as ast, rustc_hir as hir}; @@ -68,6 +69,53 @@ 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>>, +} + +#[allow(dead_code)] // FIXME: used by RDR span file infrastructure +#[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. /// @@ -295,6 +343,10 @@ pub(crate) struct CrateRoot { symbol_mangling_version: SymbolManglingVersion, specialization_enabled_in: bool, + + /// 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_stable_crate_hash: bool, } /// On-disk representation of `DefId`. @@ -388,12 +440,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 +468,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>, @@ -445,7 +497,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>, @@ -462,7 +514,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 +523,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..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, @@ -127,8 +129,10 @@ 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, rustc_span::Symbol, rustc_span::hygiene::SyntaxContextKey, // tidy-alphabetical-end 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_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..a0819b14822e2 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; @@ -97,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}; @@ -506,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 @@ -519,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 @@ -849,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 @@ -863,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 @@ -875,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 @@ -886,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 @@ -929,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) } } @@ -1179,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 } @@ -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/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..aac6a16aaa1e5 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,85 @@ 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; + + 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_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/cstore.rs b/compiler/rustc_session/src/cstore.rs index 2f969aefda182..d596cf861cba2 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 stable-crate-hash`. + pub spans: Option, } impl CrateSource { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 96bdcf8beffb9..1d317ecd90d4e 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"), + 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 1d5b36fc61b8b..f114d43584075 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,8 +1,9 @@ use std::any::Any; +use std::hash::Hash; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; use std::sync::atomic::AtomicBool; +use std::sync::{Arc, OnceLock}; use std::{env, io}; use rand::{RngCore, rng}; @@ -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::*; @@ -21,9 +23,10 @@ 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::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 +109,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 +285,24 @@ impl Session { self.psess.source_map() } + pub fn diagnostic_spans_hash(&self) -> Option { + if !self.opts.unstable_opts.stable_crate_hash { + 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 +1105,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/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 1c430099835b3..c27da0ba7f5b2 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; @@ -683,6 +683,117 @@ 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)] +pub enum SpanRef { + /// Unresolvable span (e.g. cross-file, indirect, or missing data). + Opaque { 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 }, +} + +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 { 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, .. }, + ) => 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 +1178,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 { @@ -1371,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 { @@ -1493,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<'_> { @@ -1568,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() @@ -2856,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; @@ -2925,6 +3092,51 @@ 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`), + /// 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 { .. } => { + 0u8.hash_stable(ctx, hasher); + } + 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); + } + 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); + } + } + } +} + /// 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/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, 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/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/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/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")] 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_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); +} 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_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_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/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..138ea7da8f0f5 --- /dev/null +++ b/tests/incremental/rdr_separate_spans/auxiliary/separate_spans_lib.rs @@ -0,0 +1,42 @@ +// 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 stable-crate-hash + +#![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..7eace5ca3e953 --- /dev/null +++ b/tests/incremental/rdr_separate_spans/main.rs @@ -0,0 +1,41 @@ +// This test verifies that the -Z stable-crate-hash flag works correctly +// with incremental compilation. +// +// 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 stable-crate-hash +// rpass2: Recompile without changes - should reuse everything +// rpass3: Recompile auxiliary with stable-crate-hash - 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_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"); +} 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/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(); +} 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..98dd249da8d93 --- /dev/null +++ b/tests/run-make/rdr-async-panic-location/rmake.rs @@ -0,0 +1,37 @@ +// Test that panic locations inside async functions are correct +// when using -Z stable-crate-hash. + +//@ 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("-Zstable-crate-hash") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("rdr_async_lib", "librdr_async_lib.rlib") + .edition("2024") + .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}"); + + 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..90a9c2d0d1168 --- /dev/null +++ b/tests/run-make/rdr-cdylib/rmake.rs @@ -0,0 +1,19 @@ +// Test that cdylib builds work correctly with -Z stable-crate-hash. + +//@ 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("-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}"); + }); +} diff --git a/tests/run-make/rdr-coverage/lib.rs b/tests/run-make/rdr-coverage/lib.rs new file mode 100644 index 0000000000000..5445f48aa72fc --- /dev/null +++ b/tests/run-make/rdr-coverage/lib.rs @@ -0,0 +1,47 @@ +// Library crate for testing coverage instrumentation with -Z stable-crate-hash. +// 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..bb8aec1a96400 --- /dev/null +++ b/tests/run-make/rdr-coverage/rmake.rs @@ -0,0 +1,67 @@ +// Test that coverage instrumentation works correctly with -Z stable-crate-hash. +// +// This verifies that: +// 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 +// +// 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 stable-crate-hash + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Cinstrument-coverage") + .arg("-Zstable-crate-hash") + .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("-Zstable-crate-hash") + .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..6d68b0ac5d052 --- /dev/null +++ b/tests/run-make/rdr-debuginfo/lib.rs @@ -0,0 +1,43 @@ +// Library crate for testing debuginfo with -Z stable-crate-hash. +// 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..2a6b3ea3737f3 --- /dev/null +++ b/tests/run-make/rdr-debuginfo/rmake.rs @@ -0,0 +1,52 @@ +// 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. +// +// 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 stable-crate-hash + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Cdebuginfo=2") + .arg("-Zstable-crate-hash") + .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("-Zstable-crate-hash") + .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-diagnostic-gating/rmake.rs b/tests/run-make/rdr-diagnostic-gating/rmake.rs new file mode 100644 index 0000000000000..891079354ebbb --- /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 stable-crate-hash. +//@ 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("-Zstable-crate-hash") + .arg("-Zincremental-ignore-spans") + .run(); + output1.assert_stderr_contains("unused variable"); + + let output2 = rustc() + .input("main.rs") + .incremental("incr") + .arg("-Zstable-crate-hash") + .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("-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 new file mode 100644 index 0000000000000..9fc4108fbda96 --- /dev/null +++ b/tests/run-make/rdr-hygiene-hash-collision/rmake.rs @@ -0,0 +1,91 @@ +// Test that identifiers with the same name but different hygiene contexts +// 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 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. +// +// 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 + +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. + // With SpanRef migration, -Zincremental-ignore-spans is no longer needed. + 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(); +} 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..ca63971a3242a --- /dev/null +++ b/tests/run-make/rdr-lto/rmake.rs @@ -0,0 +1,36 @@ +// Test that LTO builds work correctly with -Z stable-crate-hash, +// 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("-Zstable-crate-hash") + .arg("-Clto=thin") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("rdr_lto_lib", "librdr_lto_lib.rlib") + .arg("-Zstable-crate-hash") + .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-missing-spans-fallback/rmake.rs b/tests/run-make/rdr-missing-spans-fallback/rmake.rs new file mode 100644 index 0000000000000..989ade851688e --- /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 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. +// +//@ 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 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 stable-crate-hash and emit both rlib and metadata + rustc() + .input("dep.rs") + .crate_type("rlib") + .emit("metadata,link") + .arg("-Zstable-crate-hash") + .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"); + }); +} 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..12cf64bc3dc76 --- /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 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 stable-crate-hash. +#[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..fc6f0fec6a685 --- /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 stable-crate-hash. + +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..08e868d54bf72 --- /dev/null +++ b/tests/run-make/rdr-panic-location/rmake.rs @@ -0,0 +1,63 @@ +// 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. +// +// 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 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("-Zstable-crate-hash") + .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!"); + }); +} 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..5b476126e1662 --- /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 stable-crate-hash. + +//@ 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("-Zstable-crate-hash") + .run(); + + rustc() + .input("main.rs") + .crate_type("bin") + .extern_("proc_macro_lib", "libproc_macro_lib.dylib") + .arg("-Zstable-crate-hash") + .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}" + ); + }); +} 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..73cfcf26ff2b4 --- /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 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("-Zstable-crate-hash").run(); + + rustc() + .input("main.rs") + .arg("-Zstable-crate-hash") + .arg("--extern") + .arg(format!("dep={}", rust_lib_name("dep"))) + .run(); + + run("main"); +} 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..c7dd68434c8ad --- /dev/null +++ b/tests/run-make/rdr-separate-spans/lib.rs @@ -0,0 +1,41 @@ +// Test library for RDR stable-crate-hash 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..93f3bcb600fb7 --- /dev/null +++ b/tests/run-make/rdr-separate-spans/rmake.rs @@ -0,0 +1,93 @@ +// Test that -Z stable-crate-hash flag produces reproducible metadata. +// +// This test verifies: +// 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 +// +// 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 stable-crate-hash succeeds + run_in_tmpdir(|| { + 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 stable-crate-hash" + ); + }); + + // Test 2: Reproducibility - same source compiled twice should produce identical rlibs + run_in_tmpdir(|| { + // First compilation + rustc() + .input("lib.rs") + .crate_type("rlib") + .arg("-Zstable-crate-hash") + .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("-Zstable-crate-hash") + .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 stable-crate-hash 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("-Zstable-crate-hash") + .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("-Zstable-crate-hash") + .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" + ); + }); +}