encoding/proto[json|text]: accept lower case names for group-like fields

This is a result of the discussion in [1]. Before editions, a group defined a multiple things:

* a type
* a field
* an encoding scheme

With editions this has changed and groups no longer exist and the different parts have to be defined individually. Most importantly, the field and the type also had the same name (usually and CamelCase name). To keep compatibility with proto2 groups, [2] introduced a concept of group-like fields and adjusted the Text/JSON parsers to accept the type name instead of the field name for such fields. This means you can convert from proto2 groups to editions without changing the semantics.
Furthermore, to avoid suprises with group-like fields (e.g. when a user by coincident specified a field that is group-like) protobuf decided that group-like fields should always accept the type and the field name for group like fields. This also allows us to eventually emit the field name rather than the type name for group like fields in the future.

This change implements this decision in Go.


[1] https://github.com/protocolbuffers/protobuf/issues/16239
[2] https://go.dev/cl/575916

Change-Id: I701c4cd228d2e0867b2a87771b6c6331459c4910
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/582755
Reviewed-by: Lasse Folger <lassefolger@google.com>
Reviewed-by: Mike Kruskal <mkruskal@google.com>
Commit-Queue: Michael Stapelberg <stapelberg@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
Auto-Submit: Michael Stapelberg <stapelberg@google.com>
diff --git a/encoding/prototext/decode_test.go b/encoding/prototext/decode_test.go
index e7e7fae..be6e5a4 100644
--- a/encoding/prototext/decode_test.go
+++ b/encoding/prototext/decode_test.go
@@ -657,12 +657,16 @@
 		desc:         "group field name",
 		inputMessage: &pb2.Nests{},
 		inputText:    `optgroup: {}`,
-		wantErr:      "unknown field: optgroup",
+		wantMessage: &pb2.Nests{
+			Optgroup: &pb2.Nests_OptGroup{},
+		},
 	}, {
-		desc:         "delimited encoded group-line message field name",
+		desc:         "delimited encoded group-like message field name",
 		inputMessage: &pbeditions.Nests{},
-		inputText:    `optgroup: {}`,
-		wantErr:      "unknown field: optgroup",
+		inputText:    `optgroup {}`,
+		wantMessage: &pbeditions.Nests{
+			Optgroup: &pbeditions.Nests_OptGroup{},
+		},
 	}, {
 		desc:         "delimited encoded message field name",
 		inputMessage: &pbeditions.Nests{},
diff --git a/internal/cmd/generate-types/main.go b/internal/cmd/generate-types/main.go
index 9a7460c..76e3b89 100644
--- a/internal/cmd/generate-types/main.go
+++ b/internal/cmd/generate-types/main.go
@@ -171,6 +171,16 @@
 					if _, ok := p.byText[d.TextName()]; !ok {
 						p.byText[d.TextName()] = d
 					}
+					if isGroupLike(d) {
+						lowerJSONName := strings.ToLower(d.JSONName())
+						if _, ok := p.byJSON[lowerJSONName]; !ok {
+							p.byJSON[lowerJSONName] = d
+						}
+						lowerTextName := strings.ToLower(d.TextName())
+						if _, ok := p.byText[lowerTextName]; !ok {
+							p.byText[lowerTextName] = d
+						}
+					}
 					{{- end}}
 					{{- if .NumberExpr}}
 					if _, ok := p.byNum[d.Number()]; !ok {
@@ -200,6 +210,7 @@
 		"fmt",
 		"math",
 		"reflect",
+		"strings",
 		"sync",
 		"unicode/utf8",
 		"",
diff --git a/internal/conformance/failing_tests_text_format.txt b/internal/conformance/failing_tests_text_format.txt
index c4335e8..c4ba1b9 100644
--- a/internal/conformance/failing_tests_text_format.txt
+++ b/internal/conformance/failing_tests_text_format.txt
@@ -8,13 +8,3 @@
 Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString
 Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes
 Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString
-Required.Editions.TextFormatInput.DelimitedFieldLowercased.ProtobufOutput
-Required.Editions.TextFormatInput.DelimitedFieldLowercased.TextFormatOutput
-Required.Editions_Proto2.TextFormatInput.GroupFieldLowercased.ProtobufOutput
-Required.Editions_Proto2.TextFormatInput.GroupFieldLowercased.TextFormatOutput
-Required.Editions_Proto2.TextFormatInput.GroupFieldLowercasedMultiWord.ProtobufOutput
-Required.Editions_Proto2.TextFormatInput.GroupFieldLowercasedMultiWord.TextFormatOutput
-Required.Proto2.TextFormatInput.GroupFieldLowercased.ProtobufOutput
-Required.Proto2.TextFormatInput.GroupFieldLowercased.TextFormatOutput
-Required.Proto2.TextFormatInput.GroupFieldLowercasedMultiWord.ProtobufOutput
-Required.Proto2.TextFormatInput.GroupFieldLowercasedMultiWord.TextFormatOutput
diff --git a/internal/filedesc/desc_list_gen.go b/internal/filedesc/desc_list_gen.go
index 30db19f..f4107c0 100644
--- a/internal/filedesc/desc_list_gen.go
+++ b/internal/filedesc/desc_list_gen.go
@@ -8,6 +8,7 @@
 
 import (
 	"fmt"
+	"strings"
 	"sync"
 
 	"google.golang.org/protobuf/internal/descfmt"
@@ -198,6 +199,16 @@
 				if _, ok := p.byText[d.TextName()]; !ok {
 					p.byText[d.TextName()] = d
 				}
+				if isGroupLike(d) {
+					lowerJSONName := strings.ToLower(d.JSONName())
+					if _, ok := p.byJSON[lowerJSONName]; !ok {
+						p.byJSON[lowerJSONName] = d
+					}
+					lowerTextName := strings.ToLower(d.TextName())
+					if _, ok := p.byText[lowerTextName]; !ok {
+						p.byText[lowerTextName] = d
+					}
+				}
 				if _, ok := p.byNum[d.Number()]; !ok {
 					p.byNum[d.Number()] = d
 				}