internal/core/eval: simplify validators upon evaluator
Fixes #545
Change-Id: Ie4a7ca80b6be7a13c3547a31c3f85447fb174d55
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7222
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/testdata/basicrewrite/001_regexp.txtar b/cue/testdata/basicrewrite/001_regexp.txtar
index 52ce85a..3da41ea 100644
--- a/cue/testdata/basicrewrite/001_regexp.txtar
+++ b/cue/testdata/basicrewrite/001_regexp.txtar
@@ -18,7 +18,9 @@
b4: "foo"
s1: !="b" & =~"c" // =~"c"
-s2: !="b" & =~"[a-z]" // != "b" & =~"[a-z]"
+s2: =~"c" & !="b" // =~"c"
+s3: !="b" & =~"[a-z]" // != "b" & =~"[a-z]"
+s4: =~"[a-z]" & !="b" // != "b" & =~"[a-z]"
e1: "foo" =~ 1
e2: "foo" !~ true
@@ -55,7 +57,9 @@
b4: !~"[a-z]{4}"
b4: "foo"
s1: (!="b" & =~"c")
- s2: (!="b" & =~"[a-z]")
+ s2: (=~"c" & !="b")
+ s3: (!="b" & =~"[a-z]")
+ s4: (=~"[a-z]" & !="b")
e1: ("foo" =~ 1)
e2: ("foo" !~ true)
e3: (!="a" & <5)
@@ -66,9 +70,9 @@
b3: invalid value "foo" (out of bound =~"[a-z]{4}"):
./in.cue:10:5
e1: cannot use 1 (type int) as type (string|bytes):
- ./in.cue:18:5
+ ./in.cue:20:5
e2: cannot use true (type bool) as type (string|bytes):
- ./in.cue:19:5
+ ./in.cue:21:5
Result:
(_|_){
@@ -84,15 +88,17 @@
// ./in.cue:10:5
}
b4: (string){ "foo" }
- s1: (string){ &(!="b", =~"c") }
- s2: (string){ &(!="b", =~"[a-z]") }
+ s1: (string){ =~"c" }
+ s2: (string){ =~"c" }
+ s3: (string){ &(!="b", =~"[a-z]") }
+ s4: (string){ &(=~"[a-z]", !="b") }
e1: (_|_){
// [eval] e1: cannot use 1 (type int) as type (string|bytes):
- // ./in.cue:18:5
+ // ./in.cue:20:5
}
e2: (_|_){
// [eval] e2: cannot use true (type bool) as type (string|bytes):
- // ./in.cue:19:5
+ // ./in.cue:21:5
}
e3: (_|_){
// [eval] e3: conflicting values !="a" and <5 (mismatched types string and number)
diff --git a/cue/testdata/eval/issue545.txtar b/cue/testdata/eval/issue545.txtar
new file mode 100644
index 0000000..1a069ec
--- /dev/null
+++ b/cue/testdata/eval/issue545.txtar
@@ -0,0 +1,103 @@
+title: Simplification of validators.
+
+-- in.cue --
+package main
+
+import (
+ "strings"
+ "time"
+)
+
+t1: {
+ #Test: {
+ #HTTP: =~"^http://"
+ #SSH: !~"^ssh://"
+ #USER: strings.MinRunes(3)
+ source: #HTTP | #SSH | #USER | #Test
+ }
+
+ foo: #Test & {
+ source: "http://blablabla"
+ }
+
+ bar: #Test & {
+ source: foo
+ }
+}
+
+t2: {
+ str: "foo"
+ a: =~str
+ b: =~"foo"
+ c: a & b & a & b
+
+ d: time.Time
+ e: time.Time()
+ f: d & e & d & e
+}
+
+
+-- out/eval --
+(struct){
+ t1: (struct){
+ #Test: (#struct){
+ #HTTP: (string){ =~"^http://" }
+ #SSH: (string){ !~"^ssh://" }
+ #USER: (string){ strings.MinRunes(3) }
+ source: (string){ |((string){ =~"^http://" }, (string){ !~"^ssh://" }, (string){ strings.MinRunes(3) }) }
+ }
+ foo: (#struct){
+ #HTTP: (string){ =~"^http://" }
+ #SSH: (string){ !~"^ssh://" }
+ #USER: (string){ strings.MinRunes(3) }
+ source: (string){ "http://blablabla" }
+ }
+ bar: (#struct){
+ #HTTP: (string){ =~"^http://" }
+ #SSH: (string){ !~"^ssh://" }
+ #USER: (string){ strings.MinRunes(3) }
+ source: (#struct){
+ #HTTP: (string){ =~"^http://" }
+ #SSH: (string){ !~"^ssh://" }
+ #USER: (string){ strings.MinRunes(3) }
+ source: (string){ "http://blablabla" }
+ }
+ }
+ }
+ t2: (struct){
+ str: (string){ "foo" }
+ a: (string){ =~"foo" }
+ b: (string){ =~"foo" }
+ c: (string){ =~"foo" }
+ d: (string){ time.Time }
+ e: (string){ time.Time() }
+ f: (string){ time.Time }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ t1: {
+ #Test: {
+ #HTTP: =~"^http://"
+ #SSH: !~"^ssh://"
+ #USER: 〈import;strings〉.MinRunes(3)
+ source: (〈0;#HTTP〉|〈0;#SSH〉|〈0;#USER〉|〈1;#Test〉)
+ }
+ foo: (〈0;#Test〉 & {
+ source: "http://blablabla"
+ })
+ bar: (〈0;#Test〉 & {
+ source: 〈1;foo〉
+ })
+ }
+ t2: {
+ str: "foo"
+ a: =~〈0;str〉
+ b: =~"foo"
+ c: (((〈0;a〉 & 〈0;b〉) & 〈0;a〉) & 〈0;b〉)
+ d: 〈import;time〉.Time
+ e: 〈import;time〉.Time()
+ f: (((〈0;d〉 & 〈0;e〉) & 〈0;d〉) & 〈0;e〉)
+ }
+}
diff --git a/cue/testdata/resolve/011_bounds.txtar b/cue/testdata/resolve/011_bounds.txtar
index 4d7e3c8..6dfa594 100644
--- a/cue/testdata/resolve/011_bounds.txtar
+++ b/cue/testdata/resolve/011_bounds.txtar
@@ -162,7 +162,7 @@
s2: (number){ &(>=0, <=10) }
s3: (number){ >5 }
s4: (number){ <10 }
- s5: (number){ &(!=2, !=2) }
+ s5: (number){ !=2 }
s6: (number){ &(>=2, !=2) }
s7: (number){ &(>=2, !=2) }
s8: (number){ >5 }
diff --git a/internal/core/adt/simplify.go b/internal/core/adt/simplify.go
index cc35653..2c2e52b 100644
--- a/internal/core/adt/simplify.go
+++ b/internal/core/adt/simplify.go
@@ -33,11 +33,14 @@
switch {
case xCat == yCat:
- if x.Op == NotEqualOp || x.Op == MatchOp || x.Op == NotMatchOp {
+ switch x.Op {
+ // NOTE: EqualOp should not happen, but include it defensively.
+ // Maybe an API would use it, for instance.
+ case EqualOp, NotEqualOp, MatchOp, NotMatchOp:
if test(ctx, EqualOp, xv, yv) {
return x
}
- break // unify the two bounds
+ return nil // keep both bounds
}
// xCat == yCat && x.Op != NotEqualOp
@@ -193,3 +196,46 @@
}
return false
}
+
+// SimplifyValidator simplifies non-bound validators.
+//
+// Currently this only checks for pure equality. In the future this can be used
+// to simplify certain builtin validators analogously to how we simplify bounds
+// now.
+func SimplifyValidator(ctx *OpContext, v, w Validator) Validator {
+ switch x := v.(type) {
+ case *Builtin:
+ switch y := w.(type) {
+ case *Builtin:
+ if x == y {
+ return x
+ }
+
+ case *BuiltinValidator:
+ if y.Builtin == x && len(y.Args) == 0 {
+ return x
+ }
+ }
+
+ case *BuiltinValidator:
+ switch y := w.(type) {
+ case *Builtin:
+ return SimplifyValidator(ctx, y, x)
+
+ case *BuiltinValidator:
+ if x == y {
+ return x
+ }
+ if x.Builtin != y.Builtin || len(x.Args) != len(y.Args) {
+ return nil
+ }
+ for i, a := range x.Args {
+ if !test(ctx, EqualOp, a, y.Args[i]) {
+ return nil
+ }
+ }
+ return x
+ }
+ }
+ return nil
+}
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 5ab7c60..556d1a3 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -1294,11 +1294,36 @@
n.lowerBound = x
case adt.EqualOp, adt.NotEqualOp, adt.MatchOp, adt.NotMatchOp:
- n.checks = append(n.checks, x)
+ // This check serves as simplifier, but also to remove duplicates.
+ k := 0
+ match := false
+ for _, c := range n.checks {
+ if y, ok := c.(*adt.BoundValue); ok {
+ switch z := adt.SimplifyBounds(ctx, n.kind, x, y); {
+ case z == y:
+ match = true
+ case z == x:
+ continue
+ }
+ }
+ n.checks[k] = c
+ k++
+ }
+ n.checks = n.checks[:k]
+ if !match {
+ n.checks = append(n.checks, x)
+ }
return
}
case adt.Validator:
+ // This check serves as simplifier, but also to remove duplicates.
+ for i, y := range n.checks {
+ if b := adt.SimplifyValidator(ctx, x, y); b != nil {
+ n.checks[i] = b
+ return
+ }
+ }
n.checks = append(n.checks, x)
case *adt.Vertex: