use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{
Data, DeriveInput, Fields, FieldsNamed, Ident, ItemStruct, Token, Type, parse_macro_input,
};
struct CtxParam {
name: Ident,
ty: Type,
}
impl Parse for CtxParam {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![:]>()?;
let ty: Type = input.parse()?;
Ok(CtxParam { name, ty })
}
}
fn ctx_struct_ident(name: &Ident) -> Ident {
format_ident!("{}Ctx", name)
}
fn ctx_struct_ty(ty: &Type) -> syn::Result<TokenStream2> {
if let Type::Path(p) = ty {
let mut path = p.path.clone();
if let Some(last) = path.segments.last_mut() {
last.ident = ctx_struct_ident(&last.ident);
last.arguments = syn::PathArguments::None;
return Ok(quote!(#path));
}
}
Err(syn::Error::new_spanned(
ty,
"a `ctx`-parameterized field must have a path type (so its `…Ctx` struct can be named)",
))
}
const BYTE_ALIGNED_MSG: &str = "this struct's fields are all byte-aligned. The bare \
`#[derive(BitDecode/BitEncode)]` is the low-level bit codec; for a byte-aligned message use \
`#[bin]` — the unified codec (it handles byte-aligned data natively and adds \
magic/count/ctx/map/if/validate). The bare derive is for fields that straddle byte boundaries \
(e.g. a 108-bit payload). To keep the bare derive on an all-byte-aligned struct anyway, add \
`#[bit_stream(allow_byte_aligned)]`.";
fn named_struct(input: &DeriveInput) -> syn::Result<&FieldsNamed> {
if !input.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&input.generics,
"BitDecode/BitEncode do not support generic parameters yet",
));
}
match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => Ok(f),
_ => Err(syn::Error::new_spanned(
&input.ident,
"BitDecode/BitEncode require a struct with named fields",
)),
},
_ => Err(syn::Error::new_spanned(
&input.ident,
"BitDecode/BitEncode can only derive for structs",
)),
}
}
#[derive(Default)]
struct BitStreamAttrs {
allow_byte_aligned: bool,
lsb: bool,
little: bool,
magic: Option<syn::Expr>,
ctx: Vec<(Ident, Type)>,
auto_len: Vec<AutoLenSpec>,
}
fn parse_bit_stream(input: &DeriveInput) -> syn::Result<BitStreamAttrs> {
let mut attrs = BitStreamAttrs::default();
for attr in &input.attrs {
if attr.path().is_ident("bit_stream") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("allow_byte_aligned") {
attrs.allow_byte_aligned = true;
Ok(())
} else if meta.path.is_ident("bits") {
let val: Ident = meta.value()?.parse()?;
match val.to_string().as_str() {
"msb" => attrs.lsb = false,
"lsb" => attrs.lsb = true,
_ => return Err(meta.error("expected `msb` or `lsb`")),
}
Ok(())
} else if meta.path.is_ident("bytes") {
let val: Ident = meta.value()?.parse()?;
match val.to_string().as_str() {
"big" => attrs.little = false,
"little" => attrs.little = true,
_ => return Err(meta.error("expected `big` or `little`")),
}
Ok(())
} else if meta.path.is_ident("magic") {
attrs.magic = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("ctx") {
let content;
syn::parenthesized!(content in meta.input);
let params = Punctuated::<CtxParam, Token![,]>::parse_terminated(&content)?;
attrs.ctx = params.into_iter().map(|p| (p.name, p.ty)).collect();
Ok(())
} else {
Err(meta.error(
"unknown `#[bit_stream(...)]` option; expected `allow_byte_aligned`, `bits = msb|lsb`, `bytes = big|little`, `magic = <expr>`, or `ctx(name: Ty, …)`",
))
}
})?;
}
}
Ok(attrs)
}
fn layout_token(attrs: &BitStreamAttrs) -> TokenStream2 {
let bnb = crate::bnb_path();
let bit = if attrs.lsb {
quote!(#bnb::__private::BitOrder::Lsb)
} else {
quote!(#bnb::__private::BitOrder::Msb)
};
let byte = if attrs.little {
quote!(#bnb::__private::ByteOrder::Little)
} else {
quote!(#bnb::__private::ByteOrder::Big)
};
quote!(#bnb::__private::Layout { bit: #bit, byte: #byte })
}
fn byte_array_len(f: &syn::Field) -> Option<&syn::Expr> {
if let syn::Type::Array(arr) = &f.ty {
if let syn::Type::Path(p) = &*arr.elem {
if p.path.is_ident("u8") {
return Some(&arr.len);
}
}
}
None
}
fn vec_elem(f: &syn::Field) -> Option<&syn::Type> {
single_generic(&f.ty, "Vec")
}
fn option_elem(f: &syn::Field) -> Option<&syn::Type> {
single_generic(&f.ty, "Option")
}
fn single_generic<'a>(ty: &'a syn::Type, wrapper: &str) -> Option<&'a syn::Type> {
if let syn::Type::Path(p) = ty {
let seg = p.path.segments.last()?;
if seg.ident == wrapper {
if let syn::PathArguments::AngleBracketed(a) = &seg.arguments {
if let Some(syn::GenericArgument::Type(t)) = a.args.first() {
return Some(t);
}
}
}
}
None
}
#[derive(Default)]
struct FieldBr {
count: Option<syn::Expr>,
ctx: Option<Vec<Ident>>,
temp: bool,
cond: Option<syn::Expr>,
ignore: bool,
variable: bool,
map: Option<syn::Expr>,
try_map: Option<syn::Expr>,
parse_with: Option<syn::Expr>,
calc: Option<syn::Expr>,
br_calc: Option<syn::Expr>,
bw_map: Option<syn::Expr>,
write_with: Option<syn::Expr>,
pad_before: Option<syn::Expr>,
pad_after: Option<syn::Expr>,
align_before: bool,
align_after: bool,
restore_position: bool,
seek: Option<syn::Expr>,
dbg: bool,
asserts: Vec<(syn::Expr, Option<Punctuated<syn::Expr, Token![,]>>)>,
auto_len: Option<AutoTarget>,
}
#[derive(Clone)]
enum AutoTarget {
Count(Ident),
Bytes(Ident),
}
fn parse_auto_target(expr: &syn::Expr) -> syn::Result<AutoTarget> {
let err = || {
syn::Error::new_spanned(
expr,
"expected `count(<field>)` or `bytes(<field>)` naming a sibling field",
)
};
let syn::Expr::Call(call) = expr else {
return Err(err());
};
let syn::Expr::Path(func) = &*call.func else {
return Err(err());
};
let kind = func.path.get_ident().ok_or_else(err)?;
if call.args.len() != 1 {
return Err(err());
}
let syn::Expr::Path(arg) = &call.args[0] else {
return Err(err());
};
let field = arg.path.get_ident().ok_or_else(err)?.clone();
if kind == "count" {
Ok(AutoTarget::Count(field))
} else if kind == "bytes" {
Ok(AutoTarget::Bytes(field))
} else {
Err(err())
}
}
#[derive(Clone)]
struct AutoLenSpec {
field: Ident,
nested: Ident,
kind: Ident,
source: Ident,
}
impl syn::parse::Parse for AutoLenSpec {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let field: Ident = input.parse()?;
input.parse::<Token![.]>()?;
let nested: Ident = input.parse()?;
input.parse::<Token![=]>()?;
let kind: Ident = input.parse()?;
if kind != "count" && kind != "bytes" {
return Err(syn::Error::new_spanned(
&kind,
"expected `count(<source>)` or `bytes(<source>)`",
));
}
let content;
syn::parenthesized!(content in input);
let source: Ident = content.parse()?;
Ok(AutoLenSpec {
field,
nested,
kind,
source,
})
}
}
enum BrDirective {
Count(syn::Expr),
Ctx(Vec<Ident>),
Temp,
If(syn::Expr),
Map(syn::Expr),
TryMap(syn::Expr),
ParseWith(syn::Expr),
PadBefore(syn::Expr),
PadAfter(syn::Expr),
AlignBefore,
AlignAfter,
RestorePosition,
Seek(syn::Expr),
Dbg,
Assert(Box<syn::Expr>, Option<Punctuated<syn::Expr, Token![,]>>),
Calc(syn::Expr),
}
impl Parse for BrDirective {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![if]) {
input.parse::<Token![if]>()?;
let content;
syn::parenthesized!(content in input);
Ok(BrDirective::If(content.parse()?))
} else {
let kw: Ident = input.parse()?;
match kw.to_string().as_str() {
"count" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Count(input.parse()?))
}
"calc" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Calc(input.parse()?))
}
"temp" => Ok(BrDirective::Temp),
"assert" => {
let content;
syn::parenthesized!(content in input);
let cond: syn::Expr = content.parse()?;
let msg = if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
Some(Punctuated::<syn::Expr, Token![,]>::parse_terminated(
&content,
)?)
} else {
None
};
Ok(BrDirective::Assert(Box::new(cond), msg))
}
"ignore" => Err(syn::Error::new_spanned(
&kw,
"`ignore` marks a field as neither read nor written; write it as `#[brw(ignore)]`",
)),
"ctx" => {
let content;
syn::braced!(content in input);
let names = Punctuated::<Ident, Token![,]>::parse_terminated(&content)?;
Ok(BrDirective::Ctx(names.into_iter().collect()))
}
"map" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Map(input.parse()?))
}
"try_map" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::TryMap(input.parse()?))
}
"parse_with" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::ParseWith(input.parse()?))
}
"pad_before" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::PadBefore(input.parse()?))
}
"pad_after" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::PadAfter(input.parse()?))
}
"align_before" => Ok(BrDirective::AlignBefore),
"align_after" => Ok(BrDirective::AlignAfter),
"restore_position" => Ok(BrDirective::RestorePosition),
"seek" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Seek(input.parse()?))
}
"dbg" => Ok(BrDirective::Dbg),
_ => Err(syn::Error::new_spanned(
kw,
"unknown `#[br(...)]` directive; expected `count`, `calc`, `ctx`, `temp`, `if`, `assert(<expr>)`, `map`, `try_map`, `parse_with`, `pad_before/after`, `align_before/after`, `restore_position`, `seek = <bits>`, or `dbg`",
)),
}
}
}
}
fn parse_field_br(f: &syn::Field) -> syn::Result<FieldBr> {
let mut br = FieldBr::default();
for attr in &f.attrs {
if attr.path().is_ident("br") {
let directives =
attr.parse_args_with(Punctuated::<BrDirective, Token![,]>::parse_terminated)?;
for d in directives {
match d {
BrDirective::Count(e) => br.count = Some(e),
BrDirective::Ctx(names) => br.ctx = Some(names),
BrDirective::Temp => br.temp = true,
BrDirective::If(e) => br.cond = Some(e),
BrDirective::Map(e) => br.map = Some(e),
BrDirective::TryMap(e) => br.try_map = Some(e),
BrDirective::ParseWith(e) => br.parse_with = Some(e),
BrDirective::PadBefore(e) => br.pad_before = Some(e),
BrDirective::PadAfter(e) => br.pad_after = Some(e),
BrDirective::AlignBefore => br.align_before = true,
BrDirective::AlignAfter => br.align_after = true,
BrDirective::RestorePosition => br.restore_position = true,
BrDirective::Seek(e) => br.seek = Some(e),
BrDirective::Dbg => br.dbg = true,
BrDirective::Assert(cond, msg) => br.asserts.push((*cond, msg)),
BrDirective::Calc(e) => br.br_calc = Some(e),
}
}
} else if attr.path().is_ident("bw") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("calc") {
br.calc = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("map") {
br.bw_map = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("write_with") {
br.write_with = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("auto_len") {
br.auto_len = Some(parse_auto_target(&meta.value()?.parse()?)?);
Ok(())
} else {
Err(meta.error(
"unknown `#[bw(...)]` directive; expected `calc = <expr>`, `map = <f>`, `write_with = <f>`, or `auto_len = count(<field>)|bytes(<field>)`",
))
}
})?;
} else if attr.path().is_ident("brw") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("ignore") {
br.ignore = true;
Ok(())
} else if meta.path.is_ident("variable") {
br.variable = true;
Ok(())
} else if meta.path.is_ident("count_prefix") {
let _ = meta.value()?.parse::<syn::Type>()?; Err(meta.error(
"`count_prefix` is only supported under `#[bin]` — the bare \
`BitDecode`/`BitEncode` derives cannot inject the length field; write \
the `#[br(temp)]`/`#[bw(calc = …)]`/`#[br(count = …)]` triad by hand",
))
} else {
Err(meta.error(
"unknown `#[brw(...)]` directive; expected `ignore`, `variable`, or `count_prefix = <Ty>`",
))
}
})?;
}
}
if br.map.is_some() && br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"`#[br(map = …)]` and `#[br(try_map = …)]` are mutually exclusive",
));
}
if br.br_calc.is_some()
&& (br.count.is_some()
|| br.ctx.is_some()
|| br.temp
|| br.cond.is_some()
|| br.map.is_some()
|| br.try_map.is_some()
|| br.parse_with.is_some()
|| br.calc.is_some()
|| br.bw_map.is_some()
|| br.write_with.is_some()
|| br.auto_len.is_some()
|| br.ignore
|| br.seek.is_some()
|| br.restore_position)
{
return Err(syn::Error::new_spanned(
f,
"`#[br(calc = …)]` computes the field on decode and writes nothing on encode, \
so it cannot combine with a reading, mapping, positioning, or writing directive",
));
}
Ok(br)
}
fn field_is_temp(f: &syn::Field) -> bool {
parse_field_br(f).is_ok_and(|br| br.temp)
}
fn field_is_ignore(f: &syn::Field) -> bool {
parse_field_br(f).is_ok_and(|br| br.ignore)
}
fn field_is_br_calc(f: &syn::Field) -> bool {
parse_field_br(f).is_ok_and(|br| br.br_calc.is_some())
}
fn br_indeterminate(br: &FieldBr) -> bool {
br.ctx.is_some()
|| br.cond.is_some()
|| br.map.is_some()
|| br.try_map.is_some()
|| br.bw_map.is_some()
|| br.parse_with.is_some()
|| br.write_with.is_some()
|| br.variable
|| br.pad_before.is_some()
|| br.pad_after.is_some()
|| br.align_before
|| br.align_after
|| br.restore_position
|| br.seek.is_some()
}
enum Reserved {
Zero,
With(Box<syn::Expr>),
}
fn field_reserved(f: &syn::Field) -> syn::Result<Option<Reserved>> {
for attr in &f.attrs {
if attr.path().is_ident("reserved") {
return Ok(Some(Reserved::Zero));
}
if attr.path().is_ident("reserved_with") {
return Ok(Some(Reserved::With(Box::new(attr.parse_args()?))));
}
}
Ok(None)
}
fn reserved_spec_value(f: &syn::Field) -> syn::Result<Option<TokenStream2>> {
let bnb = crate::bnb_path();
let ty = &f.ty;
Ok(field_reserved(f)?.map(|reserved| match reserved {
Reserved::Zero => quote!(<#ty as #bnb::__private::Bits>::from_bits(0)),
Reserved::With(expr) => {
let expr = *expr;
quote!({ let __r: #ty = #expr; __r })
}
}))
}
fn field_is_reserved(f: &syn::Field) -> bool {
f.attrs
.iter()
.any(|a| a.path().is_ident("reserved") || a.path().is_ident("reserved_with"))
}
fn is_codec_field_attr(a: &syn::Attribute) -> bool {
[
"nested",
"br",
"bw",
"brw",
"builder",
"reserved",
"reserved_with",
"try_str",
]
.iter()
.any(|n| a.path().is_ident(n))
}
fn field_is_try_str(f: &syn::Field) -> bool {
f.attrs.iter().any(|a| a.path().is_ident("try_str"))
}
fn intercept_debug_derive(attrs: &[syn::Attribute]) -> syn::Result<(bool, Vec<syn::Attribute>)> {
let mut had_debug = false;
let mut out = Vec::new();
for attr in attrs {
if attr.path().is_ident("derive") {
let paths =
attr.parse_args_with(Punctuated::<syn::Path, Token![,]>::parse_terminated)?;
let kept: Vec<_> = paths
.into_iter()
.filter(|p| {
if p.is_ident("Debug") {
had_debug = true;
false
} else {
true
}
})
.collect();
if !kept.is_empty() {
out.extend(syn::Attribute::parse_outer.parse2(quote!(#[derive(#(#kept),*)]))?);
}
} else {
out.push(attr.clone());
}
}
Ok((had_debug, out))
}
fn debug_field_calls(
idents: &[syn::Ident],
try_str: &[syn::Ident],
bnb: &TokenStream2,
) -> TokenStream2 {
let calls = idents.iter().map(|id| {
if try_str.iter().any(|t| t == id) {
quote!(.field(::core::stringify!(#id), &#bnb::__private::TryStr(&self.#id)))
} else {
quote!(.field(::core::stringify!(#id), &self.#id))
}
});
quote!(#(#calls)*)
}
fn enum_try_str_debug(
name: &syn::Ident,
variants: &Punctuated<syn::Variant, Token![,]>,
bnb: &TokenStream2,
) -> Option<TokenStream2> {
let has_try_str = variants.iter().any(|v| {
v.fields
.iter()
.any(|f| !field_is_temp(f) && field_is_try_str(f))
});
if !has_try_str {
return None;
}
let arms = variants.iter().map(|v| {
let vn = &v.ident;
match &v.fields {
Fields::Unit => quote!(Self::#vn => __f.write_str(::core::stringify!(#vn))),
Fields::Named(named) => {
let stored: Vec<&syn::Field> =
named.named.iter().filter(|f| !field_is_temp(f)).collect();
let binds: Vec<&syn::Ident> =
stored.iter().filter_map(|f| f.ident.as_ref()).collect();
let calls = stored.iter().map(|f| {
let id = f.ident.as_ref().unwrap();
if field_is_try_str(f) {
quote!(.field(::core::stringify!(#id), &#bnb::__private::TryStr(#id)))
} else {
quote!(.field(::core::stringify!(#id), #id))
}
});
quote!(Self::#vn { #(#binds),* } =>
__f.debug_struct(::core::stringify!(#vn)) #(#calls)* .finish())
}
Fields::Unnamed(unnamed) => {
let stored: Vec<&syn::Field> = unnamed
.unnamed
.iter()
.filter(|f| !field_is_temp(f))
.collect();
let binds: Vec<syn::Ident> = (0..stored.len())
.map(|i| quote::format_ident!("__f{i}"))
.collect();
let calls = stored.iter().zip(&binds).map(|(f, b)| {
if field_is_try_str(f) {
quote!(.field(&#bnb::__private::TryStr(#b)))
} else {
quote!(.field(#b))
}
});
quote!(Self::#vn( #(#binds),* ) =>
__f.debug_tuple(::core::stringify!(#vn)) #(#calls)* .finish())
}
}
});
Some(quote! {
impl ::core::fmt::Debug for #name {
fn fmt(&self, __f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#(#arms),*
}
}
}
})
}
fn ctx_literal(
ctx_ty: &TokenStream2,
names: &[Ident],
field_set: Option<&[&Ident]>,
) -> TokenStream2 {
let inits = names.iter().map(|n| match field_set {
Some(fields) if fields.contains(&n) => quote!(#n: self.#n),
_ => quote!(#n),
});
quote!(#ctx_ty { #(#inits),* })
}
fn field_width(f: &syn::Field) -> TokenStream2 {
let bnb = crate::bnb_path();
let ty = &f.ty;
if field_is_ignore(f) || field_is_br_calc(f) {
return quote!(0u32); }
if let Some(elem) = vec_elem(f) {
quote!(<#elem as #bnb::__private::FixedBitLen>::BIT_LEN)
} else if let Some(len) = byte_array_len(f) {
quote!(((#len) as u32 * 8))
} else {
quote!(<#ty as #bnb::__private::FixedBitLen>::BIT_LEN)
}
}
fn pad_read_tokens(align: bool, pad: Option<&syn::Expr>) -> TokenStream2 {
let bnb = crate::bnb_path();
let align = align.then(|| quote!(#bnb::__private::align_read(__bnb_r)?;));
let pad = pad.map(|n| quote!(#bnb::__private::skip_read(__bnb_r, #n)?;));
quote!(#align #pad)
}
fn pad_write_tokens(align: bool, pad: Option<&syn::Expr>) -> TokenStream2 {
let bnb = crate::bnb_path();
let align = align.then(|| quote!(#bnb::__private::align_write(__bnb_w)?;));
let pad = pad.map(|n| quote!(#bnb::__private::skip_write(__bnb_w, #n)?;));
quote!(#align #pad)
}
fn field_read_stmt(f: &syn::Field, br: &FieldBr) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let pre = pad_read_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_read_tokens(br.align_after, br.pad_after.as_ref());
let seek = br
.seek
.as_ref()
.map(|e| quote!(#bnb::__private::Source::seek_to_bit(__bnb_r, (#e) as usize)?;));
let mut core = field_read_core(f, br)?;
if br.dbg {
let id = f.ident.as_ref().expect("named field");
core = quote! {
let __dbg_at = #bnb::__private::Source::bit_pos(__bnb_r);
#core
#bnb::__private::tracing::trace!(
target: "bnb::dbg",
field = ::core::stringify!(#id),
at_bit = __dbg_at,
value = ?#id,
);
};
}
if !br.asserts.is_empty() {
let id = f.ident.as_ref().expect("named field");
let checks = br.asserts.iter().map(|(cond, msg)| {
let msg_ts = match msg {
Some(args) => quote!(#bnb::__private::format!(#args)),
None => quote!(#bnb::__private::String::from(::core::concat!(
"assertion failed: `",
::core::stringify!(#cond),
"`"
))),
};
quote! {
if !(#cond) {
return ::core::result::Result::Err(
#bnb::__private::BitError::convert(
#msg_ts,
#bnb::__private::Source::bit_pos(__bnb_r),
)
.in_field(::core::stringify!(#id)),
);
}
}
});
core = quote!(#core #(#checks)*);
}
let mut body = quote!(#seek #core);
if br.restore_position {
body = quote! {
let __pos = #bnb::__private::Source::bit_pos(__bnb_r);
#body
#bnb::__private::Source::seek_to_bit(__bnb_r, __pos)?;
};
}
Ok(quote!(#pre #body #post))
}
fn field_read_core(f: &syn::Field, br: &FieldBr) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let id = f.ident.as_ref().expect("named field");
let ty = &f.ty;
if br.ignore {
return Ok(quote!(let #id = ::core::default::Default::default();));
}
if let Some(calc) = &br.br_calc {
return Ok(quote!(let #id: #ty = #calc;));
}
if let Some(cond) = &br.cond {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>` field")
})?;
let read_inner = quote!(<#inner as #bnb::__private::BitDecode>::bit_decode(__bnb_r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?);
return Ok(quote! {
let #id = if (#cond) {
::core::option::Option::Some(#read_inner)
} else {
::core::option::Option::None
};
});
}
if let Some(map) = &br.map {
return Ok(
quote!(let #id: #ty = #bnb::__private::read_mapped(__bnb_r, #map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
);
}
if let Some(try_map) = &br.try_map {
return Ok(
quote!(let #id: #ty = #bnb::__private::read_try_mapped(__bnb_r, #try_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
);
}
if let Some(f) = &br.parse_with {
return Ok(quote!(let #id: #ty = (#f)(__bnb_r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if let Some(elem) = vec_elem(f) {
let count = br.count.as_ref().ok_or_else(|| {
syn::Error::new_spanned(f, "a `Vec<_>` field needs `#[br(count = <expr>)]`")
})?;
let read_elem = if let Some(names) = &br.ctx {
let lit = ctx_literal(&ctx_struct_ty(elem)?, names, None);
quote! {
let __e = <#elem>::decode_with(__bnb_r, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
} else {
quote! {
let __e = <#elem as #bnb::__private::BitDecode>::bit_decode(__bnb_r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
};
Ok(quote! {
let #id = {
let __n = (#count) as usize;
let mut __v: #bnb::__private::Vec<#elem> = #bnb::__private::Vec::new();
for _ in 0..__n {
#read_elem
__v.push(__e);
}
__v
};
})
} else {
if br.count.is_some() {
return Err(syn::Error::new_spanned(
f,
"`#[br(count = …)]` applies only to a `Vec<_>` field",
));
}
if let Some(names) = &br.ctx {
let lit = ctx_literal(&ctx_struct_ty(ty)?, names, None);
Ok(quote!(let #id = <#ty>::decode_with(__bnb_r, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else if byte_array_len(f).is_some() {
Ok(quote!(let #id = #bnb::__private::read_byte_array(__bnb_r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else {
Ok(
quote!(let #id = <#ty as #bnb::__private::BitDecode>::bit_decode(__bnb_r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
}
}
}
fn field_write_stmt(
f: &syn::Field,
br: &FieldBr,
field_set: &[&Ident],
spec: bool,
) -> syn::Result<TokenStream2> {
let pre = pad_write_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_write_tokens(br.align_after, br.pad_after.as_ref());
let core = if br.restore_position {
quote!()
} else {
field_write_core(f, br, field_set, spec)?
};
Ok(quote!(#pre #core #post))
}
fn field_write_core(
f: &syn::Field,
br: &FieldBr,
field_set: &[&Ident],
spec: bool,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let id = f.ident.as_ref().expect("named field");
let ty = &f.ty;
if spec {
if let Some(value) = reserved_spec_value(f)? {
return Ok(quote!(#bnb::__private::Sink::write(__bnb_w, #value)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
}
if br.ignore {
return Ok(quote!());
}
if br.br_calc.is_some() {
return Ok(quote!());
}
if let Some(calc) = &br.calc {
if br.temp {
return Ok(quote! {
let #id: #ty = #calc;
#bnb::__private::Sink::write(__bnb_w, #id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
});
}
if spec {
return Ok(quote! {
{
let __calc: #ty = #calc;
#bnb::__private::Sink::write(__bnb_w, __calc)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
});
}
return Ok(quote!(#bnb::__private::Sink::write(__bnb_w, self.#id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if br.temp {
return Err(syn::Error::new_spanned(
f,
"a `#[br(temp)]` field is not stored, so it needs `#[bw(calc = <expr>)]` to encode",
));
}
if let Some(bw_map) = &br.bw_map {
return Ok(
quote!(#bnb::__private::write_mapped(__bnb_w, &self.#id, #bw_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
);
}
if let Some(f) = &br.write_with {
return Ok(quote!((#f)(&self.#id, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if let Some(target) = &br.auto_len {
let measure = match target {
AutoTarget::Count(field) => quote!(self.#field.len()),
AutoTarget::Bytes(field) => quote! {{
let mut __probe = #bnb::__private::BitWriter::new();
#bnb::__private::BitEncode::bit_encode(&self.#field, &mut __probe)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
__probe.bit_len().div_ceil(8)
}},
};
return Ok(quote! {
{
let __resolved = match &self.#id {
#bnb::__private::WireLen::Set(_) => ::core::clone::Clone::clone(&self.#id),
#bnb::__private::WireLen::Auto => self.#id.resolve_count(#measure)
.map_err(|e| e.in_field(::core::stringify!(#id)))?,
};
<#ty as #bnb::__private::BitEncode>::bit_encode(&__resolved, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
});
}
if br.map.is_some() || br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(map = …)]`/`#[br(try_map = …)]` field needs the inverse `#[bw(map = <f>)]` to encode",
));
}
if br.parse_with.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(parse_with = …)]` field needs the inverse `#[bw(write_with = <f>)]` to encode",
));
}
if br.cond.is_some() {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>` field")
})?;
let write_inner = quote!(<#inner as #bnb::__private::BitEncode>::bit_encode(__v, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;);
return Ok(quote! {
if let ::core::option::Option::Some(__v) = &self.#id {
#write_inner
}
});
}
if let Some(elem) = vec_elem(f) {
let write_elem = if let Some(names) = &br.ctx {
let elem_ctx = ctx_struct_ty(elem)?;
let lit = ctx_literal(&elem_ctx, names, Some(field_set));
quote!(<#elem as #bnb::EncodeWith<#elem_ctx>>::encode_with(__e, __bnb_w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(<#elem as #bnb::__private::BitEncode>::bit_encode(__e, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
Ok(quote! {
for __e in &self.#id {
#write_elem
}
})
} else if let Some(names) = &br.ctx {
let child_ctx = ctx_struct_ty(ty)?;
let lit = ctx_literal(&child_ctx, names, Some(field_set));
Ok(
quote!(<#ty as #bnb::EncodeWith<#child_ctx>>::encode_with(&self.#id, __bnb_w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
} else if byte_array_len(f).is_some() {
Ok(quote!(#bnb::__private::write_byte_array(&self.#id, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else {
Ok(
quote!(<#ty as #bnb::__private::BitEncode>::bit_encode(&self.#id, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
}
}
fn alignment_guard(fields: &FieldsNamed, allow: bool, magic: Option<&syn::Expr>) -> TokenStream2 {
if allow || (fields.named.is_empty() && magic.is_none()) {
return quote!();
}
let bnb = crate::bnb_path();
let mut terms: Vec<TokenStream2> = fields
.named
.iter()
.map(|f| {
let w = field_width(f);
quote!((#w % 8 == 0))
})
.collect();
if let Some(m) = magic {
terms.push(quote!((#bnb::__private::bits_of(&#m) % 8 == 0)));
}
quote! {
const _: () = {
assert!(!(true #(&& #terms)*), #BYTE_ALIGNED_MSG);
};
}
}
pub(crate) fn expand_decode(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match decode_inner(&input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn decode_inner(input: &DeriveInput) -> syn::Result<TokenStream2> {
gen_decode(
&input.ident,
named_struct(input)?,
&parse_bit_stream(input)?,
)
}
fn gen_decode(
name: &Ident,
fields: &FieldsNamed,
attrs: &BitStreamAttrs,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let brs: Vec<FieldBr> = fields
.named
.iter()
.map(parse_field_br)
.collect::<syn::Result<Vec<_>>>()?;
let indeterminate = !attrs.ctx.is_empty() || brs.iter().any(br_indeterminate);
let guard = alignment_guard(
fields,
attrs.allow_byte_aligned || indeterminate,
attrs.magic.as_ref(),
);
let layout = layout_token(attrs);
let (magic_read, magic_bits) = match &attrs.magic {
Some(m) => (
quote! {
#bnb::__private::verify_magic(__bnb_r, #m).map_err(|e| e.in_field("magic"))?;
},
quote!(#bnb::__private::bits_of(&#m) +),
),
None => (quote!(), quote!()),
};
let ids: Vec<&Ident> = fields
.named
.iter()
.zip(&brs)
.filter(|(_, br)| !br.temp)
.map(|(f, _)| f.ident.as_ref().expect("named field"))
.collect();
let read_stmts = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_read_stmt(f, br))
.collect::<syn::Result<Vec<_>>>()?;
if !attrs.ctx.is_empty() {
let ctx_name = ctx_struct_ident(name);
let ctx_binds = attrs.ctx.iter().map(|(n, _)| quote!(let #n = __ctx.#n;));
return Ok(quote! {
#guard
impl #name {
#[doc = "Decode from a bit source, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)] pub fn decode_with<S: #bnb::__private::Source>(
__bnb_r: &mut S,
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#(#ctx_binds)*
#magic_read
#(#read_stmts)*
::core::result::Result::Ok(Self { #(#ids),* })
}
#[doc = "Decode from bytes with context, requiring every whole byte consumed."]
pub fn decode_with_exact(
bytes: &[u8],
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |__bnb_r| Self::decode_with(__bnb_r, __ctx))
}
}
impl #bnb::DecodeWith<#ctx_name> for #name {
fn decode_with<S: #bnb::__private::Source>(
__bnb_r: &mut S,
args: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<#name>::decode_with(__bnb_r, args)
}
}
});
}
let variable = indeterminate || fields.named.iter().any(|f| vec_elem(f).is_some());
let fixed_bit_len = if variable {
quote!()
} else {
let widths = fields.named.iter().map(field_width);
quote! {
impl #bnb::__private::FixedBitLen for #name {
const BIT_LEN: u32 = #magic_bits 0 #(+ #widths)*;
}
}
};
let seeks = brs
.iter()
.any(|br| br.restore_position || br.seek.is_some());
let from_bound = if seeks {
quote!(#bnb::__private::SeekSource)
} else {
quote!(#bnb::__private::Source)
};
let from_doc = if seeks {
"Decode one message from a **seekable** bit cursor (a `BitReader`, `BufSource`, `BitBuf`, \
…), advancing it. This message uses a seeking directive (`restore_position`/`seek`), so a \
forward-only stream is rejected at compile time. The byte/bit order is the cursor's — \
build it with the message's layout, or use `decode_exact`/`decode_all`, which bake it in."
} else {
"Decode one message from a bit cursor (a `BitReader`, `BufSource`, `BitBuf`, a streaming \
reader, …), advancing it. The byte/bit order is the cursor's, not the message's — for a \
non-default (`little`/`lsb`) order build the cursor with the message's layout (e.g. \
`BitReader::with_layout`), or use `decode_exact`/`decode_all`, which bake it in."
};
Ok(quote! {
#guard
#fixed_bit_len
impl #bnb::BitDecode for #name {
fn bit_decode<S: #bnb::__private::Source>(
__bnb_r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#magic_read
#(#read_stmts)*
::core::result::Result::Ok(Self { #(#ids),* })
}
}
impl #name {
#[doc = #from_doc]
pub fn decode<S: #from_bound>(
__bnb_r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(__bnb_r)
}
#[doc = "Decode every message from `bytes` into a `Vec`, bit-aware with the message's own byte/bit order baked in. The buffer must hold whole messages (a partial tail is an error)."]
pub fn decode_all(
bytes: &[u8],
) -> ::core::result::Result<#bnb::__private::Vec<Self>, #bnb::__private::BitError> {
#bnb::__private::decode_all(bytes, #layout)
}
#[doc = "A lazy iterator decoding successive messages from `bytes` (layout baked in) until it is drained, ending after the first error if one occurs."]
pub fn decode_iter(
bytes: &[u8],
) -> impl ::core::iter::Iterator<Item = ::core::result::Result<Self, #bnb::__private::BitError>> + '_ {
#bnb::__private::decode_iter(bytes, #layout)
}
#[doc = "Decode one message from `bytes` without consuming the caller's buffer (tail-tolerant)."]
pub fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed (errors with `ErrorKind::TrailingBytes` otherwise)."]
pub fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
}
})
}
fn tokens_mention(ts: TokenStream2, names: &[&Ident]) -> bool {
ts.into_iter().any(|tt| match tt {
proc_macro2::TokenTree::Ident(id) => names.iter().any(|n| **n == id),
proc_macro2::TokenTree::Group(g) => tokens_mention(g.stream(), names),
_ => false,
})
}
pub(crate) fn expand_encode(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match encode_inner(&input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn encode_inner(input: &DeriveInput) -> syn::Result<TokenStream2> {
gen_encode(
&input.ident,
named_struct(input)?,
&parse_bit_stream(input)?,
)
}
fn field_write_stmt_auto(
f: &syn::Field,
br: &FieldBr,
field_set: &[&Ident],
spec: bool,
auto_len: &[AutoLenSpec],
) -> syn::Result<TokenStream2> {
let id = f.ident.as_ref().expect("named field");
let targeting: Vec<&AutoLenSpec> = auto_len.iter().filter(|s| &s.field == id).collect();
if targeting.is_empty() {
return field_write_stmt(f, br, field_set, spec);
}
if br.calc.is_some()
|| br.bw_map.is_some()
|| br.write_with.is_some()
|| br.map.is_some()
|| br.try_map.is_some()
|| br.parse_with.is_some()
|| br.auto_len.is_some()
|| br.cond.is_some()
|| br.count.is_some()
|| br.ctx.is_some()
|| br.temp
|| br.ignore
|| br.restore_position
{
return Err(syn::Error::new_spanned(
f,
"a field targeted by `#[bin(auto_len(...))]` cannot also carry a codec directive \
(`calc`/`map`/`write_with`/`parse_with`/`auto_len`/`if`/`count`/`ctx`/`temp`/`ignore`/\
`restore_position`); only positioning (`pad_*`/`align_*`) may accompany it",
));
}
let bnb = crate::bnb_path();
let ty = &f.ty;
let fills = targeting.iter().map(|s| {
let nested = &s.nested;
let source = &s.source;
let measure = if s.kind == "bytes" {
quote! {{
let mut __probe = #bnb::__private::BitWriter::new();
#bnb::__private::BitEncode::bit_encode(&self.#source, &mut __probe)
.map_err(|e| e.in_field(::core::stringify!(#nested)))?;
__probe.bit_len().div_ceil(8)
}}
} else {
quote!(self.#source.len())
};
quote! {
if #bnb::__private::WireLen::is_auto(&__auto.#nested) {
__auto.#nested = __auto.#nested.resolve_count(#measure)
.map_err(|e| e.in_field(::core::stringify!(#nested)))?;
}
}
});
let pre = pad_write_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_write_tokens(br.align_after, br.pad_after.as_ref());
Ok(quote! {
#pre
{
let mut __auto = ::core::clone::Clone::clone(&self.#id);
#(#fills)*
<#ty as #bnb::__private::BitEncode>::bit_encode(&__auto, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
#post
})
}
fn gen_encode(
name: &Ident,
fields: &FieldsNamed,
attrs: &BitStreamAttrs,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let brs: Vec<FieldBr> = fields
.named
.iter()
.map(parse_field_br)
.collect::<syn::Result<Vec<_>>>()?;
for (i, spec) in attrs.auto_len.iter().enumerate() {
let exists = fields
.named
.iter()
.zip(&brs)
.any(|(f, br)| !br.temp && f.ident.as_ref() == Some(&spec.field));
if !exists {
return Err(syn::Error::new_spanned(
&spec.field,
format!(
"`auto_len` names `{}`, which is not a stored field of this struct",
spec.field
),
));
}
if attrs.auto_len[..i]
.iter()
.any(|prev| prev.field == spec.field && prev.nested == spec.nested)
{
return Err(syn::Error::new_spanned(
&spec.nested,
format!(
"`auto_len` targets `{}.{}` more than once",
spec.field, spec.nested
),
));
}
}
let indeterminate = !attrs.ctx.is_empty() || brs.iter().any(br_indeterminate);
let guard = alignment_guard(
fields,
attrs.allow_byte_aligned || indeterminate,
attrs.magic.as_ref(),
);
let layout = layout_token(attrs);
let magic_write = match &attrs.magic {
Some(m) => quote! {
#bnb::__private::Sink::write(__bnb_w, #m).map_err(|e| e.in_field("magic"))?;
},
None => quote!(),
};
let field_set: Vec<&Ident> = fields
.named
.iter()
.zip(&brs)
.filter(|(_, br)| !br.temp)
.map(|(f, _)| f.ident.as_ref().expect("named field"))
.collect();
let writes = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_write_stmt_auto(f, br, &field_set, false, &attrs.auto_len))
.collect::<syn::Result<Vec<_>>>()?;
let ctx_names: Vec<&Ident> = attrs.ctx.iter().map(|(n, _)| n).collect();
let encode_uses_ctx =
!ctx_names.is_empty() && writes.iter().any(|w| tokens_mention(w.clone(), &ctx_names));
if encode_uses_ctx {
let ctx_name = ctx_struct_ident(name);
let ctx_binds = attrs.ctx.iter().map(|(n, _)| quote!(let #n = __ctx.#n;));
return Ok(quote! {
#guard
impl #name {
#[doc = "Encode to a bit sink, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)] pub fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
__ctx: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#(#ctx_binds)*
#magic_write
#(#writes)*
::core::result::Result::Ok(())
}
#[doc = "Encode to a `Vec<u8>` with context."]
pub fn to_bytes_with(
&self,
__ctx: #ctx_name,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(#layout, |__bnb_w| self.encode_with(__bnb_w, __ctx))
}
}
impl #bnb::EncodeWith<#ctx_name> for #name {
fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<#name>::encode_with(self, __bnb_w, args)
}
}
});
}
let has_canonical = fields
.named
.iter()
.zip(&brs)
.any(|(f, br)| field_is_reserved(f) || (br.calc.is_some() && !br.temp));
let (canonical_method, canonical_inherent) = if !has_canonical {
(quote!(), quote!())
} else {
let writes_canonical = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_write_stmt_auto(f, br, &field_set, true, &attrs.auto_len))
.collect::<syn::Result<Vec<_>>>()?;
let mut calc_precompute = Vec::new();
let mut field_inits = Vec::new();
let mut diff_checks = Vec::new();
for (f, br) in fields.named.iter().zip(&brs) {
if br.temp {
continue; }
let id = f.ident.as_ref().expect("named field");
let ty = &f.ty;
if let Some(calc) = &br.calc {
let local = format_ident!("__canon_{}", id);
calc_precompute.push(quote!(let #local: #ty = #calc;));
field_inits.push(quote!(#id: #local));
diff_checks
.push(quote!(if self.#id != (#calc) { __d.push(::core::stringify!(#id)); }));
} else if let Some(spec) = reserved_spec_value(f)? {
field_inits.push(quote!(#id: #spec));
diff_checks
.push(quote!(if self.#id != (#spec) { __d.push(::core::stringify!(#id)); }));
} else {
field_inits.push(quote!(#id: self.#id));
}
}
let method = quote! {
fn canonical_bit_encode<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#magic_write
#(#writes_canonical)*
::core::result::Result::Ok(())
}
};
let inherent = quote! {
impl #name {
#[doc = "Encode the **canonical** form to a `Vec<u8>`: reserved fields written as their"]
#[doc = "spec value and `calc` fields recomputed (ignoring the stored values), so the"]
#[doc = "result is always spec-compliant. (`to_bytes` is verbatim — it writes exactly"]
#[doc = "what is stored.) To emit this form over a `std::io::Write`, encode the"]
#[doc = "canonical copy: `value.to_canonical().encode(&mut w)`."]
pub fn to_canonical_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(
#layout,
|__bnb_w| <Self as #bnb::BitEncode>::canonical_bit_encode(self, __bnb_w),
)
}
#[doc = "The **canonical form in memory**: a copy with reserved fields set to their"]
#[doc = "spec value and `calc` fields recomputed. `value.to_canonical().to_bytes()`"]
#[doc = "equals `value.to_canonical_bytes()`."]
pub fn to_canonical(self) -> Self {
#(#calc_precompute)*
Self { #(#field_inits),* }
}
#[doc = "The names of the stored fields whose value differs from canonical — i.e."]
#[doc = "reserved fields not at their spec value, or `calc` fields not equal to their"]
#[doc = "recomputed value. Empty iff `self` is already canonical."]
pub fn canonical_diff(&self) -> #bnb::__private::Vec<&'static str> {
let mut __d = #bnb::__private::Vec::new();
#(#diff_checks)*
__d
}
#[doc = "Whether `self` is already in canonical form (no reserved/`calc` field differs)."]
pub fn is_canonical(&self) -> bool {
self.canonical_diff().is_empty()
}
}
};
(method, inherent)
};
let encode_with_trait = if attrs.ctx.is_empty() {
quote!()
} else {
let ctx_name = ctx_struct_ident(name);
quote! {
impl #bnb::EncodeWith<#ctx_name> for #name {
#[allow(unused_variables)]
fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, __bnb_w)
}
}
}
};
Ok(quote! {
#guard
impl #bnb::BitEncode for #name {
const LAYOUT: #bnb::Layout = #layout;
fn bit_encode<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#magic_write
#(#writes)*
::core::result::Result::Ok(())
}
#canonical_method
}
impl #name {
#[doc = "Encode to a `Vec<u8>`, **verbatim** — exactly what's stored, never silently"]
#[doc = "rewritten (so `decode` then `to_bytes` round-trips byte-for-byte). For the"]
#[doc = "spec-normalized form, use `to_canonical_bytes` (generated when the message has"]
#[doc = "a `reserved` or `calc` field). To write into an explicit bit sink (a `BitWriter`)"]
#[doc = "or a `std::io::Write`, bring [`BitEncode`](::bnb::BitEncode) /"]
#[doc = "[`EncodeExt`](::bnb::EncodeExt) into scope and call `.bit_encode(&mut sink)` /"]
#[doc = "`.encode(&mut w)` (the latter is verbatim, `std` only)."]
pub fn to_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
}
#encode_with_trait
#canonical_inherent
})
}
#[derive(Default)]
struct BinArgs {
read_only: bool,
write_only: bool,
no_builder: bool,
forward_only: bool,
lsb: bool,
little: bool,
magic: Option<syn::Expr>,
ctx: Vec<(Ident, Type)>,
auto_len: Vec<AutoLenSpec>,
validate: Option<syn::Path>,
tag: Option<Ident>,
map: Option<syn::Expr>,
try_map: Option<syn::Expr>,
bw_map: Option<syn::Expr>,
wire: Option<Type>,
try_wire: Option<Type>,
codec_parse: Option<syn::Expr>,
codec_write: Option<syn::Expr>,
}
impl BinArgs {
fn is_mapped(&self) -> bool {
self.map.is_some()
|| self.try_map.is_some()
|| self.bw_map.is_some()
|| self.wire.is_some()
|| self.try_wire.is_some()
}
fn has_codec(&self) -> bool {
self.codec_parse.is_some() || self.codec_write.is_some()
}
}
struct CodecFn {
name: Ident,
expr: syn::Expr,
}
impl Parse for CodecFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![=]>()?;
Ok(CodecFn {
name,
expr: input.parse()?,
})
}
}
pub(crate) fn expand_bin(attr: TokenStream, item: TokenStream) -> TokenStream {
match bin_inner(attr, item) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn bin_inner(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream2> {
let mut args = BinArgs::default();
let parser = syn::meta::parser(|meta| {
if meta.path.is_ident("read_only") {
args.read_only = true;
} else if meta.path.is_ident("write_only") {
args.write_only = true;
} else if meta.path.is_ident("no_builder") {
args.no_builder = true;
} else if meta.path.is_ident("forward_only") {
args.forward_only = true;
} else if meta.path.is_ident("bits") {
let v: Ident = meta.value()?.parse()?;
match v.to_string().as_str() {
"msb" => args.lsb = false,
"lsb" => args.lsb = true,
_ => return Err(meta.error("expected `msb` or `lsb`")),
}
} else if meta.path.is_ident("bytes") {
let v: Ident = meta.value()?.parse()?;
match v.to_string().as_str() {
"big" => args.little = false,
"little" => args.little = true,
_ => return Err(meta.error("expected `big` or `little`")),
}
} else if meta.path.is_ident("big") {
args.little = false;
} else if meta.path.is_ident("little") {
args.little = true;
} else if meta.path.is_ident("magic") {
args.magic = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("ctx") {
let content;
syn::parenthesized!(content in meta.input);
let params = Punctuated::<CtxParam, Token![,]>::parse_terminated(&content)?;
args.ctx = params.into_iter().map(|p| (p.name, p.ty)).collect();
} else if meta.path.is_ident("auto_len") {
let content;
syn::parenthesized!(content in meta.input);
let specs = Punctuated::<AutoLenSpec, Token![,]>::parse_terminated(&content)?;
args.auto_len = specs.into_iter().collect();
} else if meta.path.is_ident("validate") {
args.validate = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("tag") {
args.tag = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("map") {
args.map = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("try_map") {
args.try_map = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("bw_map") {
args.bw_map = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("wire") {
args.wire = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("try_wire") {
args.try_wire = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("codec") {
if args.has_codec() {
return Err(meta.error("duplicate `codec`"));
}
if meta.input.peek(Token![=]) {
let m: syn::Path = meta.value()?.parse()?;
args.codec_parse = Some(syn::parse_quote!(#m::parse));
args.codec_write = Some(syn::parse_quote!(#m::write));
} else {
let content;
syn::parenthesized!(content in meta.input);
let fns = Punctuated::<CodecFn, Token![,]>::parse_terminated(&content)?;
for f in fns {
match f.name.to_string().as_str() {
"parse" => {
if args.codec_parse.is_some() {
return Err(syn::Error::new_spanned(&f.name, "duplicate `parse`"));
}
args.codec_parse = Some(f.expr);
}
"write" => {
if args.codec_write.is_some() {
return Err(syn::Error::new_spanned(&f.name, "duplicate `write`"));
}
args.codec_write = Some(f.expr);
}
_ => {
return Err(syn::Error::new_spanned(
&f.name,
"unknown `codec(...)` entry; expected `parse = <fn>` and/or `write = <fn>`",
));
}
}
}
if !args.has_codec() {
return Err(meta.error(
"empty `codec(...)`; expected `parse = <fn>` and/or `write = <fn>`",
));
}
}
} else {
return Err(meta.error(
"unknown `#[bin(...)]` option; expected one of: read_only, write_only, \
no_builder, forward_only, big, little, bytes = big|little, bits = msb|lsb, magic = <expr>, \
ctx(name: Ty, …), validate = <path>, tag = <ctx-param>, \
map/try_map = |w: Wire| …, bw_map = |s: &Self| Wire, wire/try_wire = WireType, \
codec = <module>, or codec(parse = <f>, write = <f>)",
));
}
Ok(())
});
Parser::parse(parser, attr)?;
if args.read_only && args.write_only {
return Err(syn::Error::new(
::proc_macro2::Span::call_site(),
"`read_only` and `write_only` are mutually exclusive",
));
}
if args.has_codec() && args.is_mapped() {
return Err(syn::Error::new(
::proc_macro2::Span::call_site(),
"`codec` and struct-level wire mapping (`map`/`try_map`/`bw_map`/`wire`/`try_wire`) \
are mutually exclusive — a codec newtype's fns own its wire form",
));
}
match syn::parse::<syn::Item>(item)? {
syn::Item::Struct(s) => bin_struct(&args, &s),
syn::Item::Enum(e) => {
if args.is_mapped() {
return Err(syn::Error::new_spanned(
&e.ident,
"struct-level wire mapping (`map`/`try_map`/`bw_map`/`wire`/`try_wire`) applies \
to a `#[bin]` struct, not an enum — map the enum's variant fields, or wrap \
the enum in a mapped struct",
));
}
if args.has_codec() {
return Err(syn::Error::new_spanned(
&e.ident,
"`codec` applies to a single-field tuple struct (a newtype), not an enum",
));
}
bin_enum(&args, &e)
}
other => Err(syn::Error::new_spanned(
other,
"#[bin] requires a struct or an enum",
)),
}
}
fn mapped_wire_type(args: &BinArgs) -> syn::Result<Type> {
if let Some(w) = args.wire.as_ref().or(args.try_wire.as_ref()) {
return Ok(w.clone());
}
if let Some(expr) = args.map.as_ref().or(args.try_map.as_ref()) {
if let syn::Expr::Closure(c) = expr {
return match c.inputs.first() {
Some(syn::Pat::Type(pt)) => Ok((*pt.ty).clone()),
_ => Err(syn::Error::new_spanned(
expr,
"the `map`/`try_map` closure must annotate its wire-type parameter, \
e.g. `map = |w: WireType| …`",
)),
};
}
return Err(syn::Error::new_spanned(
expr,
"struct-level `map`/`try_map` must be a closure, e.g. `map = |w: WireType| Self::from(w)`",
));
}
let expr = args
.bw_map
.as_ref()
.expect("a mapped struct sets at least one mapping option");
if let syn::Expr::Closure(c) = expr {
if let syn::ReturnType::Type(_, ty) = &c.output {
return Ok((**ty).clone());
}
return Err(syn::Error::new_spanned(
expr,
"a write-only mapped struct needs the wire type: annotate the `bw_map` return, \
e.g. `bw_map = |s: &Self| -> WireType { … }`",
));
}
Err(syn::Error::new_spanned(
expr,
"struct-level `bw_map` must be a closure",
))
}
fn bin_struct_mapped(args: &BinArgs, s: &ItemStruct) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let name = &s.ident;
let vis = &s.vis;
let from_form = args.wire.is_some() || args.try_wire.is_some();
let closure_form = args.map.is_some() || args.try_map.is_some() || args.bw_map.is_some();
if from_form && closure_form {
return Err(syn::Error::new_spanned(
name,
"`wire`/`try_wire` (conversion-trait mapping) can't be combined with \
`map`/`try_map`/`bw_map` (closure mapping) — pick one form",
));
}
if args.wire.is_some() && args.try_wire.is_some() {
return Err(syn::Error::new_spanned(
name,
"`wire` and `try_wire` are mutually exclusive",
));
}
if args.map.is_some() && args.try_map.is_some() {
return Err(syn::Error::new_spanned(
name,
"`map` and `try_map` are mutually exclusive",
));
}
if !s.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&s.generics,
"#[bin] does not support generic parameters yet",
));
}
for (opt, present) in [
("magic", args.magic.is_some()),
("ctx", !args.ctx.is_empty()),
("validate", args.validate.is_some()),
("tag", args.tag.is_some()),
] {
if present {
return Err(syn::Error::new_spanned(
name,
format!(
"`{opt}` is not supported on a mapped `#[bin]` struct — put it on the wire \
type instead (the wire type owns the framing)"
),
));
}
}
let (has_decode, has_encode) = if from_form {
(!args.write_only, !args.read_only)
} else {
(
args.map.is_some() || args.try_map.is_some(),
args.bw_map.is_some(),
)
};
if !from_form {
if args.read_only && has_encode {
return Err(syn::Error::new_spanned(
name,
"`read_only` conflicts with `bw_map` (drop one)",
));
}
if args.write_only && has_decode {
return Err(syn::Error::new_spanned(
name,
"`write_only` conflicts with `map`/`try_map` (drop one)",
));
}
}
let wire = mapped_wire_type(args)?;
let layout = quote!(<#wire as #bnb::__private::BitEncode>::LAYOUT);
let decode_ts = if has_decode {
let decode_call = if from_form {
if args.wire.is_some() {
quote!(#bnb::__private::decode_mapped_msg(
__bnb_r,
|__w: #wire| <#name as ::core::convert::From<#wire>>::from(__w)
))
} else {
quote!(#bnb::__private::decode_try_mapped_msg(
__bnb_r,
|__w: #wire| <#name as ::core::convert::TryFrom<#wire>>::try_from(__w)
))
}
} else if let Some(map) = &args.map {
quote!(#bnb::__private::decode_mapped_msg(__bnb_r, #map))
} else {
let tm = args.try_map.as_ref().unwrap();
quote!(#bnb::__private::decode_try_mapped_msg(__bnb_r, #tm))
};
quote! {
impl #bnb::__private::BitDecode for #name {
fn bit_decode<__S: #bnb::__private::Source>(
__bnb_r: &mut __S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#decode_call
}
}
impl #name {
#[doc = "Decode one message from a `Source` cursor (mapping the wire type to this logical type)."]
#vis fn decode<__S: #bnb::__private::Source>(
__bnb_r: &mut __S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(__bnb_r)
}
#[doc = "Decode every message from `bytes` into a `Vec` (at the wire type's layout)."]
#vis fn decode_all(
bytes: &[u8],
) -> ::core::result::Result<#bnb::__private::Vec<Self>, #bnb::__private::BitError> {
#bnb::__private::decode_all(bytes, #layout)
}
#[doc = "A lazy iterator decoding successive messages from `bytes`."]
#vis fn decode_iter(
bytes: &[u8],
) -> impl ::core::iter::Iterator<Item = ::core::result::Result<Self, #bnb::__private::BitError>> + '_
{
#bnb::__private::decode_iter(bytes, #layout)
}
#[doc = "Decode one message without consuming the buffer (tail-tolerant)."]
#vis fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed."]
#vis fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
}
}
} else {
quote!()
};
let encode_ts = if has_encode {
let encode_call = if from_form {
quote!(#bnb::__private::encode_mapped_msg(
__bnb_w,
self,
|__v: &#name| -> #wire { ::core::convert::Into::into(__v) }
))
} else {
let bw = args.bw_map.as_ref().unwrap();
quote!(#bnb::__private::encode_mapped_msg(__bnb_w, self, #bw))
};
quote! {
impl #bnb::__private::BitEncode for #name {
const LAYOUT: #bnb::__private::Layout = #layout;
fn bit_encode<__K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut __K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#encode_call
}
}
impl #name {
#[doc = "Encode to a fresh `Vec` (mapping this logical type to its wire type)."]
#vis fn to_bytes(
&self,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
}
}
} else {
quote!()
};
Ok(quote! {
#s
#decode_ts
#encode_ts
})
}
fn bin_struct_codec(args: &BinArgs, s: &ItemStruct) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let name = &s.ident;
let vis = &s.vis;
if !s.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&s.generics,
"#[bin] does not support generic parameters yet",
));
}
for (opt, set) in [
("magic", args.magic.is_some()),
("ctx", !args.ctx.is_empty()),
("validate", args.validate.is_some()),
("tag", args.tag.is_some()),
] {
if set {
return Err(syn::Error::new_spanned(
&s.ident,
format!(
"`{opt}` is not supported on a `#[bin(codec = …)]` newtype — the codec \
functions own the framing; move it into (or wrap) the codec fns, or use \
a full `#[bin]` struct"
),
));
}
}
let inner_ty = match &s.fields {
Fields::Unnamed(u) if u.unnamed.len() == 1 => &u.unnamed.first().expect("len checked").ty,
_ => {
return Err(syn::Error::new_spanned(
&s.ident,
"`codec` applies to a single-field tuple struct (a newtype), e.g. \
`#[bin(codec = bnb::codecs::leb128)] pub struct Varint(pub u64);` — for a \
one-off field use `#[br(parse_with = …)]`/`#[bw(write_with = …)]` instead",
));
}
};
let (has_decode, has_encode) = (!args.write_only, !args.read_only);
if has_decode && args.codec_parse.is_none() {
return Err(syn::Error::new_spanned(
&s.ident,
"this codec has no `parse` function — add `parse = <f>` inside `codec(...)`, \
or mark the struct `write_only`",
));
}
if has_encode && args.codec_write.is_none() {
return Err(syn::Error::new_spanned(
&s.ident,
"this codec has no `write` function — add `write = <f>` inside `codec(...)`, \
or mark the struct `read_only`",
));
}
let layout = layout_token(&BitStreamAttrs {
allow_byte_aligned: true, lsb: args.lsb,
little: args.little,
magic: None,
ctx: Vec::new(),
auto_len: Vec::new(),
});
let decode_ts = if has_decode {
let parse_fn = args.codec_parse.as_ref().expect("checked above");
quote! {
impl #bnb::__private::BitDecode for #name {
fn bit_decode<__S: #bnb::__private::Source>(
__bnb_r: &mut __S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
::core::result::Result::Ok(Self((#parse_fn)(__bnb_r)?))
}
}
impl #name {
#[doc = "Decode one value from a `Source` cursor (via this type's codec functions)."]
#vis fn decode<__S: #bnb::__private::Source>(
__bnb_r: &mut __S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(__bnb_r)
}
#[doc = "Decode every value from `bytes` into a `Vec` (at this type's declared layout)."]
#vis fn decode_all(
bytes: &[u8],
) -> ::core::result::Result<#bnb::__private::Vec<Self>, #bnb::__private::BitError> {
#bnb::__private::decode_all(bytes, #layout)
}
#[doc = "A lazy iterator decoding successive values from `bytes`."]
#vis fn decode_iter(
bytes: &[u8],
) -> impl ::core::iter::Iterator<Item = ::core::result::Result<Self, #bnb::__private::BitError>> + '_
{
#bnb::__private::decode_iter(bytes, #layout)
}
#[doc = "Decode one value without consuming the buffer (tail-tolerant)."]
#vis fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed."]
#vis fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
}
}
} else {
quote!()
};
let encode_ts = if has_encode {
let write_fn = args.codec_write.as_ref().expect("checked above");
quote! {
impl #bnb::__private::BitEncode for #name {
const LAYOUT: #bnb::__private::Layout = #layout;
fn bit_encode<__K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut __K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
(#write_fn)(&self.0, __bnb_w)
}
}
impl #name {
#[doc = "Encode to a fresh `Vec` (via this type's codec functions)."]
#vis fn to_bytes(
&self,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
}
}
} else {
quote!()
};
let from_ts = quote! {
impl ::core::convert::From<#inner_ty> for #name {
fn from(v: #inner_ty) -> Self {
Self(v)
}
}
impl ::core::convert::From<#name> for #inner_ty {
fn from(v: #name) -> Self {
v.0
}
}
};
Ok(quote! {
#s
#decode_ts
#encode_ts
#from_ts
})
}
enum BrwDirective {
Ignore,
Variable,
CountPrefix(Box<syn::Type>),
}
impl Parse for BrwDirective {
fn parse(input: ParseStream) -> syn::Result<Self> {
let kw: Ident = input.parse()?;
match kw.to_string().as_str() {
"ignore" => Ok(BrwDirective::Ignore),
"variable" => Ok(BrwDirective::Variable),
"count_prefix" => {
input.parse::<Token![=]>()?;
Ok(BrwDirective::CountPrefix(Box::new(input.parse()?)))
}
_ => Err(syn::Error::new_spanned(
kw,
"unknown `#[brw(...)]` directive; expected `ignore`, `variable`, or `count_prefix = <Ty>`",
)),
}
}
}
#[derive(Clone, Copy)]
enum CountPrefixSite {
Struct,
Variant,
}
fn extract_count_prefix(f: &mut syn::Field) -> syn::Result<Option<syn::Type>> {
let mut found: Option<syn::Type> = None;
let mut rebuilt: Vec<syn::Attribute> = Vec::with_capacity(f.attrs.len());
for attr in f.attrs.drain(..) {
if !attr.path().is_ident("brw") {
rebuilt.push(attr);
continue;
}
let directives =
attr.parse_args_with(Punctuated::<BrwDirective, Token![,]>::parse_terminated)?;
if !directives
.iter()
.any(|d| matches!(d, BrwDirective::CountPrefix(_)))
{
rebuilt.push(attr);
continue;
}
for d in directives {
match d {
BrwDirective::Ignore => rebuilt.push(syn::parse_quote!(#[brw(ignore)])),
BrwDirective::Variable => rebuilt.push(syn::parse_quote!(#[brw(variable)])),
BrwDirective::CountPrefix(ty) => {
if found.is_some() {
return Err(syn::Error::new_spanned(&attr, "duplicate `count_prefix`"));
}
found = Some(*ty);
}
}
}
}
f.attrs = rebuilt;
Ok(found)
}
fn desugar_count_prefix(
bnb: &TokenStream2,
fields: &mut Punctuated<syn::Field, Token![,]>,
site: CountPrefixSite,
) -> syn::Result<()> {
let existing: Vec<String> = fields
.iter()
.filter_map(|f| f.ident.as_ref().map(ToString::to_string))
.collect();
let mut out: Punctuated<syn::Field, Token![,]> = Punctuated::new();
for mut f in core::mem::take(fields) {
let Some(pty) = extract_count_prefix(&mut f)? else {
out.push(f);
continue;
};
if vec_elem(&f).is_none() {
return Err(syn::Error::new_spanned(
&f,
"`#[brw(count_prefix = …)]` applies only to a `Vec<_>` field",
));
}
let br = parse_field_br(&f)?;
if br.count.is_some()
|| br.temp
|| br.calc.is_some()
|| br.br_calc.is_some()
|| br.ignore
|| br.cond.is_some()
|| br.map.is_some()
|| br.try_map.is_some()
|| br.bw_map.is_some()
|| br.parse_with.is_some()
|| br.write_with.is_some()
{
return Err(syn::Error::new_spanned(
&f,
"`count_prefix` generates the length field and the element count itself; it \
can't be combined with `count`, `temp`, `calc`, `ignore`, `if`, \
`map`/`try_map`, or `parse_with`/`write_with` on this field",
));
}
let id = f.ident.clone().expect("checked: named fields only");
let count_id = format_ident!("__bnb_count_{}", id, span = id.span());
let count_name = count_id.to_string();
if existing.contains(&count_name) {
return Err(syn::Error::new_spanned(
&f,
format!(
"`count_prefix` on `{id}` injects a length field named `{count_id}`; \
rename that field"
),
));
}
let len_expr = match site {
CountPrefixSite::Struct => quote!(self.#id.len()),
CountPrefixSite::Variant => quote!(#id.len()),
};
let calc_expr = quote! {
<#pty as #bnb::__private::CountPrefix>::try_from_count(#len_expr)
.map_err(|__e| #bnb::__private::BitError::from(__e)
.in_field(::core::stringify!(#id)))?
};
out.push(syn::Field::parse_named.parse2(quote! {
#[br(temp)]
#[bw(calc = #calc_expr)]
#count_id: #pty
})?);
f.attrs.push(syn::parse_quote!(
#[br(count = <#pty as #bnb::__private::CountPrefix>::to_count(#count_id))]
));
out.push(f);
}
*fields = out;
Ok(())
}
fn bin_struct(args: &BinArgs, s: &ItemStruct) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
if args.is_mapped() {
return bin_struct_mapped(args, s);
}
if args.has_codec() {
return bin_struct_codec(args, s);
}
if args.tag.is_some() {
return Err(syn::Error::new_spanned(
&s.ident,
"`tag` (variant dispatch) applies to a `#[bin]` enum, not a struct",
));
}
if !s.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&s.generics,
"#[bin] does not support generic parameters yet",
));
}
let mut s_desugared = s.clone();
if let Fields::Named(named) = &mut s_desugared.fields {
desugar_count_prefix(&bnb, &mut named.named, CountPrefixSite::Struct)?;
}
let s = &s_desugared;
let full_fields = match &s.fields {
Fields::Named(n) => n,
_ => {
return Err(syn::Error::new_spanned(
&s.ident,
"#[bin] requires a struct with named fields",
));
}
};
if args.forward_only {
for f in &full_fields.named {
let Ok(br) = parse_field_br(f) else { continue };
let seeking = if br.restore_position {
Some("restore_position")
} else if br.seek.is_some() {
Some("seek = …")
} else {
None
};
if let Some(name) = seeking {
return Err(syn::Error::new_spanned(
f,
format!(
"`#[br({name})]` needs to seek, but the struct is `#[bin(forward_only)]`"
),
));
}
}
}
let attrs = BitStreamAttrs {
allow_byte_aligned: true,
lsb: args.lsb,
little: args.little,
magic: args.magic.clone(),
ctx: args.ctx.clone(),
auto_len: args.auto_len.clone(),
};
let try_str_idents: Vec<syn::Ident> = full_fields
.named
.iter()
.filter(|f| field_is_try_str(f) && !field_is_temp(f))
.filter_map(|f| f.ident.clone())
.collect();
let decode = if args.write_only {
quote!()
} else {
gen_decode(&s.ident, full_fields, &attrs)?
};
let encode = if args.read_only {
quote!()
} else {
gen_encode(&s.ident, full_fields, &attrs)?
};
let mut clean = s.clone();
if let Fields::Named(named) = &mut clean.fields {
named.named = named
.named
.iter()
.filter(|f| !field_is_temp(f))
.cloned()
.map(|mut f| {
f.attrs.retain(|a| !is_codec_field_attr(a));
f
})
.collect();
}
let mode_extras = if !try_str_idents.is_empty() {
let (had_debug, new_attrs) = intercept_debug_derive(&clean.attrs)?;
if had_debug {
clean.attrs = new_attrs;
let name = &s.ident;
let stored_idents: Vec<syn::Ident> = match &clean.fields {
Fields::Named(n) => n.named.iter().filter_map(|f| f.ident.clone()).collect(),
_ => Vec::new(),
};
let debug_calls = debug_field_calls(&stored_idents, &try_str_idents, &bnb);
quote! {
impl ::core::fmt::Debug for #name {
fn fmt(&self, __f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
__f.debug_struct(::core::stringify!(#name))
#debug_calls
.finish()
}
}
}
} else {
quote!()
}
} else {
quote!()
};
let builder = if args.read_only || args.no_builder {
if args.validate.is_some() {
return Err(syn::Error::new_spanned(
&s.ident,
"`validate` needs the builder; it is incompatible with `read_only`/`no_builder`",
));
}
quote!()
} else {
let post_build = args.validate.as_ref().map(|path| {
quote! {
(#path)(&__value)
.map_err(|__e| #bnb::BuilderError::invalid(__e.to_string()))?;
}
});
let mut bfields = Vec::new();
for f in &full_fields.named {
if field_is_temp(f) {
continue; }
let ident = f.ident.clone().expect("named field");
let ty = f.ty.clone();
let mut default = match reserved_spec_value(f)? {
Some(spec) => crate::builder::FieldDefault::DefaultExpr(syn::parse2(spec)?),
None => crate::builder::FieldDefault::Required,
};
if parse_field_br(f)?.auto_len.is_some() {
default = crate::builder::FieldDefault::DefaultExpr(
syn::parse_quote!(#bnb::__private::WireLen::auto()),
);
}
for attr in &f.attrs {
if let Some(d) = crate::builder::parse_builder_attr(attr)? {
default = d;
}
}
bfields.push(crate::builder::BField { ident, ty, default });
}
crate::builder::generate(
&s.ident,
&s.vis,
&bfields,
crate::builder::BuildKind::Plain,
post_build.as_ref(),
)
};
let ctx_struct = if args.ctx.is_empty() {
quote!()
} else {
let ctx_name = ctx_struct_ident(&s.ident);
let vis = &s.vis;
let decls = args.ctx.iter().map(|(n, t)| {
let doc = format!("The `{n}` context parameter.");
quote!(#[doc = #doc] #vis #n: #t)
});
let params = args.ctx.iter().map(|(n, t)| quote!(#n: #t));
let names = args.ctx.iter().map(|(n, _)| n);
quote! {
#[derive(Clone)]
#[doc = "Context for the matching `#[bin(ctx(...))]` type — pass it to `decode_with`."]
#vis struct #ctx_name { #(#decls),* }
impl #ctx_name {
#[doc = "Construct the context positionally, in declaration order."]
#vis fn new(#(#params),*) -> Self {
Self { #(#names),* }
}
}
}
};
let validate_methods = if let Some(path) = &args.validate {
let vis = &s.vis;
let name = &s.ident;
quote! {
impl #name {
#[doc = "Re-run the `#[bin(validate = …)]` soundness check against the current value."]
#[doc = "`build()` runs it once at construction; call this before sending if the value"]
#[doc = "may have been mutated since. It checks **semantic** soundness — by convention"]
#[doc = "not `calc`/`reserved` fields, which are representational (normalized by"]
#[doc = "`to_canonical_bytes`) rather than a matter of validity."]
#[doc = ""]
#[doc = "# Errors"]
#[doc = "The validator's error when the value is unsound."]
#vis fn validate(&self) -> ::core::result::Result<(), impl ::core::fmt::Display> {
(#path)(self)
}
#[doc = "Whether the `#[bin(validate = …)]` check passes for the current value —"]
#[doc = "computed on demand, so it never goes stale after a mutation."]
#[must_use]
#vis fn is_valid(&self) -> bool {
self.validate().is_ok()
}
}
}
} else {
quote!()
};
Ok(quote! {
#ctx_struct
#clean
#mode_extras
#validate_methods
#builder
#decode
#encode
})
}
fn variant_bind_idents(fields: &Fields) -> Vec<Ident> {
fields
.iter()
.enumerate()
.map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("__f{}", i)))
.collect()
}
fn variant_path_fields(
name: &Ident,
vid: &Ident,
fields: &Fields,
idents: &[Ident],
) -> TokenStream2 {
match fields {
Fields::Named(_) => quote!(#name::#vid { #(#idents),* }),
Fields::Unnamed(_) => quote!(#name::#vid( #(#idents),* )),
Fields::Unit => quote!(#name::#vid),
}
}
fn ctx_literal_variant(ctx_ty: &TokenStream2, names: &[Ident], stored: &[Ident]) -> TokenStream2 {
let inits = names.iter().map(|n| {
if stored.contains(n) {
quote!(#n: *#n)
} else {
quote!(#n)
}
});
quote!(#ctx_ty { #(#inits),* })
}
fn variant_field_write(
f: &syn::Field,
br: &FieldBr,
id: &Ident,
stored: &[Ident],
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let ty = &f.ty;
let pre = pad_write_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_write_tokens(br.align_after, br.pad_after.as_ref());
if br.restore_position {
return Ok(quote!(#pre #post));
}
if let Some(calc) = &br.calc {
let core = if br.temp {
quote! {
let #id: #ty = #calc;
#bnb::__private::Sink::write(__bnb_w, #id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
} else {
quote! {{
let __v: #ty = #calc;
#bnb::__private::Sink::write(__bnb_w, __v)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}}
};
return Ok(quote!(#pre #core #post));
}
if br.temp {
return Err(syn::Error::new_spanned(
f,
"a `#[br(temp)]` variant field is not stored, so it needs `#[bw(calc = <expr>)]` to encode",
));
}
if let (Some(names), None) = (&br.ctx, vec_elem(f)) {
let child_ctx = ctx_struct_ty(ty)?;
let lit = ctx_literal_variant(&child_ctx, names, stored);
let core = quote!(<#ty as #bnb::EncodeWith<#child_ctx>>::encode_with(#id, __bnb_w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;);
return Ok(quote!(#pre #core #post));
}
let core = if br.ignore {
quote!()
} else if let Some(bw_map) = &br.bw_map {
quote!(#bnb::__private::write_mapped(__bnb_w, #id, #bw_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if let Some(wf) = &br.write_with {
quote!((#wf)(#id, __bnb_w).map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if br.map.is_some() || br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(map = …)]`/`#[br(try_map = …)]` variant field needs the inverse `#[bw(map = <f>)]`",
));
} else if br.parse_with.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(parse_with = …)]` variant field needs the inverse `#[bw(write_with = <f>)]`",
));
} else if br.cond.is_some() {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>`")
})?;
let write_inner = quote!(<#inner as #bnb::__private::BitEncode>::bit_encode(__v, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;);
quote!(if let ::core::option::Option::Some(__v) = #id { #write_inner })
} else if let Some(elem) = vec_elem(f) {
let write_elem = if let Some(names) = &br.ctx {
let elem_ctx = ctx_struct_ty(elem)?;
let lit = ctx_literal_variant(&elem_ctx, names, stored);
quote!(<#elem as #bnb::EncodeWith<#elem_ctx>>::encode_with(__e, __bnb_w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(<#elem as #bnb::__private::BitEncode>::bit_encode(__e, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
quote!(for __e in #id { #write_elem })
} else if byte_array_len(f).is_some() {
quote!(#bnb::__private::write_byte_array(#id, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(<#ty as #bnb::__private::BitEncode>::bit_encode(#id, __bnb_w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
Ok(quote!(#pre #core #post))
}
enum Magic {
Bytes(Vec<u8>),
Int { value: Box<syn::Expr>, width: usize },
}
fn int_type_for_width(width: usize) -> TokenStream2 {
match width {
1 => quote!(u8),
2 => quote!(u16),
4 => quote!(u32),
8 => quote!(u64),
16 => quote!(u128),
_ => unreachable!("magic int width is validated to 1/2/4/8/16"),
}
}
impl Magic {
fn byte_len(&self) -> usize {
match self {
Magic::Bytes(b) => b.len(),
Magic::Int { width, .. } => *width,
}
}
fn kind(&self) -> u8 {
match self {
Magic::Bytes(_) => 0,
Magic::Int { .. } => 1,
}
}
fn read_type(&self) -> TokenStream2 {
match self {
Magic::Bytes(b) => {
let n = b.len();
quote!([u8; #n])
}
Magic::Int { width, .. } => int_type_for_width(*width),
}
}
fn const_expr(&self) -> TokenStream2 {
match self {
Magic::Bytes(b) => {
let bytes = b.iter();
quote!([#(#bytes),*])
}
Magic::Int { value, .. } => quote!(#value),
}
}
fn read_into(&self, binding: &Ident) -> TokenStream2 {
let bnb = crate::bnb_path();
let ty = self.read_type();
match self {
Magic::Bytes(_) => quote!(
let #binding: #ty = #bnb::__private::read_byte_array(__bnb_r).map_err(|e| e.in_field("magic"))?;
),
Magic::Int { .. } => quote!(
let #binding: #ty = #bnb::__private::Source::read(__bnb_r).map_err(|e| e.in_field("magic"))?;
),
}
}
fn verify(&self, what: &str) -> TokenStream2 {
let bnb = crate::bnb_path();
let binding = format_ident!("__vm");
let read = self.read_into(&binding);
let expected = self.const_expr();
let msg = format!("magic mismatch ({what})");
quote! {
#read
if #binding != #expected {
return ::core::result::Result::Err(
#bnb::__private::BitError::convert(
#bnb::__private::String::from(#msg),
#bnb::__private::Source::bit_pos(__bnb_r),
).in_field("magic"),
);
}
}
}
fn write_value(&self, value: &TokenStream2) -> TokenStream2 {
let bnb = crate::bnb_path();
match self {
Magic::Bytes(_) => quote!(
#bnb::__private::write_byte_array(&#value, __bnb_w).map_err(|e| e.in_field("magic"))?;
),
Magic::Int { .. } => quote!(
#bnb::__private::Sink::write(__bnb_w, #value).map_err(|e| e.in_field("magic"))?;
),
}
}
fn write_const(&self) -> TokenStream2 {
let c = self.const_expr();
self.write_value(&c)
}
}
fn parse_magic(expr: &syn::Expr) -> syn::Result<Magic> {
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = expr {
match lit {
syn::Lit::ByteStr(s) => return Ok(Magic::Bytes(s.value())),
syn::Lit::Byte(b) => return Ok(Magic::Bytes(vec![b.value()])),
syn::Lit::Int(li) => {
let width = match li.suffix() {
"u8" => 1usize,
"u16" => 2,
"u32" => 4,
"u64" => 8,
"u128" => 16,
"" => {
return Err(syn::Error::new_spanned(
expr,
"a `magic` integer needs a width suffix so its wire size is unambiguous, e.g. `0x01u16`",
));
}
other => {
return Err(syn::Error::new_spanned(
expr,
format!(
"`{other}` is not a valid `magic` type; use a byte-aligned unsigned integer (u8/u16/u32/u64/u128) or a byte string"
),
));
}
};
return Ok(Magic::Int {
value: Box::new(expr.clone()),
width,
});
}
_ => {}
}
}
Err(syn::Error::new_spanned(
expr,
"a `magic` must be a byte string (`b\"…\"`) or a byte-aligned unsigned integer literal (`0x01u16`)",
))
}
#[derive(PartialEq, Eq, Debug)]
enum VariantRole {
TagOnly,
TagAndMagic,
MagicOnly,
Fallback,
CatchAll,
}
struct VariantDispatch<'a> {
variant: &'a syn::Variant,
tag: Option<syn::Expr>,
magic: Option<Magic>,
catch_all: bool,
}
impl VariantDispatch<'_> {
fn role(&self) -> VariantRole {
if self.catch_all {
VariantRole::CatchAll
} else {
match (self.tag.is_some(), self.magic.is_some()) {
(true, true) => VariantRole::TagAndMagic,
(true, false) => VariantRole::TagOnly,
(false, true) => VariantRole::MagicOnly,
(false, false) => VariantRole::Fallback,
}
}
}
}
#[derive(PartialEq, Eq, Debug)]
enum MagicWidth {
None,
Uniform(usize),
Mixed,
}
struct EnumDispatch<'a> {
selector: Option<Ident>,
prefix: Option<Magic>,
variants: Vec<VariantDispatch<'a>>,
}
impl<'a> EnumDispatch<'a> {
fn parse(
e: &'a syn::ItemEnum,
selector: Option<Ident>,
prefix: Option<Magic>,
) -> syn::Result<Self> {
let mut variants = Vec::new();
let mut catch_alls = 0u32;
let mut fallbacks = 0u32;
for v in &e.variants {
let mut tag = None;
let mut magic = None;
let mut catch_all = false;
for a in &v.attrs {
if a.path().is_ident("catch_all") {
catch_all = true;
} else if a.path().is_ident("bin") {
a.parse_nested_meta(|m| {
if m.path.is_ident("tag") {
tag = Some(m.value()?.parse()?);
Ok(())
} else if m.path.is_ident("magic") {
let expr: syn::Expr = m.value()?.parse()?;
magic = Some(parse_magic(&expr)?);
Ok(())
} else {
Err(m.error(
"expected `tag = <value>` or `magic = <literal>` on a variant",
))
}
})?;
}
}
let vd = VariantDispatch {
variant: v,
tag,
magic,
catch_all,
};
match vd.role() {
VariantRole::CatchAll => catch_alls += 1,
VariantRole::Fallback => fallbacks += 1,
VariantRole::TagOnly | VariantRole::TagAndMagic if selector.is_none() => {
return Err(syn::Error::new_spanned(
&v.ident,
"a variant with `tag = …` needs the enum to declare the selector via `#[bin(tag = <ctx-param>)]`",
));
}
_ => {}
}
variants.push(vd);
}
if catch_alls > 1 {
return Err(syn::Error::new_spanned(
&e.ident,
"a `#[bin]` enum may have at most one `#[catch_all]` variant",
));
}
if fallbacks > 1 {
return Err(syn::Error::new_spanned(
&e.ident,
"a `#[bin]` enum may have at most one no-tag/no-magic fallback variant",
));
}
Ok(EnumDispatch {
selector,
prefix,
variants,
})
}
fn magic_width(&self) -> MagicWidth {
let mut width: Option<usize> = None;
let mut mixed = false;
for v in &self.variants {
if v.role() == VariantRole::MagicOnly {
let len = v.magic.as_ref().expect("MagicOnly has a magic").byte_len();
match width {
None => width = Some(len),
Some(__bnb_w) if __bnb_w != len => mixed = true,
_ => {}
}
}
}
match (width, mixed) {
(_, true) => MagicWidth::Mixed,
(Some(__bnb_w), false) => MagicWidth::Uniform(__bnb_w),
(None, false) => MagicWidth::None,
}
}
fn uniform_magic_read_type(&self) -> Option<TokenStream2> {
let mut shape: Option<(u8, usize)> = None;
let mut ty = None;
for v in &self.variants {
if v.role() == VariantRole::MagicOnly {
let m = v.magic.as_ref().expect("MagicOnly has a magic");
let s = (m.kind(), m.byte_len());
match shape {
None => {
shape = Some(s);
ty = Some(m.read_type());
}
Some(prev) if prev != s => return None,
_ => {}
}
}
}
ty
}
}
fn variant_field_codec(
name: &Ident,
v: &syn::Variant,
catch_capture: Option<&TokenStream2>,
) -> syn::Result<(Vec<TokenStream2>, TokenStream2, Vec<TokenStream2>)> {
let vid = &v.ident;
let idents = variant_bind_idents(&v.fields);
let stored_idents: Vec<Ident> = v
.fields
.iter()
.zip(&idents)
.filter(|(f, _)| !field_is_temp(f))
.map(|(_, id)| id.clone())
.collect();
let path = variant_path_fields(name, vid, &v.fields, &stored_idents);
let is_catch = catch_capture.is_some();
if is_catch && v.fields.is_empty() {
return Err(syn::Error::new_spanned(
vid,
"a `#[catch_all]` variant needs a first field to hold the captured discriminant",
));
}
let mut reads = Vec::new();
for (i, f) in v.fields.iter().enumerate() {
let id = &idents[i];
let br = parse_field_br(f)?;
if is_catch && i == 0 {
if br.temp {
return Err(syn::Error::new_spanned(
f,
"the `#[catch_all]` first field holds the captured discriminant, so it can't be `#[br(temp)]`",
));
}
let cap = catch_capture.expect("catch capture present");
reads.push(quote!(let #id = #cap;));
} else {
let mut nf = f.clone();
nf.ident = Some(id.clone());
reads.push(field_read_stmt(&nf, &br)?);
}
}
let mut writes = Vec::new();
for (i, f) in v.fields.iter().enumerate() {
if is_catch && i == 0 {
continue; }
let id = &idents[i];
let br = parse_field_br(f)?;
writes.push(variant_field_write(f, &br, id, &stored_idents)?);
}
Ok((reads, path, writes))
}
fn snake_case(ident: &Ident) -> String {
let s = ident.to_string();
let mut out = String::with_capacity(s.len() + 4);
for (i, ch) in s.char_indices() {
if ch.is_ascii_uppercase() {
if i != 0 {
out.push('_');
}
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
fn bin_enum(args: &BinArgs, e: &syn::ItemEnum) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let name = &e.ident;
let vis = &e.vis;
if !e.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&e.generics,
"#[bin] does not support generic parameters yet",
));
}
if args.validate.is_some() {
return Err(syn::Error::new_spanned(
name,
"`validate` needs the builder; a `#[bin]` enum has none",
));
}
let mut e_desugared = e.clone();
for v in &mut e_desugared.variants {
match &mut v.fields {
Fields::Named(named) => {
desugar_count_prefix(&bnb, &mut named.named, CountPrefixSite::Variant)?;
}
Fields::Unnamed(unnamed) => {
for f in &mut unnamed.unnamed {
if extract_count_prefix(f)?.is_some() {
return Err(syn::Error::new_spanned(
&*f,
"`count_prefix` needs a named field (the injected length field \
must be nameable); use a struct-style variant",
));
}
}
}
Fields::Unit => {}
}
}
let e = &e_desugared;
let prefix = args.magic.as_ref().map(parse_magic).transpose()?;
let dispatch = EnumDispatch::parse(e, args.tag.clone(), prefix)?;
let has_selector = dispatch.selector.is_some();
let magic_dispatch = dispatch
.variants
.iter()
.any(|v| v.role() == VariantRole::MagicOnly);
let fallback_variant = dispatch
.variants
.iter()
.find(|v| v.role() == VariantRole::Fallback);
let catch_variant = dispatch
.variants
.iter()
.find(|v| v.role() == VariantRole::CatchAll);
let hybrid = has_selector && magic_dispatch;
if !has_selector && !magic_dispatch {
return Err(syn::Error::new_spanned(
name,
"a `#[bin]` enum dispatches on `tag = <ctx-param>` (variant `tag`s) or per-variant `magic`s; it has neither",
));
}
if fallback_variant.is_some() && catch_variant.is_some() {
return Err(syn::Error::new_spanned(
name,
"use either a typed fallback variant (no tag/magic) or a `#[catch_all]`, not both",
));
}
let mixed = magic_dispatch && dispatch.magic_width() == MagicWidth::Mixed;
let use_peek = magic_dispatch && (mixed || fallback_variant.is_some());
if use_peek
&& dispatch.variants.iter().any(|v| {
v.role() == VariantRole::MagicOnly
&& matches!(v.magic.as_ref().expect("magic-only"), Magic::Int { .. })
})
{
return Err(syn::Error::new_spanned(
name,
"variable-width / fallback magic dispatch needs byte-string magics (so an unmatched discriminant can be re-read)",
));
}
let selector_ty = dispatch
.selector
.as_ref()
.map(|sel| {
args.ctx
.iter()
.find(|(n, _)| n == sel)
.map(|(_, t)| t.clone())
.ok_or_else(|| {
syn::Error::new_spanned(
sel,
"`tag = <ctx-param>` must name a `ctx(...)` parameter",
)
})
})
.transpose()?;
let rep_magic = dispatch
.variants
.iter()
.find_map(|v| (v.role() == VariantRole::MagicOnly).then(|| v.magic.as_ref().unwrap()));
let tail_variant = fallback_variant.or(catch_variant);
let tail_capture: Option<TokenStream2> = if fallback_variant.is_some() {
None
} else if catch_variant.is_some() {
if use_peek {
None
} else if magic_dispatch {
Some(quote!(__m))
} else {
Some(quote!(__other))
}
} else {
None
};
let gen_accessor = !mixed && fallback_variant.is_none() && !hybrid;
let mut encode_arms = Vec::new();
let mut accessor_arms = Vec::new();
for v in &dispatch.variants {
let is_tail = matches!(v.role(), VariantRole::Fallback | VariantRole::CatchAll);
let cap = if is_tail { tail_capture.clone() } else { None };
let (_, pat, writes) = variant_field_codec(name, v.variant, cap.as_ref())?;
let write_disc = if v.role() == VariantRole::CatchAll && cap.is_some() && magic_dispatch {
let first = &variant_bind_idents(&v.variant.fields)[0];
match rep_magic.expect("magic dispatch").kind() {
0 => {
quote!(#bnb::__private::write_byte_array(#first, __bnb_w).map_err(|e| e.in_field("magic"))?;)
}
_ => {
quote!(#bnb::__private::Sink::write(__bnb_w, *#first).map_err(|e| e.in_field("magic"))?;)
}
}
} else if let Some(m) = v.magic.as_ref().filter(|_| !is_tail) {
m.write_const()
} else {
quote!()
};
encode_arms.push(quote!(#pat => { #write_disc #(#writes)* }));
if gen_accessor {
let acc = if is_tail {
let first = &variant_bind_idents(&v.variant.fields)[0];
quote!(*#first)
} else if magic_dispatch {
v.magic.as_ref().expect("magic variant").const_expr()
} else {
let tagval = v.tag.as_ref().expect("tag variant");
quote!(#tagval)
};
accessor_arms.push(quote!(#pat => #acc,));
}
}
let disc_field = if magic_dispatch { "magic" } else { "tag" };
let tail_body = if let Some(tv) = tail_variant {
let (reads, ctor, _) = variant_field_codec(name, tv.variant, tail_capture.as_ref())?;
quote! {{
#(#reads)*
::core::result::Result::Ok(#ctor)
}}
} else {
quote! {{
::core::result::Result::Err(#bnb::__private::BitError::convert(
#bnb::__private::String::from(concat!("unrecognized ", stringify!(#name), " discriminant")),
#bnb::__private::Source::bit_pos(__bnb_r),
).in_field(#disc_field))
}}
};
let magic_block = if !magic_dispatch {
quote!()
} else if use_peek {
let max = dispatch
.variants
.iter()
.filter(|v| v.role() == VariantRole::MagicOnly)
.map(|v| v.magic.as_ref().unwrap().byte_len())
.max()
.expect("at least one magic-only variant");
let mut chain = tail_body.clone();
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let Magic::Bytes(bytes) = v.magic.as_ref().unwrap() else {
unreachable!("peek path validated to byte-string magics");
};
let len = bytes.len();
let bytes = bytes.iter();
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
chain = quote! {
if __peek.starts_with(&[#(#bytes),*]) {
#bnb::__private::Source::seek_to_bit(__bnb_r, #bnb::__private::Source::bit_pos(__bnb_r) + #len * 8)?;
#(#reads)*
::core::result::Result::Ok(#ctor)
} else #chain
};
}
}
quote! {
let __peek = #bnb::__private::peek_bytes(__bnb_r, #max)?;
#chain
}
} else {
let read = rep_magic
.expect("magic dispatch has a representative magic")
.read_into(&format_ident!("__m"));
let mut chain = tail_body.clone();
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let c = v.magic.as_ref().unwrap().const_expr();
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
chain = quote! {
if __m == #c {
#(#reads)*
::core::result::Result::Ok(#ctor)
} else #chain
};
}
}
quote!(#read #chain)
};
let dispatch_decode = if has_selector {
let sel = dispatch
.selector
.as_ref()
.expect("tag dispatch has a selector");
let mut arms = Vec::new();
for v in &dispatch.variants {
if matches!(v.role(), VariantRole::TagOnly | VariantRole::TagAndMagic) {
let tagval = v.tag.as_ref().unwrap();
let verify = v
.magic
.as_ref()
.map(|m| m.verify(&v.variant.ident.to_string()));
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
arms.push(quote! {
#tagval => {
#verify
#(#reads)*
::core::result::Result::Ok(#ctor)
}
});
}
}
let else_body = if magic_dispatch {
quote!({ #magic_block })
} else {
tail_body.clone()
};
let catch_pat = if !magic_dispatch && tail_variant.is_some() {
quote!(__other)
} else {
quote!(_)
};
quote!(match #sel { #(#arms)* #catch_pat => #else_body })
} else {
magic_block
};
let prefix_verify = dispatch.prefix.as_ref().map(|m| m.verify("prefix"));
let prefix_write = dispatch.prefix.as_ref().map(|m| m.write_const());
let decode_body = quote! {
#prefix_verify
#dispatch_decode
};
let encode_body = quote! {
#prefix_write
match self {
#(#encode_arms)*
}
::core::result::Result::Ok(())
};
let seeks = use_peek
|| e.variants
.iter()
.flat_map(|v| &v.fields)
.any(|f| parse_field_br(f).is_ok_and(|br| br.restore_position || br.seek.is_some()));
if args.forward_only && seeks {
let reason = if use_peek {
"variable-width / fallback magic dispatch peeks (it needs to seek)"
} else {
"a seeking variant field (`seek`/`restore_position`)"
};
return Err(syn::Error::new_spanned(
name,
format!("{reason} is incompatible with `#[bin(forward_only)]`"),
));
}
let from_bound = if seeks {
quote!(#bnb::__private::SeekSource)
} else {
quote!(#bnb::__private::Source)
};
let attrs = BitStreamAttrs {
allow_byte_aligned: true,
lsb: args.lsb,
little: args.little,
magic: None,
ctx: args.ctx.clone(),
auto_len: args.auto_len.clone(),
};
let layout = layout_token(&attrs);
let want_decode = !args.write_only;
let want_encode = !args.read_only;
let is_ctx_type = !args.ctx.is_empty();
let ctx_binds: Vec<TokenStream2> = args
.ctx
.iter()
.map(|(n, _)| quote!(let #n = __ctx.#n;))
.collect();
let accessor_fn = if !gen_accessor {
quote!()
} else if magic_dispatch {
let magic_ty = dispatch
.uniform_magic_read_type()
.expect("uniform magic dispatch has a read type");
quote! {
impl #name {
#[doc = "The wire magic this value encodes as."]
#[allow(unused_variables)]
pub fn magic(&self) -> #magic_ty {
match self {
#(#accessor_arms)*
}
}
}
}
} else {
let sel_ty = selector_ty
.as_ref()
.expect("tag dispatch has a selector type");
quote! {
impl #name {
#[doc = "The selector this value dispatches as (the off-wire `tag`)."]
#[allow(unused_variables)]
pub fn tag(&self) -> #sel_ty {
match self {
#(#accessor_arms)*
}
}
}
}
};
let ctx_name = ctx_struct_ident(name);
let mut helper_methods = Vec::new();
for v in &dispatch.variants {
if v.role() == VariantRole::CatchAll {
continue; }
let mname = format_ident!("decode_as_{}", snake_case(&v.variant.ident));
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
let magic_verify = v
.magic
.as_ref()
.map(|m| m.verify(&v.variant.ident.to_string()));
let doc = format!(
"Decode `bytes` as the `{}` variant — its `magic` (if any) then its payload, requiring every whole byte consumed.",
v.variant.ident
);
let body = quote! {
#prefix_verify
#magic_verify
#(#reads)*
::core::result::Result::Ok(#ctor)
};
if is_ctx_type {
helper_methods.push(quote! {
#[doc = #doc]
#[allow(unused_variables)]
pub fn #mname(bytes: &[u8], __ctx: #ctx_name) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |__bnb_r| { #(#ctx_binds)* #body })
}
});
} else {
helper_methods.push(quote! {
#[doc = #doc]
pub fn #mname(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |__bnb_r| { #body })
}
});
}
}
if !magic_dispatch && want_decode && args.ctx.len() == 1 {
let sel = dispatch
.selector
.as_ref()
.expect("tag dispatch has a selector");
let sel_ty = selector_ty
.as_ref()
.expect("tag dispatch has a selector type");
helper_methods.push(quote! {
#[doc = "Decode `bytes` with the given selector (tag), then dispatch — sugar for `decode_with_exact`."]
pub fn decode_tagged(#sel: #sel_ty, bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
Self::decode_with_exact(bytes, #ctx_name { #sel })
}
});
}
let kind_name = format_ident!("{}Kind", name);
let kind_enum = if magic_dispatch && !has_selector && want_decode {
let tail_kind = tail_variant
.map(|tv| {
let k = &tv.variant.ident;
quote!(::core::result::Result::Ok(#kind_name::#k))
})
.unwrap_or_else(|| {
quote!(::core::result::Result::Err(
#bnb::__private::BitError::convert(
#bnb::__private::String::from(concat!(
"unrecognized ",
stringify!(#name),
" discriminant"
)),
#bnb::__private::Source::bit_pos(__bnb_r),
)
.in_field("magic")
))
});
let decision = if use_peek {
let max = dispatch
.variants
.iter()
.filter(|v| v.role() == VariantRole::MagicOnly)
.map(|v| v.magic.as_ref().unwrap().byte_len())
.max()
.expect("at least one magic-only variant");
let mut chain = quote!({ #tail_kind });
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let Magic::Bytes(bytes) = v.magic.as_ref().unwrap() else {
unreachable!("peek path validated to byte-string magics");
};
let bytes = bytes.iter();
let k = &v.variant.ident;
chain = quote!(if __peek.starts_with(&[#(#bytes),*]) { ::core::result::Result::Ok(#kind_name::#k) } else #chain);
}
}
quote! {
let __peek = #bnb::__private::peek_bytes(__bnb_r, #max)?;
#chain
}
} else {
let read = rep_magic
.expect("magic dispatch has a representative magic")
.read_into(&format_ident!("__m"));
let mut chain = quote!({ #tail_kind });
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let c = v.magic.as_ref().unwrap().const_expr();
let k = &v.variant.ident;
chain = quote!(if __m == #c { ::core::result::Result::Ok(#kind_name::#k) } else #chain);
}
}
quote!(#read #chain)
};
helper_methods.push(quote! {
#[doc = "Identify which variant `bytes` is from the wire magic, without parsing the payload."]
pub fn peek_variant(bytes: &[u8]) -> ::core::result::Result<#kind_name, #bnb::__private::BitError> {
#bnb::__private::decode_peek_with(bytes, #layout, |__bnb_r| {
#prefix_verify
#decision
})
}
});
let kvars = dispatch.variants.iter().map(|v| &v.variant.ident);
quote! {
#[doc = "The variant kind of a value, from `peek_variant` (the dispatch decision only)."]
#[derive(::core::clone::Clone, ::core::marker::Copy, ::core::fmt::Debug, ::core::cmp::PartialEq, ::core::cmp::Eq)]
#vis enum #kind_name { #(#kvars),* }
}
} else {
quote!()
};
let helpers = if helper_methods.is_empty() {
quote!()
} else {
quote!(impl #name { #(#helper_methods)* })
};
let ctx_param_names: Vec<&Ident> = args.ctx.iter().map(|(n, _)| n).collect();
let enum_encode_uses_ctx = is_ctx_type && tokens_mention(encode_body.clone(), &ctx_param_names);
let decode = if !want_decode {
quote!()
} else if is_ctx_type {
quote! {
impl #name {
#[doc = "Decode from a bit source, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)]
pub fn decode_with<S: #from_bound>(
__bnb_r: &mut S,
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#(#ctx_binds)*
#decode_body
}
#[doc = "Decode from bytes with context, requiring every whole byte consumed."]
pub fn decode_with_exact(
bytes: &[u8],
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |__bnb_r| Self::decode_with(__bnb_r, __ctx.clone()))
}
}
impl #bnb::DecodeWith<#ctx_name> for #name {
fn decode_with<S: #bnb::__private::Source>(
__bnb_r: &mut S,
args: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<#name>::decode_with(__bnb_r, args)
}
}
}
} else {
quote! {
impl #bnb::BitDecode for #name {
fn bit_decode<S: #bnb::__private::Source>(
__bnb_r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#decode_body
}
}
impl #name {
#[doc = "Decode one message from a bit cursor (a `BitReader`, `BufSource`, `BitBuf`, a streaming reader, …), advancing it; a seekable cursor is required if a variant seeks. The byte/bit order is the cursor's — use `decode_exact`/`decode_all` to bake the message's in."]
pub fn decode<S: #from_bound>(
__bnb_r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(__bnb_r)
}
#[doc = "Decode every message from `bytes` into a `Vec`, bit-aware with the message's own byte/bit order baked in. The buffer must hold whole messages (a partial tail is an error)."]
pub fn decode_all(
bytes: &[u8],
) -> ::core::result::Result<#bnb::__private::Vec<Self>, #bnb::__private::BitError> {
#bnb::__private::decode_all(bytes, #layout)
}
#[doc = "A lazy iterator decoding successive messages from `bytes` (layout baked in) until it is drained, ending after the first error if one occurs."]
pub fn decode_iter(
bytes: &[u8],
) -> impl ::core::iter::Iterator<Item = ::core::result::Result<Self, #bnb::__private::BitError>> + '_ {
#bnb::__private::decode_iter(bytes, #layout)
}
#[doc = "Decode one message from `bytes` without consuming the caller's buffer (tail-tolerant)."]
pub fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed."]
pub fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
}
}
};
let encode = if !want_encode {
quote!()
} else if enum_encode_uses_ctx {
quote! {
impl #name {
#[doc = "Encode to a bit sink, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)]
pub fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
__ctx: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#(#ctx_binds)*
#encode_body
}
#[doc = "Encode to a `Vec<u8>` with context."]
pub fn to_bytes_with(
&self,
__ctx: #ctx_name,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(#layout, |__bnb_w| self.encode_with(__bnb_w, __ctx.clone()))
}
}
impl #bnb::EncodeWith<#ctx_name> for #name {
fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<#name>::encode_with(self, __bnb_w, args)
}
}
}
} else {
let encode_with_trait = is_ctx_type.then(|| {
quote! {
impl #bnb::EncodeWith<#ctx_name> for #name {
#[allow(unused_variables)]
fn encode_with<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, __bnb_w)
}
}
}
});
quote! {
impl #bnb::BitEncode for #name {
const LAYOUT: #bnb::Layout = #layout;
fn bit_encode<K: #bnb::__private::Sink>(
&self,
__bnb_w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#encode_body
}
}
impl #name {
#[doc = "Encode to a `Vec<u8>`. To write into an explicit bit sink (a `BitWriter`)"]
#[doc = "or a `std::io::Write`, bring [`BitEncode`](::bnb::BitEncode) /"]
#[doc = "[`EncodeExt`](::bnb::EncodeExt) into scope and call `.bit_encode(&mut sink)` /"]
#[doc = "`.encode(&mut w)` (the latter is `std`-only)."]
pub fn to_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
}
#encode_with_trait
}
};
let ctx_struct = if is_ctx_type {
let decls = args.ctx.iter().map(|(n, t)| {
let doc = format!("The `{n}` context parameter.");
quote!(#[doc = #doc] #vis #n: #t)
});
let params = args.ctx.iter().map(|(n, t)| quote!(#n: #t));
let names = args.ctx.iter().map(|(n, _)| n);
quote! {
#[derive(Clone)]
#[doc = "Context for the matching `#[bin(ctx(...))]` type — pass it to `decode_with`."]
#vis struct #ctx_name { #(#decls),* }
impl #ctx_name {
#[doc = "Construct the context positionally, in declaration order."]
#vis fn new(#(#params),*) -> Self {
Self { #(#names),* }
}
}
}
} else {
quote!()
};
let strip = |f: &syn::Field| -> Option<syn::Field> {
(!field_is_temp(f)).then(|| {
let mut f = f.clone();
f.attrs.retain(|a| !is_codec_field_attr(a));
f
})
};
let mut clean = e.clone();
for v in &mut clean.variants {
v.attrs
.retain(|a| !(a.path().is_ident("bin") || a.path().is_ident("catch_all")));
match &mut v.fields {
Fields::Named(n) => n.named = n.named.iter().filter_map(&strip).collect(),
Fields::Unnamed(u) => u.unnamed = u.unnamed.iter().filter_map(&strip).collect(),
Fields::Unit => {}
}
}
let try_str_debug = match enum_try_str_debug(name, &e.variants, &bnb) {
Some(impl_) => {
let (had_debug, new_attrs) = intercept_debug_derive(&clean.attrs)?;
had_debug.then(|| {
clean.attrs = new_attrs;
impl_
})
}
None => None,
};
Ok(quote! {
#ctx_struct
#clean
#try_str_debug
#kind_enum
#accessor_fn
#helpers
#decode
#encode
})
}
#[cfg(test)]
mod dispatch_tests {
#![allow(clippy::needless_raw_string_hashes)]
use super::{EnumDispatch, Magic, MagicWidth, VariantRole};
fn enum_of(src: &str) -> syn::ItemEnum {
syn::parse_str(src).expect("valid enum source")
}
fn sel(name: &str) -> syn::Ident {
syn::parse_str(name).expect("valid ident")
}
#[test]
fn integer_magic_widths_inferred_from_suffix() {
let src = r#"enum E { #[bin(magic = 0xCAFEu16)] A(u32), #[bin(magic = 0x01u16)] B }"#;
let e = enum_of(src);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants.len(), 2);
assert_eq!(d.variants[0].magic.as_ref().unwrap().byte_len(), 2);
assert_eq!(d.variants[0].role(), VariantRole::MagicOnly);
assert_eq!(d.magic_width(), MagicWidth::Uniform(2));
}
#[test]
fn byte_string_magic_widths_and_mixed_detection() {
let e = enum_of(r#"enum E { #[bin(magic = b"IHDR")] A, #[bin(magic = b"END")] B }"#);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants[0].magic.as_ref().unwrap().byte_len(), 4);
assert_eq!(d.variants[1].magic.as_ref().unwrap().byte_len(), 3);
assert_eq!(d.magic_width(), MagicWidth::Mixed);
}
#[test]
fn tag_variant_requires_a_selector() {
let e = enum_of(r#"enum E { #[bin(tag = 1)] A(u8) }"#);
assert!(EnumDispatch::parse(&e, None, None).is_err());
let d = EnumDispatch::parse(&e, Some(sel("kind")), None).unwrap();
assert_eq!(d.variants[0].role(), VariantRole::TagOnly);
}
#[test]
fn tag_and_magic_compose_on_one_variant() {
let e = enum_of(r#"enum E { #[bin(tag = 1, magic = b"LI")] A(u32) }"#);
let d = EnumDispatch::parse(&e, Some(sel("kind")), None).unwrap();
assert_eq!(d.variants[0].role(), VariantRole::TagAndMagic);
}
#[test]
fn fallback_and_catch_all_roles() {
let e = enum_of(r#"enum E { #[bin(magic = b"X")] A, Plain(u32), #[catch_all] Other(u8) }"#);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants[1].role(), VariantRole::Fallback);
assert_eq!(d.variants[2].role(), VariantRole::CatchAll);
}
#[test]
fn rejects_invalid_magic_values() {
let bad = [
r#"enum E { #[bin(magic = SOME_CONST)] A }"#, r#"enum E { #[bin(magic = 1)] A }"#, r#"enum E { #[bin(magic = 1usize)] A }"#, r#"enum E { #[bin(magic = u4::new(1))] A }"#, ];
for src in bad {
assert!(
EnumDispatch::parse(&enum_of(src), None, None).is_err(),
"should reject: {src}"
);
}
}
#[test]
fn rejects_two_catch_alls_and_two_fallbacks() {
let two_catch = r#"enum E { #[catch_all] A(u8), #[catch_all] B(u8) }"#;
assert!(EnumDispatch::parse(&enum_of(two_catch), None, None).is_err());
let two_fallback = r#"enum E { A(u8), B(u8) }"#;
assert!(EnumDispatch::parse(&enum_of(two_fallback), None, None).is_err());
}
#[test]
fn prefix_is_recorded() {
let e = enum_of(r#"enum E { #[bin(magic = b"X")] A }"#);
let d = EnumDispatch::parse(&e, None, Some(Magic::Bytes(b"PRE".to_vec()))).unwrap();
assert_eq!(d.prefix.as_ref().unwrap().byte_len(), 3);
}
}