From 3cda0803d7aa49f71ab4b9623a19a3fe6fbe082b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:14:54 +0000 Subject: [PATCH 1/9] rename types.rs to types/mod.rs --- src/{types.rs => types/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{types.rs => types/mod.rs} (100%) diff --git a/src/types.rs b/src/types/mod.rs similarity index 100% rename from src/types.rs rename to src/types/mod.rs From 1ddfd40b42dfdff1effe7d5f8dab154da0485279 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:18:26 +0000 Subject: [PATCH 2/9] types: add README.md with notes --- src/types/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/types/README.md diff --git a/src/types/README.md b/src/types/README.md new file mode 100644 index 00000000..a313c1fd --- /dev/null +++ b/src/types/README.md @@ -0,0 +1,22 @@ +# Types in SimplicityHL + +There are three type structures in SimplicityHL: + +* `StructuralType` is essentially a copy of `simplicity::types::Final`; there are three structural + types: unit, sum and product, and these correspond to the types in the compiled Simplicity code. + +* `ResolvedType` is a SimplicityHL type; this extends `StructuralType` by adding lists, tuples, + enums, and some other stuff. + + Each `ResolvedType` can be "lowered" via `From` to a `StructuralType`. In general, an expression + of the form A -> B, where A and B are `ResolvedType`s, will compile to a Simplicity expression + whose source and target types are the lowerings of A and B, respectively. In the compiler we + explicitly call `unify` on the Simplicity type inference engine to enforce this. + +* `AliasedType` is a copy of `ResolvedType` where everything is a (re)name. Essentially they are + "AST types". They feature primarily in parse.rs and ast.rs. Before these can be used, we call + `aliased_type.resolve()` to get a `ResolvedType`. + +SimplicityHL does *not* currently support any form of nominal typing. All structurally equal types +are considered interchangeable. + From 8e45727fa92f1dc7c142f5f80909681ff7db1ac9 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:43:39 +0000 Subject: [PATCH 3/9] types: move TypeInner and UIntType to inner.rs module code move only --- src/types/inner.rs | 330 +++++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 324 +------------------------------------------- 2 files changed, 335 insertions(+), 319 deletions(-) create mode 100644 src/types/inner.rs diff --git a/src/types/inner.rs b/src/types/inner.rs new file mode 100644 index 00000000..7248a01d --- /dev/null +++ b/src/types/inner.rs @@ -0,0 +1,330 @@ +use core::fmt; +use core::str::FromStr; +use std::sync::Arc; + +use crate::num::{NonZeroPow2Usize, Pow2Usize}; +use crate::str::Identifier; + +use super::{ResolvedType, StructuralType, TypeConstructible as _}; + +/// Primitives of the SimplicityHL type system, excluding type aliases. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[non_exhaustive] +pub enum TypeInner { + /// Sum of the left and right types + Either(A, A), + /// Option of the inner type + Option(A), + /// Boolean type + Boolean, + /// Unsigned integer type + UInt(UIntType), + /// Tuple of potentially different types + Tuple(Arc<[A]>), + /// Array of the same type + Array(A, usize), + /// List of the same type + List(A, NonZeroPow2Usize), + /// Nominal enum type, represented as a balanced sum of its variants' + /// payload types + Enum(EnumInfo), +} + +impl TypeInner { + /// Helper method for displaying type primitives based on the number of yielded children. + /// + /// We cannot implement [`fmt::Display`] because `n_children_yielded` is an extra argument. + pub(super) fn display( + &self, + f: &mut fmt::Formatter<'_>, + n_children_yielded: usize, + ) -> fmt::Result { + match self { + TypeInner::Either(_, _) => match n_children_yielded { + 0 => f.write_str("Either<"), + 1 => f.write_str(", "), + n => { + debug_assert_eq!(n, 2); + f.write_str(">") + } + }, + TypeInner::Option(_) => match n_children_yielded { + 0 => f.write_str("Option<"), + n => { + debug_assert_eq!(n, 1); + f.write_str(">") + } + }, + TypeInner::Boolean => f.write_str("bool"), + TypeInner::UInt(ty) => write!(f, "{ty}"), + TypeInner::Tuple(elements) => match n_children_yielded { + 0 => { + f.write_str("(")?; + if elements.is_empty() { + f.write_str(")")?; + } + Ok(()) + } + n if n == elements.len() => { + if n == 1 { + f.write_str(",")?; + } + f.write_str(")") + } + n => { + debug_assert!(n < elements.len()); + f.write_str(", ") + } + }, + TypeInner::Array(_, size) => match n_children_yielded { + 0 => f.write_str("["), + n => { + debug_assert_eq!(n, 1); + write!(f, "; {size}]") + } + }, + TypeInner::List(_, bound) => match n_children_yielded { + 0 => f.write_str("List<"), + n => { + debug_assert_eq!(n, 1); + write!(f, ", {bound}>") + } + }, + TypeInner::Enum(info) => write!(f, "{}", info.name()), + } + } +} + +/// Unsigned integer type. +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum UIntType { + /// 1-bit unsigned integer + U1, + /// 2-bit unsigned integer + U2, + /// 4-bit unsigned integer + U4, + /// 8-bit unsigned integer + U8, + /// 16-bit unsigned integer + U16, + /// 32-bit unsigned integer + U32, + /// 64-bit unsigned integer + U64, + /// 128-bit unsigned integer + U128, + /// 256-bit unsigned integer + U256, +} + +impl UIntType { + /// Take `n` and return the `2^n`-bit unsigned integer type. + pub const fn two_n(n: u32) -> Option { + match n { + 0 => Some(UIntType::U1), + 1 => Some(UIntType::U2), + 2 => Some(UIntType::U4), + 3 => Some(UIntType::U8), + 4 => Some(UIntType::U16), + 5 => Some(UIntType::U32), + 6 => Some(UIntType::U64), + 7 => Some(UIntType::U128), + 8 => Some(UIntType::U256), + _ => None, + } + } + + /// Return the bit width of values of this type. + pub const fn bit_width(self) -> Pow2Usize { + let bit_width: usize = match self { + UIntType::U1 => 1, + UIntType::U2 => 2, + UIntType::U4 => 4, + UIntType::U8 => 8, + UIntType::U16 => 16, + UIntType::U32 => 32, + UIntType::U64 => 64, + UIntType::U128 => 128, + UIntType::U256 => 256, + }; + debug_assert!(bit_width.is_power_of_two()); + Pow2Usize::new_unchecked(bit_width) + } + + /// Create the unsigned integer type for the given `bit_width`. + pub const fn from_bit_width(bit_width: Pow2Usize) -> Option { + match bit_width.get() { + 1 => Some(UIntType::U1), + 2 => Some(UIntType::U2), + 4 => Some(UIntType::U4), + 8 => Some(UIntType::U8), + 16 => Some(UIntType::U16), + 32 => Some(UIntType::U32), + 64 => Some(UIntType::U64), + 128 => Some(UIntType::U128), + 256 => Some(UIntType::U256), + _ => None, + } + } + + /// Return the byte width of values of this type. + /// + /// Return 0 for types that take less than an entire byte: `u1`, `u2`, `u4`. + pub const fn byte_width(self) -> usize { + self.bit_width().get() / 8 + } +} + +impl fmt::Debug for UIntType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl fmt::Display for UIntType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UIntType::U1 => f.write_str("u1"), + UIntType::U2 => f.write_str("u2"), + UIntType::U4 => f.write_str("u4"), + UIntType::U8 => f.write_str("u8"), + UIntType::U16 => f.write_str("u16"), + UIntType::U32 => f.write_str("u32"), + UIntType::U64 => f.write_str("u64"), + UIntType::U128 => f.write_str("u128"), + UIntType::U256 => f.write_str("u256"), + } + } +} + +impl FromStr for UIntType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "u1" => Ok(UIntType::U1), + "u2" => Ok(UIntType::U2), + "u4" => Ok(UIntType::U4), + "u8" => Ok(UIntType::U8), + "u16" => Ok(UIntType::U16), + "u32" => Ok(UIntType::U32), + "u64" => Ok(UIntType::U64), + "u128" => Ok(UIntType::U128), + "u256" => Ok(UIntType::U256), + _ => Err("Unknown integer type".to_string()), + } + } +} + +/// Definition of a nominal enum type: its name and variants in +/// declaration order. +/// +/// An enum with `n` variants is represented as a balanced sum of its `n` +/// variant payload types (see [`BTreeSlice`] for the tree shape), so a value +/// of the type is exactly one of the `n` variants: an undeclared variant is +/// unrepresentable. A variant's position among the declared variants +/// determines its leaf in the sum; there is no separate discriminant. +/// +/// Identity is the declared name: enums may only be declared at the top +/// level of the program's own files, so the name is unique program-wide and +/// serialized forms (such as the ABI) can identify an enum by it. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct EnumInfo { + name: Arc, + variants: Arc<[EnumVariantInfo]>, +} + +impl EnumInfo { + /// Create an enum definition with the given `name` and `variants`. + /// + /// `variants` must not be empty: a sum of zero types would be + /// uninhabited, which Simplicity's type algebra cannot express. + /// A single-variant enum is a named wrapper of its payload. + pub(crate) fn new(name: Arc, variants: Arc<[EnumVariantInfo]>) -> Self { + debug_assert!(!variants.is_empty()); + Self { name, variants } + } + + /// Access the declared name of the enum. + pub fn name(&self) -> &str { + &self.name + } + + /// Access the variants of the enum in declaration order. + pub fn variants(&self) -> &[EnumVariantInfo] { + &self.variants + } + + /// Get the variant with the given `name` and its position among the + /// declared variants. + /// + /// The position determines the variant's leaf in the balanced sum. + pub fn variant(&self, name: &Identifier) -> Option<(usize, &EnumVariantInfo)> { + self.variants + .iter() + .enumerate() + .find(|(_, v)| v.name() == name) + } + + /// The structural payload types of all variants, in declaration order: + /// the leaves of the enum's balanced sum. + pub(crate) fn structural_variants(&self) -> Vec { + self.variants + .iter() + .map(EnumVariantInfo::structural_payload) + .collect() + } +} + +/// One variant of a nominal enum type: its name and payload types. +/// +/// A variant with no payload types is a unit variant; a variant with +/// payloads carries a tuple of values of those types. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct EnumVariantInfo { + name: Identifier, + payload: Arc<[ResolvedType]>, + /// The SimplicityHL type of the variant's contents: unit for unit + /// variants, the payload type itself for single payloads, a tuple + /// otherwise. Precomputed so it can be borrowed during destructuring. + payload_ty: ResolvedType, +} + +impl EnumVariantInfo { + pub(crate) fn new(name: Identifier, payload: Arc<[ResolvedType]>) -> Self { + let payload_ty = match payload.len() { + 0 => ResolvedType::unit(), + 1 => payload[0].clone(), + _ => ResolvedType::tuple(payload.iter().cloned()), + }; + Self { + name, + payload, + payload_ty, + } + } + + /// Access the name of the variant. + pub const fn name(&self) -> &Identifier { + &self.name + } + + /// Access the payload types of the variant, in declaration order. + /// Empty for unit variants. + pub fn payload(&self) -> &[ResolvedType] { + &self.payload + } + + /// The SimplicityHL type of the variant's contents, as one type. + pub fn payload_type(&self) -> &ResolvedType { + &self.payload_ty + } + + /// The structural type of the variant's contents: the leaf this + /// variant occupies in the enum's balanced sum. + pub(crate) fn structural_payload(&self) -> StructuralType { + StructuralType::from(&self.payload_ty) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 09451c2f..805fb0d1 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,3 +1,5 @@ +mod inner; + use std::fmt; use std::str::FromStr; use std::sync::Arc; @@ -6,327 +8,11 @@ use miniscript::iter::{Tree, TreeLike}; use simplicity::types::{CompleteBound, Final}; use crate::array::{BTreeSlice, Partition}; -use crate::num::{NonZeroPow2Usize, Pow2Usize}; -use crate::str::{AliasName, Identifier}; +use crate::num::NonZeroPow2Usize; +use crate::str::AliasName; use crate::unstable::impl_require_feature; -/// Primitives of the SimplicityHL type system, excluding type aliases. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -#[non_exhaustive] -pub enum TypeInner { - /// Sum of the left and right types - Either(A, A), - /// Option of the inner type - Option(A), - /// Boolean type - Boolean, - /// Unsigned integer type - UInt(UIntType), - /// Tuple of potentially different types - Tuple(Arc<[A]>), - /// Array of the same type - Array(A, usize), - /// List of the same type - List(A, NonZeroPow2Usize), - /// Nominal enum type, represented as a balanced sum of its variants' - /// payload types - Enum(EnumInfo), -} - -/// One variant of a nominal enum type: its name and payload types. -/// -/// A variant with no payload types is a unit variant; a variant with -/// payloads carries a tuple of values of those types. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct EnumVariantInfo { - name: Identifier, - payload: Arc<[ResolvedType]>, - /// The SimplicityHL type of the variant's contents: unit for unit - /// variants, the payload type itself for single payloads, a tuple - /// otherwise. Precomputed so it can be borrowed during destructuring. - payload_ty: ResolvedType, -} - -impl EnumVariantInfo { - pub(crate) fn new(name: Identifier, payload: Arc<[ResolvedType]>) -> Self { - let payload_ty = match payload.len() { - 0 => ResolvedType::unit(), - 1 => payload[0].clone(), - _ => ResolvedType::tuple(payload.iter().cloned()), - }; - Self { - name, - payload, - payload_ty, - } - } - - /// Access the name of the variant. - pub const fn name(&self) -> &Identifier { - &self.name - } - - /// Access the payload types of the variant, in declaration order. - /// Empty for unit variants. - pub fn payload(&self) -> &[ResolvedType] { - &self.payload - } - - /// The SimplicityHL type of the variant's contents, as one type. - pub fn payload_type(&self) -> &ResolvedType { - &self.payload_ty - } - - /// The structural type of the variant's contents: the leaf this - /// variant occupies in the enum's balanced sum. - pub(crate) fn structural_payload(&self) -> StructuralType { - StructuralType::from(&self.payload_ty) - } -} - -/// Definition of a nominal enum type: its name and variants in -/// declaration order. -/// -/// An enum with `n` variants is represented as a balanced sum of its `n` -/// variant payload types (see [`BTreeSlice`] for the tree shape), so a value -/// of the type is exactly one of the `n` variants: an undeclared variant is -/// unrepresentable. A variant's position among the declared variants -/// determines its leaf in the sum; there is no separate discriminant. -/// -/// Identity is the declared name: enums may only be declared at the top -/// level of the program's own files, so the name is unique program-wide and -/// serialized forms (such as the ABI) can identify an enum by it. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct EnumInfo { - name: Arc, - variants: Arc<[EnumVariantInfo]>, -} - -impl EnumInfo { - /// Create an enum definition with the given `name` and `variants`. - /// - /// `variants` must not be empty: a sum of zero types would be - /// uninhabited, which Simplicity's type algebra cannot express. - /// A single-variant enum is a named wrapper of its payload. - pub(crate) fn new(name: Arc, variants: Arc<[EnumVariantInfo]>) -> Self { - debug_assert!(!variants.is_empty()); - Self { name, variants } - } - - /// Access the declared name of the enum. - pub fn name(&self) -> &str { - &self.name - } - - /// Access the variants of the enum in declaration order. - pub fn variants(&self) -> &[EnumVariantInfo] { - &self.variants - } - - /// Get the variant with the given `name` and its position among the - /// declared variants. - /// - /// The position determines the variant's leaf in the balanced sum. - pub fn variant(&self, name: &Identifier) -> Option<(usize, &EnumVariantInfo)> { - self.variants - .iter() - .enumerate() - .find(|(_, v)| v.name() == name) - } - - /// The structural payload types of all variants, in declaration order: - /// the leaves of the enum's balanced sum. - pub(crate) fn structural_variants(&self) -> Vec { - self.variants - .iter() - .map(EnumVariantInfo::structural_payload) - .collect() - } -} - -impl TypeInner { - /// Helper method for displaying type primitives based on the number of yielded children. - /// - /// We cannot implement [`fmt::Display`] because `n_children_yielded` is an extra argument. - fn display(&self, f: &mut fmt::Formatter<'_>, n_children_yielded: usize) -> fmt::Result { - match self { - TypeInner::Either(_, _) => match n_children_yielded { - 0 => f.write_str("Either<"), - 1 => f.write_str(", "), - n => { - debug_assert_eq!(n, 2); - f.write_str(">") - } - }, - TypeInner::Option(_) => match n_children_yielded { - 0 => f.write_str("Option<"), - n => { - debug_assert_eq!(n, 1); - f.write_str(">") - } - }, - TypeInner::Boolean => f.write_str("bool"), - TypeInner::UInt(ty) => write!(f, "{ty}"), - TypeInner::Tuple(elements) => match n_children_yielded { - 0 => { - f.write_str("(")?; - if elements.is_empty() { - f.write_str(")")?; - } - Ok(()) - } - n if n == elements.len() => { - if n == 1 { - f.write_str(",")?; - } - f.write_str(")") - } - n => { - debug_assert!(n < elements.len()); - f.write_str(", ") - } - }, - TypeInner::Array(_, size) => match n_children_yielded { - 0 => f.write_str("["), - n => { - debug_assert_eq!(n, 1); - write!(f, "; {size}]") - } - }, - TypeInner::List(_, bound) => match n_children_yielded { - 0 => f.write_str("List<"), - n => { - debug_assert_eq!(n, 1); - write!(f, ", {bound}>") - } - }, - TypeInner::Enum(info) => write!(f, "{}", info.name()), - } - } -} - -/// Unsigned integer type. -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub enum UIntType { - /// 1-bit unsigned integer - U1, - /// 2-bit unsigned integer - U2, - /// 4-bit unsigned integer - U4, - /// 8-bit unsigned integer - U8, - /// 16-bit unsigned integer - U16, - /// 32-bit unsigned integer - U32, - /// 64-bit unsigned integer - U64, - /// 128-bit unsigned integer - U128, - /// 256-bit unsigned integer - U256, -} - -impl UIntType { - /// Take `n` and return the `2^n`-bit unsigned integer type. - pub const fn two_n(n: u32) -> Option { - match n { - 0 => Some(UIntType::U1), - 1 => Some(UIntType::U2), - 2 => Some(UIntType::U4), - 3 => Some(UIntType::U8), - 4 => Some(UIntType::U16), - 5 => Some(UIntType::U32), - 6 => Some(UIntType::U64), - 7 => Some(UIntType::U128), - 8 => Some(UIntType::U256), - _ => None, - } - } - - /// Return the bit width of values of this type. - pub const fn bit_width(self) -> Pow2Usize { - let bit_width: usize = match self { - UIntType::U1 => 1, - UIntType::U2 => 2, - UIntType::U4 => 4, - UIntType::U8 => 8, - UIntType::U16 => 16, - UIntType::U32 => 32, - UIntType::U64 => 64, - UIntType::U128 => 128, - UIntType::U256 => 256, - }; - debug_assert!(bit_width.is_power_of_two()); - Pow2Usize::new_unchecked(bit_width) - } - - /// Create the unsigned integer type for the given `bit_width`. - pub const fn from_bit_width(bit_width: Pow2Usize) -> Option { - match bit_width.get() { - 1 => Some(UIntType::U1), - 2 => Some(UIntType::U2), - 4 => Some(UIntType::U4), - 8 => Some(UIntType::U8), - 16 => Some(UIntType::U16), - 32 => Some(UIntType::U32), - 64 => Some(UIntType::U64), - 128 => Some(UIntType::U128), - 256 => Some(UIntType::U256), - _ => None, - } - } - - /// Return the byte width of values of this type. - /// - /// Return 0 for types that take less than an entire byte: `u1`, `u2`, `u4`. - pub const fn byte_width(self) -> usize { - self.bit_width().get() / 8 - } -} - -impl fmt::Debug for UIntType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl fmt::Display for UIntType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UIntType::U1 => f.write_str("u1"), - UIntType::U2 => f.write_str("u2"), - UIntType::U4 => f.write_str("u4"), - UIntType::U8 => f.write_str("u8"), - UIntType::U16 => f.write_str("u16"), - UIntType::U32 => f.write_str("u32"), - UIntType::U64 => f.write_str("u64"), - UIntType::U128 => f.write_str("u128"), - UIntType::U256 => f.write_str("u256"), - } - } -} - -impl FromStr for UIntType { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "u1" => Ok(UIntType::U1), - "u2" => Ok(UIntType::U2), - "u4" => Ok(UIntType::U4), - "u8" => Ok(UIntType::U8), - "u16" => Ok(UIntType::U16), - "u32" => Ok(UIntType::U32), - "u64" => Ok(UIntType::U64), - "u128" => Ok(UIntType::U128), - "u256" => Ok(UIntType::U256), - _ => Err("Unknown integer type".to_string()), - } - } -} +pub use self::inner::{EnumInfo, EnumVariantInfo, TypeInner, UIntType}; impl TryFrom<&StructuralType> for UIntType { type Error = (); From 070d447f78ce80b6831cd11e4196810e3267ebda Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:46:09 +0000 Subject: [PATCH 4/9] types: move ResolvedType into resolved.rs Code move only. --- src/types/mod.rs | 255 +--------------------------------------- src/types/resolved.rs | 262 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 253 deletions(-) create mode 100644 src/types/resolved.rs diff --git a/src/types/mod.rs b/src/types/mod.rs index 805fb0d1..e1b32932 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,4 +1,5 @@ mod inner; +mod resolved; use std::fmt; use std::str::FromStr; @@ -13,6 +14,7 @@ use crate::str::AliasName; use crate::unstable::impl_require_feature; pub use self::inner::{EnumInfo, EnumVariantInfo, TypeInner, UIntType}; +pub use self::resolved::ResolvedType; impl TryFrom<&StructuralType> for UIntType { type Error = (); @@ -36,14 +38,6 @@ impl TryFrom<&StructuralType> for UIntType { } } -impl TryFrom<&ResolvedType> for UIntType { - type Error = (); - - fn try_from(value: &ResolvedType) -> Result { - UIntType::try_from(&StructuralType::from(value)) - } -} - macro_rules! construct_int { ($name: ident, $ty: ident, $text: expr) => { #[doc = "Create the type of"] @@ -136,211 +130,6 @@ pub trait TypeDeconstructible: Sized { fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)>; } -/// SimplicityHL type without type aliases. -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct ResolvedType(TypeInner>); - -impl ResolvedType { - /// Access the inner type primitive. - pub fn as_inner(&self) -> &TypeInner> { - &self.0 - } -} - -/// Nominal enum types. -/// -/// These methods are inherent rather than part of [`TypeConstructible`] and [`TypeDeconstructible`]. -/// Those traits model the structural type algebra that every type universe (aliased, resolved, structural) -/// shares, while a nominal enum exists only at the resolved level. -/// -/// At the structural level its identity is erased into a balanced sum, and at the source level enums -/// enter types by name only. -/// Keeping the constructor off the shared traits also means that only [`crate::ast`]'s scope -/// (which owns the uniqueness of declaration ids) can mint enum types. -impl ResolvedType { - /// Create a nominal enum type from the given definition. - pub const fn enumeration(info: EnumInfo) -> Self { - Self(TypeInner::Enum(info)) - } - - /// Access the enum definition if this is an enum type. - pub const fn as_enum(&self) -> Option<&EnumInfo> { - match &self.0 { - TypeInner::Enum(info) => Some(info), - _ => None, - } - } - - /// Check whether the type mentions an enum, at any nesting depth. - pub fn contains_enum(&self) -> bool { - self.post_order_iter() - .any(|data| data.node.as_enum().is_some()) - } -} - -impl TypeConstructible for ResolvedType { - fn either(left: Self, right: Self) -> Self { - Self(TypeInner::Either(Arc::new(left), Arc::new(right))) - } - - fn option(inner: Self) -> Self { - Self(TypeInner::Option(Arc::new(inner))) - } - - fn boolean() -> Self { - Self(TypeInner::Boolean) - } - - fn tuple>(elements: I) -> Self { - Self(TypeInner::Tuple( - elements.into_iter().map(Arc::new).collect(), - )) - } - - fn array(element: Self, size: usize) -> Self { - Self(TypeInner::Array(Arc::new(element), size)) - } - - fn list(element: Self, bound: NonZeroPow2Usize) -> Self { - Self(TypeInner::List(Arc::new(element), bound)) - } -} - -impl TypeDeconstructible for ResolvedType { - fn as_either(&self) -> Option<(&Self, &Self)> { - match self.as_inner() { - TypeInner::Either(ty_l, ty_r) => Some((ty_l, ty_r)), - _ => None, - } - } - - fn as_option(&self) -> Option<&Self> { - match self.as_inner() { - TypeInner::Option(ty) => Some(ty), - _ => None, - } - } - - fn is_boolean(&self) -> bool { - matches!(self.as_inner(), TypeInner::Boolean) - } - - fn as_integer(&self) -> Option { - match self.as_inner() { - TypeInner::UInt(ty) => Some(*ty), - _ => None, - } - } - - fn as_tuple(&self) -> Option<&[Arc]> { - match self.as_inner() { - TypeInner::Tuple(components) => Some(components), - _ => None, - } - } - - fn as_array(&self) -> Option<(&Self, usize)> { - match self.as_inner() { - TypeInner::Array(ty, size) => Some((ty, *size)), - _ => None, - } - } - - fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)> { - match self.as_inner() { - TypeInner::List(ty, bound) => Some((ty, *bound)), - _ => None, - } - } -} - -impl TreeLike for &ResolvedType { - fn as_node(&self) -> Tree { - match &self.0 { - TypeInner::Boolean | TypeInner::UInt(..) | TypeInner::Enum(..) => Tree::Nullary, - TypeInner::Option(l) | TypeInner::Array(l, _) | TypeInner::List(l, _) => Tree::Unary(l), - TypeInner::Either(l, r) => Tree::Binary(l, r), - TypeInner::Tuple(elements) => Tree::Nary(elements.iter().map(Arc::as_ref).collect()), - } - } -} - -impl fmt::Debug for ResolvedType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl fmt::Display for ResolvedType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for data in self.verbose_pre_order_iter() { - data.node.0.display(f, data.n_children_yielded)?; - } - Ok(()) - } -} - -impl From for ResolvedType { - fn from(value: UIntType) -> Self { - Self(TypeInner::UInt(value)) - } -} - -#[cfg(feature = "arbitrary")] -impl crate::ArbitraryRec for ResolvedType { - // Deliberately never generates `TypeInner::Enum`. - // Enum values serialize as bare strings that only resolve against a program's declarations - // (`UnresolvedValues::resolve`), so the self-contained witness JSON round-trip target (`parse_witness_json_rtt`) - // would fail by design. - fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { - use arbitrary::Arbitrary; - - match budget.checked_sub(1) { - None => match u.int_in_range(0..=1)? { - 0 => Ok(Self::boolean()), - 1 => UIntType::arbitrary(u).map(Self::from), - _ => unreachable!(), - }, - Some(new_budget) => match u.int_in_range(0..=6)? { - 0 => Ok(Self::boolean()), - 1 => UIntType::arbitrary(u).map(Self::from), - 2 => Self::arbitrary_rec(u, new_budget).map(Self::option), - 3 => { - let left = Self::arbitrary_rec(u, new_budget)?; - let right = Self::arbitrary_rec(u, new_budget)?; - Ok(Self::either(left, right)) - } - 4 => { - let len = u.int_in_range(0..=3)?; - (0..len) - .map(|_| Self::arbitrary_rec(u, new_budget)) - .collect::>>() - .map(Self::tuple) - } - 5 => { - let element = Self::arbitrary_rec(u, new_budget)?; - let size = u.int_in_range(0..=3)?; - Ok(Self::array(element, size)) - } - 6 => { - let element = Self::arbitrary_rec(u, new_budget)?; - let exp = u.int_in_range(1u32..=4)?; - let bound = NonZeroPow2Usize::new_unchecked(2usize.saturating_pow(exp)); - Ok(Self::list(element, bound)) - } - _ => unreachable!(), - }, - } - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for ResolvedType { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - ::arbitrary_rec(u, 3) - } -} - /// SimplicityHL type with type aliases. #[derive(PartialEq, Eq, Hash, Clone)] pub struct AliasedType(AliasedInner); @@ -858,46 +647,6 @@ impl From for StructuralType { } } -impl From<&ResolvedType> for StructuralType { - fn from(value: &ResolvedType) -> Self { - let mut output = vec![]; - for data in value.post_order_iter() { - match &data.node.0 { - TypeInner::Either(_, _) => { - let right = output.pop().unwrap(); - let left = output.pop().unwrap(); - output.push(StructuralType::either(left, right)); - } - TypeInner::Option(_) => { - let inner = output.pop().unwrap(); - output.push(StructuralType::option(inner)); - } - TypeInner::Boolean => output.push(StructuralType::boolean()), - TypeInner::UInt(integer) => output.push(StructuralType::from(*integer)), - TypeInner::Tuple(_) => { - let size = data.node.n_children(); - let elements = output.split_off(output.len() - size); - debug_assert_eq!(elements.len(), size); - output.push(StructuralType::tuple(elements)); - } - TypeInner::Array(_, size) => { - let element = output.pop().unwrap(); - output.push(StructuralType::array(element, *size)); - } - TypeInner::List(_, bound) => { - let element = output.pop().unwrap(); - output.push(StructuralType::list(element, *bound)); - } - TypeInner::Enum(info) => { - output.push(StructuralType::balanced_sum(info.structural_variants())); - } - } - } - debug_assert_eq!(output.len(), 1); - output.pop().unwrap() - } -} - impl TypeConstructible for StructuralType { fn either(left: Self, right: Self) -> Self { Self(Final::sum(left.0, right.0)) diff --git a/src/types/resolved.rs b/src/types/resolved.rs new file mode 100644 index 00000000..d5a410a1 --- /dev/null +++ b/src/types/resolved.rs @@ -0,0 +1,262 @@ +use core::fmt; +use std::sync::Arc; + +use miniscript::iter::{Tree, TreeLike}; + +use super::{ + EnumInfo, StructuralType, TypeConstructible, TypeDeconstructible, TypeInner, UIntType, +}; +use crate::num::NonZeroPow2Usize; + +/// SimplicityHL type without type aliases. +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct ResolvedType(TypeInner>); + +impl ResolvedType { + /// Access the inner type primitive. + pub fn as_inner(&self) -> &TypeInner> { + &self.0 + } +} + +/// Nominal enum types. +/// +/// These methods are inherent rather than part of [`TypeConstructible`] and [`TypeDeconstructible`]. +/// Those traits model the structural type algebra that every type universe (aliased, resolved, structural) +/// shares, while a nominal enum exists only at the resolved level. +/// +/// At the structural level its identity is erased into a balanced sum, and at the source level enums +/// enter types by name only. +/// Keeping the constructor off the shared traits also means that only [`crate::ast`]'s scope +/// (which owns the uniqueness of declaration ids) can mint enum types. +impl ResolvedType { + /// Create a nominal enum type from the given definition. + pub const fn enumeration(info: EnumInfo) -> Self { + Self(TypeInner::Enum(info)) + } + + /// Access the enum definition if this is an enum type. + pub const fn as_enum(&self) -> Option<&EnumInfo> { + match &self.0 { + TypeInner::Enum(info) => Some(info), + _ => None, + } + } + + /// Check whether the type mentions an enum, at any nesting depth. + pub fn contains_enum(&self) -> bool { + self.post_order_iter() + .any(|data| data.node.as_enum().is_some()) + } +} + +impl TypeConstructible for ResolvedType { + fn either(left: Self, right: Self) -> Self { + Self(TypeInner::Either(Arc::new(left), Arc::new(right))) + } + + fn option(inner: Self) -> Self { + Self(TypeInner::Option(Arc::new(inner))) + } + + fn boolean() -> Self { + Self(TypeInner::Boolean) + } + + fn tuple>(elements: I) -> Self { + Self(TypeInner::Tuple( + elements.into_iter().map(Arc::new).collect(), + )) + } + + fn array(element: Self, size: usize) -> Self { + Self(TypeInner::Array(Arc::new(element), size)) + } + + fn list(element: Self, bound: NonZeroPow2Usize) -> Self { + Self(TypeInner::List(Arc::new(element), bound)) + } +} + +impl TypeDeconstructible for ResolvedType { + fn as_either(&self) -> Option<(&Self, &Self)> { + match self.as_inner() { + TypeInner::Either(ty_l, ty_r) => Some((ty_l, ty_r)), + _ => None, + } + } + + fn as_option(&self) -> Option<&Self> { + match self.as_inner() { + TypeInner::Option(ty) => Some(ty), + _ => None, + } + } + + fn is_boolean(&self) -> bool { + matches!(self.as_inner(), TypeInner::Boolean) + } + + fn as_integer(&self) -> Option { + match self.as_inner() { + TypeInner::UInt(ty) => Some(*ty), + _ => None, + } + } + + fn as_tuple(&self) -> Option<&[Arc]> { + match self.as_inner() { + TypeInner::Tuple(components) => Some(components), + _ => None, + } + } + + fn as_array(&self) -> Option<(&Self, usize)> { + match self.as_inner() { + TypeInner::Array(ty, size) => Some((ty, *size)), + _ => None, + } + } + + fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)> { + match self.as_inner() { + TypeInner::List(ty, bound) => Some((ty, *bound)), + _ => None, + } + } +} + +impl TreeLike for &ResolvedType { + fn as_node(&self) -> Tree { + match &self.0 { + TypeInner::Boolean | TypeInner::UInt(..) | TypeInner::Enum(..) => Tree::Nullary, + TypeInner::Option(l) | TypeInner::Array(l, _) | TypeInner::List(l, _) => Tree::Unary(l), + TypeInner::Either(l, r) => Tree::Binary(l, r), + TypeInner::Tuple(elements) => Tree::Nary(elements.iter().map(Arc::as_ref).collect()), + } + } +} + +impl fmt::Debug for ResolvedType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl fmt::Display for ResolvedType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for data in self.verbose_pre_order_iter() { + data.node.0.display(f, data.n_children_yielded)?; + } + Ok(()) + } +} + +impl From for ResolvedType { + fn from(value: UIntType) -> Self { + Self(TypeInner::UInt(value)) + } +} + +#[cfg(feature = "arbitrary")] +impl crate::ArbitraryRec for ResolvedType { + // Deliberately never generates `TypeInner::Enum`. + // Enum values serialize as bare strings that only resolve against a program's declarations + // (`UnresolvedValues::resolve`), so the self-contained witness JSON round-trip target (`parse_witness_json_rtt`) + // would fail by design. + fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { + use arbitrary::Arbitrary; + + match budget.checked_sub(1) { + None => match u.int_in_range(0..=1)? { + 0 => Ok(Self::boolean()), + 1 => UIntType::arbitrary(u).map(Self::from), + _ => unreachable!(), + }, + Some(new_budget) => match u.int_in_range(0..=6)? { + 0 => Ok(Self::boolean()), + 1 => UIntType::arbitrary(u).map(Self::from), + 2 => Self::arbitrary_rec(u, new_budget).map(Self::option), + 3 => { + let left = Self::arbitrary_rec(u, new_budget)?; + let right = Self::arbitrary_rec(u, new_budget)?; + Ok(Self::either(left, right)) + } + 4 => { + let len = u.int_in_range(0..=3)?; + (0..len) + .map(|_| Self::arbitrary_rec(u, new_budget)) + .collect::>>() + .map(Self::tuple) + } + 5 => { + let element = Self::arbitrary_rec(u, new_budget)?; + let size = u.int_in_range(0..=3)?; + Ok(Self::array(element, size)) + } + 6 => { + let element = Self::arbitrary_rec(u, new_budget)?; + let exp = u.int_in_range(1u32..=4)?; + let bound = NonZeroPow2Usize::new_unchecked(2usize.saturating_pow(exp)); + Ok(Self::list(element, bound)) + } + _ => unreachable!(), + }, + } + } +} + +impl TryFrom<&ResolvedType> for UIntType { + type Error = (); + + fn try_from(value: &ResolvedType) -> Result { + UIntType::try_from(&StructuralType::from(value)) + } +} + +impl From<&ResolvedType> for StructuralType { + fn from(value: &ResolvedType) -> Self { + let mut output = vec![]; + for data in value.post_order_iter() { + match &data.node.0 { + TypeInner::Either(_, _) => { + let right = output.pop().unwrap(); + let left = output.pop().unwrap(); + output.push(StructuralType::either(left, right)); + } + TypeInner::Option(_) => { + let inner = output.pop().unwrap(); + output.push(StructuralType::option(inner)); + } + TypeInner::Boolean => output.push(StructuralType::boolean()), + TypeInner::UInt(integer) => output.push(StructuralType::from(*integer)), + TypeInner::Tuple(_) => { + let size = data.node.n_children(); + let elements = output.split_off(output.len() - size); + debug_assert_eq!(elements.len(), size); + output.push(StructuralType::tuple(elements)); + } + TypeInner::Array(_, size) => { + let element = output.pop().unwrap(); + output.push(StructuralType::array(element, *size)); + } + TypeInner::List(_, bound) => { + let element = output.pop().unwrap(); + output.push(StructuralType::list(element, *bound)); + } + TypeInner::Enum(info) => { + output.push(StructuralType::balanced_sum(info.structural_variants())); + } + } + } + debug_assert_eq!(output.len(), 1); + output.pop().unwrap() + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ResolvedType { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + ::arbitrary_rec(u, 3) + } +} From 394e6b0cfcdb5ee3b4b424168e0a136dbbc51681 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:53:30 +0000 Subject: [PATCH 5/9] types: move AliasedType and BuiltinAlias to aliased.rs --- src/types/aliased.rs | 464 +++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 460 +----------------------------------------- 2 files changed, 467 insertions(+), 457 deletions(-) create mode 100644 src/types/aliased.rs diff --git a/src/types/aliased.rs b/src/types/aliased.rs new file mode 100644 index 00000000..e4765a03 --- /dev/null +++ b/src/types/aliased.rs @@ -0,0 +1,464 @@ +use core::fmt; +use core::str::FromStr; +use std::sync::Arc; + +use miniscript::iter::{Tree, TreeLike}; + +use super::{ResolvedType, TypeConstructible, TypeDeconstructible, TypeInner, UIntType}; +use crate::num::NonZeroPow2Usize; +use crate::str::AliasName; +use crate::unstable::impl_require_feature; + +/// SimplicityHL type with type aliases. +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct AliasedType(AliasedInner); + +/// Type alias or primitive. +/// +/// Private struct to allow future changes. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +enum AliasedInner { + /// Type alias. + Alias(AliasName), + /// Builtin type alias. + Builtin(BuiltinAlias), + /// Type primitive. + Inner(TypeInner>), +} + +/// Type alias with predefined definition. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum BuiltinAlias { + Ctx8, + Pubkey, + Message, + Message64, + Signature, + Scalar, + Fe, + Ge, + Gej, + Point, + Height, + Time, + Distance, + Duration, + Lock, + Outpoint, + Confidential1, + ExplicitAsset, + Asset1, + ExplicitAmount, + Amount1, + ExplicitNonce, + Nonce, + TokenAmount1, +} + +impl AliasedType { + /// Access a user-defined alias. + pub const fn as_alias(&self) -> Option<&AliasName> { + match &self.0 { + AliasedInner::Alias(name) => Some(name), + _ => None, + } + } + + /// Access a buitlin alias. + pub const fn as_builtin(&self) -> Option<&BuiltinAlias> { + match &self.0 { + AliasedInner::Builtin(builtin) => Some(builtin), + _ => None, + } + } + + /// Create a type alias from the given `identifier`. + pub const fn alias(name: AliasName) -> Self { + Self(AliasedInner::Alias(name)) + } + + /// Create a builtin type alias. + pub const fn builtin(builtin: BuiltinAlias) -> Self { + Self(AliasedInner::Builtin(builtin)) + } + + /// Resolve all aliases in the type based on the given map of `aliases` to types. + pub fn resolve(&self, mut get_alias: F) -> Result + where + F: FnMut(&AliasName) -> Result, + { + let mut output = vec![]; + for data in self.post_order_iter() { + match &data.node.0 { + AliasedInner::Alias(name) => { + let resolved = get_alias(name)?; + output.push(resolved); + } + AliasedInner::Builtin(builtin) => { + let resolved = builtin.resolve(); + output.push(resolved); + } + AliasedInner::Inner(inner) => match inner { + TypeInner::Either(_, _) => { + let right = output.pop().unwrap(); + let left = output.pop().unwrap(); + output.push(ResolvedType::either(left, right)); + } + TypeInner::Option(_) => { + let inner = output.pop().unwrap(); + output.push(ResolvedType::option(inner)); + } + TypeInner::Boolean => output.push(ResolvedType::boolean()), + TypeInner::UInt(integer) => output.push(ResolvedType::from(*integer)), + TypeInner::Tuple(_) => { + let size = data.node.n_children(); + let elements = output.split_off(output.len() - size); + debug_assert_eq!(elements.len(), size); + output.push(ResolvedType::tuple(elements)); + } + TypeInner::Array(_, size) => { + let element = output.pop().unwrap(); + output.push(ResolvedType::array(element, *size)); + } + TypeInner::List(_, bound) => { + let element = output.pop().unwrap(); + output.push(ResolvedType::list(element, *bound)); + } + // There is no syntax for writing an enum type inline (enums enter aliased types only by name) + TypeInner::Enum(info) => { + output.push(ResolvedType::enumeration(info.clone())); + } + }, + } + } + debug_assert_eq!(output.len(), 1); + Ok(output.pop().unwrap()) + } + + /// Resolve all aliases in the type based on the builtin type aliases only. + pub fn resolve_builtin(&self) -> Result { + self.resolve(|name: &AliasName| Err(name.clone())) + } +} + +impl_require_feature!(AliasedType { + recurse: 0; +}); + +impl_require_feature!(AliasedInner { + variants: + Alias(_), + Builtin(_), + Inner(inner), +}); + +impl_require_feature!(TypeInner> { + variants: + Either(left, right), + Option(element), + Boolean, + UInt(_), + Tuple(elements), + Array(element, _), + List(element, _), + Enum(_), +}); + +impl TypeConstructible for AliasedType { + fn either(left: Self, right: Self) -> Self { + Self(AliasedInner::Inner(TypeInner::Either( + Arc::new(left), + Arc::new(right), + ))) + } + + fn option(inner: Self) -> Self { + Self(AliasedInner::Inner(TypeInner::Option(Arc::new(inner)))) + } + + fn boolean() -> Self { + Self(AliasedInner::Inner(TypeInner::Boolean)) + } + + fn tuple>(elements: I) -> Self { + Self(AliasedInner::Inner(TypeInner::Tuple( + elements.into_iter().map(Arc::new).collect(), + ))) + } + + fn array(element: Self, size: usize) -> Self { + Self(AliasedInner::Inner(TypeInner::Array( + Arc::new(element), + size, + ))) + } + + fn list(element: Self, bound: NonZeroPow2Usize) -> Self { + Self(AliasedInner::Inner(TypeInner::List( + Arc::new(element), + bound, + ))) + } +} + +impl TypeDeconstructible for AliasedType { + fn as_either(&self) -> Option<(&Self, &Self)> { + match &self.0 { + AliasedInner::Inner(TypeInner::Either(ty_l, ty_r)) => Some((ty_l, ty_r)), + _ => None, + } + } + + fn as_option(&self) -> Option<&Self> { + match &self.0 { + AliasedInner::Inner(TypeInner::Option(ty)) => Some(ty), + _ => None, + } + } + + fn is_boolean(&self) -> bool { + matches!(&self.0, AliasedInner::Inner(TypeInner::Boolean)) + } + + fn as_integer(&self) -> Option { + match &self.0 { + AliasedInner::Inner(TypeInner::UInt(ty)) => Some(*ty), + _ => None, + } + } + + fn as_tuple(&self) -> Option<&[Arc]> { + match &self.0 { + AliasedInner::Inner(TypeInner::Tuple(components)) => Some(components), + _ => None, + } + } + + fn as_array(&self) -> Option<(&Self, usize)> { + match &self.0 { + AliasedInner::Inner(TypeInner::Array(ty, size)) => Some((ty, *size)), + _ => None, + } + } + + fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)> { + match &self.0 { + AliasedInner::Inner(TypeInner::List(ty, bound)) => Some((ty, *bound)), + _ => None, + } + } +} + +impl TreeLike for &AliasedType { + fn as_node(&self) -> Tree { + match &self.0 { + AliasedInner::Alias(_) | AliasedInner::Builtin(_) => Tree::Nullary, + AliasedInner::Inner(inner) => match inner { + TypeInner::Boolean | TypeInner::UInt(..) | TypeInner::Enum(..) => Tree::Nullary, + TypeInner::Option(l) | TypeInner::Array(l, _) | TypeInner::List(l, _) => { + Tree::Unary(l) + } + TypeInner::Either(l, r) => Tree::Binary(l, r), + TypeInner::Tuple(elements) => { + Tree::Nary(elements.iter().map(Arc::as_ref).collect()) + } + }, + } + } +} + +impl fmt::Debug for AliasedType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl fmt::Display for AliasedType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for data in self.verbose_pre_order_iter() { + match &data.node.0 { + AliasedInner::Alias(alias) => write!(f, "{alias}")?, + AliasedInner::Builtin(builtin) => write!(f, "{builtin}")?, + AliasedInner::Inner(inner) => inner.display(f, data.n_children_yielded)?, + } + } + Ok(()) + } +} + +impl From for AliasedType { + fn from(value: UIntType) -> Self { + Self(AliasedInner::Inner(TypeInner::UInt(value))) + } +} + +impl From for AliasedType { + fn from(value: AliasName) -> Self { + Self::alias(value) + } +} + +impl From for AliasedType { + fn from(value: BuiltinAlias) -> Self { + Self::builtin(value) + } +} + +#[cfg(feature = "arbitrary")] +impl crate::ArbitraryRec for AliasedType { + fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { + use arbitrary::Arbitrary; + + match budget.checked_sub(1) { + None => match u.int_in_range(0..=3)? { + 0 => AliasName::arbitrary(u).map(Self::alias), + 1 => BuiltinAlias::arbitrary(u).map(Self::builtin), + 2 => Ok(Self::boolean()), + 3 => UIntType::arbitrary(u).map(Self::from), + _ => unreachable!(), + }, + Some(new_budget) => match u.int_in_range(0..=8)? { + 0 => AliasName::arbitrary(u).map(Self::alias), + 1 => BuiltinAlias::arbitrary(u).map(Self::builtin), + 2 => Ok(Self::boolean()), + 3 => UIntType::arbitrary(u).map(Self::from), + 4 => Self::arbitrary_rec(u, new_budget).map(Self::option), + 5 => { + let left = Self::arbitrary_rec(u, new_budget)?; + let right = Self::arbitrary_rec(u, new_budget)?; + Ok(Self::either(left, right)) + } + 6 => { + let len = u.int_in_range(0..=3)?; + (0..len) + .map(|_| Self::arbitrary_rec(u, new_budget)) + .collect::>>() + .map(Self::tuple) + } + 7 => { + let element = Self::arbitrary_rec(u, new_budget)?; + let size = u.int_in_range(0..=3)?; + Ok(Self::array(element, size)) + } + 8 => { + let element = Self::arbitrary_rec(u, new_budget)?; + let bound = NonZeroPow2Usize::arbitrary(u)?; + Ok(Self::list(element, bound)) + } + _ => unreachable!(), + }, + } + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for AliasedType { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + ::arbitrary_rec(u, 3) + } +} + +impl BuiltinAlias { + pub fn resolve(self) -> ResolvedType { + use BuiltinAlias as B; + use UIntType::*; + + match self { + B::Ctx8 => ResolvedType::tuple([ + ResolvedType::list(U8.into(), NonZeroPow2Usize::new(64).unwrap()), + ResolvedType::tuple([U64.into(), U256.into()]), + ]), + B::Pubkey | B::Message | B::Scalar | B::Fe | B::ExplicitAsset | B::ExplicitNonce => { + U256.into() + } + B::Message64 | B::Signature => ResolvedType::array(U8.into(), 64), + B::Ge => ResolvedType::tuple([U256.into(), U256.into()]), + B::Gej => { + ResolvedType::tuple([ResolvedType::tuple([U256.into(), U256.into()]), U256.into()]) + } + B::Point | B::Confidential1 => ResolvedType::tuple([U1.into(), U256.into()]), + B::Height | B::Time | B::Lock => U32.into(), + B::Distance | B::Duration => U16.into(), + B::Outpoint => ResolvedType::tuple([U256.into(), U32.into()]), + B::Asset1 | B::Nonce => { + ResolvedType::either(ResolvedType::tuple([U1.into(), U256.into()]), U256.into()) + } + B::ExplicitAmount => U64.into(), + B::Amount1 | B::TokenAmount1 => { + ResolvedType::either(ResolvedType::tuple([U1.into(), U256.into()]), U64.into()) + } + } + } +} + +impl fmt::Debug for BuiltinAlias { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl fmt::Display for BuiltinAlias { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuiltinAlias::Ctx8 => f.write_str("Ctx8"), + BuiltinAlias::Pubkey => f.write_str("Pubkey"), + BuiltinAlias::Message => f.write_str("Message"), + BuiltinAlias::Message64 => f.write_str("Message64"), + BuiltinAlias::Signature => f.write_str("Signature"), + BuiltinAlias::Scalar => f.write_str("Scalar"), + BuiltinAlias::Fe => f.write_str("Fe"), + BuiltinAlias::Ge => f.write_str("Ge"), + BuiltinAlias::Gej => f.write_str("Gej"), + BuiltinAlias::Point => f.write_str("Point"), + BuiltinAlias::Height => f.write_str("Height"), + BuiltinAlias::Time => f.write_str("Time"), + BuiltinAlias::Distance => f.write_str("Distance"), + BuiltinAlias::Duration => f.write_str("Duration"), + BuiltinAlias::Lock => f.write_str("Lock"), + BuiltinAlias::Outpoint => f.write_str("Outpoint"), + BuiltinAlias::Confidential1 => f.write_str("Confidential1"), + BuiltinAlias::ExplicitAsset => f.write_str("ExplicitAsset"), + BuiltinAlias::Asset1 => f.write_str("Asset1"), + BuiltinAlias::ExplicitAmount => f.write_str("ExplicitAmount"), + BuiltinAlias::Amount1 => f.write_str("Amount1"), + BuiltinAlias::ExplicitNonce => f.write_str("ExplicitNonce"), + BuiltinAlias::Nonce => f.write_str("Nonce"), + BuiltinAlias::TokenAmount1 => f.write_str("TokenAmount1"), + } + } +} + +impl FromStr for BuiltinAlias { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "Ctx8" => Ok(BuiltinAlias::Ctx8), + "Pubkey" => Ok(BuiltinAlias::Pubkey), + "Message" => Ok(BuiltinAlias::Message), + "Message64" => Ok(BuiltinAlias::Message64), + "Signature" => Ok(BuiltinAlias::Signature), + "Scalar" => Ok(BuiltinAlias::Scalar), + "Fe" => Ok(BuiltinAlias::Fe), + "Ge" => Ok(BuiltinAlias::Ge), + "Gej" => Ok(BuiltinAlias::Gej), + "Point" => Ok(BuiltinAlias::Point), + "Height" => Ok(BuiltinAlias::Height), + "Time" => Ok(BuiltinAlias::Time), + "Distance" => Ok(BuiltinAlias::Distance), + "Duration" => Ok(BuiltinAlias::Duration), + "Lock" => Ok(BuiltinAlias::Lock), + "Outpoint" => Ok(BuiltinAlias::Outpoint), + "Confidential1" => Ok(BuiltinAlias::Confidential1), + "ExplicitAsset" => Ok(BuiltinAlias::ExplicitAsset), + "Asset1" => Ok(BuiltinAlias::Asset1), + "ExplicitAmount" => Ok(BuiltinAlias::ExplicitAmount), + "Amount1" => Ok(BuiltinAlias::Amount1), + "ExplicitNonce" => Ok(BuiltinAlias::ExplicitNonce), + "Nonce" => Ok(BuiltinAlias::Nonce), + "TokenAmount1" => Ok(BuiltinAlias::TokenAmount1), + _ => Err("Unknown alias".to_string()), + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index e1b32932..7baa315b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,8 +1,8 @@ +mod aliased; mod inner; mod resolved; use std::fmt; -use std::str::FromStr; use std::sync::Arc; use miniscript::iter::{Tree, TreeLike}; @@ -10,9 +10,8 @@ use simplicity::types::{CompleteBound, Final}; use crate::array::{BTreeSlice, Partition}; use crate::num::NonZeroPow2Usize; -use crate::str::AliasName; -use crate::unstable::impl_require_feature; +pub use self::aliased::{AliasedType, BuiltinAlias}; pub use self::inner::{EnumInfo, EnumVariantInfo, TypeInner, UIntType}; pub use self::resolved::ResolvedType; @@ -130,461 +129,8 @@ pub trait TypeDeconstructible: Sized { fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)>; } -/// SimplicityHL type with type aliases. -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct AliasedType(AliasedInner); - -/// Type alias or primitive. -/// -/// Private struct to allow future changes. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -enum AliasedInner { - /// Type alias. - Alias(AliasName), - /// Builtin type alias. - Builtin(BuiltinAlias), - /// Type primitive. - Inner(TypeInner>), -} - -/// Type alias with predefined definition. -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub enum BuiltinAlias { - Ctx8, - Pubkey, - Message, - Message64, - Signature, - Scalar, - Fe, - Ge, - Gej, - Point, - Height, - Time, - Distance, - Duration, - Lock, - Outpoint, - Confidential1, - ExplicitAsset, - Asset1, - ExplicitAmount, - Amount1, - ExplicitNonce, - Nonce, - TokenAmount1, -} - -impl AliasedType { - /// Access a user-defined alias. - pub const fn as_alias(&self) -> Option<&AliasName> { - match &self.0 { - AliasedInner::Alias(name) => Some(name), - _ => None, - } - } - - /// Access a buitlin alias. - pub const fn as_builtin(&self) -> Option<&BuiltinAlias> { - match &self.0 { - AliasedInner::Builtin(builtin) => Some(builtin), - _ => None, - } - } - - /// Create a type alias from the given `identifier`. - pub const fn alias(name: AliasName) -> Self { - Self(AliasedInner::Alias(name)) - } - - /// Create a builtin type alias. - pub const fn builtin(builtin: BuiltinAlias) -> Self { - Self(AliasedInner::Builtin(builtin)) - } - - /// Resolve all aliases in the type based on the given map of `aliases` to types. - pub fn resolve(&self, mut get_alias: F) -> Result - where - F: FnMut(&AliasName) -> Result, - { - let mut output = vec![]; - for data in self.post_order_iter() { - match &data.node.0 { - AliasedInner::Alias(name) => { - let resolved = get_alias(name)?; - output.push(resolved); - } - AliasedInner::Builtin(builtin) => { - let resolved = builtin.resolve(); - output.push(resolved); - } - AliasedInner::Inner(inner) => match inner { - TypeInner::Either(_, _) => { - let right = output.pop().unwrap(); - let left = output.pop().unwrap(); - output.push(ResolvedType::either(left, right)); - } - TypeInner::Option(_) => { - let inner = output.pop().unwrap(); - output.push(ResolvedType::option(inner)); - } - TypeInner::Boolean => output.push(ResolvedType::boolean()), - TypeInner::UInt(integer) => output.push(ResolvedType::from(*integer)), - TypeInner::Tuple(_) => { - let size = data.node.n_children(); - let elements = output.split_off(output.len() - size); - debug_assert_eq!(elements.len(), size); - output.push(ResolvedType::tuple(elements)); - } - TypeInner::Array(_, size) => { - let element = output.pop().unwrap(); - output.push(ResolvedType::array(element, *size)); - } - TypeInner::List(_, bound) => { - let element = output.pop().unwrap(); - output.push(ResolvedType::list(element, *bound)); - } - // There is no syntax for writing an enum type inline (enums enter aliased types only by name) - TypeInner::Enum(info) => { - output.push(ResolvedType::enumeration(info.clone())); - } - }, - } - } - debug_assert_eq!(output.len(), 1); - Ok(output.pop().unwrap()) - } - - /// Resolve all aliases in the type based on the builtin type aliases only. - pub fn resolve_builtin(&self) -> Result { - self.resolve(|name: &AliasName| Err(name.clone())) - } -} - -impl_require_feature!(AliasedType { - recurse: 0; -}); - -impl_require_feature!(AliasedInner { - variants: - Alias(_), - Builtin(_), - Inner(inner), -}); - -impl_require_feature!(TypeInner> { - variants: - Either(left, right), - Option(element), - Boolean, - UInt(_), - Tuple(elements), - Array(element, _), - List(element, _), - Enum(_), -}); - -impl TypeConstructible for AliasedType { - fn either(left: Self, right: Self) -> Self { - Self(AliasedInner::Inner(TypeInner::Either( - Arc::new(left), - Arc::new(right), - ))) - } - - fn option(inner: Self) -> Self { - Self(AliasedInner::Inner(TypeInner::Option(Arc::new(inner)))) - } - - fn boolean() -> Self { - Self(AliasedInner::Inner(TypeInner::Boolean)) - } - - fn tuple>(elements: I) -> Self { - Self(AliasedInner::Inner(TypeInner::Tuple( - elements.into_iter().map(Arc::new).collect(), - ))) - } - - fn array(element: Self, size: usize) -> Self { - Self(AliasedInner::Inner(TypeInner::Array( - Arc::new(element), - size, - ))) - } - - fn list(element: Self, bound: NonZeroPow2Usize) -> Self { - Self(AliasedInner::Inner(TypeInner::List( - Arc::new(element), - bound, - ))) - } -} - -impl TypeDeconstructible for AliasedType { - fn as_either(&self) -> Option<(&Self, &Self)> { - match &self.0 { - AliasedInner::Inner(TypeInner::Either(ty_l, ty_r)) => Some((ty_l, ty_r)), - _ => None, - } - } - - fn as_option(&self) -> Option<&Self> { - match &self.0 { - AliasedInner::Inner(TypeInner::Option(ty)) => Some(ty), - _ => None, - } - } - - fn is_boolean(&self) -> bool { - matches!(&self.0, AliasedInner::Inner(TypeInner::Boolean)) - } - - fn as_integer(&self) -> Option { - match &self.0 { - AliasedInner::Inner(TypeInner::UInt(ty)) => Some(*ty), - _ => None, - } - } - - fn as_tuple(&self) -> Option<&[Arc]> { - match &self.0 { - AliasedInner::Inner(TypeInner::Tuple(components)) => Some(components), - _ => None, - } - } - - fn as_array(&self) -> Option<(&Self, usize)> { - match &self.0 { - AliasedInner::Inner(TypeInner::Array(ty, size)) => Some((ty, *size)), - _ => None, - } - } - - fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)> { - match &self.0 { - AliasedInner::Inner(TypeInner::List(ty, bound)) => Some((ty, *bound)), - _ => None, - } - } -} - -impl TreeLike for &AliasedType { - fn as_node(&self) -> Tree { - match &self.0 { - AliasedInner::Alias(_) | AliasedInner::Builtin(_) => Tree::Nullary, - AliasedInner::Inner(inner) => match inner { - TypeInner::Boolean | TypeInner::UInt(..) | TypeInner::Enum(..) => Tree::Nullary, - TypeInner::Option(l) | TypeInner::Array(l, _) | TypeInner::List(l, _) => { - Tree::Unary(l) - } - TypeInner::Either(l, r) => Tree::Binary(l, r), - TypeInner::Tuple(elements) => { - Tree::Nary(elements.iter().map(Arc::as_ref).collect()) - } - }, - } - } -} - -impl fmt::Debug for AliasedType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl fmt::Display for AliasedType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for data in self.verbose_pre_order_iter() { - match &data.node.0 { - AliasedInner::Alias(alias) => write!(f, "{alias}")?, - AliasedInner::Builtin(builtin) => write!(f, "{builtin}")?, - AliasedInner::Inner(inner) => inner.display(f, data.n_children_yielded)?, - } - } - Ok(()) - } -} - -impl From for AliasedType { - fn from(value: UIntType) -> Self { - Self(AliasedInner::Inner(TypeInner::UInt(value))) - } -} - -impl From for AliasedType { - fn from(value: AliasName) -> Self { - Self::alias(value) - } -} - -impl From for AliasedType { - fn from(value: BuiltinAlias) -> Self { - Self::builtin(value) - } -} - -#[cfg(feature = "arbitrary")] -impl crate::ArbitraryRec for AliasedType { - fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { - use arbitrary::Arbitrary; - - match budget.checked_sub(1) { - None => match u.int_in_range(0..=3)? { - 0 => AliasName::arbitrary(u).map(Self::alias), - 1 => BuiltinAlias::arbitrary(u).map(Self::builtin), - 2 => Ok(Self::boolean()), - 3 => UIntType::arbitrary(u).map(Self::from), - _ => unreachable!(), - }, - Some(new_budget) => match u.int_in_range(0..=8)? { - 0 => AliasName::arbitrary(u).map(Self::alias), - 1 => BuiltinAlias::arbitrary(u).map(Self::builtin), - 2 => Ok(Self::boolean()), - 3 => UIntType::arbitrary(u).map(Self::from), - 4 => Self::arbitrary_rec(u, new_budget).map(Self::option), - 5 => { - let left = Self::arbitrary_rec(u, new_budget)?; - let right = Self::arbitrary_rec(u, new_budget)?; - Ok(Self::either(left, right)) - } - 6 => { - let len = u.int_in_range(0..=3)?; - (0..len) - .map(|_| Self::arbitrary_rec(u, new_budget)) - .collect::>>() - .map(Self::tuple) - } - 7 => { - let element = Self::arbitrary_rec(u, new_budget)?; - let size = u.int_in_range(0..=3)?; - Ok(Self::array(element, size)) - } - 8 => { - let element = Self::arbitrary_rec(u, new_budget)?; - let bound = NonZeroPow2Usize::arbitrary(u)?; - Ok(Self::list(element, bound)) - } - _ => unreachable!(), - }, - } - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for AliasedType { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - ::arbitrary_rec(u, 3) - } -} - -impl BuiltinAlias { - pub fn resolve(self) -> ResolvedType { - use BuiltinAlias as B; - use UIntType::*; - - match self { - B::Ctx8 => ResolvedType::tuple([ - ResolvedType::list(U8.into(), NonZeroPow2Usize::new(64).unwrap()), - ResolvedType::tuple([U64.into(), U256.into()]), - ]), - B::Pubkey | B::Message | B::Scalar | B::Fe | B::ExplicitAsset | B::ExplicitNonce => { - U256.into() - } - B::Message64 | B::Signature => ResolvedType::array(U8.into(), 64), - B::Ge => ResolvedType::tuple([U256.into(), U256.into()]), - B::Gej => { - ResolvedType::tuple([ResolvedType::tuple([U256.into(), U256.into()]), U256.into()]) - } - B::Point | B::Confidential1 => ResolvedType::tuple([U1.into(), U256.into()]), - B::Height | B::Time | B::Lock => U32.into(), - B::Distance | B::Duration => U16.into(), - B::Outpoint => ResolvedType::tuple([U256.into(), U32.into()]), - B::Asset1 | B::Nonce => { - ResolvedType::either(ResolvedType::tuple([U1.into(), U256.into()]), U256.into()) - } - B::ExplicitAmount => U64.into(), - B::Amount1 | B::TokenAmount1 => { - ResolvedType::either(ResolvedType::tuple([U1.into(), U256.into()]), U64.into()) - } - } - } -} - -impl fmt::Debug for BuiltinAlias { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl fmt::Display for BuiltinAlias { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BuiltinAlias::Ctx8 => f.write_str("Ctx8"), - BuiltinAlias::Pubkey => f.write_str("Pubkey"), - BuiltinAlias::Message => f.write_str("Message"), - BuiltinAlias::Message64 => f.write_str("Message64"), - BuiltinAlias::Signature => f.write_str("Signature"), - BuiltinAlias::Scalar => f.write_str("Scalar"), - BuiltinAlias::Fe => f.write_str("Fe"), - BuiltinAlias::Ge => f.write_str("Ge"), - BuiltinAlias::Gej => f.write_str("Gej"), - BuiltinAlias::Point => f.write_str("Point"), - BuiltinAlias::Height => f.write_str("Height"), - BuiltinAlias::Time => f.write_str("Time"), - BuiltinAlias::Distance => f.write_str("Distance"), - BuiltinAlias::Duration => f.write_str("Duration"), - BuiltinAlias::Lock => f.write_str("Lock"), - BuiltinAlias::Outpoint => f.write_str("Outpoint"), - BuiltinAlias::Confidential1 => f.write_str("Confidential1"), - BuiltinAlias::ExplicitAsset => f.write_str("ExplicitAsset"), - BuiltinAlias::Asset1 => f.write_str("Asset1"), - BuiltinAlias::ExplicitAmount => f.write_str("ExplicitAmount"), - BuiltinAlias::Amount1 => f.write_str("Amount1"), - BuiltinAlias::ExplicitNonce => f.write_str("ExplicitNonce"), - BuiltinAlias::Nonce => f.write_str("Nonce"), - BuiltinAlias::TokenAmount1 => f.write_str("TokenAmount1"), - } - } -} - -impl FromStr for BuiltinAlias { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "Ctx8" => Ok(BuiltinAlias::Ctx8), - "Pubkey" => Ok(BuiltinAlias::Pubkey), - "Message" => Ok(BuiltinAlias::Message), - "Message64" => Ok(BuiltinAlias::Message64), - "Signature" => Ok(BuiltinAlias::Signature), - "Scalar" => Ok(BuiltinAlias::Scalar), - "Fe" => Ok(BuiltinAlias::Fe), - "Ge" => Ok(BuiltinAlias::Ge), - "Gej" => Ok(BuiltinAlias::Gej), - "Point" => Ok(BuiltinAlias::Point), - "Height" => Ok(BuiltinAlias::Height), - "Time" => Ok(BuiltinAlias::Time), - "Distance" => Ok(BuiltinAlias::Distance), - "Duration" => Ok(BuiltinAlias::Duration), - "Lock" => Ok(BuiltinAlias::Lock), - "Outpoint" => Ok(BuiltinAlias::Outpoint), - "Confidential1" => Ok(BuiltinAlias::Confidential1), - "ExplicitAsset" => Ok(BuiltinAlias::ExplicitAsset), - "Asset1" => Ok(BuiltinAlias::Asset1), - "ExplicitAmount" => Ok(BuiltinAlias::ExplicitAmount), - "Amount1" => Ok(BuiltinAlias::Amount1), - "ExplicitNonce" => Ok(BuiltinAlias::ExplicitNonce), - "Nonce" => Ok(BuiltinAlias::Nonce), - "TokenAmount1" => Ok(BuiltinAlias::TokenAmount1), - _ => Err("Unknown alias".to_string()), - } - } -} - /// Internal structure of a SimplicityHL type. +/// /// 1:1 isomorphism to Simplicity. #[derive(Clone, PartialEq, Eq, Hash)] pub struct StructuralType(Arc); From 6aa667fd83a5d85fa6ec5bf6006b45e484ff166b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 21:58:20 +0000 Subject: [PATCH 6/9] types: move StructuralType to structural.rs Code move only. --- src/types/mod.rs | 149 +-------------------------------------- src/types/structural.rs | 151 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 147 deletions(-) create mode 100644 src/types/structural.rs diff --git a/src/types/mod.rs b/src/types/mod.rs index 7baa315b..b3b95580 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,19 +1,16 @@ mod aliased; mod inner; mod resolved; +mod structural; -use std::fmt; use std::sync::Arc; -use miniscript::iter::{Tree, TreeLike}; -use simplicity::types::{CompleteBound, Final}; - -use crate::array::{BTreeSlice, Partition}; use crate::num::NonZeroPow2Usize; pub use self::aliased::{AliasedType, BuiltinAlias}; pub use self::inner::{EnumInfo, EnumVariantInfo, TypeInner, UIntType}; pub use self::resolved::ResolvedType; +pub use self::structural::StructuralType; impl TryFrom<&StructuralType> for UIntType { type Error = (); @@ -129,148 +126,6 @@ pub trait TypeDeconstructible: Sized { fn as_list(&self) -> Option<(&Self, NonZeroPow2Usize)>; } -/// Internal structure of a SimplicityHL type. -/// -/// 1:1 isomorphism to Simplicity. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct StructuralType(Arc); - -impl AsRef for StructuralType { - fn as_ref(&self) -> &Final { - &self.0 - } -} - -impl From for Arc { - fn from(value: StructuralType) -> Self { - value.0 - } -} - -impl From> for StructuralType { - fn from(value: Arc) -> Self { - Self(value) - } -} - -impl TreeLike for StructuralType { - fn as_node(&self) -> Tree { - match self.0.bound() { - CompleteBound::Unit => Tree::Nullary, - CompleteBound::Sum(l, r) | CompleteBound::Product(l, r) => { - Tree::Binary(Self(l.clone()), Self(r.clone())) - } - } - } -} - -impl fmt::Debug for StructuralType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl fmt::Display for StructuralType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for StructuralType { - fn from(value: UIntType) -> Self { - let inner = match value { - UIntType::U1 => Final::two_two_n(0), - UIntType::U2 => Final::two_two_n(1), - UIntType::U4 => Final::two_two_n(2), - UIntType::U8 => Final::two_two_n(3), - UIntType::U16 => Final::two_two_n(4), - UIntType::U32 => Final::two_two_n(5), - UIntType::U64 => Final::two_two_n(6), - UIntType::U128 => Final::two_two_n(7), - UIntType::U256 => Final::two_two_n(8), - }; - Self(inner) - } -} - -impl TypeConstructible for StructuralType { - fn either(left: Self, right: Self) -> Self { - Self(Final::sum(left.0, right.0)) - } - - fn option(inner: Self) -> Self { - Self::either(Self::unit(), inner) - } - - fn boolean() -> Self { - Self::either(Self::unit(), Self::unit()) - } - - fn tuple>(elements: I) -> Self { - let elements: Vec<_> = elements.into_iter().collect(); - let tree = BTreeSlice::from_slice(&elements); - tree.fold(Self::product).unwrap_or_else(Self::unit) - } - - // Keep this implementation to prevent an infinite loop in ::tuple - fn unit() -> Self { - Self(Final::unit()) - } - - // Keep this implementation to prevent an infinite loop in ::tuple - fn product(left: Self, right: Self) -> Self { - Self(Final::product(left.0, right.0)) - } - - fn array(element: Self, size: usize) -> Self { - // Cheap clone because Arc consists of Arcs - let elements = vec![element; size]; - let tree = BTreeSlice::from_slice(&elements); - tree.fold(Self::product).unwrap_or_else(Self::unit) - } - - fn list(element: Self, bound: NonZeroPow2Usize) -> Self { - // Cheap clone because Arc consists of Arcs - let el_vector = vec![element.0; bound.get() - 1]; - let partition = Partition::from_slice(&el_vector, bound); - debug_assert!(partition.is_complete()); - let process = |block: &[Arc], size: usize| -> Arc { - debug_assert_eq!(block.len(), size); - let tree = BTreeSlice::from_slice(block); - let array = tree.fold(Final::product).unwrap(); - Final::sum(Final::unit(), array) - }; - let inner = partition.fold(process, Final::product); - Self(inner) - } -} - -impl StructuralType { - /// The balanced sum of the given leaf types. - /// The structural type of an enum whose variants have these payload types. - /// The tree shape is the one of [`BTreeSlice`], values ([`StructuralValue::enum_injection`]) - /// and the match lowering navigate the same shape. - /// - /// ## Panics - /// - /// `leaves` is empty: a sum of zero types would be uninhabited. - /// - /// [`StructuralValue::enum_injection`]: crate::value::StructuralValue - pub(crate) fn balanced_sum(leaves: Vec) -> Self { - BTreeSlice::from_slice(&leaves) - .fold(Self::either) - .expect("at least one leaf") - } - - /// Convert into an unfinalized type that can be used in Simplicity's unification algorithm. - pub fn to_unfinalized<'brand>( - &self, - inference_context: &simplicity::types::Context<'brand>, - ) -> simplicity::types::Type<'brand> { - simplicity::types::Type::complete(inference_context, self.0.clone()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/structural.rs b/src/types/structural.rs new file mode 100644 index 00000000..14c98e2c --- /dev/null +++ b/src/types/structural.rs @@ -0,0 +1,151 @@ +use core::fmt; +use std::sync::Arc; + +use miniscript::iter::{Tree, TreeLike}; +use simplicity::types::{CompleteBound, Final}; + +use super::{TypeConstructible, UIntType}; +use crate::array::{BTreeSlice, Partition}; +use crate::num::NonZeroPow2Usize; + +/// Internal structure of a SimplicityHL type. +/// +/// 1:1 isomorphism to Simplicity. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct StructuralType(Arc); + +impl AsRef for StructuralType { + fn as_ref(&self) -> &Final { + &self.0 + } +} + +impl From for Arc { + fn from(value: StructuralType) -> Self { + value.0 + } +} + +impl From> for StructuralType { + fn from(value: Arc) -> Self { + Self(value) + } +} + +impl TreeLike for StructuralType { + fn as_node(&self) -> Tree { + match self.0.bound() { + CompleteBound::Unit => Tree::Nullary, + CompleteBound::Sum(l, r) | CompleteBound::Product(l, r) => { + Tree::Binary(Self(l.clone()), Self(r.clone())) + } + } + } +} + +impl fmt::Debug for StructuralType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for StructuralType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for StructuralType { + fn from(value: UIntType) -> Self { + let inner = match value { + UIntType::U1 => Final::two_two_n(0), + UIntType::U2 => Final::two_two_n(1), + UIntType::U4 => Final::two_two_n(2), + UIntType::U8 => Final::two_two_n(3), + UIntType::U16 => Final::two_two_n(4), + UIntType::U32 => Final::two_two_n(5), + UIntType::U64 => Final::two_two_n(6), + UIntType::U128 => Final::two_two_n(7), + UIntType::U256 => Final::two_two_n(8), + }; + Self(inner) + } +} + +impl TypeConstructible for StructuralType { + fn either(left: Self, right: Self) -> Self { + Self(Final::sum(left.0, right.0)) + } + + fn option(inner: Self) -> Self { + Self::either(Self::unit(), inner) + } + + fn boolean() -> Self { + Self::either(Self::unit(), Self::unit()) + } + + fn tuple>(elements: I) -> Self { + let elements: Vec<_> = elements.into_iter().collect(); + let tree = BTreeSlice::from_slice(&elements); + tree.fold(Self::product).unwrap_or_else(Self::unit) + } + + // Keep this implementation to prevent an infinite loop in ::tuple + fn unit() -> Self { + Self(Final::unit()) + } + + // Keep this implementation to prevent an infinite loop in ::tuple + fn product(left: Self, right: Self) -> Self { + Self(Final::product(left.0, right.0)) + } + + fn array(element: Self, size: usize) -> Self { + // Cheap clone because Arc consists of Arcs + let elements = vec![element; size]; + let tree = BTreeSlice::from_slice(&elements); + tree.fold(Self::product).unwrap_or_else(Self::unit) + } + + fn list(element: Self, bound: NonZeroPow2Usize) -> Self { + // Cheap clone because Arc consists of Arcs + let el_vector = vec![element.0; bound.get() - 1]; + let partition = Partition::from_slice(&el_vector, bound); + debug_assert!(partition.is_complete()); + let process = |block: &[Arc], size: usize| -> Arc { + debug_assert_eq!(block.len(), size); + let tree = BTreeSlice::from_slice(block); + let array = tree.fold(Final::product).unwrap(); + Final::sum(Final::unit(), array) + }; + let inner = partition.fold(process, Final::product); + Self(inner) + } +} + +impl StructuralType { + /// The balanced sum of the given leaf types. + /// The structural type of an enum whose variants have these payload types. + /// The tree shape is the one of [`BTreeSlice`], values ([`StructuralValue::enum_injection`]) + /// and the match lowering navigate the same shape. + /// + /// ## Panics + /// + /// `leaves` is empty: a sum of zero types would be uninhabited. + /// + /// [`StructuralValue::enum_injection`]: crate::value::StructuralValue + pub(crate) fn balanced_sum(leaves: Vec) -> Self { + BTreeSlice::from_slice(&leaves) + .fold(Self::either) + .expect("at least one leaf") + } + + /// Convert into an unfinalized type that can be used in Simplicity's unification algorithm. + pub fn to_unfinalized<'brand>( + &self, + inference_context: &simplicity::types::Context<'brand>, + ) -> simplicity::types::Type<'brand> { + simplicity::types::Type::complete(inference_context, self.0.clone()) + } +} From 75092f4e2239c7be26c864e9c94a4b398593995d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 22:02:55 +0000 Subject: [PATCH 7/9] types: remove conversion from other types to UIntType These conversions don't really make conceptual sense, since they're converting a "real" type to a component of TypeInner. They're also not used anywhere. They both date back to https://github.com/BlockstreamResearch/SimplicityHL/pull/42 when the codebase looked very different, and appear to be vestigial. --- src/types/mod.rs | 22 ---------------------- src/types/resolved.rs | 8 -------- 2 files changed, 30 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index b3b95580..0b6aef9a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -12,28 +12,6 @@ pub use self::inner::{EnumInfo, EnumVariantInfo, TypeInner, UIntType}; pub use self::resolved::ResolvedType; pub use self::structural::StructuralType; -impl TryFrom<&StructuralType> for UIntType { - type Error = (); - - fn try_from(value: &StructuralType) -> Result { - let mut current = value.as_ref(); - let mut n = 0; - while let Some((left, right)) = current.as_product() { - if left.tmr() != right.tmr() { - return Err(()); - } - current = left; - n += 1; - } - if let Some((left, right)) = current.as_sum() { - if left.is_unit() && right.is_unit() { - return UIntType::two_n(n).ok_or(()); - } - } - Err(()) - } -} - macro_rules! construct_int { ($name: ident, $ty: ident, $text: expr) => { #[doc = "Create the type of"] diff --git a/src/types/resolved.rs b/src/types/resolved.rs index d5a410a1..c7a2f1af 100644 --- a/src/types/resolved.rs +++ b/src/types/resolved.rs @@ -206,14 +206,6 @@ impl crate::ArbitraryRec for ResolvedType { } } -impl TryFrom<&ResolvedType> for UIntType { - type Error = (); - - fn try_from(value: &ResolvedType) -> Result { - UIntType::try_from(&StructuralType::from(value)) - } -} - impl From<&ResolvedType> for StructuralType { fn from(value: &ResolvedType) -> Self { let mut output = vec![]; From f00b08fb4fab123b4bf7cbf47b5c007db8cd10a9 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 2 Sep 2026 14:37:15 +0000 Subject: [PATCH 8/9] ast: add a bunch more regression tests for enum casting Several of these are currently broken; I simply did `expect_err` rather than `expect` so that the tests would pass. But we should fix these and fix the tests in parallel. --- src/ast.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/ast.rs b/src/ast.rs index 81993ee9..ee31f60a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -3592,6 +3592,41 @@ mod enum_tests { ); } + #[test] + fn enum_cast_reshaping_enum_free_siblings_is_ok_2() { + // This one has an enum with a left sibling which is much bigger (as a HL type DAG) in the + // source type than the target. + let result = analyze( + "enum E { A, B, } + fn main() { + let x: ((Either<(), u8>, Either<(), u8>, Either<(), u8>), E) + = ((Left(()), Left(()), Left(())), E::A); + let _y: ((Option, Option, Option), E) + = <((Either<(), u8>, Either<(), u8>, Either<(), u8>), E)>::into(x); + }", + ); + assert!( + result.is_ok(), + "reshaping enum-free siblings must stay castable: {result:?}" + ); + + // Same thing, but we try to swap out the enums. This should fail. + let result = analyze( + "enum E { A, B, } + enum F { C, D, } + fn main() { + let x: ((Either<(), u8>, Either<(), u8>, Either<(), u8>), E) + = ((Left(()), Left(()), Left(())), E::A); + let _y: ((Option, Option, Option), F) + = <((Either<(), u8>, Either<(), u8>, Either<(), u8>), E)>::into(x); + }", + ); + assert!( + result.is_err(), + "reshaping enum-free siblings must stay non-castable: {result:?}" + ); + } + #[test] fn enum_cast_to_itself_is_ok() { let result = analyze( @@ -3607,6 +3642,54 @@ mod enum_tests { ); } + #[test] + fn enum_cast_option_either() { + let result = analyze( + "enum E { A, B, } + fn main() { + let x: Option = None; + let _y: Either<(), E> = >::into(x); + }", + ); + result.expect_err("this should work"); + } + + #[test] + fn enum_cast_array_tuple() { + let result = analyze( + "enum E { A, B, } + fn main() { + let x: [E; 2] = [E::A, E::B]; + let _y: (E, E) = <[E; 2]>::into(x); + }", + ); + result.expect_err("this should work"); + } + + #[test] + fn enum_cast_list1_option() { + let result = analyze( + "enum E { A, B, } + fn main() { + let x: List = list![]; + let _y: Option = >::into(x); + }", + ); + result.expect_err("this should work"); + } + + #[test] + fn enum_cast_list2_option() { + let result = analyze( + "enum E { A, B, } + fn main() { + let x: List = list![]; + let _y: (Option<(E, E)>, Option) = >::into(x); + }", + ); + result.expect_err("this should work"); + } + #[test] fn enum_named_after_builtin_type_is_rejected() { // `enum Signature` would shadow the built-in alias: constructions From fdcb77bb080b7a4bf8781cc10bb9c5af7786d25d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 14 Aug 2026 22:03:34 +0000 Subject: [PATCH 9/9] types: remove one calls to ResolvedType::as_inner Our goal is to reduce/eliminate the places outside of the `types` module that use the `TypeInner` type, since this is (ideally) an implementation detail of `ResolvedType`. There aren't a lot of these places, it turns out. This commit removes one "easy" one, in pattern.rs. There, previously we were matching arrays and using a wildcard _ match to return an error on mismatches. By adding a bit of code repetiton (the 'return error' line 3 times) and calling TypeDestructible::as_list and as_array, we can get rid of the wildcard match, which eliminates the `TypeInner` but also is more robust against extensions to the Pattern enum. This leaves the use in ast.rs in `cast_preserves_enum_identity` which seems quite difficult to remove correctly (the existing code is not correct either, but ok, let's leave it alone unless we can fix it completely). We want to make this nonrecursive and correct, but it will have to wait for a later PR. Aside from that, the only parts of the code, outside the ResolvedType module itself, that now need to know the internals of ResolvedType are in value.rs. Since the structure of Value mirrors the structure of ResolvedType probably we will just have to live with this; when we change the representation of ResolvedType we will need to make parallel changes in value.rs. --- src/pattern.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/pattern.rs b/src/pattern.rs index caf6f608..31b7a309 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -9,7 +9,7 @@ use crate::array::BTreeSlice; use crate::error::Error; use crate::named::{CoreExt, PairBuilder, SelectorBuilder}; use crate::str::Identifier; -use crate::types::{ResolvedType, TypeInner}; +use crate::types::{ResolvedType, TypeDeconstructible}; use crate::unstable::impl_require_feature; /// Pattern for binding values to variables. @@ -51,25 +51,37 @@ impl Pattern { let mut stack = vec![(self, ty)]; let mut output = HashMap::new(); while let Some((pattern, ty)) = stack.pop() { - match (pattern, ty.as_inner()) { - (Pattern::Identifier(i), _) => match output.entry(i.clone()) { + let unexpected_err = || Err(Error::ExpressionUnexpectedType { ty: ty.clone() }); + match pattern { + Pattern::Identifier(i) => match output.entry(i.clone()) { Entry::Occupied(..) => { return Err(Error::VariableReuseInPattern { identifier: i.clone(), - }) + }); } Entry::Vacant(entry) => { entry.insert(ty.clone()); } }, - (Pattern::Ignore, _) => {} - (Pattern::Tuple(pats), TypeInner::Tuple(types)) => { - stack.extend(pats.iter().zip(types.iter().map(Arc::as_ref))); + Pattern::Ignore => {} + Pattern::Tuple(pats) => { + if let Some(types) = ty.as_tuple() { + stack.extend(pats.iter().zip(types.iter().map(Arc::as_ref))); + } else { + return unexpected_err(); + } } - (Pattern::Array(pats), TypeInner::Array(ty, size)) if pats.len() == *size => { - stack.extend(pats.iter().zip(std::iter::repeat(ty.as_ref()))); + Pattern::Array(pats) => { + if let Some((ty, size)) = ty.as_array() { + if pats.len() == size { + stack.extend(pats.iter().zip(std::iter::repeat(ty))); + } else { + return unexpected_err(); + } + } else { + return unexpected_err(); + } } - _ => return Err(Error::ExpressionUnexpectedType { ty: ty.clone() }), } } Ok(output)