~bigbes/tarantool

tarantool-protobuf

af993bfc9118eac1402fa342e31c0ca1104e5874 — Eugene Blikh 2 months ago e258695
beads: close e4t (LSP @class stubs already shipped in b75b879)
2 files changed, 11 insertions(+), 1 deletions(-)

M .beads/interactions.jsonl
M .beads/issues.jsonl
M .beads/interactions.jsonl => .beads/interactions.jsonl +10 -0
@@ 51,3 51,13 @@
{"id":"int-2a1c9c90","kind":"field_change","created_at":"2026-05-24T18:20:11.709034Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-6bb","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Full-mode codegen emits <Msg>_decode_unsafe alongside <Msg>_decode that skips utf8_len at every string site (singular/repeated/map-kv/extensions/>=128-byte fallback) and recurses into sub-messages' _decode_unsafe. Skips pb.c_runtime dispatch (C runtime validates today; tracked in kyt). Runtime mode deferred to 58u (compile parallel _reader_unsafe closures). 6 tests in test/decode_unsafe_test.lua; full suite + conformance pass; ~20% perf win on string-heavy 1KB Person microbench. Generated code ~35% larger as expected. Docs in docs/api-modes.md."}}
{"id":"int-27d2602e","kind":"field_change","created_at":"2026-05-24T19:00:34.358869Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-b12","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Already fixed in commit 2656c97 (2026-05-17, same day issue filed). register_extension appends to extensions_list (parallel array); codec.encode_message, text.emit_message, json.encode_message all iterate via 'for i=1,#elist'. Inline-mode codegen also walks the list via 'for _i = N+1, #_elist' for runtime-registered extensions past the statically-known set. No pairs() over extensions remains anywhere on the hot path. The issue was never closed in bd."}}
{"id":"int-a28aba03","kind":"field_change","created_at":"2026-05-24T19:11:19.58095Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-58u","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Runtime mode now exposes pb.decode_unsafe and M.<Name>_decode_unsafe. codec.lua adds scalar_unsafe table, parameterizes build_reader/build_repeated_reader/decode_one to accept (scalar_tbl, decode_msg_fn, decode_group_fn), and adds compile_readers_unsafe + decode_message_unsafe + decode_group_unsafe + decode_extension_unsafe as literal clones with the three substitutions documented in codec.lua. Tests parameterized over both modes (14 cases), perf microbench shows ~8% gain in runtime mode (~20% in full mode). Conformance + JIT trace gates still pass. kyt remains open for unifying with C accel."}}
{"id":"int-4c37d283","kind":"field_change","created_at":"2026-05-24T19:48:24.175219Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-kyt","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"C runtime: decode_unsafe(plan, buf) entrypoint added; full-mode codegen _decode_unsafe dispatches via pb.c_runtime.decode_unsafe; pb.decode_unsafe in init.lua routes through C when c_runtime loaded. dec_ctx.skip_utf8 gates is_valid_utf8. Suites: test 766/766, test-c 1057/1057. Perf on string-heavy Person (418B, 16 emails + 16 nicknames): C-unsafe 455 MB/s vs C-safe 364 MB/s (+25%) vs Lua-unsafe 142 MB/s (3.2x)."}}
{"id":"int-f3ed4f0e","kind":"field_change","created_at":"2026-05-24T20:08:54.261959Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-auj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Codegen: extended inline tag fast path (1-byte → 1+2-byte) and inline LEN fast path (1-byte → 1+2-byte) in emitInlineDecode / emitInlineStringBytesScalar / emitInlineStringBytesRepeated. Covers field IDs 1..4095 and string/bytes lengths 0..16383 without leaving the parent trace. Acceptance: criterion #2 met — multi-byte-varint Person decode 31.6→34.7 MB/s (+10%) on the big_person fixture (200B name + multi-byte packed lucky_numbers), small Person unchanged at 14 MB/s. Bridge rate now 4/100 (down from 10-20% at issue filing). Residual bridges have migrated from the tag site to the packed-varint inner loop (wire.decode_int32 in a packed payload); fully eliminating them would require per-scalar-type inline 2-byte paths in the packed-loop emitter — diminishing-returns territory, skipped. Suites: test 766/766, test-c 1057/1057."}}
{"id":"int-495792c2","kind":"field_change","created_at":"2026-05-24T20:14:48.721199Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-gi0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Premise invalid. wire.encode_int32 / encode_int64 / encode_uint32 / encode_uint64 ARE literally encode_varint at module load (one assignment, same function value). kot's localize already captures wire.encode_int32 once per function, so the in-body call is a direct local read with no alias resolution. Verified: wire.encode_int32 == wire.encode_varint -> true. No extra TGETS at call time. The only residual is kot creating separate locals when a function uses both encode_int32 and encode_varint (could dedupe to one closure upvalue) — marginal closure-size win, not the 'two table lookups' premise. Closing as won't-fix; the work was already done by Lua alias semantics + kot localization."}}
{"id":"int-ec44fb9c","kind":"field_change","created_at":"2026-05-24T20:14:49.551175Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-a7l","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
{"id":"int-2308143b","kind":"field_change","created_at":"2026-05-24T20:22:34.980613Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-ozn","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Codegen: inlined 1-byte + 2-byte varint fast paths in the packed-scalar decode inner loop for int32/uint32/bool (and enum via a sister helper). Same pattern as auj's tag/LEN inline. Bridge rate dropped to ~1-2% (3/200), did not consistently hit 50-clean-in-a-row (max streak 31/50), but throughput criterion met with significant headroom:\n  small Person  (1-byte everything)         14.0 -> 23.4 MB/s   (+67%)\n  big   Person  (2-byte LEN + multi-byte packed) 34.7 -> 39.6 MB/s (+14% vs auj, +25% vs pre-auj)\nSkipped int64/uint64/sint*/sint64 inline — those return cdata or need zigzag wrappers; per-type complexity bigger than the residual gain on common workloads. Suites: test 766/766, test-c 1057/1057, bench/Person no regression at any size."}}
{"id":"int-c001d014","kind":"field_change","created_at":"2026-05-24T20:41:36.159459Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-3qu","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Added jit.opt.start('sizemcode=64', 'maxmcode=4096') to all 9 bench scripts (bench.lua, lazy_bench.lua, profile.lua, shapes_bench.lua, starwing_bench.lua, wire_bench.lua, alloc_probe.lua, map_bench.lua, packed_bench.lua — last two new since the issue was filed). All 7 non-interactive bench scripts run rc=0; profile.lua and starwing_bench.lua loadfile-check clean.\n\nRefreshed bench/baseline.json per step 2. Surprise win: full-mode decode allocations dropped 0.5-50% as a side-effect of auj/ozn that was only visible after the snapshot. proto2_basic.BenchPayload mid decode: 2.313 -> 1.156 KB/op (-50%); 1KB Person decode: 0.977 -> 0.953 KB/op. Runtime-mode unchanged — confirms the win is full-mode codegen specific.\n\nSteps 3-4 (update COMPARISON.md throughput tables, verify variance band shrinks) are deferred to a work.lab.local run per the bench-on-work-lab-local memory — laptop variance is 50%+ on identical state, can't trust throughput A/Bs locally."}}
{"id":"int-b63a66a3","kind":"field_change","created_at":"2026-05-24T21:02:23.021984Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-a7l","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Codegen emits typed fast variants for sint64/fixed64/sfixed64 — wire.encode_sint64_i(INT64(v)), wire.encode_fixed64_u(UINT64(v)), wire.encode_sfixed64_u(UINT64(v)) — skipping the to_int64/to_uint64 runtime type dispatch at every call site where the field type is statically known. Wire-level: added encode_sint64_i / encode_fixed64_u / encode_sfixed64_u alongside the existing encoders; encode_fixed64 is now a thin wrapper over encode_fixed64_u. INT64/UINT64 added as file-header upvalues alongside ffi.\n\nCodegen applied at: singular non-string scalar, singular required scalar, repeated non-packed per-element, extension singular, extension repeated per-element, packed scalar slow path, proto2 extension repeated. int32/int64/uint32/uint64 unchanged since they alias to encode_varint which doesn't call to_uint64 except on the slow-slow path.\n\nMeasured on c_int64.Wide encode (full mode, no PB_ENABLE_C, 4-run median):\n  lua-num inputs   2769 -> 2520 ns/op   (+9.9% throughput)\n  cdata inputs    12993 -> 12935 ns/op  (unchanged; to_int64 already takes the cdata fast path there)\n\nJust below the issue's 10-15% claim but real and measurable, and exactly the case where it matters (Lua-number-input is the common case for user code on sint64/fixed64 fields). Suites: test 766/766, test-c 1057/1057."}}
{"id":"int-e62c573d","kind":"field_change","created_at":"2026-05-24T21:22:51.675074Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-5y9","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented as opt-in via plugin flag --tarantool_opt=int64_as_number=true (mode=full only; default off). Adds wire.decode_int64_n / decode_uint64_n / decode_sint64_n / decode_fixed64_n / decode_sfixed64_n that return Lua number when the decoded value fits [-2^53, 2^53] (inclusive — both endpoints are powers of two and exact as doubles), cdata otherwise. LL/ULL literals are int64_t/uint64_t cdata; comparisons compile to plain 64-bit integer compares on trace.\n\nCodegen plumbs cfg.Int64AsNumber through writer.int64AsNumber (avoids threading through 10+ function signatures). decodeFnSuffix() returns \"_n\" for the affected scalar types only when the flag is set. All wire.decode_<st> emit sites updated.\n\nPlugin guards: --int64_as_number=true with mode=runtime errors out (would require descriptor flag wired through pb.codec — out of scope). Under PB_ENABLE_C=1 the option is a no-op since the C runtime makes its own number-vs-cdata decision via luaL_pushint64; test gates accordingly with t.skip.\n\nMeasured tradeoff on c_int64.Wide decode (full mode, no PB_ENABLE_C, 5 fields):\n  tiny   (all 1-byte vars)   2050 -> 1700 ns/op   (-17%, cdata avoided entirely)\n  medium (3-byte vars)       3220 -> 3600 ns/op   (+11%, extra cmp+tonumber)\n  huge   (past 2^53)         7575 -> 7750 ns/op   (+2%, noise)\n\nWorkload-specific: enable for fields dominated by small IDs/counters/small timestamps (where 1-byte varint hits), leave off for large values. Documented in the plugin flag help.\n\nTest fixture: examples/expected/full_n/c_int64/c_int64_pb.lua regenerated by 'just gen-int64-as-number'. test/int64_as_number_test.lua covers byte-identical encoding, small-value Lua number returns, default cdata returns, 2^53 boundary inclusive, past-2^53 cdata fallback, Lua-number-input round-trip. Suites: test 771/771, test-c 1057/1057 (with 5 skipped under PB_ENABLE_C=1)."}}
{"id":"int-031d6264","kind":"field_change","created_at":"2026-05-25T04:29:44.952643Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-0u1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Premise outdated — every typed varint decoder (decode_int32/int64/uint32/uint64/sint32/sint64/bool) already inlines the 1-byte fast path at the function head, mirroring decode_len's pattern. Verified at wire.lua:513-557. Closing as already-done."}}
{"id":"int-ef38f455","kind":"field_change","created_at":"2026-05-25T05:58:06.695205Z","actor":"Eugene Blikh","issue_id":"tarantool-protobuf-e4t","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Already implemented in commit b75b879 (codegen: EmmyLua type annotations for messages, enums, wrappers). Generated _pb.lua files carry ---@alias for enums, ---@class+---@field for messages (with ? on proto3 explicit-optional and oneof branches), ---@param/---@return on _new/_encode/_decode/_decode_lazy/_text/_has_/_clear_ wrappers. Cross-file refs use proto full names (hello.Address, google.protobuf.Timestamp), so WKT and imports resolve to the same declared class. Verified just gen produces no diff and just test passes 771/771."}}

