Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3363,7 +3363,7 @@ fn deserialize_idl_defined_type_to_json(
}
}
}
IdlTypeDefTy::Enum { variants } => {
IdlTypeDefTy::Enum { variants, .. } => {
let repr = <u8 as AnchorDeserialize>::deserialize(data)?;

let variant = variants
Expand Down
4 changes: 4 additions & 0 deletions idl/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ pub enum IdlTypeDefTy {
fields: Option<IdlDefinedFields>,
},
Enum {
#[serde(default, rename = "tagEncoding", skip_serializing_if = "is_default")]
tag_encoding: Option<String>,
variants: Vec<IdlEnumVariant>,
},
Type {
Expand All @@ -248,6 +250,8 @@ pub struct IdlEnumVariant {
pub name: String,
#[serde(skip_serializing_if = "is_default")]
pub fields: Option<IdlDefinedFields>,
#[serde(default, skip_serializing_if = "is_default")]
pub tag: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down
23 changes: 19 additions & 4 deletions idl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,26 @@ mod legacy {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase", tag = "kind")]
pub enum IdlTypeDefinitionTy {
Struct { fields: Vec<IdlField> },
Enum { variants: Vec<IdlEnumVariant> },
Alias { value: IdlType },
Struct {
fields: Vec<IdlField>,
},
Enum {
#[serde(default, rename = "tagEncoding")]
tag_encoding: Option<String>,
variants: Vec<IdlEnumVariant>,
},
Alias {
value: IdlType,
},
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlEnumVariant {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub fields: Option<EnumFields>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub tag: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -443,7 +453,11 @@ mod legacy {
))
},
},
IdlTypeDefinitionTy::Enum { variants } => Self::Enum {
IdlTypeDefinitionTy::Enum {
variants,
tag_encoding,
} => Self::Enum {
tag_encoding,
variants: variants
.into_iter()
.map(|variant| t::IdlEnumVariant {
Expand All @@ -456,6 +470,7 @@ mod legacy {
tys.into_iter().map(Into::into).collect(),
),
}),
tag: variant.tag,
})
.collect(),
},
Expand Down
26 changes: 17 additions & 9 deletions lang-v2/derive/src/idl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,15 +453,20 @@ pub fn build_enum_type_strings(
name: &str,
disc: &[u8],
docs: &[String],
enum_attrs: &[syn::Attribute],
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
kind: TypeKind,
) -> IdlTypeStrings {
) -> syn::Result<IdlTypeStrings> {
let mut type_def_obj = build_type_def_header(name, docs, kind);
let tag_encoding = crate::wincode_attrs::enum_tag_encoding_string(enum_attrs)?;
let variant_values: Vec<Value> = variants
.iter()
.map(|v| {
let mut obj = serde_json::Map::new();
obj.insert("name".into(), Value::String(v.ident.to_string()));
if let Some(tag) = crate::wincode_attrs::variant_tag_string(&v.attrs)? {
obj.insert("tag".into(), Value::String(tag));
}
match &v.fields {
syn::Fields::Unit => {}
syn::Fields::Named(named) => {
Expand All @@ -477,17 +482,20 @@ pub fn build_enum_type_strings(
obj.insert("fields".into(), Value::Array(tys));
}
}
Value::Object(obj)
Ok(Value::Object(obj))
})
.collect();
type_def_obj.insert(
"type".into(),
json!({ "kind": "enum", "variants": variant_values }),
);
IdlTypeStrings {
.collect::<syn::Result<_>>()?;
let mut type_obj = serde_json::Map::new();
type_obj.insert("kind".into(), Value::String("enum".into()));
if let Some(tag_encoding) = tag_encoding {
type_obj.insert("tagEncoding".into(), Value::String(tag_encoding));
}
type_obj.insert("variants".into(), Value::Array(variant_values));
type_def_obj.insert("type".into(), Value::Object(type_obj));
Ok(IdlTypeStrings {
account_entry: build_account_entry(name, disc),
type_def: Value::Object(type_def_obj).to_string(),
}
})
}

/// Compose the program-level `accounts[]` entry. Returns `None` when the
Expand Down
45 changes: 44 additions & 1 deletion lang-v2/derive/src/init_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pub fn expand(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let name = input.ident;
let enum_tag_len = match enum_tag_len_tokens(&input.attrs) {
Ok(Some(tag_len)) => tag_len,
Ok(None) => quote!(1),
Err(err) => return err.to_compile_error().into(),
};

let process_struct_fields = |fields: Punctuated<Field, Comma>| {
let recurse = fields.into_iter().map(|f| {
Expand Down Expand Up @@ -63,7 +68,7 @@ pub fn expand(item: TokenStream) -> TokenStream {
quote! {
#[automatically_derived]
impl anchor_lang_v2::Space for #name {
const INIT_SPACE: usize = 1 + #max;
const INIT_SPACE: usize = #enum_tag_len + #max;
}
}
}
Expand All @@ -81,6 +86,44 @@ pub fn expand(item: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

fn enum_tag_len_tokens(attrs: &[Attribute]) -> syn::Result<Option<TokenStream2>> {
let Some(tag_encoding) = crate::wincode_attrs::enum_tag_encoding_type(attrs)? else {
return Ok(None);
};

let Type::Path(tag_path) = &tag_encoding else {
return Err(syn::Error::new_spanned(
&tag_encoding,
"#[derive(InitSpace)] only supports primitive integer `#[wincode(tag_encoding = ...)]` \
overrides; compute the enum size manually for custom tag encodings",
));
};

let Some(segment) = tag_path.path.segments.last() else {
return Err(syn::Error::new_spanned(
&tag_encoding,
"invalid `#[wincode(tag_encoding = ...)]` type",
));
};

let len = match segment.ident.to_string().as_str() {
"i8" | "u8" => quote!(1),
"i16" | "u16" => quote!(2),
"i32" | "u32" => quote!(4),
"i64" | "u64" => quote!(8),
"i128" | "u128" => quote!(16),
_ => {
return Err(syn::Error::new_spanned(
&tag_encoding,
"#[derive(InitSpace)] only supports primitive integer `#[wincode(tag_encoding = ...)]` \
overrides; compute the enum size manually for custom tag encodings",
))
}
};

Ok(Some(len))
}

fn gen_max<T: Iterator<Item = TokenStream2>>(mut iter: T) -> TokenStream2 {
if let Some(item) = iter.next() {
let next_item = gen_max(iter);
Expand Down
70 changes: 62 additions & 8 deletions lang-v2/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod init_space;
mod parse;
mod pda;
mod pod_wrapper;
mod wincode_attrs;

use {
proc_macro::TokenStream,
Expand Down Expand Up @@ -2081,7 +2082,7 @@ pub fn account(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Margin { leverage: u8, symbol: [u8; 8] },
/// }
/// ```
#[proc_macro_derive(IdlType)]
#[proc_macro_derive(IdlType, attributes(wincode))]
pub fn derive_idl_type(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
Expand Down Expand Up @@ -2135,13 +2136,17 @@ pub fn derive_idl_type(input: TokenStream) -> TokenStream {
(strings, field_tys)
}
Data::Enum(data) => {
let strings = idl::build_enum_type_strings(
let strings = match idl::build_enum_type_strings(
&name_str,
&empty_disc,
&docs,
&input.attrs,
&data.variants,
idl::TypeKind::Borsh,
);
) {
Ok(strings) => strings,
Err(err) => return err.to_compile_error().into(),
};
// Every variant's fields contribute dependent types for the
// transitive walker — a `Foo::Bar(Inner)` variant needs to pull
// `Inner` into `types[]` just like a struct field would.
Expand Down Expand Up @@ -2913,6 +2918,20 @@ fn gen_declare_program_types(idl: &serde_json::Value) -> syn::Result<Vec<TokenSt
format!("declare_program! does not support bytemuck enum type `{name}`"),
));
}
let tag_encoding_attr = type_obj
.get("tagEncoding")
.or_else(|| type_obj.get("tag_encoding"))
.map(|tag_encoding| match tag_encoding {
serde_json::Value::String(tag_encoding) => {
let tag_encoding = syn::LitStr::new(tag_encoding, ident.span());
Ok(quote! { #[wincode(tag_encoding = #tag_encoding)] })
}
other => Err(syn::Error::new(
ident.span(),
format!("enum type `{name}` has non-string `tagEncoding`: {other}"),
)),
})
.transpose()?;
let variants = type_obj
.get("variants")
.and_then(serde_json::Value::as_array)
Expand All @@ -2927,8 +2946,41 @@ fn gen_declare_program_types(idl: &serde_json::Value) -> syn::Result<Vec<TokenSt
for variant in variants {
let variant_name = json_str(variant, "name", ident.span())?;
let variant_ident = Ident::new(variant_name, ident.span());
let tag_attr = variant
.get("tag")
.map(|tag| match tag {
serde_json::Value::String(tag) => {
let tag = tag.parse::<TokenStream2>().map_err(|err| {
syn::Error::new(
ident.span(),
format!(
"variant `{variant_name}` has invalid `tag` tokens `{tag}`: {err}"
),
)
})?;
Ok(quote! { #[wincode(tag = #tag)] })
}
serde_json::Value::Number(tag) => {
let tag = tag.to_string().parse::<TokenStream2>().map_err(|err| {
syn::Error::new(
ident.span(),
format!(
"variant `{variant_name}` has invalid numeric `tag`: {err}"
),
)
})?;
Ok(quote! { #[wincode(tag = #tag)] })
}
other => Err(syn::Error::new(
ident.span(),
format!(
"variant `{variant_name}` has unsupported `tag` value `{other}`"
),
)),
})
.transpose()?;
let Some(fields) = variant.get("fields") else {
variant_tokens.push(quote! { #variant_ident, });
variant_tokens.push(quote! { #tag_attr #variant_ident, });
continue;
};
let fields = fields.as_array().ok_or_else(|| {
Expand All @@ -2941,13 +2993,14 @@ fn gen_declare_program_types(idl: &serde_json::Value) -> syn::Result<Vec<TokenSt
idl_field_tys.extend(fields.tys().iter().cloned());
match fields {
DeclareTypeFields::Named { fields, .. } => {
variant_tokens.push(quote! { #variant_ident { #(#fields)* }, });
variant_tokens
.push(quote! { #tag_attr #variant_ident { #(#fields)* }, });
}
DeclareTypeFields::Tuple { fields, .. } => {
variant_tokens.push(quote! { #variant_ident(#(#fields),*), });
variant_tokens.push(quote! { #tag_attr #variant_ident(#(#fields),*), });
}
DeclareTypeFields::Unit => {
variant_tokens.push(quote! { #variant_ident, });
variant_tokens.push(quote! { #tag_attr #variant_ident, });
}
}
}
Expand All @@ -2963,6 +3016,7 @@ fn gen_declare_program_types(idl: &serde_json::Value) -> syn::Result<Vec<TokenSt
#(#docs)*
#repr
#[derive(Clone, anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
#tag_encoding_attr
pub enum #ident #impl_generics {
#(#variant_tokens)*
}
Expand Down Expand Up @@ -5548,7 +5602,7 @@ pub fn constant(_attr: TokenStream, input: TokenStream) -> TokenStream {
/// pub name: String,
/// }
/// ```
#[proc_macro_derive(InitSpace, attributes(max_len))]
#[proc_macro_derive(InitSpace, attributes(max_len, wincode))]
pub fn derive_init_space(item: TokenStream) -> TokenStream {
init_space::expand(item)
}
Expand Down
Loading
Loading