From 70f01520e57102ef9914225bc8c302dc0fa7c603 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 19:31:08 +0000 Subject: [PATCH 1/8] str: improve "string wrapper" macro Changes the `as_inner` method to return an `&Arc` rather than an `&str`, which makes more sense given the name, and is also useful in a couple of places. Then * Add `PartialEq` between the wrapper types and `str`, eliminating a ton of .as_inner calls entirely. * Add a `from_ident` method which converts an `Identifier` into the type, which is a common operation throughout the codebase to "strongly type" an identifier. * Add a `from_str` method to cover the remaining cases where we really do need an explicit &str --- src/ast.rs | 34 +++++++++++++++++----------------- src/driver/resolve_order.rs | 31 ++++++++++++++----------------- src/parse.rs | 30 +++++++++++++++--------------- src/resolution.rs | 2 +- src/serde.rs | 4 ++-- src/str.rs | 34 +++++++++++++++++++++++++++++----- src/tracker.rs | 2 +- src/types.rs | 2 +- 8 files changed, 80 insertions(+), 59 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index e3a084cb..47c3d2f1 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -878,7 +878,7 @@ impl Scope { /// * May also return errors propagated from item collection and insertion, such as [`Error::PrivateItem`] or [`Error::RedefinedItem`]. pub fn resolve_use(&mut self, use_decl: &UseDecl) -> Result<(), Error> { let path = use_decl.path(); - if path.first().map(|id| id.as_inner()) != Some(CRATE_STR) { + if path.first().map(|id| id.as_str()) != Some(CRATE_STR) { return Err(Error::MissingCrateKeyword); } @@ -898,13 +898,13 @@ impl Scope { .module_path .iter() .zip(&path[1..]) - .take_while(|(curr, nav)| curr.as_inner() == nav.as_inner()) + .take_while(|(curr, nav)| curr.as_str() == nav.as_str()) .count(); let mut target_scope = &self.root; for (ind, segment) in path[1..].iter().enumerate() { - let name = ModuleName::from_str_unchecked(segment.as_inner()); + let name = ModuleName::from_ident(segment); let (inner, visibility) = target_scope .submodules @@ -920,7 +920,7 @@ impl Scope { let mut collected = Vec::with_capacity(use_decl_items.len()); for (name, aliased) in use_decl_items { - if aliased.as_ref().is_some_and(|a| a.as_inner() == MAIN_STR) { + if aliased.as_ref().is_some_and(|a| a == MAIN_STR) { return Err(Error::MainCannotBeAlias); } @@ -1127,7 +1127,7 @@ impl Scope { ) -> Result<(), Error> { self.check_alias_free(&name)?; - let info = EnumInfo::new(Arc::from(name.as_inner()), variants); + let info = EnumInfo::new(Arc::clone(name.as_inner()), variants); let resolved = ResolvedType::enumeration(info); self.current_module_mut() @@ -1402,7 +1402,7 @@ impl AbstractSyntaxTree for Function { "Variables live only inside the function" ); - if from.name().as_inner() != MAIN_STR { + if from.name() != MAIN_STR { let params = from .params() .iter() @@ -1554,7 +1554,7 @@ fn analyze_enum_construction( let written = construction.enum_path_string(); let names_expected_enum = match construction.enum_path() { [single] => { - let alias = AliasName::from_str_unchecked(single.as_inner()); + let alias = AliasName::from_ident(single); match scope.get_alias(&alias) { Ok(resolved) if &resolved == ty => true, Ok(resolved) => { @@ -1578,7 +1578,7 @@ fn analyze_enum_construction( let (variant_index, variant) = info .variant(construction.variant()) - .ok_or_else(|| enum_variant_error(construction.variant().as_inner(), info)) + .ok_or_else(|| enum_variant_error(construction.variant().as_str(), info)) .with_span(span)?; if construction.args().len() != variant.payload().len() { return Err(Error::Grammar { @@ -1904,7 +1904,7 @@ impl AbstractSyntaxTree for EnumMatch { }) .with_span(span); }; - let alias = AliasName::from_str_unchecked(single.as_inner()); + let alias = AliasName::from_ident(single); let enum_ty = scope.get_alias(&alias).with_span(span)?; let info = match enum_ty.as_enum() { Some(info) => info.clone(), @@ -2977,9 +2977,9 @@ mod module_tests { "main.simf", " pub fn global_func() {} - mod inner { - use crate::global_func; - pub fn call_it() { global_func(); } + mod inner { + use crate::global_func; + pub fn call_it() { global_func(); } } fn main() {} ", @@ -3021,11 +3021,11 @@ mod module_tests { let result = analyze_multifile(vec![( "main.simf", " - mod brother { + mod brother { fn secret_toy() {} // Missing 'pub' } - mod sister { - use crate::brother::secret_toy; + mod sister { + use crate::brother::secret_toy; } fn main() {} ", @@ -3041,8 +3041,8 @@ mod module_tests { let result = analyze_multifile(vec![( "main.simf", " - mod child { - fn hidden() {} + mod child { + fn hidden() {} } use crate::child::hidden; fn main() {} diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index 32ec6c1b..e2e0f309 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -22,7 +22,7 @@ fn enum_declarations(items: &[parse::Item]) -> Vec<&parse::EnumDeclaration> { /// Enums by design are nominative, therefore to reason about same named enums in different modules /// we have to have a stable ABI with the suport of "qualified name". /// Currently, there is no support of "qualified name" concpet, therefore at the time of creating -/// enums, it is forbidden to decler them in dependencies. +/// enums, it is forbidden to decler them in dependencies. /// /// If we used current ABI we would face following problems: /// 1. Adding or removing an unrelated dependency renumbers the files, so the same enum's ABI @@ -102,9 +102,9 @@ impl DependencyGraph { .collect(); if source_id == MAIN_MODULE { - let has_main = local_items.iter().any(|item| { - matches!(item, parse::Item::Function(f) if f.name().as_inner() == MAIN_STR) - }); + let has_main = local_items + .iter() + .any(|item| matches!(item, parse::Item::Function(f) if f.name() == MAIN_STR)); if !has_main { diagnostics.push(Diagnostic::global(Error::CannotParse { @@ -121,7 +121,7 @@ impl DependencyGraph { // flattening an enum program produces source that no longer // re-parses (`TemplateProgram::flatten`). Splice the entry // file's items at the root instead of wrapping them. - let name = ModuleName::from_str_unchecked(Self::get_module_name(source_id).as_inner()); + let name = ModuleName::from_ident(&Self::get_module_name(source_id)); items.push(parse::Item::Module(parse::Module::new( source_id, Visibility::Private, @@ -233,7 +233,7 @@ mod flattening_tests { .iter() .find_map(|item| { if let parse::Item::Module(m) = item { - if m.name().as_inner() == expected_mod_name.as_str() { + if *m.name() == *expected_mod_name { return Some(m); } } @@ -246,9 +246,10 @@ mod flattening_tests { "The file wrapper module must be strictly private" ); - let has_dep_func = wrapped_module.items().iter().any( - |item| matches!(item, parse::Item::Function(f) if f.name().as_inner() == "dep_func"), - ); + let has_dep_func = wrapped_module + .items() + .iter() + .any(|item| matches!(item, parse::Item::Function(f) if f.name() == "dep_func")); assert!( has_dep_func, "The file_N module must contain the dependency's items" @@ -295,14 +296,10 @@ mod flattening_tests { path.len() >= 2, "Rewritten path must have at least 2 segments" ); + assert_eq!(path[0], *CRATE_STR, "Path must start with `crate`"); assert_eq!( - path[0].as_inner(), - CRATE_STR, - "Path must start with `crate`" - ); - assert_eq!( - path[1].as_inner(), - expected_file_segment.as_str(), + path[1], + *expected_file_segment.as_str(), "Path must route through the canonical `unit_N`" ); } @@ -367,7 +364,7 @@ mod dependency_map_tests { "main.simf", " mod brother { pub fn toy() {} } - use crate::brother::toy; + use crate::brother::toy; fn main() {} ", )]); diff --git a/src/parse.rs b/src/parse.rs index 547af341..5e019ad7 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -254,7 +254,7 @@ impl UseDecl { } pub fn str_path(&self) -> String { - let path: PathBuf = self.path().iter().map(|iden| iden.as_inner()).collect(); + let path: PathBuf = self.path().iter().map(Identifier::as_str).collect(); path.display().to_string() } @@ -264,7 +264,7 @@ impl UseDecl { /// /// Returns a `Diagnostic` if the use declaration path is completely empty. pub fn drp_name(&self) -> Result<&str, Diagnostic> { - let parts: Vec<&str> = self.path().iter().map(|iden| iden.as_inner()).collect(); + let parts: Vec<&str> = self.path().iter().map(Identifier::as_str).collect(); parts.first().copied().ok_or_else(|| { Error::CannotParse { msg: "Empty use path".to_string(), @@ -970,7 +970,7 @@ impl EnumMatchArm { pub fn enum_path_string(&self) -> String { self.enum_path .iter() - .map(Identifier::as_inner) + .map(Identifier::as_str) .collect::>() .join("::") } @@ -1056,7 +1056,7 @@ impl EnumConstruction { pub fn enum_path_string(&self) -> String { self.enum_path .iter() - .map(Identifier::as_inner) + .map(Identifier::as_str) .collect::>() .join("::") } @@ -2477,7 +2477,7 @@ impl ChumskyParse for TypeAlias { let name = AliasName::parser() .validate(|name, e, emit| { - let ident = name.as_inner(); + let ident = name.as_str(); let known_type = if ident == "bool" { Some(AliasedType::boolean()) } else if let Ok(uint_type) = UIntType::from_str(ident) { @@ -2529,7 +2529,7 @@ impl ChumskyParse for EnumDeclaration { .map(Option::unwrap_or_default); let name = AliasName::parser().try_map(|name, span| { - if RESERVED_PATTERN_NAMES.contains(&name.as_inner()) { + if RESERVED_PATTERN_NAMES.contains(&name.as_str()) { return Err(Diagnostic::new( Error::Grammar { msg: format!( @@ -2545,7 +2545,7 @@ impl ChumskyParse for EnumDeclaration { // constructions name the enum while type annotations resolve // to the builtin, and the ABI would report the bare name // ambiguously. - if crate::str::is_reserved_alias_name(name.as_inner()) { + if crate::str::is_reserved_alias_name(name.as_str()) { return Err(Diagnostic::new( Error::RedefinedAliasAsBuiltin { name: name.clone() }, span, @@ -3820,10 +3820,10 @@ fn main() { let Item::EnumDeclaration(decl) = item else { panic!("expected EnumDeclaration, got {item:?}"); }; - assert_eq!(decl.name().as_inner(), "Path"); + assert_eq!(decl.name(), "Path"); assert_eq!(decl.variants().len(), 3); - assert_eq!(decl.variants()[0].name().as_inner(), "Inherit"); - assert_eq!(decl.variants()[2].name().as_inner(), "RefreshSpend"); + assert_eq!(decl.variants()[0].name(), "Inherit"); + assert_eq!(decl.variants()[2].name(), "RefreshSpend"); } #[test] @@ -3833,7 +3833,7 @@ fn main() { panic!("expected EnumDeclaration"); }; assert_eq!(decl.visibility(), &Visibility::Public); - assert_eq!(decl.name().as_inner(), "Color"); + assert_eq!(decl.name(), "Color"); } #[test] @@ -4177,10 +4177,10 @@ fn main() { let Item::EnumDeclaration(decl) = item else { panic!("expected EnumDeclaration, got {item:?}"); }; - assert_eq!(decl.name().as_inner(), "Path"); + assert_eq!(decl.name(), "Path"); assert_eq!(decl.variants().len(), 3); - assert_eq!(decl.variants()[0].name().as_inner(), "Inherit"); - assert_eq!(decl.variants()[2].name().as_inner(), "RefreshSpend"); + assert_eq!(decl.variants()[0].name(), "Inherit"); + assert_eq!(decl.variants()[2].name(), "RefreshSpend"); } #[test] @@ -4190,7 +4190,7 @@ fn main() { panic!("expected EnumDeclaration"); }; assert_eq!(decl.visibility(), &Visibility::Public); - assert_eq!(decl.name().as_inner(), "Color"); + assert_eq!(decl.name(), "Color"); } #[test] diff --git a/src/resolution.rs b/src/resolution.rs index 470f2110..9dae947f 100644 --- a/src/resolution.rs +++ b/src/resolution.rs @@ -388,7 +388,7 @@ impl DependencyMap { let mut iter = module_parts.iter(); while let Some(part) = iter.next() { - let joined = path.join(part.as_inner()); + let joined = path.join(part.as_str()); if joined.is_dir() { path = joined; continue; diff --git a/src/serde.rs b/src/serde.rs index 99bdc8f5..e56d18df 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -282,10 +282,10 @@ impl<'a> Serialize for WitnessMapSerializer<'a> { // TODO: Consider serializing every value as a bare string and retiring the { value, type } form. // That drops "witness file readable without the program" entirely. if value.ty().contains_enum() { - map.serialize_entry(name.as_inner(), &value.to_string())?; + map.serialize_entry(name.as_str(), &value.to_string())?; continue; } - map.serialize_entry(name.as_inner(), &ValueMapSerializer(value))?; + map.serialize_entry(name.as_str(), &ValueMapSerializer(value))?; } map.end() } diff --git a/src/str.rs b/src/str.rs index 1fb95cc6..78b10c0d 100644 --- a/src/str.rs +++ b/src/str.rs @@ -22,7 +22,12 @@ macro_rules! wrapped_string { } /// Access the inner string. - pub fn as_inner(&self) -> &str { + pub fn as_inner(&self) -> &Arc { + &self.0 + } + + /// Access the inner string. + pub fn as_str(&self) -> &str { self.0.as_ref() } @@ -30,17 +35,36 @@ macro_rules! wrapped_string { pub fn shallow_clone(&self) -> Self { Self(Arc::clone(&self.0)) } + + #[doc = "Creates a "] + #[doc = $name] + #[doc = " from an identifier.\n"] + pub fn from_ident(ident: &Identifier) -> Self { + Self(Arc::clone(ident.as_inner())) + } + } + + impl core::cmp::PartialEq for $wrapper { + fn eq(&self, other: &str) -> bool { + self.0.as_ref() == other + } + } + + impl core::cmp::PartialEq<$wrapper> for str { + fn eq(&self, other: &$wrapper) -> bool { + self == other.0.as_ref() + } } - impl std::fmt::Display for $wrapper { + impl core::fmt::Display for $wrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) + self.0.fmt(f) } } - impl std::fmt::Debug for $wrapper { + impl core::fmt::Debug for $wrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) + self.0.fmt(f) } } }; diff --git a/src/tracker.rs b/src/tracker.rs index bd463e04..c6b66b92 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -97,7 +97,7 @@ impl<'a> DefaultTracker<'a> { /// /// This constructor is deprecated in favor of more flexible tracker setup. /// The deprecation is necessary to show the direction in which the SimplicityHL is moving - /// (i.e. different targets and support for possible custom Jets) + /// (i.e. different targets and support for possible custom Jets) #[deprecated(since = "0.6.0", note = "Please use `build` instead")] pub fn new(debug_symbols: &'a DebugSymbols) -> Self { Self::build(debug_symbols, Box::new(ElementsJetHinter::new())) diff --git a/src/types.rs b/src/types.rs index b2d67f67..09451c2f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1343,6 +1343,6 @@ mod tests { .variant(&Identifier::from_str_unchecked("Pair")) .expect("Pair is a declared variant"); assert_eq!(2, index); - assert_eq!("Pair", variant.name().as_inner()); + assert_eq!("Pair", variant.name()); } } From 4a9e1488a2e3c4b3f9d34b7a7b633af0b3b7935a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 18:58:02 +0000 Subject: [PATCH 2/8] serde: break up parsing of witnessname/argument hashmaps The existing code has an interesting sort of type confusion: we parse Arguments, UnresolvedValues, and WitnessValues by means of a ParserVisitor which calls into chumsky to parse the key. For each of these cases, we expect an identifier, which depending on the map, may reference either a parameter or a witness. In both cases, we put them into the WitnessName type, which covers both but does not distinguish between them. To achieve this, in src/parse.rs we implement ChumskyParse on WitnessName, by parsing an identifier and then interpreting it as a WitnessName. (We never use this codepath when parsing actual programs; in actual programs identifiers are just Identifiers until a later resolution stage; this ChumsyParse impl is only used as a helper for serde-deserialization of .args and .wit files.) (In programs, witnesses and parameters must be prefixed with `witness::` or `param::`, and these have their own parsing path.) *However*, in the following commits, we will update WitnessNames so that it distinguishes between parameters and witnesses, because we are going to delay parameter resolution until after compilation. Once we do this, the ChumskyParse impl for WitnessName will no longer be tenable, because it needs to construct a WitnessName but it doesn't know whether the identifier it's converting is supposed to be a parameter or a witness name. This commit simply refactors code and does not change any data structures or behavior. --- src/parse.rs | 1 - src/serde.rs | 88 +++++++++++++++++++++++++--------------------------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index 5e019ad7..70d867b0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1658,7 +1658,6 @@ macro_rules! impl_parse_wrapped_string { impl_parse_wrapped_string!(SymbolName, "unresolved symbol name"); impl_parse_wrapped_string!(FunctionName, "function name"); impl_parse_wrapped_string!(Identifier, "identifier"); -impl_parse_wrapped_string!(WitnessName, "witness name"); impl_parse_wrapped_string!(AliasName, "alias name"); impl_parse_wrapped_string!(ModuleName, "module name"); diff --git a/src/serde.rs b/src/serde.rs index e56d18df..6b87031c 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,25 +1,33 @@ +use core::hash::Hash; use std::collections::HashMap; use std::fmt; +use std::marker::PhantomData; -use crate::parse::ParseFromStr; -use crate::str::WitnessName; +use crate::parse::ParseFromStr as _; +use crate::str::{Identifier, WitnessName}; use crate::types::ResolvedType; use crate::value::Value; use crate::witness::{Arguments, UnresolvedValue, UnresolvedValues, WitnessValues}; use crate::{AbiMeta, Parameters, WitnessTypes}; use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; -/// Visitor for a map from witness names to values of type `V`, rejecting duplicate names. -struct NamedMapVisitor(std::marker::PhantomData); +/// Visitor for a map from identifiers to values of type `V`, rejecting duplicate names. +struct NamedMapVisitor { + key_map_fn: fn(&Identifier) -> K, + phantom: PhantomData, +} -impl NamedMapVisitor { - const fn new() -> Self { - Self(std::marker::PhantomData) +impl NamedMapVisitor { + const fn new(key_map_fn: fn(&Identifier) -> K) -> Self { + Self { + key_map_fn, + phantom: PhantomData, + } } } -impl<'de, V: Deserialize<'de>> de::Visitor<'de> for NamedMapVisitor { - type Value = HashMap; +impl<'de, K: Eq + Hash, V: Deserialize<'de>> de::Visitor<'de> for NamedMapVisitor { + type Value = HashMap; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map with string keys") @@ -30,8 +38,8 @@ impl<'de, V: Deserialize<'de>> de::Visitor<'de> for NamedMapVisitor { M: de::MapAccess<'de>, { let mut map = HashMap::new(); - while let Some((key, value)) = access.next_entry::()? { - if map.insert(key.shallow_clone(), value).is_some() { + while let Some((key, value)) = access.next_entry::()? { + if map.insert((self.key_map_fn)(&key), value).is_some() { return Err(de::Error::custom(format!("Name `{key}` is assigned twice"))); } } @@ -45,7 +53,7 @@ impl<'de> Deserialize<'de> for WitnessValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::::new()) + .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) .map(Self::from) } } @@ -93,7 +101,7 @@ impl<'de> Deserialize<'de> for UnresolvedValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::::new()) + .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) .map(Self::from_map) } } @@ -109,15 +117,6 @@ impl Serialize for ResolvedType { } } -impl Serialize for WitnessName { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.as_ref()) - } -} - impl Serialize for AbiMeta { fn serialize(&self, serializer: S) -> Result where @@ -140,7 +139,7 @@ impl Serialize for Parameters { let map_ref = self.as_ref(); let mut map = serializer.serialize_map(Some(map_ref.len()))?; for (key, value) in map_ref { - map.serialize_entry(key, value)?; + map.serialize_entry(key.as_str(), value)?; } map.end() } @@ -154,7 +153,7 @@ impl Serialize for WitnessTypes { let map_ref = self.as_ref(); let mut map = serializer.serialize_map(Some(map_ref.len()))?; for (key, value) in map_ref { - map.serialize_entry(key, value)?; + map.serialize_entry(key.as_str(), value)?; } map.end() } @@ -166,7 +165,7 @@ impl<'de> Deserialize<'de> for Arguments { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::::new()) + .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) .map(Self::from) } } @@ -239,29 +238,28 @@ impl<'de> Deserialize<'de> for Value { } } -struct ParserVisitor(std::marker::PhantomData); - -impl<'de, A: ParseFromStr> de::Visitor<'de> for ParserVisitor { - type Value = A; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a valid string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - A::parse_from_str(value).map_err(E::custom) - } -} - -impl<'de> Deserialize<'de> for WitnessName { +impl<'de> Deserialize<'de> for Identifier { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - deserializer.deserialize_str(ParserVisitor::(std::marker::PhantomData)) + struct ParserVisitor; + impl<'de> de::Visitor<'de> for ParserVisitor { + type Value = Identifier; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + crate::parse::ParseFromStr::parse_from_str(value).map_err(E::custom) + } + } + + deserializer.deserialize_str(ParserVisitor) } } @@ -427,7 +425,7 @@ mod tests { use crate::value::ValueConstructible; use std::sync::Arc; - let u32_ty: ResolvedType = ParseFromStr::parse_from_str("u32").unwrap(); + let u32_ty = ResolvedType::parse_from_str("u32").unwrap(); let variants: Arc<[EnumVariantInfo]> = Arc::from([ EnumVariantInfo::new(Identifier::from_str_unchecked("Cold"), Arc::from([])), EnumVariantInfo::new( From 54857317b9c56693b8219fdc405a6ac699ae7720 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 17:07:31 +0000 Subject: [PATCH 3/8] rename TemplateProgram to TemplateAst Our "template program" is really a "templated SimplicityHL AST". I'd like to introduce a new "templated Simplicity Program" for which the name TemplateProgram would be a better fit. So rename this out of the way. This commit can be reproduced with find bitcoind-tests external-jet-lib-example src fuzz tests \ -type f \ \( -name '*.rs' -o -name '*.md' \) \ -exec sed -i s/TemplateProgram/TemplateAst/g {} \; --- bitcoind-tests/tests/common/test.rs | 4 +- external-jet-lib-example/README.md | 6 +-- external-jet-lib-example/src/main.rs | 4 +- fuzz/fuzz_targets/compile_text.rs | 12 ++--- src/ast.rs | 8 +-- src/driver/resolve_order.rs | 2 +- src/error.rs | 2 +- src/lib.rs | 76 +++++++++++++--------------- src/main.rs | 4 +- src/tracker.rs | 10 ++-- tests/core_tracker.rs | 4 +- 11 files changed, 62 insertions(+), 70 deletions(-) diff --git a/bitcoind-tests/tests/common/test.rs b/bitcoind-tests/tests/common/test.rs index da4143a4..b2ba0856 100644 --- a/bitcoind-tests/tests/common/test.rs +++ b/bitcoind-tests/tests/common/test.rs @@ -15,7 +15,7 @@ type FnWitness = fn([u8; 32]) -> simplicityhl::WitnessValues; pub struct TestCase<'a> { pub name: &'static str, - template: Option, + template: Option, compiled: Option, witness: FnWitness, lock_time: elements::LockTime, @@ -67,7 +67,7 @@ impl<'a> TestCase<'a> { pub fn template_path>(mut self, path: P) -> Self { let text = std::fs::read_to_string(path).expect("path should be readable"); let template = - simplicityhl::TemplateProgram::new(text.as_str(), Box::new(ElementsJetHinter::new())) + simplicityhl::TemplateAst::new(text.as_str(), Box::new(ElementsJetHinter::new())) .expect("program should compile"); self.template = Some(template); self diff --git a/external-jet-lib-example/README.md b/external-jet-lib-example/README.md index b2055d7e..a9b34559 100644 --- a/external-jet-lib-example/README.md +++ b/external-jet-lib-example/README.md @@ -30,12 +30,12 @@ unsafe { ### 4. Pass `ExternalJetHinter` to the compiler -`ExternalJetHinter` implements `JetHinter` and delegates `parse_jet` / `construct_verify` to the loaded library. Pass it when constructing a `TemplateProgram`: +`ExternalJetHinter` implements `JetHinter` and delegates `parse_jet` / `construct_verify` to the loaded library. Pass it when constructing a `TemplateAst`: ```rust -use simplicityhl::{jet::external::ExternalJetHinter, TemplateProgram}; +use simplicityhl::{jet::external::ExternalJetHinter, TemplateAst}; -let program = TemplateProgram::new(simf_code, Box::new(ExternalJetHinter::new())) +let program = TemplateAst::new(simf_code, Box::new(ExternalJetHinter::new())) .expect("compilation failed"); ``` diff --git a/external-jet-lib-example/src/main.rs b/external-jet-lib-example/src/main.rs index c4389271..1ff0eab3 100644 --- a/external-jet-lib-example/src/main.rs +++ b/external-jet-lib-example/src/main.rs @@ -19,7 +19,7 @@ //! loads and runs native code from the given path. Only point it at libraries //! you trust. -use simplicityhl::{jet::external::ExternalJetHinter, TemplateProgram}; +use simplicityhl::{jet::external::ExternalJetHinter, TemplateAst}; /// Loads the external jet library named on the command line and compiles a tiny /// SimplicityHL program against it. @@ -45,7 +45,7 @@ fn main() { // (`parse_jet`, `construct_verify`, `conjure`) to the loaded library; here // `assert!(true)` is lowered via `construct_verify` to the library's // `verify` jet. - let _ = TemplateProgram::new(code, Box::new(ExternalJetHinter::new())) + let _ = TemplateAst::new(code, Box::new(ExternalJetHinter::new())) .expect("failed to compile code with external jets"); println!("External jets were successfully used to compile:\n{}", code); diff --git a/fuzz/fuzz_targets/compile_text.rs b/fuzz/fuzz_targets/compile_text.rs index adcac6c6..89aed900 100644 --- a/fuzz/fuzz_targets/compile_text.rs +++ b/fuzz/fuzz_targets/compile_text.rs @@ -40,13 +40,11 @@ fn do_test(data: &[u8]) -> libfuzzer_sys::Corpus { if slow_input(&program_text) { return Corpus::Reject; } - let template = match simplicityhl::TemplateProgram::new( - program_text, - Box::new(ElementsJetHinter::new()), - ) { - Ok(x) => x, - Err(..) => return Corpus::Keep, - }; + let template = + match simplicityhl::TemplateAst::new(program_text, Box::new(ElementsJetHinter::new())) { + Ok(x) => x, + Err(..) => return Corpus::Keep, + }; let arguments = match Arguments::arbitrary_of_type(&mut u, template.parameters()) { Ok(arguments) => arguments, Err(..) => return Corpus::Reject, diff --git a/src/ast.rs b/src/ast.rs index 47c3d2f1..03e4e36a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -3080,10 +3080,10 @@ mod module_tests { #[cfg(test)] mod enum_tests { use crate::ast::ElementsJetHinter; - use crate::{TemplateProgram, UnstableFeatures}; + use crate::{TemplateAst, UnstableFeatures}; fn analyze(src: &str) -> Result<(), String> { - TemplateProgram::new_with_unstable( + TemplateAst::new_with_unstable( src, &UnstableFeatures::all(), Box::new(ElementsJetHinter::new()), @@ -3541,7 +3541,7 @@ mod enum_tests { fn alias_named_after_pattern_stays_valid_without_enums() { // Stable programs may alias pattern names; the enums feature must // not retroactively reject them. - let result = TemplateProgram::new_with_unstable( + let result = TemplateAst::new_with_unstable( "type Left = u32;\nfn main() { let _x: Left = 1; }", &UnstableFeatures::none(), Box::new(ElementsJetHinter::new()), @@ -3596,7 +3596,7 @@ mod enum_tests { #[test] fn enum_requires_unstable_feature() { - let result = TemplateProgram::new_with_unstable( + let result = TemplateAst::new_with_unstable( "enum Color { Red, Green }\nfn main() {}", &UnstableFeatures::none(), Box::new(ElementsJetHinter::new()), diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index e2e0f309..31228798 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -119,7 +119,7 @@ impl DependencyGraph { // entry file included — in a generated module, but enum // declarations are only valid at the top level of a file, so // flattening an enum program produces source that no longer - // re-parses (`TemplateProgram::flatten`). Splice the entry + // re-parses (`TemplateAst::flatten`). Splice the entry // file's items at the root instead of wrapping them. let name = ModuleName::from_ident(&Self::get_module_name(source_id)); items.push(parse::Item::Module(parse::Module::new( diff --git a/src/error.rs b/src/error.rs index 5384ca5e..80cf2d2f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -449,7 +449,7 @@ impl DiagnosticManager { // // The only caller that hits this branch is the legacy one-file program // flow, which bypasses the driver. All modern paths (LSP, `simc`, - // Simplex, and the Web build via `TemplateProgram::flatten`) register + // Simplex, and the Web build via `TemplateAst::flatten`) register // sources with the driver and hit the `RenderCache`-based render below. // // Legacy callers get message-only output — no source snippets, no diff --git a/src/lib.rs b/src/lib.rs index eea90535..ca8cb904 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; /// /// A template has parameterized values that need to be supplied with arguments. #[derive(Debug)] -pub struct TemplateProgram { +pub struct TemplateAst { simfony: ast::Program, file: Arc, jet_hinter: Box, @@ -64,7 +64,7 @@ pub struct TemplateProgram { resolved_program: parse::Program, } -impl TemplateProgram { +impl TemplateAst { /// Parses and flattens a multi-file program into a single enriched [`parse::Program`] /// with all imports resolved and each file wrapped in a `unit_N` module. /// @@ -196,7 +196,7 @@ impl TemplateProgram { /// ## Errors /// /// The arguments are not consistent with the parameters of the program. - /// Use [`TemplateProgram::parameters`] to see which parameters the program has. + /// Use [`TemplateAst::parameters`] to see which parameters the program has. pub fn instantiate( &self, arguments: Arguments, @@ -259,8 +259,8 @@ impl CompiledProgram { /// /// ## See /// - /// - [`TemplateProgram::new_with_dep`] - /// - [`TemplateProgram::instantiate`] + /// - [`TemplateAst::new_with_dep`] + /// - [`TemplateAst::instantiate`] pub fn new_with_dep( source: CanonSourceFile, dependency_map: &DependencyMap, @@ -269,7 +269,7 @@ impl CompiledProgram { include_debug_symbols: bool, jet_hinter: Box, ) -> Result { - TemplateProgram::new_with_dep( + TemplateAst::new_with_dep( source, dependency_map, unstable_features, @@ -283,8 +283,8 @@ impl CompiledProgram { /// /// ## See /// - /// - [`TemplateProgram::new`] - /// - [`TemplateProgram::instantiate`] + /// - [`TemplateAst::new`] + /// - [`TemplateAst::instantiate`] pub fn new>>( s: Str, arguments: Arguments, @@ -309,7 +309,7 @@ impl CompiledProgram { include_debug_symbols: bool, jet_hinter: Box, ) -> Result { - TemplateProgram::new_with_unstable(s, unstable_features, jet_hinter.clone_box()) + TemplateAst::new_with_unstable(s, unstable_features, jet_hinter.clone_box()) .map_err(|error| error.to_string()) .and_then(|template| template.instantiate(arguments, include_debug_symbols)) } @@ -325,7 +325,7 @@ impl CompiledProgram { } /// The version of the compiler that produced this program — this crate's version. - /// See [`TemplateProgram::compiler_version`]. + /// See [`TemplateAst::compiler_version`]. pub fn compiler_version(&self) -> &'static str { version::SimcDirective::current_version() } @@ -415,8 +415,8 @@ impl SatisfiedProgram { /// /// ## See /// - /// - [`TemplateProgram::new`] - /// - [`TemplateProgram::instantiate`] + /// - [`TemplateAst::new`] + /// - [`TemplateAst::instantiate`] /// - [`CompiledProgram::satisfy`] pub fn new>>( s: Str, @@ -583,7 +583,7 @@ pub(crate) mod tests { Arc::from(program_text), ); - match TemplateProgram::flatten(source, dependency_map, &UnstableFeatures::all()) { + match TemplateAst::flatten(source, dependency_map, &UnstableFeatures::all()) { Ok(single_file) => single_file, Err(error) => panic!("{}", &error), } @@ -630,7 +630,7 @@ pub(crate) mod tests { include_fee_output: bool, } - impl TestCase { + impl TestCase { pub fn template_file>(program_file_path: P) -> Self { Self::template_file_with_unstable(program_file_path, UnstableFeatures::none()) } @@ -654,7 +654,7 @@ pub(crate) mod tests { Arc::from(program_text), ); - let program = match TemplateProgram::new_with_dep( + let program = match TemplateAst::new_with_dep( source, dependency_map, &unstable_features, @@ -680,7 +680,7 @@ pub(crate) mod tests { program_text: Cow, unstable_features: UnstableFeatures, ) -> Self { - let program = match TemplateProgram::new_with_unstable( + let program = match TemplateAst::new_with_unstable( program_text.as_ref(), &unstable_features, Box::new(ElementsJetHinter::new()), @@ -729,7 +729,7 @@ pub(crate) mod tests { impl TestCase { pub fn program_file>(program_file_path: P) -> Self { - TestCase::::template_file(program_file_path) + TestCase::::template_file(program_file_path) .with_arguments(Arguments::default()) } @@ -737,7 +737,7 @@ pub(crate) mod tests { program_file_path: P, unstable_features: UnstableFeatures, ) -> Self { - TestCase::::template_file_with_unstable( + TestCase::::template_file_with_unstable( program_file_path, unstable_features, ) @@ -745,7 +745,7 @@ pub(crate) mod tests { } pub fn program_text(program_text: Cow) -> Self { - TestCase::::template_text(program_text) + TestCase::::template_text(program_text) .with_arguments(Arguments::default()) } @@ -753,11 +753,8 @@ pub(crate) mod tests { program_text: Cow, unstable_features: UnstableFeatures, ) -> Self { - TestCase::::template_text_with_unstable( - program_text, - unstable_features, - ) - .with_arguments(Arguments::default()) + TestCase::::template_text_with_unstable(program_text, unstable_features) + .with_arguments(Arguments::default()) } pub fn program_file_with_deps_and_unstable( @@ -765,7 +762,7 @@ pub(crate) mod tests { dependency_map: &DependencyMap, unstable_features: UnstableFeatures, ) -> Self { - TestCase::::template_deps_with_unstable( + TestCase::::template_deps_with_unstable( prog_path.as_ref(), dependency_map, unstable_features, @@ -1077,7 +1074,7 @@ pub(crate) mod tests { let dependency_map = build_map(&canon_root, &[]).unwrap(); - TestCase::::template_deps_with_unstable( + TestCase::::template_deps_with_unstable( &main_path, &dependency_map, UnstableFeatures::all(), @@ -1090,10 +1087,10 @@ pub(crate) mod tests { #[test] fn test_anonymous_source_compiles_without_dependencies() { let code = "fn main() { assert!(true); }"; - let program = TemplateProgram::new(code, Box::new(ElementsJetHinter::new())); + let program = TemplateAst::new(code, Box::new(ElementsJetHinter::new())); assert!( program.is_ok(), - "TemplateProgram::new should successfully compile anonymous source files without requiring canonical paths" + "TemplateAst::new should successfully compile anonymous source files without requiring canonical paths" ); } @@ -1368,10 +1365,10 @@ fn main() { ]; for hinter in hinters { - let program = TemplateProgram::new(code, hinter); + let program = TemplateAst::new(code, hinter); assert!( program.is_ok(), - "TemplateProgram::new should successfully compile the same program with different jet hinters: {:?}", + "TemplateAst::new should successfully compile the same program with different jet hinters: {:?}", program.err(), ); } @@ -1387,14 +1384,14 @@ fn main() { assert!(jet::eq_32(idx, idx)); }"#; - let elements_result = TemplateProgram::new(code, Box::new(ElementsJetHinter::new())); + let elements_result = TemplateAst::new(code, Box::new(ElementsJetHinter::new())); assert!( elements_result.is_ok(), "ElementsJetHinter should compile Elements-specific jets: {:?}", elements_result.err(), ); - let core_result = TemplateProgram::new(code, Box::new(CoreJetHinter::new())); + let core_result = TemplateAst::new(code, Box::new(CoreJetHinter::new())); assert!( core_result.is_err(), "CoreJetHinter should fail to compile Elements-specific jets", @@ -1556,7 +1553,7 @@ fn main() { } } - // Smoke tests that the version check is wired into `TemplateProgram::new`: one + // Smoke tests that the version check is wired into `TemplateAst::new`: one // compatible directive compiles, one incompatible directive aborts. The semver // matching and per-kind messages are covered exhaustively in `version`'s unit // tests, so they are not re-asserted through the pipeline here. @@ -1569,15 +1566,14 @@ fn main() { .unwrap(); let compatible = format!("simc \"{version}\";\nfn main() {{}}"); assert!( - TemplateProgram::new(compatible, Box::new(crate::ast::ElementsJetHinter::new())) - .is_ok() + TemplateAst::new(compatible, Box::new(crate::ast::ElementsJetHinter::new())).is_ok() ); } /// The producing compiler's version is readable from the program objects. #[test] fn compiler_version_accessor() { - let template = TemplateProgram::new( + let template = TemplateAst::new( "fn main() {}", Box::new(crate::ast::ElementsJetHinter::new()), ) @@ -1590,7 +1586,7 @@ fn main() { #[test] fn incompatible_directive_aborts() { let too_old = "simc \">= 99.99.99\";\nfn main() {}"; - let err = TemplateProgram::new(too_old, Box::new(crate::ast::ElementsJetHinter::new())) + let err = TemplateAst::new(too_old, Box::new(crate::ast::ElementsJetHinter::new())) .unwrap_err() .to_string(); assert!( @@ -1825,7 +1821,7 @@ mod error_tests { let dependencies = dependency_map(&root_dir, "lib", &lib_dir); - let err = TemplateProgram::new_with_dep( + let err = TemplateAst::new_with_dep( source_file(&main_path), &dependencies, &UnstableFeatures::all(), @@ -1857,7 +1853,7 @@ mod error_tests { ws.create_file("workspace/lib/base.simf", "pub fn one() -> u32 { 1 }\n"); let dependencies = dependency_map(&root_dir, "lib", &lib_dir); - let _err = TemplateProgram::new_with_dep( + let _err = TemplateAst::new_with_dep( source_file(&main_path), &dependencies, &UnstableFeatures::none(), @@ -1877,7 +1873,7 @@ mod error_tests { ); let dependencies = dependency_map(&root_dir, "lib", &lib_dir); - let err = TemplateProgram::new_with_dep( + let err = TemplateAst::new_with_dep( source_file(&main_path), &dependencies, &UnstableFeatures::all(), diff --git a/src/main.rs b/src/main.rs index 73bafd05..c37e704b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use simplicityhl::error::should_color; use simplicityhl::version::SimcDirective; use simplicityhl::{ resolution::DependencyMapBuilder, source::CanonPath, source::CanonSourceFile, AbiMeta, - TemplateProgram, + TemplateAst, }; use simplicityhl::{UnstableFeature, UnstableFeatures}; use std::path::Path; @@ -197,7 +197,7 @@ fn main() -> Result<(), Box> { }; let source = CanonSourceFile::new(main_path.clone(), std::sync::Arc::from(main_text)); - let template = match TemplateProgram::new_with_dep( + let template = match TemplateAst::new_with_dep( source, &dependencies, &unstable_features, diff --git a/src/tracker.rs b/src/tracker.rs index c6b66b92..cdb5571b 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -378,7 +378,7 @@ mod tests { use crate::elements::hashes::Hash; use crate::elements::pset::Input; use crate::elements::{AssetId, OutPoint, Script, Txid}; - use crate::{Arguments, TemplateProgram, WitnessValues}; + use crate::{Arguments, TemplateAst, WitnessValues}; use super::*; @@ -451,8 +451,7 @@ mod tests { #[test] fn test_debug_and_jet_tracing() { - let program = - TemplateProgram::new(TEST_PROGRAM, Box::new(ElementsJetHinter::new())).unwrap(); + let program = TemplateAst::new(TEST_PROGRAM, Box::new(ElementsJetHinter::new())).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); @@ -523,7 +522,7 @@ mod tests { let env = create_test_env(); let program = - TemplateProgram::new(TEST_ARITHMETIC_JETS, Box::new(ElementsJetHinter::new())).unwrap(); + TemplateAst::new(TEST_ARITHMETIC_JETS, Box::new(ElementsJetHinter::new())).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); @@ -578,8 +577,7 @@ mod tests { let env = create_test_env(); let program = - TemplateProgram::new(TEST_FULL_MULTIPLY_JETS, Box::new(ElementsJetHinter::new())) - .unwrap(); + TemplateAst::new(TEST_FULL_MULTIPLY_JETS, Box::new(ElementsJetHinter::new())).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); diff --git a/tests/core_tracker.rs b/tests/core_tracker.rs index 6c9b1608..8c587740 100644 --- a/tests/core_tracker.rs +++ b/tests/core_tracker.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use simplicityhl::ast::CoreJetHinter; use simplicityhl::simplicity::jet::CoreEnv; use simplicityhl::tracker::DefaultTracker; -use simplicityhl::{Arguments, TemplateProgram, WitnessValues}; +use simplicityhl::{Arguments, TemplateAst, WitnessValues}; const CORE_PROGRAM: &str = r#"fn main() { let (_, sum): (bool, u32) = jet::add_32(10, 20); @@ -12,7 +12,7 @@ const CORE_PROGRAM: &str = r#"fn main() { }"#; fn satisfied_core_program() -> simplicityhl::SatisfiedProgram { - TemplateProgram::new(CORE_PROGRAM, Box::new(CoreJetHinter::new())) + TemplateAst::new(CORE_PROGRAM, Box::new(CoreJetHinter::new())) .unwrap() .instantiate(Arguments::default(), true) .unwrap() From 93231df0b63740cf1f5d99f47dc3dfc52fda6a78 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 11 Aug 2026 23:01:08 +0000 Subject: [PATCH 4/8] template_program: create module, move WitnessName into it Over the next couple commits I am going to generalize WitnessName to cover multiple possibilities (both named witnesses and parameters, in a new "template program" type). Start by moving stuff around, and replacing the macro-generated accessors with hand-written ones. --- src/ast.rs | 3 +- src/compile/mod.rs | 2 +- src/error.rs | 3 +- src/lib.rs | 7 ++-- src/named.rs | 2 +- src/parse.rs | 3 +- src/serde.rs | 10 ++---- src/str.rs | 17 ++------- src/template_program/mod.rs | 3 ++ src/template_program/witness.rs | 63 +++++++++++++++++++++++++++++++++ src/witness.rs | 2 +- 11 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 src/template_program/mod.rs create mode 100644 src/template_program/witness.rs diff --git a/src/ast.rs b/src/ast.rs index 03e4e36a..a613fd77 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -14,7 +14,8 @@ use crate::jet::{source_type, target_type, JetHL}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::{MatchPattern, UseDecl, Visibility}; use crate::pattern::Pattern; -use crate::str::{AliasName, FunctionName, Identifier, ModuleName, SymbolName, WitnessName}; +use crate::str::{AliasName, FunctionName, Identifier, ModuleName, SymbolName}; +use crate::template_program::WitnessName; use crate::types::{ AliasedType, EnumInfo, EnumVariantInfo, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, TypeInner, UIntType, diff --git a/src/compile/mod.rs b/src/compile/mod.rs index ed16801a..52120bb7 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -19,7 +19,7 @@ use crate::error::{Diagnostic, Error, Span, WithSpan}; use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; -use crate::str::WitnessName; +use crate::template_program::WitnessName; use crate::types::{StructuralType, TypeDeconstructible}; use crate::value::StructuralValue; use crate::witness::Arguments; diff --git a/src/error.rs b/src/error.rs index 80cf2d2f..3ada1b49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,8 @@ use itertools::Itertools; use crate::driver::{SourceMap, CRATE_STR, MAIN_MODULE}; use crate::lexer::Token; use crate::parse::MatchPattern; -use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName, WitnessName}; +use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName}; +use crate::template_program::WitnessName; use crate::types::{ResolvedType, UIntType}; use crate::unstable::UnstableFeature; diff --git a/src/lib.rs b/src/lib.rs index ca8cb904..8b9e0243 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub mod parse; pub mod pattern; pub mod resolution; pub mod source; +pub mod template_program; pub mod unstable; #[cfg(feature = "serde")] @@ -561,6 +562,8 @@ pub(crate) mod tests { use crate::resolution::tests::{build_map, canon}; use crate::resolution::DependencyMapBuilder; use crate::source::CanonPath; + use crate::str::Identifier; + use crate::template_program::WitnessName; use crate::test_utils::TempWorkspace; use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; @@ -1637,8 +1640,6 @@ fn main() { #[test] #[cfg(feature = "serde")] fn enum_match_witness_file_variant_name() { - use crate::str::WitnessName; - // The witness file names the variant; resolution constructs the // enum value at the declared type. let src = "enum Action { Hot, Cold, } @@ -1670,7 +1671,6 @@ fn main() { #[test] fn strict_satisfy_rejects_missing_witness() { - use crate::str::{Identifier, WitnessName}; use crate::value::ValueConstructible; use std::collections::HashMap; @@ -1718,7 +1718,6 @@ fn main() { #[test] fn enum_match_dispatches_every_variant() { - use crate::str::{Identifier, WitnessName}; use std::collections::HashMap; // Three and five variants cover leaves at unequal depths diff --git a/src/named.rs b/src/named.rs index a62bdd9e..c3f0ba0a 100644 --- a/src/named.rs +++ b/src/named.rs @@ -7,7 +7,7 @@ use simplicity::node::{ use simplicity::Cmr; use simplicity::{types, FailEntropy}; -use crate::str::WitnessName; +use crate::template_program::WitnessName; use crate::value::StructuralValue; use crate::witness::WitnessValues; diff --git a/src/parse.rs b/src/parse.rs index 70d867b0..56c71a4f 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -27,8 +27,9 @@ use crate::num::NonZeroPow2Usize; use crate::pattern::Pattern; use crate::str::{ AliasName, Binary, Decimal, FunctionName, Hexadecimal, Identifier, JetName, ModuleName, - SymbolName, WitnessName, + SymbolName, }; +use crate::template_program::WitnessName; use crate::types::{AliasedType, BuiltinAlias, TypeConstructible, UIntType}; use crate::unstable::{impl_require_feature, RequireFeature, UnstableFeature, UnstableFeatures}; use crate::version::SimcDirective; diff --git a/src/serde.rs b/src/serde.rs index 6b87031c..ca2a11b3 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -4,7 +4,8 @@ use std::fmt; use std::marker::PhantomData; use crate::parse::ParseFromStr as _; -use crate::str::{Identifier, WitnessName}; +use crate::str::Identifier; +use crate::template_program::WitnessName; use crate::types::ResolvedType; use crate::value::Value; use crate::witness::{Arguments, UnresolvedValue, UnresolvedValues, WitnessValues}; @@ -324,6 +325,7 @@ impl Serialize for Arguments { #[cfg(test)] mod tests { use super::*; + use crate::str::Identifier; #[test] fn witness_serde_duplicate_assignment() { @@ -339,7 +341,6 @@ mod tests { } fn unit_enum(name: &str, variants: &[&str]) -> ResolvedType { - use crate::str::Identifier; use crate::types::{EnumInfo, EnumVariantInfo}; use std::sync::Arc; @@ -352,7 +353,6 @@ mod tests { #[test] fn abi_enum_type_serializes_as_name() { - use crate::str::WitnessName; use crate::types::TypeConstructible; let action_ty = unit_enum("Action", &["Inherit", "ColdSpend"]); @@ -381,8 +381,6 @@ mod tests { #[test] fn enum_witness_value_serializes_as_variant_name() { - use crate::str::{Identifier, WitnessName}; - let action_ty = unit_enum("Action", &["Inherit", "ColdSpend"]); let value = Value::enum_variant( &action_ty, @@ -420,7 +418,6 @@ mod tests { #[test] fn payload_enum_witness_value_round_trips() { - use crate::str::{Identifier, WitnessName}; use crate::types::{EnumInfo, EnumVariantInfo}; use crate::value::ValueConstructible; use std::sync::Arc; @@ -461,7 +458,6 @@ mod tests { #[test] fn nested_enum_witness_value_serializes_as_bare_string() { - use crate::str::{Identifier, WitnessName}; use crate::types::TypeConstructible; use crate::value::ValueConstructible; diff --git a/src/str.rs b/src/str.rs index 78b10c0d..1327e5ff 100644 --- a/src/str.rs +++ b/src/str.rs @@ -77,7 +77,7 @@ macro_rules! wrapped_string { /// which cannot be used as identifiers. To ensure valid grammar /// for fuzzing, any generated keywords are padded with the `_`. macro_rules! impl_arbitrary_lowercase_alpha { - ($wrapper:ident) => { + ($wrapper:path) => { #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for $wrapper { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { @@ -96,6 +96,8 @@ macro_rules! impl_arbitrary_lowercase_alpha { }; } +impl_arbitrary_lowercase_alpha!(crate::template_program::WitnessName); + /// The name of a function. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FunctionName(Arc); @@ -214,19 +216,6 @@ pub struct Identifier(Arc); wrapped_string!(Identifier, "variable identifier"); impl_arbitrary_lowercase_alpha!(Identifier); -/// The name of a witness. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct WitnessName(Arc); - -wrapped_string!(WitnessName, "witness name"); -impl_arbitrary_lowercase_alpha!(WitnessName); - -impl AsRef for WitnessName { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - /// The name of a jet. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct JetName(Arc); diff --git a/src/template_program/mod.rs b/src/template_program/mod.rs new file mode 100644 index 00000000..a06a7946 --- /dev/null +++ b/src/template_program/mod.rs @@ -0,0 +1,3 @@ +mod witness; + +pub use self::witness::WitnessName; diff --git a/src/template_program/witness.rs b/src/template_program/witness.rs new file mode 100644 index 00000000..68688d29 --- /dev/null +++ b/src/template_program/witness.rs @@ -0,0 +1,63 @@ +use core::fmt; +use std::sync::Arc; + +/// The name of a witness. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct WitnessName(Arc); + +impl WitnessName { + /// Create a [`WitnessName`] from a bare string. + pub fn from_str_unchecked(s: &str) -> Self { + Self(Arc::from(s)) + } + + /// Access the inner string. + pub fn as_inner(&self) -> &Arc { + &self.0 + } + + /// Access the inner string. + pub fn as_str(&self) -> &str { + self.as_inner().as_ref() + } + + /// Make a cheap copy of the name. + pub fn shallow_clone(&self) -> Self { + self.clone() + } + + /// Creates a [`WitnessName`] from an identifier. + pub fn from_ident(ident: &crate::str::Identifier) -> Self { + Self(Arc::clone(ident.as_inner())) + } +} + +impl core::cmp::PartialEq for WitnessName { + fn eq(&self, other: &str) -> bool { + self.as_ref() == other + } +} + +impl core::cmp::PartialEq for str { + fn eq(&self, other: &WitnessName) -> bool { + self == other.as_ref() + } +} + +impl fmt::Display for WitnessName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_inner().fmt(f) + } +} + +impl fmt::Debug for WitnessName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_inner().fmt(f) + } +} + +impl AsRef for WitnessName { + fn as_ref(&self) -> &str { + self.as_inner().as_ref() + } +} diff --git a/src/witness.rs b/src/witness.rs index f2aad16a..22f6892d 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::error::{Diagnostic, DiagnosticManager, Error, WithSpan}; use crate::parse::ParseFromStr; -use crate::str::WitnessName; +use crate::template_program::WitnessName; use crate::types::{AliasedType, ResolvedType}; use crate::value::Value; From 4fc6698c969b6254e384521fbb7210960ce44a7a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 11 Aug 2026 23:49:19 +0000 Subject: [PATCH 5/8] template_program: rename WitnessName to TemplateProgramWitness We are already using this type for both parameters and witnesses. Update the name to reflect that. This commit is just a search-and-replace. It is hopefully easy to review even though it's big, for that reason. --- src/ast.rs | 22 +++++-- src/compile/mod.rs | 5 +- src/error.rs | 14 ++-- src/lib.rs | 23 ++++--- src/named.rs | 29 +++++---- src/parse.rs | 14 ++-- src/serde.rs | 34 +++++----- src/str.rs | 2 +- src/template_program/mod.rs | 2 +- src/template_program/witness.rs | 20 +++--- src/witness.rs | 109 ++++++++++++++++++-------------- 11 files changed, 152 insertions(+), 122 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index a613fd77..7ba62e9c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -15,13 +15,13 @@ use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::{MatchPattern, UseDecl, Visibility}; use crate::pattern::Pattern; use crate::str::{AliasName, FunctionName, Identifier, ModuleName, SymbolName}; -use crate::template_program::WitnessName; use crate::types::{ AliasedType, EnumInfo, EnumVariantInfo, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, TypeInner, UIntType, }; use crate::value::{UIntValue, Value}; use crate::witness::{Parameters, WitnessTypes}; +use crate::TemplateProgramWitness; use crate::{impl_eq_hash, parse}; /// A program consists of the main function. @@ -228,9 +228,9 @@ pub enum SingleExpressionInner { /// Constant value. Constant(Value), /// Witness value. - Witness(WitnessName), + Witness(TemplateProgramWitness), /// Parameter value. - Parameter(WitnessName), + Parameter(TemplateProgramWitness), /// Variable that has been assigned a value. Variable(Identifier), /// Expression in parentheses. @@ -726,8 +726,8 @@ struct Scope { /// Block-level variable scopes. Push on block enter, pop on block exit. variables: Vec>, - parameters: HashMap, - witnesses: HashMap, + parameters: HashMap, + witnesses: HashMap, /// Allow enum constructions to name an enum by its declared name even /// when that name is not an alias in scope. Enabled only for value /// parsing (witness and argument files), which runs without a scope. @@ -1143,7 +1143,11 @@ impl Scope { /// ## Errors /// /// * [`Error::ExpressionTypeMismatch`] A parameter of the same name has already been defined as a different type. - pub fn insert_parameter(&mut self, name: WitnessName, ty: ResolvedType) -> Result<(), Error> { + pub fn insert_parameter( + &mut self, + name: TemplateProgramWitness, + ty: ResolvedType, + ) -> Result<(), Error> { match self.parameters.entry(name.clone()) { Entry::Occupied(entry) if entry.get() == &ty => Ok(()), Entry::Occupied(entry) => Err(Error::ExpressionTypeMismatch { @@ -1163,7 +1167,11 @@ impl Scope { /// /// * [`Error::WitnessOutsideMain`] The current scope is not inside the main function. /// * [`Error::WitnessReused`] A witness with the same name has already been defined. - pub fn insert_witness(&mut self, name: WitnessName, ty: ResolvedType) -> Result<(), Error> { + pub fn insert_witness( + &mut self, + name: TemplateProgramWitness, + ty: ResolvedType, + ) -> Result<(), Error> { if !self.is_main { return Err(Error::WitnessOutsideMain); } diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 52120bb7..d737faa4 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -19,11 +19,10 @@ use crate::error::{Diagnostic, Error, Span, WithSpan}; use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; -use crate::template_program::WitnessName; use crate::types::{StructuralType, TypeDeconstructible}; use crate::value::StructuralValue; use crate::witness::Arguments; -use crate::Value; +use crate::{TemplateProgramWitness, Value}; type ProgNode<'brand> = Arc>; @@ -217,7 +216,7 @@ impl<'brand> Scope<'brand> { } } - pub fn get_argument(&self, name: &WitnessName) -> &Value { + pub fn get_argument(&self, name: &TemplateProgramWitness) -> &Value { self.arguments .get(name) .expect("Precondition: Arguments are consistent with parameters") diff --git a/src/error.rs b/src/error.rs index 3ada1b49..b5b088ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,9 +22,9 @@ use crate::driver::{SourceMap, CRATE_STR, MAIN_MODULE}; use crate::lexer::Token; use crate::parse::MatchPattern; use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName}; -use crate::template_program::WitnessName; use crate::types::{ResolvedType, UIntType}; use crate::unstable::UnstableFeature; +use crate::TemplateProgramWitness; /// Area that an object spans inside a file. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -865,18 +865,18 @@ pub enum Error { identifier: Identifier, }, WitnessReused { - name: WitnessName, + name: TemplateProgramWitness, }, WitnessMissing { - name: WitnessName, + name: TemplateProgramWitness, }, WitnessTypeMismatch { - name: WitnessName, + name: TemplateProgramWitness, declared: ResolvedType, assigned: ResolvedType, }, WitnessReassigned { - name: WitnessName, + name: TemplateProgramWitness, }, WitnessOutsideMain, ModuleRedefined { @@ -889,10 +889,10 @@ pub enum Error { name: ModuleName, }, ArgumentMissing { - name: WitnessName, + name: TemplateProgramWitness, }, ArgumentTypeMismatch { - name: WitnessName, + name: TemplateProgramWitness, declared: ResolvedType, assigned: ResolvedType, }, diff --git a/src/lib.rs b/src/lib.rs index 8b9e0243..ce0fdbad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ pub mod parse; pub mod pattern; pub mod resolution; pub mod source; -pub mod template_program; +mod template_program; pub mod unstable; #[cfg(feature = "serde")] @@ -46,6 +46,7 @@ use crate::error::DiagnosticManager; use crate::parse::ParseFromStrWithErrors; use crate::resolution::DependencyMap; use crate::source::CanonSourceFile; +pub use crate::template_program::TemplateProgramWitness; pub use crate::types::ResolvedType; pub use crate::unstable::{UnstableFeature, UnstableFeatures}; pub use crate::value::Value; @@ -563,7 +564,6 @@ pub(crate) mod tests { use crate::resolution::DependencyMapBuilder; use crate::source::CanonPath; use crate::str::Identifier; - use crate::template_program::WitnessName; use crate::test_utils::TempWorkspace; use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; @@ -1662,7 +1662,7 @@ fn main() { serde_json::from_str(r#"{ "ACT": "Action::Cold" }"#).unwrap(); let witness: WitnessValues = unresolved.resolve(compiled.witness_types()).unwrap(); assert!(witness - .get(&WitnessName::from_str_unchecked("ACT")) + .get(&TemplateProgramWitness::from_str_unchecked("ACT")) .is_some()); TestCase::program_text_with_unstable(Cow::Borrowed(src), UnstableFeatures::all()) .with_witness_values(witness) @@ -1693,19 +1693,22 @@ fn main() { .unwrap(); let selector_ty = compiled .witness_types() - .get(&WitnessName::from_str_unchecked("SELECTOR")) + .get(&TemplateProgramWitness::from_str_unchecked("SELECTOR")) .unwrap() .clone(); // Only SELECTOR and A are provided; B is omitted. The strict entry // points must reject the omitted witness rather than zero-filling it. - let mut map: HashMap = HashMap::new(); + let mut map: HashMap = HashMap::new(); map.insert( - WitnessName::from_str_unchecked("SELECTOR"), + TemplateProgramWitness::from_str_unchecked("SELECTOR"), Value::enum_variant(&selector_ty, &Identifier::from_str_unchecked("A"), vec![]) .unwrap(), ); - map.insert(WitnessName::from_str_unchecked("A"), Value::u32(0)); + map.insert( + TemplateProgramWitness::from_str_unchecked("A"), + Value::u32(0), + ); let err = compiled .satisfy(WitnessValues::from(map.clone())) @@ -1752,7 +1755,7 @@ fn main() { .unwrap(); let action_ty = compiled .witness_types() - .get(&WitnessName::from_str_unchecked("ACT")) + .get(&TemplateProgramWitness::from_str_unchecked("ACT")) .expect("ACT is declared") .clone(); @@ -1762,9 +1765,9 @@ fn main() { .expect("declared variant"); let expected = u32::try_from((i + 1) * 10).unwrap(); let map = HashMap::from([ - (WitnessName::from_str_unchecked("ACT"), action), + (TemplateProgramWitness::from_str_unchecked("ACT"), action), ( - WitnessName::from_str_unchecked("EXPECTED"), + TemplateProgramWitness::from_str_unchecked("EXPECTED"), crate::value::ValueConstructible::u32(expected), ), ]); diff --git a/src/named.rs b/src/named.rs index c3f0ba0a..93e394f2 100644 --- a/src/named.rs +++ b/src/named.rs @@ -7,16 +7,16 @@ use simplicity::node::{ use simplicity::Cmr; use simplicity::{types, FailEntropy}; -use crate::template_program::WitnessName; use crate::value::StructuralValue; use crate::witness::WitnessValues; +use crate::TemplateProgramWitness; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct WithNames(T); impl node::Marker for WithNames { type CachedData = M::CachedData; - type Witness = WitnessName; + type Witness = TemplateProgramWitness; // 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; @@ -84,7 +84,7 @@ where N::Witness: Nullable, F: FnMut( &Node>, - Inner<&N::CachedData, &NoDisconnect, &WitnessName>, + Inner<&N::CachedData, &NoDisconnect, &TemplateProgramWitness>, ) -> Result, { struct Translator(F); @@ -96,7 +96,7 @@ where N::Witness: Nullable, F: FnMut( &Node>, - Inner<&N::CachedData, &NoDisconnect, &WitnessName>, + Inner<&N::CachedData, &NoDisconnect, &TemplateProgramWitness>, ) -> Result, { type Error = E; @@ -104,8 +104,8 @@ where fn convert_witness( &mut self, _: &PostOrderIterItem<&Node>>, - wit: &WitnessName, - ) -> Result { + wit: &TemplateProgramWitness, + ) -> Result { Ok(wit.shallow_clone()) } @@ -121,7 +121,7 @@ where fn convert_data( &mut self, data: &PostOrderIterItem<&Node>>, - inner: Inner<&Arc>>, &NoDisconnect, &WitnessName>, + inner: Inner<&Arc>>, &NoDisconnect, &TemplateProgramWitness>, ) -> Result { let new_inner = inner.map(|node| node.cached_data()); self.0(data.node, new_inner) @@ -151,7 +151,7 @@ where fn convert_witness( &mut self, _: &PostOrderIterItem<&Node>>, - _: &WitnessName, + _: &TemplateProgramWitness, ) -> Result { Ok(M::Witness::none()) } @@ -206,7 +206,7 @@ pub fn populate_witnesses( fn convert_witness( &mut self, _: &PostOrderIterItem<&CommitNode>, - witness: &WitnessName, + witness: &TemplateProgramWitness, ) -> Result { match self.values.get(witness) { Some(val) => Ok(simplicity::Value::from(StructuralValue::from(val))), @@ -251,8 +251,8 @@ pub fn populate_witnesses( // 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<'brand> WitnessConstructible<'brand, WitnessName> for node::ConstructData<'brand> { - fn witness(inference_context: &types::Context<'brand>, _: WitnessName) -> Self { +impl<'brand> WitnessConstructible<'brand, TemplateProgramWitness> for node::ConstructData<'brand> { + fn witness(inference_context: &types::Context<'brand>, _: TemplateProgramWitness) -> Self { WitnessConstructible::>::witness(inference_context, None) } } @@ -602,7 +602,7 @@ impl<'brand, P: CoreExt<'brand>> PairBuilder

{ } } -impl<'brand, P: WitnessConstructible<'brand, WitnessName>> PairBuilder

