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