From 15c6e6e0925aea95e21adad02a992f070dca0002 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 1 Apr 2026 22:01:00 +1100 Subject: [PATCH] Add a `handle_cycle_error` query modifier. This modifier indicates that a query has a custom handler for cycles. That custom handler must be found at `rustc_query_impl::handle_cycle_error::$name`. This eliminates the need for `specialize_query_vtables`, which is the current hack to install custom handlers. It's more lines of code in total, but indicating special treatment of a query via a modifier in `queries.rs` is more consistent with how other aspects of queries are handled. --- compiler/rustc_macros/src/query.rs | 9 +++ compiler/rustc_middle/src/queries.rs | 5 ++ compiler/rustc_middle/src/query/modifiers.rs | 8 +++ compiler/rustc_middle/src/query/plumbing.rs | 1 + .../rustc_query_impl/src/dep_kind_vtables.rs | 1 + .../src/handle_cycle_error.rs | 71 +++++++++++-------- compiler/rustc_query_impl/src/lib.rs | 4 +- compiler/rustc_query_impl/src/query_impl.rs | 12 ++-- 8 files changed, 74 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index 6960920367ff..3880894572c9 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -142,6 +142,7 @@ struct QueryModifiers { desc: Desc, eval_always: Option, feedable: Option, + handle_cycle_error: Option, no_force: Option, no_hash: Option, separate_provide_extern: Option, @@ -156,6 +157,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result { let mut desc = None; let mut eval_always = None; let mut feedable = None; + let mut handle_cycle_error = None; let mut no_force = None; let mut no_hash = None; let mut separate_provide_extern = None; @@ -190,6 +192,8 @@ macro_rules! try_insert { try_insert!(eval_always = modifier); } else if modifier == "feedable" { try_insert!(feedable = modifier); + } else if modifier == "handle_cycle_error" { + try_insert!(handle_cycle_error = modifier); } else if modifier == "no_force" { try_insert!(no_force = modifier); } else if modifier == "no_hash" { @@ -211,6 +215,7 @@ macro_rules! try_insert { desc, eval_always, feedable, + handle_cycle_error, no_force, no_hash, separate_provide_extern, @@ -246,6 +251,7 @@ fn make_modifiers_stream(query: &Query) -> proc_macro2::TokenStream { desc, eval_always, feedable, + handle_cycle_error, no_force, no_hash, separate_provide_extern, @@ -270,6 +276,7 @@ fn make_modifiers_stream(query: &Query) -> proc_macro2::TokenStream { }; let eval_always = eval_always.is_some(); let feedable = feedable.is_some(); + let handle_cycle_error = handle_cycle_error.is_some(); let no_force = no_force.is_some(); let no_hash = no_hash.is_some(); let returns_error_guaranteed = returns_error_guaranteed(&query.return_ty); @@ -290,6 +297,7 @@ fn make_modifiers_stream(query: &Query) -> proc_macro2::TokenStream { desc: #desc, eval_always: #eval_always, feedable: #feedable, + handle_cycle_error: #handle_cycle_error, no_force: #no_force, no_hash: #no_hash, returns_error_guaranteed: #returns_error_guaranteed, @@ -358,6 +366,7 @@ macro_rules! doc_link { // `desc` is handled above eval_always, feedable, + handle_cycle_error, no_force, no_hash, separate_provide_extern, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 35e33fd2d545..63be8a63e9f2 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -583,6 +583,7 @@ // messages about cycles that then abort.) query check_representability(key: LocalDefId) { desc { "checking if `{}` is representable", tcx.def_path_str(key) } + handle_cycle_error // We don't want recursive representability calls to be forced with // incremental compilation because, if a cycle occurs, we need the // entire cycle to be in memory for diagnostics. @@ -593,6 +594,7 @@ /// details, particularly on the modifiers. query check_representability_adt_ty(key: Ty<'tcx>) { desc { "checking if `{}` is representable", key } + handle_cycle_error no_force } @@ -1032,6 +1034,7 @@ query variances_of(def_id: DefId) -> &'tcx [ty::Variance] { desc { "computing the variances of `{}`", tcx.def_path_str(def_id) } cache_on_disk + handle_cycle_error separate_provide_extern } @@ -1164,6 +1167,7 @@ query fn_sig(key: DefId) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { desc { "computing function signature of `{}`", tcx.def_path_str(key) } cache_on_disk + handle_cycle_error separate_provide_extern } @@ -1756,6 +1760,7 @@ ) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { depth_limit desc { "computing layout of `{}`", key.value } + handle_cycle_error } /// Compute a `FnAbi` suitable for indirect calls, i.e. to `fn` pointers. diff --git a/compiler/rustc_middle/src/query/modifiers.rs b/compiler/rustc_middle/src/query/modifiers.rs index 2d22a548b735..4fd91caa94cd 100644 --- a/compiler/rustc_middle/src/query/modifiers.rs +++ b/compiler/rustc_middle/src/query/modifiers.rs @@ -58,6 +58,14 @@ /// Generate a `feed` method to set the query's value from another query. pub(crate) struct feedable; +/// # `handle_cycle_error` query modifier +/// +/// The default behaviour for a query cycle is to emit a cycle error and halt +/// compilation. Queries with this modifier will instead use a custom handler, +/// which must be provided at `rustc_query_impl::handle_cycle_error::$name`, +/// where `$name` is the query name. +pub(crate) struct handle_cycle_error; + /// # `no_force` query modifier /// /// Dep nodes of queries with this modifier will never be "forced" when trying diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 11f66f10cd54..6b207be245ba 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -304,6 +304,7 @@ fn $name:ident($($K:tt)*) -> $V:ty desc: $desc:expr, eval_always: $eval_always:literal, feedable: $feedable:literal, + handle_cycle_error: $handle_cycle_error:literal, no_force: $no_force:literal, no_hash: $no_hash:literal, returns_error_guaranteed: $returns_error_guaranteed:literal, diff --git a/compiler/rustc_query_impl/src/dep_kind_vtables.rs b/compiler/rustc_query_impl/src/dep_kind_vtables.rs index e0d357586367..d12db3784f71 100644 --- a/compiler/rustc_query_impl/src/dep_kind_vtables.rs +++ b/compiler/rustc_query_impl/src/dep_kind_vtables.rs @@ -138,6 +138,7 @@ fn $name:ident($K:ty) -> $V:ty desc: $desc:expr, eval_always: $eval_always:literal, feedable: $feedable:literal, + handle_cycle_error: $handle_cycle_error:literal, no_force: $no_force:literal, no_hash: $no_hash:literal, returns_error_guaranteed: $returns_error_guaranteed:literal, diff --git a/compiler/rustc_query_impl/src/handle_cycle_error.rs b/compiler/rustc_query_impl/src/handle_cycle_error.rs index 22f8ac9837f6..07565254969c 100644 --- a/compiler/rustc_query_impl/src/handle_cycle_error.rs +++ b/compiler/rustc_query_impl/src/handle_cycle_error.rs @@ -9,48 +9,29 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_middle::bug; -use rustc_middle::queries::{QueryVTables, TaggedQueryKey}; +use rustc_middle::queries::TaggedQueryKey; use rustc_middle::query::Cycle; -use rustc_middle::query::erase::erase_val; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{ErrorGuaranteed, Span}; use crate::job::create_cycle_error; -pub(crate) fn specialize_query_vtables<'tcx>(vtables: &mut QueryVTables<'tcx>) { - vtables.fn_sig.handle_cycle_error_fn = |tcx, key, _, err| { - let guar = err.delay_as_bug(); - erase_val(fn_sig(tcx, key, guar)) - }; - - vtables.check_representability.handle_cycle_error_fn = - |tcx, _, cycle, _err| check_representability(tcx, cycle); - - vtables.check_representability_adt_ty.handle_cycle_error_fn = - |tcx, _, cycle, _err| check_representability(tcx, cycle); - - vtables.variances_of.handle_cycle_error_fn = |tcx, key, _, err| { - let _guar = err.delay_as_bug(); - erase_val(variances_of(tcx, key)) - }; - - vtables.layout_of.handle_cycle_error_fn = |tcx, _, cycle, err| { - let _guar = err.delay_as_bug(); - erase_val(Err(layout_of(tcx, cycle))) - } -} - +// Default cycle handler used for all queries that don't use the `handle_cycle_error` query +// modifier. pub(crate) fn default(err: Diag<'_>) -> ! { let guar = err.emit(); guar.raise_fatal() } -fn fn_sig<'tcx>( +pub(crate) fn fn_sig<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, - guar: ErrorGuaranteed, + _: Cycle<'tcx>, + err: Diag<'_>, ) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { + let guar = err.delay_as_bug(); + let err = Ty::new_error(tcx, guar); let arity = if let Some(node) = tcx.hir_get_if_local(def_id) @@ -71,7 +52,25 @@ fn fn_sig<'tcx>( ))) } -fn check_representability<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> ! { +pub(crate) fn check_representability<'tcx>( + tcx: TyCtxt<'tcx>, + _key: LocalDefId, + cycle: Cycle<'tcx>, + _err: Diag<'_>, +) { + check_representability_inner(tcx, cycle); +} + +pub(crate) fn check_representability_adt_ty<'tcx>( + tcx: TyCtxt<'tcx>, + _key: Ty<'tcx>, + cycle: Cycle<'tcx>, + _err: Diag<'_>, +) { + check_representability_inner(tcx, cycle); +} + +fn check_representability_inner<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> ! { let mut item_and_field_ids = Vec::new(); let mut representable_ids = FxHashSet::default(); for frame in &cycle.frames { @@ -102,7 +101,13 @@ fn check_representability<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> ! { guar.raise_fatal() } -fn variances_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx [ty::Variance] { +pub(crate) fn variances_of<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: DefId, + _cycle: Cycle<'tcx>, + err: Diag<'_>, +) -> &'tcx [ty::Variance] { + let _guar = err.delay_as_bug(); let n = tcx.generics_of(def_id).own_params.len(); tcx.arena.alloc_from_iter(iter::repeat_n(ty::Bivariant, n)) } @@ -126,7 +131,13 @@ fn search_for_cycle_permutation( otherwise() } -fn layout_of<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> &'tcx ty::layout::LayoutError<'tcx> { +pub(crate) fn layout_of<'tcx>( + tcx: TyCtxt<'tcx>, + _key: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, + cycle: Cycle<'tcx>, + err: Diag<'_>, +) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { + let _guar = err.delay_as_bug(); let diag = search_for_cycle_permutation( &cycle.frames, |frames| { diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index de03b48394b1..27bfe1451f64 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -48,11 +48,9 @@ pub fn query_system<'tcx>( on_disk_cache: Option, incremental: bool, ) -> QuerySystem<'tcx> { - let mut query_vtables = query_impl::make_query_vtables(incremental); - handle_cycle_error::specialize_query_vtables(&mut query_vtables); QuerySystem { arenas: Default::default(), - query_vtables, + query_vtables: query_impl::make_query_vtables(incremental), side_effects: Default::default(), on_disk_cache, local_providers, diff --git a/compiler/rustc_query_impl/src/query_impl.rs b/compiler/rustc_query_impl/src/query_impl.rs index c1e7f3ef3cdb..101bf2c4e80f 100644 --- a/compiler/rustc_query_impl/src/query_impl.rs +++ b/compiler/rustc_query_impl/src/query_impl.rs @@ -22,6 +22,7 @@ fn $name:ident($K:ty) -> $V:ty desc: $desc:expr, eval_always: $eval_always:literal, feedable: $feedable:literal, + handle_cycle_error: $handle_cycle_error:literal, no_force: $no_force:literal, no_hash: $no_hash:literal, returns_error_guaranteed: $returns_error_guaranteed:literal, @@ -144,7 +145,6 @@ pub(crate) fn make_query_vtable<'tcx>(incremental: bool) -> QueryVTable<'tcx, rustc_middle::queries::$name::Cache<'tcx>> { use rustc_middle::queries::$name::Value; - QueryVTable { name: stringify!($name), eval_always: $eval_always, @@ -177,9 +177,13 @@ pub(crate) fn make_query_vtable<'tcx>(incremental: bool) #[cfg(not($cache_on_disk))] try_load_from_disk_fn: |_tcx, _key, _prev_index, _index| None, - // The default just emits `err` and then aborts. - // `handle_cycle_error::specialize_query_vtables` overwrites this default - // for certain queries. + #[cfg($handle_cycle_error)] + handle_cycle_error_fn: |tcx, key, cycle, err| { + use rustc_middle::query::erase::erase_val; + + erase_val($crate::handle_cycle_error::$name(tcx, key, cycle, err)) + }, + #[cfg(not($handle_cycle_error))] handle_cycle_error_fn: |_tcx, _key, _cycle, err| { $crate::handle_cycle_error::default(err) },