// 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] }