diff --git a/crates/chia-puzzles/src/proof.rs b/crates/chia-puzzles/src/proof.rs index 27ab04938..37bfbbf73 100644 --- a/crates/chia-puzzles/src/proof.rs +++ b/crates/chia-puzzles/src/proof.rs @@ -3,7 +3,7 @@ use clvm_traits::{FromClvm, ToClvm}; #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(untagged, tuple)] +#[clvm(transparent)] pub enum Proof { Lineage(LineageProof), Eve(EveProof), diff --git a/crates/chia-puzzles/src/puzzles/offer.rs b/crates/chia-puzzles/src/puzzles/offer.rs index 1c2843ceb..69eb2eeea 100644 --- a/crates/chia-puzzles/src/puzzles/offer.rs +++ b/crates/chia-puzzles/src/puzzles/offer.rs @@ -5,22 +5,23 @@ use hex_literal::hex; #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(tuple)] +#[clvm(transparent)] pub struct SettlementPaymentsSolution { 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(transparent)] pub enum Payment { WithoutMemos(PaymentWithoutMemos), WithMemos(PaymentWithMemos), 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/apply_constants.rs b/crates/clvm-derive/src/apply_constants.rs new file mode 100644 index 000000000..dd3b2fe83 --- /dev/null +++ b/crates/clvm-derive/src/apply_constants.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_apply_constants(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).constant.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).constant.is_none()); + + fields.unnamed = Punctuated::from_iter(retained_fields); + } + syn::Fields::Unit => {} + } +} diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index cf3ef9b4a..33170d3bc 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -1,331 +1,389 @@ -use proc_macro2::{Ident, Literal, Span, TokenStream}; -use quote::quote; -use syn::{ - parse_quote, spanned::Spanned, Data, DeriveInput, Expr, Fields, FieldsNamed, FieldsUnnamed, - GenericParam, Type, -}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +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, 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 | Repr::Transparent => 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.as_ref().map(|node| decoder.clone_node(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, )* }); + let mut decoded_names = Vec::new(); + let mut decoded_values = Vec::new(); + + for (i, field) in fields.iter().enumerate() { + let ident = &temp_names[i]; + let ty = &field.ty; + + // 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) + }; + + if let Some(default) = &field.optional_with_default { + 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?); + } - FieldInfo { - field_types, - field_names, - initializer, - } -} + let field_ident = field.ident.clone(); -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, )* )); + 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; - FieldInfo { - field_types, - field_names, - initializer, + if #decoded_value != value { + return Err(#crate_name::FromClvmError::Custom( + format!( + "constant `{}` 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); + } } -} - -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()); - - 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) - }; - generate_from_clvm(crate_name, ast, &node_name, &body) + ParsedFields { + decoded_names, + decoded_values, + body, + } } -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()); - - 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 initializer.is_empty() { - quote! { - #discriminant_ident => { - Ok(Self::#name) - } +fn check_rest_value(crate_name: &Ident, repr: Repr) -> TokenStream { + match repr { + 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. + 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(), + }); + } + } + } + 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(), + }); } - } 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) - } + 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 body = quote! { - #parse_value + let mut body = TokenStream::new(); - #( #discriminant_definitions )* + if enum_info.is_untagged { + let variant_parsers = enum_variant_parsers(&crate_name, &node_name, &enum_info); - match value { - #( #variant_bodies )* - _ => Err(#crate_name::FromClvmError::Custom( - format!("failed to match any enum variants of `{}`", #type_name) + // 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); + } + }); + } + + 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 | Repr::Transparent => 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..9c400d420 100644 --- a/crates/clvm-derive/src/helpers.rs +++ b/crates/clvm-derive/src/helpers.rs @@ -1,98 +1,71 @@ -use std::fmt; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{parse_quote, Expr, GenericParam, Generics, Ident, TypeParamBound}; -use proc_macro2::{Ident, Span}; -use syn::{ - ext::IdentExt, punctuated::Punctuated, Attribute, GenericParam, Generics, Token, TypeParamBound, -}; +use crate::parser::EnumInfo; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Repr { - Tuple, - List, - Curry, -} - -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 struct DiscriminantInfo { + pub discriminant_consts: Vec, + pub discriminant_names: Vec, + pub variant_names: Vec, + pub discriminant_type: Ident, } -impl ClvmAttr { - pub fn expect_repr(&self) -> Repr { - self.repr - .expect("expected clvm attribute parameter of either `tuple`, `list`, or `curry`") - } -} +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(); -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; - }; + // 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())); - if ident != "clvm" { - continue; - } + // We need to keep track of the previous discriminant to increment it for each variant. + let mut previous_discriminant = None; - let args = attr - .parse_args_with(Punctuated::::parse_terminated) - .unwrap(); + for (i, variant) in enum_info.variants.iter().enumerate() { + variant_names.push(variant.name.clone()); - for arg in args { - let existing = result.repr; + 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 + }; - 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}`"), - }); + previous_discriminant = Some(discriminant.clone()); - if let Some(existing) = existing { - panic!("`{arg}` conflicts with `{existing}`"); - } - } - } - result -} + // 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()); -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; - }; - if ident == "repr" { - int_repr = Some(attr.parse_args_with(Ident::parse_any).unwrap()); - } + 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/lib.rs b/crates/clvm-derive/src/lib.rs index f2165922c..c956b2434 100644 --- a/crates/clvm-derive/src/lib.rs +++ b/crates/clvm-derive/src/lib.rs @@ -1,22 +1,39 @@ extern crate proc_macro; +mod apply_constants; mod from_clvm; mod helpers; -mod macros; +mod parser; mod to_clvm; +use apply_constants::impl_apply_constants; use from_clvm::from_clvm; -use syn::{parse_macro_input, DeriveInput}; +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 apply_constants(_attr: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + impl_apply_constants(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..7e346a578 --- /dev/null +++ b/crates/clvm-derive/src/parser/attributes.rs @@ -0,0 +1,190 @@ +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 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`, `transparent`, 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::Transparent => "transparent", + 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 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. + 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), + Constant(Expr), + CrateName(Ident), + Untagged, + Default(Option), + 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)), + "transparent" => Ok(Self::Repr(Repr::Transparent)), + "atom" => Ok(Self::Repr(Repr::Atom)), + "untagged" => Ok(Self::Untagged), + "constant" => { + input.parse::()?; + Ok(Self::Constant(input.parse()?)) + } + "crate_name" => { + input.parse::()?; + Ok(Self::CrateName(input.parse()?)) + } + "default" => { + 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")), + } + } +} + +/// 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, + constant: 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::Constant(value) => { + if options.constant.is_some() { + panic!("duplicate `constant` option"); + } + options.constant = 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!("duplicate `default` option"); + } + options.default = Some(default); + } + 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..8fca34d5a --- /dev/null +++ b/crates/clvm-derive/src/parser/enum_info.rs @@ -0,0 +1,77 @@ +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(mut options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo { + if options.constant.is_some() { + panic!("`constant` only applies to fields"); + } + + if options.default.is_some() { + panic!("`default` only applies to fields"); + } + + if options.rest { + panic!("`rest` only applies to fields"); + } + + 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_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_repr.is_some() { + panic!("cannot specify representation for individual enum variants in a tagged enum"); + } + + 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 { + 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..b4a58919d --- /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 constant: 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 { + panic!("only the last field can be optional, to prevent ambiguity"); + } + + rest = options.rest; + optional = options.default.is_some(); + + items.push(FieldInfo { + ident, + ty, + constant: options.constant, + 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 { + panic!("only the last field can be optional, to prevent ambiguity"); + } + + rest = options.rest; + optional = options.default.is_some(); + + items.push(FieldInfo { + ident, + ty, + constant: options.constant, + 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.constant.is_some() { + panic!("`default` can't be used with `constant` set"); + } + + if options.default.is_some() && options.rest { + 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 new file mode 100644 index 000000000..43a53fdcb --- /dev/null +++ b/crates/clvm-derive/src/parser/struct_info.rs @@ -0,0 +1,69 @@ +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.constant.is_some() { + panic!("`constant` only applies to fields"); + } + + if options.default.is_some() { + panic!("`default` only applies to fields"); + } + + if options.rest { + panic!("`rest` only applies to fields"); + } + + let mut repr = Repr::expect(options.repr); + + if repr == Repr::Atom { + panic!("`atom` is not a valid representation for structs"); + } + + let crate_name = options.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 new file mode 100644 index 000000000..14e1f4a3a --- /dev/null +++ b/crates/clvm-derive/src/parser/variant_info.rs @@ -0,0 +1,67 @@ +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.constant.is_some() { + panic!("`constant` 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` only applies 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"); + } + + 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 1463a8638..52beea339 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -1,243 +1,305 @@ -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::{ - parse_quote, spanned::Spanned, Data, DeriveInput, Expr, Fields, FieldsNamed, FieldsUnnamed, - GenericParam, Index, -}; +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.constant { + body.extend(quote! { + // Use the constant'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 | Repr::Transparent => 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 | Repr::Transparent => 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"); - } + // 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; + }); - 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) + // 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)?; + }); } - 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 { + 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); + } } + + 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, +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() { + // We can't encode fields that are constant, since they aren't on the actual struct. + if field.constant.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 { + 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!(), + } } + + body.extend(encode_fields( + &crate_name, + &node_name, + &struct_info.fields, + struct_info.repr, + )); + + body.extend(quote! { + Ok(node) + }); + + trait_impl(ast, crate_name, node_name, 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_enum(ast: DeriveInput, enum_info: EnumInfo, node_name: Ident) -> TokenStream { + let crate_name = crate_name(enum_info.crate_name.clone()); + + let mut variant_destructures = Vec::new(); + + for variant in enum_info.variants.iter() { + let variant_name = &variant.name; + + 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_destructures.push(destructure); } -} -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()); + let body = if enum_info.is_untagged { + let mut variant_bodies = Vec::new(); - let body = quote! { - let value = #clvm_macro!( #( &self.#field_accessors ),* ); - #crate_name::ToClvm::to_clvm(&value, encoder) - }; + for variant in enum_info.variants.iter() { + let repr = variant.repr.unwrap_or(enum_info.default_repr); - generate_to_clvm(crate_name, ast, &node_name, &body) -} + variant_bodies.push(encode_fields( + &crate_name, + &node_name, + &variant.fields, + repr, + )); + } -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) - } + // Encode the variant's fields directly. + 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 { + // Encode the discriminant by itself as an atom. + quote! { + #( #discriminant_consts )* + + match self { + #( Self::#variant_names => { + <#discriminant_type as #crate_name::ToClvm<#node_name>>::to_clvm( + &#discriminant_names, + encoder, + ) + }, )* } } - }) - .collect::>(); + } else { + let encode_next = match enum_info.default_repr { + 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. + 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, + )); + } - let body = quote! { - match self { - #( #variant_bodies )* + // Encode the discriminant followed by the variant's fields. + 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/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index 96f184366..b440b0e33 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -12,52 +12,113 @@ 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. +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: +For example, with the list `[A, B, C]`, we build the list in reverse: -- `()` 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. +- 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 . ())))` ```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 Tiers { + high: u8, + medium: u8, + low: u8, } -let point = Point { - x: 5, - y: 2, +// 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 = point.to_clvm(a).unwrap(); -assert_eq!(Point::from_clvm(a, ptr).unwrap(), point); +let ptr = value.to_clvm(a).unwrap(); +assert_eq!(Tiers::from_clvm(a, ptr).unwrap(), value); ``` -### List +### Curry + +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)))`. + +You can read more about currying on the [Chia blockchain documentation](https://docs.chia.net/guides/chialisp-currying). + +The following example is for demonstration purposes only: + +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; + +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(curry)] +struct PuzzleArgs { + code_to_unlock: u32, + verification_level: u8, +} + +// 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!(PuzzleArgs::from_clvm(a, ptr).unwrap(), args); +``` + +### Transparent -This represents values in a null terminated series of nested cons-pairs, also known as a proper list. +If you want a struct to have the same CLVM representation as its inner struct (a newtype), you can use the `transparent` representation. -For example: +```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); +``` -- `()` 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. +## Optional Fields -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 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 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; @@ -65,49 +126,115 @@ use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(list)] -struct PasswordSolution { - password: String, +struct Person { + name: String, + #[clvm(default)] + email: Option, } -let solution = PasswordSolution { - password: "Hello".into(), +// 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()), }; let a = &mut Allocator::new(); -let ptr = solution.to_clvm(a).unwrap(); -assert_eq!(PasswordSolution::from_clvm(a, ptr).unwrap(), solution); +let ptr = person.to_clvm(a).unwrap(); +assert_eq!(Person::from_clvm(a, ptr).unwrap(), person); ``` -### Curry +### Default Value -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. +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. -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. +```rust +use clvmr::Allocator; +use clvm_traits::{ToClvm, FromClvm}; -You can read more about currying on the [Chia blockchain documentation](https://docs.chia.net/guides/chialisp-currying). +#[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +struct Person { + name: String, + #[clvm(default = 18)] + age: u8, +} -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. +// 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, +}; + +let a = &mut Allocator::new(); +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(curry)] -struct PasswordArgs { - password: String, +#[clvm(list)] +struct Point { + x: i32, + #[clvm(rest)] + y: i32, } -let args = PasswordArgs { - password: "Hello".into(), +// The CLVM representation of this is `(5 . 2)` (with no nil terminator). +let point = Point { + x: 5, + y: 2, }; let a = &mut Allocator::new(); -let ptr = args.to_clvm(a).unwrap(); -assert_eq!(PasswordArgs::from_clvm(a, ptr).unwrap(), args); +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, +} + +// 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], +}; + +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]); ``` ## Enums @@ -118,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 @@ -126,12 +253,13 @@ use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple)] +#[clvm(atom)] enum Status { Pending, Completed, } +// The CLVM representation of this is just `0`. let status = Status::Pending; let a = &mut Allocator::new(); @@ -150,13 +278,14 @@ use clvmr::Allocator; use clvm_traits::{ToClvm, FromClvm}; #[derive(Debug, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(tuple)] +#[clvm(atom)] #[repr(u8)] enum Status { Pending = 36, Completed = 42, } +// The CLVM representation of this is `36`. let status = Status::Pending; let a = &mut Allocator::new(); @@ -177,11 +306,10 @@ use clvm_traits::{ToClvm, FromClvm}; #[clvm(list)] enum SpendMode { AppendValue { value: i32 }, - - #[clvm(tuple)] ClearValues, } +// The CLVM representation of this is `(42)`. let mode = SpendMode::AppendValue { value: 42 }; @@ -194,7 +322,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,15 +332,69 @@ 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]), } +// The CLVM representation of this is `((42 42 42 42))`. let value = Either::ShortList([42; 4]); let a = &mut Allocator::new(); let ptr = value.to_clvm(a).unwrap(); assert_eq!(Either::from_clvm(a, ptr).unwrap(), value); ``` + +## 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(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 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::{apply_constants, ToClvm, FromClvm}; + +#[derive(ToClvm, FromClvm)] +#[apply_constants] +#[derive(Debug, PartialEq, Eq)] +#[clvm(list)] +struct CustomTerminator { + value: u32, + #[clvm(constant = 42, rest)] + terminator: u8, +} + +// The CLVM representation of this is `(100 . 42)`. +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: + +```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; +``` diff --git a/crates/clvm-traits/src/clvm_decoder.rs b/crates/clvm-traits/src/clvm_decoder.rs index af953e9d0..b8311f5aa 100644 --- a/crates/clvm-traits/src/clvm_decoder.rs +++ b/crates/clvm-traits/src/clvm_decoder.rs @@ -1,13 +1,28 @@ 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>; + fn decode_curried_arg( + &self, + node: &Self::Node, + ) -> Result<(Self::Node, Self::Node), FromClvmError> { + 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. /// 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..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( @@ -12,6 +12,15 @@ pub trait ClvmEncoder { rest: Self::Node, ) -> Result; + fn encode_curried_arg( + &mut self, + first: Self::Node, + rest: Self::Node, + ) -> Result { + 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. /// It's required only because the compiler can't infer that `N` is `Clone`, /// since there's no `Clone` bound on the `ToClvm` trait. diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index 2686c8ed6..90b8850c6 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -63,81 +63,183 @@ mod derive_tests { } #[test] - fn test_tuple() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] - struct TupleStruct { + fn test_list_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { a: u64, 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)] - #[clvm(tuple)] - struct UnnamedStruct(String, String); + 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 Struct(String, String); + + check(Struct("A".to_string(), "B".to_string()), "ff41ff4280"); + } + + #[test] + fn test_newtype_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct(#[clvm(rest)] String); + + check(Struct("XYZ".to_string()), "8358595a"); + } + + #[test] + fn test_optional() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] + struct Struct { + a: u64, + #[clvm(default)] + 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(UnnamedStruct("A".to_string(), "B".to_string()), "ff4142"); + check( + Struct { + a: 52, + b: "World".to_string(), + }, + "ff34ff85576f726c6480", + ); + check( + Struct { + a: 52, + b: "Hello".to_string(), + }, + "ff3480", + ); } #[test] - fn test_newtype() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] - struct NewTypeStruct(String); + fn test_constants() { + #[derive(ToClvm, FromClvm)] + #[apply_constants] + #[derive(Debug, PartialEq)] + #[clvm(list)] + struct RunTailCondition { + #[clvm(constant = 51)] + opcode: u8, + #[clvm(constant = ())] + blank_puzzle_hash: (), + #[clvm(constant = -113)] + magic_amount: i8, + puzzle: P, + solution: S, + } - check(NewTypeStruct("XYZ".to_string()), "8358595a"); + check( + RunTailCondition { + puzzle: "puzzle".to_string(), + solution: "solution".to_string(), + }, + "ff33ff80ff818fff8670757a7a6c65ff88736f6c7574696f6e80", + ); } #[test] fn test_enum() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] enum Enum { 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() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple)] + fn test_explicit_discriminant() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list)] #[repr(u8)] enum Enum { A(i32) = 42, @@ -145,31 +247,28 @@ mod derive_tests { 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"); } #[test] fn test_untagged_enum() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple, untagged)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list, untagged)] enum Enum { A(i32), - - #[clvm(list)] 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 { @@ -181,8 +280,8 @@ mod derive_tests { #[test] fn test_untagged_enum_parsing_order() { - #[derive(Debug, ToClvm, FromClvm, PartialEq, Eq)] - #[clvm(tuple, untagged)] + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(list, untagged)] enum Enum { // This variant is parsed first, so `B` will never be deserialized. A(i32), @@ -212,4 +311,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"); + } }