Repository: bytecodealliance/wasm-tools
Pull request #1264 (2023-10-25 21:04:05)

`wasmparser`: Define core Wasm types in a macro
This sets the stage for a second set of these definitions that don't use `u32` indices, but instead use something similar to `CoreTypeId`. This new set of types will be used in validation and will support canonicalization.

At the same time, I've taken the liberty to rename `StructuralType` to `CompositeType` to match the latest spec and to change `HeapType::Indexed` to `HeapType::Concrete` for the same reason.

The macro prevents users from having to worry about type parameters while still letting us avoid duplication.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b3e631cc..7f0f0b58 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1397,9 +1397,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
 
 [[package]]
 name = "smallvec"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
 
 [[package]]
 name = "spdx"
diff --git a/Cargo.toml b/Cargo.toml
index 1658fb03..9e7e1ac8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,7 @@ wasmtime = { version = "12.0.0", default-features = false, features = ['cranelif
 url = "2.0.0"
 pretty_assertions = "1.3.0"
 semver = "1.0.0"
+smallvec = "1.11.1"
 
 wasm-compose = { version = "0.4.10", path = "crates/wasm-compose" }
 wasm-encoder = { version = "0.35.0", path = "crates/wasm-encoder" }
diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs
index 5303193c..14c6f1a6 100644
--- a/crates/wasm-compose/src/encoding.rs
+++ b/crates/wasm-compose/src/encoding.rs
@@ -434,7 +434,7 @@ impl<'a> TypeEncoder<'a> {
                 wasmparser::HeapType::Struct => HeapType::Struct,
                 wasmparser::HeapType::Array => HeapType::Array,
                 wasmparser::HeapType::I31 => HeapType::I31,
-                wasmparser::HeapType::Indexed(i) => HeapType::Indexed(i),
+                wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i),
             },
         }
     }
diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs
index a36a913a..d28d863f 100644
--- a/crates/wasm-encoder/src/core/types.rs
+++ b/crates/wasm-encoder/src/core/types.rs
@@ -7,8 +7,8 @@ pub struct SubType {
     pub is_final: bool,
     /// The list of supertype indexes. As of GC MVP, there can be at most one supertype.
     pub supertype_idx: Option<u32>,
-    /// The structural type of the subtype.
-    pub structural_type: StructuralType,
+    /// The composite type of the subtype.
+    pub composite_type: CompositeType,
 }
 
 #[cfg(feature = "wasmparser")]
@@ -17,14 +17,14 @@ impl From<wasmparser::SubType> for SubType {
         SubType {
             is_final: sub_ty.is_final,
             supertype_idx: sub_ty.supertype_idx,
-            structural_type: sub_ty.structural_type.into(),
+            composite_type: sub_ty.composite_type.into(),
         }
     }
 }
 
-/// Represents a structural type in a WebAssembly module.
+/// Represents a composite type in a WebAssembly module.
 #[derive(Debug, Clone)]
