[gccrs COMMIT] gccrs: Recursive types support

gerris.rs@gmail.com gerris.rs@gmail.com
Fri Sep 18 09:55:45 GMT 2026


From: Philip Herron <herron.philip@googlemail.com>

This patch implements recusive ADT's support the basic principle is that during typecheck
like for each type check item we register the ADTType with an empty set of variants so that
when we process each variants field types we can allow them to resolve to the root ADT in
place which will get updated as variants are added. Then we need a bunch of recusion guards
for the general mechanics of how we do things like is_concrete etc.

We also need the new ActiveADT's guard to track any recusion during unify. The akward bit was
during code-gen we need to make sure to do a similar thing with GCC using make_node so any
recusive nodes get caught by the recusion guard and then we can walk the variants and fill them
in when its fully layed out.

Fixes Rust-GCC/gccrs#1178
Fixes Rust-GCC/gccrs#2757
Fixes Rust-GCC/gccrs#4709

gcc/rust/ChangeLog:

	* backend/rust-compile-context.cc (Context::type_hasher): track active adt's
	* backend/rust-compile-context.h: likewise
	* backend/rust-compile-type.cc (type_reaches_adt): likewise
	(is_recursive_adt): new helper
	(TyTyResolveCompile::compile): check for active adt
	(TyTyResolveCompile::visit): setup placeholder node then layout
	* rust-session-manager.cc (Session::compile_crate): saw errors early return
	* typecheck/rust-hir-type-check-item.cc (validate_adt_size): new helper
	(TypeCheckItem::visit): setup type node then add variants
	* typecheck/rust-hir-type-check-type.cc (TypeCheckType::visit): check for incomplete
	* typecheck/rust-hir-type-check.h: new helpers
	* typecheck/rust-typecheck-context.cc (make_adt_substitution_key): likewise
	* typecheck/rust-tyty.cc (BaseType::monomorphized_clone): early return
	(BaseType::contains_infer): track visited for recursive safety
	(BaseType::is_concrete): likewise
	(ADTType::is_equal): likewise
	(ADTType::handle_substitions): likewise
	(FnPtr::as_string): likewise
	(PointerType::as_string): likewise
	* typecheck/rust-tyty.h: recusision safety
	* typecheck/rust-unify.cc (UnifyRules::UnifyRules): new ActiveADT's guard
	(UnifyRules::Resolve): likewise
	(UnifyRules::resolve_subtype): likewise
	(UnifyRules::expect_adt): likewise
	* typecheck/rust-unify.h: likewise

gcc/testsuite/ChangeLog:

	* rust/compile/issues/issue-1178.rs: New test.
	* rust/compile/issues/issue-2757.rs: New test.
	* rust/compile/issues/issue-4709.rs: New test.
	* rust/compile/recursive-types-infinite.rs: New test.
	* rust/compile/recursive-types-mismatch.rs: New test.
	* rust/compile/recusive-types-1.rs: New test.
	* rust/compile/recusive-types-2.rs: New test.
	* rust/compile/recusive-types-3.rs: New test.
	* rust/compile/recusive-types-4.rs: New test.
	* rust/compile/recusive-types-5.rs: New test.
	* rust/compile/recusive-types-6.rs: New test.
	* rust/compile/recusive-types-7.rs: New test.
	* rust/execute/recursive-types-fnptr.rs: New test.

Signed-off-by: Philip Herron <herron.philip@googlemail.com>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: https://github.com/Rust-GCC/gccrs/commit/50e9221c4a02ba0b4add6f5fd6f0f13cf4e11db1

The commit has been mentioned in the following issue(s):
 - Rust-GCC/gccrs#1178: https://github.com/Rust-GCC/gccrs/issues/1178
 - Rust-GCC/gccrs#2757: https://github.com/Rust-GCC/gccrs/issues/2757
 - Rust-GCC/gccrs#4709: https://github.com/Rust-GCC/gccrs/issues/4709

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4905

 gcc/rust/backend/rust-compile-context.cc      |  21 +-
 gcc/rust/backend/rust-compile-context.h       |  34 ++
 gcc/rust/backend/rust-compile-type.cc         | 144 +++++++-
 gcc/rust/rust-session-manager.cc              |   3 +
 .../typecheck/rust-hir-type-check-item.cc     | 318 ++++++++++++------
 .../typecheck/rust-hir-type-check-type.cc     |  13 +-
 gcc/rust/typecheck/rust-hir-type-check.h      |  11 +
 gcc/rust/typecheck/rust-typecheck-context.cc  |  37 ++
 gcc/rust/typecheck/rust-tyty.cc               | 118 ++++---
 gcc/rust/typecheck/rust-tyty.h                |   4 +
 gcc/rust/typecheck/rust-unify.cc              |  25 +-
 gcc/rust/typecheck/rust-unify.h               |  49 ++-
 .../rust/compile/issues/issue-1178.rs         |   8 +
 .../rust/compile/issues/issue-2757.rs         |   7 +
 .../rust/compile/issues/issue-4709.rs         |   8 +
 .../rust/compile/recursive-types-infinite.rs  |  14 +
 .../rust/compile/recursive-types-mismatch.rs  |  20 ++
 .../rust/compile/recusive-types-1.rs          |  23 ++
 .../rust/compile/recusive-types-2.rs          |  16 +
 .../rust/compile/recusive-types-3.rs          |  12 +
 .../rust/compile/recusive-types-4.rs          |  16 +
 .../rust/compile/recusive-types-5.rs          |  17 +
 .../rust/compile/recusive-types-6.rs          |  26 ++
 .../rust/compile/recusive-types-7.rs          |  10 +
 .../rust/execute/recursive-types-fnptr.rs     |  14 +
 25 files changed, 801 insertions(+), 167 deletions(-)
 create mode 100644 gcc/testsuite/rust/compile/issues/issue-1178.rs
 create mode 100644 gcc/testsuite/rust/compile/issues/issue-2757.rs
 create mode 100644 gcc/testsuite/rust/compile/issues/issue-4709.rs
 create mode 100644 gcc/testsuite/rust/compile/recursive-types-infinite.rs
 create mode 100644 gcc/testsuite/rust/compile/recursive-types-mismatch.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-1.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-2.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-3.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-4.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-5.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-6.rs
 create mode 100644 gcc/testsuite/rust/compile/recusive-types-7.rs
 create mode 100644 gcc/testsuite/rust/execute/recursive-types-fnptr.rs

diff --git a/gcc/rust/backend/rust-compile-context.cc b/gcc/rust/backend/rust-compile-context.cc
index 0ae57de4c..fe28a478c 100644
--- a/gcc/rust/backend/rust-compile-context.cc
+++ b/gcc/rust/backend/rust-compile-context.cc
@@ -48,6 +48,13 @@ Context::setup_builtins ()
 
 hashval_t
 Context::type_hasher (tree type)
+{
+  hash_set<tree> active_types;
+  return type_hasher (type, active_types);
+}
+
+hashval_t
+Context::type_hasher (tree type, hash_set<tree> &active_types)
 {
   inchash::hash hstate;
 
@@ -64,6 +71,15 @@ Context::type_hasher (tree type)
     /* Just the identifier is adequate to distinguish.  */
     hstate.add_object (IDENTIFIER_HASH_VALUE (TREE_PURPOSE (t)));
 
+  /* recursive guard */
+  bool found = active_types.contains (type);
+  if (found)
+    {
+      hstate.add_int (TYPE_UID (TYPE_MAIN_VARIANT (type)));
+      return hstate.end ();
+    }
+  active_types.add (type);
+
   switch (TREE_CODE (type))
     {
     case METHOD_TYPE:
@@ -120,7 +136,7 @@ Context::type_hasher (tree type)
 	for (tree t = TYPE_FIELDS (type); t; t = TREE_CHAIN (t))
 	  {
 	    hashval_t name_hash = IDENTIFIER_HASH_VALUE (DECL_NAME (t));
-	    hashval_t type_hash = type_hasher (TREE_TYPE (t));
+	    hashval_t type_hash = type_hasher (TREE_TYPE (t), active_types);
 	    hstate.add_object (name_hash);
 	    hstate.add_object (type_hash);
 	  }
@@ -133,7 +149,7 @@ Context::type_hasher (tree type)
     case REFERENCE_TYPE:
     case POINTER_TYPE:
       {
-	hashval_t type_hash = type_hasher (TREE_TYPE (type));
+	hashval_t type_hash = type_hasher (TREE_TYPE (type), active_types);
 	hstate.add_object (type_hash);
       }
       break;
@@ -142,6 +158,7 @@ Context::type_hasher (tree type)
       break;
     }
 
+  active_types.remove (type);
   return hstate.end ();
 }
 
