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")