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

Fix inline tables with dotted keys inside inline arrays #400

Merged
merged 1 commit into from
Dec 7, 2023
Merged
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
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