cue/parser: attach attributes to correct field

Right now they are attached to the first label
in a label sequence, but it should be the last.

To avoid any doubt, make the formatter insert
curly braces for such cases.

This also fixes a bug where attributes at the end
of a multi-label field were attached to the
wrong field and discarded when formatting.

Change-Id: If19e2b95dd7c5c69a3523ce46e8a30a47609d70c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2706
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/format/node.go b/cue/format/node.go
index 4e75dd6..2ab6395 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -138,7 +138,7 @@
 		// Lbrace does not signal the intend to collapse fields.
 		for n.Label.Pos().IsValid() || f.printer.cfg.simplify {
 			obj, ok := n.Value.(*ast.StructLit)
-			if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) {
+			if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) || len(n.Attrs) > 0 {
 				break
 			}
 
@@ -155,7 +155,7 @@
 			}
 
 			mem, ok := obj.Elts[0].(*ast.Field)
-			if !ok {
+			if !ok || len(mem.Attrs) > 0 {
 				break
 			}
 			entry := labelEntry{mem.Label, mem.Optional != token.NoPos}
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index 98a8f9e..be7b001 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -36,6 +36,8 @@
 		bbbb: 100 @go(Bbbb) /* a */ @xml(,attr) // comment
 	}
 
+	foo: {bar: string @go(-)}
+
 	e:  1 + 2*3
 	e:  1 * 2 * 3 // error
 	e:  >=2 & <=3
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index ccd38bb..f8b61d3 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -36,6 +36,8 @@
         bbbb: 100 @go(Bbbb) /* a */ @xml(,attr) // comment
     }
 
+    foo bar: string @go(-)
+
     e: 1+2*3
     e: 1*2*3 // error
     e: >=2 & <=3
diff --git a/cue/format/testdata/simplify.input b/cue/format/testdata/simplify.input
index 08e374d..c6dd770 100644
--- a/cue/format/testdata/simplify.input
+++ b/cue/format/testdata/simplify.input
@@ -2,4 +2,4 @@
 
 a "B": 42
 
-"a.b" "foo-" "cc_dd": x
\ No newline at end of file
+"a.b" "foo-" "cc_dd": x
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7e0715e..2268450 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -762,7 +762,7 @@
 		a := &ast.Attribute{At: p.pos, Text: p.lit}
 		p.next()
 		c.closeNode(p, a)
-		this.Attrs = append(this.Attrs, a)
+		m.Attrs = append(m.Attrs, a)
 	}
 	p.closeList()
 
@@ -775,7 +775,7 @@
 
 	case token.FOR, token.IF:
 		if !allowComprehension {
-			p.errf(p.pos, "comprehension not alowed for this field")
+			p.errf(p.pos, "comprehension not allowed for this field")
 		}
 		clauses := p.parseComprehensionClauses()
 		return &ast.ComprehensionDecl{
diff --git a/encoding/protobuf/testdata/attributes.proto.out.cue b/encoding/protobuf/testdata/attributes.proto.out.cue
index dc5a1b1..055c6c8 100644
--- a/encoding/protobuf/testdata/attributes.proto.out.cue
+++ b/encoding/protobuf/testdata/attributes.proto.out.cue
@@ -65,7 +65,9 @@
 //  3) Forward attributes from client proxy to server proxy for HTTP requests.
 Attributes: {
 	//  A map of attribute name to its value.
-	attributes <_>: Attributes_AttributeValue
+	attributes: {
+		<_>: Attributes_AttributeValue
+	} @protobuf(1,type=map<string,AttributeValue>)
 }
 
 //  Specifies one attribute value with different type.
@@ -101,7 +103,9 @@
 //  Defines a string map.
 Attributes_StringMap: {
 	//  Holds a set of name/value pairs.
-	entries <_>: string
+	entries: {
+		<_>: string
+	} @protobuf(1,type=map<string,string>)
 }
 
 //  Defines a list of attributes in compressed format optimized for transport.
@@ -116,33 +120,51 @@
 	words?: [...string] @protobuf(1)
 
 	//  Holds attributes of type STRING, DNS_NAME, EMAIL_ADDRESS, URI
-	strings <_>: int32
+	strings: {
+		<_>: int32
+	} @protobuf(2,type=map<sint32,sint32>)
 
 	//  Holds attributes of type INT64
-	int64s <_>: int64
+	int64s: {
+		<_>: int64
+	} @protobuf(3,type=map<sint32,int64>)
 
 	//  Holds attributes of type DOUBLE
-	doubles <_>: float64
+	doubles: {
+		<_>: float64
+	} @protobuf(4,type=map<sint32,double>)
 
 	//  Holds attributes of type BOOL
-	bools <_>: bool
+	bools: {
+		<_>: bool
+	} @protobuf(5,type=map<sint32,bool>)
 
 	//  Holds attributes of type TIMESTAMP
-	timestamps <_>: timestamp.Timestamp
+	timestamps: {
+		<_>: timestamp.Timestamp
+	} @protobuf(6,type=map<sint32,google.protobuf.Timestamp>,"(gogoproto.nullable)=false","(gogoproto.stdtime)")
 
 	//  Holds attributes of type DURATION
-	durations <_>: duration.Duration
+	durations: {
+		<_>: duration.Duration
+	} @protobuf(7,type=map<sint32,google.protobuf.Duration>,"(gogoproto.nullable)=false","(gogoproto.stdduration)")
 
 	//  Holds attributes of type BYTES
-	bytes <_>: bytes
+	bytes: {
+		<_>: bytes
+	} @protobuf(8,type=map<sint32,bytes>)
 
 	//  Holds attributes of type STRING_MAP
-	stringMaps <_>: StringMap
+	stringMaps: {
+		<_>: StringMap
+	} @protobuf(9,type=map<sint32,StringMap>,string_maps,"(gogoproto.nullable)=false")
 }
 
 //  A map of string to string. The keys and values in this map are dictionary
 //  indices (see the [Attributes][istio.mixer.v1.CompressedAttributes] message for an explanation)
 StringMap: {
 	//  Holds a set of name/value pairs.
-	entries <_>: int32
+	entries: {
+		<_>: int32
+	} @protobuf(1,type=map<sint32,sint32>)
 }
