From 8896e7861c8f6c6ab6bedccf9caad001d63e2189 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 24 Aug 2026 13:49:17 +0000 Subject: [PATCH 1/8] merkle: feature-gate ConstructibleCmr Despite being `pub` this is not actually exported in the public API. It is only used by the policy module, which is gated on the `elements` feature. So when compiling with `--no-default-features` we get warnings about it being unused. --- src/merkle/cmr.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/merkle/cmr.rs b/src/merkle/cmr.rs index 653940ed..1b5ca6b7 100644 --- a/src/merkle/cmr.rs +++ b/src/merkle/cmr.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 use crate::jet::Jet; +#[cfg(feature = "elements")] use crate::node::{CoreConstructible, DisconnectConstructible, WitnessConstructible}; +#[cfg(feature = "elements")] use crate::types::{self, Error}; use crate::value::Word; use crate::{FailEntropy, Tmr}; @@ -254,11 +256,13 @@ impl Cmr { /// Wrapper around a CMR which allows it to be constructed with the /// `*Constructible*` traits, allowing CMRs to be computed using the /// same generic construction code that nodes are. +#[cfg(feature = "elements")] // only used by policy module pub struct ConstructibleCmr<'brand> { pub cmr: Cmr, pub inference_context: types::Context<'brand>, } +#[cfg(feature = "elements")] // only used by policy module impl<'brand> CoreConstructible<'brand> for ConstructibleCmr<'brand> { fn iden(inference_context: &types::Context<'brand>) -> Self { ConstructibleCmr { @@ -366,6 +370,7 @@ impl<'brand> CoreConstructible<'brand> for ConstructibleCmr<'brand> { } } +#[cfg(feature = "elements")] // only used by policy module impl<'brand, X> DisconnectConstructible<'brand, X> for ConstructibleCmr<'brand> { // Specifically with disconnect we don't check for consistency between the // type inference context of the disconnected node, if any, and that of @@ -379,6 +384,7 @@ impl<'brand, X> DisconnectConstructible<'brand, X> for ConstructibleCmr<'brand> } } +#[cfg(feature = "elements")] // only used by policy module impl<'brand, W> WitnessConstructible<'brand, W> for ConstructibleCmr<'brand> { fn witness(inference_context: &types::Context<'brand>, _witness: W) -> Self { ConstructibleCmr { @@ -393,6 +399,7 @@ mod tests { use super::*; use crate::node::{ConstructNode, CoreConstructible}; + use crate::types; use std::str::FromStr; use std::sync::Arc; From 7508bd1929e856c384b35df899b51f41382ad87c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 4 Aug 2026 14:03:06 +0000 Subject: [PATCH 2/8] types: don't impl Constructible for Arrow In the next commits we will change the Constructible trait to represent something like "can be constructed given an Arrow". It will then be a bit hard to implement the Constructible traits for Arrow itself. (Well, it's not *that* hard, but you have to tiptoe around the possibility that your methods call each other in a cycle causing infinite recursion.) These traits are already not a great fit for Arrow itself: the witness, fail, assertl and assertr combinators all take auxiliary data that Arrow does not care about and which we can remove. The word combinator takes a whole word by value, but Arrow only cares about its length. In some sense ConstructData is already the "Arrow, but wrapped to impl the Constructible traits" type, so having Arrow implement these traits too is wasteful. We do lose the derived methods that we get from Constructible -- in particular scribe() and from_inner() are pretty useful. But scribe() is actually not that useful for Arrow (the Arrow for a scribe always has unit source and target equal to the value's type) and is never called. And from_inner is only called once, and requires a bunch of contortions with the Inner mapping combinators to make this call work. The code is actually clearer if I just inline the call (which I did, in src/node/commit.rs in the `unfinalize_types` method). --- src/node/commit.rs | 36 +++++++++++++++++++++--------- src/node/construct.rs | 16 ++++++------- src/types/arrow.rs | 52 ++++++++++++++++++++----------------------- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/src/node/commit.rs b/src/node/commit.rs index 616a6001..1f65a629 100644 --- a/src/node/commit.rs +++ b/src/node/commit.rs @@ -7,8 +7,8 @@ use crate::{encode, types, Value}; use crate::{Amr, BitIter, BitWriter, Cmr, DecodeError, Ihr, Imr}; use super::{ - Construct, ConstructData, ConstructNode, Constructible, Converter, Inner, Marker, NoDisconnect, - NoWitness, Node, Redeem, RedeemNode, + Construct, ConstructData, ConstructNode, Converter, Inner, Marker, NoDisconnect, NoWitness, + Node, Redeem, RedeemNode, }; use std::io; @@ -213,14 +213,30 @@ impl CommitNode { &Option, >, ) -> Result, Self::Error> { - let inner = inner - .map(|node| node.arrow()) - .map_disconnect(|maybe_node| maybe_node.as_ref().map(|node| node.arrow())); - let inner = inner.disconnect_as_ref(); // lol sigh rust - Ok(ConstructData::new(Arrow::from_inner( - self.inference_context, - inner, - )?)) + use crate::node::DisconnectConstructible as _; + + let new_arrow = match inner { + Inner::Iden => Arrow::iden(self.inference_context), + Inner::Unit => Arrow::unit(self.inference_context), + Inner::InjL(child) => Arrow::injl(child.arrow()), + Inner::InjR(child) => Arrow::injr(child.arrow()), + Inner::Take(child) => Arrow::take(child.arrow()), + Inner::Drop(child) => Arrow::drop_(child.arrow()), + Inner::Comp(lft, rgt) => Arrow::comp(lft.arrow(), rgt.arrow())?, + Inner::Case(lft, rgt) => Arrow::case(lft.arrow(), rgt.arrow())?, + Inner::Pair(lft, rgt) => Arrow::pair(lft.arrow(), rgt.arrow())?, + Inner::Disconnect(lft, rgt) => { + Arrow::disconnect(lft.arrow(), &rgt.as_ref().map(|node| node.arrow()))? + } + Inner::AssertL(lft, _) => Arrow::assertl(lft.arrow())?, + Inner::AssertR(_, rgt) => Arrow::assertr(rgt.arrow())?, + Inner::Witness(_) => Arrow::witness(self.inference_context), + Inner::Fail(_) => Arrow::fail(self.inference_context), + Inner::Jet(ref jet) => Arrow::jet(self.inference_context, jet.as_ref()), + Inner::Word(ref word) => Arrow::const_word(self.inference_context, word), + }; + + Ok(ConstructData::new(new_arrow)) } } diff --git a/src/node/construct.rs b/src/node/construct.rs index ad3eff45..c5c918bf 100644 --- a/src/node/construct.rs +++ b/src/node/construct.rs @@ -330,15 +330,15 @@ impl<'brand> CoreConstructible<'brand> for ConstructData<'brand> { }) } - fn assertl(left: &Self, right: Cmr) -> Result { + fn assertl(left: &Self, _: Cmr) -> Result { Ok(ConstructData { - arrow: Arrow::assertl(&left.arrow, right)?, + arrow: Arrow::assertl(&left.arrow)?, }) } - fn assertr(left: Cmr, right: &Self) -> Result { + fn assertr(_: Cmr, right: &Self) -> Result { Ok(ConstructData { - arrow: Arrow::assertr(left, &right.arrow)?, + arrow: Arrow::assertr(&right.arrow)?, }) } @@ -348,15 +348,15 @@ impl<'brand> CoreConstructible<'brand> for ConstructData<'brand> { }) } - fn fail(inference_context: &types::Context<'brand>, entropy: FailEntropy) -> Self { + fn fail(inference_context: &types::Context<'brand>, _: FailEntropy) -> Self { ConstructData { - arrow: Arrow::fail(inference_context, entropy), + arrow: Arrow::fail(inference_context), } } fn const_word(inference_context: &types::Context<'brand>, word: Word) -> Self { ConstructData { - arrow: Arrow::const_word(inference_context, word), + arrow: Arrow::const_word(inference_context, &word), } } @@ -388,7 +388,7 @@ impl<'brand> DisconnectConstructible<'brand, Option>>> impl<'brand> WitnessConstructible<'brand, Option> for ConstructData<'brand> { fn witness(inference_context: &types::Context<'brand>, _witness: Option) -> Self { ConstructData { - arrow: Arrow::witness(inference_context, NoWitness), + arrow: Arrow::witness(inference_context), } } } diff --git a/src/types/arrow.rs b/src/types/arrow.rs index a9df5c42..bd25dee9 100644 --- a/src/types/arrow.rs +++ b/src/types/arrow.rs @@ -15,7 +15,7 @@ use std::fmt; use std::sync::Arc; use crate::jet::Jet; -use crate::node::{CoreConstructible, DisconnectConstructible, NoDisconnect, WitnessConstructible}; +use crate::node::{DisconnectConstructible, NoDisconnect}; use crate::types::{Context, Error, Final, Type}; use crate::value::Word; @@ -183,10 +183,8 @@ impl<'brand> Arrow<'brand> { inference_context: lchild_arrow.inference_context.shallow_clone(), }) } -} -impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { - fn iden(inference_context: &Context<'brand>) -> Self { + pub fn iden(inference_context: &Context<'brand>) -> Self { // Throughout this module, when two types are the same, we reuse a // pointer to them rather than creating distinct types and unifying // them. This theoretically could lead to more confusing errors for @@ -200,7 +198,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn unit(inference_context: &Context<'brand>) -> Self { + pub fn unit(inference_context: &Context<'brand>) -> Self { Arrow { source: Type::free(inference_context, new_name("unit_src_")), target: Type::unit(inference_context), @@ -208,7 +206,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn injl(child: &Self) -> Self { + pub fn injl(child: &Self) -> Self { Arrow { source: child.source.shallow_clone(), target: Type::sum( @@ -220,7 +218,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn injr(child: &Self) -> Self { + pub fn injr(child: &Self) -> Self { Arrow { source: child.source.shallow_clone(), target: Type::sum( @@ -232,7 +230,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn take(child: &Self) -> Self { + pub fn take(child: &Self) -> Self { Arrow { source: Type::product( &child.inference_context, @@ -244,7 +242,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn drop_(child: &Self) -> Self { + pub fn drop_(child: &Self) -> Self { Arrow { source: Type::product( &child.inference_context, @@ -256,7 +254,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn comp(left: &Self, right: &Self) -> Result { + pub fn comp(left: &Self, right: &Self) -> Result { left.inference_context.check_eq(&right.inference_context)?; left.inference_context.unify( &left.target, @@ -270,19 +268,19 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { }) } - fn case(left: &Self, right: &Self) -> Result { + pub fn case(left: &Self, right: &Self) -> Result { Self::for_case(Some(left), Some(right)) } - fn assertl(left: &Self, _: crate::Cmr) -> Result { + pub fn assertl(left: &Self) -> Result { Self::for_case(Some(left), None) } - fn assertr(_: crate::Cmr, right: &Self) -> Result { + pub fn assertr(right: &Self) -> Result { Self::for_case(None, Some(right)) } - fn pair(left: &Self, right: &Self) -> Result { + pub fn pair(left: &Self, right: &Self) -> Result { left.inference_context.check_eq(&right.inference_context)?; left.inference_context.unify( &left.source, @@ -300,7 +298,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { }) } - fn fail(inference_context: &Context<'brand>, _: crate::FailEntropy) -> Self { + pub fn fail(inference_context: &Context<'brand>) -> Self { Arrow { source: Type::free(inference_context, new_name("fail_src_")), target: Type::free(inference_context, new_name("fail_tgt_")), @@ -308,7 +306,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn const_word(inference_context: &Context<'brand>, word: Word) -> Self { + pub fn const_word(inference_context: &Context<'brand>, word: &Word) -> Self { Arrow { source: Type::unit(inference_context), target: Type::two_two_n(inference_context, word.n()), @@ -316,7 +314,7 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn jet(inference_context: &Context<'brand>, jet: &dyn Jet) -> Self { + pub fn jet(inference_context: &Context<'brand>, jet: &dyn Jet) -> Self { inference_context.check_jet(jet); Arrow { @@ -326,7 +324,15 @@ impl<'brand> CoreConstructible<'brand> for Arrow<'brand> { } } - fn inference_context(&self) -> &Context<'brand> { + pub fn witness(inference_context: &Context<'brand>) -> Self { + Arrow { + source: Type::free(inference_context, new_name("witness_src_")), + target: Type::free(inference_context, new_name("witness_tgt_")), + inference_context: inference_context.shallow_clone(), + } + } + + pub fn inference_context(&self) -> &Context<'brand> { &self.inference_context } } @@ -360,13 +366,3 @@ impl<'brand> DisconnectConstructible<'brand, Option<&Arrow<'brand>>> for Arrow<' } } } - -impl<'brand, W> WitnessConstructible<'brand, W> for Arrow<'brand> { - fn witness(inference_context: &Context<'brand>, _: W) -> Self { - Arrow { - source: Type::free(inference_context, new_name("witness_src_")), - target: Type::free(inference_context, new_name("witness_tgt_")), - inference_context: inference_context.shallow_clone(), - } - } -} From 7efdb8969d6f796082637eb9a8f6d989ca9aabfc Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 4 Aug 2026 14:42:43 +0000 Subject: [PATCH 3/8] types: reexport FinalArrow and Arrow It will become very annoying to type arrow::Arrow all the time. types::Arrow would read much better (assuming you don't want to just type Arrow). The arrow module only exports two symbols, Arrow and FinalArrow, so just reexport both of these from the types module. Leave the module `pub`, though we could now make it private, to avoid unnecessarily breaking existing code. --- src/human_encoding/named_node.rs | 3 +-- src/merkle/amr.rs | 2 +- src/merkle/ihr.rs | 2 +- src/node/commit.rs | 2 +- src/node/construct.rs | 2 +- src/node/redeem.rs | 2 +- src/types/mod.rs | 1 + 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/human_encoding/named_node.rs b/src/human_encoding/named_node.rs index 9548abd1..1b4919ac 100644 --- a/src/human_encoding/named_node.rs +++ b/src/human_encoding/named_node.rs @@ -8,8 +8,7 @@ use crate::node::{ self, Commit, CommitData, CommitNode, Construct, ConstructData, Constructible as _, Converter, CoreConstructible as _, Inner, NoDisconnect, NoWitness, Node, }; -use crate::types; -use crate::types::arrow::{Arrow, FinalArrow}; +use crate::types::{self, Arrow, FinalArrow}; use crate::{encode, ConstructNode, Value}; use crate::{BitWriter, Cmr, Ihr}; diff --git a/src/merkle/amr.rs b/src/merkle/amr.rs index 21dbeb15..6ae8efda 100644 --- a/src/merkle/amr.rs +++ b/src/merkle/amr.rs @@ -2,7 +2,7 @@ use crate::jet::Jet; use crate::merkle::compact_value; -use crate::types::arrow::FinalArrow; +use crate::types::FinalArrow; use crate::value::Word; use crate::{Cmr, Tmr, Value}; use hashes::sha256::Midstate; diff --git a/src/merkle/ihr.rs b/src/merkle/ihr.rs index 14c1d4fe..8a6f492d 100644 --- a/src/merkle/ihr.rs +++ b/src/merkle/ihr.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 use crate::jet::Jet; -use crate::types::arrow::FinalArrow; +use crate::types::FinalArrow; use crate::value::Word; use crate::{Cmr, Tmr, Value}; use hashes::sha256::Midstate; diff --git a/src/node/commit.rs b/src/node/commit.rs index 1f65a629..501ae76d 100644 --- a/src/node/commit.rs +++ b/src/node/commit.rs @@ -2,7 +2,7 @@ use crate::dag::{DagLike, MaxSharing, NoSharing, PostOrderIterItem}; use crate::jet::Jet; -use crate::types::arrow::{Arrow, FinalArrow}; +use crate::types::{Arrow, FinalArrow}; use crate::{encode, types, Value}; use crate::{Amr, BitIter, BitWriter, Cmr, DecodeError, Ihr, Imr}; diff --git a/src/node/construct.rs b/src/node/construct.rs index c5c918bf..5a77ed16 100644 --- a/src/node/construct.rs +++ b/src/node/construct.rs @@ -2,7 +2,7 @@ use crate::dag::{InternalSharing, PostOrderIterItem}; use crate::jet::{Jet, JetEnvironment}; -use crate::types::{self, arrow::Arrow}; +use crate::types::{self, Arrow}; use crate::{encode, BitIter, BitWriter, Cmr, FailEntropy, FinalizeError, RedeemNode, Value, Word}; use std::io; diff --git a/src/node/redeem.rs b/src/node/redeem.rs index 1b67d106..ea4c4270 100644 --- a/src/node/redeem.rs +++ b/src/node/redeem.rs @@ -4,7 +4,7 @@ use crate::analysis::NodeBounds; use crate::bit_machine::{ExecutionError, PruneTracker, SetTracker}; use crate::dag::{DagLike, InternalSharing, MaxSharing, PostOrderIterItem}; use crate::jet::{Jet, JetEnvironment}; -use crate::types::{self, arrow::FinalArrow}; +use crate::types::{self, FinalArrow}; use crate::{encode, BitMachine}; use crate::{Amr, BitIter, BitWriter, Cmr, DecodeError, Ihr, Imr, Value}; diff --git a/src/types/mod.rs b/src/types/mod.rs index bef9550e..ef6d39a8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -86,6 +86,7 @@ mod precomputed; mod union_bound; mod variable; +pub use arrow::{Arrow, FinalArrow}; pub use context::{BoundRef, Context}; pub use final_data::{CompleteBound, Final, TypeTooLargeError}; pub use incomplete::Incomplete; From 3d8e48fdecbddbb18a8b28f29e5a782b11f58671 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 10 Aug 2026 20:37:12 +0000 Subject: [PATCH 4/8] types: fix the use_self lint for the arrow module This was just kinda bugging me. It's better to use `Self` than explicitly writing `Arrow` both because it makes refactorings easier (e.g. if we copy/paste or macroize the code, it will require no changes to apply to a different type), and because `Self` is more specific than `Arrow`. That is, `Self` means `Arrow<'brand>` while just `Arrow` means `Arrow<'_>`. This improves compiler error messages when there are lifetime problems. --- src/types/arrow.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/types/arrow.rs b/src/types/arrow.rs index bd25dee9..5564f77b 100644 --- a/src/types/arrow.rs +++ b/src/types/arrow.rs @@ -67,7 +67,7 @@ impl fmt::Display for FinalArrow { impl FinalArrow { /// Same as [`Self::clone`] but named to make it clearer that this is cheap pub fn shallow_clone(&self) -> Self { - FinalArrow { + Self { source: Arc::clone(&self.source), target: Arc::clone(&self.target), } @@ -85,7 +85,7 @@ impl<'brand> Arrow<'brand> { /// Same as [`Self::clone`] but named to make it clearer that this is cheap pub fn shallow_clone(&self) -> Self { - Arrow { + Self { source: self.source.shallow_clone(), target: self.target.shallow_clone(), inference_context: self.inference_context.shallow_clone(), @@ -143,7 +143,7 @@ impl<'brand> Arrow<'brand> { )?; } - Ok(Arrow { + Ok(Self { source: prod_sum_a_b_c, target, inference_context: ctx, @@ -177,7 +177,7 @@ impl<'brand> Arrow<'brand> { let prod_b_d = Type::product(ctx, b, d); - Ok(Arrow { + Ok(Self { source: a, target: prod_b_d, inference_context: lchild_arrow.inference_context.shallow_clone(), @@ -191,7 +191,7 @@ impl<'brand> Arrow<'brand> { // the user during type inference, but in practice type inference // is completely opaque and there's no harm in making it moreso. let new = Type::free(inference_context, new_name("iden_src_")); - Arrow { + Self { source: new.shallow_clone(), target: new, inference_context: inference_context.shallow_clone(), @@ -199,7 +199,7 @@ impl<'brand> Arrow<'brand> { } pub fn unit(inference_context: &Context<'brand>) -> Self { - Arrow { + Self { source: Type::free(inference_context, new_name("unit_src_")), target: Type::unit(inference_context), inference_context: inference_context.shallow_clone(), @@ -207,7 +207,7 @@ impl<'brand> Arrow<'brand> { } pub fn injl(child: &Self) -> Self { - Arrow { + Self { source: child.source.shallow_clone(), target: Type::sum( &child.inference_context, @@ -219,7 +219,7 @@ impl<'brand> Arrow<'brand> { } pub fn injr(child: &Self) -> Self { - Arrow { + Self { source: child.source.shallow_clone(), target: Type::sum( &child.inference_context, @@ -231,7 +231,7 @@ impl<'brand> Arrow<'brand> { } pub fn take(child: &Self) -> Self { - Arrow { + Self { source: Type::product( &child.inference_context, child.source.shallow_clone(), @@ -243,7 +243,7 @@ impl<'brand> Arrow<'brand> { } pub fn drop_(child: &Self) -> Self { - Arrow { + Self { source: Type::product( &child.inference_context, Type::free(&child.inference_context, new_name("drop_src_")), @@ -261,7 +261,7 @@ impl<'brand> Arrow<'brand> { &right.source, "comp combinator: left target = right source", )?; - Ok(Arrow { + Ok(Self { source: left.source.shallow_clone(), target: right.target.shallow_clone(), inference_context: left.inference_context.shallow_clone(), @@ -287,7 +287,7 @@ impl<'brand> Arrow<'brand> { &right.source, "pair combinator: left source = right source", )?; - Ok(Arrow { + Ok(Self { source: left.source.shallow_clone(), target: Type::product( &left.inference_context, @@ -299,7 +299,7 @@ impl<'brand> Arrow<'brand> { } pub fn fail(inference_context: &Context<'brand>) -> Self { - Arrow { + Self { source: Type::free(inference_context, new_name("fail_src_")), target: Type::free(inference_context, new_name("fail_tgt_")), inference_context: inference_context.shallow_clone(), @@ -307,7 +307,7 @@ impl<'brand> Arrow<'brand> { } pub fn const_word(inference_context: &Context<'brand>, word: &Word) -> Self { - Arrow { + Self { source: Type::unit(inference_context), target: Type::two_two_n(inference_context, word.n()), inference_context: inference_context.shallow_clone(), @@ -317,7 +317,7 @@ impl<'brand> Arrow<'brand> { pub fn jet(inference_context: &Context<'brand>, jet: &dyn Jet) -> Self { inference_context.check_jet(jet); - Arrow { + Self { source: jet.source_ty().to_type(inference_context), target: jet.target_ty().to_type(inference_context), inference_context: inference_context.shallow_clone(), @@ -325,7 +325,7 @@ impl<'brand> Arrow<'brand> { } pub fn witness(inference_context: &Context<'brand>) -> Self { - Arrow { + Self { source: Type::free(inference_context, new_name("witness_src_")), target: Type::free(inference_context, new_name("witness_tgt_")), inference_context: inference_context.shallow_clone(), @@ -337,7 +337,7 @@ impl<'brand> Arrow<'brand> { } } -impl<'brand> DisconnectConstructible<'brand, Arrow<'brand>> for Arrow<'brand> { +impl<'brand> DisconnectConstructible<'brand, Self> for Arrow<'brand> { fn disconnect(left: &Self, right: &Self) -> Result { Self::for_disconnect(left, right) } @@ -349,7 +349,7 @@ impl<'brand> DisconnectConstructible<'brand, NoDisconnect> for Arrow<'brand> { let target = Type::free(&left.inference_context, "disc_tgt".into()); Self::for_disconnect( left, - &Arrow { + &Self { source, target, inference_context: left.inference_context.shallow_clone(), @@ -358,7 +358,7 @@ impl<'brand> DisconnectConstructible<'brand, NoDisconnect> for Arrow<'brand> { } } -impl<'brand> DisconnectConstructible<'brand, Option<&Arrow<'brand>>> for Arrow<'brand> { +impl<'brand> DisconnectConstructible<'brand, Option<&Self>> for Arrow<'brand> { fn disconnect(left: &Self, right: &Option<&Self>) -> Result { match *right { Some(right) => Self::disconnect(left, right), From 002a4638166ff5d99b0426b5eb7b2e7c2847b942 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 10 Aug 2026 20:31:03 +0000 Subject: [PATCH 5/8] types: add Arrow::hidden and Arrow::program In two commits we are going to refactor the `CoreConstructible` type to add an `arrow()` accessor. That is, we are redefining a "constructible node" to be one that has an unfinalized type arrow associated with it, rather than just something that shares the recursive constructor structure. This was already implicitly the case, since CoreConstructible::pair and others return a `Result<(), types::Error>`, but this change will make things explicit. To make construction of hidden nodes and "unknown programs" easier, introduce Arrow constructors that directly provide new free arrows and new 1-1 arrows. We already have two "free arrow" constructors -- Arrow::fail and Arrow::witness -- but this one is more clearly for an "unknown node" rather than a particular node that happens to have a free arrow. --- src/types/arrow.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/types/arrow.rs b/src/types/arrow.rs index 5564f77b..8839c30e 100644 --- a/src/types/arrow.rs +++ b/src/types/arrow.rs @@ -184,6 +184,27 @@ impl<'brand> Arrow<'brand> { }) } + /// A type arrow where both source and target types are new free variables. + /// + /// This should be used for hidden nodes or other nodes where nothing about + /// the node is known. + pub fn hidden(inference_context: &Context<'brand>) -> Self { + Self { + source: Type::free(inference_context, new_name("hidden_src_")), + target: Type::free(inference_context, new_name("hidden_tgt_")), + inference_context: inference_context.shallow_clone(), + } + } + + /// A type arrow where both source and target types fixed to the unit type. + pub fn program(inference_context: &Context<'brand>) -> Self { + Self { + source: Type::unit(inference_context), + target: Type::unit(inference_context), + inference_context: inference_context.shallow_clone(), + } + } + pub fn iden(inference_context: &Context<'brand>) -> Self { // Throughout this module, when two types are the same, we reuse a // pointer to them rather than creating distinct types and unifying From 47d1c25209b9f41b6aae1ce771307980c14534a4 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 31 Aug 2026 19:09:27 +0000 Subject: [PATCH 6/8] policy: add regression test for CMR computation of hidden nodes Found by Claude in a review of an initial version of https://github.com/BlockstreamResearch/rust-simplicity/pull/377#issuecomment-5421625181 --- src/policy/satisfy.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index da7c866c..3ea0d10a 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -715,4 +715,34 @@ mod tests { } }); } + + #[test] + fn satisfy_hidden_branch_cmr_matches_commit() { + // Regression test: when a policy branch is unsatisfiable it becomes a + // "hidden" node during satisfaction. That hidden node flows through + // `drop_` (in `policy::serialize::or`) and then a `case` that becomes an + // assertion. The hidden node's CMR must be wrapped as it passes through + // the `drop_` combinator, otherwise the satisfied program's Merkle root + // no longer matches the committed policy's Merkle root. + types::Context::with_context(|ctx| { + let env = ElementsEnv::dummy(); + let satisfier = get_satisfier(ctx, &env); + let images: Vec<_> = satisfier.preimages.keys().copied().collect(); + + // Left branch is unsatisfiable (unknown preimage) => hidden. + let policy = Policy::Or { + left: Arc::new(Policy::Sha256(sha256::Hash::from_byte_array([9; 32]))), + right: Arc::new(Policy::Sha256(images[0])), + }; + + let committed_cmr = policy.commit().cmr(); + let program = policy.satisfy(&satisfier, &env).expect("satisfiable"); + let satisfied_cmr = program.cmr(); + + assert_eq!( + committed_cmr, satisfied_cmr, + "satisfied program CMR ({satisfied_cmr}) must equal committed policy CMR ({committed_cmr})", + ); + }); + } } From 36fc4ce28ef2c273d8ce3e5d0ebdbd5ff9d093fd Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 10 Aug 2026 21:01:03 +0000 Subject: [PATCH 7/8] node: rewrite node::Hiding to hold an arrow instead of an inference context In the next commit we will change CoreConstructible to add an arrow() accessor. The `Hiding` type in particular requires significant refactoring to hold an arrow rather than directly holding an inference context. In particular, when creating hidden nodes, we should be creating fresh hidden arrows for them, since each hidden node is independent from all others. (Even if they have the same CMR, they may infer to different type arrows.) This is something of an annoying change -- hidden nodes don't really have type errors; there are no constraints on their source/target types so they can be used literally anywhere. This is arguably refrected best by the existing code, which does not track any type arrow for hidden nodes. However, when we change CoreConstructible to use the type arrow, we need to have something there, so everywhere that we construct a new hidden node, we call Arrow::hidden to get a new hidden arrow. --- src/node/hiding.rs | 231 +++++++++++++++++++++++++----------------- src/policy/satisfy.rs | 2 +- 2 files changed, 137 insertions(+), 96 deletions(-) diff --git a/src/node/hiding.rs b/src/node/hiding.rs index 2e7bc57c..c537ffc1 100644 --- a/src/node/hiding.rs +++ b/src/node/hiding.rs @@ -1,8 +1,14 @@ use crate::jet::Jet; use crate::node::{CoreConstructible, DisconnectConstructible, WitnessConstructible}; -use crate::types::{Context, Error}; +use crate::types::{Arrow, Context, Error}; use crate::{Cmr, FailEntropy, HasCmr, Word}; +#[derive(Clone, Debug)] +enum HidingInner<'brand, N> { + Node(N), + Hidden { cmr: Cmr, arrow: Arrow<'brand> }, +} + /// Wrapper that allows a node to be "hidden" during program construction. /// /// ## Program construction @@ -30,103 +36,147 @@ use crate::{Cmr, FailEntropy, HasCmr, Word}; /// The wrapper merely _simulates_ hidden nodes. /// At no point are actual hidden nodes created. /// To stress this fact, I write "hidden" in quotation marks. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug)] pub struct Hiding<'brand, N> { - result: HidingResult, - /// Inference context for program construction. - /// - /// Even a "hidden" node needs an inference context - /// because the context may be queried via [`CoreConstructible::inference_context`]. - /// When a "hidden" node is converted into an assertion via the - /// [`CoreConstructible::case`] constructor, this context is required to build the case node. - /// For soundness, the same context should be returned for all nodes of the same program. - ctx: Context<'brand>, + inner: HidingInner<'brand, N>, } -type HidingResult = Result; - impl<'brand, N> Hiding<'brand, N> { /// Create a "hidden" node with the given CMR. /// /// To enable the construction of possible parent nodes, /// the inference context of the current program must be passed. - pub const fn hidden(cmr: Cmr, ctx: Context<'brand>) -> Self { - // # Soundness - // The hidden node introduces no type variables. + pub fn hidden(cmr: Cmr, ctx: &Context<'brand>) -> Self { Self { - result: Err(cmr), - ctx, + inner: HidingInner::Hidden { + cmr, + arrow: Arrow::hidden(ctx), + }, } } - fn hidden_cloned_ctx(&self, cmr: Cmr) -> Self { - Self { - result: Err(cmr), - ctx: self.ctx.shallow_clone(), + /// If the node is not hidden, apply a function to the underlying node. If it is hidden, + /// apply a function to its CMR. + #[inline] + pub fn map_ref( + &self, + mapfn: impl FnOnce(&N) -> M, + cmrfn: impl FnOnce(Cmr) -> Cmr, + ) -> Hiding<'brand, M> { + use core::convert::Infallible; + match self.map_ref_result::<_, Infallible>(|node| Ok(mapfn(node)), cmrfn) { + Ok(res) => res, + Err(inf) => match inf {}, } } + /// If the node is not hidden, apply a function to the underlying node. If it is hidden, + /// do nothing. + #[inline] + pub fn map_ref_result( + &self, + mapfn: impl FnOnce(&N) -> Result, + cmrfn: impl FnOnce(Cmr) -> Cmr, + ) -> Result, Err> { + Ok(Hiding { + inner: match self.inner { + HidingInner::Node(ref n) => HidingInner::Node(mapfn(n)?), + HidingInner::Hidden { cmr, ref arrow } => HidingInner::Hidden { + cmr: cmrfn(cmr), + arrow: arrow.shallow_clone(), + }, + }, + }) + } + /// Access the non-hidden node inside in the wrapper. /// /// Return `None` if the wrapped node is "hidden". pub fn as_node(&self) -> Option<&N> { - self.result.as_ref().ok() + match self.inner { + HidingInner::Node(ref n) => Some(n), + HidingInner::Hidden { .. } => None, + } } /// Consume the wrapper and return the non-hidden node that was inside. /// /// Return `None` if the wrapped node is "hidden". - pub fn get_node(self) -> Option { - self.result.ok() + pub fn into_node(self) -> Option { + match self.inner { + HidingInner::Node(n) => Some(n), + HidingInner::Hidden { .. } => None, + } } } -impl Hiding<'_, N> { - /// Ensure that the wrapped node is "hidden". - /// Convert non-hidden nodes into "hidden" nodes with the same CMR. +impl<'brand, N: HasCmr + CoreConstructible<'brand>> Hiding<'brand, N> { + /// If neither node is hidden, apply a function to the underlying nodes to produce a new + /// non-hidden node. If either node is hidden, apply an alternate function to the CMRs + /// to produce a new CMR. + /// + /// Non-public since the API is kinda messy. + fn zip_ref>( + &self, + other: &Self, + node_zipfn: impl FnOnce(&N, &N) -> Result, + cmr_zipfn: impl FnOnce(Cmr, Cmr) -> Cmr, + ) -> Result, Error> { + Ok(Hiding { + inner: match (&self.inner, &other.inner) { + (HidingInner::Node(ref left), HidingInner::Node(ref right)) => { + node_zipfn(left, right).map(HidingInner::Node)? + } + _ => { + self.inference_context() + .check_eq(other.inference_context())?; + HidingInner::Hidden { + cmr: cmr_zipfn(self.cmr(), other.cmr()), + arrow: Arrow::hidden(self.inference_context()), + } + } + }, + }) + } + + /// Replace the node, if any, with its CMR; replace its type arrow with a new free arrow. + /// + /// Once hidden, a node's original type arrow loses its original bounds. In effect, a + /// hidden node is a completely separate node from its "original" node and is typechecked + /// and shared independently. pub fn hide(self) -> Self { - // # Soundness - // Hiding a node means converting it into its CMR. - // The node's type variables remain in the inference context. - // - // The type variables of a "hidden" child don't influence the construction of a parent, - // because merely the child's CMR is passed to the parent constructor. - // A CMR has no connection to any type variables. - // - // Hiding a node creates a CMR that is independent from the original node. - // The CMR can be used in one part of the program - // while the node itself is used in a different part. - match self.result { - Ok(node) => Self::hidden(node.cmr(), self.ctx), - Err(..) => self, + match self.inner { + HidingInner::Node(node) => Self { + inner: HidingInner::Hidden { + cmr: node.cmr(), + arrow: Arrow::hidden(node.inference_context()), + }, + }, + HidingInner::Hidden { .. } => self, } } } impl HasCmr for Hiding<'_, N> { fn cmr(&self) -> Cmr { - match &self.result { - Ok(node) => node.cmr(), - Err(cmr) => *cmr, + match self.inner { + HidingInner::Node(ref node) => node.cmr(), + HidingInner::Hidden { cmr, .. } => cmr, } } } -// We need `N: CoreConstructible` to access the inference context. -// Because of this, implementations of `{Jet, Disconnect, Witness}Constructible` -// for `Hiding` require `N: CoreConstructible`. -impl<'brand, N: CoreConstructible<'brand>> From for Hiding<'brand, N> { +impl<'brand, N> From for Hiding<'brand, N> { fn from(node: N) -> Self { Self { - ctx: node.inference_context().shallow_clone(), - result: Ok(node), + inner: HidingInner::Node(node), } } } // # Soundness // See [`Hiding::hide`]. -impl<'brand, N: CoreConstructible<'brand> + HasCmr> CoreConstructible<'brand> +impl<'brand, N: HasCmr + CoreConstructible<'brand>> CoreConstructible<'brand> for Hiding<'brand, N> { fn iden(inference_context: &Context<'brand>) -> Self { @@ -138,68 +188,59 @@ impl<'brand, N: CoreConstructible<'brand> + HasCmr> CoreConstructible<'brand> } fn injl(child: &Self) -> Self { - match &child.result { - Ok(child) => N::injl(child).into(), - Err(cmr) => child.hidden_cloned_ctx(Cmr::injl(*cmr)), - } + child.map_ref(N::injl, Cmr::injl) } fn injr(child: &Self) -> Self { - match &child.result { - Ok(child) => N::injr(child).into(), - Err(cmr) => child.hidden_cloned_ctx(Cmr::injr(*cmr)), - } + child.map_ref(N::injr, Cmr::injr) } fn take(child: &Self) -> Self { - match &child.result { - Ok(child) => N::take(child).into(), - Err(cmr) => child.hidden_cloned_ctx(Cmr::take(*cmr)), - } + child.map_ref(N::take, Cmr::take) } fn drop_(child: &Self) -> Self { - match &child.result { - Ok(child) => N::drop_(child).into(), - Err(cmr) => child.hidden_cloned_ctx(Cmr::drop(*cmr)), - } + child.map_ref(N::drop_, Cmr::drop) } fn comp(left: &Self, right: &Self) -> Result { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => N::comp(left, right).map(Self::from), - _ => Ok(left.hidden_cloned_ctx(Cmr::comp(left.cmr(), right.cmr()))), - } + left.zip_ref(right, N::comp, Cmr::comp) } fn case(left: &Self, right: &Self) -> Result { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => N::case(left, right).map(Self::from), - (Err(left), Ok(right)) => N::assertr(*left, right).map(Self::from), - (Ok(left), Err(right)) => N::assertl(left, *right).map(Self::from), - _ => Ok(left.hidden_cloned_ctx(Cmr::case(left.cmr(), right.cmr()))), - } + use HidingInner as I; + + left.inference_context() + .check_eq(right.inference_context())?; + let inner = match (&left.inner, &right.inner) { + (I::Node(left), I::Node(right)) => I::Node(N::case(left, right)?), + (I::Hidden { cmr, .. }, I::Node(right)) => I::Node(N::assertr(*cmr, right)?), + (I::Node(left), I::Hidden { cmr, .. }) => I::Node(N::assertl(left, *cmr)?), + (I::Hidden { cmr: l_cmr, .. }, I::Hidden { cmr: r_cmr, .. }) => I::Hidden { + cmr: Cmr::case(*l_cmr, *r_cmr), + arrow: Arrow::hidden(left.inference_context()), + }, + }; + + Ok(Self { inner }) } fn assertl(left: &Self, right: Cmr) -> Result { - match &left.result { - Ok(left) => N::assertl(left, right).map(Self::from), - _ => Ok(left.hidden_cloned_ctx(Cmr::case(left.cmr(), right))), - } + left.map_ref_result( + |left| N::assertl(left, right), + |lcmr| Cmr::case(lcmr, right), + ) } fn assertr(left: Cmr, right: &Self) -> Result { - match &right.result { - Ok(right) => N::assertr(left, right).map(Self::from), - _ => Ok(right.hidden_cloned_ctx(Cmr::case(left, right.cmr()))), - } + right.map_ref_result( + |right| N::assertr(left, right), + |rcmr| Cmr::case(left, rcmr), + ) } fn pair(left: &Self, right: &Self) -> Result { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => N::pair(left, right).map(Self::from), - _ => Ok(left.hidden_cloned_ctx(Cmr::pair(left.cmr(), right.cmr()))), - } + left.zip_ref(right, N::pair, Cmr::pair) } fn fail(inference_context: &Context<'brand>, entropy: FailEntropy) -> Self { @@ -215,7 +256,10 @@ impl<'brand, N: CoreConstructible<'brand> + HasCmr> CoreConstructible<'brand> } fn inference_context(&self) -> &Context<'brand> { - &self.ctx + match self.inner { + HidingInner::Node(ref node) => node.inference_context(), + HidingInner::Hidden { ref arrow, .. } => arrow.inference_context(), + } } } @@ -224,10 +268,7 @@ where N: DisconnectConstructible<'brand, Option> + CoreConstructible<'brand> + HasCmr, { fn disconnect(left: &Self, right: &Option) -> Result { - match &left.result { - Ok(left) => N::disconnect(left, right).map(Self::from), - Err(..) => Ok(left.hidden_cloned_ctx(Cmr::disconnect(left.cmr()))), - } + left.map_ref_result(|left| N::disconnect(left, right), Cmr::disconnect) } } diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index 3ea0d10a..534c5b5a 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -271,7 +271,7 @@ impl Policy { env: &ElementsEnv>, ) -> Result, SatisfierError> { let result = self.satisfy_internal(satisfier)?; - match result.get_node() { + match result.into_node() { Some(program) => program .finalize_unpruned() .expect("serialization should be sound") From 6a3561c766729e4f2eae642c51ca06925590a1c6 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 10 Aug 2026 20:49:00 +0000 Subject: [PATCH 8/8] node: add arrow() accessor to CoreConstructible We are going to move type inference out of the `pair`/`comp`/`case` combinator functions. This will let us bypass type inference in the new "type promise" API, which in turn will eliminate the PairBuilder API in SimplicityHL as well as a ton of unwraps/expect in "constant program constructors". --- src/merkle/cmr.rs | 43 ++++++++++++++++++++----------------------- src/node/construct.rs | 4 ++-- src/node/hiding.rs | 6 +++--- src/node/mod.rs | 10 +++++++--- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/merkle/cmr.rs b/src/merkle/cmr.rs index 1b5ca6b7..920aaeb8 100644 --- a/src/merkle/cmr.rs +++ b/src/merkle/cmr.rs @@ -4,7 +4,7 @@ use crate::jet::Jet; #[cfg(feature = "elements")] use crate::node::{CoreConstructible, DisconnectConstructible, WitnessConstructible}; #[cfg(feature = "elements")] -use crate::types::{self, Error}; +use crate::types::{self, Arrow, Error}; use crate::value::Word; use crate::{FailEntropy, Tmr}; use hashes::sha256::Midstate; @@ -259,7 +259,7 @@ impl Cmr { #[cfg(feature = "elements")] // only used by policy module pub struct ConstructibleCmr<'brand> { pub cmr: Cmr, - pub inference_context: types::Context<'brand>, + pub arrow: Arrow<'brand>, } #[cfg(feature = "elements")] // only used by policy module @@ -267,106 +267,103 @@ impl<'brand> CoreConstructible<'brand> for ConstructibleCmr<'brand> { fn iden(inference_context: &types::Context<'brand>) -> Self { ConstructibleCmr { cmr: Cmr::iden(), - inference_context: inference_context.shallow_clone(), + arrow: Arrow::iden(inference_context), } } fn unit(inference_context: &types::Context<'brand>) -> Self { ConstructibleCmr { cmr: Cmr::unit(), - inference_context: inference_context.shallow_clone(), + arrow: Arrow::unit(inference_context), } } fn injl(child: &Self) -> Self { ConstructibleCmr { cmr: Cmr::injl(child.cmr), - inference_context: child.inference_context.shallow_clone(), + arrow: Arrow::injl(child.arrow()), } } fn injr(child: &Self) -> Self { ConstructibleCmr { cmr: Cmr::injr(child.cmr), - inference_context: child.inference_context.shallow_clone(), + arrow: Arrow::injr(child.arrow()), } } fn take(child: &Self) -> Self { ConstructibleCmr { cmr: Cmr::take(child.cmr), - inference_context: child.inference_context.shallow_clone(), + arrow: Arrow::take(child.arrow()), } } fn drop_(child: &Self) -> Self { ConstructibleCmr { cmr: Cmr::drop(child.cmr), - inference_context: child.inference_context.shallow_clone(), + arrow: Arrow::drop_(child.arrow()), } } fn comp(left: &Self, right: &Self) -> Result { - left.inference_context.check_eq(&right.inference_context)?; Ok(ConstructibleCmr { cmr: Cmr::comp(left.cmr, right.cmr), - inference_context: left.inference_context.shallow_clone(), + arrow: Arrow::comp(left.arrow(), right.arrow())?, }) } fn case(left: &Self, right: &Self) -> Result { - left.inference_context.check_eq(&right.inference_context)?; Ok(ConstructibleCmr { cmr: Cmr::case(left.cmr, right.cmr), - inference_context: left.inference_context.shallow_clone(), + arrow: Arrow::case(left.arrow(), right.arrow())?, }) } fn assertl(left: &Self, right: Cmr) -> Result { Ok(ConstructibleCmr { cmr: Cmr::case(left.cmr, right), - inference_context: left.inference_context.shallow_clone(), + arrow: Arrow::assertl(left.arrow())?, }) } fn assertr(left: Cmr, right: &Self) -> Result { Ok(ConstructibleCmr { cmr: Cmr::case(left, right.cmr), - inference_context: right.inference_context.shallow_clone(), + arrow: Arrow::assertr(right.arrow())?, }) } fn pair(left: &Self, right: &Self) -> Result { - left.inference_context.check_eq(&right.inference_context)?; Ok(ConstructibleCmr { cmr: Cmr::pair(left.cmr, right.cmr), - inference_context: left.inference_context.shallow_clone(), + arrow: Arrow::pair(left.arrow(), right.arrow())?, }) } fn fail(inference_context: &types::Context<'brand>, entropy: FailEntropy) -> Self { ConstructibleCmr { cmr: Cmr::fail(entropy), - inference_context: inference_context.shallow_clone(), + arrow: Arrow::fail(inference_context), } } fn const_word(inference_context: &types::Context<'brand>, word: Word) -> Self { ConstructibleCmr { cmr: Cmr::const_word(&word), - inference_context: inference_context.shallow_clone(), + arrow: Arrow::const_word(inference_context, &word), } } fn jet(inference_context: &types::Context<'brand>, jet: &dyn Jet) -> Self { ConstructibleCmr { cmr: jet.cmr(), - inference_context: inference_context.shallow_clone(), + arrow: Arrow::jet(inference_context, jet), } } - fn inference_context(&self) -> &types::Context<'brand> { - &self.inference_context + fn arrow(&self) -> &Arrow<'brand> { + &self.arrow } } @@ -379,7 +376,7 @@ impl<'brand, X> DisconnectConstructible<'brand, X> for ConstructibleCmr<'brand> fn disconnect(left: &Self, _right: &X) -> Result { Ok(ConstructibleCmr { cmr: Cmr::disconnect(left.cmr), - inference_context: left.inference_context.shallow_clone(), + arrow: left.arrow.shallow_clone(), }) } } @@ -388,8 +385,8 @@ impl<'brand, X> DisconnectConstructible<'brand, X> for ConstructibleCmr<'brand> impl<'brand, W> WitnessConstructible<'brand, W> for ConstructibleCmr<'brand> { fn witness(inference_context: &types::Context<'brand>, _witness: W) -> Self { ConstructibleCmr { + arrow: Arrow::witness(inference_context), cmr: Cmr::witness(), - inference_context: inference_context.shallow_clone(), } } } diff --git a/src/node/construct.rs b/src/node/construct.rs index 5a77ed16..9895cb3e 100644 --- a/src/node/construct.rs +++ b/src/node/construct.rs @@ -366,8 +366,8 @@ impl<'brand> CoreConstructible<'brand> for ConstructData<'brand> { } } - fn inference_context(&self) -> &types::Context<'brand> { - self.arrow.inference_context() + fn arrow(&self) -> &Arrow<'brand> { + &self.arrow } } diff --git a/src/node/hiding.rs b/src/node/hiding.rs index c537ffc1..12c3697f 100644 --- a/src/node/hiding.rs +++ b/src/node/hiding.rs @@ -255,10 +255,10 @@ impl<'brand, N: HasCmr + CoreConstructible<'brand>> CoreConstructible<'brand> N::jet(inference_context, jet).into() } - fn inference_context(&self) -> &Context<'brand> { + fn arrow(&self) -> &Arrow<'brand> { match self.inner { - HidingInner::Node(ref node) => node.inference_context(), - HidingInner::Hidden { ref arrow, .. } => arrow.inference_context(), + HidingInner::Node(ref node) => node.arrow(), + HidingInner::Hidden { ref arrow, .. } => arrow, } } } diff --git a/src/node/mod.rs b/src/node/mod.rs index 7f6ac600..4f7dba76 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -192,7 +192,11 @@ pub trait CoreConstructible<'brand>: Sized { fn jet(inference_context: &types::Context<'brand>, jet: &dyn Jet) -> Self; /// Accessor for the type inference context used to create the object. - fn inference_context(&self) -> &types::Context<'brand>; + #[inline] + fn inference_context(&self) -> &types::Context<'brand> { + &self.arrow().inference_context + } + fn arrow(&self) -> &types::Arrow<'brand>; /// Create an expression that produces the given `value`. /// @@ -550,8 +554,8 @@ where }) } - fn inference_context(&self) -> &types::Context<'brand> { - self.data.inference_context() + fn arrow(&self) -> &types::Arrow<'brand> { + self.data.arrow() } }