mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-28 06:35:29 +00:00
eb7b468655
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>
1307 lines
44 KiB
Go
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]
|
|
}
|