diff --git a/encoding/protobuf/testdata/client_config.proto.out.cue b/encoding/protobuf/testdata/client_config.proto.out.cue
index fc7f747..a3db5df 100644
--- a/encoding/protobuf/testdata/client_config.proto.out.cue
+++ b/encoding/protobuf/testdata/client_config.proto.out.cue
@@ -144,7 +144,9 @@
 	//  Map of control configuration indexed by destination.service. This
 	//  is used to support per-service configuration for cases where a
 	//  mixerclient serves multiple services.
-	serviceConfigs <_>: ServiceConfig
+	serviceConfigs: {
+		<_>: ServiceConfig
+	} @protobuf(2,type=map<string,ServiceConfig>,service_configs)
 
 	//  Default destination service name if none was specified in the
 	//  client request.
diff --git a/encoding/protobuf/testdata/gateway.proto.out.cue b/encoding/protobuf/testdata/gateway.proto.out.cue
index d85721e..ac72914 100644
--- a/encoding/protobuf/testdata/gateway.proto.out.cue
+++ b/encoding/protobuf/testdata/gateway.proto.out.cue
@@ -213,7 +213,9 @@
 	//  label search is restricted to the configuration namespace in which the
 	//  the resource is present. In other words, the Gateway resource must
 	//  reside in the same namespace as the gateway workload instance.
-	selector <_>: string
+	selector: {
+		<_>: string
+	} @protobuf(2,type=map<string,string>)
 	selector? <name>: name
 }
 
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
index df7d3ae..e5ee16d 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
@@ -65,7 +65,9 @@
 //  3) Forward attributes from client proxy to server proxy for HTTP requests.
 Attributes: {
 	//  A map of attribute name to its value.
-	attributes <_>: Attributes_AttributeValue
+	attributes: {
+		<_>: Attributes_AttributeValue
+	} @protobuf(1,type=map<string,AttributeValue>)
 }
 
 //  Specifies one attribute value with different type.
@@ -101,7 +103,9 @@
 //  Defines a string map.
 Attributes_StringMap: {
 	//  Holds a set of name/value pairs.
-	entries <_>: string
+	entries: {
+		<_>: string
+	} @protobuf(1,type=map<string,string>)
 }
 
 //  Defines a list of attributes in compressed format optimized for transport.
@@ -116,33 +120,51 @@
 	words?: [...string] @protobuf(1)
 
 	//  Holds attributes of type STRING, DNS_NAME, EMAIL_ADDRESS, URI
-	strings <_>: int32
+	strings: {
+		<_>: int32
+	} @protobuf(2,type=map<sint32,sint32>)
 
 	//  Holds attributes of type INT64
-	int64s <_>: int64
+	int64s: {
+		<_>: int64
+	} @protobuf(3,type=map<sint32,int64>)
 
 	//  Holds attributes of type DOUBLE
