From 7f001df65a172f68a7a0f1a2fa767cf8f339297c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 21:21:38 -0400 Subject: [PATCH 01/16] Initial rewrite of clvm-derive --- .vscode/settings.json | 3 + crates/chia-puzzles/src/proof.rs | 6 +- crates/chia-puzzles/src/puzzles/offer.rs | 12 +- crates/chia-puzzles/src/puzzles/singleton.rs | 3 +- crates/clvm-derive/src/from_clvm.rs | 587 ++++++++++-------- crates/clvm-derive/src/helpers.rs | 170 ++--- crates/clvm-derive/src/hide_values.rs | 45 ++ crates/clvm-derive/src/lib.rs | 25 +- crates/clvm-derive/src/macros.rs | 41 -- crates/clvm-derive/src/parser.rs | 28 + crates/clvm-derive/src/parser/attributes.rs | 188 ++++++ crates/clvm-derive/src/parser/enum_info.rs | 55 ++ crates/clvm-derive/src/parser/field_info.rs | 109 ++++ crates/clvm-derive/src/parser/struct_info.rs | 68 ++ crates/clvm-derive/src/parser/variant_info.rs | 76 +++ crates/clvm-derive/src/to_clvm.rs | 408 ++++++------ crates/clvm-traits/src/clvm_decoder.rs | 29 + crates/clvm-traits/src/clvm_encoder.rs | 25 +- crates/clvm-traits/src/lib.rs | 60 +- 19 files changed, 1343 insertions(+), 595 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 crates/clvm-derive/src/hide_values.rs delete mode 100644 crates/clvm-derive/src/macros.rs create mode 100644 crates/clvm-derive/src/parser.rs create mode 100644 crates/clvm-derive/src/parser/attributes.rs create mode 100644 crates/clvm-derive/src/parser/enum_info.rs create mode 100644 crates/clvm-derive/src/parser/field_info.rs create mode 100644 crates/clvm-derive/src/parser/struct_info.rs create mode 100644 crates/clvm-derive/src/parser/variant_info.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..058f815b1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.checkOnSave": true +} diff --git a/crates/chia-puzzles/src/proof.rs b/crates/chia-puzzles/src/proof.rs index 27ab04938..6d891e767 100644 --- a/crates/chia-puzzles/src/proof.rs +++ b/crates/chia-puzzles/src/proof.rs @@ -3,10 +3,10 @@ use clvm_traits::{FromClvm, ToClvm}; #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(untagged, tuple)] +#[clvm(untagged, list)] pub enum Proof { - Lineage(LineageProof), - Eve(EveProof), + Lineage(#[clvm(rest)] LineageProof), + Eve(#[clvm(rest)] EveProof), } #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] diff --git a/crates/chia-puzzles/src/puzzles/offer.rs b/crates/chia-puzzles/src/puzzles/offer.rs index 1c2843ceb..a760a432d 100644 --- a/crates/chia-puzzles/src/puzzles/offer.rs +++ b/crates/chia-puzzles/src/puzzles/offer.rs @@ -5,25 +5,27 @@ use hex_literal::hex; #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(tuple)] +#[clvm(list)] pub struct SettlementPaymentsSolution { + #[clvm(rest)] pub notarized_payments: Vec, } #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(tuple)] +#[clvm(list)] pub struct NotarizedPayment { pub nonce: Bytes32, + #[clvm(rest)] pub payments: Vec, } #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(tuple, untagged)] +#[clvm(untagged, list)] pub enum Payment { - WithoutMemos(PaymentWithoutMemos), - WithMemos(PaymentWithMemos), + WithoutMemos(#[clvm(rest)] PaymentWithoutMemos), + WithMemos(#[clvm(rest)] PaymentWithMemos), } #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] diff --git a/crates/chia-puzzles/src/puzzles/singleton.rs b/crates/chia-puzzles/src/puzzles/singleton.rs index 6137d051b..1a12c684a 100644 --- a/crates/chia-puzzles/src/puzzles/singleton.rs +++ b/crates/chia-puzzles/src/puzzles/singleton.rs @@ -37,10 +37,11 @@ impl SingletonArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(tuple)] +#[clvm(list)] pub struct SingletonStruct { pub mod_hash: Bytes32, pub launcher_id: Bytes32, + #[clvm(rest)] pub launcher_puzzle_hash: Bytes32, } diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index cf3ef9b4a..2a596470b 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -1,331 +1,404 @@ -use proc_macro2::{Ident, Literal, Span, TokenStream}; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{ - parse_quote, spanned::Spanned, Data, DeriveInput, Expr, Fields, FieldsNamed, FieldsUnnamed, - GenericParam, Type, -}; +use syn::{parse_quote, DeriveInput, GenericParam, Ident}; use crate::{ - helpers::{add_trait_bounds, parse_clvm_attr, parse_int_repr, Repr}, - macros::{repr_macros, Macros}, + crate_name, + helpers::{add_trait_bounds, option_type, variant_discriminants, DiscriminantInfo}, + parser::{parse, EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind}, }; -#[derive(Default)] -struct FieldInfo { - field_types: Vec, - field_names: Vec, - initializer: TokenStream, +pub fn from_clvm(ast: DeriveInput) -> TokenStream { + let parsed = parse("FromClvm", &ast); + let node_name = Ident::new("Node", Span::mixed_site()); + + match parsed { + ParsedInfo::Struct(struct_info) => impl_for_struct(ast, struct_info, node_name), + ParsedInfo::Enum(enum_info) => impl_for_enum(ast, enum_info, node_name), + } } -struct VariantInfo { - name: Ident, - discriminant: Expr, - field_info: FieldInfo, - macros: Macros, +struct ParsedFields { + decoded_names: Vec, + decoded_values: Vec, + body: TokenStream, } -pub fn from_clvm(ast: DeriveInput) -> TokenStream { - let clvm_attr = parse_clvm_attr(&ast.attrs); - let crate_name = quote!(clvm_traits); +fn field_parser_fn_body( + crate_name: &Ident, + node_name: &Ident, + fields: &[FieldInfo], + repr: Repr, +) -> ParsedFields { + let mut body = TokenStream::new(); + + // Generate temporary names for the fields, used in the function body. + let temp_names: Vec = (0..fields.len()) + .map(|i| Ident::new(&format!("field_{}", i), Span::mixed_site())) + .collect(); - match &ast.data { - Data::Struct(data_struct) => { - if clvm_attr.untagged { - panic!("cannot use `untagged` on a struct"); - } - let macros = repr_macros(&crate_name, clvm_attr.expect_repr()); - let field_info = fields(&data_struct.fields); - impl_for_struct(&crate_name, &ast, ¯os, &field_info) - } - Data::Enum(data_enum) => { - if !clvm_attr.untagged && clvm_attr.repr == Some(Repr::Curry) { - panic!("cannot use `curry` on a tagged enum, since unlike other representations, each argument is wrapped"); - } + let decode_next = match repr { + Repr::Atom => unreachable!(), + // Decode `(A . B)` pairs for lists. + Repr::List => quote!(decode_pair), + // Decode `(c (q . A) B)` pairs for curried arguments. + Repr::Curry => quote!(decode_curried_arg), + }; - let mut next_discriminant: Expr = parse_quote!(0); - let mut variants = Vec::new(); + let mut optional = false; - for variant in data_enum.variants.iter() { - let field_info = fields(&variant.fields); - let variant_clvm_attr = parse_clvm_attr(&variant.attrs); + for (i, field) in fields.iter().enumerate() { + let ident = &temp_names[i]; - if variant_clvm_attr.untagged { - panic!("cannot use `untagged` on an enum variant"); - } + if field.rest { + // Consume the rest of the `node` as the final argument. + body.extend(quote! { + let #ident = node; + }); + } else if field.optional_with_default.is_some() { + // We need to start tracking the `node` as being optional going forward. + if !optional { + body.extend(quote! { + let optional_node = Some(decoder.clone_node(&node)); + }); + } - let repr = variant_clvm_attr - .repr - .unwrap_or_else(|| clvm_attr.expect_repr()); - if !clvm_attr.untagged && repr == Repr::Curry { - panic!("cannot use `curry` on a tagged enum variant, since unlike other representations, each argument is wrapped"); - } + optional = true; - let macros = repr_macros(&crate_name, repr); - let variant_info = VariantInfo { - name: variant.ident.clone(), - discriminant: variant - .discriminant - .as_ref() - .map(|(_, discriminant)| { - next_discriminant = parse_quote!(#discriminant + 1); - discriminant.clone() - }) - .unwrap_or_else(|| { - let discriminant = next_discriminant.clone(); - next_discriminant = parse_quote!(#next_discriminant + 1); - discriminant - }), - field_info, - macros, - }; - variants.push(variant_info); - } + // Decode the pair and assign the `Option` value to the field. + body.extend(quote! { + let (#ident, optional_node) = optional_node.and_then(|node| decoder.#decode_next(&node).ok()) + .map(|(a, b)| (Some(a), Some(b))).unwrap_or((None, None)); - if clvm_attr.untagged { - impl_for_untagged_enum(&crate_name, &ast, &variants) - } else { - let int_repr = parse_int_repr(&ast.attrs); - impl_for_enum(&crate_name, &ast, &int_repr, &variants) - } + if let Some(new_node) = optional_node { + node = new_node; + } + }); + } else { + // Otherwise, simply decode a pair and return an error if it fails. + body.extend(quote! { + let (#ident, new_node) = decoder.#decode_next(&node)?; + node = new_node; + }); } - Data::Union(_union) => panic!("cannot derive `FromClvm` for a union"), } -} -fn fields(fields: &Fields) -> FieldInfo { - match fields { - Fields::Named(fields) => named_fields(fields), - Fields::Unnamed(fields) => unnamed_fields(fields), - Fields::Unit => FieldInfo::default(), + if !fields.last().map(|field| field.rest).unwrap_or(false) { + body.extend(check_rest_value(crate_name, repr)); } -} -fn named_fields(fields: &FieldsNamed) -> FieldInfo { - let fields = &fields.named; - let field_types = fields.iter().map(|field| field.ty.clone()).collect(); - let field_names: Vec = fields - .iter() - .map(|field| field.ident.clone().unwrap()) - .collect(); - let initializer = quote!({ #( #field_names, )* }); - - FieldInfo { - field_types, - field_names, - initializer, - } -} + let mut decoded_names = Vec::new(); + let mut decoded_values = Vec::new(); -fn unnamed_fields(fields: &FieldsUnnamed) -> FieldInfo { - let fields = &fields.unnamed; - let field_types = fields.iter().map(|field| field.ty.clone()).collect(); - let field_names: Vec = fields - .iter() - .enumerate() - .map(|(i, field)| Ident::new(&format!("field_{i}"), field.span())) - .collect(); - let initializer = quote!(( #( #field_names, )* )); + for (i, field) in fields.iter().enumerate() { + let ident = &temp_names[i]; + let mut ty = &field.ty; - FieldInfo { - field_types, - field_names, - initializer, - } -} + // If the field is optional, we need to unwrap the `Option` type into just `T`. + // This is because it's still important to validate the value is correct. + // We only set it to `None` if it's non-existent, not if parsing fails. + if field.optional_with_default == Some(None) { + ty = option_type(ty).expect("expected `Option` type for `optional` field"); + } -fn impl_for_struct( - crate_name: &TokenStream, - ast: &DeriveInput, - Macros { - match_macro, - destructure_macro, - .. - }: &Macros, - FieldInfo { - field_types, - field_names, - initializer, - }: &FieldInfo, -) -> TokenStream { - let node_name = Ident::new("Node", Span::mixed_site()); + // This handles the actual decoding of the field's value. + let mut decoded_value = quote! { + <#ty as #crate_name::FromClvm<#node_name>>::from_clvm(decoder, #ident) + }; - let body = quote! { - let #destructure_macro!( #( #field_names, )* ) = - <#match_macro!( #( #field_types ),* ) - as #crate_name::FromClvm<#node_name>>::from_clvm(decoder, node)?; - Ok(Self #initializer) - }; + if let Some(default) = &field.optional_with_default { + if let Some(default) = default { + // If there's a default value, we need to use it instead if the field isn't present. + decoded_value = quote! { + #ident.map(|#ident| #decoded_value).unwrap_or(Ok(#default))? + }; + } else { + // Otherwise, we can just return `None` if the field isn't present. + // And wrap it in `Some` if it is. + decoded_value = quote! { + match #ident.map(|#ident| -> ::std::result::Result<#ty, #crate_name::FromClvmError> { + #decoded_value + }) { + Some(value) => value.map(Some), + None => Ok(None), + }? + }; + } + } else { + // If the field isn't optional, we can simply return any parsing errors early for this field. + decoded_value = quote!(#decoded_value?); + } - generate_from_clvm(crate_name, ast, &node_name, &body) -} + let field_ident = field.ident.clone(); -fn impl_for_enum( - crate_name: &TokenStream, - ast: &DeriveInput, - int_repr: &Ident, - variants: &[VariantInfo], -) -> TokenStream { - let type_name = Literal::string(&ast.ident.to_string()); - let node_name = Ident::new("Node", Span::mixed_site()); + if let Some(value) = &field.hidden_with_value { + // If the field is hidden, we need to check that the value is correct before continuing. + body.extend(quote! { + let value: #ty = #value; - let mut discriminant_definitions = Vec::new(); - let mut has_initializers = false; - - let variant_bodies = variants - .iter() - .enumerate() - .map(|(i, variant_info)| { - let VariantInfo { - name, - discriminant, - field_info, - macros, - } = variant_info; - - let FieldInfo { - field_types, - field_names, - initializer, - } = field_info; - - let Macros { - match_macro, - destructure_macro, - .. - } = macros; - - let discriminant_ident = Ident::new(&format!("VALUE_{}", i), Span::mixed_site()); - discriminant_definitions.push(quote! { - const #discriminant_ident: #int_repr = #discriminant; + if #decoded_value != value { + return Err(#crate_name::FromClvmError::Custom( + format!( + "hidden field `{}` has an incorrect value", + stringify!(#field_ident), + ) + )); + } }); + } else { + // Otherwise, we can include the field name and decoded value in the constructor. + decoded_names.push(field_ident); + decoded_values.push(decoded_value); + } + } + + ParsedFields { + decoded_names, + decoded_values, + body, + } +} - if initializer.is_empty() { - quote! { - #discriminant_ident => { - Ok(Self::#name) - } +fn check_rest_value(crate_name: &Ident, repr: Repr) -> TokenStream { + match repr { + Repr::Atom => unreachable!(), + Repr::List => { + // If the last field is not `rest`, we need to check that the `node` is nil. + // If it's not nil, it's not a proper list, and we should return an error. + quote! { + let atom = decoder.decode_atom(&node)?; + let atom_ref = atom.as_ref(); + if !atom_ref.is_empty() { + return Err(#crate_name::FromClvmError::WrongAtomLength { + expected: 0, + found: atom_ref.len(), + }); } - } else { - has_initializers = true; - quote! { - #discriminant_ident => { - let #destructure_macro!( #( #field_names ),* ) = - <#match_macro!( #( #field_types ),* ) - as #crate_name::FromClvm<#node_name>>::from_clvm(decoder, args.0)?; - Ok(Self::#name #initializer) - } + } + } + Repr::Curry => { + // Do the same for curried arguments, but check for a terminator of `1` instead. + // This is because `1` points to the all of the arguments in the program's environment. + quote! { + let atom = decoder.decode_atom(&node)?; + let atom_ref = atom.as_ref(); + if atom_ref.len() != 1 { + return Err(#crate_name::FromClvmError::WrongAtomLength { + expected: 1, + found: atom_ref.len(), + }); + } + if atom_ref != &[1] { + return Err(#crate_name::FromClvmError::Custom( + "expected curried argument terminator of 1".to_string(), + )); } } - }) - .collect::>(); + } + } +} - let parse_value = if has_initializers { - quote! { - let (value, args) = <(#int_repr, #crate_name::Raw<#node_name>)>::from_clvm(decoder, node)?; +fn impl_for_struct(ast: DeriveInput, struct_info: StructInfo, node_name: Ident) -> TokenStream { + let crate_name = crate_name(struct_info.crate_name); + + let ParsedFields { + decoded_names, + decoded_values, + mut body, + } = field_parser_fn_body( + &crate_name, + &node_name, + &struct_info.fields, + struct_info.repr, + ); + + // Generate the constructor for the return value, if all parsing was successful. + match struct_info.kind { + StructKind::Unit => { + body.extend(quote!(Ok(Self))); } - } else { - quote! { - let value = #int_repr::from_clvm(decoder, node)?; + StructKind::Unnamed => { + body.extend(quote! { + Ok(Self ( #( #decoded_values, )* )) + }); } - }; + StructKind::Named => { + body.extend(quote! { + Ok(Self { + #( #decoded_names: #decoded_values, )* + }) + }); + } + } + + trait_impl(ast, crate_name, node_name, body) +} + +fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> TokenStream { + let crate_name = crate_name(enum_info.crate_name.clone()); + + let mut body = TokenStream::new(); - let body = quote! { - #parse_value + if enum_info.is_untagged { + let variant_parsers = enum_variant_parsers(&crate_name, &node_name, &enum_info); - #( #discriminant_definitions )* + // If the enum is untagged, we need to try each variant parser until one succeeds. + for parser in variant_parsers { + body.extend(quote! { + if let Ok(value) = (#parser)(decoder.clone_node(&node)) { + return Ok(value); + } + }); + } - match value { - #( #variant_bodies )* - _ => Err(#crate_name::FromClvmError::Custom( - format!("failed to match any enum variants of `{}`", #type_name) + body.extend(quote! { + Err(#crate_name::FromClvmError::Custom( + "failed to parse any enum variant".to_string(), )) + }); + } else { + let DiscriminantInfo { + discriminant_type, + discriminant_consts, + discriminant_names, + variant_names, + } = variant_discriminants(&enum_info); + + if enum_info.default_repr == Repr::Atom { + // If the enum is represented as an atom, we can simply decode the discriminant and match against it. + body.extend(quote! { + let discriminant = <#discriminant_type as #crate_name::FromClvm<#node_name>>::from_clvm( + decoder, + node, + )?; + + #( #discriminant_consts )* + + match discriminant { + #( #discriminant_names => Ok(Self::#variant_names), )* + _ => Err(#crate_name::FromClvmError::Custom( + format!("unknown enum variant discriminant: {}", discriminant), + )), + } + }); + } else { + let variant_parsers = enum_variant_parsers(&crate_name, &node_name, &enum_info); + + let decode_next = match enum_info.default_repr { + Repr::Atom => unreachable!(), + // Decode `(A . B)` pairs for lists. + Repr::List => quote!(decode_pair), + // Decode `(c (q . A) B)` pairs for curried arguments. + Repr::Curry => quote!(decode_curried_arg), + }; + + // If the enum is represented as a list or curried argument, we need to decode the discriminant first. + // Then we can match against it to determine which variant to parse. + body.extend(quote! { + let (discriminant_node, node) = decoder.#decode_next(&node)?; + + let discriminant = <#discriminant_type as #crate_name::FromClvm<#node_name>>::from_clvm( + decoder, + discriminant_node, + )?; + + #( #discriminant_consts )* + + match discriminant { + #( #discriminant_names => (#variant_parsers)(node), )* + _ => Err(#crate_name::FromClvmError::Custom( + format!("unknown enum variant discriminant: {}", discriminant), + )), + } + }); } - }; + } - generate_from_clvm(crate_name, ast, &node_name, &body) + trait_impl(ast, crate_name, node_name, body) } -fn impl_for_untagged_enum( - crate_name: &TokenStream, - ast: &DeriveInput, - variants: &[VariantInfo], -) -> TokenStream { - let type_name = Literal::string(&ast.ident.to_string()); - let node_name = Ident::new("Node", Span::mixed_site()); - - let variant_bodies = variants - .iter() - .map(|variant_info| { - let VariantInfo { - name, - field_info, - macros, - .. - } = variant_info; - - let FieldInfo { - field_types, - field_names, - initializer, - } = field_info; - - let Macros { - match_macro, - destructure_macro, - .. - } = macros; - - quote! { - if let Ok(#destructure_macro!( #( #field_names ),* )) = - <#match_macro!( #( #field_types ),* ) - as #crate_name::FromClvm<#node_name>>::from_clvm(decoder, decoder.clone_node(&node)) - { - return Ok(Self::#name #initializer); - } +fn enum_variant_parsers( + crate_name: &Ident, + node_name: &Ident, + enum_info: &EnumInfo, +) -> Vec { + let mut variant_parsers = Vec::new(); + + for variant in enum_info.variants.iter() { + let variant_name = &variant.name; + let repr = variant.repr.unwrap_or(enum_info.default_repr); + + let ParsedFields { + decoded_names, + decoded_values, + mut body, + } = field_parser_fn_body(crate_name, node_name, &variant.fields, repr); + + match variant.kind { + VariantKind::Unit => { + body.extend(quote!(Ok(Self::#variant_name))); } - }) - .collect::>(); - - let body = quote! { - #( #variant_bodies )* + VariantKind::Unnamed => { + body.extend(quote! { + Ok(Self::#variant_name ( #( #decoded_values, )* )) + }); + } + VariantKind::Named => { + body.extend(quote! { + Ok(Self::#variant_name { + #( #decoded_names: #decoded_values, )* + }) + }); + } + }; - Err(#crate_name::FromClvmError::Custom( - format!("failed to match any enum variants of `{}`", #type_name) - )) - }; + // Generate a function that parses the variant's fields and returns the variant or an error. + // It takes a `node` so that you can pass it a clone of the original `node` to parse from. + // It uses a reference to the `decoder` from the outer scope as well. + variant_parsers.push(quote! { + |mut node: #node_name| -> ::std::result::Result { + #body + } + }); + } - generate_from_clvm(crate_name, ast, &node_name, &body) + variant_parsers } -fn generate_from_clvm( - crate_name: &TokenStream, - ast: &DeriveInput, - node_name: &Ident, - body: &TokenStream, +// This generates the `FromClvm` trait implementation and augments generics with the `FromClvm` bound. +fn trait_impl( + mut ast: DeriveInput, + crate_name: Ident, + node_name: Ident, + body: TokenStream, ) -> TokenStream { - let mut ast = ast.clone(); let type_name = ast.ident; + // Every generic type must implement `FromClvm` as well in order for the derived type to implement `FromClvm`. + // This isn't always perfect, but it's how derive macros work. add_trait_bounds( &mut ast.generics, parse_quote!(#crate_name::FromClvm<#node_name>), ); let generics_clone = ast.generics.clone(); + let (_, ty_generics, where_clause) = generics_clone.split_for_impl(); ast.generics .params .push(GenericParam::Type(node_name.clone().into())); + let (impl_generics, _, _) = ast.generics.split_for_impl(); + // Generate the final trait implementation. quote! { #[automatically_derived] impl #impl_generics #crate_name::FromClvm<#node_name> for #type_name #ty_generics #where_clause { fn from_clvm( decoder: &impl #crate_name::ClvmDecoder, - node: #node_name, + mut node: #node_name, ) -> ::std::result::Result { #body } diff --git a/crates/clvm-derive/src/helpers.rs b/crates/clvm-derive/src/helpers.rs index 87e4010a4..ce662a43f 100644 --- a/crates/clvm-derive/src/helpers.rs +++ b/crates/clvm-derive/src/helpers.rs @@ -1,98 +1,112 @@ -use std::fmt; - -use proc_macro2::{Ident, Span}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; use syn::{ - ext::IdentExt, punctuated::Punctuated, Attribute, GenericParam, Generics, Token, TypeParamBound, + parse_quote, Expr, GenericArgument, GenericParam, Generics, Ident, PathArguments, Type, + TypeParamBound, }; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Repr { - Tuple, - List, - Curry, -} +use crate::parser::EnumInfo; -impl fmt::Display for Repr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Tuple => "tuple", - Self::List => "list", - Self::Curry => "curry", - }) +pub fn add_trait_bounds(generics: &mut Generics, bound: TypeParamBound) { + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param.bounds.push(bound.clone()); + } } } -#[derive(Default)] -pub struct ClvmAttr { - pub repr: Option, - pub untagged: bool, -} +pub fn option_type(ty: &Type) -> Option<&Type> { + let Type::Path(ty) = ty else { return None }; -impl ClvmAttr { - pub fn expect_repr(&self) -> Repr { - self.repr - .expect("expected clvm attribute parameter of either `tuple`, `list`, or `curry`") + if ty.qself.is_some() { + return None; } -} -pub fn parse_clvm_attr(attrs: &[Attribute]) -> ClvmAttr { - let mut result = ClvmAttr::default(); - for attr in attrs { - let Some(ident) = attr.path().get_ident() else { - continue; - }; + let ty = &ty.path; - if ident != "clvm" { - continue; - } + if ty.segments.is_empty() || ty.segments.last().unwrap().ident != "Option" { + return None; + } - let args = attr - .parse_args_with(Punctuated::::parse_terminated) - .unwrap(); - - for arg in args { - let existing = result.repr; - - result.repr = Some(match arg.to_string().as_str() { - "tuple" => Repr::Tuple, - "list" => Repr::List, - "curry" => Repr::Curry, - "untagged" => { - if result.untagged { - panic!("`untagged` specified twice"); - } else { - result.untagged = true; - } - continue; - } - ident => panic!("unknown argument `{ident}`"), - }); - - if let Some(existing) = existing { - panic!("`{arg}` conflicts with `{existing}`"); - } - } + if !(ty.segments.len() == 1 + || (ty.segments.len() == 3 + && ["core", "std"].contains(&ty.segments[0].ident.to_string().as_str()) + && ty.segments[1].ident == "option")) + { + return None; } - result + + let last_segment = ty.segments.last().unwrap(); + + let PathArguments::AngleBracketed(generics) = &last_segment.arguments else { + return None; + }; + + if generics.args.len() != 1 { + return None; + } + + let GenericArgument::Type(inner_type) = &generics.args[0] else { + return None; + }; + + Some(inner_type) +} + +pub struct DiscriminantInfo { + pub discriminant_consts: Vec, + pub discriminant_names: Vec, + pub variant_names: Vec, + pub discriminant_type: Ident, } -pub fn parse_int_repr(attrs: &[Attribute]) -> Ident { - let mut int_repr: Option = None; - for attr in attrs { - let Some(ident) = attr.path().get_ident() else { - continue; +pub fn variant_discriminants(enum_info: &EnumInfo) -> DiscriminantInfo { + let mut discriminant_consts = Vec::new(); + let mut discriminant_names = Vec::new(); + let mut variant_names = Vec::new(); + + // The default discriminant type is `isize`, but can be overridden with `#[repr(...)]`. + let discriminant_type = enum_info + .discriminant_type + .clone() + .unwrap_or(Ident::new("isize", Span::mixed_site())); + + // We need to keep track of the previous discriminant to increment it for each variant. + let mut previous_discriminant = None; + + for (i, variant) in enum_info.variants.iter().enumerate() { + variant_names.push(variant.name.clone()); + + let discriminant = if let Some(expr) = &variant.discriminant { + // If an explicit discriminant is set, we use that. + expr.clone() + } else if let Some(expr) = previous_discriminant { + // If no explicit discriminant is set, we increment the previous one. + let expr: Expr = parse_quote!( #expr + 1 ); + expr + } else { + // The first variant's discriminant is `0` unless specified otherwise. + let expr: Expr = parse_quote!(0); + expr }; - if ident == "repr" { - int_repr = Some(attr.parse_args_with(Ident::parse_any).unwrap()); - } + + previous_discriminant = Some(discriminant.clone()); + + // Generate a constant for each variant's discriminant. + // This is required because you can't directly put an expression inside of a match pattern. + // So we use a constant to match against instead. + let discriminant_name = Ident::new(&format!("DISCRIMINANT_{}", i), Span::mixed_site()); + + discriminant_names.push(discriminant_name.clone()); + discriminant_consts.push(quote! { + const #discriminant_name: #discriminant_type = #discriminant; + }); } - int_repr.unwrap_or(Ident::new("isize", Span::call_site())) -} -pub fn add_trait_bounds(generics: &mut Generics, bound: TypeParamBound) { - for param in &mut generics.params { - if let GenericParam::Type(ref mut type_param) = *param { - type_param.bounds.push(bound.clone()); - } + DiscriminantInfo { + discriminant_consts, + discriminant_names, + variant_names, + discriminant_type, } } diff --git a/crates/clvm-derive/src/hide_values.rs b/crates/clvm-derive/src/hide_values.rs new file mode 100644 index 000000000..f7dcfdc4f --- /dev/null +++ b/crates/clvm-derive/src/hide_values.rs @@ -0,0 +1,45 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{punctuated::Punctuated, Data, DeriveInput, Fields}; + +use crate::parser::parse_clvm_options; + +pub fn impl_hide_values(mut ast: DeriveInput) -> TokenStream { + match &mut ast.data { + Data::Enum(data_enum) => { + for variant in data_enum.variants.iter_mut() { + remove_fields(&mut variant.fields); + } + } + Data::Struct(data_struct) => { + remove_fields(&mut data_struct.fields); + } + _ => {} + } + + ast.into_token_stream() +} + +fn remove_fields(fields: &mut Fields) { + match fields { + syn::Fields::Named(fields) => { + let retained_fields = fields + .named + .clone() + .into_iter() + .filter(|field| parse_clvm_options(&field.attrs).hidden_value.is_none()); + + fields.named = Punctuated::from_iter(retained_fields); + } + syn::Fields::Unnamed(fields) => { + let retained_fields = fields + .unnamed + .clone() + .into_iter() + .filter(|field| parse_clvm_options(&field.attrs).hidden_value.is_none()); + + fields.unnamed = Punctuated::from_iter(retained_fields); + } + syn::Fields::Unit => {} + } +} diff --git a/crates/clvm-derive/src/lib.rs b/crates/clvm-derive/src/lib.rs index f2165922c..0b4473258 100644 --- a/crates/clvm-derive/src/lib.rs +++ b/crates/clvm-derive/src/lib.rs @@ -2,21 +2,38 @@ extern crate proc_macro; mod from_clvm; mod helpers; -mod macros; +mod hide_values; +mod parser; mod to_clvm; use from_clvm::from_clvm; -use syn::{parse_macro_input, DeriveInput}; +use hide_values::impl_hide_values; +use proc_macro::TokenStream; + +use proc_macro2::Span; +use syn::{parse_macro_input, DeriveInput, Ident}; use to_clvm::to_clvm; +const CRATE_NAME: &str = "clvm_traits"; + +fn crate_name(name: Option) -> Ident { + name.unwrap_or_else(|| Ident::new(CRATE_NAME, Span::call_site())) +} + #[proc_macro_derive(ToClvm, attributes(clvm))] -pub fn to_clvm_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn to_clvm_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); to_clvm(ast).into() } #[proc_macro_derive(FromClvm, attributes(clvm))] -pub fn from_clvm_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn from_clvm_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); from_clvm(ast).into() } + +#[proc_macro_attribute] +pub fn hide_values(_attr: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + impl_hide_values(ast).into() +} diff --git a/crates/clvm-derive/src/macros.rs b/crates/clvm-derive/src/macros.rs deleted file mode 100644 index 632657285..000000000 --- a/crates/clvm-derive/src/macros.rs +++ /dev/null @@ -1,41 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; - -use crate::helpers::Repr; - -pub struct Macros { - /// Encodes a nested tuple containing each of the field values within. - pub clvm_macro: TokenStream, - - /// Decodes a nested tuple containing each of the field types within. - pub match_macro: TokenStream, - - /// Destructures the values into the field names. - pub destructure_macro: TokenStream, -} - -pub fn repr_macros(crate_name: &TokenStream, repr: Repr) -> Macros { - let (clvm_macro, match_macro, destructure_macro) = match repr { - Repr::List => ( - quote!( #crate_name::clvm_list ), - quote!( #crate_name::match_list ), - quote!( #crate_name::destructure_list ), - ), - Repr::Tuple => ( - quote!( #crate_name::clvm_tuple ), - quote!( #crate_name::match_tuple ), - quote!( #crate_name::destructure_tuple ), - ), - Repr::Curry => ( - quote!( #crate_name::clvm_curried_args ), - quote!( #crate_name::match_curried_args ), - quote!( #crate_name::destructure_curried_args ), - ), - }; - - Macros { - clvm_macro, - match_macro, - destructure_macro, - } -} diff --git a/crates/clvm-derive/src/parser.rs b/crates/clvm-derive/src/parser.rs new file mode 100644 index 000000000..08c18e2f5 --- /dev/null +++ b/crates/clvm-derive/src/parser.rs @@ -0,0 +1,28 @@ +use syn::{Data, DeriveInput}; + +mod attributes; +mod enum_info; +mod field_info; +mod struct_info; +mod variant_info; + +pub use attributes::*; +pub use enum_info::*; +pub use field_info::*; +pub use struct_info::*; +pub use variant_info::*; + +pub enum ParsedInfo { + Struct(StructInfo), + Enum(EnumInfo), +} + +pub fn parse(derive: &'static str, ast: &DeriveInput) -> ParsedInfo { + let options = parse_clvm_options(&ast.attrs); + + match &ast.data { + Data::Struct(data_struct) => ParsedInfo::Struct(parse_struct(options, data_struct)), + Data::Enum(data_enum) => ParsedInfo::Enum(parse_enum(options, data_enum)), + Data::Union(..) => panic!("cannot derive `{derive}` for a union"), + } +} diff --git a/crates/clvm-derive/src/parser/attributes.rs b/crates/clvm-derive/src/parser/attributes.rs new file mode 100644 index 000000000..a71c9d161 --- /dev/null +++ b/crates/clvm-derive/src/parser/attributes.rs @@ -0,0 +1,188 @@ +use std::fmt; + +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Attribute, Expr, Ident, Token, +}; + +/// The representation of fields when converted to and from CLVM. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Repr { + /// Represents `(A . (B . (C . ())))`. + List, + /// Represents `(c (q . A) (c (q . B) (c (q . C) 1)))`. + Curry, + /// Represents `A` on its own. + Atom, +} + +impl Repr { + pub fn expect(repr: Option) -> Repr { + repr.expect("missing either `list`, `curry`, or `atom` in `clvm` attribute options") + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::List => "list", + Self::Curry => "curry", + Self::Atom => "atom", + }) + } +} + +/// All of the possible options of the `clvm` attribute and the enum `repr` attribute. +/// They must be validated after being parsed to prevent invalid option configurations. +pub struct ClvmOptions { + /// The representation of the fields. + pub repr: Option, + /// The value of the field, also removed the actual field from the struct. + /// This is useful for constant fields which shouldn't be in the constructor. + pub hidden_value: Option, + /// Whether the enum should parse variants one after the other instead of using the discriminant. + pub untagged: bool, + /// The integer type used for the enum discriminant. + pub enum_repr: Option, + /// The name of the `clvm_traits` crate to use, useful for renamed dependencies for example. + pub crate_name: Option, + /// The default value of the field, if it's not present in the CLVM object. + /// If the default is set to `None`, it will assume the type is `Option` and the default will be `None`. + pub default: Option>, + /// Whether the field is a rest field, which will consume the rest of the CLVM object. + pub rest: bool, +} + +/// All of the possible options of the `clvm` attribute. +enum ClvmOption { + Repr(Repr), + HiddenValue(Expr), + CrateName(Ident), + Untagged, + Optional, + Default(Expr), + Rest, +} + +impl Parse for ClvmOption { + fn parse(input: ParseStream) -> syn::Result { + let ident = input.parse::()?; + + match ident.to_string().as_str() { + "list" => Ok(Self::Repr(Repr::List)), + "curry" => Ok(Self::Repr(Repr::Curry)), + "atom" => Ok(Self::Repr(Repr::Atom)), + "untagged" => Ok(Self::Untagged), + "hidden_value" => { + input.parse::()?; + Ok(Self::HiddenValue(input.parse()?)) + } + "crate_name" => { + input.parse::()?; + Ok(Self::CrateName(input.parse()?)) + } + "optional" => Ok(Self::Optional), + "default" => { + input.parse::()?; + Ok(Self::Default(input.parse()?)) + } + "rest" => Ok(Self::Rest), + _ => Err(syn::Error::new(ident.span(), "unknown argument")), + } + } +} + +/// Parses the `clvm` attribute options and `repr` option from the given attributes. +pub fn parse_clvm_options(attrs: &[Attribute]) -> ClvmOptions { + let mut options = ClvmOptions { + repr: None, + hidden_value: None, + untagged: false, + enum_repr: None, + crate_name: None, + default: None, + rest: false, + }; + + for attr in attrs { + let Some(ident) = attr.path().get_ident() else { + continue; + }; + + if ident == "repr" { + let repr = attr.parse_args::().unwrap(); + let text = repr.to_string(); + let text = text.as_str(); + + // Check if the repr is an integer type. If not, it's not an enum discriminant repr. + // For example, `#[repr(C)]` should not be relevant to the CLVM conversions. + // This is intended for things like `#[repr(u8)]` or `#[repr(i32)]`. + let is_unsigned_int = matches!(text, "u8" | "u16" | "u32" | "u64" | "u128" | "usize"); + let is_signed_int = matches!(text, "i8" | "i16" | "i32" | "i64" | "i128" | "isize"); + + if !is_unsigned_int && !is_signed_int { + continue; + } + + options.enum_repr = Some(repr); + } + + if ident != "clvm" { + continue; + } + + let parsed_options = attr + .parse_args_with(Punctuated::::parse_terminated) + .unwrap_or_else(|error| panic!("failed to parse `clvm` attribute options: {}", error)); + + for option in parsed_options { + match option { + ClvmOption::Untagged => { + if options.untagged { + panic!("duplicate `untagged` option"); + } + options.untagged = true; + } + ClvmOption::Repr(repr) => { + if options.repr.is_some() { + panic!("duplicate repr option `{repr}`"); + } + options.repr = Some(repr); + } + ClvmOption::HiddenValue(value) => { + if options.hidden_value.is_some() { + panic!("duplicate `hidden_value` option"); + } + options.hidden_value = Some(value); + } + ClvmOption::CrateName(crate_name) => { + if options.crate_name.is_some() { + panic!("duplicate `crate_name` option"); + } + options.crate_name = Some(crate_name); + } + ClvmOption::Default(default) => { + if options.default.is_some() { + panic!("can't specify `default` again when `default` or `optional` is already set"); + } + options.default = Some(Some(default)); + } + ClvmOption::Optional => { + if options.default.is_some() { + panic!("can't specify `optional` again when `default` or `optional` is already set"); + } + options.default = Some(None); + } + ClvmOption::Rest => { + if options.rest { + panic!("duplicate `rest` option"); + } + options.rest = true; + } + } + } + } + + options +} diff --git a/crates/clvm-derive/src/parser/enum_info.rs b/crates/clvm-derive/src/parser/enum_info.rs new file mode 100644 index 000000000..65fbfa318 --- /dev/null +++ b/crates/clvm-derive/src/parser/enum_info.rs @@ -0,0 +1,55 @@ +use syn::{DataEnum, Ident}; + +use super::{parse_clvm_options, parse_variant, ClvmOptions, Repr, VariantInfo}; + +pub struct EnumInfo { + pub variants: Vec, + pub discriminant_type: Option, + pub is_untagged: bool, + pub default_repr: Repr, + pub crate_name: Option, +} + +pub fn parse_enum(options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { + if options.hidden_value.is_some() { + panic!("`hidden_value` only applies to fields"); + } + + if options.default.is_some() { + panic!("`default` and `optional` only apply to fields"); + } + + if options.rest { + panic!("`rest` only applies to fields"); + } + + let repr = Repr::expect(options.repr); + + let mut variants = Vec::new(); + + for variant in data_enum.variants.iter() { + let variant_options = parse_clvm_options(&variant.attrs); + + if repr == Repr::Atom && variant_options.repr.is_some() { + panic!("cannot override `atom` representation for individual enum variants"); + } + + if repr == Repr::Atom && !variant.fields.is_empty() { + panic!("cannot have fields in an `atom` enum variant"); + } + + if !options.untagged && variant_options.repr.is_some() { + panic!("cannot specify representation for individual enum variants in a tagged enum"); + } + + variants.push(parse_variant(variant_options, variant)); + } + + EnumInfo { + variants, + discriminant_type: options.enum_repr, + is_untagged: options.untagged, + default_repr: repr, + crate_name: options.crate_name, + } +} diff --git a/crates/clvm-derive/src/parser/field_info.rs b/crates/clvm-derive/src/parser/field_info.rs new file mode 100644 index 000000000..abb4baf84 --- /dev/null +++ b/crates/clvm-derive/src/parser/field_info.rs @@ -0,0 +1,109 @@ +use syn::{spanned::Spanned, Expr, FieldsNamed, FieldsUnnamed, Ident, Type}; + +use super::{parse_clvm_options, ClvmOptions}; + +pub struct FieldInfo { + pub ident: Ident, + pub ty: Type, + pub hidden_with_value: Option, + pub optional_with_default: Option>, + pub rest: bool, +} + +pub fn parse_named_fields(fields: &FieldsNamed) -> Vec { + let mut items = Vec::new(); + + let mut rest = false; + let mut optional = false; + + for field in fields.named.iter() { + let ident = field.ident.clone().unwrap(); + let ty = field.ty.clone(); + + let options = parse_clvm_options(&field.attrs); + check_field_options(&options); + + if rest { + panic!("nothing can come after the `rest` field, since it consumes all arguments"); + } + + if optional && options.default.is_none() { + panic!("all fields after an optional field must also be optional"); + } + + rest = options.rest; + optional = options.default.is_some(); + + items.push(FieldInfo { + ident, + ty, + hidden_with_value: options.hidden_value, + optional_with_default: options.default, + rest: options.rest, + }); + } + + items +} + +pub fn parse_unnamed_fields(fields: &FieldsUnnamed) -> Vec { + let mut items = Vec::new(); + + let mut rest = false; + let mut optional = false; + + for (i, field) in fields.unnamed.iter().enumerate() { + let ident = Ident::new(&format!("field_{i}"), field.span()); + let ty = field.ty.clone(); + + let options = parse_clvm_options(&field.attrs); + check_field_options(&options); + + if rest { + panic!("nothing can come after the `rest` field, since it consumes all arguments"); + } + + if optional && options.default.is_none() { + panic!("all fields after an optional field must also be optional"); + } + + rest = options.rest; + optional = options.default.is_some(); + + items.push(FieldInfo { + ident, + ty, + hidden_with_value: options.hidden_value, + optional_with_default: options.default, + rest: options.rest, + }); + } + + items +} + +fn check_field_options(options: &ClvmOptions) { + if options.untagged { + panic!("`untagged` only applies to enums"); + } + + if options.enum_repr.is_some() { + panic!("`repr` only applies to enums"); + } + + if let Some(repr) = options.repr { + panic!("`{repr}` can't be set on individual fields"); + } + + if options.crate_name.is_some() { + panic!("`crate_name` can't be set on individual fields"); + } + + if options.default.is_some() && options.hidden_value.is_some() { + panic!("neither `default` nor `optional` can be used with the `hidden_value` option set"); + } + + if options.default.is_some() && options.rest { + panic!("`default` can't be used with the `rest` option set"); + } +} diff --git a/crates/clvm-derive/src/parser/struct_info.rs b/crates/clvm-derive/src/parser/struct_info.rs new file mode 100644 index 000000000..c13143592 --- /dev/null +++ b/crates/clvm-derive/src/parser/struct_info.rs @@ -0,0 +1,68 @@ +use syn::{DataStruct, Fields, Ident}; + +use super::{parse_named_fields, parse_unnamed_fields, ClvmOptions, FieldInfo, Repr}; + +pub struct StructInfo { + pub kind: StructKind, + pub fields: Vec, + pub repr: Repr, + pub crate_name: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructKind { + Unit, + Unnamed, + Named, +} + +pub fn parse_struct(options: ClvmOptions, data_struct: &DataStruct) -> StructInfo { + if options.untagged { + panic!("`untagged` only applies to enums"); + } + + if options.enum_repr.is_some() { + panic!("`repr` only applies to enums"); + } + + if options.hidden_value.is_some() { + panic!("`hidden_value` only applies to fields"); + } + + if options.default.is_some() { + panic!("`default` and `optional` only apply to fields"); + } + + if options.rest { + panic!("`rest` only applies to fields"); + } + + let repr = Repr::expect(options.repr); + + if repr == Repr::Atom { + panic!("`atom` is not a valid representation for structs"); + } + + let crate_name = options.crate_name; + + match &data_struct.fields { + Fields::Unit => StructInfo { + kind: StructKind::Unit, + fields: Vec::new(), + repr, + crate_name, + }, + Fields::Named(fields) => StructInfo { + kind: StructKind::Named, + fields: parse_named_fields(fields), + repr, + crate_name, + }, + Fields::Unnamed(fields) => StructInfo { + kind: StructKind::Unnamed, + fields: parse_unnamed_fields(fields), + repr, + crate_name, + }, + } +} diff --git a/crates/clvm-derive/src/parser/variant_info.rs b/crates/clvm-derive/src/parser/variant_info.rs new file mode 100644 index 000000000..d7d8cff0f --- /dev/null +++ b/crates/clvm-derive/src/parser/variant_info.rs @@ -0,0 +1,76 @@ +use syn::{Expr, Fields, Ident, Variant}; + +use super::{parse_named_fields, parse_unnamed_fields, ClvmOptions, FieldInfo, Repr}; + +pub struct VariantInfo { + pub kind: VariantKind, + pub name: Ident, + pub fields: Vec, + pub discriminant: Option, + pub repr: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariantKind { + Unit, + Unnamed, + Named, +} + +pub fn parse_variant(options: ClvmOptions, variant: &Variant) -> VariantInfo { + if options.untagged { + panic!("`untagged` only applies to enums"); + } + + if options.enum_repr.is_some() { + panic!("`repr` only applies to enums"); + } + + if options.hidden_value.is_some() { + panic!("`hidden_value` only applies to fields"); + } + + if options.crate_name.is_some() { + panic!("`crate_name` can't be set on individual enum variants"); + } + + if options.default.is_some() { + panic!("`default` and `optional` only apply to fields"); + } + + if options.rest { + panic!("`rest` only applies to fields"); + } + + let name = variant.ident.clone(); + let discriminant = variant.discriminant.clone().map(|(_, expr)| expr); + let repr = options.repr; + + if repr == Some(Repr::Atom) { + panic!("`atom` is not a valid representation for individual enum variants"); + } + + match &variant.fields { + Fields::Unit => VariantInfo { + kind: VariantKind::Unit, + name, + fields: Vec::new(), + discriminant, + repr, + }, + Fields::Named(fields) => VariantInfo { + kind: VariantKind::Named, + name, + fields: parse_named_fields(fields), + discriminant, + repr, + }, + Fields::Unnamed(fields) => VariantInfo { + kind: VariantKind::Unnamed, + name, + fields: parse_unnamed_fields(fields), + discriminant, + repr, + }, + } +} diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index 1463a8638..a04cde384 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -1,243 +1,277 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{ - parse_quote, spanned::Spanned, Data, DeriveInput, Expr, Fields, FieldsNamed, FieldsUnnamed, - GenericParam, Index, -}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{parse_quote, DeriveInput, GenericParam, Ident, Index}; use crate::{ - helpers::{add_trait_bounds, parse_clvm_attr, Repr}, - macros::{repr_macros, Macros}, + crate_name, + helpers::{add_trait_bounds, variant_discriminants, DiscriminantInfo}, + parser::{parse, EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind}, }; -#[derive(Default)] -struct FieldInfo { - field_names: Vec, - field_accessors: Vec, - initializer: TokenStream, -} +pub fn to_clvm(ast: DeriveInput) -> TokenStream { + let parsed = parse("ToClvm", &ast); + let node_name = Ident::new("Node", Span::mixed_site()); -struct VariantInfo { - name: Ident, - discriminant: Expr, - field_info: FieldInfo, - macros: Macros, + match parsed { + ParsedInfo::Struct(struct_info) => impl_for_struct(ast, struct_info, node_name), + ParsedInfo::Enum(enum_info) => impl_for_enum(ast, enum_info, node_name), + } } -pub fn to_clvm(ast: DeriveInput) -> TokenStream { - let clvm_attr = parse_clvm_attr(&ast.attrs); - let crate_name = quote!(clvm_traits); +fn encode_fields( + crate_name: &Ident, + node_name: &Ident, + fields: &[FieldInfo], + repr: Repr, +) -> TokenStream { + let mut body = TokenStream::new(); + let mut value_names = Vec::new(); - match &ast.data { - Data::Struct(data_struct) => { - if clvm_attr.untagged { - panic!("cannot use `untagged` on a struct"); - } - let macros = repr_macros(&crate_name, clvm_attr.expect_repr()); - let field_info = fields(&data_struct.fields); - impl_for_struct(&crate_name, &ast, ¯os, &field_info) + // Generate the values that need to be encoded for each field. + // As well as a unique name for each field to reference later. + for (i, field) in fields.iter().enumerate() { + let value_name = Ident::new(&format!("field_{}", i), Span::mixed_site()); + + if let Some(value) = &field.hidden_with_value { + body.extend(quote! { + // Use the hidden field's value directly, since it's not in `self`. + let #value_name = #value; + }); } - Data::Enum(data_enum) => { - if !clvm_attr.untagged && clvm_attr.repr == Some(Repr::Curry) { - panic!("cannot use `curry` on a tagged enum, since unlike other representations, each argument is wrapped"); - } - let mut next_discriminant: Expr = parse_quote!(0); - let mut variants = Vec::new(); + value_names.push(value_name); + } - for variant in data_enum.variants.iter() { - let field_info = fields(&variant.fields); - let variant_clvm_attr = parse_clvm_attr(&variant.attrs); + let encode_next = match repr { + Repr::Atom => unreachable!(), + // Encode `(A . B)` pairs for lists. + Repr::List => quote!(encode_pair), + // Encode `(c (q . A) B)` pairs for curried arguments. + Repr::Curry => quote!(encode_curried_arg), + }; - if variant_clvm_attr.untagged { - panic!("cannot use `untagged` on an enum variant"); - } + let initial_value = match repr { + Repr::Atom => unreachable!(), + Repr::List => quote!(encoder.encode_atom(&[])?), + Repr::Curry => quote!(encoder.encode_atom(&[1])?), + }; - let repr = variant_clvm_attr - .repr - .unwrap_or_else(|| clvm_attr.expect_repr()); - if !clvm_attr.untagged && repr == Repr::Curry { - panic!("cannot use `curry` on a tagged enum variant, since unlike other representations, each argument is wrapped"); - } + body.extend(quote! { + let mut node = #initial_value; + }); - let macros = repr_macros(&crate_name, repr); - let variant_info = VariantInfo { - name: variant.ident.clone(), - discriminant: variant - .discriminant - .as_ref() - .map(|(_, discriminant)| { - next_discriminant = parse_quote!(#discriminant + 1); - discriminant.clone() - }) - .unwrap_or_else(|| { - let discriminant = next_discriminant.clone(); - next_discriminant = parse_quote!(#next_discriminant + 1); - discriminant - }), - field_info, - macros, - }; - variants.push(variant_info); - } + for (i, field) in fields.iter().enumerate().rev() { + let value_name = &value_names[i]; + let ty = &field.ty; + + let mut if_body = TokenStream::new(); - impl_for_enum(&crate_name, &ast, clvm_attr.untagged, &variants) + if_body.extend(quote! { + let value_node = <#ty as #crate_name::ToClvm<#node_name>>::to_clvm(&#value_name, encoder)?; + }); + + if field.rest { + if_body.extend(quote! { + node = value_node; + }); + } else { + if_body.extend(quote! { + node = encoder.#encode_next(value_node, node)?; + }); } - Data::Union(_union) => panic!("cannot derive `ToClvm` for a union"), - } -} -fn fields(fields: &Fields) -> FieldInfo { - match fields { - Fields::Named(fields) => named_fields(fields), - Fields::Unnamed(fields) => unnamed_fields(fields), - Fields::Unit => FieldInfo::default(), + if let Some(default) = &field.optional_with_default { + if let Some(default) = default { + body.extend(quote! { + if #value_name != #default { + #if_body + } + }); + } else { + body.extend(quote! { + if let Some(#value_name) = #value_name { + #if_body + } + }); + } + } else { + body.extend(if_body); + } } -} -fn named_fields(fields: &FieldsNamed) -> FieldInfo { - let field_names: Vec = fields - .named - .iter() - .map(|field| field.ident.clone().unwrap()) - .collect(); - let field_accessors = field_names - .iter() - .map(|field_name| field_name.clone().to_token_stream()) - .collect(); - let initializer = quote!({ #( #field_names, )* }); - - FieldInfo { - field_names, - field_accessors, - initializer, - } + body } -fn unnamed_fields(fields: &FieldsUnnamed) -> FieldInfo { - let field_names: Vec = fields - .unnamed - .iter() - .enumerate() - .map(|(i, field)| Ident::new(&format!("field_{i}"), field.span())) - .collect(); - let field_accessors = field_names - .iter() - .enumerate() - .map(|(i, _)| Index::from(i).to_token_stream()) - .collect(); - let initializer = quote!(( #( #field_names, )* )); - - FieldInfo { - field_names, - field_accessors, - initializer, +fn impl_for_struct(ast: DeriveInput, struct_info: StructInfo, node_name: Ident) -> TokenStream { + let crate_name = crate_name(struct_info.crate_name); + + let mut body = TokenStream::new(); + + for (i, field) in struct_info.fields.iter().enumerate() { + if field.hidden_with_value.is_some() { + continue; + } + + let value_name = Ident::new(&format!("field_{}", i), Span::mixed_site()); + + match struct_info.kind { + StructKind::Named => { + let field_name = &field.ident; + body.extend(quote! { + let #value_name = &self.#field_name; + }); + } + StructKind::Unnamed => { + let field_index = Index::from(i); + body.extend(quote! { + let #value_name = &self.#field_index; + }); + } + StructKind::Unit => unreachable!(), + } } -} -fn impl_for_struct( - crate_name: &TokenStream, - ast: &DeriveInput, - Macros { clvm_macro, .. }: &Macros, - FieldInfo { - field_accessors, .. - }: &FieldInfo, -) -> TokenStream { - let node_name = Ident::new("Node", Span::mixed_site()); + body.extend(encode_fields( + &crate_name, + &node_name, + &struct_info.fields, + struct_info.repr, + )); - let body = quote! { - let value = #clvm_macro!( #( &self.#field_accessors ),* ); - #crate_name::ToClvm::to_clvm(&value, encoder) - }; + body.extend(quote! { + Ok(node) + }); - generate_to_clvm(crate_name, ast, &node_name, &body) + trait_impl(ast, crate_name, node_name, body) } -fn impl_for_enum( - crate_name: &TokenStream, - ast: &DeriveInput, - untagged: bool, - variants: &[VariantInfo], -) -> TokenStream { - let node_name = Ident::new("Node", Span::mixed_site()); - let has_initializers = variants - .iter() - .any(|variant| !variant.field_info.initializer.is_empty()); - - let variant_bodies = variants - .iter() - .map(|variant_info| { - let VariantInfo { - name, - discriminant, - field_info, - macros, - } = variant_info; - - let FieldInfo { - field_names, - initializer, - .. - } = field_info; - - let Macros { clvm_macro, .. } = macros; - - if untagged { - quote! { - Self::#name #initializer => { - #clvm_macro!( #( #field_names ),* ).to_clvm(encoder) - } - } - } else if has_initializers { - quote! { - Self::#name #initializer => { - (#discriminant, #clvm_macro!( #( #field_names ),* )).to_clvm(encoder) - } - } - } else { - quote! { - Self::#name => { - (#discriminant).to_clvm(encoder) - } +fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> TokenStream { + let crate_name = crate_name(enum_info.crate_name.clone()); + + let mut variant_bodies = Vec::new(); + let mut variant_destructures = Vec::new(); + + for variant in enum_info.variants.iter() { + let variant_name = &variant.name; + + let repr = variant.repr.unwrap_or(enum_info.default_repr); + let variant_body = encode_fields(&crate_name, &node_name, &variant.fields, repr); + + let field_names: Vec = variant + .fields + .iter() + .map(|field| field.ident.clone()) + .collect(); + + let value_names: Vec = (0..variant.fields.len()) + .map(|i| Ident::new(&format!("field_{}", i), Span::mixed_site())) + .collect(); + + let destructure = match variant.kind { + VariantKind::Unit => quote!(Self::#variant_name), + VariantKind::Unnamed => { + quote!(Self::#variant_name( #( #value_names, )* )) + } + VariantKind::Named => { + quote!(Self::#variant_name { #( #field_names: #value_names, )* }) + } + }; + + variant_bodies.push(variant_body); + variant_destructures.push(destructure); + } + + let body = if enum_info.is_untagged { + quote! { + match self { + #( #variant_destructures => { + #variant_bodies + Ok(node) + }, )* + } + } + } else { + let DiscriminantInfo { + discriminant_type, + discriminant_consts, + discriminant_names, + variant_names, + } = variant_discriminants(&enum_info); + + if enum_info.default_repr == Repr::Atom { + quote! { + #( #discriminant_consts )* + + match self { + #( Self::#variant_names => { + #crate_name::ToClvm::<#discriminant_type>::to_clvm( + &#discriminant_names, + encoder, + ) + }, )* } } - }) - .collect::>(); + } else { + let encode_next = match enum_info.default_repr { + Repr::Atom => unreachable!(), + // Encode `(A . B)` pairs for lists. + Repr::List => quote!(encode_pair), + // Encode `(c (q . A) B)` pairs for curried arguments. + Repr::Curry => quote!(encode_curried_arg), + }; - let body = quote! { - match self { - #( #variant_bodies )* + quote! { + #( #discriminant_consts )* + + match self { + #( #variant_destructures => { + #variant_bodies + + let discriminant_node = <#discriminant_type as #crate_name::ToClvm<#node_name>>::to_clvm( + &#discriminant_names, + encoder, + )?; + + encoder.#encode_next( discriminant_node, node ) + }, )* + } + } } }; - generate_to_clvm(crate_name, ast, &node_name, &body) + trait_impl(ast, crate_name, node_name, body) } -fn generate_to_clvm( - crate_name: &TokenStream, - ast: &DeriveInput, - node_name: &Ident, - body: &TokenStream, +fn trait_impl( + mut ast: DeriveInput, + crate_name: Ident, + node_name: Ident, + body: TokenStream, ) -> TokenStream { - let mut ast = ast.clone(); let type_name = ast.ident; + // Every generic type must implement `ToClvm` as well in order for the derived type to implement `ToClvm`. + // This isn't always perfect, but it's how derive macros work. add_trait_bounds( &mut ast.generics, parse_quote!(#crate_name::ToClvm<#node_name>), ); let generics_clone = ast.generics.clone(); + let (_, ty_generics, where_clause) = generics_clone.split_for_impl(); ast.generics .params .push(GenericParam::Type(node_name.clone().into())); + let (impl_generics, _, _) = ast.generics.split_for_impl(); + // Generate the final trait implementation. quote! { #[automatically_derived] - impl #impl_generics #crate_name::ToClvm<#node_name> for #type_name #ty_generics #where_clause { + impl #impl_generics #crate_name::ToClvm<#node_name> + for #type_name #ty_generics #where_clause { fn to_clvm( &self, encoder: &mut impl #crate_name::ClvmEncoder diff --git a/crates/clvm-traits/src/clvm_decoder.rs b/crates/clvm-traits/src/clvm_decoder.rs index af953e9d0..90942b5bf 100644 --- a/crates/clvm-traits/src/clvm_decoder.rs +++ b/crates/clvm-traits/src/clvm_decoder.rs @@ -8,6 +8,35 @@ pub trait ClvmDecoder { fn decode_atom(&self, node: &Self::Node) -> Result; fn decode_pair(&self, node: &Self::Node) -> Result<(Self::Node, Self::Node), FromClvmError>; + fn decode_curried_arg( + &self, + node: &Self::Node, + ) -> Result<(Self::Node, Self::Node), FromClvmError> { + let (c, rest) = self.decode_pair(node)?; + if self.decode_atom(&c)?.as_ref() != [4] { + return Err(FromClvmError::Custom("expected `c` operator".to_string())); + } + + let (quoted_value, rest) = self.decode_pair(&rest)?; + + let (q, value) = self.decode_pair("ed_value)?; + if self.decode_atom(&q)?.as_ref() != [1] { + return Err(FromClvmError::Custom("expected `q` operator".to_string())); + } + + let (rest, nil) = self.decode_pair(&rest)?; + let nil_atom = self.decode_atom(&nil)?; + let nil_ref = nil_atom.as_ref(); + if !nil_ref.is_empty() { + return Err(FromClvmError::WrongAtomLength { + expected: 0, + found: nil_ref.len(), + }); + } + + Ok((value, rest)) + } + /// This is a helper function that just calls `clone` on the node. /// It's required only because the compiler can't infer that `N` is `Clone`, /// since there's no `Clone` bound on the `FromClvm` trait. diff --git a/crates/clvm-traits/src/clvm_encoder.rs b/crates/clvm-traits/src/clvm_encoder.rs index 609b750a0..c3dde4639 100644 --- a/crates/clvm-traits/src/clvm_encoder.rs +++ b/crates/clvm-traits/src/clvm_encoder.rs @@ -12,6 +12,23 @@ pub trait ClvmEncoder { rest: Self::Node, ) -> Result; + fn encode_curried_arg( + &mut self, + first: Self::Node, + rest: Self::Node, + ) -> Result { + let q = self.encode_atom(&[1])?; + let c = self.encode_atom(&[4])?; + + let quoted = self.encode_pair(q, first)?; + let nil = self.encode_atom(&[])?; + + let op_rest = self.encode_pair(rest, nil)?; + let op_args = self.encode_pair(quoted, op_rest)?; + + self.encode_pair(c, op_args) + } + /// This is a helper function that just calls `clone` on the node. /// It's required only because the compiler can't infer that `N` is `Clone`, /// since there's no `Clone` bound on the `ToClvm` trait. @@ -24,7 +41,13 @@ impl ClvmEncoder for Allocator { type Node = NodePtr; fn encode_atom(&mut self, bytes: &[u8]) -> Result { - self.new_atom(bytes).or(Err(ToClvmError::OutOfMemory)) + Ok(if bytes.is_empty() { + self.nil() + } else if bytes == [1] { + self.one() + } else { + self.new_atom(bytes).or(Err(ToClvmError::OutOfMemory))? + }) } fn encode_pair( diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index 2686c8ed6..32a9655b7 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -65,9 +65,10 @@ mod derive_tests { #[test] fn test_tuple() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] + #[clvm(list)] struct TupleStruct { a: u64, + #[clvm(rest)] b: i32, } @@ -104,8 +105,8 @@ mod derive_tests { #[test] fn test_unnamed() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] - struct UnnamedStruct(String, String); + #[clvm(list)] + struct UnnamedStruct(String, #[clvm(rest)] String); check(UnnamedStruct("A".to_string(), "B".to_string()), "ff4142"); } @@ -113,19 +114,40 @@ mod derive_tests { #[test] fn test_newtype() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] - struct NewTypeStruct(String); + #[clvm(list)] + struct NewTypeStruct(#[clvm(rest)] String); check(NewTypeStruct("XYZ".to_string()), "8358595a"); } + #[test] + fn test_literals() { + #[derive(ToClvm, FromClvm)] + #[hide_values] + #[derive(Debug, PartialEq, Eq)] + #[clvm(list)] + struct RunTailCondition { + #[clvm(hidden_value = 51)] + opcode: u8, + #[clvm(hidden_value = ())] + blank_puzzle_hash: (), + #[clvm(hidden_value = -113)] + magic_amount: i8, + puzzle: P, + solution: S, + } + } + #[test] fn test_enum() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] + #[clvm(list)] enum Enum { - A(i32), - B { x: i32 }, + A(#[clvm(rest)] i32), + B { + #[clvm(rest)] + x: i32, + }, C, } @@ -137,11 +159,14 @@ mod derive_tests { #[test] fn test_explicit_enum() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] + #[clvm(list)] #[repr(u8)] enum Enum { - A(i32) = 42, - B { x: i32 } = 34, + A(#[clvm(rest)] i32) = 42, + B { + #[clvm(rest)] + x: i32, + } = 34, C = 11, } @@ -153,11 +178,10 @@ mod derive_tests { #[test] fn test_untagged_enum() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple, untagged)] + #[clvm(list, untagged)] enum Enum { - A(i32), + A(#[clvm(rest)] i32), - #[clvm(list)] B { x: i32, y: i32, @@ -182,14 +206,14 @@ mod derive_tests { #[test] fn test_untagged_enum_parsing_order() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple, untagged)] + #[clvm(list, untagged)] enum Enum { // This variant is parsed first, so `B` will never be deserialized. - A(i32), + A(#[clvm(rest)] i32), // When `B` is serialized, it will round trip as `A` instead. - B(i32), + B(#[clvm(rest)] i32), // `C` will be deserialized as a fallback when the bytes don't deserialize to a valid `i32`. - C(String), + C(#[clvm(rest)] String), } // This round trips to the same value, since `A` is parsed first. From f0154763e67b740e0805b66d97a9bf316a3ce82c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 21:38:00 -0400 Subject: [PATCH 02/16] Docs update and fix --- crates/clvm-derive/src/to_clvm.rs | 31 +++++++-- crates/clvm-traits/docs/derive_macros.md | 83 ++++++++++-------------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index a04cde384..6e1aaab36 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -148,15 +148,11 @@ fn impl_for_struct(ast: DeriveInput, struct_info: StructInfo, node_name: Ident) fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> TokenStream { let crate_name = crate_name(enum_info.crate_name.clone()); - let mut variant_bodies = Vec::new(); let mut variant_destructures = Vec::new(); for variant in enum_info.variants.iter() { let variant_name = &variant.name; - let repr = variant.repr.unwrap_or(enum_info.default_repr); - let variant_body = encode_fields(&crate_name, &node_name, &variant.fields, repr); - let field_names: Vec = variant .fields .iter() @@ -177,11 +173,22 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok } }; - variant_bodies.push(variant_body); variant_destructures.push(destructure); } let body = if enum_info.is_untagged { + let mut variant_bodies = Vec::new(); + + for variant in enum_info.variants.iter() { + let repr = variant.repr.unwrap_or(enum_info.default_repr); + variant_bodies.push(encode_fields( + &crate_name, + &node_name, + &variant.fields, + repr, + )); + } + quote! { match self { #( #variant_destructures => { @@ -204,7 +211,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok match self { #( Self::#variant_names => { - #crate_name::ToClvm::<#discriminant_type>::to_clvm( + <#discriminant_type as #crate_name::ToClvm<#node_name>>::to_clvm( &#discriminant_names, encoder, ) @@ -220,6 +227,18 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok Repr::Curry => quote!(encode_curried_arg), }; + let mut variant_bodies = Vec::new(); + + for variant in enum_info.variants.iter() { + let repr = variant.repr.unwrap_or(enum_info.default_repr); + variant_bodies.push(encode_fields( + &crate_name, + &node_name, + &variant.fields, + repr, + )); + } + quote! { #( #discriminant_consts )* diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 96f184366..d952f63d7 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -12,52 +12,41 @@ Pick whichever representation fits your use-case the best. Note that the syntax `(A . B)` represents a cons-pair with two values, `A` and `B`. This is how non-atomic values are structured in CLVM. -### Tuple +### List + +This represents values in a nil terminated series of nested cons-pairs, also known as a proper list. -This represents values in an unterminated series of nested cons-pairs. +For example, with the list `[A, B, C]`, we build the list in reverse: -For example: +- Start with the nil terminator `()` +- Create the first cons pair `(C . ())` +- Then continue on with `(B . (C . ()))` +- Finally, the list is represented as `(A . (B . (C . ())))` -- `()` is encoded as `()`, since it's not possible to create a cons-pair with no values. -- `(A)` is encoded as `A`, since it's not possible to create a cons-pair with one value. -- `(A, B)` is encoded as `(A . B)`, since it's already a valid cons-pair. -- `(A, B, C)` is encoded as `(A . (B . C))`, since every cons-pair must contain two values. -- `(A, B, C, D)` is encoded as `(A . (B . (C . D)))` for the same reason as above. +Note that the following example of using a password for a Chia puzzle is insecure, but it's effective for demonstration. ```rust use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple)] -struct Point { - x: i32, - y: i32, +#[clvm(list)] +struct PasswordSolution { + password: String, } -let point = Point { - x: 5, - y: 2, +let solution = PasswordSolution { + password: "Hello".into(), }; let a = &mut Allocator::new(); -let ptr = point.to_clvm(a).unwrap(); -assert_eq!(Point::from_clvm(a, ptr).unwrap(), point); +let ptr = solution.to_clvm(a).unwrap(); +assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); ``` -### List - -This represents values in a null terminated series of nested cons-pairs, also known as a proper list. - -For example: - -- `()` is encoded as `()`, since it's already a null value. -- `(A)` is encoded as `(A, ())`, since it's null terminated. -- `(A, B)` is encoded as `(A . (B . ()))`, nesting the cons-pairs just like tuples, except with a null terminator. -- `(A, B, C)` is encoded as `(A . (B . (C . ())))` for the same reason. +### Unterminated List -Note that the following code is for example purposes only and is not indicative of how to create a secure program. -Using a password like shown in this example is an insecure method of locking coins, but it's effective for learning. +You can omit the nil terminator by using `#[clvm(rest)]`: ```rust use clvmr::Allocator; @@ -65,31 +54,33 @@ use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(list)] -struct PasswordSolution { - password: String, +struct Point { + x: i32, + #[clvm(rest)] + y: i32, } -let solution = PasswordSolution { - password: "Hello".into(), +let point = Point { + x: 5, + y: 2, }; let a = &mut Allocator::new(); -let ptr = solution.to_clvm(a).unwrap(); -assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); +let ptr = point.to_clvm(a).unwrap(); +assert_eq!(Point::from_clvm(a, ptr).unwrap(), point); ``` ### Curry -This represents the argument part of a curried CLVM program. Currying is a method of partially -applying some of the arguments without immediately calling the function. +This represents the argument part of a curried CLVM program. Currying is a way to partially +apply some of the arguments without immediately calling the function. -For example, `(A, B, C)` is encoded as `(c (q . A) (c (q . B) (c (q . C) 1)))`. Note that the -arguments are quoted and terminated with `1`, which is how partial application is implemented in CLVM. +For example, the curried arguments `[A, B, C]` are encoded as `(c (q . A) (c (q . B) (c (q . C) 1)))`. +Note that the arguments are quoted and terminated with `1`, which is how partial application is implemented in CLVM. You can read more about currying on the [Chia blockchain documentation](https://docs.chia.net/guides/chialisp-currying). -Note that the following code is for example purposes only and is not indicative of how to create a secure program. -Using a password like shown in this example is an insecure method of locking coins, but it's effective for learning. +Again, the following example is for demonstration purposes only: ```rust use clvmr::Allocator; @@ -126,7 +117,7 @@ use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple)] +#[clvm(atom)] enum Status { Pending, Completed, @@ -150,7 +141,7 @@ use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple)] +#[clvm(atom)] #[repr(u8)] enum Status { Pending = 36, @@ -177,8 +168,6 @@ use clvm_traits::{ToClvm, FromClvm}; #[clvm(list)] enum SpendMode { AppendValue { value: i32 }, - - #[clvm(tuple)] ClearValues, } @@ -194,7 +183,7 @@ assert_eq!(SpendMode::from_clvm(a, ptr).unwrap(), mode); ### Untagged Enums Often, the discriminator isn't necessary to encode, and you'd prefer to try to match each variant in order until one matches. -This is what `#[clvm(untagged)]` allows you to do. However, due to current limitations, it's not possible to mix this with `#[clvm(curry)]`. +This is what `#[clvm(untagged)]` allows you to do. Note that if there is any ambiguity, the first variant which matches a value will be the resulting value. For example, if both `A` and `B` are in that order and are the same type, if you serialize a value of `B`, it will be deserialized as `A`. @@ -204,7 +193,7 @@ use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple, untagged)] +#[clvm(list, untagged)] enum Either { ShortList([i32; 4]), ExtendedList([i32; 16]), From 6461d51494fb3c660e0e5e5a2bbdb62c35321934 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 21:42:15 -0400 Subject: [PATCH 03/16] Remove accidentally committed folder --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 058f815b1..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.checkOnSave": true -} From 06b8842fbaced2294f9a64b760fe9c7e50c0c97b Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 21:47:39 -0400 Subject: [PATCH 04/16] Some more comments --- crates/clvm-derive/src/to_clvm.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index 6e1aaab36..a698f59fc 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -56,6 +56,7 @@ fn encode_fields( Repr::Curry => quote!(encoder.encode_atom(&[1])?), }; + // We're going to build the return value in reverse order, so we need to start with the terminator. body.extend(quote! { let mut node = #initial_value; }); @@ -66,15 +67,18 @@ fn encode_fields( let mut if_body = TokenStream::new(); + // Encode the field value. if_body.extend(quote! { let value_node = <#ty as #crate_name::ToClvm<#node_name>>::to_clvm(&#value_name, encoder)?; }); if field.rest { + // This field represents the rest of the arguments, so we can replace the terminator with it. if_body.extend(quote! { node = value_node; }); } else { + // Prepend the field value to the existing node with a new pair. if_body.extend(quote! { node = encoder.#encode_next(value_node, node)?; }); @@ -82,12 +86,14 @@ fn encode_fields( if let Some(default) = &field.optional_with_default { if let Some(default) = default { + // If the field is equal to the default value, don't encode it. body.extend(quote! { if #value_name != #default { #if_body } }); } else { + // If the field is `None` and optional, don't encode it. body.extend(quote! { if let Some(#value_name) = #value_name { #if_body @@ -95,6 +101,7 @@ fn encode_fields( }); } } else { + // Encode the field unconditionally if it's not optional. body.extend(if_body); } } @@ -108,10 +115,12 @@ fn impl_for_struct(ast: DeriveInput, struct_info: StructInfo, node_name: Ident) let mut body = TokenStream::new(); for (i, field) in struct_info.fields.iter().enumerate() { + // We can't encode fields that are hidden, since they aren't on the actual struct. if field.hidden_with_value.is_some() { continue; } + // Rename the field so it doesn't clash with anything else in scope such as `node`. let value_name = Ident::new(&format!("field_{}", i), Span::mixed_site()); match struct_info.kind { @@ -181,6 +190,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok for variant in enum_info.variants.iter() { let repr = variant.repr.unwrap_or(enum_info.default_repr); + variant_bodies.push(encode_fields( &crate_name, &node_name, @@ -189,6 +199,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok )); } + // Encode the variant's fields directly. quote! { match self { #( #variant_destructures => { @@ -206,6 +217,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok } = variant_discriminants(&enum_info); if enum_info.default_repr == Repr::Atom { + // Encode the discriminant by itself as an atom. quote! { #( #discriminant_consts )* @@ -239,6 +251,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok )); } + // Encode the discriminant followed by the variant's fields. quote! { #( #discriminant_consts )* From e0795e994f75e5348f1a150dbf353ced0fcc567a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 23:05:21 -0400 Subject: [PATCH 05/16] Update tests --- crates/clvm-derive/src/from_clvm.rs | 2 +- crates/clvm-derive/src/to_clvm.rs | 11 +- crates/clvm-traits/src/lib.rs | 214 ++++++++++++++++++++++------ 3 files changed, 179 insertions(+), 48 deletions(-) diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index 2a596470b..f024c17cd 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -70,7 +70,7 @@ fn field_parser_fn_body( let (#ident, optional_node) = optional_node.and_then(|node| decoder.#decode_next(&node).ok()) .map(|(a, b)| (Some(a), Some(b))).unwrap_or((None, None)); - if let Some(new_node) = optional_node { + if let Some(new_node) = optional_node.as_ref().map(|node| decoder.clone_node(node)) { node = new_node; } }); diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index a698f59fc..c55c5e421 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -4,7 +4,7 @@ use syn::{parse_quote, DeriveInput, GenericParam, Ident, Index}; use crate::{ crate_name, - helpers::{add_trait_bounds, variant_discriminants, DiscriminantInfo}, + helpers::{add_trait_bounds, option_type, variant_discriminants, DiscriminantInfo}, parser::{parse, EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind}, }; @@ -63,7 +63,12 @@ fn encode_fields( for (i, field) in fields.iter().enumerate().rev() { let value_name = &value_names[i]; - let ty = &field.ty; + let mut ty = &field.ty; + + // If the field is optional, we need to unwrap the `Option` type into just `T`. + if field.optional_with_default == Some(None) { + ty = option_type(ty).expect("expected `Option` type for `optional` field"); + } let mut if_body = TokenStream::new(); @@ -88,7 +93,7 @@ fn encode_fields( if let Some(default) = default { // If the field is equal to the default value, don't encode it. body.extend(quote! { - if #value_name != #default { + if #value_name != &#default { #if_body } }); diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index 32a9655b7..c42d2c313 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -63,65 +63,178 @@ mod derive_tests { } #[test] - fn test_tuple() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + fn test_list_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] - struct TupleStruct { + struct Struct { a: u64, - #[clvm(rest)] b: i32, } - check(TupleStruct { a: 52, b: -32 }, "ff3481e0"); + // Includes the nil terminator. + check(Struct { a: 52, b: -32 }, "ff34ff81e080"); } #[test] - fn test_list() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + fn test_list_struct_with_rest() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] - struct ListStruct { + struct Struct { a: u64, + #[clvm(rest)] b: i32, } - check(ListStruct { a: 52, b: -32 }, "ff34ff81e080"); + // Does not include the nil terminator. + check(Struct { a: 52, b: -32 }, "ff3481e0"); } #[test] - fn test_curry() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + fn test_curry_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(curry)] - struct CurryStruct { + struct Struct { a: u64, b: i32, } check( - CurryStruct { a: 52, b: -32 }, + Struct { a: 52, b: -32 }, "ff04ffff0134ffff04ffff0181e0ff018080", ); } #[test] - fn test_unnamed() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + fn test_curry_struct_with_rest() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(curry)] + struct Struct { + a: u64, + #[clvm(rest)] + b: i32, + } + + check(Struct { a: 52, b: -32 }, "ff04ffff0134ff81e080"); + } + + #[test] + fn test_tuple_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] - struct UnnamedStruct(String, #[clvm(rest)] String); + struct Struct(String, String); - check(UnnamedStruct("A".to_string(), "B".to_string()), "ff4142"); + check(Struct("A".to_string(), "B".to_string()), "ff41ff4280"); } #[test] - fn test_newtype() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + fn test_newtype_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] - struct NewTypeStruct(#[clvm(rest)] String); + struct Struct(#[clvm(rest)] String); - check(NewTypeStruct("XYZ".to_string()), "8358595a"); + check(Struct("XYZ".to_string()), "8358595a"); } #[test] - fn test_literals() { + fn test_optional() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { + a: u64, + #[clvm(optional)] + b: Option, + } + + check( + Struct { + a: 52, + b: Some(-32), + }, + "ff34ff81e080", + ); + check(Struct { a: 52, b: None }, "ff3480"); + } + + #[test] + fn test_default() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { + a: u64, + #[clvm(default = 42)] + b: i32, + } + + check(Struct { a: 52, b: 32 }, "ff34ff2080"); + check(Struct { a: 52, b: 42 }, "ff3480"); + } + + #[test] + fn test_default_owned() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { + a: u64, + #[clvm(default = "Hello".to_string())] + b: String, + } + + check( + Struct { + a: 52, + b: "World".to_string(), + }, + "ff34ff85576f726c6480", + ); + check( + Struct { + a: 52, + b: "Hello".to_string(), + }, + "ff3480", + ); + } + + #[test] + fn test_multiple_optional() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { + a: u64, + #[clvm(optional)] + b: Option, + #[clvm(optional)] + c: Option, + } + + check( + Struct { + a: 52, + b: Some(-32), + c: Some(42), + }, + "ff34ff81e0ff2a80", + ); + check( + Struct { + a: 52, + b: Some(42), + c: None, + }, + "ff34ff2a80", + ); + check( + Struct { + a: 52, + b: None, + c: None, + }, + "ff3480", + ); + } + + #[test] + fn test_hidden_values() { #[derive(ToClvm, FromClvm)] #[hide_values] #[derive(Debug, PartialEq, Eq)] @@ -136,6 +249,14 @@ mod derive_tests { puzzle: P, solution: S, } + + check( + RunTailCondition { + puzzle: "puzzle".to_string(), + solution: "solution".to_string(), + }, + "ff33ff80ff818fff8670757a7a6c65ff88736f6c7574696f6e80", + ); } #[test] @@ -143,35 +264,29 @@ mod derive_tests { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] #[clvm(list)] enum Enum { - A(#[clvm(rest)] i32), - B { - #[clvm(rest)] - x: i32, - }, + A(i32), + B { x: i32 }, C, } - check(Enum::A(32), "ff8020"); - check(Enum::B { x: -72 }, "ff0181b8"); + check(Enum::A(32), "ff80ff2080"); + check(Enum::B { x: -72 }, "ff01ff81b880"); check(Enum::C, "ff0280"); } #[test] - fn test_explicit_enum() { + fn test_explicit_discriminant() { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] #[clvm(list)] #[repr(u8)] enum Enum { - A(#[clvm(rest)] i32) = 42, - B { - #[clvm(rest)] - x: i32, - } = 34, + A(i32) = 42, + B { x: i32 } = 34, C = 11, } - check(Enum::A(32), "ff2a20"); - check(Enum::B { x: -72 }, "ff2281b8"); + check(Enum::A(32), "ff2aff2080"); + check(Enum::B { x: -72 }, "ff22ff81b880"); check(Enum::C, "ff0b80"); } @@ -180,20 +295,18 @@ mod derive_tests { #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] #[clvm(list, untagged)] enum Enum { - A(#[clvm(rest)] i32), - + A(i32), B { x: i32, y: i32, }, - #[clvm(curry)] C { curried_value: String, }, } - check(Enum::A(32), "20"); + check(Enum::A(32), "ff2080"); check(Enum::B { x: -72, y: 94 }, "ff81b8ff5e80"); check( Enum::C { @@ -209,11 +322,11 @@ mod derive_tests { #[clvm(list, untagged)] enum Enum { // This variant is parsed first, so `B` will never be deserialized. - A(#[clvm(rest)] i32), + A(i32), // When `B` is serialized, it will round trip as `A` instead. - B(#[clvm(rest)] i32), + B(i32), // `C` will be deserialized as a fallback when the bytes don't deserialize to a valid `i32`. - C(#[clvm(rest)] String), + C(String), } // This round trips to the same value, since `A` is parsed first. @@ -236,4 +349,17 @@ mod derive_tests { Enum::C("Hello, world!".into()) ); } + + #[test] + fn test_custom_crate_name() { + use clvm_traits as clvm_traits2; + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list, crate_name = clvm_traits2)] + struct Struct { + a: u64, + b: i32, + } + + check(Struct { a: 52, b: -32 }, "ff34ff81e080"); + } } From bcf16e7ddc794c95fb1c3141db8a4a056119850d Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 23:18:34 -0400 Subject: [PATCH 06/16] Examples for default and optional --- crates/clvm-derive/src/parser/field_info.rs | 8 +-- crates/clvm-traits/docs/derive_macros.md | 57 +++++++++++++++++++++ crates/clvm-traits/src/lib.rs | 38 -------------- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/crates/clvm-derive/src/parser/field_info.rs b/crates/clvm-derive/src/parser/field_info.rs index abb4baf84..a82065de8 100644 --- a/crates/clvm-derive/src/parser/field_info.rs +++ b/crates/clvm-derive/src/parser/field_info.rs @@ -27,8 +27,8 @@ pub fn parse_named_fields(fields: &FieldsNamed) -> Vec { panic!("nothing can come after the `rest` field, since it consumes all arguments"); } - if optional && options.default.is_none() { - panic!("all fields after an optional field must also be optional"); + if optional { + panic!("only the last field can be optional, to prevent ambiguity"); } rest = options.rest; @@ -63,8 +63,8 @@ pub fn parse_unnamed_fields(fields: &FieldsUnnamed) -> Vec { panic!("nothing can come after the `rest` field, since it consumes all arguments"); } - if optional && options.default.is_none() { - panic!("all fields after an optional field must also be optional"); + if optional { + panic!("only the last field can be optional, to prevent ambiguity"); } rest = options.rest; diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index d952f63d7..756984196 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -101,6 +101,63 @@ let ptr = args.to_clvm(a).unwrap(); assert_eq!(PasswordArgs::from_clvm(a, ptr).unwrap(), args); ``` +## Optional Fields + +You may mark the last field in a struct or enum variant as optional. +However, specifying multiple optional fields would be ambiguous, so it's not allowed. + +### Optional Value + +You can specify a field as optional directly, which will be set to `None` if it's not present: + +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +struct Person { + name: String, + #[clvm(optional)] + email: Option, +} + +let person = Person { + name: "Bob".to_string(), + email: Some("bob@example.com".to_string()), +}; + +let a = &mut Allocator::new(); +let ptr = person.to_clvm(a).unwrap(); +assert_eq!(Person::from_clvm(a, ptr).unwrap(), person); +``` + +### Default Value + +You can also specify the default value manually. The field will not be serialized if it matches the default value: + +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +struct Person { + name: String, + #[clvm(default = 18)] + age: u8, +} + +let person = Person { + name: "Bob".to_string(), + age: 24, +}; + +let a = &mut Allocator::new(); +let ptr = person.to_clvm(a).unwrap(); +assert_eq!(Person::from_clvm(a, ptr).unwrap(), person); +``` + ## Enums In Rust, enums contain a discriminant, a value used to distinguish between each variant of the enum. diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index c42d2c313..e22ba82df 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -195,44 +195,6 @@ mod derive_tests { ); } - #[test] - fn test_multiple_optional() { - #[derive(Debug, ToClvm, FromClvm, PartialEq)] - #[clvm(list)] - struct Struct { - a: u64, - #[clvm(optional)] - b: Option, - #[clvm(optional)] - c: Option, - } - - check( - Struct { - a: 52, - b: Some(-32), - c: Some(42), - }, - "ff34ff81e0ff2a80", - ); - check( - Struct { - a: 52, - b: Some(42), - c: None, - }, - "ff34ff2a80", - ); - check( - Struct { - a: 52, - b: None, - c: None, - }, - "ff3480", - ); - } - #[test] fn test_hidden_values() { #[derive(ToClvm, FromClvm)] From 2ab9e230a6bc23e03b93a612936b451fbdf5b273 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 23:26:32 -0400 Subject: [PATCH 07/16] Add rest and crate name example --- crates/clvm-traits/docs/derive_macros.md | 98 +++++++++++++++++------- 1 file changed, 72 insertions(+), 26 deletions(-) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 756984196..fbfe9cbc7 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -44,32 +44,6 @@ let ptr = solution.to_clvm(a).unwrap(); assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); ``` -### Unterminated List - -You can omit the nil terminator by using `#[clvm(rest)]`: - -```rust -use clvmr::Allocator; -use clvm_traits::{ToClvm, FromClvm}; - -#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(list)] -struct Point { - x: i32, - #[clvm(rest)] - y: i32, -} - -let point = Point { - x: 5, - y: 2, -}; - -let a = &mut Allocator::new(); -let ptr = point.to_clvm(a).unwrap(); -assert_eq!(Point::from_clvm(a, ptr).unwrap(), point); -``` - ### Curry This represents the argument part of a curried CLVM program. Currying is a way to partially @@ -158,6 +132,65 @@ let ptr = person.to_clvm(a).unwrap(); assert_eq!(Person::from_clvm(a, ptr).unwrap(), person); ``` +## Consume the Rest + +You can consume the rest of the list items (or curried arguments, if using the `curry` representation) by using `#[clvm(rest)]`. +This is useful for types which are represented compactly, without a nil terminator. Or for extending a list of arguments with another. +You can also use it if you want to lazily parse the rest later. + +Here's a simple example of a compact representation: + +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +struct Point { + x: i32, + #[clvm(rest)] + y: i32, +} + +let point = Point { + x: 5, + y: 2, +}; + +let a = &mut Allocator::new(); +let ptr = point.to_clvm(a).unwrap(); +assert_eq!(Point::from_clvm(a, ptr).unwrap(), point); +``` + +And here's an example of lazily parsing the rest later: + +```rust +use clvmr::{Allocator, NodePtr}; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +struct Items { + first: String, + #[clvm(rest)] + rest: T, +} + +let items = Items { + first: "First Item".to_string(), + rest: [1, 2, 3, 4, 5], +}; + +let a = &mut Allocator::new(); +let ptr = items.to_clvm(a).unwrap(); + +let items = Items::::from_clvm(a, ptr).unwrap(); +assert_eq!(items.first, "First Item".to_string()); + +let rest: [u8; 5] = FromClvm::from_clvm(a, items.rest).unwrap(); +assert_eq!(rest, [1, 2, 3, 4, 5]); +``` + ## Enums In Rust, enums contain a discriminant, a value used to distinguish between each variant of the enum. @@ -262,3 +295,16 @@ let a = &mut Allocator::new(); let ptr = value.to_clvm(a).unwrap(); assert_eq!(Either::from_clvm(a, ptr).unwrap(), value); ``` + +## Crate Name + +You can override the name of the `clvm_traits` crate used within the macros: + +```rust +use clvmr::Allocator; +use clvm_traits::{self as renamed_clvm_traits, ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list, crate_name = renamed_clvm_traits)] +struct Example; +``` From 7c9ec49a1fc107c8572d016e94907e8cc4d50d80 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 19 May 2024 23:31:30 -0400 Subject: [PATCH 08/16] Add hidden value example --- crates/clvm-traits/docs/derive_macros.md | 39 ++++++++++++++++++++++++ crates/clvm-traits/src/lib.rs | 10 +++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index fbfe9cbc7..0fe2cafde 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -296,6 +296,45 @@ let ptr = value.to_clvm(a).unwrap(); assert_eq!(Either::from_clvm(a, ptr).unwrap(), value); ``` +## Hidden Values + +Sometimes you may want to include constants inside of a struct without actually exposing them as fields. +It's possible to do this with `#[clvm(hidden_value)]`, however you must use an attribute macro to remove the hidden fields. + +This has to be done in the proper order, or it will not work. + +The order is as follows: + +- Derive `ToClvm` and `FromClvm`, so that the hidden fields are serialized and deserialized properly. +- Use `#[hide_values]` to remove them from the struct or enum. +- Add any other derives you want after, so they don't see the hidden fields. +- Write any `#[clvm(...)]` options you want to use. + +Here is an example of this: + +```rust +use clvmr::Allocator; +use clvm_traits::{hide_values, ToClvm, FromClvm}; + +#[derive(ToClvm, FromClvm)] +#[hide_values] +#[derive(Debug, PartialEq, Eq)] +#[clvm(list)] +struct CustomTerminator { + value: u32, + #[clvm(hidden_value = 42, rest)] + terminator: u8, +} + +let value = CustomTerminator { + value: 100, +}; + +let a = &mut Allocator::new(); +let ptr = value.to_clvm(a).unwrap(); +assert_eq!(CustomTerminator::from_clvm(a, ptr).unwrap(), value); +``` + ## Crate Name You can override the name of the `clvm_traits` crate used within the macros: diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index e22ba82df..1a568941a 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -199,7 +199,7 @@ mod derive_tests { fn test_hidden_values() { #[derive(ToClvm, FromClvm)] #[hide_values] - #[derive(Debug, PartialEq, Eq)] + #[derive(Debug, PartialEq)] #[clvm(list)] struct RunTailCondition { #[clvm(hidden_value = 51)] @@ -223,7 +223,7 @@ mod derive_tests { #[test] fn test_enum() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] enum Enum { A(i32), @@ -238,7 +238,7 @@ mod derive_tests { #[test] fn test_explicit_discriminant() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list)] #[repr(u8)] enum Enum { @@ -254,7 +254,7 @@ mod derive_tests { #[test] fn test_untagged_enum() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list, untagged)] enum Enum { A(i32), @@ -280,7 +280,7 @@ mod derive_tests { #[test] fn test_untagged_enum_parsing_order() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] #[clvm(list, untagged)] enum Enum { // This variant is parsed first, so `B` will never be deserialized. From 4a951361b32b503549240b60bad9f13d60476df9 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 21 May 2024 10:17:44 -0400 Subject: [PATCH 09/16] Improve curry description --- crates/clvm-traits/docs/derive_macros.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 0fe2cafde..21de39cc4 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -46,8 +46,8 @@ assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); ### Curry -This represents the argument part of a curried CLVM program. Currying is a way to partially -apply some of the arguments without immediately calling the function. +This represents the argument part of a curried CLVM program. +In Chia, currying commits to and partially applies some of the arguments of a program, without calling it. For example, the curried arguments `[A, B, C]` are encoded as `(c (q . A) (c (q . B) (c (q . C) 1)))`. Note that the arguments are quoted and terminated with `1`, which is how partial application is implemented in CLVM. From 4f43c0fbf622bead9387913849bc15d6ea460486 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 21 May 2024 11:44:07 -0400 Subject: [PATCH 10/16] Rework default, docs, and add transparent --- crates/chia-puzzles/src/proof.rs | 6 +-- crates/chia-puzzles/src/puzzles/offer.rs | 9 ++-- crates/clvm-derive/src/from_clvm.rs | 45 +++++++------------ crates/clvm-derive/src/helpers.rs | 43 +----------------- crates/clvm-derive/src/parser/attributes.rs | 32 ++++++------- crates/clvm-derive/src/parser/enum_info.rs | 32 ++++++++++--- crates/clvm-derive/src/parser/field_info.rs | 4 +- crates/clvm-derive/src/parser/struct_info.rs | 43 +++++++++--------- crates/clvm-derive/src/parser/variant_info.rs | 37 ++++++--------- crates/clvm-derive/src/to_clvm.rs | 43 +++++++----------- crates/clvm-traits/docs/derive_macros.md | 21 ++++++--- crates/clvm-traits/src/clvm_encoder.rs | 8 +--- crates/clvm-traits/src/lib.rs | 2 +- 13 files changed, 139 insertions(+), 186 deletions(-) diff --git a/crates/chia-puzzles/src/proof.rs b/crates/chia-puzzles/src/proof.rs index 6d891e767..37bfbbf73 100644 --- a/crates/chia-puzzles/src/proof.rs +++ b/crates/chia-puzzles/src/proof.rs @@ -3,10 +3,10 @@ use clvm_traits::{FromClvm, ToClvm}; #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(untagged, list)] +#[clvm(transparent)] pub enum Proof { - Lineage(#[clvm(rest)] LineageProof), - Eve(#[clvm(rest)] EveProof), + Lineage(LineageProof), + Eve(EveProof), } #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] diff --git a/crates/chia-puzzles/src/puzzles/offer.rs b/crates/chia-puzzles/src/puzzles/offer.rs index a760a432d..69eb2eeea 100644 --- a/crates/chia-puzzles/src/puzzles/offer.rs +++ b/crates/chia-puzzles/src/puzzles/offer.rs @@ -5,9 +5,8 @@ use hex_literal::hex; #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(transparent)] pub struct SettlementPaymentsSolution { - #[clvm(rest)] pub notarized_payments: Vec, } @@ -22,10 +21,10 @@ pub struct NotarizedPayment { #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(untagged, list)] +#[clvm(transparent)] pub enum Payment { - WithoutMemos(#[clvm(rest)] PaymentWithoutMemos), - WithMemos(#[clvm(rest)] PaymentWithMemos), + WithoutMemos(PaymentWithoutMemos), + WithMemos(PaymentWithMemos), } #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index f024c17cd..2a47caed8 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -1,10 +1,10 @@ use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{parse_quote, DeriveInput, GenericParam, Ident}; use crate::{ crate_name, - helpers::{add_trait_bounds, option_type, variant_discriminants, DiscriminantInfo}, + helpers::{add_trait_bounds, variant_discriminants, DiscriminantInfo}, parser::{parse, EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind}, }; @@ -38,7 +38,7 @@ fn field_parser_fn_body( .collect(); let decode_next = match repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), // Decode `(A . B)` pairs for lists. Repr::List => quote!(decode_pair), // Decode `(c (q . A) B)` pairs for curried arguments. @@ -92,14 +92,7 @@ fn field_parser_fn_body( for (i, field) in fields.iter().enumerate() { let ident = &temp_names[i]; - let mut ty = &field.ty; - - // If the field is optional, we need to unwrap the `Option` type into just `T`. - // This is because it's still important to validate the value is correct. - // We only set it to `None` if it's non-existent, not if parsing fails. - if field.optional_with_default == Some(None) { - ty = option_type(ty).expect("expected `Option` type for `optional` field"); - } + let ty = &field.ty; // This handles the actual decoding of the field's value. let mut decoded_value = quote! { @@ -107,23 +100,15 @@ fn field_parser_fn_body( }; if let Some(default) = &field.optional_with_default { - if let Some(default) = default { - // If there's a default value, we need to use it instead if the field isn't present. - decoded_value = quote! { - #ident.map(|#ident| #decoded_value).unwrap_or(Ok(#default))? - }; - } else { - // Otherwise, we can just return `None` if the field isn't present. - // And wrap it in `Some` if it is. - decoded_value = quote! { - match #ident.map(|#ident| -> ::std::result::Result<#ty, #crate_name::FromClvmError> { - #decoded_value - }) { - Some(value) => value.map(Some), - None => Ok(None), - }? - }; - } + let default = default + .as_ref() + .map(|expr| expr.to_token_stream()) + .unwrap_or_else(|| quote!(<#ty as ::std::default::Default>::default())); + + // If there's a default value, we need to use it instead if the field isn't present. + decoded_value = quote! { + #ident.map(|#ident| #decoded_value).unwrap_or(Ok(#default))? + }; } else { // If the field isn't optional, we can simply return any parsing errors early for this field. decoded_value = quote!(#decoded_value?); @@ -161,7 +146,7 @@ fn field_parser_fn_body( fn check_rest_value(crate_name: &Ident, repr: Repr) -> TokenStream { match repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), Repr::List => { // If the last field is not `rest`, we need to check that the `node` is nil. // If it's not nil, it's not a proper list, and we should return an error. @@ -285,7 +270,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok let variant_parsers = enum_variant_parsers(&crate_name, &node_name, &enum_info); let decode_next = match enum_info.default_repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), // Decode `(A . B)` pairs for lists. Repr::List => quote!(decode_pair), // Decode `(c (q . A) B)` pairs for curried arguments. diff --git a/crates/clvm-derive/src/helpers.rs b/crates/clvm-derive/src/helpers.rs index ce662a43f..9c400d420 100644 --- a/crates/clvm-derive/src/helpers.rs +++ b/crates/clvm-derive/src/helpers.rs @@ -1,9 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{ - parse_quote, Expr, GenericArgument, GenericParam, Generics, Ident, PathArguments, Type, - TypeParamBound, -}; +use syn::{parse_quote, Expr, GenericParam, Generics, Ident, TypeParamBound}; use crate::parser::EnumInfo; @@ -15,44 +12,6 @@ pub fn add_trait_bounds(generics: &mut Generics, bound: TypeParamBound) { } } -pub fn option_type(ty: &Type) -> Option<&Type> { - let Type::Path(ty) = ty else { return None }; - - if ty.qself.is_some() { - return None; - } - - let ty = &ty.path; - - if ty.segments.is_empty() || ty.segments.last().unwrap().ident != "Option" { - return None; - } - - if !(ty.segments.len() == 1 - || (ty.segments.len() == 3 - && ["core", "std"].contains(&ty.segments[0].ident.to_string().as_str()) - && ty.segments[1].ident == "option")) - { - return None; - } - - let last_segment = ty.segments.last().unwrap(); - - let PathArguments::AngleBracketed(generics) = &last_segment.arguments else { - return None; - }; - - if generics.args.len() != 1 { - return None; - } - - let GenericArgument::Type(inner_type) = &generics.args[0] else { - return None; - }; - - Some(inner_type) -} - pub struct DiscriminantInfo { pub discriminant_consts: Vec, pub discriminant_names: Vec, diff --git a/crates/clvm-derive/src/parser/attributes.rs b/crates/clvm-derive/src/parser/attributes.rs index a71c9d161..4cc87f471 100644 --- a/crates/clvm-derive/src/parser/attributes.rs +++ b/crates/clvm-derive/src/parser/attributes.rs @@ -13,13 +13,17 @@ pub enum Repr { List, /// Represents `(c (q . A) (c (q . B) (c (q . C) 1)))`. Curry, - /// Represents `A` on its own. + /// Represents the first field `A` on its own, with no other fields allowed. + Transparent, + /// Represents `A` on its own, if it's an atom. Atom, } impl Repr { pub fn expect(repr: Option) -> Repr { - repr.expect("missing either `list`, `curry`, or `atom` in `clvm` attribute options") + repr.expect( + "missing either `list`, `curry`, `transparent`, or `atom` in `clvm` attribute options", + ) } } @@ -28,6 +32,7 @@ impl fmt::Display for Repr { f.write_str(match self { Self::List => "list", Self::Curry => "curry", + Self::Transparent => "transparent", Self::Atom => "atom", }) } @@ -60,8 +65,7 @@ enum ClvmOption { HiddenValue(Expr), CrateName(Ident), Untagged, - Optional, - Default(Expr), + Default(Option), Rest, } @@ -72,6 +76,7 @@ impl Parse for ClvmOption { match ident.to_string().as_str() { "list" => Ok(Self::Repr(Repr::List)), "curry" => Ok(Self::Repr(Repr::Curry)), + "transparent" => Ok(Self::Repr(Repr::Transparent)), "atom" => Ok(Self::Repr(Repr::Atom)), "untagged" => Ok(Self::Untagged), "hidden_value" => { @@ -82,10 +87,13 @@ impl Parse for ClvmOption { input.parse::()?; Ok(Self::CrateName(input.parse()?)) } - "optional" => Ok(Self::Optional), "default" => { - input.parse::()?; - Ok(Self::Default(input.parse()?)) + if input.peek(Token![=]) { + input.parse::()?; + Ok(Self::Default(Some(input.parse()?))) + } else { + Ok(Self::Default(None)) + } } "rest" => Ok(Self::Rest), _ => Err(syn::Error::new(ident.span(), "unknown argument")), @@ -164,15 +172,9 @@ pub fn parse_clvm_options(attrs: &[Attribute]) -> ClvmOptions { } ClvmOption::Default(default) => { if options.default.is_some() { - panic!("can't specify `default` again when `default` or `optional` is already set"); - } - options.default = Some(Some(default)); - } - ClvmOption::Optional => { - if options.default.is_some() { - panic!("can't specify `optional` again when `default` or `optional` is already set"); + panic!("duplicate `default` option"); } - options.default = Some(None); + options.default = Some(default); } ClvmOption::Rest => { if options.rest { diff --git a/crates/clvm-derive/src/parser/enum_info.rs b/crates/clvm-derive/src/parser/enum_info.rs index 65fbfa318..3a937e5ad 100644 --- a/crates/clvm-derive/src/parser/enum_info.rs +++ b/crates/clvm-derive/src/parser/enum_info.rs @@ -10,13 +10,13 @@ pub struct EnumInfo { pub crate_name: Option, } -pub fn parse_enum(options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { +pub fn parse_enum(mut options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { if options.hidden_value.is_some() { panic!("`hidden_value` only applies to fields"); } if options.default.is_some() { - panic!("`default` and `optional` only apply to fields"); + panic!("`default` only applies to fields"); } if options.rest { @@ -25,12 +25,21 @@ pub fn parse_enum(options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { let repr = Repr::expect(options.repr); + if repr == Repr::Transparent { + if options.untagged { + panic!("`transparent` enums are implicitly untagged"); + } + + options.untagged = true; + } + let mut variants = Vec::new(); for variant in data_enum.variants.iter() { let variant_options = parse_clvm_options(&variant.attrs); + let variant_repr = variant_options.repr; - if repr == Repr::Atom && variant_options.repr.is_some() { + if repr == Repr::Atom && variant_repr.is_some() { panic!("cannot override `atom` representation for individual enum variants"); } @@ -38,11 +47,24 @@ pub fn parse_enum(options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { panic!("cannot have fields in an `atom` enum variant"); } - if !options.untagged && variant_options.repr.is_some() { + if !options.untagged && variant_repr.is_some() { panic!("cannot specify representation for individual enum variants in a tagged enum"); } - variants.push(parse_variant(variant_options, variant)); + let mut variant_info = parse_variant(variant_options, variant); + + if (repr == Repr::Transparent && variant_repr.is_none()) + || variant_repr == Some(Repr::Transparent) + { + if variant_info.fields.len() != 1 { + panic!("`transparent` enum variants must have exactly one field"); + } + + variant_info.fields[0].rest = true; + variant_info.repr = Some(Repr::List); + } + + variants.push(variant_info); } EnumInfo { diff --git a/crates/clvm-derive/src/parser/field_info.rs b/crates/clvm-derive/src/parser/field_info.rs index a82065de8..fa196ece7 100644 --- a/crates/clvm-derive/src/parser/field_info.rs +++ b/crates/clvm-derive/src/parser/field_info.rs @@ -100,10 +100,10 @@ fn check_field_options(options: &ClvmOptions) { } if options.default.is_some() && options.hidden_value.is_some() { - panic!("neither `default` nor `optional` can be used with the `hidden_value` option set"); + panic!("`default` can't be used with `hidden_value` set"); } if options.default.is_some() && options.rest { - panic!("`default` can't be used with the `rest` option set"); + panic!("`default` can't be used with `rest` option set"); } } diff --git a/crates/clvm-derive/src/parser/struct_info.rs b/crates/clvm-derive/src/parser/struct_info.rs index c13143592..cb7360a85 100644 --- a/crates/clvm-derive/src/parser/struct_info.rs +++ b/crates/clvm-derive/src/parser/struct_info.rs @@ -30,14 +30,14 @@ pub fn parse_struct(options: ClvmOptions, data_struct: &DataStruct) -> StructInf } if options.default.is_some() { - panic!("`default` and `optional` only apply to fields"); + panic!("`default` only applies to fields"); } if options.rest { panic!("`rest` only applies to fields"); } - let repr = Repr::expect(options.repr); + let mut repr = Repr::expect(options.repr); if repr == Repr::Atom { panic!("`atom` is not a valid representation for structs"); @@ -45,24 +45,25 @@ pub fn parse_struct(options: ClvmOptions, data_struct: &DataStruct) -> StructInf let crate_name = options.crate_name; - match &data_struct.fields { - Fields::Unit => StructInfo { - kind: StructKind::Unit, - fields: Vec::new(), - repr, - crate_name, - }, - Fields::Named(fields) => StructInfo { - kind: StructKind::Named, - fields: parse_named_fields(fields), - repr, - crate_name, - }, - Fields::Unnamed(fields) => StructInfo { - kind: StructKind::Unnamed, - fields: parse_unnamed_fields(fields), - repr, - crate_name, - }, + let (kind, mut fields) = match &data_struct.fields { + Fields::Unit => (StructKind::Unit, Vec::new()), + Fields::Named(fields) => (StructKind::Named, parse_named_fields(fields)), + Fields::Unnamed(fields) => (StructKind::Unnamed, parse_unnamed_fields(fields)), + }; + + if repr == Repr::Transparent { + if fields.len() != 1 { + panic!("`transparent` structs must have exactly one field"); + } + + fields[0].rest = true; + repr = Repr::List; + } + + StructInfo { + kind, + fields, + repr, + crate_name, } } diff --git a/crates/clvm-derive/src/parser/variant_info.rs b/crates/clvm-derive/src/parser/variant_info.rs index d7d8cff0f..8ac270aae 100644 --- a/crates/clvm-derive/src/parser/variant_info.rs +++ b/crates/clvm-derive/src/parser/variant_info.rs @@ -35,7 +35,7 @@ pub fn parse_variant(options: ClvmOptions, variant: &Variant) -> VariantInfo { } if options.default.is_some() { - panic!("`default` and `optional` only apply to fields"); + panic!("`default` only applies to fields"); } if options.rest { @@ -44,33 +44,24 @@ pub fn parse_variant(options: ClvmOptions, variant: &Variant) -> VariantInfo { let name = variant.ident.clone(); let discriminant = variant.discriminant.clone().map(|(_, expr)| expr); + let repr = options.repr; if repr == Some(Repr::Atom) { panic!("`atom` is not a valid representation for individual enum variants"); } - match &variant.fields { - Fields::Unit => VariantInfo { - kind: VariantKind::Unit, - name, - fields: Vec::new(), - discriminant, - repr, - }, - Fields::Named(fields) => VariantInfo { - kind: VariantKind::Named, - name, - fields: parse_named_fields(fields), - discriminant, - repr, - }, - Fields::Unnamed(fields) => VariantInfo { - kind: VariantKind::Unnamed, - name, - fields: parse_unnamed_fields(fields), - discriminant, - repr, - }, + let (kind, fields) = match &variant.fields { + Fields::Unit => (VariantKind::Unit, Vec::new()), + Fields::Named(fields) => (VariantKind::Named, parse_named_fields(fields)), + Fields::Unnamed(fields) => (VariantKind::Unnamed, parse_unnamed_fields(fields)), + }; + + VariantInfo { + kind, + name, + fields, + discriminant, + repr, } } diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index c55c5e421..a9262ea07 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -1,10 +1,10 @@ use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{parse_quote, DeriveInput, GenericParam, Ident, Index}; use crate::{ crate_name, - helpers::{add_trait_bounds, option_type, variant_discriminants, DiscriminantInfo}, + helpers::{add_trait_bounds, variant_discriminants, DiscriminantInfo}, parser::{parse, EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind}, }; @@ -43,7 +43,7 @@ fn encode_fields( } let encode_next = match repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), // Encode `(A . B)` pairs for lists. Repr::List => quote!(encode_pair), // Encode `(c (q . A) B)` pairs for curried arguments. @@ -51,7 +51,7 @@ fn encode_fields( }; let initial_value = match repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), Repr::List => quote!(encoder.encode_atom(&[])?), Repr::Curry => quote!(encoder.encode_atom(&[1])?), }; @@ -63,12 +63,7 @@ fn encode_fields( for (i, field) in fields.iter().enumerate().rev() { let value_name = &value_names[i]; - let mut ty = &field.ty; - - // If the field is optional, we need to unwrap the `Option` type into just `T`. - if field.optional_with_default == Some(None) { - ty = option_type(ty).expect("expected `Option` type for `optional` field"); - } + let ty = &field.ty; let mut if_body = TokenStream::new(); @@ -90,21 +85,17 @@ fn encode_fields( } if let Some(default) = &field.optional_with_default { - if let Some(default) = default { - // If the field is equal to the default value, don't encode it. - body.extend(quote! { - if #value_name != &#default { - #if_body - } - }); - } else { - // If the field is `None` and optional, don't encode it. - body.extend(quote! { - if let Some(#value_name) = #value_name { - #if_body - } - }); - } + let default = default + .as_ref() + .map(|expr| expr.to_token_stream()) + .unwrap_or_else(|| quote!(<#ty as ::std::default::Default>::default())); + + // If the field is equal to the default value, don't encode it. + body.extend(quote! { + if #value_name != &#default { + #if_body + } + }); } else { // Encode the field unconditionally if it's not optional. body.extend(if_body); @@ -237,7 +228,7 @@ fn impl_for_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> Tok } } else { let encode_next = match enum_info.default_repr { - Repr::Atom => unreachable!(), + Repr::Atom | Repr::Transparent => unreachable!(), // Encode `(A . B)` pairs for lists. Repr::List => quote!(encode_pair), // Encode `(c (q . A) B)` pairs for curried arguments. diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 21de39cc4..8241ae8c1 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -16,6 +16,8 @@ This is how non-atomic values are structured in CLVM. This represents values in a nil terminated series of nested cons-pairs, also known as a proper list. +Note that if you mark the last field to [consume the rest of the list](#consume-the-rest), there is no nil terminator. + For example, with the list `[A, B, C]`, we build the list in reverse: - Start with the nil terminator `()` @@ -49,8 +51,10 @@ assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); This represents the argument part of a curried CLVM program. In Chia, currying commits to and partially applies some of the arguments of a program, without calling it. +The arguments are quoted and terminated with `1`, which is how partial application is implemented in CLVM. +Note that if you mark the last field to [consume the rest of the arguments](#consume-the-rest), there is no `1` terminator. + For example, the curried arguments `[A, B, C]` are encoded as `(c (q . A) (c (q . B) (c (q . C) 1)))`. -Note that the arguments are quoted and terminated with `1`, which is how partial application is implemented in CLVM. You can read more about currying on the [Chia blockchain documentation](https://docs.chia.net/guides/chialisp-currying). @@ -77,12 +81,16 @@ assert_eq!(PasswordArgs::from_clvm(a, ptr).unwrap(), args); ## Optional Fields -You may mark the last field in a struct or enum variant as optional. -However, specifying multiple optional fields would be ambiguous, so it's not allowed. +You can only mark the last field in a struct or enum variant as optional. + +This restriction is in place because if you were able to have multiple optional fields, +or an optional field that isn't at the end, it would be ambiguous. ### Optional Value -You can specify a field as optional directly, which will be set to `None` if it's not present: +You can set a field as optional by marking it as `#[clvm(default)]`. +If the field isn't present when deserializing, it will default to the `Default` implementation of the type. +When serializing, it will check if it's equal to the default and omit it if so. ```rust use clvmr::Allocator; @@ -92,7 +100,7 @@ use clvm_traits::{ToClvm, FromClvm}; #[clvm(list)] struct Person { name: String, - #[clvm(optional)] + #[clvm(default)] email: Option, } @@ -108,7 +116,8 @@ assert_eq!(Person::from_clvm(a, ptr).unwrap(), person); ### Default Value -You can also specify the default value manually. The field will not be serialized if it matches the default value: +You can also specify the default value to check against manually. +This is useful if you want to override the `Default` trait, or if the `Default` trait isn't implemented. ```rust use clvmr::Allocator; diff --git a/crates/clvm-traits/src/clvm_encoder.rs b/crates/clvm-traits/src/clvm_encoder.rs index c3dde4639..f825b82a3 100644 --- a/crates/clvm-traits/src/clvm_encoder.rs +++ b/crates/clvm-traits/src/clvm_encoder.rs @@ -41,13 +41,7 @@ impl ClvmEncoder for Allocator { type Node = NodePtr; fn encode_atom(&mut self, bytes: &[u8]) -> Result { - Ok(if bytes.is_empty() { - self.nil() - } else if bytes == [1] { - self.one() - } else { - self.new_atom(bytes).or(Err(ToClvmError::OutOfMemory))? - }) + self.new_atom(bytes).or(Err(ToClvmError::OutOfMemory)) } fn encode_pair( diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index 1a568941a..c08483231 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -141,7 +141,7 @@ mod derive_tests { #[clvm(list)] struct Struct { a: u64, - #[clvm(optional)] + #[clvm(default)] b: Option, } From 06e29818f09c1200b1b3f56081f08bd15a483385 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 21 May 2024 11:55:22 -0400 Subject: [PATCH 11/16] Improve examples in clvm-traits docs --- crates/clvm-traits/docs/derive_macros.md | 49 +++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 8241ae8c1..e91a8b09f 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -25,25 +25,29 @@ For example, with the list `[A, B, C]`, we build the list in reverse: - Then continue on with `(B . (C . ()))` - Finally, the list is represented as `(A . (B . (C . ())))` -Note that the following example of using a password for a Chia puzzle is insecure, but it's effective for demonstration. - ```rust use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(list)] -struct PasswordSolution { - password: String, +struct Tiers { + high: u8, + medium: u8, + low: u8, } -let solution = PasswordSolution { - password: "Hello".into(), +// The CLVM representation for this is `(10 5 1)`. +// It can also be written as `(10 . (5 . (1 . ())))`. +let value = Tiers { + high: 10, + medium: 5, + low: 1, }; let a = &mut Allocator::new(); -let ptr = solution.to_clvm(a).unwrap(); -assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); +let ptr = value.to_clvm(a).unwrap(); +assert_eq!(Tiers::from_clvm(a, ptr).unwrap(), value); ``` ### Curry @@ -58,7 +62,7 @@ For example, the curried arguments `[A, B, C]` are encoded as `(c (q . A) (c (q You can read more about currying on the [Chia blockchain documentation](https://docs.chia.net/guides/chialisp-currying). -Again, the following example is for demonstration purposes only: +The following example is for demonstration purposes only: ```rust use clvmr::Allocator; @@ -66,17 +70,20 @@ use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(curry)] -struct PasswordArgs { - password: String, +struct PuzzleArgs { + code_to_unlock: u32, + verification_level: u8, } -let args = PasswordArgs { - password: "Hello".into(), +// The CLVM representation for this is `(c (q . 4328714) (c (q . 5) 1))`. +let args = PuzzleArgs { + code_to_unlock: 4328714, + verification_level: 5, }; let a = &mut Allocator::new(); let ptr = args.to_clvm(a).unwrap(); -assert_eq!(PasswordArgs::from_clvm(a, ptr).unwrap(), args); +assert_eq!(PuzzleArgs::from_clvm(a, ptr).unwrap(), args); ``` ## Optional Fields @@ -104,6 +111,8 @@ struct Person { email: Option, } +// The CLVM representation of this is `("Bob" "bob@example.com")`. +// If `email` had been set to `None`, the representation would have just been `("Bob")`. let person = Person { name: "Bob".to_string(), email: Some("bob@example.com".to_string()), @@ -131,6 +140,8 @@ struct Person { age: u8, } +// The CLVM representation for this is `("Bob" 24)`. +// If `age` had been set to `18`, the representation would have been just `("Bob")`. let person = Person { name: "Bob".to_string(), age: 24, @@ -161,6 +172,7 @@ struct Point { y: i32, } +// The CLVM representation of this is `(5 . 2)` (with no nil terminator). let point = Point { x: 5, y: 2, @@ -185,6 +197,8 @@ struct Items { rest: T, } +// The CLVM representation of this is `("First Item" 1 2 3 4 5)`. +// Notice how the list is not a separate argument, but rather the rest of the arguments. let items = Items { first: "First Item".to_string(), rest: [1, 2, 3, 4, 5], @@ -193,9 +207,11 @@ let items = Items { let a = &mut Allocator::new(); let ptr = items.to_clvm(a).unwrap(); +// We parse `("First Item" . )` let items = Items::::from_clvm(a, ptr).unwrap(); assert_eq!(items.first, "First Item".to_string()); +// Then parse rest into a separate list of `(1 2 3 4 5)`. let rest: [u8; 5] = FromClvm::from_clvm(a, items.rest).unwrap(); assert_eq!(rest, [1, 2, 3, 4, 5]); ``` @@ -222,6 +238,7 @@ enum Status { Completed, } +// The CLVM representation of this is just `0`. let status = Status::Pending; let a = &mut Allocator::new(); @@ -247,6 +264,7 @@ enum Status { Completed = 42, } +// The CLVM representation of this is `36`. let status = Status::Pending; let a = &mut Allocator::new(); @@ -270,6 +288,7 @@ enum SpendMode { ClearValues, } +// The CLVM representation of this is `(42)`. let mode = SpendMode::AppendValue { value: 42 }; @@ -298,6 +317,7 @@ enum Either { ExtendedList([i32; 16]), } +// The CLVM representation of this is `((42 42 42 42))`. let value = Either::ShortList([42; 4]); let a = &mut Allocator::new(); @@ -335,6 +355,7 @@ struct CustomTerminator { terminator: u8, } +// The CLVM representation of this is `(100 . 42)`. let value = CustomTerminator { value: 100, }; From 447b3e097687f7e2b368b2009408ef1e94eb4cb9 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 21 May 2024 16:12:59 -0400 Subject: [PATCH 12/16] Add documentation for the transparent representation --- crates/clvm-traits/docs/derive_macros.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index e91a8b09f..bd30bd747 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -86,6 +86,27 @@ let ptr = args.to_clvm(a).unwrap(); assert_eq!(PuzzleArgs::from_clvm(a, ptr).unwrap(), args); ``` +### Transparent + +If you want a struct to have the same CLVM representation as its inner struct (a newtype), you can use the `transparent` representation. + +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(transparent)] +struct CustomString(String); + +// The CLVM representation for this is the same as the string itself. +// So `"Hello"` in this case. +let string = CustomString("Hello".to_string()); + +let a = &mut Allocator::new(); +let ptr = string.to_clvm(a).unwrap(); +assert_eq!(CustomString::from_clvm(a, ptr).unwrap(), string); +``` + ## Optional Fields You can only mark the last field in a struct or enum variant as optional. From 81d83e0f169b38d2c2dd9ae0fe017919fbd30b92 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 23 May 2024 08:43:53 -0400 Subject: [PATCH 13/16] Remove old reference to --- crates/clvm-traits/docs/derive_macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index bd30bd747..81f46fece 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -245,7 +245,7 @@ For convenience, this is the behavior when deriving `ToClvm` and `FromClvm` for ### Simple Example -In this example, since the `tuple` representation is used and the only values are the discriminants, the variants will be encoded as an atom. +In this example, since the `atom` representation, the variants will be encoded as an integer. Discriminants default to the `isize` type and the first value is `0`. Subsequent values are incremented by `1` by default. ```rust From 8e11d157b8d87df48480218aa8986a277813ee3e Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 23 May 2024 09:01:07 -0400 Subject: [PATCH 14/16] Use existing primitives to encode and decode curried args --- crates/clvm-traits/src/clvm_decoder.rs | 38 ++++++++------------------ crates/clvm-traits/src/clvm_encoder.rs | 18 ++++-------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/crates/clvm-traits/src/clvm_decoder.rs b/crates/clvm-traits/src/clvm_decoder.rs index 90942b5bf..b8311f5aa 100644 --- a/crates/clvm-traits/src/clvm_decoder.rs +++ b/crates/clvm-traits/src/clvm_decoder.rs @@ -1,9 +1,12 @@ use clvmr::{allocator::SExp, Allocator, Atom, NodePtr}; -use crate::{FromClvm, FromClvmError}; +use crate::{ + destructure_list, destructure_quote, match_list, match_quote, FromClvm, FromClvmError, + MatchByte, +}; -pub trait ClvmDecoder { - type Node: Clone; +pub trait ClvmDecoder: Sized { + type Node: Clone + FromClvm; fn decode_atom(&self, node: &Self::Node) -> Result; fn decode_pair(&self, node: &Self::Node) -> Result<(Self::Node, Self::Node), FromClvmError>; @@ -12,29 +15,12 @@ pub trait ClvmDecoder { &self, node: &Self::Node, ) -> Result<(Self::Node, Self::Node), FromClvmError> { - let (c, rest) = self.decode_pair(node)?; - if self.decode_atom(&c)?.as_ref() != [4] { - return Err(FromClvmError::Custom("expected `c` operator".to_string())); - } - - let (quoted_value, rest) = self.decode_pair(&rest)?; - - let (q, value) = self.decode_pair("ed_value)?; - if self.decode_atom(&q)?.as_ref() != [1] { - return Err(FromClvmError::Custom("expected `q` operator".to_string())); - } - - let (rest, nil) = self.decode_pair(&rest)?; - let nil_atom = self.decode_atom(&nil)?; - let nil_ref = nil_atom.as_ref(); - if !nil_ref.is_empty() { - return Err(FromClvmError::WrongAtomLength { - expected: 0, - found: nil_ref.len(), - }); - } - - Ok((value, rest)) + let destructure_list!(_, destructure_quote!(first), rest) = + , match_quote!(Self::Node), Self::Node)>::from_clvm( + self, + node.clone(), + )?; + Ok((first, rest)) } /// This is a helper function that just calls `clone` on the node. diff --git a/crates/clvm-traits/src/clvm_encoder.rs b/crates/clvm-traits/src/clvm_encoder.rs index f825b82a3..7870ba29e 100644 --- a/crates/clvm-traits/src/clvm_encoder.rs +++ b/crates/clvm-traits/src/clvm_encoder.rs @@ -1,9 +1,9 @@ use clvmr::{Allocator, NodePtr}; -use crate::{ToClvm, ToClvmError}; +use crate::{clvm_list, clvm_quote, ToClvm, ToClvmError}; -pub trait ClvmEncoder { - type Node: Clone; +pub trait ClvmEncoder: Sized { + type Node: Clone + ToClvm; fn encode_atom(&mut self, bytes: &[u8]) -> Result; fn encode_pair( @@ -17,16 +17,8 @@ pub trait ClvmEncoder { first: Self::Node, rest: Self::Node, ) -> Result { - let q = self.encode_atom(&[1])?; - let c = self.encode_atom(&[4])?; - - let quoted = self.encode_pair(q, first)?; - let nil = self.encode_atom(&[])?; - - let op_rest = self.encode_pair(rest, nil)?; - let op_args = self.encode_pair(quoted, op_rest)?; - - self.encode_pair(c, op_args) + const OP_C: u8 = 4; + clvm_list!(OP_C, clvm_quote!(first), rest).to_clvm(self) } /// This is a helper function that just calls `clone` on the node. From 404c229ad8ea7651cad5791c0f72e3a4f1327cd0 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 23 May 2024 09:12:00 -0400 Subject: [PATCH 15/16] apply_constants --- crates/clvm-derive/src/from_clvm.rs | 6 +++--- crates/clvm-derive/src/hide_values.rs | 6 +++--- crates/clvm-derive/src/lib.rs | 6 +++--- crates/clvm-derive/src/parser/attributes.rs | 18 +++++++++--------- crates/clvm-derive/src/parser/enum_info.rs | 4 ++-- crates/clvm-derive/src/parser/field_info.rs | 10 +++++----- crates/clvm-derive/src/parser/struct_info.rs | 4 ++-- crates/clvm-derive/src/parser/variant_info.rs | 4 ++-- crates/clvm-derive/src/to_clvm.rs | 8 ++++---- crates/clvm-traits/docs/derive_macros.md | 16 ++++++++-------- crates/clvm-traits/src/lib.rs | 10 +++++----- 11 files changed, 46 insertions(+), 46 deletions(-) diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index 2a47caed8..33170d3bc 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -116,15 +116,15 @@ fn field_parser_fn_body( let field_ident = field.ident.clone(); - if let Some(value) = &field.hidden_with_value { - // If the field is hidden, we need to check that the value is correct before continuing. + if let Some(value) = &field.constant { + // If the field is constant, we need to check that the value is correct before continuing. body.extend(quote! { let value: #ty = #value; if #decoded_value != value { return Err(#crate_name::FromClvmError::Custom( format!( - "hidden field `{}` has an incorrect value", + "constant `{}` has an incorrect value", stringify!(#field_ident), ) )); diff --git a/crates/clvm-derive/src/hide_values.rs b/crates/clvm-derive/src/hide_values.rs index f7dcfdc4f..dd3b2fe83 100644 --- a/crates/clvm-derive/src/hide_values.rs +++ b/crates/clvm-derive/src/hide_values.rs @@ -4,7 +4,7 @@ use syn::{punctuated::Punctuated, Data, DeriveInput, Fields}; use crate::parser::parse_clvm_options; -pub fn impl_hide_values(mut ast: DeriveInput) -> TokenStream { +pub fn impl_apply_constants(mut ast: DeriveInput) -> TokenStream { match &mut ast.data { Data::Enum(data_enum) => { for variant in data_enum.variants.iter_mut() { @@ -27,7 +27,7 @@ fn remove_fields(fields: &mut Fields) { .named .clone() .into_iter() - .filter(|field| parse_clvm_options(&field.attrs).hidden_value.is_none()); + .filter(|field| parse_clvm_options(&field.attrs).constant.is_none()); fields.named = Punctuated::from_iter(retained_fields); } @@ -36,7 +36,7 @@ fn remove_fields(fields: &mut Fields) { .unnamed .clone() .into_iter() - .filter(|field| parse_clvm_options(&field.attrs).hidden_value.is_none()); + .filter(|field| parse_clvm_options(&field.attrs).constant.is_none()); fields.unnamed = Punctuated::from_iter(retained_fields); } diff --git a/crates/clvm-derive/src/lib.rs b/crates/clvm-derive/src/lib.rs index 0b4473258..2d5036db2 100644 --- a/crates/clvm-derive/src/lib.rs +++ b/crates/clvm-derive/src/lib.rs @@ -7,7 +7,7 @@ mod parser; mod to_clvm; use from_clvm::from_clvm; -use hide_values::impl_hide_values; +use hide_values::impl_apply_constants; use proc_macro::TokenStream; use proc_macro2::Span; @@ -33,7 +33,7 @@ pub fn from_clvm_derive(input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn hide_values(_attr: TokenStream, input: TokenStream) -> TokenStream { +pub fn apply_constants(_attr: TokenStream, input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - impl_hide_values(ast).into() + impl_apply_constants(ast).into() } diff --git a/crates/clvm-derive/src/parser/attributes.rs b/crates/clvm-derive/src/parser/attributes.rs index 4cc87f471..7e346a578 100644 --- a/crates/clvm-derive/src/parser/attributes.rs +++ b/crates/clvm-derive/src/parser/attributes.rs @@ -45,7 +45,7 @@ pub struct ClvmOptions { pub repr: Option, /// The value of the field, also removed the actual field from the struct. /// This is useful for constant fields which shouldn't be in the constructor. - pub hidden_value: Option, + pub constant: Option, /// Whether the enum should parse variants one after the other instead of using the discriminant. pub untagged: bool, /// The integer type used for the enum discriminant. @@ -62,7 +62,7 @@ pub struct ClvmOptions { /// All of the possible options of the `clvm` attribute. enum ClvmOption { Repr(Repr), - HiddenValue(Expr), + Constant(Expr), CrateName(Ident), Untagged, Default(Option), @@ -79,9 +79,9 @@ impl Parse for ClvmOption { "transparent" => Ok(Self::Repr(Repr::Transparent)), "atom" => Ok(Self::Repr(Repr::Atom)), "untagged" => Ok(Self::Untagged), - "hidden_value" => { + "constant" => { input.parse::()?; - Ok(Self::HiddenValue(input.parse()?)) + Ok(Self::Constant(input.parse()?)) } "crate_name" => { input.parse::()?; @@ -105,7 +105,7 @@ impl Parse for ClvmOption { pub fn parse_clvm_options(attrs: &[Attribute]) -> ClvmOptions { let mut options = ClvmOptions { repr: None, - hidden_value: None, + constant: None, untagged: false, enum_repr: None, crate_name: None, @@ -158,11 +158,11 @@ pub fn parse_clvm_options(attrs: &[Attribute]) -> ClvmOptions { } options.repr = Some(repr); } - ClvmOption::HiddenValue(value) => { - if options.hidden_value.is_some() { - panic!("duplicate `hidden_value` option"); + ClvmOption::Constant(value) => { + if options.constant.is_some() { + panic!("duplicate `constant` option"); } - options.hidden_value = Some(value); + options.constant = Some(value); } ClvmOption::CrateName(crate_name) => { if options.crate_name.is_some() { diff --git a/crates/clvm-derive/src/parser/enum_info.rs b/crates/clvm-derive/src/parser/enum_info.rs index 3a937e5ad..8fca34d5a 100644 --- a/crates/clvm-derive/src/parser/enum_info.rs +++ b/crates/clvm-derive/src/parser/enum_info.rs @@ -11,8 +11,8 @@ pub struct EnumInfo { } pub fn parse_enum(mut options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { - if options.hidden_value.is_some() { - panic!("`hidden_value` only applies to fields"); + if options.constant.is_some() { + panic!("`constant` only applies to fields"); } if options.default.is_some() { diff --git a/crates/clvm-derive/src/parser/field_info.rs b/crates/clvm-derive/src/parser/field_info.rs index fa196ece7..b4a58919d 100644 --- a/crates/clvm-derive/src/parser/field_info.rs +++ b/crates/clvm-derive/src/parser/field_info.rs @@ -5,7 +5,7 @@ use super::{parse_clvm_options, ClvmOptions}; pub struct FieldInfo { pub ident: Ident, pub ty: Type, - pub hidden_with_value: Option, + pub constant: Option, pub optional_with_default: Option>, pub rest: bool, } @@ -37,7 +37,7 @@ pub fn parse_named_fields(fields: &FieldsNamed) -> Vec { items.push(FieldInfo { ident, ty, - hidden_with_value: options.hidden_value, + constant: options.constant, optional_with_default: options.default, rest: options.rest, }); @@ -73,7 +73,7 @@ pub fn parse_unnamed_fields(fields: &FieldsUnnamed) -> Vec { items.push(FieldInfo { ident, ty, - hidden_with_value: options.hidden_value, + constant: options.constant, optional_with_default: options.default, rest: options.rest, }); @@ -99,8 +99,8 @@ fn check_field_options(options: &ClvmOptions) { panic!("`crate_name` can't be set on individual fields"); } - if options.default.is_some() && options.hidden_value.is_some() { - panic!("`default` can't be used with `hidden_value` set"); + if options.default.is_some() && options.constant.is_some() { + panic!("`default` can't be used with `constant` set"); } if options.default.is_some() && options.rest { diff --git a/crates/clvm-derive/src/parser/struct_info.rs b/crates/clvm-derive/src/parser/struct_info.rs index cb7360a85..43a53fdcb 100644 --- a/crates/clvm-derive/src/parser/struct_info.rs +++ b/crates/clvm-derive/src/parser/struct_info.rs @@ -25,8 +25,8 @@ pub fn parse_struct(options: ClvmOptions, data_struct: &DataStruct) -> StructInf panic!("`repr` only applies to enums"); } - if options.hidden_value.is_some() { - panic!("`hidden_value` only applies to fields"); + if options.constant.is_some() { + panic!("`constant` only applies to fields"); } if options.default.is_some() { diff --git a/crates/clvm-derive/src/parser/variant_info.rs b/crates/clvm-derive/src/parser/variant_info.rs index 8ac270aae..14e1f4a3a 100644 --- a/crates/clvm-derive/src/parser/variant_info.rs +++ b/crates/clvm-derive/src/parser/variant_info.rs @@ -26,8 +26,8 @@ pub fn parse_variant(options: ClvmOptions, variant: &Variant) -> VariantInfo { panic!("`repr` only applies to enums"); } - if options.hidden_value.is_some() { - panic!("`hidden_value` only applies to fields"); + if options.constant.is_some() { + panic!("`constant` only applies to fields"); } if options.crate_name.is_some() { diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index a9262ea07..52beea339 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -32,9 +32,9 @@ fn encode_fields( for (i, field) in fields.iter().enumerate() { let value_name = Ident::new(&format!("field_{}", i), Span::mixed_site()); - if let Some(value) = &field.hidden_with_value { + if let Some(value) = &field.constant { body.extend(quote! { - // Use the hidden field's value directly, since it's not in `self`. + // Use the constant's value directly, since it's not in `self`. let #value_name = #value; }); } @@ -111,8 +111,8 @@ fn impl_for_struct(ast: DeriveInput, struct_info: StructInfo, node_name: Ident) let mut body = TokenStream::new(); for (i, field) in struct_info.fields.iter().enumerate() { - // We can't encode fields that are hidden, since they aren't on the actual struct. - if field.hidden_with_value.is_some() { + // We can't encode fields that are constant, since they aren't on the actual struct. + if field.constant.is_some() { continue; } diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 81f46fece..b440b0e33 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -346,33 +346,33 @@ let ptr = value.to_clvm(a).unwrap(); assert_eq!(Either::from_clvm(a, ptr).unwrap(), value); ``` -## Hidden Values +## Constant Values Sometimes you may want to include constants inside of a struct without actually exposing them as fields. -It's possible to do this with `#[clvm(hidden_value)]`, however you must use an attribute macro to remove the hidden fields. +It's possible to do this with `#[clvm(constant = ...)]`, however you must use an attribute macro to remove the constant fields. This has to be done in the proper order, or it will not work. The order is as follows: -- Derive `ToClvm` and `FromClvm`, so that the hidden fields are serialized and deserialized properly. -- Use `#[hide_values]` to remove them from the struct or enum. -- Add any other derives you want after, so they don't see the hidden fields. +- Derive `ToClvm` and `FromClvm`, so that the constant fields are serialized and deserialized properly. +- Use `#[apply_constants]` to remove them from the actual struct or enum, but keep them in the encoded output. +- Add any other derives you want after, so they don't see the constant fields. - Write any `#[clvm(...)]` options you want to use. Here is an example of this: ```rust use clvmr::Allocator; -use clvm_traits::{hide_values, ToClvm, FromClvm}; +use clvm_traits::{apply_constants, ToClvm, FromClvm}; #[derive(ToClvm, FromClvm)] -#[hide_values] +#[apply_constants] #[derive(Debug, PartialEq, Eq)] #[clvm(list)] struct CustomTerminator { value: u32, - #[clvm(hidden_value = 42, rest)] + #[clvm(constant = 42, rest)] terminator: u8, } diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index c08483231..90b8850c6 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -196,17 +196,17 @@ mod derive_tests { } #[test] - fn test_hidden_values() { + fn test_constants() { #[derive(ToClvm, FromClvm)] - #[hide_values] + #[apply_constants] #[derive(Debug, PartialEq)] #[clvm(list)] struct RunTailCondition { - #[clvm(hidden_value = 51)] + #[clvm(constant = 51)] opcode: u8, - #[clvm(hidden_value = ())] + #[clvm(constant = ())] blank_puzzle_hash: (), - #[clvm(hidden_value = -113)] + #[clvm(constant = -113)] magic_amount: i8, puzzle: P, solution: S, From db2d940c65888add22b9211291d6f189524b4c4b Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 23 May 2024 09:12:40 -0400 Subject: [PATCH 16/16] Rename apply_constants.rs --- crates/clvm-derive/src/{hide_values.rs => apply_constants.rs} | 0 crates/clvm-derive/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/clvm-derive/src/{hide_values.rs => apply_constants.rs} (100%) diff --git a/crates/clvm-derive/src/hide_values.rs b/crates/clvm-derive/src/apply_constants.rs similarity index 100% rename from crates/clvm-derive/src/hide_values.rs rename to crates/clvm-derive/src/apply_constants.rs diff --git a/crates/clvm-derive/src/lib.rs b/crates/clvm-derive/src/lib.rs index 2d5036db2..c956b2434 100644 --- a/crates/clvm-derive/src/lib.rs +++ b/crates/clvm-derive/src/lib.rs @@ -1,13 +1,13 @@ extern crate proc_macro; +mod apply_constants; mod from_clvm; mod helpers; -mod hide_values; mod parser; mod to_clvm; +use apply_constants::impl_apply_constants; use from_clvm::from_clvm; -use hide_values::impl_apply_constants; use proc_macro::TokenStream; use proc_macro2::Span;