You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace today's [[clang::annotate(\"pb::field=N\")]] string-encoded annotations with the proper pb::* namespace-scoped vocabulary specified in tasks/h5cpp-compiler-pb-attribute-taxonomy.md in the workspace. Phase 1 of a 5-phase migration; this issue covers Phase 1 only.
Background
Today's 30-pb-producer ships four string-encoded annotations parsed by src/consumer_pb.hpp:181-244:
Today
Target form (Phase B / C++17 standard-attribute syntax)
Phase C / C++26 reflection
[[clang::annotate(\"pb::field=2\")]]
[[pb::field(2)]]
[[=pb::field{2}]]
[[clang::annotate(\"pb::oneof_tags=5,6,7\")]]
[[pb::field(5, 6, 7)]](variadic on std::variant)
[[=pb::field{5, 6, 7}]]
[[clang::annotate(\"pb::wire=sint32\")]]
[[pb::wire(sint32)]]
[[=pb::wire{pb::spec::sint32}]]
[[clang::annotate(\"pb::adapter=Timestamp\")]]
[[pb::adapter(Timestamp)]]
[[=pb::adapter{pb::adapter_kind::Timestamp}]]
The taxonomy doc (sections 1-2, 5, 6) specifies the full vocabulary, the C++26 typed-value sketches, and the permissive int-or-enum-tag-argument policy:
`pb::field(...)` accepts integer literals, enum values, and any mix of the two. We are the guides — this is the user's story. The library's job is to clear the path, not to choose the route.
Scope (Phase 1 only)
In: the four field-level attributes that exist today (field, oneof_tags/variadic field, wire, adapter). Plus the int-or-enum tag-argument policy.
New syntax accepted. Fixtures using [[pb::field(N)]], [[pb::field(N1, N2, ...)]], [[pb::wire(spec)]], [[pb::adapter(name)]] parse and emit the same pb::meta::descriptor_t<T> specialization as today's [[clang::annotate(\"pb::...\")]] fixtures.
Old syntax still accepted. Existing tests/fixtures/pb_*.cpp continue to pass without modification — both forms coexist during a transition window.
Enum tag arguments work. A fixture like:
```cpp
enum class user_profile_tag : std::uint32_t { name = 1, scores = 2 };
struct user_profile_t {
[[pb::field(user_profile_tag::name)]] std::string name;
[[pb::field(user_profile_tag::scores)]] std::map<std::string, std::int32_t> scores;
};
```
emits the same descriptor as the integer-literal form.
Mixed forms within a class allowed. No compile-error for [[pb::field(my_tag::name)]] next to [[pb::field(7)]]. Permissive default per taxonomy §6.
Argument validation diagnostics. Tag values outside [1, 2^29-1], or inside protoc's reserved [19000, 19999], emit a clean compiler diagnostic at h5cpp-compiler time (open question ci, add GitHub Actions CI with compiler/OS matrix #5 in the taxonomy doc).
Test coverage. Add new fixtures under tests/fixtures/ exercising:
Each attribute in the new form (4 fixtures, mirroring existing pb_* fixtures)
Enum-tag form on one fixture
Mixed int/enum form on one fixture
Pre-existing fixtures (pb_primitives.cpp, etc.) preserved unchanged as the regression net for the clang::annotate path.
Zero impact on emitted descriptor output. Both forms produce byte-identical generated headers when measured against the existing golden tests.
Implementation paths to evaluate
Path A — Enrich clang::annotate with multi-arg form (mechanical). Keep the clang::annotate envelope; switch the encoding from string-equality (\"pb::field=2\") to multi-arg (\"pb::field\", 2). The integer arg becomes a Clang Expr* accessible via Expr::EvaluateAsInt(ctx), which handles both integer literals and enum constant references uniformly. Keeps today's ugly outer syntax ([[clang::annotate(...)]]) but delivers enum support without plugin work. Suitable as Phase 1a.
Path B — Clang plugin registering the pb::* attribute namespace (substantial). Implements the clean [[pb::field(N)]] syntax by extending Clang's attribute parser via the libtooling plugin API. Delivers the actual target syntax. Plugin development effort; depends on a stable Clang plugin ABI (we're on Clang 20.1.8). Suitable as Phase 1b.
Recommendation: ship Path A first as a smaller, lower-risk increment that unblocks enum tag arguments. Path B (clean syntax) follows once Path A is stable; Phase 5 (C++26 reflection) eventually obsoletes both.
Branch + worktree
Issue number: this one
Branch: `-pb-attribute-vocabulary` cut from `staging` per CLAUDE.md flow
Goal
Replace today's
[[clang::annotate(\"pb::field=N\")]]string-encoded annotations with the properpb::*namespace-scoped vocabulary specified intasks/h5cpp-compiler-pb-attribute-taxonomy.mdin the workspace. Phase 1 of a 5-phase migration; this issue covers Phase 1 only.Background
Today's
30-pb-producerships four string-encoded annotations parsed bysrc/consumer_pb.hpp:181-244:[[clang::annotate(\"pb::field=2\")]][[pb::field(2)]][[=pb::field{2}]][[clang::annotate(\"pb::oneof_tags=5,6,7\")]][[pb::field(5, 6, 7)]](variadic onstd::variant)[[=pb::field{5, 6, 7}]][[clang::annotate(\"pb::wire=sint32\")]][[pb::wire(sint32)]][[=pb::wire{pb::spec::sint32}]][[clang::annotate(\"pb::adapter=Timestamp\")]][[pb::adapter(Timestamp)]][[=pb::adapter{pb::adapter_kind::Timestamp}]]The taxonomy doc (sections 1-2, 5, 6) specifies the full vocabulary, the C++26 typed-value sketches, and the permissive int-or-enum-tag-argument policy:
Scope (Phase 1 only)
In: the four field-level attributes that exist today (
field,oneof_tags/variadicfield,wire,adapter). Plus the int-or-enum tag-argument policy.Out: universal
pb::name/pb::ignore/pb::doc/pb::on_missing(Phase 2). The.prototext emitter (Phase 3). Tier-2/3/4 attributes (Phase 4). C++26 reflection (Phase 5).Acceptance criteria
[[pb::field(N)]],[[pb::field(N1, N2, ...)]],[[pb::wire(spec)]],[[pb::adapter(name)]]parse and emit the samepb::meta::descriptor_t<T>specialization as today's[[clang::annotate(\"pb::...\")]]fixtures.tests/fixtures/pb_*.cppcontinue to pass without modification — both forms coexist during a transition window.```cpp
enum class user_profile_tag : std::uint32_t { name = 1, scores = 2 };
struct user_profile_t {
[[pb::field(user_profile_tag::name)]] std::string name;
[[pb::field(user_profile_tag::scores)]] std::map<std::string, std::int32_t> scores;
};
```
emits the same descriptor as the integer-literal form.
[[pb::field(my_tag::name)]]next to[[pb::field(7)]]. Permissive default per taxonomy §6.[1, 2^29-1], or inside protoc's reserved[19000, 19999], emit a clean compiler diagnostic at h5cpp-compiler time (open question ci, add GitHub Actions CI with compiler/OS matrix #5 in the taxonomy doc).tests/fixtures/exercising:Pre-existing fixtures (
pb_primitives.cpp, etc.) preserved unchanged as the regression net for theclang::annotatepath.Implementation paths to evaluate
Path A — Enrich
clang::annotatewith multi-arg form (mechanical). Keep theclang::annotateenvelope; switch the encoding from string-equality (\"pb::field=2\") to multi-arg (\"pb::field\", 2). The integer arg becomes a ClangExpr*accessible viaExpr::EvaluateAsInt(ctx), which handles both integer literals and enum constant references uniformly. Keeps today's ugly outer syntax ([[clang::annotate(...)]]) but delivers enum support without plugin work. Suitable as Phase 1a.Path B — Clang plugin registering the
pb::*attribute namespace (substantial). Implements the clean[[pb::field(N)]]syntax by extending Clang's attribute parser via the libtooling plugin API. Delivers the actual target syntax. Plugin development effort; depends on a stable Clang plugin ABI (we're on Clang 20.1.8). Suitable as Phase 1b.Recommendation: ship Path A first as a smaller, lower-risk increment that unblocks enum tag arguments. Path B (clean syntax) follows once Path A is stable; Phase 5 (C++26 reflection) eventually obsoletes both.
Branch + worktree
References