summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_ty_utils/src/representability.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_ty_utils/src/representability.rs')
-rw-r--r--compiler/rustc_ty_utils/src/representability.rs386
1 files changed, 386 insertions, 0 deletions
diff --git a/compiler/rustc_ty_utils/src/representability.rs b/compiler/rustc_ty_utils/src/representability.rs
new file mode 100644
index 000000000..eded78916
--- /dev/null
+++ b/compiler/rustc_ty_utils/src/representability.rs
@@ -0,0 +1,386 @@
+//! Check whether a type is representable.
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_span::Span;
+use std::cmp;
+
+/// Describes whether a type is representable. For types that are not
+/// representable, 'SelfRecursive' and 'ContainsRecursive' are used to
+/// distinguish between types that are recursive with themselves and types that
+/// contain a different recursive type. These cases can therefore be treated
+/// differently when reporting errors.
+///
+/// The ordering of the cases is significant. They are sorted so that cmp::max
+/// will keep the "more erroneous" of two values.
+#[derive(Clone, PartialOrd, Ord, Eq, PartialEq, Debug)]
+pub enum Representability {
+ Representable,
+ ContainsRecursive,
+ /// Return a list of types that are included in themselves:
+ /// the spans where they are self-included, and (if found)
+ /// the HirId of the FieldDef that defines the self-inclusion.
+ SelfRecursive(Vec<(Span, Option<hir::HirId>)>),
+}
+
+/// Check whether a type is representable. This means it cannot contain unboxed
+/// structural recursion. This check is needed for structs and enums.
+pub fn ty_is_representable<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ ty: Ty<'tcx>,
+ sp: Span,
+ field_id: Option<hir::HirId>,
+) -> Representability {
+ debug!("is_type_representable: {:?}", ty);
+ // To avoid a stack overflow when checking an enum variant or struct that
+ // contains a different, structurally recursive type, maintain a stack of
+ // seen types and check recursion for each of them (issues #3008, #3779,
+ // #74224, #84611). `shadow_seen` contains the full stack and `seen` only
+ // the one for the current type (e.g. if we have structs A and B, B contains
+ // a field of type A, and we're currently looking at B, then `seen` will be
+ // cleared when recursing to check A, but `shadow_seen` won't, so that we
+ // can catch cases of mutual recursion where A also contains B).
+ let mut seen: Vec<Ty<'_>> = Vec::new();
+ let mut shadow_seen: Vec<ty::AdtDef<'tcx>> = Vec::new();
+ let mut representable_cache = FxHashMap::default();
+ let mut force_result = false;
+ let r = is_type_structurally_recursive(
+ tcx,
+ &mut seen,
+ &mut shadow_seen,
+ &mut representable_cache,
+ ty,
+ sp,
+ field_id,
+ &mut force_result,
+ );
+ debug!("is_type_representable: {:?} is {:?}", ty, r);
+ r
+}
+
+// Iterate until something non-representable is found
+fn fold_repr<It: Iterator<Item = Representability>>(iter: It) -> Representability {
+ iter.fold(Representability::Representable, |r1, r2| match (r1, r2) {
+ (Representability::SelfRecursive(v1), Representability::SelfRecursive(v2)) => {
+ Representability::SelfRecursive(v1.into_iter().chain(v2).collect())
+ }
+ (r1, r2) => cmp::max(r1, r2),
+ })
+}
+
+fn are_inner_types_recursive<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ seen: &mut Vec<Ty<'tcx>>,
+ shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
+ representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
+ ty: Ty<'tcx>,
+ sp: Span,
+ field_id: Option<hir::HirId>,
+ force_result: &mut bool,
+) -> Representability {
+ debug!("are_inner_types_recursive({:?}, {:?}, {:?})", ty, seen, shadow_seen);
+ match ty.kind() {
+ ty::Tuple(fields) => {
+ // Find non representable
+ fold_repr(fields.iter().map(|ty| {
+ is_type_structurally_recursive(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ ty,
+ sp,
+ field_id,
+ force_result,
+ )
+ }))
+ }
+ // Fixed-length vectors.
+ // FIXME(#11924) Behavior undecided for zero-length vectors.
+ ty::Array(ty, _) => is_type_structurally_recursive(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ *ty,
+ sp,
+ field_id,
+ force_result,
+ ),
+ ty::Adt(def, substs) => {
+ // Find non representable fields with their spans
+ fold_repr(def.all_fields().map(|field| {
+ let ty = field.ty(tcx, substs);
+ let (sp, field_id) = match field
+ .did
+ .as_local()
+ .map(|id| tcx.hir().local_def_id_to_hir_id(id))
+ .and_then(|id| tcx.hir().find(id))
+ {
+ Some(hir::Node::Field(field)) => (field.ty.span, Some(field.hir_id)),
+ _ => (sp, field_id),
+ };
+
+ let mut result = None;
+
+ // First, we check whether the field type per se is representable.
+ // This catches cases as in #74224 and #84611. There is a special
+ // case related to mutual recursion, though; consider this example:
+ //
+ // struct A<T> {
+ // z: T,
+ // x: B<T>,
+ // }
+ //
+ // struct B<T> {
+ // y: A<T>
+ // }
+ //
+ // Here, without the following special case, both A and B are
+ // ContainsRecursive, which is a problem because we only report
+ // errors for SelfRecursive. We fix this by detecting this special
+ // case (shadow_seen.first() is the type we are originally
+ // interested in, and if we ever encounter the same AdtDef again,
+ // we know that it must be SelfRecursive) and "forcibly" returning
+ // SelfRecursive (by setting force_result, which tells the calling
+ // invocations of are_inner_types_representable to forward the
+ // result without adjusting).
+ if shadow_seen.len() > seen.len() && shadow_seen.first() == Some(def) {
+ *force_result = true;
+ result = Some(Representability::SelfRecursive(vec![(sp, field_id)]));
+ }
+
+ if result == None {
+ result = Some(Representability::Representable);
+
+ // Now, we check whether the field types per se are representable, e.g.
+ // for struct Foo { x: Option<Foo> }, we first check whether Option<_>
+ // by itself is representable (which it is), and the nesting of Foo
+ // will be detected later. This is necessary for #74224 and #84611.
+
+ // If we have encountered an ADT definition that we have not seen
+ // before (no need to check them twice), recurse to see whether that
+ // definition is SelfRecursive. If so, we must be ContainsRecursive.
+ if shadow_seen.len() > 1
+ && !shadow_seen
+ .iter()
+ .take(shadow_seen.len() - 1)
+ .any(|seen_def| seen_def == def)
+ {
+ let adt_def_id = def.did();
+ let raw_adt_ty = tcx.type_of(adt_def_id);
+ debug!("are_inner_types_recursive: checking nested type: {:?}", raw_adt_ty);
+
+ // Check independently whether the ADT is SelfRecursive. If so,
+ // we must be ContainsRecursive (except for the special case
+ // mentioned above).
+ let mut nested_seen: Vec<Ty<'_>> = vec![];
+ result = Some(
+ match is_type_structurally_recursive(
+ tcx,
+ &mut nested_seen,
+ shadow_seen,
+ representable_cache,
+ raw_adt_ty,
+ sp,
+ field_id,
+ force_result,
+ ) {
+ Representability::SelfRecursive(_) => {
+ if *force_result {
+ Representability::SelfRecursive(vec![(sp, field_id)])
+ } else {
+ Representability::ContainsRecursive
+ }
+ }
+ x => x,
+ },
+ );
+ }
+
+ // We only enter the following block if the type looks representable
+ // so far. This is necessary for cases such as this one (#74224):
+ //
+ // struct A<T> {
+ // x: T,
+ // y: A<A<T>>,
+ // }
+ //
+ // struct B {
+ // z: A<usize>
+ // }
+ //
+ // When checking B, we recurse into A and check field y of type
+ // A<A<usize>>. We haven't seen this exact type before, so we recurse
+ // into A<A<usize>>, which contains, A<A<A<usize>>>, and so forth,
+ // ad infinitum. We can prevent this from happening by first checking
+ // A separately (the code above) and only checking for nested Bs if
+ // A actually looks representable (which it wouldn't in this example).
+ if result == Some(Representability::Representable) {
+ // Now, even if the type is representable (e.g. Option<_>),
+ // it might still contribute to a recursive type, e.g.:
+ // struct Foo { x: Option<Option<Foo>> }
+ // These cases are handled by passing the full `seen`
+ // stack to is_type_structurally_recursive (instead of the
+ // empty `nested_seen` above):
+ result = Some(
+ match is_type_structurally_recursive(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ ty,
+ sp,
+ field_id,
+ force_result,
+ ) {
+ Representability::SelfRecursive(_) => {
+ Representability::SelfRecursive(vec![(sp, field_id)])
+ }
+ x => x,
+ },
+ );
+ }
+ }
+
+ result.unwrap()
+ }))
+ }
+ ty::Closure(..) => {
+ // this check is run on type definitions, so we don't expect
+ // to see closure types
+ bug!("requires check invoked on inapplicable type: {:?}", ty)
+ }
+ _ => Representability::Representable,
+ }
+}
+
+fn same_adt<'tcx>(ty: Ty<'tcx>, def: ty::AdtDef<'tcx>) -> bool {
+ match *ty.kind() {
+ ty::Adt(ty_def, _) => ty_def == def,
+ _ => false,
+ }
+}
+
+// Does the type `ty` directly (without indirection through a pointer)
+// contain any types on stack `seen`?
+fn is_type_structurally_recursive<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ seen: &mut Vec<Ty<'tcx>>,
+ shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
+ representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
+ ty: Ty<'tcx>,
+ sp: Span,
+ field_id: Option<hir::HirId>,
+ force_result: &mut bool,
+) -> Representability {
+ debug!("is_type_structurally_recursive: {:?} {:?} {:?}", ty, sp, field_id);
+ if let Some(representability) = representable_cache.get(&ty) {
+ debug!(
+ "is_type_structurally_recursive: {:?} {:?} {:?} - (cached) {:?}",
+ ty, sp, field_id, representability
+ );
+ return representability.clone();
+ }
+
+ let representability = is_type_structurally_recursive_inner(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ ty,
+ sp,
+ field_id,
+ force_result,
+ );
+
+ representable_cache.insert(ty, representability.clone());
+ representability
+}
+
+fn is_type_structurally_recursive_inner<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ seen: &mut Vec<Ty<'tcx>>,
+ shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
+ representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
+ ty: Ty<'tcx>,
+ sp: Span,
+ field_id: Option<hir::HirId>,
+ force_result: &mut bool,
+) -> Representability {
+ match ty.kind() {
+ ty::Adt(def, _) => {
+ {
+ debug!("is_type_structurally_recursive_inner: adt: {:?}, seen: {:?}", ty, seen);
+
+ // Iterate through stack of previously seen types.
+ let mut iter = seen.iter();
+
+ // The first item in `seen` is the type we are actually curious about.
+ // We want to return SelfRecursive if this type contains itself.
+ // It is important that we DON'T take generic parameters into account
+ // for this check, so that Bar<T> in this example counts as SelfRecursive:
+ //
+ // struct Foo;
+ // struct Bar<T> { x: Bar<Foo> }
+
+ if let Some(&seen_adt) = iter.next() {
+ if same_adt(seen_adt, *def) {
+ debug!("SelfRecursive: {:?} contains {:?}", seen_adt, ty);
+ return Representability::SelfRecursive(vec![(sp, field_id)]);
+ }
+ }
+
+ // We also need to know whether the first item contains other types
+ // that are structurally recursive. If we don't catch this case, we
+ // will recurse infinitely for some inputs.
+ //
+ // It is important that we DO take generic parameters into account
+ // here, because nesting e.g. Options is allowed (as long as the
+ // definition of Option doesn't itself include an Option field, which
+ // would be a case of SelfRecursive above). The following, too, counts
+ // as SelfRecursive:
+ //
+ // struct Foo { Option<Option<Foo>> }
+
+ for &seen_adt in iter {
+ if ty == seen_adt {
+ debug!("ContainsRecursive: {:?} contains {:?}", seen_adt, ty);
+ return Representability::ContainsRecursive;
+ }
+ }
+ }
+
+ // For structs and enums, track all previously seen types by pushing them
+ // onto the 'seen' stack.
+ seen.push(ty);
+ shadow_seen.push(*def);
+ let out = are_inner_types_recursive(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ ty,
+ sp,
+ field_id,
+ force_result,
+ );
+ shadow_seen.pop();
+ seen.pop();
+ out
+ }
+ _ => {
+ // No need to push in other cases.
+ are_inner_types_recursive(
+ tcx,
+ seen,
+ shadow_seen,
+ representable_cache,
+ ty,
+ sp,
+ field_id,
+ force_result,
+ )
+ }
+ }
+}