cue: handle builtins used as values
The API needs to handle the cases of builtins
being able to be used as validator without a call.
They are a bit weird in that they are concrete,
but not ground, thus the explicit distinction is
introduced.
Change-Id: I11202c797ea3bddbb322d8c370c8ccbe38e5199e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2721
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index 21167d5..2b2672a 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -173,6 +173,13 @@
},
}
+func (x *builtin) representedKind() kind {
+ if x.isValidator() {
+ return x.Params[0]
+ }
+ return x.kind()
+}
+
func (x *builtin) kind() kind {
return lambdaKind
}
@@ -195,9 +202,13 @@
return fmt.Sprintf("%s.%s", ctx.labelStr(x.pkg), x.Name)
}
+func (x *builtin) isValidator() bool {
+ return len(x.Params) == 1 && x.Result == boolKind
+}
+
func convertBuiltin(v evaluated) evaluated {
x, ok := v.(*builtin)
- if ok && len(x.Params) == 1 && x.Result == boolKind {
+ if ok && x.isValidator() {
return &customValidator{v.base(), []evaluated{}, x}
}
return v
diff --git a/cue/eval.go b/cue/eval.go
index 74256db..1aa390f 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -56,7 +56,11 @@
v := e.eval(x.x, structKind|lambdaKind, msgType, x)
if e.is(v, structKind|lambdaKind, "") {
- n := v.(scope).lookup(ctx, x.feature)
+ sc, ok := v.(scope)
+ if !ok {
+ return ctx.mkErr(x, "invalid subject to selector (found %v)", v.kind())
+ }
+ n := sc.lookup(ctx, x.feature)
if n.val() == nil {
field := ctx.labelStr(x.feature)
// m.foo undefined (type map[string]bool has no field or method foo)
diff --git a/cue/export_test.go b/cue/export_test.go
index ef18af0..4aa01e7 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -446,6 +446,20 @@
b: int
})
}`),
+ }, {
+ in: `
+ import "time"
+
+ a: { b: time.Duration } | { c: time.Duration }
+ `,
+ out: unindent(`
+ import "time"
+
+ a: {
+ b: time.Duration
+ } | {
+ c: time.Duration
+ }`),
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
diff --git a/cue/kind.go b/cue/kind.go
index 425df87..9deac1b 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -88,7 +88,8 @@
// isDone means that the value will not evaluate further.
func (k kind) isDone() bool { return k&referenceKind == bottomKind }
func (k kind) hasReferences() bool { return k&referenceKind != bottomKind }
-func (k kind) isGround() bool { return k&nonGround == bottomKind }
+func (k kind) isConcrete() bool { return k&^(lambdaKind-1) == bottomKind }
+func (k kind) isGround() bool { return k&^(nonGround-1) == bottomKind }
func (k kind) isAtom() bool { return k.isGround() && k&atomKind != bottomKind }
func (k kind) isAnyOf(of kind) bool {
return k&of != bottomKind
diff --git a/cue/types.go b/cue/types.go
index bb40acf..dc1fcb1 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -647,7 +647,16 @@
if v.path == nil {
return BottomKind
}
- k := v.path.v.evalPartial(v.ctx()).kind()
+ var k kind
+ x := v.path.v.evalPartial(v.ctx())
+ switch x := convertBuiltin(x).(type) {
+ case *builtin:
+ k = x.representedKind()
+ case *customValidator:
+ k = x.call.Params[0]
+ default:
+ k = x.kind()
+ }
vk := BottomKind // Everything is a bottom kind.
for i := kind(1); i < nonGround; i <<= 1 {
if k&i != 0 {
@@ -824,8 +833,8 @@
return pos
}
-// IsConcrete reports whether the current value is a concrete scalar value,
-// not relying on default values, a terminal error, a list, or a struct.
+// IsConcrete reports whether the current value is a concrete scalar value
+// (not relying on default values), a terminal error, a list, or a struct.
// It does not verify that values of lists or structs are concrete themselves.
// To check whether there is a concrete default, use v.Default().IsConcrete().
func (v Value) IsConcrete() bool {
@@ -839,17 +848,17 @@
return false
}
// Other errors are considered ground.
- return x.kind().isGround()
+ return x.kind().isConcrete()
}
-// IsIncomplete is deprecated.
+// Deprecated: IsIncomplete
//
// It indicates that the value cannot be fully evaluated due to
// insufficient information.
func (v Value) IsIncomplete() bool {
// TODO: remove
x := v.eval(v.ctx())
- if x.kind().hasReferences() || !x.kind().isGround() {
+ if !x.kind().isConcrete() {
return true
}
return isIncomplete(x)
diff --git a/cue/types_test.go b/cue/types_test.go
index df59170..163e5a4 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -158,6 +158,30 @@
value: `{a: int, b: [1][a]}.b`,
kind: BottomKind,
concrete: false,
+ }, {
+ value: `import "time"
+ {a: time.Time}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "time"
+ {a: time.Time & string}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "strings"
+ {a: strings.ContainsAny("D")}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "struct"
+ {a: struct.MaxFields(2) & {}}.a`,
+ kind: StructKind, // Can determine a valid struct already.
+ incompleteKind: StructKind,
+ concrete: true,
}}
for _, tc := range testCases {
t.Run(tc.value, func(t *testing.T) {
@@ -1109,6 +1133,13 @@
`,
opts: []Option{DisallowCycles(true)},
err: true,
+ }, {
+ desc: "builtins are okay",
+ in: `
+ import "time"
+
+ a: { b: time.Duration } | { c: time.Duration }
+ `,
}}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {