internal/core/eval: keep semantics for [K]: T

We were contemplating to redefine [string]: T to
be JSON Schema semantics. For this purpose, tool
fix rewrote it into a form that means the same in
both interpretations.

In the end, the original CUE semantics seem useful
enough to keep with the benefit of not breaking
people. We now anticipate to add the JSON semantics
using the notation: `[...K]: V`. With this approach, any
additional fields are always indicated with `...`.

Change-Id: I09dd4cd0bedc87917a29edba959e30a7b22cac5e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7906
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/testdata/eval/bulk.txtar b/cue/testdata/eval/bulk.txtar
index df71f2b..fd65e50 100644
--- a/cue/testdata/eval/bulk.txtar
+++ b/cue/testdata/eval/bulk.txtar
@@ -45,11 +45,15 @@
   a: (struct){
     foo: (struct){
       a: (int){ 1 }
+      b: (int){ 1 }
+      name: (string){ "foo" }
     }
   }
   d: (struct){
     foo: (struct){
       a: (int){ 1 }
+      b: (int){ 1 }
+      name: (string){ "foo" }
     }
     foobar: (struct){
       c: (int){ 2 }
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 43c896a..4e86611 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -1648,7 +1648,6 @@
 		childEnv.Deref = env.Deref
 	}
 
-	var hasOther, hasBulk adt.Node
 	hasEmbed := false
 
 	opt := fieldSet{pos: s, env: childEnv, id: closeID}
@@ -1669,21 +1668,17 @@
 		case *adt.DynamicField:
 			n.aStruct = s
 			n.aStructID = closeID
-			hasOther = x
 			n.dynamicFields = append(n.dynamicFields, envDynamic{childEnv, x, closeID, nil})
 			opt.AddDynamic(ctx, childEnv, x)
 
 		case *adt.ForClause:
-			hasOther = x
 			n.forClauses = append(n.forClauses, envYield{childEnv, x, closeID, nil})
 
 		case adt.Yielder:
-			hasOther = x
 			n.ifClauses = append(n.ifClauses, envYield{childEnv, x, closeID, nil})
 
 		case adt.Expr:
 			hasEmbed = true
-			hasOther = x
 
 			// add embedding to optional
 
@@ -1697,7 +1692,6 @@
 		case *adt.BulkOptionalField:
 			n.aStruct = s
 			n.aStructID = closeID
-			hasBulk = x
 			opt.AddBulk(ctx, x)
 
 		case *adt.Ellipsis:
@@ -1710,10 +1704,6 @@
 		}
 	}
 
-	if hasBulk != nil && hasOther != nil {
-		n.addErr(ctx.Newf("cannot mix bulk optional fields with dynamic fields, embeddings, or comprehensions within the same struct"))
-	}
-
 	if !hasEmbed {
 		n.aStruct = s
 		n.aStructID = closeID
diff --git a/internal/core/eval/optionals.go b/internal/core/eval/optionals.go
index b7ea096..86c9227 100644
--- a/internal/core/eval/optionals.go
+++ b/internal/core/eval/optionals.go
@@ -35,10 +35,9 @@
 	// Required fields are marked as empty
 	fields []field
 
-	// literal map[adt.Feature][]adt.Node
-
 	// excluded are all literal fields that already exist.
-	bulk       []bulkField
+	bulk []bulkField
+
 	additional []adt.Expr
 	isOpen     bool // has a ...
 }
