about summary refs log tree commit diff
path: root/compiler/rustc_middle
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-10-08 11:53:25 +0000
committerbors <bors@rust-lang.org>2022-10-08 11:53:25 +0000
commitbba9785dd73f61aacd301a2cb379e1e85a129047 (patch)
tree1325e86a86ce101c60353107590ac9f990a3e41c /compiler/rustc_middle
parenta688a0305fad9219505a8f2576446510601bafe8 (diff)
parentff940db666daeae83c6c71685901c6c14df17018 (diff)
downloadrust-bba9785dd73f61aacd301a2cb379e1e85a129047.tar.gz
rust-bba9785dd73f61aacd301a2cb379e1e85a129047.zip
Auto merge of #100720 - camsteffen:representable, r=cjgillot
Rewrite representability

 * Improve placement of `Box` in the suggestion
 * Multiple items in a cycle emit 1 error instead of an error for each item in the cycle
 * Introduce `representability` query to avoid traversing an item every time it is used.
 * Also introduce `params_in_repr` query to avoid traversing generic items every time it is used.
Diffstat (limited to 'compiler/rustc_middle')
-rw-r--r--compiler/rustc_middle/src/arena.rs1
-rw-r--r--compiler/rustc_middle/src/query/mod.rs26
-rw-r--r--compiler/rustc_middle/src/ty/adt.rs7
-rw-r--r--compiler/rustc_middle/src/ty/parameterized.rs1
-rw-r--r--compiler/rustc_middle/src/values.rs170
5 files changed, 200 insertions, 5 deletions
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index 6bdf5a023b6..d2847e4bc12 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -103,6 +103,7 @@ macro_rules! arena_types {
             [] dep_kind: rustc_middle::dep_graph::DepKindStruct<'tcx>,
 
             [decode] trait_impl_trait_tys: rustc_data_structures::fx::FxHashMap<rustc_hir::def_id::DefId, rustc_middle::ty::Ty<'tcx>>,
+            [] bit_set_u32: rustc_index::bit_set::BitSet<u32>,
         ]);
     )
 }
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 334eb953cbc..a175b1eb467 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -301,6 +301,32 @@ rustc_queries! {
         separate_provide_extern
     }
 
+    /// Checks whether a type is representable or infinitely sized
+    query representability(_: LocalDefId) -> rustc_middle::ty::Representability {
+        desc { "checking if {:?} is representable", tcx.def_path_str(key.to_def_id()) }
+        // infinitely sized types will cause a cycle
+        cycle_delay_bug
+        // we don't want recursive representability calls to be forced with
+        // incremental compilation because, if a cycle occurs, we need the
+        // entire cycle to be in memory for diagnostics
+        anon
+    }
+
+    /// An implementation detail for the `representability` query
+    query representability_adt_ty(_: Ty<'tcx>) -> rustc_middle::ty::Representability {
+        desc { "checking if {:?} is representable", key }
+        cycle_delay_bug
+        anon
+    }
+
+    /// Set of param indexes for type params that are in the type's representation
+    query params_in_repr(key: DefId) -> rustc_index::bit_set::BitSet<u32> {
+        desc { "finding type parameters in the representation" }
+        arena_cache
+        no_hash
+        separate_provide_extern
+    }
+
     /// Fetch the THIR for a given body. If typeck for that body failed, returns an empty `Thir`.
     query thir_body(key: ty::WithOptConstParam<LocalDefId>)
         -> Result<(&'tcx Steal<thir::Thir<'tcx>>, thir::ExprId), ErrorGuaranteed>
diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs
index 3c485e26409..80bbc8e630e 100644
--- a/compiler/rustc_middle/src/ty/adt.rs
+++ b/compiler/rustc_middle/src/ty/adt.rs
@@ -566,3 +566,10 @@ impl<'tcx> AdtDef<'tcx> {
         ty::EarlyBinder(tcx.adt_sized_constraint(self.did()).0)
     }
 }
