internal/core/adt: don't pre-expand disjunctions

This also simplifies combining defeault modes, which
really is just max, given the proper ordering of
modes.

Change-Id: I468c4c343e53ae86a5a14b6a477c8a389a212855
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8109
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/testdata/basicrewrite/014_disjunctions.txtar b/cue/testdata/basicrewrite/014_disjunctions.txtar
index a691403..8165bb1 100644
--- a/cue/testdata/basicrewrite/014_disjunctions.txtar
+++ b/cue/testdata/basicrewrite/014_disjunctions.txtar
@@ -84,7 +84,7 @@
   o1: (int){ |((int){ 1 }, (int){ 2 }, (int){ 3 }) }
   o2: (int){ 1 }
   o3: (int){ 2 }
-  o4: (int){ |((int){ 2 }, (int){ 1 }, (int){ 3 }) }
+  o4: (int){ |((int){ 1 }, (int){ 2 }, (int){ 3 }) }
   o5: (int){ |(*(int){ 2 }, (int){ 1 }, (int){ 3 }) }
   o6: (int){ |((int){ 1 }, (int){ 2 }, (int){ 3 }) }
   o7: (int){ |((int){ 2 }, (int){ 3 }) }
diff --git a/cue/testdata/cycle/025_cannot_resolve_references_that_would_be_ambiguous.txtar b/cue/testdata/cycle/025_cannot_resolve_references_that_would_be_ambiguous.txtar
index 885c159..1213209 100644
--- a/cue/testdata/cycle/025_cannot_resolve_references_that_would_be_ambiguous.txtar
+++ b/cue/testdata/cycle/025_cannot_resolve_references_that_would_be_ambiguous.txtar
@@ -63,7 +63,7 @@
   }
   a3: (int){ 1 }
   b1: (int){ |((int){ 0 }, (int){ 1 }) }
-  b2: (int){ |((int){ 1 }, (int){ 0 }) }
+  b2: (int){ |((int){ 0 }, (int){ 1 }) }
   c1: (struct){ |((struct){
       a: (int){ 1 }
       b: (int){ 2 }
diff --git a/cue/testdata/eval/disjunctions.txtar b/cue/testdata/eval/disjunctions.txtar
index fa6adb6..31eba44 100644
--- a/cue/testdata/eval/disjunctions.txtar
+++ b/cue/testdata/eval/disjunctions.txtar
@@ -95,6 +95,11 @@
     a: #A
 }
 
+cross: {
+    a: *"word" | string
+    a:  string | *"word"
+}
+
 
 d100: {
   // Should we allow a selector to imply a struct or list? Would be convenient.
@@ -234,13 +239,16 @@
       }, (#struct){
       }) }
   }
+  cross: (struct){
+    a: (string){ |(*(string){ "word" }, (string){ string }) }
+  }
   d100: (struct){
     i: ((null|struct)){ |((null){ null }, (struct){
         bar: (int){ 2 }
       }) }
     j: (_|_){
       // [incomplete] d100.j: unresolved disjunction null | {bar:2} (type (null|struct)):
-      //     ./in.cue:102:6
+      //     ./in.cue:107:6
     }
   }
 }
@@ -393,6 +401,10 @@
     a: 怈0;b怉
     a: 怈0;#A怉
   }
+  cross: {
+    a: (*"word"|string)
+    a: (string|*"word")
+  }
   d100: {
     i: (null|{
       bar: 2
diff --git a/internal/core/adt/disjunct.go b/internal/core/adt/disjunct.go
index aae182e..77cc0a1 100644
--- a/internal/core/adt/disjunct.go
+++ b/internal/core/adt/disjunct.go
@@ -15,8 +15,6 @@
 package adt
 
 import (
-	"sort"
-
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/token"
 )
@@ -86,48 +84,30 @@
 
 type envDisjunct struct {
 	env         *Environment
-	values      []disjunct
-	numDefaults int
-	hasDefaults bool
 	cloneID     CloseInfo
-}
-
-type disjunct struct {
-	v         *Vertex
-	expr      Expr
-	isDefault bool
+	expr        *DisjunctionExpr
+	value       *Disjunction
+	hasDefaults bool
 }
 
 func (n *nodeContext) addDisjunction(env *Environment, x *DisjunctionExpr, cloneID CloseInfo) {
-	a := make([]disjunct, 0, len(x.Values))
 
+	// TODO: precompute
 	numDefaults := 0
 	for _, v := range x.Values {
 		isDef := v.Default // || n.hasDefaults(env, v.Val)
 		if isDef {
 			numDefaults++
 		}
-		a = append(a, disjunct{nil, v.Val, isDef})
 	}
 
-	sort.SliceStable(a, func(i, j int) bool {
-		return !a[j].isDefault && a[i].isDefault != a[j].isDefault
-	})
-
 	n.disjunctions = append(n.disjunctions,
-		envDisjunct{env, a, numDefaults, numDefaults > 0, cloneID})
-
+		envDisjunct{env, cloneID, x, nil, numDefaults > 0})
 }
 
 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, nil, i < x.NumDefaults})
