Files
go-toml/unmarshaler.go
T
Thomas Pelletier 8f6d0d8be7 Specialize map[string]iface when unmarshaling kvs
name                               old time/op    new time/op    delta
UnmarshalDataset/config-8            12.3ms ± 0%    10.9ms ± 0%  -11.36%  (p=0.008 n=5+5)
UnmarshalDataset/canada-8            55.2ms ± 0%    55.4ms ± 1%     ~     (p=0.690 n=5+5)
UnmarshalDataset/citm_catalog-8      16.5ms ± 1%    15.7ms ± 0%   -4.43%  (p=0.008 n=5+5)
UnmarshalDataset/twitter-8           7.01ms ± 1%    6.63ms ± 0%   -5.34%  (p=0.008 n=5+5)
UnmarshalDataset/code-8              52.0ms ± 0%    48.7ms ± 2%   -6.43%  (p=0.008 n=5+5)
UnmarshalDataset/example-8            119µs ± 0%     110µs ± 3%   -6.81%  (p=0.008 n=5+5)
Unmarshal/SimpleDocument/struct-8     432ns ± 1%     431ns ± 1%     ~     (p=0.222 n=5+5)
Unmarshal/SimpleDocument/map-8        573ns ± 1%     540ns ± 1%   -5.64%  (p=0.008 n=5+5)
Unmarshal/ReferenceFile/struct-8     33.7µs ± 0%    33.6µs ± 0%     ~     (p=0.310 n=5+5)
Unmarshal/ReferenceFile/map-8        44.2µs ± 1%    41.7µs ± 1%   -5.63%  (p=0.008 n=5+5)
Unmarshal/HugoFrontMatter-8          7.28µs ± 1%    6.74µs ± 1%   -7.42%  (p=0.008 n=5+5)
[Geo mean]                            292µs          277µs        -4.91%

name                               old speed      new speed      delta
UnmarshalDataset/config-8          85.2MB/s ± 0%  96.1MB/s ± 0%  +12.82%  (p=0.008 n=5+5)
UnmarshalDataset/canada-8          39.9MB/s ± 0%  39.8MB/s ± 1%     ~     (p=0.690 n=5+5)
UnmarshalDataset/citm_catalog-8    33.9MB/s ± 1%  35.5MB/s ± 0%   +4.64%  (p=0.008 n=5+5)
UnmarshalDataset/twitter-8         63.1MB/s ± 1%  66.6MB/s ± 0%   +5.65%  (p=0.008 n=5+5)
UnmarshalDataset/code-8            51.6MB/s ± 0%  55.1MB/s ± 2%   +6.88%  (p=0.008 n=5+5)
UnmarshalDataset/example-8         68.3MB/s ± 0%  73.4MB/s ± 3%   +7.34%  (p=0.008 n=5+5)
Unmarshal/SimpleDocument/struct-8  25.4MB/s ± 1%  25.5MB/s ± 1%     ~     (p=0.246 n=5+5)
Unmarshal/SimpleDocument/map-8     19.2MB/s ± 1%  20.4MB/s ± 1%   +5.99%  (p=0.008 n=5+5)
Unmarshal/ReferenceFile/struct-8    156MB/s ± 0%   156MB/s ± 0%     ~     (p=0.310 n=5+5)
Unmarshal/ReferenceFile/map-8       119MB/s ± 1%   126MB/s ± 1%   +5.97%  (p=0.008 n=5+5)
Unmarshal/HugoFrontMatter-8        75.0MB/s ± 1%  81.0MB/s ± 1%   +8.01%  (p=0.008 n=5+5)
[Geo mean]                         56.1MB/s       59.0MB/s        +5.17%

