internal/core/adt: clean up Builtin and Validator semantics
- A Builtin now no longer validates
- Instead, the evaluator explicitly converts Builtins to
BuiltinValidators when appropriate
- Builtins now have the "func" type. This is because their
return type is not known (depends on how it is used).
- Builtin implementations may now return *Bottom,
insta-promoting them to validators (not strictly necessary,
but they make no sense as a builtin per se).
Fixes #603
Change-Id: Id72d7088fa1cea71b0b606ca7252399bea3518c1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7884
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index c8698cd..679d493 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -104,7 +104,9 @@
`{a:1}`,
}, {
test("struct", `struct.MinFields(2) & {a: 1}`),
- `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`,
+ // TODO: original value may be better.
+ // `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`,
+ `_|_(struct has 1 fields < MinFields(2))`,
}, {
test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`),
`"1937-01-01T12:00:27.87+00:20"`,
diff --git a/cue/testdata/builtins/validators.txtar b/cue/testdata/builtins/validators.txtar
new file mode 100644
index 0000000..bafa49e
--- /dev/null
+++ b/cue/testdata/builtins/validators.txtar
@@ -0,0 +1,273 @@
+-- in.cue --
+import (
+ "struct"
+ "encoding/json"
+)
+
+
+// non-monotonic builtins must fail with an "incomplete" error if there
+// is a possibility the constraint can get resolved by becoming more specific.
+incompleteError1: {
+ MyType: {
+ kv: struct.MinFields(1)
+ }
+
+ foo: MyType & {
+ kv: joel: "testing"
+ }
+}
+
+incompleteError2: {
+ MyType: {
+ kv: [string]: string
+ kv: struct.MinFields(1)
+ }
+
+ foo: MyType & {
+ kv: joel: "testing"
+ }
+}
+
+incompleteError3: {
+ t: string
+ t: json.Validate(string)
+}
+
+uniqueConstrains1: {
+ t: string
+ t: json.Validate(string)
+ t: json.Validate(string)
+}
+
+uniqueConstrains2: {
+ t: struct.MaxFields(1)
+ t: struct.MaxFields(1)
+}
+
+violation: {
+ #MyType: {
+ kv: [string]: string
+ kv: struct.MinFields(1)
+ }
+
+ foo: #MyType & {
+ kv: joel: "testing"
+ kv: tony: "testing"
+ }
+}
+
+conjuncts: {
+ kv: struct.MinFields(1)
+ kv: struct.MaxFields(3)
+}
+
+// TODO: stripe off conflicting pairs
+// conflicting: {
+// kv: struct.MinFields(3)
+// kv: struct.MaxFields(1)
+// }
+
+// Builtins with bool return that can be used as validator.
+
+bareBuiltin: {
+ a: json.Valid
+ a: json.Valid
+}
+
+bareBuiltinCheck: {
+ a: json.Valid
+ a: "3"
+}
+
+builtinValidator: {
+ a: json.Valid()
+ a: json.Valid()
+}
+
+builtinValidatorCheck: {
+ a: json.Valid()
+ a: "3"
+}
+
+callOfCallToValidator: {
+ a: json.Valid
+ b: a()
+ e: b() // not allowed
+ e: "5"
+}
+
+validatorAsFunction: {
+ a: json.Valid
+ b: a("3")
+ c: json.Valid("3")
+}
+
+-- out/eval --
+Errors:
+callOfCallToValidator.e: cannot call previously called validator b:
+ ./in.cue:94:8
+
+Result:
+(_|_){
+ // [eval]
+ incompleteError1: (struct){
+ MyType: (struct){
+ kv: (struct){ struct.MinFields(1) }
+ }
+ foo: (struct){
+ kv: (struct){
+ joel: (string){ "testing" }
+ }
+ }
+ }
+ incompleteError2: (struct){
+ MyType: (struct){
+ kv: (_|_){
+ // [incomplete] struct has 0 fields < MinFields(1)
+ }
+ }
+ foo: (struct){
+ kv: (struct){
+ joel: (string){ "testing" }
+ }
+ }
+ }
+ incompleteError3: (struct){
+ t: (string){ &("encoding/json".Validate(string), string) }
+ }
+ uniqueConstrains1: (struct){
+ t: (string){ &("encoding/json".Validate(string), string) }
+ }
+ uniqueConstrains2: (struct){
+ t: (struct){ struct.MaxFields(1) }
+ }
+ violation: (struct){
+ #MyType: (#struct){
+ kv: (_|_){
+ // [incomplete] struct has 0 fields < MinFields(1)
+ }
+ }
+ foo: (#struct){
+ kv: (#struct){
+ joel: (string){ "testing" }
+ tony: (string){ "testing" }
+ }
+ }
+ }
+ conjuncts: (struct){
+ kv: (struct){ &(struct.MinFields(1), struct.MaxFields(3)) }
+ }
+ bareBuiltin: (struct){
+ a: ((string|bytes)){ "encoding/json".Valid() }
+ }
+ bareBuiltinCheck: (struct){
+ a: (string){ "3" }
+ }
+ builtinValidator: (struct){
+ a: ((string|bytes)){ "encoding/json".Valid() }
+ }
+ builtinValidatorCheck: (struct){
+ a: (string){ "3" }
+ }
+ callOfCallToValidator: (_|_){
+ // [eval]
+ a: ((string|bytes)){ "encoding/json".Valid() }
+ b: ((string|bytes)){ "encoding/json".Valid() }
+ e: (_|_){
+ // [eval] callOfCallToValidator.e: cannot call previously called validator b:
+ // ./in.cue:94:8
+ }
+ }
+ validatorAsFunction: (struct){
+ a: ((string|bytes)){ "encoding/json".Valid() }
+ b: (bool){ true }
+ c: (bool){ true }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ incompleteError1: {
+ MyType: {
+ kv: 〈import;struct〉.MinFields(1)
+ }
+ foo: (〈0;MyType〉 & {
+ kv: {
+ joel: "testing"
+ }
+ })
+ }
+ incompleteError2: {
+ MyType: {
+ kv: {
+ [string]: string
+ }
+ kv: 〈import;struct〉.MinFields(1)
+ }
+ foo: (〈0;MyType〉 & {
+ kv: {
+ joel: "testing"
+ }
+ })
+ }
+ incompleteError3: {
+ t: string
+ t: 〈import;"encoding/json"〉.Validate(string)
+ }
+ uniqueConstrains1: {
+ t: string
+ t: 〈import;"encoding/json"〉.Validate(string)
+ t: 〈import;"encoding/json"〉.Validate(string)
+ }
+ uniqueConstrains2: {
+ t: 〈import;struct〉.MaxFields(1)
+ t: 〈import;struct〉.MaxFields(1)
+ }
+ violation: {
+ #MyType: {
+ kv: {
+ [string]: string
+ }
+ kv: 〈import;struct〉.MinFields(1)
+ }
+ foo: (〈0;#MyType〉 & {
+ kv: {
+ joel: "testing"
+ }
+ kv: {
+ tony: "testing"
+ }
+ })
+ }
+ conjuncts: {
+ kv: 〈import;struct〉.MinFields(1)
+ kv: 〈import;struct〉.MaxFields(3)
+ }
+ bareBuiltin: {
+ a: 〈import;"encoding/json"〉.Valid
+ a: 〈import;"encoding/json"〉.Valid
+ }
+ bareBuiltinCheck: {
+ a: 〈import;"encoding/json"〉.Valid
+ a: "3"
+ }
+ builtinValidator: {
+ a: 〈import;"encoding/json"〉.Valid()
+ a: 〈import;"encoding/json"〉.Valid()
+ }
+ builtinValidatorCheck: {
+ a: 〈import;"encoding/json"〉.Valid()
+ a: "3"
+ }
+ callOfCallToValidator: {
+ a: 〈import;"encoding/json"〉.Valid
+ b: 〈0;a〉()
+ e: 〈0;b〉()
+ e: "5"
+ }
+ validatorAsFunction: {
+ a: 〈import;"encoding/json"〉.Valid
+ b: 〈0;a〉("3")
+ c: 〈import;"encoding/json"〉.Valid("3")
+ }
+}
diff --git a/cue/testdata/eval/issue545.txtar b/cue/testdata/eval/issue545.txtar
index 1a069ec..7e6fa60 100644
--- a/cue/testdata/eval/issue545.txtar
+++ b/cue/testdata/eval/issue545.txtar
@@ -69,9 +69,9 @@
a: (string){ =~"foo" }
b: (string){ =~"foo" }
c: (string){ =~"foo" }
- d: (string){ time.Time }
+ d: (string){ time.Time() }
e: (string){ time.Time() }
- f: (string){ time.Time }
+ f: (string){ time.Time() }
}
}
-- out/compile --
diff --git a/cue/testdata/resolve/011_bounds.txtar b/cue/testdata/resolve/011_bounds.txtar
index 6fca110..d489811 100644
--- a/cue/testdata/resolve/011_bounds.txtar
+++ b/cue/testdata/resolve/011_bounds.txtar
@@ -138,10 +138,10 @@
}
-- out/eval --
Errors:
-e1: conflicting values null and !=null (mismatched types null and (bool|string|bytes|list|struct|number)):
+e1: conflicting values null and !=null (mismatched types null and (bool|string|bytes|func|list|struct|number)):
./in.cue:40:5
./in.cue:40:12
-e2: conflicting values !=null and null (mismatched types (bool|string|bytes|list|struct|number) and null):
+e2: conflicting values !=null and null (mismatched types (bool|string|bytes|func|list|struct|number) and null):
./in.cue:41:5
./in.cue:41:14
e5: incompatible bounds >1 and <0:
@@ -200,12 +200,12 @@
s23e: (number){ &(>0.0, <2.0) }
s30: (int){ &(>0, int) }
e1: (_|_){
- // [eval] e1: conflicting values null and !=null (mismatched types null and (bool|string|bytes|list|struct|number)):
+ // [eval] e1: conflicting values null and !=null (mismatched types null and (bool|string|bytes|func|list|struct|number)):
// ./in.cue:40:5
// ./in.cue:40:12
}
e2: (_|_){
- // [eval] e2: conflicting values !=null and null (mismatched types (bool|string|bytes|list|struct|number) and null):
+ // [eval] e2: conflicting values !=null and null (mismatched types (bool|string|bytes|func|list|struct|number) and null):
// ./in.cue:41:5
// ./in.cue:41:14
}
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index d471164..d9d1865 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -792,7 +792,7 @@
case cue.CallOp:
name := fmt.Sprint(a[0])
switch name {
- case "list.UniqueItems":
+ case "list.UniqueItems", "list.UniqueItems()":
b.checkArgs(a, 0)
b.setFilter("Schema", "uniqueItems", ast.NewBool(true))
return
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index ccc237a..7306660 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -36,6 +36,7 @@
"bytes": "binary",
+ "time.Time()": "date-time",
"time.Time": "date-time",
`time.Format ("2006-01-02")`: "date",
diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go
index 13038a5..d746190 100644
--- a/internal/core/adt/adt.go
+++ b/internal/core/adt/adt.go
@@ -162,8 +162,7 @@
func (*Disjunction) Concreteness() Concreteness { return Constraint }
func (*BoundValue) Concreteness() Concreteness { return Constraint }
-// Constraint only applies if Builtin is used as constraint.
-func (*Builtin) Concreteness() Concreteness { return Constraint }
+func (*Builtin) Concreteness() Concreteness { return Concrete }
func (*BuiltinValidator) Concreteness() Concreteness { return Constraint }
// Value and Expr
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 16a7ec9..fa791d9 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -898,6 +898,33 @@
func (x *CallExpr) evaluate(c *OpContext) Value {
fun := c.value(x.Fun)
+ var b *Builtin
+ switch f := fun.(type) {
+ case *Builtin:
+ b = f
+
+ case *BuiltinValidator:
+ // We allow a validator that takes no arguments accept the validated
+ // value to be called with zero arguments.
+ switch {
+ case f.Src != nil:
+ c.addErrf(0, pos(x.Fun),
+ "cannot call previously called validator %s", c.Str(x.Fun))
+
+ case f.Builtin.IsValidator(len(x.Args)):
+ v := *f
+ v.Src = x
+ return &v
+
+ default:
+ b = f.Builtin
+ }
+
+ default:
+ c.addErrf(0, pos(x.Fun), "cannot call non-function %s (type %s)",
+ c.Str(x.Fun), kind(fun))
+ return nil
+ }
args := []Value{}
for i, a := range x.Args {
expr := c.value(a)
@@ -921,13 +948,10 @@
if c.HasErr() {
return nil
}
- b, _ := fun.(*Builtin)
- if b == nil {
- c.addErrf(0, pos(x.Fun), "cannot call non-function %s (type %s)",
- c.Str(x.Fun), kind(fun))
- return nil
+ if b.IsValidator(len(args)) {
+ return &BuiltinValidator{x, b, args}
}
- result := b.call(c, x.Src, args)
+ result := b.call(c, pos(x), args)
if result == nil {
return nil
}
@@ -943,8 +967,6 @@
Package Feature
Name string
- // REMOVE: for legacy
- Const string
}
type Param struct {
@@ -970,22 +992,19 @@
// Kind here represents the case where Builtin is used as a Validator.
func (x *Builtin) Kind() Kind {
- if len(x.Params) == 0 {
- return BottomKind
- }
- return x.Params[0].Kind()
+ return FuncKind
}
-func (x *Builtin) validate(c *OpContext, v Value) *Bottom {
- if x.Result != BoolKind {
- return c.NewErrf(
- "invalid validator %s: not a bool return", x.Name)
+func (x *Builtin) BareValidator() *BuiltinValidator {
+ if len(x.Params) != 1 ||
+ (x.Result != BoolKind && x.Result != BottomKind) {
+ return nil
}
- if len(x.Params) != 1 {
- return c.NewErrf(
- "invalid validator %s: may only have one validator to be used without call", x.Name)
- }
- return validateWithBuiltin(c, nil, x, []Value{v})
+ return &BuiltinValidator{Builtin: x}
+}
+
+func (x *Builtin) IsValidator(numArgs int) bool {
+ return len(x.Params)-1 == numArgs && x.Result&^BoolKind == 0
}
func bottom(v Value) *Bottom {
@@ -996,23 +1015,20 @@
return b
}
-func (x *Builtin) call(c *OpContext, call *ast.CallExpr, args []Value) Expr {
- if len(x.Params)-1 == len(args) && x.Result == BoolKind {
- // We have a custom builtin
- return &BuiltinValidator{call, x, args}
- }
+func (x *Builtin) call(c *OpContext, p token.Pos, args []Value) Expr {
+ fun := x // right now always x.
if len(args) > len(x.Params) {
- c.addErrf(0, call.Rparen,
+ c.addErrf(0, p,
"too many arguments in call to %s (have %d, want %d)",
- call.Fun, len(args), len(x.Params))
+ fun, len(args), len(x.Params))
return nil
}
for i := len(args); i < len(x.Params); i++ {
v := x.Params[i].Default()
if v == nil {
- c.addErrf(0, call.Rparen,
+ c.addErrf(0, p,
"not enough arguments in call to %s (have %d, want %d)",
- call.Fun, len(args), len(x.Params))
+ fun, len(args), len(x.Params))
return nil
}
args = append(args, v)
@@ -1030,7 +1046,7 @@
}
c.addErrf(code, pos(a),
"cannot use %s (type %s) as %s in argument %d to %s",
- a, k, x.Params[i].Kind(), i+1, call.Fun)
+ a, k, x.Params[i].Kind(), i+1, fun)
return nil
}
}
@@ -1046,16 +1062,23 @@
// strings.MinRunes(4)
//
type BuiltinValidator struct {
- Src *ast.CallExpr
+ Src *CallExpr
Builtin *Builtin
Args []Value // any but the first value
}
func (x *BuiltinValidator) Source() ast.Node {
if x.Src == nil {
- return nil
+ return x.Builtin.Source()
}
- return x.Src
+ return x.Src.Source()
+}
+
+func (x *BuiltinValidator) Pos() token.Pos {
+ if src := x.Source(); src != nil {
+ return src.Pos()
+ }
+ return token.NoPos
}
func (x *BuiltinValidator) Kind() Kind {
@@ -1066,10 +1089,11 @@
args := make([]Value, len(x.Args)+1)
args[0] = v
copy(args[1:], x.Args)
- return validateWithBuiltin(c, x.Src, x.Builtin, args)
+
+ return validateWithBuiltin(c, x.Pos(), x.Builtin, args)
}
-func validateWithBuiltin(c *OpContext, src *ast.CallExpr, b *Builtin, args []Value) *Bottom {
+func validateWithBuiltin(c *OpContext, src token.Pos, b *Builtin, args []Value) *Bottom {
res := b.call(c, src, args)
switch v := res.(type) {
case nil:
diff --git a/internal/core/adt/kind.go b/internal/core/adt/kind.go
index 5715693..1c3bd7e 100644
--- a/internal/core/adt/kind.go
+++ b/internal/core/adt/kind.go
@@ -63,6 +63,7 @@
FloatKind
StringKind
BytesKind
+ FuncKind
ListKind
StructKind
@@ -162,6 +163,7 @@
FloatKind: "float",
StringKind: "string",
BytesKind: "bytes",
+ FuncKind: "func",
StructKind: "struct",
ListKind: "list",
_numberKind: "number",
@@ -175,6 +177,7 @@
FloatKind: "float",
StringKind: "string",
BytesKind: "bytes",
+ FuncKind: "_",
StructKind: "{...}",
ListKind: "[...]",
_numberKind: "number",
diff --git a/internal/core/adt/simplify.go b/internal/core/adt/simplify.go
index 9869ce8..c58ad47 100644
--- a/internal/core/adt/simplify.go
+++ b/internal/core/adt/simplify.go
@@ -204,24 +204,8 @@
// 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
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 84333de..0602a4d 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -554,7 +554,7 @@
}
}
for _, v := range n.checks {
- // TODO(errors): make Validate return boolean and generate
+ // TODO(errors): make Validate return bottom and generate
// optimized conflict message. Also track and inject IDs
// to determine origin location.s
if b := ctx.Validate(v, n.node); b != nil {
@@ -1459,9 +1459,15 @@
// TODO: Use the Closer to close other fields as well?
}
- if b, ok := v.(*adt.Bottom); ok {
+ switch b := v.(type) {
+ case *adt.Bottom:
n.addBottom(b)
return
+ case *adt.Builtin:
+ if v := b.BareValidator(); v != nil {
+ n.addValueConjunct(env, v, id)
+ return
+ }
}
if !n.updateNodeType(v.Kind(), v, id) {
@@ -1552,12 +1558,13 @@
return
}
}
+ n.updateNodeType(x.Kind(), x, id)
n.checks = append(n.checks, x)
case *adt.Vertex:
// handled above.
- case adt.Value: // *NullLit, *BoolLit, *NumLit, *StringLit, *BytesLit
+ case adt.Value: // *NullLit, *BoolLit, *NumLit, *StringLit, *BytesLit, *Builtin
if y := n.scalar; y != nil {
if b, ok := adt.BinOp(ctx, adt.EqualOp, x, y).(*adt.Bool); !ok || !b.B {
n.addConflict(x, y, x.Kind(), y.Kind(), n.scalarID, id)
diff --git a/pkg/gen/gen.go b/pkg/gen/gen.go
index ba2b9bd..145fd4e 100644
--- a/pkg/gen/gen.go
+++ b/pkg/gen/gen.go
@@ -314,8 +314,10 @@
fmt.Fprintf(g.w, "{Kind: %s},\n", k)
}
fmt.Fprintf(g.w, "\n},\n")
- result := g.goToCUE(x.Type.Results.List[0].Type)
- fmt.Fprintf(g.w, "Result: %s,\n", result)
+
+ expr := x.Type.Results.List[0].Type
+ fmt.Fprintf(g.w, "Result: %s,\n", g.goToCUE(expr))
+
argList := strings.Join(args, ", ")
valList := strings.Join(vals, ", ")
init := ""
@@ -351,6 +353,8 @@
return "bigFloat"
case "big.Rat":
return "bigRat"
+ case "adt.Bottom":
+ return "error"
case "internal.Decimal":
return "decimal"
case "[]*internal.Decimal":
diff --git a/pkg/internal/builtin.go b/pkg/internal/builtin.go
index 29c94e7..bbd0ccb 100644
--- a/pkg/internal/builtin.go
+++ b/pkg/internal/builtin.go
@@ -148,6 +148,9 @@
}()
b.Func(c)
switch v := c.Ret.(type) {
+ case nil:
+ // Validators may return a nil in case validation passes.
+ return nil
case adt.Value:
return v
case bottomer:
diff --git a/pkg/math/testdata/round.txtar b/pkg/math/testdata/round.txtar
index fa3b04e..ae9093c 100644
--- a/pkg/math/testdata/round.txtar
+++ b/pkg/math/testdata/round.txtar
@@ -36,7 +36,7 @@
Errors:
error in call to math.MultipleOf: division by zero
floorE1: too many arguments in call to math.Floor (have 2, want 1):
- ./in.cue:17:25
+ ./in.cue:17:10
floorE2: cannot use "foo" (type string) as number in argument 1 to math.Floor:
./in.cue:18:21
diff --git a/pkg/struct/pkg.go b/pkg/struct/pkg.go
index 9807c35..f7bf56e 100644
--- a/pkg/struct/pkg.go
+++ b/pkg/struct/pkg.go
@@ -23,11 +23,11 @@
{Kind: adt.StructKind},
{Kind: adt.IntKind},
},
- Result: adt.BoolKind,
+ Result: adt.BottomKind,
Func: func(c *internal.CallCtxt) {
object, n := c.Struct(0), c.Int(1)
if c.Do() {
- c.Ret, c.Err = MinFields(object, n)
+ c.Ret = MinFields(object, n)
}
},
}, {
diff --git a/pkg/struct/struct.go b/pkg/struct/struct.go
index 00094e1..d9b034e 100644
--- a/pkg/struct/struct.go
+++ b/pkg/struct/struct.go
@@ -17,22 +17,33 @@
import (
"cuelang.org/go/cue"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
)
// MinFields validates the minimum number of fields that are part of a struct.
+// It can only be used as a validator, for instance `MinFields(3)`.
//
// Only fields that are part of the data model count. This excludes hidden
// fields, optional fields, and definitions.
-func MinFields(object *cue.Struct, n int) (bool, error) {
+func MinFields(object *cue.Struct, n int) *adt.Bottom {
iter := object.Fields(cue.Hidden(false), cue.Optional(false))
count := 0
for iter.Next() {
count++
}
- return count >= n, nil
+ if count < n {
+ return &adt.Bottom{
+ Code: adt.IncompleteError, // could still be resolved
+ Err: errors.Newf(token.NoPos, "struct has %d fields < MinFields(%d)", count, n),
+ }
+ }
+ return nil
}
// MaxFields validates the maximum number of fields that are part of a struct.
+// It can only be used as a validator, for instance `MaxFields(3)`.
//
// Only fields that are part of the data model count. This excludes hidden
// fields, optional fields, and definitions.
@@ -42,5 +53,6 @@
for iter.Next() {
count++
}
+ // permanent error is okay here.
return count <= n, nil
}
diff --git a/pkg/struct/testdata/gen.txtar b/pkg/struct/testdata/gen.txtar
index 83c1382..1ef363b 100644
--- a/pkg/struct/testdata/gen.txtar
+++ b/pkg/struct/testdata/gen.txtar
@@ -13,17 +13,19 @@
t1: conflicting values struct.MinFields(0) and "" (mismatched types struct and string):
./in.cue:3:5
./in.cue:3:27
-t3: invalid value {a:1} (does not satisfy struct.MinFields(2)):
- ./in.cue:5:5
t4: invalid value {a:1} (does not satisfy struct.MaxFields(0)):
./in.cue:6:5
Result:
+import "struct"
+
t1: _|_ // t1: conflicting values struct.MinFields(0) and "" (mismatched types struct and string)
t2: {
a: 1
}
-t3: _|_ // t3: invalid value {a:1} (does not satisfy struct.MinFields(2))
+t3: struct.MinFields(2) & {
+ a: 1
+}
t4: _|_ // t4: invalid value {a:1} (does not satisfy struct.MaxFields(0))
t5: {
a: 1