internal/core/eval: breadth-first disjunction resolution
This CL makes disjunction resolution polynomial instead
of exponential. More precisely, running time for a field
reduces from
O(m)*O(c^d)
to
O(m)+O(d*c),
where m is the number of non-disjunction conjuncts for a field,
d is the number of disjunctions, and c the maximum number of
disjuncts per disjunction.
So overall, this reduces the running time by improving the big O
runtime complexity as well as reducing the constant.
This change also fixes numerous issues:
- results properly reflect disjunction where this was previously
not the case in some instances
- error messages that were previously dropped in some cases are
now retained.
- this also means that the "empty disjunction" error message now
properly reflect the error type, whereas previously this was
sometimes lost.
- embedding of disjunctions with struct and non-struct types
are now properly resolved.
- the scalar hack has been replaced with a more principled
"non-default" erasure.
Fixes #565
- was already at 10s for alpha6 on 16' Macbook Pro, but now
down to 1.2s. (Orignally >10m).
Fixes #547
Change-Id: Ib050ba3f15e481391e4a60e6d3ab7c3f512643fc
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8045
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/benchmarks/disjunction.txtar b/cue/testdata/benchmarks/disjunction.txtar
new file mode 100644
index 0000000..7124097
--- /dev/null
+++ b/cue/testdata/benchmarks/disjunction.txtar
@@ -0,0 +1,146 @@
+// Triggering recomputation of disjunctions on each branch results
+// in exponential run time. Ensure this does not happen.
+
+-- in.cue --
+x: a0: {}
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+x: [_]: f: *1 | int
+
+
+x: a0: {}
+-- out/compile --
+--- in.cue
+{
+ x: {
+ a0: {}
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ [_]: {
+ f: (*1|int)
+ }
+ }
+ x: {
+ a0: {}
+ }
+}
+-- out/eval --
+(struct){
+ x: (struct){
+ a0: (struct){
+ f: (int){ |(*(int){ 1 }, (int){ int }) }
+ }
+ }
+}
diff --git a/cue/testdata/comprehensions/errors.txtar b/cue/testdata/comprehensions/errors.txtar
index 483db98..85fa787 100644
--- a/cue/testdata/comprehensions/errors.txtar
+++ b/cue/testdata/comprehensions/errors.txtar
@@ -32,7 +32,9 @@
// [eval]
circularIf: (struct){
#list: (#struct){
- tail: (null){ null }
+ tail: ((null|struct)){ |(*(null){ null }, (#struct){
+ tail: (null){ null }
+ }) }
}
}
circularFor: (_|_){
@@ -44,7 +46,7 @@
}
}
userError: (_|_){
- // [incomplete] userError.a: empty disjunction:
+ // [incomplete] userError: non-concrete value string in operand to !=:
// ./in.cue:21:8
a: (string){ string }
}
diff --git a/cue/testdata/comprehensions/issue436.txtar b/cue/testdata/comprehensions/issue436.txtar
index c206191..fcd8b64 100644
--- a/cue/testdata/comprehensions/issue436.txtar
+++ b/cue/testdata/comprehensions/issue436.txtar
@@ -26,7 +26,7 @@
result: (string){ string }
}
a: (#struct){
- val: (string){ "default" }
+ val: (string){ |(*(string){ "default" }, (string){ string }) }
result: (string){ |(*(string){ "not-matched" }, (string){ string }) }
}
match: (#struct){
diff --git a/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar b/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
index ea59006..89f4dd7 100644
--- a/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
+++ b/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
@@ -159,6 +159,10 @@
}
-- out/eval --
Errors:
+xe1: 2 errors in empty disjunction:
+xe1: conflicting values 8 and 9:
+ ./in.cue:43:12
+ ./in.cue:48:6
xe3: conflicting values 7 and 6:
./in.cue:45:6
./in.cue:45:10
@@ -191,7 +195,13 @@
xd4: (int){ 9 }
xd5: (int){ 10 }
xe1: (_|_){
- // [incomplete] xe1: empty disjunction
+ // [eval] xe1: 2 errors in empty disjunction:
+ // xe1: conflicting values 8 and 9:
+ // ./in.cue:43:12
+ // ./in.cue:48:6
+ // xe3: conflicting values 7 and 6:
+ // ./in.cue:45:6
+ // ./in.cue:45:10
}
xe2: (_|_){
// [eval] xe3: conflicting values 7 and 6:
diff --git a/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar b/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
index 46c6286..773bdca 100644
--- a/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
+++ b/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
@@ -129,6 +129,10 @@
}
-- out/eval --
Errors:
+xe1: 2 errors in empty disjunction:
+xe1: conflicting values 8 and 9:
+ ./in.cue:36:14
+ ./in.cue:41:6
xe3: conflicting values 7 and 6:
./in.cue:38:6
./in.cue:38:10
@@ -142,7 +146,7 @@
xa4: (int){ 10 }
xb1: (int){ 8 }
xb2: (int){ 8 }
- xb3: (int){ 6 }
+ xb3: (int){ |(*(int){ 6 }, (int){ 9 }) }
xb4: (_|_){
// [cycle] cycle error:
// ./in.cue:14:6
@@ -158,7 +162,13 @@
xd4: (int){ 9 }
xd5: (int){ 10 }
xe1: (_|_){
- // [incomplete] xe1: empty disjunction
+ // [eval] xe1: 2 errors in empty disjunction:
+ // xe1: conflicting values 8 and 9:
+ // ./in.cue:36:14
+ // ./in.cue:41:6
+ // xe3: conflicting values 7 and 6:
+ // ./in.cue:38:6
+ // ./in.cue:38:10
}
xe2: (_|_){
// [eval] xe3: conflicting values 7 and 6:
diff --git a/cue/testdata/cycle/issue429.txtar b/cue/testdata/cycle/issue429.txtar
index 9a816c9..876994b 100644
--- a/cue/testdata/cycle/issue429.txtar
+++ b/cue/testdata/cycle/issue429.txtar
@@ -52,8 +52,22 @@
-- out/eval --
Errors:
-er3.min: 1 errors in empty disjunction:
-es3.max: 1 errors in empty disjunction:
+er3.min: 2 errors in empty disjunction:
+er3.min: conflicting values 1 and 5:
+ ./in.cue:28:11
+ ./in.cue:43:6
+ ./in.cue:44:10
+es3.max: 3 errors in empty disjunction:
+es3.max: conflicting values 1 and 5:
+ ./in.cue:4:19
+ ./in.cue:5:18
+ ./in.cue:20:6
+ ./in.cue:22:10
+es3.max: conflicting values 10 and 5:
+ ./in.cue:5:18
+ ./in.cue:20:6
+ ./in.cue:21:10
+ ./in.cue:22:10
es3.max: invalid value 5 (out of bound >10):
./in.cue:5:10
./in.cue:22:10
@@ -94,7 +108,17 @@
res: (int){ |(*(int){ 0 }, (int){ &(>=0, int) }) }
min: (int){ 10 }
max: (_|_){
- // [eval] es3.max: 1 errors in empty disjunction:
+ // [eval] es3.max: 3 errors in empty disjunction:
+ // es3.max: conflicting values 1 and 5:
+ // ./in.cue:4:19
+ // ./in.cue:5:18
+ // ./in.cue:20:6
+ // ./in.cue:22:10
+ // es3.max: conflicting values 10 and 5:
+ // ./in.cue:5:18
+ // ./in.cue:20:6
+ // ./in.cue:21:10
+ // ./in.cue:22:10
// es3.max: invalid value 5 (out of bound >10):
// ./in.cue:5:10
// ./in.cue:22:10
@@ -123,13 +147,21 @@
er3: (_|_){
// [eval]
min: (_|_){
- // [eval] er3.min: 1 errors in empty disjunction:
+ // [eval] er3.min: 2 errors in empty disjunction:
+ // er3.min: conflicting values 1 and 5:
+ // ./in.cue:28:11
+ // ./in.cue:43:6
+ // ./in.cue:44:10
// er3.min: invalid value 5 (out of bound <5):
// ./in.cue:29:10
// ./in.cue:44:10
}
max: (_|_){
- // [eval] er3.min: 1 errors in empty disjunction:
+ // [eval] er3.min: 2 errors in empty disjunction:
+ // er3.min: conflicting values 1 and 5:
+ // ./in.cue:28:11
+ // ./in.cue:43:6
+ // ./in.cue:44:10
// er3.min: invalid value 5 (out of bound <5):
// ./in.cue:29:10
// ./in.cue:44:10
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index 924e72b..4a19c49 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -409,7 +409,6 @@
c1.a.c.c: structural cycle
d1.a.b.c.d.t: structural cycle
d1.r: structural cycle
-d2.a.b.c.d.t: structural cycle
e1.a.c: structural cycle
e1.b.c: structural cycle
e2.a.c: structural cycle
@@ -469,11 +468,9 @@
p5.#T.a.0.link: structural cycle
p6.#U.#T.a.0.link: structural cycle
z1.z.g.h: structural cycle
-b4.x.y.0: structural cycle:
+b4.x.y.0.0: structural cycle:
./in.cue:53:8
-d2.a.b.c.d.t: structural cycle:
- ./in.cue:254:8
-d2.r: structural cycle:
+d2.r.c.d.t: structural cycle:
./in.cue:254:8
0: structural cycle:
./in.cue:264:19
@@ -564,7 +561,7 @@
b4: (_|_){
// [structural cycle]
b: (_|_){
- // [structural cycle] b4.x.y.0: structural cycle:
+ // [structural cycle] b4.x.y.0.0: structural cycle:
// ./in.cue:53:8
0: (int){ 1 }
}
@@ -589,7 +586,9 @@
x: (struct){
y: (struct){
a: (#list){
- 0: (int){ int }
+ 0: ((int|list)){ |((#list){
+ 0: (int){ int }
+ }, (int){ int }) }
}
}
}
@@ -699,13 +698,19 @@
}
b11: (struct){
#list: (#struct){
- tail: (null){ null }
+ tail: ((null|struct)){ |(*(null){ null }, (#struct){
+ tail: (null){ null }
+ }) }
}
}
b12: (struct){
#list: (#struct){
value: (int){ int }
- tail: (null){ null }
+ tail: ((null|struct)){ |(*(null){ null }, (#struct){
+ value: (int){ int }
+ tail: (null){ null }
+ sum: (int){ int }
+ }) }
sum: (int){ int }
}
list1: (#struct){
@@ -1048,18 +1053,28 @@
d2: (_|_){
// [structural cycle]
x: (_|_){
- // [structural cycle] d2.a.b.c.d.t: structural cycle:
+ // [structural cycle] d2.r.c.d.t: structural cycle:
// ./in.cue:254:8
}
r: (_|_){
- // [structural cycle] d2.r: structural cycle:
- // ./in.cue:254:8
- c: (_|_){// {
- // d: {
- // h: int
- // t: 〈4;r〉
- // }
- // }
+ // [structural cycle]
+ c: (_|_){
+ // [structural cycle]
+ d: (_|_){
+ // [structural cycle]
+ h: (int){ int }
+ t: (_|_){
+ // [structural cycle] d2.r.c.d.t: structural cycle:
+ // ./in.cue:254:8
+ c: (_|_){// {
+ // d: {
+ // h: int
+ // t: 〈4;r〉
+ // }
+ // }
+ }
+ }
+ }
}
}
a: (_|_){
@@ -1072,14 +1087,7 @@
// [structural cycle]
h: (int){ int }
t: (_|_){
- // [structural cycle] d2.a.b.c.d.t: structural cycle
- c: (_|_){// {
- // d: {
- // h: int
- // t: 〈4;r〉
- // }
- // }
- }
+ // [structural cycle]
}
}
}
@@ -1104,7 +1112,7 @@
// [structural cycle] 0: structural cycle:
// ./in.cue:264:19
}
- i: (int){ 1 }
+ i: (int){ |(*(int){ 1 }, (int){ int }) }
}
x: (_|_){
// [structural cycle] 0: structural cycle:
diff --git a/cue/testdata/disjunctions/embed.txtar b/cue/testdata/disjunctions/embed.txtar
new file mode 100644
index 0000000..72de3c1
--- /dev/null
+++ b/cue/testdata/disjunctions/embed.txtar
@@ -0,0 +1,131 @@
+-- in.cue --
+// Given the existence of this field, embedDefault is a struct. This
+// means that embedding this conjunction into `embedDefault` itself should
+// make it resolve to `{a: 2}`.
+
+default: {
+ y: *1 | {a: 2}
+ y
+}
+
+unambiguous: {
+ y: 1 | {a: 2}
+ y
+}
+
+forDefault: {
+ y: *1 | {a: 2}
+ for x in [1] {y}
+}
+
+// Carry over default to first disjunct.
+openDefault: {
+ #y: *1 | {a: 2}
+ #y
+}
+
+openAmbiguous: {
+ #y: 1 | {a: 2}
+ #y
+}
+
+forceStruct: {
+ #y: 1 | {a: 2}
+ #y
+ {}
+}
+
+-- out/eval --
+(struct){
+ default: (struct){
+ y: ((int|struct)){ |(*(int){ 1 }, (struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }
+ unambiguous: (struct){
+ y: ((int|struct)){ |((int){ 1 }, (struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }
+ forDefault: (struct){
+ y: ((int|struct)){ |(*(int){ 1 }, (struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }
+ openDefault: ((int|struct)){ |(*(int){
+ 1
+ #y: ((int|struct)){ |(*(int){ 1 }, (#struct){
+ a: (int){ 2 }
+ }) }
+ }, (#struct){
+ #y: ((int|struct)){ |(*(int){ 1 }, (#struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }) }
+ openAmbiguous: ((int|struct)){ |((int){
+ 1
+ #y: ((int|struct)){ |((int){ 1 }, (#struct){
+ a: (int){ 2 }
+ }) }
+ }, (#struct){
+ #y: ((int|struct)){ |((int){ 1 }, (#struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }) }
+ forceStruct: (#struct){
+ #y: ((int|struct)){ |((int){ 1 }, (#struct){
+ a: (int){ 2 }
+ }) }
+ a: (int){ 2 }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ default: {
+ y: (*1|{
+ a: 2
+ })
+ 〈0;y〉
+ }
+ unambiguous: {
+ y: (1|{
+ a: 2
+ })
+ 〈0;y〉
+ }
+ forDefault: {
+ y: (*1|{
+ a: 2
+ })
+ for _, x in [
+ 1,
+ ] {
+ 〈2;y〉
+ }
+ }
+ openDefault: {
+ #y: (*1|{
+ a: 2
+ })
+ 〈0;#y〉
+ }
+ openAmbiguous: {
+ #y: (1|{
+ a: 2
+ })
+ 〈0;#y〉
+ }
+ forceStruct: {
+ #y: (1|{
+ a: 2
+ })
+ 〈0;#y〉
+ {}
+ }
+}
diff --git a/cue/testdata/disjunctions/errors.txtar b/cue/testdata/disjunctions/errors.txtar
index f4f9365..983c466 100644
--- a/cue/testdata/disjunctions/errors.txtar
+++ b/cue/testdata/disjunctions/errors.txtar
@@ -124,7 +124,7 @@
}
}
explicitDefaultError: (_|_){
- // [incomplete] explicitDefaultError.a: empty disjunction:
+ // [incomplete] explicitDefaultError: non-concrete value string in operand to !=:
// ./in.cue:30:8
a: (string){ string }
}
diff --git a/cue/testdata/disjunctions/specdeviation.txtar b/cue/testdata/disjunctions/specdeviation.txtar
index 180bf3b..4ebf0d4 100644
--- a/cue/testdata/disjunctions/specdeviation.txtar
+++ b/cue/testdata/disjunctions/specdeviation.txtar
@@ -40,12 +40,12 @@
p: (int){ |(*(int){ 2 }, (int){ int }) }
s1: (#struct){
max: (number){ |(*(int){ 5 }, (number){ >5 }) }
- res: (int){ 0 }
+ res: (int){ |(*(int){ 0 }, (int){ &(>=0, int) }) }
min: (int){ 5 }
}
#Size: (#struct){
max: (number){ |(*(int){ 1 }, (number){ >0 }, (number){ >1 }) }
- res: (int){ 0 }
+ res: (int){ |(*(int){ 0 }, (int){ &(>=0, int) }) }
min: (number){ |(*(int){ 1 }, (number){ >0 }) }
}
}
diff --git a/cue/testdata/eval/disjunctions.txtar b/cue/testdata/eval/disjunctions.txtar
index 45a4c19..fa6adb6 100644
--- a/cue/testdata/eval/disjunctions.txtar
+++ b/cue/testdata/eval/disjunctions.txtar
@@ -51,6 +51,50 @@
b: {} & (int | {c: 1})
}
+t10: {
+ schema: test
+ schema: string | {name: string}
+ #A: {string | {name: string}}
+
+ test: name: "Test"
+ test: #A
+}
+
+t10: {
+ schema: string | {name: string}
+ schema: test
+ #A: {string | {name: string}}
+
+ test: name: "Test"
+ test: #A
+}
+
+t10: {
+ #A: {string | {name: string}}
+
+ test: name: "Test"
+ test: #A
+
+ schema: string | {name: string}
+ schema: test
+}
+
+t11: {
+ a: #A
+ a: b
+
+ b: #A & ["b"]
+ #A: ["a" | "b"] | {}
+}
+
+t11: {
+ b: #A & ["b"]
+ #A: ["a" | "b"] | {}
+
+ a: b
+ a: #A
+}
+
d100: {
// Should we allow a selector to imply a struct or list? Would be convenient.
@@ -167,13 +211,36 @@
c: (int){ 1 }
}
}
+ t10: (struct){
+ schema: (#struct){
+ name: (string){ "Test" }
+ }
+ #A: ((string|struct)){ |((string){ string }, (#struct){
+ name: (string){ string }
+ }) }
+ test: (#struct){
+ name: (string){ "Test" }
+ }
+ }
+ t11: (struct){
+ a: (#list){
+ 0: (string){ "b" }
+ }
+ b: (#list){
+ 0: (string){ "b" }
+ }
+ #A: ((list|struct)){ |((#list){
+ 0: (string){ |((string){ "a" }, (string){ "b" }) }
+ }, (#struct){
+ }) }
+ }
d100: (struct){
i: ((null|struct)){ |((null){ null }, (struct){
bar: (int){ 2 }
}) }
j: (_|_){
// [incomplete] d100.j: unresolved disjunction null | {bar:2} (type (null|struct)):
- // ./in.cue:58:6
+ // ./in.cue:102:6
}
}
}
@@ -261,6 +328,71 @@
c: 1
}))
}
+ t10: {
+ schema: 〈0;test〉
+ schema: (string|{
+ name: string
+ })
+ #A: {
+ (string|{
+ name: string
+ })
+ }
+ test: {
+ name: "Test"
+ }
+ test: 〈0;#A〉
+ }
+ t10: {
+ schema: (string|{
+ name: string
+ })
+ schema: 〈0;test〉
+ #A: {
+ (string|{
+ name: string
+ })
+ }
+ test: {
+ name: "Test"
+ }
+ test: 〈0;#A〉
+ }
+ t10: {
+ #A: {
+ (string|{
+ name: string
+ })
+ }
+ test: {
+ name: "Test"
+ }
+ test: 〈0;#A〉
+ schema: (string|{
+ name: string
+ })
+ schema: 〈0;test〉
+ }
+ t11: {
+ a: 〈0;#A〉
+ a: 〈0;b〉
+ b: (〈0;#A〉 & [
+ "b",
+ ])
+ #A: ([
+ ("a"|"b"),
+ ]|{})
+ }
+ t11: {
+ b: (〈0;#A〉 & [
+ "b",
+ ])
+ #A: ([
+ ("a"|"b"),
+ ]|{})
+ a: 〈0;b〉
+ a: 〈0;#A〉
+ }
d100: {
i: (null|{
bar: 2
diff --git a/cue/testdata/fulleval/000_detect_conflicting_value.txtar b/cue/testdata/fulleval/000_detect_conflicting_value.txtar
index a4a9787..d79a7e3 100644
--- a/cue/testdata/fulleval/000_detect_conflicting_value.txtar
+++ b/cue/testdata/fulleval/000_detect_conflicting_value.txtar
@@ -1,5 +1,3 @@
-# DO NOT EDIT; generated by go run testdata/gen.go
-#
#name: detect conflicting value
#evalFull
-- in.cue --
@@ -16,8 +14,25 @@
a: (7080|int)
}
-- out/eval --
-(struct){
+Errors:
+a: 2 errors in empty disjunction:
+a: conflicting values 8000.9 and 7080 (mismatched types float and int):
+ ./in.cue:1:4
+ ./in.cue:2:4
+a: conflicting values 8000.9 and int (mismatched types float and int):
+ ./in.cue:1:4
+ ./in.cue:2:11
+
+Result:
+(_|_){
+ // [eval]
a: (_|_){
- // [incomplete] a: empty disjunction
+ // [eval] a: 2 errors in empty disjunction:
+ // a: conflicting values 8000.9 and 7080 (mismatched types float and int):
+ // ./in.cue:1:4
+ // ./in.cue:2:4
+ // a: conflicting values 8000.9 and int (mismatched types float and int):
+ // ./in.cue:1:4
+ // ./in.cue:2:11
}
}
diff --git a/cue/testdata/fulleval/041.txtar b/cue/testdata/fulleval/041.txtar
index 35b4e1c..062e156 100644
--- a/cue/testdata/fulleval/041.txtar
+++ b/cue/testdata/fulleval/041.txtar
@@ -37,7 +37,7 @@
-- out/eval --
(struct){
t: (struct){
- #ok: (bool){ true }
+ #ok: (bool){ |(*(bool){ true }, (bool){ bool }) }
x: (int){ int }
}
s: (struct){
diff --git a/cue/testdata/fulleval/054_issue312.txtar b/cue/testdata/fulleval/054_issue312.txtar
index 20833cf..5269842 100644
--- a/cue/testdata/fulleval/054_issue312.txtar
+++ b/cue/testdata/fulleval/054_issue312.txtar
@@ -1,5 +1,3 @@
-# DO NOT EDIT; generated by go run testdata/gen.go
-#
#name: issue312
#evalFull
-- in.cue --
@@ -37,4 +35,5 @@
y: ((int|struct)){ |(*(int){ 1 }, (struct){
a: (int){ 2 }
}) }
+ a: (int){ 2 }
}
diff --git a/cue/testdata/resolve/048_builtins.txtar b/cue/testdata/resolve/048_builtins.txtar
index 4a24b24..cc7e94b 100644
--- a/cue/testdata/resolve/048_builtins.txtar
+++ b/cue/testdata/resolve/048_builtins.txtar
@@ -46,6 +46,17 @@
<0>{a1: <1>{a: (=~"oo" & =~"fo"), b: =~"oo", c: =~"fo"}, a2: <2>{a: "foo", b: =~"oo", c: =~"fo"}, a3: <3>{a: _|_((=~"oo" & "bar"):invalid value "bar" (does not match =~"oo")), b: =~"oo", c: =~"fo"}, o1: <4>{a: string, b: string, c: "bar"}, o2: <5>{a: "foo", b: string, c: "bar"}, o3: <6>{a: _|_(("baz" & "foo"):empty disjunction: conflicting values "baz" and "foo";("bar" & "foo"):empty disjunction: conflicting values "bar" and "foo"), b: "baz", c: "bar"}}
-- out/eval --
Errors:
+o3.a: 2 errors in empty disjunction:
+o3.a: conflicting values "bar" and "foo":
+ ./in.cue:10:12
+ ./in.cue:12:5
+ ./in.cue:15:5
+ ./in.cue:15:14
+o3.a: conflicting values "baz" and "foo":
+ ./in.cue:10:9
+ ./in.cue:15:5
+ ./in.cue:15:14
+ ./in.cue:15:24
a3.a: invalid value "bar" (out of bound =~"oo"):
./in.cue:3:5
a3.a: invalid value "bar" (out of bound =~"fo"):
@@ -85,9 +96,20 @@
b: (string){ string }
c: (string){ "bar" }
}
- o3: (struct){
+ o3: (_|_){
+ // [eval]
a: (_|_){
- // [incomplete] o3.a: empty disjunction
+ // [eval] o3.a: 2 errors in empty disjunction:
+ // o3.a: conflicting values "bar" and "foo":
+ // ./in.cue:10:12
+ // ./in.cue:12:5
+ // ./in.cue:15:5
+ // ./in.cue:15:14
+ // o3.a: conflicting values "baz" and "foo":
+ // ./in.cue:10:9
+ // ./in.cue:15:5
+ // ./in.cue:15:14
+ // ./in.cue:15:24
}
b: (string){ "baz" }
c: (string){ "bar" }
diff --git a/internal/core/eval/disjunct.go b/internal/core/eval/disjunct.go
index b02d425..b2110aa 100644
--- a/internal/core/eval/disjunct.go
+++ b/internal/core/eval/disjunct.go
@@ -98,7 +98,7 @@
}
func (n *nodeContext) addDisjunction(env *adt.Environment, x *adt.DisjunctionExpr, cloneID adt.CloseInfo) {
- a := []disjunct{}
+ a := make([]disjunct, 0, len(x.Values))
numDefaults := 0
for _, v := range x.Values {
@@ -118,7 +118,7 @@
}
func (n *nodeContext) addDisjunctionValue(env *adt.Environment, x *adt.Disjunction, cloneID adt.CloseInfo) {
- a := []disjunct{}
+ a := make([]disjunct, 0, len(x.Values))
for i, v := range x.Values {
a = append(a, disjunct{v, i < x.NumDefaults})
@@ -128,182 +128,183 @@
envDisjunct{env, a, x.NumDefaults, cloneID})
}
-func (n *nodeContext) updateResult(state adt.VertexStatus) {
- n.postDisjunct(state)
+func (n *nodeContext) expandDisjuncts(
+ state adt.VertexStatus,
+ parent *nodeContext,
+ m defaultMode,
+ recursive bool) {
- if n.hasErr() {
- x := n.node
- err, ok := x.BaseValue.(*adt.Bottom)
- if !ok {
- err = n.getErr()
- }
- if err == nil {
- // TODO(disjuncts): Is this always correct? Especially for partial
- // evaluation it is okay for child errors to have incomplete errors.
- // Perhaps introduce an Err() method.
- err = x.ChildErrors
- }
- if err != nil {
- n.disjunctErrs = append(n.disjunctErrs, err)
- }
- return
+ n.eval.stats.DisjunctCount++
+
+ for n.expandOne() {
}
- n.touched = true
- d := &n.nodeShared.disjunct
+ // save node to snapShot in nodeContex
+ // save nodeContext.
- result := *n.node
- if result.BaseValue == nil {
- result.BaseValue = n.getValidators()
- }
-
- for _, v := range d.Values {
- if adt.Equal(n.ctx, v, &result) {
- return
- }
- }
-
- p := &result
- d.Values = append(d.Values, p)
-
- if n.done() {
- n.nodeShared.isDone = true
- }
-
- if n.defaultMode == isDefault {
- // Keep defaults sorted first.
- i := d.NumDefaults
- j := i + 1
- copy(d.Values[j:], d.Values[i:])
- d.Values[i] = p
- d.NumDefaults = j
+ if recursive || len(n.disjunctions) > 0 {
+ n.snapshot = snapshotVertex(*n.node)
+ } else {
+ n.snapshot = *n.node
}
switch {
- case !n.nodeShared.hasResult():
-
- case n.nodeShared.isDefault() && n.defaultMode != isDefault:
- return
-
- case !n.nodeShared.isDefault() && n.defaultMode == isDefault:
-
- default:
- return // n.defaultMode == isDefault
- }
-
- n.nodeShared.setResult(n.node)
-
- return
-}
-
-func (n *nodeContext) processDisjuncts(state adt.VertexStatus) {
- n.processDisjunct(state, 0, len(n.disjunctions))
-
- if n.nodeShared.hasResult() {
- return // found something
- }
-
- if len(n.disjunctions) > 0 {
- code := adt.IncompleteError
-
- if len(n.disjunctErrs) > 0 {
- code = adt.EvalError
- for _, c := range n.disjunctErrs {
- if c.Code > code {
- code = c.Code
- }
- }
- }
-
- b := &adt.Bottom{
- Code: code,
- Err: n.disjunctError(),
- }
- n.node.SetValue(n.ctx, adt.Finalized, b)
- }
-}
-
-// TODO: move state to nodeShared.
-func (n *nodeContext) processDisjunct(state adt.VertexStatus, k, sub int) {
- isSub := false
- var d envDisjunct
- switch {
- case sub < len(n.disjunctions):
- d = n.disjunctions[sub]
- sub++
- isSub = true
-
- case k < len(n.disjunctions):
- d = n.disjunctions[k]
- k++
-
- default:
- n.updateResult(state)
- return
- }
-
- // save current state of node and nodeContext
- nSaved := snapshotVertex(n.node)
- saved := *n
-
- for i, v := range d.values {
- n.eval.stats.DisjunctCount++
-
- if i > 0 {
- *n = saved
- *(n.node) = nSaved
- // restore state
- }
-
- // TODO: HACK ALERT: we ignore the default tags of the subexpression
- // if we already have a scalar value and can no longer change the
- // outcome.
- // This is not conform the spec, but mimics the old implementation.
- // It also results in nicer default semantics. Changing this will
- // break existing CUE code in awkward ways.
- // We probably should address this when we figure out how to change
- // the spec to accommodate for this. For instance, we could say
- // that if a disjunction only contributes a single disjunct to an
- // end result, default information is ignored. Not the greatest
- // definition, though.
- // Another alternative might be to have a special builtin that
- // mimics the good behavior.
- // Note that the same result can be obtained in CUE by adding
- // 0 to a referenced number (forces the default to be discarded).
- wasScalar := n.scalar != nil // Hack line 1
-
- c := adt.MakeConjunct(d.env, v.expr, d.cloneID)
- n.addExprConjunct(c)
-
- for n.expandOne() {
- }
+ default: // len(n.disjunctions) == 0
+ m := *n
+ n.postDisjunct(state)
if n.hasErr() {
- continue
- }
-
- var mode defaultMode
- switch {
- case d.numDefaults == 0:
- mode = maybeDefault
- case v.isDefault:
- mode = isDefault
- default:
- mode = notDefault
- }
-
- if isSub {
- if !wasScalar { // Hack line 2.
- n.subMode = combineDefault(n.subMode, mode)
+ x := n.node
+ err, ok := x.BaseValue.(*adt.Bottom)
+ if !ok {
+ err = n.getErr()
}
- } else if sub == len(n.disjunctions) {
- n.defaultMode = combineDefault(n.defaultMode, n.subMode)
- n.defaultMode = combineDefault(n.defaultMode, mode)
- n.subMode = maybeDefault
+ if err == nil {
+ // TODO(disjuncts): Is this always correct? Especially for partial
+ // evaluation it is okay for child errors to have incomplete errors.
+ // Perhaps introduce an Err() method.
+ err = x.ChildErrors
+ }
+ if err != nil {
+ n.disjunctErrs = append(n.disjunctErrs, err)
+ }
+ if recursive || len(n.disjunctions) > 0 {
+ n.eval.freeNodeContext(n)
+ }
+ return
+ }
+ // TODO: clean up this mess:
+ n.touched = true // move result setting here
+ result := *n.node // XXX: n.result = snapshotVertex(n.node)?
+
+ if result.BaseValue == nil {
+ result.BaseValue = n.getValidators()
}
- n.processDisjunct(state, k, sub)
+ n.nodeShared.setResult(n.node)
+ if n.node.BaseValue == nil {
+ n.node.BaseValue = result.BaseValue
+ }
+ if state < adt.Finalized {
+ *n = m
+ }
+ n.result = result
+ n.disjuncts = append(n.disjuncts, n)
+
+ case len(n.disjunctions) > 0:
+
+ n.disjuncts = append(n.disjuncts, n)
+
+ for i, d := range n.disjunctions {
+ a := n.disjuncts
+ n.disjuncts = n.buffer[:0]
+ n.buffer = a[:0]
+
+ state := state
+ if i+1 < len(n.disjunctions) {
+ // If this is not the last disjunction, set it to
+ // partial evaluation. This will disable the closedness
+ // check and any other non-monotonic check that should
+ // not be done unless there is complete information.
+ state = adt.Partial
+ }
+
+ for _, dn := range a {
+ for _, v := range d.values {
+ cn := dn.clone()
+ *cn.node = snapshotVertex(dn.snapshot)
+
+ c := adt.MakeConjunct(d.env, v.expr, d.cloneID)
+ cn.addExprConjunct(c)
+
+ newMode := mode(d, v)
+
+ cn.expandDisjuncts(state, n, newMode, true)
+
+ cn.defaultMode = combineDefault(dn.defaultMode, newMode)
+ }
+ }
+
+ if i > 0 {
+ for _, d := range a {
+ n.eval.freeNodeContext(d)
+ }
+ }
+
+ if len(n.disjuncts) == 0 {
+ n.makeError()
+ }
+ }
+
+ // HACK alert: this replaces the hack of the previous algorithm with a
+ // slightly less worse hack: instead of dropping the default info when
+ // the value was scalar before, we drop this information when there
+ // is only one disjunct, while not discarding hard defaults.
+ // TODO: a more principled approach would be to recognize that there
+ // is only one default at a point where this does not break
+ // commutativity.
+ if len(n.disjuncts) == 1 && n.disjuncts[0].defaultMode != isDefault {
+ n.disjuncts[0].defaultMode = maybeDefault
+ }
}
+
+ // Compare to root, but add to this one.
+ // TODO: if only one value is left, set to maybeDefault.
+ switch p := parent; {
+ case p != nil:
+ k := 0
+ outer:
+ for _, d := range n.disjuncts {
+ for _, v := range p.disjuncts {
+ if adt.Equal(n.ctx, &v.result, &d.result) {
+ n.eval.freeNodeContext(n)
+ continue outer
+ }
+ }
+ n.disjuncts[k] = d
+ k++
+
+ d.defaultMode = combineDefault(m, d.defaultMode)
+ }
+
+ p.disjuncts = append(p.disjuncts, n.disjuncts[:k]...)
+ n.disjuncts = n.disjuncts[:0]
+
+ case n.done():
+ n.nodeShared.isDone = true
+ }
+}
+
+func (n *nodeShared) makeError() {
+ code := adt.IncompleteError
+
+ if len(n.disjunctErrs) > 0 {
+ code = adt.EvalError
+ for _, c := range n.disjunctErrs {
+ if c.Code > code {
+ code = c.Code
+ }
+ }
+ }
+
+ b := &adt.Bottom{
+ Code: code,
+ Err: n.disjunctError(),
+ }
+ n.node.SetValue(n.ctx, adt.Finalized, b)
+}
+
+func mode(d envDisjunct, v disjunct) defaultMode {
+ var mode defaultMode
+ switch {
+ case d.numDefaults == 0:
+ mode = maybeDefault
+ case v.isDefault:
+ mode = isDefault
+ default:
+ mode = notDefault
+ }
+ return mode
}
// Clone makes a shallow copy of a Vertex. The purpose is to create different
@@ -316,28 +317,26 @@
// longer needed and can become nil. All other fields can be copied shallowly.
//
// USE TO SAVE NODE BRANCH FOR DISJUNCTION, BUT BEFORE POSTDIJSUNCT.
-func snapshotVertex(v *adt.Vertex) adt.Vertex {
- c := *v
-
- if len(v.Arcs) > 0 {
- c.Arcs = make([]*adt.Vertex, len(v.Arcs))
- for i, arc := range v.Arcs {
+func snapshotVertex(v adt.Vertex) adt.Vertex {
+ if a := v.Arcs; len(a) > 0 {
+ v.Arcs = make([]*adt.Vertex, len(a))
+ for i, arc := range a {
// For child arcs, only Conjuncts are set and Arcs and
// Structs will be nil.
a := *arc
- c.Arcs[i] = &a
+ v.Arcs[i] = &a
a.Conjuncts = make([]adt.Conjunct, len(arc.Conjuncts))
copy(a.Conjuncts, arc.Conjuncts)
}
}
- if len(v.Structs) > 0 {
- c.Structs = make([]*adt.StructInfo, len(v.Structs))
- copy(c.Structs, v.Structs)
+ if a := v.Structs; len(a) > 0 {
+ v.Structs = make([]*adt.StructInfo, len(a))
+ copy(v.Structs, a)
}
- return c
+ return v
}
// Default rules from spec:
@@ -403,13 +402,13 @@
//
// TODO(perf): the set of errors is now computed during evaluation. Eventually,
// this could be done lazily.
-func (n *nodeContext) disjunctError() (errs errors.Error) {
+func (n *nodeShared) disjunctError() (errs errors.Error) {
ctx := n.ctx
disjuncts := selectErrors(n.disjunctErrs)
if disjuncts == nil {
- errs = ctx.Newf("empty disjunction")
+ errs = ctx.Newf("empty disjunction") // XXX: add space to sort first
} else {
disjuncts = errors.Sanitize(disjuncts)
k := len(errors.Errors(disjuncts))
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 74ce4d5..d5869f1 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -163,7 +163,7 @@
if s.result_.BaseValue != nil { // There is a complete result.
*v = s.result_
- result = *v
+ // result = *v
} else if b, ok := v.BaseValue.(*adt.Bottom); ok {
*v = save
return b
@@ -174,7 +174,7 @@
switch {
case !s.touched:
- case len(s.disjunct.Values) == 1 || s.disjunct.NumDefaults == 1:
+ case len(s.disjuncts) == 1 || s.numDefaults == 1:
// TODO: this seems unnecessary as long as we have a better way
// to handle incomplete, and perhaps referenced. nodes.
if c.IsTentative() && isStruct(v) {
@@ -235,7 +235,8 @@
return b
}
// TODO: Only use result when not a cycle.
- v = s.result()
+ w := s.result()
+ v = &w
}
// TODO: Store if concrete and fully resolved.
}
@@ -245,6 +246,9 @@
// gets the concrete value.
//
if v.BaseValue == nil {
+ if result.BaseValue == nil {
+ panic("nil basevalue")
+ }
return &result
}
return v
@@ -275,10 +279,10 @@
defer e.freeSharedNode(n)
switch {
- case len(n.disjunct.Values) == 1:
- *v = *(n.disjunct.Values[0])
+ case len(n.disjuncts) == 1:
+ *v = n.disjuncts[0].result
- case len(n.disjunct.Values) > 0:
+ case len(n.disjuncts) > 0:
d := n.createDisjunct()
v.BaseValue = d
// The conjuncts will have too much information. Better have no
@@ -307,7 +311,7 @@
default:
if r := n.result(); r.BaseValue != nil {
- *v = *r
+ *v = r
}
}
@@ -337,7 +341,7 @@
}
}
- if !v.Label.IsInt() && v.Parent != nil && !ignore {
+ if !v.Label.IsInt() && v.Parent != nil && !ignore && state == adt.Finalized {
// Visit arcs recursively to validate and compute error.
if _, err := verifyArc(c, v.Label, v, v.Closed); err != nil {
// Record error in child node to allow recording multiple
@@ -392,6 +396,7 @@
e.freeNodeContext(n)
return shared
}
+
if !n.done() && len(n.disjunctions) > 0 && isEvaluating(v) {
// We disallow entering computations of disjunctions with
// incomplete data.
@@ -403,15 +408,13 @@
return shared
}
- n.processDisjuncts(state)
-
- // Handle disjunctions. If there are no disjunctions, this call is
- // equivalent to calling n.postDisjunct.
+ n.expandDisjuncts(state, nil, maybeDefault, false)
+ // TODO: reorganize nodeShared and nodeContext
+ n.nodeShared.disjuncts = append(n.nodeShared.disjuncts, n.disjuncts...)
if v.BaseValue == nil {
v.BaseValue = n.getValidators()
}
-
- e.freeNodeContext(n)
+ // e.freeNodeContext(n) freed in freeSharedNode
return shared
}
@@ -589,7 +592,7 @@
if state == adt.Finalized {
n.node.UpdateStatus(adt.EvaluatingArcs)
}
- n.eval.Unify(ctx, a, adt.Finalized)
+ n.eval.Unify(ctx, a, state)
if err, _ := a.BaseValue.(*adt.Bottom); err != nil {
n.node.AddChildError(err)
}
@@ -625,8 +628,9 @@
// Disjunction handling
touched bool
- disjunct adt.Disjunction
+ disjuncts []*nodeContext
disjunctErrs []*adt.Bottom
+ numDefaults int
result_ adt.Vertex
isDone bool
@@ -642,7 +646,7 @@
ctx: ctx,
node: node,
- disjunct: adt.Disjunction{Values: n.disjunct.Values[:0]},
+ disjuncts: n.disjuncts[:0],
disjunctErrs: n.disjunctErrs[:0],
}
@@ -661,20 +665,33 @@
e.stats.Freed++
n.nextFree = e.freeListShared
e.freeListShared = n
-}
-func (n *nodeShared) createDisjunct() *adt.Disjunction {
- a := make([]*adt.Vertex, len(n.disjunct.Values))
- copy(a, n.disjunct.Values)
- return &adt.Disjunction{
- Values: a,
- NumDefaults: n.disjunct.NumDefaults,
+ for _, d := range n.disjuncts {
+ e.freeNodeContext(d)
}
}
-func (n *nodeShared) result() *adt.Vertex {
- x := n.result_
- return &x
+func (n *nodeShared) createDisjunct() *adt.Disjunction {
+ a := make([]*adt.Vertex, len(n.disjuncts))
+ p := 0
+ for i, x := range n.disjuncts {
+ v := x.result
+ if x.defaultMode == isDefault {
+ a[i] = a[p]
+ a[p] = &v
+ p++
+ } else {
+ a[i] = &v
+ }
+ }
+ return &adt.Disjunction{
+ Values: a,
+ NumDefaults: p,
+ }
+}
+
+func (n *nodeShared) result() adt.Vertex {
+ return n.result_
}
func (n *nodeShared) setResult(v *adt.Vertex) {
@@ -682,17 +699,13 @@
}
func (n *nodeShared) hasResult() bool {
- return len(n.disjunct.Values) > 1
+ return len(n.disjuncts) > 0
}
func (n *nodeShared) done() bool {
return n.isDone
}
-func (n *nodeShared) isDefault() bool {
- return n.disjunct.NumDefaults > 0
-}
-
type arcKey struct {
arc *adt.Vertex
id adt.CloseInfo
@@ -707,7 +720,7 @@
*nodeShared
- // TODO:
+ // TODO: (this is CL is first step)
// filter *adt.Vertex a subset of composite with concrete fields for
// bloom-like filtering of disjuncts. We should first verify, however,
// whether some breath-first search gives sufficient performance, as this
@@ -716,6 +729,13 @@
arcMap []arcKey
+ // snapshot holds the last value of the vertex before calling postDisjunct.
+ snapshot adt.Vertex
+
+ // Result holds the last evaluated value of the vertex after calling
+ // postDisjunct.
+ result adt.Vertex
+
// Current value (may be under construction)
scalar adt.Value // TODO: use Value in node.
scalarID adt.CloseInfo
@@ -728,7 +748,6 @@
upperBound *adt.BoundValue // < or <=
checks []adt.Validator // BuiltinValidator, other bound values.
errs *adt.Bottom
- incomplete *adt.Bottom
// Struct information
dynamicFields []envDynamic
@@ -736,20 +755,53 @@
forClauses []envYield
aStruct adt.Expr
aStructID adt.CloseInfo
- hasTop bool
// Expression conjuncts
lists []envList
vLists []*adt.Vertex
exprs []envExpr
+ hasTop bool
hasCycle bool // has conjunct with structural cycle
hasNonCycle bool // has conjunct without structural cycle
// Disjunction handling
disjunctions []envDisjunct
defaultMode defaultMode
- subMode defaultMode
+ disjuncts []*nodeContext
+ buffer []*nodeContext
+}
+
+func (n *nodeContext) clone() *nodeContext {
+ d := n.eval.newNodeContext(n.nodeShared)
+
+ d.scalar = n.scalar
+ d.scalarID = n.scalarID
+ d.kind = n.kind
+ d.kindExpr = n.kindExpr
+ d.kindID = n.kindID
+ d.aStruct = n.aStruct
+ d.aStructID = n.aStructID
+ d.hasTop = n.hasTop
+
+ d.lowerBound = n.lowerBound
+ d.upperBound = n.upperBound
+ d.errs = n.errs
+ d.hasTop = n.hasTop
+ d.hasCycle = n.hasCycle
+ d.hasNonCycle = n.hasNonCycle
+
+ // d.arcMap = append(d.arcMap, n.arcMap...) // XXX add?
+ d.checks = append(d.checks, n.checks...)
+ d.dynamicFields = append(d.dynamicFields, n.dynamicFields...)
+ d.ifClauses = append(d.ifClauses, n.ifClauses...)
+ d.forClauses = append(d.forClauses, n.forClauses...)
+ d.lists = append(d.lists, n.lists...)
+ d.vLists = append(d.vLists, n.vLists...)
+ d.exprs = append(d.exprs, n.exprs...)
+ // No need to clone d.disjunctions
+
+ return d
}
func (e *Evaluator) newNodeContext(shared *nodeShared) *nodeContext {
@@ -769,6 +821,8 @@
vLists: n.vLists[:0],
exprs: n.exprs[:0],
disjunctions: n.disjunctions[:0],
+ disjuncts: n.disjuncts[:0],
+ buffer: n.buffer[:0],
}
return n
@@ -922,7 +976,7 @@
}
func (n *nodeContext) maybeSetCache() {
- if n.node.Status() > adt.Evaluating { // n.node.Value != nil
+ if n.node.Status() > adt.Evaluating { // n.node.BaseValue != nil
return
}
if n.scalar != nil {
@@ -1300,7 +1354,7 @@
// TODO: evaluate value?
switch v := x.BaseValue.(type) {
default:
- panic("invalid value")
+ panic(fmt.Sprintf("invalid type %T", x.BaseValue))
case *adt.ListMarker:
n.vLists = append(n.vLists, x)
diff --git a/pkg/path/testdata/error.txtar b/pkg/path/testdata/error.txtar
index f53048c..023994a 100644
--- a/pkg/path/testdata/error.txtar
+++ b/pkg/path/testdata/error.txtar
@@ -19,10 +19,12 @@
joinErr: path.Join(["a", "b"], "foo")
-- out/path --
Errors:
+cannot use _|_(2 errors in empty disjunction: (and 2 more errors)) (type _|_) as string in argument 1 to path.Join: 2 errors in empty disjunction: (and 2 more errors):
+ ./in.cue:4:10
joinErr: cannot use "foo" as *"unix" | "windows" | "plan9" | "aix" | "android" | "darwin" | "dragonfly" | "freebsd" | "hurd" | "illumos" | "ios" | "js" | "linux" | "nacl" | "netbsd" | "openbsd" | "solaris" | "zos" in argument 2 to path.Join:
./in.cue:4:32
Result:
joinOK: "a/b"
-joinErr: _|_ // joinErr: cannot use "foo" as *"unix" | "windows" | "plan9" | "aix" | "android" | "darwin" | "dragonfly" | "freebsd" | "hurd" | "illumos" | "ios" | "js" | "linux" | "nacl" | "netbsd" | "openbsd" | "solaris" | "zos" in argument 2 to path.Join (and 1 more errors)
+joinErr: _|_ // joinErr: cannot use "foo" as *"unix" | "windows" | "plan9" | "aix" | "android" | "darwin" | "dragonfly" | "freebsd" | "hurd" | "illumos" | "ios" | "js" | "linux" | "nacl" | "netbsd" | "openbsd" | "solaris" | "zos" in argument 2 to path.Join (and 3 more errors)