internal/encoding/tag: centralize logic for protobuf struct tag serialization

The bespoke text-serialization of field descriptors in protoc-gen-go is also
used in the legacy implementation of protobuf reflection to derive a
protoreflect.FieldDescriptor from legacy messages and also to convert to/from
protoreflect.ExtensionDescriptor and protoV1.ExtensionDesc.

Centralize this logic in a single place:
* to avoid reimplementing the same logic in internal/impl
* to keep the marshal and unmarshal logic co-located

Change-Id: I634c5afbb9dc6eda91d6cb6b0e68dbd724cb1ccb
Reviewed-on: https://go-review.googlesource.com/c/146758
Reviewed-by: Herbie Ong <herbie@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index b5b6655..8bc1b81 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -18,6 +18,7 @@
 
 	"github.com/golang/protobuf/proto"
 	descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/golang/protobuf/v2/internal/encoding/tag"
 	"github.com/golang/protobuf/v2/protogen"
 	"github.com/golang/protobuf/v2/reflect/protoreflect"
 )
@@ -636,112 +637,11 @@
 }
 
 func fieldProtobufTag(field *protogen.Field) string {
-	var tag []string
-	// wire type
-	tag = append(tag, wireTypes[field.Desc.Kind()])
-	// field number
-	tag = append(tag, strconv.Itoa(int(field.Desc.Number())))
-	// cardinality
-	switch field.Desc.Cardinality() {
-	case protoreflect.Optional:
-		tag = append(tag, "opt")
-	case protoreflect.Required:
-		tag = append(tag, "req")
-	case protoreflect.Repeated:
-		tag = append(tag, "rep")
-	}
-	if field.Desc.IsPacked() {
-		tag = append(tag, "packed")
-	}
-	// TODO: packed
-	// name
-	name := string(field.Desc.Name())
-	if field.Desc.Kind() == protoreflect.GroupKind {
-		// The name of the FieldDescriptor for a group field is
-		// lowercased. To find the original capitalization, we
-		// look in the field's MessageType.
-		name = string(field.MessageType.Desc.Name())
-	}
-	tag = append(tag, "name="+name)
-	// JSON name
-	if jsonName := field.Desc.JSONName(); jsonName != "" && jsonName != name {
-		tag = append(tag, "json="+jsonName)
-	}
-	// proto3
-	// The previous implementation does not tag extension fields as proto3,
-	// even when the field is defined in a proto3 file. Match that behavior
-	// for consistency.
-	if field.Desc.Syntax() == protoreflect.Proto3 && field.Desc.ExtendedType() == nil {
-		tag = append(tag, "proto3")
-	}
-	// enum
+	var enumName string
 	if field.Desc.Kind() == protoreflect.EnumKind {
-		tag = append(tag, "enum="+enumRegistryName(field.EnumType))
+		enumName = enumRegistryName(field.EnumType)
 	}
-	// oneof
-	if field.Desc.OneofType() != nil {
-		tag = append(tag, "oneof")
-	}
-	// default value
-	// This must appear last in the tag, since commas in strings aren't escaped.
-	if field.Desc.HasDefault() {
-		var def string
-		switch field.Desc.Kind() {
-		case protoreflect.BoolKind:
-			if field.Desc.Default().Bool() {
-				def = "1"
-			} else {
-				def = "0"
-			}
-		case protoreflect.BytesKind:
-			// Preserve protoc-gen-go's historical output of escaped bytes.
-			// This behavior is buggy, but fixing it makes it impossible to
-			// distinguish between the escaped and unescaped forms.
-			//
-			// To match the exact output of protoc, this is identical to the
-			// CEscape function in strutil.cc of the protoc source code.
-			var b []byte
-			for _, c := range field.Desc.Default().Bytes() {
-				switch c {
-				case '\n':
-					b = append(b, `\n`...)
-				case '\r':
-					b = append(b, `\r`...)
-				case '\t':
-					b = append(b, `\t`...)
-				case '"':
-					b = append(b, `\"`...)
-				case '\'':
-					b = append(b, `\'`...)
-				case '\\':
-					b = append(b, `\\`...)
-				default:
-					if c >= 0x20 && c <= 0x7e {
-						b = append(b, c)
-					} else {
-						b = append(b, fmt.Sprintf(`\%03o`, c)...)
-					}
-				}
-			}
-			def = string(b)
-		case protoreflect.FloatKind, protoreflect.DoubleKind:
-			f := field.Desc.Default().Float()
-			switch {
-			case math.IsInf(f, -1):
-				def = "-inf"
-			case math.IsInf(f, 1):
-				def = "inf"
-			case math.IsNaN(f):
-				def = "nan"
-			default:
-				def = fmt.Sprint(field.Desc.Default().Interface())
-			}
-		default:
-			def = fmt.Sprint(field.Desc.Default().Interface())
-		}
-		tag = append(tag, "def="+def)
-	}
-	return strings.Join(tag, ",")
+	return tag.Marshal(field.Desc, enumName)
 }
 
 func fieldDefaultValue(g *protogen.GeneratedFile, message *protogen.Message, field *protogen.Field) string {
@@ -789,27 +689,6 @@
 	return true
 }
 
-var wireTypes = map[protoreflect.Kind]string{
-	protoreflect.BoolKind:     "varint",
-	protoreflect.EnumKind:     "varint",
-	protoreflect.Int32Kind:    "varint",
-	protoreflect.Sint32Kind:   "zigzag32",
-	protoreflect.Uint32Kind:   "varint",
-	protoreflect.Int64Kind:    "varint",
-	protoreflect.Sint64Kind:   "zigzag64",
-	protoreflect.Uint64Kind:   "varint",
-	protoreflect.Sfixed32Kind: "fixed32",
-	protoreflect.Fixed32Kind:  "fixed32",
-	protoreflect.FloatKind:    "fixed32",
-	protoreflect.Sfixed64Kind: "fixed64",
-	protoreflect.Fixed64Kind:  "fixed64",
-	protoreflect.DoubleKind:   "fixed64",
-	protoreflect.StringKind:   "bytes",
-	protoreflect.BytesKind:    "bytes",
-	protoreflect.MessageKind:  "bytes",
-	protoreflect.GroupKind:    "group",
-}
-
 func fieldJSONTag(field *protogen.Field) string {
 	return string(field.Desc.Name()) + ",omitempty"
 }