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