name                               old alloc/op   new alloc/op   delta
UnmarshalDataset/config-8            5.26MB ± 0%    4.75MB ± 0%   -9.66%  (p=0.008 n=5+5)
UnmarshalDataset/canada-8            83.0MB ± 0%    83.0MB ± 0%   -0.00%  (p=0.008 n=5+5)
UnmarshalDataset/citm_catalog-8      34.7MB ± 0%    34.3MB ± 0%   -1.13%  (p=0.008 n=5+5)
UnmarshalDataset/twitter-8           12.7MB ± 0%    12.5MB ± 0%   -1.44%  (p=0.008 n=5+5)
UnmarshalDataset/code-8              15.3MB ± 0%    13.9MB ± 0%   -9.27%  (p=0.008 n=5+5)
UnmarshalDataset/example-8            186kB ± 0%     182kB ± 0%   -2.20%  (p=0.008 n=5+5)
Unmarshal/SimpleDocument/struct-8      805B ± 0%      805B ± 0%     ~     (all equal)
Unmarshal/SimpleDocument/map-8       1.13kB ± 0%    1.12kB ± 0%   -1.41%  (p=0.008 n=5+5)
Unmarshal/ReferenceFile/struct-8     20.9kB ± 0%    20.9kB ± 0%     ~     (all equal)
Unmarshal/ReferenceFile/map-8        36.4kB ± 0%    35.4kB ± 0%     ~     (p=0.079 n=4+5)
Unmarshal/HugoFrontMatter-8          7.20kB ± 0%    6.98kB ± 0%   -3.11%  (p=0.008 n=5+5)
[Geo mean]                            312kB          303kB        -2.86%

name                               old allocs/op  new allocs/op  delta
UnmarshalDataset/config-8              189k ± 0%      157k ± 0%  -16.80%  (p=0.029 n=4+4)
UnmarshalDataset/canada-8              782k ± 0%      782k ± 0%   -0.00%  (p=0.008 n=5+5)
UnmarshalDataset/citm_catalog-8        191k ± 0%      167k ± 0%  -12.75%  (p=0.000 n=4+5)
UnmarshalDataset/twitter-8            56.9k ± 0%     45.5k ± 0%  -20.02%  (p=0.016 n=5+4)
UnmarshalDataset/code-8                626k ± 0%      537k ± 0%  -14.22%  (p=0.008 n=5+5)
UnmarshalDataset/example-8            1.36k ± 0%     1.11k ± 0%  -18.53%  (p=0.008 n=5+5)
Unmarshal/SimpleDocument/struct-8      9.00 ± 0%      9.00 ± 0%     ~     (all equal)
Unmarshal/SimpleDocument/map-8         13.0 ± 0%      12.0 ± 0%   -7.69%  (p=0.008 n=5+5)
Unmarshal/ReferenceFile/struct-8        183 ± 0%       183 ± 0%     ~     (all equal)
Unmarshal/ReferenceFile/map-8           526 ± 0%       466 ± 0%  -11.41%  (p=0.008 n=5+5)
Unmarshal/HugoFrontMatter-8             126 ± 0%       112 ± 0%  -11.11%  (p=0.008 n=5+5)
[Geo mean]                            3.73k          3.34k       -10.51%
2022-04-15 17:41:18 -04:00

1267 lines
29 KiB
Go

