Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return []any for hash arrays when decoding to map / any. #421

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,9 +642,9 @@ func (d *dish) UnmarshalTOML(p any) error {
data, _ := p.(map[string]any)
d.Name, _ = data["name"].(string)
d.Price, _ = data["price"].(float32)
ingredients, _ := data["ingredients"].([]map[string]any)
ingredients, _ := data["ingredients"].([]any)
for _, e := range ingredients {
n, _ := any(e).(map[string]any)
n, _ := e.(map[string]any)
name, _ := n["name"].(string)
i := ingredient{name}
d.Ingredients = append(d.Ingredients, i)
Expand Down
6 changes: 0 additions & 6 deletions internal/tag/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ func Add(key string, tomlData any) any {

// An array: we don't need to add any tags, just recurse for every table
// entry.
case []map[string]any:
typed := make([]map[string]any, len(orig))
for i, v := range orig {
typed[i] = Add("", v).(map[string]any)
}
return typed
case []any:
typed := make([]any, len(orig))
for i, v := range orig {
Expand Down
23 changes: 3 additions & 20 deletions internal/toml-test/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ func (r Test) CompareTOML(want, have any) Test {
switch w := want.(type) {
case map[string]any:
return r.cmpTOMLMap(w, have)
case []map[string]any:
ww := make([]any, 0, len(w))
for _, v := range w {
ww = append(ww, v)
}
return r.cmpTOMLArrays(ww, have)
case []any:
return r.cmpTOMLArrays(w, have)
default:
Expand Down Expand Up @@ -83,21 +77,10 @@ func (r Test) cmpTOMLMap(want map[string]any, have any) Test {
}

func (r Test) cmpTOMLArrays(want []any, have any) Test {
// Slice can be decoded to []any for an array of primitives, or
// []map[string]any for an array of tables.
//
// TODO: it would be nicer if it could always decode to []any?
haveSlice, ok := have.([]any)
if !ok {
tblArray, ok := have.([]map[string]any)
if !ok {
return r.mismatch("array", want, have)
}

haveSlice = make([]any, len(tblArray))
for i := range tblArray {
haveSlice[i] = tblArray[i]
}
if !ok {
return r.mismatch("array", want, have)
}

if len(want) != len(haveSlice) {
Expand Down Expand Up @@ -146,7 +129,7 @@ func deepEqual(want, have any) bool {

func isTomlValue(v any) bool {
switch v.(type) {
case map[string]any, []map[string]any, []any:
case map[string]any, []any:
return false
}
return true
Expand Down
55 changes: 30 additions & 25 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type parser struct {
type keyInfo struct {
pos Position
tomlType tomlType
locked bool // Writing to inline arrays after definition is forbidden.
}

func parse(data string) (p *parser, err error) {
Expand Down Expand Up @@ -173,7 +174,7 @@ func (p *parser) topLevel(item item) {
p.assertEqual(itemTableEnd, name.typ)

p.addContext(key, false)
p.setType("", tomlHash, item.pos)
p.setTypeContext(tomlHash, item.pos, false)
p.ordered = append(p.ordered, key)
case itemArrayTableStart: // [[ .. ]]
name := p.nextPos()
Expand All @@ -185,7 +186,7 @@ func (p *parser) topLevel(item item) {
p.assertEqual(itemArrayTableEnd, name.typ)

p.addContext(key, true)
p.setType("", tomlArrayHash, item.pos)
p.setTypeContext(tomlArrayHash, item.pos, false)
p.ordered = append(p.ordered, key)
case itemKeyStart: // key = ..
outerContext := p.context
Expand All @@ -212,7 +213,7 @@ func (p *parser) topLevel(item item) {
vItem := p.next()
val, typ := p.value(vItem, false)
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ, vItem.pos)
p.setType(p.currentKey, typ, vItem.pos, true)

/// Remove the context we added (preserving any context from [tbl] lines).
p.context = outerContext
Expand Down Expand Up @@ -408,7 +409,7 @@ func missingLeadingZero(d, l string) bool {
}

func (p *parser) valueArray(it item) (any, tomlType) {
p.setType(p.currentKey, tomlArray, it.pos)
p.setType(p.currentKey, tomlArray, it.pos, false)

var (
// Initialize to a non-nil slice to make it consistent with how S = []
Expand Down Expand Up @@ -478,7 +479,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) {
/// Set the value.
val, typ := p.value(p.next(), false)
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ, it.pos)
p.setType(p.currentKey, typ, it.pos, true)

hash := topHash
for _, c := range context {
Expand Down Expand Up @@ -575,8 +576,12 @@ func (p *parser) addContext(key Key, array bool) {
// Otherwise, it better be a table, since this MUST be a key group (by
// virtue of it not being the last element in a key).
switch t := hashContext[k].(type) {
case []map[string]any:
hashContext = t[len(t)-1]
case []any:
if !p.isLocked(keyContext) {
hashContext = t[len(t)-1].(map[string]any)
} else {
p.panicf("Key '%s' was already created as a hash.", keyContext)
}
case map[string]any:
hashContext = t
default:
Expand All @@ -590,13 +595,17 @@ func (p *parser) addContext(key Key, array bool) {
// list of tables for it.
k := key.last()
if _, ok := hashContext[k]; !ok {
hashContext[k] = make([]map[string]any, 0, 4)
hashContext[k] = make([]any, 0, 4)
}

// Add a new table. But make sure the key hasn't already been used
// for something else.
if hash, ok := hashContext[k].([]map[string]any); ok {
hashContext[k] = append(hash, make(map[string]any))
if hash, ok := hashContext[k].([]any); ok {
if !p.isLocked(append(keyContext, k)) {
hashContext[k] = append(hash, make(map[string]any))
} else {
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
}
} else {
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
}
Expand All @@ -622,10 +631,10 @@ func (p *parser) setValue(key string, value any) {
p.bug("Context for key '%s' has not been established.", keyContext)
}
switch t := tmpHash.(type) {
case []map[string]any:
case []any:
// The context is a table of hashes. Pick the most recent table
// defined as the current hash.
hash = t[len(t)-1]
hash = t[len(t)-1].(map[string]any)
case map[string]any:
hash = t
default:
Expand Down Expand Up @@ -666,19 +675,14 @@ func (p *parser) setValue(key string, value any) {
//
// Note that if `key` is empty, then the type given will be applied to the
// current context (which is either a table or an array of tables).
func (p *parser) setType(key string, typ tomlType, pos Position) {
keyContext := make(Key, 0, len(p.context)+1)
keyContext = append(keyContext, p.context...)
if len(key) > 0 { // allow type setting for hashes
keyContext = append(keyContext, key)
}
// Special case to make empty keys ("" = 1) work.
// Without it it will set "" rather than `""`.
// TODO: why is this needed? And why is this only needed here?
if len(keyContext) == 0 {
keyContext = Key{""}
}
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
func (p *parser) setType(key string, typ tomlType, pos Position, locked bool) {
keyContext := p.context.add(key)
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos, locked: locked}
}

func (p *parser) setTypeContext(typ tomlType, pos Position, locked bool) {
keyContext := p.context
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos, locked: locked}
}

// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
Expand All @@ -687,6 +691,7 @@ func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struc
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
func (p *parser) isLocked(key Key) bool { return p.keyInfo[key.String()].locked }
func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) }

// current returns the full key name of the current context.
Expand Down
Loading