diff --git a/gcc/rust/backend/rust-compile-context.h b/gcc/rust/backend/rust-compile-context.h
index d3d4ec744..dba6791a3 100644
--- a/gcc/rust/backend/rust-compile-context.h
+++ b/gcc/rust/backend/rust-compile-context.h
@@ -87,6 +87,28 @@ public:
     return type;
   }
 
+  bool lookup_compiled_adt (const TyTy::ADTType &adt, tree *type) const
+  {
+    auto it = compiled_adt_types.find (compiled_adt_key (adt));
+    if (it == compiled_adt_types.end ())
+      return false;
+
+    *type = it->second;
+    return true;
+  }
+
+  void insert_compiled_adt (const TyTy::ADTType &adt, tree type)
+  {
+    auto key = compiled_adt_key (adt);
+    rust_assert (compiled_adt_types.find (key) == compiled_adt_types.end ());
+    compiled_adt_types.insert ({key, type});
+  }
+
+  void erase_compiled_adt (const TyTy::ADTType &adt)
+  {
+    rust_assert (compiled_adt_types.erase (compiled_adt_key (adt)) == 1);
+  }
+
   tree insert_main_variant (tree type)
   {
     hashval_t h = type_hasher (type);
@@ -478,6 +500,8 @@ private:
   friend class DropBuilder;
   Context ();
 
+  static hashval_t type_hasher (tree type, hash_set<tree> &active_types);
+
   tree pop_block_impl (tree cleanup, location_t cleanup_locus)
   {
     auto block = scope_stack.back ();
@@ -512,6 +536,15 @@ private:
     return block;
   }
 
+  // we cant just use DefId because we can setup Adt<u32> vs Adt<i32> so the
+  // tyref gets us the uniqueness we need
+  static std::pair<DefId, HirId> compiled_adt_key (const TyTy::ADTType &adt)
+  {
+    HirId substitution
+      = adt.has_substitutions_defined () ? adt.get_ty_ref () : UNKNOWN_HIRID;
+    return {adt.get_id (), substitution};
+  }
+
   Resolver::TypeCheckContext *tyctx;
   Analysis::Mappings &mappings;
   Mangler mangler;
@@ -521,6 +554,7 @@ private:
   std::map<HirId, ::Bvariable *> compiled_var_decls;
   std::map<std::pair<tree, HirId>, ::Bvariable *> drop_flags;
   std::map<hashval_t, tree> compiled_type_map;
+  std::map<std::pair<DefId, HirId>, tree> compiled_adt_types;
   std::map<HirId, tree> compiled_fn_map;
   std::map<HirId, tree> compiled_consts;
   std::map<HirId, tree> compiled_labels;
diff --git a/gcc/rust/backend/rust-compile-type.cc b/gcc/rust/backend/rust-compile-type.cc
index 4b39a44f1..126705c5d 100644
--- a/gcc/rust/backend/rust-compile-type.cc
+++ b/gcc/rust/backend/rust-compile-type.cc
@@ -17,8 +17,6 @@
 // <http://www.gnu.org/licenses/>.
 
 #include "rust-compile-type.h"
-#include "rust-constexpr.h"
-#include "rust-compile-base.h"
 #include "rust-type-util.h"
 
 #include "rust-tyty.h"
@@ -31,6 +29,90 @@ namespace Compile {
 
 static const std::string RUST_ENUM_DISR_FIELD_NAME = "RUST$ENUM$DISR";
 
+static bool
+type_reaches_adt (TyTy::BaseType *type, DefId target, bool indirect,
+		  std::set<std::pair<DefId, bool>> &visited)
+{
+  type = type->destructure ();
+  switch (type->get_kind ())
+    {
+    case TyTy::TypeKind::POINTER:
+      return type_reaches_adt (
+	static_cast<TyTy::PointerType *> (type)->get_base (), target, true,
+	visited);
+
+    case TyTy::TypeKind::REF:
+      return type_reaches_adt (
+	static_cast<TyTy::ReferenceType *> (type)->get_base (), target, true,
+	visited);
+
+    case TyTy::TypeKind::TUPLE:
+      {
+	auto *tuple = static_cast<TyTy::TupleType *> (type);
+	for (size_t i = 0; i < tuple->num_fields (); i++)
+	  {
+	    if (type_reaches_adt (tuple->get_field (i), target, indirect,
+				  visited))
+	      return true;
+	  }
+	return false;
+      }
+
+    case TyTy::TypeKind::ARRAY:
+      return type_reaches_adt (
+	static_cast<TyTy::ArrayType *> (type)->get_element_type (), target,
+	indirect, visited);
+
+    case TyTy::TypeKind::FNPTR:
+      {
+	auto *fn = static_cast<TyTy::FnPtr *> (type);
+	for (const auto &param : fn->get_params ())
+	  {
+	    if (type_reaches_adt (param.get_tyty (), target, true, visited))
+	      return true;
+	  }
+
+	return type_reaches_adt (fn->get_return_type (), target, true, visited);
+      }
+
+    case TyTy::TypeKind::ADT:
+      break;
+
+    default:
+      return false;
+    }
+
+  auto *adt = static_cast<TyTy::ADTType *> (type);
+  if (adt->get_id () == target && indirect)
+    return true;
+
+  auto key = std::make_pair (adt->get_id (), indirect);
+  bool found = visited.find (key) != visited.end ();
+  if (found)
+    return false;
+  visited.insert (key);
+
+  for (auto *variant : adt->get_variants ())
+    {
+      for (auto *field : variant->get_fields ())
+	{
+	  if (type_reaches_adt (field->get_field_type (), target, indirect,
+				visited))
+	    return true;
+	}
+    }
+
+  return false;
+}
+
+static bool
+is_recursive_adt (const TyTy::ADTType &type)
+{
+  std::set<std::pair<DefId, bool>> visited;
+  return type_reaches_adt (const_cast<TyTy::ADTType *> (&type), type.get_id (),
+			   false, visited);
+}
+
 TyTyResolveCompile::TyTyResolveCompile (Context *ctx, bool trait_object_mode)
   : ctx (ctx), trait_object_mode (trait_object_mode),
     translated (error_mark_node)
@@ -42,6 +124,14 @@ TyTyResolveCompile::compile (Context *ctx, const TyTy::BaseType *ty,
 {
   TyTyResolveCompile compiler (ctx, trait_object_mode);
   const TyTy::BaseType *destructured = ty->destructure ();
+
+  if (const auto *adt = destructured->try_as<const TyTy::ADTType> ())
+    {
+      tree compiled_adt = NULL_TREE;
+      if (ctx->lookup_compiled_adt (*adt, &compiled_adt))
+	return compiled_adt;
+    }
+
   destructured->accept_vis (compiler);
 
   if (compiler.translated != error_mark_node
@@ -297,6 +387,24 @@ TyTyResolveCompile::visit (const TyTy::ADTType &type)
   tree type_record = error_mark_node;
 
   TyTy::ADTType::ReprOptions repr = type.get_repr_options ();
+  bool cached_incomplete_record = false;
+
+  if (is_recursive_adt (type)
+      && repr.repr_kind != TyTy::ADTType::ReprKind::TRANSPARENT
+      && repr.repr_kind != TyTy::ADTType::ReprKind::SIMD)
+    {
+      tree aggregate = make_node (type.is_union () ? UNION_TYPE : RECORD_TYPE);
+      std::string name
+	= type.get_ident ().path.get () + type.subst_as_string ();
+
+      tree decl = build_decl (type.get_ident ().locus, TYPE_DECL,
+			      Backend::get_identifier_node (name), aggregate);
+      TYPE_NAME (aggregate) = decl;
+      type_record = aggregate;
+      ctx->insert_compiled_adt (type, type_record);
+      cached_incomplete_record = true;
+    }
+
   if (repr.repr_kind == TyTy::ADTType::ReprKind::TRANSPARENT)
     {
       rust_assert (type.number_of_variants () == 1);
@@ -400,8 +508,11 @@ TyTyResolveCompile::visit (const TyTy::ADTType &type)
 				   type.get_locus ());
 	    }
 	}
-      type_record = type.is_union () ? Backend::union_type (fields, false)
-				     : Backend::struct_type (fields, false);
+      if (cached_incomplete_record)
+	type_record = Backend::fill_in_fields (type_record, fields, false);
+      else
+	type_record = type.is_union () ? Backend::union_type (fields, false)
+				       : Backend::struct_type (fields, false);
     }
   else
     {
@@ -523,7 +634,10 @@ TyTyResolveCompile::visit (const TyTy::ADTType &type)
 
       std::vector<Backend::typed_identifier> fields
 	= {discrim, variants_union_field};
-      type_record = Backend::struct_type (fields, false);
+      if (cached_incomplete_record)
+	type_record = Backend::fill_in_fields (type_record, fields, false);
+      else
+	type_record = Backend::struct_type (fields, false);
     }
 
   // Handle repr options
@@ -549,10 +663,22 @@ TyTyResolveCompile::visit (const TyTy::ADTType &type)
       layout_type (type_record);
     }
 
-  std::string named_struct_str
-    = type.get_ident ().path.get () + type.subst_as_string ();
-  translated = Backend::named_type (named_struct_str, type_record,
-				    type.get_ident ().locus);
+  if (cached_incomplete_record)
+    {
+      // see gcc/c/c-decl.cc this sets up any incomplete variants of the tree
+      for (tree variant = TYPE_NEXT_VARIANT (type_record); variant != NULL_TREE;
+	   variant = TYPE_NEXT_VARIANT (variant))
+	TYPE_FIELDS (variant) = TYPE_FIELDS (type_record);
+
+      translated = type_record;
+    }
+  else
+    {
+      std::string named_struct_str
+	= type.get_ident ().path.get () + type.subst_as_string ();
+      translated = Backend::named_type (named_struct_str, type_record,
+					type.get_ident ().locus);
+    }
 }
 
 void
