perf(encode): write scalars and scalar arrays without boxing
Test / test (push) Successful in 1m34s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-22 01:46:26 +02:00
parent ebaca18093
commit 80b2bc6e0f
4 changed files with 354 additions and 28 deletions
+291 -28
View File
@@ -303,8 +303,9 @@ const (
type entry struct {
kind entryKind
key string
val any // entryScalar
doc *tomlDoc // entryTable
val any // entryScalar, boxed
rv reflect.Value // entryScalar, the value the walk kept unboxed
doc *tomlDoc // entryTable
docs []*tomlDoc
// inline forces a table entry to emit as `key = {…}`; it is set by the
@@ -359,6 +360,12 @@ func (d *tomlDoc) addScalar(key string, val any) {
d.entries = append(d.entries, entry{kind: entryScalar, key: key, val: val})
}
// addScalarReflect adds a scalar whose value stays in its reflect cell until
// emission writes it, the path that keeps plain scalars from being boxed.
func (d *tomlDoc) addScalarReflect(key string, v reflect.Value) {
d.entries = append(d.entries, entry{kind: entryScalar, key: key, rv: v})
}
func (d *tomlDoc) addTable(key string, sub *tomlDoc) *entry {
e := entry{kind: entryTable, key: key, doc: sub}
d.entries = append(d.entries, e)
@@ -712,6 +719,19 @@ func addField(doc *tomlDoc, name string, v reflect.Value, path encPath, forceInl
case reflect.Slice, reflect.Array:
return addArrayValue(doc, name, v, path, forceInline)
default:
// A plain scalar stays in its reflect cell until emission, which
// writes it without the boxing Interface() would cost. The kinds the
// boxed rules rewrite (duration, Number, the date-time structs) take
// the boxed path as before.
if t := v.Type(); t != durationType && t != numberType {
switch v.Kind() {
case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
doc.addScalarReflect(name, v)
return nil
}
}
val, err := normaliseValue(v)
if err != nil {
return fmt.Errorf("interpres: %s: %w", path.key(name), err)
@@ -762,39 +782,92 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, path encPath, for
// names them.
apath := path.key(name)
// Every element is resolved through MarshalTOML first, so an element that
// renders itself as a scalar, a table or a value array is classified by
// what it produces rather than by its Go kind, and its method runs once.
elems := make([]reflect.Value, n)
for i := range n {
if i%ctxCheckInterval == 0 {
if err := doc.checkCtx(); err != nil {
return err
// The per-element resolution runs only where the array could still take
// the [[header]] form: an interface element can never (its [[form]] would
// re-parse as []map[string]any), and a plain scalar element can only when
// a Marshaler rewrites it into a table. Everywhere else the array is a
// value array and the elements are written straight from the slice.
elem := v.Type().Elem()
maybeTables := true
if elem.Kind() == reflect.Interface {
maybeTables = false
} else {
switch elem.Kind() {
case reflect.Struct, reflect.Map, reflect.Pointer:
default:
f := encTypeFlags(elem)
if f&encFlagMarshaler == 0 && f&encFlagAddrMarshaler == 0 {
maybeTables = false
}
}
ev, err := resolveElement(v.Index(i), apath.elem(i))
if err != nil {
return err
}
// A typed array of plain scalars takes the direct emission path: no
// per-element slice, no boxing, and no error the boxed rules could raise.
// Everything wider, []any first among it, keeps the boxed path, because
// its element errors carry the element's path.
directValueArray := false
switch elem.Kind() {
case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
f := encTypeFlags(elem)
if elem != durationType && elem != numberType &&
f&(encFlagMarshaler|encFlagAddrMarshaler|encFlagTextMarshaler|encFlagAddrTextMarshaler) == 0 {
directValueArray = true
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
f := encTypeFlags(elem)
if f&(encFlagMarshaler|encFlagAddrMarshaler|encFlagTextMarshaler|encFlagAddrTextMarshaler) == 0 {
directValueArray = true
// The one error the boxed path raises for these is the int64
// overflow, checked here so the direct path keeps the contract.
for i := range n {
if u := v.Index(i).Uint(); u > math.MaxInt64 {
return &EncodeError{Path: apath.elem(i).segments(), Err: fmt.Errorf("unsigned value %d overflows int64", u)}
}
}
}
}
var elems []reflect.Value
if maybeTables {
// Every element is resolved through MarshalTOML first, so an element
// that renders itself as a scalar, a table or a value array is
// classified by what it produces rather than by its Go kind, and its
// method runs once.
elems = make([]reflect.Value, n)
for i := range n {
if i%ctxCheckInterval == 0 {
if err := doc.checkCtx(); err != nil {
return err
}
}
ev, err := resolveElement(v.Index(i), apath.elem(i))
if err != nil {
return err
}
elems[i] = ev
}
elems[i] = ev
}
// An array keeps the [[header]] form only when every element is a table.
// TOML lets one array mix tables with scalars, and that mix renders as a
// value array with the table elements written inline.
allTables := true
for _, ev := range elems {
if !ev.IsValid() || !isTableElementValue(ev) {
allTables := maybeTables
if allTables {
for _, ev := range elems {
if !ev.IsValid() || !isTableElementValue(ev) {
allTables = false
break
}
}
// A []any of tables is what Parse produces for a value array of inline
// tables; the [[header]] form would re-parse as []map[string]any and so
// change the value's Go type across a round-trip. The header form is
// reserved for typed table slices.
if v.Type().Elem().Kind() == reflect.Interface {
allTables = false
break
}
}
// A []any of tables is what Parse produces for a value array of inline
// tables; the [[header]] form would re-parse as []map[string]any and so
// change the value's Go type across a round-trip. The header form is
// reserved for typed table slices.
if v.Type().Elem().Kind() == reflect.Interface {
allTables = false
}
// A `,inline` tag on an array of tables asks for a form that would change
// the value's Go type on re-parse, so the error is the honest answer.
@@ -839,15 +912,24 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, path encPath, for
return nil
}
if directValueArray {
doc.addScalarReflect(name, v)
return nil
}
// Value array. Table elements normalise to map[string]any and the emitter
// writes them as inline tables.
items := make([]any, n)
for i, ev := range elems {
for i := range n {
if i%ctxCheckInterval == 0 {
if err := doc.checkCtx(); err != nil {
return err
}
}
ev := v.Index(i)
if elems != nil {
ev = elems[i]
}
val, err := normaliseValue(ev)
if err != nil {
return &EncodeError{Path: apath.elem(i).segments(), Err: err}
@@ -858,6 +940,149 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, path encPath, for
return nil
}
// writeArrayValue writes a value array from its reflect value, its elements
// written one by one, each falling back to the boxed path only where the
// boxed rules rewrite it.
func (e *encoder) writeArrayValue(v reflect.Value, depth int) error {
if atDepthLimit(depth) {
return errDepthLimit()
}
e.buf.WriteByte('[')
for i := range v.Len() {
if i > 0 {
e.buf.WriteString(", ")
}
if err := e.writeArrayElem(v.Index(i), depth+1); err != nil {
return err
}
}
e.buf.WriteByte(']')
return nil
}
// writeArrayElem writes one element of a value array. The kinds the boxed
// rules rewrite are handed to normaliseValue and the boxed writer; the rest
// write directly, including nested arrays and inline tables.
func (e *encoder) writeArrayElem(v reflect.Value, depth int) error {
if v.Kind() == reflect.Interface {
if v.IsNil() {
return fmt.Errorf("interpres: cannot encode nil value")
}
v = v.Elem()
}
switch v.Kind() {
case reflect.Slice, reflect.Array:
return e.writeArrayValue(v, depth)
case reflect.Map:
return e.writeInlineMapFromReflect(v, depth)
}
if _, isMarshaler := marshalerOf(v); isMarshaler {
return e.writeNormalisedElem(v, depth)
}
if _, isText, err := textValue(v); err != nil || isText {
if err != nil {
return err
}
return e.writeNormalisedElem(v, depth)
}
switch v.Kind() {
case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
if t := v.Type(); t != durationType && t != numberType {
return e.writeScalarValue(v)
}
}
return e.writeNormalisedElem(v, depth)
}
// writeNormalisedElem normalises one element through the boxed rules and
// writes the result.
func (e *encoder) writeNormalisedElem(v reflect.Value, depth int) error {
val, err := normaliseValueAt(v, depth)
if err != nil {
return err
}
return e.writeValue(val)
}
// writeInlineMapFromReflect renders a map from its reflect value as an inline
// table, the shape the boxed path gives the table elements of a value array:
// sorted keys, single line when it fits, across lines when it does not.
func (e *encoder) writeInlineMapFromReflect(v reflect.Value, depth int) error {
if e.limit >= noInlineBreak {
return e.writeInlineMapFlatReflect(v, depth)
}
flat := e.flat()
err := flat.writeInlineMapFlatReflect(v, depth)
if err != nil {
flat.release()
return err
}
fits := e.column()+flat.buf.Len() <= e.limit
if fits {
e.buf.Write(flat.buf.Bytes())
}
flat.release()
if fits {
return nil
}
return e.writeInlineMapMultilineReflect(v, depth)
}
// writeInlineMapFlatReflect renders the single-line form.
func (e *encoder) writeInlineMapFlatReflect(v reflect.Value, depth int) error {
if v.Type().Key().Kind() != reflect.String {
return fmt.Errorf("map key must be string, got %s", v.Type().Key())
}
keys := make([]string, 0, v.Len())
for _, k := range v.MapKeys() {
keys = append(keys, k.String())
}
slices.Sort(keys)
e.buf.WriteByte('{')
for i, k := range keys {
if i > 0 {
e.buf.WriteString(", ")
}
if err := e.writeKey(k); err != nil {
return err
}
e.buf.WriteString(" = ")
if err := e.writeArrayElem(v.MapIndex(reflect.ValueOf(k)), depth+1); err != nil {
return err
}
}
e.buf.WriteByte('}')
return nil
}
// writeInlineMapMultilineReflect renders the across-lines form.
func (e *encoder) writeInlineMapMultilineReflect(v reflect.Value, depth int) error {
keys := make([]string, 0, v.Len())
for _, k := range v.MapKeys() {
keys = append(keys, k.String())
}
slices.Sort(keys)
e.buf.WriteString("{\n")
e.inlineDepth++
for _, k := range keys {
e.writeInlineIndent()
if err := e.writeKey(k); err != nil {
return err
}
e.buf.WriteString(" = ")
if err := e.writeArrayElem(v.MapIndex(reflect.ValueOf(k)), depth+1); err != nil {
return err
}
e.buf.WriteString(",\n")
}
e.inlineDepth--
e.writeInlineIndent()
e.buf.WriteByte('}')
return nil
}
// marshalerOf finds the Marshaler a value carries: on the value itself, or on
// its address, so a pointer-receiver MarshalTOML is found on an addressable
// struct field or slice element, exactly as textMarshalerOf finds MarshalText.
@@ -1296,13 +1521,51 @@ func (e *encoder) writeKV(ent *entry) error {
return err
}
e.buf.WriteString(" = ")
if err := e.writeValue(ent.val); err != nil {
if err := e.writeEntryValue(ent); err != nil {
return err
}
e.buf.WriteByte('\n')
return nil
}
// writeEntryValue writes a scalar entry's value: from the reflect cell the
// walk kept when it can be written unboxed, from the boxed value otherwise.
func (e *encoder) writeEntryValue(ent *entry) error {
if ent.rv.IsValid() {
switch ent.rv.Kind() {
case reflect.Slice, reflect.Array:
return e.writeArrayValue(ent.rv, 0)
}
return e.writeScalarValue(ent.rv)
}
return e.writeValue(ent.val)
}
// writeScalarValue writes a scalar from its reflect value, boxing only the
// kinds the boxed writer handles specially.
func (e *encoder) writeScalarValue(v reflect.Value) error {
switch v.Kind() {
case reflect.String:
return e.writeStringVal(v.String())
case reflect.Bool:
e.buf.WriteString(strconv.FormatBool(v.Bool()))
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
e.buf.WriteString(strconv.FormatInt(v.Int(), 10))
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u := v.Uint()
if u > math.MaxInt64 {
return fmt.Errorf("unsigned value %d overflows int64", u)
}
e.buf.WriteString(strconv.FormatUint(u, 10))
return nil
case reflect.Float32, reflect.Float64:
return e.writeFloat(v.Float())
}
return e.writeValue(v.Interface())
}
func (e *encoder) writeKeyPath(path []string) error {
for i, p := range path {
if i > 0 {
@@ -1573,7 +1836,7 @@ func (e *encoder) writeInlineDocEntry(ent entry) error {
case entryArray:
return errInlineArrayOfTables
default:
return e.writeValue(ent.val)
return e.writeEntryValue(&ent)
}
}