-	doubles <_>: float64
+	doubles: {
+		<_>: float64
+	} @protobuf(4,type=map<sint32,double>)
 
 	//  Holds attributes of type BOOL
-	bools <_>: bool
+	bools: {
+		<_>: bool
+	} @protobuf(5,type=map<sint32,bool>)
 
 	//  Holds attributes of type TIMESTAMP
-	timestamps <_>: timestamp.Timestamp
+	timestamps: {
+		<_>: timestamp.Timestamp
+	} @protobuf(6,type=map<sint32,google.protobuf.Timestamp>,"(gogoproto.nullable)=false","(gogoproto.stdtime)")
 
 	//  Holds attributes of type DURATION
-	durations <_>: duration.Duration
+	durations: {
+		<_>: duration.Duration
+	} @protobuf(7,type=map<sint32,google.protobuf.Duration>,"(gogoproto.nullable)=false","(gogoproto.stdduration)")
 
 	//  Holds attributes of type BYTES
-	bytes <_>: bytes
+	bytes: {
+		<_>: bytes
+	} @protobuf(8,type=map<sint32,bytes>)
 
 	//  Holds attributes of type STRING_MAP
-	stringMaps <_>: StringMap
+	stringMaps: {
+		<_>: StringMap
+	} @protobuf(9,type=map<sint32,StringMap>,string_maps,"(gogoproto.nullable)=false")
 }
 
 //  A map of string to string. The keys and values in this map are dictionary
 //  indices (see the [Attributes][istio.mixer.v1.CompressedAttributes] message for an explanation)
 StringMap: {
 	//  Holds a set of name/value pairs.
-	entries <_>: int32
+	entries: {
+		<_>: int32
+	} @protobuf(1,type=map<sint32,sint32>)
 }
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
index 07e33a3..771cc01 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
@@ -143,7 +143,9 @@
 	//  Map of control configuration indexed by destination.service. This
 	//  is used to support per-service configuration for cases where a
 	//  mixerclient serves multiple services.
-	serviceConfigs <_>: ServiceConfig
+	serviceConfigs: {
+		<_>: ServiceConfig
+	} @protobuf(2,type=map<string,ServiceConfig>,service_configs)
 
 	//  Default destination service name if none was specified in the
 	//  client request.
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/quota_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/quota_proto_gen.cue
index e0a314b..41206be 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/quota_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/quota_proto_gen.cue
@@ -97,7 +97,9 @@
 	//        exact: SOURCE_UID
 	//      request.http_method:
 	//        exact: POST
-	clause <_>: StringMatch
+	clause: {
+		<_>: StringMatch
+	} @protobuf(1,type=map<string,StringMatch>)
 }
 
 //  Specifies a quota to use with quota name and amount.
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/service_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/service_proto_gen.cue
index 9f97d90..46b03df 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/service_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/service_proto_gen.cue
@@ -42,5 +42,7 @@
 	// 
 	//  *Note:* When used for a VirtualService destination, labels MUST be empty.
 	// 
-	labels <_>: string
+	labels: {
+		<_>: string
+	} @protobuf(5,type=map<string,string>)
 }
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
index 335b311..44a4cad 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
@@ -40,7 +40,9 @@
 	deduplicationId?: string @protobuf(3,name=deduplication_id)
 
 	//  The individual quotas to allocate
-	quotas <_>: CheckRequest_QuotaParams
+	quotas: {
+		<_>: CheckRequest_QuotaParams
+	} @protobuf(4,type=map<string,QuotaParams>,"(gogoproto.nullable)=false")
 }
 
 //  parameters for a quota allocation
@@ -59,7 +61,9 @@
 	precondition?: CheckResponse_PreconditionResult @protobuf(2,type=PreconditionResult,"(gogoproto.nullable)=false")
 
 	//  The resulting quota, one entry per requested quota.
-	quotas <_>: CheckResponse_QuotaResult
+	quotas: {
+		<_>: CheckResponse_QuotaResult
+	} @protobuf(3,type=map<string,QuotaResult>,"(gogoproto.nullable)=false")
 }
 
 //  Expresses the result of a precondition check.
diff --git a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
index 5356909..26f4a97 100644
--- a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
@@ -212,7 +212,9 @@
 	//  label search is restricted to the configuration namespace in which the
 	//  the resource is present. In other words, the Gateway resource must
 	//  reside in the same namespace as the gateway workload instance.
-	selector <_>: string
+	selector: {
+		<_>: string
+	} @protobuf(2,type=map<string,string>)
 	selector?: {<name>: name}
 }