encoding/jsonschema: fix type exclusion logic

The current implementation misinterpreted the spec
that constraints from a certain type imply a value is of
that type: it does not.

This means that if, for instance, a schema contains only
constraints for a struct, one must still explicitly allow all
other types besides struct in CUE. The new logic does so,
and also addes logic to opimize

Change-Id: I69fecd9b6888b0dad89db194c17acb56841de490
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5648
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/jsonschema/constraints.go b/encoding/jsonschema/constraints.go
index b7d4335..90a6df2 100644
--- a/encoding/jsonschema/constraints.go
+++ b/encoding/jsonschema/constraints.go
@@ -58,25 +58,6 @@
 	return &constraint{key: name, phase: 2, fn: f}
 }
 
-func combineSequence(name string, n cue.Value, s *state, op token.Token, f func(n cue.Value) ast.Expr) {
-	if n.Kind() != cue.ListKind {
-		s.errf(n, `value of %q must be an array, found %v`, name, n.Kind())
-	}
-	var a ast.Expr
-	for _, n := range list(n) {
-		if a == nil {
-			a = f(n)
-			continue
-		}
-		a = ast.NewBinExpr(token.OR, a, f(n))
-	}
-	if a == nil {
-		s.errf(n, `empty array for %q`, name)
-		return
-	}
-	s.add(a)
-}
-
 // TODO:
 // writeOnly, readOnly
 
