diff --git a/benchmarks/buffa/benches/reflect.rs b/benchmarks/buffa/benches/reflect.rs index 4d4811f..87809be 100644 --- a/benchmarks/buffa/benches/reflect.rs +++ b/benchmarks/buffa/benches/reflect.rs @@ -13,20 +13,24 @@ //! `decode/view` (`decode_view`, zero-copy — strings/bytes borrow from the //! input, so this is the floor, below even generated decode). //! 2. **Encode** — `encode/generated` vs. `encode/reflect`. -//! 3. **Bridge round-trip** — `t.reflect()`: one full encode + decode + boxed -//! `DynamicMessage`, the cost of the codegen-emitted `Reflectable` impl. +//! 3. **Owned-message reflection** — `bridge_round_trip` +//! (`DynamicMessage::from_message`: encode + decode + box, the bridge cost) +//! vs. `owned_vtable_read_all` (borrow the owned message via `ReflectMessage` +//! and scan it — no round-trip). This is the win from vtable mode making +//! `Reflectable::reflect()` borrow `self`. //! 4. **From wire bytes to reflective field reads** — the interceptor / //! field-mask workload, in `read_one` and `read_all` variants across three //! handle strategies: `vtable_*` (`decode_view` + borrow as -//! `&dyn ReflectMessage`), `bridge_*` (`T::decode` then `.reflect()`), and -//! `dynamic_*` (`DynamicMessage::decode`). Vtable reflection is dominated by -//! the cheap zero-copy `decode_view`, so it lands well below both the bridge -//! round-trip and pure `DynamicMessage` reflection. +//! `&dyn ReflectMessage`), `bridge_*` (`T::decode` then +//! `DynamicMessage::from_message`), and `dynamic_*` (`DynamicMessage::decode`). +//! Vtable reflection is dominated by the cheap zero-copy `decode_view`, so it +//! lands well below both the bridge round-trip and pure `DynamicMessage` +//! reflection. use std::sync::Arc; use buffa::{Message, MessageView}; -use buffa_descriptor::reflect::{DynamicMessage, ReflectMessage, Reflectable}; +use buffa_descriptor::reflect::{DynamicMessage, ReflectMessage}; use buffa_descriptor::{DescriptorPool, MessageIndex}; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; @@ -101,7 +105,7 @@ fn bench_message( vt_read_one: impl Fn(&[u8]), vt_read_all: impl Fn(&[u8]), ) where - M: Message + Default + Reflectable, + M: Message + Default + ReflectMessage, { let dataset = load_dataset(dataset_bytes); let bytes = total_payload_bytes(&dataset); @@ -179,7 +183,18 @@ fn bench_message( group.bench_function("reflect/bridge_round_trip", |b| { b.iter(|| { for m in &typed { - criterion::black_box(m.reflect()); + criterion::black_box(DynamicMessage::from_message(m, Arc::clone(p), idx)); + } + }); + }); + + // Owned-message reflection (a server reflecting its in-memory response): + // vtable mode borrows the owned message directly via `ReflectMessage`, so + // there is no round-trip — contrast with `bridge_round_trip` above. + group.bench_function("reflect/owned_vtable_read_all", |b| { + b.iter(|| { + for m in &typed { + read_all(m); } }); }); @@ -191,7 +206,7 @@ fn bench_message( // "read one field" and "read all set fields" variant: // // vtable — decode_view(bytes), borrow as &dyn ReflectMessage - // bridge — M::decode(bytes) then .reflect() (encode + decode + Box) + // bridge — M::decode(bytes) then DynamicMessage::from_message (round-trip) // dynamic — DynamicMessage::decode(bytes) (pure reflection, no typed step) group.bench_function("reflect/vtable_read_one", |b| { @@ -213,7 +228,8 @@ fn bench_message( b.iter(|| { for payload in &dataset.payload { let m = M::decode_from_slice(payload).expect("decode"); - read_one(&*m.reflect()); + let dm = DynamicMessage::from_message(&m, Arc::clone(p), idx); + read_one(&dm); } }); }); @@ -221,7 +237,8 @@ fn bench_message( b.iter(|| { for payload in &dataset.payload { let m = M::decode_from_slice(payload).expect("decode"); - read_all(&*m.reflect()); + let dm = DynamicMessage::from_message(&m, Arc::clone(p), idx); + read_all(&dm); } }); }); diff --git a/benchmarks/buffa/build.rs b/benchmarks/buffa/build.rs index 2592be9..f392a63 100644 --- a/benchmarks/buffa/build.rs +++ b/benchmarks/buffa/build.rs @@ -7,8 +7,7 @@ fn main() { ]) .includes(&["../proto/"]) .generate_json(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .compile() .expect("failed to compile benchmark protos"); } diff --git a/buffa-build/src/lib.rs b/buffa-build/src/lib.rs index 2cea96e..14bf2d1 100644 --- a/buffa-build/src/lib.rs +++ b/buffa-build/src/lib.rs @@ -35,6 +35,8 @@ use buffa_codegen::generated::descriptor::FileDescriptorSet; #[doc(inline)] pub use buffa_codegen::CodeGenConfig; #[doc(inline)] +pub use buffa_codegen::ReflectMode; +#[doc(inline)] pub use buffa_codegen::StringRepr; /// How to produce a `FileDescriptorSet` from `.proto` files. @@ -271,13 +273,18 @@ impl Config { self } - /// Generate `impl Reflectable` for owned message types (default: false). + /// Enable reflection on generated types (default: off). + /// + /// `generate_reflection(true)` selects [`ReflectMode::VTable`] — the fast + /// path: `foo.reflect()` borrows `foo` directly (no encode/decode + /// round-trip), and owned and view types implement `ReflectMessage`. For + /// the smaller bridge implementation (`reflect()` round-trips through a + /// [`DynamicMessage`]), use [`reflect_mode(ReflectMode::Bridge)`](Self::reflect_mode) + /// instead. `generate_reflection(false)` is [`ReflectMode::Off`]. /// - /// When enabled, each generated message gets a bridge-mode reflection - /// impl: `foo.reflect()` returns a [`ReflectCow`] wrapping a - /// [`DynamicMessage`] decoded from `foo`'s wire encoding, against a - /// lazily-built [`DescriptorPool`] embedded as `FileDescriptorSet` - /// bytes. The pool is reachable as `your_crate::your_pkg::descriptor_pool()`. + /// Either mode embeds a lazily-built [`DescriptorPool`] (as + /// `FileDescriptorSet` bytes) reachable as + /// `your_crate::your_pkg::descriptor_pool()`. /// /// # Cargo.toml setup /// @@ -308,10 +315,10 @@ impl Config { /// /// # Performance /// - /// `reflect()` is one full encode/decode round-trip plus a heap - /// allocation. For repeated reflective access, hold onto the returned - /// handle rather than calling `reflect()` per field. The first call - /// also pays a one-time pool build cost. + /// In the default vtable mode, `reflect()` borrows `self` — no round-trip, + /// no allocation; reflective accessors read fields in place. (Bridge mode + /// instead pays one encode/decode round-trip plus a heap allocation per + /// call.) Either way the first call pays a one-time pool build cost. /// /// # Build time and binary size /// @@ -320,34 +327,42 @@ impl Config { /// crate this is one copy. For a multi-package codegen run the bytes /// duplicate per package — measurable for large proto trees. The /// serialization happens once per `compile()` call (not per package), - /// so build-time CPU does not scale with package count. + /// so build-time CPU does not scale with package count. Vtable mode also + /// emits an `impl ReflectMessage` per type, so it produces more code than + /// bridge mode. /// /// [`ReflectCow`]: https://docs.rs/buffa-descriptor/latest/buffa_descriptor/reflect/enum.ReflectCow.html /// [`DynamicMessage`]: https://docs.rs/buffa-descriptor/latest/buffa_descriptor/reflect/struct.DynamicMessage.html /// [`DescriptorPool`]: https://docs.rs/buffa-descriptor/latest/buffa_descriptor/struct.DescriptorPool.html #[must_use] pub fn generate_reflection(mut self, enabled: bool) -> Self { - self.codegen_config.generate_reflection = enabled; + // The simple on/off knob selects the fast vtable path; Bridge is opt-in + // via `reflect_mode`. + let mode = if enabled { + ReflectMode::VTable + } else { + ReflectMode::Off + }; + mode.apply(&mut self.codegen_config); self } - /// Additionally emit vtable-mode reflection (`impl ReflectMessage` on view - /// types) on top of the bridge-mode `Reflectable` impl. + /// Select the reflection mode (the fuller form of + /// [`generate_reflection`](Self::generate_reflection)). /// - /// Requires [`generate_reflection`](Self::generate_reflection) and view - /// generation (both must be enabled — [`compile`](Self::compile) errors - /// otherwise). Vtable mode reads view struct fields directly through - /// `ReflectMessage`, with no encode/decode round-trip and no per-field - /// allocation for fields that are not read. + /// - [`ReflectMode::Off`] — no reflection (the default). + /// - [`ReflectMode::Bridge`] — `reflect()` round-trips through + /// `DynamicMessage`; equivalent to `generate_reflection(true)`. + /// - [`ReflectMode::VTable`] — `impl ReflectMessage` on owned and view + /// types, and `reflect()` borrows `self` with no round-trip. Requires + /// view generation (on by default). /// - /// **Experimental and `#[doc(hidden)]`.** This is a stopgap until the - /// public `ReflectMode` selector lands; the name and shape may change. It - /// is hidden from the rendered docs to avoid advertising an API that will - /// be superseded — internal builds use it directly. - #[doc(hidden)] + /// All non-`Off` modes require the consuming crate to depend on + /// `buffa-descriptor` with its `reflect` feature and on `std`. The call + /// site (`foo.reflect().get(fd)`) is identical across modes. #[must_use] - pub fn generate_reflection_vtable(mut self, enabled: bool) -> Self { - self.codegen_config.generate_reflection_vtable = enabled; + pub fn reflect_mode(mut self, mode: ReflectMode) -> Self { + mode.apply(&mut self.codegen_config); self } diff --git a/buffa-codegen/src/impl_message.rs b/buffa-codegen/src/impl_message.rs index 89e7d70..575df97 100644 --- a/buffa-codegen/src/impl_message.rs +++ b/buffa-codegen/src/impl_message.rs @@ -598,20 +598,45 @@ pub fn generate_message_impl( "e! { #name_ident }, ); - // Bridge-mode reflection: `impl Reflectable` resolving against the - // package's embedded descriptor pool. Skipped for map entry synthetic - // messages — they're not registered in the pool by name and consumers - // never reflect over them directly. - let reflectable_impl = if ctx.config.generate_reflection - && !msg - .options - .as_option() - .is_some_and(|o| o.map_entry.unwrap_or(false)) - { - crate::feature_gates::cfg_block( - crate::reflect::reflectable_impl("e! { #name_ident }, "e! { __buffa }), - ctx.config.feature_gates().reflect, - ) + // Reflection: `impl Reflectable` resolving against the package's embedded + // descriptor pool. Skipped for map entry synthetic messages — they're not + // registered in the pool by name and consumers never reflect over them. + // + // In vtable mode this also emits `impl ReflectMessage` / `impl + // ReflectElement` on the owned struct and makes `reflect()` borrow `self` + // directly (no encode/decode round-trip). In bridge mode `reflect()` boxes + // a `DynamicMessage` from a round-trip. + let is_map_entry = msg + .options + .as_option() + .is_some_and(|o| o.map_entry.unwrap_or(false)); + let reflectable_impl = if ctx.config.generate_reflection && !is_map_entry { + let gate = ctx.config.feature_gates().reflect; + if ctx.config.generate_reflection_vtable { + let buffa_path = quote! { __buffa }; + let owned = crate::reflect_owned::reflect_owned_impls( + &crate::reflect_owned::OwnedReflectScope { + ctx, + msg, + name_ident: &name_ident, + buffa_path: &buffa_path, + current_package, + features, + oneof_idents, + oneof_prefix, + nesting, + }, + )?; + let reflect_body = crate::reflect::reflectable_impl_vtable("e! { #name_ident }); + // Multiple sibling impls (ReflectMessage, ReflectElement, the memo + // inherent impl, Reflectable) — gate them together with one cfg. + crate::feature_gates::cfg_const_block(quote! { #owned #reflect_body }, gate) + } else { + crate::feature_gates::cfg_block( + crate::reflect::reflectable_impl("e! { #name_ident }, "e! { __buffa }), + gate, + ) + } } else { quote! {} }; diff --git a/buffa-codegen/src/lib.rs b/buffa-codegen/src/lib.rs index 55c9c9a..f924798 100644 --- a/buffa-codegen/src/lib.rs +++ b/buffa-codegen/src/lib.rs @@ -36,6 +36,7 @@ pub(crate) mod imports; pub(crate) mod message; pub(crate) mod oneof; pub(crate) mod reflect; +pub(crate) mod reflect_owned; pub(crate) mod reflect_view; pub(crate) mod view; @@ -229,6 +230,43 @@ impl StringRepr { } } +/// How much reflection support generated types get. +/// +/// Selected through `buffa_build`'s `reflect_mode` builder method (or the +/// `protoc-gen-buffa` `reflect_mode=` option). All modes need the consuming +/// crate to depend on `buffa-descriptor` with its `reflect` feature and on +/// `std`; the call site is `foo.reflect().get(fd)` regardless of mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[non_exhaustive] +pub enum ReflectMode { + /// No reflection impls. + #[default] + Off, + /// `Reflectable::reflect()` round-trips the message through a + /// `DynamicMessage` (encode → decode → boxed handle). Smaller generated + /// code; pays an allocation and a re-encode per `reflect()` call. + Bridge, + /// `impl ReflectMessage` directly on the owned and view types, and + /// `Reflectable::reflect()` borrows `self` with no round-trip. Larger + /// generated code; near-free reflective access. Requires view generation. + VTable, +} + +impl ReflectMode { + /// Apply this mode to a [`CodeGenConfig`] (sets `generate_reflection` / + /// `generate_reflection_vtable`). Used by the `buffa-build` and + /// `protoc-gen-buffa` front-ends. + pub fn apply(self, config: &mut CodeGenConfig) { + let (reflection, vtable) = match self { + ReflectMode::Off => (false, false), + ReflectMode::Bridge => (true, false), + ReflectMode::VTable => (true, true), + }; + config.generate_reflection = reflection; + config.generate_reflection_vtable = vtable; + } +} + /// Configuration for code generation. #[derive(Debug, Clone)] #[non_exhaustive] @@ -478,15 +516,20 @@ pub struct CodeGenConfig { /// /// Defaults to `false`. pub generate_reflection: bool, - /// Additionally emit `impl ReflectMessage` / `impl ReflectElement` on view - /// types (vtable mode), on top of the bridge-mode `Reflectable` impl. + /// Emit vtable-mode reflection: `impl ReflectMessage` / `impl + /// ReflectElement` on **both** the view types and the owned message + /// structs, and switch the owned `Reflectable::reflect()` body to borrow + /// `self` (`ReflectCow::Borrowed(self)`) instead of the bridge round-trip. + /// + /// Reflective access then reads struct fields in place — no encode/decode + /// round-trip and no per-field allocation — for both a decoded view and an + /// in-memory owned message. /// - /// Requires [`generate_reflection`](Self::generate_reflection) (the vtable - /// impls resolve against the same embedded `DescriptorPool`) and - /// [`generate_views`](Self::generate_views). Internal flag, not yet exposed - /// through `buffa-build`; the public `ReflectMode` surface is wired - /// separately. Vtable mode reads view struct fields directly — no - /// encode/decode round-trip and no per-field allocation. + /// Requires [`generate_reflection`](Self::generate_reflection) (the impls + /// resolve against the same embedded `DescriptorPool`) and + /// [`generate_views`](Self::generate_views). Set via [`ReflectMode::VTable`] + /// — front-ends expose it as `buffa_build::Config::reflect_mode` / + /// `protoc-gen-buffa`'s `reflect_mode=vtable`. /// /// Defaults to `false`. pub generate_reflection_vtable: bool, @@ -820,24 +863,23 @@ pub fn generate( /// /// Returns [`CodeGenError::FileNotFound`] if a name in `files_to_generate` has /// no matching descriptor, [`CodeGenError::Other`] if `generate_reflection_vtable` -/// is set without `generate_reflection` and `generate_views`, and other -/// [`CodeGenError`] variants for malformed descriptors (e.g. a missing required -/// field) encountered while generating. +/// is set without `generate_reflection`, and other [`CodeGenError`] variants for +/// malformed descriptors (e.g. a missing required field) encountered while +/// generating. pub fn generate_with_diagnostics( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], config: &CodeGenConfig, ) -> Result<(Vec, Vec), CodeGenError> { - // Vtable reflection emits `impl ReflectMessage` on view types and resolves - // against the per-package descriptor pool, so it needs both view generation - // and bridge-mode reflection (which emits that pool). Without this check the - // flag would silently emit nothing and the consumer would hit an opaque - // "FooView: ReflectMessage is not satisfied" error far from the cause. - if config.generate_reflection_vtable && (!config.generate_reflection || !config.generate_views) - { + // Vtable reflection resolves against the per-package descriptor pool, which + // is emitted by bridge-mode reflection — so it requires `generate_reflection`. + // It does NOT require views: the owned `impl ReflectMessage` is self-contained, + // so with views off, vtable mode still emits owned-message reflection (the + // view impls are simply skipped along with the views). + if config.generate_reflection_vtable && !config.generate_reflection { return Err(CodeGenError::Other( - "generate_reflection_vtable requires both generate_reflection and \ - generate_views to be enabled" + "generate_reflection_vtable requires generate_reflection to be enabled \ + (it provides the descriptor pool the reflect impls resolve against)" .into(), )); } diff --git a/buffa-codegen/src/reflect.rs b/buffa-codegen/src/reflect.rs index a78253b..e64bd8b 100644 --- a/buffa-codegen/src/reflect.rs +++ b/buffa-codegen/src/reflect.rs @@ -1,17 +1,24 @@ -//! Code generation for `impl Reflectable` (bridge mode). +//! Code generation for the owned message's `impl Reflectable` and the +//! per-package descriptor pool. //! -//! Wired through [`CodeGenConfig::generate_reflection`]. When enabled, each -//! generated owned message gets an `impl ::buffa_descriptor::reflect::Reflectable` -//! that round-trips through [`DynamicMessage`](buffa_descriptor::DynamicMessage) -//! (encode → decode → reflective handle), plus a per-package -//! `__buffa::reflect` submodule embedding the `FileDescriptorSet` bytes and -//! a lazy [`DescriptorPool`](buffa_descriptor::DescriptorPool) accessor. +//! Wired through [`CodeGenConfig::generate_reflection`]. Every generated owned +//! message gets an `impl ::buffa_descriptor::reflect::Reflectable`, plus a +//! per-package `__buffa::reflect` submodule embedding the `FileDescriptorSet` +//! bytes and a lazy [`DescriptorPool`](buffa_descriptor::DescriptorPool) +//! accessor that both modes resolve against. //! -//! The bridge mode is the v1 reflection target. The vtable mode (zero-copy -//! reflective access without the round-trip) is deferred — see -//! `docs/investigations/reflection.md`. The call-site contract is the same -//! either way (`foo.reflect().get(fd)`), so flipping modes later requires no -//! diff in consumer code. +//! Two `reflect()` bodies are emitted, selected by mode: +//! +//! - **Bridge** ([`reflectable_impl`]) — round-trips through +//! [`DynamicMessage`](buffa_descriptor::DynamicMessage) (encode → decode → +//! boxed handle). +//! - **Vtable** ([`reflectable_impl_vtable`]) — returns +//! `ReflectCow::Borrowed(self)`, with no round-trip. Requires the owned +//! `impl ReflectMessage` emitted by [`reflect_owned`](crate::reflect_owned) +//! (and the view impls by [`reflect_view`](crate::reflect_view)). +//! +//! The call-site contract is identical (`foo.reflect().get(fd)`), so flipping a +//! message between modes requires no diff in consumer code. //! //! ## Runtime requirements //! @@ -82,6 +89,23 @@ pub(crate) fn reflectable_impl(ty: &TokenStream, buffa_path: &TokenStream) -> To } } +/// Generate the vtable-mode `impl Reflectable for #ty`, whose `reflect()` +/// borrows `self` directly as `ReflectCow::Borrowed(self)` — no encode/decode +/// round-trip. Requires `#ty: ReflectMessage` (the owned vtable impl emitted by +/// [`reflect_owned`](crate::reflect_owned)). +pub(crate) fn reflectable_impl_vtable(ty: &TokenStream) -> TokenStream { + quote! { + impl ::buffa_descriptor::reflect::Reflectable for #ty { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } + } +} + /// Serialize the full `FileDescriptorSet` once per codegen run. /// /// `reflect_pool_module` is invoked once per package, so without caching @@ -109,8 +133,9 @@ pub(crate) fn encode_fds_once(file_descriptors: &[FileDescriptorProto]) -> Vec TokenStream { let byte_literals = fds_bytes.iter().map(|b| quote! { #b }); quote! { - /// Reflection support: embedded descriptor pool for bridge-mode - /// [`Reflectable`](::buffa_descriptor::reflect::Reflectable) impls. + /// Reflection support: embedded descriptor pool shared by this + /// package's [`Reflectable`](::buffa_descriptor::reflect::Reflectable) + /// and `ReflectMessage` impls (bridge and vtable mode alike). pub mod reflect { /// The serialized `FileDescriptorSet` for this codegen run, /// including transitive dependencies. Used to build the diff --git a/buffa-codegen/src/reflect_owned.rs b/buffa-codegen/src/reflect_owned.rs new file mode 100644 index 0000000..21247e3 --- /dev/null +++ b/buffa-codegen/src/reflect_owned.rs @@ -0,0 +1,355 @@ +//! Code generation for vtable-mode reflection on owned message types. +//! +//! Parallel to [`reflect_view`](crate::reflect_view), but for the owned struct +//! rather than the zero-copy view. When vtable mode is on, each owned message +//! gets: +//! +//! - `impl ReflectMessage for Foo` — reads owned struct fields directly +//! (`String`/`Vec`/`MessageField`/`Vec`/`HashMap`/owned oneof enum), +//! backed by the owned-container reflect impls in `buffa-descriptor`. +//! - `impl ReflectElement for Foo` — so a `Vec` / `HashMap<_, Foo>` +//! reflects through the generic container impls. +//! - A memoized per-message `MessageIndex` accessor. +//! +//! The owned `Reflectable::reflect()` body is then `ReflectCow::Borrowed(self)` +//! (emitted by [`reflect`](crate::reflect)), so reflecting an in-memory message +//! costs no encode/decode round-trip — the interceptor use case. Bridge mode +//! keeps the round-trip body and emits none of this. + +use std::collections::HashMap; + +use proc_macro2::TokenStream; +use quote::quote; + +use crate::context::CodeGenContext; +use crate::features::{resolve_field, ResolvedFeatures}; +use crate::generated::descriptor::field_descriptor_proto::{Label, Type}; +use crate::generated::descriptor::DescriptorProto; +use crate::idents::make_field_ident; +use crate::impl_message::{ + effective_type, is_explicit_presence_scalar, is_real_oneof_member, is_supported_field_type, +}; +use crate::message::{is_closed_enum, is_map_field, rust_path_to_tokens}; +use crate::oneof::oneof_variant_ident; +use crate::reflect_view::{scalar_default, scalar_variant}; +use crate::CodeGenError; + +/// Context needed to emit the owned-message vtable impls, mirroring the +/// arguments [`generate_message_impl`](crate::impl_message::generate_message_impl) +/// already has in hand. +pub(crate) struct OwnedReflectScope<'a> { + pub ctx: &'a CodeGenContext<'a>, + pub msg: &'a DescriptorProto, + pub name_ident: &'a proc_macro2::Ident, + pub buffa_path: &'a TokenStream, + pub current_package: &'a str, + pub features: &'a ResolvedFeatures, + pub oneof_idents: &'a HashMap, + pub oneof_prefix: &'a TokenStream, + pub nesting: usize, +} + +/// Generate `impl ReflectMessage` + `impl ReflectElement` + the memoized +/// `MessageIndex` accessor for an owned message. +pub(crate) fn reflect_owned_impls( + scope: &OwnedReflectScope<'_>, +) -> Result { + let ctx = scope.ctx; + let msg = scope.msg; + let name_ident = scope.name_ident; + let buffa_path = scope.buffa_path; + let current_package = scope.current_package; + let features = scope.features; + let oneof_idents = scope.oneof_idents; + let oneof_prefix = scope.oneof_prefix; + let nesting = scope.nesting; + let vr = quote! { ::buffa_descriptor::reflect::ValueRef }; + let cow = quote! { ::buffa_descriptor::reflect::ReflectCow }; + + let mut get_arms: Vec = Vec::new(); + let mut has_arms: Vec = Vec::new(); + + // Direct (non-oneof) fields. + for field in &msg.field { + if is_real_oneof_member(field) { + continue; + } + let ty = effective_type(ctx, field, features); + if !is_supported_field_type(ty) { + continue; + } + let name = field + .name + .as_deref() + .ok_or(CodeGenError::MissingField("field.name"))?; + let id = make_field_ident(name); + let number = field.number.unwrap_or(0) as u32; + let is_repeated = field.label.unwrap_or_default() == Label::LABEL_REPEATED; + + if is_repeated && is_map_field(msg, field) { + get_arms.push(quote! { #number => #vr::Map(&self.#id), }); + has_arms.push(quote! { #number => !self.#id.is_empty(), }); + continue; + } + if is_repeated { + get_arms.push(quote! { #number => #vr::List(&self.#id), }); + has_arms.push(quote! { #number => !self.#id.is_empty(), }); + continue; + } + + let f_features = resolve_field(ctx, field, features); + let (get_val, has_val) = if is_explicit_presence_scalar(field, ty, &f_features) { + // Stored as `Option`; absent singular returns the type default. + match ty { + Type::TYPE_STRING => ( + quote! { #vr::String(self.#id.as_deref().unwrap_or("")) }, + quote! { self.#id.is_some() }, + ), + Type::TYPE_BYTES => ( + quote! { #vr::Bytes(self.#id.as_deref().unwrap_or(&[])) }, + quote! { self.#id.is_some() }, + ), + Type::TYPE_ENUM => ( + quote! { #vr::EnumNumber(self.#id.map_or(0, |e| e.to_i32())) }, + quote! { self.#id.is_some() }, + ), + _ => { + let variant = scalar_variant(ty); + let def = scalar_default(ty); + ( + quote! { #vr::#variant(self.#id.unwrap_or(#def)) }, + quote! { self.#id.is_some() }, + ) + } + } + } else { + // Implicit presence: absent is the default value, present is + // non-default. proto2 `required` fields also fall here (stored as + // bare types), so a required field set to its default reflects as + // `has() == false` — the same limitation the view path documents. + match ty { + Type::TYPE_STRING => ( + quote! { #vr::String(&self.#id) }, + quote! { !self.#id.is_empty() }, + ), + Type::TYPE_BYTES => ( + quote! { #vr::Bytes(&self.#id[..]) }, + quote! { !self.#id.is_empty() }, + ), + Type::TYPE_MESSAGE | Type::TYPE_GROUP => ( + // `MessageField` derefs to the inner message, or the static + // default instance when unset, so the borrow is always valid. + quote! { #vr::Message(#cow::Borrowed(&*self.#id)) }, + quote! { self.#id.is_set() }, + ), + Type::TYPE_ENUM => { + let has_val = if is_closed_enum(&f_features) { + quote! { self.#id != ::core::default::Default::default() } + } else { + quote! { self.#id.to_i32() != 0 } + }; + (quote! { #vr::EnumNumber(self.#id.to_i32()) }, has_val) + } + _ => { + let variant = scalar_variant(ty); + let has_val = match ty { + Type::TYPE_BOOL => quote! { self.#id }, + Type::TYPE_FLOAT | Type::TYPE_DOUBLE => quote! { self.#id != 0.0 }, + _ => quote! { self.#id != 0 }, + }; + (quote! { #vr::#variant(self.#id) }, has_val) + } + } + }; + get_arms.push(quote! { #number => #get_val, }); + has_arms.push(quote! { #number => #has_val, }); + } + + // Oneof members dispatch through the `Option` struct field. + for (idx, oneof) in msg.oneof_decl.iter().enumerate() { + let Some(base_ident) = oneof_idents.get(&idx) else { + continue; + }; + let oneof_name = oneof + .name + .as_deref() + .ok_or(CodeGenError::MissingField("oneof.name"))?; + let field_ident = make_field_ident(oneof_name); + let oneof_enum = quote! { #oneof_prefix #base_ident }; + + for field in msg + .field + .iter() + .filter(|f| is_real_oneof_member(f) && f.oneof_index == Some(idx as i32)) + { + let name = field + .name + .as_deref() + .ok_or(CodeGenError::MissingField("field.name"))?; + let number = field.number.unwrap_or(0) as u32; + let variant = oneof_variant_ident(name); + let ty = effective_type(ctx, field, features); + + let (active, default) = match ty { + Type::TYPE_STRING => (quote! { #vr::String(v) }, quote! { #vr::String("") }), + Type::TYPE_BYTES => (quote! { #vr::Bytes(&v[..]) }, quote! { #vr::Bytes(&[]) }), + Type::TYPE_MESSAGE | Type::TYPE_GROUP => { + let owned_ty = resolve_owned_message_ty(ctx, field, current_package, nesting)?; + ( + quote! { #vr::Message(#cow::Borrowed(&**v)) }, + quote! { + #vr::Message(#cow::Borrowed( + <#owned_ty as ::buffa::DefaultInstance>::default_instance(), + )) + }, + ) + } + Type::TYPE_ENUM => ( + quote! { #vr::EnumNumber(v.to_i32()) }, + quote! { #vr::EnumNumber(0) }, + ), + _ => { + let variant_v = scalar_variant(ty); + let def = scalar_default(ty); + ( + quote! { #vr::#variant_v(*v) }, + quote! { #vr::#variant_v(#def) }, + ) + } + }; + + get_arms.push(quote! { + #number => match &self.#field_ident { + ::core::option::Option::Some(#oneof_enum::#variant(v)) => #active, + _ => #default, + }, + }); + has_arms.push(quote! { + #number => ::core::matches!( + &self.#field_ident, + ::core::option::Option::Some(#oneof_enum::#variant(_)) + ), + }); + } + } + + let pool = quote! { #buffa_path::reflect::descriptor_pool() }; + + // Preserve unknown fields through reflection, matching the bridge path + // (`DynamicMessage` carries them). Without this override the trait default + // returns an empty set, so a recursive reflective walk over nested messages + // would silently drop fields the local schema doesn't know — the exact + // regression `ReflectMessage::unknown_fields`'s own doc warns against. + let unknown_fields_method = if ctx.config.preserve_unknown_fields { + quote! { + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + } + } else { + quote! {} + }; + + Ok(quote! { + impl ::buffa_descriptor::reflect::ReflectMessage for #name_ident { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + #pool.message(Self::__buffa_reflect_message_index()) + } + + fn pool(&self) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + #pool + } + + #unknown_fields_method + + fn get(&self, field: &::buffa_descriptor::FieldDescriptor) -> #vr<'_> { + // Closed enums use the `Enumeration` trait `to_i32`; open enums + // (`EnumValue`) use the inherent one. No-op import for messages + // without enum fields. + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + #(#get_arms)* + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + #vr::Bool(false) + } + } + } + + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + #(#has_arms)* + _ => false, + } + } + + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut(&::buffa_descriptor::FieldDescriptor, #vr<'_>), + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor(self); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(#pool), + Self::__buffa_reflect_message_index(), + ) + } + } + + impl ::buffa_descriptor::reflect::ReflectElement for #name_ident { + fn as_value_ref(&self) -> #vr<'_> { + #vr::Message(#cow::Borrowed(self)) + } + } + + impl #name_ident { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = + ::std::sync::OnceLock::new(); + *IDX.get_or_init(|| { + #pool + .message_index(::FULL_NAME) + .expect("generated message is registered in the embedded descriptor pool") + }) + } + } + }) +} + +/// Resolve the owned Rust type token for a message-typed oneof member, used for +/// the unset-member default (`::default_instance()`). +fn resolve_owned_message_ty( + ctx: &CodeGenContext, + field: &crate::generated::descriptor::FieldDescriptorProto, + current_package: &str, + nesting: usize, +) -> Result { + let dotted = field + .type_name + .as_deref() + .ok_or(CodeGenError::MissingField("field.type_name"))?; + let path = ctx + .rust_type_relative(dotted, current_package, nesting) + .ok_or_else(|| { + CodeGenError::Other(format!( + "owned type for oneof message '{dotted}' not resolvable" + )) + })?; + Ok(rust_path_to_tokens(&path)) +} diff --git a/buffa-codegen/src/reflect_view.rs b/buffa-codegen/src/reflect_view.rs index e07f0dc..1399851 100644 --- a/buffa-codegen/src/reflect_view.rs +++ b/buffa-codegen/src/reflect_view.rs @@ -40,7 +40,7 @@ use crate::CodeGenError; /// Mirrors the wire form, matching `DynamicMessage`: `int32`/`sint32`/`sfixed32` /// all map to `I32`, etc. String, bytes, enum, and message types are handled by /// the callers, not here. -fn scalar_variant(ty: Type) -> TokenStream { +pub(crate) fn scalar_variant(ty: Type) -> TokenStream { match ty { Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => quote! { I32 }, Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => quote! { I64 }, @@ -55,7 +55,7 @@ fn scalar_variant(ty: Type) -> TokenStream { } /// The default literal for a wire-numeric proto type (`0`, `0.0`, or `false`). -fn scalar_default(ty: Type) -> TokenStream { +pub(crate) fn scalar_default(ty: Type) -> TokenStream { match ty { Type::TYPE_BOOL => quote! { false }, Type::TYPE_FLOAT | Type::TYPE_DOUBLE => quote! { 0.0 }, diff --git a/buffa-codegen/src/tests/reflect_view.rs b/buffa-codegen/src/tests/reflect_view.rs index 4a43a49..8fb7145 100644 --- a/buffa-codegen/src/tests/reflect_view.rs +++ b/buffa-codegen/src/tests/reflect_view.rs @@ -1,6 +1,21 @@ //! Vtable-mode reflection codegen: config validation and emitted impls. use super::*; +use crate::ReflectMode; + +#[test] +fn reflect_mode_maps_to_config_flags() { + let mut c = CodeGenConfig::default(); + + ReflectMode::Off.apply(&mut c); + assert!(!c.generate_reflection && !c.generate_reflection_vtable); + + ReflectMode::Bridge.apply(&mut c); + assert!(c.generate_reflection && !c.generate_reflection_vtable); + + ReflectMode::VTable.apply(&mut c); + assert!(c.generate_reflection && c.generate_reflection_vtable); +} /// A config with both bridge reflection and vtable mode enabled. fn vtable_config() -> CodeGenConfig { @@ -63,18 +78,26 @@ fn vtable_without_reflection_is_rejected() { } #[test] -fn vtable_without_views_is_rejected() { +fn vtable_without_views_emits_owned_only() { + // Owned vtable is self-contained, so views-off + vtable is allowed: it + // emits the owned `impl ReflectMessage` but no view impls (there are no + // views). let config = CodeGenConfig { generate_reflection: true, generate_reflection_vtable: true, generate_views: false, ..Default::default() }; - let err = generate(&[msg_file()], &["vt.proto".to_string()], &config) - .expect_err("vtable without views must error"); + let files = + generate(&[msg_file()], &["vt.proto".to_string()], &config).expect("should generate"); + let content = joined(&files); assert!( - err.to_string().contains("generate_views"), - "error should name the missing prerequisite: {err}" + content.contains("impl ::buffa_descriptor::reflect::ReflectMessage for Msg"), + "owned ReflectMessage must be emitted: {content}" + ); + assert!( + !content.contains("ReflectMessage for MsgView"), + "no view impls when views are off: {content}" ); } diff --git a/buffa-descriptor/Cargo.toml b/buffa-descriptor/Cargo.toml index e7eb85e..51926d2 100644 --- a/buffa-descriptor/Cargo.toml +++ b/buffa-descriptor/Cargo.toml @@ -43,3 +43,11 @@ text = ["buffa/text"] arbitrary = ["buffa/arbitrary", "dep:arbitrary"] ## Runtime reflection: `DescriptorPool`, edition feature resolution. reflect = [] +## `ReflectElement` impls for the configurable `string_type` representations, +## needed to reflect a `repeated ` field in vtable mode. Each forwards to +## the matching `buffa` feature so `buffa::::` resolves. A consumer +## building with `string_type(SmolStr)` + vtable reflection enables the matching +## feature here (typically via its own `reflect` feature). +smol_str = ["buffa/smol_str"] +ecow = ["buffa/ecow"] +compact_str = ["buffa/compact_str"] diff --git a/buffa-descriptor/src/reflect/view.rs b/buffa-descriptor/src/reflect/containers.rs similarity index 60% rename from buffa-descriptor/src/reflect/view.rs rename to buffa-descriptor/src/reflect/containers.rs index 2855b7b..b22a0f9 100644 --- a/buffa-descriptor/src/reflect/view.rs +++ b/buffa-descriptor/src/reflect/containers.rs @@ -1,14 +1,20 @@ -//! Reflective container access over zero-copy [view types](buffa::view). +//! Reflective container access for vtable-mode reflection. //! //! This module bridges the reflection value model ([`ValueRef`], -//! [`ReflectList`], [`ReflectMap`]) to the view containers -//! [`RepeatedView`](buffa::RepeatedView) and [`MapView`](buffa::MapView), so a -//! vtable-mode `impl ReflectMessage for FooView<'a>` can return -//! `ValueRef::List(&self.tags)` / `ValueRef::Map(&self.labels)` without -//! materializing a `Vec` or `MapValue`. The bridge path -//! ([`DynamicMessage`](super::DynamicMessage)) implements the same -//! [`ReflectList`] / [`ReflectMap`] traits for `Vec` / `MapValue` (see -//! `value.rs`); these impls are the vtable counterpart. +//! [`ReflectList`], [`ReflectMap`]) to the concrete containers generated code +//! holds, so a vtable-mode `impl ReflectMessage` can return +//! `ValueRef::List(&self.tags)` / `ValueRef::Map(&self.labels)` directly: +//! +//! - **View types** — [`RepeatedView`](buffa::RepeatedView) / +//! [`MapView`](buffa::MapView), borrowing `&str` / `&[u8]` elements. +//! - **Owned types** — `Vec` / `std::collections::HashMap`, with owned +//! `String` / `Vec` / [`Bytes`](buffa::bytes::Bytes) elements. +//! +//! The generic `ReflectList for Vec` impl also subsumes the bridge +//! [`DynamicMessage`](super::DynamicMessage)'s `Vec` storage (via +//! `impl ReflectElement for Value`), so there is a single list impl rather than +//! a bespoke one per backing type. [`MapValue`](super::MapValue) keeps its own +//! [`ReflectMap`] impl in `value.rs` (it is a distinct sorted-vec type). //! //! ## Why a per-element helper trait, and why it is not a blanket //! @@ -27,24 +33,39 @@ //! //! See `docs/investigations/reflection-vtable.md` §3 for the full rationale. +use alloc::string::String; +use alloc::vec::Vec; + +use buffa::bytes::Bytes; use buffa::{EnumValue, Enumeration, MapView, RepeatedView}; -use super::value::{MapKey, MapKeyRef, ReflectList, ReflectMap, ValueRef}; +use super::value::{MapKey, MapKeyRef, ReflectList, ReflectMap, Value, ValueRef}; /// Conversion of a single repeated-field element (or map value) to a borrowed /// [`ValueRef`]. /// -/// Implemented here for the closed set of view element types (scalars, `&str`, -/// `&[u8]`, [`EnumValue`]). Codegen emits a one-line impl for each generated -/// message view type (yielding [`ValueRef::Message`]) and each bare closed -/// enum (yielding [`ValueRef::EnumNumber`]) — these cannot be covered by a -/// blanket impl without colliding with the scalar impls under Rust's coherence -/// rules. +/// Implemented here for the closed set of element types the runtime knows: +/// scalars, `&str` / `&[u8]` (view storage), `String` / `Vec` / +/// [`Bytes`](buffa::bytes::Bytes) (owned storage), [`EnumValue`], and +/// [`Value`](super::Value) (bridge storage). Codegen emits a one-line impl for +/// each generated message type (view and owned, yielding [`ValueRef::Message`]) +/// and each bare closed enum (yielding [`ValueRef::EnumNumber`]) — these cannot +/// be covered by a blanket impl without colliding with the scalar impls under +/// Rust's coherence rules. +/// +/// # Contract /// -/// A hand-written or codegen-emitted implementer must also derive [`Debug`]: -/// the [`core::fmt::Debug`] supertrait is what lets the generic [`ReflectList`] -/// / [`ReflectMap`] impls below satisfy *their* own `Debug` supertrait through -/// the `RepeatedView: Debug` / `MapView: Debug` derives. +/// `as_value_ref` must return the **same variant** on every call for a given +/// value — the generic [`ReflectList`] / [`ReflectMap`] impls and their callers +/// assume a `Vec` / `HashMap<_, T>` is homogeneous. An implementer must also +/// derive [`Debug`] (the supertrait lets the generic container impls satisfy +/// *their* `Debug` supertrait through the container's derive). +/// +/// The non-default `string_type` representations (`SmolStr`, `EcoString`, +/// `CompactString`) have impls gated behind the matching `buffa-descriptor` +/// feature (`smol_str` / `ecow` / `compact_str`), for reflecting a `repeated` +/// field of that type in vtable mode. A consumer building with both that +/// `string_type` and vtable reflection must enable the corresponding feature. pub trait ReflectElement: core::fmt::Debug { /// Borrow this element as a [`ValueRef`]. #[must_use] @@ -111,6 +132,65 @@ impl ReflectElement for EnumValue { } } +// ── Owned element impls ───────────────────────────────────────────────────── +// +// Owned messages hold `String` / `Vec` / `Bytes` (rather than the view +// path's borrowed `&str` / `&[u8]`) and store repeated/map fields as `Vec` / +// `HashMap`. These impls let the generic container impls below cover owned +// collections too. `Value` is included so the bridge `DynamicMessage`'s +// `Vec` rides the same generic `ReflectList` impl. + +impl ReflectElement for String { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::String(self) + } +} + +impl ReflectElement for Vec { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::Bytes(self) + } +} + +impl ReflectElement for Bytes { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::Bytes(self) + } +} + +impl ReflectElement for Value { + fn as_value_ref(&self) -> ValueRef<'_> { + self.as_ref() + } +} + +// Configurable `string_type` representations, for reflecting a `repeated ` +// field in vtable mode. Each is gated on the matching `buffa-descriptor` feature +// (which forwards to `buffa`'s). Only the repeated case needs these: singular +// fields reflect via `&self.field` (any repr derefs to `str`), and `map` string +// keys/values always stay `String`. All reprs satisfy `AsRef`. + +#[cfg(feature = "smol_str")] +impl ReflectElement for buffa::smol_str::SmolStr { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::String(self.as_ref()) + } +} + +#[cfg(feature = "ecow")] +impl ReflectElement for buffa::ecow::EcoString { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::String(self.as_ref()) + } +} + +#[cfg(feature = "compact_str")] +impl ReflectElement for buffa::compact_str::CompactString { + fn as_value_ref(&self) -> ValueRef<'_> { + ValueRef::String(self.as_ref()) + } +} + // ── Map key impls (spec-valid key set) ────────────────────────────────────── macro_rules! impl_scalar_key { @@ -137,6 +217,12 @@ impl ReflectMapKey for &str { } } +impl ReflectMapKey for String { + fn as_map_key_ref(&self) -> MapKeyRef<'_> { + MapKeyRef::String(self) + } +} + impl ReflectMapKey for &[u8] { fn as_map_key_ref(&self) -> MapKeyRef<'_> { // Reached only for a `string` map key with editions @@ -211,6 +297,57 @@ impl ReflectMap for MapView<'_, K, V> { } } +// ── Owned containers ──────────────────────────────────────────────────────── + +/// Owned repeated storage (`Vec`). Also subsumes the bridge +/// `DynamicMessage`'s `Vec` (via `impl ReflectElement for Value`), so +/// there is no separate `ReflectList for Vec`. +impl ReflectList for Vec { + fn len(&self) -> usize { + self.as_slice().len() + } + + fn get(&self, idx: usize) -> Option> { + self.as_slice().get(idx).map(ReflectElement::as_value_ref) + } + + fn for_each(&self, f: &mut dyn FnMut(ValueRef<'_>)) { + for elem in self { + f(elem.as_value_ref()); + } + } +} + +/// Owned map storage. Keys are unique by construction (no dedup needed). Vtable +/// reflection requires `std` (the descriptor pool uses `OnceLock`), so the +/// owned-map impl is `std`-gated and targets `std::collections::HashMap` — the +/// concrete type generated code uses for `map` fields under `std`. +#[cfg(feature = "std")] +impl ReflectMap for std::collections::HashMap { + fn len(&self) -> usize { + Self::len(self) + } + + fn get(&self, key: &MapKey) -> Option> { + let want = key.as_ref(); + self.iter() + .find(|(k, _)| k.as_map_key_ref() == want) + .map(|(_, v)| v.as_value_ref()) + } + + fn get_str(&self, key: &str) -> Option> { + self.iter() + .find(|(k, _)| matches!(k.as_map_key_ref(), MapKeyRef::String(s) if s == key)) + .map(|(_, v)| v.as_value_ref()) + } + + fn for_each(&self, f: &mut dyn FnMut(MapKeyRef<'_>, ValueRef<'_>)) { + for (k, v) in self { + f(k.as_map_key_ref(), v.as_value_ref()); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -346,4 +483,48 @@ mod tests { }); assert_eq!(keys, vec!["apples".to_string()]); } + + // ── Owned containers (owned-message vtable path) ───────────────────────── + + #[test] + fn owned_vec_of_string() { + let v: Vec = vec!["a".to_string(), "b".to_string()]; + let list: &dyn ReflectList = &v; + assert_eq!(list.len(), 2); + assert!(matches!(list.get(0), Some(ValueRef::String("a")))); + assert!(matches!(list.get(1), Some(ValueRef::String("b")))); + } + + #[test] + fn owned_vec_of_value_still_reflects() { + // The bridge `DynamicMessage` repeated storage rides the generic impl + // via `impl ReflectElement for Value`. + let v: Vec = vec![Value::I32(7), Value::I32(11)]; + let list: &dyn ReflectList = &v; + assert_eq!(list.len(), 2); + assert!(matches!(list.get(1), Some(ValueRef::I32(11)))); + } + + #[cfg(feature = "std")] + #[test] + fn owned_hashmap() { + let mut m: std::collections::HashMap = std::collections::HashMap::new(); + m.insert("apples".to_string(), 3); + m.insert("oranges".to_string(), 7); + let map: &dyn ReflectMap = &m; + assert_eq!(map.len(), 2); + assert!(matches!(map.get_str("apples"), Some(ValueRef::I32(3)))); + assert!(matches!( + map.get(&MapKey::String("oranges".into())), + Some(ValueRef::I32(7)) + )); + assert!(map.get_str("durian").is_none()); + let mut total = 0; + map.for_each(&mut |_k, v| { + if let ValueRef::I32(n) = v { + total += n; + } + }); + assert_eq!(total, 10); + } } diff --git a/buffa-descriptor/src/reflect/message.rs b/buffa-descriptor/src/reflect/message.rs index d2857e2..5e3681d 100644 --- a/buffa-descriptor/src/reflect/message.rs +++ b/buffa-descriptor/src/reflect/message.rs @@ -118,12 +118,14 @@ pub trait ReflectMessage { None } - /// Snapshot this message as a [`DynamicMessage`]. - /// - /// For an already-dynamic message this is a clone; for a generated - /// message in bridge mode this is the encode/decode round-trip. The - /// default implementation is provided so that `dyn ReflectMessage` can - /// be converted, which `ReflectCow::to_dynamic` needs. + /// Snapshot this message as an owned [`DynamicMessage`]. + /// + /// For an already-dynamic message this is a clone; for a generated message + /// (bridge or vtable mode) this is an encode/decode round-trip. Required + /// rather than defaulted so that a `dyn ReflectMessage` can always be + /// converted, which [`ReflectCow::to_dynamic`] relies on — and so a + /// borrowed vtable handle can be promoted to an owned snapshot that + /// outlives `self`. fn to_dynamic(&self) -> DynamicMessage; } @@ -213,30 +215,37 @@ pub trait Reflectable { /// /// # Performance /// - /// In the codegen-emitted **bridge mode** impl, `reflect()` is one full - /// encode + decode round-trip plus a heap allocation per call. Hold - /// onto the returned handle for repeated field reads rather than - /// calling `reflect()` per field. The first call also pays a one-time - /// pool build cost (linking the embedded `FileDescriptorSet`). + /// Which body codegen emits depends on the reflection mode: + /// + /// - **Bridge mode** — `reflect()` is one full encode + decode round-trip + /// plus a heap allocation per call, returning an owned `DynamicMessage` + /// snapshot. The first call also pays a one-time pool build cost (linking + /// the embedded `FileDescriptorSet`). + /// - **Vtable mode** — `reflect()` borrows `self` directly + /// (`ReflectCow::Borrowed`), with no round-trip and no allocation; the + /// reflective accessors read the message's fields in place. /// - /// The vtable mode (deferred) is a borrow with no round-trip; the call - /// site doesn't change between modes. + /// Either way the returned handle borrows `self` (the signature ties it to + /// `&self`), so the call site is identical between modes. Hold onto the + /// handle for repeated reads rather than calling `reflect()` per field; for + /// an owned snapshot that outlives `self`, use + /// [`ReflectCow::to_dynamic`](super::ReflectCow::to_dynamic). /// /// # Panics /// - /// The codegen-emitted bridge impl panics if the embedded - /// `FileDescriptorSet` is malformed or `Self::FULL_NAME` is not - /// registered in the package pool — both indicate a codegen bug, not - /// consumer misuse. + /// The bridge-mode body panics if the embedded `FileDescriptorSet` is + /// malformed or `Self::FULL_NAME` is not registered in the package pool — + /// both indicate a codegen bug, not consumer misuse. (Vtable mode resolves + /// the descriptor lazily on first access with the same invariant.) /// /// # Setup /// /// The `Reflectable` impl is generated by enabling - /// `buffa_build::Config::generate_reflection(true)` in `build.rs`. The - /// consuming crate must also depend on `buffa-descriptor` with its - /// `reflect` feature and on `std`. See the `generate_reflection` doc - /// for the `Cargo.toml` pattern. - #[must_use = "reflect() round-trips through encode/decode in bridge mode; bind the handle"] + /// `buffa_build::Config::generate_reflection(true)` (bridge) or + /// `generate_reflection_vtable(true)` (vtable) in `build.rs`. The consuming + /// crate must also depend on `buffa-descriptor` with its `reflect` feature + /// and on `std`. + #[must_use = "reflect() returns a reflective handle borrowing self; bind it before reading fields"] fn reflect(&self) -> ReflectCow<'_>; // `reflect_mut(&mut self) -> ReflectCowMut<'_>` is part of the design but diff --git a/buffa-descriptor/src/reflect/mod.rs b/buffa-descriptor/src/reflect/mod.rs index 5cf04da..69a3309 100644 --- a/buffa-descriptor/src/reflect/mod.rs +++ b/buffa-descriptor/src/reflect/mod.rs @@ -22,19 +22,19 @@ //! also the natural home, since reflection consumers already declare //! `buffa-descriptor` for the descriptor types. +mod containers; mod dynamic; #[cfg(feature = "json")] mod json; mod message; mod value; -mod view; +pub use containers::{ReflectElement, ReflectMapKey}; pub use dynamic::{AnyError, DynamicMessage}; #[cfg(feature = "json")] pub use json::DynamicMessageSeed; pub use message::{ReflectCow, ReflectMessage, ReflectMessageMut, Reflectable}; pub use value::{MapKey, MapKeyRef, MapValue, ReflectList, ReflectMap, Value, ValueRef}; -pub use view::{ReflectElement, ReflectMapKey}; /// Per-message reflection mode, selected at codegen time. /// diff --git a/buffa-descriptor/src/reflect/value.rs b/buffa-descriptor/src/reflect/value.rs index 69c8ecf..70680f0 100644 --- a/buffa-descriptor/src/reflect/value.rs +++ b/buffa-descriptor/src/reflect/value.rs @@ -272,22 +272,9 @@ impl MapKey { } } -// `Vec` rather than `[Value]` so the `&Vec → &dyn ReflectList` -// unsizing coercion is valid — Rust requires the source type of an unsizing -// coercion to be `Sized`, and `[Value]` isn't. -impl ReflectList for Vec { - fn len(&self) -> usize { - Self::len(self) - } - fn get(&self, idx: usize) -> Option> { - self.as_slice().get(idx).map(Value::as_ref) - } - fn for_each(&self, f: &mut dyn FnMut(ValueRef<'_>)) { - for v in self.as_slice() { - f(v.as_ref()); - } - } -} +// `Vec` reflects through the generic `impl +// ReflectList for Vec` in `view.rs` (with `impl ReflectElement for Value`), +// so there is no bespoke `ReflectList for Vec` here. impl ReflectMap for MapValue { fn len(&self) -> usize { diff --git a/buffa-test/Cargo.toml b/buffa-test/Cargo.toml index 0725dde..559cb9f 100644 --- a/buffa-test/Cargo.toml +++ b/buffa-test/Cargo.toml @@ -23,7 +23,16 @@ buffa = { workspace = true, features = [ # embedded descriptor pool resolve. `json` because `basic.proto` is also # generated with `generate_text(true)` and the pool decodes the WKT # descriptors that need it. -buffa-descriptor = { workspace = true, features = ["reflect", "json", "std"] } +buffa-descriptor = { workspace = true, features = [ + "reflect", + "json", + "std", + # `ReflectElement` impls for the configurable string reprs, so a + # `repeated ` field reflects in vtable mode (vtable_string_repr test). + "smol_str", + "ecow", + "compact_str", +] } buffa-types = { workspace = true, features = ["json"] } bytes = { workspace = true } serde = { workspace = true } diff --git a/buffa-test/build.rs b/buffa-test/build.rs index 0cef5a7..10d29db 100644 --- a/buffa-test/build.rs +++ b/buffa-test/build.rs @@ -6,11 +6,31 @@ fn main() { .files(&["protos/basic.proto"]) .includes(&["protos/"]) .generate_text(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .compile() .expect("buffa_build failed for basic.proto"); + // views(false) + vtable: owned-message vtable reflection is self-contained, + // so it must compile without view generation (only owned impls emitted). + buffa_build::Config::new() + .files(&["protos/vtable_no_views.proto"]) + .includes(&["protos/"]) + .generate_views(false) + .reflect_mode(buffa_build::ReflectMode::VTable) + .compile() + .expect("buffa_build failed for vtable_no_views.proto"); + + // string_type(SmolStr) + vtable: exercises `ReflectElement for SmolStr` on + // the repeated-string element path (`Vec`). Singular string fields + // reflect via deref; map string keys/values stay `String`. + buffa_build::Config::new() + .files(&["protos/vtable_string_repr.proto"]) + .includes(&["protos/"]) + .string_type(buffa_build::StringRepr::SmolStr) + .reflect_mode(buffa_build::ReflectMode::VTable) + .compile() + .expect("buffa_build failed for vtable_string_repr.proto"); + // Comprehensive proto3 semantics: implicit vs explicit presence for all // scalar types, open-enum contexts, default packing, synthetic oneofs. buffa_build::Config::new() @@ -116,8 +136,7 @@ fn main() { .files(&["protos/proto2_defaults.proto"]) .includes(&["protos/"]) .generate_text(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .compile() .expect("buffa_build failed for proto2_defaults.proto"); diff --git a/buffa-test/protos/vtable_no_views.proto b/buffa-test/protos/vtable_no_views.proto new file mode 100644 index 0000000..aaffa08 --- /dev/null +++ b/buffa-test/protos/vtable_no_views.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package vtable_no_views; + +// Built with generate_views(false) + reflect_mode(VTable): proves owned-message +// vtable reflection is self-contained and does not require view generation. Only +// the owned `impl ReflectMessage` is emitted; there are no view impls. +message Simple { + int32 id = 1; + string name = 2; + repeated string tags = 3; +} diff --git a/buffa-test/protos/vtable_string_repr.proto b/buffa-test/protos/vtable_string_repr.proto new file mode 100644 index 0000000..b4cbfcb --- /dev/null +++ b/buffa-test/protos/vtable_string_repr.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package vtable_string_repr; + +// Built with `string_type(SmolStr)` + `reflect_mode(VTable)` to exercise the +// `ReflectElement for SmolStr` impl on the repeated-string element path. The +// owned `items` field is `Vec`; singular `name` is `SmolStr` (reflects +// via deref, no `ReflectElement`); the map stays `String`-keyed/valued. +message Labels { + string name = 1; + repeated string items = 2; + map attrs = 3; +} diff --git a/buffa-test/src/lib.rs b/buffa-test/src/lib.rs index 48d1755..b75edf7 100644 --- a/buffa-test/src/lib.rs +++ b/buffa-test/src/lib.rs @@ -9,6 +9,27 @@ pub mod basic { buffa::include_proto!("basic"); } +/// `string_type(SmolStr)` + vtable reflection — exercises `ReflectElement for +/// SmolStr` on the repeated-string element path. +#[allow( + clippy::derivable_impls, + clippy::match_single_binding, + non_camel_case_types +)] +pub mod vtable_string_repr { + buffa::include_proto!("vtable_string_repr"); +} + +/// `generate_views(false)` + vtable reflection — owned-only vtable, no views. +#[allow( + clippy::derivable_impls, + clippy::match_single_binding, + non_camel_case_types +)] +pub mod vtable_no_views { + buffa::include_proto!("vtable_no_views"); +} + #[allow( clippy::derivable_impls, clippy::match_single_binding, diff --git a/buffa-test/tests/reflect_vtable.rs b/buffa-test/tests/reflect_vtable.rs index 9f2c269..6c7f37e 100644 --- a/buffa-test/tests/reflect_vtable.rs +++ b/buffa-test/tests/reflect_vtable.rs @@ -8,7 +8,7 @@ //! reflection consumer holding raw bytes (an interceptor, a field-mask //! evaluator) uses. -use buffa::{Message, MessageView}; +use buffa::{Message, MessageView, OwnedView}; use buffa_descriptor::reflect::{MapKey, ReflectMessage, ValueRef}; use buffa_test::basic::*; @@ -221,3 +221,20 @@ fn vtable_to_dynamic_snapshot() { ValueRef::I32(42) )); } + +#[test] +fn vtable_owned_view_entry_point() { + // The entry point a reflection consumer holding raw wire bytes uses: wrap + // them in an `OwnedView` (lifetime-erased), reborrow to a tied-lifetime + // view, and reflect through `&dyn ReflectMessage`. + let bytes = buffa::bytes::Bytes::from(person_bytes()); + let owned = OwnedView::>::decode(bytes).expect("OwnedView::decode"); + let view = owned.reborrow(); + let r: &dyn ReflectMessage = view; + let md = r.message_descriptor(); + assert!(matches!(r.get(md.field(1).unwrap()), ValueRef::I32(42))); + assert!(matches!( + r.get(md.field(2).unwrap()), + ValueRef::String("Ada") + )); +} diff --git a/buffa-test/tests/reflectable.rs b/buffa-test/tests/reflectable.rs index b472625..178c6ca 100644 --- a/buffa-test/tests/reflectable.rs +++ b/buffa-test/tests/reflectable.rs @@ -107,6 +107,96 @@ fn for_each_set_visits_set_fields() { assert_eq!(seen, vec!["id", "score", "verified"]); } +#[test] +fn owned_vtable_matches_bridge_for_every_field() { + use buffa_descriptor::reflect::DynamicMessage; + use buffa_test::basic::{person, Inventory, Status}; + use std::sync::Arc; + + // A Person exercising every owned-vtable arm: scalars, string, bytes, + // bool, double, enum, nested message, repeated string + repeated scalar, + // explicit-presence optional, and a oneof. + let person = Person { + id: 42, + name: "Ada".into(), + avatar: vec![0xDE, 0xAD], + verified: true, + score: 9.5, + status: Status::ACTIVE.into(), + address: MessageField::some(Address { + street: "1 Main".into(), + zip_code: 12345, + ..Default::default() + }), + tags: vec!["x".into(), "y".into()], + lucky_numbers: vec![7, 11, 13], + maybe_age: Some(30), + contact: Some(person::Contact::Email("ada@example.com".into())), + ..Default::default() + }; + + // Compare the vtable reflection (reflect() → Borrowed(self)) against the + // bridge round-trip, field by field. `ValueRef` has no `PartialEq` (trait + // objects), so compare the owned `Value` snapshots. + let vt = person.reflect(); + let md = vt.message_descriptor(); + let pool = Arc::clone(vt.pool()); + let idx = pool.message_index("basic.Person").unwrap(); + let bridge = DynamicMessage::from_message(&person, Arc::clone(&pool), idx); + + for fd in md.fields() { + assert_eq!( + vt.has(fd), + bridge.has(fd), + "has() mismatch on field {}", + fd.number() + ); + if vt.has(fd) { + assert_eq!( + vt.get(fd).to_owned(), + bridge.get(fd).to_owned(), + "get() mismatch on field {}", + fd.number() + ); + } + } + + // Map fields (Inventory.stock: map) — separate message. + let mut stock = std::collections::HashMap::new(); + stock.insert("apples".to_string(), 3); + stock.insert("oranges".to_string(), 7); + let inv = Inventory { + stock, + ..Default::default() + }; + let inv_vt = inv.reflect(); + let inv_md = inv_vt.message_descriptor(); + let inv_idx = pool.message_index("basic.Inventory").unwrap(); + let inv_bridge = DynamicMessage::from_message(&inv, Arc::clone(&pool), inv_idx); + let stock_fd = inv_md + .fields() + .iter() + .find(|f| f.name() == "stock") + .unwrap(); + assert_eq!( + inv_vt.get(stock_fd).to_owned(), + inv_bridge.get(stock_fd).to_owned(), + "map field mismatch" + ); +} + +#[test] +fn reflect_borrows_in_vtable_mode() { + use buffa_descriptor::reflect::ReflectCow; + // basic.proto is generated with vtable mode, so `reflect()` borrows `self` + // directly (no `DynamicMessage` round-trip / heap allocation). + let person = Person { + id: 1, + ..Default::default() + }; + assert!(matches!(person.reflect(), ReflectCow::Borrowed(_))); +} + #[test] fn descriptor_pool_is_built_once() { // The pool is `OnceLock`-backed; multiple `reflect()` calls share it. diff --git a/buffa-test/tests/vtable_no_views.rs b/buffa-test/tests/vtable_no_views.rs new file mode 100644 index 0000000..ea865f2 --- /dev/null +++ b/buffa-test/tests/vtable_no_views.rs @@ -0,0 +1,31 @@ +//! Owned-message vtable reflection without view generation. +//! +//! `vtable_no_views.proto` is built with `generate_views(false)` + +//! `reflect_mode(VTable)`. The owned `impl ReflectMessage` is self-contained +//! (no view types involved), so `reflect()` borrows `self` and reads owned +//! fields directly — proving vtable mode does not require views. + +use buffa_descriptor::reflect::{ReflectCow, Reflectable, ValueRef}; +use buffa_test::vtable_no_views::Simple; + +#[test] +fn owned_vtable_reflects_without_views() { + let msg = Simple { + id: 7, + name: "x".into(), + tags: vec!["a".into(), "b".into()], + ..Default::default() + }; + + // Vtable mode: reflect() borrows self. + let r = msg.reflect(); + assert!(matches!(msg.reflect(), ReflectCow::Borrowed(_))); + + let md = r.message_descriptor(); + assert!(matches!(r.get(md.field(1).unwrap()), ValueRef::I32(7))); + assert!(matches!(r.get(md.field(2).unwrap()), ValueRef::String("x"))); + let ValueRef::List(tags) = r.get(md.field(3).unwrap()) else { + panic!("expected List") + }; + assert_eq!(tags.len(), 2); +} diff --git a/buffa-test/tests/vtable_string_repr.rs b/buffa-test/tests/vtable_string_repr.rs new file mode 100644 index 0000000..f86a96c --- /dev/null +++ b/buffa-test/tests/vtable_string_repr.rs @@ -0,0 +1,44 @@ +//! Vtable reflection over a message generated with `string_type(SmolStr)`. +//! +//! The repeated-string field is `Vec` in the owned struct, so its +//! reflective `get()` (`ValueRef::List(&self.items)`) relies on +//! `ReflectElement for SmolStr` in `buffa-descriptor` (feature `smol_str`). +//! Singular string fields reflect via deref regardless of the repr, and map +//! string keys/values stay `String`. + +use buffa_descriptor::reflect::{Reflectable, ValueRef}; +use buffa_test::vtable_string_repr::Labels; + +#[test] +fn smol_str_repeated_field_reflects() { + let labels = Labels { + name: "svc".into(), + items: vec!["a".into(), "bb".into(), "ccc".into()], + ..Default::default() + }; + + let r = labels.reflect(); + let md = r.message_descriptor(); + + // Singular SmolStr string (field 1) — reflects via deref. + assert!(matches!( + r.get(md.field(1).unwrap()), + ValueRef::String("svc") + )); + + // Repeated SmolStr (field 2) — the element path through ReflectElement. + let ValueRef::List(items) = r.get(md.field(2).unwrap()) else { + panic!("expected List") + }; + assert_eq!(items.len(), 3); + assert!(matches!(items.get(0), Some(ValueRef::String("a")))); + assert!(matches!(items.get(2), Some(ValueRef::String("ccc")))); + + let mut collected = Vec::new(); + items.for_each(&mut |v| { + if let ValueRef::String(s) = v { + collected.push(s.to_string()); + } + }); + assert_eq!(collected, vec!["a", "bb", "ccc"]); +} diff --git a/buffa-types/Cargo.toml b/buffa-types/Cargo.toml index 632820d..9a99d10 100644 --- a/buffa-types/Cargo.toml +++ b/buffa-types/Cargo.toml @@ -22,7 +22,7 @@ arbitrary = ["dep:arbitrary", "buffa/arbitrary"] # etc.) and owned types. Pulls `buffa-descriptor` and requires `std` (the # embedded descriptor pool uses `std::sync::OnceLock`). Off by default so # `no_std` consumers and those that don't reflect pay nothing. -reflect = ["dep:buffa-descriptor", "buffa-descriptor/reflect", "std"] +reflect = ["dep:buffa-descriptor", "buffa-descriptor/reflect", "buffa-descriptor/std", "std"] [dependencies] arbitrary = { workspace = true, optional = true } diff --git a/buffa-types/src/generated/google.protobuf.any.rs b/buffa-types/src/generated/google.protobuf.any.rs index c8d3ce9..276ba07 100644 --- a/buffa-types/src/generated/google.protobuf.any.rs +++ b/buffa-types/src/generated/google.protobuf.any.rs @@ -165,48 +165,102 @@ impl ::buffa::DefaultInstance for Any { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Any { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Any { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::String(&self.type_url), + 2u32 => ::buffa_descriptor::reflect::ValueRef::Bytes(&self.value[..]), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.type_url.is_empty(), + 2u32 => !self.value.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Any { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Any { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Any { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Any { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Any"; diff --git a/buffa-types/src/generated/google.protobuf.duration.rs b/buffa-types/src/generated/google.protobuf.duration.rs index 811bf90..a117497 100644 --- a/buffa-types/src/generated/google.protobuf.duration.rs +++ b/buffa-types/src/generated/google.protobuf.duration.rs @@ -108,48 +108,102 @@ impl ::buffa::DefaultInstance for Duration { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Duration { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Duration { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::I64(self.seconds), + 2u32 => ::buffa_descriptor::reflect::ValueRef::I32(self.nanos), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.seconds != 0, + 2u32 => self.nanos != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Duration { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Duration { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Duration { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Duration { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Duration"; diff --git a/buffa-types/src/generated/google.protobuf.empty.rs b/buffa-types/src/generated/google.protobuf.empty.rs index f3adc0d..5cd41b2 100644 --- a/buffa-types/src/generated/google.protobuf.empty.rs +++ b/buffa-types/src/generated/google.protobuf.empty.rs @@ -35,48 +35,98 @@ impl ::buffa::DefaultInstance for Empty { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Empty { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Empty { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Empty { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Empty { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Empty { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Empty { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Empty"; diff --git a/buffa-types/src/generated/google.protobuf.field_mask.rs b/buffa-types/src/generated/google.protobuf.field_mask.rs index 592ff41..37c9173 100644 --- a/buffa-types/src/generated/google.protobuf.field_mask.rs +++ b/buffa-types/src/generated/google.protobuf.field_mask.rs @@ -253,48 +253,100 @@ impl ::buffa::DefaultInstance for FieldMask { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for FieldMask { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for FieldMask { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::List(&self.paths), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.paths.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for FieldMask { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl FieldMask { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for FieldMask { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for FieldMask { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "FieldMask"; diff --git a/buffa-types/src/generated/google.protobuf.mod.rs b/buffa-types/src/generated/google.protobuf.mod.rs index abd823e..1a01986 100644 --- a/buffa-types/src/generated/google.protobuf.mod.rs +++ b/buffa-types/src/generated/google.protobuf.mod.rs @@ -43,8 +43,9 @@ pub mod __buffa { include!("google.protobuf.struct.__oneof.rs"); } #[cfg(feature = "reflect")] - /// Reflection support: embedded descriptor pool for bridge-mode - /// [`Reflectable`](::buffa_descriptor::reflect::Reflectable) impls. + /// Reflection support: embedded descriptor pool shared by this + /// package's [`Reflectable`](::buffa_descriptor::reflect::Reflectable) + /// and `ReflectMessage` impls (bridge and vtable mode alike). pub mod reflect { /// The serialized `FileDescriptorSet` for this codegen run, /// including transitive dependencies. Used to build the diff --git a/buffa-types/src/generated/google.protobuf.struct.rs b/buffa-types/src/generated/google.protobuf.struct.rs index c2cb8f7..af2ebbd 100644 --- a/buffa-types/src/generated/google.protobuf.struct.rs +++ b/buffa-types/src/generated/google.protobuf.struct.rs @@ -84,48 +84,100 @@ impl ::buffa::DefaultInstance for Struct { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Struct { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Struct { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::Map(&self.fields), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.fields.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Struct { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Struct { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Struct { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Struct { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Struct"; @@ -404,48 +456,204 @@ impl ::buffa::DefaultInstance for Value { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Value { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Value { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::NullValue(v), + ) => { + ::buffa_descriptor::reflect::ValueRef::EnumNumber(v.to_i32()) + } + _ => ::buffa_descriptor::reflect::ValueRef::EnumNumber(0), + } + } + 2u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::NumberValue(v), + ) => ::buffa_descriptor::reflect::ValueRef::F64(*v), + _ => ::buffa_descriptor::reflect::ValueRef::F64(0.0), + } + } + 3u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::StringValue(v), + ) => ::buffa_descriptor::reflect::ValueRef::String(v), + _ => ::buffa_descriptor::reflect::ValueRef::String(""), + } + } + 4u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::BoolValue(v), + ) => ::buffa_descriptor::reflect::ValueRef::Bool(*v), + _ => ::buffa_descriptor::reflect::ValueRef::Bool(false), + } + } + 5u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::StructValue(v), + ) => { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(&**v), + ) + } + _ => { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed( + ::default_instance(), + ), + ) + } + } + } + 6u32 => { + match &self.kind { + ::core::option::Option::Some( + __buffa::oneof::value::Kind::ListValue(v), + ) => { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(&**v), + ) + } + _ => { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed( + ::default_instance(), + ), + ) + } + } + } + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::NullValue(_)) + ) + } + 2u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::NumberValue(_)) + ) + } + 3u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::StringValue(_)) + ) + } + 4u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::BoolValue(_)) + ) + } + 5u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::StructValue(_)) + ) + } + 6u32 => { + ::core::matches!( + & self.kind, + ::core::option::Option::Some(__buffa::oneof::value::Kind::ListValue(_)) + ) + } + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Value { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Value { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Value { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Value { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Value"; @@ -864,48 +1072,100 @@ impl ::buffa::DefaultInstance for ListValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for ListValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for ListValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::List(&self.values), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.values.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for ListValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl ListValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for ListValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for ListValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "ListValue"; diff --git a/buffa-types/src/generated/google.protobuf.timestamp.rs b/buffa-types/src/generated/google.protobuf.timestamp.rs index 9001ec2..f3abe5a 100644 --- a/buffa-types/src/generated/google.protobuf.timestamp.rs +++ b/buffa-types/src/generated/google.protobuf.timestamp.rs @@ -143,48 +143,102 @@ impl ::buffa::DefaultInstance for Timestamp { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Timestamp { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Timestamp { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::I64(self.seconds), + 2u32 => ::buffa_descriptor::reflect::ValueRef::I32(self.nanos), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.seconds != 0, + 2u32 => self.nanos != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Timestamp { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Timestamp { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Timestamp { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Timestamp { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Timestamp"; diff --git a/buffa-types/src/generated/google.protobuf.wrappers.rs b/buffa-types/src/generated/google.protobuf.wrappers.rs index 91585d9..0d8dacb 100644 --- a/buffa-types/src/generated/google.protobuf.wrappers.rs +++ b/buffa-types/src/generated/google.protobuf.wrappers.rs @@ -33,48 +33,100 @@ impl ::buffa::DefaultInstance for DoubleValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for DoubleValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for DoubleValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::F64(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0.0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for DoubleValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl DoubleValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for DoubleValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for DoubleValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "DoubleValue"; @@ -221,48 +273,100 @@ impl ::buffa::DefaultInstance for FloatValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for FloatValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for FloatValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::F32(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0.0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for FloatValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl FloatValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for FloatValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for FloatValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "FloatValue"; @@ -409,48 +513,100 @@ impl ::buffa::DefaultInstance for Int64Value { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Int64Value { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Int64Value { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::I64(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Int64Value { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Int64Value { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Int64Value { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Int64Value { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Int64Value"; @@ -597,48 +753,100 @@ impl ::buffa::DefaultInstance for UInt64Value { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for UInt64Value { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for UInt64Value { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::U64(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for UInt64Value { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl UInt64Value { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for UInt64Value { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for UInt64Value { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "UInt64Value"; @@ -785,48 +993,100 @@ impl ::buffa::DefaultInstance for Int32Value { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for Int32Value { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for Int32Value { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::I32(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for Int32Value { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl Int32Value { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for Int32Value { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for Int32Value { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "Int32Value"; @@ -973,48 +1233,100 @@ impl ::buffa::DefaultInstance for UInt32Value { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for UInt32Value { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for UInt32Value { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::U32(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value != 0, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for UInt32Value { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl UInt32Value { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for UInt32Value { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for UInt32Value { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "UInt32Value"; @@ -1161,48 +1473,100 @@ impl ::buffa::DefaultInstance for BoolValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for BoolValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for BoolValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::Bool(self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => self.value, + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for BoolValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl BoolValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for BoolValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for BoolValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "BoolValue"; @@ -1349,48 +1713,100 @@ impl ::buffa::DefaultInstance for StringValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for StringValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for StringValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::String(&self.value), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.value.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for StringValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl StringValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for StringValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for StringValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "StringValue"; @@ -1540,48 +1956,100 @@ impl ::buffa::DefaultInstance for BytesValue { } } #[cfg(feature = "reflect")] -impl ::buffa_descriptor::reflect::Reflectable for BytesValue { - /// Bridge-mode reflective handle: encodes `self` and decodes - /// it into a [`DynamicMessage`](::buffa_descriptor::reflect::DynamicMessage) - /// against the package's embedded descriptor pool. - /// - /// # Performance - /// - /// One full encode/decode round-trip plus a heap allocation per - /// call. Hold onto the returned handle for repeated field reads - /// rather than calling `reflect()` per field. - /// - /// # Panics - /// - /// Panics if the embedded `FileDescriptorSet` is malformed or - /// `Self::FULL_NAME` is not registered. Both indicate codegen - /// emitted inconsistent output, not consumer misuse — except - /// when this type was re-exported from a different - /// `buffa-build` invocation, whose pool is a different - /// instance. Each `generate_reflection(true)` codegen run - /// embeds its own pool; do not mix `reflect()` calls across - /// independently-generated crates. - fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { - let pool = __buffa::reflect::descriptor_pool(); - let idx = pool - .message_index(::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, - ), +const _: () = { + impl ::buffa_descriptor::reflect::ReflectMessage for BytesValue { + fn message_descriptor(&self) -> &::buffa_descriptor::MessageDescriptor { + __buffa::reflect::descriptor_pool() + .message(Self::__buffa_reflect_message_index()) + } + fn pool( + &self, + ) -> &::buffa::alloc::sync::Arc<::buffa_descriptor::DescriptorPool> { + __buffa::reflect::descriptor_pool() + } + fn unknown_fields(&self) -> &::buffa::UnknownFields { + &self.__buffa_unknown_fields + } + fn get( + &self, + field: &::buffa_descriptor::FieldDescriptor, + ) -> ::buffa_descriptor::reflect::ValueRef<'_> { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match field.number() { + 1u32 => ::buffa_descriptor::reflect::ValueRef::Bytes(&self.value[..]), + _ => { + ::core::debug_assert!( + false, + "field number {} is not a member of this message's reflect get()", + field.number(), + ); + ::buffa_descriptor::reflect::ValueRef::Bool(false) + } + } + } + fn has(&self, field: &::buffa_descriptor::FieldDescriptor) -> bool { + match field.number() { + 1u32 => !self.value.is_empty(), + _ => false, + } + } + fn for_each_set( + &self, + f: &mut dyn ::core::ops::FnMut( + &::buffa_descriptor::FieldDescriptor, + ::buffa_descriptor::reflect::ValueRef<'_>, ), - ) + ) { + let md = ::buffa_descriptor::reflect::ReflectMessage::message_descriptor( + self, + ); + for fd in md.fields() { + if ::buffa_descriptor::reflect::ReflectMessage::has(self, fd) { + f(fd, ::buffa_descriptor::reflect::ReflectMessage::get(self, fd)); + } + } + } + fn to_dynamic(&self) -> ::buffa_descriptor::reflect::DynamicMessage { + ::buffa_descriptor::reflect::DynamicMessage::from_message( + self, + ::buffa::alloc::sync::Arc::clone(__buffa::reflect::descriptor_pool()), + Self::__buffa_reflect_message_index(), + ) + } } -} + impl ::buffa_descriptor::reflect::ReflectElement for BytesValue { + fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> { + ::buffa_descriptor::reflect::ValueRef::Message( + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self), + ) + } + } + impl BytesValue { + /// Memoized `MessageIndex` for this message type, resolved once + /// against the package's embedded descriptor pool. + #[doc(hidden)] + fn __buffa_reflect_message_index() -> ::buffa_descriptor::MessageIndex { + static IDX: ::std::sync::OnceLock<::buffa_descriptor::MessageIndex> = ::std::sync::OnceLock::new(); + *IDX + .get_or_init(|| { + __buffa::reflect::descriptor_pool() + .message_index(::FULL_NAME) + .expect( + "generated message is registered in the embedded descriptor pool", + ) + }) + } + } + impl ::buffa_descriptor::reflect::Reflectable for BytesValue { + /// Vtable-mode reflective handle: borrows `self` directly. No + /// encode/decode round-trip and no allocation — the reflective + /// accessors read this message's fields in place. + fn reflect(&self) -> ::buffa_descriptor::reflect::ReflectCow<'_> { + ::buffa_descriptor::reflect::ReflectCow::Borrowed(self) + } + } +}; impl ::buffa::MessageName for BytesValue { const PACKAGE: &'static str = "google.protobuf"; const NAME: &'static str = "BytesValue"; diff --git a/conformance/build.rs b/conformance/build.rs index 1530db0..f33af85 100644 --- a/conformance/build.rs +++ b/conformance/build.rs @@ -32,8 +32,7 @@ fn main() { .includes(&["protos/"]) .generate_json(true) .generate_text(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .gate_reflect_on_crate_feature(true) .compile() .expect("buffa_build failed for test_messages_proto3.proto"); @@ -45,8 +44,7 @@ fn main() { .generate_json(true) .generate_text(true) .allow_message_set(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .gate_reflect_on_crate_feature(true) .compile() .expect("buffa_build failed for test_messages_proto2.proto"); @@ -59,8 +57,7 @@ fn main() { .includes(&[&protos_dir]) .generate_json(true) .generate_text(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .gate_reflect_on_crate_feature(true) .compile() .expect("buffa_build failed for test_messages_proto3_editions.proto"); @@ -72,8 +69,7 @@ fn main() { .generate_json(true) .generate_text(true) .allow_message_set(true) - .generate_reflection(true) - .generate_reflection_vtable(true) + .reflect_mode(buffa_build::ReflectMode::VTable) .gate_reflect_on_crate_feature(true) .compile() .expect("buffa_build failed for test_messages_proto2_editions.proto"); diff --git a/docs/investigations/reflection-vtable.md b/docs/investigations/reflection-vtable.md index 058dab4..73e1c0d 100644 --- a/docs/investigations/reflection-vtable.md +++ b/docs/investigations/reflection-vtable.md @@ -360,38 +360,58 @@ will use. ## Sequencing -Step 1 (`ValueRef::List`/`Map` trait objects) has landed — see the Progress -note. The remaining work, as a sequence of self-contained PRs each within the -≤250-net-line budget: +**All steps below have landed.** The feature set the original plan called for — +view *and* owned vtable reflection, the public `ReflectMode` selector, and the +acceptance tests — is complete and conformance-validated. One unanticipated +prerequisite surfaced during implementation (WKT view reflection, step 3.5 +below). The remaining open items are noted under "Not yet done" at the end. -1. ✅ **`ValueRef::List`/`Map` trait-object refactor.** Done. `ValueRef` carries +1. ✅ **`ValueRef::List`/`Map` trait-object refactor.** `ValueRef` carries `&dyn ReflectList` / `&dyn ReflectMap`; `DynamicMessage` implements the - bridge side; conformance passes. -2. **Runtime container impls for the view types (§3).** `ReflectElement` / - `ReflectMapKey` traits plus blanket `ReflectList` / `ReflectMap` for - `RepeatedView` / `MapView`, in `buffa-descriptor`. Independent of codegen and - testable with hand-written views. Do this first — it de-risks the rest. -3. **`impl ReflectMessage for FooView<'a>` codegen + per-message `MessageIndex` - memoization (§1, §2).** The main deliverable, and the only high-risk PR. - Land it behind an internal mode flag so it merges without flipping any - default. Oneof dispatch and enum-number extraction are the tricky `get()` - arms. -4. **`ReflectMode::VTable` plumbing (§4).** `BuildConfig::reflect_mode`, the - `CodeGenConfig` field, `feature_gates`, the `protoc-gen-buffa` option, and - the vtable `Reflectable::reflect()` body (`ReflectCow::Borrowed(self)`). - Once (3) is solid. -5. **Acceptance: conformance via-vtable run + `OwnedView` entry-point test - (§5).** A `BUFFA_VIA_VTABLE` runner mode (decode_view → reflect → reflective - serialize), parallel to the existing `BUFFA_VIEW_JSON` / `BUFFA_VIA_REFLECT` - modes, plus the `OwnedView` → `&dyn ReflectMessage` test. Mostly test code; - a strong correctness gate. -6. **Owned-message vtable (optional follow-up).** `impl ReflectMessage` for the - owned struct, for the interceptor use case that holds an in-memory message - rather than wire bytes. Needs container impls for owned collections - (`Vec`), which forces the coherence resolution noted in §3: implement - `ReflectElement for Value` and drop the bespoke `impl ReflectList for - Vec`, unifying both paths. Scoped after views because views are the - zero-copy headline and owned vtable touches landed bridge code. + bridge side. +2. ✅ **Runtime container impls (§3).** `ReflectElement` / `ReflectMapKey` traits + plus generic `ReflectList` / `ReflectMap` for `RepeatedView` / `MapView` + (and, for owned vtable, `Vec` / `HashMap`), in `buffa-descriptor`. +3. ✅ **`impl ReflectMessage for FooView<'a>` codegen + memoized `MessageIndex` + (§1, §2).** Behind an internal mode flag; oneof dispatch and enum-number + extraction handled. +3.5. ✅ **WKT view reflection (unplanned prerequisite).** The conformance corpus + references well-known types, whose views live in `buffa-types` with no path + to `ReflectMessage`. Added a `reflect` feature to `buffa-types` (gated via the + new `gate_reflect_on_crate_feature` codegen flag) so WKT views/owned types + implement `ReflectMessage`. Without this, vtable mode only worked for + WKT-free protos. +4. ✅ **`ReflectMode` plumbing (§4).** Public `ReflectMode` enum + (Off/Bridge/VTable), exposed as `buffa_build::Config::reflect_mode` and + `protoc-gen-buffa`'s `reflect_mode=` option. Vtable mode emits + `impl ReflectMessage` for the view *and* the owned message and switches the + owned `Reflectable::reflect()` body to `ReflectCow::Borrowed(self)`. Default + stays `Off`/`Bridge`; flipping the default to `VTable` is deferred. +5. ✅ **Acceptance: conformance via-vtable run + `OwnedView` entry-point test + (§5).** `BUFFA_VIA_VTABLE` runner mode (decode_view → reflect → rebuild → + JSON) passes 1246 binary→JSON cases across proto2/proto3/editions with zero + failures, parallel to `BUFFA_VIEW_JSON` / `BUFFA_VIA_REFLECT`. The + `OwnedView` → `&dyn ReflectMessage` test and an owned-vtable↔bridge parity + test (every field kind) lock in correctness. +6. ✅ **Owned-message vtable (§6).** `impl ReflectMessage` for the owned struct; + `owned.reflect()` borrows `self` with no round-trip (the interceptor use + case). Required the coherence resolution noted in §3: `ReflectElement for + Value` and dropping the bespoke `impl ReflectList for Vec`. owned + `unknown_fields()` is overridden to preserve unknowns (bridge parity). + +### Not yet done + +- **Benchmark numbers in the README.** The `reflect` bench gained vtable cases + (6–10× over bridge, ~4× over pure `DynamicMessage`); regenerating the README + charts through the pinned Xeon harness is outstanding. + +### Done since + +- ✅ **`VTable` is the default reflection mode.** `generate_reflection(true)` (and + `protoc-gen-buffa`'s `reflection=true`) now select `VTable`; `Bridge` is opt-in + via `reflect_mode(ReflectMode::Bridge)`. Vtable no longer requires views — the + owned `impl ReflectMessage` is self-contained, so views-off builds get + owned-only vtable reflection rather than an error. ## What this does *not* solve diff --git a/protoc-gen-buffa/src/main.rs b/protoc-gen-buffa/src/main.rs index cc4c337..7e5ad1f 100644 --- a/protoc-gen-buffa/src/main.rs +++ b/protoc-gen-buffa/src/main.rs @@ -193,7 +193,32 @@ fn parse_config(params: &str) -> Result { // methods. Like `register_types`, the default is on, so the // accepted spelling is the negation. "with_setters" => codegen.generate_with_setters = value.trim() != "false", - "reflection" => codegen.generate_reflection = value.trim() == "true", + // `reflection=true` selects the fast vtable mode (same as + // `reflect_mode=vtable`); `reflect_mode=bridge` opts into the + // smaller round-trip implementation. + "reflection" => { + let mode = if value.trim() == "true" { + buffa_codegen::ReflectMode::VTable + } else { + buffa_codegen::ReflectMode::Off + }; + mode.apply(&mut codegen); + } + // `reflect_mode=off|bridge|vtable` is the fuller form of + // `reflection=`. `vtable` additionally emits `impl ReflectMessage` + // on owned + view types and makes `reflect()` borrow `self`. + "reflect_mode" => match value.trim() { + "off" => buffa_codegen::ReflectMode::Off.apply(&mut codegen), + "bridge" => buffa_codegen::ReflectMode::Bridge.apply(&mut codegen), + "vtable" => buffa_codegen::ReflectMode::VTable.apply(&mut codegen), + other => { + eprintln!( + "protoc-gen-buffa: invalid reflect_mode '{}', \ + expected off, bridge, or vtable", + other + ); + } + }, "file_per_package" => codegen.file_per_package = value.trim() == "true", "extern_path" => { // value is "="