Files
zeekay 9d9e477978 v1/codegen: emit REPEATED nested objects (repeated message, zero-copy)
The repeated-message wire shape: a list whose elements carry their own
variable-length tails (string/bytes/nested), which the fixed-stride inline
List cannot hold. Completes proto field-shape coverage in the runtime —
map<k,v> reduces to a repeated {key,value} entry, so this is its keystone too.

Composition, not a new wire concept: ListNested = List ∘ NestedAt. The list
slots are a contiguous stride-4 object-pointer array; each pointer is
followed exactly as a singular nested pointer to an out-of-line tailed View.

Runtime:
- builder.go: ListBuilder.AddObjectPtr(targetPos) — appends a signed 4-byte
  relative object pointer (the repeated-message element kind).
- v1/list_nested.go: ListNested[N] (Len/At/All/IsZero), ListNestedAt[S,N]
  (read), WriteListNested[S,N] + NestedElemSetter[N] (write). Same audited
  bounds discipline as Object.Object / ListStride (RED-HIGH-1/2 inherited).

Codegen:
- elemIsTailed: a list element with a string/bytes/nested/list sub-field
  auto-routes to ListNested/WriteListNested/ListNestedAt; scalar-only
  elements stay in the tighter inline List/WriteList/ListAt. One Elem
  declaration, the generator picks the correct encoding.

Proven end-to-end: runtime roundtrip (3 elements each with its OWN string
tail, via At and All, + empty), emit routing test (tailed->ListNested,
scalar-only->List), and a generated-code wire roundtrip (testpkg/repeatwire).
Root zap + full v1+codegen suite green; scalar listwire unchanged (no regression).
2026-06-23 16:38:05 -07:00

