From b707e7ae52065f659f08fe6fd5858623b83da5e4 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Fri, 6 Mar 2026 15:58:10 +0300 Subject: [PATCH 1/2] Remove `FromCycleError` trait before code formatting Currently `QueryVTable`'s `value_from_cycle_error` function pointer uses the `FromCycleError` trait to call query cycle handling code and to construct an erroneous query output value. This trick however relies too heavily on trait impl specialization and makes those impls inconsistent (which is bad AFAIK). Instead this commit directly modifies `value_from_cycle_error` function pointer upon vtable initialization and gets rid of `FromCycleError`. --- .../rustc_query_impl/src/from_cycle_error.rs | 117 +++++++----------- compiler/rustc_query_impl/src/lib.rs | 5 +- compiler/rustc_query_impl/src/plumbing.rs | 6 +- 3 files changed, 51 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_query_impl/src/from_cycle_error.rs b/compiler/rustc_query_impl/src/from_cycle_error.rs index eb6942ba491ff..a8df8b23441cc 100644 --- a/compiler/rustc_query_impl/src/from_cycle_error.rs +++ b/compiler/rustc_query_impl/src/from_cycle_error.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use std::fmt::Write; +use std::iter; use std::ops::ControlFlow; use rustc_data_structures::fx::FxHashSet; @@ -9,55 +10,56 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_middle::dep_graph::DepKind; use rustc_middle::query::CycleError; +use rustc_middle::query::erase::erase_val; use rustc_middle::query::plumbing::CyclePlaceholder; -use rustc_middle::ty::{self, Representability, Ty, TyCtxt}; +use rustc_middle::queries::QueryVTables; +use rustc_middle::ty::layout::{LayoutError, TyAndLayout}; +use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::LocalDefId; use rustc_span::{ErrorGuaranteed, Span}; use crate::job::report_cycle; -pub(crate) trait FromCycleError<'tcx>: Sized { - /// Try to produce a `Self` value that represents an error form (e.g. `TyKind::Error`). - /// - /// Note: the default impl calls `raise_fatal`, ending compilation immediately! Only a few - /// types override this with a non-fatal impl. - fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self; +pub(crate) fn specialize_query_vtables<'tcx>(vtables: &mut QueryVTables<'tcx>) { + vtables.type_of.value_from_cycle_error = + |tcx, _, guar| erase_val(ty::EarlyBinder::bind(Ty::new_error(tcx, guar))); + + vtables.type_of_opaque_hir_typeck.value_from_cycle_error = + |tcx, _, guar| erase_val(ty::EarlyBinder::bind(Ty::new_error(tcx, guar))); + + vtables.erase_and_anonymize_regions_ty.value_from_cycle_error = + |tcx, _, guar| erase_val(Ty::new_error(tcx, guar)); + + vtables.type_of_opaque.value_from_cycle_error = + |_, _, guar| erase_val(Err(CyclePlaceholder(guar))); + + vtables.fn_sig.value_from_cycle_error = |tcx, cycle, guar| erase_val(fn_sig(tcx, cycle, guar)); + + vtables.check_representability.value_from_cycle_error = + |tcx, cycle, guar| check_representability(tcx, cycle, guar); + + vtables.check_representability_adt_ty.value_from_cycle_error = + |tcx, cycle, guar| check_representability(tcx, cycle, guar); + + vtables.variances_of.value_from_cycle_error = + |tcx, cycle, guar| erase_val(variances_of(tcx, cycle, guar)); + + vtables.layout_of.value_from_cycle_error = + |tcx, cycle, guar| erase_val(layout_of(tcx, cycle, guar)); } -impl<'tcx, T> FromCycleError<'tcx> for T { - default fn from_cycle_error( - tcx: TyCtxt<'tcx>, - cycle_error: CycleError, - _guar: ErrorGuaranteed, - ) -> T { + pub(crate) fn default<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError, query_name: &str) -> ! { let Some(guar) = tcx.sess.dcx().has_errors() else { bug!( - "<{} as FromCycleError>::from_cycle_error called without errors: {:#?}", - std::any::type_name::(), + "`from_cycle_error_default` on query `{query_name}` called without errors: {:#?}", cycle_error.cycle, ); }; - guar.raise_fatal(); - } -} - -impl<'tcx> FromCycleError<'tcx> for Ty<'_> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { - // SAFETY: This is never called when `Self` is not `Ty<'tcx>`. - // FIXME: Represent the above fact in the trait system somehow. - unsafe { std::mem::transmute::, Ty<'_>>(Ty::new_error(tcx, guar)) } - } -} - -impl<'tcx> FromCycleError<'tcx> for Result>, CyclePlaceholder> { - fn from_cycle_error(_tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { - Err(CyclePlaceholder(guar)) + guar.raise_fatal() } -} -impl<'tcx> FromCycleError<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { + fn fn_sig<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { let err = Ty::new_error(tcx, guar); let arity = if let Some(info) = cycle_error.cycle.get(0) @@ -72,26 +74,20 @@ impl<'tcx> FromCycleError<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { unreachable!() }; - let fn_sig = ty::Binder::dummy(tcx.mk_fn_sig( + ty::EarlyBinder::bind(ty::Binder::dummy(tcx.mk_fn_sig( std::iter::repeat_n(err, arity), err, false, rustc_hir::Safety::Safe, rustc_abi::ExternAbi::Rust, - )); - - // SAFETY: This is never called when `Self` is not `ty::Binder<'tcx, ty::FnSig<'tcx>>`. - // FIXME: Represent the above fact in the trait system somehow. - unsafe { std::mem::transmute::, ty::Binder<'_, ty::FnSig<'_>>>(fn_sig) } + ))) } -} -impl<'tcx> FromCycleError<'tcx> for Representability { - fn from_cycle_error( + fn check_representability<'tcx>( tcx: TyCtxt<'tcx>, cycle_error: CycleError, _guar: ErrorGuaranteed, - ) -> Self { + ) -> ! { let mut item_and_field_ids = Vec::new(); let mut representable_ids = FxHashSet::default(); for info in &cycle_error.cycle { @@ -120,28 +116,14 @@ impl<'tcx> FromCycleError<'tcx> for Representability { // We used to continue here, but the cycle error printed next is actually less useful than // the error produced by `recursive_type_error`. let guar = recursive_type_error(tcx, item_and_field_ids, &representable_ids); - guar.raise_fatal(); + guar.raise_fatal() } -} -impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, Ty<'_>> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { - ty::EarlyBinder::bind(Ty::from_cycle_error(tcx, cycle_error, guar)) - } -} - -impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { - ty::EarlyBinder::bind(ty::Binder::from_cycle_error(tcx, cycle_error, guar)) - } -} - -impl<'tcx> FromCycleError<'tcx> for &[ty::Variance] { - fn from_cycle_error( + fn variances_of<'tcx>( tcx: TyCtxt<'tcx>, cycle_error: CycleError, _guar: ErrorGuaranteed, - ) -> Self { + ) -> &'tcx [ty::Variance] { search_for_cycle_permutation( &cycle_error.cycle, |cycle| { @@ -150,7 +132,7 @@ impl<'tcx> FromCycleError<'tcx> for &[ty::Variance] { && let Some(def_id) = info.frame.def_id { let n = tcx.generics_of(def_id).own_params.len(); - ControlFlow::Break(vec![ty::Bivariant; n].leak()) + ControlFlow::Break(tcx.arena.alloc_from_iter(iter::repeat_n(ty::Bivariant, n))) } else { ControlFlow::Continue(()) } @@ -163,7 +145,6 @@ impl<'tcx> FromCycleError<'tcx> for &[ty::Variance] { }, ) } -} // Take a cycle of `Q` and try `try_cycle` on every permutation, falling back to `otherwise`. fn search_for_cycle_permutation( @@ -184,12 +165,11 @@ fn search_for_cycle_permutation( otherwise() } -impl<'tcx, T> FromCycleError<'tcx> for Result> { - fn from_cycle_error( + fn layout_of<'tcx>( tcx: TyCtxt<'tcx>, cycle_error: CycleError, _guar: ErrorGuaranteed, - ) -> Self { + ) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { let diag = search_for_cycle_permutation( &cycle_error.cycle, |cycle| { @@ -267,13 +247,8 @@ impl<'tcx, T> FromCycleError<'tcx> for Result ); let guar = diag.emit(); - - // tcx.arena.alloc cannot be used because we are not allowed to use &'tcx LayoutError under - // min_specialization. Since this is an error path anyways, leaking doesn't matter (and really, - // tcx.arena.alloc is pretty much equal to leaking). - Err(Box::leak(Box::new(ty::layout::LayoutError::Cycle(guar)))) + Err(tcx.arena.alloc(LayoutError::Cycle(guar))) } -} // item_and_field_ids should form a cycle where each field contains the // type in the next element in the list diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index d92d80dbfb860..6482384f99ea3 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -18,7 +18,6 @@ use rustc_middle::ty::TyCtxt; use rustc_span::Span; pub use crate::dep_kind_vtables::make_dep_kind_vtables; -use crate::from_cycle_error::FromCycleError; pub use crate::job::{QueryJobMap, break_query_cycles, print_query_stack}; use crate::profiling_support::QueryKeyStringCache; @@ -52,9 +51,11 @@ pub fn query_system<'tcx>( on_disk_cache: Option, incremental: bool, ) -> QuerySystem<'tcx> { + let mut query_vtables = make_query_vtables(incremental); + from_cycle_error::specialize_query_vtables(&mut query_vtables); QuerySystem { arenas: Default::default(), - query_vtables: make_query_vtables(incremental), + query_vtables, on_disk_cache, local_providers, extern_providers, diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index f4649b2403a6b..558ecdda5c4b0 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -467,10 +467,8 @@ macro_rules! define_queries { #[cfg(not($cache_on_disk))] is_loadable_from_disk_fn: |_tcx, _key, _index| false, - value_from_cycle_error: |tcx, cycle, guar| { - let result: queries::$name::Value<'tcx> = - FromCycleError::from_cycle_error(tcx, cycle, guar); - erase::erase_val(result) + value_from_cycle_error: |tcx, cycle, _| { + $crate::from_cycle_error::default(tcx, cycle, stringify!($name)) }, #[cfg($no_hash)] From 94187453541be9f80889f0fe9cfa5318c242229d Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Fri, 6 Mar 2026 16:00:20 +0300 Subject: [PATCH 2/2] Run code formatter --- .../rustc_query_impl/src/from_cycle_error.rs | 339 +++++++++--------- 1 file changed, 171 insertions(+), 168 deletions(-) diff --git a/compiler/rustc_query_impl/src/from_cycle_error.rs b/compiler/rustc_query_impl/src/from_cycle_error.rs index a8df8b23441cc..2599c2fde5d08 100644 --- a/compiler/rustc_query_impl/src/from_cycle_error.rs +++ b/compiler/rustc_query_impl/src/from_cycle_error.rs @@ -9,10 +9,10 @@ use rustc_errors::{Applicability, MultiSpan, pluralize, struct_span_code_err}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_middle::dep_graph::DepKind; +use rustc_middle::queries::QueryVTables; use rustc_middle::query::CycleError; use rustc_middle::query::erase::erase_val; use rustc_middle::query::plumbing::CyclePlaceholder; -use rustc_middle::queries::QueryVTables; use rustc_middle::ty::layout::{LayoutError, TyAndLayout}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; @@ -49,102 +49,106 @@ pub(crate) fn specialize_query_vtables<'tcx>(vtables: &mut QueryVTables<'tcx>) { |tcx, cycle, guar| erase_val(layout_of(tcx, cycle, guar)); } - pub(crate) fn default<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError, query_name: &str) -> ! { - let Some(guar) = tcx.sess.dcx().has_errors() else { - bug!( - "`from_cycle_error_default` on query `{query_name}` called without errors: {:#?}", - cycle_error.cycle, - ); - }; - guar.raise_fatal() - } +pub(crate) fn default<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError, query_name: &str) -> ! { + let Some(guar) = tcx.sess.dcx().has_errors() else { + bug!( + "`from_cycle_error_default` on query `{query_name}` called without errors: {:#?}", + cycle_error.cycle, + ); + }; + guar.raise_fatal() +} - fn fn_sig<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { - let err = Ty::new_error(tcx, guar); +fn fn_sig<'tcx>( + tcx: TyCtxt<'tcx>, + cycle_error: CycleError, + guar: ErrorGuaranteed, +) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { + let err = Ty::new_error(tcx, guar); - let arity = if let Some(info) = cycle_error.cycle.get(0) - && info.frame.dep_kind == DepKind::fn_sig - && let Some(def_id) = info.frame.def_id - && let Some(node) = tcx.hir_get_if_local(def_id) - && let Some(sig) = node.fn_sig() - { - sig.decl.inputs.len() - } else { - tcx.dcx().abort_if_errors(); - unreachable!() - }; + let arity = if let Some(info) = cycle_error.cycle.get(0) + && info.frame.dep_kind == DepKind::fn_sig + && let Some(def_id) = info.frame.def_id + && let Some(node) = tcx.hir_get_if_local(def_id) + && let Some(sig) = node.fn_sig() + { + sig.decl.inputs.len() + } else { + tcx.dcx().abort_if_errors(); + unreachable!() + }; - ty::EarlyBinder::bind(ty::Binder::dummy(tcx.mk_fn_sig( - std::iter::repeat_n(err, arity), - err, - false, - rustc_hir::Safety::Safe, - rustc_abi::ExternAbi::Rust, - ))) - } + ty::EarlyBinder::bind(ty::Binder::dummy(tcx.mk_fn_sig( + std::iter::repeat_n(err, arity), + err, + false, + rustc_hir::Safety::Safe, + rustc_abi::ExternAbi::Rust, + ))) +} - fn check_representability<'tcx>( - tcx: TyCtxt<'tcx>, - cycle_error: CycleError, - _guar: ErrorGuaranteed, - ) -> ! { - let mut item_and_field_ids = Vec::new(); - let mut representable_ids = FxHashSet::default(); - for info in &cycle_error.cycle { - if info.frame.dep_kind == DepKind::check_representability - && let Some(field_id) = info.frame.def_id - && let Some(field_id) = field_id.as_local() - && let Some(DefKind::Field) = info.frame.info.def_kind - { - let parent_id = tcx.parent(field_id.to_def_id()); - let item_id = match tcx.def_kind(parent_id) { - DefKind::Variant => tcx.parent(parent_id), - _ => parent_id, - }; - item_and_field_ids.push((item_id.expect_local(), field_id)); - } +fn check_representability<'tcx>( + tcx: TyCtxt<'tcx>, + cycle_error: CycleError, + _guar: ErrorGuaranteed, +) -> ! { + let mut item_and_field_ids = Vec::new(); + let mut representable_ids = FxHashSet::default(); + for info in &cycle_error.cycle { + if info.frame.dep_kind == DepKind::check_representability + && let Some(field_id) = info.frame.def_id + && let Some(field_id) = field_id.as_local() + && let Some(DefKind::Field) = info.frame.info.def_kind + { + let parent_id = tcx.parent(field_id.to_def_id()); + let item_id = match tcx.def_kind(parent_id) { + DefKind::Variant => tcx.parent(parent_id), + _ => parent_id, + }; + item_and_field_ids.push((item_id.expect_local(), field_id)); } - for info in &cycle_error.cycle { - if info.frame.dep_kind == DepKind::check_representability_adt_ty - && let Some(def_id) = info.frame.def_id_for_ty_in_cycle - && let Some(def_id) = def_id.as_local() - && !item_and_field_ids.iter().any(|&(id, _)| id == def_id) - { - representable_ids.insert(def_id); - } + } + for info in &cycle_error.cycle { + if info.frame.dep_kind == DepKind::check_representability_adt_ty + && let Some(def_id) = info.frame.def_id_for_ty_in_cycle + && let Some(def_id) = def_id.as_local() + && !item_and_field_ids.iter().any(|&(id, _)| id == def_id) + { + representable_ids.insert(def_id); } - // We used to continue here, but the cycle error printed next is actually less useful than - // the error produced by `recursive_type_error`. - let guar = recursive_type_error(tcx, item_and_field_ids, &representable_ids); - guar.raise_fatal() } + // We used to continue here, but the cycle error printed next is actually less useful than + // the error produced by `recursive_type_error`. + let guar = recursive_type_error(tcx, item_and_field_ids, &representable_ids); + guar.raise_fatal() +} - fn variances_of<'tcx>( - tcx: TyCtxt<'tcx>, - cycle_error: CycleError, - _guar: ErrorGuaranteed, - ) -> &'tcx [ty::Variance] { - search_for_cycle_permutation( - &cycle_error.cycle, - |cycle| { - if let Some(info) = cycle.get(0) - && info.frame.dep_kind == DepKind::variances_of - && let Some(def_id) = info.frame.def_id - { - let n = tcx.generics_of(def_id).own_params.len(); - ControlFlow::Break(tcx.arena.alloc_from_iter(iter::repeat_n(ty::Bivariant, n))) - } else { - ControlFlow::Continue(()) - } - }, - || { - span_bug!( - cycle_error.usage.as_ref().unwrap().0, - "only `variances_of` returns `&[ty::Variance]`" - ) - }, - ) - } +fn variances_of<'tcx>( + tcx: TyCtxt<'tcx>, + cycle_error: CycleError, + _guar: ErrorGuaranteed, +) -> &'tcx [ty::Variance] { + search_for_cycle_permutation( + &cycle_error.cycle, + |cycle| { + if let Some(info) = cycle.get(0) + && info.frame.dep_kind == DepKind::variances_of + && let Some(def_id) = info.frame.def_id + { + let n = tcx.generics_of(def_id).own_params.len(); + ControlFlow::Break(tcx.arena.alloc_from_iter(iter::repeat_n(ty::Bivariant, n))) + } else { + ControlFlow::Continue(()) + } + }, + || { + span_bug!( + cycle_error.usage.as_ref().unwrap().0, + "only `variances_of` returns `&[ty::Variance]`" + ) + }, + ) +} // Take a cycle of `Q` and try `try_cycle` on every permutation, falling back to `otherwise`. fn search_for_cycle_permutation( @@ -165,90 +169,89 @@ fn search_for_cycle_permutation( otherwise() } - fn layout_of<'tcx>( - tcx: TyCtxt<'tcx>, - cycle_error: CycleError, - _guar: ErrorGuaranteed, - ) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { - let diag = search_for_cycle_permutation( - &cycle_error.cycle, - |cycle| { - if cycle[0].frame.dep_kind == DepKind::layout_of - && let Some(def_id) = cycle[0].frame.def_id_for_ty_in_cycle - && let Some(def_id) = def_id.as_local() - && let def_kind = tcx.def_kind(def_id) - && matches!(def_kind, DefKind::Closure) - && let Some(coroutine_kind) = tcx.coroutine_kind(def_id) - { - // FIXME: `def_span` for an fn-like coroutine will point to the fn's body - // due to interactions between the desugaring into a closure expr and the - // def_span code. I'm not motivated to fix it, because I tried and it was - // not working, so just hack around it by grabbing the parent fn's span. - let span = if coroutine_kind.is_fn_like() { - tcx.def_span(tcx.local_parent(def_id)) - } else { - tcx.def_span(def_id) +fn layout_of<'tcx>( + tcx: TyCtxt<'tcx>, + cycle_error: CycleError, + _guar: ErrorGuaranteed, +) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { + let diag = search_for_cycle_permutation( + &cycle_error.cycle, + |cycle| { + if cycle[0].frame.dep_kind == DepKind::layout_of + && let Some(def_id) = cycle[0].frame.def_id_for_ty_in_cycle + && let Some(def_id) = def_id.as_local() + && let def_kind = tcx.def_kind(def_id) + && matches!(def_kind, DefKind::Closure) + && let Some(coroutine_kind) = tcx.coroutine_kind(def_id) + { + // FIXME: `def_span` for an fn-like coroutine will point to the fn's body + // due to interactions between the desugaring into a closure expr and the + // def_span code. I'm not motivated to fix it, because I tried and it was + // not working, so just hack around it by grabbing the parent fn's span. + let span = if coroutine_kind.is_fn_like() { + tcx.def_span(tcx.local_parent(def_id)) + } else { + tcx.def_span(def_id) + }; + let mut diag = struct_span_code_err!( + tcx.sess.dcx(), + span, + E0733, + "recursion in {} {} requires boxing", + tcx.def_kind_descr_article(def_kind, def_id.to_def_id()), + tcx.def_kind_descr(def_kind, def_id.to_def_id()), + ); + for (i, info) in cycle.iter().enumerate() { + if info.frame.dep_kind != DepKind::layout_of { + continue; + } + let Some(frame_def_id) = info.frame.def_id_for_ty_in_cycle else { + continue; }; - let mut diag = struct_span_code_err!( - tcx.sess.dcx(), - span, - E0733, - "recursion in {} {} requires boxing", - tcx.def_kind_descr_article(def_kind, def_id.to_def_id()), - tcx.def_kind_descr(def_kind, def_id.to_def_id()), - ); - for (i, info) in cycle.iter().enumerate() { - if info.frame.dep_kind != DepKind::layout_of { - continue; - } - let Some(frame_def_id) = info.frame.def_id_for_ty_in_cycle else { - continue; - }; - let Some(frame_coroutine_kind) = tcx.coroutine_kind(frame_def_id) else { - continue; - }; - let frame_span = - info.frame.info.default_span(cycle[(i + 1) % cycle.len()].span); - if frame_span.is_dummy() { - continue; - } - if i == 0 { - diag.span_label(frame_span, "recursive call here"); - } else { - let coroutine_span: Span = if frame_coroutine_kind.is_fn_like() { - tcx.def_span(tcx.parent(frame_def_id)) - } else { - tcx.def_span(frame_def_id) - }; - let mut multispan = MultiSpan::from_span(coroutine_span); - multispan - .push_span_label(frame_span, "...leading to this recursive call"); - diag.span_note( - multispan, - format!("which leads to this {}", tcx.def_descr(frame_def_id)), - ); - } + let Some(frame_coroutine_kind) = tcx.coroutine_kind(frame_def_id) else { + continue; + }; + let frame_span = + info.frame.info.default_span(cycle[(i + 1) % cycle.len()].span); + if frame_span.is_dummy() { + continue; } - // FIXME: We could report a structured suggestion if we had - // enough info here... Maybe we can use a hacky HIR walker. - if matches!( - coroutine_kind, - hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) - ) { - diag.note("a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future"); + if i == 0 { + diag.span_label(frame_span, "recursive call here"); + } else { + let coroutine_span: Span = if frame_coroutine_kind.is_fn_like() { + tcx.def_span(tcx.parent(frame_def_id)) + } else { + tcx.def_span(frame_def_id) + }; + let mut multispan = MultiSpan::from_span(coroutine_span); + multispan.push_span_label(frame_span, "...leading to this recursive call"); + diag.span_note( + multispan, + format!("which leads to this {}", tcx.def_descr(frame_def_id)), + ); } - - ControlFlow::Break(diag) - } else { - ControlFlow::Continue(()) } - }, - || report_cycle(tcx.sess, &cycle_error), - ); + // FIXME: We could report a structured suggestion if we had + // enough info here... Maybe we can use a hacky HIR walker. + if matches!( + coroutine_kind, + hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) + ) { + diag.note("a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future"); + } - let guar = diag.emit(); - Err(tcx.arena.alloc(LayoutError::Cycle(guar))) - } + ControlFlow::Break(diag) + } else { + ControlFlow::Continue(()) + } + }, + || report_cycle(tcx.sess, &cycle_error), + ); + + let guar = diag.emit(); + Err(tcx.arena.alloc(LayoutError::Cycle(guar))) +} // item_and_field_ids should form a cycle where each field contains the // type in the next element in the list