-pub enum StructuralType {
+pub enum CompositeType {
     /// The type is for a function.
     Func(FuncType),
     /// The type is for an array.
@@ -33,18 +33,18 @@ pub enum StructuralType {
     Struct(StructType),
 }
 
-impl Encode for StructuralType {
+impl Encode for CompositeType {
     fn encode(&self, sink: &mut Vec<u8>) {
         match self {
-            StructuralType::Func(ty) => TypeSection::encode_function(
+            CompositeType::Func(ty) => TypeSection::encode_function(
                 sink,
                 ty.params().iter().copied(),
                 ty.results().iter().copied(),
             ),
-            StructuralType::Array(ArrayType(ty)) => {
+            CompositeType::Array(ArrayType(ty)) => {
                 TypeSection::encode_array(sink, &ty.element_type, ty.mutable)
             }
-            StructuralType::Struct(ty) => {
+            CompositeType::Struct(ty) => {
                 TypeSection::encode_struct(sink, ty.fields.iter().cloned())
             }
         }
@@ -52,12 +52,12 @@ impl Encode for StructuralType {
 }
 
 #[cfg(feature = "wasmparser")]
-impl From<wasmparser::StructuralType> for StructuralType {
-    fn from(structural_ty: wasmparser::StructuralType) -> Self {
-        match structural_ty {
-            wasmparser::StructuralType::Func(f) => StructuralType::Func(f.into()),
-            wasmparser::StructuralType::Array(a) => StructuralType::Array(a.into()),
-            wasmparser::StructuralType::Struct(s) => StructuralType::Struct(s.into()),
+impl From<wasmparser::CompositeType> for CompositeType {
+    fn from(composite_ty: wasmparser::CompositeType) -> Self {
+        match composite_ty {
+            wasmparser::CompositeType::Func(f) => CompositeType::Func(f.into()),
+            wasmparser::CompositeType::Array(a) => CompositeType::Array(a.into()),
+            wasmparser::CompositeType::Struct(s) => CompositeType::Struct(s.into()),
         }
     }
 }
@@ -108,7 +108,7 @@ impl From<wasmparser::StructType> for StructType {
     }
 }
 
-/// Field type in structural types (structs, arrays).
+/// Field type in composite types (structs, arrays).
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 pub struct FieldType {
     /// Storage type of the field.
@@ -127,7 +127,7 @@ impl From<wasmparser::FieldType> for FieldType {
     }
 }
 
-/// Storage type for structural type fields.
+/// Storage type for composite type fields.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 pub enum StorageType {
     /// The `i8` type.
@@ -313,27 +313,51 @@ impl From<RefType> for ValType {
 pub enum HeapType {
     /// Untyped (any) function.
     Func,
-    /// External heap type.
+
+    /// The abstract external heap type.
     Extern,
-    /// The `any` heap type. The common supertype (a.k.a. top) of all internal types.
+
+    /// The abstract `any` heap type.
+    ///
+    /// The common supertype (a.k.a. top) of all internal types.
     Any,
-    /// The `none` heap type. The common subtype (a.k.a. bottom) of all internal types.
+
+    /// The abstract `none` heap type.
+    ///
+    /// The common subtype (a.k.a. bottom) of all internal types.
     None,
-    /// The `noextern` heap type. The common subtype (a.k.a. bottom) of all external types.
+
+    /// The abstract `noextern` heap type.
+    ///
+    /// The common subtype (a.k.a. bottom) of all external types.
     NoExtern,
-    /// The `nofunc` heap type. The common subtype (a.k.a. bottom) of all function types.
+
+    /// The abstract `nofunc` heap type.
+    ///
+    /// The common subtype (a.k.a. bottom) of all function types.
     NoFunc,
-    /// The `eq` heap type. The common supertype of all referenceable types on which comparison
+
+    /// The abstract `eq` heap type.
+    ///
+    /// The common supertype of all referenceable types on which comparison
     /// (ref.eq) is allowed.
     Eq,
-    /// The `struct` heap type. The common supertype of all struct types.
+
+    /// The abstract `struct` heap type.
+    ///
+    /// The common supertype of all struct types.
     Struct,
-    /// The `array` heap type. The common supertype of all array types.
+
+    /// The abstract `array` heap type.
+    ///
+    /// The common supertype of all array types.
     Array,
-    /// The i31 heap type.
+
+    /// The unboxed `i31` heap type.
     I31,
-    /// User defined type at the given index.
-    Indexed(u32),
+
+    /// A concrete Wasm-defined type at the given index.
+    Concrete(u32),
 }
 
 impl Encode for HeapType {
@@ -351,7 +375,7 @@ impl Encode for HeapType {
             HeapType::I31 => sink.push(0x6C),
             // Note that this is encoded as a signed type rather than unsigned
             // as it's decoded as an s33
-            HeapType::Indexed(i) => i64::from(*i).encode(sink),
+            HeapType::Concrete(i) => i64::from(*i).encode(sink),
         }
     }
 }
@@ -360,7 +384,7 @@ impl Encode for HeapType {
 impl From<wasmparser::HeapType> for HeapType {
     fn from(heap_type: wasmparser::HeapType) -> Self {
         match heap_type {
-            wasmparser::HeapType::Indexed(i) => HeapType::Indexed(i),
+            wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i),
             wasmparser::HeapType::Func => HeapType::Func,
             wasmparser::HeapType::Extern => HeapType::Extern,
             wasmparser::HeapType::Any => HeapType::Any,
@@ -486,14 +510,14 @@ impl TypeSection {
 
     /// Define an explicit subtype in this type section.
     pub fn subtype(&mut self, ty: &SubType) -> &mut Self {
-        // We only need to emit a prefix byte before the actual structural type
+        // We only need to emit a prefix byte before the actual composite type
         // when either the type is not final or it has a declared super type.
         if ty.supertype_idx.is_some() || !ty.is_final {
             self.bytes.push(if ty.is_final { 0x4f } else { 0x50 });
             ty.supertype_idx.encode(&mut self.bytes);
         }
 
-        ty.structural_type.encode(&mut self.bytes);
+        ty.composite_type.encode(&mut self.bytes);
         self.num_added += 1;
         self
     }
@@ -522,7 +546,7 @@ mod tests {
         types.subtype(&SubType {
             is_final: true,
             supertype_idx: None,
-            structural_type: StructuralType::Func(FuncType::new([], [])),
+            composite_type: CompositeType::Func(FuncType::new([], [])),
         });
 
         let mut module = Module::new();
diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs
index 4431a14f..0e4cf614 100644
--- a/crates/wasm-mutate/src/module.rs
+++ b/crates/wasm-mutate/src/module.rs
@@ -90,7 +90,7 @@ pub fn map_ref_type(ref_ty: wasmparser::RefType) -> Result<RefType> {
             wasmparser::HeapType::Struct => HeapType::Struct,
             wasmparser::HeapType::Array => HeapType::Array,
             wasmparser::HeapType::I31 => HeapType::I31,
-            wasmparser::HeapType::Indexed(i) => HeapType::Indexed(i.into()),
+            wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.into()),
         },
     })
 }
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index f3044817..6587826d 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -210,8 +210,8 @@ pub fn heapty(t: &mut dyn Translator, ty: &wasmparser::HeapType) -> Result<HeapT
         wasmparser::HeapType::Struct => Ok(HeapType::Struct),
         wasmparser::HeapType::Array => Ok(HeapType::Array),
         wasmparser::HeapType::I31 => Ok(HeapType::I31),
-        wasmparser::HeapType::Indexed(i) => {
-            Ok(HeapType::Indexed(t.remap(Item::Type, (*i).into())?))
+        wasmparser::HeapType::Concrete(i) => {
+            Ok(HeapType::Concrete(t.remap(Item::Type, (*i).into())?))
         }
     }
 }
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index 9fe15faa..ddc01154 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -1654,7 +1654,7 @@ fn convert_reftype(ty: wasmparser::RefType) -> RefType {
             wasmparser::HeapType::Struct => HeapType::Struct,
             wasmparser::HeapType::Array => HeapType::Array,
             wasmparser::HeapType::I31 => HeapType::I31,
-            wasmparser::HeapType::Indexed(i) => HeapType::Indexed(i.into()),
+            wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.into()),
         },
     }
 }
diff --git a/crates/wasmparser/src/define_types.rs b/crates/wasmparser/src/define_types.rs
new file mode 100644
index 00000000..761d57e2
--- /dev/null
+++ b/crates/wasmparser/src/define_types.rs
@@ -0,0 +1,782 @@
+// Define the core Wasm type hierarchy with the given index type.
+//
+// The index type must satisfy the following constraints:
+//
+// * It must implement `Display`
+//
+// * It must implement `Into<u32>` and `From<u32>`
+//
+// * `$index_type::from(u32::from(index))` must be the identity function.
+//
+// * `u32::from($index_type::from(x))` must also be the identity function.
+//
+// * Its `u32` representation must fit within 20 bits, that is
+//
+//       index.into() <= (1 << 20) - 1
+//
+//   must hold true for all indices.
+macro_rules! define_core_wasm_types {
+    ($index_type:ty) => {
+        /// Represents a recursive type group in a WebAssembly module.
+        #[derive(Debug, Clone)]
+        pub struct RecGroup {
+            inner: RecGroupInner,
+        }
+
+        #[derive(Debug, Clone)]
+        enum RecGroupInner {
+            Implicit(SubType),
+            Explicit(Vec<SubType>),
+        }
+
+        impl RecGroup {
+            /// Create an explicit `RecGroup` for the given types.
+            pub(crate) fn explicit(types: Vec<SubType>) -> Self {
+                RecGroup {
+                    inner: RecGroupInner::Explicit(types),
+                }
+            }
+
+            /// Create an implicit `RecGroup` for a type that was not contained
+            /// in a `(rec ...)`.
+            pub(crate) fn implicit(ty: SubType) -> Self {
+                RecGroup {
+                    inner: RecGroupInner::Implicit(ty),
+                }
+            }
+
+            /// Is this an explicit recursion group?
+            pub fn is_explicit_rec_group(&self) -> bool {
+                matches!(self.inner, RecGroupInner::Explicit(_))
+            }
+
+            /// Returns the list of subtypes in the recursive type group.
+            pub fn types(&self) -> &[SubType] {
+                match &self.inner {
+                    RecGroupInner::Implicit(ty) => std::slice::from_ref(ty),
+                    RecGroupInner::Explicit(types) => types,
+                }
+            }
+
+            /// Returns an owning iterator of all subtypes in this recursion
+            /// group.
+            pub fn into_types(self) -> impl ExactSizeIterator<Item = SubType> {
+                return match self.inner {
+                    RecGroupInner::Implicit(ty) => Iter::Implicit(Some(ty)),
+                    RecGroupInner::Explicit(types) => Iter::Explicit(types.into_iter()),
+                };
+
+                enum Iter {
+                    Implicit(Option<SubType>),
+                    Explicit(std::vec::IntoIter<SubType>),
+                }
+
+                impl Iterator for Iter {
+                    type Item = SubType;
+
+                    fn next(&mut self) -> Option<SubType> {
+                        match self {
+                            Self::Implicit(ty) => ty.take(),
+                            Self::Explicit(types) => types.next(),
+                        }
+                    }
+
+                    fn size_hint(&self) -> (usize, Option<usize>) {
+                        match self {
+                            Self::Implicit(None) => (0, Some(0)),
+                            Self::Implicit(Some(_)) => (1, Some(1)),
+                            Self::Explicit(types) => types.size_hint(),
+                        }
+                    }
+                }
+
+                impl ExactSizeIterator for Iter {}
+            }
+        }
+
+        /// Represents a subtype of possible other types in a WebAssembly module.
+        #[derive(Debug, Clone)]
+        pub struct SubType {
+            /// Is the subtype final.
+            pub is_final: bool,
+            /// The list of supertype indexes. As of GC MVP, there can be at most one supertype.
+            pub supertype_idx: Option<$index_type>,
+            /// The composite type of the subtype.
+            pub composite_type: CompositeType,
+        }
+
+        impl SubType {
+            /// Unwrap an `ArrayType` or panic.
+            ///
+            /// Does not check finality or whether there is a supertype.
+            pub fn unwrap_array(&self) -> &ArrayType {
+                self.composite_type.unwrap_array()
+            }
+
+            /// Unwrap an `FuncType` or panic.
+            ///
+            /// Does not check finality or whether there is a supertype.
+            pub fn unwrap_func(&self) -> &FuncType {
+                self.composite_type.unwrap_func()
+            }
+
+            /// Unwrap an `StructType` or panic.
+            ///
+            /// Does not check finality or whether there is a supertype.
+            pub fn unwrap_struct(&self) -> &StructType {
+                self.composite_type.unwrap_struct()
+            }
+        }
+
+        /// Represents a composite type in a WebAssembly module.
+        #[derive(Debug, Clone)]
+        pub enum CompositeType {
+            /// The type is for a function.
+            Func(FuncType),
+            /// The type is for an array.
+            Array(ArrayType),
+            /// The type is for a struct.
+            Struct(StructType),
+        }
+
+        impl CompositeType {
+            /// Unwrap a `FuncType` or panic.
+            pub fn unwrap_func(&self) -> &FuncType {
+                match self {
+                    Self::Func(f) => f,
+                    _ => panic!("not a func"),
+                }
+            }
+
+            /// Unwrap a `ArrayType` or panic.
+            pub fn unwrap_array(&self) -> &ArrayType {
+                match self {
+                    Self::Array(a) => a,
+                    _ => panic!("not a array"),
+                }
+            }
+
+            /// Unwrap a `StructType` or panic.
+            pub fn unwrap_struct(&self) -> &StructType {
+                match self {
+                    Self::Struct(s) => s,
+                    _ => panic!("not a struct"),
+                }
+            }
+        }
+
+        /// Represents a type of a function in a WebAssembly module.
+        #[derive(Clone, Eq, PartialEq, Hash)]
+        pub struct FuncType {
+            /// The combined parameters and result types.
+            params_results: Box<[ValType]>,
+            /// The number of parameter types.
+            len_params: usize,
+        }
+
+        impl std::fmt::Debug for FuncType {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                f.debug_struct("FuncType")
+                    .field("params", &self.params())
+                    .field("results", &self.results())
+                    .finish()
+            }
+        }
+
+        impl FuncType {
+            /// Creates a new [`FuncType`] from the given `params` and `results`.
+            pub fn new<P, R>(params: P, results: R) -> Self
+            where
+                P: IntoIterator<Item = ValType>,
+                R: IntoIterator<Item = ValType>,
+            {
+                let mut buffer = params.into_iter().collect::<Vec<_>>();
+                let len_params = buffer.len();
+                buffer.extend(results);
+                Self {
+                    params_results: buffer.into(),
+                    len_params,
+                }
+            }
+
+            /// Creates a new [`FuncType`] fom its raw parts.
+            ///
+            /// # Panics
+            ///
+            /// If `len_params` is greater than the length of `params_results` combined.
+            pub(crate) fn from_raw_parts(
+                params_results: Box<[ValType]>,
+                len_params: usize,
+            ) -> Self {
+                assert!(len_params <= params_results.len());
+                Self {
+                    params_results,
+                    len_params,
+                }
+            }
+
+            /// Returns a shared slice to the parameter types of the [`FuncType`].
+            #[inline]
+            pub fn params(&self) -> &[ValType] {
+                &self.params_results[..self.len_params]
+            }
+
+            /// Returns a shared slice to the result types of the [`FuncType`].
+            #[inline]
+            pub fn results(&self) -> &[ValType] {
+                &self.params_results[self.len_params..]
+            }
+
+            pub(crate) fn desc(&self) -> String {
+                let mut s = String::new();
+                s.push_str("[");
+                for (i, param) in self.params().iter().enumerate() {
+                    if i > 0 {
+                        s.push_str(" ");
+                    }
+                    write!(s, "{param}").unwrap();
+                }
+                s.push_str("] -> [");
+                for (i, result) in self.results().iter().enumerate() {
+                    if i > 0 {
+                        s.push_str(" ");
+                    }
+                    write!(s, "{result}").unwrap();
+                }
+                s.push_str("]");
+                s
+            }
+        }
+
+        /// Represents a type of an array in a WebAssembly module.
+        #[derive(Debug, Clone, Eq, PartialEq, Hash)]
+        pub struct ArrayType(pub FieldType);
+
+        /// Represents a field type of an array or a struct.
+        #[derive(Debug, Clone, Eq, PartialEq, Hash)]
+        pub struct FieldType {
+            /// Array element type.
+            pub element_type: StorageType,
+            /// Are elements mutable.
+            pub mutable: bool,
+        }
+
+        /// Represents storage types introduced in the GC spec for array and struct fields.
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum StorageType {
+            /// The storage type is i8.
+            I8,
+            /// The storage type is i16.
+            I16,
+            /// The storage type is a value type.
+            Val(ValType),
+        }
+
+        /// Represents a type of a struct in a WebAssembly module.
+        #[derive(Debug, Clone, Eq, PartialEq, Hash)]
+        pub struct StructType {
+            /// Struct fields.
+            pub fields: Box<[FieldType]>,
+        }
+
+        /// Represents the types of values in a WebAssembly module.
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum ValType {
+            /// The value type is i32.
+            I32,
+            /// The value type is i64.
+            I64,
+            /// The value type is f32.
+            F32,
+            /// The value type is f64.
+            F64,
+            /// The value type is v128.
+            V128,
+            /// The value type is a reference.
+            Ref(RefType),
+        }
+
+        impl From<RefType> for ValType {
+            #[inline]
+            fn from(ty: RefType) -> ValType {
+                ValType::Ref(ty)
+            }
+        }
+
+        impl std::fmt::Display for ValType {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                match self {
+                    ValType::I32 => f.write_str("i32"),
+                    ValType::I64 => f.write_str("i64"),
+                    ValType::F32 => f.write_str("f32"),
+                    ValType::F64 => f.write_str("f64"),
+                    ValType::V128 => f.write_str("v128"),
+                    ValType::Ref(r) => std::fmt::Display::fmt(r, f),
+                }
+            }
+        }
+
+        impl ValType {
+            /// Alias for the wasm `funcref` type.
+            pub const FUNCREF: ValType = ValType::Ref(RefType::FUNCREF);
+
+            /// Alias for the wasm `externref` type.
+            pub const EXTERNREF: ValType = ValType::Ref(RefType::EXTERNREF);
+
+            /// Returns whether this value type is a "reference type".
+            ///
+            /// Only reference types are allowed in tables, for example, and with some
+            /// instructions. Current reference types include `funcref` and `externref`.
+            pub fn is_reference_type(&self) -> bool {
+                matches!(self, ValType::Ref(_))
+            }
+
+            /// Whether the type is defaultable, i.e. it is not a non-nullable reference
+            /// type.
+            pub fn is_defaultable(&self) -> bool {
+                match *self {
+                    Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
+                    Self::Ref(rt) => rt.is_nullable(),
+                }
+            }
+        }
+
+        /// A reference type.
+        ///
+        /// The reference types proposal first introduced `externref` and
+        /// `funcref`.
+        ///
+        /// The function references proposal introduced typed function
+        /// references.
+        ///
+        /// The GC proposal introduces heap types: any, eq, i31, struct, array,
+        /// nofunc, noextern, none.
+        //
+        // RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`.
+        // Note that its content is opaque (and subject to change), but its API
+        // is stable.
+        //
+        // It has the following internal structure:
+        //
+        // ```
+        // [nullable:u1 concrete==1:u1 unused:u2 index:u20]
+        // [nullable:u1 concrete==0:u1 abstype:u4 (unused):u18]
+        // ```
+        //
+        // Where
+        //
+        // - `nullable` determines nullability of the ref,
+        //
+        // - `concrete` determines if the ref is of a dynamically defined type
+        //   with an index (encoded in a following bit-packing section) or of a
+        //   known fixed type,
+        //
+        // - `index` is the type index,
+        //
+        // - `abstype` is an enumeration of abstract types:
+        //
+        //   ```
+        //   1111 = any
+        //
+        //   1101 = eq
+        //   1000 = i31
+        //   1001 = struct
+        //   1100 = array
+        //
+        //   0101 = func
+        //   0100 = nofunc
+        //
+        //   0011 = extern
+        //   0010 = noextern
+        //
+        //   0000 = none
+        //   ```
+        #[derive(Copy, Clone, PartialEq, Eq, Hash)]
+        pub struct RefType([u8; 3]);
+
+        impl std::fmt::Debug for RefType {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                match (self.is_nullable(), self.heap_type()) {
+                    (true, HeapType::Any) => write!(f, "anyref"),
+                    (false, HeapType::Any) => write!(f, "(ref any)"),
+                    (true, HeapType::None) => write!(f, "nullref"),
+                    (false, HeapType::None) => write!(f, "(ref none)"),
+                    (true, HeapType::NoExtern) => write!(f, "nullexternref"),
+                    (false, HeapType::NoExtern) => write!(f, "(ref noextern)"),
+                    (true, HeapType::NoFunc) => write!(f, "nullfuncref"),
+                    (false, HeapType::NoFunc) => write!(f, "(ref nofunc)"),
+                    (true, HeapType::Eq) => write!(f, "eqref"),
+                    (false, HeapType::Eq) => write!(f, "(ref eq)"),
+                    (true, HeapType::Struct) => write!(f, "structref"),
+                    (false, HeapType::Struct) => write!(f, "(ref struct)"),
+                    (true, HeapType::Array) => write!(f, "arrayref"),
+                    (false, HeapType::Array) => write!(f, "(ref array)"),
+                    (true, HeapType::I31) => write!(f, "i31ref"),
+                    (false, HeapType::I31) => write!(f, "(ref i31)"),
+                    (true, HeapType::Extern) => write!(f, "externref"),
+                    (false, HeapType::Extern) => write!(f, "(ref extern)"),
+                    (true, HeapType::Func) => write!(f, "funcref"),
+                    (false, HeapType::Func) => write!(f, "(ref func)"),
+                    (true, HeapType::Concrete(idx)) => write!(f, "(ref null {idx})"),
+                    (false, HeapType::Concrete(idx)) => write!(f, "(ref {idx})"),
+                }
+            }
+        }
+
+        impl std::fmt::Display for RefType {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                std::fmt::Debug::fmt(self, f)
+            }
+        }
+
+        // Assert that we can fit indices up to `MAX_WASM_TYPES` inside `RefType`.
+        #[test]
+        fn can_fit_max_wasm_types_in_ref_type() {
+            fn can_roundtrip_index(index: u32) -> bool {
+                assert!(RefType::can_represent_type_index(index));
+                let rt = match RefType::concrete(true, index) {
+                    Some(rt) => rt,
+                    None => panic!(),
+                };
+                assert!(rt.is_nullable());
+                let actual_index = match rt.type_index() {
+                    Some(i) => i,
+                    None => panic!(),
+                };
+                actual_index == index
+            }
+
+            assert!(can_roundtrip_index(crate::limits::MAX_WASM_TYPES as u32));
+            assert!(can_roundtrip_index(0b00000000_00001111_00000000_00000000));
+            assert!(can_roundtrip_index(0b00000000_00000000_11111111_00000000));
+            assert!(can_roundtrip_index(0b00000000_00000000_00000000_11111111));
+            assert!(can_roundtrip_index(0));
+        }
+
+        impl RefType {
+            const NULLABLE_BIT: u32 = 1 << 23; // bit #23
+            const CONCRETE_BIT: u32 = 1 << 22; // bit #22
+
+            const ABSTYPE_MASK: u32 = 0b1111 << 18; // 4 bits #21-#18 (if `concrete == 0`)
+            const ANY_ABSTYPE: u32 = 0b1111 << 18;
+            const EQ_ABSTYPE: u32 = 0b1101 << 18;
+            const I31_ABSTYPE: u32 = 0b1000 << 18;
+            const STRUCT_ABSTYPE: u32 = 0b1001 << 18;
+            const ARRAY_ABSTYPE: u32 = 0b1100 << 18;
+            const FUNC_ABSTYPE: u32 = 0b0101 << 18;
+            const NOFUNC_ABSTYPE: u32 = 0b0100 << 18;
+            const EXTERN_ABSTYPE: u32 = 0b0011 << 18;
+            const NOEXTERN_ABSTYPE: u32 = 0b0010 << 18;
+            const NONE_ABSTYPE: u32 = 0b0000 << 18;
+
+            const INDEX_MASK: u32 = (1 << 20) - 1; // 20 bits #19-#0 (if `concrete == 1`)
+
+            /// A nullable untyped function reference aka `(ref null func)` aka
+            /// `funcref` aka `anyfunc`.
+            pub const FUNCREF: Self = RefType::FUNC.nullable();
+
+            /// A nullable reference to an extern object aka `(ref null extern)` aka
+            /// `externref`.
+            pub const EXTERNREF: Self = RefType::EXTERN.nullable();
+
+            /// A non-nullable untyped function reference aka `(ref func)`.
+            pub const FUNC: Self = RefType::from_u32(Self::FUNC_ABSTYPE);
+
+            /// A non-nullable reference to an extern object aka `(ref extern)`.
+            pub const EXTERN: Self = RefType::from_u32(Self::EXTERN_ABSTYPE);
+
+            /// A non-nullable reference to any object aka `(ref any)`.
+            pub const ANY: Self = RefType::from_u32(Self::ANY_ABSTYPE);
+
+            /// A non-nullable reference to no object aka `(ref none)`.
+            pub const NONE: Self = RefType::from_u32(Self::NONE_ABSTYPE);
+
+            /// A non-nullable reference to a noextern object aka `(ref noextern)`.
+            pub const NOEXTERN: Self = RefType::from_u32(Self::NOEXTERN_ABSTYPE);
+
+            /// A non-nullable reference to a nofunc object aka `(ref nofunc)`.
+            pub const NOFUNC: Self = RefType::from_u32(Self::NOFUNC_ABSTYPE);
+
+            /// A non-nullable reference to an eq object aka `(ref eq)`.
+            pub const EQ: Self = RefType::from_u32(Self::EQ_ABSTYPE);
+
+            /// A non-nullable reference to a struct aka `(ref struct)`.
+            pub const STRUCT: Self = RefType::from_u32(Self::STRUCT_ABSTYPE);
+
+            /// A non-nullable reference to an array aka `(ref array)`.
+            pub const ARRAY: Self = RefType::from_u32(Self::ARRAY_ABSTYPE);
+
+            /// A non-nullable reference to an i31 object aka `(ref i31)`.
+            pub const I31: Self = RefType::from_u32(Self::I31_ABSTYPE);
+
+            const fn can_represent_type_index(index: u32) -> bool {
+                index & Self::INDEX_MASK == index
+            }
+
+            const fn u24_to_u32(bytes: [u8; 3]) -> u32 {
+                let expanded_bytes = [bytes[0], bytes[1], bytes[2], 0];
+                u32::from_le_bytes(expanded_bytes)
+            }
+
+            const fn u32_to_u24(x: u32) -> [u8; 3] {
+                let bytes = x.to_le_bytes();
+                debug_assert!(bytes[3] == 0);
+                [bytes[0], bytes[1], bytes[2]]
+            }
+
+            #[inline]
+            const fn as_u32(&self) -> u32 {
+                Self::u24_to_u32(self.0)
+            }
+
+            #[inline]
+            const fn from_u32(x: u32) -> Self {
+                debug_assert!(x & (0b11111111 << 24) == 0);
+
+                // Either concrete or it must be a known abstract type.
+                debug_assert!(
+                    x & Self::CONCRETE_BIT != 0
+                        || matches!(
+                            x & Self::ABSTYPE_MASK,
+                            Self::ANY_ABSTYPE
+                                | Self::EQ_ABSTYPE
+                                | Self::I31_ABSTYPE
+                                | Self::STRUCT_ABSTYPE
+                                | Self::ARRAY_ABSTYPE
+                                | Self::FUNC_ABSTYPE
+                                | Self::NOFUNC_ABSTYPE
+                                | Self::EXTERN_ABSTYPE
+                                | Self::NOEXTERN_ABSTYPE
+                                | Self::NONE_ABSTYPE
+                        )
+                );
+
+                RefType(Self::u32_to_u24(x))
+            }
+
+            /// Create a reference to a concrete Wasm-defined type at the given
+            /// index.
+            ///
+            /// Returns `None` when the type index is beyond this crate's
+            /// implementation limits and therefore is not representable.
+            pub fn concrete(nullable: bool, index: $index_type) -> Option<Self> {
+                let index: u32 = index.into();
+                if Self::can_represent_type_index(index) {
+                    let nullable32 = Self::NULLABLE_BIT * nullable as u32;
+                    Some(RefType::from_u32(nullable32 | Self::CONCRETE_BIT | index))
+                } else {
+                    None
+                }
+            }
+
+            /// Create a new `RefType`.
+            ///
+            /// Returns `None` when the heap type's type index (if any) is
+            /// beyond this crate's implementation limits and therfore is not
+            /// representable.
+            pub fn new(nullable: bool, heap_type: HeapType) -> Option<Self> {
+                let nullable32 = Self::NULLABLE_BIT * (nullable as u32);
+                match heap_type {
+                    HeapType::Concrete(index) => RefType::concrete(nullable, index),
+                    HeapType::Func => Some(Self::from_u32(nullable32 | Self::FUNC_ABSTYPE)),
+                    HeapType::Extern => Some(Self::from_u32(nullable32 | Self::EXTERN_ABSTYPE)),
+                    HeapType::Any => Some(Self::from_u32(nullable32 | Self::ANY_ABSTYPE)),
+                    HeapType::None => Some(Self::from_u32(nullable32 | Self::NONE_ABSTYPE)),
+                    HeapType::NoExtern => Some(Self::from_u32(nullable32 | Self::NOEXTERN_ABSTYPE)),
+                    HeapType::NoFunc => Some(Self::from_u32(nullable32 | Self::NOFUNC_ABSTYPE)),
+                    HeapType::Eq => Some(Self::from_u32(nullable32 | Self::EQ_ABSTYPE)),
+                    HeapType::Struct => Some(Self::from_u32(nullable32 | Self::STRUCT_ABSTYPE)),
+                    HeapType::Array => Some(Self::from_u32(nullable32 | Self::ARRAY_ABSTYPE)),
+                    HeapType::I31 => Some(Self::from_u32(nullable32 | Self::I31_ABSTYPE)),
+                }
+            }
+
+            /// Is this a reference to an concrete type?
+            pub const fn is_concrete_type_ref(&self) -> bool {
+                self.as_u32() & Self::CONCRETE_BIT != 0
+            }
+
+            /// If this is a reference to a typed function, get its type index.
+            pub fn type_index(&self) -> Option<$index_type> {
+                if self.is_concrete_type_ref() {
+                    let index = self.as_u32() & Self::INDEX_MASK;
+                    Some(<$index_type>::from(index))
+                } else {
+                    None
+                }
+            }
+
+            const fn abstype(&self) -> u32 {
+                self.as_u32() & Self::ABSTYPE_MASK
+            }
+
+            /// Is this the abstract untyped function reference type aka `(ref
+            /// null func)` aka `funcref` aka `anyfunc`?
+            pub const fn is_func_ref(&self) -> bool {
+                !self.is_concrete_type_ref() && self.abstype() == Self::FUNC_ABSTYPE
+            }
+
+            /// Is this the abstract external reference type aka `(ref null
+            /// extern)` aka `externref`?
+            pub const fn is_extern_ref(&self) -> bool {
+                !self.is_concrete_type_ref() && self.abstype() == Self::EXTERN_ABSTYPE
+            }
+
+            /// Is this the abstract untyped array refrence type aka `(ref null
+            /// array)` aka `arrayref`?
+            pub const fn is_array_ref(&self) -> bool {
+                !self.is_concrete_type_ref() && self.abstype() == Self::ARRAY_ABSTYPE
+            }
+
+            /// Is this the abstract untyped struct reference type aka `(ref
+            /// null struct)` aka `structref`?
+            pub const fn is_struct_ref(&self) -> bool {
+                !self.is_concrete_type_ref() && self.abstype() == Self::STRUCT_ABSTYPE
+            }
+
+            /// Is this ref type nullable?
+            pub const fn is_nullable(&self) -> bool {
+                self.as_u32() & Self::NULLABLE_BIT != 0
+            }
+
+            /// Get the non-nullable version of this ref type.
+            pub const fn as_non_null(&self) -> Self {
+                Self::from_u32(self.as_u32() & !Self::NULLABLE_BIT)
+            }
+
+            /// Get the non-nullable version of this ref type.
+            pub const fn nullable(&self) -> Self {
+                Self::from_u32(self.as_u32() | Self::NULLABLE_BIT)
+            }
+
+            /// Get the heap type that this is a reference to.
+            pub fn heap_type(&self) -> HeapType {
+                let s = self.as_u32();
+                if self.is_concrete_type_ref() {
+                    HeapType::Concrete(self.type_index().unwrap())
+                } else {
+                    match s & Self::ABSTYPE_MASK {
+                        Self::FUNC_ABSTYPE => HeapType::Func,
+                        Self::EXTERN_ABSTYPE => HeapType::Extern,
+                        Self::ANY_ABSTYPE => HeapType::Any,
+                        Self::NONE_ABSTYPE => HeapType::None,
+                        Self::NOEXTERN_ABSTYPE => HeapType::NoExtern,
+                        Self::NOFUNC_ABSTYPE => HeapType::NoFunc,
+                        Self::EQ_ABSTYPE => HeapType::Eq,
+                        Self::STRUCT_ABSTYPE => HeapType::Struct,
+                        Self::ARRAY_ABSTYPE => HeapType::Array,
+                        Self::I31_ABSTYPE => HeapType::I31,
+                        _ => unreachable!(),
+                    }
+                }
+            }
+
+            // Note that this is similar to `Display for RefType` except that it has
+            // the indexes stubbed out.
+            pub(crate) fn wat(&self) -> &'static str {
+                match (self.is_nullable(), self.heap_type()) {
+                    (true, HeapType::Func) => "funcref",
+                    (true, HeapType::Extern) => "externref",
+                    (true, HeapType::Concrete(_)) => "(ref null $type)",
+                    (true, HeapType::Any) => "anyref",
+                    (true, HeapType::None) => "nullref",
+                    (true, HeapType::NoExtern) => "nullexternref",
+                    (true, HeapType::NoFunc) => "nullfuncref",
+                    (true, HeapType::Eq) => "eqref",
+                    (true, HeapType::Struct) => "structref",
+                    (true, HeapType::Array) => "arrayref",
+                    (true, HeapType::I31) => "i31ref",
+                    (false, HeapType::Func) => "(ref func)",
+                    (false, HeapType::Extern) => "(ref extern)",
+                    (false, HeapType::Concrete(_)) => "(ref $type)",
+                    (false, HeapType::Any) => "(ref any)",
+                    (false, HeapType::None) => "(ref none)",
+                    (false, HeapType::NoExtern) => "(ref noextern)",
+                    (false, HeapType::NoFunc) => "(ref nofunc)",
+                    (false, HeapType::Eq) => "(ref eq)",
+                    (false, HeapType::Struct) => "(ref struct)",
+                    (false, HeapType::Array) => "(ref array)",
+                    (false, HeapType::I31) => "(ref i31)",
+                }
+            }
+        }
+
+        /// A heap type.
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum HeapType {
+            /// A concrete, user-defined type.
+            ///
+            /// Introduced in the function-references proposal.
+            Concrete($index_type),
+
+            /// The abstract, untyped (any) function.
+            ///
+            /// Introduced in the references-types proposal.
+            Func,
+
+            /// The abstract, external heap type.
+            ///
+            /// Introduced in the references-types proposal.
+            Extern,
+
+            /// The abstract `any` heap type.
+            ///
+            /// The common supertype (a.k.a. top) of all internal types.
+            ///
+            /// Introduced in the GC proposal.
+            Any,
+
+            /// The abstract `none` heap type.
+            ///
+            /// The common subtype (a.k.a. bottom) of all internal types.
+            ///
+            /// Introduced in the GC proposal.
+            None,
+
+            /// The abstract `noextern` heap type.
+            ///
+            /// The common subtype (a.k.a. bottom) of all external types.
+            ///
+            /// Introduced in the GC proposal.
+            NoExtern,
+
+            /// The abstract `nofunc` heap type.
+            ///
+            /// The common subtype (a.k.a. bottom) of all function types.
+            ///
+            /// Introduced in the GC proposal.
+            NoFunc,
+
+            /// The abstract `eq` heap type.
+            ///
+            /// The common supertype of all heap types on which the `ref.eq`
+            /// instruction is allowed.
+            ///
+            /// Introduced in the GC proposal.
+            Eq,
+
+            /// The abstract `struct` heap type.
+            ///
+            /// The common supertype of all struct types.
+            ///
+            /// Introduced in the GC proposal.
+            Struct,
+
+            /// The abstract `array` heap type.
+            ///
+            /// The common supertype of all array types.
+            ///
+            /// Introduced in the GC proposal.
+            Array,
+
+            /// The abstract `i31` heap type.
+            ///
+            /// It is not expected that Wasm runtimes actually store these
+            /// values on the heap, but unbox them inline into the `i31ref`s
+            /// themselves instead.
+            ///
+            /// Introduced in the GC proposal.
+            I31,
+        }
+    };
+}
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index 19416723..b377b54b 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -718,6 +718,9 @@ pub use crate::readers::*;
 pub use crate::resources::*;
 pub use crate::validator::*;
 
+#[macro_use]
+mod define_types;
+
 mod binary_reader;
 mod limits;
 mod parser;
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index 53e6aa61..b1ac1f24 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -14,7 +14,6 @@
  */
 
 use std::fmt::{self, Debug, Write};
-use std::slice;
 
 use crate::limits::{
     MAX_WASM_FUNCTION_PARAMS, MAX_WASM_FUNCTION_RETURNS, MAX_WASM_STRUCT_FIELDS,
@@ -22,75 +21,15 @@ use crate::limits::{
 };
 use crate::{BinaryReader, BinaryReaderError, FromReader, Result, SectionLimited};
 
-/// Represents the types of values in a WebAssembly module.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum ValType {
-    /// The value type is i32.
-    I32,
-    /// The value type is i64.
-    I64,
-    /// The value type is f32.
-    F32,
-    /// The value type is f64.
-    F64,
-    /// The value type is v128.
-    V128,
-    /// The value type is a reference.
-    Ref(RefType),
-}
-
-/// Represents storage types introduced in the GC spec for array and struct fields.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum StorageType {
-    /// The storage type is i8.
-    I8,
-    /// The storage type is i16.
-    I16,
-    /// The storage type is a value type.
-    Val(ValType),
-}
-
-// The size of `ValType` is performance sensitive.
-const _: () = {
-    assert!(std::mem::size_of::<ValType>() == 4);
-};
-
 pub(crate) trait Matches {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
         F: Fn(u32) -> &'a SubType;
 }
 
-impl From<RefType> for ValType {
-    fn from(ty: RefType) -> ValType {
-        ValType::Ref(ty)
-    }
-}
+define_core_wasm_types!(u32);
 
 impl ValType {
-    /// Alias for the wasm `funcref` type.
-    pub const FUNCREF: ValType = ValType::Ref(RefType::FUNCREF);
-
-    /// Alias for the wasm `externref` type.
-    pub const EXTERNREF: ValType = ValType::Ref(RefType::EXTERNREF);
-
-    /// Returns whether this value type is a "reference type".
-    ///
-    /// Only reference types are allowed in tables, for example, and with some
-    /// instructions. Current reference types include `funcref` and `externref`.
-    pub fn is_reference_type(&self) -> bool {
-        matches!(self, ValType::Ref(_))
-    }
-
-    /// Whether the type is defaultable, i.e. it is not a non-nullable reference
-    /// type.
-    pub fn is_defaultable(&self) -> bool {
-        match *self {
-            Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
-            Self::Ref(rt) => rt.is_nullable(),
-        }
-    }
-
     pub(crate) fn is_valtype_byte(byte: u8) -> bool {
         match byte {
             0x7F | 0x7E | 0x7D | 0x7C | 0x7B | 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72
@@ -159,392 +98,6 @@ impl<'a> FromReader<'a> for ValType {
     }
 }
 
-impl fmt::Display for ValType {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let s = match self {
-            ValType::I32 => "i32",
-            ValType::I64 => "i64",
-            ValType::F32 => "f32",
-            ValType::F64 => "f64",
-            ValType::V128 => "v128",
-            ValType::Ref(r) => return fmt::Display::fmt(r, f),
-        };
-        f.write_str(s)
-    }
-}
-
-/// A reference type.
-///
-/// The reference types proposal first introduced `externref` and `funcref`.
-///
-/// The function references proposal introduced typed function references.
-///
-/// The GC proposal introduces heap types: any, eq, i31, struct, array, nofunc, noextern, none.
-//
-// RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`.
-// Note that its content is opaque (and subject to change), but its API is stable.
-//
-// It has the following internal structure:
-//
-// ```
-// [nullable:u1] [indexed==1:u1] [kind:u2] [index:u20]
-// [nullable:u1] [indexed==0:u1] [type:u4] [(unused):u18]
-// ```
-//
-// Where
-//
-// - `nullable` determines nullability of the ref
-//
-// - `indexed` determines if the ref is of a dynamically defined type with an
-//   index (encoded in a following bit-packing section) or of a known fixed type
-//
-// - `kind` determines what kind of indexed type the index is pointing to:
-//
-//   ```
-//   10 = struct
-//   11 = array
-//   01 = function
-//   ```
-//
-// - `index` is the type index
-//
-// - `type` is an enumeration of known types:
-//
-//   ```
-//   1111 = any
-//
-//   1101 = eq
-//   1000 = i31
-//   1001 = struct
-//   1100 = array
-//
-//   0101 = func
-//   0100 = nofunc
-//
-//   0011 = extern
-//   0010 = noextern
-//
-//   0000 = none
-//   ```
-//
-// - `(unused)` is unused sequence of bits
-#[derive(Copy, Clone, PartialEq, Eq, Hash)]
-pub struct RefType([u8; 3]);
-
-impl Debug for RefType {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match (self.is_nullable(), self.heap_type()) {
-            (true, HeapType::Any) => write!(f, "anyref"),
-            (false, HeapType::Any) => write!(f, "(ref any)"),
-            (true, HeapType::None) => write!(f, "nullref"),
-            (false, HeapType::None) => write!(f, "(ref none)"),
-            (true, HeapType::NoExtern) => write!(f, "nullexternref"),
-            (false, HeapType::NoExtern) => write!(f, "(ref noextern)"),
-            (true, HeapType::NoFunc) => write!(f, "nullfuncref"),
-            (false, HeapType::NoFunc) => write!(f, "(ref nofunc)"),
-            (true, HeapType::Eq) => write!(f, "eqref"),
-            (false, HeapType::Eq) => write!(f, "(ref eq)"),
-            (true, HeapType::Struct) => write!(f, "structref"),
-            (false, HeapType::Struct) => write!(f, "(ref struct)"),
-            (true, HeapType::Array) => write!(f, "arrayref"),
-            (false, HeapType::Array) => write!(f, "(ref array)"),
-            (true, HeapType::I31) => write!(f, "i31ref"),
-            (false, HeapType::I31) => write!(f, "(ref i31)"),
-            (true, HeapType::Extern) => write!(f, "externref"),
-            (false, HeapType::Extern) => write!(f, "(ref extern)"),
-            (true, HeapType::Func) => write!(f, "funcref"),
-            (false, HeapType::Func) => write!(f, "(ref func)"),
-            (true, HeapType::Indexed(idx)) => write!(f, "(ref null {idx})"),
-            (false, HeapType::Indexed(idx)) => write!(f, "(ref {idx})"),
-        }
-    }
-}
-
-// Static assert that we can fit indices up to `MAX_WASM_TYPES` inside `RefType`.
-const _: () = {
-    const fn can_roundtrip_index(index: u32) -> bool {
-        assert!(RefType::can_represent_type_index(index));
-        let rt = match RefType::indexed_func(true, index) {
-            Some(rt) => rt,
-            None => panic!(),
-        };
-        assert!(rt.is_nullable());
-        let actual_index = match rt.type_index() {
-            Some(i) => i,
-            None => panic!(),
-        };
-        actual_index == index
-    }
-
-    assert!(can_roundtrip_index(crate::limits::MAX_WASM_TYPES as u32));
-    assert!(can_roundtrip_index(0b00000000_00001111_00000000_00000000));
-    assert!(can_roundtrip_index(0b00000000_00000000_11111111_00000000));
-    assert!(can_roundtrip_index(0b00000000_00000000_00000000_11111111));
-    assert!(can_roundtrip_index(0));
-};
-
-impl RefType {
-    const NULLABLE_BIT: u32 = 1 << 23; // bit #23
-    const INDEXED_BIT: u32 = 1 << 22; // bit #22
-
-    const TYPE_MASK: u32 = 0b1111 << 18; // 4 bits #21-#18 (if `indexed == 0`)
-    const ANY_TYPE: u32 = 0b1111 << 18;
-    const EQ_TYPE: u32 = 0b1101 << 18;
-    const I31_TYPE: u32 = 0b1000 << 18;
-    const STRUCT_TYPE: u32 = 0b1001 << 18;
-    const ARRAY_TYPE: u32 = 0b1100 << 18;
-    const FUNC_TYPE: u32 = 0b0101 << 18;
-    const NOFUNC_TYPE: u32 = 0b0100 << 18;
-    const EXTERN_TYPE: u32 = 0b0011 << 18;
-    const NOEXTERN_TYPE: u32 = 0b0010 << 18;
-    const NONE_TYPE: u32 = 0b0000 << 18;
-
-    const KIND_MASK: u32 = 0b11 << 20; // 2 bits #21-#20 (if `indexed == 1`)
-    const STRUCT_KIND: u32 = 0b10 << 20;
-    const ARRAY_KIND: u32 = 0b11 << 20;
-    const FUNC_KIND: u32 = 0b01 << 20;
-
-    const INDEX_MASK: u32 = (1 << 20) - 1; // 20 bits #19-#0 (if `indexed == 1`)
-
-    /// A nullable untyped function reference aka `(ref null func)` aka
-    /// `funcref` aka `anyfunc`.
-    pub const FUNCREF: Self = RefType::FUNC.nullable();
-
-    /// A nullable reference to an extern object aka `(ref null extern)` aka
-    /// `externref`.
-    pub const EXTERNREF: Self = RefType::EXTERN.nullable();
-
-    /// A non-nullable untyped function reference aka `(ref func)`.
-    pub const FUNC: Self = RefType::from_u32(Self::FUNC_TYPE);
-
-    /// A non-nullable reference to an extern object aka `(ref extern)`.
-    pub const EXTERN: Self = RefType::from_u32(Self::EXTERN_TYPE);
-
-    /// A non-nullable reference to any object aka `(ref any)`.
-    pub const ANY: Self = RefType::from_u32(Self::ANY_TYPE);
-
-    /// A non-nullable reference to no object aka `(ref none)`.
-    pub const NONE: Self = RefType::from_u32(Self::NONE_TYPE);
-
-    /// A non-nullable reference to a noextern object aka `(ref noextern)`.
-    pub const NOEXTERN: Self = RefType::from_u32(Self::NOEXTERN_TYPE);
-
-    /// A non-nullable reference to a nofunc object aka `(ref nofunc)`.
-    pub const NOFUNC: Self = RefType::from_u32(Self::NOFUNC_TYPE);
-
-    /// A non-nullable reference to an eq object aka `(ref eq)`.
-    pub const EQ: Self = RefType::from_u32(Self::EQ_TYPE);
-
-    /// A non-nullable reference to a struct aka `(ref struct)`.
-    pub const STRUCT: Self = RefType::from_u32(Self::STRUCT_TYPE);
-
-    /// A non-nullable reference to an array aka `(ref array)`.
-    pub const ARRAY: Self = RefType::from_u32(Self::ARRAY_TYPE);
-
-    /// A non-nullable reference to an i31 object aka `(ref i31)`.
-    pub const I31: Self = RefType::from_u32(Self::I31_TYPE);
-
-    const fn can_represent_type_index(index: u32) -> bool {
-        index & Self::INDEX_MASK == index
-    }
-
-    const fn u24_to_u32(bytes: [u8; 3]) -> u32 {
-        let expanded_bytes = [bytes[0], bytes[1], bytes[2], 0];
-        u32::from_le_bytes(expanded_bytes)
-    }
-
-    const fn u32_to_u24(x: u32) -> [u8; 3] {
-        let bytes = x.to_le_bytes();
-        debug_assert!(bytes[3] == 0);
-        [bytes[0], bytes[1], bytes[2]]
-    }
-
-    #[inline]
-    const fn as_u32(&self) -> u32 {
-        Self::u24_to_u32(self.0)
-    }
-
-    #[inline]
-    const fn from_u32(x: u32) -> Self {
-        debug_assert!(x & (0b11111111 << 24) == 0);
-
-        // if not indexed, type must be any/eq/i31/struct/array/func/extern/nofunc/noextern/none
-        debug_assert!(
-            x & Self::INDEXED_BIT != 0
-                || matches!(
-                    x & Self::TYPE_MASK,
-                    Self::ANY_TYPE
-                        | Self::EQ_TYPE
-                        | Self::I31_TYPE
-                        | Self::STRUCT_TYPE
-                        | Self::ARRAY_TYPE
-                        | Self::FUNC_TYPE
-                        | Self::NOFUNC_TYPE
-                        | Self::EXTERN_TYPE
-                        | Self::NOEXTERN_TYPE
-                        | Self::NONE_TYPE
-                )
-        );
-        RefType(Self::u32_to_u24(x))
-    }
-
-    /// Create a reference to a typed function with the type at the given index.
-    ///
-    /// Returns `None` when the type index is beyond this crate's implementation
-    /// limits and therefore is not representable.
-    pub const fn indexed_func(nullable: bool, index: u32) -> Option<Self> {
-        Self::indexed(nullable, Self::FUNC_KIND, index)
-    }
-
-    /// Create a reference to an array with the type at the given index.
-    ///
-    /// Returns `None` when the type index is beyond this crate's implementation
-    /// limits and therefore is not representable.
-    pub const fn indexed_array(nullable: bool, index: u32) -> Option<Self> {
-        Self::indexed(nullable, Self::ARRAY_KIND, index)
-    }
-
-    /// Create a reference to a struct with the type at the given index.
-    ///
-    /// Returns `None` when the type index is beyond this crate's implementation
-    /// limits and therefore is not representable.
-    pub const fn indexed_struct(nullable: bool, index: u32) -> Option<Self> {
-        Self::indexed(nullable, Self::STRUCT_KIND, index)
-    }
-
-    /// Create a reference to a user defined type at the given index.
-    ///
-    /// Returns `None` when the type index is beyond this crate's implementation
-    /// limits and therefore is not representable, or when the heap type is not
-    /// a typed array, struct or function.
-    const fn indexed(nullable: bool, kind: u32, index: u32) -> Option<Self> {
-        if Self::can_represent_type_index(index) {
-            let nullable32 = Self::NULLABLE_BIT * nullable as u32;
-            Some(RefType::from_u32(
-                nullable32 | Self::INDEXED_BIT | kind | index,
-            ))
-        } else {
-            None
-        }
-    }
-
-    /// Create a new `RefType`.
-    ///
-    /// Returns `None` when the heap type's type index (if any) is beyond this
-    /// crate's implementation limits and therfore is not representable.
-    pub const fn new(nullable: bool, heap_type: HeapType) -> Option<Self> {
-        let nullable32 = Self::NULLABLE_BIT * nullable as u32;
-        match heap_type {
-            HeapType::Indexed(index) => RefType::indexed(nullable, 0, index), // 0 bc we don't know the kind
-            HeapType::Func => Some(Self::from_u32(nullable32 | Self::FUNC_TYPE)),
-            HeapType::Extern => Some(Self::from_u32(nullable32 | Self::EXTERN_TYPE)),
-            HeapType::Any => Some(Self::from_u32(nullable32 | Self::ANY_TYPE)),
-            HeapType::None => Some(Self::from_u32(nullable32 | Self::NONE_TYPE)),
-            HeapType::NoExtern => Some(Self::from_u32(nullable32 | Self::NOEXTERN_TYPE)),
-            HeapType::NoFunc => Some(Self::from_u32(nullable32 | Self::NOFUNC_TYPE)),
-            HeapType::Eq => Some(Self::from_u32(nullable32 | Self::EQ_TYPE)),
-            HeapType::Struct => Some(Self::from_u32(nullable32 | Self::STRUCT_TYPE)),
-            HeapType::Array => Some(Self::from_u32(nullable32 | Self::ARRAY_TYPE)),
-            HeapType::I31 => Some(Self::from_u32(nullable32 | Self::I31_TYPE)),
-        }
-    }
-
-    /// Is this a reference to a typed function?
-    pub const fn is_typed_func_ref(&self) -> bool {
-        self.is_indexed_type_ref() && self.as_u32() & Self::KIND_MASK == Self::FUNC_KIND
-    }
-
-    /// Is this a reference to an indexed type?
-    pub const fn is_indexed_type_ref(&self) -> bool {
-        self.as_u32() & Self::INDEXED_BIT != 0
-    }
-
-    /// If this is a reference to a typed function, get its type index.
-    pub const fn type_index(&self) -> Option<u32> {
-        if self.is_indexed_type_ref() {
-            Some(self.as_u32() & Self::INDEX_MASK)
-        } else {
-            None
-        }
-    }
-
-    /// Is this an untyped function reference aka `(ref null func)` aka `funcref` aka `anyfunc`?
-    pub const fn is_func_ref(&self) -> bool {
-        !self.is_indexed_type_ref() && self.as_u32() & Self::TYPE_MASK == Self::FUNC_TYPE
-    }
-
-    /// Is this a `(ref null extern)` aka `externref`?
-    pub const fn is_extern_ref(&self) -> bool {
-        !self.is_indexed_type_ref() && self.as_u32() & Self::TYPE_MASK == Self::EXTERN_TYPE
-    }
-
-    /// Is this ref type nullable?
-    pub const fn is_nullable(&self) -> bool {
-        self.as_u32() & Self::NULLABLE_BIT != 0
-    }
-
-    /// Get the non-nullable version of this ref type.
-    pub const fn as_non_null(&self) -> Self {
-        Self::from_u32(self.as_u32() & !Self::NULLABLE_BIT)
-    }
-
-    /// Get the non-nullable version of this ref type.
-    pub const fn nullable(&self) -> Self {
-        Self::from_u32(self.as_u32() | Self::NULLABLE_BIT)
-    }
-
-    /// Get the heap type that this is a reference to.
-    pub fn heap_type(&self) -> HeapType {
-        let s = self.as_u32();
-        if self.is_indexed_type_ref() {
-            HeapType::Indexed(self.type_index().unwrap())
-        } else {
-            match s & Self::TYPE_MASK {
-                Self::FUNC_TYPE => HeapType::Func,
-                Self::EXTERN_TYPE => HeapType::Extern,
-                Self::ANY_TYPE => HeapType::Any,
-                Self::NONE_TYPE => HeapType::None,
-                Self::NOEXTERN_TYPE => HeapType::NoExtern,
-                Self::NOFUNC_TYPE => HeapType::NoFunc,
-                Self::EQ_TYPE => HeapType::Eq,
-                Self::STRUCT_TYPE => HeapType::Struct,
-                Self::ARRAY_TYPE => HeapType::Array,
-                Self::I31_TYPE => HeapType::I31,
-                _ => unreachable!(),
-            }
-        }
-    }
-
-    // Note that this is similar to `Display for RefType` except that it has
-    // the indexes stubbed out.
-    pub(crate) fn wat(&self) -> &'static str {
-        match (self.is_nullable(), self.heap_type()) {
-            (true, HeapType::Func) => "funcref",
-            (true, HeapType::Extern) => "externref",
-            (true, HeapType::Indexed(_)) => "(ref null $type)",
-            (true, HeapType::Any) => "anyref",
-            (true, HeapType::None) => "nullref",
-            (true, HeapType::NoExtern) => "nullexternref",
-            (true, HeapType::NoFunc) => "nullfuncref",
-            (true, HeapType::Eq) => "eqref",
-            (true, HeapType::Struct) => "structref",
-            (true, HeapType::Array) => "arrayref",
-            (true, HeapType::I31) => "i31ref",
-            (false, HeapType::Func) => "(ref func)",
-            (false, HeapType::Extern) => "(ref extern)",
-            (false, HeapType::Indexed(_)) => "(ref $type)",
-            (false, HeapType::Any) => "(ref any)",
-            (false, HeapType::None) => "(ref none)",
-            (false, HeapType::NoExtern) => "(ref noextern)",
-            (false, HeapType::NoFunc) => "(ref nofunc)",
-            (false, HeapType::Eq) => "(ref eq)",
-            (false, HeapType::Struct) => "(ref struct)",
-            (false, HeapType::Array) => "(ref array)",
-            (false, HeapType::I31) => "(ref i31)",
-        }
-    }
-}
-
 impl Matches for RefType {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
@@ -580,67 +133,6 @@ impl<'a> FromReader<'a> for RefType {
     }
 }
 