@@ -89,7 +70,6 @@
 }
 
 func addDefinitions(n cue.Value, s *state) {
-	s.kind |= cue.StructKind
 	if n.Kind() != cue.StructKind {
 		s.errf(n, `"definitions" expected an object, found %v`, n.Kind)
 	}
@@ -134,29 +114,65 @@
 	// Generic constraint
 
 	p0("type", func(n cue.Value, s *state) {
+		var types cue.Kind
+		set := func(n cue.Value) {
+			str, ok := s.strValue(n)
+			if !ok {
+				s.errf(n, "type value should be a string")
+			}
+			switch str {
+			case "null":
+				types |= cue.NullKind
+				// TODO: handle OpenAPI restrictions.
+			case "boolean":
+				types |= cue.BoolKind
+			case "string":
+				types |= cue.StringKind
+			case "number":
+				types |= cue.NumberKind
+			case "integer":
+				types |= cue.IntKind
+			case "array":
+				types |= cue.ListKind
+			case "object":
+				types |= cue.StructKind
+
+			default:
+				s.errf(n, "unknown type %q", n)
+			}
+		}
+
 		switch n.Kind() {
 		case cue.StringKind:
-			s.types = append(s.types, n)
+			set(n)
 		case cue.ListKind:
 			for i, _ := n.List(); i.Next(); {
-				s.types = append(s.types, i.Value())
+				set(i.Value())
 			}
 		default:
 			s.errf(n, `value of "type" must be a string or list of strings`)
 		}
+
+		s.allowedTypes &= types
 	}),
 
 	p0("enum", func(n cue.Value, s *state) {
-		combineSequence("enum", n, s, token.OR, s.value)
+		var a []ast.Expr
+		for _, x := range s.listItems("enum", n, true) {
+			a = append(a, s.value(x))
+		}
+		s.addConjunct(ast.NewBinExpr(token.OR, a...))
 		s.typeOptional = true
 	}),
 
 	p0d("const", 6, func(n cue.Value, s *state) {
-		s.add(s.value(n))
+		s.addConjunct(s.value(n))
 	}),
 
 	p0("default", func(n cue.Value, s *state) {
+		allowed, used := s.allowedTypes, s.usedTypes
 		s.default_ = s.value(n)
+		s.allowedTypes, s.usedTypes = allowed, used
 		// must validate that the default is subsumed by the normal value,
 		// as CUE will otherwise broaden the accepted values with the default.
 		s.examples = append(s.examples, s.default_)
@@ -172,8 +188,10 @@
 		if n.Kind() != cue.ListKind {
 			s.errf(n, `value of "examples" must be an array, found %v`, n.Kind)
 		}
-		for _, n := range list(n) {
-			s.examples = append(s.examples, s.schema(n))
+		for _, n := range s.listItems("examples", n, true) {
+			if ex := s.schema(n); !isAny(ex) {
+				s.examples = append(s.examples, ex)
+			}
 		}
 	}),
 
@@ -191,16 +209,17 @@
 	p0("$def", addDefinitions),
 	p0("definitions", addDefinitions),
 	p0("$ref", func(n cue.Value, s *state) {
+		s.usedTypes = allTypes
 		str, _ := s.strValue(n)
 		a := s.parseRef(n.Pos(), str)
 		if a != nil {
 			a = s.mapRef(n.Pos(), str, a)
 		}
 		if a == nil {
-			s.add(&ast.BadExpr{From: n.Pos()})
+			s.addConjunct(&ast.BadExpr{From: n.Pos()})
 			return
 		}
-		s.add(ast.NewSel(ast.NewIdent(a[0]), a[1:]...))
+		s.addConjunct(ast.NewSel(ast.NewIdent(a[0]), a[1:]...))
 	}),
 
 	// Combinators
@@ -223,16 +242,59 @@
 	// This is not necessary if the values are mutually exclusive/ have a
 	// discriminator.
 
-	p0("allOf", func(n cue.Value, s *state) {
-		combineSequence("allOf", n, s, token.AND, s.schema)
+	p1("allOf", func(n cue.Value, s *state) {
+		var a []ast.Expr
+		for _, v := range s.listItems("allOf", n, false) {
+			x, sub := s.schemaState(v, s.allowedTypes, true)
+			s.allowedTypes &= sub.allowedTypes
+			s.usedTypes |= sub.usedTypes
+			if sub.hasConstraints() {
+				a = append(a, x)
+			}
+		}
+		if len(a) > 0 {
+			s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.AND, a...))
+		}
 	}),
 
-	p0("anyOf", func(n cue.Value, s *state) {
-		combineSequence("anyOf", n, s, token.OR, s.schema)
+	p1("anyOf", func(n cue.Value, s *state) {
+		var types cue.Kind
+		var a []ast.Expr
+		for _, v := range s.listItems("anyOf", n, false) {
+			x, sub := s.schemaState(v, s.allowedTypes, true)
+			types |= sub.allowedTypes
+			if sub.hasConstraints() {
+				a = append(a, x)
+			}
+		}
+		s.allowedTypes &= types
+		if len(a) > 0 {
+			s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.OR, a...))
+		}
 	}),
 
-	p0("oneOf", func(n cue.Value, s *state) {
-		combineSequence("oneOf", n, s, token.OR, s.schema)
+	p1("oneOf", func(n cue.Value, s *state) {
+		var types cue.Kind
+		var a []ast.Expr
+		hasSome := false
+		for _, v := range s.listItems("oneOf", n, false) {
+			x, sub := s.schemaState(v, s.allowedTypes, true)
+			types |= sub.allowedTypes
+
+			// TODO: make more finegrained by making it two pass.
+			if sub.hasConstraints() {
+				hasSome = true
+			}
+
+			if !isAny(x) {
+				a = append(a, x)
+			}
+		}
+		s.allowedTypes &= types
+		if len(a) > 0 && hasSome {
+			s.usedTypes = allTypes
+			s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.OR, a...))
+		}
 
 		// TODO: oneOf({a:x}, {b:y}, ..., not(anyOf({a:x}, {b:y}, ...))),
 		// can be translated to {} | {a:x}, {b:y}, ...
@@ -241,16 +303,16 @@
 	// String constraints
 
 	p0("pattern", func(n cue.Value, s *state) {
-		s.kind |= cue.StringKind
-		s.add(&ast.UnaryExpr{Op: token.MAT, X: s.string(n)})
+		s.usedTypes |= cue.StringKind
+		s.addConjunct(&ast.UnaryExpr{Op: token.MAT, X: s.string(n)})
 	}),
 
 	p0d("contentMediaType", 7, func(n cue.Value, s *state) {
-		s.kind |= cue.StringKind
+		s.usedTypes |= cue.StringKind
 	}),
 
 	p0d("contentEncoding", 7, func(n cue.Value, s *state) {
-		s.kind |= cue.StringKind
+		s.usedTypes |= cue.StringKind
 		// 7bit, 8bit, binary, quoted-printable and base64.
 		// RFC 2054, part 6.1.
 		// https://tools.ietf.org/html/rfc2045
@@ -260,29 +322,29 @@
 	// Number constraints
 
 	p0("minimum", func(n cue.Value, s *state) {
-		s.kind |= cue.NumberKind
-		s.add(&ast.UnaryExpr{Op: token.GEQ, X: s.number(n)})
+		s.usedTypes |= cue.NumberKind
+		s.addConjunct(&ast.UnaryExpr{Op: token.GEQ, X: s.number(n)})
 	}),
 
 	p0("exclusiveMinimum", func(n cue.Value, s *state) {
 		// TODO: should we support Draft 4 booleans?
-		s.kind |= cue.NumberKind
-		s.add(&ast.UnaryExpr{Op: token.GTR, X: s.number(n)})
+		s.usedTypes |= cue.NumberKind
+		s.addConjunct(&ast.UnaryExpr{Op: token.GTR, X: s.number(n)})
 	}),
 
 	p0("maximum", func(n cue.Value, s *state) {
-		s.kind |= cue.NumberKind
-		s.add(&ast.UnaryExpr{Op: token.LEQ, X: s.number(n)})
+		s.usedTypes |= cue.NumberKind
+		s.addConjunct(&ast.UnaryExpr{Op: token.LEQ, X: s.number(n)})
 	}),
 
 	p0("exclusiveMaximum", func(n cue.Value, s *state) {
 		// TODO: should we support Draft 4 booleans?
-		s.kind |= cue.NumberKind
-		s.add(&ast.UnaryExpr{Op: token.LSS, X: s.number(n)})
+		s.usedTypes |= cue.NumberKind
+		s.addConjunct(&ast.UnaryExpr{Op: token.LSS, X: s.number(n)})
 	}),
 
 	p0("multipleOf", func(n cue.Value, s *state) {
-		s.kind |= cue.NumberKind
+		s.usedTypes |= cue.NumberKind
 		multiple := s.number(n)
 		var x big.Int
 		_, _ = n.MantExp(&x)
@@ -290,13 +352,14 @@
 			s.errf(n, `"multipleOf" value must be < 0; found %s`, n)
 		}
 		math := s.addImport("math")
-		s.add(ast.NewCall(ast.NewSel(math, "MultipleOf"), multiple))
+		s.addConjunct(ast.NewCall(ast.NewSel(math, "MultipleOf"), multiple))
 	}),
 
 	// Object constraints
 
 	p0("properties", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
+
 		if s.obj == nil {
 			s.obj = &ast.StructLit{}
 		}
@@ -306,7 +369,7 @@
 
 		s.processMap(n, func(key string, n cue.Value) {
 			// property?: value
-			expr, state := s.schemaState(n)
+			expr, state := s.schemaState(n, allTypes, false)
 			f := &ast.Field{Label: ast.NewString(key), Value: expr}
 			state.doc(f)
 			f.Optional = token.Blank.Pos()
@@ -328,12 +391,16 @@
 	}),
 
 	p1("required", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
-		if s.obj == nil {
-			s.errf(n, `"required" without a "properties" field`)
-		}
+		s.usedTypes |= cue.StructKind
 		if n.Kind() != cue.ListKind {
 			s.errf(n, `value of "required" must be list of strings, found %v`, n.Kind)
+			return
+		}
+
+		if s.obj == nil {
+			s.obj = &ast.StructLit{}
+			// TODO: detect that properties is defined somewhere.
+			// s.errf(n, `"required" without a "properties" field`)
 		}
 
 		// Create field map
@@ -346,11 +413,16 @@
 			}
 		}
 
-		for _, n := range list(n) {
+		for _, n := range s.listItems("required", n, true) {
 			str, ok := s.strValue(n)
 			f := fields[str]
 			if f == nil && ok {
-				s.errf(n, "required field %q not in properties", str)
+				f := &ast.Field{
+					Label: ast.NewString(str),
+					Value: ast.NewIdent("_"),
+				}
+				fields[str] = f
+				s.obj.Elts = append(s.obj.Elts, f)
 				continue
 			}
 			if f.Optional == token.NoPos {
@@ -361,25 +433,31 @@
 	}),
 
 	p0d("propertyNames", 6, func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
+
 		// [=~pattern]: _
-		s.add(ast.NewStruct(ast.NewList(s.schema(n)), ast.NewIdent("_")))
+		if names, _ := s.schemaState(n, cue.StringKind, false); !isAny(names) {
+			s.addConjunct(ast.NewStruct(ast.NewList((names)), ast.NewIdent("_")))
+		}
 	}),
 
 	p0("minProperties", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
+
 		pkg := s.addImport("struct")
-		s.add(ast.NewCall(ast.NewSel(pkg, "MinFields"), s.uint(n)))
+		s.addConjunct(ast.NewCall(ast.NewSel(pkg, "MinFields"), s.uint(n)))
 	}),
 
 	p0("maxProperties", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
+
 		pkg := s.addImport("struct")
-		s.add(ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n)))
+		s.addConjunct(ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n)))
 	}),
 
 	p0("dependencies", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
+
 		// Schema and property dependencies.
 		// TODO: the easiest implementation is with comprehensions.
 		// The nicer implementation is with disjunctions. This has to be done
@@ -393,7 +471,7 @@
 	}),
 
 	p1("patternProperties", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
 		if n.Kind() != cue.StructKind {
 			s.errf(n, `value of "patternProperties" must be an an object, found %v`, n.Kind)
 		}
@@ -417,7 +495,7 @@
 	}),
 
 	p2("additionalProperties", func(n cue.Value, s *state) {
-		s.kind |= cue.StructKind
+		s.usedTypes |= cue.StructKind
 		switch n.Kind() {
 		case cue.BoolKind:
 			s.closeStruct = !s.boolValue(n)
@@ -451,7 +529,7 @@
 	// Array constraints.
 
 	p0("items", func(n cue.Value, s *state) {
-		s.kind |= cue.ListKind
+		s.usedTypes |= cue.ListKind
 		if s.list != nil {
 			s.errf(n, `"items" declared more than once, previous declaration at %s`, s.list.Pos())
 		}
@@ -459,16 +537,16 @@
 		case cue.StructKind:
 			elem := s.schema(n)
 			ast.SetRelPos(elem, token.NoRelPos)
-			s.add(ast.NewList(&ast.Ellipsis{Type: elem}))
+			s.addConjunct(ast.NewList(&ast.Ellipsis{Type: elem}))
 
 		case cue.ListKind:
 			var a []ast.Expr
-			for _, n := range list(n) {
+			for _, n := range s.listItems("items", n, true) {
 				v := s.schema(n)
 				ast.SetRelPos(v, token.NoRelPos)
 				a = append(a, v)
 			}
-			s.add(ast.NewList(a...))
+			s.addConjunct(ast.NewList(a...))
 
 		default:
 			s.errf(n, `value of "items" must be an object or array`)
@@ -476,29 +554,31 @@
 	}),
 
 	p0("contains", func(n cue.Value, s *state) {
-		s.kind |= cue.ListKind
+		s.usedTypes |= cue.ListKind
 		list := s.addImport("list")
 		// TODO: Passing non-concrete values is not yet supported in CUE.
-		s.add(ast.NewCall(ast.NewSel(list, "Contains"), clearPos(s.schema(n))))
+		if x := s.schema(n); !isAny(x) {
+			s.addConjunct(ast.NewCall(ast.NewSel(list, "Contains"), clearPos(x)))
+		}
 	}),
 
 	p0("minItems", func(n cue.Value, s *state) {
-		s.kind |= cue.ListKind
+		s.usedTypes |= cue.ListKind
 		list := s.addImport("list")
-		s.add(ast.NewCall(ast.NewSel(list, "MinItems"), clearPos(s.uint(n))))
+		s.addConjunct(ast.NewCall(ast.NewSel(list, "MinItems"), clearPos(s.uint(n))))
 	}),
 
 	p0("maxItems", func(n cue.Value, s *state) {
-		s.kind |= cue.ListKind
+		s.usedTypes |= cue.ListKind
 		list := s.addImport("list")
-		s.add(ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n))))
+		s.addConjunct(ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n))))
 	}),
 
 	p0("uniqueItems", func(n cue.Value, s *state) {
-		s.kind |= cue.ListKind
+		s.usedTypes |= cue.ListKind
 		if s.boolValue(n) {
 			list := s.addImport("list")
-			s.add(ast.NewCall(ast.NewSel(list, "UniqueItems")))
+			s.addConjunct(ast.NewCall(ast.NewSel(list, "UniqueItems")))
 		}
 	}),
 }
diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go
index 09838e6..bf58b4b 100644
--- a/encoding/jsonschema/decode.go
+++ b/encoding/jsonschema/decode.go
@@ -20,6 +20,7 @@
 
 import (
 	"fmt"
+	"math/bits"
 	"sort"
 	"strings"
 
@@ -111,7 +112,7 @@
 	inner := len(ref) - 1
 	name := ref[inner]
 
-	expr, state := root.schemaState(v)
+	expr, state := root.schemaState(v, allTypes, false)
 
 	tags := []string{}
 	if state.jsonschema != "" {
@@ -204,13 +205,15 @@
 type state struct {
 	*decoder
 
+	parent *state
+
 	path []string
 
 	pos cue.Value
 
-	types        []cue.Value
 	typeOptional bool
-	kind         cue.Kind
+	usedTypes    cue.Kind
+	allowedTypes cue.Kind
 
 	default_    ast.Expr
 	examples    []ast.Expr
@@ -229,51 +232,56 @@
 	list *ast.ListLit
 }
 
+func (s *state) hasConstraints() bool {
+	return len(s.conjuncts) > 0 ||
+		len(s.patterns) > 0 ||
+		s.title != "" ||
+		s.description != "" ||
+		s.obj != nil ||
+		s.list != nil
+}
+
+const allTypes = cue.NullKind | cue.BoolKind | cue.NumberKind | cue.IntKind |
+	cue.StringKind | cue.ListKind | cue.StructKind
+
 // finalize constructs a CUE type from the collected constraints.
 func (s *state) finalize() (e ast.Expr) {
-	if s.typeOptional || s.kind != 0 {
-		if len(s.types) > 1 {
-			s.errf(s.pos, "constraints require specific type")
-		}
-		s.types = nil
-	}
-
 	conjuncts := []ast.Expr{}
 	disjuncts := []ast.Expr{}
-	for _, n := range s.types {
-		add := func(e ast.Expr) {
-			disjuncts = append(disjuncts, setPos(e, n))
-		}
-		str, ok := s.strValue(n)
-		if !ok {
-			s.errf(n, "type value should be a string")
-			return
-		}
-		switch str {
-		case "null":
+
+	add := func(e ast.Expr) {
+		disjuncts = append(disjuncts, e) // TODO: use setPos
+	}
+
+	types := s.allowedTypes &^ s.usedTypes
+	if types == allTypes {
+		add(ast.NewIdent("_"))
+		types = 0
+	}
+	if types&cue.FloatKind != 0 {
+		add(ast.NewIdent("number"))
+		types &^= cue.IntKind
+	}
+	for types != 0 {
+		k := cue.Kind(1 << uint(bits.TrailingZeros(uint(types))))
+		types &^= k
+
+		switch k {
+		case cue.NullKind:
 			// TODO: handle OpenAPI restrictions.
 			add(ast.NewIdent("null"))
-		case "boolean":
+		case cue.BoolKind:
 			add(ast.NewIdent("bool"))
-		case "string":
+		case cue.StringKind:
 			add(ast.NewIdent("string"))
-		case "number":
-			add(ast.NewIdent("number"))
-		case "integer":
+		case cue.IntKind:
 			add(ast.NewIdent("int"))
-		case "array":
-			if s.kind&cue.ListKind == 0 {
-				add(ast.NewList(&ast.Ellipsis{}))
-			}
-		case "object":
+		case cue.ListKind:
+			add(ast.NewList(&ast.Ellipsis{}))
+		case cue.StructKind:
 			add(ast.NewStruct(&ast.Ellipsis{}))
-		default:
-			s.errf(n, "unknown type %q", n)
 		}
 	}
-	if len(disjuncts) > 0 {
-		conjuncts = append(conjuncts, ast.NewBinExpr(token.OR, disjuncts...))
-	}
 
 	conjuncts = append(conjuncts, s.conjuncts...)
 
@@ -284,17 +292,22 @@
 		conjuncts = append(conjuncts, s.obj)
 	}
 
-	if len(conjuncts) == 0 {
-		return ast.NewString(fmt.Sprint(s.pos))
+	if len(conjuncts) > 0 {
+		disjuncts = append(disjuncts,
+			ast.NewBinExpr(token.AND, conjuncts...))
 	}
 
-	e = ast.NewBinExpr(token.AND, conjuncts...)
+	if len(disjuncts) == 0 {
+		e = &ast.BottomLit{}
+	} else {
+		e = ast.NewBinExpr(token.OR, disjuncts...)
+	}
 
 	if s.default_ != nil {
 		// check conditions where default can be skipped.
 		switch x := s.default_.(type) {
 		case *ast.ListLit:
-			if s.kind == cue.ListKind && len(x.Elts) == 0 {
+			if s.usedTypes == cue.ListKind && len(x.Elts) == 0 {
 				return e
 			}
 		}
@@ -303,6 +316,11 @@
 	return e
 }
 
+func isAny(s ast.Expr) bool {
+	i, ok := s.(*ast.Ident)
+	return ok && i.Name == "_"
+}
+
 func (s *state) comment() *ast.CommentGroup {
 	// Create documentation.
 	doc := strings.TrimSpace(s.title)
@@ -327,18 +345,30 @@
 	}
 }
 
-func (s *state) add(e ast.Expr) {
-	s.conjuncts = append(s.conjuncts, e)
+func (s *state) addConjunct(e ast.Expr) {
+	if !isAny(e) {
+		s.conjuncts = append(s.conjuncts, e)
+	}
 }
 
 func (s *state) schema(n cue.Value) ast.Expr {
-	expr, _ := s.schemaState(n)
+	expr, _ := s.schemaState(n, allTypes, false)
 	// TODO: report unused doc.
 	return expr
 }
 
-func (s *state) schemaState(n cue.Value) (ast.Expr, *state) {
-	state := &state{path: s.path, pos: n, decoder: s.decoder}
+// schemaState is a low-level API for schema. isLogical specifies whether the
+// caller is a logical operator like anyOf, allOf, oneOf, or not.
+func (s *state) schemaState(n cue.Value, types cue.Kind, isLogical bool) (ast.Expr, *state) {
+	state := &state{
+		decoder:      s.decoder,
+		allowedTypes: types,
+		path:         s.path,
+		pos:          n,
+	}
+	if isLogical {
+		state.parent = s
+	}
 
 	if n.Kind() != cue.StructKind {
 		return s.errf(n, "schema expects mapping node, found %s", n.Kind()), state
@@ -365,7 +395,10 @@
 }
 
 func (s *state) value(n cue.Value) ast.Expr {
-	switch n.Kind() {
+	k := n.Kind()
+	s.usedTypes |= k
+	s.allowedTypes &= k
+	switch k {
 	case cue.ListKind:
 		a := []ast.Expr{}
 		for i, _ := n.List(); i.Next(); {
@@ -409,10 +442,16 @@
 	}
 }
 
-func list(n cue.Value) (a []cue.Value) {
+func (s *state) listItems(name string, n cue.Value, allowEmpty bool) (a []cue.Value) {
+	if n.Kind() != cue.ListKind {
+		s.errf(n, `value of %q must be an array, found %v`, name, n.Kind())
+	}
 	for i, _ := n.List(); i.Next(); {
 		a = append(a, i.Value())
 	}
+	if !allowEmpty && len(a) == 0 {
+		s.errf(n, `array for %q must be non-empty`, name)
+	}
 	return a
 }
 
diff --git a/encoding/jsonschema/testdata/list.txtar b/encoding/jsonschema/testdata/list.txtar
index e76ca8e..f02810e 100644
--- a/encoding/jsonschema/testdata/list.txtar
+++ b/encoding/jsonschema/testdata/list.txtar
@@ -3,20 +3,24 @@
 
 properties:
   foo:
+    type: array
     items:
       type: string
 
   tuple:
+    type: array
     items:
       - type: string
       - type: integer
       - const: 2
 
   has:
+    type: array
     contains:
       const: 3
 
   size:
+    type: array
     minItems: 3
     maxItems: 9
     uniqueItems: true
diff --git a/encoding/jsonschema/testdata/num.txtar b/encoding/jsonschema/testdata/num.txtar
index 77236a1..a7e028a 100644
--- a/encoding/jsonschema/testdata/num.txtar
+++ b/encoding/jsonschema/testdata/num.txtar
@@ -13,10 +13,12 @@
         "maximum": 3
     },
     "exclusive": {
+        "type": "number",
         "exclusiveMinimum": 2,
         "exclusiveMaximum": 3
     },
     "cents": {
+      "type": "number",
       "multipleOf": 0.05
     }
   },
diff --git a/encoding/jsonschema/testdata/type.txtar b/encoding/jsonschema/testdata/type.txtar
index cd97a78..e851653 100644
--- a/encoding/jsonschema/testdata/type.txtar
+++ b/encoding/jsonschema/testdata/type.txtar
@@ -18,7 +18,10 @@
     "numOrList": {
       "oneOf": [
         { "type": "number" },
-        { "items": { "type": "number" } }
+        {
+          "type": "array",
+          "items": { "type": "number" }
+        }
       ],
       "default": [ 1, 2, 3 ]
     }
@@ -30,9 +33,9 @@
 // Main schema
 Schema :: {
 	// an integer or string.
-	intString?: string | int | bool | [...] | null
+	intString?: null | bool | int | string | [...]
 	object?:    {
-			...
+		...
 	} | *{
 		foo: "bar"
 		baz: 1.3
diff --git a/encoding/jsonschema/testdata/typedis.txtar b/encoding/jsonschema/testdata/typedis.txtar
new file mode 100644
index 0000000..d718d0c
--- /dev/null
+++ b/encoding/jsonschema/testdata/typedis.txtar
@@ -0,0 +1,56 @@
+-- type.json --
+{
+  "type": "object",
+  "title": "Main schema",
+
+  "properties": {
+    "intOrString1": {
+      "type": [ "string", "integer" ]
+    },
+    "intOrString2": {
+      "oneOf": [
+          { "type": "integer" },
+          { "type": "string" }
+      ]
+    },
+    "intOrString3": {
+      "anyOf": [
+          { "type": "integer" },
+          { "type": "string" }
+      ]
+    },
+
+    "disjunction": {
+      "oneOf": [
+        {
+            "anyOf": [
+                { "type": "integer" },
+                { "type": "string" }
+            ]
+        },
+        {
+            "type": "integer",
+            "minimum": 3
+        }
+      ]
+    },
+
+
+    "empty": {
+        "allOf": [
+            { "type": "object" },
+            { "type": "string" }
+        ]
+    }
+  }
+}
+-- out.cue --
+// Main schema
+Schema :: {
+	intOrString1?: int | string
+	intOrString2?: int | string
+	intOrString3?: int | string
+	disjunction?:  int | string | >=3
+	empty?:        _|_
+	...
+}