{ +impl<'brand, P: WitnessConstructible<'brand, TemplateProgramWitness>> PairBuilder

{ /// Create the witness expression. /// /// ## Invariant @@ -613,7 +613,10 @@ impl<'brand, P: WitnessConstructible<'brand, WitnessName>> PairBuilder

{ /// --------------- /// witness : A → B /// ``` - pub fn witness(inference_context: &types::Context<'brand>, witness: WitnessName) -> Self { + pub fn witness( + inference_context: &types::Context<'brand>, + witness: TemplateProgramWitness, + ) -> Self { Self(P::witness(inference_context, witness)) } } diff --git a/src/parse.rs b/src/parse.rs index 56c71a4f..08efeeda 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -29,10 +29,10 @@ use crate::str::{ AliasName, Binary, Decimal, FunctionName, Hexadecimal, Identifier, JetName, ModuleName, SymbolName, }; -use crate::template_program::WitnessName; use crate::types::{AliasedType, BuiltinAlias, TypeConstructible, UIntType}; use crate::unstable::{impl_require_feature, RequireFeature, UnstableFeature, UnstableFeatures}; use crate::version::SimcDirective; +use crate::TemplateProgramWitness; #[cfg(feature = "fmt")] use crate::lexer::{FmtToken, FmtTokens}; @@ -817,9 +817,9 @@ pub enum SingleExpressionInner { /// Hexadecimal string literal. Hexadecimal(Hexadecimal), /// Witness value. - Witness(WitnessName), + Witness(TemplateProgramWitness), /// Parameter value. - Parameter(WitnessName), + Parameter(TemplateProgramWitness), /// Variable identifier expression Variable(Identifier), /// Function call @@ -2710,8 +2710,8 @@ impl SingleExpression { Token::DecLiteral(s) => SingleExpressionInner::Decimal(s), Token::HexLiteral(s) => SingleExpressionInner::Hexadecimal(s), Token::BinLiteral(s) => SingleExpressionInner::Binary(s), - Token::Witness(s) => SingleExpressionInner::Witness(WitnessName::from_str_unchecked(s)), - Token::Param(s) => SingleExpressionInner::Parameter(WitnessName::from_str_unchecked(s)), + Token::Witness(s) => SingleExpressionInner::Witness(TemplateProgramWitness::from_str_unchecked(s)), + Token::Param(s) => SingleExpressionInner::Parameter(TemplateProgramWitness::from_str_unchecked(s)), }; // Enum variant construction: `Path::To::Enum::Variant(args..)`. @@ -3420,7 +3420,7 @@ impl crate::ArbitraryRec for SingleExpression { 2 => Decimal::arbitrary(u).map(S::Decimal), 3 => Hexadecimal::arbitrary(u).map(S::Hexadecimal), 4 => Identifier::arbitrary(u).map(S::Variable), - 5 => WitnessName::arbitrary(u).map(S::Witness), + 5 => TemplateProgramWitness::arbitrary(u).map(S::Witness), 6 => Ok(S::Option(None)), _ => unreachable!(), }, @@ -3430,7 +3430,7 @@ impl crate::ArbitraryRec for SingleExpression { 2 => Decimal::arbitrary(u).map(S::Decimal), 3 => Hexadecimal::arbitrary(u).map(S::Hexadecimal), 4 => Identifier::arbitrary(u).map(S::Variable), - 5 => WitnessName::arbitrary(u).map(S::Witness), + 5 => TemplateProgramWitness::arbitrary(u).map(S::Witness), 6 => Ok(S::Option(None)), 7 => Expression::arbitrary_rec(u, new_budget) .map(Arc::new) diff --git a/src/serde.rs b/src/serde.rs index ca2a11b3..c0756b1e 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -5,11 +5,10 @@ use std::marker::PhantomData; use crate::parse::ParseFromStr as _; use crate::str::Identifier; -use crate::template_program::WitnessName; use crate::types::ResolvedType; use crate::value::Value; use crate::witness::{Arguments, UnresolvedValue, UnresolvedValues, WitnessValues}; -use crate::{AbiMeta, Parameters, WitnessTypes}; +use crate::{AbiMeta, Parameters, TemplateProgramWitness, WitnessTypes}; use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; /// Visitor for a map from identifiers to values of type `V`, rejecting duplicate names. @@ -54,7 +53,7 @@ impl<'de> Deserialize<'de> for WitnessValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) + .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) .map(Self::from) } } @@ -102,7 +101,7 @@ impl<'de> Deserialize<'de> for UnresolvedValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) + .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) .map(Self::from_map) } } @@ -166,7 +165,7 @@ impl<'de> Deserialize<'de> for Arguments { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(WitnessName::from_ident)) + .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) .map(Self::from) } } @@ -264,7 +263,7 @@ impl<'de> Deserialize<'de> for Identifier { } } -struct WitnessMapSerializer<'a>(&'a HashMap); +struct WitnessMapSerializer<'a>(&'a HashMap); impl<'a> Serialize for WitnessMapSerializer<'a> { fn serialize(&self, serializer: S) -> Result @@ -357,17 +356,20 @@ mod tests { let action_ty = unit_enum("Action", &["Inherit", "ColdSpend"]); let witness_types = WitnessTypes::from(HashMap::from([ - (WitnessName::from_str_unchecked("ACTION"), action_ty.clone()), ( - WitnessName::from_str_unchecked("MAYBE"), + TemplateProgramWitness::from_str_unchecked("ACTION"), + action_ty.clone(), + ), + ( + TemplateProgramWitness::from_str_unchecked("MAYBE"), ResolvedType::option(action_ty.clone()), ), ( - WitnessName::from_str_unchecked("PAIR"), + TemplateProgramWitness::from_str_unchecked("PAIR"), ResolvedType::tuple([action_ty, unit_enum("Reaction", &["Fast", "Slow"])]), ), ( - WitnessName::from_str_unchecked("PLAIN"), + TemplateProgramWitness::from_str_unchecked("PLAIN"), crate::parse::ParseFromStr::parse_from_str("u32").unwrap(), ), ])); @@ -389,7 +391,7 @@ mod tests { ) .unwrap(); let witness = WitnessValues::from(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), value, )])); @@ -401,7 +403,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), action_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); @@ -438,7 +440,7 @@ mod tests { ) .unwrap(); let witness = WitnessValues::from(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), value, )])); @@ -449,7 +451,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), action_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); @@ -466,7 +468,7 @@ mod tests { let cold = Value::enum_variant(&action_ty, &Identifier::from_str_unchecked("Cold"), vec![]) .unwrap(); let witness = WitnessValues::from(HashMap::from([( - WitnessName::from_str_unchecked("MAYBE"), + TemplateProgramWitness::from_str_unchecked("MAYBE"), Value::some(cold), )])); @@ -478,7 +480,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - WitnessName::from_str_unchecked("MAYBE"), + TemplateProgramWitness::from_str_unchecked("MAYBE"), option_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); diff --git a/src/str.rs b/src/str.rs index 1327e5ff..5456139c 100644 --- a/src/str.rs +++ b/src/str.rs @@ -96,7 +96,7 @@ macro_rules! impl_arbitrary_lowercase_alpha { }; } -impl_arbitrary_lowercase_alpha!(crate::template_program::WitnessName); +impl_arbitrary_lowercase_alpha!(crate::template_program::TemplateProgramWitness); /// The name of a function. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] diff --git a/src/template_program/mod.rs b/src/template_program/mod.rs index a06a7946..b70de719 100644 --- a/src/template_program/mod.rs +++ b/src/template_program/mod.rs @@ -1,3 +1,3 @@ mod witness; -pub use self::witness::WitnessName; +pub use self::witness::TemplateProgramWitness; diff --git a/src/template_program/witness.rs b/src/template_program/witness.rs index 68688d29..02e88cba 100644 --- a/src/template_program/witness.rs +++ b/src/template_program/witness.rs @@ -3,10 +3,10 @@ use std::sync::Arc; /// The name of a witness. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct WitnessName(Arc); +pub struct TemplateProgramWitness(Arc); -impl WitnessName { - /// Create a [`WitnessName`] from a bare string. +impl TemplateProgramWitness { + /// Create a [`TemplateProgramWitness`] from a bare string. pub fn from_str_unchecked(s: &str) -> Self { Self(Arc::from(s)) } @@ -26,37 +26,37 @@ impl WitnessName { self.clone() } - /// Creates a [`WitnessName`] from an identifier. + /// Creates a [`TemplateProgramWitness`] from an identifier. pub fn from_ident(ident: &crate::str::Identifier) -> Self { Self(Arc::clone(ident.as_inner())) } } -impl core::cmp::PartialEq for WitnessName { +impl core::cmp::PartialEq for TemplateProgramWitness { fn eq(&self, other: &str) -> bool { self.as_ref() == other } } -impl core::cmp::PartialEq for str { - fn eq(&self, other: &WitnessName) -> bool { +impl core::cmp::PartialEq for str { + fn eq(&self, other: &TemplateProgramWitness) -> bool { self == other.as_ref() } } -impl fmt::Display for WitnessName { +impl fmt::Display for TemplateProgramWitness { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_inner().fmt(f) } } -impl fmt::Debug for WitnessName { +impl fmt::Debug for TemplateProgramWitness { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_inner().fmt(f) } } -impl AsRef for WitnessName { +impl AsRef for TemplateProgramWitness { fn as_ref(&self) -> &str { self.as_inner().as_ref() } diff --git a/src/witness.rs b/src/witness.rs index 22f6892d..b0df67b9 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -4,20 +4,20 @@ use std::sync::Arc; use crate::error::{Diagnostic, DiagnosticManager, Error, WithSpan}; use crate::parse::ParseFromStr; -use crate::template_program::WitnessName; use crate::types::{AliasedType, ResolvedType}; use crate::value::Value; +use crate::TemplateProgramWitness; macro_rules! impl_name_type_map { ($wrapper: ident) => { impl $wrapper { /// Get the type that is assigned to the given name. - pub fn get(&self, name: &WitnessName) -> Option<&ResolvedType> { + pub fn get(&self, name: &TemplateProgramWitness) -> Option<&ResolvedType> { self.0.get(name) } /// Create an iterator over all name-type pairs. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -27,8 +27,8 @@ macro_rules! impl_name_type_map { } } - impl From> for $wrapper { - fn from(value: HashMap) -> Self { + impl From> for $wrapper { + fn from(value: HashMap) -> Self { Self(Arc::new(value)) } } @@ -40,17 +40,17 @@ macro_rules! impl_name_value_map { impl $wrapper { /// Access the inner map. #[cfg(feature = "serde")] - pub(crate) fn as_inner(&self) -> &HashMap { + pub(crate) fn as_inner(&self) -> &HashMap { &self.0 } /// Get the value that is assigned to the given name. - pub fn get(&self, name: &WitnessName) -> Option<&Value> { + pub fn get(&self, name: &TemplateProgramWitness) -> Option<&Value> { self.0.get(name) } /// Create an iterator over all name-value pairs. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -60,8 +60,8 @@ macro_rules! impl_name_value_map { } } - impl From> for $wrapper { - fn from(value: HashMap) -> Self { + impl From> for $wrapper { + fn from(value: HashMap) -> Self { Self(Arc::new(value)) } } @@ -83,12 +83,12 @@ macro_rules! impl_name_value_map { /// Map of witness types. #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct WitnessTypes(Arc>); +pub struct WitnessTypes(Arc>); impl_name_type_map!(WitnessTypes); -impl AsRef> for WitnessTypes { - fn as_ref(&self) -> &HashMap { +impl AsRef> for WitnessTypes { + fn as_ref(&self) -> &HashMap { self.0.as_ref() } } @@ -106,7 +106,7 @@ impl AsRef> for WitnessTypes { /// program's declared witness types. #[derive(Clone, Debug, Eq, PartialEq, Default)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub struct WitnessValues(Arc>); +pub struct WitnessValues(Arc>); impl_name_value_map!(WitnessValues, "witness"); @@ -166,11 +166,11 @@ pub(crate) enum UnresolvedValue { /// [`WitnessValues`] or [`Arguments`]. #[cfg(feature = "serde")] #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct UnresolvedValues(HashMap); +pub struct UnresolvedValues(HashMap); #[cfg(feature = "serde")] impl UnresolvedValues { - pub(crate) fn from_map(map: HashMap) -> Self { + pub(crate) fn from_map(map: HashMap) -> Self { Self(map) } @@ -186,8 +186,8 @@ impl UnresolvedValues { /// A bare value string does not parse at the declared type. pub fn resolve(self, declared_types: &M) -> Result where - T: From>, - M: AsRef>, + T: From>, + M: AsRef>, { let declared_types = declared_types.as_ref(); let mut map = HashMap::with_capacity(self.0.len()); @@ -224,12 +224,12 @@ impl ParseFromStr for ResolvedType { /// A parameter is a named variable that resolves to a value of a given type. /// Parameters have a name and a type. #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct Parameters(Arc>); +pub struct Parameters(Arc>); impl_name_type_map!(Parameters); -impl AsRef> for Parameters { - fn as_ref(&self) -> &HashMap { +impl AsRef> for Parameters { + fn as_ref(&self) -> &HashMap { self.0.as_ref() } } @@ -248,7 +248,7 @@ impl AsRef> for Parameters { /// declared parameter types. #[derive(Clone, Debug, Eq, PartialEq, Default)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub struct Arguments(Arc>); +pub struct Arguments(Arc>); impl_name_value_map!(Arguments, "param"); @@ -335,7 +335,7 @@ mod tests { }"#; let witness = WitnessValues::from(HashMap::from([( - WitnessName::from_str_unchecked("A"), + TemplateProgramWitness::from_str_unchecked("A"), Value::u16(42), )])); match SatisfiedProgram::new( @@ -383,44 +383,47 @@ fn main() { let u32_ty = ResolvedType::parse_from_str("u32").unwrap(); let sig_ty = ResolvedType::parse_from_str("Signature").unwrap(); let witness_types = WitnessTypes::from(HashMap::from([ - (WitnessName::from_str_unchecked("A"), u32_ty.clone()), - (WitnessName::from_str_unchecked("SIG"), sig_ty), + ( + TemplateProgramWitness::from_str_unchecked("A"), + u32_ty.clone(), + ), + (TemplateProgramWitness::from_str_unchecked("SIG"), sig_ty), ])); let unresolved = UnresolvedValues::from_map(HashMap::from([ ( - WitnessName::from_str_unchecked("A"), + TemplateProgramWitness::from_str_unchecked("A"), UnresolvedValue::Untyped("42".to_string()), ), ( - WitnessName::from_str_unchecked("B"), + TemplateProgramWitness::from_str_unchecked("B"), UnresolvedValue::Typed(Value::u16(7)), ), ])); let resolved: WitnessValues = unresolved.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&WitnessName::from_str_unchecked("A")), + resolved.get(&TemplateProgramWitness::from_str_unchecked("A")), Some(&Value::u32(42)) ); assert_eq!( - resolved.get(&WitnessName::from_str_unchecked("B")), + resolved.get(&TemplateProgramWitness::from_str_unchecked("B")), Some(&Value::u16(7)) ); // Entries the program does not declare are skipped (consistent with `WitnessValues::is_consistent`) let extra = UnresolvedValues::from_map(HashMap::from([( - WitnessName::from_str_unchecked("UNUSED"), + TemplateProgramWitness::from_str_unchecked("UNUSED"), UnresolvedValue::Untyped("1".to_string()), )])); let resolved: WitnessValues = extra.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&WitnessName::from_str_unchecked("UNUSED")), + resolved.get(&TemplateProgramWitness::from_str_unchecked("UNUSED")), None, "undeclared bare entries are ignored" ); let bad = UnresolvedValues::from_map(HashMap::from([( - WitnessName::from_str_unchecked("A"), + TemplateProgramWitness::from_str_unchecked("A"), UnresolvedValue::Untyped("not-a-number".to_string()), )])); let err = bad.resolve::(&witness_types).unwrap_err(); @@ -441,16 +444,16 @@ fn main() { let unresolved: UnresolvedValues = serde_json::from_str(s).unwrap(); let u32_ty = ResolvedType::parse_from_str("u32").unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - WitnessName::from_str_unchecked("A"), + TemplateProgramWitness::from_str_unchecked("A"), u32_ty, )])); let resolved: WitnessValues = unresolved.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&WitnessName::from_str_unchecked("A")), + resolved.get(&TemplateProgramWitness::from_str_unchecked("A")), Some(&Value::u32(42)) ); assert_eq!( - resolved.get(&WitnessName::from_str_unchecked("B")), + resolved.get(&TemplateProgramWitness::from_str_unchecked("B")), Some(&Value::u16(7)) ); @@ -468,18 +471,18 @@ fn main() { .collect(); let action_ty = ResolvedType::enumeration(EnumInfo::new(Arc::from("Action"), variants)); let witness_types = WitnessTypes::from(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), action_ty.clone(), )])); let resolve_one = |input: &str| -> Result { let unresolved = UnresolvedValues::from_map(HashMap::from([( - WitnessName::from_str_unchecked("ACTION"), + TemplateProgramWitness::from_str_unchecked("ACTION"), UnresolvedValue::Untyped(input.to_string()), )])); let resolved: WitnessValues = unresolved.resolve(&witness_types)?; Ok(resolved - .get(&WitnessName::from_str_unchecked("ACTION")) + .get(&TemplateProgramWitness::from_str_unchecked("ACTION")) .unwrap() .clone()) }; @@ -514,17 +517,20 @@ fn main() { ResolvedType::parse_from_str("u32").unwrap(), ]); let witness_types = WitnessTypes::from(HashMap::from([ - (WitnessName::from_str_unchecked("MAYBE"), option_ty), - (WitnessName::from_str_unchecked("PAIR"), tuple_ty), + ( + TemplateProgramWitness::from_str_unchecked("MAYBE"), + option_ty, + ), + (TemplateProgramWitness::from_str_unchecked("PAIR"), tuple_ty), ])); let unresolved = UnresolvedValues::from_map(HashMap::from([ ( - WitnessName::from_str_unchecked("MAYBE"), + TemplateProgramWitness::from_str_unchecked("MAYBE"), UnresolvedValue::Untyped("Some(Action::Cold)".to_string()), ), ( - WitnessName::from_str_unchecked("PAIR"), + TemplateProgramWitness::from_str_unchecked("PAIR"), UnresolvedValue::Untyped("(Action::Hot, 42)".to_string()), ), ])); @@ -532,11 +538,11 @@ fn main() { .resolve(&witness_types) .expect("variants resolve inside options and tuples"); let maybe = resolved - .get(&WitnessName::from_str_unchecked("MAYBE")) + .get(&TemplateProgramWitness::from_str_unchecked("MAYBE")) .unwrap(); assert_eq!("Some(Action::Cold)", &maybe.to_string()); let pair = resolved - .get(&WitnessName::from_str_unchecked("PAIR")) + .get(&TemplateProgramWitness::from_str_unchecked("PAIR")) .unwrap(); assert_eq!("(Action::Hot, 42)", &pair.to_string()); @@ -551,9 +557,18 @@ fn main() { #[test] fn witness_to_string() { let witness = WitnessValues::from(HashMap::from([ - (WitnessName::from_str_unchecked("A"), Value::u32(1)), - (WitnessName::from_str_unchecked("B"), Value::u32(2)), - (WitnessName::from_str_unchecked("C"), Value::u32(3)), + ( + TemplateProgramWitness::from_str_unchecked("A"), + Value::u32(1), + ), + ( + TemplateProgramWitness::from_str_unchecked("B"), + Value::u32(2), + ), + ( + TemplateProgramWitness::from_str_unchecked("C"), + Value::u32(3), + ), ])); let expected_string = r#"mod witness { const A: u32 = 1; From c8720ac34d1e3cb6600f3fc48adf86d3bacaeb94 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 20:25:32 +0000 Subject: [PATCH 6/8] witness: introduce WitnessNameToValuesMap There are two purposes to this trait: * Moves a bunch of function definitions out of macro-generated code and into default trait methods; this is easier to read and improves LSP integration * Introduces the ident_to_key method, which will allow Arguments and WitnessValues to both be created from the UnresolvedValues map. Currently this is done using a From bound, but when we extend WitnessNames to hold parameters as well as witness names, this From bound won't be sufficient. Also, change UnresolvedValues to be keyed by Identifier rather than by WitnessName. Until we resolve the name, we don't know whether we have a parameter name or a witness name, so until then, Identifier is the more correct type. --- src/compile/mod.rs | 6 +-- src/lib.rs | 9 ++-- src/named.rs | 2 +- src/serde.rs | 16 ++++---- src/witness.rs | 100 +++++++++++++++++++++++++++++---------------- 5 files changed, 82 insertions(+), 51 deletions(-) diff --git a/src/compile/mod.rs b/src/compile/mod.rs index d737faa4..9811204c 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -19,10 +19,10 @@ use crate::error::{Diagnostic, Error, Span, WithSpan}; use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; +use crate::template_program::TemplateProgramWitness; use crate::types::{StructuralType, TypeDeconstructible}; -use crate::value::StructuralValue; -use crate::witness::Arguments; -use crate::{TemplateProgramWitness, Value}; +use crate::value::{StructuralValue, Value}; +use crate::witness::{Arguments, WitnessNameToValueMap as _}; type ProgNode<'brand> = Arc>; diff --git a/src/lib.rs b/src/lib.rs index ce0fdbad..6d88d37e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,9 +50,9 @@ pub use crate::template_program::TemplateProgramWitness; pub use crate::types::ResolvedType; pub use crate::unstable::{UnstableFeature, UnstableFeatures}; pub use crate::value::Value; -#[cfg(feature = "serde")] -pub use crate::witness::UnresolvedValues; pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; +#[cfg(feature = "serde")] +pub use crate::witness::{UnresolvedValues, WitnessNameToValueMap}; /// The template of a SimplicityHL program. /// @@ -565,6 +565,7 @@ pub(crate) mod tests { use crate::source::CanonPath; use crate::str::Identifier; use crate::test_utils::TempWorkspace; + use crate::witness::WitnessNameToValueMap as _; use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use simplicity::BitMachine; @@ -1711,7 +1712,7 @@ fn main() { ); let err = compiled - .satisfy(WitnessValues::from(map.clone())) + .satisfy(WitnessValues::from_map(map)) .expect_err("satisfy must reject a missing witness"); assert!( err.contains('B'), @@ -1775,7 +1776,7 @@ fn main() { Cow::Owned(src.clone()), UnstableFeatures::all(), ) - .with_witness_values(WitnessValues::from(map)) + .with_witness_values(WitnessValues::from_map(map)) .assert_run_success(); } } diff --git a/src/named.rs b/src/named.rs index 93e394f2..13ae8d25 100644 --- a/src/named.rs +++ b/src/named.rs @@ -8,7 +8,7 @@ use simplicity::Cmr; use simplicity::{types, FailEntropy}; use crate::value::StructuralValue; -use crate::witness::WitnessValues; +use crate::witness::{WitnessNameToValueMap as _, WitnessValues}; use crate::TemplateProgramWitness; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] diff --git a/src/serde.rs b/src/serde.rs index c0756b1e..75fab266 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -7,7 +7,9 @@ use crate::parse::ParseFromStr as _; use crate::str::Identifier; use crate::types::ResolvedType; use crate::value::Value; -use crate::witness::{Arguments, UnresolvedValue, UnresolvedValues, WitnessValues}; +use crate::witness::{ + Arguments, UnresolvedValue, UnresolvedValues, WitnessNameToValueMap as _, WitnessValues, +}; use crate::{AbiMeta, Parameters, TemplateProgramWitness, WitnessTypes}; use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -54,7 +56,7 @@ impl<'de> Deserialize<'de> for WitnessValues { { deserializer .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) - .map(Self::from) + .map(Self::from_map) } } @@ -101,7 +103,7 @@ impl<'de> Deserialize<'de> for UnresolvedValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) + .deserialize_map(NamedMapVisitor::new(Identifier::shallow_clone)) .map(Self::from_map) } } @@ -166,7 +168,7 @@ impl<'de> Deserialize<'de> for Arguments { { deserializer .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) - .map(Self::from) + .map(Self::from_map) } } @@ -390,7 +392,7 @@ mod tests { vec![], ) .unwrap(); - let witness = WitnessValues::from(HashMap::from([( + let witness = WitnessValues::from_map(HashMap::from([( TemplateProgramWitness::from_str_unchecked("ACTION"), value, )])); @@ -439,7 +441,7 @@ mod tests { vec![Value::u32(42)], ) .unwrap(); - let witness = WitnessValues::from(HashMap::from([( + let witness = WitnessValues::from_map(HashMap::from([( TemplateProgramWitness::from_str_unchecked("ACTION"), value, )])); @@ -467,7 +469,7 @@ mod tests { let option_ty = ResolvedType::option(action_ty.clone()); let cold = Value::enum_variant(&action_ty, &Identifier::from_str_unchecked("Cold"), vec![]) .unwrap(); - let witness = WitnessValues::from(HashMap::from([( + let witness = WitnessValues::from_map(HashMap::from([( TemplateProgramWitness::from_str_unchecked("MAYBE"), Value::some(cold), )])); diff --git a/src/witness.rs b/src/witness.rs index b0df67b9..e115be23 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use crate::error::{Diagnostic, DiagnosticManager, Error, WithSpan}; use crate::parse::ParseFromStr; +use crate::str::Identifier; use crate::types::{AliasedType, ResolvedType}; use crate::value::Value; use crate::TemplateProgramWitness; @@ -35,34 +36,60 @@ macro_rules! impl_name_type_map { }; } -macro_rules! impl_name_value_map { - ($wrapper: ident, $module_name: expr) => { - impl $wrapper { - /// Access the inner map. - #[cfg(feature = "serde")] - pub(crate) fn as_inner(&self) -> &HashMap { - &self.0 - } +/// Trait describing a map from template-program "witness names" to values. +/// +/// In a templated program, witness names may refer to actual witnesses, or to parameters. This +/// trait allows manipulating such maps. +pub trait WitnessNameToValueMap { + /// Create a map from an `Arc`. + fn from_inner(map: Arc>) -> Self; - /// Get the value that is assigned to the given name. - pub fn get(&self, name: &TemplateProgramWitness) -> Option<&Value> { - self.0.get(name) - } + /// Access the inner map. + fn as_inner(&self) -> &Arc>; - /// Create an iterator over all name-value pairs. - pub fn iter(&self) -> impl Iterator { - self.0.iter() + /// Convert a bare identifier to a key. + fn ident_to_key(ident: &Identifier) -> TemplateProgramWitness; + + /// Create a map from a `HashMap`. + fn from_map(map: HashMap) -> Self + where + Self: Sized, + { + Self::from_inner(Arc::new(map)) + } + + /// Make a cheap copy of the map. + fn shallow_clone(&self) -> Self + where + Self: Sized, + { + Self::from_inner(Arc::clone(self.as_inner())) + } + + /// Get the value that is assigned to the given name. + fn get(&self, name: &TemplateProgramWitness) -> Option<&Value> { + self.as_inner().get(name) + } + + /// Create an iterator over all name-value pairs. + fn iter(&self) -> impl Iterator { + self.as_inner().iter() + } +} + +macro_rules! impl_name_value_map { + ($wrapper: ident, $module_name: expr) => { + impl WitnessNameToValueMap for $wrapper { + fn from_inner(map: Arc>) -> Self { + Self(map) } - /// Make a cheap copy of the map. - pub fn shallow_clone(&self) -> Self { - Self(Arc::clone(&self.0)) + fn as_inner(&self) -> &Arc> { + &self.0 } - } - impl From> for $wrapper { - fn from(value: HashMap) -> Self { - Self(Arc::new(value)) + fn ident_to_key(ident: &Identifier) -> TemplateProgramWitness { + TemplateProgramWitness::from_ident(ident) } } @@ -166,11 +193,11 @@ pub(crate) enum UnresolvedValue { /// [`WitnessValues`] or [`Arguments`]. #[cfg(feature = "serde")] #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct UnresolvedValues(HashMap); +pub struct UnresolvedValues(HashMap); #[cfg(feature = "serde")] impl UnresolvedValues { - pub(crate) fn from_map(map: HashMap) -> Self { + pub(crate) fn from_map(map: HashMap) -> Self { Self(map) } @@ -186,12 +213,13 @@ impl UnresolvedValues { /// A bare value string does not parse at the declared type. pub fn resolve(self, declared_types: &M) -> Result where - T: From>, + T: WitnessNameToValueMap, M: AsRef>, { let declared_types = declared_types.as_ref(); let mut map = HashMap::with_capacity(self.0.len()); for (name, unresolved) in self.0 { + let name = T::ident_to_key(&name); let value = match unresolved { UnresolvedValue::Typed(value) => value, UnresolvedValue::Untyped(s) => { @@ -205,7 +233,7 @@ impl UnresolvedValues { }; map.insert(name, value); } - Ok(T::from(map)) + Ok(T::from_map(map)) } } @@ -297,7 +325,7 @@ impl crate::ArbitraryOfType for Arguments { Value::arbitrary_of_type(u, parameter_ty)?, ); } - Ok(Self::from(map)) + Ok(Self::from_map(map)) } } @@ -334,7 +362,7 @@ mod tests { assert!(jet::is_zero_32(witness::A)); }"#; - let witness = WitnessValues::from(HashMap::from([( + let witness = WitnessValues::from_map(HashMap::from([( TemplateProgramWitness::from_str_unchecked("A"), Value::u16(42), )])); @@ -392,11 +420,11 @@ fn main() { let unresolved = UnresolvedValues::from_map(HashMap::from([ ( - TemplateProgramWitness::from_str_unchecked("A"), + Identifier::from_str_unchecked("A"), UnresolvedValue::Untyped("42".to_string()), ), ( - TemplateProgramWitness::from_str_unchecked("B"), + Identifier::from_str_unchecked("B"), UnresolvedValue::Typed(Value::u16(7)), ), ])); @@ -412,7 +440,7 @@ fn main() { // Entries the program does not declare are skipped (consistent with `WitnessValues::is_consistent`) let extra = UnresolvedValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("UNUSED"), + Identifier::from_str_unchecked("UNUSED"), UnresolvedValue::Untyped("1".to_string()), )])); let resolved: WitnessValues = extra.resolve(&witness_types).unwrap(); @@ -423,7 +451,7 @@ fn main() { ); let bad = UnresolvedValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("A"), + Identifier::from_str_unchecked("A"), UnresolvedValue::Untyped("not-a-number".to_string()), )])); let err = bad.resolve::(&witness_types).unwrap_err(); @@ -477,7 +505,7 @@ fn main() { let resolve_one = |input: &str| -> Result { let unresolved = UnresolvedValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + Identifier::from_str_unchecked("ACTION"), UnresolvedValue::Untyped(input.to_string()), )])); let resolved: WitnessValues = unresolved.resolve(&witness_types)?; @@ -526,11 +554,11 @@ fn main() { let unresolved = UnresolvedValues::from_map(HashMap::from([ ( - TemplateProgramWitness::from_str_unchecked("MAYBE"), + Identifier::from_str_unchecked("MAYBE"), UnresolvedValue::Untyped("Some(Action::Cold)".to_string()), ), ( - TemplateProgramWitness::from_str_unchecked("PAIR"), + Identifier::from_str_unchecked("PAIR"), UnresolvedValue::Untyped("(Action::Hot, 42)".to_string()), ), ])); @@ -556,7 +584,7 @@ fn main() { #[test] fn witness_to_string() { - let witness = WitnessValues::from(HashMap::from([ + let witness = WitnessValues::from_map(HashMap::from([ ( TemplateProgramWitness::from_str_unchecked("A"), Value::u32(1), From 1943dd849d7a36212e8011e2dc50fcd9d976228e Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 18:01:08 +0000 Subject: [PATCH 7/8] split TemplateProgramWitness into parameter and witness name variants After all our prep work this is fairly easy to do. --- src/lib.rs | 17 +++---- src/parse.rs | 4 +- src/serde.rs | 28 ++++++----- src/str.rs | 2 - src/template_program/witness.rs | 87 ++++++++++++++++++++++++++++----- src/witness.rs | 56 +++++++++------------ 6 files changed, 123 insertions(+), 71 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6d88d37e..ebc6e235 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1663,7 +1663,7 @@ fn main() { serde_json::from_str(r#"{ "ACT": "Action::Cold" }"#).unwrap(); let witness: WitnessValues = unresolved.resolve(compiled.witness_types()).unwrap(); assert!(witness - .get(&TemplateProgramWitness::from_str_unchecked("ACT")) + .get(&TemplateProgramWitness::witness_from_str("ACT")) .is_some()); TestCase::program_text_with_unstable(Cow::Borrowed(src), UnstableFeatures::all()) .with_witness_values(witness) @@ -1694,7 +1694,7 @@ fn main() { .unwrap(); let selector_ty = compiled .witness_types() - .get(&TemplateProgramWitness::from_str_unchecked("SELECTOR")) + .get(&TemplateProgramWitness::witness_from_str("SELECTOR")) .unwrap() .clone(); @@ -1702,14 +1702,11 @@ fn main() { // points must reject the omitted witness rather than zero-filling it. let mut map: HashMap = HashMap::new(); map.insert( - TemplateProgramWitness::from_str_unchecked("SELECTOR"), + TemplateProgramWitness::witness_from_str("SELECTOR"), Value::enum_variant(&selector_ty, &Identifier::from_str_unchecked("A"), vec![]) .unwrap(), ); - map.insert( - TemplateProgramWitness::from_str_unchecked("A"), - Value::u32(0), - ); + map.insert(TemplateProgramWitness::witness_from_str("A"), Value::u32(0)); let err = compiled .satisfy(WitnessValues::from_map(map)) @@ -1756,7 +1753,7 @@ fn main() { .unwrap(); let action_ty = compiled .witness_types() - .get(&TemplateProgramWitness::from_str_unchecked("ACT")) + .get(&TemplateProgramWitness::witness_from_str("ACT")) .expect("ACT is declared") .clone(); @@ -1766,9 +1763,9 @@ fn main() { .expect("declared variant"); let expected = u32::try_from((i + 1) * 10).unwrap(); let map = HashMap::from([ - (TemplateProgramWitness::from_str_unchecked("ACT"), action), + (TemplateProgramWitness::witness_from_str("ACT"), action), ( - TemplateProgramWitness::from_str_unchecked("EXPECTED"), + TemplateProgramWitness::witness_from_str("EXPECTED"), crate::value::ValueConstructible::u32(expected), ), ]); diff --git a/src/parse.rs b/src/parse.rs index 08efeeda..dc175d02 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2710,8 +2710,8 @@ impl SingleExpression { Token::DecLiteral(s) => SingleExpressionInner::Decimal(s), Token::HexLiteral(s) => SingleExpressionInner::Hexadecimal(s), Token::BinLiteral(s) => SingleExpressionInner::Binary(s), - Token::Witness(s) => SingleExpressionInner::Witness(TemplateProgramWitness::from_str_unchecked(s)), - Token::Param(s) => SingleExpressionInner::Parameter(TemplateProgramWitness::from_str_unchecked(s)), + Token::Witness(s) => SingleExpressionInner::Witness(TemplateProgramWitness::witness_from_str(s)), + Token::Param(s) => SingleExpressionInner::Parameter(TemplateProgramWitness::parameter_from_str(s)), }; // Enum variant construction: `Path::To::Enum::Variant(args..)`. diff --git a/src/serde.rs b/src/serde.rs index 75fab266..5e1d1643 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -55,7 +55,9 @@ impl<'de> Deserialize<'de> for WitnessValues { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) + .deserialize_map(NamedMapVisitor::new( + TemplateProgramWitness::witness_from_ident, + )) .map(Self::from_map) } } @@ -167,7 +169,9 @@ impl<'de> Deserialize<'de> for Arguments { D: Deserializer<'de>, { deserializer - .deserialize_map(NamedMapVisitor::new(TemplateProgramWitness::from_ident)) + .deserialize_map(NamedMapVisitor::new( + TemplateProgramWitness::parameter_from_ident, + )) .map(Self::from_map) } } @@ -359,19 +363,19 @@ mod tests { let action_ty = unit_enum("Action", &["Inherit", "ColdSpend"]); let witness_types = WitnessTypes::from(HashMap::from([ ( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), action_ty.clone(), ), ( - TemplateProgramWitness::from_str_unchecked("MAYBE"), + TemplateProgramWitness::witness_from_str("MAYBE"), ResolvedType::option(action_ty.clone()), ), ( - TemplateProgramWitness::from_str_unchecked("PAIR"), + TemplateProgramWitness::witness_from_str("PAIR"), ResolvedType::tuple([action_ty, unit_enum("Reaction", &["Fast", "Slow"])]), ), ( - TemplateProgramWitness::from_str_unchecked("PLAIN"), + TemplateProgramWitness::witness_from_str("PLAIN"), crate::parse::ParseFromStr::parse_from_str("u32").unwrap(), ), ])); @@ -393,7 +397,7 @@ mod tests { ) .unwrap(); let witness = WitnessValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), value, )])); @@ -405,7 +409,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), action_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); @@ -442,7 +446,7 @@ mod tests { ) .unwrap(); let witness = WitnessValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), value, )])); @@ -453,7 +457,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), action_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); @@ -470,7 +474,7 @@ mod tests { let cold = Value::enum_variant(&action_ty, &Identifier::from_str_unchecked("Cold"), vec![]) .unwrap(); let witness = WitnessValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("MAYBE"), + TemplateProgramWitness::witness_from_str("MAYBE"), Value::some(cold), )])); @@ -482,7 +486,7 @@ mod tests { let text = serde_json::to_string(&witness).unwrap(); let unresolved: UnresolvedValues = serde_json::from_str(&text).unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("MAYBE"), + TemplateProgramWitness::witness_from_str("MAYBE"), option_ty, )])); let round_tripped: WitnessValues = unresolved.resolve(&witness_types).unwrap(); diff --git a/src/str.rs b/src/str.rs index 5456139c..b4f1ee73 100644 --- a/src/str.rs +++ b/src/str.rs @@ -96,8 +96,6 @@ macro_rules! impl_arbitrary_lowercase_alpha { }; } -impl_arbitrary_lowercase_alpha!(crate::template_program::TemplateProgramWitness); - /// The name of a function. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FunctionName(Arc); diff --git a/src/template_program/witness.rs b/src/template_program/witness.rs index 02e88cba..d8d78e06 100644 --- a/src/template_program/witness.rs +++ b/src/template_program/witness.rs @@ -1,19 +1,61 @@ use core::fmt; use std::sync::Arc; -/// The name of a witness. +/// A witness node in a templated Simplicity program. +/// +/// Such a node may represent: +/// +/// * An actual witness, identified by its name in the source code, which will be preserved +/// as a witness node when instantiating. +/// * A program parameter, identified by name in the source code, which will be replaced by +/// code to compute the parameter's value when instantiating. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct TemplateProgramWitness(Arc); +pub struct TemplateProgramWitness { + inner: TemplateProgramWitnessInner, +} impl TemplateProgramWitness { - /// Create a [`TemplateProgramWitness`] from a bare string. - pub fn from_str_unchecked(s: &str) -> Self { - Self(Arc::from(s)) + /// Create a witness-name [`TemplateProgramWitness`] from a bare string. + /// + /// The string is **not** checked to determine whether it collides with a + /// language keyword, contains valid characters, or is otherwise valid. + pub fn witness_from_str>>(s: S) -> Self { + Self { + inner: TemplateProgramWitnessInner::Witness(s.into()), + } + } + + /// Create a parameter [`TemplateProgramWitness`] from a bare string. + /// + /// The string is **not** checked to determine whether it collides with a + /// language keyword, contains valid characters, or is otherwise valid. + pub fn parameter_from_str>>(s: S) -> Self { + Self { + inner: TemplateProgramWitnessInner::Parameter(s.into()), + } + } + + /// Creates a witness-name [`TemplateProgramWitness`] from an identifier. + pub fn witness_from_ident(ident: &crate::str::Identifier) -> Self { + Self { + inner: TemplateProgramWitnessInner::Witness(Arc::clone(ident.as_inner())), + } + } + + /// Creates a parameter [`TemplateProgramWitness`] from an identifier. + pub fn parameter_from_ident(ident: &crate::str::Identifier) -> Self { + Self { + inner: TemplateProgramWitnessInner::Parameter(Arc::clone(ident.as_inner())), + } } /// Access the inner string. pub fn as_inner(&self) -> &Arc { - &self.0 + use TemplateProgramWitnessInner as Inn; + match self.inner { + Inn::Witness(ref arc) => arc, + Inn::Parameter(ref arc) => arc, + } } /// Access the inner string. @@ -25,11 +67,6 @@ impl TemplateProgramWitness { pub fn shallow_clone(&self) -> Self { self.clone() } - - /// Creates a [`TemplateProgramWitness`] from an identifier. - pub fn from_ident(ident: &crate::str::Identifier) -> Self { - Self(Arc::clone(ident.as_inner())) - } } impl core::cmp::PartialEq for TemplateProgramWitness { @@ -61,3 +98,31 @@ impl AsRef for TemplateProgramWitness { self.as_inner().as_ref() } } + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +enum TemplateProgramWitnessInner { + /// An actual witness value. + Witness(Arc), + /// A program paramater. + Parameter(Arc), +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for TemplateProgramWitness { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let len = u.int_in_range(1..=10)?; + let mut string = String::with_capacity(len); + for _ in 0..len { + let offset = u.int_in_range(0..=25)?; + string.push((b'a' + offset) as char) + } + if crate::lexer::is_keyword(string.as_str()) { + string.push('_'); + } + if bool::arbitrary(u)? { + Ok(Self::witness_from_str(string.as_str())) + } else { + Ok(Self::parameter_from_str(string.as_str())) + } + } +} diff --git a/src/witness.rs b/src/witness.rs index e115be23..994f02c4 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -78,7 +78,7 @@ pub trait WitnessNameToValueMap { } macro_rules! impl_name_value_map { - ($wrapper: ident, $module_name: expr) => { + ($wrapper: ident, $module_name: expr, $ident_fn:ident) => { impl WitnessNameToValueMap for $wrapper { fn from_inner(map: Arc>) -> Self { Self(map) @@ -89,7 +89,7 @@ macro_rules! impl_name_value_map { } fn ident_to_key(ident: &Identifier) -> TemplateProgramWitness { - TemplateProgramWitness::from_ident(ident) + TemplateProgramWitness::$ident_fn(ident) } } @@ -135,7 +135,7 @@ impl AsRef> for WitnessTypes { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct WitnessValues(Arc>); -impl_name_value_map!(WitnessValues, "witness"); +impl_name_value_map!(WitnessValues, "witness", witness_from_ident); impl WitnessValues { /// Check if the witness values are consistent with the declared witness types. @@ -278,7 +278,7 @@ impl AsRef> for Parameters { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Arguments(Arc>); -impl_name_value_map!(Arguments, "param"); +impl_name_value_map!(Arguments, "param", parameter_from_ident); impl Arguments { /// Check if the arguments are consistent with the given parameters. @@ -363,7 +363,7 @@ mod tests { }"#; let witness = WitnessValues::from_map(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("A"), + TemplateProgramWitness::witness_from_str("A"), Value::u16(42), )])); match SatisfiedProgram::new( @@ -412,10 +412,10 @@ fn main() { let sig_ty = ResolvedType::parse_from_str("Signature").unwrap(); let witness_types = WitnessTypes::from(HashMap::from([ ( - TemplateProgramWitness::from_str_unchecked("A"), + TemplateProgramWitness::witness_from_str("A"), u32_ty.clone(), ), - (TemplateProgramWitness::from_str_unchecked("SIG"), sig_ty), + (TemplateProgramWitness::witness_from_str("SIG"), sig_ty), ])); let unresolved = UnresolvedValues::from_map(HashMap::from([ @@ -430,11 +430,11 @@ fn main() { ])); let resolved: WitnessValues = unresolved.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&TemplateProgramWitness::from_str_unchecked("A")), + resolved.get(&TemplateProgramWitness::witness_from_str("A")), Some(&Value::u32(42)) ); assert_eq!( - resolved.get(&TemplateProgramWitness::from_str_unchecked("B")), + resolved.get(&TemplateProgramWitness::witness_from_str("B")), Some(&Value::u16(7)) ); @@ -445,7 +445,7 @@ fn main() { )])); let resolved: WitnessValues = extra.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&TemplateProgramWitness::from_str_unchecked("UNUSED")), + resolved.get(&TemplateProgramWitness::witness_from_str("UNUSED")), None, "undeclared bare entries are ignored" ); @@ -472,16 +472,16 @@ fn main() { let unresolved: UnresolvedValues = serde_json::from_str(s).unwrap(); let u32_ty = ResolvedType::parse_from_str("u32").unwrap(); let witness_types = WitnessTypes::from(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("A"), + TemplateProgramWitness::witness_from_str("A"), u32_ty, )])); let resolved: WitnessValues = unresolved.resolve(&witness_types).unwrap(); assert_eq!( - resolved.get(&TemplateProgramWitness::from_str_unchecked("A")), + resolved.get(&TemplateProgramWitness::witness_from_str("A")), Some(&Value::u32(42)) ); assert_eq!( - resolved.get(&TemplateProgramWitness::from_str_unchecked("B")), + resolved.get(&TemplateProgramWitness::witness_from_str("B")), Some(&Value::u16(7)) ); @@ -499,7 +499,7 @@ fn main() { .collect(); let action_ty = ResolvedType::enumeration(EnumInfo::new(Arc::from("Action"), variants)); let witness_types = WitnessTypes::from(HashMap::from([( - TemplateProgramWitness::from_str_unchecked("ACTION"), + TemplateProgramWitness::witness_from_str("ACTION"), action_ty.clone(), )])); @@ -510,7 +510,7 @@ fn main() { )])); let resolved: WitnessValues = unresolved.resolve(&witness_types)?; Ok(resolved - .get(&TemplateProgramWitness::from_str_unchecked("ACTION")) + .get(&TemplateProgramWitness::witness_from_str("ACTION")) .unwrap() .clone()) }; @@ -545,11 +545,8 @@ fn main() { ResolvedType::parse_from_str("u32").unwrap(), ]); let witness_types = WitnessTypes::from(HashMap::from([ - ( - TemplateProgramWitness::from_str_unchecked("MAYBE"), - option_ty, - ), - (TemplateProgramWitness::from_str_unchecked("PAIR"), tuple_ty), + (TemplateProgramWitness::witness_from_str("MAYBE"), option_ty), + (TemplateProgramWitness::witness_from_str("PAIR"), tuple_ty), ])); let unresolved = UnresolvedValues::from_map(HashMap::from([ @@ -566,11 +563,11 @@ fn main() { .resolve(&witness_types) .expect("variants resolve inside options and tuples"); let maybe = resolved - .get(&TemplateProgramWitness::from_str_unchecked("MAYBE")) + .get(&TemplateProgramWitness::witness_from_str("MAYBE")) .unwrap(); assert_eq!("Some(Action::Cold)", &maybe.to_string()); let pair = resolved - .get(&TemplateProgramWitness::from_str_unchecked("PAIR")) + .get(&TemplateProgramWitness::witness_from_str("PAIR")) .unwrap(); assert_eq!("(Action::Hot, 42)", &pair.to_string()); @@ -585,18 +582,9 @@ fn main() { #[test] fn witness_to_string() { let witness = WitnessValues::from_map(HashMap::from([ - ( - TemplateProgramWitness::from_str_unchecked("A"), - Value::u32(1), - ), - ( - TemplateProgramWitness::from_str_unchecked("B"), - Value::u32(2), - ), - ( - TemplateProgramWitness::from_str_unchecked("C"), - Value::u32(3), - ), + (TemplateProgramWitness::witness_from_str("A"), Value::u32(1)), + (TemplateProgramWitness::witness_from_str("B"), Value::u32(2)), + (TemplateProgramWitness::witness_from_str("C"), Value::u32(3)), ])); let expected_string = r#"mod witness { const A: u32 = 1; From 9942a5a7c8ea0b8056d5c98a944f93fde5bfea90 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 12 Aug 2026 17:31:08 +0000 Subject: [PATCH 8/8] template_program: introduce TemplateProgram type Currently this doesn't do anything. It just adds an extra step to compilation. The next commit will rearrange things such that instantiation happens -after- compilation. --- fuzz/fuzz_targets/compile_parse_tree.rs | 11 +++++--- src/compile/mod.rs | 11 +++----- src/lib.rs | 11 ++++---- src/template_program/mod.rs | 36 +++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/fuzz/fuzz_targets/compile_parse_tree.rs b/fuzz/fuzz_targets/compile_parse_tree.rs index a4e89c8c..f5b6dfb9 100644 --- a/fuzz/fuzz_targets/compile_parse_tree.rs +++ b/fuzz/fuzz_targets/compile_parse_tree.rs @@ -5,7 +5,7 @@ fn do_test(data: &[u8]) { use arbitrary::Arbitrary; use simplicityhl::ast::ElementsJetHinter; - use simplicityhl::{ast, named, parse, ArbitraryOfType, Arguments}; + use simplicityhl::{ast, named, parse, ArbitraryOfType, Arguments, WitnessNameToValueMap as _}; let mut u = arbitrary::Unstructured::new(data); let parse_program = match parse::Program::arbitrary(&mut u) { @@ -22,8 +22,13 @@ fn do_test(data: &[u8]) { Err(..) => return, }; let simplicity_named_construct = ast_program - .compile(arguments, false, Box::new(ElementsJetHinter::new())) - .expect("AST should compile with given arguments"); + .compile( + arguments.shallow_clone(), + false, + Box::new(ElementsJetHinter::new()), + ) + .expect("AST should compile with given arguments") + .instantiate(arguments); let _simplicity_commit = named::forget_names(&simplicity_named_construct); } diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 9811204c..02ac4468 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -19,7 +19,7 @@ use crate::error::{Diagnostic, Error, Span, WithSpan}; use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; -use crate::template_program::TemplateProgramWitness; +use crate::template_program::{TemplateProgram, TemplateProgramWitness}; use crate::types::{StructuralType, TypeDeconstructible}; use crate::value::{StructuralValue, Value}; use crate::witness::{Arguments, WitnessNameToValueMap as _}; @@ -265,7 +265,7 @@ impl Program { arguments: Arguments, include_debug_symbols: bool, jet_hinter: Box, - ) -> Result, Diagnostic> { + ) -> Result { types::Context::with_context(|ctx| { let mut scope = Scope::new( ctx, @@ -275,11 +275,8 @@ impl Program { jet_hinter, ); - 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) + let construct = self.main().compile(&mut scope).map(PairBuilder::build)?; + Ok(TemplateProgram::from_construct_node(&construct)) }) } } diff --git a/src/lib.rs b/src/lib.rs index ebc6e235..cc50f758 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,11 +46,13 @@ use crate::error::DiagnosticManager; use crate::parse::ParseFromStrWithErrors; use crate::resolution::DependencyMap; use crate::source::CanonSourceFile; -pub use crate::template_program::TemplateProgramWitness; +pub use crate::template_program::{TemplateProgram, TemplateProgramWitness}; pub use crate::types::ResolvedType; pub use crate::unstable::{UnstableFeature, UnstableFeatures}; pub use crate::value::Value; -pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; +pub use crate::witness::{ + Arguments, Parameters, WitnessNameToValueMap as _, WitnessTypes, WitnessValues, +}; #[cfg(feature = "serde")] pub use crate::witness::{UnresolvedValues, WitnessNameToValueMap}; @@ -214,14 +216,14 @@ impl TemplateAst { } let commit = self.simfony.compile( - arguments, + arguments.shallow_clone(), include_debug_symbols, self.jet_hinter.clone_box(), )?; Ok(CompiledProgram { debug_symbols: self.simfony.debug_symbols(self.file.as_ref()), - simplicity: commit, + simplicity: commit.instantiate(arguments), witness_types: self.simfony.witness_types().shallow_clone(), parameter_types: self.simfony.parameters().shallow_clone(), }) @@ -565,7 +567,6 @@ pub(crate) mod tests { use crate::source::CanonPath; use crate::str::Identifier; use crate::test_utils::TempWorkspace; - use crate::witness::WitnessNameToValueMap as _; use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use simplicity::BitMachine; diff --git a/src/template_program/mod.rs b/src/template_program/mod.rs index b70de719..59e0156b 100644 --- a/src/template_program/mod.rs +++ b/src/template_program/mod.rs @@ -1,3 +1,39 @@ mod witness; +use std::sync::Arc; + +use crate::named; +use crate::Arguments; + pub use self::witness::TemplateProgramWitness; + +/// A program which has been compiled to Simplicity, except that its parameters have not been +/// resolved. +pub struct TemplateProgram { + inner: Arc, +} + +impl TemplateProgram { + /// Creates a [`TemplateProgram`] from a [`named::ConstructNode`] output from the compiler. + /// + /// # Panics + /// + /// Panics if given a program that doesn't typecheck -- that is, which has infinitely sized + /// types or whose source and target types are non-unit and non-free. + pub(crate) fn from_construct_node<'brand>(node: &named::ConstructNode<'brand>) -> Self { + Self { + inner: named::finalize_types(node) + .expect("SimplicityHL types are 1->1 and have finite types by construction"), + } + } + + /// Instantiates the templated program with the given arguments. + /// + /// ## Precondition + /// + /// The supplied `arguments` are consistent with the program's parameters. + /// Call [`Arguments::is_consistent`] before calling this method! + pub fn instantiate(self, _: Arguments) -> Arc { + self.inner + } +}