mirror of
https://github.com/luxfi/zap.git
synced 2026-07-27 05:54:26 +00:00
builder: value-typed ListBuilder + fix latent zap/v1 breakage
StartList returns ListBuilder by value (mirrors the value-typed ObjectBuilder):
Add*/Finish keep pointer receivers, every caller binds to an addressable local so
Go auto-addresses it. Dropped the dead elemSize field (never read — stride is
implicit in which Add* is called). Byte-identical wire (golden+fuzz+all round-trips
green). NOTE: not a measured alloc win — escape analysis already stack-allocated
the &ListBuilder{} at all 9 call sites (composite stays 583ns/5allocs, the 5 being
mandatory writeEnvelopePrefix pool-egress copies). Value is code-cleanliness +
consistency.
Also fixes zap/v1 subpackage: v1.2.4's ObjectBuilder value change left v1/*.go
referencing *zap.ObjectBuilder → the subpackage didn't build (latent; only the
root package is imported downstream so it went unnoticed). Converted to the value
type. Full module (root + v1) builds + tests green.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
+10
-5
@@ -330,17 +330,22 @@ func (b *Builder) WriteText(s string) int {
|
||||
type ListBuilder struct {
|
||||
b *Builder
|
||||
startPos int
|
||||
elemSize int
|
||||
count int
|
||||
}
|
||||
|
||||
// StartList starts building a list.
|
||||
func (b *Builder) StartList(elemSize int) *ListBuilder {
|
||||
// StartList starts building a list. Returns a VALUE (not a heap pointer): the
|
||||
// Add*/Finish methods take pointer receivers, and every caller binds the result
|
||||
// to an addressable local (`lb := b.StartList(...)`), so Go auto-addresses the
|
||||
// local and count mutations accumulate on it — no per-list heap allocation.
|
||||
// Mirrors the value-typed StartObject. elemSize is unused (the stride is
|
||||
// implicit in which Add* the caller invokes) and kept only as a documenting
|
||||
// param.
|
||||
func (b *Builder) StartList(elemSize int) ListBuilder {
|
||||
_ = elemSize
|
||||
b.align(Alignment)
|
||||
return &ListBuilder{
|
||||
return ListBuilder{
|
||||
b: b,
|
||||
startPos: b.pos,
|
||||
elemSize: elemSize,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -12,13 +12,13 @@ import "github.com/luxfi/zap"
|
||||
// straight-line `b.StartObject` construction that inlines at the call
|
||||
// site. The (ob, b) must be the pair returned by the same StartObject —
|
||||
// passing a mismatched pair is a programming error.
|
||||
func SetterFrom[S Schema](ob *zap.ObjectBuilder, b *zap.Builder) Setter[S] {
|
||||
func SetterFrom[S Schema](ob zap.ObjectBuilder, b *zap.Builder) Setter[S] {
|
||||
return Setter[S]{ob: ob, b: b}
|
||||
}
|
||||
|
||||
// WriteString writes a variable-length UTF-8 string field to the object
|
||||
// tail and stores its {relOffset, length} pointer at byteOffset in the
|
||||
// fixed payload. The typed peer of [*zap.ObjectBuilder.SetText] for the
|
||||
// fixed payload. The typed peer of [zap.ObjectBuilder.SetText] for the
|
||||
// Setter-based build path. Read it back with the generated string
|
||||
// accessor (which calls v1's zero-copy Object.Text).
|
||||
func WriteString[S Schema](s Setter[S], byteOffset uint32, v string) {
|
||||
@@ -28,7 +28,7 @@ func WriteString[S Schema](s Setter[S], byteOffset uint32, v string) {
|
||||
|
||||
// WriteBytes writes a variable-length byte slice to the object tail and
|
||||
// stores its {relOffset, length} pointer at byteOffset. The typed peer
|
||||
// of [*zap.ObjectBuilder.SetBytes]. Zero-copy on read via Object.Bytes.
|
||||
// of [zap.ObjectBuilder.SetBytes]. Zero-copy on read via Object.Bytes.
|
||||
func WriteBytes[S Schema](s Setter[S], byteOffset uint32, v []byte) {
|
||||
ob, _ := s.raw()
|
||||
ob.SetBytes(int(byteOffset), v)
|
||||
|
||||
+4
-4
@@ -34,7 +34,7 @@ import (
|
||||
// passed by value.
|
||||
type Builder[S Schema] struct {
|
||||
b *zap.Builder
|
||||
ob *zap.ObjectBuilder
|
||||
ob zap.ObjectBuilder
|
||||
}
|
||||
|
||||
// NewBuilderFor returns a fresh Builder[S] sized for S's fixed
|
||||
@@ -55,7 +55,7 @@ func NewBuilderFor[S Schema]() Builder[S] {
|
||||
|
||||
// startBuild is the non-generic, shape-free counterpart of
|
||||
// [NewBuilderFor]: allocate a v1 [*zap.Builder] sized for the fixed
|
||||
// payload, anchor an [*zap.ObjectBuilder] at the root, and write the
|
||||
// payload, anchor an [zap.ObjectBuilder] at the root, and write the
|
||||
// kind discriminator. The caller (per-schema shim, codegen, or
|
||||
// generic [NewBuilderFor]) supplies size and kind as constants — the
|
||||
// Go compiler folds them at compile time when called from a concrete
|
||||
@@ -64,7 +64,7 @@ func NewBuilderFor[S Schema]() Builder[S] {
|
||||
// Centralizing the allocation here keeps the generic [NewBuilderFor]
|
||||
// body tiny and lets the inliner fold it into the caller — same
|
||||
// trick as [WrapRaw] / [parseHeaderImpl].
|
||||
func startBuild(size int, kind uint8) (*zap.Builder, *zap.ObjectBuilder) {
|
||||
func startBuild(size int, kind uint8) (*zap.Builder, zap.ObjectBuilder) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + size)
|
||||
ob := b.StartObject(size)
|
||||
ob.SetUint8(0, kind)
|
||||
@@ -93,7 +93,7 @@ func (bb Builder[S]) Finish() (View[S], []byte) {
|
||||
// The Raw's payload is the fixed-section slice; the caller wraps it
|
||||
// as [View[S]] via [AsView]. The buffer is returned for callers that
|
||||
// need the on-wire bytes.
|
||||
func finishBuild(b *zap.Builder, ob *zap.ObjectBuilder, size int) (Raw, []byte) {
|
||||
func finishBuild(b *zap.Builder, ob zap.ObjectBuilder, size int) (Raw, []byte) {
|
||||
rootOff := ob.FinishAsRoot()
|
||||
buf := b.Finish()
|
||||
end := rootOff + size
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ func WriteB[S Schema, T FieldKind](bb Builder[S], f Field[S, T], val T) {
|
||||
// against unsafe.Sizeof on the type parameter, each branch calling
|
||||
// one v1 ObjectBuilder.Set* method, folds to one MOV per concrete T
|
||||
// after generic instantiation and inlining.
|
||||
func writeAt[T FieldKind](ob *zap.ObjectBuilder, offset uint32, val T) {
|
||||
func writeAt[T FieldKind](ob zap.ObjectBuilder, offset uint32, val T) {
|
||||
off := int(offset)
|
||||
switch unsafe.Sizeof(val) {
|
||||
case 1:
|
||||
|
||||
+2
-2
@@ -388,13 +388,13 @@ func Build[S Schema](init func(Setter[S])) (View[S], []byte) {
|
||||
// them and is itself reachable from the caller's stack). Setter values
|
||||
// are NOT safe to retain after the builder's Finish returns.
|
||||
type Setter[S Schema] struct {
|
||||
ob *zap.ObjectBuilder
|
||||
ob zap.ObjectBuilder
|
||||
b *zap.Builder
|
||||
}
|
||||
|
||||
// raw exposes the underlying builder for advanced primitives that
|
||||
// need to write variable-length tails (Text, Bytes, Lists). It is
|
||||
// unexported; the variable-length writers in [list.go] use it.
|
||||
func (s Setter[S]) raw() (*zap.ObjectBuilder, *zap.Builder) {
|
||||
func (s Setter[S]) raw() (zap.ObjectBuilder, *zap.Builder) {
|
||||
return s.ob, s.b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user