package toml
import (
"encoding"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"reflect"
"strings"
"sync/atomic"
"time"
"github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger"
"github.com/pelletier/go-toml/v2/internal/tracker"
)
// Unmarshal deserializes a TOML document into a Go value.
//
// It is a shortcut for Decoder.Decode() with the default options.
func Unmarshal(data []byte, v interface{}) error {
p := parser{}
p.Reset(data)
d := decoder{p: &p}
return d.FromParser(v)
}
// Decoder reads and decode a TOML document from an input stream.
type Decoder struct {
// input
r io.Reader
// global settings
strict bool
}
// NewDecoder creates a new Decoder that will read from r.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// DisallowUnknownFields causes the Decoder to return an error when the
// destination is a struct and the input contains a key that does not match a
// non-ignored field.
//
// In that case, the Decoder returns a StrictMissingError that can be used to
// retrieve the individual errors as well as generate a human readable
// description of the missing fields.
func (d *Decoder) DisallowUnknownFields() *Decoder {
d.strict = true
return d
}
// Decode the whole content of r into v.
//
// By default, values in the document that don't exist in the target Go value
// are ignored. See Decoder.DisallowUnknownFields() to change this behavior.
//
// When a TOML local date, time, or date-time is decoded into a time.Time, its
// value is represented in time.Local timezone. Otherwise the approriate Local*
// structure is used. For time values, precision up to the nanosecond is
// supported by truncating extra digits.
//
// Empty tables decoded in an interface{} create an empty initialized
// map[string]interface{}.
//
// Types implementing the encoding.TextUnmarshaler interface are decoded from a
// TOML string.
//
// When decoding a number, go-toml will return an error if the number is out of
// bounds for the target type (which includes negative numbers when decoding
// into an unsigned int).
//
// If an error occurs while decoding the content of the document, this function
// returns a toml.DecodeError, providing context about the issue. When using
// strict mode and a field is missing, a `toml.StrictMissingError` is
// returned. In any other case, this function returns a standard Go error.
//
// Type mapping
//
// List of supported TOML types and their associated accepted Go types:
//
// String -> string
// Integer -> uint*, int*, depending on size
// Float -> float*, depending on size
// Boolean -> bool
// Offset Date-Time -> time.Time
// Local Date-time -> LocalDateTime, time.Time
// Local Date -> LocalDate, time.Time
// Local Time -> LocalTime, time.Time
// Array -> slice and array, depending on elements types
// Table -> map and struct
// Inline Table -> same as Table
// Array of Tables -> same as Array and Table
func (d *Decoder) Decode(v interface{}) error {
b, err := ioutil.ReadAll(d.r)
if err != nil {
return fmt.Errorf("toml: %w", err)
}
p := parser{}
p.Reset(b)
dec := decoder{
p: &p,
strict: strict{
Enabled: d.strict,
},
}
return dec.FromParser(v)
}
type decoder struct {
// Which parser instance in use for this decoding session.
p *parser
// Flag indicating that the current expression is stashed.
// If set to true, calling nextExpr will not actually pull a new expression
// but turn off the flag instead.
stashedExpr bool
// Skip expressions until a table is found. This is set to true when a
// table could not be create (missing field in map), so all KV expressions
// need to be skipped.
skipUntilTable bool
// Tracks position in Go arrays.
// This is used when decoding [[array tables]] into Go arrays. Given array
// tables are separate TOML expression, we need to keep track of where we
// are at in the Go array, as we can't just introspect its size.
arrayIndexes map[reflect.Value]int
// Tracks keys that have been seen, with which type.
seen tracker.SeenTracker
// Strict mode
strict strict
// Current context for the error.
errorContext *errorContext
}
type errorContext struct {
Struct reflect.Type
Field []int
}
func (d *decoder) typeMismatchError(toml string, target reflect.Type) error {
if d.errorContext != nil && d.errorContext.Struct != nil {
ctx := d.errorContext
f := ctx.Struct.FieldByIndex(ctx.Field)
return fmt.Errorf("toml: cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type)
}
return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target)
}
func (d *decoder) expr() *ast.Node {
return d.p.Expression()
}
func (d *decoder) nextExpr() bool {
if d.stashedExpr {
d.stashedExpr = false
return true
}
return d.p.NextExpression()
}
func (d *decoder) stashExpr() {
d.stashedExpr = true
}
func (d *decoder) arrayIndex(shouldAppend bool, v reflect.Value) int {
if d.arrayIndexes == nil {
d.arrayIndexes = make(map[reflect.Value]int, 1)
}
idx, ok := d.arrayIndexes[v]
if !ok {
d.arrayIndexes[v] = 0
} else if shouldAppend {
idx++
d.arrayIndexes[v] = idx
}
return idx
}
func (d *decoder) FromParser(v interface{}) error {
r := reflect.ValueOf(v)
if r.Kind() != reflect.Ptr {
return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind())
}
if r.IsNil() {
return fmt.Errorf("toml: decoding pointer target cannot be nil")
}
r = r.Elem()
if r.Kind() == reflect.Interface && r.IsNil() {
newMap := map[string]interface{}{}
r.Set(reflect.ValueOf(newMap))
}
err := d.fromParser(r)
if err == nil {
return d.strict.Error(d.p.data)
}
var e *decodeError
if errors.As(err, &e) {
return wrapDecodeError(d.p.data, e)
}
return err
}
func (d *decoder) fromParser(root reflect.Value) error {
for d.nextExpr() {
err := d.handleRootExpression(d.expr(), root)
if err != nil {
return err
}
}
return d.p.Error()
}
func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error {
var x reflect.Value
var err error
if !(d.skipUntilTable && expr.Kind == ast.KeyValue) {
err = d.seen.CheckExpression(expr)
if err != nil {
return err
}
}
switch expr.Kind {
case ast.KeyValue:
if d.skipUntilTable {
return nil
}
x, err = d.handleKeyValue(expr, v)
case ast.Table:
d.skipUntilTable = false
d.strict.EnterTable(expr)
x, err = d.handleTable(expr.Key(), v)
case ast.ArrayTable:
d.skipUntilTable = false
d.strict.EnterArrayTable(expr)
x, err = d.handleArrayTable(expr.Key(), v)
default:
panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind))
}
if d.skipUntilTable {
if expr.Kind == ast.Table || expr.Kind == ast.ArrayTable {
d.strict.MissingTable(expr)
}
} else if err == nil && x.IsValid() {
v.Set(x)
}
return err
}
func (d *decoder) handleArrayTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
if key.Next() {
return d.handleArrayTablePart(key, v)
}
return d.handleKeyValues(v)
}
func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
switch v.Kind() {
case reflect.Interface:
elem := v.Elem()
if !elem.IsValid() {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if elem.Kind() == reflect.Slice {
if elem.Type() != sliceInterfaceType {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if !elem.CanSet() {
nelem := reflect.New(sliceInterfaceType).Elem()
nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap()))
reflect.Copy(nelem, elem)
elem = nelem
}
}
return d.handleArrayTableCollectionLast(key, elem)
case reflect.Ptr:
elem := v.Elem()
if !elem.IsValid() {
ptr := reflect.New(v.Type().Elem())
v.Set(ptr)
elem = ptr.Elem()
}
elem, err := d.handleArrayTableCollectionLast(key, elem)
if err != nil {
return reflect.Value{}, err
}
v.Elem().Set(elem)
return v, nil
case reflect.Slice:
elemType := v.Type().Elem()
var elem reflect.Value
if elemType.Kind() == reflect.Interface {
elem = makeMapStringInterface()
} else {
elem = reflect.New(elemType).Elem()
}
elem2, err := d.handleArrayTable(key, elem)
if err != nil {
return reflect.Value{}, err
}
if elem2.IsValid() {
elem = elem2
}
return reflect.Append(v, elem), nil
case reflect.Array:
idx := d.arrayIndex(true, v)
if idx >= v.Len() {
return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx)
}
elem := v.Index(idx)
_, err := d.handleArrayTable(key, elem)
return v, err
}
return d.handleArrayTable(key, v)
}
// When parsing an array table expression, each part of the key needs to be
// evaluated like a normal key, but if it returns a collection, it also needs to
// point to the last element of the collection. Unless it is the last part of
// the key, then it needs to create a new element at the end.
func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
if key.IsLast() {
return d.handleArrayTableCollectionLast(key, v)
}
switch v.Kind() {
case reflect.Ptr:
elem := v.Elem()
if !elem.IsValid() {
ptr := reflect.New(v.Type().Elem())
v.Set(ptr)
elem = ptr.Elem()
}
elem, err := d.handleArrayTableCollection(key, elem)
if err != nil {
return reflect.Value{}, err
}
if elem.IsValid() {
v.Elem().Set(elem)
}
return v, nil
case reflect.Slice:
elem := v.Index(v.Len() - 1)
x, err := d.handleArrayTable(key, elem)
if err != nil || d.skipUntilTable {
return reflect.Value{}, err
}
if x.IsValid() {
elem.Set(x)
}
return v, err
case reflect.Array:
idx := d.arrayIndex(false, v)
if idx >= v.Len() {
return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx)
}
elem := v.Index(idx)
_, err := d.handleArrayTable(key, elem)
return v, err
}
return d.handleArrayTable(key, v)
}
func (d *decoder) handleKeyValuePartMapStringInterface(key ast.Iterator, value *ast.Node, m map[string]interface{}) (reflect.Value, error) {
k := string(key.Node().Data)
newMap := false
if m == nil {
newMap = true
m = make(map[string]interface{}, 8)
}
set := false
v, ok := m[k]
if !ok || key.IsLast() {
set = true
v = nil
}
mv := reflect.ValueOf(&v).Elem()
x, err := d.handleKeyValueInner(key, value, mv)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
mv = x
set = true
}
if set {
m[k] = mv.Interface()
}
if newMap {
return reflect.ValueOf(m), nil
}
return reflect.Value{}, nil
}
func (d *decoder) handleKeyPartMapStringInterface(key ast.Iterator, m map[string]interface{}, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) {
newMap := false
k := string(key.Node().Data)
if m == nil {
newMap = true
m = make(map[string]interface{}, 8)
}
v, ok := m[k]
set := false
if !ok || v == nil {
set = true
v = makeFn().Interface()
}
mv := reflect.ValueOf(v)
x, err := nextFn(key, mv)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
mv = x
set = true
}
if set {
m[k] = mv.Interface()
}
if newMap {
return reflect.ValueOf(m), nil
}
return reflect.Value{}, nil
}
func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) {
var rv reflect.Value
// First, dispatch over v to make sure it is a valid object.
// There is no guarantee over what it could be.
switch v.Kind() {
case reflect.Ptr:
elem := v.Elem()
if !elem.IsValid() {
v.Set(reflect.New(v.Type().Elem()))
}
elem = v.Elem()
return d.handleKeyPart(key, elem, nextFn, makeFn)
case reflect.Map:
vt := v.Type()
if vt == mapStringInterfaceType {
m := v.Interface().(map[string]interface{})
return d.handleKeyPartMapStringInterface(key, m, nextFn, makeFn)
}
// Create the key for the map element. Convert to key type.
mk := reflect.ValueOf(string(key.Node().Data)).Convert(vt.Key())
// If the map does not exist, create it.
if v.IsNil() {
vt := v.Type()
v = reflect.MakeMap(vt)
rv = v
}
mv := v.MapIndex(mk)
set := false
if !mv.IsValid() {
// If there is no value in the map, create a new one according to
// the map type. If the element type is interface, create either a
// map[string]interface{} or a []interface{} depending on whether
// this is the last part of the array table key.
t := vt.Elem()
if t.Kind() == reflect.Interface {
mv = makeFn()
} else {
mv = reflect.New(t).Elem()
}
set = true
} else if mv.Kind() == reflect.Interface {
mv = mv.Elem()
if !mv.IsValid() {
mv = makeFn()
}
set = true
} else if !mv.CanAddr() {
vt := v.Type()
t := vt.Elem()
oldmv := mv
mv = reflect.New(t).Elem()
mv.Set(oldmv)
set = true
}
x, err := nextFn(key, mv)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
mv = x
set = true
}
if set {
v.SetMapIndex(mk, mv)
}
case reflect.Struct:
path, found := structFieldPath(v, string(key.Node().Data))
if !found {
d.skipUntilTable = true
return reflect.Value{}, nil
}
if d.errorContext == nil {
d.errorContext = new(errorContext)
}
t := v.Type()
d.errorContext.Struct = t
d.errorContext.Field = path
f := v.FieldByIndex(path)
x, err := nextFn(key, f)
if err != nil || d.skipUntilTable {
return reflect.Value{}, err
}
if x.IsValid() {
f.Set(x)
}
d.errorContext.Field = nil
d.errorContext.Struct = nil
case reflect.Interface:
if v.Elem().IsValid() {
v = v.Elem()
} else {
v = makeMapStringInterface()
}
x, err := d.handleKeyPart(key, v, nextFn, makeFn)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
v = x
}
rv = v
default:
panic(fmt.Errorf("unhandled part: %s", v.Kind()))
}
return rv, nil
}
// HandleArrayTablePart navigates the Go structure v using the key v. It is
// only used for the prefix (non-last) parts of an array-table. When
// encountering a collection, it should go to the last element.
func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
var makeFn valueMakerFn
if key.IsLast() {
makeFn = makeSliceInterface
} else {
makeFn = makeMapStringInterface
}
return d.handleKeyPart(key, v, d.handleArrayTableCollection, makeFn)
}
// HandleTable returns a reference when it has checked the next expression but
// cannot handle it.
func (d *decoder) handleTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
if v.Kind() == reflect.Slice {
if v.Len() == 0 {
return reflect.Value{}, newDecodeError(key.Node().Data, "cannot store a table in a slice")
}
elem := v.Index(v.Len() - 1)
x, err := d.handleTable(key, elem)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
elem.Set(x)
}
return reflect.Value{}, nil
}
if key.Next() {
// Still scoping the key
return d.handleTablePart(key, v)
}
// Done scoping the key.
// Now handle all the key-value expressions in this table.
return d.handleKeyValues(v)
}
// Handle root expressions until the end of the document or the next
// non-key-value.
func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
var rv reflect.Value
for d.nextExpr() {
expr := d.expr()
if expr.Kind != ast.KeyValue {
// Stash the expression so that fromParser can just loop and use
// the right handler.
// We could just recurse ourselves here, but at least this gives a
// chance to pop the stack a bit.
d.stashExpr()
break
}
err := d.seen.CheckExpression(expr)
if err != nil {
return reflect.Value{}, err
}
x, err := d.handleKeyValue(expr, v)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
v = x
rv = x
}
}
return rv, nil
}
type (
handlerFn func(key ast.Iterator, v reflect.Value) (reflect.Value, error)
valueMakerFn func() reflect.Value
)
func makeMapStringInterface() reflect.Value {
return reflect.MakeMap(mapStringInterfaceType)
}
func makeSliceInterface() reflect.Value {
return reflect.MakeSlice(sliceInterfaceType, 0, 16)
}
func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) {
return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface)
}
func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, error) {
// Special case for time, because we allow to unmarshal to it from
// different kind of AST nodes.
if v.Type() == timeType {
return false, nil
}
if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) {
err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data)
if err != nil {
return false, newDecodeError(d.p.Raw(node.Raw), "%w", err)
}
return true, nil
}
return false, nil
}
func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
for v.Kind() == reflect.Ptr {
v = initAndDereferencePointer(v)
}
ok, err := d.tryTextUnmarshaler(value, v)
if ok || err != nil {
return err
}
switch value.Kind {
case ast.String:
return d.unmarshalString(value, v)
case ast.Integer:
return d.unmarshalInteger(value, v)
case ast.Float:
return d.unmarshalFloat(value, v)
case ast.Bool:
return d.unmarshalBool(value, v)
case ast.DateTime:
return d.unmarshalDateTime(value, v)
case ast.LocalDate:
return d.unmarshalLocalDate(value, v)
case ast.LocalTime:
return d.unmarshalLocalTime(value, v)
case ast.LocalDateTime:
return d.unmarshalLocalDateTime(value, v)
case ast.InlineTable:
return d.unmarshalInlineTable(value, v)
case ast.Array:
return d.unmarshalArray(value, v)
default:
panic(fmt.Errorf("handleValue not implemented for %s", value.Kind))
}
}
func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
switch v.Kind() {
case reflect.Slice:
if v.IsNil() {
v.Set(reflect.MakeSlice(v.Type(), 0, 16))
} else {
v.SetLen(0)
}
case reflect.Array:
// arrays are always initialized
case reflect.Interface:
elem := v.Elem()
if !elem.IsValid() {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if elem.Kind() == reflect.Slice {
if elem.Type() != sliceInterfaceType {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if !elem.CanSet() {
nelem := reflect.New(sliceInterfaceType).Elem()
nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap()))
reflect.Copy(nelem, elem)
elem = nelem
}
}
err := d.unmarshalArray(array, elem)
if err != nil {
return err
}
v.Set(elem)
return nil
default:
// TODO: use newDecodeError, but first the parser needs to fill
// array.Data.
return d.typeMismatchError("array", v.Type())
}
elemType := v.Type().Elem()
it := array.Children()
idx := 0
for it.Next() {
n := it.Node()
// TODO: optimize
if v.Kind() == reflect.Slice {
elem := reflect.New(elemType).Elem()
err := d.handleValue(n, elem)
if err != nil {
return err
}
v.Set(reflect.Append(v, elem))
} else { // array
if idx >= v.Len() {
return nil
}
elem := v.Index(idx)
err := d.handleValue(n, elem)
if err != nil {
return err
}
idx++
}
}
return nil
}
func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error {
// Make sure v is an initialized object.
switch v.Kind() {
case reflect.Map:
if v.IsNil() {
v.Set(reflect.MakeMap(v.Type()))
}
case reflect.Struct:
// structs are always initialized.
case reflect.Interface:
elem := v.Elem()
if !elem.IsValid() {
elem = makeMapStringInterface()
v.Set(elem)
}
return d.unmarshalInlineTable(itable, elem)
default:
return newDecodeError(itable.Data, "cannot store inline table in Go type %s", v.Kind())
}
it := itable.Children()
for it.Next() {
n := it.Node()
x, err := d.handleKeyValue(n, v)
if err != nil {
return err
}
if x.IsValid() {
v = x
}
}
return nil
}
func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error {
dt, err := parseDateTime(value.Data)
if err != nil {
return err
}
v.Set(reflect.ValueOf(dt))
return nil
}
func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
ld, err := parseLocalDate(value.Data)
if err != nil {
return err
}
if v.Type() == timeType {
cast := ld.AsTime(time.Local)
v.Set(reflect.ValueOf(cast))
return nil
}
v.Set(reflect.ValueOf(ld))
return nil
}
func (d *decoder) unmarshalLocalTime(value *ast.Node, v reflect.Value) error {
lt, rest, err := parseLocalTime(value.Data)
if err != nil {
return err
}
if len(rest) > 0 {
return newDecodeError(rest, "extra characters at the end of a local time")
}
v.Set(reflect.ValueOf(lt))
return nil
}
func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error {
ldt, rest, err := parseLocalDateTime(value.Data)
if err != nil {
return err
}
if len(rest) > 0 {
return newDecodeError(rest, "extra characters at the end of a local date time")
}
if v.Type() == timeType {
cast := ldt.AsTime(time.Local)
v.Set(reflect.ValueOf(cast))
return nil
}
v.Set(reflect.ValueOf(ldt))
return nil
}
func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error {
b := value.Data[0] == 't'
switch v.Kind() {
case reflect.Bool:
v.SetBool(b)
case reflect.Interface:
v.Set(reflect.ValueOf(b))
default:
return newDecodeError(value.Data, "cannot assign boolean to a %t", b)
}
return nil
}
func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error {
f, err := parseFloat(value.Data)
if err != nil {
return err
}
switch v.Kind() {
case reflect.Float64:
v.SetFloat(f)
case reflect.Float32:
if f > math.MaxFloat32 {
return newDecodeError(value.Data, "number %f does not fit in a float32", f)
}
v.SetFloat(f)
case reflect.Interface:
v.Set(reflect.ValueOf(f))
default:
return newDecodeError(value.Data, "float cannot be assigned to %s", v.Kind())
}
return nil
}
func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
const (
maxInt = int64(^uint(0) >> 1)
minInt = -maxInt - 1
)
i, err := parseInteger(value.Data)
if err != nil {
return err
}
var r reflect.Value
switch v.Kind() {
case reflect.Int64:
v.SetInt(i)
return nil
case reflect.Int32:
if i < math.MinInt32 || i > math.MaxInt32 {
return fmt.Errorf("toml: number %d does not fit in an int32", i)
}
r = reflect.ValueOf(int32(i))
case reflect.Int16:
if i < math.MinInt16 || i > math.MaxInt16 {
return fmt.Errorf("toml: number %d does not fit in an int16", i)
}
r = reflect.ValueOf(int16(i))
case reflect.Int8:
if i < math.MinInt8 || i > math.MaxInt8 {
return fmt.Errorf("toml: number %d does not fit in an int8", i)
}
r = reflect.ValueOf(int8(i))
case reflect.Int:
if i < minInt || i > maxInt {
return fmt.Errorf("toml: number %d does not fit in an int", i)
}
r = reflect.ValueOf(int(i))
case reflect.Uint64:
if i < 0 {
return fmt.Errorf("toml: negative number %d does not fit in an uint64", i)
}
r = reflect.ValueOf(uint64(i))
case reflect.Uint32:
if i < 0 || i > math.MaxUint32 {
return fmt.Errorf("toml: negative number %d does not fit in an uint32", i)
}
r = reflect.ValueOf(uint32(i))
case reflect.Uint16:
if i < 0 || i > math.MaxUint16 {
return fmt.Errorf("toml: negative number %d does not fit in an uint16", i)
}
r = reflect.ValueOf(uint16(i))
case reflect.Uint8:
if i < 0 || i > math.MaxUint8 {
return fmt.Errorf("toml: negative number %d does not fit in an uint8", i)
}
r = reflect.ValueOf(uint8(i))
case reflect.Uint:
if i < 0 {
return fmt.Errorf("toml: negative number %d does not fit in an uint", i)
}
r = reflect.ValueOf(uint(i))
case reflect.Interface:
r = reflect.ValueOf(i)
default:
return d.typeMismatchError("integer", v.Type())
}
if !r.Type().AssignableTo(v.Type()) {
r = r.Convert(v.Type())
}
v.Set(r)
return nil
}
func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error {
switch v.Kind() {
case reflect.String:
v.SetString(string(value.Data))
case reflect.Interface:
v.Set(reflect.ValueOf(string(value.Data)))
default:
return newDecodeError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind())
}
return nil
}
func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value, error) {
d.strict.EnterKeyValue(expr)
v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v)
if d.skipUntilTable {
d.strict.MissingField(expr)
d.skipUntilTable = false
}
d.strict.ExitKeyValue(expr)
return v, err
}
func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) {
if key.Next() {
// Still scoping the key
return d.handleKeyValuePart(key, value, v)
}
// Done scoping the key.
// v is whatever Go value we need to fill.
return reflect.Value{}, d.handleValue(value, v)
}
func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) {
// contains the replacement for v
var rv reflect.Value
// First, dispatch over v to make sure it is a valid object.
// There is no guarantee over what it could be.
switch v.Kind() {
case reflect.Map:
vt := v.Type()
if vt == mapStringInterfaceType {
m := v.Interface().(map[string]interface{})
return d.handleKeyValuePartMapStringInterface(key, value, m)
}
mk := reflect.ValueOf(string(key.Node().Data))
mkt := stringType
keyType := vt.Key()
if !mkt.AssignableTo(keyType) {
if !mkt.ConvertibleTo(keyType) {
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", mkt, keyType)
}
mk = mk.Convert(keyType)
}
// If the map does not exist, create it.
if v.IsNil() {
v = reflect.MakeMap(vt)
rv = v
}
mv := v.MapIndex(mk)
set := false
if !mv.IsValid() {
set = true
mv = reflect.New(v.Type().Elem()).Elem()
} else if key.IsLast() {
var x interface{}
mv = reflect.ValueOf(&x).Elem()
set = true
}
nv, err := d.handleKeyValueInner(key, value, mv)
if err != nil {
return reflect.Value{}, err
}
if nv.IsValid() {
mv = nv
set = true
}
if set {
v.SetMapIndex(mk, mv)
}
case reflect.Struct:
path, found := structFieldPath(v, string(key.Node().Data))
if !found {
d.skipUntilTable = true
break
}
if d.errorContext == nil {
d.errorContext = new(errorContext)
}
t := v.Type()
d.errorContext.Struct = t
d.errorContext.Field = path
f := v.FieldByIndex(path)
x, err := d.handleKeyValueInner(key, value, f)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
f.Set(x)
}
d.errorContext.Struct = nil
d.errorContext.Field = nil
case reflect.Interface:
v = v.Elem()
// Following encoding/json: decoding an object into an
// interface{}, it needs to always hold a
// map[string]interface{}. This is for the types to be
// consistent whether a previous value was set or not.
if !v.IsValid() || v.Type() != mapStringInterfaceType {
v = makeMapStringInterface()
}
x, err := d.handleKeyValuePart(key, value, v)
if err != nil {
return reflect.Value{}, err
}
if x.IsValid() {
v = x
}
rv = v
case reflect.Ptr:
elem := v.Elem()
if !elem.IsValid() {
ptr := reflect.New(v.Type().Elem())
v.Set(ptr)
rv = v
elem = ptr.Elem()
}
elem2, err := d.handleKeyValuePart(key, value, elem)
if err != nil {
return reflect.Value{}, err
}
if elem2.IsValid() {
elem = elem2
}
v.Elem().Set(elem)
default:
return reflect.Value{}, fmt.Errorf("unhandled kv part: %s", v.Kind())
}
return rv, nil
}
func initAndDereferencePointer(v reflect.Value) reflect.Value {
var elem reflect.Value
if v.IsNil() {
ptr := reflect.New(v.Type().Elem())
v.Set(ptr)
}
elem = v.Elem()
return elem
}
type fieldPathsMap = map[string][]int
var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap
func structFieldPath(v reflect.Value, name string) ([]int, bool) {
t := v.Type()
cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap)
fieldPaths, ok := cache[danger.MakeTypeID(t)]
if !ok {
fieldPaths = map[string][]int{}
forEachField(t, nil, func(name string, path []int) {
fieldPaths[name] = path
// extra copy for the case-insensitive match
fieldPaths[strings.ToLower(name)] = path
})
newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1)
newCache[danger.MakeTypeID(t)] = fieldPaths
for k, v := range cache {
newCache[k] = v
}
globalFieldPathsCache.Store(newCache)
}
path, ok := fieldPaths[name]
if !ok {
path, ok = fieldPaths[strings.ToLower(name)]
}
return path, ok
}
func forEachField(t reflect.Type, path []int, do func(name string, path []int)) {
n := t.NumField()
for i := 0; i < n; i++ {
f := t.Field(i)
if !f.Anonymous && f.PkgPath != "" {
// only consider exported fields.
continue
}
fieldPath := append(path, i)
fieldPath = fieldPath[:len(fieldPath):len(fieldPath)]
if f.Anonymous {
forEachField(f.Type, fieldPath, do)
continue
}
name := f.Tag.Get("toml")
if name == "-" {
continue
}
if i := strings.IndexByte(name, ','); i >= 0 {
name = name[:i]
}
if name == "" {
name = f.Name
}
do(name, fieldPath)
}
}