Skip to content

Commit

Permalink
Fix inline tables with dotted keys inside inline arrays (#400)
Browse files Browse the repository at this point in the history
For example this:

	arr = [
		{a.b.c = 1},
	]

Would lose the a.b context, and it would just be map["c" = 1].
  • Loading branch information
arp242 authored Dec 7, 2023
1 parent 45e7e49 commit 4223137
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 30 deletions.
8 changes: 4 additions & 4 deletions internal/toml-test/tests/valid/inline-table/inline-table.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
simple = { a = 1 }
str-key = { "a" = 1 }
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
simple = { a = 1 }
str-key = { "a" = 1 }
table-array = [{ "a" = 1 }, { "b" = 2 }]
66 changes: 66 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"arr-1": [
{
"a": {
"b": {
"type": "integer",
"value": "1"
}
}
}
],
"arr-2": [
{
"type": "string",
"value": "str"
},
{
"a": {
"b": {
"type": "integer",
"value": "1"
}
}
}
],
"arr-3": [
{
"a": {
"b": {
"type": "integer",
"value": "1"
}
}
},
{
"a": {
"b": {
"type": "integer",
"value": "2"
}
}
}
],
"arr-4": [
{
"type": "string",
"value": "str"
},
{
"a": {
"b": {
"type": "integer",
"value": "1"
}
}
},
{
"a": {
"b": {
"type": "integer",
"value": "2"
}
}
}
]
}
5 changes: 5 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-5.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
arr-1 = [{a.b = 1}]
arr-2 = ["str", {a.b = 1}]

arr-3 = [{a.b = 1}, {a.b = 2}]
arr-4 = ["str", {a.b = 1}, {a.b = 2}]
28 changes: 28 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"top": {
"dot": {
"dot": [
{
"dot": {
"dot": {
"dot": {
"type": "integer",
"value": "1"
}
}
}
},
{
"dot": {
"dot": {
"dot": {
"type": "integer",
"value": "2"
}
}
}
}
]
}
}
}
4 changes: 4 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-6.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
top.dot.dot = [
{dot.dot.dot = 1},
{dot.dot.dot = 2},
]
18 changes: 18 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"arr": [
{
"a": {
"b": [
{
"c": {
"d": {
"type": "integer",
"value": "1"
}
}
}
]
}
}
]
}
3 changes: 3 additions & 0 deletions internal/toml-test/tests/valid/inline-table/key-dotted-7.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
arr = [
{a.b = [{c.d = 1}]}
]
7 changes: 7 additions & 0 deletions meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,16 @@ func (k Key) maybeQuoted(i int) string {
return k[i]
}

// Like append(), but only increase the cap by 1.
func (k Key) add(piece string) Key {
if cap(k) > len(k) {
return append(k, piece)
}
newKey := make(Key, len(k)+1)
copy(newKey, k)
newKey[len(k)] = piece
return newKey
}

func (k Key) parent() Key { return k[:len(k)-1] } // all except the last piece.
func (k Key) last() string { return k[len(k)-1] } // last piece of this key.
55 changes: 29 additions & 26 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@ func (p *parser) topLevel(item item) {
p.assertEqual(itemKeyEnd, k.typ)

/// The current key is the last part.
p.currentKey = key[len(key)-1]
p.currentKey = key.last()

/// All the other parts (if any) are the context; need to set each part
/// as implicit.
context := key[:len(key)-1]
context := key.parent()
for i := range context {
p.addImplicitContext(append(p.context, context[i:i+1]...))
}
Expand All @@ -209,7 +209,8 @@ func (p *parser) topLevel(item item) {
/// Set value.
vItem := p.next()
val, typ := p.value(vItem, false)
p.set(p.currentKey, val, typ, vItem.pos)
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ, vItem.pos)

/// Remove the context we added (preserving any context from [tbl] lines).
p.context = outerContext
Expand Down Expand Up @@ -434,7 +435,7 @@ func (p *parser) valueArray(it item) (any, tomlType) {

func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) {
var (
hash = make(map[string]any)
topHash = make(map[string]any)
outerContext = p.context
outerKey = p.currentKey
)
Expand Down Expand Up @@ -462,27 +463,38 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) {
p.assertEqual(itemKeyEnd, k.typ)

/// The current key is the last part.
p.currentKey = key[len(key)-1]
p.currentKey = key.last()

/// All the other parts (if any) are the context; need to set each part
/// as implicit.
context := key[:len(key)-1]
context := key.parent()
for i := range context {
p.addImplicitContext(append(p.context, context[i:i+1]...))
}
p.ordered = append(p.ordered, p.context.add(p.currentKey))

/// Set the value.
val, typ := p.value(p.next(), false)
p.set(p.currentKey, val, typ, it.pos)
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ, it.pos)

hash := topHash
for _, c := range context {
h, ok := hash[c]
if !ok {
h = make(map[string]any)
hash[c] = h
}
hash = h.(map[string]any)
}
hash[p.currentKey] = val

/// Restore context.
p.context = prevContext
}
p.context = outerContext
p.currentKey = outerKey
return hash, tomlHash
return topHash, tomlHash
}

// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
Expand Down Expand Up @@ -537,15 +549,13 @@ func numPeriodsOK(s string) bool {
// Establishing the context also makes sure that the key isn't a duplicate, and
// will create implicit hashes automatically.
func (p *parser) addContext(key Key, array bool) {
var ok bool

// Always start at the top level and drill down for our context.
/// Always start at the top level and drill down for our context.
hashContext := p.mapping
keyContext := make(Key, 0, len(key)-1)

// We only need implicit hashes for key[0:-1]
for _, k := range key[0 : len(key)-1] {
_, ok = hashContext[k]
/// We only need implicit hashes for the parents.
for _, k := range key.parent() {
_, ok := hashContext[k]
keyContext = append(keyContext, k)

// No key? Make an implicit hash and move on.
Expand Down Expand Up @@ -573,7 +583,7 @@ func (p *parser) addContext(key Key, array bool) {
if array {
// If this is the first element for this array, then allocate a new
// list of tables for it.
k := key[len(key)-1]
k := key.last()
if _, ok := hashContext[k]; !ok {
hashContext[k] = make([]map[string]any, 0, 4)
}
Expand All @@ -586,15 +596,9 @@ func (p *parser) addContext(key Key, array bool) {
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
}
} else {
p.setValue(key[len(key)-1], make(map[string]any))
p.setValue(key.last(), make(map[string]any))
}
p.context = append(p.context, key[len(key)-1])
}

// set calls setValue and setType.
func (p *parser) set(key string, val any, typ tomlType, pos Position) {
p.setValue(key, val)
p.setType(key, typ, pos)
p.context = append(p.context, key.last())
}

// setValue sets the given key to the given value in the current context.
Expand Down Expand Up @@ -644,9 +648,8 @@ func (p *parser) setValue(key string, value any) {
p.removeImplicit(keyContext)
return
}

// Otherwise, we have a concrete key trying to override a previous
// key, which is *always* wrong.
// Otherwise, we have a concrete key trying to override a previous key,
// which is *always* wrong.
p.panicf("Key '%s' has already been defined.", keyContext)
}

Expand Down

0 comments on commit 4223137

Please sign in to comment.