Reflection: view-type vtable + dynamic container plumbing#148
Merged
Conversation
Add the runtime half of vtable-mode reflection: ReflectList/ReflectMap impls for the zero-copy view containers RepeatedView and MapView, so a generated view's reflective get() can return a borrowed list/map without materializing a Vec<Value>. Introduce two helper traits, ReflectElement and ReflectMapKey, with concrete impls for scalars, &str, &[u8], and EnumValue<E>. A trait-bound blanket over ReflectMessage is not possible — it would overlap the scalar impls under Rust's coherence rules — so codegen will emit a one-line ReflectElement impl per generated view and enum in a later change. Map reflection deduplicates duplicate wire entries (last-write-wins) via MapView::iter_unique to match the bridge path's distinct-key semantics, keeping len() and for_each() consistent across both reflection modes. Also record the landed ValueRef::List/Map refactor and the coherence constraint in the vtable design doc.
Generate `impl ReflectMessage for FooView<'a>` directly on view types when both reflection and the internal vtable flag are set. get()/has() read view struct fields through a per-field match — no encode/decode round-trip and no DynamicMessage — covering scalars, string/bytes, enums, nested messages, repeated, map, optional presence, and oneof members. for_each_set walks the descriptor; to_dynamic falls back to a bridge-style snapshot. Also emit `impl ReflectElement` for view types and generated enums so a RepeatedView/MapView of messages or closed enums reflects through the generic container impls. Per-message MessageIndex is memoized via an inherent associated fn (collision-free across sibling views in a shared module). Gated behind a new internal CodeGenConfig flag, exposed experimentally as buffa_build::Config::generate_reflection_vtable. Bridge mode and the reflect() call-site contract are unchanged.
Make the WKT view types (TimestampView, DurationView, AnyView, StructView, ValueView, wrappers, etc.) implement ReflectMessage, so a message that has a WKT field can reflect over it. Previously vtable reflection only worked for protos that did not reference WKTs, because the WKT views lived in buffa-types with no path to ReflectMessage. The reflection surface pulls a buffa-descriptor dependency and requires std (the embedded descriptor pool uses OnceLock), so it is gated behind a new buffa-types `reflect` feature; views and text stay unconditional. This is enabled by a targeted codegen flag, gate_reflect_on_crate_feature, which gates only the reflection impls — unlike gate_impls_on_crate_features, which gates json/views/text/reflect together and would have forced buffa-types consumers to opt into views/text. All seven WKT protos share the google.protobuf package, so one embedded descriptor pool backs every WKT view's reflection.
Add a sixth conformance run that exercises the generated `impl ReflectMessage for FooView`: decode a view, walk its vtable reflection surface (for_each_set/get) to rebuild a DynamicMessage, and serialize that to JSON. This reuses DynamicMessage's JSON serializer — which passes the corpus cleanly under BUFFA_VIA_REFLECT — so any failure isolates a bug in the vtable get/has surface rather than in JSON formatting. Binary and text output are skipped (the reflect rebuild drops unknown fields, which JSON omits anyway). The run passes 1246 binary->JSON conformance tests with zero failures across proto2/proto3/editions, so known_failures_view_vtable.txt is empty. Vtable reflection is enabled on the four view-bearing conformance protos and gated behind the conformance `reflect` feature (via the new buffa_build::Config::gate_reflect_on_crate_feature), so the no_std binary omits it. Also fix a latent needless-return in process_editions surfaced by clippy once the editions protos are present, and correct the stale conformance-run count in CONTRIBUTING (four runs -> six, including the previously undocumented via-reflect run).
Extend the reflection benchmark with the zero-copy view path so the vtable
ReflectMessage work can be measured against the alternatives:
- decode/view — decode_view alone (no reflection), the zero-copy floor.
- reflect/{vtable,bridge,dynamic}_read_{one,all} — from wire bytes, obtain a
reflective handle and read one field / all set fields, comparing the view
vtable path, the bridge round-trip, and pure DynamicMessage reflection.
Enables generate_reflection_vtable on the bench types so the view types
implement ReflectMessage. Measurements: vtable view reflection runs ~6-10x
faster than the bridge round-trip and ~4x faster than pure DynamicMessage
reflection, because it is dominated by decode_view, which is itself cheaper
than owned decode (borrowed strings/bytes, no per-field allocation).
|
All contributors have signed the CLA ✍️ ✅ |
This was referenced May 23, 2026
asacamano
previously approved these changes
May 24, 2026
Resolves conflicts from the idiomatic enum aliases (#152) and module collision fix (#145) landing on main while the vtable reflection stack was open: - enumeration.rs: keep both the vtable `ReflectElement` impl for closed enums and the idiomatic-alias doc-note wrapping of `enum_doc`. - codegen lib.rs: keep all three new config fields (generate_reflection_vtable, gate_reflect_on_crate_feature, idiomatic_enum_aliases); move the vtable-config validation into the new generate_with_diagnostics so both entry points enforce it. - buffa-build: keep both new builder methods. - buffa-test/build.rs: keep the new modcollide/modrace build blocks and the vtable-aware proto2 comment. Generated WKT/bootstrap code regenerated; matches the textual merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of three stacked PRs adding vtable-mode reflection — generated types implement
ReflectMessagedirectly, so reflective field access reads struct fields in place with noDynamicMessageround-trip. This PR lands the view-type path and the shared plumbing.What's here
buffa-descriptor:ReflectElement(element →ValueRef) andReflectMapKey(key →MapKeyRef), with genericReflectList/ReflectMapimpls overRepeatedView/MapView.ValueRef::List/Mapnow carry&dyn ReflectList/&dyn ReflectMap.impl ReflectMessage for FooView<'a>(get/has/for_each_set/to_dynamic) plus a memoized per-messageMessageIndex, behind an internal mode flag.reflectfeature onbuffa-typesso well-known-type views implementReflectMessage(needed by any message that embeds a WKT).BUFFA_VIA_VTABLErun that decodes a view, walks itsReflectMessagesurface to rebuild aDynamicMessage, and serializes that to JSON.Validation
via-vtableconformance suite passes all 1246 binary→JSON cases across proto2/proto3/editions with zero failures (12CONFORMANCE SUITE PASSEDlines total).cargo test --workspace --all-features, clippy-D warnings, and markdownlint all green.Stack
ReflectModepublic API + vtable-by-default (Reflection: owned-message vtable, ReflectMode, vtable by default #149)Owned-type reflection still uses the bridge round-trip after this PR; that flips in PR 2.