cmd/protoc-gen-go: generate XXX_OneofWrappers instead of XXX_OneofFuncs
The marshaler, unmarshaler, and sizer functions are unused ever since
the underlying implementation was switched to be table-driven.
Change the function to only return the wrapper structs.
This change:
* enables generated protos to drop dependencies on certain proto types
* reduces the size of generated protos
* simplifies the implementation of oneofs in protoc-gen-go
Updates #708
Change-Id: I845c9009bc0236d1b51d34b014dc3e184303c0f2
Reviewed-on: https://go-review.googlesource.com/c/151357
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 3dcf7c9..6fd1fca 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -27,7 +27,7 @@
// It is incremented whenever an incompatibility between the generated code and
// proto package is introduced; the generated code references
// a constant, proto.ProtoPackageIsVersionN (where N is generatedCodeVersion).
-const generatedCodeVersion = 2
+const generatedCodeVersion = 3
const (
fmtPackage = protogen.GoImportPath("fmt")
@@ -563,7 +563,7 @@
}
if len(message.Oneofs) > 0 {
- genOneofFuncs(gen, g, f, message)
+ genOneofWrappers(gen, g, f, message)
}
for _, extension := range message.Extensions {
genExtension(gen, g, f, extension)
@@ -832,3 +832,116 @@
"google.protobuf.UInt64Value": true,
"google.protobuf.Value": true,
}
+
+// genOneofField generates the struct field for a oneof.
+func genOneofField(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message, oneof *protogen.Oneof) {
+ if g.PrintLeadingComments(oneof.Location) {
+ g.P("//")
+ }
+ g.P("// Types that are valid to be assigned to ", oneofFieldName(oneof), ":")
+ for _, field := range oneof.Fields {
+ g.PrintLeadingComments(field.Location)
+ g.P("//\t*", fieldOneofType(field))
+ }
+ g.Annotate(message.GoIdent.GoName+"."+oneofFieldName(oneof), oneof.Location)
+ g.P(oneofFieldName(oneof), " ", oneofInterfaceName(oneof), " `protobuf_oneof:\"", oneof.Desc.Name(), "\"`")
+}
+
+// genOneofTypes generates the interface type used for a oneof field,
+// and the wrapper types that satisfy that interface.
+//
+// It also generates the getter method for the parent oneof field
+// (but not the member fields).
+func genOneofTypes(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message, oneof *protogen.Oneof) {
+ ifName := oneofInterfaceName(oneof)
+ g.P("type ", ifName, " interface {")
+ g.P(ifName, "()")
+ g.P("}")
+ g.P()
+ for _, field := range oneof.Fields {
+ name := fieldOneofType(field)
+ g.Annotate(name.GoName, field.Location)
+ g.Annotate(name.GoName+"."+field.GoName, field.Location)
+ g.P("type ", name, " struct {")
+ goType, _ := fieldGoType(g, field)
+ tags := []string{
+ fmt.Sprintf("protobuf:%q", fieldProtobufTag(field)),
+ }
+ g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`")
+ g.P("}")
+ g.P()
+ }
+ for _, field := range oneof.Fields {
+ g.P("func (*", fieldOneofType(field), ") ", ifName, "() {}")
+ g.P()
+ }
+ g.Annotate(message.GoIdent.GoName+".Get"+oneof.GoName, oneof.Location)
+ g.P("func (m *", message.GoIdent.GoName, ") Get", oneof.GoName, "() ", ifName, " {")
+ g.P("if m != nil {")
+ g.P("return m.", oneofFieldName(oneof))
+ g.P("}")
+ g.P("return nil")
+ g.P("}")
+ g.P()
+}
+
+// oneofFieldName returns the name of the struct field holding the oneof value.
+//
+// This function is trivial, but pulling out the name like this makes it easier
+// to experiment with alternative oneof implementations.
+func oneofFieldName(oneof *protogen.Oneof) string {
+ return oneof.GoName
+}
+
+// oneofInterfaceName returns the name of the interface type implemented by
+// the oneof field value types.
+func oneofInterfaceName(oneof *protogen.Oneof) string {
+ return fmt.Sprintf("is%s_%s", oneof.ParentMessage.GoIdent.GoName, oneof.GoName)
+}
+
+// genOneofWrappers generates the XXX_OneofWrappers method for a message.
+func genOneofWrappers(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
+ g.P("// XXX_OneofWrappers is for the internal use of the proto package.")
+ g.P("func (*", message.GoIdent.GoName, ") XXX_OneofWrappers() []interface{} {")
+ g.P("return []interface{}{")
+ for _, oneof := range message.Oneofs {
+ for _, field := range oneof.Fields {
+ g.P("(*", fieldOneofType(field), ")(nil),")
+ }
+ }
+ g.P("}")
+ g.P("}")
+ g.P()
+}
+
+// fieldOneofType returns the wrapper type used to represent a field in a oneof.
+func fieldOneofType(field *protogen.Field) protogen.GoIdent {
+ ident := protogen.GoIdent{
+ GoImportPath: field.ParentMessage.GoIdent.GoImportPath,
+ GoName: field.ParentMessage.GoIdent.GoName + "_" + field.GoName,
+ }
+ // Check for collisions with nested messages or enums.
+ //
+ // This conflict resolution is incomplete: Among other things, it
+ // does not consider collisions with other oneof field types.
+ //
+ // TODO: Consider dropping this entirely. Detecting conflicts and
+ // producing an error is almost certainly better than permuting
+ // field and type names in mostly unpredictable ways.
+Loop:
+ for {
+ for _, message := range field.ParentMessage.Messages {
+ if message.GoIdent == ident {
+ ident.GoName += "_"
+ continue Loop
+ }
+ }
+ for _, enum := range field.ParentMessage.Enums {
+ if enum.GoIdent == ident {
+ ident.GoName += "_"
+ continue Loop
+ }
+ }
+ return ident
+ }
+}