diff --git a/gcc/rust/rust-session-manager.cc b/gcc/rust/rust-session-manager.cc
index 45fe3ede7..4f4f8d411 100644
--- a/gcc/rust/rust-session-manager.cc
+++ b/gcc/rust/rust-session-manager.cc
@@ -809,6 +809,9 @@ Session::compile_crate (const char *filename)
   Compile::Context *ctx = Compile::Context::get ();
   Resolver::TypeResolution::Resolve (hir);
 
+  if (saw_errors ())
+    return;
+
   Resolver::TypeCheckContext::get ()->get_variance_analysis_ctx ().solve ();
 
   if (saw_errors ())
diff --git a/gcc/rust/typecheck/rust-hir-type-check-item.cc b/gcc/rust/typecheck/rust-hir-type-check-item.cc
index 63f970dfe..5f54c0726 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-item.cc
+++ b/gcc/rust/typecheck/rust-hir-type-check-item.cc
@@ -43,6 +43,87 @@
 namespace Rust {
 namespace Resolver {
 
+static bool
+type_contains_adt_without_indirection (
+  const TyTy::BaseType *type, DefId target,
+  std::set<const TyTy::BaseType *> &visited)
+{
+  bool found = visited.find (type) != visited.end ();
+  if (found)
+    return false;
+  visited.insert (type);
+
+  switch (type->get_kind ())
+    {
+    case TyTy::TypeKind::ADT:
+      {
+	auto *adt = static_cast<const TyTy::ADTType *> (type);
+	if (adt->get_id () == target)
+	  return true;
+
+	for (auto *variant : adt->get_variants ())
+	  {
+	    for (auto *field : variant->get_fields ())
+	      {
+		if (type_contains_adt_without_indirection (
+		      field->get_field_type (), target, visited))
+		  return true;
+	      }
+	  }
+
+	return false;
+      }
+
+    case TyTy::TypeKind::TUPLE:
+      {
+	auto *tuple = static_cast<const TyTy::TupleType *> (type);
+	for (const auto &field : tuple->get_fields ())
+	  {
+	    if (type_contains_adt_without_indirection (field.get_tyty (),
+						       target, visited))
+	      return true;
+	  }
+
+	return false;
+      }
+
+    case TyTy::TypeKind::ARRAY:
+      return type_contains_adt_without_indirection (
+	static_cast<const TyTy::ArrayType *> (type)->get_element_type (),
+	target, visited);
+
+    case TyTy::TypeKind::SLICE:
+      return type_contains_adt_without_indirection (
+	static_cast<const TyTy::SliceType *> (type)->get_element_type (),
+	target, visited);
+
+    default:
+      return false;
+    }
+}
+
+static bool
+validate_adt_size (TyTy::ADTType *type)
+{
+  for (auto *variant : type->get_variants ())
+    {
+      for (auto *field : variant->get_fields ())
+	{
+	  std::set<const TyTy::BaseType *> visited;
+	  if (!type_contains_adt_without_indirection (field->get_field_type (),
+						      type->get_id (), visited))
+	    continue;
+
+	  rust_error_at (field->get_locus (), ErrorCode::E0072,
+			 "recursive type %qs has infinite size",
+			 type->get_identifier ().c_str ());
+	  return false;
+	}
+    }
+
+  return true;
+}
+
 // Const-evaluate the discriminants of a repr(C) enum and warn when a value does
 // not fit into a C int/unsigned int. Done here, during type resolution, using
 // the compile context (a singleton shared with the backend).
@@ -335,6 +416,25 @@ TypeCheckItem::visit (HIR::TupleStruct &struct_decl)
   TyTy::ADTType::ReprOptions repr
     = parse_repr_options (attrs, struct_decl.get_locus ());
 
+  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
+  CanonicalPath path
+    = nr_ctx.to_canonical_path (struct_decl.get_mappings ().get_nodeid (),
+				Resolver2_0::Namespace::Types);
+  RustIdent ident{path, struct_decl.get_locus ()};
+
+  std::vector<TyTy::VariantDef *> variants;
+  auto *type = new TyTy::ADTType (
+    struct_decl.get_mappings ().get_defid (),
+    struct_decl.get_mappings ().get_hirid (),
+    struct_decl.get_mappings ().get_hirid (),
+    struct_decl.get_identifier ().as_string (), ident,
+    TyTy::ADTType::ADTKind::TUPLE_STRUCT, std::move (variants),
+    std::move (substitutions), repr,
+    TyTy::SubstitutionArgumentMappings::empty (
+      context->get_lifetime_resolver ().get_num_bound_regions ()),
+    region_constraints);
+  context->insert_type (struct_decl.get_mappings (), type);
+
   std::vector<TyTy::StructFieldType *> fields;
   size_t idx = 0;
   for (auto &field : struct_decl.get_fields ())
@@ -357,37 +457,20 @@ TypeCheckItem::visit (HIR::TupleStruct &struct_decl)
 	return;
     }
 
-  // get the path
-
-  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
-
-  CanonicalPath path
-    = nr_ctx.to_canonical_path (struct_decl.get_mappings ().get_nodeid (),
-				Resolver2_0::Namespace::Types);
-
-  RustIdent ident{path, struct_decl.get_locus ()};
-
   // its a single variant ADT
-  std::vector<TyTy::VariantDef *> variants;
-  variants.push_back (
-    new TyTy::VariantDef (struct_decl.get_mappings ().get_hirid (),
-			  struct_decl.get_mappings ().get_defid (),
-			  struct_decl.get_identifier ().as_string (), ident,
-			  TyTy::VariantDef::VariantType::TUPLE, tl::nullopt,
-			  std::move (fields)));
-
-  auto *type = new TyTy::ADTType (
-    struct_decl.get_mappings ().get_defid (),
-    struct_decl.get_mappings ().get_hirid (),
-    struct_decl.get_mappings ().get_hirid (),
-    struct_decl.get_identifier ().as_string (), ident,
-    TyTy::ADTType::ADTKind::TUPLE_STRUCT, std::move (variants),
-    std::move (substitutions), repr,
-    TyTy::SubstitutionArgumentMappings::empty (
-      context->get_lifetime_resolver ().get_num_bound_regions ()),
-    region_constraints);
-
-  context->insert_type (struct_decl.get_mappings (), type);
+  auto variant
+    = new TyTy::VariantDef (struct_decl.get_mappings ().get_hirid (),
+			    struct_decl.get_mappings ().get_defid (),
+			    struct_decl.get_identifier ().as_string (), ident,
+			    TyTy::VariantDef::VariantType::TUPLE, tl::nullopt,
+			    std::move (fields));
+  type->get_variants ().push_back (variant);
+  if (!validate_adt_size (type))
+    {
+      infered = new TyTy::ErrorType (struct_decl.get_mappings ().get_hirid ());
+      context->insert_type (struct_decl.get_mappings (), infered);
+      return;
+    }
   infered = type;
 
   context->get_variance_analysis_ctx ().add_type_constraints (*type);
@@ -399,6 +482,12 @@ TypeCheckItem::visit (HIR::StructStruct &struct_decl)
   auto lifetime_pin = context->push_clean_lifetime_resolver ();
   auto &mappings = Analysis::Mappings::get ();
 
+  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
+  CanonicalPath path
+    = nr_ctx.to_canonical_path (struct_decl.get_mappings ().get_nodeid (),
+				Resolver2_0::Namespace::Types);
+  RustIdent ident{path, struct_decl.get_locus ()};
+
   std::vector<TyTy::SubstitutionParamMapping> substitutions;
   if (struct_decl.has_generics ())
     resolve_generic_params (HIR::Item::ItemKind::Struct,
@@ -414,6 +503,19 @@ TypeCheckItem::visit (HIR::StructStruct &struct_decl)
   TyTy::ADTType::ReprOptions repr
     = parse_repr_options (attrs, struct_decl.get_locus ());
 
+  std::vector<TyTy::VariantDef *> variants;
+  auto *type = new TyTy::ADTType (
+    struct_decl.get_mappings ().get_defid (),
+    struct_decl.get_mappings ().get_hirid (),
+    struct_decl.get_mappings ().get_hirid (),
+    struct_decl.get_identifier ().as_string (), ident,
+    TyTy::ADTType::ADTKind::STRUCT_STRUCT, std::move (variants),
+    std::move (substitutions), repr,
+    TyTy::SubstitutionArgumentMappings::empty (
+      context->get_lifetime_resolver ().get_num_bound_regions ()),
+    region_constraints);
+  context->insert_type (struct_decl.get_mappings (), type);
+
   std::vector<TyTy::StructFieldType *> fields;
   for (auto &field : struct_decl.get_fields ())
     {
@@ -425,6 +527,9 @@ TypeCheckItem::visit (HIR::StructStruct &struct_decl)
 	  rust_error_at (mappings.lookup_location (infer_type->get_ref ()),
 			 "the placeholder %<_%> is not allowed within types on "
 			 "item signatures for structs");
+	  infered
+	    = new TyTy::ErrorType (struct_decl.get_mappings ().get_hirid ());
+	  context->insert_type (struct_decl.get_mappings (), infered);
 	  return;
 	}
       auto *ty_field
@@ -459,37 +564,22 @@ TypeCheckItem::visit (HIR::StructStruct &struct_decl)
 	}
     }
 
