Skip to main content

rustc_query_impl/
from_cycle_error.rs

1use std::collections::VecDeque;
2use std::fmt::Write;
3use std::ops::ControlFlow;
4
5use rustc_data_structures::fx::FxHashSet;
6use rustc_errors::codes::*;
7use rustc_errors::{Applicability, MultiSpan, pluralize, struct_span_code_err};
8use rustc_hir as hir;
9use rustc_hir::def::{DefKind, Res};
10use rustc_middle::dep_graph::DepKind;
11use rustc_middle::query::CycleError;
12use rustc_middle::query::plumbing::CyclePlaceholder;
13use rustc_middle::ty::{self, Representability, Ty, TyCtxt};
14use rustc_middle::{bug, span_bug};
15use rustc_span::def_id::LocalDefId;
16use rustc_span::{ErrorGuaranteed, Span};
17
18use crate::job::report_cycle;
19
20pub(crate) trait FromCycleError<'tcx>: Sized {
21    /// Try to produce a `Self` value that represents an error form (e.g. `TyKind::Error`).
22    ///
23    /// Note: the default impl calls `raise_fatal`, ending compilation immediately! Only a few
24    /// types override this with a non-fatal impl.
25    fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self;
26}
27
28impl<'tcx, T> FromCycleError<'tcx> for T {
29    default fn from_cycle_error(
30        tcx: TyCtxt<'tcx>,
31        cycle_error: CycleError,
32        _guar: ErrorGuaranteed,
33    ) -> T {
34        let Some(guar) = tcx.sess.dcx().has_errors() else {
35            ::rustc_middle::util::bug::bug_fmt(format_args!("<{0} as FromCycleError>::from_cycle_error called without errors: {1:#?}",
        std::any::type_name::<T>(), cycle_error.cycle));bug!(
36                "<{} as FromCycleError>::from_cycle_error called without errors: {:#?}",
37                std::any::type_name::<T>(),
38                cycle_error.cycle,
39            );
40        };
41        guar.raise_fatal();
42    }
43}
44
45impl<'tcx> FromCycleError<'tcx> for Ty<'_> {
46    fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self {
47        // SAFETY: This is never called when `Self` is not `Ty<'tcx>`.
48        // FIXME: Represent the above fact in the trait system somehow.
49        unsafe { std::mem::transmute::<Ty<'tcx>, Ty<'_>>(Ty::new_error(tcx, guar)) }
50    }
51}
52
53impl<'tcx> FromCycleError<'tcx> for Result<ty::EarlyBinder<'_, Ty<'_>>, CyclePlaceholder> {
54    fn from_cycle_error(_tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self {
55        Err(CyclePlaceholder(guar))
56    }
57}
58
59impl<'tcx> FromCycleError<'tcx> for ty::Binder<'_, ty::FnSig<'_>> {
60    fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self {
61        let err = Ty::new_error(tcx, guar);
62
63        let arity = if let Some(info) = cycle_error.cycle.get(0)
64            && info.frame.dep_kind == DepKind::fn_sig
65            && let Some(def_id) = info.frame.def_id
66            && let Some(node) = tcx.hir_get_if_local(def_id)
67            && let Some(sig) = node.fn_sig()
68        {
69            sig.decl.inputs.len()
70        } else {
71            tcx.dcx().abort_if_errors();
72            ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
73        };
74
75        let fn_sig = ty::Binder::dummy(tcx.mk_fn_sig(
76            std::iter::repeat_n(err, arity),
77            err,
78            false,
79            rustc_hir::Safety::Safe,
80            rustc_abi::ExternAbi::Rust,
81        ));
82
83        // SAFETY: This is never called when `Self` is not `ty::Binder<'tcx, ty::FnSig<'tcx>>`.
84        // FIXME: Represent the above fact in the trait system somehow.
85        unsafe { std::mem::transmute::<ty::PolyFnSig<'tcx>, ty::Binder<'_, ty::FnSig<'_>>>(fn_sig) }
86    }
87}
88
89impl<'tcx> FromCycleError<'tcx> for Representability {
90    fn from_cycle_error(
91        tcx: TyCtxt<'tcx>,
92        cycle_error: CycleError,
93        _guar: ErrorGuaranteed,
94    ) -> Self {
95        let mut item_and_field_ids = Vec::new();
96        let mut representable_ids = FxHashSet::default();
97        for info in &cycle_error.cycle {
98            if info.frame.dep_kind == DepKind::check_representability
99                && let Some(field_id) = info.frame.def_id
100                && let Some(field_id) = field_id.as_local()
101                && let Some(DefKind::Field) = info.frame.info.def_kind
102            {
103                let parent_id = tcx.parent(field_id.to_def_id());
104                let item_id = match tcx.def_kind(parent_id) {
105                    DefKind::Variant => tcx.parent(parent_id),
106                    _ => parent_id,
107                };
108                item_and_field_ids.push((item_id.expect_local(), field_id));
109            }
110        }
111        for info in &cycle_error.cycle {
112            if info.frame.dep_kind == DepKind::check_representability_adt_ty
113                && let Some(def_id) = info.frame.def_id_for_ty_in_cycle
114                && let Some(def_id) = def_id.as_local()
115                && !item_and_field_ids.iter().any(|&(id, _)| id == def_id)
116            {
117                representable_ids.insert(def_id);
118            }
119        }
120        // We used to continue here, but the cycle error printed next is actually less useful than
121        // the error produced by `recursive_type_error`.
122        let guar = recursive_type_error(tcx, item_and_field_ids, &representable_ids);
123        guar.raise_fatal();
124    }
125}
126
127impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, Ty<'_>> {
128    fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self {
129        ty::EarlyBinder::bind(Ty::from_cycle_error(tcx, cycle_error, guar))
130    }
131}
132
133impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> {
134    fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self {
135        ty::EarlyBinder::bind(ty::Binder::from_cycle_error(tcx, cycle_error, guar))
136    }
137}
138
139impl<'tcx> FromCycleError<'tcx> for &[ty::Variance] {
140    fn from_cycle_error(
141        tcx: TyCtxt<'tcx>,
142        cycle_error: CycleError,
143        _guar: ErrorGuaranteed,
144    ) -> Self {
145        search_for_cycle_permutation(
146            &cycle_error.cycle,
147            |cycle| {
148                if let Some(info) = cycle.get(0)
149                    && info.frame.dep_kind == DepKind::variances_of
150                    && let Some(def_id) = info.frame.def_id
151                {
152                    let n = tcx.generics_of(def_id).own_params.len();
153                    ControlFlow::Break(::alloc::vec::from_elem(ty::Bivariant, n)vec![ty::Bivariant; n].leak())
154                } else {
155                    ControlFlow::Continue(())
156                }
157            },
158            || {
159                ::rustc_middle::util::bug::span_bug_fmt(cycle_error.usage.as_ref().unwrap().0,
    format_args!("only `variances_of` returns `&[ty::Variance]`"))span_bug!(
160                    cycle_error.usage.as_ref().unwrap().0,
161                    "only `variances_of` returns `&[ty::Variance]`"
162                )
163            },
164        )
165    }
166}
167
168// Take a cycle of `Q` and try `try_cycle` on every permutation, falling back to `otherwise`.
169fn search_for_cycle_permutation<Q, T>(
170    cycle: &[Q],
171    try_cycle: impl Fn(&mut VecDeque<&Q>) -> ControlFlow<T, ()>,
172    otherwise: impl FnOnce() -> T,
173) -> T {
174    let mut cycle: VecDeque<_> = cycle.iter().collect();
175    for _ in 0..cycle.len() {
176        match try_cycle(&mut cycle) {
177            ControlFlow::Continue(_) => {
178                cycle.rotate_left(1);
179            }
180            ControlFlow::Break(t) => return t,
181        }
182    }
183
184    otherwise()
185}
186
187impl<'tcx, T> FromCycleError<'tcx> for Result<T, &'_ ty::layout::LayoutError<'_>> {
188    fn from_cycle_error(
189        tcx: TyCtxt<'tcx>,
190        cycle_error: CycleError,
191        _guar: ErrorGuaranteed,
192    ) -> Self {
193        let diag = search_for_cycle_permutation(
194            &cycle_error.cycle,
195            |cycle| {
196                if cycle[0].frame.dep_kind == DepKind::layout_of
197                    && let Some(def_id) = cycle[0].frame.def_id_for_ty_in_cycle
198                    && let Some(def_id) = def_id.as_local()
199                    && let def_kind = tcx.def_kind(def_id)
200                    && #[allow(non_exhaustive_omitted_patterns)] match def_kind {
    DefKind::Closure => true,
    _ => false,
}matches!(def_kind, DefKind::Closure)
201                    && let Some(coroutine_kind) = tcx.coroutine_kind(def_id)
202                {
203                    // FIXME: `def_span` for an fn-like coroutine will point to the fn's body
204                    // due to interactions between the desugaring into a closure expr and the
205                    // def_span code. I'm not motivated to fix it, because I tried and it was
206                    // not working, so just hack around it by grabbing the parent fn's span.
207                    let span = if coroutine_kind.is_fn_like() {
208                        tcx.def_span(tcx.local_parent(def_id))
209                    } else {
210                        tcx.def_span(def_id)
211                    };
212                    let mut diag = {
    tcx.sess.dcx().struct_span_err(span,
            ::alloc::__export::must_use({
                    ::alloc::fmt::format(format_args!("recursion in {0} {1} 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())))
                })).with_code(E0733)
}struct_span_code_err!(
213                        tcx.sess.dcx(),
214                        span,
215                        E0733,
216                        "recursion in {} {} requires boxing",
217                        tcx.def_kind_descr_article(def_kind, def_id.to_def_id()),
218                        tcx.def_kind_descr(def_kind, def_id.to_def_id()),
219                    );
220                    for (i, info) in cycle.iter().enumerate() {
221                        if info.frame.dep_kind != DepKind::layout_of {
222                            continue;
223                        }
224                        let Some(frame_def_id) = info.frame.def_id_for_ty_in_cycle else {
225                            continue;
226                        };
227                        let Some(frame_coroutine_kind) = tcx.coroutine_kind(frame_def_id) else {
228                            continue;
229                        };
230                        let frame_span =
231                            info.frame.info.default_span(cycle[(i + 1) % cycle.len()].span);
232                        if frame_span.is_dummy() {
233                            continue;
234                        }
235                        if i == 0 {
236                            diag.span_label(frame_span, "recursive call here");
237                        } else {
238                            let coroutine_span: Span = if frame_coroutine_kind.is_fn_like() {
239                                tcx.def_span(tcx.parent(frame_def_id))
240                            } else {
241                                tcx.def_span(frame_def_id)
242                            };
243                            let mut multispan = MultiSpan::from_span(coroutine_span);
244                            multispan
245                                .push_span_label(frame_span, "...leading to this recursive call");
246                            diag.span_note(
247                                multispan,
248                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("which leads to this {0}",
                tcx.def_descr(frame_def_id)))
    })format!("which leads to this {}", tcx.def_descr(frame_def_id)),
249                            );
250                        }
251                    }
252                    // FIXME: We could report a structured suggestion if we had
253                    // enough info here... Maybe we can use a hacky HIR walker.
254                    if #[allow(non_exhaustive_omitted_patterns)] match coroutine_kind {
    hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) => true,
    _ => false,
}matches!(
255                        coroutine_kind,
256                        hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)
257                    ) {
258                        diag.note("a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future");
259                    }
260
261                    ControlFlow::Break(diag)
262                } else {
263                    ControlFlow::Continue(())
264                }
265            },
266            || report_cycle(tcx.sess, &cycle_error),
267        );
268
269        let guar = diag.emit();
270
271        // tcx.arena.alloc cannot be used because we are not allowed to use &'tcx LayoutError under
272        // min_specialization. Since this is an error path anyways, leaking doesn't matter (and really,
273        // tcx.arena.alloc is pretty much equal to leaking).
274        Err(Box::leak(Box::new(ty::layout::LayoutError::Cycle(guar))))
275    }
276}
277
278// item_and_field_ids should form a cycle where each field contains the
279// type in the next element in the list
280fn recursive_type_error(
281    tcx: TyCtxt<'_>,
282    mut item_and_field_ids: Vec<(LocalDefId, LocalDefId)>,
283    representable_ids: &FxHashSet<LocalDefId>,
284) -> ErrorGuaranteed {
285    const ITEM_LIMIT: usize = 5;
286
287    // Rotate the cycle so that the item with the lowest span is first
288    let start_index = item_and_field_ids
289        .iter()
290        .enumerate()
291        .min_by_key(|&(_, &(id, _))| tcx.def_span(id))
292        .unwrap()
293        .0;
294    item_and_field_ids.rotate_left(start_index);
295
296    let cycle_len = item_and_field_ids.len();
297    let show_cycle_len = cycle_len.min(ITEM_LIMIT);
298
299    let mut err_span = MultiSpan::from_spans(
300        item_and_field_ids[..show_cycle_len]
301            .iter()
302            .map(|(id, _)| tcx.def_span(id.to_def_id()))
303            .collect(),
304    );
305    let mut suggestion = Vec::with_capacity(show_cycle_len * 2);
306    for i in 0..show_cycle_len {
307        let (_, field_id) = item_and_field_ids[i];
308        let (next_item_id, _) = item_and_field_ids[(i + 1) % cycle_len];
309        // Find the span(s) that contain the next item in the cycle
310        let hir::Node::Field(field) = tcx.hir_node_by_def_id(field_id) else {
311            ::rustc_middle::util::bug::bug_fmt(format_args!("expected field"))bug!("expected field")
312        };
313        let mut found = Vec::new();
314        find_item_ty_spans(tcx, field.ty, next_item_id, &mut found, representable_ids);
315
316        // Couldn't find the type. Maybe it's behind a type alias?
317        // In any case, we'll just suggest boxing the whole field.
318        if found.is_empty() {
319            found.push(field.ty.span);
320        }
321
322        for span in found {
323            err_span.push_span_label(span, "recursive without indirection");
324            // FIXME(compiler-errors): This suggestion might be erroneous if Box is shadowed
325            suggestion.push((span.shrink_to_lo(), "Box<".to_string()));
326            suggestion.push((span.shrink_to_hi(), ">".to_string()));
327        }
328    }
329    let items_list = {
330        let mut s = String::new();
331        for (i, &(item_id, _)) in item_and_field_ids.iter().enumerate() {
332            let path = tcx.def_path_str(item_id);
333            (&mut s).write_fmt(format_args!("`{0}`", path))write!(&mut s, "`{path}`").unwrap();
334            if i == (ITEM_LIMIT - 1) && cycle_len > ITEM_LIMIT {
335                (&mut s).write_fmt(format_args!(" and {0} more", cycle_len - 5))write!(&mut s, " and {} more", cycle_len - 5).unwrap();
336                break;
337            }
338            if cycle_len > 1 && i < cycle_len - 2 {
339                s.push_str(", ");
340            } else if cycle_len > 1 && i == cycle_len - 2 {
341                s.push_str(" and ")
342            }
343        }
344        s
345    };
346    {
    tcx.dcx().struct_span_err(err_span,
            ::alloc::__export::must_use({
                    ::alloc::fmt::format(format_args!("recursive type{0} {1} {2} infinite size",
                            if cycle_len == 1 { "" } else { "s" }, items_list,
                            if cycle_len == 1 { "has" } else { "have" }))
                })).with_code(E0072)
}struct_span_code_err!(
347        tcx.dcx(),
348        err_span,
349        E0072,
350        "recursive type{} {} {} infinite size",
351        pluralize!(cycle_len),
352        items_list,
353        pluralize!("has", cycle_len),
354    )
355    .with_multipart_suggestion(
356        "insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle",
357        suggestion,
358        Applicability::HasPlaceholders,
359    )
360    .emit()
361}
362
363fn find_item_ty_spans(
364    tcx: TyCtxt<'_>,
365    ty: &hir::Ty<'_>,
366    needle: LocalDefId,
367    spans: &mut Vec<Span>,
368    seen_representable: &FxHashSet<LocalDefId>,
369) {
370    match ty.kind {
371        hir::TyKind::Path(hir::QPath::Resolved(_, path)) => {
372            if let Res::Def(kind, def_id) = path.res
373                && #[allow(non_exhaustive_omitted_patterns)] match kind {
    DefKind::Enum | DefKind::Struct | DefKind::Union => true,
    _ => false,
}matches!(kind, DefKind::Enum | DefKind::Struct | DefKind::Union)
374            {
375                let check_params = def_id.as_local().is_none_or(|def_id| {
376                    if def_id == needle {
377                        spans.push(ty.span);
378                    }
379                    seen_representable.contains(&def_id)
380                });
381                if check_params && let Some(args) = path.segments.last().unwrap().args {
382                    let params_in_repr = tcx.params_in_repr(def_id);
383                    // the domain size check is needed because the HIR may not be well-formed at this point
384                    for (i, arg) in args.args.iter().enumerate().take(params_in_repr.domain_size())
385                    {
386                        if let hir::GenericArg::Type(ty) = arg
387                            && params_in_repr.contains(i as u32)
388                        {
389                            find_item_ty_spans(
390                                tcx,
391                                ty.as_unambig_ty(),
392                                needle,
393                                spans,
394                                seen_representable,
395                            );
396                        }
397                    }
398                }
399            }
400        }
401        hir::TyKind::Array(ty, _) => find_item_ty_spans(tcx, ty, needle, spans, seen_representable),
402        hir::TyKind::Tup(tys) => {
403            tys.iter().for_each(|ty| find_item_ty_spans(tcx, ty, needle, spans, seen_representable))
404        }
405        _ => {}
406    }
407}