-	}
-
 	n.disjunctions = append(n.disjunctions,
-		envDisjunct{env, a, x.NumDefaults, x.HasDefaults, cloneID})
+		envDisjunct{env, cloneID, nil, x, x.HasDefaults})
 
 }
 
@@ -216,22 +196,33 @@
 			}
 
 			for _, dn := range a {
-				for _, v := range d.values {
-					cn := dn.clone()
-					*cn.node = snapshotVertex(dn.snapshot)
+				switch {
+				case d.expr != nil:
+					for _, v := range d.expr.Values {
+						cn := dn.clone()
+						*cn.node = snapshotVertex(dn.snapshot)
 
-					if v.v != nil {
-						cn.addValueConjunct(d.env, v.v, d.cloneID)
-					} else {
-						c := MakeConjunct(d.env, v.expr, d.cloneID)
+						c := MakeConjunct(d.env, v.Val, d.cloneID)
 						cn.addExprConjunct(c)
+
+						newMode := mode(d.hasDefaults, v.Default)
+						cn.defaultMode = combineDefault(dn.defaultMode, newMode)
+
+						cn.expandDisjuncts(state, n, newMode, true)
 					}
 
-					newMode := mode(d, v)
+				case d.value != nil:
+					for i, v := range d.value.Values {
+						cn := dn.clone()
+						*cn.node = snapshotVertex(dn.snapshot)
 
-					cn.expandDisjuncts(state, n, newMode, true)
+						cn.addValueConjunct(d.env, v, d.cloneID)
 
-					cn.defaultMode = combineDefault(dn.defaultMode, newMode)
+						newMode := mode(d.hasDefaults, i < d.value.NumDefaults)
+						cn.defaultMode = combineDefault(dn.defaultMode, newMode)
+
+						cn.expandDisjuncts(state, n, newMode, true)
+					}
 				}
 			}
 
@@ -243,6 +234,7 @@
 
 			if len(n.disjuncts) == 0 {
 				n.makeError()
+				break
 			}
 		}
 
@@ -271,6 +263,9 @@
 			for _, v := range p.disjuncts {
 				if Equal(n.ctx, &v.result, &d.result) {
 					n.ctx.Unifier.freeNodeContext(n)
+					if d.defaultMode == isDefault {
+						v.defaultMode = isDefault
+					}
 					continue outer
 				}
 			}
@@ -307,12 +302,12 @@
 	n.node.SetValue(n.ctx, Finalized, b)
 }
 
-func mode(d envDisjunct, v disjunct) defaultMode {
+func mode(hasDefault, marked bool) defaultMode {
 	var mode defaultMode
 	switch {
-	case !d.hasDefaults:
+	case !hasDefault:
 		mode = maybeDefault
-	case v.isDefault:
+	case marked:
 		mode = isDefault
 	default:
 		mode = notDefault
@@ -379,8 +374,8 @@
 
 const (
 	maybeDefault defaultMode = iota
-	notDefault
 	isDefault
+	notDefault
 )
 
 // combineDefaults combines default modes for unifying conjuncts.
@@ -391,24 +386,9 @@
 // U2: (v1, d1) & (v2, d2) => (v1&v2, d1&d2)
 func combineDefault(a, b defaultMode) defaultMode {
 	if a > b {
-		a, b = b, a
+		return a
 	}
-	switch {
-	case a == maybeDefault && b == maybeDefault:
-		return maybeDefault
-	case a == maybeDefault && b == notDefault:
-		return notDefault
-	case a == maybeDefault && b == isDefault:
-		return isDefault
-	case a == notDefault && b == notDefault:
-		return notDefault
-	case a == notDefault && b == isDefault:
-		return notDefault
-	case a == isDefault && b == isDefault:
-		return isDefault
-	default:
-		panic("unreachable")
-	}
+	return b
 }
 
 // disjunctError returns a compound error for a failed disjunction.