+
+#[derive(Clone, Copy, Debug)]
+#[derive(HashStable)]
+pub enum Representability {
+    Representable,
+    Infinite,
+}
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index 0257ca7b29c..f289f2265a2 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -82,6 +82,7 @@ trivially_parameterized_over_tcx! {
     rustc_hir::def::DefKind,
     rustc_hir::def_id::DefIndex,
     rustc_hir::definitions::DefKey,
+    rustc_index::bit_set::BitSet<u32>,
     rustc_index::bit_set::FiniteBitSet<u32>,
     rustc_session::cstore::ForeignModule,
     rustc_session::cstore::LinkagePreference,
diff --git a/compiler/rustc_middle/src/values.rs b/compiler/rustc_middle/src/values.rs
index 7fbe9ae2a84..bb89959b29d 100644
--- a/compiler/rustc_middle/src/values.rs
+++ b/compiler/rustc_middle/src/values.rs
@@ -1,8 +1,18 @@
-use rustc_middle::ty::{self, AdtSizedConstraint, Ty, TyCtxt};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_middle::ty::Representability;
+use rustc_middle::ty::{self, AdtSizedConstraint, DefIdTree, Ty, TyCtxt};
+use rustc_query_system::query::QueryInfo;
 use rustc_query_system::Value;
+use rustc_span::def_id::LocalDefId;
+use rustc_span::Span;
+
+use std::fmt::Write;
 
 impl<'tcx> Value<TyCtxt<'tcx>> for Ty<'_> {
-    fn from_cycle_error(tcx: TyCtxt<'tcx>) -> Self {
+    fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &[QueryInfo]) -> 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<'tcx>, Ty<'_>>(tcx.ty_error()) }
@@ -10,7 +20,7 @@ impl<'tcx> Value<TyCtxt<'tcx>> for Ty<'_> {
 }
 
 impl<'tcx> Value<TyCtxt<'tcx>> for ty::SymbolName<'_> {
