diff --git a/cue/testdata/builtins/isconcrete.txtar b/cue/testdata/builtins/isconcrete.txtar new file mode 100644 index 00000000000..98930e83ec2 --- /dev/null +++ b/cue/testdata/builtins/isconcrete.txtar @@ -0,0 +1,106 @@ +-- in.cue -- +a: {} +b: int + +c: isconcrete(a) // true +d: isconcrete(b) // false +e: isconcrete(a.b) // false(b could still be defined) +f: isconcrete(b.c) // fatal error (b.c can never be satisfied) + +foo: { + a?: string + b?: string + a: "test" +} + +foo_a: isconcrete(foo.a) // true +foo_b: isconcrete(foo.b) // false(b could still be defined) +foo_c: isconcrete(foo.c) +foo_a_x: isconcrete(foo.a.x) // fatal error (foo.a.x can never be satisfied) +foo_b_x: isconcrete(foo.b.x) // fatal error (foo.b.x can never be satisfied) +foo_c_x: isconcrete(foo.c.x) +foo_e: isconcrete({a: 1, a: 2}.a) // fatal error (conflict values) + +-- out/compile -- +--- in.cue +{ + a: {} + b: int + c: isconcrete(〈0;a〉) + d: isconcrete(〈0;b〉) + e: isconcrete(〈0;a〉.b) + f: isconcrete(〈0;b〉.c) + foo: { + a?: string + b?: string + a: "test" + } + foo_a: isconcrete(〈0;foo〉.a) + foo_b: isconcrete(〈0;foo〉.b) + foo_c: isconcrete(〈0;foo〉.c) + foo_a_x: isconcrete(〈0;foo〉.a.x) + foo_b_x: isconcrete(〈0;foo〉.b.x) + foo_c_x: isconcrete(〈0;foo〉.c.x) + foo_e: isconcrete({ + a: 1 + a: 2 + }.a) +} +-- out/eval/stats -- +Leaks: 2 +Freed: 17 +Reused: 15 +Allocs: 4 +Retain: 2 + +Unifications: 19 +Conjuncts: 27 +Disjuncts: 18 +-- out/eval -- +Errors: +a: conflicting values 2 and 1: + ./in.cue:21:23 + ./in.cue:21:29 +f: invalid operand b (found int, want list or struct): + ./in.cue:7:15 +foo_a_x: invalid operand foo.a (found string, want list or struct): + ./in.cue:18:21 + +Result: +(_|_){ + // [eval] + a: (struct){ + } + b: (int){ int } + c: (bool){ true } + d: (bool){ false } + e: (bool){ false } + f: (_|_){ + // [eval] f: invalid operand b (found int, want list or struct): + // ./in.cue:7:15 + } + foo: (struct){ + a: (string){ "test" } + b?: (string){ string } + } + foo_a: (bool){ true } + foo_b: (bool){ false } + foo_c: (bool){ false } + foo_a_x: (_|_){ + // [eval] foo_a_x: invalid operand foo.a (found string, want list or struct): + // ./in.cue:18:21 + } + foo_b_x: (_|_){ + // [incomplete] foo_b_x: cannot reference optional field: b: + // ./in.cue:19:25 + } + foo_c_x: (_|_){ + // [incomplete] foo_c_x: undefined field: c: + // ./in.cue:20:25 + } + foo_e: (_|_){ + // [eval] a: conflicting values 2 and 1: + // ./in.cue:21:23 + // ./in.cue:21:29 + } +} diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go index 6463c0329e8..37c5fb87a79 100644 --- a/internal/core/adt/expr.go +++ b/internal/core/adt/expr.go @@ -1468,6 +1468,10 @@ func (x *CallExpr) evaluate(c *OpContext, state vertexStatus) Value { case *Bottom: // TODO(errors): consider adding an argument index for this errors. + if b.AllowErrors { + args = append(args, expr) + continue + } c.errs = CombineErrors(a.Source(), c.errs, v) default: @@ -1475,7 +1479,7 @@ func (x *CallExpr) evaluate(c *OpContext, state vertexStatus) Value { } c.errs = CombineErrors(a.Source(), saved, c.errs) } - if c.HasErr() { + if c.HasErr() && (b == nil || !b.AllowErrors) { return nil } if b.IsValidator(len(args)) { @@ -1495,8 +1499,9 @@ type Builtin struct { Result Kind Func func(c *OpContext, args []Value) Expr - Package Feature - Name string + Package Feature + Name string + AllowErrors bool } type Param struct { diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go index 85cb5e95590..9e59cb966d9 100644 --- a/internal/core/compile/builtin.go +++ b/internal/core/compile/builtin.go @@ -192,6 +192,32 @@ var remBuiltin = &adt.Builtin{ }, } +var isConcreteBuiltin = &adt.Builtin{ + Name: "isconcrete", + Params: []adt.Param{{Value: &adt.BasicType{K: adt.BottomKind}}}, + Result: adt.BoolKind, + AllowErrors: true, + Func: func(c *adt.OpContext, args []adt.Value) adt.Expr { + switch x := args[0].(type) { + case *adt.Bottom: + if x.IsIncomplete() && x.Permanent { + _ = c.Err() + return &adt.Bool{B: false} + } + return x + default: + concrete := adt.IsConcrete(x) + if e := c.Err(); e != nil { + if e.IsIncomplete() && e.Permanent { + return &adt.Bool{B: false} + } + return e + } + return &adt.Bool{B: concrete} + } + }, +} + type intFunc func(c *adt.OpContext, x, y *adt.Num) adt.Value func intDivOp(c *adt.OpContext, fn intFunc, name string, args []adt.Value) adt.Value { diff --git a/internal/core/compile/predeclared.go b/internal/core/compile/predeclared.go index 6345147ac90..5259d6c4692 100644 --- a/internal/core/compile/predeclared.go +++ b/internal/core/compile/predeclared.go @@ -40,6 +40,8 @@ func predeclared(n *ast.Ident) adt.Expr { case "number", "__number": return &adt.BasicType{Src: n, K: adt.NumKind} + case "isconcrete", "__isconcrete": + return isConcreteBuiltin case "len", "__len": return lenBuiltin case "close", "__close":