-impl fmt::Display for RefType {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        // Note that this is similar to `RefType::wat` except that it has the
-        // indexes filled out.
-        let s = match (self.is_nullable(), self.heap_type()) {
-            (true, HeapType::Func) => "funcref",
-            (true, HeapType::Extern) => "externref",
-            (true, HeapType::Indexed(i)) => return write!(f, "(ref null {i})"),
-            (true, HeapType::Any) => "anyref",
-            (true, HeapType::None) => "nullref",
-            (true, HeapType::NoExtern) => "nullexternref",
-            (true, HeapType::NoFunc) => "nullfuncref",
-            (true, HeapType::Eq) => "eqref",
-            (true, HeapType::Struct) => "structref",
-            (true, HeapType::Array) => "arrayref",
-            (true, HeapType::I31) => "i31ref",
-            (false, HeapType::Func) => "(ref func)",
-            (false, HeapType::Extern) => "(ref extern)",
-            (false, HeapType::Indexed(i)) => return write!(f, "(ref {i})"),
-            (false, HeapType::Any) => "(ref any)",
-            (false, HeapType::None) => "(ref none)",
-            (false, HeapType::NoExtern) => "(ref noextern)",
-            (false, HeapType::NoFunc) => "(ref nofunc)",
-            (false, HeapType::Eq) => "(ref eq)",
-            (false, HeapType::Struct) => "(ref struct)",
-            (false, HeapType::Array) => "(ref array)",
-            (false, HeapType::I31) => "(ref i31)",
-        };
-        f.write_str(s)
-    }
-}
-
-/// A heap type from function references. When the proposal is disabled, Index
-/// is an invalid type.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum HeapType {
-    /// User defined type at the given index.
-    Indexed(u32),
-    /// Untyped (any) function.
-    Func,
-    /// External heap type.
-    Extern,
-    /// The `any` heap type. The common supertype (a.k.a. top) of all internal types.
-    Any,
-    /// The `none` heap type. The common subtype (a.k.a. bottom) of all internal types.
-    None,
-    /// The `noextern` heap type. The common subtype (a.k.a. bottom) of all external types.
-    NoExtern,
-    /// The `nofunc` heap type. The common subtype (a.k.a. bottom) of all function types.
-    NoFunc,
-    /// The `eq` heap type. The common supertype of all referenceable types on which comparison
-    /// (ref.eq) is allowed.
-    Eq,
-    /// The `struct` heap type. The common supertype of all struct types.
-    Struct,
-    /// The `array` heap type. The common supertype of all array types.
-    Array,
-    /// The i31 heap type.
-    I31,
-}
-
 impl Matches for HeapType {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
@@ -658,34 +150,34 @@ impl Matches for HeapType {
             (HT::NoFunc, HT::Func) => true,
             (HT::None, HT::I31 | HT::Array | HT::Struct) => true,
 
-            (HT::Indexed(a), HT::Eq | HT::Any) => matches!(
-                type_at(*a).structural_type,
-                StructuralType::Array(_) | StructuralType::Struct(_)
+            (HT::Concrete(a), HT::Eq | HT::Any) => matches!(
+                type_at(*a).composite_type,
+                CompositeType::Array(_) | CompositeType::Struct(_)
             ),
 
-            (HT::Indexed(a), HT::Struct) => {
-                matches!(type_at(*a).structural_type, StructuralType::Struct(_))
+            (HT::Concrete(a), HT::Struct) => {
+                matches!(type_at(*a).composite_type, CompositeType::Struct(_))
             }
 
-            (HT::Indexed(a), HT::Array) => {
-                matches!(type_at(*a).structural_type, StructuralType::Array(_))
+            (HT::Concrete(a), HT::Array) => {
+                matches!(type_at(*a).composite_type, CompositeType::Array(_))
             }
 
-            (HT::Indexed(a), HT::Func) => {
-                matches!(type_at(*a).structural_type, StructuralType::Func(_))
+            (HT::Concrete(a), HT::Func) => {
+                matches!(type_at(*a).composite_type, CompositeType::Func(_))
             }
 
-            (HT::Indexed(a), HT::Indexed(b)) => type_at(*a)
-                .structural_type
-                .matches(&type_at(*b).structural_type, type_at),
+            (HT::Concrete(a), HT::Concrete(b)) => type_at(*a)
+                .composite_type
+                .matches(&type_at(*b).composite_type, type_at),
 
-            (HT::None, HT::Indexed(b)) => matches!(
-                type_at(*b).structural_type,
-                StructuralType::Array(_) | StructuralType::Struct(_)
+            (HT::None, HT::Concrete(b)) => matches!(
+                type_at(*b).composite_type,
+                CompositeType::Array(_) | CompositeType::Struct(_)
             ),
 
-            (HT::NoFunc, HT::Indexed(b)) => {
-                matches!(type_at(*b).structural_type, StructuralType::Func(_))
+            (HT::NoFunc, HT::Concrete(b)) => {
+                matches!(type_at(*b).composite_type, CompositeType::Func(_))
             }
 
             _ => false,
@@ -743,231 +235,35 @@ impl<'a> FromReader<'a> for HeapType {
                         bail!(reader.original_position(), "invalid indexed ref heap type");
                     }
                 };
-                Ok(HeapType::Indexed(idx))
+                Ok(HeapType::Concrete(idx))
             }
         }
     }
 }
 
-/// Represents a structural type in a WebAssembly module.
-#[derive(Debug, Clone)]
-pub enum StructuralType {
-    /// The type is for a function.
-    Func(FuncType),
-    /// The type is for an array.
-    Array(ArrayType),
-    /// The type is for a struct.
-    Struct(StructType),
-}
-
-impl StructuralType {
-    /// Unwrap a `FuncType` or panic.
-    pub fn unwrap_func(&self) -> &FuncType {
-        match self {
-            Self::Func(f) => f,
-            _ => panic!("not a func"),
-        }
-    }
-}
-
-/// Represents a subtype of possible other types in a WebAssembly module.
-#[derive(Debug, Clone)]
-pub struct SubType {
-    /// Is the subtype final.
-    pub is_final: bool,
-    /// The list of supertype indexes. As of GC MVP, there can be at most one supertype.
-    pub supertype_idx: Option<u32>,
-    /// The structural type of the subtype.
-    pub structural_type: StructuralType,
-}
-
-impl SubType {
-    /// Unwrap a `FuncType` or panic.
-    pub fn unwrap_func(&self) -> &FuncType {
-        self.structural_type.unwrap_func()
-    }
-}
-
-/// Represents a recursive type group in a WebAssembly module.
-#[derive(Debug, Clone)]
-pub enum RecGroup {
-    /// The list of subtypes in the recursive type group.
-    Many(Vec<SubType>),
-    /// A single subtype in the recursive type group.
-    Single(SubType),
-}
-
-impl RecGroup {
-    /// Returns the list of subtypes in the recursive type group.
-    pub fn types(&self) -> &[SubType] {
-        match self {
-            RecGroup::Many(types) => types,
-            RecGroup::Single(ty) => slice::from_ref(ty),
-        }
-    }
-
-    /// Return an iterator over the types in this rec group, giving ownership of
-    /// the types.
-    pub fn into_types(self) -> impl ExactSizeIterator<Item = SubType> {
-        return match self {
-            RecGroup::Single(ty) => IntoIter::Single(Some(ty)),
-            RecGroup::Many(tys) => IntoIter::Many(tys.into_iter()),
-        };
-
-        enum IntoIter {
-            Single(Option<SubType>),
-            Many(std::vec::IntoIter<SubType>),
-        }
-
-        impl Iterator for IntoIter {
-            type Item = SubType;
-
-            fn next(&mut self) -> Option<Self::Item> {
-                match self {
-                    IntoIter::Single(ty) => ty.take(),
-                    IntoIter::Many(tys) => tys.next(),
-                }
-            }
-
-            fn size_hint(&self) -> (usize, Option<usize>) {
-                match self {
-                    IntoIter::Single(None) => (0, Some(0)),
-                    IntoIter::Single(Some(_)) => (1, Some(1)),
-                    IntoIter::Many(tys) => tys.size_hint(),
-                }
-            }
-        }
-
-        impl ExactSizeIterator for IntoIter {}
-    }
-}
-
 impl Matches for SubType {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
         F: Fn(u32) -> &'a SubType,
     {
-        !other.is_final
-            && self
-                .structural_type
-                .matches(&other.structural_type, type_at)
+        !other.is_final && self.composite_type.matches(&other.composite_type, type_at)
     }
 }
 
-/// Represents a type of a function in a WebAssembly module.
-#[derive(Clone, Eq, PartialEq, Hash)]
-pub struct FuncType {
-    /// The combined parameters and result types.
-    params_results: Box<[ValType]>,
-    /// The number of parameter types.
-    len_params: usize,
-}
-
-/// Represents a type of an array in a WebAssembly module.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct ArrayType(pub FieldType);
-
-/// Represents a field type of an array or a struct.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct FieldType {
-    /// Array element type.
-    pub element_type: StorageType,
-    /// Are elements mutable.
-    pub mutable: bool,
-}
-
-/// Represents a type of a struct in a WebAssembly module.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct StructType {
-    /// Struct fields.
-    pub fields: Box<[FieldType]>,
-}
-
-impl Matches for StructuralType {
+impl Matches for CompositeType {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
         F: Fn(u32) -> &'a SubType,
     {
         match (self, other) {
-            (StructuralType::Func(a), StructuralType::Func(b)) => a.matches(b, type_at),
-            (StructuralType::Array(a), StructuralType::Array(b)) => a.matches(b, type_at),
-            (StructuralType::Struct(a), StructuralType::Struct(b)) => a.matches(b, type_at),
+            (CompositeType::Func(a), CompositeType::Func(b)) => a.matches(b, type_at),
+            (CompositeType::Array(a), CompositeType::Array(b)) => a.matches(b, type_at),
+            (CompositeType::Struct(a), CompositeType::Struct(b)) => a.matches(b, type_at),
             _ => false,
         }
     }
 }
 
-impl Debug for FuncType {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("FuncType")
-            .field("params", &self.params())
-            .field("returns", &self.results())
-            .finish()
-    }
-}
-
-impl FuncType {
-    /// Creates a new [`FuncType`] from the given `params` and `results`.
-    pub fn new<P, R>(params: P, results: R) -> Self
-    where
-        P: IntoIterator<Item = ValType>,
-        R: IntoIterator<Item = ValType>,
-    {
-        let mut buffer = params.into_iter().collect::<Vec<_>>();
-        let len_params = buffer.len();
-        buffer.extend(results);
-        Self {
-            params_results: buffer.into(),
-            len_params,
-        }
-    }
-
-    /// Creates a new [`FuncType`] fom its raw parts.
-    ///
-    /// # Panics
-    ///
-    /// If `len_params` is greater than the length of `params_results` combined.
-    pub(crate) fn from_raw_parts(params_results: Box<[ValType]>, len_params: usize) -> Self {
-        assert!(len_params <= params_results.len());
-        Self {
-            params_results,
-            len_params,
-        }
-    }
-
-    /// Returns a shared slice to the parameter types of the [`FuncType`].
-    #[inline]
-    pub fn params(&self) -> &[ValType] {
-        &self.params_results[..self.len_params]
-    }
-
-    /// Returns a shared slice to the result types of the [`FuncType`].
-    #[inline]
-    pub fn results(&self) -> &[ValType] {
-        &self.params_results[self.len_params..]
-    }
-
-    pub(crate) fn desc(&self) -> String {
-        let mut s = String::new();
-        s.push_str("[");
-        for (i, param) in self.params().iter().enumerate() {
-            if i > 0 {
-                s.push_str(" ");
-            }
-            write!(s, "{param}").unwrap();
-        }
-        s.push_str("] -> [");
-        for (i, result) in self.results().iter().enumerate() {
-            if i > 0 {
-                s.push_str(" ");
-            }
-            write!(s, "{result}").unwrap();
-        }
-        s.push_str("]");
-        s
-    }
-}
-
 impl Matches for FuncType {
     fn matches<'a, F>(&self, other: &Self, type_at: &F) -> bool
     where
@@ -1122,16 +418,17 @@ impl<'a> TypeSectionReader<'a> {
     pub fn into_iter_err_on_gc_types(self) -> impl Iterator<Item = Result<FuncType>> + 'a {
         self.into_iter_with_offsets().map(|item| {
             let (offset, group) = item?;
-            let ty = match group {
-                RecGroup::Single(ty) => ty,
-                RecGroup::Many(_) => bail!(offset, "gc proposal not supported"),
+            let mut types = group.into_types();
+            let ty = match (types.next(), types.next()) {
+                (Some(ty), None) => ty,
+                _ => bail!(offset, "gc proposal not supported"),
             };
             if !ty.is_final || ty.supertype_idx.is_some() {
                 bail!(offset, "gc proposal not supported");
             }
-            match ty.structural_type {
-                StructuralType::Func(f) => Ok(f),
-                StructuralType::Array(_) | StructuralType::Struct(_) => {
+            match ty.composite_type {
+                CompositeType::Func(f) => Ok(f),
+                CompositeType::Array(_) | CompositeType::Struct(_) => {
                     bail!(offset, "gc proposal not supported");
                 }
             }
@@ -1139,34 +436,34 @@ impl<'a> TypeSectionReader<'a> {
     }
 }
 
-impl<'a> FromReader<'a> for StructuralType {
+impl<'a> FromReader<'a> for CompositeType {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
-        read_structural_type(reader.read_u8()?, reader)
+        read_composite_type(reader.read_u8()?, reader)
     }
 }
 
-fn read_structural_type(
+fn read_composite_type(
     opcode: u8,
     reader: &mut BinaryReader,
-) -> Result<StructuralType, BinaryReaderError> {
+) -> Result<CompositeType, BinaryReaderError> {
     Ok(match opcode {
-        0x60 => StructuralType::Func(reader.read()?),
-        0x5e => StructuralType::Array(reader.read()?),
-        0x5f => StructuralType::Struct(reader.read()?),
+        0x60 => CompositeType::Func(reader.read()?),
+        0x5e => CompositeType::Array(reader.read()?),
+        0x5f => CompositeType::Struct(reader.read()?),
         x => return reader.invalid_leading_byte(x, "type"),
     })
 }
 
 impl<'a> FromReader<'a> for RecGroup {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
-        Ok(match reader.peek()? {
+        match reader.peek()? {
             0x4e => {
                 reader.read_u8()?;
                 let types = reader.read_iter(MAX_WASM_TYPES, "rec group types")?;
-                RecGroup::Many(types.collect::<Result<_>>()?)
+                Ok(RecGroup::explicit(types.collect::<Result<_>>()?))
             }
-            _ => RecGroup::Single(reader.read()?),
-        })
+            _ => Ok(RecGroup::implicit(reader.read()?)),
+        }
     }
 }
 
@@ -1186,13 +483,13 @@ impl<'a> FromReader<'a> for SubType {
                 SubType {
                     is_final: opcode == 0x4f,
                     supertype_idx: idxs.first().copied(),
-                    structural_type: read_structural_type(reader.read_u8()?, reader)?,
+                    composite_type: read_composite_type(reader.read_u8()?, reader)?,
                 }
             }
             opcode => SubType {
                 is_final: true,
                 supertype_idx: None,
-                structural_type: read_structural_type(opcode, reader)?,
+                composite_type: read_composite_type(opcode, reader)?,
             },
         })
     }
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index c64233f8..30a8b3a5 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -311,7 +311,7 @@ impl WasmFeatures {
                     }
                     // indexed types require at least the function-references
                     // proposal
-                    (HeapType::Indexed(_), _) => {
+                    (HeapType::Concrete(_), _) => {
                         if self.function_references {
                             Ok(())
                         } else {
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 8550bf28..97e5ff88 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -19,9 +19,9 @@ use crate::{
         TupleType, TypeInfo, VariantType,
     },
     BinaryReaderError, CanonicalOption, ComponentExternName, ComponentExternalKind,
-    ComponentOuterAliasKind, ComponentTypeRef, ExternalKind, FuncType, GlobalType,
-    InstantiationArgKind, MemoryType, RecGroup, Result, StructuralType, SubType, TableType,
-    TypeBounds, ValType, WasmFeatures,
+    ComponentOuterAliasKind, ComponentTypeRef, CompositeType, ExternalKind, FuncType, GlobalType,
+    InstantiationArgKind, MemoryType, RecGroup, Result, SubType, TableType, TypeBounds, ValType,
+    WasmFeatures,
 };
 use indexmap::{map::Entry, IndexMap, IndexSet};
 use std::collections::{HashMap, HashSet};
@@ -392,7 +392,7 @@ impl ComponentState {
                 // function and has the correct signature.
                 if let Some(dtor) = dtor {
                     let ty = component.core_function_at(dtor, offset)?;
-                    let ty = types[ty].structural_type.unwrap_func();
+                    let ty = types[ty].composite_type.unwrap_func();
                     if ty.params() != [rep] || ty.results() != [] {
                         bail!(
                             offset,
@@ -1002,7 +1002,7 @@ impl ComponentState {
         let lowered_ty = SubType {
             is_final: false,
             supertype_idx: None,
-            structural_type: StructuralType::Func(info.into_func_type()),
+            composite_type: CompositeType::Func(info.into_func_type()),
         };
 
         let id = types.push_ty(lowered_ty);
@@ -1021,7 +1021,7 @@ impl ComponentState {
         let core_ty = SubType {
             is_final: false,
             supertype_idx: None,
-            structural_type: StructuralType::Func(FuncType::new([rep], [ValType::I32])),
+            composite_type: CompositeType::Func(FuncType::new([rep], [ValType::I32])),
         };
         self.core_funcs.push(types.push_ty(core_ty));
         Ok(())
@@ -1037,7 +1037,7 @@ impl ComponentState {
         let core_ty = SubType {
             is_final: false,
             supertype_idx: None,
-            structural_type: StructuralType::Func(FuncType::new([ValType::I32], [])),
+            composite_type: CompositeType::Func(FuncType::new([ValType::I32], [])),
         };
         self.core_funcs.push(types.push_ty(core_ty));
         Ok(())
@@ -1053,7 +1053,7 @@ impl ComponentState {
         let core_ty = SubType {
             is_final: false,
             supertype_idx: None,
-            structural_type: StructuralType::Func(FuncType::new([ValType::I32], [rep])),
+            composite_type: CompositeType::Func(FuncType::new([ValType::I32], [rep])),
         };
         self.core_funcs.push(types.push_ty(core_ty));
         Ok(())
@@ -1473,7 +1473,7 @@ impl ComponentState {
         for decl in decls {
             match decl {
                 crate::ModuleTypeDeclaration::Type(ty) => {
-                    state.add_types(&RecGroup::Single(ty), features, types, offset, true)?;
+                    state.add_types(&RecGroup::implicit(ty), features, types, offset, true)?;
                 }
                 crate::ModuleTypeDeclaration::Export { name, ty } => {
                     let ty = state.check_type_ref(&ty, features, types, offset)?;
@@ -1482,9 +1482,10 @@ impl ComponentState {
                 crate::ModuleTypeDeclaration::OuterAlias { kind, count, index } => {
                     if count > 1 {
                         return Err(BinaryReaderError::new(
-                                    "outer type aliases in module type declarations are limited to a maximum count of 1",
-                                    offset,
-                                ));
+                            "outer type aliases in module type declarations are limited to a \
+                             maximum count of 1",
+                            offset,
+                        ));
                     }
                     match kind {
                         crate::OuterAliasKind::Type => {
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index 46fee3e7..f371b673 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -9,9 +9,9 @@ use crate::limits::*;
 use crate::readers::Matches;
 use crate::validator::core::arc::MaybeOwned;
 use crate::{
-    BinaryReaderError, ConstExpr, Data, DataKind, Element, ElementKind, ExternalKind, FuncType,
-    Global, GlobalType, HeapType, MemoryType, RecGroup, RefType, Result, StorageType,
-    StructuralType, SubType, Table, TableInit, TableType, TagType, TypeRef, ValType, VisitOperator,
+    BinaryReaderError, CompositeType, ConstExpr, Data, DataKind, Element, ElementKind,
+    ExternalKind, FuncType, Global, GlobalType, HeapType, MemoryType, RecGroup, RefType, Result,
+    StorageType, SubType, Table, TableInit, TableType, TagType, TypeRef, ValType, VisitOperator,
     WasmFeatures, WasmModuleResources,
 };
 
@@ -504,7 +504,8 @@ impl Module {
         offset: usize,
         check_limit: bool,
     ) -> Result<()> {
-        if matches!(&rec_group, RecGroup::Many(_)) && !features.gc {
+        debug_assert!(rec_group.is_explicit_rec_group() || rec_group.types().len() == 1);
+        if rec_group.is_explicit_rec_group() && !features.gc {
             bail!(
                 offset,
                 "rec group usage requires `gc` proposal to be enabled"
@@ -556,7 +557,7 @@ impl Module {
             bail!(offset, "gc proposal must be enabled to use subtypes");
         }
 
-        self.check_structural_type(&ty.structural_type, features, offset)?;
+        self.check_composite_type(&ty.composite_type, features, offset)?;
 
         if let Some(supertype_index) = ty.supertype_idx {
             // Check the supertype exists, is not final, and the subtype matches it.
@@ -575,14 +576,14 @@ impl Module {
         Ok(())
     }
 
-    fn check_structural_type(
+    fn check_composite_type(
         &mut self,
-        ty: &StructuralType,
+        ty: &CompositeType,
         features: &WasmFeatures,
         offset: usize,
     ) -> Result<()> {
         match ty {
-            StructuralType::Func(t) => {
+            CompositeType::Func(t) => {
                 for ty in t.params().iter().chain(t.results()) {
                     self.check_value_type(*ty, features, offset)?;
                 }
@@ -593,7 +594,7 @@ impl Module {
                     ));
                 }
             }
-            StructuralType::Array(t) => {
+            CompositeType::Array(t) => {
                 if !features.gc {
                     return Err(BinaryReaderError::new(
                         "array indexed types not supported without the gc feature",
@@ -607,7 +608,7 @@ impl Module {
                     }
                 };
             }
-            StructuralType::Struct(t) => {
+            CompositeType::Struct(t) => {
                 if !features.gc {
                     return Err(BinaryReaderError::new(
                         "struct indexed types not supported without the gc feature",
@@ -756,8 +757,8 @@ impl Module {
         types: &'a TypeList,
         offset: usize,
     ) -> Result<&'a FuncType> {
-        match &self.sub_type_at(types, type_index, offset)?.structural_type {
-            StructuralType::Func(f) => Ok(f),
+        match &self.sub_type_at(types, type_index, offset)?.composite_type {
+            CompositeType::Func(f) => Ok(f),
             _ => bail!(offset, "type index {type_index} is not a function type"),
         }
     }
@@ -928,7 +929,7 @@ impl Module {
             | HeapType::Struct
             | HeapType::Array
             | HeapType::I31 => (),
-            HeapType::Indexed(type_index) => {
+            HeapType::Concrete(type_index) => {
                 // Just check that the index is valid
                 self.type_id_at(type_index, offset)?;
             }
@@ -1140,7 +1141,11 @@ impl WasmModuleResources for OperatorValidatorResources<'_> {
     }
 
     fn func_type_at(&self, at: u32) -> Option<&Self::FuncType> {
-        Some(self.types[*self.module.types.get(at as usize)?].unwrap_func())
+        let id = *self.module.types.get(at as usize)?;
+        match &self.types[id].composite_type {
+            CompositeType::Func(f) => Some(f),
+            _ => None,
+        }
     }
 
     fn type_index_of_function(&self, at: u32) -> Option<u32> {
@@ -1192,7 +1197,11 @@ impl WasmModuleResources for ValidatorResources {
     }
 
     fn tag_at(&self, at: u32) -> Option<&Self::FuncType> {
-        Some(self.0.snapshot.as_ref().unwrap()[*self.0.tags.get(at as usize)?].unwrap_func())
+        let id = *self.0.tags.get(at as usize)?;
+        match &self.0.snapshot.as_ref().unwrap()[id].composite_type {
+            CompositeType::Func(f) => Some(f),
+            _ => None,
+        }
     }
 
     fn global_at(&self, at: u32) -> Option<GlobalType> {
@@ -1200,7 +1209,11 @@ impl WasmModuleResources for ValidatorResources {
     }
 
     fn func_type_at(&self, at: u32) -> Option<&Self::FuncType> {
-        Some(self.0.snapshot.as_ref().unwrap()[*self.0.types.get(at as usize)?].unwrap_func())
+        let id = *self.0.types.get(at as usize)?;
+        match &self.0.snapshot.as_ref().unwrap()[id].composite_type {
+            CompositeType::Func(f) => Some(f),
+            _ => None,
+        }
     }
 
     fn type_index_of_function(&self, at: u32) -> Option<u32> {
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 308a464b..feab8786 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -1272,13 +1272,13 @@ where
         Ok(())
     }
     fn visit_call_ref(&mut self, type_index: u32) -> Self::Output {
-        let hty = HeapType::Indexed(type_index);
+        let hty = HeapType::Concrete(type_index);
         self.resources
             .check_heap_type(hty, &self.features, self.offset)?;
         // If `None` is popped then that means a "bottom" type was popped which
         // is always considered equivalent to the `hty` tag.
         if let Some(rt) = self.pop_ref()? {
-            let expected = RefType::indexed_func(true, type_index)
+            let expected = RefType::concrete(true, type_index)
                 .expect("existing heap types should be within our limits");
             if !self
                 .resources
@@ -2292,7 +2292,7 @@ where
         // proposals.
         if self.features.function_references {
             self.push_operand(
-                RefType::indexed_func(false, type_index)
+                RefType::concrete(false, type_index)
                     .expect("our limits on number of types should fit into ref type"),
             )?;
         } else {
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index eec81143..786f63b5 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -6,8 +6,8 @@ use super::{
 };
 use crate::validator::names::KebabString;
 use crate::{
-    BinaryReaderError, Export, ExternalKind, FuncType, GlobalType, Import, MemoryType,
-    PrimitiveValType, RefType, Result, StructuralType, SubType, TableType, TypeRef, ValType,
+    BinaryReaderError, CompositeType, Export, ExternalKind, FuncType, GlobalType, Import,
+    MemoryType, PrimitiveValType, RefType, Result, SubType, TableType, TypeRef, ValType,
 };
 use indexmap::{IndexMap, IndexSet};
 use std::collections::HashMap;
@@ -294,10 +294,10 @@ impl TypeData for SubType {
 
     fn type_info(&self, _types: &TypeList) -> TypeInfo {
         // TODO(#1036): calculate actual size for func, array, struct.
-        let size = 1 + match &self.structural_type {
-            StructuralType::Func(ty) => 1 + (ty.params().len() + ty.results().len()) as u32,
-            StructuralType::Array(_) => 2,
-            StructuralType::Struct(ty) => 1 + 2 * ty.fields.len() as u32,
+        let size = 1 + match &self.composite_type {
+            CompositeType::Func(ty) => 1 + (ty.params().len() + ty.results().len()) as u32,
+            CompositeType::Array(_) => 2,
+            CompositeType::Struct(ty) => 1 + 2 * ty.fields.len() as u32,
         };
         TypeInfo::core(size)
     }
@@ -314,10 +314,10 @@ impl CoreType {
 
     /// Get the underlying `FuncType` within this `SubType` or panic.
     pub fn unwrap_func(&self) -> &FuncType {
-        match &self.unwrap_sub().structural_type {
-            StructuralType::Func(f) => f,
-            StructuralType::Array(_) | StructuralType::Struct(_) => {
-                panic!("`unwrap_func` on non-func structural type")
+        match &self.unwrap_sub().composite_type {
+            CompositeType::Func(f) => f,
+            CompositeType::Array(_) | CompositeType::Struct(_) => {
+                panic!("`unwrap_func` on non-func composite type")
             }
         }
     }
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 1a6b0386..a5cdc8cf 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -641,9 +641,9 @@ impl Printer {
         )?;
         let ty = match ty {
             CoreType::Sub(ty) => {
-                let ty = match &ty.structural_type {
-                    StructuralType::Func(f) => f,
-                    StructuralType::Array(_) | StructuralType::Struct(_) => {
+                let ty = match &ty.composite_type {
+                    CompositeType::Func(f) => f,
+                    CompositeType::Array(_) | CompositeType::Struct(_) => {
                         unreachable!("Wasm GC types cannot appear in components yet")
                     }
                 };
@@ -654,7 +654,7 @@ impl Printer {
                 Some(SubType {
                     is_final: true,
                     supertype_idx: None,
-                    structural_type: StructuralType::Func(ty.clone()),
+                    composite_type: CompositeType::Func(ty.clone()),
                 })
             }
             CoreType::Module(decls) => {
@@ -668,7 +668,12 @@ impl Printer {
         Ok(())
     }
 
-    fn print_rec(&mut self, state: &mut State, offset: usize, types: Vec<SubType>) -> Result<()> {
+    fn print_rec(
+        &mut self,
+        state: &mut State,
+        offset: usize,
+        types: impl Iterator<Item = SubType>,
+    ) -> Result<()> {
         self.start_group("rec");
         for ty in types {
             self.newline(offset + 2);
@@ -692,35 +697,35 @@ impl Printer {
         let r = if !ty.is_final || !ty.supertype_idx.is_none() {
             self.start_group("sub");
             self.print_sub_type(state, ty)?;
-            let r = self.print_structural(state, &ty.structural_type, names_for)?;
+            let r = self.print_composite(state, &ty.composite_type, names_for)?;
             self.end_group(); // `sub`
             r
         } else {
-            self.print_structural(state, &ty.structural_type, names_for)?
+            self.print_composite(state, &ty.composite_type, names_for)?
         };
         Ok(r)
     }
 
-    fn print_structural(
+    fn print_composite(
         &mut self,
         state: &State,
-        ty: &StructuralType,
+        ty: &CompositeType,
         names_for: Option<u32>,
     ) -> Result<u32> {
         let r = match &ty {
-            StructuralType::Func(ty) => {
+            CompositeType::Func(ty) => {
                 self.start_group("func");
                 let r = self.print_func_type(state, ty, names_for)?;
                 self.end_group(); // `func`
                 r
             }
-            StructuralType::Array(ty) => {
+            CompositeType::Array(ty) => {
                 self.start_group("array");
                 let r = self.print_array_type(ty)?;
                 self.end_group(); // `array`
                 r
             }
-            StructuralType::Struct(ty) => {
+            CompositeType::Struct(ty) => {
                 self.start_group("struct");
                 let r = self.print_struct_type(ty)?;
                 self.end_group(); // `struct`
@@ -748,9 +753,11 @@ impl Printer {
         for ty in parser.into_iter_with_offsets() {
             let (offset, rec_group) = ty?;
             self.newline(offset);
-            match rec_group {
-                RecGroup::Many(items) => self.print_rec(state, offset, items)?,
-                RecGroup::Single(ty) => self.print_type(state, ty)?,
+            if rec_group.types().len() == 1 {
+                let ty = rec_group.into_types().next().unwrap();
+                self.print_type(state, ty)?;
+            } else {
+                self.print_rec(state, offset, rec_group.into_types())?
             }
         }
 
@@ -767,7 +774,7 @@ impl Printer {
 
         match state.core.types.get(idx as usize) {
             Some(Some(SubType {
-                structural_type: StructuralType::Func(ty),
+                composite_type: CompositeType::Func(ty),
                 ..
             })) => self.print_func_type(state, ty, names_for).map(Some),
             Some(Some(_)) | Some(None) | None => Ok(None),
@@ -905,7 +912,7 @@ impl Printer {
             HeapType::Struct => self.result.push_str("struct"),
             HeapType::Array => self.result.push_str("array"),
             HeapType::I31 => self.result.push_str("i31"),
-            HeapType::Indexed(i) => self.result.push_str(&format!("{}", u32::from(i))),
+            HeapType::Concrete(i) => self.result.push_str(&format!("{}", u32::from(i))),
         }
         Ok(())
     }
diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs
index 7edb4c2b..24e45893 100644
--- a/crates/wast/src/component/binary.rs
+++ b/crates/wast/src/component/binary.rs
@@ -602,8 +602,8 @@ impl From<core::HeapType<'_>> for wasm_encoder::HeapType {
             core::HeapType::Exn => {
                 todo!("encoding of exceptions proposal types not yet implemented")
             }
-            core::HeapType::Index(Index::Num(i, _)) => Self::Indexed(i),
-            core::HeapType::Index(_) => panic!("unresolved index"),
+            core::HeapType::Concrete(Index::Num(i, _)) => Self::Concrete(i),
+            core::HeapType::Concrete(_) => panic!("unresolved index"),
             core::HeapType::Any
             | core::HeapType::Eq
             | core::HeapType::Struct
diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs
index 3e3169aa..c0122ef7 100644
--- a/crates/wast/src/component/resolve.rs
+++ b/crates/wast/src/component/resolve.rs
@@ -532,7 +532,7 @@ impl<'a> Resolver<'a> {
                         | core::HeapType::None
                         | core::HeapType::NoFunc
                         | core::HeapType::NoExtern => {}
-                        core::HeapType::Index(id) => {
+                        core::HeapType::Concrete(id) => {
                             self.resolve_ns(id, Ns::Type)?;
                         }
                     },
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 3cfe18a3..2daba2aa 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -257,8 +257,8 @@ impl<'a> Encode for HeapType<'a> {
             HeapType::None => e.push(0x71),
             // Note that this is encoded as a signed leb128 so be sure to cast
             // to an i64 first
-            HeapType::Index(Index::Num(n, _)) => i64::from(*n).encode(e),
-            HeapType::Index(Index::Id(n)) => {
+            HeapType::Concrete(Index::Num(n, _)) => i64::from(*n).encode(e),
+            HeapType::Concrete(Index::Id(n)) => {
                 panic!("unresolved index in emission: {:?}", n)
             }
         }
diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs
index 4f252ad4..b06eb67f 100644
--- a/crates/wast/src/core/resolve/names.rs
+++ b/crates/wast/src/core/resolve/names.rs
@@ -293,7 +293,7 @@ impl<'a> Resolver<'a> {
 
     fn resolve_heaptype(&self, ty: &mut HeapType<'a>) -> Result<(), Error> {
         match ty {
-            HeapType::Index(i) => {
+            HeapType::Concrete(i) => {
                 self.resolve(i, Ns::Type)?;
             }
             _ => {}
diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs
index 4bf69fe0..5f9489fd 100644
--- a/crates/wast/src/core/types.rs
+++ b/crates/wast/src/core/types.rs
@@ -86,9 +86,9 @@ pub enum HeapType<'a> {
     NoExtern,
     /// The bottom type of the anyref hierarchy. Part of the GC proposal.
     None,
-    /// A reference to a function, struct, or array: ref T. This is part of the
-    /// GC proposal.
-    Index(Index<'a>),
+    /// A reference to a concrete function, struct, or array type defined by
+    /// Wasm: `ref T`. This is part of the function references and GC proposals.
+    Concrete(Index<'a>),
 }
 
 impl<'a> Parse<'a> for HeapType<'a> {
@@ -128,7 +128,7 @@ impl<'a> Parse<'a> for HeapType<'a> {
             parser.parse::<kw::none>()?;
             Ok(HeapType::None)
         } else if l.peek::<Index>()? {
-            Ok(HeapType::Index(parser.parse()?))
+            Ok(HeapType::Concrete(parser.parse()?))
         } else {
             Err(l.error())
         }
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index c4e1dff6..c5a7928b 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -492,7 +492,7 @@ impl<'a> Module<'a> {
             | HeapType::Struct
             | HeapType::Array
             | HeapType::I31 => {}
-            HeapType::Indexed(i) => self.ty(i),
+            HeapType::Concrete(i) => self.ty(i),
         }
     }
 
@@ -1114,7 +1114,7 @@ impl Encoder {
             HeapType::Struct => wasm_encoder::HeapType::Struct,
             HeapType::Array => wasm_encoder::HeapType::Array,
             HeapType::I31 => wasm_encoder::HeapType::I31,
-            HeapType::Indexed(idx) => wasm_encoder::HeapType::Indexed(self.types.remap(idx)),
+            HeapType::Concrete(idx) => wasm_encoder::HeapType::Concrete(self.types.remap(idx)),
         }
     }
 }


Test patch:
diff --git a/tests/cli/dump/alias.wat.stdout b/tests/cli/dump/alias.wat.stdout
index a8089ee7..37f9689e 100644
--- a/tests/cli/dump/alias.wat.stdout
+++ b/tests/cli/dump/alias.wat.stdout
@@ -30,7 +30,7 @@
         | 01 00 00 00
    0x57 | 01 04       | type section
    0x59 | 01          | 1 count
-   0x5a | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x5a | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x5d | 03 02       | func section
    0x5f | 01          | 1 count
    0x60 | 00          | [func 0] type 0
diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout
index b43761d6..b489dc93 100644
--- a/tests/cli/dump/alias2.wat.stdout
+++ b/tests/cli/dump/alias2.wat.stdout
@@ -126,7 +126,7 @@
          | 01 00 00 00
    0x158 | 01 04       | type section
    0x15a | 01          | 1 count
-   0x15b | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x15b | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x15e | 03 02       | func section
    0x160 | 01          | 1 count
    0x161 | 00          | [func 0] type 0
@@ -162,7 +162,7 @@
          | 01 00 00 00
    0x1a2 | 01 04       | type section
    0x1a4 | 01          | 1 count
-   0x1a5 | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x1a5 | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x1a8 | 02 19       | import section
    0x1aa | 04          | 4 count
    0x1ab | 00 01 31 00 | import [func 0] Import { module: "", name: "1", ty: Func(0) }
diff --git a/tests/cli/dump/blockty.wat.stdout b/tests/cli/dump/blockty.wat.stdout
index 9dc651ec..6bc841ea 100644
--- a/tests/cli/dump/blockty.wat.stdout
+++ b/tests/cli/dump/blockty.wat.stdout
@@ -2,12 +2,12 @@
       | 01 00 00 00
   0x8 | 01 17       | type section
   0xa | 05          | 5 count
-  0xb | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
-  0xe | 60 00 01 7f | [type 1] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [I32] }) })
- 0x12 | 60 01 7f 00 | [type 2] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32], returns: [] }) })
- 0x16 | 60 01 7f 01 | [type 3] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32], returns: [I32] }) })
+  0xb | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
+  0xe | 60 00 01 7f | [type 1] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [I32] }) }) }
+ 0x12 | 60 01 7f 00 | [type 2] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) }) }
+ 0x16 | 60 01 7f 01 | [type 3] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [I32] }) }) }
       | 7f         
- 0x1b | 60 01 7f 02 | [type 4] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32], returns: [I32, I32] }) })
+ 0x1b | 60 01 7f 02 | [type 4] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [I32, I32] }) }) }
       | 7f 7f      
  0x21 | 03 02       | func section
  0x23 | 01          | 1 count
diff --git a/tests/cli/dump/bundled.wat.stdout b/tests/cli/dump/bundled.wat.stdout
index 51d43108..ad449f16 100644
--- a/tests/cli/dump/bundled.wat.stdout
+++ b/tests/cli/dump/bundled.wat.stdout
@@ -25,7 +25,7 @@
          | 01 00 00 00
     0x54 | 01 09       | type section
     0x56 | 01          | 1 count
-    0x57 | 60 04 7f 7f | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32, I32, I32, I32], returns: [I32] }) })
+    0x57 | 60 04 7f 7f | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32, I32], results: [I32] }) }) }
          | 7f 7f 01 7f
     0x5f | 03 02       | func section
     0x61 | 01          | 1 count
@@ -61,9 +61,9 @@
          | 01 00 00 00
     0xa0 | 01 09       | type section
     0xa2 | 02          | 2 count
-    0xa3 | 60 02 7f 7f | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32, I32], returns: [] }) })
+    0xa3 | 60 02 7f 7f | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32], results: [] }) }) }
          | 00         
-    0xa8 | 60 00 00    | [type 1] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+    0xa8 | 60 00 00    | [type 1] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
     0xab | 02 12       | import section
     0xad | 01          | 1 count
     0xae | 09 77 61 73 | import [func 0] Import { module: "wasi-file", name: "read", ty: Func(0) }
@@ -103,9 +103,9 @@
          | 01 00 00 00
    0x101 | 01 0c       | type section
    0x103 | 02          | 2 count
-   0x104 | 60 02 7f 7f | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32, I32], returns: [] }) })
+   0x104 | 60 02 7f 7f | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32], results: [] }) }) }
          | 00         