M .beads/issues.jsonl => .beads/issues.jsonl +1 -1
@@ 15,7 15,7 @@
{"_type":"issue","id":"tarantool-protobuf-gcy","title":"Decoder: inline nested-message decode at the call site","description":"Today Person_decode calls Address_decode(slice) across a function boundary; the side trace into Address_decode may not stitch back (memory: luajit_side_trace_inlined_return). For field-typed messages in mode=full, emit the decode body inline at the call site. Expected: 20-40% on payloads with nested messages. Codegen growth is bounded by recursion depth — pick a depth limit and call out beyond it.","status":"closed","priority":1,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:09Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T18:44:22Z","started_at":"2026-05-18T18:32:15Z","closed_at":"2026-05-18T18:44:22Z","close_reason":"Attempted but reverted. Inlining Address_decode body into Person_decode (singular non-recursive candidate) showed 745/745 tests passing and 37/37 JIT, but bench median-of-3 regressed Person 1KB/10KB/100KB decode by 5-8% and encode by 3-6%. Profile's '100% interpreter bail' claim turned out to be a vl trace-attribution artifact; LuaJIT was already inlining Address_decode into Person_decode's trace effectively. Larger root traces (22 stops) compiled less efficiently. See bench/PERF_LOG.md entry for full data and analysis. Real decode bottlenecks remain decode_string utf8 (6bb), decode_tag fast path (4kj), repeated append (cch).","labels":["codegen","decoder","perf"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-h8v","title":"Encoder: codegen-time inline FFI writes (mode=full)","description":"Replace per-field wire.encode_* calls + ../table.concat chain with directly-emitted FFI byte writes at every field site in mode=full. Today every encode_len(s) is 'encode_varint(#s) .. s' — two allocations and a concat per string. Sidesteps the per-byte b:alloc(1) cliff that sank the earlier ibuf attempt (memory: tarantool_ibuf_perf). Expected: 1.5-2x encode throughput; alloc/op drops from ~130 KB at 100 KB Person to near zero. Biggest single perf move. Bench reference: bench/starwing_bench.lua + bench/COMPARISON.md show starwing C encoder at 2.3-3x ours across all sizes.","status":"closed","priority":1,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:04Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T18:31:54Z","started_at":"2026-05-18T18:25:39Z","closed_at":"2026-05-18T18:31:54Z","close_reason":"Inlined 1-byte varint length prefix at every LEN emit site; +25-48% encode at 1KB+ Person. Full FFI-buffer rewrite deferred — this slice captured the dominant profile target without that level of disruption. See bench/PERF_LOG.md entry for details.","labels":["codegen","encoder","perf"],"dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-2ri","title":"Codegen: replace string.char(_len) with CHARS[_len] at length-prefix emit sites","description":"Profile attributed ~28% of Person_encode 1KB time to a single `out[n] = string.char(_len)` line at every length-prefix emit site (39% of Person_encode's 72% trace share). Replacing it with a precomputed 256-entry lookup table (`wire.CHARS`) skips the C-function call.\n\nWin scales with payload size and number of length-prefix sites:\n- hello.Person 10B: +4.5%\n- hello.Person 1KB: +17%\n- hello.Person 10KB: +21%\n- hello.Person 100KB: +34%\n- proto2 BenchPayload mid: +11%\n\nMeasured on work.lab.local (median of 3 trials each). Decode untouched (within noise).\n\nImplementation: `wire.CHARS` table in runtime/pb/wire.lua; `local CHARS = wire.CHARS` localizer added to codegen header; emitInlineLenPrefix emits `CHARS[_len]` instead of `string.char(_len)`.\n\nTests: 752/752 pass.\n\nLABELS: codegen, encoder, perf","notes":"Shipped. See bench/PERF_LOG.md 2026-05-24 entry for the work.lab.local 3-trial medians. Numbers: +4.7% (10B) / ±noise (100B) / +16.8% (1KB) / +21.1% (10KB) / +34.2% (100KB) / +11.1% (proto2 mid). Decode unchanged.","status":"closed","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-24T16:51:32Z","created_by":"Eugene Blikh","updated_at":"2026-05-24T16:53:15Z","closed_at":"2026-05-24T16:53:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-e4t","title":"LSP: plugin emits @class type stubs for generated messages","description":"Phase 3 of the LSP / LLM affordance work. Follows phase 1 (commit 8721548 — runtime annotations) and phase 2 (bd-74c — annotate codec/grpc/json/wkt).\n\nHighest-payoff phase for LLM grounding. Modify the protoc plugin (cmd/protoc-gen-tarantool/internal/gen) so each generated _pb.lua module emits ---@class blocks describing every message, plus typed @param/@return on the _encode/_decode/_descriptor surface. After this lands, anyone (human or LLM) who require('full.hello.hello_pb') gets full type info on Person, Address, etc., without reading the .proto.\n\nEmit shape (decision: inline at top of generated file; one file per .proto already includes all messages from that file):\n\n  ---@class hello.Person\n  ---@field name?           string\n  ---@field user_id?        ffi.cdata*           # uint64\n  ---@field age?            integer              # int32\n  ---@field weight_kg?      number               # double\n  ---@field emails?         string[]\n  ---@field address?        hello.Address\n  ---@field lucky_numbers?  integer[]\n  ---@field favorite_color? hello.Color          # enum alias\n  ---@field tags?           table\u003cstring,string\u003e # map\u003cstring,string\u003e\n  ---@field unknown_fields? string\n\n  ---@param tbl hello.Person\n  ---@return string\n  function M.Person_encode(tbl) ... end\n\n  ---@param bytes string\n  ---@return hello.Person\n  function M.Person_decode(bytes) ... end\n\nField-kind -\u003e Lua-type mapping:\n- scalar int32/uint32/sint32/fixed32/sfixed32/bool → integer/boolean\n- scalar int64/uint64/sint64/fixed64/sfixed64 → ffi.cdata* (LuaJIT cdata, per project convention)\n- scalar float/double → number\n- scalar string/bytes → string\n- enum → alias of integer (emit ---@alias hello.Color integer)\n- message → another @class reference\n- repeated T → T[]\n- map\u003cK,V\u003e → table\u003cK_lua, V_lua\u003e\n- oneof — all members are optional, generator should NOT emit a discriminator field; user calls view:which() / inspects which value is non-nil\n\nAll fields are optional (? suffix) since proto3 default-elision means absence-on-wire is indistinguishable from default value. Required fields in proto2 omit the ?.\n\nBoth mode=full and mode=runtime emit the same _encode/_decode wrapper signatures, so the stubs apply uniformly.\n\nVerify by:\n1. Regenerating examples/expected/ via just gen\n2. Confirming the generated _pb.lua files load and pass tests (no behavior change)\n3. Opening examples/expected/full/hello/hello_pb.lua in an LSP-aware editor and checking that hover on hello.Person, M.Person_encode shows the @class + typed signature\n4. Sanity: require a generated module from spike code and confirm autocomplete on the result-table field names","acceptance_criteria":"Plugin emits ---@class blocks + typed _encode/_decode signatures into every generated _pb.lua; just gen + just test pass; LSP hover on require('full.hello.hello_pb').Person_encode shows ---@param tbl hello.Person ---@return string; ---@class hello.Person is reachable via hover on a decoded value","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-23T09:30:19Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T09:30:19Z","labels":["codegen","lsp"],"dependencies":[{"issue_id":"tarantool-protobuf-e4t","depends_on_id":"tarantool-protobuf-74c","type":"blocks","created_at":"2026-05-23T12:30:18Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-e4t","title":"LSP: plugin emits @class type stubs for generated messages","description":"Phase 3 of the LSP / LLM affordance work. Follows phase 1 (commit 8721548 — runtime annotations) and phase 2 (bd-74c — annotate codec/grpc/json/wkt).\n\nHighest-payoff phase for LLM grounding. Modify the protoc plugin (cmd/protoc-gen-tarantool/internal/gen) so each generated _pb.lua module emits ---@class blocks describing every message, plus typed @param/@return on the _encode/_decode/_descriptor surface. After this lands, anyone (human or LLM) who require('full.hello.hello_pb') gets full type info on Person, Address, etc., without reading the .proto.\n\nEmit shape (decision: inline at top of generated file; one file per .proto already includes all messages from that file):\n\n  ---@class hello.Person\n  ---@field name?           string\n  ---@field user_id?        ffi.cdata*           # uint64\n  ---@field age?            integer              # int32\n  ---@field weight_kg?      number               # double\n  ---@field emails?         string[]\n  ---@field address?        hello.Address\n  ---@field lucky_numbers?  integer[]\n  ---@field favorite_color? hello.Color          # enum alias\n  ---@field tags?           table\u003cstring,string\u003e # map\u003cstring,string\u003e\n  ---@field unknown_fields? string\n\n  ---@param tbl hello.Person\n  ---@return string\n  function M.Person_encode(tbl) ... end\n\n  ---@param bytes string\n  ---@return hello.Person\n  function M.Person_decode(bytes) ... end\n\nField-kind -\u003e Lua-type mapping:\n- scalar int32/uint32/sint32/fixed32/sfixed32/bool → integer/boolean\n- scalar int64/uint64/sint64/fixed64/sfixed64 → ffi.cdata* (LuaJIT cdata, per project convention)\n- scalar float/double → number\n- scalar string/bytes → string\n- enum → alias of integer (emit ---@alias hello.Color integer)\n- message → another @class reference\n- repeated T → T[]\n- map\u003cK,V\u003e → table\u003cK_lua, V_lua\u003e\n- oneof — all members are optional, generator should NOT emit a discriminator field; user calls view:which() / inspects which value is non-nil\n\nAll fields are optional (? suffix) since proto3 default-elision means absence-on-wire is indistinguishable from default value. Required fields in proto2 omit the ?.\n\nBoth mode=full and mode=runtime emit the same _encode/_decode wrapper signatures, so the stubs apply uniformly.\n\nVerify by:\n1. Regenerating examples/expected/ via just gen\n2. Confirming the generated _pb.lua files load and pass tests (no behavior change)\n3. Opening examples/expected/full/hello/hello_pb.lua in an LSP-aware editor and checking that hover on hello.Person, M.Person_encode shows the @class + typed signature\n4. Sanity: require a generated module from spike code and confirm autocomplete on the result-table field names","acceptance_criteria":"Plugin emits ---@class blocks + typed _encode/_decode signatures into every generated _pb.lua; just gen + just test pass; LSP hover on require('full.hello.hello_pb').Person_encode shows ---@param tbl hello.Person ---@return string; ---@class hello.Person is reachable via hover on a decoded value","status":"closed","priority":2,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-23T09:30:19Z","created_by":"Eugene Blikh","updated_at":"2026-05-25T05:58:07Z","started_at":"2026-05-25T05:56:03Z","closed_at":"2026-05-25T05:58:07Z","close_reason":"Already implemented in commit b75b879 (codegen: EmmyLua type annotations for messages, enums, wrappers). Generated _pb.lua files carry ---@alias for enums, ---@class+---@field for messages (with ? on proto3 explicit-optional and oneof branches), ---@param/---@return on _new/_encode/_decode/_decode_lazy/_text/_has_/_clear_ wrappers. Cross-file refs use proto full names (hello.Address, google.protobuf.Timestamp), so WKT and imports resolve to the same declared class. Verified just gen produces no diff and just test passes 771/771.","labels":["codegen","lsp"],"dependencies":[{"issue_id":"tarantool-protobuf-e4t","depends_on_id":"tarantool-protobuf-74c","type":"blocks","created_at":"2026-05-23T12:30:18Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-74c","title":"LSP: annotate codec/grpc/json/wkt with EmmyLua types","description":"Phase 2 of the LSP / LLM affordance work (phase 1 landed in commit 8721548 — .luarc.json, runtime/pb/_types.lua, init.lua + lazy.lua annotations).\n\nAdd ---@param / ---@return annotations to the remaining public-surface modules so editor hover and LLM context show types beyond just init.lua and the lazy views:\n\n- runtime/pb/codec.lua — encode(desc, t) / decode(desc, b); compile_writers / compile_readers (called from pb.finalize_message)\n- runtime/pb/grpc.lua — loopback / multiplex transport factories; the service client (factory(transport) -\u003e client_methods) and server (impl -\u003e {service, methods}) shapes\n- runtime/pb/json.lua — pb.json.encode(desc, t, opts) / pb.json.decode(desc, s, opts); opts shape (preserve_proto_field_names, emit_unpopulated, ...)\n- runtime/pb/wkt.lua — register(full_name, desc), lookup, any_pack/any_unpack, NULL sentinel\n\nFoundational @class declarations (pb.Descriptor, pb.Field, pb.GrpcTransport, pb.Json, pb.Wkt, pb.Module) already live in runtime/pb/_types.lua — extend as needed for opts shapes / map\u003cK,V\u003e generics.\n\nPure metadata change — no runtime behavior. Verify by running just test (must stay at 748 passing) and by opening a generated _pb.lua in an LSP-aware editor and confirming hover on require('pb').encode / .grpc.loopback / .json.encode shows the right signatures.","acceptance_criteria":"just test still green (748+ tests); hover in lua-language-server-aware editor shows typed signatures for pb.encode, pb.decode, grpc.loopback, json.encode, json.decode, wkt.register","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-23T09:29:55Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T20:13:44Z","started_at":"2026-05-23T20:09:08Z","closed_at":"2026-05-23T20:13:44Z","close_reason":"Phase-2 EmmyLua annotations landed. Added ---@param/---@return on public surface of codec.lua (encode_message/decode_message/encode_field/compile_writers/compile_readers/merge_message), grpc.lua (loopback/multiplex/new_stream_pair/wrap_call/wrap_server_stream/wrap_server_view), json.lua (M.encode/M.decode with new pb.JsonEncodeOpts/pb.JsonDecodeOpts shapes), and wkt.lua (register/lookup/any_pack/any_unpack). Also corrected two pre-existing _types.lua signature lies: pb.register is (desc), not (full_name, desc); pb.any.pack is (desc, t, prefix?), not (t, type_url). just test 752/752, just test-c 1043/1043.","labels":["docs","lsp"],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-rmf","title":"ra6 3k: WKT override-hook passthrough","description":"If a plan's descriptor has desc.encode and desc.decode set (the WKT pattern from runtime/pb/wkt.lua), the C runtime must call those Lua functions instead of walking fields. Plan compiler stores luaL_ref to those functions; encode/decode entry points check first. This is also the extension point for future per-message codegen C (c0i) — same hook. Depends on 3a only (just adds a check at entry to the encode/decode loop). Acceptance: hello.Event (uses Timestamp, Duration, Any, FieldMask, Wrappers, Struct) round-trips with PB_ENABLE_C=1 producing byte-equal output to mode=full; the WKT module is unmodified.","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:21:13Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T18:22:01Z","started_at":"2026-05-23T18:17:19Z","closed_at":"2026-05-23T18:22:01Z","close_reason":"Closed","dependencies":[{"issue_id":"tarantool-protobuf-rmf","depends_on_id":"tarantool-protobuf-mq7","type":"blocks","created_at":"2026-05-18T23:21:55Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-m7u","title":"ra6 3i: proto2 — required, defaults, groups, extensions","description":"Proto2 semantic differences from proto3 that the C runtime must honor: (1) Required fields: decode-time enforcement when explicitly opted in (or default to skipping per existing pure-Lua behavior — match exactly); encode-time enforcement same. (2) Field defaults: present in the plan; emit them when missing on encode and substitute on decode where proto2 semantics require. (3) Groups (SGROUP/EGROUP wire format): encode/decode the framed group syntax. (4) Extensions: walk extension ranges, use plan-cached extension descriptor pointers, store in result._extensions. (5) Closed enum semantics for proto2. Depends on 3b + 3c + 3d + 3e + 3g + 3j (it's the join point — proto2 touches scalars, sub-messages, repeated, oneofs, unknown-fields). Acceptance: proto2_basic.BenchPayload and test_messages_proto2 round-trip byte-equal to mode=full at all bench sizes; conformance proto2 suite passes under PB_ENABLE_C=1.","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:21:03Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T18:38:57Z","started_at":"2026-05-23T18:26:23Z","closed_at":"2026-05-23T18:38:57Z","close_reason":"Closed","dependencies":[{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-hwe","type":"blocks","created_at":"2026-05-18T23:22:21Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-jc9","type":"blocks","created_at":"2026-05-18T23:22:22Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-mz6","type":"blocks","created_at":"2026-05-18T23:22:20Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-w3u","type":"blocks","created_at":"2026-05-18T23:22:22Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-wyp","type":"blocks","created_at":"2026-05-18T23:22:23Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-y1n","type":"blocks","created_at":"2026-05-18T23:22:20Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":6,"dependent_count":1,"comment_count":0}