Michael Stapelberg eb7b468655 all: Release the Opaque API
For golang/protobuf#1657

Change-Id: I7b2b0c30506706015ce278e6054439c9ad9ef727
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/634815
TryBot-Bypass: Michael Stapelberg <stapelberg@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Damien Neil <dneil@google.com>
2024-12-11 03:16:51 -08:00

1307 lines
44 KiB
Go

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal_gengo
import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/internal/genid"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
)
func opaqueGenMessageHook(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) bool {
opaqueGenMessage(g, f, message)
return true
}
func opaqueGenMessage(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
// Message type declaration.
g.AnnotateSymbol(message.GoIdent.GoName, protogen.Annotation{Location: message.Location})
leadingComments := appendDeprecationSuffix(message.Comments.Leading,
message.Desc.ParentFile(),
message.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
g.P(leadingComments,
"type ", message.GoIdent, " struct {")
sf := f.allMessageFieldsByPtr[message]
if sf == nil {
sf = new(structFields)
f.allMessageFieldsByPtr[message] = sf
}
var tags structTags
switch {
case message.isOpen():
tags = structTags{{"protogen", "open.v1"}}
case message.isHybrid():
tags = structTags{{"protogen", "hybrid.v1"}}
case message.isOpaque():
tags = structTags{{"protogen", "opaque.v1"}}
}
g.P(genid.State_goname, " ", protoimplPackage.Ident("MessageState"), tags)
sf.append(genid.State_goname)
fields := message.Fields
for _, field := range fields {
opaqueGenMessageField(g, f, message, field, sf)
}
opaqueGenMessageInternalFields(g, f, message, sf)
g.P("}")
g.P()
genMessageKnownFunctions(g, f, message)
genMessageDefaultDecls(g, f, message)
opaqueGenMessageMethods(g, f, message)
opaqueGenMessageBuilder(g, f, message)
opaqueGenOneofWrapperTypes(g, f, message)
}
// opaqueGenMessageField generates a struct field.
func opaqueGenMessageField(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field, sf *structFields) {
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
// It would be a bit simpler to iterate over the oneofs below,
// but generating the field here keeps the contents of the Go
// struct in the same order as the contents of the source
// .proto file.
if field != oneof.Fields[0] {
return
}
opaqueGenOneofFields(g, f, message, oneof, sf)
return
}
goType, pointer := opaqueFieldGoType(g, f, message, field)
if pointer {
goType = "*" + goType
}
protobufTagValue := fieldProtobufTagValue(field)
jsonTagValue := fieldJSONTagValue(field)
if g.InternalStripForEditionsDiff() {
if field.Desc.ContainingOneof() != nil && field.Desc.ContainingOneof().IsSynthetic() {
protobufTagValue = strings.ReplaceAll(protobufTagValue, ",oneof", "")
}
protobufTagValue = strings.ReplaceAll(protobufTagValue, ",proto3", "")
}
tags := structTags{
{"protobuf", protobufTagValue},
{"json", jsonTagValue},
}
if field.Desc.IsMap() {
keyTagValue := fieldProtobufTagValue(field.Message.Fields[0])
valTagValue := fieldProtobufTagValue(field.Message.Fields[1])
keyTagValue = strings.ReplaceAll(keyTagValue, ",proto3", "")
valTagValue = strings.ReplaceAll(valTagValue, ",proto3", "")
tags = append(tags, structTags{
{"protobuf_key", keyTagValue},
{"protobuf_val", valTagValue},
}...)
}
name := field.GoName
if field.Desc.IsWeak() {
g.P("// Deprecated: Do not use. This will be deleted in the near future.")
name = genid.WeakFieldPrefix_goname + name
} else if message.isOpaque() {
name = "xxx_hidden_" + name
}
if message.isOpaque() && !field.Desc.IsWeak() {
g.P(name, " ", goType, tags)
sf.append(name)
if message.isTracked {
g.P("// Deprecated: Do not use. This will be deleted in the near future.")
g.P("XXX_ft_", field.GoName, " struct{} `go:\"track\"`")
sf.append("XXX_ft_" + field.GoName)
}
} else {
if message.isTracked {
tags = append(tags, structTags{
{"go", "track"},
}...)
}
g.AnnotateSymbol(field.Parent.GoIdent.GoName+"."+name, protogen.Annotation{Location: field.Location})
leadingComments := appendDeprecationSuffix(field.Comments.Leading,
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.P(leadingComments,
name, " ", goType, tags,
trailingComment(field.Comments.Trailing))
sf.append(name)
}
}
// opaqueGenOneofFields generates the message fields for a oneof.
func opaqueGenOneofFields(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof, sf *structFields) {
tags := structTags{
{"protobuf_oneof", string(oneof.Desc.Name())},
}
if message.isTracked {
tags = append(tags, structTags{
{"go", "track"},
}...)
}
oneofName := opaqueOneofFieldName(oneof, message.isOpaque())
goType := opaqueOneofInterfaceName(oneof)
if message.isOpaque() {
g.P(oneofName, " ", goType, tags)
sf.append(oneofName)
if message.isTracked {
g.P("// Deprecated: Do not use. This will be deleted in the near future.")
g.P("XXX_ft_", oneof.GoName, " struct{} `go:\"track\"`")
sf.append("XXX_ft_" + oneof.GoName)
}
return
}
leadingComments := oneof.Comments.Leading
if leadingComments != "" {
leadingComments += "\n"
}
// NOTE(rsc): The extra \n here is working around #52605,
// making the comment be in Go 1.19 doc comment format
// even though it's not really a doc comment.
ss := []string{" Types that are valid to be assigned to ", oneofName, ":\n\n"}
for _, field := range oneof.Fields {
ss = append(ss, "\t*"+opaqueFieldOneofType(field, message.isOpaque()).GoName+"\n")
}
leadingComments += protogen.Comments(strings.Join(ss, ""))
g.P(leadingComments, oneofName, " ", goType, tags)
sf.append(oneofName)
}
// opaqueGenMessageInternalFields adds additional XXX_ fields to a message struct.
func opaqueGenMessageInternalFields(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, sf *structFields) {
if opaqueNeedsPresenceArray(message) {
if opaqueNeedsLazyStruct(message) {
g.P("// Deprecated: Do not use. This will be deleted in the near future.")
g.P("XXX_lazyUnmarshalInfo ", protoimplPackage.Ident("LazyUnmarshalInfo"))
sf.append("XXX_lazyUnmarshalInfo")
}
g.P("XXX_raceDetectHookData ", protoimplPackage.Ident("RaceDetectHookData"))
sf.append("XXX_raceDetectHookData")
// Presence must be stored in a data type no larger than 32 bit:
//
// Presence used to be a uint64, accessed with atomic.LoadUint64, but it
// turns out that on 32-bit platforms like GOARCH=arm, the struct field
// was 32-bit aligned (not 64-bit aligned) and hence atomic accesses
// failed.
//
// The easiest solution was to switch to a uint32 on all platforms,
// which did not come with a performance penalty.
g.P("XXX_presence [", (opaqueNumPresenceFields(message)+31)/32, "]uint32")
sf.append("XXX_presence")
}
if message.hasWeak {
g.P(genid.WeakFields_goname, " ", protoimplPackage.Ident("WeakFields"))
sf.append(genid.WeakFields_goname)
}
if message.Desc.ExtensionRanges().Len() > 0 {
g.P(genid.ExtensionFields_goname, " ", protoimplPackage.Ident("ExtensionFields"))
sf.append(genid.ExtensionFields_goname)
}
g.P(genid.UnknownFields_goname, " ", protoimplPackage.Ident("UnknownFields"))
sf.append(genid.UnknownFields_goname)
g.P(genid.SizeCache_goname, " ", protoimplPackage.Ident("SizeCache"))
sf.append(genid.SizeCache_goname)
}
func opaqueGenMessageMethods(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
genMessageBaseMethods(g, f, message)
isRepeated := func(field *protogen.Field) bool {
return field.Desc.Cardinality() == protoreflect.Repeated
}
for _, field := range message.Fields {
if isFirstOneofField(field) && !message.isOpaque() {
opaqueGenGetOneof(g, f, message, field.Oneof)
}
opaqueGenGet(g, f, message, field)
}
for _, field := range message.Fields {
// For the plain open mode, we only have set methods for weak fields.
if message.isOpen() && !field.Desc.IsWeak() {
continue
}
opaqueGenSet(g, f, message, field)
}
for _, field := range message.Fields {
// Open API does not have Has method.
// Repeated (includes map) fields do not have Has method.
if message.isOpen() || isRepeated(field) {
continue
}
if !field.Desc.HasPresence() {
continue
}
if isFirstOneofField(field) {
opaqueGenHasOneof(g, f, message, field.Oneof)
}
opaqueGenHas(g, f, message, field)
}
for _, field := range message.Fields {
// Open API does not have Clear method.
// Repeated (includes map) fields do not have Clear method.
if message.isOpen() || isRepeated(field) {
continue
}
if !field.Desc.HasPresence() {
continue
}
if isFirstOneofField(field) {
opaqueGenClearOneof(g, f, message, field.Oneof)
}
opaqueGenClear(g, f, message, field)
}
// Plain open protos do not have which methods.
if !message.isOpen() {
opaqueGenWhichOneof(g, f, message)
}
if g.InternalStripForEditionsDiff() {
return
}
}
func isLazy(field *protogen.Field) bool {
// Prerequisite: field is of kind message
if field.Message == nil {
return false
}
// Was the field marked as [lazy = true] in the .proto file?
fopts := field.Desc.Options().(*descriptorpb.FieldOptions)
return fopts.GetLazy()
}
// opaqueGenGet generates a Get method for a field.
func opaqueGenGet(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
goType, pointer := opaqueFieldGoType(g, f, message, field)
getterName, bcName := field.MethodName("Get")
// If we need a backwards compatible getter name, we add it now.
if bcName != "" {
defer func() {
g.P("// Deprecated: Use ", getterName, " instead.")
g.P("func (x *", message.GoIdent, ") ", bcName, "() ", goType, " {")
g.P("return x.", getterName, "()")
g.P("}")
g.P()
}()
}
leadingComments := appendDeprecationSuffix("",
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
fieldtrackNoInterface(g, message.isTracked)
g.AnnotateSymbol(message.GoIdent.GoName+"."+getterName, protogen.Annotation{Location: field.Location})
// Weak field.
if field.Desc.IsWeak() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", protoPackage.Ident("Message"), "{")
g.P("var w ", protoimplPackage.Ident("WeakFields"))
g.P("if x != nil {")
g.P("w = x.", genid.WeakFields_goname)
if message.isTracked {
g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
}
g.P("}")
g.P("return ", protoimplPackage.Ident("X"), ".GetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ")")
g.P("}")
g.P()
return
}
defaultValue := fieldDefaultValue(g, f, message, field)
// Oneof field.
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
structPtr := "x"
g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, " {")
g.P("if x != nil {")
if message.isOpaque() && message.isTracked {
g.P("_ = ", structPtr, ".XXX_ft_", field.Oneof.GoName)
}
g.P("if x, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), "); ok {")
g.P("return x.", field.GoName)
g.P("}")
// End if m != nil {.
g.P("}")
g.P("return ", defaultValue)
g.P("}")
g.P()
return
}
// Non-oneof field for open type message.
if !message.isOpaque() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, " {")
if !field.Desc.HasPresence() || defaultValue == "nil" {
g.P("if x != nil {")
} else {
g.P("if x != nil && x.", field.GoName, " != nil {")
}
star := ""
if pointer {
star = "*"
}
g.P("return ", star, " x.", field.GoName)
g.P("}")
g.P("return ", defaultValue)
g.P("}")
g.P()
return
}
// Non-oneof field for opaque type message.
g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, "{")
structPtr := "x"
g.P("if x != nil {")
if message.isTracked {
g.P("_ = ", structPtr, ".XXX_ft_", field.GoName)
}
if usePresence(message, field) {
pi := opaqueFieldPresenceIndex(field)
ai := pi / 32
// For
//
// 1. Message fields of lazy messages (unmarshalled lazily),
// 2. Fields with a default value,
// 3. Closed enums
//
// ...we check presence, but for other fields using presence, we can return
// whatever is there and it should be correct regardless of presence, which
// saves us an atomic operation.
isEnum := field.Desc.Kind() == protoreflect.EnumKind
usePresenceForRead := (isLazy(field)) ||
field.Desc.HasDefault() || isEnum
if usePresenceForRead {
g.P("if ", protoimplPackage.Ident("X"), ".Present(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ") {")
}
// For lazy, check if pointer is nil and optionally unmarshal
if isLazy(field) {
// Since pointer to lazily unmarshaled sub-message can be written during a conceptual
// "read" operation, all read/write accesses to the pointer must be atomic. This
// function gets inlined on x86 as just a simple get and compare. Still need to make the
// slice accesses be atomic.
g.P("if ", protoimplPackage.Ident("X"), ".AtomicCheckPointerIsNil(&", structPtr, ".xxx_hidden_", field.GoName, ") {")
g.P(protoimplPackage.Ident("X"), ".UnmarshalField(", structPtr, ", ", field.Desc.Number(), ")")
g.P("}")
}
if field.Message == nil || field.Desc.IsMap() {
star := ""
if pointer {
star = "*"
}
if pointer {
g.P("if ", structPtr, ".xxx_hidden_", field.GoName, "!= nil {")
}
g.P("return ", star, structPtr, ".xxx_hidden_", field.GoName)
if pointer {
g.P("}")
g.P("return ", defaultValue)
}
} else {
// We need to do an atomic load of the msg pointer field, but cannot explicitly use
// unsafe pointers here. We load the value and store into rv, via protoimpl.Pointer,
// which is aliased to unsafe.Pointer in pointer_unsafe.go, but is aliased to
// interface{} in pointer_reflect.go
star := ""
if pointer {
star = "*"
}
if isLazy(field) {
g.P("var rv ", star, goType)
g.P(protoimplPackage.Ident("X"), ".AtomicLoadPointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&rv))")
g.P("return ", star, "rv")
} else {
if pointer {
g.P("if ", structPtr, ".xxx_hidden_", field.GoName, "!= nil {")
}
g.P("return ", star, structPtr, ".xxx_hidden_", field.GoName)
if pointer {
g.P("}")
}
}
}
if usePresenceForRead {
g.P("}")
}
} else if pointer {
g.P("if ", structPtr, ".xxx_hidden_", field.GoName, " != nil {")
g.P("return *", structPtr, ".xxx_hidden_", field.GoName)
g.P("}")
} else {
g.P("return ", structPtr, ".xxx_hidden_", field.GoName)
}
// End if m != nil {.
g.P("}")
g.P("return ", defaultValue)
g.P("}")
g.P()
}
// opaqueGenSet generates a Set method for a field.
func opaqueGenSet(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
goType, pointer := opaqueFieldGoType(g, f, message, field)
setterName, bcName := field.MethodName("Set")
// If we need a backwards compatible setter name, we add it now.
if bcName != "" {
defer func() {
g.P("// Deprecated: Use ", setterName, " instead.")
g.P("func (x *", message.GoIdent, ") ", bcName, "(v ", goType, ") {")
g.P("x.", setterName, "(v)")
g.P("}")
g.P()
}()
}
leadingComments := appendDeprecationSuffix("",
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.AnnotateSymbol(message.GoIdent.GoName+"."+setterName, protogen.Annotation{
Location: field.Location,
Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
})
fieldtrackNoInterface(g, message.noInterface)
// Weak field.
if field.Desc.IsWeak() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", protoPackage.Ident("Message"), ") {")
g.P("var w *", protoimplPackage.Ident("WeakFields"))
g.P("if x != nil {")
g.P("w = &x.", genid.WeakFields_goname)
if message.isTracked {
g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
}
g.P("}")
g.P(protoimplPackage.Ident("X"), ".SetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ", v)")
g.P("}")
g.P()
return
}
// Oneof field.
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
structPtr := "x"
if message.isOpaque() && message.isTracked {
// Add access to zero field for tracking
g.P(structPtr, ".XXX_ft_", oneof.GoName, " = struct{}{}")
}
if field.Desc.Kind() == protoreflect.BytesKind {
g.P("if v == nil { v = []byte{} }")
} else if field.Message != nil {
g.P("if v == nil {")
g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), "= nil")
g.P("return")
g.P("}")
}
g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), "= &", opaqueFieldOneofType(field, message.isOpaque()), "{v}")
g.P("}")
g.P()
return
}
// Non-oneof field for open type message.
if !message.isOpaque() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
if field.Desc.Cardinality() != protoreflect.Repeated && field.Desc.Kind() == protoreflect.BytesKind {
g.P("if v == nil { v = []byte{} }")
}
amp := ""
if pointer {
amp = "&"
}
v := "v"
g.P("x.", field.GoName, " = ", amp, v)
g.P("}")
g.P()
return
}
// Non-oneof field for opaque type message.
g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
structPtr := "x"
if message.isTracked {
// Add access to zero field for tracking
g.P(structPtr, ".XXX_ft_", field.GoName, " = struct{}{}")
}
if field.Desc.Cardinality() != protoreflect.Repeated && field.Desc.Kind() == protoreflect.BytesKind {
g.P("if v == nil { v = []byte{} }")
}
amp := ""
if pointer {
amp = "&"
}
if usePresence(message, field) {
pi := opaqueFieldPresenceIndex(field)
ai := pi / 32
if field.Message != nil && field.Desc.IsList() {
g.P("var sv *", goType)
g.P(protoimplPackage.Ident("X"), ".AtomicLoadPointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&sv))")
g.P("if sv == nil {")
g.P("sv = &", goType, "{}")
g.P(protoimplPackage.Ident("X"), ".AtomicInitializePointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&sv))")
g.P("}")
g.P("*sv = v")
g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
} else if field.Message != nil && !field.Desc.IsMap() {
// Only for lazy messages do we need to set pointers atomically
if isLazy(field) {
g.P(protoimplPackage.Ident("X"), ".AtomicSetPointer(&", structPtr, ".xxx_hidden_", field.GoName, ", ", amp, "v)")
} else {
g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
}
// When setting a message or slice of messages to a nil
// value, we must clear the presence bit, else we will
// later think that this field still needs to be lazily decoded.
g.P("if v == nil {")
g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
g.P("} else {")
g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
g.P("}")
} else {
// Any map or non-message, possibly repeated, field that uses presence (proto2 only)
g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
// For consistent behaviour with lazy fields, non-map repeated fields should be cleared when
// the last object is removed. Maps are cleared when set to a nil map.
if field.Desc.Cardinality() == protoreflect.Repeated { // Includes maps.
g.P("if v == nil {")
g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
g.P("} else {")
}
g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
if field.Desc.Cardinality() == protoreflect.Repeated {
g.P("}")
}
}
} else {
// proto3 non-lazy fields
g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
}
g.P("}")
g.P()
}
// usePresence returns true if the presence map should be used for a field. It
// is always true for lazy message types. It is also true for all scalar fields.
// repeated, map or message fields are not using the presence map.
func usePresence(message *messageInfo, field *protogen.Field) bool {
if !message.isOpaque() || field.Desc.IsWeak() {
return false
}
return opaqueFieldNeedsPresenceArray(message, field)
}
func opaqueFieldNeedsPresenceArray(message *messageInfo, field *protogen.Field) bool {
// Non optional fields need presence if truly lazy field, i.e. are message fields.
if isLazy(field) {
return true
}
isNotOneof := field.Desc.ContainingOneof() == nil || field.Desc.ContainingOneof().IsSynthetic()
return field.Desc.HasPresence() && field.Message == nil && isNotOneof
}
// opaqueGenHas generates a Has method for a field.
func opaqueGenHas(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
hasserName, _ := field.MethodName("Has")
leadingComments := appendDeprecationSuffix("",
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.AnnotateSymbol(message.GoIdent.GoName+"."+hasserName, protogen.Annotation{Location: field.Location})
fieldtrackNoInterface(g, message.noInterface)
// Weak field.
if field.Desc.IsWeak() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
g.P("var w ", protoimplPackage.Ident("WeakFields"))
g.P("if x != nil {")
g.P("w = x.", genid.WeakFields_goname)
if message.isTracked {
g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
}
g.P("}")
g.P("return ", protoimplPackage.Ident("X"), ".HasWeak(w, ", field.Desc.Number(), ")")
g.P("}")
g.P()
return
}
// Oneof field.
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
structPtr := "x"
g.P("if ", structPtr, " == nil {")
g.P("return false")
g.P("}")
if message.isOpaque() && message.isTracked {
// Add access to zero field for tracking
g.P("_ = ", structPtr, ".", "XXX_ft_", oneof.GoName)
}
g.P("_, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), ")")
g.P("return ok")
g.P("}")
g.P()
return
}
// Non-oneof field in open message.
if !message.isOpaque() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
g.P("if x == nil {")
g.P("return false")
g.P("}")
g.P("return ", "x.", field.GoName, " != nil")
g.P("}")
g.P()
return
}
// Non-oneof field in opaque message.
g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
g.P("if x == nil {")
g.P("return false")
g.P("}")
structPtr := "x"
if message.isTracked {
// Add access to zero field for tracking
g.P("_ = ", structPtr, ".", "XXX_ft_"+field.GoName)
}
if usePresence(message, field) {
pi := opaqueFieldPresenceIndex(field)
ai := pi / 32
g.P("return ", protoimplPackage.Ident("X"), ".Present(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
} else {
// Has for proto3 message without presence
g.P("return ", structPtr, ".xxx_hidden_", field.GoName, " != nil")
}
g.P("}")
g.P()
}
// opaqueGenClear generates a Clear method for a field.
func opaqueGenClear(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
clearerName, _ := field.MethodName("Clear")
pi := opaqueFieldPresenceIndex(field)
ai := pi / 32
leadingComments := appendDeprecationSuffix("",
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.AnnotateSymbol(message.GoIdent.GoName+"."+clearerName, protogen.Annotation{
Location: field.Location,
Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
})
fieldtrackNoInterface(g, message.noInterface)
// Weak field.
if field.Desc.IsWeak() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
g.P("var w *", protoimplPackage.Ident("WeakFields"))
g.P("if x != nil {")
g.P("w = &x.", genid.WeakFields_goname)
if message.isTracked {
g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
}
g.P("}")
g.P(protoimplPackage.Ident("X"), ".ClearWeak(w, ", field.Desc.Number(), ")")
g.P("}")
g.P()
return
}
// Oneof field.
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
structPtr := "x"
if message.isOpaque() && message.isTracked {
// Add access to zero field for tracking
g.P(structPtr, ".", "XXX_ft_", oneof.GoName, " = struct{}{}")
}
g.P("if _, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), "); ok {")
g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " = nil")
g.P("}")
g.P("}")
g.P()
return
}
// Non-oneof field in open message.
if !message.isOpaque() {
g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
g.P("x.", field.GoName, " = nil")
g.P("}")
g.P()
return
}
// Non-oneof field in opaque message.
g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
structPtr := "x"
if message.isTracked {
// Add access to zero field for tracking
g.P(structPtr, ".", "XXX_ft_", field.GoName, " = struct{}{}")
}
if usePresence(message, field) {
g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
}
// Avoid needing to read the presence value in Get by ensuring that we set the
// right zero value (unless we have an explicit default, in which case we
// revert to presence checking in Get). Rationale: Get is called far more
// frequently than Clear, it should be as lean as possible.
zv := opaqueZeroValueForField(g, field)
// For lazy, (repeated) message fields are unmarshalled lazily. Hence they are
// assigned atomically in Getters (which are allowed to be called
// concurrently). Due to this, historically, the code generator would use
// atomic operations everywhere.
//
// TODO(b/291588964): Stop using atomic operations for non-presence fields in
// write calls (Set/Clear). Concurrent reads are allowed,
// but concurrent read/write or write/write are not, we
// shouldn't cater to it.
if isLazy(field) {
goType, _ := opaqueFieldGoType(g, f, message, field)
g.P(protoimplPackage.Ident("X"), ".AtomicSetPointer(&", structPtr, ".xxx_hidden_", field.GoName, ",(", goType, ")(", zv, "))")
} else if !field.Desc.HasDefault() {
g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", zv)
}
g.P("}")
g.P()
}
// Determine what value to set a cleared field to.
func opaqueZeroValueForField(g *protogen.GeneratedFile, field *protogen.Field) string {
if field.Desc.Cardinality() == protoreflect.Repeated {
return "nil"
}
switch field.Desc.Kind() {
case protoreflect.StringKind:
return "nil"
case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind:
return "nil"
case protoreflect.BoolKind:
return "false"
case protoreflect.EnumKind:
return g.QualifiedGoIdent(field.Enum.Values[0].GoIdent)
default:
return "0"
}
}
// opaqueGenGetOneof generates a Get function for a oneof union.
func opaqueGenGetOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
ifName := opaqueOneofInterfaceName(oneof)
g.AnnotateSymbol(message.GoIdent.GoName+".Get"+oneof.GoName, protogen.Annotation{Location: oneof.Location})
fieldtrackNoInterface(g, message.isTracked)
g.P("func (x *", message.GoIdent.GoName, ") Get", oneof.GoName, "() ", ifName, " {")
g.P("if x != nil {")
g.P("return x.", opaqueOneofFieldName(oneof, message.isOpaque()))
g.P("}")
g.P("return nil")
g.P("}")
g.P()
}
// opaqueGenHasOneof generates a Has function for a oneof union.
func opaqueGenHasOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
fieldtrackNoInterface(g, message.noInterface)
hasserName := oneof.MethodName("Has")
g.P("func (x *", message.GoIdent, ") ", hasserName, "() bool {")
g.P("if x == nil {")
g.P("return false")
g.P("}")
structPtr := "x"
if message.isOpaque() && message.isTracked {
// Add access to zero field for tracking
g.P("_ = ", structPtr, ".XXX_ft_", oneof.GoName)
}
g.P("return ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " != nil")
g.P("}")
g.P()
}
// opaqueGenClearOneof generates a Clear function for a oneof union.
func opaqueGenClearOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
fieldtrackNoInterface(g, message.noInterface)
clearerName := oneof.MethodName("Clear")
g.P("func (x *", message.GoIdent, ") ", clearerName, "() {")
structPtr := "x"
if message.isOpaque() && message.isTracked {
// Add access to zero field for tracking
g.P(structPtr, ".", "XXX_ft_", oneof.GoName, " = struct{}{}")
}
g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " = nil")
g.P("}")
g.P()
}
// opaqueGenWhichOneof generates the Which method for each oneof union, as well as the case values for each member
// of that union.
func opaqueGenWhichOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
// Go through the message, and for each field that is the first of a oneof field, dig down
// and generate constants + the actual which method.
oneofIndex := 0
for _, field := range message.Fields {
if oneof := field.Oneof; oneof != nil {
if !isFirstOneofField(field) {
continue
}
caseType := opaqueOneofCaseTypeName(oneof)
g.P("const ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ", caseType, " = ", 0)
for _, f := range oneof.Fields {
g.P("const ", message.GoIdent.GoName, "_", f.GoName, "_case ", caseType, " = ", f.Desc.Number())
}
fieldtrackNoInterface(g, message.noInterface)
whicherName := oneof.MethodName("Which")
g.P("func (x *", message.GoIdent, ") ", whicherName, "() ", caseType, " {")
g.P("if x == nil {")
g.P("return ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ")
g.P("}")
g.P("switch x.", opaqueOneofFieldName(oneof, message.isOpaque()), ".(type) {")
for _, f := range oneof.Fields {
g.P("case *", opaqueFieldOneofType(f, message.isOpaque()), ":")
g.P("return ", message.GoIdent.GoName, "_", f.GoName, "_case")
}
g.P("default", ":")
g.P("return ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ")
g.P("}")
g.P("}")
g.P()
oneofIndex++
}
}
}
func opaqueNeedsPresenceArray(message *messageInfo) bool {
if !message.isOpaque() {
return false
}
for _, field := range message.Fields {
if opaqueFieldNeedsPresenceArray(message, field) {
return true
}
}
return false
}
func opaqueNeedsLazyStruct(message *messageInfo) bool {
for _, field := range message.Fields {
if isLazy(field) {
return true
}
}
return false
}
// opaqueGenMessageBuilder generates a Builder type for a message.
func opaqueGenMessageBuilder(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
if message.isOpen() {
return
}
// Builder type.
bName := g.QualifiedGoIdent(message.GoIdent) + genid.BuilderSuffix_goname
g.AnnotateSymbol(message.GoIdent.GoName+genid.BuilderSuffix_goname, protogen.Annotation{Location: message.Location})
leadingComments := appendDeprecationSuffix("",
message.Desc.ParentFile(),
message.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
g.P(leadingComments, "type ", bName, " struct {")
g.P("_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.")
g.P()
for _, field := range message.Fields {
oneof := field.Oneof
if oneof == nil && field.Desc.IsWeak() {
continue
}
goType, pointer := opaqueBuilderFieldGoType(g, f, message, field)
if pointer {
goType = "*" + goType
} else if oneof != nil && fieldDefaultValue(g, f, message, field) != "nil" {
goType = "*" + goType
}
// Track all non-oneof fields. Note: synthetic oneofs are an
// implementation detail of proto3 optional fields:
// go/proto-proposals/proto3-presence.md, which should be tracked.
tag := ""
if (oneof == nil || oneof.Desc.IsSynthetic()) && message.isTracked {
tag = "`go:\"track\"`"
}
if oneof != nil && oneof.Fields[0] == field && !oneof.Desc.IsSynthetic() {
if oneof.Comments.Leading != "" {
g.P(oneof.Comments.Leading)
g.P()
}
g.P("// Fields of oneof ", opaqueOneofFieldName(oneof, message.isOpaque()), ":")
}
g.AnnotateSymbol(field.Parent.GoIdent.GoName+genid.BuilderSuffix_goname+"."+field.BuilderFieldName(), protogen.Annotation{Location: field.Location})
leadingComments := appendDeprecationSuffix(field.Comments.Leading,
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.P(leadingComments,
field.BuilderFieldName(), " ", goType, " ", tag)
if oneof != nil && oneof.Fields[len(oneof.Fields)-1] == field && !oneof.Desc.IsSynthetic() {
g.P("// -- end of ", opaqueOneofFieldName(oneof, message.isOpaque()))
}
}
g.P("}")
g.P()
opaqueGenBuildMethod(g, f, message, bName)
}
// opaqueGenBuildMethod generates the actual Build method for the builder
func opaqueGenBuildMethod(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, bName string) {
// Build method on the builder type.
fieldtrackNoInterface(g, message.noInterface)
g.P("func (b0 ", bName, ") Build() *", message.GoIdent, " {")
g.P("m0 := &", message.GoIdent, "{}")
if message.isTracked {
// Redeclare the builder and message types as local
// defined types, so that field tracking records the
// field uses against these types instead of the
// original struct types.
//
// TODO: Actually redeclare the struct types
// without `go:"track"` tags?
g.P("type (notrackB ", bName, "; notrackM ", message.GoIdent, ")")
g.P("b, x := (*notrackB)(&b0), (*notrackM)(m0)")
} else {
g.P("b, x := &b0, m0")
}
g.P("_, _ = b, x")
for _, field := range message.Fields {
oneof := field.Oneof
if oneof == nil && field.Desc.IsWeak() {
continue
}
if oneof != nil && !oneof.Desc.IsSynthetic() {
qual := ""
if fieldDefaultValue(g, f, message, field) != "nil" {
qual = "*"
}
g.P("if b.", field.BuilderFieldName(), " != nil {")
oneofName := opaqueOneofFieldName(oneof, message.isOpaque())
oneofType := opaqueFieldOneofType(field, message.isOpaque())
g.P("x.", oneofName, " = &", oneofType, "{", qual, "b.", field.BuilderFieldName(), "}")
g.P("}")
} else { // proto3 optional ends up here (synthetic oneof)
qual := ""
_, pointer := opaqueBuilderFieldGoType(g, f, message, field)
if pointer && message.isOpaque() && !field.Desc.IsList() && field.Desc.Kind() != protoreflect.StringKind {
qual = "*"
} else if message.isOpaque() && field.Desc.IsList() && field.Desc.Message() != nil {
qual = "&"
}
presence := usePresence(message, field)
if presence {
g.P("if b.", field.BuilderFieldName(), " != nil {")
}
if presence {
pi := opaqueFieldPresenceIndex(field)
g.P(protoimplPackage.Ident("X"), ".SetPresentNonAtomic(&(x.XXX_presence[", pi/32, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
}
goName := field.GoName
if message.isOpaque() {
goName = "xxx_hidden_" + goName
}
g.P("x.", goName, " = ", qual, "b.", field.BuilderFieldName())
if presence {
g.P("}")
}
}
}
g.P("return m0")
g.P("}")
g.P()
}
// opaqueBuilderFieldGoType does the same as opaqueFieldGoType, but corrects for
// types that are different in a builder
func opaqueBuilderFieldGoType(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) (goType string, pointer bool) {
goType, pointer = opaqueFieldGoType(g, f, message, field)
kind := field.Desc.Kind()
// Use []T instead of *[]T for opaque repeated lists.
if message.isOpaque() && field.Desc.IsList() {
pointer = false
}
// Use *T for optional fields.
optional := field.Desc.HasPresence()
if optional &&
kind != protoreflect.GroupKind &&
kind != protoreflect.MessageKind &&
kind != protoreflect.BytesKind &&
field.Desc.Cardinality() != protoreflect.Repeated {
pointer = true
}
return goType, pointer
}
func opaqueGenOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
// TODO: We should avoid generating these wrapper types in pure-opaque mode.
if !message.isOpen() {
for _, oneof := range message.Oneofs {
if oneof.Desc.IsSynthetic() {
continue
}
caseTypeName := opaqueOneofCaseTypeName(oneof)
g.P("type ", caseTypeName, " ", protoreflectPackage.Ident("FieldNumber"))
g.P("")
idx := f.allMessagesByPtr[message]
typesVar := messageTypesVarName(f)
g.P("func (x ", caseTypeName, ") String() string {")
g.P("md := ", typesVar, "[", idx, "].Descriptor()")
g.P("if x == 0 {")
g.P(`return "not set"`)
g.P("}")
g.P("return ", protoimplPackage.Ident("X"), ".MessageFieldStringOf(md, ", protoreflectPackage.Ident("FieldNumber"), "(x))")
g.P("}")
g.P()
}
}
for _, oneof := range message.Oneofs {
if oneof.Desc.IsSynthetic() {
continue
}
ifName := opaqueOneofInterfaceName(oneof)
g.P("type ", ifName, " interface {")
g.P(ifName, "()")
g.P("}")
g.P()
for _, field := range oneof.Fields {
name := opaqueFieldOneofType(field, message.isOpaque())
g.AnnotateSymbol(name.GoName, protogen.Annotation{Location: field.Location})
g.AnnotateSymbol(name.GoName+"."+field.GoName, protogen.Annotation{Location: field.Location})
g.P("type ", name, " struct {")
goType, _ := opaqueFieldGoType(g, f, message, field)
protobufTagValue := fieldProtobufTagValue(field)
if g.InternalStripForEditionsDiff() {
protobufTagValue = strings.ReplaceAll(protobufTagValue, ",proto3", "")
}
tags := structTags{
{"protobuf", protobufTagValue},
}
leadingComments := appendDeprecationSuffix(field.Comments.Leading,
field.Desc.ParentFile(),
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
g.P(leadingComments,
field.GoName, " ", goType, tags,
trailingComment(field.Comments.Trailing))
g.P("}")
g.P()
}
for _, field := range oneof.Fields {
g.P("func (*", opaqueFieldOneofType(field, message.isOpaque()), ") ", ifName, "() {}")
g.P()
}
}
}
// opaqueFieldGoType returns the Go type used for a field.
//
// If it returns pointer=true, the struct field is a pointer to the type.
func opaqueFieldGoType(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) (goType string, pointer bool) {
if field.Desc.IsWeak() {
return "struct{}", false
}
pointer = true
switch field.Desc.Kind() {
case protoreflect.BoolKind:
goType = "bool"
case protoreflect.EnumKind:
goType = g.QualifiedGoIdent(field.Enum.GoIdent)
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
goType = "int32"
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
goType = "uint32"
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
goType = "int64"
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
goType = "uint64"
case protoreflect.FloatKind:
goType = "float32"
case protoreflect.DoubleKind:
goType = "float64"
case protoreflect.StringKind:
goType = "string"
case protoreflect.BytesKind:
goType = "[]byte"
pointer = false
case protoreflect.MessageKind, protoreflect.GroupKind:
goType = opaqueMessageFieldGoType(g, f, field, message.isOpaque())
pointer = false
}
switch {
case field.Desc.IsList():
goType = "[]" + goType
pointer = false
case field.Desc.IsMap():
keyType, _ := opaqueFieldGoType(g, f, message, field.Message.Fields[0])
valType, _ := opaqueFieldGoType(g, f, message, field.Message.Fields[1])
return fmt.Sprintf("map[%v]%v", keyType, valType), false
}
// Extension fields always have pointer type, even when defined in a proto3 file.
if !field.Desc.IsExtension() && !field.Desc.HasPresence() {
pointer = false
}
if message.isOpaque() {
switch {
case field.Desc.IsList() && field.Desc.Message() != nil:
pointer = true
case !field.Desc.IsList() && field.Desc.Kind() == protoreflect.StringKind:
switch {
case field.Desc.HasPresence():
pointer = true
default:
pointer = false
}
default:
pointer = false
}
}
return goType, pointer
}
func opaqueMessageFieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field, isOpaque bool) string {
return "*" + g.QualifiedGoIdent(field.Message.GoIdent)
}
// opaqueFieldPresenceIndex returns the index to pass to presence functions.
//
// TODO: field.Desc.Index() would be simpler, and would give space to record the presence of oneof fields.
func opaqueFieldPresenceIndex(field *protogen.Field) int {
structFieldIndex := 0
for _, f := range field.Parent.Fields {
if field == f {
break
}
if f.Oneof == nil || isLastOneofField(f) {
structFieldIndex++
}
}
return structFieldIndex
}
// opaqueNumPresenceFields returns the number of fields that may be passed to presence functions.
//
// Since all fields in a oneof currently share a single entry in the presence bitmap,
// this is not just len(message.Fields).
func opaqueNumPresenceFields(message *messageInfo) int {
if len(message.Fields) == 0 {
return 0
}
return opaqueFieldPresenceIndex(message.Fields[len(message.Fields)-1]) + 1
}
func fieldtrackNoInterface(g *protogen.GeneratedFile, isTracked bool) {
if isTracked {
g.P("//go:nointerface")
}
}
// opaqueOneofFieldName returns the name of the struct field that holds
// the value of a oneof.
func opaqueOneofFieldName(oneof *protogen.Oneof, isOpaque bool) string {
if isOpaque {
return "xxx_hidden_" + oneof.GoName
}
return oneof.GoName
}
func opaqueFieldOneofType(field *protogen.Field, isOpaque bool) protogen.GoIdent {
ident := protogen.GoIdent{
GoImportPath: field.Parent.GoIdent.GoImportPath,
GoName: field.Parent.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.
Loop:
for {
for _, message := range field.Parent.Messages {
if message.GoIdent == ident {
ident.GoName += "_"
continue Loop
}
}
for _, enum := range field.Parent.Enums {
if enum.GoIdent == ident {
ident.GoName += "_"
continue Loop
}
}
return unexportIdent(ident, isOpaque)
}
}
// unexportIdent turns id into its unexported version (by lower-casing), but
// only if isOpaque is set. This function is used for oneof wrapper types,
// which remain exported in the non-opaque API for now.
func unexportIdent(id protogen.GoIdent, isOpaque bool) protogen.GoIdent {
if !isOpaque {
return id
}
r, sz := utf8.DecodeRuneInString(id.GoName)
if r == utf8.RuneError {
panic(fmt.Sprintf("Go identifier %q contains invalid UTF8?!", id.GoName))
}
r = unicode.ToLower(r)
id.GoName = string(r) + id.GoName[sz:]
return id
}
func opaqueOneofInterfaceName(oneof *protogen.Oneof) string {
return fmt.Sprintf("is%s_%s", oneof.Parent.GoIdent.GoName, oneof.GoName)
}
func opaqueOneofCaseTypeName(oneof *protogen.Oneof) string {
return fmt.Sprintf("case_%s_%s", oneof.Parent.GoIdent.GoName, oneof.GoName)
}
// isFirstOneofField reports whether this is the first field in a oneof.
func isFirstOneofField(field *protogen.Field) bool {
return field.Oneof != nil && field == field.Oneof.Fields[0] && !field.Oneof.Desc.IsSynthetic()
}
// isLastOneofField returns true if this is the last field in a oneof.
func isLastOneofField(field *protogen.Field) bool {
return field.Oneof != nil && field == field.Oneof.Fields[len(field.Oneof.Fields)-1]
}