#![doc(html_root_url = "https://docs.rs/bilrost-derive/0.1016.0")]
#![recursion_limit = "4096"]
#![no_std]
#![forbid(unsafe_code)]
use crate::attrs::{
bilrost_attrs, enum_val_attr, named_attr, set_bool, set_option, set_option_with_display,
shorthand_enum_val, string_attr, tag_list_attr, word_attr, TagList,
};
use crate::context::Context;
use crate::field::traits::{
DecodeLifetime::{Borrowed, Owned},
DecodeMode::{Distinguished, Relaxed},
FieldBearer, SinglyTagged, Tagged,
WhereFor::{self, Decode, Encode, Schema as ForSchema},
};
use crate::field::{
initializer_class_definition, parse_message_fields, tag_measurer, Field, FieldTarget, InitMode,
MessageFieldsSorted, OneofVariant,
};
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use eyre::{bail, eyre as err, Result};
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse2, Data, DeriveInput, Expr, Fields, Generics, Ident, Path, Variant, WhereClause};
extern crate alloc;
mod attrs;
mod field;
mod context {
use super::*;
pub struct Context {
pub crate_name: Path,
}
impl Context {
pub fn new(crate_name: Option<Path>) -> Self {
Context {
crate_name: crate_name.unwrap_or_else(|| parse2(quote!(::bilrost)).unwrap()),
}
}
}
}
fn version_assert(ctx: &Context) -> TokenStream {
let crate_ = &ctx.crate_name;
let derive_version = core::env!("CARGO_PKG_VERSION");
let error_msg = alloc::format!(
"derive version {derive_version} does not match library version at {path}",
path = quote!(#crate_),
);
quote! {
let derive_version = #derive_version;
let library_version = #crate_::encoding::VERSION;
let different = if derive_version.len() != library_version.len() {
true
} else {
let mut i = 0;
let mut different = false;
while i < derive_version.len() {
if derive_version.as_bytes()[i] != library_version.as_bytes()[i] {
different = true;
break;
}
i += 1;
}
different
};
if different {
panic!(#error_msg);
}
}
}
fn encoder_alias_header(ctx: &Context) -> TokenStream {
let crate_ = &ctx.crate_name;
quote! {
use #crate_::encoding::{
Fixed as fixed,
General as general,
GeneralPacked as general_packed,
Map as map,
Packed as packed,
PlainBytes as plainbytes,
Unpacked as unpacked,
Varint as varint,
};
}
}
fn append_wheres(
where_clause: Option<&WhereClause>,
wheres: impl IntoIterator<Item = TokenStream>,
) -> Option<TokenStream> {
let where_terms: BTreeMap<_, _> = wheres
.into_iter()
.chain(
where_clause
.into_iter()
.flat_map(|w| w.predicates.iter().map(|term| quote!(#term))),
)
.map(|where_| (where_.to_string(), where_))
.collect();
if where_terms.is_empty() {
None
} else {
let each_where_term = where_terms.values();
Some(quote! { where #(#each_where_term,)* })
}
}
fn append_wheres_with_fields(
where_clause: Option<&WhereClause>,
wheres: impl IntoIterator<Item = TokenStream>,
fields: impl FieldBearer,
field_purpose: WhereFor,
ctx: &Context,
) -> Option<TokenStream> {
append_wheres(
where_clause,
wheres
.into_iter()
.chain(fields.where_terms(field_purpose, ctx)),
)
}
fn combine_generics(generics: &Generics, to_add: TokenStream) -> TokenStream {
let mut new_generics: Generics = parse2(quote!(<#to_add>)).expect("invalid generic terms");
new_generics.params.extend(generics.params.iter().cloned());
let (wrapped, _, _) = new_generics.split_for_impl();
quote!(#wrapped)
}
struct PreprocessedMessageStruct {
ident: Ident,
generics: Generics,
fields: Vec<Field>,
distinguished: bool,
borrow_only: bool,
struct_update_expr: Option<Expr>,
crate_name: Option<Path>,
schema_type_name: String,
}
fn preprocess_message_struct(input: DeriveInput) -> Result<PreprocessedMessageStruct> {
let DeriveInput {
ident,
attrs: input_attrs,
generics,
data: Data::Struct(data_struct),
..
} = input
else {
panic!("non-struct derive input sent to preprocess_message_struct");
};
let mut reserved_tags: Option<TagList> = None;
let mut distinguished = false;
let mut borrow_only = false;
let mut default_per_field = false;
let mut struct_update_expr: Option<Expr> = None;
let mut crate_name: Option<Path> = None;
let mut schema_type_name = None;
let mut unknown_attrs = Vec::new();
for attr in bilrost_attrs(&input_attrs, None)? {
if let Some(tags) = tag_list_attr(&attr, "reserved_tags", None)? {
set_option_with_display(
&mut reserved_tags,
tags,
"duplicate reserved_tags attributes",
TagList::display,
)?;
} else if word_attr(&attr, "distinguished") {
set_bool(&mut distinguished, "duplicated distinguished attributes")?;
} else if word_attr(&attr, "borrowed_only") {
set_bool(&mut borrow_only, "duplicated borrowed_only attributes")?;
} else if word_attr(&attr, "default_per_field") {
set_bool(
&mut default_per_field,
"duplicated default_per_field attributes",
)?;
} else if let Some(expr) = named_attr(&attr, "default")? {
set_option_with_display(
&mut struct_update_expr,
expr,
"duplicated default (expression) attributes",
|t| quote!((#t)).to_string(),
)?;
} else if let Some(path) = named_attr(&attr, "crate")? {
set_option_with_display(
&mut crate_name,
path,
"duplicated crate path attributes",
|t| quote!(#t).to_string(),
)?;
} else if let Some(name) = string_attr(&attr, "name")? {
set_option(
&mut schema_type_name,
name,
"duplicated message name attributes",
)?;
} else {
unknown_attrs.push(attr);
}
}
if default_per_field && struct_update_expr.is_some() {
bail!("default_per_field and default (expression) attributes are mutually exclusive");
}
if !unknown_attrs.is_empty() {
bail!(
"unknown attribute(s) for message: {attrs}",
attrs = quote!(#(#unknown_attrs),*),
);
}
let ignored_field_init_mode = match default_per_field {
true => InitMode::DefaultPerField,
false => InitMode::FromStructUpdate,
};
let fields = parse_message_fields(data_struct.fields, ignored_field_init_mode, reserved_tags)?;
let schema_type_name = schema_type_name.unwrap_or_else(|| ident.to_string());
Ok(PreprocessedMessageStruct {
ident,
generics,
fields,
distinguished,
borrow_only,
struct_update_expr,
crate_name,
schema_type_name,
})
}
fn try_message(input: TokenStream) -> Result<TokenStream> {
let input: DeriveInput = parse2(input)?;
match &input.data {
Data::Enum(..) => return try_message_via_oneof(input),
Data::Struct(..) => {}
_ => {
bail!("Message can only be derived for a struct or an enum");
}
}
let PreprocessedMessageStruct {
ident,
generics,
fields,
distinguished,
borrow_only,
struct_update_expr,
crate_name,
schema_type_name,
} = preprocess_message_struct(input)?;
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let (ignored_fields, unsorted_fields): (Vec<_>, Vec<_>) =
fields.into_iter().partition(Field::is_ignored);
if distinguished && !ignored_fields.is_empty() {
bail!("messages with ignored fields cannot be distinguished");
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let self_where = if ignored_fields
.iter()
.any(Field::ignored_and_uses_struct_update_syntax)
&& struct_update_expr.is_none()
{
Some(quote!(Self: ::core::default::Default))
} else {
None
};
let borrow_generics = combine_generics(&generics, quote!('__a));
let where_fields = vec![unsorted_fields.as_slice(), ignored_fields.as_slice()];
let encoder_where_clause =
append_wheres_with_fields(where_clause, self_where.clone(), &where_fields, Encode, ctx);
let [owned_decoder_where_clause, borrowed_decoder_where_clause] =
[Owned, Borrowed].map(|lifetime| {
append_wheres_with_fields(
where_clause,
self_where.clone(),
&where_fields,
Decode(lifetime, Relaxed),
ctx,
)
});
let self_instance = FieldTarget::MessageInstance(quote!(self));
let fields = MessageFieldsSorted::new(&unsorted_fields);
let encoded_len = fields.encoded_len(&self_instance, ctx);
let encode = fields.encode(&self_instance, ctx);
let prepend = fields.prepend(&self_instance, ctx);
let [decode_owned, decode_borrowed] = [Owned, Borrowed].map(|lifetime| {
let self_instance = self_instance.clone();
let schema_type_name = schema_type_name.clone();
unsorted_fields.iter().filter_map(move |field| {
let tags = field.tags();
if tags.is_empty() {
return None;
}
let decode = field.decode(&self_instance, lifetime, Relaxed, ctx);
let tags = tags.into_iter().map(|tag| quote!(#tag));
let tags = Itertools::intersperse(tags, quote!(|));
let schema_field_name = field.schema_field_name();
Some(quote! {
#(#tags)* => {
if let ::core::result::Result::Err(mut error) = #decode {
error.push(#schema_type_name, #schema_field_name);
return ::core::result::Result::Err(error);
}
}
})
})
});
let methods = unsorted_fields
.iter()
.flat_map(|field| field.methods(ctx))
.collect::<Vec<_>>();
let methods = if methods.is_empty() {
None
} else {
Some(quote! {
#[allow(dead_code)]
impl #impl_generics __Self #ty_generics #encoder_where_clause {
#(#methods)*
}
})
};
let static_guards = unsorted_fields
.iter()
.filter_map(|field| field.tag_list_guard(ctx))
.chain([version_assert(ctx)]);
let empties: Vec<_> = unsorted_fields
.iter()
.chain(ignored_fields.iter())
.flat_map(|field| {
let empty = field.empty(None, ctx)?;
let ident = field.ident();
Some(quote!(#ident: #empty))
})
.collect();
let is_empties: Vec<_> = unsorted_fields
.iter()
.map(|field| field.is_empty(&self_instance, ctx))
.collect();
let clears: Vec<_> = unsorted_fields
.iter()
.map(|field| field.clear(&self_instance, ctx))
.collect();
let maybe_struct_update = if ignored_fields
.iter()
.any(Field::ignored_and_uses_struct_update_syntax)
{
Some(if struct_update_expr.is_some() {
quote!(..__BilrostInitializer::<Self>::struct_update())
} else {
quote!(..::core::default::Default::default())
})
} else {
None
};
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::RawMessageDecoder
for __Self #ty_generics #owned_decoder_where_clause {
#[allow(unused_variables)]
#[inline]
fn raw_decode_field<__B>(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
duplicated: bool,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError>
where
__B: #crate_::bytes::Buf + ?Sized,
{
let _ = <Self as #crate_::encoding::RawMessage>::__ASSERTIONS;
match tag {
#(#decode_owned)*
_ => #crate_::encoding::skip_field(wire_type, buf)?,
}
::core::result::Result::Ok(())
}
}
}
});
let impls = quote! {
impl #impl_generics #crate_::encoding::RawMessage
for __Self #ty_generics #encoder_where_clause {
const __ASSERTIONS: () = { #(#static_guards)* };
fn empty() -> Self {
Self {
#(#empties,)*
#maybe_struct_update
}
}
fn is_empty(&self) -> bool {
true #(&& #is_empties)*
}
fn clear(&mut self) {
#(#clears)*
}
#[allow(unused_variables)]
fn raw_encode<__B>(&self, buf: &mut __B)
where
__B: #crate_::bytes::BufMut + ?Sized,
{
let _ = <Self as #crate_::encoding::RawMessage>::__ASSERTIONS;
#encode
}
#[allow(unused_variables)]
fn raw_prepend<__B>(&self, buf: &mut __B)
where
__B: #crate_::buf::ReverseBuf + ?Sized,
{
let _ = <Self as #crate_::encoding::RawMessage>::__ASSERTIONS;
#prepend
}
#[inline]
fn raw_encoded_len(&self) -> usize {
let _ = <Self as #crate_::encoding::RawMessage>::__ASSERTIONS;
#encoded_len
}
}
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::RawMessageBorrowDecoder<'__a>
for __Self #ty_generics #borrowed_decoder_where_clause {
#[allow(unused_variables)]
#[inline]
fn raw_borrow_decode_field(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
duplicated: bool,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError> {
let _ = <Self as #crate_::encoding::RawMessage>::__ASSERTIONS;
match tag {
#(#decode_borrowed)*
_ => #crate_::encoding::skip_field(wire_type, buf)?,
}
::core::result::Result::Ok(())
}
}
impl #impl_generics #crate_::encoding::ForOverwrite<(), __Self #ty_generics> for ()
#encoder_where_clause {
fn for_overwrite() -> __Self #ty_generics {
<__Self #ty_generics as #crate_::encoding::RawMessage>::empty()
}
}
impl #impl_generics #crate_::encoding::EmptyState<(), __Self #ty_generics> for ()
#encoder_where_clause {
fn is_empty(val: &__Self #ty_generics) -> bool {
<__Self #ty_generics as #crate_::encoding::RawMessage>::is_empty(val)
}
fn clear(val: &mut __Self #ty_generics) {
<__Self #ty_generics as #crate_::encoding::RawMessage>::clear(val);
}
}
};
let distinguished_impls = distinguished.then(|| {
let distinguished_self_where = [quote!(Self: ::core::cmp::Eq)]
.into_iter()
.chain(self_where);
let [owned_decoder_where_clause, borrowed_decoder_where_clause] =
[Owned, Borrowed].map(|lifetime| {
append_wheres_with_fields(
where_clause,
distinguished_self_where.clone(),
&where_fields,
Decode(lifetime, Distinguished),
ctx,
)
});
let [decode_owned, decode_borrowed] = [Owned, Borrowed].map(|lifetime| {
let schema_type_name = &schema_type_name;
let self_instance = &self_instance;
unsorted_fields.iter().filter_map(move |field| {
let tags = field.tags();
if tags.is_empty() {
return None;
}
let decode = field.decode(self_instance, lifetime, Distinguished, ctx);
let tags = field.tags().into_iter().map(|tag| quote!(#tag));
let tags = Itertools::intersperse(tags, quote!(|));
let schema_field_name = field.schema_field_name();
Some(quote! {
#(#tags)* => {
match #decode {
::core::result::Result::Ok(new_canon) => {
canon.update(new_canon);
}
::core::result::Result::Err(mut error) => {
error.push(#schema_type_name, #schema_field_name);
return ::core::result::Result::Err(error);
}
}
}
})
})
});
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::RawDistinguishedMessageDecoder
for __Self #ty_generics #owned_decoder_where_clause {
#[allow(unused_variables)]
#[inline]
fn raw_decode_field_distinguished<__B>(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
duplicated: bool,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError>
where
__B: #crate_::bytes::Buf + ?Sized,
{
let mut canon = #crate_::Canonicity::Canonical;
match tag {
#(#decode_owned)*
_ => {
canon.update(ctx.check(#crate_::Canonicity::HasExtensions)?);
#crate_::encoding::skip_field(wire_type, buf)?;
}
}
::core::result::Result::Ok(canon)
}
}
}
});
quote! {
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::RawDistinguishedMessageBorrowDecoder<'__a>
for __Self #ty_generics #borrowed_decoder_where_clause {
#[allow(unused_variables)]
#[inline]
fn raw_borrow_decode_field_distinguished(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
duplicated: bool,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError> {
let canon = &mut #crate_::Canonicity::Canonical;
match tag {
#(#decode_borrowed)*
_ => {
canon.update(ctx.check(#crate_::Canonicity::HasExtensions)?);
#crate_::encoding::skip_field(wire_type, buf)?;
}
}
::core::result::Result::Ok(*canon)
}
}
}
});
let aliases = encoder_alias_header(ctx);
let initializer_class = initializer_class_definition(
ignored_fields
.iter()
.flat_map(|field| field.initializer_method(None)),
struct_update_expr,
&generics,
);
let expanded = quote! {
const _: () = {
use #ident as __Self;
#initializer_class
const _: () = {
#aliases
#impls
#distinguished_impls
#methods
};
};
};
Ok(expanded)
}
fn try_message_via_oneof(input: DeriveInput) -> Result<TokenStream> {
let PreprocessedOneof {
ident,
generics,
variants,
distinguished,
borrow_only,
empty_variant,
crate_name,
schema_type_name: _,
} = preprocess_oneof(input)?;
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let version_guard = version_assert(ctx);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let tag_measurer_ty = tag_measurer(&variants)(ctx);
if empty_variant.is_none() {
bail!("Message can only be derived for Oneof enums that have an empty variant.");
}
let borrow_generics = combine_generics(&generics, quote!('__a));
let encoder_where_clause = append_wheres(
where_clause,
[quote!(#ident #ty_generics: #crate_::encoding::Oneof)],
);
let owned_decoder_where_clause = append_wheres(
where_clause,
[quote!(#ident #ty_generics: #crate_::encoding::OneofDecoder)],
);
let borrowed_decoder_where_clause = append_wheres(
where_clause,
[quote!(#ident #ty_generics: #crate_::encoding::OneofBorrowDecoder<'__a>)],
);
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::RawMessageDecoder
for #ident #ty_generics #owned_decoder_where_clause {
#[inline(always)]
fn raw_decode_field<__B>(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
_duplicated: bool,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError>
where
__B: #crate_::bytes::Buf + ?Sized,
{
if <Self as #crate_::encoding::Oneof>::FIELD_TAGS.contains(&tag) {
<Self as #crate_::encoding::OneofDecoder>::oneof_decode_field(
self,
tag,
wire_type,
buf,
ctx,
)
} else {
#crate_::encoding::skip_field(wire_type, buf)
}
}
}
}
});
let impls = quote! {
impl #impl_generics #crate_::encoding::RawMessage
for #ident #ty_generics #encoder_where_clause {
const __ASSERTIONS: () = { #version_guard };
#[inline(always)]
fn empty() -> Self {
<Self as #crate_::encoding::Oneof>::empty()
}
#[inline(always)]
fn is_empty(&self) -> bool {
<Self as #crate_::encoding::Oneof>::is_empty(self)
}
#[inline(always)]
fn clear(&mut self) {
<Self as #crate_::encoding::Oneof>::clear(self)
}
#[inline(always)]
fn raw_encode<__B>(&self, buf: &mut __B)
where
__B: #crate_::bytes::BufMut + ?Sized,
{
<Self as #crate_::encoding::Oneof>::oneof_encode(
self,
buf,
&mut #crate_::encoding::TagWriter::new(),
);
}
#[inline(always)]
fn raw_prepend<__B>(&self, buf: &mut __B)
where
__B: #crate_::buf::ReverseBuf + ?Sized,
{
let tw = &mut #crate_::encoding::TagRevWriter::new();
<Self as #crate_::encoding::Oneof>::oneof_prepend(self, buf, tw);
tw.finalize(buf);
}
#[inline(always)]
fn raw_encoded_len(&self) -> usize {
<Self as #crate_::encoding::Oneof>::oneof_encoded_len(
self,
&mut #tag_measurer_ty::new(),
)
}
}
impl #impl_generics #crate_::encoding::ForOverwrite<(), #ident #ty_generics> for ()
#encoder_where_clause {
#[inline(always)]
fn for_overwrite() -> #ident #ty_generics {
<#ident #ty_generics as #crate_::encoding::Oneof>::empty()
}
}
impl #impl_generics #crate_::encoding::EmptyState<(), #ident #ty_generics> for ()
#encoder_where_clause {
#[inline(always)]
fn is_empty(val: &#ident #ty_generics) -> bool {
<#ident #ty_generics as #crate_::encoding::Oneof>::is_empty(val)
}
#[inline(always)]
fn clear(val: &mut #ident #ty_generics) {
<#ident #ty_generics as #crate_::encoding::Oneof>::clear(val);
}
}
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::RawMessageBorrowDecoder<'__a>
for #ident #ty_generics #borrowed_decoder_where_clause {
#[inline(always)]
fn raw_borrow_decode_field(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
_duplicated: bool,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError> {
if <Self as #crate_::encoding::Oneof>::FIELD_TAGS.contains(&tag) {
<Self as #crate_::encoding::OneofBorrowDecoder>::oneof_borrow_decode_field(
self,
tag,
wire_type,
buf,
ctx,
)
} else {
#crate_::encoding::skip_field(wire_type, buf)
}
}
}
};
let distinguished_impls = distinguished.then(|| {
let owned_decoder_where_clause = append_wheres(
where_clause,
[quote!(
Self: #crate_::encoding::DistinguishedOneofDecoder + ::core::cmp::Eq
)],
);
let borrowed_decoder_where_clause = append_wheres(
where_clause,
[quote!(
Self: #crate_::encoding::DistinguishedOneofBorrowDecoder<'__a> + ::core::cmp::Eq
)],
);
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::RawDistinguishedMessageDecoder
for #ident #ty_generics #owned_decoder_where_clause {
#[inline(always)]
fn raw_decode_field_distinguished<__B>(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
_duplicated: bool,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError>
where
__B: #crate_::bytes::Buf + ?Sized,
{
if <Self as #crate_::encoding::Oneof>::FIELD_TAGS.contains(&tag) {
<Self as #crate_::encoding::DistinguishedOneofDecoder>::
oneof_decode_field_distinguished
(
self,
tag,
wire_type,
buf,
ctx,
)
} else {
_ = ctx.check(#crate_::Canonicity::HasExtensions)?;
#crate_::encoding::skip_field(wire_type, buf)?;
::core::result::Result::Ok(#crate_::Canonicity::HasExtensions)
}
}
}
}
});
quote! {
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::RawDistinguishedMessageBorrowDecoder<'__a>
for #ident #ty_generics #borrowed_decoder_where_clause {
#[inline(always)]
fn raw_borrow_decode_field_distinguished(
&mut self,
tag: u32,
wire_type: #crate_::encoding::WireType,
_duplicated: bool,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError> {
if <Self as #crate_::encoding::Oneof>::FIELD_TAGS.contains(&tag) {
<Self as #crate_::encoding::DistinguishedOneofBorrowDecoder>::
oneof_borrow_decode_field_distinguished
(
self,
tag,
wire_type,
buf,
ctx,
)
} else {
_ = ctx.check(#crate_::Canonicity::HasExtensions)?;
#crate_::encoding::skip_field(wire_type, buf)?;
::core::result::Result::Ok(#crate_::Canonicity::HasExtensions)
}
}
}
}
});
Ok(quote!(
#impls
#distinguished_impls
))
}
#[proc_macro_derive(Message, attributes(bilrost))]
pub fn message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
try_message(input.into()).unwrap().into()
}
fn try_enumeration(input: TokenStream) -> Result<TokenStream> {
let input: DeriveInput = parse2(input)?;
let ident = input.ident;
let mut crate_name: Option<Path> = None;
let mut schema_type_name = None;
let mut unknown_attrs = Vec::new();
for attr in bilrost_attrs(&input.attrs, None)? {
if let Some(path) = named_attr(&attr, "crate")? {
set_option_with_display(
&mut crate_name,
path,
"duplicated crate path attributes",
|t| quote!(#t).to_string(),
)?;
} else if let Some(name) = string_attr(&attr, "name")? {
set_option(
&mut schema_type_name,
name,
"duplicated enumeration name attributes",
)?;
} else {
unknown_attrs.push(attr);
}
}
if !unknown_attrs.is_empty() {
bail!(
"unknown attribute(s) for enumeration: {attrs}",
attrs = quote!(#(#unknown_attrs),*),
);
}
let schema_type_name = schema_type_name.unwrap_or_else(|| ident.to_string());
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let unborrowed_generics = combine_generics(generics, quote!(const __G: u8));
let borrow_generics = combine_generics(generics, quote!('__a, const __G: u8));
let punctuated_variants = match input.data {
Data::Enum(enum_) => enum_.variants,
Data::Struct(_) => {
bail!("Enumeration can not be derived for a struct");
}
Data::Union(..) => {
bail!("Enumeration can not be derived for a union");
}
};
struct EnumVariant {
variant_ident: Ident,
schema_variant_name: String,
discriminant_expr: Expr,
}
let mut variants = vec![];
for Variant {
attrs,
ident: variant_ident,
fields,
discriminant,
..
} in punctuated_variants
{
if !match fields {
Fields::Unit => true,
Fields::Named(named) => named.named.is_empty(),
Fields::Unnamed(unnamed) => unnamed.unnamed.is_empty(),
} {
bail!("Enumeration variants may not have fields");
}
let mut variant_val: Option<Expr> = None;
let mut schema_variant_name = None;
let mut unknown_attrs = vec![];
for attr in bilrost_attrs(&attrs, Some(shorthand_enum_val))? {
if let Some(expr) = enum_val_attr(&attr)? {
set_option_with_display(
&mut variant_val,
expr,
"duplicate value attributes on enumeration variant",
|t| quote!((#t)).to_string(),
)?;
} else if let Some(name) = string_attr(&attr, "name")? {
set_option(
&mut schema_variant_name,
name,
"duplicate variant name attributes",
)?;
} else {
unknown_attrs.push(attr);
}
}
if !unknown_attrs.is_empty() {
bail!(
"unknown attribute(s) for Enumeration variant: {}",
quote!(#(#unknown_attrs),*)
);
}
let schema_variant_name = schema_variant_name.unwrap_or_else(|| variant_ident.to_string());
let discriminant_expr = variant_val
.or(discriminant.map(|(_, expr)| expr))
.ok_or_else(|| {
err!(
"Enumeration variants must have a discriminant or a #[bilrost(val = ..)] \
attribute (shorthand #[bilrost(..)]) with a constant value"
)
})?;
variants.push(EnumVariant {
variant_ident,
schema_variant_name,
discriminant_expr,
});
}
let zero_variant_ident = variants
.iter()
.find(|variant| is_zero_discriminant(&variant.discriminant_expr))
.map(|variant| &variant.variant_ident);
let Some(EnumVariant {
variant_ident: first_variant,
..
}) = variants.first()
else {
bail!("Enumerations must have at least one variant");
};
let variant_idents: Vec<_> = variants
.iter()
.map(|variant| &variant.variant_ident)
.collect();
let schema_variant_names: Vec<_> = variants
.iter()
.map(|variant| &variant.schema_variant_name)
.collect();
let discriminant_exprs: Vec<_> = variants
.iter()
.map(|variant| &variant.discriminant_expr)
.collect();
let creation_impl = if let Some(zero) = &zero_variant_ident {
quote! {
impl #impl_generics #crate_::encoding::ForOverwrite<(), #ident #ty_generics> for ()
#where_clause {
#[inline]
fn for_overwrite() -> #ident #ty_generics {
#ident::#zero { }
}
}
impl #impl_generics #crate_::encoding::EmptyState<(), #ident #ty_generics> for ()
#where_clause {
#[inline]
fn is_empty(val: &#ident #ty_generics) -> bool {
matches!(val, #ident::#zero { })
}
#[inline]
fn clear(val: &mut #ident #ty_generics) {
*val = #ident::#zero { };
}
}
}
} else {
quote! {
impl #impl_generics #crate_::encoding::ForOverwrite<(), #ident #ty_generics> for ()
#where_clause {
fn for_overwrite() -> #ident #ty_generics {
#ident::#first_variant { }
}
}
}
};
let expanded = quote! {
impl #impl_generics #crate_::Enumeration for #ident #ty_generics #where_clause {
#[inline]
fn to_number(&self) -> u32 {
match self {
#(#ident::#variant_idents { } => #discriminant_exprs,)*
}
}
#[inline]
fn try_from_number(value: u32) -> ::core::result::Result<#ident, u32> {
#[forbid(unreachable_patterns)]
::core::result::Result::Ok(match value {
#(#discriminant_exprs => #ident::#variant_idents { },)*
_ => ::core::result::Result::Err(value)?,
})
}
#[inline]
fn is_valid(__n: u32) -> bool {
#[forbid(unreachable_patterns)]
match __n {
#(#discriminant_exprs => true,)*
_ => false,
}
}
}
#creation_impl
impl #unborrowed_generics
#crate_::encoding::Wiretyped<
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
const WIRE_TYPE: #crate_::encoding::WireType = #crate_::encoding::WireType::Varint;
}
impl #unborrowed_generics
#crate_::encoding::schema::ValueRepr<
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
fn repr(
schema: &#crate_::encoding::schema::Schema,
) -> #crate_::alloc::boxed::Box<dyn ::core::fmt::Display> {
#crate_::encoding::schema::PopulateSchema::register_enumeration::<
#ident #ty_generics
>(
schema,
#schema_type_name,
|fields| {
#(fields.add_value(#schema_variant_names, #discriminant_exprs);)*
},
);
#crate_::encoding::schema::PopulateSchema::make_lazy_repr(
schema,
|schema| {
#crate_::alloc::format!(
"varint, unsigned; one of enumeration {enum_type}",
enum_type =
#crate_::encoding::schema::PopulateSchema::type_reference::<
#ident #ty_generics
>(schema),
)
},
)
}
}
impl #unborrowed_generics
#crate_::encoding::ValueEncoder<
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
#[inline]
fn encode_value<__B: #crate_::bytes::BufMut + ?Sized>(
value: &#ident #ty_generics,
buf: &mut __B,
) {
#crate_::encoding::encode_varint(
#crate_::Enumeration::to_number(value) as u64,
buf,
);
}
#[inline]
fn prepend_value<__B: #crate_::buf::ReverseBuf + ?Sized>(
value: &#ident #ty_generics,
buf: &mut __B,
) {
#crate_::encoding::prepend_varint(
#crate_::Enumeration::to_number(value) as u64,
buf,
);
}
#[inline]
fn value_encoded_len(value: &#ident #ty_generics) -> usize {
#crate_::encoding::encoded_len_varint(
#crate_::encoding::Enumeration::to_number(value) as u64
)
}
}
impl #unborrowed_generics
#crate_::encoding::ValueDecoder<
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
#[inline]
fn decode_value<__B: #crate_::bytes::Buf + ?Sized>(
value: &mut #ident #ty_generics,
mut buf: #crate_::encoding::Capped<__B>,
_ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError> {
let decoded = buf.decode_varint()?;
let ::core::result::Result::Ok(in_range) = u32::try_from(decoded) else {
return ::core::result::Result::Err(
#crate_::DecodeErrorKind::OutOfDomainValue.into()
);
};
let ::core::result::Result::Ok(typed) =
<#ident #ty_generics as #crate_::Enumeration>::try_from_number(in_range) else {
return ::core::result::Result::Err(
#crate_::DecodeErrorKind::OutOfDomainValue.into()
);
};
*value = typed;
::core::result::Result::Ok(())
}
}
impl #unborrowed_generics
#crate_::encoding::DistinguishedValueDecoder<
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
const CHECKS_EMPTY: bool = false;
#[inline]
fn decode_value_distinguished<const ALLOW_EMPTY: bool>(
value: &mut #ident #ty_generics,
buf: #crate_::encoding::Capped<impl #crate_::bytes::Buf + ?Sized>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError> {
<() as #crate_::encoding::ValueDecoder<
#crate_::encoding::GeneralGeneric<__G>, #ident #ty_generics
>>::decode_value(
value,
buf,
ctx.into_inner(),
)?;
::core::result::Result::Ok(#crate_::Canonicity::Canonical)
}
}
impl #borrow_generics
#crate_::encoding::ValueBorrowDecoder<
'__a,
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
#[inline(always)]
fn borrow_decode_value(
value: &mut #ident #ty_generics,
mut buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<(), #crate_::DecodeError> {
<() as #crate_::encoding::ValueDecoder<
#crate_::encoding::GeneralGeneric<__G>, #ident #ty_generics
>>::decode_value(
value,
buf,
ctx,
)
}
}
impl #borrow_generics
#crate_::encoding::DistinguishedValueBorrowDecoder<
'__a,
#crate_::encoding::GeneralGeneric<__G>,
#ident #ty_generics
> for () #where_clause {
const CHECKS_EMPTY: bool = false;
#[inline(always)]
fn borrow_decode_value_distinguished<const ALLOW_EMPTY: bool>(
value: &mut #ident #ty_generics,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#crate_::Canonicity, #crate_::DecodeError> {
<() as #crate_::encoding::ValueDecoder<
#crate_::encoding::GeneralGeneric<__G>, #ident #ty_generics
>>::decode_value(
value,
buf,
ctx.into_inner(),
)?;
::core::result::Result::Ok(#crate_::Canonicity::Canonical)
}
}
};
Ok(expanded)
}
#[proc_macro_derive(Enumeration, attributes(bilrost))]
pub fn enumeration(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
try_enumeration(input.into()).unwrap().into()
}
fn is_zero_discriminant(expr: &Expr) -> bool {
expr.to_token_stream().to_string() == "0"
}
struct PreprocessedOneof {
ident: Ident,
generics: Generics,
variants: Vec<OneofVariant>,
distinguished: bool,
borrow_only: bool,
empty_variant: Option<Ident>,
crate_name: Option<Path>,
schema_type_name: String,
}
fn preprocess_oneof(input: DeriveInput) -> Result<PreprocessedOneof> {
let input_variants = match &input.data {
Data::Enum(enum_) => enum_.variants.clone(),
Data::Struct(..) => {
bail!("Oneof can not be derived for a struct");
}
Data::Union(..) => {
bail!("Oneof can not be derived for a union");
}
};
let ident = input.ident;
let generics = input.generics;
let mut reserved_tags = None;
let mut unknown_attrs = Vec::new();
let mut distinguished = false;
let mut borrow_only = false;
let mut crate_name: Option<Path> = None;
let mut schema_type_name = None;
for attr in bilrost_attrs(&input.attrs, None)? {
if let Some(tags) = tag_list_attr(&attr, "reserved_tags", None)? {
set_option_with_display(
&mut reserved_tags,
tags,
"duplicate reserved_tags attributes",
TagList::display,
)?;
} else if word_attr(&attr, "distinguished") {
set_bool(&mut distinguished, "duplicated distinguished attributes")?;
} else if word_attr(&attr, "borrowed_only") {
set_bool(&mut borrow_only, "duplicated borrowed_only attributes")?;
} else if let Some(path) = named_attr(&attr, "crate")? {
set_option_with_display(
&mut crate_name,
path,
"duplicated crate path attributes",
|t| quote!(#t).to_string(),
)?;
} else if let Some(name) = string_attr(&attr, "name")? {
set_option(
&mut schema_type_name,
name,
"duplicated message name attributes",
)?;
} else {
unknown_attrs.push(attr);
}
}
if !unknown_attrs.is_empty() {
bail!(
"unknown attribute(s) for oneof-message: {}",
quote!(#(#unknown_attrs),*)
);
}
let mut empty_variant: Option<Ident> = None;
let mut variants = vec![];
for variant in input_variants {
let variant_ident = variant.ident.clone();
match OneofVariant::new(variant)? {
Some(variant) => {
variants.push(variant);
}
None => {
set_option_with_display(
&mut empty_variant,
variant_ident,
"Oneofs may have at most one empty enum variant. To use multiple \
variants without fields, the non-empty variants can be marked as values with \
the 'message' attribute and the empty variant can be either left un-marked or \
explicitly marked with the 'empty' attribute.\n\nThe conflicting variants were",
|t| t.to_string(),
)?;
}
}
}
if distinguished && variants.iter().any(OneofVariant::has_ignored_fields) {
bail!("Oneofs with ignored fields cannot be distinguished");
}
let all_tags: BTreeMap<u32, &Ident> = variants
.iter()
.map(|variant| (variant.tag(), variant.ident()))
.collect();
for reserved_range in reserved_tags.unwrap_or_default().iter_tag_ranges() {
if let Some((forbidden_tag, variant_ident)) = all_tags.range(reserved_range).next() {
bail!("oneof {ident} variant {variant_ident} has reserved tag {forbidden_tag}");
}
}
let schema_type_name = schema_type_name.unwrap_or_else(|| ident.to_string());
Ok(PreprocessedOneof {
ident,
generics,
variants,
distinguished,
borrow_only,
empty_variant,
crate_name,
schema_type_name,
})
}
fn try_oneof(input: TokenStream) -> Result<TokenStream> {
let input: DeriveInput = parse2(input)?;
let PreprocessedOneof {
ident,
generics,
variants,
distinguished,
borrow_only,
empty_variant,
crate_name,
schema_type_name,
} = preprocess_oneof(input)?;
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let borrow_generics = combine_generics(&generics, quote!('__a));
let encoder_where_clause =
append_wheres_with_fields(where_clause, None, &variants, Encode, ctx);
let owned_decoder_where_clause =
append_wheres_with_fields(where_clause, None, &variants, Decode(Owned, Relaxed), ctx);
let borrowed_decoder_where_clause = append_wheres_with_fields(
where_clause,
None,
&variants,
Decode(Borrowed, Relaxed),
ctx,
);
let sorted_tags: Vec<u32> = variants
.iter()
.map(|variant| variant.tag())
.sorted_unstable()
.collect();
if let Some((duplicate_tag, _)) = sorted_tags.iter().tuple_windows().find(|(a, b)| a == b) {
bail!(
"invalid oneof {}: multiple variants have tag {}",
ident,
duplicate_tag
);
}
let self_alias = quote!(Self);
let mut encode: Vec<TokenStream> = variants
.iter()
.map(|variant| variant.encode(&self_alias, ctx))
.collect();
let mut prepend: Vec<TokenStream> = variants
.iter()
.map(|variant| variant.prepend(&self_alias, ctx))
.collect();
let mut encoded_len: Vec<TokenStream> = variants
.iter()
.map(|variant| variant.encoded_len(&self_alias, ctx))
.collect();
let encoder_trait;
let owned_decoder_trait;
let borrowed_decoder_trait;
let decode_field_self_arg;
let decode_field_return_ty;
let current_tag_ty;
let current_tag: Vec<TokenStream>;
let empty_methods_impl;
let some;
if let Some(empty_ident) = &empty_variant {
encoder_trait = quote!(Oneof);
owned_decoder_trait = quote!(OneofDecoder);
borrowed_decoder_trait = quote!(OneofBorrowDecoder<'__a>);
decode_field_self_arg = Some(quote!(value: &mut Self,));
decode_field_return_ty = quote!(());
some = Some(quote!(::core::option::Option::Some));
current_tag_ty = quote!(::core::option::Option<u32>);
current_tag = variants
.iter()
.map(|variant| {
let tag = variant.tag();
let variant_ident = variant.ident();
quote!(Self::#variant_ident { .. } => ::core::option::Option::Some(#tag))
})
.chain([quote!(Self::#empty_ident => ::core::option::Option::None)])
.collect();
encode.push(quote!(Self::#empty_ident => {}));
prepend.push(quote!(Self::#empty_ident => {}));
encoded_len.push(quote!(Self::#empty_ident => 0));
empty_methods_impl = Some(quote! {
fn empty() -> Self {
Self::#empty_ident
}
fn is_empty(&self) -> bool {
matches!(self, Self::#empty_ident)
}
fn clear(&mut self) {
*self = Self::#empty_ident;
}
});
} else {
encoder_trait = quote!(NonEmptyOneof);
owned_decoder_trait = quote!(NonEmptyOneofDecoder);
borrowed_decoder_trait = quote!(NonEmptyOneofBorrowDecoder<'__a>);
decode_field_self_arg = None;
decode_field_return_ty = quote!(Self);
some = None;
current_tag_ty = quote!(u32);
current_tag = variants
.iter()
.map(|variant| {
let tag = variant.tag();
let variant_ident = variant.ident();
quote!(Self::#variant_ident { .. } => #tag)
})
.chain(
variants
.is_empty()
.then(|| quote! { _ => { unreachable!("no non-empty variants"); } }),
)
.collect();
if variants.is_empty() {
encode.push(quote! { _ => { unreachable!("no non-empty variants"); } });
prepend.push(quote! { _ => { unreachable!("no non-empty variants"); } });
encoded_len.push(quote! { _ => { unreachable!("no non-empty variants"); } });
}
empty_methods_impl = None;
};
let variant_name_arms = variants.iter().map(|variant| {
let tag = variant.tag();
let schema_variant_name = variant.schema_variant_name();
quote! {
#tag => (#schema_type_name, #schema_variant_name),
}
});
let decode_arms = |lifetime, mode| {
let ident_str = ident.to_string();
let arms = variants
.iter()
.map(|variant| variant.decode(&self_alias, lifetime, mode, ctx));
quote! {
match tag {
#(#arms,)*
_ => unreachable!(
concat!("invalid ", #ident_str, " tag: {}"), tag,
),
}
}
};
let [decode_owned, decode_borrowed] = match empty_variant {
_ if variants.is_empty() => {
let ident_str = ident.to_string();
let cannot_decode = quote!{
unreachable!(concat!("oneof ", #ident_str, " has no non-empty variants"));
};
[cannot_decode.clone(), cannot_decode]
}
None => [decode_arms(Owned, Relaxed), decode_arms(Borrowed, Relaxed)],
Some(ref empty_ident) => [
decode_arms(Owned, Relaxed),
decode_arms(Borrowed, Relaxed),
]
.map(|decode| quote! {
match if let Self::#empty_ident = value {
match #decode {
::core::result::Result::Ok(decoded) => {
*value = decoded;
::core::result::Result::Ok(())
}
::core::result::Result::Err(error) => ::core::result::Result::Err(error),
}
} else {
::core::result::Result::Err(#crate_::DecodeError::new(
if #crate_::encoding::#encoder_trait::oneof_current_tag(value) == #some(tag) {
#crate_::DecodeErrorKind::UnexpectedlyRepeated
} else {
#crate_::DecodeErrorKind::ConflictingFields
}
))
} {
::core::result::Result::Err(mut error) => {
let (msg, field) =
<Self as #crate_::encoding::#encoder_trait>::oneof_variant_name(tag);
error.push(msg, field);
::core::result::Result::Err(error)
}
ok => ok,
}
})
};
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::#owned_decoder_trait
for __Self #ty_generics #owned_decoder_where_clause
{
fn oneof_decode_field<__B: #crate_::bytes::Buf + ?Sized>(
#decode_field_self_arg
tag: u32,
wire_type: #crate_::encoding::WireType,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<#decode_field_return_ty, #crate_::DecodeError> {
#decode_owned
}
}
}
});
let impls = quote! {
impl #impl_generics #crate_::encoding::#encoder_trait
for __Self #ty_generics #encoder_where_clause
{
const FIELD_TAGS: &'static [u32] = &[#(#sorted_tags),*];
#empty_methods_impl
fn oneof_encode<__B: #crate_::bytes::BufMut + ?Sized>(
&self,
buf: &mut __B,
tw: &mut #crate_::encoding::TagWriter,
) {
match self {
#(#encode,)*
}
}
fn oneof_prepend<__B: #crate_::buf::ReverseBuf + ?Sized>(
&self,
buf: &mut __B,
tw: &mut #crate_::encoding::TagRevWriter,
) {
match self {
#(#prepend,)*
}
}
fn oneof_encoded_len(
&self,
tm: &mut impl #crate_::encoding::TagMeasurer,
) -> usize {
match self {
#(#encoded_len,)*
}
}
fn oneof_current_tag(&self) -> #current_tag_ty {
match self {
#(#current_tag,)*
}
}
fn oneof_variant_name(tag: u32) -> (&'static str, &'static str) {
match tag {
#(#variant_name_arms)*
_ => ("", ""),
}
}
}
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::#borrowed_decoder_trait
for __Self #ty_generics #borrowed_decoder_where_clause
{
fn oneof_borrow_decode_field(
#decode_field_self_arg
tag: u32,
wire_type: #crate_::encoding::WireType,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::DecodeContext,
) -> ::core::result::Result<#decode_field_return_ty, #crate_::DecodeError> {
#decode_borrowed
}
}
};
let distinguished_impls = distinguished.then(|| {
let owned_decoder_trait;
let borrowed_decoder_trait;
let relaxed_oneof_trait; let decode_field_self_arg;
let decode_field_return_ty;
let some; let owned_decoder_where_clause;
let borrowed_decoder_where_clause;
if empty_variant.is_some() {
owned_decoder_trait = quote!(DistinguishedOneofDecoder);
borrowed_decoder_trait = quote!(DistinguishedOneofBorrowDecoder<'__a>);
relaxed_oneof_trait = quote!(Oneof);
decode_field_self_arg = Some(quote!(value: &mut Self,));
decode_field_return_ty = quote!(#crate_::Canonicity);
some = Some(quote!(::core::option::Option::Some));
[owned_decoder_where_clause, borrowed_decoder_where_clause] =
[Owned, Borrowed].map(|lifetime| {
append_wheres_with_fields(
where_clause,
[quote!(Self: #crate_::encoding::Oneof)],
&variants,
Decode(lifetime, Distinguished),
ctx,
)
});
} else {
owned_decoder_trait = quote!(NonEmptyDistinguishedOneofDecoder);
borrowed_decoder_trait = quote!(NonEmptyDistinguishedOneofBorrowDecoder<'__a>);
relaxed_oneof_trait = quote!(NonEmptyOneof);
decode_field_self_arg = None;
decode_field_return_ty = quote!((Self, #crate_::Canonicity));
some = None;
[owned_decoder_where_clause, borrowed_decoder_where_clause] =
[Owned, Borrowed].map(|lifetime| {
append_wheres_with_fields(
where_clause,
None,
&variants,
Decode(lifetime, Distinguished),
ctx,
)
});
};
let [decode_owned, decode_borrowed] = match empty_variant {
_ if variants.is_empty() => {
let ident_str = ident.to_string();
let cannot_decode = quote! {
unreachable!(concat!("oneof ", #ident_str, " has no non-empty variants"));
};
[cannot_decode.clone(), cannot_decode]
}
None => [
decode_arms(Owned, Distinguished),
decode_arms(Borrowed, Distinguished),
],
Some(empty_ident) => [
decode_arms(Owned, Distinguished),
decode_arms(Borrowed, Distinguished),
]
.map(|decode| {
quote! {
match if let Self::#empty_ident = value {
match #decode {
::core::result::Result::Ok((decoded, canon)) => {
*value = decoded;
::core::result::Result::Ok(canon)
}
::core::result::Result::Err(error) => {
::core::result::Result::Err(error)
},
}
} else {
::core::result::Result::Err(#crate_::DecodeError::new(
if #crate_::encoding::#relaxed_oneof_trait::oneof_current_tag(value)
== #some(tag)
{
#crate_::DecodeErrorKind::UnexpectedlyRepeated
} else {
#crate_::DecodeErrorKind::ConflictingFields
}
))
} {
::core::result::Result::Err(mut error) => {
let (msg, field) =
<Self as #crate_::encoding::#relaxed_oneof_trait>::
oneof_variant_name(tag);
error.push(msg, field);
::core::result::Result::Err(error)
}
ok => ok,
}
}
}),
};
let impl_owned_decoder = (!borrow_only).then(|| {
quote! {
impl #impl_generics #crate_::encoding::#owned_decoder_trait
for __Self #ty_generics #owned_decoder_where_clause
{
fn oneof_decode_field_distinguished<__B: #crate_::bytes::Buf + ?Sized>(
#decode_field_self_arg
tag: u32,
wire_type: #crate_::encoding::WireType,
buf: #crate_::encoding::Capped<__B>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#decode_field_return_ty, #crate_::DecodeError> {
#decode_owned
}
}
}
});
quote! {
#impl_owned_decoder
impl #borrow_generics #crate_::encoding::#borrowed_decoder_trait
for __Self #ty_generics #borrowed_decoder_where_clause
{
fn oneof_borrow_decode_field_distinguished(
#decode_field_self_arg
tag: u32,
wire_type: #crate_::encoding::WireType,
buf: #crate_::encoding::Capped<&'__a [u8]>,
ctx: #crate_::encoding::RestrictedDecodeContext,
) -> ::core::result::Result<#decode_field_return_ty, #crate_::DecodeError> {
#decode_borrowed
}
}
}
});
let aliases = encoder_alias_header(ctx);
let initializer_class = initializer_class_definition(
variants.iter().flat_map(OneofVariant::initializer_methods),
None,
&generics,
);
Ok(quote! {
const _: () = {
use #ident as __Self;
#initializer_class
const _: () = {
#aliases
#impls
#distinguished_impls
};
};
})
}
#[proc_macro_derive(Oneof, attributes(bilrost))]
pub fn oneof(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
try_oneof(input.into()).unwrap().into()
}
fn try_schema(input: TokenStream) -> Result<TokenStream> {
let input: DeriveInput = parse2(input)?;
match &input.data {
Data::Struct(..) => try_struct_schema(input),
Data::Enum(..) => try_enum_schema(input),
Data::Union(..) => {
bail!("schema cannot be derived for a union type");
}
}
}
fn try_struct_schema(input: DeriveInput) -> Result<TokenStream> {
let PreprocessedMessageStruct {
ident,
generics,
fields,
distinguished: _,
borrow_only: _,
struct_update_expr,
crate_name,
schema_type_name,
} = preprocess_message_struct(input)?;
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let (ignored_fields, implemented_fields): (Vec<_>, Vec<_>) =
fields.into_iter().partition(Field::is_ignored);
let self_where = if ignored_fields
.iter()
.any(Field::ignored_and_uses_struct_update_syntax)
&& struct_update_expr.is_none()
{
Some(quote!(Self: ::core::default::Default))
} else {
None
};
let schema_where_clause = append_wheres_with_fields(
where_clause,
self_where
.into_iter()
.chain([quote!(Self: ::core::any::Any)]),
&implemented_fields,
ForSchema,
ctx,
);
let field_schemas: Vec<_> = implemented_fields
.iter()
.flat_map(|field| field.schema(ctx))
.collect();
let impls = quote! {
impl #impl_generics #crate_::encoding::schema::RegisterMessage for __Self #ty_generics
#schema_where_clause {
fn register(schema: &#crate_::encoding::schema::Schema) {
#crate_::encoding::schema::PopulateSchema::register_message::<Self>(
schema,
#schema_type_name,
|fields| {
#(#field_schemas)*
},
);
}
}
};
let aliases = encoder_alias_header(ctx);
let expanded = quote! {
const _: () = {
use #ident as __Self;
const _: () = {
#aliases
#impls
};
};
};
Ok(expanded)
}
fn try_enum_schema(input: DeriveInput) -> Result<TokenStream> {
let PreprocessedOneof {
ident,
generics,
variants,
distinguished: _,
borrow_only: _,
empty_variant,
crate_name,
schema_type_name,
} = preprocess_oneof(input)?;
let ctx = &Context::new(crate_name);
let crate_ = &ctx.crate_name;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let schema_where_clause = append_wheres_with_fields(
where_clause,
Some(quote!(Self: ::core::any::Any)),
&variants,
ForSchema,
ctx,
);
let submessage_schemas = {
let submessage_registrations: Vec<_> = variants
.iter()
.flat_map(|variant| variant.subtype_schema(ctx))
.collect();
if submessage_registrations.is_empty() {
None
} else {
Some(quote! {
#crate_::encoding::schema::PopulateSchema::register_oneof_messages::<Self>(
schema,
#schema_type_name,
|messages| {
#(#submessage_registrations)*
},
);
})
}
};
let field_schemas: Vec<_> = variants.iter().map(|variant| variant.schema(ctx)).collect();
let as_oneof_impls = quote! {
impl #impl_generics #crate_::encoding::schema::AddOneofFields
for __Self #ty_generics #schema_where_clause
{
fn add_fields(
schema: &#crate_::encoding::schema::Schema,
fields: &mut #crate_::encoding::schema::MessageFields,
field_name: ::core::option::Option<&str>,
) {
#submessage_schemas
#(#field_schemas)*
}
}
};
let as_message_impls = empty_variant.map(|_| {
quote! {
impl #impl_generics #crate_::encoding::schema::RegisterMessage
for __Self #ty_generics #schema_where_clause
{
fn register(schema: &#crate_::encoding::schema::Schema) {
#crate_::encoding::schema::PopulateSchema::register_message::<Self>(
schema,
#schema_type_name,
|fields| {
fields.add_oneof(
#schema_type_name,
<Self as #crate_::encoding::Oneof>::FIELD_TAGS,
);
<Self as #crate_::encoding::schema::AddOneofFields>::add_fields(
schema,
fields,
None,
);
},
);
}
}
}
});
let aliases = encoder_alias_header(ctx);
let expanded = quote! {
const _: () = {
use #ident as __Self;
const _: () = {
#aliases
#as_oneof_impls
#as_message_impls
};
};
};
Ok(expanded)
}
#[proc_macro_derive(Schema, attributes(bilrost))]
pub fn schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
try_schema(input.into()).unwrap().into()
}
#[cfg(test)]
mod test {
use crate::{try_enumeration, try_message, try_oneof};
use alloc::format;
use alloc::string::ToString;
use quote::quote;
#[test]
fn test_rejects_colliding_message_fields() {
let output = try_message(quote! {
struct Invalid {
#[bilrost(tag = "1")]
a: bool,
#[bilrost(oneof(4, 5, 1))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("duplicate tags not detected").to_string(),
"multiple fields have tag 1"
);
let output = try_message(quote! {
struct Invalid {
#[bilrost(tag = "2")]
a: bool,
#[bilrost(oneof(1-3))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("duplicate tags not detected").to_string(),
"multiple fields have tag 2"
);
let output = try_message(quote! {
struct Invalid {
#[bilrost(tag = "10")]
a: bool,
#[bilrost(oneof = "5-10")]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("duplicate tags not detected").to_string(),
"multiple fields have tag 10"
);
_ = try_message(quote! {
struct Valid {
#[bilrost(tag = "4")]
a: bool,
#[bilrost(oneof(5-10, 1-3))]
b: Option<super::Whatever>,
}
})
.unwrap();
}
#[test]
fn test_rejects_reserved_message_fields() {
let output = try_message(quote! {
#[bilrost(reserved_tags(1, 100))]
struct Invalid {
#[bilrost(tag = "1")]
a: bool,
#[bilrost(oneof(3-5))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"field a has reserved tag 1"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(4, 55))]
struct Invalid {
#[bilrost(tag = "1")]
a: bool,
#[bilrost(oneof(3-5))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"field b has reserved tag 4"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(5-10, 55))]
struct Invalid {
#[bilrost(tag = "1")]
a: bool,
#[bilrost(oneof(3-5))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"field b has reserved tag 5"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(..=3, 55))]
struct Invalid {
#[bilrost(tag = "999")]
a: bool,
#[bilrost(oneof(3-5))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"field b has reserved tag 3"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(0, 5..))]
struct Invalid {
#[bilrost(tag = "1")]
a: bool,
#[bilrost(oneof(3-5))]
b: Option<super::Whatever>,
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"field b has reserved tag 5"
);
}
#[test]
fn test_rejects_reserved_oneof_fields() {
let output = try_message(quote! {
#[bilrost(reserved_tags(1, 100))]
enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(5)]
B(super::Whatever),
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"oneof Invalid variant A has reserved tag 1"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(5, 55))]
enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(5)]
B(super::Whatever),
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"oneof Invalid variant B has reserved tag 5"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(5-10, 55))]
enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(5)]
B(super::Whatever),
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"oneof Invalid variant B has reserved tag 5"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(..=3, 55))]
enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(5)]
B(super::Whatever),
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"oneof Invalid variant A has reserved tag 1"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(0, 5..))]
enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(5)]
B(super::Whatever),
}
});
assert_eq!(
output.expect_err("reserved tags not detected").to_string(),
"oneof Invalid variant B has reserved tag 5"
);
}
#[test]
fn test_rejects_oversize_oneof_tag_ranges() {
let output = try_message(quote! {
struct Invalid {
#[bilrost(oneof(1-100))]
a: SomeOneof,
}
});
assert_eq!(
format!(
"{:#}",
output.expect_err("oversized tag range not detected")
),
"invalid field a: too-large tag range 1-100; use smaller ranges"
);
}
#[test]
fn test_accepts_tag_ranges() {
try_message(quote! {
#[bilrost(reserved_tags(1, 2, 3))]
struct Valid {
#[bilrost(4)]
x: String,
}
})
.unwrap();
try_message(quote! {
#[bilrost(reserved_tags(1-3, 8-100))]
struct Valid {
#[bilrost(4)]
x: String,
}
})
.unwrap();
try_message(quote! {
#[bilrost(reserved_tags(..=3, 8..))]
struct Valid {
#[bilrost(4)]
x: String,
}
})
.unwrap();
}
#[test]
fn test_rejects_colliding_tag_ranges() {
let output = try_message(quote! {
#[bilrost(reserved_tags(10, 15, 10))]
struct Invalid;
});
assert_eq!(
format!(
"{:#}",
output.expect_err("colliding reserved tag ranges not detected")
),
"tag 10 is duplicated in tag list"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(1-100, 55))]
struct Invalid;
});
assert_eq!(
format!(
"{:#}",
output.expect_err("colliding reserved tag ranges not detected")
),
"tag 55 is duplicated in tag list"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(1-100, 50-200))]
struct Invalid;
});
assert_eq!(
format!(
"{:#}",
output.expect_err("colliding reserved tag ranges not detected")
),
"tag 50 is duplicated in tag list"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(..=100, 6-10, 2))]
struct Invalid;
});
assert_eq!(
format!(
"{:#}",
output.expect_err("colliding reserved tag ranges not detected")
),
"tag 2 is duplicated in tag list"
);
let output = try_message(quote! {
#[bilrost(reserved_tags(100.., 60, 9999))]
struct Invalid;
});
assert_eq!(
format!(
"{:#}",
output.expect_err("colliding reserved tag ranges not detected")
),
"tag 9999 is duplicated in tag list"
);
}
#[test]
fn test_rejects_colliding_oneof_variants() {
let output = try_oneof(quote! {
pub enum Invalid {
#[bilrost(tag = "1")]
A(bool),
#[bilrost(tag = "1")]
B(bool),
}
});
assert_eq!(
output
.expect_err("conflicting variant tags not detected")
.to_string(),
"invalid oneof Invalid: multiple variants have tag 1"
);
}
#[test]
fn test_basic_message() {
_ = try_message(quote! {
pub struct Struct {
#[bilrost(3)]
pub fields: BTreeMap<String, i64>,
#[bilrost(0)]
pub foo: String,
#[bilrost(1)]
pub bar: i64,
#[bilrost(2)]
pub baz: bool,
}
})
.unwrap();
}
#[test]
fn test_attribute_forms_are_equivalent() {
let one = try_message(quote! {
struct A (
#[bilrost(tag = "0")] bool,
#[bilrost(oneof = "2, 3")] B,
#[bilrost(tag = "4")] u32,
#[bilrost(tag = "5", encoding = "::custom<Z>")] String,
#[bilrost(tag = "1000")] i64,
#[bilrost(tag = "1001")] bool,
);
})
.unwrap()
.to_string();
let two = try_message(quote! {
struct A (
bool,
#[bilrost(oneof = "2, 3")] B,
#[bilrost(4)] u32,
#[bilrost(encoding(::custom< Z >))] String,
#[bilrost(tag = 1000)] i64,
bool,
);
})
.unwrap()
.to_string();
let three = try_message(quote! {
struct A (
#[bilrost(tag(0))] bool,
#[bilrost(oneof(2, 3))] B,
u32,
#[bilrost(encoding = "::custom <Z>")] String,
#[bilrost = 1000] i64,
bool,
);
})
.unwrap()
.to_string();
let four = try_message(quote! {
struct A (
#[bilrost(0)] bool,
#[bilrost(oneof(2, 3))] B,
u32,
#[bilrost(encoding(::custom<Z>))] String,
#[bilrost = 1000] i64,
#[bilrost()] bool,
);
})
.unwrap()
.to_string();
let minimal = try_message(quote! {
struct A (
bool,
#[bilrost(oneof(2, 3))] B,
u32,
#[bilrost(encoding(::custom<Z>))] String,
#[bilrost(1000)] i64,
bool,
);
})
.unwrap()
.to_string();
assert_eq!(one, two);
assert_eq!(one, three);
assert_eq!(one, four);
assert_eq!(one, minimal);
}
#[test]
fn test_tuple_message() {
_ = try_message(quote! {
struct Tuple(
#[bilrost(5)] bool,
#[bilrost(0)] String,
i64,
);
})
.unwrap();
}
#[test]
fn test_overlapping_message() {
_ = try_message(quote! {
struct Struct {
#[bilrost(0)]
zero: bool,
#[bilrost(oneof(1, 10, 20))]
a: Option<A>,
#[bilrost(4)]
four: bool,
#[bilrost(5)]
five: bool,
#[bilrost(oneof(9, 11))]
b: Option<B>,
twelve: bool, #[bilrost(oneof(13, 16, 22))]
c: Option<C>,
#[bilrost(14)]
fourteen: bool,
fifteen: bool, #[bilrost(17)]
seventeen: bool,
#[bilrost(oneof(18, 19))]
d: Option<D>,
#[bilrost(21)]
twentyone: bool,
#[bilrost(50)]
fifty: bool,
}
})
.unwrap();
}
#[test]
fn test_rejects_conflicting_empty_oneof_variants() {
let output = try_oneof(quote!(
enum AB {
Empty,
AlsoEmpty,
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output
.expect_err("conflicting empty variants not detected")
.to_string(),
"Oneofs may have at most one empty enum variant. To use multiple variants without \
fields, the non-empty variants can be marked as values with the 'message' attribute \
and the empty variant can be either left un-marked or explicitly marked with the \
'empty' attribute.\n\nThe conflicting variants were: Empty and AlsoEmpty"
);
}
#[test]
fn test_rejects_meaningless_empty_variant_attrs() {
let output = try_oneof(quote!(
enum AB {
#[bilrost(empty, anything_else)]
Empty,
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output
.expect_err("unknown attrs on empty variant not detected")
.to_string(),
"the 'empty' attribute is combined with other attributes on variant Empty, but it must \
always be alone"
);
}
#[test]
fn test_rejects_meaningless_empty_value_variants() {
let output = try_oneof(quote!(
enum AB {
#[bilrost(encoding(X))]
Empty,
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output
.expect_err("tagless unit variant not detected")
.to_string(),
"missing tag attribute on value variant Empty"
);
let output = try_oneof(quote!(
enum AB {
#[bilrost(tag(0))]
Empty,
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output.expect_err("unit variant not detected").to_string(),
"Oneof value variants must have exactly one field, but variant Empty has no fields"
);
let output = try_oneof(quote!(
enum AB {
#[bilrost(tag(0))]
Empty {},
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output
.expect_err("brace unit variant not detected")
.to_string(),
"Oneof value variants must have exactly one field, but variant Empty has 0 fields"
);
let output = try_oneof(quote!(
enum AB {
#[bilrost(tag(0))]
Empty(),
#[bilrost(1)]
A(bool),
#[bilrost(2)]
B(bool),
}
));
assert_eq!(
output
.expect_err("tuple unit variant not detected")
.to_string(),
"Oneof value variants must have exactly one field, but variant Empty has 0 fields"
);
}
#[test]
fn test_rejects_unnumbered_oneof_variants() {
let output = try_oneof(quote!(
enum AB {
#[bilrost(1)]
A(u32),
#[bilrost(encoding(packed))]
B(Vec<String>),
}
));
assert_eq!(
output
.expect_err("unnumbered oneof variant not detected")
.to_string(),
"missing tag attribute on value variant B"
);
}
#[test]
fn test_rejects_attributes_inside_value_variant() {
let output = try_oneof(quote!(
enum A {
#[bilrost(1)]
A {
#[bilrost(encoding(fixed))]
val: u64,
},
}
));
assert_eq!(
output
.expect_err("attributes on value variant not detected")
.to_string(),
"bilrost attributes found on the field inside variant A; those attributes should \
probably go on the variant instead, or the variant should be a message variant"
);
}
#[test]
fn test_rejects_struct_and_union_enumerations() {
let output = try_enumeration(quote!(
struct X {
x: String,
}
));
assert_eq!(
output
.expect_err("enumeration of struct not detected")
.to_string(),
"Enumeration can not be derived for a struct"
);
let output = try_enumeration(quote!(
union XY {
x: String,
Y: Vec<u8>,
}
));
assert_eq!(
output
.expect_err("enumeration of union not detected")
.to_string(),
"Enumeration can not be derived for a union"
);
}
#[test]
fn test_rejects_variant_with_field_in_enumeration() {
let output = try_enumeration(quote!(
enum X {
A = 1,
B(u32) = 2,
}
));
assert_eq!(
output
.expect_err("variant with field not detected")
.to_string(),
"Enumeration variants may not have fields"
);
}
#[test]
fn test_accepts_mixed_values_in_enumeration() {
_ = try_enumeration(quote!(
enum X<T> {
A = 1,
#[bilrost = 2]
B,
#[bilrost(3)]
C,
#[bilrost(SomeType::<T>::SOME_CONSTANT)]
D,
}
))
.unwrap();
}
#[test]
fn test_rejects_variant_without_value_in_enumeration() {
let output = try_enumeration(quote!(
enum X<T> {
A = 1,
#[bilrost = 2]
B,
#[bilrost(3)]
C,
#[bilrost(SomeType::<T>::SOME_CONSTANT)]
D,
HasNoValue,
}
));
assert_eq!(
output
.expect_err("variant without discriminant not detected")
.to_string(),
"Enumeration variants must have a discriminant or a #[bilrost(val = ..)] attribute \
(shorthand #[bilrost(..)]) with a constant value"
);
}
#[test]
fn test_rejects_empty_enumeration() {
let output = try_enumeration(quote!(
enum X {}
));
assert_eq!(
output
.expect_err("enumeration without variants not detected")
.to_string(),
"Enumerations must have at least one variant"
);
}
#[test]
fn test_accepts_distinguished_and_borrowed_messages() {
_ = try_message(quote!(
#[bilrost(distinguished, borrowed_only)]
struct DistinguishedBorrowedMessage {
name: &str,
}
))
.unwrap();
_ = try_message(quote!(
#[bilrost(distinguished, borrowed_only)]
enum DistinguishedBorrowedOneof {
Empty,
#[bilrost(1)]
Name(&str),
}
))
.unwrap();
}
#[test]
fn test_accepts_distinguished_and_borrowed_oneofs() {
_ = try_oneof(quote!(
#[bilrost(distinguished, borrowed_only)]
enum DistinguishedBorrowedOneof {
#[bilrost(1)]
Name(&str),
}
))
.unwrap();
}
#[test]
fn test_rejects_duplicated_message_attrs() {
let output = try_message(quote!(
#[bilrost(distinguished, distinguished)]
struct DistinguishedBorrowedMessage {
name: &str,
}
));
assert_eq!(
output
.expect_err("message with duplicated distinguished attributes not detected")
.to_string(),
"duplicated distinguished attributes"
);
let output = try_message(quote!(
#[bilrost(borrowed_only, distinguished, borrowed_only)]
struct DistinguishedBorrowedMessage {
name: &str,
}
));
assert_eq!(
output
.expect_err("message with duplicated borrowed_only attributes not detected")
.to_string(),
"duplicated borrowed_only attributes"
);
let output = try_message(quote!(
#[bilrost(distinguished, distinguished)]
enum DistinguishedBorrowedOneof {
Empty,
#[bilrost(1)]
Name(&str),
}
));
assert_eq!(
output
.expect_err("message with duplicated distinguished attributes not detected")
.to_string(),
"duplicated distinguished attributes"
);
let output = try_message(quote!(
#[bilrost(borrowed_only, distinguished, borrowed_only)]
enum DistinguishedBorrowedOneof {
Empty,
#[bilrost(1)]
Name(&str),
}
));
assert_eq!(
output
.expect_err("message with duplicated borrowed_only attributes not detected")
.to_string(),
"duplicated borrowed_only attributes"
);
}
#[test]
fn test_rejects_duplicated_oneof_attrs() {
let output = try_message(quote!(
#[bilrost(distinguished, distinguished)]
enum DistinguishedBorrowedOneof {
#[bilrost(1)]
Name(&str),
}
));
assert_eq!(
output
.expect_err("message with duplicated distinguished attributes not detected")
.to_string(),
"duplicated distinguished attributes"
);
let output = try_message(quote!(
#[bilrost(borrowed_only, distinguished, borrowed_only)]
enum DistinguishedBorrowedOneof {
#[bilrost(1)]
Name(&str),
}
));
assert_eq!(
output
.expect_err("message with duplicated borrowed_only attributes not detected")
.to_string(),
"duplicated borrowed_only attributes"
);
}
#[test]
fn test_rejects_duplicated_ignore_attrs() {
let output = try_message(quote!(
struct VeryIgnored {
#[bilrost(ignore, ignore)]
what: u32,
}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.root_cause()
.to_string(),
"invalid field what: duplicated ignore attributes for field: ignore , ignore"
);
let output = try_message(quote!(
enum Thing {
#[bilrost(tag(1), message)]
VeryIgnored {
#[bilrost(ignore, ignore)]
what: u32,
},
}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.root_cause()
.to_string(),
"in message variant VeryIgnored: invalid field what: duplicated ignore attributes \
for field: ignore , ignore"
);
let output = try_message(quote!(
enum Thing {
#[bilrost(tag(1), message)]
VeryIgnored {
#[bilrost(ignore, ignore = "1")]
what: u32,
},
}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.root_cause()
.to_string(),
"in message variant VeryIgnored: invalid field what: duplicated ignore attributes \
for field: ignore , ignore = \"1\""
);
let output = try_message(quote!(
struct MixedIgnores {
#[bilrost(tag(123), ignore)]
what: u32,
}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.root_cause()
.to_string(),
"invalid field what: ignore attribute mixed with other attributes on the same field: \
tag (123) , ignore"
);
let output = try_message(quote!(
enum Foo {
#[bilrost(tag(1), message)]
MixedIgnores {
#[bilrost(tag(123), ignore)]
what: u32,
},
}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.root_cause()
.to_string(),
"in message variant MixedIgnores: invalid field what: ignore attribute mixed with \
other attributes on the same field: tag (123) , ignore"
);
let output = try_message(quote!(
#[bilrost(default_per_field, default_per_field)]
struct DuplicatedOnStruct {}
));
assert_eq!(
output
.expect_err("field with duplicated ignore attributes not detected")
.to_string(),
"duplicated default_per_field attributes"
);
}
#[test]
fn test_field_specific_ignore() {
let _ = try_message(quote!(
struct Foo {
bar: usize,
#[bilrost(ignore = "5")]
baz: u32,
#[bilrost(ignore(wabl()))]
bear: String,
}
))
.unwrap();
let _ = try_message(quote!(
#[bilrost(default_per_field)]
struct Foo {
bar: usize,
#[bilrost(ignore = "5")]
baz: u32,
#[bilrost(ignore(wabl()))]
bear: String,
}
))
.unwrap();
let _ = try_message(quote!(
#[bilrost(default(Trait::new()))]
struct Foo {
bar: usize,
#[bilrost(ignore = "5")]
baz: u32,
#[bilrost(ignore(wabl()))]
bear: String,
}
))
.unwrap();
let _ = try_oneof(quote!(
enum Foo {
#[bilrost(tag(1), message)]
Thing {
#[bilrost(ignore = "5")]
baz: u32,
#[bilrost(ignore(wabl()))]
bear: String,
},
}
))
.unwrap();
}
#[test]
fn test_conflicting_struct_default_attributes() {
let output = try_message(quote!(
#[bilrost(default = "Trait::new()", default_per_field)]
struct Struct {
x: usize,
}
));
assert_eq!(
output
.expect_err("conflicting default attributes not detected")
.to_string(),
"default_per_field and default (expression) attributes are mutually exclusive"
);
let output = try_message(quote!(
#[bilrost(default = "Trait::new()", default(Trait::even_newer()))]
struct Struct {
x: usize,
}
));
assert_eq!(
output
.expect_err("duplicated default attributes not detected")
.to_string(),
"duplicated default (expression) attributes: (Trait :: new ()) and \
(Trait :: even_newer ())"
);
}
}