-   0x109 | 60 03 7f 7f | [type 1] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32, I32, I32], returns: [] }) })
+   0x109 | 60 03 7f 7f | [type 1] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32], results: [] }) }) }
          | 7f 00      
    0x10f | 02 12       | import section
    0x111 | 01          | 1 count
diff --git a/tests/cli/dump/component-expand-bundle.wat.stdout b/tests/cli/dump/component-expand-bundle.wat.stdout
index a1a2b3f0..4fcd4afc 100644
--- a/tests/cli/dump/component-expand-bundle.wat.stdout
+++ b/tests/cli/dump/component-expand-bundle.wat.stdout
@@ -5,7 +5,7 @@
         | 01 00 00 00
    0x12 | 01 04       | type section
    0x14 | 01          | 1 count
-   0x15 | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x15 | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x18 | 03 02       | func section
    0x1a | 01          | 1 count
    0x1b | 00          | [func 0] type 0
@@ -28,7 +28,7 @@
         | 01 00 00 00
    0x3d | 01 04       | type section
    0x3f | 01          | 1 count
-   0x40 | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x40 | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x43 | 02 06       | import section
    0x45 | 01          | 1 count
    0x46 | 00 01 61 00 | import [func 0] Import { module: "", name: "a", ty: Func(0) }
