internal/core/adt: don't count optional fields as proof of struct

Technically, optional fields are not concrete fields yet and
should not be taken as proof that a value is a struct.

Fixes #783

Change-Id: I866f9ac0316b6de5079a0c486e54782cb63e5dde
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8841
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/scalars/emptystruct.txtar b/cue/testdata/scalars/emptystruct.txtar
new file mode 100644
index 0000000..fda3eca
--- /dev/null
+++ b/cue/testdata/scalars/emptystruct.txtar
@@ -0,0 +1,222 @@
+// Issue #783
+
+-- in.cue --
+elipsis: {
+    test1: {
+        string
+        #foo: "bar"
+    }
+
+    #Def: {
+        ...
+        #foo: string
+        _
+    }
+    check: test1 & #Def
+}
+
+bulk: {
+    test1: {
+        string
+        #foo: "bar"
+    }
+
+    #Def: {
+        [string]: int
+        #foo: string
+        _
+    }
+    check: test1 & #Def
+}
+
+optional: {
+    test1: {
+        string
+        #foo: "bar"
+    }
+
+    #Def: {
+        bar?: int
+        #foo: string
+        _
+    }
+    check: test1 & #Def
+}
+
+issue783: {
+    test1: {
+        string
+        #foo: "bar"
+    }
+
+    test2: {
+        hello: "world"
+        #foo: "bar"
+    }
+
+
+    #Def1: {
+        ...
+        #foo: string
+    } | {
+        string
+        #foo: string
+    }
+    check1a: test1 & #Def1
+    check1b: test2 & #Def1
+
+    #Def2: {
+        ...
+        #foo: string
+        _
+    }
+    check2a: test1 & #Def2
+    check2b: test2 & #Def2
+}
+-- out/eval --
+(struct){
+  elipsis: (struct){
+    test1: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    #Def: (_){
+      _
+      #foo: (string){ string }
+    }
+    check: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+  }
+  bulk: (struct){
+    test1: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    #Def: (_){
+      _
+      #foo: (string){ string }
+    }
+    check: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+  }
+  optional: (struct){
+    test1: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    #Def: (_){
+      _
+      #foo: (string){ string }
+    }
+    check: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+  }
+  issue783: (struct){
+    test1: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    test2: (struct){
+      hello: (string){ "world" }
+      #foo: (string){ "bar" }
+    }
+    #Def1: ((string|struct)){ |((#struct){
+        #foo: (string){ string }
+      }, (string){
+        string
+        #foo: (string){ string }
+      }) }
+    check1a: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    check1b: (#struct){
+      hello: (string){ "world" }
+      #foo: (string){ "bar" }
+    }
+    #Def2: (_){
+      _
+      #foo: (string){ string }
+    }
+    check2a: (string){
+      string
+      #foo: (string){ "bar" }
+    }
+    check2b: (#struct){
+      hello: (string){ "world" }
+      #foo: (string){ "bar" }
+    }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  elipsis: {
+    test1: {
+      string
+      #foo: "bar"
+    }
+    #Def: {
+      ...
+      #foo: string
+      _
+    }
+    check: (〈0;test1〉 & 〈0;#Def〉)
+  }
+  bulk: {
+    test1: {
+      string
+      #foo: "bar"
+    }
+    #Def: {
+      [string]: int
+      #foo: string
+      _
+    }
+    check: (〈0;test1〉 & 〈0;#Def〉)
+  }
+  optional: {
+    test1: {
+      string
+      #foo: "bar"
+    }
+    #Def: {
+      bar?: int
+      #foo: string
+      _
+    }
+    check: (〈0;test1〉 & 〈0;#Def〉)
+  }
+  issue783: {
+    test1: {
+      string
+      #foo: "bar"
+    }
+    test2: {
+      hello: "world"
+      #foo: "bar"
+    }
+    #Def1: ({
+      ...
+      #foo: string
+    }|{
+      string
+      #foo: string
+    })
+    check1a: (〈0;test1〉 & 〈0;#Def1〉)
+    check1b: (〈0;test2〉 & 〈0;#Def1〉)
+    #Def2: {
+      ...
+      #foo: string
+      _
+    }
+    check2a: (〈0;test1〉 & 〈0;#Def2〉)
+    check2b: (〈0;test2〉 & 〈0;#Def2〉)
+  }
+}
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index 330c54b..393b69d 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -1702,12 +1702,6 @@
 		case *Field:
 			// handle in next iteration.
 
-		case *OptionalField:
-			if x.Label.IsString() {
-				n.aStruct = s
-				n.aStructID = closeInfo
-			}
-
 		case *DynamicField:
 			n.aStruct = s
 			n.aStructID = closeInfo
@@ -1731,13 +1725,10 @@
 			// push and opo embedding type.
 			n.addExprConjunct(MakeConjunct(childEnv, x, id))
 
-		case *BulkOptionalField:
-			n.aStruct = s
-			n.aStructID = closeInfo
-
-		case *Ellipsis:
-			n.aStruct = s
-			n.aStructID = closeInfo
+		case *OptionalField, *BulkOptionalField, *Ellipsis:
+			// Nothing to do here. Note that the precense of these fields do not
+			// excluded embedded scalars: only when they match actual fields
+			// does it exclude those.
 
 		default:
 			panic("unreachable")