Files
zip/layout.go
T

876 lines
30 KiB
Go

package zip
// Layouts — the source that STATES a type's ZAP wire instead of deriving one at
// every call. Nothing here runs on the served path; this is a generator.
//
// [LayoutOf] names three readers of the one derivation: the plane encodes
// against it, a .zap schema states it as `Name type @Offset`, and a generator
// emits it as constants. This is that generator. What it writes is a [Wire]
// implementation: two methods per type, offsets as constants, no reflection on
// the served path.
//
// # Why generate rather than reflect
//
// The derivation is cached; the COPY is not. Every reflective call walks the
// fields again and sizes a builder by guess — work whose answer was fixed the
// moment the type was declared.
//
// It is also the only way to carry an id. bytes_fixed[N] has an offset and a
// width, so the layout knows its shape and a schema states it correctly, and the
// reflective encoder refuses it outright — deliberately, because an id is
// exactly the case that should force a type to declare its own wire. An ids.ID
// is [32]byte, so a reply carrying one crosses on a codec or not at all.
//
// The Go SDK writes its own with this emitter rather than a second one, through
// the [naming] seam: it restates the fleet's types under its own names, so the
// offsets come from the declared type and the identifiers come from it. See
// [App.SDK].
//
// # The wire does not move
//
// The emitted code writes the SAME BYTES the reflective encoder writes, field
// for field and in the same order, for every field the reflective encoder can
// write at all. That is not a promise about intent — it falls out of reading the
// offsets from [LayoutOf] rather than deriving them a second time, and
// TestTheLayoutKeepsTheWire holds it against the reflective encoder directly.
//
// The sequence it reproduces: every list is written FIRST, because SetList takes
// an offset that must already exist; then the object is started at the declared
// size; then each field is set in declaration order; then the list slots are
// filled. Deferred text and bytes are laid down by the builder in the order they
// were set, so declaration order is load-bearing and not cosmetic.
//
// # What the emitted package depends on
//
// The ZAP builder, and nothing else. [Wire] is restated in the emitted file, the
// way internal/zapenc restates it, so a leaf module — an id package, a shared
// types package — can state its wire without taking on this one's dependencies.
// The restatement is a compile-time assertion per type, which is what makes a
// deleted codec a build failure rather than a silent return to reflection.
import (
"bytes"
"fmt"
"go/format"
"reflect"
"sort"
"strconv"
"strings"
)
// Layout is one emitted file: the Go source stating the wire of every type in one
// package, and the names of the types it states.
type Layout struct {
Path string // the package's import path
Package string // its package clause
Types []string // the types declared, alphabetically
Source []byte
// Stated is true when every type here ALREADY answers for its own bytes.
// A generator writing into one module reads types from several, and the
// ones it cannot write to are only a problem while they are still silent —
// so this is what tells "another module has to release" from "another
// module already did".
Stated bool
}
// Layouts renders a [Wire] implementation for each of roots and for every struct
// nested below them, grouped by the package that owns the type.
//
// A nested value is reached BY ITS METHOD, so the set is closed downward: a
// parent that states its wire needs every struct it holds to state one too, id
// or no id. The exception is a value with no slots — see [empty] — which crosses
// as bytes the parent writes inline.
//
// A type the derivation refuses — one holding a map, an interface, or a fixed
// array of anything but bytes — comes back as an error naming it, because there
// is no layout to state and a codec that guessed one would speak a wire nobody
// reads. Answering that is a change to the TYPE, which is not a generator's to
// make.
func Layouts(roots ...reflect.Type) ([]Layout, error) {
want := map[reflect.Type]bool{}
var refused []string
for _, r := range roots {
if err := reach(r, want); err != nil {
refused = append(refused, err.Error())
}
}
if len(refused) > 0 {
sort.Strings(refused)
return nil, fmt.Errorf("zip: %s", strings.Join(dedup(refused), "; "))
}
byPath := map[string][]reflect.Type{}
for t := range want {
byPath[t.PkgPath()] = append(byPath[t.PkgPath()], t)
}
out := make([]Layout, 0, len(byPath))
for path, ts := range byPath {
sort.Slice(ts, func(i, j int) bool { return ts[i].Name() < ts[j].Name() })
c, err := one(path, ts, want)
if err != nil {
return nil, err
}
c.Stated = true
for _, t := range ts {
if !reflect.PointerTo(t).Implements(wired) {
c.Stated = false
break
}
}
out = append(out, c)
}
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
return out, nil
}
var wired = reflect.TypeOf((*Wire)(nil)).Elem()
// reach adds t and everything below it to want.
func reach(t reflect.Type, want map[reflect.Type]bool) error {
t = under(t)
if t == nil || t.Kind() != reflect.Struct || want[t] {
return nil
}
shape, err := LayoutOf(t)
if err != nil {
return fmt.Errorf("%s has no layout: %w", goname(t), err)
}
want[t] = true
for _, s := range shape.Slots {
f, ok := held(t, s.Name)
if !ok {
continue
}
if n := nests(f.Type); n != nil && !empty(n) {
if err := reach(n, want); err != nil {
return err
}
}
}
return nil
}
// empty reports a value with no slots — every field unexported, which is what a
// time.Time or a netip.AddrPort is from here. It crosses as a complete and EMPTY
// ZAP object and carries nothing, so a parent writes those bytes inline rather
// than reaching for a method, and there is nothing to declare a wire for. The
// value is lost either way; that is the reflective encoder's answer too, and
// changing it would be a different wire.
func empty(t reflect.Type) bool {
sh, err := LayoutOf(t)
return err == nil && len(sh.Slots) == 0
}
// nests is the struct a field reaches through a pointer or a list, or nil. A
// slot is "bytes" for both a []byte and a nested value, so the Go type is what
// tells them apart.
func nests(t reflect.Type) reflect.Type {
t = under(t)
if t.Kind() == reflect.Slice {
t = under(t.Elem())
}
if t.Kind() == reflect.Struct {
return t
}
return nil
}
func under(t reflect.Type) reflect.Type {
for t != nil && t.Kind() == reflect.Pointer {
t = t.Elem()
}
return t
}
// held is the DIRECT field of that name. FieldByName would also find one
// promoted through an embedded struct, and an embedded struct is a slot of its
// own here — its fields are nested, never flattened.
func held(t reflect.Type, name string) (reflect.StructField, bool) {
for i := range t.NumField() {
if f := t.Field(i); f.Name == name {
return f, true
}
}
return reflect.StructField{}, false
}
func goname(t reflect.Type) string {
if t.PkgPath() == "" {
return t.String()
}
return t.PkgPath() + "." + t.Name()
}
func dedup(s []string) []string {
out := s[:0]
var last string
for _, v := range s {
if v != last {
out = append(out, v)
}
last = v
}
return out
}
// ---- emission --------------------------------------------------------------
// pkg is the import set of one emitted file: an alias per path, so a name that
// two packages share is unambiguous in the source rather than in a convention.
// wide records whether any list of scalars was emitted, which is the one helper
// the file may need.
type pkg struct {
self string
alias map[string]string
taken map[string]bool
plain map[string]bool // imports whose alias is their own name
// as is how a file that did NOT declare these types spells them. The Go SDK
// restates the fleet's types under its own names and emits their codec
// beside them, so the offsets come from the declared type and every
// identifier comes from here. nil is the ordinary case — a generator
// writing into the package that declared the types, where every name is the
// name the type already has.
as naming
}
// naming is what one emitted file calls the types it writes: a declaration's
// name, a field's name inside it, and the type as Go source. Three questions
// rather than one, because a rename does not reach them all the same way — an
// embedded field IS its type's name, and a field of a renamed type is not.
type naming interface {
name(reflect.Type) string
field(reflect.Type, string) string
spell(reflect.Type) string
}
// name is what the emitted file calls t.
func (p *pkg) name(t reflect.Type) string {
if p.as != nil {
return p.as.name(t)
}
return t.Name()
}
// field is what the emitted file calls t's field. It differs from the declared
// name only for an embedded field, which is spelled as its type — so a file
// that renamed the type renamed the field with it.
func (p *pkg) field(t reflect.Type, name string) string {
if p.as != nil {
return p.as.field(t, name)
}
return name
}
func (p *pkg) of(path, name string) string {
if path == "" || path == p.self {
return ""
}
if a, ok := p.alias[path]; ok {
return a
}
a := name
for n := 2; p.taken[a]; n++ {
a = name + strconv.Itoa(n)
}
p.taken[a] = true
p.alias[path] = a
if a == name {
p.plain[path] = true
}
return a
}
// std takes a standard-library import the emitted code needs directly.
func (p *pkg) std(path string) { p.of(path, path[strings.LastIndex(path, "/")+1:]) }
// spell renders t as Go source inside this file, taking the import it needs.
func (p *pkg) spell(t reflect.Type) string {
if p.as != nil {
// A file that restated these types spells them itself, and takes no
// import to do it: the restatement is why it has none to take.
return p.as.spell(t)
}
if t.Name() != "" {
if a := p.of(t.PkgPath(), strings.SplitN(t.String(), ".", 2)[0]); a != "" {
return a + "." + t.Name()
}
return t.Name()
}
switch t.Kind() {
case reflect.Pointer:
return "*" + p.spell(t.Elem())
case reflect.Slice:
return "[]" + p.spell(t.Elem())
case reflect.Array:
return "[" + strconv.Itoa(t.Len()) + "]" + p.spell(t.Elem())
}
return t.String()
}
func one(path string, ts []reflect.Type, want map[reflect.Type]bool) (Layout, error) {
p := &pkg{
self: path,
alias: map[string]string{},
taken: map[string]bool{"zap": true, "fmt": true},
plain: map[string]bool{},
}
var body bytes.Buffer
names := make([]string, 0, len(ts))
for _, t := range ts {
if err := declare(&body, t, p); err != nil {
return Layout{}, err
}
names = append(names, t.Name())
}
clause := path[strings.LastIndex(path, "/")+1:]
if len(ts) > 0 {
clause = strings.SplitN(ts[0].String(), ".", 2)[0]
}
var out bytes.Buffer
fmt.Fprintf(&out, "// Code generated by zip; DO NOT EDIT.\n\npackage %s\n\nimport (\n\t\"fmt\"\n", clause)
paths := make([]string, 0, len(p.alias))
for q := range p.alias {
paths = append(paths, q)
}
sort.Strings(paths)
for _, q := range paths {
if p.plain[q] && !strings.Contains(q, ".") {
fmt.Fprintf(&out, "\t%q\n", q)
}
}
fmt.Fprint(&out, "\n\tzap \"github.com/zap-proto/go\"\n")
for _, q := range paths {
if !p.plain[q] || strings.Contains(q, ".") {
fmt.Fprintf(&out, "\t%s %q\n", p.alias[q], q)
}
}
fmt.Fprint(&out, ")\n\n")
out.Write(body.Bytes())
src, err := format.Source(out.Bytes())
if err != nil {
return Layout{}, fmt.Errorf("zip: emitted source for %s does not parse: %w\n%s", path, err, out.String())
}
return Layout{Path: path, Package: clause, Types: names, Source: src}, nil
}
// stated is the compile-time assertion that a type answers for its own bytes.
// It is [Wire] written out where it is consumed, so an emitted package depends
// on the ZAP builder and nothing else — and written INLINE, because a named
// interface would be a package-level identifier the emitter cannot know is free.
// github.com/luxfi/utxo imports a package called wire.
//
// Both halves are required together: a type carrying one would encode from these
// constants and decode by reflection, which is two answers to where its layout
// lives.
const stated = `var _ interface {
MarshalZAP() ([]byte, error)
UnmarshalZAP([]byte) error
} = (*%s)(nil)
`
// emptying is the ZAP message a value with no slots crosses as. A type whose
// fields are all unexported — a time.Time, a netip.AddrPort — states nothing, so
// what crosses is a complete and empty object. It is what the reflective encoder
// writes for one, byte for byte.
const emptying = `// empty is the ZAP message a value with no slots crosses as.
func empty() []byte {
b := zap.NewBuilder(zap.HeaderSize)
b.StartObject(0).FinishAsRoot()
return b.Finish()
}
`
func declare(w *bytes.Buffer, t reflect.Type, p *pkg) error {
shape, err := LayoutOf(t)
if err != nil {
return fmt.Errorf("zip: %s: %w", goname(t), err)
}
name := p.name(t)
lo := lower(name)
fmt.Fprintf(w, "// ---- %s %s\n\n", name, strings.Repeat("-", max(1, 66-len(name))))
fmt.Fprint(w, "const (\n")
for _, s := range shape.Slots {
fmt.Fprintf(w, "\t%s%sAt = %d\n", lo, p.field(t, s.Name), s.Offset)
}
fmt.Fprintf(w, "\t%sSize = %d\n)\n\n", lo, shape.Size)
fmt.Fprintf(w, stated, name)
fmt.Fprintf(w, "// MarshalZAP writes %s from constant offsets.\n", name)
fmt.Fprintf(w, "func (x *%s) MarshalZAP() ([]byte, error) {\n", name)
// A nil receiver is an absent body, not an empty object — the answer the
// reflective encoder gives, and a handler returning a nil *Out reaches here
// as a non-nil interface holding one.
fmt.Fprint(w, "\tif x == nil {\n\t\treturn nil, nil\n\t}\n")
fmt.Fprintf(w, "\tb := zap.NewBuilder(%sSize + 256)\n", lo)
for _, s := range shape.Slots {
f, ok := held(t, s.Name)
if !ok {
continue
}
switch kindOf(s, f.Type) {
case aList:
if err := writeList(w, p.field(t, s.Name), s, f, p); err != nil {
return err
}
case aOpt:
if err := writeOpt(w, p.field(t, s.Name), s, f, p); err != nil {
return err
}
}
}
fmt.Fprintf(w, "\tob := b.StartObject(%sSize)\n", lo)
for _, s := range shape.Slots {
f, ok := held(t, s.Name)
if !ok {
continue
}
if listed(kindOf(s, f.Type)) {
continue
}
if err := writeField(w, lo, p.field(t, s.Name), s, f, p); err != nil {
return err
}
}
for _, s := range shape.Slots {
f, ok := held(t, s.Name)
if !ok || !listed(kindOf(s, f.Type)) {
continue
}
fn := p.field(t, s.Name)
v := lower(fn)
fmt.Fprintf(w, "\tif %sN > 0 {\n\t\tob.SetList(%s%sAt, %sAt, %sN)\n\t}\n", v, lo, fn, v, v)
}
fmt.Fprint(w, "\tob.FinishAsRoot()\n\treturn b.Finish(), nil\n}\n\n")
fmt.Fprintf(w, "// UnmarshalZAP reads %s out of the buffer that arrived.\n", name)
fmt.Fprintf(w, "func (x *%s) UnmarshalZAP(data []byte) error {\n", name)
// An empty message leaves the value untouched: a void reply is an absence,
// not a zero somebody might mistake for an answer.
fmt.Fprint(w, "\tif x == nil || len(data) == 0 {\n\t\treturn nil\n\t}\n")
fmt.Fprintf(w, "\tm, err := zap.Parse(data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s: %%w\", err)\n\t}\n", name)
// The root is taken only if a field reads it. A message with no slot to
// read still PARSES — an unreadable one is an error, not an empty value —
// but Go refuses a variable nobody uses.
var reads bytes.Buffer
for _, s := range shape.Slots {
f, ok := held(t, s.Name)
if !ok {
continue
}
if err := readField(&reads, lo, p.field(t, s.Name), s, f, p); err != nil {
return err
}
}
if reads.Len() > 0 {
fmt.Fprint(w, "\to := m.Root()\n")
w.Write(reads.Bytes())
} else {
fmt.Fprint(w, "\t_ = m\n")
}
fmt.Fprint(w, "\treturn nil\n}\n\n")
return nil
}
// The shapes a slot takes on this wire. Everything else is a scalar, which the
// setter table answers for by its .zap name.
type form int
const (
aScalar form = iota
aFixed
aNest
aList
// aOpt is a pointer to a scalar, text or bytes: a list of at most one
// element, so a pointer to zero is not mistaken for no pointer.
aOpt
)
// listed is whether a slot is written before the object and filled by SetList
// after it — every list, including the one-element list an optional is.
func listed(f form) bool { return f == aList || f == aOpt }
func kindOf(s Slot, ft reflect.Type) form {
ft = under(ft)
switch {
case ft.Kind() == reflect.Array:
return aFixed
case ft.Kind() == reflect.Struct:
return aNest
case !strings.HasPrefix(s.Type, "list<"):
return aScalar
case ft.Kind() == reflect.Slice && ft.Elem().Kind() != reflect.Uint8:
return aList // a []byte is one value, so a list<…> over one is an optional
case s.Ptr:
return aOpt
}
return aScalar
}
// setter is the builder call for one .zap type and getter its reader; goType is
// what the reader hands back, for the conversion a defined type needs. They are
// one table so the two directions cannot name different widths.
var setter = map[string][3]string{
"bool": {"SetBool", "Bool", "bool"},
"i8": {"SetInt8", "Int8", "int8"},
"i16": {"SetInt16", "Int16", "int16"},
"i32": {"SetInt32", "Int32", "int32"},
"i64": {"SetInt64", "Int64", "int64"},
"u8": {"SetUint8", "Uint8", "uint8"},
"u16": {"SetUint16", "Uint16", "uint16"},
"u32": {"SetUint32", "Uint32", "uint32"},
"u64": {"SetUint64", "Uint64", "uint64"},
"f32": {"SetFloat32", "Float32", "float32"},
"f64": {"SetFloat64", "Float64", "float64"},
"text": {"SetText", "Text", "string"},
"bytes": {"SetBytes", "Bytes", "[]byte"},
}
// writeField emits one field's write. fn is what the file being written calls
// the field, which is the declared name unless that file restated the type.
func writeField(w *bytes.Buffer, lo, fn string, s Slot, f reflect.StructField, p *pkg) error {
at := lo + fn + "At"
ref, tab, end := "x."+fn, "\t", ""
if s.Ptr {
// An absent pointer writes nothing: a null, not a zero, and the slot is
// already zeroed.
fmt.Fprintf(w, "\tif x.%s != nil {\n", fn)
ref, tab, end = "(*x."+fn+")", "\t\t", "\t}\n"
}
switch kindOf(s, f.Type) {
case aFixed:
fmt.Fprintf(w, "%sob.SetBytesFixed(%s, %s[:])\n", tab, at, ref)
case aNest:
if empty(under(f.Type)) {
fmt.Fprintf(w, "%s{\n%s\teb := zap.NewBuilder(zap.HeaderSize)\n%s\teb.StartObject(0).FinishAsRoot()\n%s\tob.SetBytes(%s, eb.Finish())\n%s}\n",
tab, tab, tab, tab, at, tab)
break
}
// x.F reaches the pointer method on its own: a field of a pointer
// receiver is addressable, and a pointer field already is one.
fmt.Fprintf(w, "%sinner%s, err := x.%s.MarshalZAP()\n%sif err != nil {\n%s\treturn nil, err\n%s}\n",
tab, fn, fn, tab, tab, tab)
fmt.Fprintf(w, "%sob.SetBytes(%s, inner%s)\n", tab, at, fn)
default:
c, ok := setter[s.Type]
if !ok {
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
}
fmt.Fprintf(w, "%sob.%s(%s, %s(%s))\n", tab, c[0], at, c[2], ref)
}
fmt.Fprint(w, end)
return nil
}
func readField(w *bytes.Buffer, lo, fn string, s Slot, f reflect.StructField, p *pkg) error {
at := lo + fn + "At"
ft := f.Type
if s.Ptr {
ft = under(ft)
}
switch kindOf(s, f.Type) {
case aFixed:
if !s.Ptr {
fmt.Fprintf(w, "\tcopy(x.%s[:], o.BytesFixed(%s, %d))\n", fn, at, s.N)
return nil
}
// A POINTER to a fixed array. Reading straight into x.F[:] dereferences
// a nil, and the value arriving is always a fresh one — so every read of
// a type with an optional id PANICKED, on the plane, at the far end.
//
// An inline slot has no null: the writer leaves an absent pointer's
// bytes zeroed (see [writeField]), so all-zero is what absence looks
// like coming back, and a pointer to an all-zero array reads back nil.
// This is the one pointer that still cannot say present-and-zero: a
// scalar crosses as a list of at most one element, and bytes_fixed has no
// list form inline. Giving it one moves the wire of every optional id.
name := p.spell(ft)
fmt.Fprintf(w, "\tif raw := o.BytesFixed(%s, %d); len(raw) > 0 {\n", at, s.N)
fmt.Fprintf(w, "\t\tvar v %s\n\t\tcopy(v[:], raw)\n", name)
fmt.Fprintf(w, "\t\tif v != (%s{}) {\n\t\t\tx.%s = &v\n\t\t}\n\t}\n", name, fn)
return nil
case aNest:
if empty(under(f.Type)) {
return nil // nothing crossed, so there is nothing to read back
}
fmt.Fprintf(w, "\tif raw := o.Bytes(%s); len(raw) > 0 {\n", at)
if s.Ptr {
fmt.Fprintf(w, "\t\tvar v %s\n\t\tif err := v.UnmarshalZAP(raw); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tx.%s = &v\n", p.spell(ft), fn)
} else {
fmt.Fprintf(w, "\t\tif err := x.%s.UnmarshalZAP(raw); err != nil {\n\t\t\treturn err\n\t\t}\n", fn)
}
fmt.Fprint(w, "\t}\n")
return nil
case aList:
return readList(w, at, fn, s, f, p)
case aOpt:
return readOpt(w, at, fn, s, f, p)
}
c, ok := setter[s.Type]
if !ok {
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
}
spell := p.spell(ft)
read := fmt.Sprintf("%s(o.%s(%s))", spell, c[1], at)
switch s.Type {
case "bytes":
read = fmt.Sprintf("%s(append([]byte(nil), o.Bytes(%s)...))", spell, at)
case "text":
// ZAP decodes a string zero-copy, over the frame that arrived, and the
// next call on that connection reuses the frame. A reply that outlives
// its call would mutate under its owner, so the codec copies here.
read = fmt.Sprintf("%s(strings.Clone(o.Text(%s)))", spell, at)
p.std("strings")
}
fmt.Fprintf(w, "\tx.%s = %s\n", fn, read)
return nil
}
// writeOpt emits an optional's one-element list, written before the object as
// every list is. Its element is what a list element of the same type is, so the
// reflective encoder and this agree byte for byte.
func writeOpt(w *bytes.Buffer, fn string, s Slot, f reflect.StructField, p *pkg) error {
v := lower(fn)
ref := "(*x." + fn + ")"
fmt.Fprintf(w, "\t%sAt, %sN := 0, 0\n\tif x.%s != nil {\n", v, v, fn)
switch s.Elem {
case "text", "bytes":
fmt.Fprintf(w, "\t\tenc := []byte(%s)\n", ref)
case "bool":
fmt.Fprintf(w, "\t\tenc := []byte{0}\n\t\tif %s {\n\t\t\tenc[0] = 1\n\t\t}\n", ref)
default:
if _, ok := setter[s.Elem]; !ok {
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
}
fmt.Fprint(w, "\t\tvar full [8]byte\n")
fmt.Fprintf(w, "\t\tbinary.LittleEndian.PutUint64(full[:], %s)\n", bits(s.Elem, ref))
fmt.Fprintf(w, "\t\tenc := full[:%d]\n", widthOf(s.Elem))
if s.Elem == "f32" || s.Elem == "f64" {
p.std("math")
}
}
fmt.Fprintf(w, "\t\t%sAt = b.WriteBytes(append(binary.LittleEndian.AppendUint32(nil, uint32(len(enc))), enc...))\n", v)
fmt.Fprintf(w, "\t\t%sN = 1\n\t}\n", v)
p.std("encoding/binary")
return nil
}
// readOpt emits the read of an optional: present exactly when its list holds an
// element, whatever that element's value.
func readOpt(w *bytes.Buffer, at, fn string, s Slot, f reflect.StructField, p *pkg) error {
spell := p.spell(under(f.Type))
fmt.Fprintf(w, "\tif l := o.List(%s); l.Len() > 0 {\n\t\traw := l.BytesAt(0)\n", at)
switch s.Elem {
case "text":
fmt.Fprintf(w, "\t\tv := %s(raw)\n", spell)
case "bytes":
fmt.Fprintf(w, "\t\tv := %s(append([]byte(nil), raw...))\n", spell)
case "bool":
fmt.Fprintf(w, "\t\tv := %s(len(raw) > 0 && raw[0] != 0)\n", spell)
default:
if _, ok := setter[s.Elem]; !ok {
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
}
fmt.Fprint(w, "\t\tvar full [8]byte\n\t\tcopy(full[:], raw)\n")
fmt.Fprintf(w, "\t\tv := %s(%s)\n", spell, elementRead(s))
p.std("encoding/binary")
if s.Elem == "f32" || s.Elem == "f64" {
p.std("math")
}
}
fmt.Fprintf(w, "\t\tx.%s = &v\n\t}\n", fn)
return nil
}
func writeList(w *bytes.Buffer, fn string, s Slot, f reflect.StructField, p *pkg) error {
v := lower(fn)
ref := "x." + fn
if s.Ptr {
ref = "(*x." + fn + ")"
}
et := under(f.Type).Elem()
ptr := et.Kind() == reflect.Pointer
el := under(et)
var elem bytes.Buffer
w, outer := &elem, w
switch {
case el.Kind() == reflect.Struct && empty(el):
fmt.Fprint(w, "\t\t\teb := zap.NewBuilder(zap.HeaderSize)\n\t\t\teb.StartObject(0).FinishAsRoot()\n\t\t\tenc := eb.Finish()\n")
case el.Kind() == reflect.Struct:
// A nil element encodes as the zero value, which is what the reflective
// encoder writes for one: a list has no hole to leave.
if ptr {
fmt.Fprintf(w, "\t\t\telem := %s[i]\n\t\t\tif elem == nil {\n\t\t\t\telem = new(%s)\n\t\t\t}\n", ref, p.spell(el))
fmt.Fprint(w, "\t\t\tenc, err := elem.MarshalZAP()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n")
} else {
fmt.Fprintf(w, "\t\t\tenc, err := %s[i].MarshalZAP()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n", ref)
}
case el.Kind() == reflect.Array:
fmt.Fprintf(w, "\t\t\tenc := %s[i][:]\n", ref)
case s.Elem == "text" || s.Elem == "bytes":
fmt.Fprintf(w, "\t\t\tenc := []byte(%s[i])\n", ref)
case s.Elem == "bool":
fmt.Fprintf(w, "\t\t\tenc := []byte{0}\n\t\t\tif %s[i] {\n\t\t\t\tenc[0] = 1\n\t\t\t}\n", ref)
default:
c, ok := setter[s.Elem]
if !ok {
return fmt.Errorf("zip: field %s: no wire form for list element %s", s.Name, s.Elem)
}
_ = c
fmt.Fprint(w, "\t\t\tvar full [8]byte\n")
fmt.Fprintf(w, "\t\t\tbinary.LittleEndian.PutUint64(full[:], %s)\n", element(s, ref))
fmt.Fprintf(w, "\t\t\tenc := full[:%d]\n", widthOf(s.Elem))
if s.Elem == "f32" || s.Elem == "f64" {
p.std("math")
}
}
fmt.Fprint(w, "\t\t\tvar n [4]byte\n\t\t\tbinary.LittleEndian.PutUint32(n[:], uint32(len(enc)))\n")
fmt.Fprint(w, "\t\t\tblob = append(blob, n[:]...)\n\t\t\tblob = append(blob, enc...)\n\t\t}\n")
w = outer
if s.Ptr {
// An absent pointer writes nothing, and its length cannot be asked for.
fmt.Fprintf(w, "\t%sAt, %sN := 0, 0\n\tif x.%s != nil {\n\t\t%sN = len(%s)\n\t}\n", v, v, fn, v, ref)
} else {
fmt.Fprintf(w, "\t%sAt, %sN := 0, len(%s)\n", v, v, ref)
}
fmt.Fprintf(w, "\tif %sN > 0 {\n\t\tvar blob []byte\n\t\tfor %s range %s {\n", v, index(&elem), ref)
w.Write(elem.Bytes())
fmt.Fprintf(w, "\t\t%sAt = b.WriteBytes(blob)\n\t}\n", v)
p.std("encoding/binary")
return nil
}
// index is the loop variable a list body needs, and nothing when it needs none:
// an element with no slots reads no index, and Go refuses a variable nobody uses.
func index(body *bytes.Buffer) string {
if bytes.Contains(body.Bytes(), []byte("[i]")) || bytes.Contains(body.Bytes(), []byte("(i)")) {
return "i :="
}
return ""
}
// element spells one list element for the little-endian write.
func element(s Slot, ref string) string { return bits(s.Elem, ref+"[i]") }
// bits spells one scalar as the uint64 its little-endian bytes are cut from. A
// float crosses as its bits, which is what the reflective encoder writes, so the
// two agree.
func bits(zaptype, v string) string {
switch zaptype {
case "f32":
return fmt.Sprintf("uint64(math.Float32bits(float32(%s)))", v)
case "f64":
return fmt.Sprintf("math.Float64bits(float64(%s))", v)
}
return fmt.Sprintf("uint64(%s)", v)
}
// readList emits a single pass over the list. Element i by index re-walks the
// stream from its start, so reading a list that way is quadratic in its length;
// the rows grow from the elements actually there rather than from the count the
// message claims.
func readList(w *bytes.Buffer, at, fn string, s Slot, f reflect.StructField, p *pkg) error {
ft := under(f.Type)
et := ft.Elem()
ptr := et.Kind() == reflect.Pointer
el := under(et)
fallible := el.Kind() == reflect.Struct && !empty(el)
var elem bytes.Buffer
w, outer := &elem, w
// A scalar element arrives as its value and is pointed at after; a struct
// element is decoded in place, so a pointer one is allocated first.
value, point := "e", ""
if ptr && el.Kind() != reflect.Struct {
value, point = "v", "\t\t\te := &v\n"
}
switch {
case el.Kind() == reflect.Struct:
if ptr {
fmt.Fprintf(w, "\t\t\te := new(%s)\n", p.spell(el))
} else {
fmt.Fprintf(w, "\t\t\tvar e %s\n", p.spell(el))
}
if fallible {
fmt.Fprint(w, "\t\t\tif ferr = e.UnmarshalZAP(raw); ferr != nil {\n\t\t\t\treturn false\n\t\t\t}\n")
}
case el.Kind() == reflect.Array:
fmt.Fprintf(w, "\t\t\tvar %s %s\n\t\t\tcopy(%s[:], raw)\n", value, p.spell(el), value)
case s.Elem == "text":
fmt.Fprintf(w, "\t\t\t%s := %s(raw)\n", value, p.spell(el))
case s.Elem == "bytes":
fmt.Fprintf(w, "\t\t\t%s := %s(append([]byte(nil), raw...))\n", value, p.spell(el))
case s.Elem == "bool":
fmt.Fprintf(w, "\t\t\t%s := %s(len(raw) > 0 && raw[0] != 0)\n", value, p.spell(el))
default:
fmt.Fprint(w, "\t\t\tvar full [8]byte\n\t\t\tcopy(full[:], raw)\n")
fmt.Fprintf(w, "\t\t\t%s := %s(%s)\n", value, p.spell(el), elementRead(s))
p.std("encoding/binary")
if s.Elem == "f32" || s.Elem == "f64" {
p.std("math")
}
}
fmt.Fprint(w, point)
w = outer
fmt.Fprintf(w, "\tif l := o.List(%s); l.Len() > 0 {\n\t\tvar rows %s\n", at, p.spell(ft))
if fallible {
fmt.Fprint(w, "\t\tvar ferr error\n")
}
fmt.Fprint(w, "\t\tl.EachBytes(func(_ int, raw []byte) bool {\n")
w.Write(elem.Bytes())
fmt.Fprint(w, "\t\t\trows = append(rows, e)\n\t\t\treturn true\n\t\t})\n")
if fallible {
fmt.Fprint(w, "\t\tif ferr != nil {\n\t\t\treturn ferr\n\t\t}\n")
}
assign := "x." + fn + " = rows"
if s.Ptr {
assign = "x." + fn + " = &rows"
}
fmt.Fprintf(w, "\t\t%s\n\t}\n", assign)
return nil
}
// elementRead widens one list element that arrived at its own width. Anything
// narrower than eight bytes was zero-extended into full by the caller; a signed
// element is then sign-extended from its own width, which is what the reflective
// decoder does — a negative int32 read as a uint64 is a very large number.
func elementRead(s Slot) string {
n := widthOf(s.Elem)
switch s.Elem {
case "i8", "i16", "i32", "i64":
return fmt.Sprintf("int64(binary.LittleEndian.Uint64(full[:])<<%d) >> %d", 64-8*n, 64-8*n)
case "f32":
return "math.Float32frombits(uint32(binary.LittleEndian.Uint64(full[:])))"
case "f64":
return "math.Float64frombits(binary.LittleEndian.Uint64(full[:]))"
}
return "binary.LittleEndian.Uint64(full[:])"
}
func widthOf(zaptype string) int {
switch zaptype {
case "bool", "i8", "u8":
return 1
case "i16", "u16":
return 2
case "i32", "u32", "f32":
return 4
}
return 8
}
func lower(s string) string {
if s == "" {
return s
}
return strings.ToLower(s[:1]) + s[1:]
}