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/protogen/protogen.go b/protogen/protogen.go
index ee4c735..5e1cd83 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -94,10 +94,12 @@
 	Files       []*File
 	filesByName map[string]*File
 
-	fileReg  *protoregistry.Files
-	pathType pathType
-	genFiles []*GeneratedFile
-	err      error
+	fileReg        *protoregistry.Files
+	messagesByName map[protoreflect.FullName]*Message
+	enumsByName    map[protoreflect.FullName]*Enum
+	pathType       pathType
+	genFiles       []*GeneratedFile
+	err            error
 }
 
 // Options are optional parameters to New.
@@ -136,9 +138,11 @@
 		opts = &Options{}
 	}
 	gen := &Plugin{
-		Request:     req,
-		filesByName: make(map[string]*File),
-		fileReg:     protoregistry.NewFiles(),
+		Request:        req,
+		filesByName:    make(map[string]*File),
+		fileReg:        protoregistry.NewFiles(),
+		messagesByName: make(map[protoreflect.FullName]*Message),
+		enumsByName:    make(map[protoreflect.FullName]*Enum),
 	}
 
 	packageNames := make(map[string]GoPackageName) // filename -> package name
@@ -387,7 +391,11 @@
 	f.GeneratedFilenamePrefix = prefix
 
 	for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
-		f.Messages = append(f.Messages, newMessage(gen, f, nil, mdescs.Get(i)))
+		message, err := newMessage(gen, f, nil, mdescs.Get(i))
+		if err != nil {
+			return nil, err
+		}
+		f.Messages = append(f.Messages, message)
 	}
 	for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
 		f.Enums = append(f.Enums, newEnum(gen, f, nil, edescs.Get(i)))
@@ -420,12 +428,13 @@
 	Desc protoreflect.MessageDescriptor
 
 	GoIdent  GoIdent    // name of the generated Go type
+	Fields   []*Field   // message field declarations
 	Messages []*Message // nested message declarations
 	Enums    []*Enum    // nested enum declarations
 	Path     []int32    // location path of this message
 }
 
-func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
+func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) (*Message, error) {
 	var path []int32
 	if parent != nil {
 		path = pathAppend(parent.Path, messageMessageField, int32(desc.Index()))
@@ -437,13 +446,107 @@
 		GoIdent: newGoIdent(f, desc),
 		Path:    path,
 	}
+	gen.messagesByName[desc.FullName()] = message
 	for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
-		message.Messages = append(message.Messages, newMessage(gen, f, message, mdescs.Get(i)))
+		m, err := newMessage(gen, f, message, mdescs.Get(i))
+		if err != nil {
+			return nil, err
+		}
+		message.Messages = append(message.Messages, m)
 	}
 	for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
 		message.Enums = append(message.Enums, newEnum(gen, f, message, edescs.Get(i)))
 	}
-	return message
+	for i, fdescs := 0, desc.Fields(); i < fdescs.Len(); i++ {
+		field, err := newField(gen, f, message, fdescs.Get(i))
+		if err != nil {
+			return nil, err
+		}
+		message.Fields = append(message.Fields, field)
+	}
+
+	// Field name conflict resolution.
+	//
+	// We assume well-known method names that may be attached to a generated
+	// message type, as well as a 'Get*' method for each field. For each
+	// field in turn, we add _s to its name until there are no conflicts.
+	//
+	// Any change to the following set of method names is a potential
+	// incompatible API change because it may change generated field names.
+	//
+	// TODO: If we ever support a 'go_name' option to set the Go name of a
+	// field, we should consider dropping this entirely. The conflict
+	// resolution algorithm is subtle and surprising (changing the order
+	// in which fields appear in the .proto source file can change the
+	// names of fields in generated code), and does not adapt well to
+	// adding new per-field methods such as setters.
+	usedNames := map[string]bool{
+		"Reset":               true,
+		"String":              true,
+		"ProtoMessage":        true,
+		"Marshal":             true,
+		"Unmarshal":           true,
+		"ExtensionRangeArray": true,
+		"ExtensionMap":        true,
+		"Descriptor":          true,
+	}
+	makeNameUnique := func(name string) string {
+		for usedNames[name] || usedNames["Get"+name] {
+			name += "_"
+		}
+		usedNames[name] = true
+		usedNames["Get"+name] = true
+		return name
+	}
+	for _, field := range message.Fields {
+		field.GoIdent.GoName = makeNameUnique(field.GoIdent.GoName)
+		// TODO: If this is the first field of a oneof that we haven't seen
+		// before, generate the name for the oneof.
+	}
+
+	return message, nil
+}
+
+// A Field describes a message field.
+type Field struct {
+	Desc protoreflect.FieldDescriptor
+
+	// GoIdent is the base name of this field's Go fields and methods.
+	// For code generated by protoc-gen-go, this means a field named
+	// '{{GoIdent}}' and a getter method named 'Get{{GoIdent}}'.
+	GoIdent GoIdent
+
+	MessageType *Message // type for message or group fields; nil otherwise
+	EnumType    *Enum    // type for enum fields; nil otherwise
+	Path        []int32  // location path of this field
+}
+
+func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) (*Field, error) {
+	field := &Field{
+		Desc: desc,
+		GoIdent: GoIdent{
+			GoName:       camelCase(string(desc.Name())),
+			GoImportPath: f.GoImportPath,
+		},
+		Path: pathAppend(message.Path, messageFieldField, int32(desc.Index())),
+	}
+	switch desc.Kind() {
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		mname := desc.MessageType().FullName()
+		message, ok := gen.messagesByName[mname]
+		if !ok {
+			return nil, fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), mname)
+		}
+		field.MessageType = message
+	case protoreflect.EnumKind:
+		ename := desc.EnumType().FullName()
+		enum, ok := gen.enumsByName[ename]
+		if !ok {
+			return nil, fmt.Errorf("field %v: no descriptor for enum %v", desc.FullName(), ename)
+		}
+		field.EnumType = enum
+	}
+	return field, nil
 }
 
 // An Enum describes an enum.
@@ -467,6 +570,7 @@
 		GoIdent: newGoIdent(f, desc),
 		Path:    path,
 	}
+	gen.enumsByName[desc.FullName()] = enum
 	for i, evdescs := 0, enum.Desc.Values(); i < evdescs.Len(); i++ {
 		enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, evdescs.Get(i)))
 	}
@@ -561,19 +665,6 @@
 	return string(packageName) + "." + ident.GoName
 }
 
-func (g *GeneratedFile) goPackageName(importPath GoImportPath) GoPackageName {
-	if name, ok := g.packageNames[importPath]; ok {
-		return name
-	}
-	name := cleanPackageName(baseName(string(importPath)))
-	for i, orig := 1, name; g.usedPackageNames[name]; i++ {
-		name = orig + GoPackageName(strconv.Itoa(i))
-	}
-	g.packageNames[importPath] = name
-	g.usedPackageNames[name] = true
-	return name
-}
-
 // Write implements io.Writer.
 func (g *GeneratedFile) Write(p []byte) (n int, err error) {
 	return g.buf.Write(p)