From 72cf04575f02a0bed898e6fe456969a14bf95f26 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 10 Sep 2025 17:03:20 +0000 Subject: [PATCH 1/6] remove Default impl from CompiledProgram This Default impl creates a new type inference context which then gets attached to a unit ConstructNode. This will be incompatible with the upstream change to make type inference contexts tied to scopes. --- src/lib.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 913e3d6..d8d3e37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,17 +99,6 @@ pub struct CompiledProgram { debug_symbols: DebugSymbols, } -impl Default for CompiledProgram { - fn default() -> Self { - use simplicity::node::CoreConstructible; - Self { - simplicity: ProgNode::unit(&simplicity::types::Context::new()), - witness_types: WitnessTypes::default(), - debug_symbols: DebugSymbols::default(), - } - } -} - impl CompiledProgram { /// Parse and compile a SimplicityHL program from the given string. /// From c8fb7464049811993b3f1c790a2b4b7d93602c6c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 10 Sep 2025 23:04:02 +0000 Subject: [PATCH 2/6] named: replace ad-hoc ConstructNode/CommitNode/WitnessNode with generic ones Now we have a marker type WithNames which wraps a marker type from rust-simplicity, erasing its disconnect nodes and replacing its witness nodes with names. This gets us an analogue of ConstructNode, CommitNode, WitnessNode, RedeemNode, etc., all at once. Then replace: * to_commit_node, whose role was to forget names and finalize types, with a general method forget_names, which just forgets the names (then the user can call .finalize_types() themselves if they want) * to_witness_node, whose role was to replace names with actual witnesses, with a general method populate_witnesses, which does this (Actually, `to_witness_node` is not very general at all. It specifically converts a WtihNames to an unnamed ConstructNode. In this commit, it's possible to implement it in a general way, converting an arbitrary Node> to a Node. But in a couple commits, when we replace ConstructNode with CommitNode in CompiledProgram, we will be forced to fix its signature to convert a CommitNode to a RedeemNode. The diff will be easier to read starting from this non-generic version than it would be if we started from the generic version.) Along the way, use core::convert::Infallible for error types so that we don't need to unwrap errors. This leaves the precondition on `populate_witnesses` that the user call `is_consistent` on witness nodes before converting from SimplicityHL to Simplicity. I believe I can remove this precondition and return an error if the witness values don't match the Simplicity node, but I need to update the library to get the IncompleteType type first, so that I can return a useful error. So I will defer this improvement. --- src/lib.rs | 7 +- src/named.rs | 253 ++++++++++++++++++++------------------------------- 2 files changed, 102 insertions(+), 158 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d8d3e37..de69f3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! Library for parsing and compiling SimplicityHL -pub type ProgNode = Arc; +pub type ProgNode = Arc>; pub mod array; pub mod ast; @@ -122,7 +122,8 @@ impl CompiledProgram { /// Access the Simplicity target code, without witness data. pub fn commit(&self) -> Arc> { - named::to_commit_node(&self.simplicity) + named::forget_names(&self.simplicity) + .finalize_types() .expect("Compiled SimplicityHL program has type 1 -> 1") } @@ -151,7 +152,7 @@ impl CompiledProgram { witness_values .is_consistent(&self.witness_types) .map_err(|e| e.to_string())?; - let simplicity_witness = named::to_witness_node(&self.simplicity, witness_values); + let simplicity_witness = named::populate_witnesses(&self.simplicity, witness_values); let simplicity_redeem = match env { Some(env) => simplicity_witness.finalize_pruned(env), None => simplicity_witness.finalize_unpruned(), diff --git a/src/named.rs b/src/named.rs index 4a50ab9..e62ffd7 100644 --- a/src/named.rs +++ b/src/named.rs @@ -1,96 +1,126 @@ use std::sync::Arc; use simplicity::dag::{InternalSharing, PostOrderIterItem}; -use simplicity::jet::{Elements, Jet}; +use simplicity::jet::Jet; use simplicity::node::{ - self, CommitData, ConstructData as WitnessData, Constructible, Converter, CoreConstructible, - Inner, JetConstructible, NoDisconnect, NoWitness, Node, WitnessConstructible, + self, Converter, CoreConstructible, Inner, NoDisconnect, NoWitness, Node, WitnessConstructible, }; -use simplicity::types::arrow::Arrow; -use simplicity::{types, CommitNode, FailEntropy}; -use simplicity::{Cmr, ConstructNode as WitnessNode}; +use simplicity::Cmr; +use simplicity::{types, FailEntropy}; use crate::str::WitnessName; use crate::value::StructuralValue; use crate::witness::WitnessValues; -/// Marker for [`ConstructNode`]. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Construct { - /// Makes the type non-constructible. - never: std::convert::Infallible, - /// Required by Rust. - phantom: std::marker::PhantomData, -} - -/// Sharing ID of [`ConstructNode`]. -/// Cannot be constructed because there is no sharing. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub enum ConstructId {} +pub struct WithNames(T); -impl node::Marker for Construct { - type CachedData = ConstructData; +impl node::Marker for WithNames { + type CachedData = M::CachedData; type Witness = WitnessName; + // It's quite difficult to wrap M::Disconnect because of Rust's lack of HKTs, and + // we don't use disconnect in this library right now, so punt on it for now. type Disconnect = NoDisconnect; - type SharingId = ConstructId; - type Jet = J; + type SharingId = M::SharingId; + type Jet = M::Jet; + + fn compute_sharing_id(cmr: Cmr, cached_data: &Self::CachedData) -> Option { + M::compute_sharing_id(cmr, cached_data) + } +} + +/// Helper trait so we can abstract over witness and disconnects of any type, +/// as long as we can produce them from a no-witness no-disconnect object. +pub trait Nullable { + fn none() -> Self; +} - fn compute_sharing_id(_: Cmr, _cached_data: &Self::CachedData) -> Option { +impl Nullable for Option { + fn none() -> Self { None } } +impl Nullable for NoWitness { + fn none() -> Self { + NoWitness + } +} + +impl Nullable for NoDisconnect { + fn none() -> Self { + NoDisconnect + } +} + /// [`simplicity::ConstructNode`] with named witness nodes. /// /// Nodes other than witness don't have names. -pub type ConstructNode = Node>; +pub type ConstructNode = Node>>; + +/// [`simplicity::CommitNode`] with named witness nodes. +/// +/// Nodes other than witness don't have names. +pub type CommitNode = Node>>; // FIXME: The following methods cannot be implemented for simplicity::node::Node because that is a foreign type /// Convert [`ConstructNode`] into [`CommitNode`] by dropping the name of witness nodes. -pub fn to_commit_node(node: &ConstructNode) -> Result>, types::Error> { +pub fn forget_names(node: &Node>) -> Arc> +where + M: node::Marker, + M::Disconnect: Nullable, + M::Witness: Nullable, +{ struct Forgetter; - impl Converter, node::Commit> for Forgetter { - type Error = types::Error; + impl Converter, M> for Forgetter + where + M: node::Marker, + M::Disconnect: Nullable, + M::Witness: Nullable, + { + type Error = core::convert::Infallible; fn convert_witness( &mut self, - _: &PostOrderIterItem<&Node>>, + _: &PostOrderIterItem<&Node>>, _: &WitnessName, - ) -> Result { - Ok(NoWitness) + ) -> Result { + Ok(M::Witness::none()) } fn convert_disconnect( &mut self, - _: &PostOrderIterItem<&Node>>, - _: Option<&Arc>>, + _: &PostOrderIterItem<&Node>>, + _: Option<&Arc>>, _: &NoDisconnect, - ) -> Result { - Ok(NoDisconnect) + ) -> Result { + Ok(M::Disconnect::none()) } fn convert_data( &mut self, - data: &PostOrderIterItem<&Node>>, - inner: Inner<&Arc>, J, &NoDisconnect, &NoWitness>, - ) -> Result>, Self::Error> { - let arrow = data.node.cached_data().arrow(); - let inner = inner.map(Arc::as_ref).map(CommitNode::::cached_data); - CommitData::new(arrow, inner).map(Arc::new) + data: &PostOrderIterItem<&Node>>, + _: Inner<&Arc>, M::Jet, &M::Disconnect, &M::Witness>, + ) -> Result { + Ok(data.node.cached_data().clone()) } } - node.convert::(&mut Forgetter) + match node.convert::(&mut Forgetter) { + Ok(ret) => ret, + Err(inf) => match inf {}, + } } -/// Convert [`ConstructNode`] into [`WitnessNode`] by populating witness nodes with their assigned values. +/// Converts a named [`ConstructNode`] into a standard [`node::ConstructNode`], by populating +/// witness nodes with their assigned values. /// /// Each witness node has a name. If there is no value assigned to this name, /// then the node is left empty. /// -/// When [`WitnessNode`] is finalized to [`node::RedeemNode`], there will be an error if any witness +/// When [`node::ConstructNode`] is finalized to [`node::RedeemNode`], there will be an error if any witness /// node on a used (unpruned) branch is empty. It is the responsibility of the caller to ensure that /// all used witness nodes have an assigned value. /// @@ -99,18 +129,20 @@ pub fn to_commit_node(node: &ConstructNode) -> Result>, /// It is the responsibility of the caller to ensure that the given witness `values` match the /// types in the construct `node`. This can be done by calling [`WitnessValues::is_consistent`] /// on the original SimplicityHL program before it is compiled to Simplicity. -pub fn to_witness_node(node: &ConstructNode, values: WitnessValues) -> Arc> { +pub fn populate_witnesses( + node: &ConstructNode, + values: WitnessValues, +) -> Arc> { struct Populator { values: WitnessValues, - inference_context: types::Context, } - impl Converter, node::Construct> for Populator { - type Error = (); + impl Converter>, node::Construct> for Populator { + type Error = core::convert::Infallible; fn convert_witness( &mut self, - _: &PostOrderIterItem<&Node>>, + _: &PostOrderIterItem<&ConstructNode>, witness: &WitnessName, ) -> Result, Self::Error> { let maybe_value = self @@ -123,129 +155,40 @@ pub fn to_witness_node(node: &ConstructNode, values: WitnessValues) -> Arc>>, - _: Option<&Arc>>, + _: &PostOrderIterItem<&ConstructNode>, + _: Option<&Arc>>, _: &NoDisconnect, - ) -> Result>>, Self::Error> { + ) -> Result>>, Self::Error> { Ok(None) } fn convert_data( &mut self, - _: &PostOrderIterItem<&Node>>, - inner: Inner< - &Arc>, + data: &PostOrderIterItem<&ConstructNode>, + _: Inner< + &Arc>, J, - &Option>>, + &Option>>, &Option, >, - ) -> Result, Self::Error> { - let inner = inner - .map(Arc::as_ref) - .map(WitnessNode::::cached_data) - .map_witness(Option::::clone); - Ok(WitnessData::from_inner(&self.inference_context, inner).unwrap()) - } - } - - let mut populator = Populator { - inference_context: types::Context::new(), - values, - }; - node.convert::(&mut populator) - .unwrap() -} - -/// Copy of [`node::ConstructData`] with an implementation of [`WitnessConstructible`]. -#[derive(Clone, Debug)] -pub struct ConstructData { - arrow: Arrow, - phantom: std::marker::PhantomData, -} - -impl ConstructData { - /// Access the arrow of the node. - pub fn arrow(&self) -> &Arrow { - &self.arrow - } -} - -impl From for ConstructData { - fn from(arrow: Arrow) -> Self { - Self { - arrow, - phantom: std::marker::PhantomData, + ) -> Result, Self::Error> { + Ok(data.node.cached_data().clone()) } } -} - -impl CoreConstructible for ConstructData { - fn iden(inference_context: &types::Context) -> Self { - Arrow::iden(inference_context).into() - } - - fn unit(inference_context: &types::Context) -> Self { - Arrow::unit(inference_context).into() - } - - fn injl(child: &Self) -> Self { - Arrow::injl(&child.arrow).into() - } - - fn injr(child: &Self) -> Self { - Arrow::injr(&child.arrow).into() - } - - fn take(child: &Self) -> Self { - Arrow::take(&child.arrow).into() - } - - fn drop_(child: &Self) -> Self { - Arrow::drop_(&child.arrow).into() - } - - fn comp(left: &Self, right: &Self) -> Result { - Arrow::comp(&left.arrow, &right.arrow).map(Self::from) - } - - fn case(left: &Self, right: &Self) -> Result { - Arrow::case(&left.arrow, &right.arrow).map(Self::from) - } - - fn assertl(left: &Self, right: Cmr) -> Result { - Arrow::assertl(&left.arrow, right).map(Self::from) - } - - fn assertr(left: Cmr, right: &Self) -> Result { - Arrow::assertr(left, &right.arrow).map(Self::from) - } - - fn pair(left: &Self, right: &Self) -> Result { - Arrow::pair(&left.arrow, &right.arrow).map(Self::from) - } - - fn fail(inference_context: &types::Context, entropy: FailEntropy) -> Self { - Arrow::fail(inference_context, entropy).into() - } - - fn const_word(inference_context: &types::Context, word: simplicity::Word) -> Self { - Arrow::const_word(inference_context, word).into() - } - - fn inference_context(&self) -> &types::Context { - self.arrow.inference_context() - } -} -impl JetConstructible for ConstructData { - fn jet(inference_context: &types::Context, jet: J) -> Self { - Arrow::jet(inference_context, jet).into() + let mut populator = Populator { values }; + match node.convert::(&mut populator) { + Ok(ret) => ret, + Err(inf) => match inf {}, } } -impl WitnessConstructible for ConstructData { +// This awkward construction is required by rust-simplicity to implement WitnessConstructible +// for Node>. See +// https://docs.rs/simplicity-lang/latest/simplicity/node/trait.WitnessConstructible.html#foreign-impls +impl WitnessConstructible for node::ConstructData { fn witness(inference_context: &types::Context, _: WitnessName) -> Self { - Arrow::witness(inference_context, ()).into() + WitnessConstructible::>::witness(inference_context, None) } } From ec7b4bde7bf8a7bce20267503ba5d8e26e3584aa Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Sep 2025 14:30:45 +0000 Subject: [PATCH 3/6] named: add finalize_types function This method takes a Named and produces a Named. The goal is to finalize all the types without forgetting the names. This version simply --- src/named.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/named.rs b/src/named.rs index e62ffd7..b82f5d7 100644 --- a/src/named.rs +++ b/src/named.rs @@ -64,6 +64,74 @@ pub type ConstructNode = Node>>; pub type CommitNode = Node>>; // FIXME: The following methods cannot be implemented for simplicity::node::Node because that is a foreign type +pub fn finalize_types( + node: &Node>>, +) -> Result>>>, types::Error> { + // We finalize all types but don't bother to set the root source and target + // to unit. This is a bit annoying to do, and anyway these types will already + // be unit by construction. + translate(node, |node, inner| { + let inner = inner.map_witness(|_| &NoWitness); + node::CommitData::new(node.cached_data().arrow(), inner).map(Arc::new) + }) +} + +fn translate( + node: &Node>, + translatefn: F, +) -> Result>>, E> +where + M: node::Marker, + N: node::Marker, + N::Witness: Nullable, + F: FnMut( + &Node>, + Inner<&N::CachedData, N::Jet, &NoDisconnect, &WitnessName>, + ) -> Result, +{ + struct Translator(F); + + impl Converter, WithNames> for Translator + where + M: node::Marker, + N: node::Marker, + N::Witness: Nullable, + F: FnMut( + &Node>, + Inner<&N::CachedData, N::Jet, &NoDisconnect, &WitnessName>, + ) -> Result, + { + type Error = E; + + fn convert_witness( + &mut self, + _: &PostOrderIterItem<&Node>>, + wit: &WitnessName, + ) -> Result { + Ok(wit.shallow_clone()) + } + + fn convert_disconnect( + &mut self, + _: &PostOrderIterItem<&Node>>, + _: Option<&Arc>>>, + _: &NoDisconnect, + ) -> Result { + Ok(NoDisconnect) + } + + fn convert_data( + &mut self, + data: &PostOrderIterItem<&Node>>, + inner: Inner<&Arc>>, N::Jet, &NoDisconnect, &WitnessName>, + ) -> Result { + let new_inner = inner.map(|node| node.cached_data()); + self.0(data.node, new_inner) + } + } + + node.convert::(&mut Translator(translatefn)) +} /// Convert [`ConstructNode`] into [`CommitNode`] by dropping the name of witness nodes. pub fn forget_names(node: &Node>) -> Arc> From 252521b09146caf9f840510b0c8e688e168e62de Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Sep 2025 14:30:26 +0000 Subject: [PATCH 4/6] replace Construct with Commit in CompiledProgram This commit represents an important change: in the user-facing types we are no longer holding ConstructNode objects, which mean that we are not handing the user incomplete types which are tied to a specific inference context. This means that within our code, whenever we need to manipulate types, we need to create a new local type inference context which is then dropped at the end of the function. We'll need this when we update rust-simplicity to a version that has only locally-scoped context objects. The bulk of the diff is in src/named.rs, changing the populate_witness method to convert a CommitNode to a RedeemNode, which is mostly just fixing type signatures by chasing compiler errors. Sorry for the noise. I also add a new stringly-typed error, which I'm not thrilled with, but this crate is already full of them. --- src/lib.rs | 28 +++++++++++----------- src/named.rs | 65 ++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index de69f3a..e86bddf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,12 +80,16 @@ impl TemplateProgram { arguments .is_consistent(self.simfony.parameters()) .map_err(|error| error.to_string())?; + + let construct = self + .simfony + .compile(arguments, include_debug_symbols) + .with_file(Arc::clone(&self.file))?; + let commit = named::finalize_types(&construct).map_err(|e| e.to_string())?; + Ok(CompiledProgram { debug_symbols: self.simfony.debug_symbols(self.file.as_ref()), - simplicity: self - .simfony - .compile(arguments, include_debug_symbols) - .with_file(Arc::clone(&self.file))?, + simplicity: commit, witness_types: self.simfony.witness_types().shallow_clone(), }) } @@ -94,7 +98,7 @@ impl TemplateProgram { /// A SimplicityHL program, compiled to Simplicity. #[derive(Clone, Debug)] pub struct CompiledProgram { - simplicity: ProgNode, + simplicity: Arc>, witness_types: WitnessTypes, debug_symbols: DebugSymbols, } @@ -123,8 +127,6 @@ impl CompiledProgram { /// Access the Simplicity target code, without witness data. pub fn commit(&self) -> Arc> { named::forget_names(&self.simplicity) - .finalize_types() - .expect("Compiled SimplicityHL program has type 1 -> 1") } /// Satisfy the SimplicityHL program with the given `witness_values`. @@ -152,13 +154,13 @@ impl CompiledProgram { witness_values .is_consistent(&self.witness_types) .map_err(|e| e.to_string())?; - let simplicity_witness = named::populate_witnesses(&self.simplicity, witness_values); - let simplicity_redeem = match env { - Some(env) => simplicity_witness.finalize_pruned(env), - None => simplicity_witness.finalize_unpruned(), - }; + + let mut simplicity_redeem = named::populate_witnesses(&self.simplicity, witness_values)?; + if let Some(env) = env { + simplicity_redeem = simplicity_redeem.prune(env).map_err(|e| e.to_string())?; + } Ok(SatisfiedProgram { - simplicity: simplicity_redeem.map_err(|e| e.to_string())?, + simplicity: simplicity_redeem, debug_symbols: self.debug_symbols.clone(), }) } diff --git a/src/named.rs b/src/named.rs index b82f5d7..6310b91 100644 --- a/src/named.rs +++ b/src/named.rs @@ -185,12 +185,9 @@ where /// Converts a named [`ConstructNode`] into a standard [`node::ConstructNode`], by populating /// witness nodes with their assigned values. /// -/// Each witness node has a name. If there is no value assigned to this name, -/// then the node is left empty. -/// -/// When [`node::ConstructNode`] is finalized to [`node::RedeemNode`], there will be an error if any witness -/// node on a used (unpruned) branch is empty. It is the responsibility of the caller to ensure that -/// all used witness nodes have an assigned value. +/// Each witness node has a name. If there is no value assigned to this name, an error is +/// returned. This is true even if the witness node is ultimately unused in the final +/// program. /// /// ## Soundness /// @@ -198,57 +195,59 @@ where /// types in the construct `node`. This can be done by calling [`WitnessValues::is_consistent`] /// on the original SimplicityHL program before it is compiled to Simplicity. pub fn populate_witnesses( - node: &ConstructNode, + node: &CommitNode, values: WitnessValues, -) -> Arc> { +) -> Result>, String> { struct Populator { values: WitnessValues, } - impl Converter>, node::Construct> for Populator { - type Error = core::convert::Infallible; + impl Converter>, node::Redeem> for Populator { + type Error = String; fn convert_witness( &mut self, - _: &PostOrderIterItem<&ConstructNode>, + _: &PostOrderIterItem<&CommitNode>, witness: &WitnessName, - ) -> Result, Self::Error> { - let maybe_value = self - .values - .get(witness) - .map(StructuralValue::from) - .map(simplicity::Value::from); - Ok(maybe_value) + ) -> Result { + match self.values.get(witness) { + Some(val) => Ok(simplicity::Value::from(StructuralValue::from(val))), + None => Err(format!("missing witness for {witness}")), + } } fn convert_disconnect( &mut self, - _: &PostOrderIterItem<&ConstructNode>, - _: Option<&Arc>>, + _: &PostOrderIterItem<&CommitNode>, + _: Option<&Arc>>, _: &NoDisconnect, - ) -> Result>>, Self::Error> { - Ok(None) + ) -> Result>, Self::Error> { + unreachable!("SimplicityHL does not use disconnect right now") } fn convert_data( &mut self, - data: &PostOrderIterItem<&ConstructNode>, - _: Inner< - &Arc>, + data: &PostOrderIterItem<&CommitNode>, + inner: Inner< + &Arc>, J, - &Option>>, - &Option, + &Arc>, + &simplicity::Value, >, - ) -> Result, Self::Error> { - Ok(data.node.cached_data().clone()) + ) -> Result>, Self::Error> { + let inner = inner + .map(|node| node.cached_data()) + .map_disconnect(|node| node.cached_data()) + .map_witness(simplicity::Value::shallow_clone); + Ok(Arc::new(node::RedeemData::new( + data.node.cached_data().arrow().shallow_clone(), + inner, + ))) } } let mut populator = Populator { values }; - match node.convert::(&mut populator) { - Ok(ret) => ret, - Err(inf) => match inf {}, - } + node.convert::(&mut populator) } // This awkward construction is required by rust-simplicity to implement WitnessConstructible From 090370ca79b5da47fd51d574b69a71a1100a502a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 12 Sep 2025 14:53:50 +0000 Subject: [PATCH 5/6] move type checking from lib.rs into compile.rs Now the only use of ConstructNode, and incomplete types more broadly, occurs within compile.rs. This will make it much easier for us to update rust-simplicity to require scoped type inference contexts. --- src/compile.rs | 11 ++++++++--- src/lib.rs | 3 +-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 741cf85..6372cd3 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -14,7 +14,7 @@ use crate::ast::{ }; use crate::debug::CallTracker; use crate::error::{Error, RichError, Span, WithSpan}; -use crate::named::{CoreExt, PairBuilder}; +use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; use crate::str::WitnessName; @@ -257,13 +257,18 @@ impl Program { &self, arguments: Arguments, include_debug_symbols: bool, - ) -> Result { + ) -> Result>, RichError> { let mut scope = Scope::new( Arc::clone(self.call_tracker()), arguments, include_debug_symbols, ); - self.main().compile(&mut scope).map(PairBuilder::build) + + let main = self.main(); + let construct = main.compile(&mut scope).map(PairBuilder::build)?; + // SimplicityHL types should be correct by construction. If not, assign the + // whole main function as the span for them, which is as sensible as anything. + named::finalize_types(&construct).with_span(main) } } diff --git a/src/lib.rs b/src/lib.rs index e86bddf..c9fd8e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,11 +81,10 @@ impl TemplateProgram { .is_consistent(self.simfony.parameters()) .map_err(|error| error.to_string())?; - let construct = self + let commit = self .simfony .compile(arguments, include_debug_symbols) .with_file(Arc::clone(&self.file))?; - let commit = named::finalize_types(&construct).map_err(|e| e.to_string())?; Ok(CompiledProgram { debug_symbols: self.simfony.debug_symbols(self.file.as_ref()), From 96e7df240a5ed0fa60b3dcab5e2d9b16dcd9fe1c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 12 Sep 2025 16:06:11 +0000 Subject: [PATCH 6/6] move ProgNode into compile.rs as a private type Now that no public types contain ConstructNodes, we don't need a type alias for a ConstructNode to be exported. Move this into compile.rs, make it private, and replace a couple isolated uses in pattern.rs with generic ones. --- src/compile.rs | 4 +++- src/lib.rs | 2 -- src/pattern.rs | 18 +++++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 6372cd3..f631a03 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -21,7 +21,9 @@ use crate::str::WitnessName; use crate::types::{StructuralType, TypeDeconstructible}; use crate::value::StructuralValue; use crate::witness::Arguments; -use crate::{ProgNode, Value}; +use crate::Value; + +type ProgNode = Arc>; /// Each SimplicityHL expression expects an _input value_. /// A SimplicityHL expression is translated into a Simplicity expression diff --git a/src/lib.rs b/src/lib.rs index c9fd8e4..1f2781a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ //! Library for parsing and compiling SimplicityHL -pub type ProgNode = Arc>; - pub mod array; pub mod ast; pub mod compile; diff --git a/src/pattern.rs b/src/pattern.rs index 518c50b..1e2dae3 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -7,10 +7,9 @@ use miniscript::iter::{Tree, TreeLike}; use crate::array::BTreeSlice; use crate::error::Error; -use crate::named::{PairBuilder, SelectorBuilder}; +use crate::named::{CoreExt, PairBuilder, SelectorBuilder}; use crate::str::Identifier; use crate::types::{ResolvedType, TypeInner}; -use crate::ProgNode; /// Pattern for binding values to variables. #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -195,14 +194,15 @@ impl BasePattern { /// Check if the `identifier` is contained inside the pattern. pub fn contains(&self, identifier: &Identifier) -> bool { - self.get(identifier).is_some() + self.pre_order_iter() + .any(|node| matches!(node, BasePattern::Identifier(id) if id == identifier)) } /// Compute a Simplicity expression that returns the value of the given `identifier`. /// The expression takes as input a value that matches the `self` pattern. /// /// The expression is a sequence of `take` and `drop` followed by `iden`. - fn get(&self, identifier: &Identifier) -> Option> { + fn get(&self, identifier: &Identifier) -> Option> { let mut selector = SelectorBuilder::default(); for data in self.verbose_pre_order_iter() { @@ -302,11 +302,11 @@ impl BasePattern { /// This means there are infinitely many translating expressions from `self` to `to`. /// For instance, `iden`, `iden & iden`, `(iden & iden) & iden`, and so on. /// We enforce a unique translation by banning ignore from the `to` pattern. - pub fn translate( + pub fn translate( &self, ctx: &simplicity::types::Context, to: &Self, - ) -> Option> { + ) -> Option> { #[derive(Debug, Clone)] enum Task<'a> { Translate(&'a BasePattern, &'a BasePattern), @@ -439,6 +439,8 @@ impl From<&Pattern> for BasePattern { #[cfg(test)] mod tests { use super::*; + use crate::named; + use simplicity::jet::Elements; #[test] fn translate_pattern() { @@ -462,7 +464,9 @@ mod tests { for (target, expected_expr) in target_expr { let ctx = simplicity::types::Context::new(); - let expr = env.translate(&ctx, &target).unwrap(); + let expr = env + .translate::>>(&ctx, &target) + .unwrap(); assert_eq!(expected_expr, expr.as_ref().display_expr().to_string()); } }