cmd/protoc-gen-go: generate message fields

This produces exactly the same output as github.com/golang/protobuf.

Change-Id: I01aacc9277c5cb5b4cc295f5ee8af12b4a524781
Reviewed-on: https://go-review.googlesource.com/134955
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index c243e00..1f24a1a 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -200,26 +200,55 @@
 
 	genWellKnownType(g, enum.GoIdent, enum.Desc)
 
-	// The name registered is, confusingly, <proto_package>.<go_ident>.
-	// This probably should have been the full name of the proto enum
-	// type instead, but changing it at this point would require thought.
-	regName := string(f.Desc.Package()) + "." + enum.GoIdent.GoName
 	f.init = append(f.init, fmt.Sprintf("%s(%q, %s, %s)",
 		g.QualifiedGoIdent(protogen.GoIdent{
 			GoImportPath: protoPackage,
 			GoName:       "RegisterEnum",
 		}),
-		regName, nameMap, valueMap,
+		enumRegistryName(enum), nameMap, valueMap,
 	))
 }
 
+// enumRegistryName returns the name used to register an enum with the proto
+// package registry.
+//
+// Confusingly, this is <proto_package>.<go_ident>. This probably should have
+// been the full name of the proto enum type instead, but changing it at this
+// point would require thought.
+func enumRegistryName(enum *protogen.Enum) string {
+	// Find the FileDescriptor for this enum.
+	var desc protoreflect.Descriptor = enum.Desc
+	for {
+		p, ok := desc.Parent()
+		if !ok {
+			break
+		}
+		desc = p
+	}
+	fdesc := desc.(protoreflect.FileDescriptor)
+	return string(fdesc.Package()) + "." + enum.GoIdent.GoName
+}
+
 func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
-	for _, enum := range message.Enums {
-		genEnum(gen, g, f, enum)
+	for _, e := range message.Enums {
+		genEnum(gen, g, f, e)
 	}
 
 	genComment(g, f, message.Path)
+	// TODO: deprecation
 	g.P("type ", message.GoIdent, " struct {")
+	for _, field := range message.Fields {
+		if field.Desc.OneofType() != nil {
+			// TODO oneofs
+			continue
+		}
+		genComment(g, f, field.Path)
+		g.P(field.GoIdent, " ", fieldGoType(g, field), fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
+	}
+	g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
+	// TODO XXX_InternalExtensions
+	g.P("XXX_unrecognized []byte `json:\"-\"`")
+	g.P("XXX_sizecache int32 `json:\"-\"`")
 	g.P("}")
 	g.P()
 
@@ -228,6 +257,123 @@
 	}
 }
 
+func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) string {
+	// TODO: map types
+	var typ string
+	switch field.Desc.Kind() {
+	case protoreflect.BoolKind:
+		typ = "bool"
+	case protoreflect.EnumKind:
+		typ = g.QualifiedGoIdent(field.EnumType.GoIdent)
+	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
+		typ = "int32"
+	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
+		typ = "uint32"
+	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
+		typ = "int64"
+	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
+		typ = "uint64"
+	case protoreflect.FloatKind:
+		typ = "float32"
+	case protoreflect.DoubleKind:
+		typ = "float64"
+	case protoreflect.StringKind:
+		typ = "string"
+	case protoreflect.BytesKind:
+		typ = "[]byte"
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		typ = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
+	}
+	if field.Desc.Cardinality() == protoreflect.Repeated {
+		return "[]" + typ
+	}
+	if field.Desc.Syntax() == protoreflect.Proto3 {
+		return typ
+	}
+	if field.Desc.OneofType() != nil {
+		return typ
+	}
+	nonPointerKinds := map[protoreflect.Kind]bool{
+		protoreflect.GroupKind:   true,
+		protoreflect.MessageKind: true,
+		protoreflect.BytesKind:   true,
+	}
+	if !nonPointerKinds[field.Desc.Kind()] {
+		return "*" + typ
+	}
+	return typ
+}
+
+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")
+	}
+	// TODO: default values
+	// 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
+	if field.Desc.Syntax() == protoreflect.Proto3 {
+		tag = append(tag, "proto3")
+	}
+	// enum
+	if field.Desc.Kind() == protoreflect.EnumKind {
+		tag = append(tag, "enum="+enumRegistryName(field.EnumType))
+	}
+	// oneof
+	if field.Desc.OneofType() != nil {
+		tag = append(tag, "oneof")
+	}
+	return strings.Join(tag, ",")
+}
+
+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"
+}
+
 func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
 	for _, loc := range f.locationMap[pathKey(path)] {
 		if loc.LeadingComments == nil {