@@ -81,8 +80,9 @@
 }
 
 type bulkField struct {
-	check fieldMatcher
-	expr  adt.Node // *adt.BulkOptionalField // Conjunct
+	check      fieldMatcher
+	expr       adt.Node // *adt.BulkOptionalField // Conjunct
+	additional bool     // used with ...
 }
 
 func (o *fieldSet) Accept(c *adt.OpContext, f adt.Feature) bool {
@@ -107,18 +107,17 @@
 	env := o.env
 
 	// Match normal fields
-	p := 0
-	for ; p < len(o.fields); p++ {
-		if o.fields[p].label == arc.Label {
-			break
+	matched := false
+outer:
+	for _, f := range o.fields {
+		if f.label == arc.Label {
+			for _, e := range f.optional {
+				arc.AddConjunct(adt.MakeConjunct(env, e, o.id))
+			}
+			matched = true
+			break outer
 		}
 	}
-	if p < len(o.fields) {
-		for _, e := range o.fields[p].optional {
-			arc.AddConjunct(adt.MakeConjunct(env, e, o.id))
-		}
-		return
-	}
 
 	if !arc.Label.IsRegular() {
 		return
@@ -130,8 +129,10 @@
 	bulkEnv.Cycles = nil
 
 	// match bulk optional fields / pattern properties
-	matched := false
 	for _, f := range o.bulk {
+		if matched && f.additional {
+			continue
+		}
 		if f.check.Match(c, arc.Label) {
 			matched = true
 			if f.expr != nil {
@@ -179,7 +180,7 @@
 
 func (o *fieldSet) AddDynamic(c *adt.OpContext, env *adt.Environment, x *adt.DynamicField) {
 	// not in bulk: count as regular field?
-	o.bulk = append(o.bulk, bulkField{dynamicMatcher{env, x.Key}, nil})
+	o.bulk = append(o.bulk, bulkField{dynamicMatcher{env, x.Key}, nil, false})
 }
 
 func (o *fieldSet) AddBulk(c *adt.OpContext, x *adt.BulkOptionalField) {
@@ -190,7 +191,7 @@
 	}
 
 	if m := o.getMatcher(c, v); m != nil {
-		o.bulk = append(o.bulk, bulkField{m, x})
+		o.bulk = append(o.bulk, bulkField{m, x, false})
 	}
 }
 
diff --git a/tools/fix/fix.go b/tools/fix/fix.go
index ce82077..98f5ffd 100644
--- a/tools/fix/fix.go
+++ b/tools/fix/fix.go
@@ -26,7 +26,6 @@
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/ast/astutil"
 	"cuelang.org/go/cue/token"
-	"cuelang.org/go/internal"
 )
 
 // File applies fixes to f and returns it. It alters the original f.
@@ -49,29 +48,6 @@
 		return true
 	}, nil).(*ast.File)
 
-	// Isolate bulk optional fields into a single struct.
-	ast.Walk(f, func(n ast.Node) bool {
-		var decls []ast.Decl
-		switch x := n.(type) {
-		case *ast.StructLit:
-			decls = x.Elts
-		case *ast.File:
-			decls = x.Decls
-		}
-
-		if len(decls) <= 1 {
-			return true
-		}
-
-		for i, d := range decls {
-			if internal.IsBulkField(d) {
-				decls[i] = internal.EmbedStruct(ast.NewStruct(d))
-			}
-		}
-
-		return true
-	}, nil)
-
 	// Rewrite an old-style alias to a let clause.
 	ast.Walk(f, func(n ast.Node) bool {
 		var decls []ast.Decl
diff --git a/tools/fix/fix_test.go b/tools/fix/fix_test.go
index 9ad093b..83c1976 100644
--- a/tools/fix/fix_test.go
+++ b/tools/fix/fix_test.go
@@ -85,48 +85,6 @@
 		out: `
 let y = foo
 `,
-	}, {
-		name: "wrap bulk fields",
-		in: `
-		a: {
-			[allGood]: int
-		}
-		b: {
-			a: int
-
-			b: [string]: string
-			[string]: wrap
-
-			// Comment
-			[string]: wrap
-			...
-		}
-		c: {
-			a: int
-
-			{[string]: alreadyGreat}
-		}
-
-		`,
-		out: `a: {
-	[allGood]: int
-}
-b: {
-	a: int
-
-	b: [string]: string
-	{[string]: wrap}
-
-	// Comment
-	{[string]: wrap}
-	...
-}
-c: {
-	a: int
-
-	{[string]: alreadyGreat}
-}
-`,
 		// 	}, {
 		// 		name: "slice",
 		// 		in: `package foo