internal/core/adt: introduce HasDefaults for Disjunction

Unlike DisjunctionExpr, there is additional information
needed for Disjunction that cannot be derived from its
context:
If a Disjunction had failing defaults, all remaining terms
should be interpreted as not default in marked disjunction,
instead of terms in an unmarked disjunction. This is
indistinguishable from just NumDefaults and Errors.

Change-Id: I053d825da795c5487b5966df7378fd3cc37bd9a5
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8108
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/adt/default.go b/internal/core/adt/default.go
index ad3382c..3140845 100644
--- a/internal/core/adt/default.go
+++ b/internal/core/adt/default.go
@@ -29,6 +29,10 @@
 func (d *Disjunction) Default() Value {
 	switch d.NumDefaults {
 	case 0:
+		if d.HasDefaults {
+			// empty disjunction
+			return &Bottom{}
+		}
 		return d
 	case 1:
 		return d.Values[0]
@@ -54,6 +58,13 @@
 
 		switch d.NumDefaults {
 		case 0:
+			if d.HasDefaults {
+				v = &Vertex{
+					Parent:    v.Parent,
+					status:    Finalized,
+					BaseValue: &Bottom{},
+				}
+			}
 			return v
 		case 1:
 			w = d.Values[0]
diff --git a/internal/core/adt/disjunct.go b/internal/core/adt/disjunct.go
index b47c32c..aae182e 100644
--- a/internal/core/adt/disjunct.go
+++ b/internal/core/adt/disjunct.go
@@ -88,10 +88,12 @@
 	env         *Environment
 	values      []disjunct
 	numDefaults int
+	hasDefaults bool
 	cloneID     CloseInfo
 }
 
 type disjunct struct {
+	v         *Vertex
 	expr      Expr
 	isDefault bool
 }
@@ -105,7 +107,7 @@
 		if isDef {
 			numDefaults++
 		}
-		a = append(a, disjunct{v.Val, isDef})
+		a = append(a, disjunct{nil, v.Val, isDef})
 	}
 
 	sort.SliceStable(a, func(i, j int) bool {
@@ -113,18 +115,20 @@
 	})
 
 	n.disjunctions = append(n.disjunctions,
-		envDisjunct{env, a, numDefaults, cloneID})
+		envDisjunct{env, a, numDefaults, numDefaults > 0, cloneID})
+
 }
 
 func (n *nodeContext) addDisjunctionValue(env *Environment, x *Disjunction, cloneID CloseInfo) {
 	a := make([]disjunct, 0, len(x.Values))
 
 	for i, v := range x.Values {
-		a = append(a, disjunct{v, i < x.NumDefaults})
+		a = append(a, disjunct{v, nil, i < x.NumDefaults})
 	}
 
 	n.disjunctions = append(n.disjunctions,
-		envDisjunct{env, a, x.NumDefaults, cloneID})
+		envDisjunct{env, a, x.NumDefaults, x.HasDefaults, cloneID})
+
 }
 
 func (n *nodeContext) expandDisjuncts(
@@ -216,8 +220,12 @@
 					cn := dn.clone()
 					*cn.node = snapshotVertex(dn.snapshot)
 
-					c := MakeConjunct(d.env, v.expr, d.cloneID)
-					cn.addExprConjunct(c)
+					if v.v != nil {
+						cn.addValueConjunct(d.env, v.v, d.cloneID)
+					} else {
+						c := MakeConjunct(d.env, v.expr, d.cloneID)
+						cn.addExprConjunct(c)
+					}
 
 					newMode := mode(d, v)
 
@@ -302,7 +310,7 @@
 func mode(d envDisjunct, v disjunct) defaultMode {
 	var mode defaultMode
 	switch {
-	case d.numDefaults == 0:
+	case !d.hasDefaults:
 		mode = maybeDefault
 	case v.isDefault:
 		mode = isDefault
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index fc62de6..0365249 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -569,20 +569,28 @@
 func (n *nodeContext) createDisjunct() *Disjunction {
 	a := make([]*Vertex, len(n.disjuncts))
 	p := 0
+	hasDefaults := false
 	for i, x := range n.disjuncts {
 		v := new(Vertex)
 		*v = x.result
-		if x.defaultMode == isDefault {
+		switch x.defaultMode {
+		case isDefault:
 			a[i] = a[p]
 			a[p] = v
 			p++
-		} else {
+			hasDefaults = true
+
+		case notDefault:
+			hasDefaults = true
+			fallthrough
+		case maybeDefault:
 			a[i] = v
 		}
 	}
 	return &Disjunction{
 		Values:      a,
 		NumDefaults: p,
+		HasDefaults: hasDefaults,
 	}
 }
 
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 4563e88..d8f5118 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -1323,6 +1323,7 @@
 
 	// NumDefaults indicates the number of default values.
 	NumDefaults int
+	HasDefaults bool
 }
 
 func (x *Disjunction) Source() ast.Node { return x.Src }