diff --git a/tests/cli/dump/import-modules.wat.stdout b/tests/cli/dump/import-modules.wat.stdout
index 7609dbc7..4c4f2b55 100644
--- a/tests/cli/dump/import-modules.wat.stdout
+++ b/tests/cli/dump/import-modules.wat.stdout
@@ -2,7 +2,7 @@
       | 0d 00 01 00
   0x8 | 03 0d       | core type section
   0xa | 01          | 1 count
-  0xb | 50 02 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) })])
+  0xb | 50 02 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) })])
       | 00 00 00 00
       | 01 66 00 00
  0x17 | 0a 07       | component import section
@@ -14,7 +14,7 @@
         | 01 00 00 00
    0x2a | 01 04       | type section
    0x2c | 01          | 1 count
-   0x2d | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+   0x2d | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
    0x30 | 03 02       | func section
    0x32 | 01          | 1 count
    0x33 | 00          | [func 0] type 0
diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout
index e4fbcbe6..2430291f 100644
--- a/tests/cli/dump/module-types.wat.stdout
+++ b/tests/cli/dump/module-types.wat.stdout
@@ -2,7 +2,7 @@
       | 0d 00 01 00
   0x8 | 03 23       | core type section
   0xa | 01          | 1 count
-  0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None }) })])
+  0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None }) })])
       | 00 00 00 00
       | 01 66 00 00
       | 00 00 01 67