-  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
-
-  CanonicalPath path
-    = nr_ctx.to_canonical_path (struct_decl.get_mappings ().get_nodeid (),
-				Resolver2_0::Namespace::Types);
-
-  RustIdent ident{path, struct_decl.get_locus ()};
-
   // its a single variant ADT
   auto variant_type = struct_decl.is_unit_struct ()
 			? TyTy::VariantDef::VariantType::UNIT
 			: TyTy::VariantDef::VariantType::STRUCT;
-  std::vector<TyTy::VariantDef *> variants;
-  variants.push_back (
-    new TyTy::VariantDef (struct_decl.get_mappings ().get_hirid (),
-			  struct_decl.get_mappings ().get_defid (),
-			  struct_decl.get_identifier ().as_string (), ident,
-			  variant_type, tl::nullopt, std::move (fields)));
-
-  auto *type = new TyTy::ADTType (
-    struct_decl.get_mappings ().get_defid (),
-    struct_decl.get_mappings ().get_hirid (),
-    struct_decl.get_mappings ().get_hirid (),
-    struct_decl.get_identifier ().as_string (), ident,
-    TyTy::ADTType::ADTKind::STRUCT_STRUCT, std::move (variants),
-    std::move (substitutions), repr,
-    TyTy::SubstitutionArgumentMappings::empty (
-      context->get_lifetime_resolver ().get_num_bound_regions ()),
-    region_constraints);
-
-  context->insert_type (struct_decl.get_mappings (), type);
+  auto variant
+    = new TyTy::VariantDef (struct_decl.get_mappings ().get_hirid (),
+			    struct_decl.get_mappings ().get_defid (),
+			    struct_decl.get_identifier ().as_string (), ident,
+			    variant_type, tl::nullopt, std::move (fields));
+  type->get_variants ().push_back (variant);
+  if (!validate_adt_size (type))
+    {
+      infered = new TyTy::ErrorType (struct_decl.get_mappings ().get_hirid ());
+      context->insert_type (struct_decl.get_mappings (), infered);
+      return;
+    }
   infered = type;
 
   context->get_variance_analysis_ctx ().add_type_constraints (*type);
@@ -504,12 +594,35 @@ TypeCheckItem::visit (HIR::Enum &enum_decl)
     resolve_generic_params (HIR::Item::ItemKind::Enum, enum_decl.get_locus (),
 			    enum_decl.get_generic_params (), substitutions);
 
+  TyTy::RegionConstraints region_constraints;
+  ResolveWhereClauseItem::Resolve (enum_decl.get_where_clause (),
+				   region_constraints);
+
+  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
+  CanonicalPath canonical_path
+    = nr_ctx.to_canonical_path (enum_decl.get_mappings ().get_nodeid (),
+				Resolver2_0::Namespace::Types);
+
+  RustIdent ident{canonical_path, enum_decl.get_locus ()};
+
   // Process #[repr(X)] attribute, if any
   const AST::AttrVec &attrs = enum_decl.get_outer_attrs ();
   TyTy::ADTType::ReprOptions repr
     = parse_repr_options (attrs, enum_decl.get_locus ());
 
   std::vector<TyTy::VariantDef *> variants;
+  auto *type = new TyTy::ADTType (
+    enum_decl.get_mappings ().get_defid (),
+    enum_decl.get_mappings ().get_hirid (),
+    enum_decl.get_mappings ().get_hirid (),
+    enum_decl.get_identifier ().as_string (), ident,
+    TyTy::ADTType::ADTKind::ENUM, std::move (variants),
+    std::move (substitutions), repr,
+    TyTy::SubstitutionArgumentMappings::empty (
+      context->get_lifetime_resolver ().get_num_bound_regions ()),
+    region_constraints);
+  context->insert_type (enum_decl.get_mappings (), type);
+
   int64_t discriminant_value = 0;
   for (auto &variant : enum_decl.get_variants ())
     {
@@ -518,7 +631,7 @@ TypeCheckItem::visit (HIR::Enum &enum_decl)
       if (field_type)
 	{
 	  discriminant_value++;
-	  variants.push_back (field_type);
+	  type->get_variants ().push_back (field_type);
 	}
     }
 
@@ -534,25 +647,12 @@ TypeCheckItem::visit (HIR::Enum &enum_decl)
 	}
     }
 
-  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
-
-  // get the path
-  CanonicalPath canonical_path
-    = nr_ctx.to_canonical_path (enum_decl.get_mappings ().get_nodeid (),
-				Resolver2_0::Namespace::Types);
-
-  RustIdent ident{canonical_path, enum_decl.get_locus ()};
-
-  // multi variant ADT
-  auto *type
-    = new TyTy::ADTType (enum_decl.get_mappings ().get_defid (),
-			 enum_decl.get_mappings ().get_hirid (),
-			 enum_decl.get_mappings ().get_hirid (),
-			 enum_decl.get_identifier ().as_string (), ident,
-			 TyTy::ADTType::ADTKind::ENUM, std::move (variants),
-			 std::move (substitutions), repr);
-
-  context->insert_type (enum_decl.get_mappings (), type);
+  if (!validate_adt_size (type))
+    {
+      infered = new TyTy::ErrorType (enum_decl.get_mappings ().get_hirid ());
+      context->insert_type (enum_decl.get_mappings (), infered);
+      return;
+    }
   infered = type;
 
   context->get_variance_analysis_ctx ().add_type_constraints (*type);
@@ -574,6 +674,31 @@ TypeCheckItem::visit (HIR::Union &union_decl)
   ResolveWhereClauseItem::Resolve (union_decl.get_where_clause (),
 				   region_constraints);
 
