cue: keep sane references for embedded disjunctions in Expr

In Expr, simulate a struct with just the fields so that embedded
disjunctions with references to such fields will direct to the unmerged
values of these references.

This also addes OpenAPI tests, which relies on this functionality.

Change-Id: I5d127fee1389a88a45f3705ad1a0c929ee1c2bb0
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9881
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cue/types.go b/cue/types.go
index 32af926..7957c15 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -2383,38 +2383,34 @@
 		op = CallOp
 
 	case *adt.StructLit:
-		// Simulate old embeddings.
-		envEmbed := &adt.Environment{
-			Up:     env,
-			Vertex: v.v,
-		}
+		hasEmbed := false
 		fields := []adt.Decl{}
-		ctx := v.ctx()
 		for _, d := range x.Decls {
-			switch x := d.(type) {
+			switch d.(type) {
 			default:
 				fields = append(fields, d)
 			case adt.Value:
 				fields = append(fields, d)
 			case adt.Expr:
-				// embedding
-				n := &adt.Vertex{Label: v.v.Label}
-				c := adt.MakeRootConjunct(envEmbed, x)
-				n.AddConjunct(c)
-				n.Finalize(ctx)
-				n.Parent = v.v.Parent
-				a = append(a, makeValue(v.idx, n, v.parent_))
+				hasEmbed = true
 			}
 		}
-		if len(a) == 0 {
+
+		if !hasEmbed {
 			a = append(a, v)
 			break
 		}
 
+		ctx := v.ctx()
+
+		n := v.v
+
 		if len(fields) > 0 {
-			n := &adt.Vertex{
-				Label: v.v.Label,
+			n = &adt.Vertex{
+				Parent: v.v.Parent,
+				Label:  v.v.Label,
 			}
+
 			s := &adt.StructLit{}
 			if k := v.v.Kind(); k != adt.StructKind && k != BottomKind {
 				// TODO: we should also add such a declaration for embeddings
@@ -2428,10 +2424,36 @@
 			n.AddConjunct(c)
 			n.Finalize(ctx)
 			n.Parent = v.v.Parent
+		}
+
+		// Simulate old embeddings.
+		envEmbed := &adt.Environment{
+			Up:     env,
+			Vertex: n,
+		}
+
+		for _, d := range x.Decls {
+			switch x := d.(type) {
+			case adt.Value:
+			case adt.Expr:
+				// embedding
+				n := &adt.Vertex{Label: v.v.Label}
+				c := adt.MakeRootConjunct(envEmbed, x)
+				n.AddConjunct(c)
+				n.Finalize(ctx)
+				n.Parent = v.v.Parent
+				a = append(a, makeValue(v.idx, n, v.parent_))
+			}
+		}
+
+		// Could be done earlier, but keep struct with fields at end.
+		if len(fields) > 0 {
 			a = append(a, makeValue(v.idx, n, v.parent_))
 		}
 
-		op = adt.AndOp
+		if len(a) > 1 {
+			op = adt.AndOp
+		}
 
 	default:
 		a = append(a, v)
diff --git a/cue/types_test.go b/cue/types_test.go
index ba1494d..eda326b 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -3344,6 +3344,9 @@
 		input: `v: { "foo", #def: 1 }`,
 		want:  `{"foo",#def:1}`,
 	}, {
+		input: `v: { {} | { a: #A, b: #B}, #A: {} | { c: int} }, #B: int | bool`,
+		want:  `&(|({} {a:#A,b:#B}) {#A:({}|{c:int})})`,
+	}, {
 		input: `v: { {c: a}, b: a }, a: int`,
 		want:  `&({c:a} {b:a})`,
 	}, {
diff --git a/encoding/openapi/openapi_test.go b/encoding/openapi/openapi_test.go
index 3f96175..41af380 100644
--- a/encoding/openapi/openapi_test.go
+++ b/encoding/openapi/openapi_test.go
@@ -105,6 +105,14 @@
 		out:    "openapi-norefs.json",
 		config: resolveRefs,
 	}, {
+		in:     "embed.cue",
+		out:    "embed.json",
+		config: defaultConfig,
+	}, {
+		in:     "embed.cue",
+		out:    "embed-norefs.json",
+		config: resolveRefs,
+	}, {
 		in:  "oneof.cue",
 		out: "oneof-funcs.json",
 		config: &openapi.Config{
diff --git a/encoding/openapi/testdata/embed-norefs.json b/encoding/openapi/testdata/embed-norefs.json
new file mode 100644
index 0000000..4274bcc
--- /dev/null
+++ b/encoding/openapi/testdata/embed-norefs.json
@@ -0,0 +1,121 @@
+{
+   "openapi": "3.0.0",
+   "info": {
+      "title": "test",
+      "version": "v1"
+   },
+   "paths": {},
+   "components": {
+      "schemas": {
+         "Foo": {
+            "type": "string"
+         },
+         "LoadBalancerSettings": {
+            "type": "object",
+            "properties": {
+               "consistentHash": {
+                  "type": "object",
+                  "properties": {
+                     "httpHeaderName": {
+                        "type": "string"
+                     }
+                  }
+               },
+               "b": {
+                  "type": "string"
+               }
+            },
+            "oneOf": [
+               {
+                  "not": {
+                     "anyOf": [
+                        {
+                           "required": [
+                              "consistentHash",
+                              "b"
+                           ],
+                           "properties": {
+                              "consistentHash": {
+                                 "oneOf": [
+                                    {
+                                       "not": {
+                                          "anyOf": [
+                                             {
+                                                "required": [
+                                                   "httpHeaderName"
+                                                ]
+                                             }
+                                          ]
+                                       }
+                                    },
+                                    {
+                                       "required": [
+                                          "httpHeaderName"
+                                       ]
+                                    }
+                                 ]
+                              }
+                           }
+                        }
+                     ]
+                  }
+               },
+               {
+                  "required": [
+                     "consistentHash",
+                     "b"
+                  ],
+                  "properties": {
+                     "consistentHash": {
+                        "oneOf": [
+                           {
+                              "not": {
+                                 "anyOf": [
+                                    {
+                                       "required": [
+                                          "httpHeaderName"
+                                       ]
+                                    }
+                                 ]
+                              }
+                           },
+                           {
+                              "required": [
+                                 "httpHeaderName"
+                              ]
+                           }
+                        ]
+                     }
+                  }
+               }
+            ]
+         },
+         "LoadBalancerSettings.ConsistentHashLB": {
+            "type": "object",
+            "properties": {
+               "httpHeaderName": {
+                  "type": "string"
+               }
+            },
+            "oneOf": [
+               {
+                  "not": {
+                     "anyOf": [
+                        {
+                           "required": [
+                              "httpHeaderName"
+                           ]
+                        }
+                     ]
+                  }
+               },
+               {
+                  "required": [
+                     "httpHeaderName"
+                  ]
+               }
+            ]
+         }
+      }
+   }
+}
\ No newline at end of file
diff --git a/encoding/openapi/testdata/embed.cue b/encoding/openapi/testdata/embed.cue
new file mode 100644
index 0000000..c3bc3b4
--- /dev/null
+++ b/encoding/openapi/testdata/embed.cue
@@ -0,0 +1,11 @@
+#Foo: string
+
+#LoadBalancerSettings: {
+	{} | {
+		consistentHash: #ConsistentHashLB
+		b:              #Foo
+	}
+	#ConsistentHashLB: {} | {
+		httpHeaderName: string
+	}
+}
diff --git a/encoding/openapi/testdata/embed.json b/encoding/openapi/testdata/embed.json
new file mode 100644
index 0000000..d88cc2f
--- /dev/null
+++ b/encoding/openapi/testdata/embed.json
@@ -0,0 +1,85 @@
+{
+   "openapi": "3.0.0",
+   "info": {
+      "title": "Generated by cue.",
+      "version": "no version"
+   },
+   "paths": {},
+   "components": {
+      "schemas": {
+         "Foo": {
+            "type": "string"
+         },
+         "LoadBalancerSettings": {
+            "type": "object",
+            "oneOf": [
+               {
+                  "not": {
+                     "anyOf": [
+                        {
+                           "required": [
+                              "consistentHash",
+                              "b"
+                           ],
+                           "properties": {
+                              "consistentHash": {
+                                 "$ref": "#/components/schemas/LoadBalancerSettings.ConsistentHashLB"
+                              },
+                              "b": {
+                                 "$ref": "#/components/schemas/Foo"
+                              }
+                           }
+                        }
+                     ]
+                  }
+               },
+               {
+                  "required": [
+                     "consistentHash",
+                     "b"
+                  ],
+                  "properties": {
+                     "consistentHash": {
+                        "$ref": "#/components/schemas/LoadBalancerSettings.ConsistentHashLB"
+                     },
+                     "b": {
+                        "$ref": "#/components/schemas/Foo"
+                     }
+                  }
+               }
+            ]
+         },
+         "LoadBalancerSettings.ConsistentHashLB": {
+            "type": "object",
+            "oneOf": [
+               {
+                  "not": {
+                     "anyOf": [
+                        {
+                           "required": [
+                              "httpHeaderName"
+                           ],
+                           "properties": {
+                              "httpHeaderName": {
+                                 "type": "string"
+                              }
+                           }
+                        }
+                     ]
+                  }
+               },
+               {
+                  "required": [
+                     "httpHeaderName"
+                  ],
+                  "properties": {
+                     "httpHeaderName": {
+                        "type": "string"
+                     }
+                  }
+               }
+            ]
+         }
+      }
+   }
+}
\ No newline at end of file