Skip to content

Commit

Permalink
Auto merge of rust-lang#100720 - camsteffen:representable, r=cjgillot
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Oct 8, 2022
2 parents a688a03 + ff940db commit bba9785
Show file tree
Hide file tree
Showing 61 changed files with 529 additions and 736 deletions.
1 change: 0 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3552,7 +3552,6 @@ dependencies = [
"rustc_span",
"rustc_target",
"rustc_trait_selection",
"rustc_ty_utils",
"rustc_type_ir",
"smallvec",
"tracing",
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_hir_analysis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ rustc_span = { path = "../rustc_span" }
rustc_index = { path = "../rustc_index" }
rustc_infer = { path = "../rustc_infer" }
rustc_trait_selection = { path = "../rustc_trait_selection" }
rustc_ty_utils = { path = "../rustc_ty_utils" }
rustc_lint = { path = "../rustc_lint" }
rustc_serialize = { path = "../rustc_serialize" }
rustc_type_ir = { path = "../rustc_type_ir" }
Expand Down
28 changes: 3 additions & 25 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ use rustc_span::{self, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::{self, ObligationCtxt};
use rustc_ty_utils::representability::{self, Representability};

use std::ops::ControlFlow;

Expand Down Expand Up @@ -381,7 +380,7 @@ fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) {
let def = tcx.adt_def(def_id);
let span = tcx.def_span(def_id);
def.destructor(tcx); // force the destructor to be evaluated
check_representable(tcx, span, def_id);
let _ = tcx.representability(def_id);

if def.repr().simd() {
check_simd(tcx, span, def_id);
Expand All @@ -395,7 +394,7 @@ fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) {
let def = tcx.adt_def(def_id);
let span = tcx.def_span(def_id);
def.destructor(tcx); // force the destructor to be evaluated
check_representable(tcx, span, def_id);
let _ = tcx.representability(def_id);
check_transparent(tcx, span, def);
check_union_fields(tcx, span, def_id);
check_packed(tcx, span, def);
Expand Down Expand Up @@ -1151,27 +1150,6 @@ fn check_impl_items_against_trait<'tcx>(
}
}

/// Checks whether a type can be represented in memory. In particular, it
/// identifies types that contain themselves without indirection through a
/// pointer, which would mean their size is unbounded.
pub(super) fn check_representable(tcx: TyCtxt<'_>, sp: Span, item_def_id: LocalDefId) -> bool {
let rty = tcx.type_of(item_def_id);

// Check that it is possible to represent this type. This call identifies
// (1) types that contain themselves and (2) types that contain a different
// recursive type. It is only necessary to throw an error on those that
// contain themselves. For case 2, there must be an inner type that will be
// caught by case 1.
match representability::ty_is_representable(tcx, rty, sp, None) {
Representability::SelfRecursive(spans) => {
recursive_type_with_infinite_size_error(tcx, item_def_id.to_def_id(), spans);
return false;
}
Representability::Representable | Representability::ContainsRecursive => (),
}
true
}

pub fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
let t = tcx.type_of(def_id);
if let ty::Adt(def, substs) = t.kind()
Expand Down Expand Up @@ -1509,7 +1487,7 @@ fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, vs: &'tcx [hir::Variant<'tcx>], def_id: L

detect_discriminant_duplicate(tcx, def.discriminants(tcx).collect(), vs, sp);

check_representable(tcx, sp, def_id);
let _ = tcx.representability(def_id);
check_transparent(tcx, sp, def);
}

Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_hir_analysis/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ use rustc_span::{self, BytePos, Span, Symbol};
use rustc_target::abi::VariantIdx;
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::error_reporting::recursive_type_with_infinite_size_error;
use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
use std::cell::RefCell;
use std::num::NonZeroU32;
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ macro_rules! pluralize {
($x:expr) => {
if $x != 1 { "s" } else { "" }
};
("has", $x:expr) => {
if $x == 1 { "has" } else { "have" }
};
("is", $x:expr) => {
if $x == 1 { "is" } else { "are" }
};
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ provide! { tcx, def_id, other, cdata,
lookup_const_stability => { table }
lookup_default_body_stability => { table }
lookup_deprecation_entry => { table }
params_in_repr => { table }
unused_generic_params => { table }
opt_def_kind => { table_direct }
impl_parent => { table }
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
if let DefKind::Trait | DefKind::TraitAlias = def_kind {
record!(self.tables.super_predicates_of[def_id] <- self.tcx.super_predicates_of(def_id));
}
if let DefKind::Enum | DefKind::Struct | DefKind::Union = def_kind {
let params_in_repr = self.tcx.params_in_repr(def_id);
record!(self.tables.params_in_repr[def_id] <- params_in_repr);
}
if should_encode_trait_impl_trait_tys(tcx, def_id)
&& let Ok(table) = self.tcx.collect_trait_impl_trait_tys(def_id)
{
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use rustc_hir::def::{CtorKind, DefKind};
use rustc_hir::def_id::{CrateNum, DefId, DefIndex, DefPathHash, StableCrateId};
use rustc_hir::definitions::DefKey;
use rustc_hir::lang_items;
use rustc_index::{bit_set::FiniteBitSet, vec::IndexVec};
use rustc_index::bit_set::{BitSet, FiniteBitSet};
use rustc_index::vec::IndexVec;
use rustc_middle::metadata::ModChild;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo};
Expand Down Expand Up @@ -383,6 +384,7 @@ define_tables! {
inherent_impls: Table<DefIndex, LazyArray<DefIndex>>,
expn_that_defined: Table<DefIndex, LazyValue<ExpnId>>,
unused_generic_params: Table<DefIndex, LazyValue<FiniteBitSet<u32>>>,
params_in_repr: Table<DefIndex, LazyValue<BitSet<u32>>>,
repr_options: Table<DefIndex, LazyValue<ReprOptions>>,
// `def_keys` and `def_path_hashes` represent a lazy version of a
// `DefPathTable`. This allows us to avoid deserializing an entire
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
]);
)
}
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/ty/parameterized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
170 changes: 165 additions & 5 deletions compiler/rustc_middle/src/values.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
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()) }
}
}

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 {
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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))
}
_ => {}
}
}
Loading

0 comments on commit bba9785

Please sign in to comment.