From a3e9af52783b7aac5ff839b7d91be3f13501e6c5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 21 Sep 2026 02:10:46 +0900 Subject: [PATCH 1/2] feat: add inherited-array pairing --- internal/json.go | 81 +++++++++++++++++-- .../flat-splice-layers/expected.json | 1 + .../flat-splice-layers/input.json | 1 + .../flat-splice-layers/m1.json | 1 + .../flat-splice-layers/m2.json | 1 + .../flat-splice-layers/test.json | 1 + .../grouped-splice-layers/expected.json | 1 + .../grouped-splice-layers/input.json | 1 + .../grouped-splice-layers/m1.json | 1 + .../grouped-splice-layers/m2.json | 1 + .../grouped-splice-layers/test.json | 1 + .../array-composition/pair-arrays/P.json | 1 + .../pair-arrays/expected.json | 1 + .../array-composition/pair-arrays/input.json | 1 + .../array-composition/pair-arrays/test.json | 1 + .../pair-empty-object/P.json | 1 + .../pair-empty-object/expected.json | 1 + .../pair-empty-object/input.json | 1 + .../pair-empty-object/test.json | 1 + .../pair-inherited-tail/P.json | 1 + .../pair-inherited-tail/expected.json | 1 + .../pair-inherited-tail/input.json | 1 + .../pair-inherited-tail/test.json | 1 + .../array-composition/pair-null/P.json | 1 + .../array-composition/pair-null/expected.json | 1 + .../array-composition/pair-null/input.json | 1 + .../array-composition/pair-null/test.json | 1 + .../pair-object-key-override/P.json | 1 + .../pair-object-key-override/expected.json | 1 + .../pair-object-key-override/input.json | 1 + .../pair-object-key-override/test.json | 1 + .../array-composition/pair-objects/P.json | 1 + .../pair-objects/expected.json | 1 + .../array-composition/pair-objects/input.json | 1 + .../array-composition/pair-objects/test.json | 1 + .../pair-prefix-and-extras/P.json | 1 + .../pair-prefix-and-extras/expected.json | 1 + .../pair-prefix-and-extras/input.json | 1 + .../pair-prefix-and-extras/test.json | 1 + .../pair-array-object/P.json | 1 + .../pair-array-object/expected.txt | 3 + .../pair-array-object/input.json | 1 + .../pair-array-object/test.json | 1 + .../array-composition/pair-object-atom/P.json | 1 + .../pair-object-atom/expected.txt | 3 + .../pair-object-atom/input.json | 1 + .../pair-object-atom/test.json | 1 + .../array-composition/pair-over-splice/P.json | 1 + .../pair-over-splice/expected.txt | 3 + .../pair-over-splice/input.json | 1 + .../pair-over-splice/test.json | 1 + .../array-composition/splice-over-pair/P.json | 1 + .../splice-over-pair/expected.txt | 3 + .../splice-over-pair/input.json | 1 + .../splice-over-pair/test.json | 1 + 55 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 tools/etc/autotest/inheritance/array-composition/flat-splice-layers/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/flat-splice-layers/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m1.json create mode 100644 tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m2.json create mode 100644 tools/etc/autotest/inheritance/array-composition/flat-splice-layers/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m1.json create mode 100644 tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m2.json create mode 100644 tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-arrays/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-arrays/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-arrays/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-arrays/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-empty-object/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-empty-object/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-empty-object/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-empty-object/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-null/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-null/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-null/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-null/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-object-key-override/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-object-key-override/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-object-key-override/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-object-key-override/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-objects/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-objects/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-objects/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-objects/test.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/P.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/expected.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/input.json create mode 100644 tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/test.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-array-object/P.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-array-object/expected.txt create mode 100644 tools/etc/autotest/negative/array-composition/pair-array-object/input.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-array-object/test.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-object-atom/P.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-object-atom/expected.txt create mode 100644 tools/etc/autotest/negative/array-composition/pair-object-atom/input.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-object-atom/test.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-over-splice/P.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-over-splice/expected.txt create mode 100644 tools/etc/autotest/negative/array-composition/pair-over-splice/input.json create mode 100644 tools/etc/autotest/negative/array-composition/pair-over-splice/test.json create mode 100644 tools/etc/autotest/negative/array-composition/splice-over-pair/P.json create mode 100644 tools/etc/autotest/negative/array-composition/splice-over-pair/expected.txt create mode 100644 tools/etc/autotest/negative/array-composition/splice-over-pair/input.json create mode 100644 tools/etc/autotest/negative/array-composition/splice-over-pair/test.json diff --git a/internal/json.go b/internal/json.go index 9d0556f..a36272a 100644 --- a/internal/json.go +++ b/internal/json.go @@ -241,7 +241,7 @@ func mergeObjectsAt(a, b map[string]interface{}, policy MergePolicy, path []any) } if child, ok := v.([]any); ok { inherited, present := result[k] - composed, err := composeArrayAt(inherited, present, child, childPath(k)) + composed, err := composeArrayAt(inherited, present, child, policy, childPath(k)) if err != nil { return nil, err } @@ -257,7 +257,7 @@ func mergeObjectsAt(a, b map[string]interface{}, policy MergePolicy, path []any) // array replaces, exactly as it always has. A marked array is a delta: it // carries unchanged while the inherited array is absent, and resolves against // it once one is present. -func composeArrayAt(inherited any, present bool, child []any, path []any) (any, error) { +func composeArrayAt(inherited any, present bool, child []any, policy MergePolicy, path []any) (any, error) { kind, at, err := scanArrayMarkers(child) if err != nil { return nil, fmt.Errorf("%v: at '%s'", err, toPathExpression(path)) @@ -275,13 +275,20 @@ func composeArrayAt(inherited any, present bool, child []any, path []any) (any, if !ok { return nil, fmt.Errorf("array composition marker requires an inherited array, but got %T: at '%s'", inherited, toPathExpression(path)) } + inheritedKind, _, err := scanArrayMarkers(inheritedArray) + if err != nil { + return nil, fmt.Errorf("%v: at '%s'", err, toPathExpression(path)) + } + if (kind == PairMarker && inheritedKind.IsMarker()) || (inheritedKind == PairMarker && kind.IsMarker()) { + return nil, fmt.Errorf("array composition markers cannot compose: %q with %q", inheritedKind.String(), kind.String()) + } switch kind { case SpliceMarker: return spliceArray(inheritedArray, child, at), nil + case PairMarker: + return pairArray(inheritedArray, child, at, policy, path) default: - // MarkerPair is not implemented yet; it carries and is reported by - // grounding until pairing lands. - return child, nil + panic("unreachable: marked array has no marker kind") } } @@ -299,6 +306,70 @@ func spliceArray(inherited []any, child []any, at int) []any { return out } +// pairArray emits the literal prefix before $super*, then combines the queue +// after it with inherited elements at matching offsets. Elements without a +// partner survive in their original order. +func pairArray(inherited, child []any, at int, policy MergePolicy, path []any) ([]any, error) { + prefix := child[:at] + queue := child[at+1:] + maxLen := len(inherited) + if len(queue) > maxLen { + maxLen = len(queue) + } + out := make([]any, 0, len(prefix)+maxLen) + out = append(out, prefix...) + for i := 0; i < maxLen; i++ { + switch { + case i >= len(inherited): + out = append(out, queue[i]) + case i >= len(queue): + out = append(out, inherited[i]) + default: + paired, err := pairValues(inherited[i], queue[i], policy, appendPathSegment(path, i)) + if err != nil { + return nil, err + } + out = append(out, paired) + } + } + return out, nil +} + +// pairValues implements the same-kind rule for $super* pairing. A pair of +// objects merges; arrays and atoms are queue-wins overrides. Cross-kind pairs +// are errors because they signal a likely index misalignment. +func pairValues(inherited, queue any, policy MergePolicy, path []any) (any, error) { + inheritedKind := valueKind(inherited) + queueKind := valueKind(queue) + if inheritedKind != queueKind { + return nil, fmt.Errorf("array composition pair mixes kinds at index %d: inherited %s, but got %s", path[len(path)-1], inheritedKind, queueKind) + } + if inheritedObject, ok := inherited.(map[string]any); ok { + queueObject := queue.(map[string]any) + return mergeObjectsAt(inheritedObject, queueObject, policy, path) + } + return queue, nil +} + +type arrayCompositionKind string + +const ( + objectKind arrayCompositionKind = "object" + arrayKind arrayCompositionKind = "array" + atomKind arrayCompositionKind = "atom" +) + +func valueKind(v any) arrayCompositionKind { + switch v.(type) { + case map[string]any: + return objectKind + case []any: + return arrayKind + default: + return atomKind + } +} + // scanArrayMarkers finds the array's marker, rejecting an undefined spelling in // the reserved namespace and an array carrying more than one marker. func scanArrayMarkers(arr []any) (MarkerKind, int, error) { diff --git a/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/expected.json b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/expected.json new file mode 100644 index 0000000..cb5d76a --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/expected.json @@ -0,0 +1 @@ +{ "m": ["base", "m1", "m2"] } diff --git a/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/input.json b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/input.json new file mode 100644 index 0000000..6065d48 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/input.json @@ -0,0 +1 @@ +{ "$includes": ["m1.json", "m2.json"], "m": ["base"] } diff --git a/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m1.json b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m1.json new file mode 100644 index 0000000..956daf9 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m1.json @@ -0,0 +1 @@ +{ "m": ["$super", "m1"] } diff --git a/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m2.json b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m2.json new file mode 100644 index 0000000..5aa5cf0 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/m2.json @@ -0,0 +1 @@ +{ "m": ["$super", "m2"] } diff --git a/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/test.json b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/flat-splice-layers/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/expected.json b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/expected.json new file mode 100644 index 0000000..cb5d76a --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/expected.json @@ -0,0 +1 @@ +{ "m": ["base", "m1", "m2"] } diff --git a/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/input.json b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/input.json new file mode 100644 index 0000000..8a46f8f --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/input.json @@ -0,0 +1 @@ +{ "$includes": ["m2.json"], "m": ["base"] } diff --git a/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m1.json b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m1.json new file mode 100644 index 0000000..956daf9 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m1.json @@ -0,0 +1 @@ +{ "m": ["$super", "m1"] } diff --git a/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m2.json b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m2.json new file mode 100644 index 0000000..b470f6b --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/m2.json @@ -0,0 +1 @@ +{ "$extends": ["m1.json"], "m": ["$super", "m2"] } diff --git a/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/test.json b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/grouped-splice-layers/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-arrays/P.json b/tools/etc/autotest/inheritance/array-composition/pair-arrays/P.json new file mode 100644 index 0000000..bc51a3d --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-arrays/P.json @@ -0,0 +1 @@ +{ "m": [[1, 2]] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-arrays/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-arrays/expected.json new file mode 100644 index 0000000..1189743 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-arrays/expected.json @@ -0,0 +1 @@ +{ "m": [[9]] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-arrays/input.json b/tools/etc/autotest/inheritance/array-composition/pair-arrays/input.json new file mode 100644 index 0000000..1149964 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-arrays/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", [9]] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-arrays/test.json b/tools/etc/autotest/inheritance/array-composition/pair-arrays/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-arrays/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-empty-object/P.json b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/P.json new file mode 100644 index 0000000..a63c3a0 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/P.json @@ -0,0 +1 @@ +{ "m": [{"a": 1}, {"b": 2}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-empty-object/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/expected.json new file mode 100644 index 0000000..075bd69 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/expected.json @@ -0,0 +1 @@ +{ "m": [{"a": 1}, {"b": 20}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-empty-object/input.json b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/input.json new file mode 100644 index 0000000..dc7a9fd --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", {}, {"b": 20}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-empty-object/test.json b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-empty-object/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/P.json b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/P.json new file mode 100644 index 0000000..2b09f8e --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/P.json @@ -0,0 +1 @@ +{ "m": ["a", "b", "c"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/expected.json new file mode 100644 index 0000000..5c63ef3 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/expected.json @@ -0,0 +1 @@ +{ "m": ["A", "b", "c"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/input.json b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/input.json new file mode 100644 index 0000000..1619ea6 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", "A"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/test.json b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-inherited-tail/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-null/P.json b/tools/etc/autotest/inheritance/array-composition/pair-null/P.json new file mode 100644 index 0000000..96b6975 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-null/P.json @@ -0,0 +1 @@ +{ "m": [null] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-null/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-null/expected.json new file mode 100644 index 0000000..cd86a1f --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-null/expected.json @@ -0,0 +1 @@ +{ "m": [true] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-null/input.json b/tools/etc/autotest/inheritance/array-composition/pair-null/input.json new file mode 100644 index 0000000..9ef2522 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-null/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", true] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-null/test.json b/tools/etc/autotest/inheritance/array-composition/pair-null/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-null/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/P.json b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/P.json new file mode 100644 index 0000000..ff90026 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/P.json @@ -0,0 +1 @@ +{ "m": [{"name": "a", "opts": {"x": 1}}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/expected.json new file mode 100644 index 0000000..9266275 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/expected.json @@ -0,0 +1 @@ +{ "m": [{"name": "a", "opts": "none"}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/input.json b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/input.json new file mode 100644 index 0000000..af327cb --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", {"opts": "none"}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/test.json b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-object-key-override/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-objects/P.json b/tools/etc/autotest/inheritance/array-composition/pair-objects/P.json new file mode 100644 index 0000000..3d2c014 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-objects/P.json @@ -0,0 +1 @@ +{ "m": [{"a": 1, "b": 2}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-objects/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-objects/expected.json new file mode 100644 index 0000000..f40de56 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-objects/expected.json @@ -0,0 +1 @@ +{ "m": [{"a": 1, "b": 20}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-objects/input.json b/tools/etc/autotest/inheritance/array-composition/pair-objects/input.json new file mode 100644 index 0000000..24b6821 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-objects/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", {"b": 20}] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-objects/test.json b/tools/etc/autotest/inheritance/array-composition/pair-objects/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-objects/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/P.json b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/P.json new file mode 100644 index 0000000..2b09f8e --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/P.json @@ -0,0 +1 @@ +{ "m": ["a", "b", "c"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/expected.json b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/expected.json new file mode 100644 index 0000000..901871e --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/expected.json @@ -0,0 +1 @@ +{ "m": ["p", "A", "B", "C", "D"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/input.json b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/input.json new file mode 100644 index 0000000..7c9d66e --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["p", "$super*", "A", "B", "C", "D"] } diff --git a/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/test.json b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/test.json new file mode 100644 index 0000000..772bc47 --- /dev/null +++ b/tools/etc/autotest/inheritance/array-composition/pair-prefix-and-extras/test.json @@ -0,0 +1 @@ +{ "testType": "normal" } diff --git a/tools/etc/autotest/negative/array-composition/pair-array-object/P.json b/tools/etc/autotest/negative/array-composition/pair-array-object/P.json new file mode 100644 index 0000000..bc51a3d --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-array-object/P.json @@ -0,0 +1 @@ +{ "m": [[1, 2]] } diff --git a/tools/etc/autotest/negative/array-composition/pair-array-object/expected.txt b/tools/etc/autotest/negative/array-composition/pair-array-object/expected.txt new file mode 100644 index 0000000..d723d74 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-array-object/expected.txt @@ -0,0 +1,3 @@ +ERROR +array composition pair mixes kinds at index 0 +negative/array-composition/pair-array-object/input.json diff --git a/tools/etc/autotest/negative/array-composition/pair-array-object/input.json b/tools/etc/autotest/negative/array-composition/pair-array-object/input.json new file mode 100644 index 0000000..53e96bc --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-array-object/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", {"a": 1}] } diff --git a/tools/etc/autotest/negative/array-composition/pair-array-object/test.json b/tools/etc/autotest/negative/array-composition/pair-array-object/test.json new file mode 100644 index 0000000..36a53df --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-array-object/test.json @@ -0,0 +1 @@ +{ "testType": "negative" } diff --git a/tools/etc/autotest/negative/array-composition/pair-object-atom/P.json b/tools/etc/autotest/negative/array-composition/pair-object-atom/P.json new file mode 100644 index 0000000..0e7f81a --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-object-atom/P.json @@ -0,0 +1 @@ +{ "m": [{"a": 1}] } diff --git a/tools/etc/autotest/negative/array-composition/pair-object-atom/expected.txt b/tools/etc/autotest/negative/array-composition/pair-object-atom/expected.txt new file mode 100644 index 0000000..410e304 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-object-atom/expected.txt @@ -0,0 +1,3 @@ +ERROR +array composition pair mixes kinds at index 0 +negative/array-composition/pair-object-atom/input.json diff --git a/tools/etc/autotest/negative/array-composition/pair-object-atom/input.json b/tools/etc/autotest/negative/array-composition/pair-object-atom/input.json new file mode 100644 index 0000000..1619ea6 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-object-atom/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", "A"] } diff --git a/tools/etc/autotest/negative/array-composition/pair-object-atom/test.json b/tools/etc/autotest/negative/array-composition/pair-object-atom/test.json new file mode 100644 index 0000000..36a53df --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-object-atom/test.json @@ -0,0 +1 @@ +{ "testType": "negative" } diff --git a/tools/etc/autotest/negative/array-composition/pair-over-splice/P.json b/tools/etc/autotest/negative/array-composition/pair-over-splice/P.json new file mode 100644 index 0000000..8c61ffb --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-over-splice/P.json @@ -0,0 +1 @@ +{ "m": ["$super", {"a": 1}] } diff --git a/tools/etc/autotest/negative/array-composition/pair-over-splice/expected.txt b/tools/etc/autotest/negative/array-composition/pair-over-splice/expected.txt new file mode 100644 index 0000000..4aac099 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-over-splice/expected.txt @@ -0,0 +1,3 @@ +ERROR +array composition markers cannot compose +negative/array-composition/pair-over-splice/input.json diff --git a/tools/etc/autotest/negative/array-composition/pair-over-splice/input.json b/tools/etc/autotest/negative/array-composition/pair-over-splice/input.json new file mode 100644 index 0000000..d496e82 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-over-splice/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super*", {"b": 2}] } diff --git a/tools/etc/autotest/negative/array-composition/pair-over-splice/test.json b/tools/etc/autotest/negative/array-composition/pair-over-splice/test.json new file mode 100644 index 0000000..36a53df --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/pair-over-splice/test.json @@ -0,0 +1 @@ +{ "testType": "negative" } diff --git a/tools/etc/autotest/negative/array-composition/splice-over-pair/P.json b/tools/etc/autotest/negative/array-composition/splice-over-pair/P.json new file mode 100644 index 0000000..fcaed0a --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/splice-over-pair/P.json @@ -0,0 +1 @@ +{ "m": ["$super*", {"a": 1}] } diff --git a/tools/etc/autotest/negative/array-composition/splice-over-pair/expected.txt b/tools/etc/autotest/negative/array-composition/splice-over-pair/expected.txt new file mode 100644 index 0000000..c2773bb --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/splice-over-pair/expected.txt @@ -0,0 +1,3 @@ +ERROR +array composition markers cannot compose +negative/array-composition/splice-over-pair/input.json diff --git a/tools/etc/autotest/negative/array-composition/splice-over-pair/input.json b/tools/etc/autotest/negative/array-composition/splice-over-pair/input.json new file mode 100644 index 0000000..c835194 --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/splice-over-pair/input.json @@ -0,0 +1 @@ +{ "$extends": ["P.json"], "m": ["$super", {"b": 2}] } diff --git a/tools/etc/autotest/negative/array-composition/splice-over-pair/test.json b/tools/etc/autotest/negative/array-composition/splice-over-pair/test.json new file mode 100644 index 0000000..36a53df --- /dev/null +++ b/tools/etc/autotest/negative/array-composition/splice-over-pair/test.json @@ -0,0 +1 @@ +{ "testType": "negative" } From d25eee334ccf33ae3259127d5fe32a05670dddff Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 21 Sep 2026 09:42:19 +0900 Subject: [PATCH 2/2] fix: report paired-element paths after prefix --- internal/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/json.go b/internal/json.go index a36272a..4f437c0 100644 --- a/internal/json.go +++ b/internal/json.go @@ -325,7 +325,7 @@ func pairArray(inherited, child []any, at int, policy MergePolicy, path []any) ( case i >= len(queue): out = append(out, inherited[i]) default: - paired, err := pairValues(inherited[i], queue[i], policy, appendPathSegment(path, i)) + paired, err := pairValues(inherited[i], queue[i], policy, appendPathSegment(path, len(prefix)+i)) if err != nil { return nil, err }