internal/core/eval: fix issue 507

Select default when a disjunction is used in
for loop sources or selection.

Also verify defaults are picked according to
spec in other places.

Fixes #507

Change-Id: I9b29213c412f2e881ac41e9d6fcd2a3ad9334690
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7022
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/comprehensions/issue507.txtar b/cue/testdata/comprehensions/issue507.txtar
new file mode 100644
index 0000000..c4b6b38
--- /dev/null
+++ b/cue/testdata/comprehensions/issue507.txtar
@@ -0,0 +1,75 @@
+-- in.cue --
+package x
+
+somelist: [...string] | * []
+// Works just fine
+foo: [
+  for e in somelist {
+    "hello foo: \(e)"
+  }
+]
+
+otherlist: ["something"]
+// Works fine as well
+z: [
+  for e in otherlist {
+    "hello z: \(e)"
+  }
+]
+
+extlist: [...string] | * ["something"]
+bar: [
+  for e in extlist {
+    "hello bar: \(e)"
+  }
+]
+-- out/eval --
+(struct){
+  somelist: (#list){
+  }
+  foo: (#list){
+  }
+  otherlist: (#list){
+    0: (string){ "something" }
+  }
+  z: (#list){
+    0: (string){ "hello z: something" }
+  }
+  extlist: (list){ |(*(#list){
+      0: (string){ "something" }
+    }, (list){
+    }) }
+  bar: (#list){
+    0: (string){ "hello bar: something" }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  somelist: ([
+    ...string,
+  ]|*[])
+  foo: [
+    for _, e in 〈0;somelist〉 {
+      "hello foo: \(〈1;e〉)"
+    },
+  ]
+  otherlist: [
+    "something",
+  ]
+  z: [
+    for _, e in 〈0;otherlist〉 {
+      "hello z: \(〈1;e〉)"
+    },
+  ]
+  extlist: ([
+    ...string,
+  ]|*[
+    "something",
+  ])
+  bar: [
+    for _, e in 〈0;extlist〉 {
+      "hello bar: \(〈1;e〉)"
+    },
+  ]
+}
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index 97b5575..678384c 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -84,6 +84,7 @@
 }
 
 d3: {
+    // TODO(errors): position reporting in structural cycle.
     config: {
         a: b: c: indirect
         indirect: [a.b, null][i]
@@ -228,8 +229,6 @@
 d1.a.b.c.d.t: structural cycle
 d1.r: structural cycle
 d2.a.b.c.d.t: structural cycle
-d3.x.a.b.c: structural cycle
-d3.x.indirect: structural cycle
 e1.a.c: structural cycle
 e1.b.c: structural cycle
 e2.a.c: structural cycle
@@ -501,13 +500,20 @@
   }
   d3: (_|_){
     // [structural cycle]
-    config: (struct){
-      a: (struct){
-        b: (struct){
-          c: (null){ null }
+    config: (_|_){
+      // [structural cycle]
+      a: (_|_){
+        // [structural cycle]
+        b: (_|_){
+          // [structural cycle]
+          c: (_|_){
+            // [structural cycle]
+          }
         }
       }
-      indirect: (null){ null }
+      indirect: (_|_){
+        // [structural cycle]
+      }
       i: (int){ 1 }
     }
     x: (_|_){
@@ -517,16 +523,12 @@
         b: (_|_){
           // [structural cycle]
           c: (_|_){
-            // [structural cycle] d3.x.a.b.c: structural cycle
-            c: (_|_){// 〈2;indirect〉
-            }
+            // [structural cycle]
           }
         }
       }
       indirect: (_|_){
-        // [structural cycle] d3.x.indirect: structural cycle
-        c: (_|_){// 〈2;indirect〉
-        }
+        // [structural cycle]
       }
       i: (int){ 0 }
     }
diff --git a/cue/testdata/disjunctions/operands.txtar b/cue/testdata/disjunctions/operands.txtar
new file mode 100644
index 0000000..3666ce8
--- /dev/null
+++ b/cue/testdata/disjunctions/operands.txtar
@@ -0,0 +1,115 @@
+# This file tests disjunctions used as operands.
+
+-- in.cue --
+
+list: *[1] | [2]
+condition: *true | false
+num: *1 | 2
+object: *{ a: 1 } | { a: 2 }
+
+forLoop: [
+  for e in list {
+    "count: \(e)"
+  }
+]
+
+conditional: {
+    if condition {
+        a: 3
+    }
+    if num < 5 {
+        b: 3
+    }
+}
+
+selector: {
+    a: object.a
+}
+
+index: {
+    a: list[0]
+}
+
+binOp: {
+    a: num + 4
+}
+
+unaryOp: {
+    a: -num
+}
+
+-- out/eval --
+(struct){
+  list: (list){ |(*(#list){
+      0: (int){ 1 }
+    }, (#list){
+      0: (int){ 2 }
+    }) }
+  condition: (bool){ |(*(bool){ true }, (bool){ false }) }
+  num: (int){ |(*(int){ 1 }, (int){ 2 }) }
+  object: (struct){ |(*(struct){
+      a: (int){ 1 }
+    }, (struct){
+      a: (int){ 2 }
+    }) }
+  forLoop: (#list){
+    0: (string){ "count: 1" }
+  }
+  conditional: (struct){
+    a: (int){ 3 }
+    b: (int){ 3 }
+  }
+  selector: (struct){
+    a: (int){ 1 }
+  }
+  index: (struct){
+    a: (int){ 1 }
+  }
+  binOp: (struct){
+    a: (int){ 5 }
+  }
+  unaryOp: (struct){
+    a: (int){ -1 }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  list: (*[
+    1,
+  ]|[
+    2,
+  ])
+  condition: (*true|false)
+  num: (*1|2)
+  object: (*{
+    a: 1
+  }|{
+    a: 2
+  })
+  forLoop: [
+    for _, e in 〈0;list〉 {
+      "count: \(〈1;e〉)"
+    },
+  ]
+  conditional: {
+    if 〈1;condition〉 {
+      a: 3
+    }
+    if (〈1;num〉 < 5) {
+      b: 3
+    }
+  }
+  selector: {
+    a: 〈1;object〉.a
+  }
+  index: {
+    a: 〈1;list〉[0]
+  }
+  binOp: {
+    a: (〈1;num〉 + 4)
+  }
+  unaryOp: {
+    a: -〈1;num〉
+  }
+}
diff --git a/cue/testdata/eval/disjunctions.txtar b/cue/testdata/eval/disjunctions.txtar
index ab8f9ac..6e30441 100644
--- a/cue/testdata/eval/disjunctions.txtar
+++ b/cue/testdata/eval/disjunctions.txtar
@@ -55,7 +55,6 @@
 d100: {
   // Should we allow a selector to imply a struct or list? Would be convenient.
   // This would be a spec change. Disallow for now.
-  // TODO(errors): better error message
   i: null | {bar: 2}
   j: i.bar
 }
@@ -154,8 +153,8 @@
         bar: (int){ 2 }
       }) }
     j: (_|_){
-      // [incomplete] d100.j: incomplete feed source value i (type (null|struct)):
-      //     ./in.cue:59:6
+      // [incomplete] d100.j: unresolved disjunction null | {bar:2} (type (null|struct)):
+      //     ./in.cue:58:6
     }
   }
 }
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 7cb2aa0..bdf1af9 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -606,6 +606,12 @@
 func (c *OpContext) node(x Expr, state VertexStatus) *Vertex {
 	v := c.evalState(x, state)
 
+	v, ok := c.getDefault(v)
+	if !ok {
+		// Error already generated by getDefault.
+		return emptyNode
+	}
+
 	node, ok := v.(*Vertex)
 	if !ok {
 		if isError(v) {