+  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
+  CanonicalPath canonical_path
+    = nr_ctx.to_canonical_path (union_decl.get_mappings ().get_nodeid (),
+				Resolver2_0::Namespace::Types);
+
+  RustIdent ident{canonical_path, union_decl.get_locus ()};
+
+  // Process #[repr(X)] attribute, if any
+  const AST::AttrVec &attrs = union_decl.get_outer_attrs ();
+  TyTy::ADTType::ReprOptions repr
+    = parse_repr_options (attrs, union_decl.get_locus ());
+
+  std::vector<TyTy::VariantDef *> variants;
+  auto *type = new TyTy::ADTType (
+    union_decl.get_mappings ().get_defid (),
+    union_decl.get_mappings ().get_hirid (),
+    union_decl.get_mappings ().get_hirid (),
+    union_decl.get_identifier ().as_string (), ident,
+    TyTy::ADTType::ADTKind::UNION, std::move (variants),
+    std::move (substitutions), repr,
+    TyTy::SubstitutionArgumentMappings::empty (
+      context->get_lifetime_resolver ().get_num_bound_regions ()),
+    region_constraints);
+  context->insert_type (union_decl.get_mappings (), type);
+
   std::vector<TyTy::StructFieldType *> fields;
   for (auto &variant : union_decl.get_variants ())
     {
@@ -588,33 +713,20 @@ TypeCheckItem::visit (HIR::Union &union_decl)
 			    ty_variant->get_field_type ());
     }
 
-  auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
-
-  // get the path
-  CanonicalPath canonical_path
-    = nr_ctx.to_canonical_path (union_decl.get_mappings ().get_nodeid (),
-				Resolver2_0::Namespace::Types);
-
-  RustIdent ident{canonical_path, union_decl.get_locus ()};
-
-  // there is only a single variant
-  std::vector<TyTy::VariantDef *> variants;
-  variants.push_back (
-    new TyTy::VariantDef (union_decl.get_mappings ().get_hirid (),
-			  union_decl.get_mappings ().get_defid (),
-			  union_decl.get_identifier ().as_string (), ident,
-			  TyTy::VariantDef::VariantType::STRUCT, tl::nullopt,
-			  std::move (fields)));
-
-  auto *type
-    = new TyTy::ADTType (union_decl.get_mappings ().get_defid (),
-			 union_decl.get_mappings ().get_hirid (),
-			 union_decl.get_mappings ().get_hirid (),
-			 union_decl.get_identifier ().as_string (), ident,
-			 TyTy::ADTType::ADTKind::UNION, std::move (variants),
-			 std::move (substitutions));
+  auto variant
+    = new TyTy::VariantDef (union_decl.get_mappings ().get_hirid (),
+			    union_decl.get_mappings ().get_defid (),
+			    union_decl.get_identifier ().as_string (), ident,
+			    TyTy::VariantDef::VariantType::STRUCT, tl::nullopt,
+			    std::move (fields));
+  type->get_variants ().push_back (variant);
+  if (!validate_adt_size (type))
+    {
+      infered = new TyTy::ErrorType (union_decl.get_mappings ().get_hirid ());
+      context->insert_type (union_decl.get_mappings (), infered);
+      return;
+    }
 
-  context->insert_type (union_decl.get_mappings (), type);
   infered = type;
 
   context->get_variance_analysis_ctx ().add_type_constraints (*type);
diff --git a/gcc/rust/typecheck/rust-hir-type-check-type.cc b/gcc/rust/typecheck/rust-hir-type-check-type.cc
index c60d419e5..8bf304fa0 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-type.cc
+++ b/gcc/rust/typecheck/rust-hir-type-check-type.cc
@@ -156,8 +156,17 @@ TypeCheckType::visit (HIR::TypePath &path)
   TyTy::BaseType *path_type = root;
   if (mode == ResolutionMode::REFERENCE)
     {
-      path_type = root->clone ();
-      path_type->set_ref (path.get_mappings ().get_hirid ());
+      // A recursive path must keep referring to the in-progress nominal ADT.
+      // Cloning its currently empty variant list would leave a permanently
+      // incomplete snapshot in the recursive field.
+      auto *root_adt = root->try_as<TyTy::ADTType> ();
+      bool recursive_root
+	= root_adt != nullptr && root_adt->get_variants ().empty ();
+      if (!recursive_root)
+	{
+	  path_type = root->clone ();
+	  path_type->set_ref (path.get_mappings ().get_hirid ());
+	}
       context->insert_implicit_type (path.get_mappings ().get_hirid (),
 				     path_type);
     }
diff --git a/gcc/rust/typecheck/rust-hir-type-check.h b/gcc/rust/typecheck/rust-hir-type-check.h
index 4efbaa893..c9572b13c 100644
--- a/gcc/rust/typecheck/rust-hir-type-check.h
+++ b/gcc/rust/typecheck/rust-hir-type-check.h
@@ -215,6 +215,15 @@ public:
 
   void insert_implicit_type (HirId id, TyTy::BaseType *type);
 
+  bool
+  lookup_adt_substitution (DefId id,
+			   const TyTy::SubstitutionArgumentMappings &mappings,
+			   TyTy::ADTType **type) const;
+  void
+  insert_adt_substitution (DefId id,
+			   const TyTy::SubstitutionArgumentMappings &mappings,
+			   TyTy::ADTType *type);
+
   void insert_type_by_node_id (NodeId ref, HirId id);
   bool lookup_type_by_node_id (NodeId ref, HirId *id);
 
@@ -343,6 +352,8 @@ private:
 
   std::map<NodeId, HirId> node_id_refs;
   std::map<HirId, TyTy::BaseType *> resolved;
+  std::map<std::pair<DefId, std::vector<HirId>>, TyTy::ADTType *>
+    adt_substitutions;
   std::set<DefId> function_bodies_pending;
   std::vector<std::unique_ptr<TyTy::BaseType>> builtins;
   std::vector<std::pair<TypeCheckContextItem, TyTy::BaseType *>>
diff --git a/gcc/rust/typecheck/rust-typecheck-context.cc b/gcc/rust/typecheck/rust-typecheck-context.cc
index a1f10ea29..6f364a4ac 100644
--- a/gcc/rust/typecheck/rust-typecheck-context.cc
+++ b/gcc/rust/typecheck/rust-typecheck-context.cc
@@ -98,6 +98,43 @@ TypeCheckContext::insert_implicit_type (HirId id, TyTy::BaseType *type)
   resolved[id] = type;
 }
 