676 lines
23 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package codegen
import (
"fmt"
"io"
"strings"
)
// typeToSize maps a [zapv1.FieldKind] Go type name to its byte width.
// Used by the field-write code to pick the right ObjectBuilder.Set*
// method.
var typeToSize = map[string]int{
"bool": 1,
"int8": 1,
"uint8": 1,
"int16": 2,
"uint16": 2,
"int32": 4,
"uint32": 4,
"float32": 4,
"int64": 8,
"uint64": 8,
"float64": 8,
}
// typeToSetMethod maps a Go type name to the v1 ObjectBuilder method
// that writes it.
var typeToSetMethod = map[string]string{
"bool": "SetBool",
"int8": "SetInt8",
"uint8": "SetUint8",
"int16": "SetInt16",
"uint16": "SetUint16",
"int32": "SetInt32",
"uint32": "SetUint32",
"float32": "SetFloat32",
"int64": "SetInt64",
"uint64": "SetUint64",
"float64": "SetFloat64",
}
// fieldByteSize returns the size in bytes of a schema field. Handles
// both scalar FieldKind types and "bytes<N>" byte-array types.
// Returns (0, false) for unknown types.
func fieldByteSize(f Field) (int, bool) {
if _, ok := f.IsList(); ok {
return 8, true // list pointer {relOffset uint32, length uint32}
}
if _, ok := f.IsNested(); ok {
return 4, true // object pointer {relOffset uint32} — singular, no length
}
if n, ok := f.IsBytes(); ok {
return n, true
}
if f.IsVariable() {
return 8, true // tail pointer {relOffset uint32, length uint32}
}
if sz, ok := typeToSize[f.Type]; ok {
return sz, true
}
return 0, false
}
// Emit writes the per-schema *.go file to w. Returns an error if the
// schema is invalid (unknown field type, offset overlap, fields past
// declared Size, etc.).
//
// The emitted code follows the v1 hand-rolled pattern: every step
// (Parse, Root, Uint reads, kind check, View compose) uses v1
// primitives that inline into the caller's frame. The result matches
// v1 hand-rolled performance to within compiler noise — verified by
// the bench suite.
//
// Scalar fields go through the [zapv1.Field][S, T] generic handle
// (declared in the per-schema <GoName>Fields var). Fixed-width
// byte-array fields ("bytes<N>") are emitted as standalone typed
// accessor functions that call v1's SetBytesFixed / BytesFixedSlice
// — they do NOT appear in the Fields struct because [N]byte is not a
// [zapv1.FieldKind] member. Both forms produce the same wire layout.
func Emit(w io.Writer, s Schema) error {
// Validate field types + offsets.
for _, f := range s.Fields {
fsz, ok := fieldByteSize(f)
if !ok {
return fmt.Errorf("codegen: field %q has unsupported type %q",
f.Name, f.Type)
}
if int(f.Offset)+fsz > s.Size {
return fmt.Errorf("codegen: field %q at offset %d size %d extends past declared payload size %d",
f.Name, f.Offset, fsz, s.Size)
}
}
// Detect offset overlap (each byte may belong to at most one field).
if s.Size > 0 {
owner := make([]string, s.Size)
// Reserve offset 0 for the kind discriminator — UNLESS this is a
// flat list-element schema, whose slot carries no kind byte (fields
// may legitimately start at offset 0).
if !s.Element {
owner[0] = "<kind>"
}
for _, f := range s.Fields {
fsz, _ := fieldByteSize(f)
for i := 0; i < fsz; i++ {
p := int(f.Offset) + i
if owner[p] != "" {
return fmt.Errorf("codegen: field %q at offset %d overlaps field %q",
f.Name, p, owner[p])
}
owner[p] = f.Name
}
}
}
// Header.
fmt.Fprintf(w, `// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// Code generated by github.com/luxfi/zap/v1/codegen — DO NOT EDIT.
// Source schema: %s (kind=0x%02x, size=%d).
package %s
import (
"github.com/luxfi/zap"
zapv1 "github.com/luxfi/zap/v1"
)
`, s.WireName, s.Kind, s.Size, s.Package)
// Constants.
fmt.Fprintf(w, `// Kind%s is the wire discriminator for %s.
const Kind%s zapv1.KindByte = 0x%02x
// Size%s is the fixed object payload size in bytes.
const Size%s = %d
`, s.WireName, s.WireName, s.WireName, s.Kind,
s.WireName, s.WireName, s.Size)
// Schema marker type.
fmt.Fprintf(w, `// %s is the v1 schema marker for %s. Methods are constant-
// returning so concrete-typed [zapv1.Wrap[%s]] calls fold to
// literals at the call site.
type %s struct{}
func (%s) Kind() zapv1.KindByte { return Kind%s }
func (%s) Size() int { return Size%s }
func (%s) Name() string { return %q }
`, s.GoName, s.WireName, s.GoName, s.GoName,
s.GoName, s.WireName,
s.GoName, s.WireName,
s.GoName, s.WireName)
// Field offset constants — all fields, scalar and byte-array.
for _, f := range s.Fields {
fmt.Fprintf(w, "// Offset%s_%s is the byte offset of the %s field.\nconst Offset%s_%s = %d\n\n",
s.WireName, f.Name, f.Name,
s.WireName, f.Name, f.Offset)
}
// <SchemaGoName>Fields struct — scalar fields only.
// Byte-array fields are handled by standalone accessor functions
// (emitted after the constructor) because [N]byte is not a
// FieldKind union member.
// Scalar fields go in the Fields struct (zapv1.Field handles).
// Fixed byte-arrays and variable-length tails (string/bytes) get
// standalone typed accessors instead — neither [N]byte nor string/
// []byte is a zapv1.FieldKind member.
var scalarFields []Field
var accessorFields []Field
var listFields []Field
var nestedFields []Field
for _, f := range s.Fields {
switch {
case isList(f):
listFields = append(listFields, f)
case isNested(f):
nestedFields = append(nestedFields, f)
default:
if _, isBytes := f.IsBytes(); isBytes || f.IsVariable() {
accessorFields = append(accessorFields, f)
} else {
scalarFields = append(scalarFields, f)
}
}
}
if len(scalarFields) > 0 {
fmt.Fprintf(w, "// %sFields is the namespace of every scalar field accessor for %s.\n",
s.GoName, s.WireName)
fmt.Fprintf(w, "// Use with [zapv1.Read] / [zapv1.Write] for type-safe access.\n")
fmt.Fprintf(w, "// Byte-array fields are emitted as standalone functions, see below.\nvar %sFields = struct {\n",
s.GoName)
for _, f := range scalarFields {
fmt.Fprintf(w, "\t%s zapv1.Field[%s, %s]\n", f.Name, s.GoName, f.Type)
}
fmt.Fprintf(w, "}{\n")
for _, f := range scalarFields {
fmt.Fprintf(w, "\t%s: zapv1.At[%s, %s](%d),\n",
f.Name, s.GoName, f.Type, f.Offset)
}
fmt.Fprintf(w, "}\n\n")
} else {
// Still emit the empty struct so callers can rely on the
// <GoName>Fields symbol existing.
fmt.Fprintf(w, "// %sFields is empty: %s has no scalar fields.\n",
s.GoName, s.WireName)
fmt.Fprintf(w, "var %sFields = struct{}{}\n\n", s.GoName)
}
// New constructor.
emitNewConstructor(w, s, scalarFields, accessorFields, listFields, nestedFields)
emitWrapFunction(w, s)
emitAccessorFields(w, s, accessorFields)
emitListSupport(w, s, listFields)
emitNestedSupport(w, s, nestedFields)
emitRegisterInit(w, s)
return nil
}
// isList reports whether f is a list field (has an element descriptor).
func isList(f Field) bool { _, ok := f.IsList(); return ok }
// isNested reports whether f is a singular nested-object field.
func isNested(f Field) bool { _, ok := f.IsNested(); return ok }
// elemIsTailed reports whether a list element carries its own
// variable-length tail (a string/bytes field, or a nested/list sub-field).
// Such elements cannot live in a fixed-stride [zapv1.List] — they need the
// out-of-line pointer-array [zapv1.ListNested] (the "repeated message"
// encoding) so each element's tail has somewhere to go. Scalar-only (and
// fixed-byte-only) elements stay in the tighter inline List.
func elemIsTailed(elem *ListElem) bool {
for _, ef := range elem.Fields {
if ef.IsVariable() || isList(ef) || isNested(ef) {
return true
}
}
return false
}
// emitElemWrites writes one builder statement per element/nested field,
// reading from srcExpr (e.g. "it" for a list element or "inner" for a
// nested value) into the element Setter named setVar (e.g. "e"). Shared by
// the list-element and nested-object build bodies so the two stay
// orthogonal: scalars go through the typed [zapv1.Write] handle, while
// string/bytes tails use [zapv1.WriteString]/[zapv1.WriteBytes] against the
// element schema's Offset<Wire>_<Field> constants. wire is the element
// schema's WireName (for those Offset constants); goName is its marker
// GoName (for the <GoName>Fields handle). indent prefixes each line.
func emitElemWrites(w io.Writer, goName, wire, setVar, srcExpr, indent string, fields []Field) {
for _, f := range fields {
switch {
case f.IsVarString():
fmt.Fprintf(w, "%szapv1.WriteString(%s, Offset%s_%s, %s.%s)\n",
indent, setVar, wire, f.Name, srcExpr, f.Name)
case f.IsVarBytes():
fmt.Fprintf(w, "%szapv1.WriteBytes(%s, Offset%s_%s, %s.%s)\n",
indent, setVar, wire, f.Name, srcExpr, f.Name)
default:
fmt.Fprintf(w, "%szapv1.Write(%s, %sFields.%s, %s.%s)\n",
indent, setVar, goName, f.Name, srcExpr, f.Name)
}
}
}
// emitNewConstructor writes the New%s function that builds a fresh
// ZAP buffer with all fields set from parameters. The body uses v1
// primitives (zap.NewBuilder, b.StartObject, ob.Set*, b.Finish) so
// every step inlines into the caller.
func emitNewConstructor(w io.Writer, s Schema, scalar, accessor, list, nested []Field) {
// Function signature — scalar fields first, then accessor fields
// (fixed byte arrays as [N]byte, variable string as string, variable
// bytes as []byte), then list fields ([]ElementValue), then nested
// fields (*NestedValue — pointer so nil encodes unset), in declared
// order.
var args []string
for _, f := range scalar {
args = append(args, fmt.Sprintf("%s %s", lowerFirst(f.Name), f.Type))
}
for _, f := range accessor {
switch {
case f.IsVarString():
args = append(args, fmt.Sprintf("%s string", lowerFirst(f.Name)))
case f.IsVarBytes():
args = append(args, fmt.Sprintf("%s []byte", lowerFirst(f.Name)))
default:
n, _ := f.IsBytes()
args = append(args, fmt.Sprintf("%s [%d]byte", lowerFirst(f.Name), n))
}
}
for _, f := range list {
elem, _ := f.IsList()
args = append(args, fmt.Sprintf("%s []%s", lowerFirst(f.Name), elem.Value))
}
for _, f := range nested {
nm, _ := f.IsNested()
args = append(args, fmt.Sprintf("%s *%s", lowerFirst(f.Name), nm.Value))
}
// Buffer capacity: fixed payload plus the byte length of every
// variable-length tail value, so the common case is a single alloc.
// (The Builder still grows if the estimate is short.)
capExpr := fmt.Sprintf("zap.HeaderSize + Size%s", s.WireName)
for _, f := range accessor {
if f.IsVariable() {
capExpr += fmt.Sprintf(" + len(%s)", lowerFirst(f.Name))
}
}
fmt.Fprintf(w, `// New%s builds a fresh %s in a ZAP buffer.
//
// Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
// [*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
// so the entire build expands at the call site. Scalars and fixed byte
// arrays live in the fixed payload; variable-length string/bytes go in
// the object tail (the fixed payload holds their 8-byte pointers).
func New%s(%s) (zapv1.View[%s], []byte) {
b := zap.NewBuilder(%s)
ob := b.StartObject(Size%s)
`, s.WireName, s.WireName, s.WireName, strings.Join(args, ", "), s.GoName,
capExpr, s.WireName)
// Flat list-element slots carry no kind byte; top-level messages stamp
// the discriminator at offset 0.
if !s.Element {
fmt.Fprintf(w, "\tob.SetUint8(0, uint8(Kind%s))\n", s.WireName)
}
for _, f := range scalar {
fmt.Fprintf(w, "\tob.%s(Offset%s_%s, %s)\n",
typeToSetMethod[f.Type], s.WireName, f.Name, lowerFirst(f.Name))
}
for _, f := range accessor {
switch {
case f.IsVarString():
fmt.Fprintf(w, "\tob.SetText(Offset%s_%s, %s)\n",
s.WireName, f.Name, lowerFirst(f.Name))
case f.IsVarBytes():
fmt.Fprintf(w, "\tob.SetBytes(Offset%s_%s, %s)\n",
s.WireName, f.Name, lowerFirst(f.Name))
default:
// Fixed array passed as a slice via [:] so SetBytesFixed gets []byte.
fmt.Fprintf(w, "\tob.SetBytesFixed(Offset%s_%s, %s[:])\n",
s.WireName, f.Name, lowerFirst(f.Name))
}
}
// List and nested fields write into the object tail AFTER every
// fixed/string/bytes field, via the typed [zapv1.WriteList] /
// [zapv1.WriteNested] machinery bridged onto the hand-rolled
// ObjectBuilder by [zapv1.SetterFrom]. Each sub-object is built inline
// (one write per sub-field) so variable-length parents and tail objects
// never interleave.
if len(list) > 0 || len(nested) > 0 {
fmt.Fprintf(w, "\tls := zapv1.SetterFrom[%s](ob, b)\n", s.GoName)
for _, f := range list {
elem, _ := f.IsList()
// Tailed elements (string/bytes/nested sub-fields) need the
// out-of-line pointer array; scalar-only elements stay inline.
writeFn, setterT := "WriteList", "ElemSetter"
if elemIsTailed(elem) {
writeFn, setterT = "WriteListNested", "NestedElemSetter"
}
fmt.Fprintf(w, "\tzapv1.%s[%s, %s](ls, Offset%s_%s, func(es *zapv1.%s[%s]) {\n",
writeFn, s.GoName, elem.Schema, s.WireName, f.Name, setterT, elem.Schema)
fmt.Fprintf(w, "\t\tfor _, it := range %s {\n", lowerFirst(f.Name))
fmt.Fprintf(w, "\t\t\tes.Append(func(e zapv1.Setter[%s]) {\n", elem.Schema)
emitElemWrites(w, elem.Schema, elem.Wire, "e", "it", "\t\t\t\t", elem.Fields)
fmt.Fprintf(w, "\t\t\t})\n\t\t}\n\t})\n")
}
// Nested: a singular out-of-line object, written only when present
// (nil pointer => unset => null object pointer).
for _, f := range nested {
nm, _ := f.IsNested()
pname := lowerFirst(f.Name)
fmt.Fprintf(w, "\tif %s != nil {\n", pname)
fmt.Fprintf(w, "\t\tzapv1.WriteNested[%s, %s](ls, Offset%s_%s, func(e zapv1.Setter[%s]) {\n",
s.GoName, nm.Schema, s.WireName, f.Name, nm.Schema)
emitElemWrites(w, nm.Schema, nm.Wire, "e", pname, "\t\t\t", nm.Fields)
fmt.Fprintf(w, "\t\t})\n\t}\n")
}
}
fmt.Fprintf(w, ` rootOff := ob.FinishAsRoot()
buf := b.Finish()
end := rootOff + Size%s
return zapv1.AsView[%s](zapv1.RawFromSlices(buf, rootOff, end)), buf
}
`, s.WireName, s.GoName)
}
// emitWrapFunction writes the Wrap%s function that parses and
// type-checks a buffer. The body uses v1 primitives so it inlines.
func emitWrapFunction(w io.Writer, s Schema) {
fmt.Fprintf(w, `// Wrap%s parses a ZAP buffer into a typed
// [zapv1.View[%s]]. Validates the wire frame and the kind
// discriminator at offset 0; returns [*zapv1.SchemaError] on kind
// mismatch.
//
// Hand-rolled fast path: inlines [zap.Parse] (which is itself
// inlineable). Zero heap allocs on the happy path; matches v1
// hand-rolled performance.
func Wrap%s(b []byte) (zapv1.View[%s], error) {
msg, err := zap.Parse(b)
if err != nil {
return zapv1.View[%s]{}, err
}
root := msg.Root()
`, s.WireName, s.WireName, s.WireName, s.GoName, s.GoName)
// Top-level messages validate the kind discriminator at offset 0; flat
// list-element schemas carry no kind byte, so they skip the check.
if !s.Element {
fmt.Fprintf(w, ` if got := root.Uint8(0); got != uint8(Kind%s) {
return zapv1.View[%s]{}, zapv1.NewSchemaError(
Kind%s, zapv1.KindByte(got), %q)
}
`, s.WireName, s.GoName, s.WireName, s.WireName)
}
fmt.Fprintf(w, ` data := msg.Bytes()
rootOff := root.Offset()
end := rootOff + Size%s
if end > len(data) {
end = len(data) // Read returns zero on OOB; v1 graceful degradation
}
return zapv1.AsView[%s](zapv1.RawFromSlices(data, rootOff, end)), nil
}
`, s.WireName, s.GoName)
}
// emitByteFieldAccessors emits per-byte-array-field typed accessor
// functions. Each field gets a Get<Field>(view) [N]byte function that
// reads the inline byte array from the view's payload via v1's
// Object.BytesFixedSlice; zero copy on the read path, one [N]byte
// copy into the returned value (necessary because the slice is alive
// only as long as the View's underlying buffer is, and the typed
// accessor must hand the caller an owned value).
func emitAccessorFields(w io.Writer, s Schema, fields []Field) {
if len(fields) == 0 {
return
}
for _, f := range fields {
switch {
case f.IsVarString():
fmt.Fprintf(w, `// %s%s reads the variable-length %s string field of %s.
//
// Zero-copy: v1's Object.Text returns a string over a sub-slice of the
// underlying buffer. Valid only while the View's buffer is alive.
func %s%s(v zapv1.View[%s]) string {
msg := zap.WrapBuffer(v.Bytes())
obj := msg.RootObjectAt(int(zapv1.RootOff(v)))
return obj.Text(Offset%s_%s)
}
`, s.WireName, f.Name, f.Name, s.WireName,
s.WireName, f.Name, s.GoName,
s.WireName, f.Name)
case f.IsVarBytes():
fmt.Fprintf(w, `// %s%s reads the variable-length %s bytes field of %s.
//
// Zero-copy: v1's Object.Bytes returns a sub-slice of the underlying
// buffer. Valid only while the View's buffer is alive; copy to retain.
func %s%s(v zapv1.View[%s]) []byte {
msg := zap.WrapBuffer(v.Bytes())
obj := msg.RootObjectAt(int(zapv1.RootOff(v)))
return obj.Bytes(Offset%s_%s)
}
`, s.WireName, f.Name, f.Name, s.WireName,
s.WireName, f.Name, s.GoName,
s.WireName, f.Name)
default:
n, _ := f.IsBytes()
fmt.Fprintf(w, `// %s%s reads the %d-byte inline %s field of %s.
//
// Zero-copy on the read side (v1's Object.BytesFixedSlice returns
// a sub-slice of the underlying buffer); the returned [%d]byte is a
// value copy so the caller's slot owns the bytes after the View's
// buffer is freed.
func %s%s(v zapv1.View[%s]) [%d]byte {
msg := zap.WrapBuffer(v.Bytes())
obj := msg.RootObjectAt(int(zapv1.RootOff(v)))
slice := obj.BytesFixedSlice(Offset%s_%s, %d)
var out [%d]byte
if len(slice) == %d {
copy(out[:], slice)
}
return out
}
`, s.WireName, f.Name, n, f.Name, s.WireName,
n,
s.WireName, f.Name, s.GoName, n,
s.WireName, f.Name, n,
n,
n)
}
}
}
// emitListSupport writes, for each list field: the per-element value
// input struct the constructor accepts, and the typed read accessor that
// returns a [zapv1.List][E] over the object tail (range-over-func ready,
// zero-copy). The element schema's own Fields/Wrap live in its emitted
// file; here we only need its marker GoName and field shape.
func emitListSupport(w io.Writer, s Schema, list []Field) {
for _, f := range list {
elem, _ := f.IsList()
// Per-element value-input struct (e.g. `type Item struct{ ID,
// Value uint64 }`). The constructor takes []<Value>; WriteList
// builds each element from it.
fmt.Fprintf(w, "// %s is the per-element input for the %s.%s list.\ntype %s struct {\n",
elem.Value, s.WireName, f.Name, elem.Value)
for _, ef := range elem.Fields {
fmt.Fprintf(w, "\t%s %s\n", ef.Name, nestedFieldGoType(ef))
}
fmt.Fprintf(w, "}\n\n")
// Read accessor: a typed, range-over-func-ready list view. Tailed
// elements use the out-of-line ListNested[E]; scalar-only elements
// use the inline List[E]. Both are zero-copy and range-over-func.
listT, atFn := "List", "ListAt"
if elemIsTailed(elem) {
listT, atFn = "ListNested", "ListNestedAt"
}
fmt.Fprintf(w, `// %s%s returns the variable-length %s list of %s as a
// range-over-func-ready [zapv1.%s][%s]. Zero-copy: each element view
// indexes straight into the underlying buffer.
//
// for it := range %s%s(view).All() {
// // zapv1.Read(it, %sFields.X)
// }
func %s%s(v zapv1.View[%s]) zapv1.%s[%s] {
return zapv1.%s[%s, %s](v, Offset%s_%s)
}
`, s.WireName, f.Name, f.Name, s.WireName,
listT, elem.Schema,
s.WireName, f.Name,
elem.Schema,
s.WireName, f.Name, s.GoName, listT, elem.Schema,
atFn, s.GoName, elem.Schema, s.WireName, f.Name)
}
}
// emitNestedSupport writes, for each nested-object field: the value-input
// struct the constructor accepts (by pointer), and the typed read accessor
// returning a [zapv1.View][N] over the nested object in the tail (zero-copy;
// the zero View when the field is unset/null). The singular peer of
// [emitListSupport]. The nested schema's own Fields/Wrap live in its own
// emitted file; here we need only its marker GoName and field shape.
func emitNestedSupport(w io.Writer, s Schema, nested []Field) {
for _, f := range nested {
nm, _ := f.IsNested()
// Per-nested value-input struct (e.g. `type Inner struct{ ID
// uint64; Label string }`). The constructor takes *<Value>;
// WriteNested builds the object from it when non-nil.
fmt.Fprintf(w, "// %s is the value input for the singular %s.%s nested object.\ntype %s struct {\n",
nm.Value, s.WireName, f.Name, nm.Value)
for _, ef := range nm.Fields {
fmt.Fprintf(w, "\t%s %s\n", ef.Name, nestedFieldGoType(ef))
}
fmt.Fprintf(w, "}\n\n")
// Read accessor: a typed View over the nested object. Test the
// result with View.IsZero for the unset case.
fmt.Fprintf(w, `// %s%s returns the singular %s nested object of %s as a
// zero-copy [zapv1.View][%s]. An unset (null) nested field yields the zero
// View — test it with [zapv1.View.IsZero].
func %s%s(v zapv1.View[%s]) zapv1.View[%s] {
return zapv1.NestedAt[%s, %s](v, Offset%s_%s)
}
`, s.WireName, f.Name, f.Name, s.WireName,
nm.Schema,
s.WireName, f.Name, s.GoName, nm.Schema,
s.GoName, nm.Schema, s.WireName, f.Name)
}
}
// nestedFieldGoType returns the Go type to use in a value-input struct
// field: the declared scalar type, "string"/"[]byte" for variable tails,
// or "[N]byte" for fixed byte arrays.
func nestedFieldGoType(f Field) string {
switch {
case f.IsVarString():
return "string"
case f.IsVarBytes():
return "[]byte"
default:
if n, ok := f.IsBytes(); ok {
return fmt.Sprintf("[%d]byte", n)
}
return f.Type
}
}
// emitRegisterInit writes an init function that registers the schema
// with the [zapv1.DefaultRegistry]. Skipped when [Schema.SkipRegistry]
// is true, used for schemas whose Kind byte is local to a per-package
// (private) registry rather than globally unique.
func emitRegisterInit(w io.Writer, s Schema) {
if s.SkipRegistry {
return
}
fmt.Fprintf(w, `func init() {
zapv1.Register[%s](zapv1.DefaultRegistry)
}
`, s.GoName)
}
// lowerFirst returns s with its first character lower-cased. Used to
// turn an exported field name ("Time") into a parameter name ("time").
//
// If the lower-cased result collides with a Go keyword (e.g. "Type" →
// "type"), the result is suffixed with "_" ("type_") to avoid a parse
// error in the emitted function signature. Identifier semantics are
// preserved: the parameter name is local to the constructor, and the
// schema field name (exported) is untouched.
func lowerFirst(s string) string {
if s == "" {
return s
}
var out string
if s[0] >= 'A' && s[0] <= 'Z' {
out = string(s[0]+32) + s[1:]
} else {
out = s
}
if goKeywords[out] {
return out + "_"
}
return out
}
// goKeywords is the set of reserved identifiers in Go 1.x. Listed from
// the Go spec §"Keywords" (https://go.dev/ref/spec#Keywords).
var goKeywords = map[string]bool{
"break": true,
"case": true,
"chan": true,
"const": true,
"continue": true,
"default": true,
"defer": true,
"else": true,
"fallthrough": true,
"for": true,
"func": true,
"go": true,
"goto": true,
"if": true,
"import": true,
"interface": true,
"map": true,
"package": true,
"range": true,
"return": true,
"select": true,
"struct": true,
"switch": true,
"type": true,
"var": true,
}