Skip to main content

rustc_query_impl/
handle_cycle_error.rs

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