use proc_macro2::TokenStream;
use quote::quote;
use crate::generated::descriptor::{FileDescriptorProto, FileDescriptorSet};
pub(crate) fn reflectable_impl(ty: &TokenStream, buffa_path: &TokenStream) -> TokenStream {
quote! {
impl ::buffa_descriptor::reflect::Reflectable for #ty {
fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> {
let pool = #buffa_path::reflect::descriptor_pool();
let idx = pool
.message_index(<Self as ::buffa::MessageName>::FULL_NAME)
.unwrap_or_else(|| panic!(
"type {:?} not registered in this package's descriptor pool (cross-crate reflect()?)",
<Self as ::buffa::MessageName>::FULL_NAME,
));
::buffa_descriptor::reflect::ReflectCow::Owned(
::buffa::alloc::boxed::Box::new(
::buffa_descriptor::reflect::DynamicMessage::from_message(
self,
::buffa::alloc::sync::Arc::clone(pool),
idx,
),
),
)
}
}
}
}
pub(crate) fn reflect_element_impl_bridge(ty: &TokenStream) -> TokenStream {
quote! {
impl ::buffa_descriptor::reflect::ReflectElement for #ty {
fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> {
::buffa_descriptor::reflect::ValueRef::Message(
::buffa_descriptor::reflect::Reflectable::reflect(self),
)
}
}
}
}
pub(crate) fn reflectable_impl_vtable(ty: &TokenStream) -> TokenStream {
quote! {
impl ::buffa_descriptor::reflect::Reflectable for #ty {
#[inline]
fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> {
::buffa_descriptor::reflect::ReflectCow::Borrowed(self)
}
}
}
}
pub(crate) fn encode_fds_once(file_descriptors: &[FileDescriptorProto]) -> Vec<u8> {
use buffa::Message;
let mut files = file_descriptors.to_vec();
for file in &mut files {
file.source_code_info = Default::default();
}
FileDescriptorSet {
file: files,
..Default::default()
}
.encode_to_vec()
}
pub(crate) fn reflect_pool_module(fds_bytes: &[u8]) -> TokenStream {
let byte_literals = fds_bytes.iter().map(|b| quote! { #b });
quote! {
pub mod reflect {
pub const FILE_DESCRIPTOR_SET_BYTES: &[u8] = &[#(#byte_literals),*];
pub fn descriptor_pool() -> &'static ::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> {
static POOL: ::std::sync::OnceLock<
::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool>,
> = ::std::sync::OnceLock::new();
POOL.get_or_init(|| {
::buffa::alloc::sync::Arc::new(
::buffa_descriptor::DescriptorPool::decode(FILE_DESCRIPTOR_SET_BYTES)
.expect("embedded FileDescriptorSet is well-formed"),
)
})
}
}
}
}
pub(crate) fn reflect_reexports(buffa_path: &TokenStream, gate: Option<&str>) -> TokenStream {
let reexport = |docs: &[&str], name: TokenStream| {
crate::feature_gates::cfg_block(
quote! {
#(#[doc = #docs])*
#[doc(inline)]
pub use self::#buffa_path::reflect::#name;
},
gate,
)
};
let pool = reexport(
&[
"The lazily-built descriptor pool for this package's",
"`Reflectable` impls. Re-exported from `__buffa::reflect`.",
],
quote! { descriptor_pool },
);
let fds_bytes = reexport(
&[
"The serialized `FileDescriptorSet` this package's descriptor",
"pool is built from (`source_code_info` stripped).",
"Re-exported from `__buffa::reflect`.",
],
quote! { FILE_DESCRIPTOR_SET_BYTES },
);
quote! {
#pool
#fds_bytes
}
}
const _: usize = {
0
};
#[cfg(test)]
mod tests {
use super::*;
use quote::format_ident;
#[test]
fn reflectable_impl_emits_well_formed_tokens() {
let ty = format_ident!("Person");
let ty_ts = quote! { #ty };
let buffa = quote! { __buffa };
let tokens = reflectable_impl(&ty_ts, &buffa);
let parsed = syn::parse2::<syn::ItemImpl>(tokens.clone());
assert!(parsed.is_ok(), "generated impl must parse: {tokens}");
}
#[test]
fn reflect_pool_module_emits_well_formed_tokens() {
let fd = FileDescriptorProto {
name: Some("test.proto".into()),
package: Some("test".into()),
..Default::default()
};
let bytes = encode_fds_once(&[fd]);
{
use buffa::Message;
let decoded =
FileDescriptorSet::decode_from_slice(&bytes).expect("encoded FDS round-trips");
assert_eq!(decoded.file.len(), 1);
assert_eq!(decoded.file[0].name.as_deref(), Some("test.proto"));
}
let tokens = reflect_pool_module(&bytes);
let parsed = syn::parse2::<syn::ItemMod>(tokens.clone());
assert!(parsed.is_ok(), "generated module must parse: {tokens}");
assert!(tokens.to_string().contains("FILE_DESCRIPTOR_SET_BYTES"));
}
#[test]
fn reflect_reexports_emit_well_formed_tokens() {
let buffa = quote! { __buffa };
let tokens = reflect_reexports(&buffa, None);
let parsed = syn::parse2::<syn::File>(tokens.clone());
let file = parsed.expect("generated re-exports must parse");
assert_eq!(file.items.len(), 2, "pool accessor + FDS bytes constant");
assert!(
file.items.iter().all(|i| matches!(i, syn::Item::Use(_))),
"both items must be `use` re-exports"
);
let rendered = tokens.to_string();
assert!(rendered.contains("descriptor_pool"));
assert!(rendered.contains("FILE_DESCRIPTOR_SET_BYTES"));
}
#[test]
fn reflect_reexports_gate_each_item() {
let buffa = quote! { __buffa };
let tokens = reflect_reexports(&buffa, Some("reflect"));
let file = syn::parse2::<syn::File>(tokens).expect("gated re-exports must parse");
assert_eq!(file.items.len(), 2);
for item in &file.items {
let syn::Item::Use(item_use) = item else {
panic!("expected a use item");
};
assert!(
item_use.attrs.iter().any(|a| a.path().is_ident("cfg")),
"re-export missing its own #[cfg] gate"
);
}
}
#[test]
fn encode_fds_once_strips_source_code_info() {
use crate::generated::descriptor::SourceCodeInfo;
let fd = FileDescriptorProto {
name: Some("test.proto".into()),
package: Some("test".into()),
source_code_info: SourceCodeInfo::default().into(),
..Default::default()
};
assert!(fd.source_code_info.is_set());
let bytes = encode_fds_once(&[fd]);
use buffa::Message;
let decoded =
FileDescriptorSet::decode_from_slice(&bytes).expect("encoded FDS round-trips");
assert_eq!(decoded.file.len(), 1);
assert!(
!decoded.file[0].source_code_info.is_set(),
"source_code_info must not survive into the embedded FDS"
);
}
}