+static std::pair<DefId, std::vector<HirId>>
+make_adt_substitution_key (DefId id,
+			   const TyTy::SubstitutionArgumentMappings &mappings)
+{
+  std::vector<HirId> arguments;
+  arguments.reserve (mappings.size ());
+  for (const auto &mapping : mappings.get_mappings ())
+    arguments.push_back (mapping.get_tyty ()->get_ty_ref ());
+
+  return {id, std::move (arguments)};
+}
+
+bool
+TypeCheckContext::lookup_adt_substitution (
+  DefId id, const TyTy::SubstitutionArgumentMappings &mappings,
+  TyTy::ADTType **type) const
+{
+  auto it = adt_substitutions.find (make_adt_substitution_key (id, mappings));
+  if (it == adt_substitutions.end ())
+    return false;
+
+  *type = it->second;
+  return true;
+}
+
+void
+TypeCheckContext::insert_adt_substitution (
+  DefId id, const TyTy::SubstitutionArgumentMappings &mappings,
+  TyTy::ADTType *type)
+{
+  rust_assert (type != nullptr);
+  auto inserted
+    = adt_substitutions.emplace (make_adt_substitution_key (id, mappings),
+				 type);
+  rust_assert (inserted.second);
+}
+
 bool
 TypeCheckContext::lookup_type (HirId id, TyTy::BaseType **type) const
 {
diff --git a/gcc/rust/typecheck/rust-tyty.cc b/gcc/rust/typecheck/rust-tyty.cc
index bc6844595..51adb831e 100644
--- a/gcc/rust/typecheck/rust-tyty.cc
+++ b/gcc/rust/typecheck/rust-tyty.cc
@@ -767,6 +767,9 @@ BaseType::monomorphized_clone () const
     }
   else if (auto adt = x->try_as<const ADTType> ())
     {
+      if (!adt->has_substitutions_defined () || !adt->needs_substitution ())
+	return const_cast<ADTType *> (adt);
+
       std::vector<VariantDef *> cloned_variants;
       for (auto &variant : adt->get_variants ())
 	cloned_variants.push_back (variant->monomorphized_clone ());
@@ -824,31 +827,46 @@ BaseType::debug () const
 
 const TyTy::BaseType *
 BaseType::contains_infer () const
+{
+  std::set<const TyTy::BaseType *> visited;
+  return contains_infer (visited);
+}
+
+const TyTy::BaseType *
+BaseType::contains_infer (std::set<const TyTy::BaseType *> &visited) const
 {
   const TyTy::BaseType *x = destructure ();
+  bool found = visited.find (x) != visited.end ();
+  if (found)
+    return nullptr;
+  visited.insert (x);
 
   if (auto fn = x->try_as<const FnType> ())
     {
       for (const auto &param : fn->get_params ())
 	{
-	  auto infer = param.get_type ()->contains_infer ();
+	  auto infer = param.get_type ()->contains_infer (visited);
 	  if (infer)
 	    return infer;
 	}
-      return fn->get_return_type ()->contains_infer ();
+      return fn->get_return_type ()->contains_infer (visited);
     }
   else if (auto fn = x->try_as<const FnPtr> ())
     {
       for (const auto &param : fn->get_params ())
 	{
-	  auto infer = param.get_tyty ()->contains_infer ();
+	  auto infer = param.get_tyty ()->contains_infer (visited);
 	  if (infer)
 	    return infer;
 	}
-      return fn->get_return_type ()->contains_infer ();
+      return fn->get_return_type ()->contains_infer (visited);
     }
   else if (auto adt = x->try_as<const ADTType> ())
     {
+      // non generic adt's cannot have infer vars on them
+      if (!adt->has_substitutions_defined ())
+	return nullptr;
+
       for (auto &variant : adt->get_variants ())
 	{
 	  bool is_num_variant
@@ -861,7 +879,7 @@ BaseType::contains_infer () const
 	  for (auto &field : variant->get_fields ())
 	    {
 	      const BaseType *field_type = field->get_field_type ();
-	      auto infer = (field_type->contains_infer ());
+	      auto infer = field_type->contains_infer (visited);
 	      if (infer)
 		return infer;
 	    }
@@ -870,28 +888,28 @@ BaseType::contains_infer () const
     }
   else if (auto arr = x->try_as<const ArrayType> ())
     {
-      auto type_infer = (arr->get_element_type ()->contains_infer ());
+      auto type_infer = arr->get_element_type ()->contains_infer (visited);
       if (type_infer)
 	return type_infer;
-      return arr->get_capacity ()->contains_infer ();
+      return arr->get_capacity ()->contains_infer (visited);
     }
   else if (auto slice = x->try_as<const SliceType> ())
     {
-      return slice->get_element_type ()->contains_infer ();
+      return slice->get_element_type ()->contains_infer (visited);
     }
   else if (auto ptr = x->try_as<const PointerType> ())
     {
-      return ptr->get_base ()->contains_infer ();
+      return ptr->get_base ()->contains_infer (visited);
     }
   else if (auto ref = x->try_as<const ReferenceType> ())
     {
-      return ref->get_base ()->contains_infer ();
+      return ref->get_base ()->contains_infer (visited);
     }
   else if (auto tuple = x->try_as<const TupleType> ())
     {
       for (size_t i = 0; i < tuple->num_fields (); i++)
 	{
-	  auto infer = (tuple->get_field (i)->contains_infer ());
+	  auto infer = tuple->get_field (i)->contains_infer (visited);
 	  if (infer)
 	    return infer;
 	}
@@ -899,10 +917,10 @@ BaseType::contains_infer () const
     }
   else if (auto closure = x->try_as<const ClosureType> ())
     {
-      auto infer = (closure->get_parameters ().contains_infer ());
+      auto infer = closure->get_parameters ().contains_infer (visited);
       if (infer)
 	return infer;
-      return closure->get_result_type ().contains_infer ();
+      return closure->get_result_type ().contains_infer (visited);
     }
   else if (x->is<InferType> ())
     {
@@ -921,8 +939,19 @@ BaseType::contains_infer () const
 
 bool
 BaseType::is_concrete () const
+{
+  std::set<const TyTy::BaseType *> visited;
+  return is_concrete (visited);
+}
+
+bool
+BaseType::is_concrete (std::set<const TyTy::BaseType *> &visited) const
 {
   const TyTy::BaseType *x = destructure ();
+  bool found = visited.find (x) != visited.end ();
+  if (found)
+    return true;
+  visited.insert (x);
 
   if (x->is<ParamType> ())
     {
@@ -939,7 +968,7 @@ BaseType::is_concrete () const
   else if (x->is<ProjectionType> ())
     {
       const auto p = x->as<const TyTy::ProjectionType> ();
-      return p->get_self ()->is_concrete ();
+      return p->get_self ()->is_concrete (visited);
     }
   // placeholder is a special case for this case when it is not resolvable
   // it means we its just an empty placeholder associated type which is
@@ -952,22 +981,25 @@ BaseType::is_concrete () const
     {
       for (const auto &param : fn->get_params ())
 	{
-	  if (!param.get_type ()->is_concrete ())
+	  if (!param.get_type ()->is_concrete (visited))
 	    return false;
 	}
-      return fn->get_return_type ()->is_concrete ();
+      return fn->get_return_type ()->is_concrete (visited);
     }
   else if (auto fn = x->try_as<const FnPtr> ())
     {
       for (const auto &param : fn->get_params ())
 	{
-	  if (!param.get_tyty ()->is_concrete ())
+	  if (!param.get_tyty ()->is_concrete (visited))
 	    return false;
 	}
-      return fn->get_return_type ()->is_concrete ();
+      return fn->get_return_type ()->is_concrete (visited);
     }
   else if (auto adt = x->try_as<const ADTType> ())
     {
+      if (!adt->has_substitutions_defined ())
+	return true;
+
       if (adt->is_unit ())
 	return !adt->needs_substitution ();
 
@@ -983,7 +1015,7 @@ BaseType::is_concrete () const
 	  for (auto &field : variant->get_fields ())
 	    {
 	      const BaseType *field_type = field->get_field_type ();
-	      if (!field_type->is_concrete ())
+	      if (!field_type->is_concrete (visited))
 		return false;
 	    }
 	}
@@ -991,35 +1023,35 @@ BaseType::is_concrete () const
     }
   else if (auto arr = x->try_as<const ArrayType> ())
     {
-      return arr->get_element_type ()->is_concrete ()
-	     && arr->get_capacity ()->is_concrete ();
+      return arr->get_element_type ()->is_concrete (visited)
+	     && arr->get_capacity ()->is_concrete (visited);
     }
   else if (auto slice = x->try_as<const SliceType> ())
     {
-      return slice->get_element_type ()->is_concrete ();
+      return slice->get_element_type ()->is_concrete (visited);
     }
   else if (auto ptr = x->try_as<const PointerType> ())
     {
-      return ptr->get_base ()->is_concrete ();
+      return ptr->get_base ()->is_concrete (visited);
     }
   else if (auto ref = x->try_as<const ReferenceType> ())
     {
-      return ref->get_base ()->is_concrete ();
+      return ref->get_base ()->is_concrete (visited);
     }
   else if (auto tuple = x->try_as<const TupleType> ())
     {
       for (size_t i = 0; i < tuple->num_fields (); i++)
 	{
-	  if (!tuple->get_field (i)->is_concrete ())
+	  if (!tuple->get_field (i)->is_concrete (visited))
 	    return false;
 	}
       return true;
     }
   else if (auto closure = x->try_as<const ClosureType> ())
     {
-      if (closure->get_parameters ().is_concrete ())
+      if (closure->get_parameters ().is_concrete (visited))
 	return false;
-      return closure->get_result_type ().is_concrete ();
+      return closure->get_result_type ().is_concrete (visited);
     }
   else if (x->is<InferType> () || x->is<BoolType> () || x->is<CharType> ()
 	   || x->is<IntType> () || x->is<UintType> () || x->is<FloatType> ()
@@ -1998,10 +2030,16 @@ ADTType::as_string () const
 bool
 ADTType::is_equal (const BaseType &other) const
 {
+  if (this == &other)
+    return true;
+
   if (get_kind () != other.get_kind ())
     return false;
 
   auto other2 = other.as<const ADTType> ();
+  if (get_id () != other2->get_id ())
+    return false;
+
   if (get_adt_kind () != other2->get_adt_kind ())
     return false;
 
@@ -2028,15 +2066,8 @@ ADTType::is_equal (const BaseType &other) const
 	}
     }
 
-  for (size_t i = 0; i < number_of_variants (); i++)
-    {
-      const TyTy::VariantDef *a = get_variants ().at (i);
-      const TyTy::VariantDef *b = other2->get_variants ().at (i);
-
-      if (!a->is_equal (*b))
-	return false;
-    }
-
+  // ADTs are nominal.  Their fields describe the definition, not the type's
+  // identity, and walking them here cannot terminate for recursive ADTs.
   return true;
 }
 
@@ -2131,10 +2162,19 @@ handle_substitions (SubstitutionArgumentMappings &subst_mappings,
 ADTType *
 ADTType::handle_substitions (SubstitutionArgumentMappings &subst_mappings)
 {
+  auto context = Resolver::TypeCheckContext::get ();
+  ADTType *cached = nullptr;
+  if (context->lookup_adt_substitution (get_id (), subst_mappings, &cached))
+    return cached;
+
   auto adt = clone ()->as<ADTType> ();
   adt->set_ty_ref (mappings.get_next_hir_id ());
   adt->used_arguments = subst_mappings;
 
+  // track these to handle recursive types
+  context->insert_implicit_type (adt->get_ty_ref (), adt);
+  context->insert_adt_substitution (get_id (), subst_mappings, adt);
+
   for (auto &sub : adt->get_substs ())
     {
       SubstitutionArg arg = SubstitutionArg::error ();
@@ -2589,7 +2629,7 @@ FnPtr::as_string () const
   auto &params = get_params ();
   for (auto &p : params)
     {
-      params_str += p.get_tyty ()->as_string () + " ,";
+      params_str += p.get_tyty ()->get_name () + " ,";
     }
 
   std::string unsafety = "";
@@ -2598,7 +2638,7 @@ FnPtr::as_string () const
 
   std::string abi = get_string_from_abi (get_abi ());
   return unsafety + "abi:" + abi + " " + "fnptr (" + params_str + ") -> "
-	 + get_return_type ()->as_string ();
+	 + get_return_type ()->get_name ();
 }
 
 bool
@@ -3641,7 +3681,7 @@ std::string
 PointerType::as_string () const
 {
   return std::string ("* ") + (is_mutable () ? "mut" : "const") + " "
-	 + get_base ()->as_string ();
+	 + get_base ()->get_name ();
 }
 
 std::string
diff --git a/gcc/rust/typecheck/rust-tyty.h b/gcc/rust/typecheck/rust-tyty.h
index 7ecea5759..c4f0ad59a 100644
--- a/gcc/rust/typecheck/rust-tyty.h
+++ b/gcc/rust/typecheck/rust-tyty.h
@@ -368,6 +368,10 @@ public:
   virtual bool is_box () const { return false; }
 
 protected:
+  const TyTy::BaseType *
+  contains_infer (std::set<const TyTy::BaseType *> &visited) const;
+  bool is_concrete (std::set<const TyTy::BaseType *> &visited) const;
+
   BaseType (HirId ref, HirId ty_ref, TypeKind kind, RustIdent ident,
 	    std::set<HirId> refs = std::set<HirId> ());
 
diff --git a/gcc/rust/typecheck/rust-unify.cc b/gcc/rust/typecheck/rust-unify.cc
index 3d5454741..edb6e20e8 100644
--- a/gcc/rust/typecheck/rust-unify.cc
+++ b/gcc/rust/typecheck/rust-unify.cc
@@ -38,11 +38,13 @@ UnifyRules::UnifyRules (TyTy::TyWithLocation lhs, TyTy::TyWithLocation rhs,
 			location_t locus, bool commit_flag, bool emit_error,
 			bool check_bounds, bool infer,
 			std::vector<CommitSite> &commits,
-			std::vector<InferenceSite> &infers)
+			std::vector<InferenceSite> &infers,
+			ActiveADTs &active_adts)
   : lhs (lhs), rhs (rhs), locus (locus), commit_flag (commit_flag),
     emit_error (emit_error), infer_flag (infer),
     check_bounds_flag (check_bounds), commits (commits), infers (infers),
-    mappings (Analysis::Mappings::get ()), context (*TypeCheckContext::get ())
+    active_adts (active_adts), mappings (Analysis::Mappings::get ()),
+    context (*TypeCheckContext::get ())
 {}
 
 TyTy::BaseType *
@@ -50,10 +52,15 @@ UnifyRules::Resolve (TyTy::TyWithLocation lhs, TyTy::TyWithLocation rhs,
 		     location_t locus, bool commit_flag, bool emit_error,
 		     bool check_bounds, bool infer,
 		     std::vector<CommitSite> &commits,
-		     std::vector<InferenceSite> &infers)
+		     std::vector<InferenceSite> &infers,
+		     ActiveADTs *active_adts)
 {
+  ActiveADTs root_active_adts;
+  if (active_adts == nullptr)
+    active_adts = &root_active_adts;
+
   UnifyRules r (lhs, rhs, locus, commit_flag, emit_error, check_bounds, infer,
-		commits, infers);
+		commits, infers, *active_adts);
 
   TyTy::BaseType *result = r.go ();
   bool failed = result->get_kind () == TyTy::TypeKind::ERROR;
@@ -76,7 +83,8 @@ UnifyRules::resolve_subtype (TyTy::TyWithLocation lhs, TyTy::TyWithLocation rhs)
 {
   TyTy::BaseType *result
     = UnifyRules::Resolve (lhs, rhs, locus, commit_flag, emit_error,
-			   check_bounds_flag, infer_flag, commits, infers);
+			   check_bounds_flag, infer_flag, commits, infers,
+			   &active_adts);
 
   // If the recursive call resulted in an error and would have emitted an error
   // message, disable error emission for the current level to avoid duplicate
@@ -585,6 +593,9 @@ UnifyRules::expect_adt (TyTy::ADTType *ltype, TyTy::BaseType *rtype)
     case TyTy::ADT:
       {
 	TyTy::ADTType &type = *static_cast<TyTy::ADTType *> (rtype);
+	if (ltype == &type)
+	  return ltype;
+
 	if (ltype->get_adt_kind () != type.get_adt_kind ())
 	  {
 	    return unify_error_type_node ();
@@ -605,6 +616,10 @@ UnifyRules::expect_adt (TyTy::ADTType *ltype, TyTy::BaseType *rtype)
 	    return unify_error_type_node ();
 	  }
 
+	ActiveADTGuard guard (active_adts, {ltype, &type});
+	if (guard.already_active ())
+	  return ltype;
+
 	for (size_t i = 0; i < type.number_of_variants (); ++i)
 	  {
 	    TyTy::VariantDef *a = ltype->get_variants ().at (i);
diff --git a/gcc/rust/typecheck/rust-unify.h b/gcc/rust/typecheck/rust-unify.h
index f57914032..2091bd1b1 100644
--- a/gcc/rust/typecheck/rust-unify.h
+++ b/gcc/rust/typecheck/rust-unify.h
@@ -28,6 +28,40 @@ namespace Resolver {
 class UnifyRules
 {
 public:
+  using ADTPair = std::pair<const TyTy::ADTType *, const TyTy::ADTType *>;
+  using ActiveADTs = std::set<ADTPair>;
+
+  class ActiveADTGuard
+  {
+  public:
+    ActiveADTGuard (ActiveADTs &active, ADTPair pair)
+      : active (active), pair (pair), inserted (false)
+    {
+      bool found = active.find (this->pair) != active.end ();
+      if (!found)
+	{
+	  active.insert (this->pair);
+	  inserted = true;
+	}
+    }
+
+    ~ActiveADTGuard ()
+    {
+      if (inserted)
+	active.erase (pair);
+    }
+
+    bool already_active () const { return !inserted; }
+
+    ActiveADTGuard (const ActiveADTGuard &) = delete;
+    ActiveADTGuard &operator= (const ActiveADTGuard &) = delete;
+
+  private:
+    ActiveADTs &active;
+    ADTPair pair;
+    bool inserted;
+  };
+
   struct InferenceSite
   {
     InferenceSite (HirId pref, HirId ptyref, TyTy::BaseGeneric *param,
@@ -52,12 +86,11 @@ public:
     TyTy::BaseType *resolved;
   };
 
-  static TyTy::BaseType *Resolve (TyTy::TyWithLocation lhs,
-				  TyTy::TyWithLocation rhs, location_t locus,
-				  bool commit_flag, bool emit_error,
-				  bool check_bounds, bool infer,
-				  std::vector<CommitSite> &commits,
-				  std::vector<InferenceSite> &infers);
+  static TyTy::BaseType *
+  Resolve (TyTy::TyWithLocation lhs, TyTy::TyWithLocation rhs, location_t locus,
+	   bool commit_flag, bool emit_error, bool check_bounds, bool infer,
+	   std::vector<CommitSite> &commits, std::vector<InferenceSite> &infers,
+	   ActiveADTs *active_adts = nullptr);
 
   static void commit (TyTy::BaseType *base, TyTy::BaseType *other,
 		      TyTy::BaseType *resolved);
@@ -102,7 +135,7 @@ private:
   UnifyRules (TyTy::TyWithLocation lhs, TyTy::TyWithLocation rhs,
 	      location_t locus, bool commit_flag, bool emit_error, bool infer,
 	      bool check_bounds, std::vector<CommitSite> &commits,
-	      std::vector<InferenceSite> &infers);
+	      std::vector<InferenceSite> &infers, ActiveADTs &active_adts);
 
   TyTy::BaseType *resolve_subtype (TyTy::TyWithLocation lhs,
 				   TyTy::TyWithLocation rhs);
@@ -126,6 +159,8 @@ private:
   std::vector<CommitSite> &commits;
   std::vector<InferenceSite> &infers;
 
+  ActiveADTs &active_adts;
+
   Analysis::Mappings &mappings;
   TypeCheckContext &context;
 };
diff --git a/gcc/testsuite/rust/compile/issues/issue-1178.rs b/gcc/testsuite/rust/compile/issues/issue-1178.rs
new file mode 100644
index 000000000..833fa7dcb
--- /dev/null
+++ b/gcc/testsuite/rust/compile/issues/issue-1178.rs
@@ -0,0 +1,8 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+pub struct Edge<'a> {
+    pub next: &'a Edge<'a>
+}
diff --git a/gcc/testsuite/rust/compile/issues/issue-2757.rs b/gcc/testsuite/rust/compile/issues/issue-2757.rs
new file mode 100644
index 000000000..39cad67a5
--- /dev/null
+++ b/gcc/testsuite/rust/compile/issues/issue-2757.rs
@@ -0,0 +1,7 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct S(&'static T);
+struct T(&'static S);
diff --git a/gcc/testsuite/rust/compile/issues/issue-4709.rs b/gcc/testsuite/rust/compile/issues/issue-4709.rs
new file mode 100644
index 000000000..29d3c17c5
--- /dev/null
+++ b/gcc/testsuite/rust/compile/issues/issue-4709.rs
@@ -0,0 +1,8 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct Node<'a> {
+    n: &'a Node<'a>,
+}
diff --git a/gcc/testsuite/rust/compile/recursive-types-infinite.rs b/gcc/testsuite/rust/compile/recursive-types-infinite.rs
new file mode 100644
index 000000000..402b8b0cc
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recursive-types-infinite.rs
@@ -0,0 +1,14 @@
+// { dg-additional-options "-w" }
+#![feature(no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+trait Sized {}
+
+struct A { b: B } // { dg-error "recursive type .A. has infinite size" }
+struct B { a: A }
+struct TupleBad((TupleBad,)); // { dg-error "recursive type .TupleBad. has infinite size" }
+struct ArrayBad([ArrayBad; 1]); // { dg-error "recursive type .ArrayBad. has infinite size" }
+struct Mixed {
+    pointer: *const Mixed,
+    inline: Mixed, // { dg-error "recursive type .Mixed. has infinite size" }
+}
diff --git a/gcc/testsuite/rust/compile/recursive-types-mismatch.rs b/gcc/testsuite/rust/compile/recursive-types-mismatch.rs
new file mode 100644
index 000000000..50526d9d2
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recursive-types-mismatch.rs
@@ -0,0 +1,20 @@
+// { dg-additional-options "-w" }
+#![feature(no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+trait Sized {}
+
+struct A { next: *const A }
+struct B { next: *const B }
+fn nominal(a: A) -> B {
+    a // { dg-error "mismatched types" "" { target *-*-* } .-1 }
+}
+struct Node<T> { next: *const Node<T>, value: T }
+fn generic(a: Node<i32>) -> Node<u32> {
+    a // { dg-error "mismatched types" "" { target *-*-* } .-1 }
+}
+struct Left<T> { right: *const Right<T>, value: T }
+struct Right<T> { left: *const Left<T>, value: T }
+fn mutual(a: Left<i32>) -> Left<u32> {
+    a // { dg-error "mismatched types" "" { target *-*-* } .-1 }
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-1.rs b/gcc/testsuite/rust/compile/recusive-types-1.rs
new file mode 100644
index 000000000..5d38ff3de
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-1.rs
@@ -0,0 +1,23 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+#![feature(lang_items)]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+struct Node {
+    value: i32,
+    next: *const Node,
+}
+
+fn main() {
+    let node = Node {
+        value: 1,
+        next: 0 as *const Node,
+    };
+
+    let _value = node.value;
+    let _next = node.next;
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-2.rs b/gcc/testsuite/rust/compile/recusive-types-2.rs
new file mode 100644
index 000000000..d98a07879
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-2.rs
@@ -0,0 +1,16 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct A {
+    b: &'static B,
+}
+
+struct B {
+    c: &'static C,
+}
+
+struct C {
+    a: &'static A,
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-3.rs b/gcc/testsuite/rust/compile/recusive-types-3.rs
new file mode 100644
index 000000000..17146bc36
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-3.rs
@@ -0,0 +1,12 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct A(&'static B);
+
+struct B {
+    c: *const C,
+}
+
+struct C(&'static A);
diff --git a/gcc/testsuite/rust/compile/recusive-types-4.rs b/gcc/testsuite/rust/compile/recusive-types-4.rs
new file mode 100644
index 000000000..ac6060ed1
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-4.rs
@@ -0,0 +1,16 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+#![feature(lang_items)]
+
+#[lang = "sized"]
+trait Sized {}
+
+struct TupleNested {
+    link: (*const TupleNested,),
+}
+
+struct ArrayNested {
+    links: [*const ArrayNested; 2],
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-5.rs b/gcc/testsuite/rust/compile/recusive-types-5.rs
new file mode 100644
index 000000000..8263ee5ab
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-5.rs
@@ -0,0 +1,17 @@
+// { dg-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct A {
+    b: B,
+}
+
+struct B {
+    a: *const A,
+}
+
+struct Node {
+    parent: *const Node,
+    next: *mut Node,
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-6.rs b/gcc/testsuite/rust/compile/recusive-types-6.rs
new file mode 100644
index 000000000..2e8ecce79
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-6.rs
@@ -0,0 +1,26 @@
+// { dg-options "-w" }
+
+#![feature(lang_items, no_core)]
+#![no_core]
+
+#[lang = "sized"]
+trait Sized {}
+
+struct Node<T> {
+    value: T,
+    next: *const Node<T>,
+}
+
+fn main() {
+    let node = Node::<i32> {
+        value: 1,
+        next: 0 as *const Node<i32>,
+    };
+    let _value = node.value;
+
+    let other = Node::<u32> {
+        value: 2,
+        next: 0 as *const Node<u32>,
+    };
+    let _other_value = other.value;
+}
diff --git a/gcc/testsuite/rust/compile/recusive-types-7.rs b/gcc/testsuite/rust/compile/recusive-types-7.rs
new file mode 100644
index 000000000..3b1505ca6
--- /dev/null
+++ b/gcc/testsuite/rust/compile/recusive-types-7.rs
@@ -0,0 +1,10 @@
+// { dg-additional-options "-w" }
+
+#![feature(no_core)]
+#![no_core]
+
+struct Bad {
+    bad: Bad, // { dg-error "recursive type .Bad. has infinite size" }
+}
+
+fn main() {}
diff --git a/gcc/testsuite/rust/execute/recursive-types-fnptr.rs b/gcc/testsuite/rust/execute/recursive-types-fnptr.rs
new file mode 100644
index 000000000..847dd7587
--- /dev/null
+++ b/gcc/testsuite/rust/execute/recursive-types-fnptr.rs
@@ -0,0 +1,14 @@
+// { dg-additional-options "-w" }
+#![feature(no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+trait Sized {}
+
+struct Callback { call: fn(Callback) -> Callback, value: i32 }
+fn identity(c: Callback) -> Callback { c }
+fn main() -> i32 {
+    let c = Callback { call: identity, value: 0 };
+    let f = c.call;
+    let result = f(c);
+    result.value
+}

base-commit: fb59a24364383561f5d00eec88af51e044df87ba
-- 
2.55.0



More information about the Gcc-rust mailing list