diff --git a/tests/cli/dump/names.wat.stdout b/tests/cli/dump/names.wat.stdout
index 35981448..32927bfc 100644
--- a/tests/cli/dump/names.wat.stdout
+++ b/tests/cli/dump/names.wat.stdout
@@ -2,7 +2,7 @@
       | 01 00 00 00
   0x8 | 01 05       | type section
   0xa | 01          | 1 count
-  0xb | 60 01 7f 00 | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32], returns: [] }) })
+  0xb | 60 01 7f 00 | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) }) }
   0xf | 03 02       | func section
  0x11 | 01          | 1 count
  0x12 | 00          | [func 0] type 0
diff --git a/tests/cli/dump/select.wat.stdout b/tests/cli/dump/select.wat.stdout
index 07d338db..2368e115 100644
--- a/tests/cli/dump/select.wat.stdout
+++ b/tests/cli/dump/select.wat.stdout
@@ -2,7 +2,7 @@
       | 01 00 00 00
   0x8 | 01 04       | type section
   0xa | 01          | 1 count
-  0xb | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+  0xb | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
   0xe | 03 02       | func section
  0x10 | 01          | 1 count
  0x11 | 00          | [func 0] type 0
diff --git a/tests/cli/dump/simple.wat.stdout b/tests/cli/dump/simple.wat.stdout
index 12c36351..d9c3f0df 100644
--- a/tests/cli/dump/simple.wat.stdout
+++ b/tests/cli/dump/simple.wat.stdout
@@ -2,8 +2,8 @@
       | 01 00 00 00
   0x8 | 01 08       | type section
   0xa | 02          | 2 count
-  0xb | 60 01 7f 00 | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [I32], returns: [] }) })
-  0xf | 60 00 00    | [type 1] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+  0xb | 60 01 7f 00 | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) }) }
+  0xf | 60 00 00    | [type 1] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
  0x12 | 02 07       | import section
  0x14 | 01          | 1 count
  0x15 | 01 6d 01 6e | import [func 0] Import { module: "m", name: "n", ty: Func(0) }
diff --git a/tests/cli/dump/try-delegate.wat.stdout b/tests/cli/dump/try-delegate.wat.stdout
index bb0255f1..5503e0d4 100644
--- a/tests/cli/dump/try-delegate.wat.stdout
+++ b/tests/cli/dump/try-delegate.wat.stdout
@@ -2,7 +2,7 @@
       | 01 00 00 00
   0x8 | 01 04       | type section
   0xa | 01          | 1 count
-  0xb | 60 00 00    | [type 0] Single(SubType { is_final: true, supertype_idx: None, structural_type: Func(FuncType { params: [], returns: [] }) })
+  0xb | 60 00 00    | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) }
   0xe | 03 02       | func section
  0x10 | 01          | 1 count
  0x11 | 00          | [func 0] type 0