-    fn from_cycle_error(tcx: TyCtxt<'tcx>) -> Self {
+    fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &[QueryInfo]) -> Self {
         // SAFETY: This is never called when `Self` is not `SymbolName<'tcx>`.
         // FIXME: Represent the above fact in the trait system somehow.
         unsafe {
@@ -22,7 +32,7 @@ impl<'tcx> Value<TyCtxt<'tcx>> for ty::SymbolName<'_> {
 }
 
 impl<'tcx> Value<TyCtxt<'tcx>> for AdtSizedConstraint<'_> {
-    fn from_cycle_error(tcx: TyCtxt<'tcx>) -> Self {
+    fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &[QueryInfo]) -> Self {
         // SAFETY: This is never called when `Self` is not `AdtSizedConstraint<'tcx>`.
         // FIXME: Represent the above fact in the trait system somehow.
         unsafe {
@@ -34,7 +44,7 @@ impl<'tcx> Value<TyCtxt<'tcx>> for AdtSizedConstraint<'_> {
 }
 
 impl<'tcx> Value<TyCtxt<'tcx>> for ty::Binder<'_, ty::FnSig<'_>> {
-    fn from_cycle_error(tcx: TyCtxt<'tcx>) -> Self {
+    fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &[QueryInfo]) -> Self {
         let err = tcx.ty_error();
         // FIXME(compiler-errors): It would be nice if we could get the
         // query key, so we could at least generate a fn signature that
@@ -52,3 +62,153 @@ impl<'tcx> Value<TyCtxt<'tcx>> for ty::Binder<'_, ty::FnSig<'_>> {
         unsafe { std::mem::transmute::<ty::PolyFnSig<'tcx>, ty::Binder<'_, ty::FnSig<'_>>>(fn_sig) }
     }
 }
+
+impl<'tcx> Value<TyCtxt<'tcx>> for Representability {
+    fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle: &[QueryInfo]) -> Self {
+        let mut item_and_field_ids = Vec::new();
+        let mut representable_ids = FxHashSet::default();
+        for info in cycle {
+            if info.query.name == "representability"
+                && let Some(field_id) = info.query.def_id
+                && let Some(field_id) = field_id.as_local()
+                && let Some(DefKind::Field) = info.query.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 {
+            if info.query.name == "representability_adt_ty"
+                && let Some(def_id) = info.query.ty_adt_id
+                && let Some(def_id) = def_id.as_local()
+                && !item_and_field_ids.iter().any(|&(id, _)| id == def_id)
+            {
+                representable_ids.insert(def_id);
+            }
+        }
+        recursive_type_error(tcx, item_and_field_ids, &representable_ids);
+        Representability::Infinite
+    }
+}
+
+// item_and_field_ids should form a cycle where each field contains the
+// type in the next element in the list
+pub fn recursive_type_error(
+    tcx: TyCtxt<'_>,
+    mut item_and_field_ids: Vec<(LocalDefId, LocalDefId)>,
+    representable_ids: &FxHashSet<LocalDefId>,
+) {
+    const ITEM_LIMIT: usize = 5;
+
+    // Rotate the cycle so that the item with the lowest span is first
+    let start_index = item_and_field_ids
+        .iter()
+        .enumerate()
+        .min_by_key(|&(_, &(id, _))| tcx.def_span(id))
+        .unwrap()
+        .0;
+    item_and_field_ids.rotate_left(start_index);
+
+    let cycle_len = item_and_field_ids.len();
+    let show_cycle_len = cycle_len.min(ITEM_LIMIT);
+
+    let mut err_span = MultiSpan::from_spans(
+        item_and_field_ids[..show_cycle_len]
+            .iter()
+            .map(|(id, _)| tcx.def_span(id.to_def_id()))
+            .collect(),
+    );
+    let mut suggestion = Vec::with_capacity(show_cycle_len * 2);
+    for i in 0..show_cycle_len {
+        let (_, field_id) = item_and_field_ids[i];
+        let (next_item_id, _) = item_and_field_ids[(i + 1) % cycle_len];
+        // Find the span(s) that contain the next item in the cycle
+        let hir_id = tcx.hir().local_def_id_to_hir_id(field_id);
+        let hir::Node::Field(field) = tcx.hir().get(hir_id) else { bug!("expected field") };
+        let mut found = Vec::new();
+        find_item_ty_spans(tcx, field.ty, next_item_id, &mut found, representable_ids);
+
+        // Couldn't find the type. Maybe it's behind a type alias?
+        // In any case, we'll just suggest boxing the whole field.
+        if found.is_empty() {
+            found.push(field.ty.span);
+        }
+
+        for span in found {
+            err_span.push_span_label(span, "recursive without indirection");
+            // FIXME(compiler-errors): This suggestion might be erroneous if Box is shadowed
+            suggestion.push((span.shrink_to_lo(), "Box<".to_string()));
+            suggestion.push((span.shrink_to_hi(), ">".to_string()));
+        }
+    }
+    let items_list = {
+        let mut s = String::new();
+        for (i, (item_id, _)) in item_and_field_ids.iter().enumerate() {
+            let path = tcx.def_path_str(item_id.to_def_id());
+            write!(&mut s, "`{path}`").unwrap();
+            if i == (ITEM_LIMIT - 1) && cycle_len > ITEM_LIMIT {
+                write!(&mut s, " and {} more", cycle_len - 5).unwrap();
+                break;
+            }
+            if cycle_len > 1 && i < cycle_len - 2 {
+                s.push_str(", ");
+            } else if cycle_len > 1 && i == cycle_len - 2 {
+                s.push_str(" and ")
+            }
+        }
+        s
+    };
+    let mut err = struct_span_err!(
+        tcx.sess,
+        err_span,
+        E0072,
+        "recursive type{} {} {} infinite size",
+        pluralize!(cycle_len),
+        items_list,
+        pluralize!("has", cycle_len),
+    );
+    err.multipart_suggestion(
+        "insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle",
+        suggestion,
+        Applicability::HasPlaceholders,
+    );
+    err.emit();
+}
+
+fn find_item_ty_spans(
+    tcx: TyCtxt<'_>,
+    ty: &hir::Ty<'_>,
+    needle: LocalDefId,
+    spans: &mut Vec<Span>,
+    seen_representable: &FxHashSet<LocalDefId>,
+) {
+    match ty.kind {
+        hir::TyKind::Path(hir::QPath::Resolved(_, path)) => {
+            if let Some(def_id) = path.res.opt_def_id() {
+                let check_params = def_id.as_local().map_or(true, |def_id| {
+                    if def_id == needle {
+                        spans.push(ty.span);
+                    }
+                    seen_representable.contains(&def_id)
+                });
+                if check_params && let Some(args) = path.segments.last().unwrap().args {
+                    let params_in_repr = tcx.params_in_repr(def_id);
+                    for (i, arg) in args.args.iter().enumerate() {
+                        if let hir::GenericArg::Type(ty) = arg && params_in_repr.contains(i as u32) {
+                            find_item_ty_spans(tcx, ty, needle, spans, seen_representable);
+                        }
+                    }
+                }
+            }
+        }
+        hir::TyKind::Array(ty, _) => find_item_ty_spans(tcx, ty, needle, spans, seen_representable),
+        hir::TyKind::Tup(tys) => {
+            tys.iter().for_each(|ty| find_item_ty_spans(tcx, ty, needle, spans, seen_representable))
+        }
+        _ => {}
+    }
+}