internal/core/compile: make `>10 <20` a compile error
Or in general, fail on any expression that must be concrete,
but can never evaluate to a concrete value.
Although technically correct CUE, it can never resolve to
a valid value and allowing it gives too much confusion to
the user.
At evaluation time expressions of the form `int + 1` were
already flagged. Now it also flags `>a + 1`, as `>a` will
never be concrete, even if `a` resolves fine.
Note that `(>10 & <a) + 1` is fine, though, as the bounds
can still resolve to concrete value when unified.
Making it a compile error, instead of a evaluation error,
ensures that this error doesn't go unnoticed and give
unexpected results.
Fixes #742
Fixes #405
Change-Id: Ib2032778ca5dcb6ef1681b4e03498ff039d37c7f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8665
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cue/testdata/eval/incomplete.txtar b/cue/testdata/eval/incomplete.txtar
index 5d8e170..c075991 100644
--- a/cue/testdata/eval/incomplete.txtar
+++ b/cue/testdata/eval/incomplete.txtar
@@ -1,5 +1,3 @@
-These should all be incomplete errors.
-
-- in.cue --
s: string
@@ -17,23 +15,11 @@
c: a+b & >=5
}
-// Issue #129
-permanentlyIncompleteOperands: {
- a: string + ":" + string
- a: "golang/go:1.13.5"
-}
-
-issue680: (>10 * 2) & 0
-
+a: int
+okay: (>10 & <a) + 3
-- out/eval --
-Errors:
-permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
- ./in.cue:19:8
-
-Result:
-(_|_){
- // [eval]
+(struct){
s: (string){ string }
e1: (_|_){
// [incomplete] e1: non-concrete value string in operand to +:
@@ -65,16 +51,10 @@
// ./in.cue:13:6
}
}
- permanentlyIncompleteOperands: (_|_){
- // [eval]
- a: (_|_){
- // [eval] permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
- // ./in.cue:19:8
- }
- }
- issue680: (_|_){
- // [incomplete] issue680: non-concrete value >10 in operand to *:
- // ./in.cue:23:12
+ a: (int){ int }
+ okay: (_|_){
+ // [incomplete] okay: non-concrete value >10 & int in operand to +:
+ // ./in.cue:18:7
}
}
-- out/compile --
@@ -92,9 +72,6 @@
b: (〈0;c〉 - 〈0;a〉)
c: ((〈0;a〉 + 〈0;b〉) & >=5)
}
- permanentlyIncompleteOperands: {
- a: ((string + ":") + string)
- a: "golang/go:1.13.5"
- }
- issue680: ((>10 * 2) & 0)
+ a: int
+ okay: ((>10 & <〈0;a〉) + 3)
}
diff --git a/cue/testdata/eval/incompleteperm.txtar b/cue/testdata/eval/incompleteperm.txtar
new file mode 100644
index 0000000..d3ba612
--- /dev/null
+++ b/cue/testdata/eval/incompleteperm.txtar
@@ -0,0 +1,60 @@
+// Issue #742
+// Issue #405
+
+-- in.cue --
+// Issue #129
+permanentlyIncompleteOperands: {
+ a: string + ":" + string
+ a: "golang/go:1.13.5"
+}
+
+permanentlyIncompleteOperandsNested: {
+ a: (int + 1) + (int + 1)
+}
+
+permanentlyIncompleteOperandsDisjunct: {
+ a: (int + 1) | (int + 1)
+}
+
+
+issue680: (>10 * 2) & 0
+
+issue405: >=100 <=200
+
+-- out/eval --
+permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
+ ./in.cue:3:8
+permanentlyIncompleteOperandsNested.a: invalid operand int ('+' requires concrete value):
+ ./in.cue:8:9
+permanentlyIncompleteOperandsDisjunct.a: invalid operand int ('+' requires concrete value):
+ ./in.cue:12:9
+issue680: invalid operand >10 ('*' requires concrete value):
+ ./in.cue:16:12
+issue405: invalid operand >=100 ('<=' requires concrete value):
+ ./in.cue:18:11
+-- out/compile --
+permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
+ ./in.cue:3:8
+permanentlyIncompleteOperandsNested.a: invalid operand int ('+' requires concrete value):
+ ./in.cue:8:9
+permanentlyIncompleteOperandsDisjunct.a: invalid operand int ('+' requires concrete value):
+ ./in.cue:12:9
+issue680: invalid operand >10 ('*' requires concrete value):
+ ./in.cue:16:12
+issue405: invalid operand >=100 ('<=' requires concrete value):
+ ./in.cue:18:11
+--- in.cue
+{
+ permanentlyIncompleteOperands: {
+ a: ((string + ":") + string)
+ a: "golang/go:1.13.5"
+ }
+ permanentlyIncompleteOperandsNested: {
+ a: ((int + 1) + (int + 1))
+ }
+ permanentlyIncompleteOperandsDisjunct: {
+ a: ((int + 1)|(int + 1))
+ }
+ issue680: ((>10 * 2) & 0)
+ issue405: (>=100 <= 200)
+}
diff --git a/cue/testdata/export/009.txtar b/cue/testdata/export/009.txtar
index 7ab8e78..78510bb 100644
--- a/cue/testdata/export/009.txtar
+++ b/cue/testdata/export/009.txtar
@@ -4,12 +4,6 @@
{
a: 5 * [int]
a: [1, 2, ...]
- b: <=5 * [int]
- b: [1, 2, ...]
- c: (>=3 & <=5) * [int]
- c: [1, 2, ...]
- d: >=2 * [int]
- d: [1, 2, ...]
e: [...int]
e: [1, 2, ...]
f: [1, 2, ...]
@@ -33,30 +27,6 @@
2,
...,
]
- b: (<=5 * [
- int,
- ])
- b: [
- 1,
- 2,
- ...,
- ]
- c: ((>=3 & <=5) * [
- int,
- ])
- c: [
- 1,
- 2,
- ...,
- ]
- d: (>=2 * [
- int,
- ])
- d: [
- 1,
- 2,
- ...,
- ]
e: [
...int,
]
@@ -81,24 +51,6 @@
3: (int){ int }
4: (int){ int }
}
- b: (_|_){
- // [incomplete] b: non-concrete value <=5 in operand to *:
- // ./in.cue:4:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
- c: (_|_){
- // [incomplete] c: non-concrete value >=3 & <=5 in operand to *:
- // ./in.cue:6:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
- d: (_|_){
- // [incomplete] d: non-concrete value >=2 in operand to *:
- // ./in.cue:8:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
e: (list){
0: (int){ 1 }
1: (int){ 2 }
diff --git a/cue/testdata/export/010.txtar b/cue/testdata/export/010.txtar
index bbaaec3..fea507f 100644
--- a/cue/testdata/export/010.txtar
+++ b/cue/testdata/export/010.txtar
@@ -5,12 +5,6 @@
{
a: 5 * [int]
a: [1, 2, ...]
- b: <=5 * [int]
- b: [1, 2, ...]
- c: (>=3 & <=5) * [int]
- c: [1, 2, ...]
- d: >=2 * [int]
- d: [1, 2, ...]
e: [...int]
e: [1, 2, ...]
f: [1, 2, ...]
@@ -34,30 +28,6 @@
2,
...,
]
- b: (<=5 * [
- int,
- ])
- b: [
- 1,
- 2,
- ...,
- ]
- c: ((>=3 & <=5) * [
- int,
- ])
- c: [
- 1,
- 2,
- ...,
- ]
- d: (>=2 * [
- int,
- ])
- d: [
- 1,
- 2,
- ...,
- ]
e: [
...int,
]
@@ -82,24 +52,6 @@
3: (int){ int }
4: (int){ int }
}
- b: (_|_){
- // [incomplete] b: non-concrete value <=5 in operand to *:
- // ./in.cue:4:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
- c: (_|_){
- // [incomplete] c: non-concrete value >=3 & <=5 in operand to *:
- // ./in.cue:6:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
- d: (_|_){
- // [incomplete] d: non-concrete value >=2 in operand to *:
- // ./in.cue:8:5
- 0: (int){ 1 }
- 1: (int){ 2 }
- }
e: (list){
0: (int){ 1 }
1: (int){ 2 }
diff --git a/cue/testdata/lists/020_list_arithmetic.txtar b/cue/testdata/lists/020_list_arithmetic.txtar
index 8992835..93ad628 100644
--- a/cue/testdata/lists/020_list_arithmetic.txtar
+++ b/cue/testdata/lists/020_list_arithmetic.txtar
@@ -1,14 +1,9 @@
-# DO NOT EDIT; generated by go run testdata/gen.go
-#
#name: list arithmetic
#evalPartial
-- in.cue --
l0: 3 * [1, 2, 3]
l1: 0 * [1, 2, 3]
l2: 10 * []
-l3: <=2 * []
-l4: <=2 * [int]
-l5: <=2 * (int * [int])
l6: 3 * [...int]
l7: 3 * [1, ...int]
l8: 3 * [1, 2, ...int]
@@ -112,13 +107,6 @@
3,
])
l2: (10 * [])
- l3: (<=2 * [])
- l4: (<=2 * [
- int,
- ])
- l5: (<=2 * (int * [
- int,
- ]))
l6: (3 * [
...int,
])
@@ -337,13 +325,7 @@
])
}
-- out/eval --
-Errors:
-l5: invalid operand int ('*' requires concrete value):
- ./in.cue:6:12
-
-Result:
-(_|_){
- // [eval]
+(struct){
l0: (#list){
0: (int){ 1 }
1: (int){ 2 }
@@ -359,18 +341,6 @@
}
l2: (#list){
}
- l3: (_|_){
- // [incomplete] l3: non-concrete value <=2 in operand to *:
- // ./in.cue:4:5
- }
- l4: (_|_){
- // [incomplete] l4: non-concrete value <=2 in operand to *:
- // ./in.cue:5:5
- }
- l5: (_|_){
- // [eval] l5: invalid operand int ('*' requires concrete value):
- // ./in.cue:6:12
- }
l6: (#list){
}
l7: (#list){
diff --git a/cue/testdata/lists/020_list_compilefail.txtar b/cue/testdata/lists/020_list_compilefail.txtar
new file mode 100644
index 0000000..65ec7ff
--- /dev/null
+++ b/cue/testdata/lists/020_list_compilefail.txtar
@@ -0,0 +1,69 @@
+#name: list arithmetic
+#evalPartial
+-- in.cue --
+l3: <=2 * []
+l4: <=2 * [int]
+l5: <=2 * (int * [int])
+b: <=5 * [int]
+b: [1, 2, ...]
+c: (>=3 & <=5) * [int]
+c: [1, 2, ...]
+d: >=2 * [int]
+d: [1, 2, ...]
+
+-- out/compile --
+l3: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:1:5
+l4: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:2:5
+l5: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:3:5
+b: invalid operand <=5 ('*' requires concrete value):
+ ./in.cue:4:4
+d: invalid operand >=2 ('*' requires concrete value):
+ ./in.cue:8:4
+--- in.cue
+{
+ l3: (<=2 * [])
+ l4: (<=2 * [
+ int,
+ ])
+ l5: (<=2 * (int * [
+ int,
+ ]))
+ b: (<=5 * [
+ int,
+ ])
+ b: [
+ 1,
+ 2,
+ ...,
+ ]
+ c: ((>=3 & <=5) * [
+ int,
+ ])
+ c: [
+ 1,
+ 2,
+ ...,
+ ]
+ d: (>=2 * [
+ int,
+ ])
+ d: [
+ 1,
+ 2,
+ ...,
+ ]
+}
+-- out/eval --
+l3: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:1:5
+l4: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:2:5
+l5: invalid operand <=2 ('*' requires concrete value):
+ ./in.cue:3:5
+b: invalid operand <=5 ('*' requires concrete value):
+ ./in.cue:4:4
+d: invalid operand >=2 ('*' requires concrete value):
+ ./in.cue:8:4
diff --git a/cue/types_test.go b/cue/types_test.go
index e3b7ab0..fed0acd 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -642,9 +642,6 @@
value: `[1,2,3]`,
res: "[1,2,3,]",
}, {
- value: `>=5*[1,2,3, ...int]`,
- err: "non-concrete value >=5 in operand to *",
- }, {
value: `[for x in #y if x > 1 { x }]
#y: [1,2,3]`,
res: "[2,3,]",
@@ -2326,9 +2323,6 @@
value: `[int]`,
err: `0: cannot convert incomplete value`,
}, {
- value: `(>=3 * [1, 2])`,
- err: "cue: marshal error: non-concrete value >=3 in operand to *",
- }, {
value: `{}`,
json: `{}`,
}, {
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 8a7d4ea..0eacb24 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -273,12 +273,22 @@
}
func (c *OpContext) concreteIsPossible(op Op, x Expr) bool {
- if v, ok := x.(Value); ok {
- if v.Concreteness() > Concrete {
- c.AddErrf("invalid operand %s ('%s' requires concrete value)",
- c.Str(x), op)
- return false
- }
+ if !AssertConcreteIsPossible(op, x) {
+ c.AddErrf("invalid operand %s ('%s' requires concrete value)",
+ c.Str(x), op)
+ return false
+ }
+ return true
+}
+
+// Assert that the given expression can evaluate to a concrete value.
+func AssertConcreteIsPossible(op Op, x Expr) bool {
+ switch v := x.(type) {
+ case *Bottom:
+ case *BoundExpr:
+ return false
+ case Value:
+ return v.Concreteness() == Concrete
}
return true
}
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
index 178cf6f..37769b3 100644
--- a/internal/core/compile/compile.go
+++ b/internal/core/compile/compile.go
@@ -927,13 +927,15 @@
return d
default:
+ op := adt.OpFromToken(n.Op)
+ x := c.expr(n.X)
+ y := c.expr(n.Y)
+ if op != adt.AndOp {
+ c.assertConcreteIsPossible(n.X, op, x)
+ c.assertConcreteIsPossible(n.Y, op, y)
+ }
// return updateBin(c,
- return &adt.BinaryExpr{
- Src: n,
- Op: adt.OpFromToken(n.Op), // op
- X: c.expr(n.X), // left
- Y: c.expr(n.Y), // right
- } // )
+ return &adt.BinaryExpr{Src: n, Op: op, X: x, Y: y} // )
}
default:
@@ -941,6 +943,14 @@
}
}
+func (c *compiler) assertConcreteIsPossible(src ast.Node, op adt.Op, x adt.Expr) bool {
+ if !adt.AssertConcreteIsPossible(op, x) {
+ str := internal.DebugStr(src)
+ c.errf(src, "invalid operand %s ('%s' requires concrete value)", str, op)
+ }
+ return false
+}
+
func (c *compiler) addDisjunctionElem(d *adt.DisjunctionExpr, n ast.Expr, mark bool) {
switch x := n.(type) {
case *ast.BinaryExpr: