diff --git a/third_party/github.com/BurntSushi/toml/decode.go b/third_party/github.com/BurntSushi/toml/decode.go index ddb26cf2b..2597e0d0d 100644 --- a/third_party/github.com/BurntSushi/toml/decode.go +++ b/third_party/github.com/BurntSushi/toml/decode.go @@ -151,29 +151,41 @@ func unifyStruct(mapping interface{}, rv reflect.Value) error { return mismatch(rv, "map", mapping) } - rt := rv.Type() - for i := 0; i < rt.NumField(); i++ { - // A little tricky. We want to use the special `toml` name in the - // struct tag if it exists. In particular, we need to make sure that - // this struct field is in the current map before trying to unify it. - sft := rt.Field(i) - kname := sft.Tag.Get("toml") - if len(kname) == 0 { - kname = sft.Name + for key, datum := range tmap { + var f *field + fields := cachedTypeFields(rv.Type()) + for i := range fields { + ff := &fields[i] + if ff.name == key { + f = ff + break + } + if f == nil && strings.EqualFold(ff.name, key) { + f = ff + } } - if datum, ok := insensitiveGet(tmap, kname); ok { - sf := indirect(rv.Field(i)) + if f != nil { + subv := rv + for _, i := range f.index { + if subv.Kind() == reflect.Ptr { + if subv.IsNil() { + subv.Set(reflect.New(subv.Type().Elem())) + } + subv = subv.Elem() + } + subv = subv.Field(i) + } + sf := indirect(subv) - // Don't try to mess with unexported types and other such things. if sf.CanSet() { if err := unify(datum, sf); err != nil { return e("Type mismatch for '%s.%s': %s", - rt.String(), sft.Name, err) + rv.Type().String(), f.name, err) } - } else if len(sft.Tag.Get("toml")) > 0 { + } else if f.name != "" { // Bad user! No soup for you! return e("Field '%s.%s' is unexported, and therefore cannot "+ - "be loaded with reflection.", rt.String(), sft.Name) + "be loaded with reflection.", rv.Type().String(), f.name) } } } diff --git a/third_party/github.com/BurntSushi/toml/decode_test.go b/third_party/github.com/BurntSushi/toml/decode_test.go index ad80adee3..853d7b047 100644 --- a/third_party/github.com/BurntSushi/toml/decode_test.go +++ b/third_party/github.com/BurntSushi/toml/decode_test.go @@ -1,6 +1,7 @@ package toml import ( + "encoding/json" "fmt" "log" "reflect" @@ -63,6 +64,53 @@ func TestDecode(t *testing.T) { testf("%v\n", val) } +func TestDecodeEmbedded(t *testing.T) { + type Dog struct{ Name string } + + tests := map[string]struct { + input string + decodeInto interface{} + wantDecoded interface{} + }{ + "embedded struct": { + input: `Name = "milton"`, + decodeInto: &struct{ Dog }{}, + wantDecoded: &struct{ Dog }{Dog{"milton"}}, + }, + "embedded non-nil pointer to struct": { + input: `Name = "milton"`, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, + }, + "embedded nil pointer to struct": { + input: ``, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{nil}, + }, + } + + for label, test := range tests { + _, err := Decode(test.input, test.decodeInto) + if err != nil { + t.Fatal(err) + } + + want, got := jsonstr(test.wantDecoded), jsonstr(test.decodeInto) + if want != got { + t.Errorf("%s: want decoded == %+v, got %+v", label, want, got) + } + } +} + +// jsonstr allows comparison of deeply nested structs with pointer members. +func jsonstr(o interface{}) string { + s, err := json.MarshalIndent(o, "", " ") + if err != nil { + panic(err.Error()) + } + return string(s) +} + var tomlTableArrays = ` [[albums]] name = "Born to Run" @@ -124,8 +172,6 @@ tOpdate = 2006-01-02T15:04:05Z tOparray = [ "array" ] Match = "i should be in Match only" MatcH = "i should be in MatcH only" -Field = "neat" -FielD = "messy" once = "just once" [nEst.eD] nEstedString = "another string" @@ -140,7 +186,6 @@ type Insensitive struct { TopArray []string Match string MatcH string - Field string Once string OncE string Nest InsensitiveNest @@ -168,9 +213,8 @@ func TestCase(t *testing.T) { TopArray: []string{"array"}, MatcH: "i should be in MatcH only", Match: "i should be in Match only", - Field: "neat", // encoding/json would store "messy" here Once: "just once", - OncE: "just once", // wait, what? + OncE: "", Nest: InsensitiveNest{ Ed: InsensitiveEd{NestedString: "another string"}, }, diff --git a/third_party/github.com/BurntSushi/toml/type_fields.go b/third_party/github.com/BurntSushi/toml/type_fields.go new file mode 100644 index 000000000..a380d80a0 --- /dev/null +++ b/third_party/github.com/BurntSushi/toml/type_fields.go @@ -0,0 +1,241 @@ +package toml + +// Struct field handling is adapted from code in encoding/json: +// +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string + tag bool + index []int + typ reflect.Type +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from toml tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that TOML should recognize for the given +// type. The algorithm is breadth-first search over the set of structs to +// include - the top struct and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + name := sf.Tag.Get("toml") + if name == "-" { + continue + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + f := field{name: ft.Name(), index: index, typ: ft} + next = append(next, f) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with TOML tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// TOML tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +}