Skip to content

Commit

Permalink
internal/core/adt: clean up Builtin and Validator semantics
Browse files Browse the repository at this point in the history
- 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>
  • Loading branch information
mpvl committed Dec 4, 2020
1 parent e841714 commit f62bfed
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 75 deletions.
4 changes: 3 additions & 1 deletion cue/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ func TestBuiltins(t *testing.T) {
`{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"`,
Expand Down
273 changes: 273 additions & 0 deletions cue/testdata/builtins/validators.txtar
Original file line number Diff line number Diff line change
@@ -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")
}
}
4 changes: 2 additions & 2 deletions cue/testdata/eval/issue545.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ t2: {
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 --
Expand Down
8 changes: 4 additions & 4 deletions cue/testdata/resolve/011_bounds.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ e9: _|_ // conflicting values >"a" and <1 (mismatched types string and number)
}
-- 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:
Expand Down Expand Up @@ -200,12 +200,12 @@ Result:
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
}
Expand Down
2 changes: 1 addition & 1 deletion encoding/openapi/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ func (b *builder) array(v cue.Value) {
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
Expand Down
1 change: 1 addition & 0 deletions encoding/openapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var cueToOpenAPI = map[string]string{

"bytes": "binary",

"time.Time()": "date-time",
"time.Time": "date-time",
`time.Format ("2006-01-02")`: "date",

Expand Down
3 changes: 1 addition & 2 deletions internal/core/adt/adt.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ func (*Conjunction) Concreteness() Concreteness { return Constraint }
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
Expand Down

0 comments on commit f62bfed

Please sign in to comment.