Skip to content

feature, pb:: namespace-scoped attribute vocabulary (taxonomy Phase 1) #31

@steven-varga

Description

@steven-varga

Goal

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.

Out: universal pb::name / pb::ignore / pb::doc / pb::on_missing (Phase 2). The .proto text emitter (Phase 3). Tier-2/3/4 attributes (Phase 4). C++26 reflection (Phase 5).

Acceptance criteria

  1. 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.
  2. Old syntax still accepted. Existing tests/fixtures/pb_*.cpp continue to pass without modification — both forms coexist during a transition window.
  3. 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.
  4. 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.
  5. 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).
  6. 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.
  7. 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
  • Worktree: `/home/steven/projects/vargalabs-workspace/worktrees/h5cpp-compiler/-pb-attribute-vocabulary`

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions