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)