diff options
Diffstat (limited to 'vendor/github.com/bufbuild/protocompile/options')
3 files changed, 2958 insertions, 0 deletions
diff --git a/vendor/github.com/bufbuild/protocompile/options/options.go b/vendor/github.com/bufbuild/protocompile/options/options.go new file mode 100644 index 0000000..e22bb2a --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/options/options.go @@ -0,0 +1,2267 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package options contains the logic for interpreting options. The parse step +// of compilation stores the options in uninterpreted form, which contains raw +// identifiers and literal values. +// +// The process of interpreting an option is to resolve identifiers, by examining +// descriptors for the google.protobuf.*Options types and their available +// extensions (custom options). As field names are resolved, the values can be +// type-checked against the types indicated in field descriptors. +// +// On success, the various fields and extensions of the options message are +// populated and the field holding the uninterpreted form is cleared. +package options + +import ( + "bytes" + "errors" + "fmt" + "math" + "strings" + + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/dynamicpb" + + "github.com/bufbuild/protocompile/ast" + "github.com/bufbuild/protocompile/internal" + "github.com/bufbuild/protocompile/internal/messageset" + "github.com/bufbuild/protocompile/linker" + "github.com/bufbuild/protocompile/parser" + "github.com/bufbuild/protocompile/reporter" + "github.com/bufbuild/protocompile/sourceinfo" +) + +type interpreter struct { + file file + resolver linker.Resolver + overrideDescriptorProto linker.File + + index sourceinfo.OptionIndex + pathBuffer []int32 + + reporter *reporter.Handler + lenient bool + + // lenienceEnabled is set to true when errors reported to reporter + // should be lenient + lenienceEnabled bool + lenientErrReported bool +} + +type file interface { + parser.Result + ResolveMessageLiteralExtensionName(ast.IdentValueNode) string +} + +type noResolveFile struct { + parser.Result +} + +func (n noResolveFile) ResolveMessageLiteralExtensionName(ast.IdentValueNode) string { + return "" +} + +// InterpreterOption is an option that can be passed to InterpretOptions and +// its variants. +type InterpreterOption func(*interpreter) + +// WithOverrideDescriptorProto returns an option that indicates that the given file +// should be consulted when looking up a definition for an option type. The given +// file should usually have the path "google/protobuf/descriptor.proto". The given +// file will only be consulted if the option type is otherwise not visible to the +// file whose options are being interpreted. +func WithOverrideDescriptorProto(f linker.File) InterpreterOption { + return func(interp *interpreter) { + interp.overrideDescriptorProto = f + } +} + +// InterpretOptions interprets options in the given linked result, returning +// an index that can be used to generate source code info. This step mutates +// the linked result's underlying proto to move option elements out of the +// "uninterpreted_option" fields and into proper option fields and extensions. +// +// The given handler is used to report errors and warnings. If any errors are +// reported, this function returns a non-nil error. +func InterpretOptions(linked linker.Result, handler *reporter.Handler, opts ...InterpreterOption) (sourceinfo.OptionIndex, error) { + return interpretOptions(false, linked, linker.ResolverFromFile(linked), handler, opts) +} + +// InterpretOptionsLenient interprets options in a lenient/best-effort way in +// the given linked result, returning an index that can be used to generate +// source code info. This step mutates the linked result's underlying proto to +// move option elements out of the "uninterpreted_option" fields and into proper +// option fields and extensions. +// +// In lenient more, errors resolving option names and type errors are ignored. +// Any options that are uninterpretable (due to such errors) will remain in the +// "uninterpreted_option" fields. +func InterpretOptionsLenient(linked linker.Result, opts ...InterpreterOption) (sourceinfo.OptionIndex, error) { + return interpretOptions(true, linked, linker.ResolverFromFile(linked), reporter.NewHandler(nil), opts) +} + +// InterpretUnlinkedOptions does a best-effort attempt to interpret options in +// the given parsed result, returning an index that can be used to generate +// source code info. This step mutates the parsed result's underlying proto to +// move option elements out of the "uninterpreted_option" fields and into proper +// option fields and extensions. +// +// This is the same as InterpretOptionsLenient except that it accepts an +// unlinked result. Because the file is unlinked, custom options cannot be +// interpreted. Other errors resolving option names or type errors will be +// effectively ignored. Any options that are uninterpretable (due to such +// errors) will remain in the "uninterpreted_option" fields. +func InterpretUnlinkedOptions(parsed parser.Result, opts ...InterpreterOption) (sourceinfo.OptionIndex, error) { + return interpretOptions(true, noResolveFile{parsed}, nil, reporter.NewHandler(nil), opts) +} + +func interpretOptions(lenient bool, file file, res linker.Resolver, handler *reporter.Handler, interpOpts []InterpreterOption) (sourceinfo.OptionIndex, error) { + interp := &interpreter{ + file: file, + resolver: res, + lenient: lenient, + reporter: handler, + index: sourceinfo.OptionIndex{}, + pathBuffer: make([]int32, 0, 16), + } + for _, opt := range interpOpts { + opt(interp) + } + // We have to do this in two phases. First we interpret non-custom options. + // This allows us to handle standard options and features that may needed to + // correctly reference the custom options in the second phase. + if err := interp.interpretFileOptions(file, false); err != nil { + return nil, err + } + // Now we can do custom options. + if err := interp.interpretFileOptions(file, true); err != nil { + return nil, err + } + return interp.index, nil +} + +func (interp *interpreter) handleErrorf(span ast.SourceSpan, msg string, args ...interface{}) error { + if interp.lenienceEnabled { + interp.lenientErrReported = true + return nil + } + return interp.reporter.HandleErrorf(span, msg, args...) +} + +func (interp *interpreter) handleErrorWithPos(span ast.SourceSpan, err error) error { + if interp.lenienceEnabled { + interp.lenientErrReported = true + return nil + } + return interp.reporter.HandleErrorWithPos(span, err) +} + +func (interp *interpreter) handleError(err error) error { + if interp.lenienceEnabled { + interp.lenientErrReported = true + return nil + } + return interp.reporter.HandleError(err) +} + +func (interp *interpreter) interpretFileOptions(file file, customOpts bool) error { + fd := file.FileDescriptorProto() + prefix := fd.GetPackage() + if prefix != "" { + prefix += "." + } + err := interpretElementOptions(interp, fd.GetName(), targetTypeFile, fd, customOpts) + if err != nil { + return err + } + for _, md := range fd.GetMessageType() { + fqn := prefix + md.GetName() + if err := interp.interpretMessageOptions(fqn, md, customOpts); err != nil { + return err + } + } + for _, fld := range fd.GetExtension() { + fqn := prefix + fld.GetName() + if err := interp.interpretFieldOptions(fqn, fld, customOpts); err != nil { + return err + } + } + for _, ed := range fd.GetEnumType() { + fqn := prefix + ed.GetName() + if err := interp.interpretEnumOptions(fqn, ed, customOpts); err != nil { + return err + } + } + for _, sd := range fd.GetService() { + fqn := prefix + sd.GetName() + err := interpretElementOptions(interp, fqn, targetTypeService, sd, customOpts) + if err != nil { + return err + } + for _, mtd := range sd.GetMethod() { + mtdFqn := fqn + "." + mtd.GetName() + err := interpretElementOptions(interp, mtdFqn, targetTypeMethod, mtd, customOpts) + if err != nil { + return err + } + } + } + return nil +} + +func resolveDescriptor[T protoreflect.Descriptor](res linker.Resolver, name string) T { + var zero T + if res == nil { + return zero + } + if len(name) > 0 && name[0] == '.' { + name = name[1:] + } + desc, _ := res.FindDescriptorByName(protoreflect.FullName(name)) + typedDesc, ok := desc.(T) + if ok { + return typedDesc + } + return zero +} + +func (interp *interpreter) resolveExtensionType(name string) (protoreflect.ExtensionTypeDescriptor, error) { + if interp.resolver == nil { + return nil, protoregistry.NotFound + } + if len(name) > 0 && name[0] == '.' { + name = name[1:] + } + ext, err := interp.resolver.FindExtensionByName(protoreflect.FullName(name)) + if err != nil { + return nil, err + } + return ext.TypeDescriptor(), nil +} + +func (interp *interpreter) resolveOptionsType(name string) protoreflect.MessageDescriptor { + md := resolveDescriptor[protoreflect.MessageDescriptor](interp.resolver, name) + if md != nil { + return md + } + if interp.overrideDescriptorProto == nil { + return nil + } + if len(name) > 0 && name[0] == '.' { + name = name[1:] + } + desc := interp.overrideDescriptorProto.FindDescriptorByName(protoreflect.FullName(name)) + if md, ok := desc.(protoreflect.MessageDescriptor); ok { + return md + } + return nil +} + +func (interp *interpreter) nodeInfo(n ast.Node) ast.NodeInfo { + return interp.file.FileNode().NodeInfo(n) +} + +func (interp *interpreter) interpretMessageOptions(fqn string, md *descriptorpb.DescriptorProto, customOpts bool) error { + err := interpretElementOptions(interp, fqn, targetTypeMessage, md, customOpts) + if err != nil { + return err + } + for _, fld := range md.GetField() { + fldFqn := fqn + "." + fld.GetName() + if err := interp.interpretFieldOptions(fldFqn, fld, customOpts); err != nil { + return err + } + } + for _, ood := range md.GetOneofDecl() { + oodFqn := fqn + "." + ood.GetName() + err := interpretElementOptions(interp, oodFqn, targetTypeOneof, ood, customOpts) + if err != nil { + return err + } + } + for _, fld := range md.GetExtension() { + fldFqn := fqn + "." + fld.GetName() + if err := interp.interpretFieldOptions(fldFqn, fld, customOpts); err != nil { + return err + } + } + for _, er := range md.GetExtensionRange() { + erFqn := fmt.Sprintf("%s.%d-%d", fqn, er.GetStart(), er.GetEnd()) + err := interpretElementOptions(interp, erFqn, targetTypeExtensionRange, er, customOpts) + if err != nil { + return err + } + } + for _, nmd := range md.GetNestedType() { + nmdFqn := fqn + "." + nmd.GetName() + if err := interp.interpretMessageOptions(nmdFqn, nmd, customOpts); err != nil { + return err + } + } + for _, ed := range md.GetEnumType() { + edFqn := fqn + "." + ed.GetName() + if err := interp.interpretEnumOptions(edFqn, ed, customOpts); err != nil { + return err + } + } + + // We also copy features for map fields down to their synthesized key and value fields. + for _, fld := range md.GetField() { + entryName := internal.InitCap(internal.JSONName(fld.GetName())) + "Entry" + if fld.GetLabel() != descriptorpb.FieldDescriptorProto_LABEL_REPEATED || + fld.GetType() != descriptorpb.FieldDescriptorProto_TYPE_MESSAGE && + fld.GetTypeName() != "."+fqn+"."+entryName { + // can't be a map field + continue + } + if fld.Options == nil || fld.Options.Features == nil { + // no features to propagate + continue + } + for _, nmd := range md.GetNestedType() { + if nmd.GetName() == entryName { + // found the entry message + if !nmd.GetOptions().GetMapEntry() { + break // not a map + } + for _, mapField := range nmd.Field { + if mapField.Options == nil { + mapField.Options = &descriptorpb.FieldOptions{} + } + features := proto.Clone(fld.Options.Features).(*descriptorpb.FeatureSet) //nolint:errcheck + if mapField.Options.Features != nil { + proto.Merge(features, mapField.Options.Features) + } + mapField.Options.Features = features + } + break + } + } + } + + return nil +} + +var emptyFieldOptions = &descriptorpb.FieldOptions{} + +func (interp *interpreter) interpretFieldOptions(fqn string, fld *descriptorpb.FieldDescriptorProto, customOpts bool) error { + opts := fld.GetOptions() + emptyOptionsAlreadyPresent := opts != nil && len(opts.GetUninterpretedOption()) == 0 + + // For non-custom phase, first process pseudo-options + if len(opts.GetUninterpretedOption()) > 0 && !customOpts { + interp.enableLenience(true) + err := interp.interpretFieldPseudoOptions(fqn, fld, opts) + interp.enableLenience(false) + if err != nil { + return err + } + } + + // Must re-check length of uninterpreted options since above step could remove some. + if len(opts.GetUninterpretedOption()) == 0 { + // If the message has no other interpreted options, we clear it out. But don't + // do that if the descriptor came in with empty options or if it already has + // interpreted option fields. + if opts != nil && !emptyOptionsAlreadyPresent && proto.Equal(fld.Options, emptyFieldOptions) { + fld.Options = nil + } + return nil + } + + // Then process actual options. + return interpretElementOptions(interp, fqn, targetTypeField, fld, customOpts) +} + +func (interp *interpreter) interpretFieldPseudoOptions(fqn string, fld *descriptorpb.FieldDescriptorProto, opts *descriptorpb.FieldOptions) error { + scope := "field " + fqn + uo := opts.UninterpretedOption + + // process json_name pseudo-option + if index, err := internal.FindOption(interp.file, interp.handleErrorf, scope, uo, "json_name"); err != nil { + return err + } else if index >= 0 { + opt := uo[index] + optNode := interp.file.OptionNode(opt) + if opt.StringValue == nil { + return interp.handleErrorf(interp.nodeInfo(optNode.GetValue()), "%s: expecting string value for json_name option", scope) + } + jsonName := string(opt.StringValue) + // Extensions don't support custom json_name values. + // If the value is already set (via the descriptor) and doesn't match the default value, return an error. + if fld.GetExtendee() != "" && jsonName != "" && jsonName != internal.JSONName(fld.GetName()) { + return interp.handleErrorf(interp.nodeInfo(optNode.GetName()), "%s: option json_name is not allowed on extensions", scope) + } + // attribute source code info + if on, ok := optNode.(*ast.OptionNode); ok { + interp.index[on] = &sourceinfo.OptionSourceInfo{Path: []int32{-1, internal.FieldJSONNameTag}} + } + uo = internal.RemoveOption(uo, index) + if strings.HasPrefix(jsonName, "[") && strings.HasSuffix(jsonName, "]") { + return interp.handleErrorf(interp.nodeInfo(optNode.GetValue()), "%s: option json_name value cannot start with '[' and end with ']'; that is reserved for representing extensions", scope) + } + fld.JsonName = proto.String(jsonName) + } + + // and process default pseudo-option + if index, err := interp.processDefaultOption(scope, fqn, fld, uo); err != nil { + return err + } else if index >= 0 { + // attribute source code info + optNode := interp.file.OptionNode(uo[index]) + if on, ok := optNode.(*ast.OptionNode); ok { + interp.index[on] = &sourceinfo.OptionSourceInfo{Path: []int32{-1, internal.FieldDefaultTag}} + } + uo = internal.RemoveOption(uo, index) + } + + opts.UninterpretedOption = uo + return nil +} + +func (interp *interpreter) processDefaultOption(scope string, fqn string, fld *descriptorpb.FieldDescriptorProto, uos []*descriptorpb.UninterpretedOption) (defaultIndex int, err error) { + found, err := internal.FindOption(interp.file, interp.handleErrorf, scope, uos, "default") + if err != nil || found == -1 { + return -1, err + } + opt := uos[found] + optNode := interp.file.OptionNode(opt) + if fld.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED { + return -1, interp.handleErrorf(interp.nodeInfo(optNode.GetName()), "%s: default value cannot be set because field is repeated", scope) + } + if fld.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP || fld.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE { + return -1, interp.handleErrorf(interp.nodeInfo(optNode.GetName()), "%s: default value cannot be set because field is a message", scope) + } + mc := &internal.MessageContext{ + File: interp.file, + ElementName: fqn, + ElementType: descriptorType(fld), + Option: opt, + } + + val := optNode.GetValue() + var v interface{} + if val.Value() == nil { + // no value in the AST, so we dig the value out of the uninterpreted option proto + v, err = interp.defaultValueFromProto(mc, fld, opt, val) + } else { + // compute value from AST + v, err = interp.defaultValue(mc, fld, val) + } + if err != nil { + return -1, interp.handleError(err) + } + + if str, ok := v.(string); ok { + fld.DefaultValue = proto.String(str) + } else if b, ok := v.([]byte); ok { + fld.DefaultValue = proto.String(encodeDefaultBytes(b)) + } else { + var flt float64 + var ok bool + if flt, ok = v.(float64); !ok { + var flt32 float32 + if flt32, ok = v.(float32); ok { + flt = float64(flt32) + } + } + if ok { + switch { + case math.IsInf(flt, 1): + fld.DefaultValue = proto.String("inf") + case math.IsInf(flt, -1): + fld.DefaultValue = proto.String("-inf") + case math.IsNaN(flt): + fld.DefaultValue = proto.String("nan") + default: + fld.DefaultValue = proto.String(fmt.Sprintf("%v", v)) + } + } else { + fld.DefaultValue = proto.String(fmt.Sprintf("%v", v)) + } + } + return found, nil +} + +func (interp *interpreter) defaultValue(mc *internal.MessageContext, fld *descriptorpb.FieldDescriptorProto, val ast.ValueNode) (interface{}, error) { + if _, ok := val.(*ast.MessageLiteralNode); ok { + return -1, reporter.Errorf(interp.nodeInfo(val), "%vdefault value cannot be a message", mc) + } + if fld.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM { + ed := resolveDescriptor[protoreflect.EnumDescriptor](interp.resolver, fld.GetTypeName()) + if ed == nil { + return -1, reporter.Errorf(interp.nodeInfo(val), "%vunable to resolve enum type %q for field %q", mc, fld.GetTypeName(), fld.GetName()) + } + _, name, err := interp.enumFieldValue(mc, ed, val, false) + if err != nil { + return -1, err + } + return string(name), nil + } + return interp.scalarFieldValue(mc, fld.GetType(), val, false) +} + +func (interp *interpreter) defaultValueFromProto(mc *internal.MessageContext, fld *descriptorpb.FieldDescriptorProto, opt *descriptorpb.UninterpretedOption, node ast.Node) (interface{}, error) { + if opt.AggregateValue != nil { + return -1, reporter.Errorf(interp.nodeInfo(node), "%vdefault value cannot be a message", mc) + } + if fld.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM { + ed := resolveDescriptor[protoreflect.EnumDescriptor](interp.resolver, fld.GetTypeName()) + if ed == nil { + return -1, reporter.Errorf(interp.nodeInfo(node), "%vunable to resolve enum type %q for field %q", mc, fld.GetTypeName(), fld.GetName()) + } + _, name, err := interp.enumFieldValueFromProto(mc, ed, opt, node) + if err != nil { + return nil, err + } + return string(name), nil + } + return interp.scalarFieldValueFromProto(mc, fld.GetType(), opt, node) +} + +func encodeDefaultBytes(b []byte) string { + var buf bytes.Buffer + internal.WriteEscapedBytes(&buf, b) + return buf.String() +} + +func (interp *interpreter) interpretEnumOptions(fqn string, ed *descriptorpb.EnumDescriptorProto, customOpts bool) error { + err := interpretElementOptions(interp, fqn, targetTypeEnum, ed, customOpts) + if err != nil { + return err + } + for _, evd := range ed.GetValue() { + evdFqn := fqn + "." + evd.GetName() + err := interpretElementOptions(interp, evdFqn, targetTypeEnumValue, evd, customOpts) + if err != nil { + return err + } + } + return nil +} + +func interpretElementOptions[Elem elementType[OptsStruct, Opts], OptsStruct any, Opts optionsType[OptsStruct]]( + interp *interpreter, + fqn string, + target *targetType[Elem, OptsStruct, Opts], + elem Elem, + customOpts bool, +) error { + opts := elem.GetOptions() + uninterpreted := opts.GetUninterpretedOption() + if len(uninterpreted) > 0 { + remain, err := interp.interpretOptions(fqn, target.t, elem, opts, uninterpreted, customOpts) + if err != nil { + return err + } + target.setUninterpretedOptions(opts, remain) + } else if customOpts { + // If customOpts is true, we are in second pass of interpreting. + // For second pass, even if there are no options to interpret, we still + // need to verify feature usage. + features := opts.GetFeatures() + var msg protoreflect.Message + if len(features.ProtoReflect().GetUnknown()) > 0 { + // We need to first convert to a message that uses the sources' definition + // of FeatureSet. + optsDesc := opts.ProtoReflect().Descriptor() + optsFqn := string(optsDesc.FullName()) + if md := interp.resolveOptionsType(optsFqn); md != nil { + dm := dynamicpb.NewMessage(md) + if err := cloneInto(dm, opts, interp.resolver); err != nil { + node := interp.file.Node(elem) + return interp.handleError(reporter.Error(interp.nodeInfo(node), err)) + } + msg = dm + } + } + if msg == nil { + msg = opts.ProtoReflect() + } + err := interp.validateRecursive(false, msg, "", elem, nil, false, false, false) + if err != nil { + return err + } + } + return nil +} + +// interpretOptions processes the options in uninterpreted, which are interpreted as fields +// of the given opts message. The first return value is the features to use for child elements. +// On success, the latter two return values will usually be nil, nil. But if the current +// operation is lenient, it may return a non-nil slice of uninterpreted options on success. +// In such a case, the returned slice contains the options which could not be interpreted. +func (interp *interpreter) interpretOptions( + fqn string, + targetType descriptorpb.FieldOptions_OptionTargetType, + element, opts proto.Message, + uninterpreted []*descriptorpb.UninterpretedOption, + customOpts bool, +) ([]*descriptorpb.UninterpretedOption, error) { + optsDesc := opts.ProtoReflect().Descriptor() + optsFqn := string(optsDesc.FullName()) + var msg protoreflect.Message + // see if the parse included an override copy for these options + if md := interp.resolveOptionsType(optsFqn); md != nil { + dm := dynamicpb.NewMessage(md) + if err := cloneInto(dm, opts, interp.resolver); err != nil { + node := interp.file.Node(element) + return nil, interp.handleError(reporter.Error(interp.nodeInfo(node), err)) + } + msg = dm + } else { + msg = proto.Clone(opts).ProtoReflect() + } + + mc := &internal.MessageContext{ + File: interp.file, + ElementName: fqn, + ElementType: descriptorType(element), + } + var remain []*descriptorpb.UninterpretedOption + for _, uo := range uninterpreted { + isCustom := uo.Name[0].GetIsExtension() + if isCustom != customOpts { + // We're not looking at these this phase. + remain = append(remain, uo) + continue + } + firstName := uo.Name[0].GetNamePart() + if targetType == descriptorpb.FieldOptions_TARGET_TYPE_FIELD && + !isCustom && (firstName == "default" || firstName == "json_name") { + // Field pseudo-option that we can skip and is handled elsewhere. + remain = append(remain, uo) + continue + } + node := interp.file.OptionNode(uo) + if !isCustom && firstName == "uninterpreted_option" { + if interp.lenient { + remain = append(remain, uo) + continue + } + // uninterpreted_option might be found reflectively, but is not actually valid for use + if err := interp.handleErrorf(interp.nodeInfo(node.GetName()), "%vinvalid option 'uninterpreted_option'", mc); err != nil { + return nil, err + } + } + mc.Option = uo + interp.enableLenience(true) + srcInfo, err := interp.interpretField(targetType, mc, msg, uo, 0, interp.pathBuffer) + interp.enableLenience(false) + if err != nil { + return nil, err + } + if interp.lenientErrReported { + remain = append(remain, uo) + continue + } + + if srcInfo != nil { + if optn, ok := node.(*ast.OptionNode); ok { + interp.index[optn] = srcInfo + } + } + } + + // customOpts is true for the second pass, which is also when we want to validate feature usage. + doValidation := customOpts + if doValidation { + validateRequiredFields := !interp.lenient + err := interp.validateRecursive(validateRequiredFields, msg, "", element, nil, false, false, false) + if err != nil { + return nil, err + } + } + + if interp.lenient { + // If we're lenient, then we don't want to clobber the passed in message + // and leave it partially populated. So we convert into a copy first + optsClone := opts.ProtoReflect().New().Interface() + if err := cloneInto(optsClone, msg.Interface(), interp.resolver); err != nil { + // TODO: do this in a more granular way, so we can convert individual + // fields and leave bad ones uninterpreted instead of skipping all of + // the work we've done so far. + return uninterpreted, nil + } + if doValidation { + if err := proto.CheckInitialized(optsClone); err != nil { + // Conversion from dynamic message failed to set some required fields. + // TODO above applies here as well... + return uninterpreted, nil + } + } + // conversion from dynamic message above worked, so now + // it is safe to overwrite the passed in message + proto.Reset(opts) + proto.Merge(opts, optsClone) + + return remain, nil + } + + // now try to convert into the passed in message and fail if not successful + if err := cloneInto(opts, msg.Interface(), interp.resolver); err != nil { + node := interp.file.Node(element) + return nil, interp.handleError(reporter.Error(interp.nodeInfo(node), err)) + } + + return remain, nil +} + +// checkFieldUsage verifies that the given option field can be used +// for the given target type. It reports an error if not and returns +// a non-nil error if the handler returned a non-nil error. +func (interp *interpreter) checkFieldUsage( + targetType descriptorpb.FieldOptions_OptionTargetType, + fld protoreflect.FieldDescriptor, + node ast.Node, +) error { + msgOpts, _ := fld.ContainingMessage().Options().(*descriptorpb.MessageOptions) + if msgOpts.GetMessageSetWireFormat() && !messageset.CanSupportMessageSets() { + err := interp.handleErrorf(interp.nodeInfo(node), "field %q may not be used in an option: it uses 'message set wire format' legacy proto1 feature which is not supported", fld.FullName()) + if err != nil { + return err + } + } + + opts, ok := fld.Options().(*descriptorpb.FieldOptions) + if !ok { + return nil + } + targetTypes := opts.GetTargets() + if len(targetTypes) == 0 { + return nil + } + for _, allowedType := range targetTypes { + if allowedType == targetType { + return nil + } + } + allowedTypes := make([]string, len(targetTypes)) + for i, t := range targetTypes { + allowedTypes[i] = targetTypeString(t) + } + if len(targetTypes) == 1 && targetTypes[0] == descriptorpb.FieldOptions_TARGET_TYPE_UNKNOWN { + return interp.handleErrorf(interp.nodeInfo(node), "field %q may not be used in an option (it declares no allowed target types)", fld.FullName()) + } + return interp.handleErrorf(interp.nodeInfo(node), "field %q is allowed on [%s], not on %s", fld.FullName(), strings.Join(allowedTypes, ","), targetTypeString(targetType)) +} + +func targetTypeString(t descriptorpb.FieldOptions_OptionTargetType) string { + return strings.ToLower(strings.ReplaceAll(strings.TrimPrefix(t.String(), "TARGET_TYPE_"), "_", " ")) +} + +func editionString(t descriptorpb.Edition) string { + return strings.ToLower(strings.ReplaceAll(strings.TrimPrefix(t.String(), "EDITION_"), "_", "-")) +} + +func cloneInto(dest proto.Message, src proto.Message, res linker.Resolver) error { + if dest.ProtoReflect().Descriptor() == src.ProtoReflect().Descriptor() { + proto.Reset(dest) + proto.Merge(dest, src) + return nil + } + + // If descriptors are not the same, we could have field descriptors in src that + // don't match the ones in dest. There's no easy/sane way to handle that. So we + // just marshal to bytes and back to do this + marshaler := proto.MarshalOptions{ + // We've already validated required fields before this point, + // so we can allow partial here. + AllowPartial: true, + } + data, err := marshaler.Marshal(src) + if err != nil { + return err + } + unmarshaler := proto.UnmarshalOptions{AllowPartial: true} + if res != nil { + unmarshaler.Resolver = res + } else { + // Use a typed nil, which returns "not found" to all queries + // and prevents fallback to protoregistry.GlobalTypes. + unmarshaler.Resolver = (*protoregistry.Types)(nil) + } + return unmarshaler.Unmarshal(data, dest) +} + +func (interp *interpreter) validateRecursive( + validateRequiredFields bool, + msg protoreflect.Message, + prefix string, + element proto.Message, + path []int32, + isFeatures bool, + inFeatures bool, + inMap bool, +) error { + if validateRequiredFields { + flds := msg.Descriptor().Fields() + var missingFields []string + for i := 0; i < flds.Len(); i++ { + fld := flds.Get(i) + if fld.Cardinality() == protoreflect.Required && !msg.Has(fld) { + missingFields = append(missingFields, fmt.Sprintf("%s%s", prefix, fld.Name())) + } + } + if len(missingFields) > 0 { + node := interp.findOptionNode(path, element) + err := interp.handleErrorf(interp.nodeInfo(node), "error in %s options: some required fields missing: %v", descriptorType(element), strings.Join(missingFields, ", ")) + if err != nil { + return err + } + } + } + + var err error + msg.Range(func(fld protoreflect.FieldDescriptor, val protoreflect.Value) bool { + chpath := path + if !inMap { + chpath = append(chpath, int32(fld.Number())) + } + chInFeatures := isFeatures || inFeatures + chIsFeatures := !chInFeatures && len(path) == 0 && fld.Name() == "features" + + if (isFeatures || (inFeatures && fld.IsExtension())) && + interp.file.FileNode().Name() == fld.ParentFile().Path() { + var what, name string + if fld.IsExtension() { + what = "custom feature" + name = "(" + string(fld.FullName()) + ")" + } else { + what = "feature" + name = string(fld.Name()) + } + node := interp.findOptionNode(path, element) + err = interp.handleErrorf(interp.nodeInfo(node), "%s %s cannot be used from the same file in which it is defined", what, name) + if err != nil { + return false + } + } + + if chInFeatures { + // Validate feature usage against feature settings. + + // First, check the feature support settings of the field. + opts, _ := fld.Options().(*descriptorpb.FieldOptions) + edition := interp.file.FileDescriptorProto().GetEdition() + if opts != nil && opts.FeatureSupport != nil { + err = interp.validateFeatureSupport(edition, opts.FeatureSupport, "field", string(fld.FullName()), chpath, element) + if err != nil { + return false + } + } + // Then, if it's an enum or has an enum, check the feature support settings of the enum values. + var enum protoreflect.EnumDescriptor + if fld.Enum() != nil { + enum = fld.Enum() + } else if fld.IsMap() && fld.MapValue().Enum() != nil { + enum = fld.MapValue().Enum() + } + if enum != nil { + switch { + case fld.IsMap(): + val.Map().Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool { + // Can't construct path to particular map entry since we don't this entry's index. + // So we leave chpath alone, and it will have to point to the whole map value (or + // the first entry if the map is de-structured across multiple option statements). + err = interp.validateEnumValueFeatureSupport(edition, enum, v.Enum(), chpath, element) + return err == nil + }) + if err != nil { + return false + } + case fld.IsList(): + sl := val.List() + for i := 0; i < sl.Len(); i++ { + v := sl.Get(i) + err = interp.validateEnumValueFeatureSupport(edition, enum, v.Enum(), append(chpath, int32(i)), element) + if err != nil { + return false + } + } + default: + err = interp.validateEnumValueFeatureSupport(edition, enum, val.Enum(), chpath, element) + if err != nil { + return false + } + } + } + } + + // If it's a message or contains a message, recursively validate fields in those messages. + switch { + case fld.IsMap() && fld.MapValue().Message() != nil: + val.Map().Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { + chprefix := fmt.Sprintf("%s%s[%v].", prefix, fieldName(fld), k) + err = interp.validateRecursive(validateRequiredFields, v.Message(), chprefix, element, chpath, chIsFeatures, chInFeatures, true) + return err == nil + }) + if err != nil { + return false + } + case fld.IsList() && fld.Message() != nil: + sl := val.List() + for i := 0; i < sl.Len(); i++ { + v := sl.Get(i) + chprefix := fmt.Sprintf("%s%s[%d].", prefix, fieldName(fld), i) + if !inMap { + chpath = append(chpath, int32(i)) + } + err = interp.validateRecursive(validateRequiredFields, v.Message(), chprefix, element, chpath, chIsFeatures, chInFeatures, inMap) + if err != nil { + return false + } + } + case !fld.IsMap() && fld.Message() != nil: + chprefix := fmt.Sprintf("%s%s.", prefix, fieldName(fld)) + err = interp.validateRecursive(validateRequiredFields, val.Message(), chprefix, element, chpath, chIsFeatures, chInFeatures, inMap) + if err != nil { + return false + } + } + return true + }) + return err +} + +func (interp *interpreter) validateEnumValueFeatureSupport( + edition descriptorpb.Edition, + enum protoreflect.EnumDescriptor, + number protoreflect.EnumNumber, + path []int32, + element proto.Message, +) error { + enumVal := enum.Values().ByNumber(number) + if enumVal == nil { + return nil + } + enumValOpts, _ := enumVal.Options().(*descriptorpb.EnumValueOptions) + if enumValOpts == nil || enumValOpts.FeatureSupport == nil { + return nil + } + return interp.validateFeatureSupport(edition, enumValOpts.FeatureSupport, "enum value", string(enumVal.Name()), path, element) +} + +func (interp *interpreter) validateFeatureSupport( + edition descriptorpb.Edition, + featureSupport *descriptorpb.FieldOptions_FeatureSupport, + what string, + name string, + path []int32, + element proto.Message, +) error { + if featureSupport.EditionIntroduced != nil && edition < featureSupport.GetEditionIntroduced() { + node := interp.findOptionNode(path, element) + err := interp.handleErrorf(interp.nodeInfo(node), "%s %q was not introduced until edition %s", what, name, editionString(featureSupport.GetEditionIntroduced())) + if err != nil { + return err + } + } + if featureSupport.EditionRemoved != nil && edition >= featureSupport.GetEditionRemoved() { + node := interp.findOptionNode(path, element) + err := interp.handleErrorf(interp.nodeInfo(node), "%s %q was removed in edition %s", what, name, editionString(featureSupport.GetEditionRemoved())) + if err != nil { + return err + } + } + if featureSupport.EditionDeprecated != nil && edition >= featureSupport.GetEditionDeprecated() { + node := interp.findOptionNode(path, element) + var suffix string + if featureSupport.GetDeprecationWarning() != "" { + suffix = ": " + featureSupport.GetDeprecationWarning() + } + interp.reporter.HandleWarningf(interp.nodeInfo(node), "%s %q is deprecated as of edition %s%s", what, name, editionString(featureSupport.GetEditionDeprecated()), suffix) + } + return nil +} + +func (interp *interpreter) findOptionNode( + path []int32, + element proto.Message, +) ast.Node { + elementNode := interp.file.Node(element) + nodeWithOpts, _ := elementNode.(ast.NodeWithOptions) + if nodeWithOpts == nil { + return elementNode + } + node, _ := findOptionNode[*ast.OptionNode]( + path, + optionsRanger{nodeWithOpts}, + func(n *ast.OptionNode) *sourceinfo.OptionSourceInfo { + return interp.index[n] + }, + ) + if node != nil { + return node + } + return elementNode +} + +func findOptionNode[N ast.Node]( + path []int32, + nodes interface { + Range(func(N, ast.ValueNode) bool) + }, + srcInfoAccessor func(N) *sourceinfo.OptionSourceInfo, +) (ast.Node, int) { + var bestMatch ast.Node + var bestMatchLen int + nodes.Range(func(node N, val ast.ValueNode) bool { + srcInfo := srcInfoAccessor(node) + if srcInfo == nil { + // can happen if we are lenient when interpreting -- this node + // could not be interpreted and thus has no source info; skip + return true + } + if srcInfo.Path[0] < 0 { + // negative first value means it's a field pseudo-option; skip + return true + } + match, matchLen := findOptionValueNode(path, node, val, srcInfo) + if matchLen > bestMatchLen { + bestMatch = match + bestMatchLen = matchLen + if matchLen >= len(path) { + // not going to find a better one + return false + } + } + return true + }) + return bestMatch, bestMatchLen +} + +type optionsRanger struct { + node ast.NodeWithOptions +} + +func (r optionsRanger) Range(f func(*ast.OptionNode, ast.ValueNode) bool) { + r.node.RangeOptions(func(optNode *ast.OptionNode) bool { + return f(optNode, optNode.Val) + }) +} + +type valueRanger []ast.ValueNode + +func (r valueRanger) Range(f func(ast.ValueNode, ast.ValueNode) bool) { + for _, elem := range r { + if !f(elem, elem) { + return + } + } +} + +type fieldRanger map[*ast.MessageFieldNode]*sourceinfo.OptionSourceInfo + +func (r fieldRanger) Range(f func(*ast.MessageFieldNode, ast.ValueNode) bool) { + for elem := range r { + if !f(elem, elem.Val) { + return + } + } +} + +func isPathMatch(a, b []int32) bool { + length := len(a) + if len(b) < length { + length = len(b) + } + for i := 0; i < length; i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func findOptionValueNode( + path []int32, + node ast.Node, + value ast.ValueNode, + srcInfo *sourceinfo.OptionSourceInfo, +) (ast.Node, int) { + srcInfoPath := srcInfo.Path + if _, ok := srcInfo.Children.(*sourceinfo.ArrayLiteralSourceInfo); ok { + // Last path element for array source info is the index of the + // first element. So exclude in the comparison, since path could + // indicate a later index, which is present in the array. + srcInfoPath = srcInfo.Path[:len(srcInfo.Path)-1] + } + + if !isPathMatch(path, srcInfoPath) { + return nil, 0 + } + if len(srcInfoPath) >= len(path) { + return node, len(path) + } + + switch children := srcInfo.Children.(type) { + case *sourceinfo.ArrayLiteralSourceInfo: + array, ok := value.(*ast.ArrayLiteralNode) + if !ok { + break // should never happen + } + var i int + match, matchLen := findOptionNode[ast.ValueNode]( + path, + valueRanger(array.Elements), + func(_ ast.ValueNode) *sourceinfo.OptionSourceInfo { + val := &children.Elements[i] + i++ + return val + }, + ) + if match != nil { + return match, matchLen + } + + case *sourceinfo.MessageLiteralSourceInfo: + match, matchLen := findOptionNode[*ast.MessageFieldNode]( + path, + fieldRanger(children.Fields), + func(n *ast.MessageFieldNode) *sourceinfo.OptionSourceInfo { + return children.Fields[n] + }, + ) + if match != nil { + return match, matchLen + } + } + + return node, len(srcInfoPath) +} + +// interpretField interprets the option described by opt, as a field inside the given msg. This +// interprets components of the option name starting at nameIndex. When nameIndex == 0, then +// msg must be an options message. For nameIndex > 0, msg is a nested message inside of the +// options message. The given pathPrefix is the path (sequence of field numbers and indices +// with a FileDescriptorProto as the start) up to but not including the given nameIndex. +// +// Any errors encountered will be handled, so the returned error will only be non-nil if +// the handler returned non-nil. Callers must check that the source info is non-nil before +// using it since it can be nil (in the event of a problem) even if the error is nil. +func (interp *interpreter) interpretField( + targetType descriptorpb.FieldOptions_OptionTargetType, + mc *internal.MessageContext, + msg protoreflect.Message, + opt *descriptorpb.UninterpretedOption, + nameIndex int, + pathPrefix []int32, +) (*sourceinfo.OptionSourceInfo, error) { + var fld protoreflect.FieldDescriptor + nm := opt.GetName()[nameIndex] + node := interp.file.OptionNamePartNode(nm) + if nm.GetIsExtension() { + extName := nm.GetNamePart() + if extName[0] == '.' { + extName = extName[1:] /* skip leading dot */ + } + var err error + fld, err = interp.resolveExtensionType(extName) + if errors.Is(err, protoregistry.NotFound) { + return nil, interp.handleErrorf(interp.nodeInfo(node), + "%vunrecognized extension %s of %s", + mc, extName, msg.Descriptor().FullName()) + } else if err != nil { + return nil, interp.handleErrorWithPos(interp.nodeInfo(node), err) + } + if fld.ContainingMessage().FullName() != msg.Descriptor().FullName() { + return nil, interp.handleErrorf(interp.nodeInfo(node), + "%vextension %s should extend %s but instead extends %s", + mc, extName, msg.Descriptor().FullName(), fld.ContainingMessage().FullName()) + } + } else { + fld = msg.Descriptor().Fields().ByName(protoreflect.Name(nm.GetNamePart())) + if fld == nil { + return nil, interp.handleErrorf(interp.nodeInfo(node), + "%vfield %s of %s does not exist", + mc, nm.GetNamePart(), msg.Descriptor().FullName()) + } + } + pathPrefix = append(pathPrefix, int32(fld.Number())) + + if err := interp.checkFieldUsage(targetType, fld, node); err != nil { + return nil, err + } + + if len(opt.GetName()) > nameIndex+1 { + nextnm := opt.GetName()[nameIndex+1] + nextnode := interp.file.OptionNamePartNode(nextnm) + k := fld.Kind() + if k != protoreflect.MessageKind && k != protoreflect.GroupKind { + return nil, interp.handleErrorf(interp.nodeInfo(nextnode), + "%vcannot set field %s because %s is not a message", + mc, nextnm.GetNamePart(), nm.GetNamePart()) + } + if fld.Cardinality() == protoreflect.Repeated { + return nil, interp.handleErrorf(interp.nodeInfo(nextnode), + "%vcannot set field %s because %s is repeated (must use an aggregate)", + mc, nextnm.GetNamePart(), nm.GetNamePart()) + } + var fdm protoreflect.Message + if msg.Has(fld) { + v := msg.Mutable(fld) + fdm = v.Message() + } else { + if ood := fld.ContainingOneof(); ood != nil { + existingFld := msg.WhichOneof(ood) + if existingFld != nil && existingFld.Number() != fld.Number() { + return nil, interp.handleErrorf(interp.nodeInfo(node), + "%voneof %q already has field %q set", + mc, ood.Name(), fieldName(existingFld)) + } + } + fldVal := msg.NewField(fld) + fdm = fldVal.Message() + msg.Set(fld, fldVal) + } + // recurse to set next part of name + return interp.interpretField(targetType, mc, fdm, opt, nameIndex+1, pathPrefix) + } + + optNode := interp.file.OptionNode(opt) + optValNode := optNode.GetValue() + var srcInfo *sourceinfo.OptionSourceInfo + var err error + if optValNode.Value() == nil { + err = interp.setOptionFieldFromProto(targetType, mc, msg, fld, node, opt, optValNode) + srcInfoVal := newSrcInfo(pathPrefix, nil) + srcInfo = &srcInfoVal + } else { + srcInfo, err = interp.setOptionField(targetType, mc, msg, fld, node, optValNode, false, pathPrefix) + } + if err != nil { + return nil, err + } + + return srcInfo, nil +} + +// setOptionField sets the value for field fld in the given message msg to the value represented +// by AST node val. The given name is the AST node that corresponds to the name of fld. On success, +// it returns additional metadata about the field that was set. +func (interp *interpreter) setOptionField( + targetType descriptorpb.FieldOptions_OptionTargetType, + mc *internal.MessageContext, + msg protoreflect.Message, + fld protoreflect.FieldDescriptor, + name ast.Node, + val ast.ValueNode, + insideMsgLiteral bool, + pathPrefix []int32, +) (*sourceinfo.OptionSourceInfo, error) { + v := val.Value() + if sl, ok := v.([]ast.ValueNode); ok { + // handle slices a little differently than the others + if fld.Cardinality() != protoreflect.Repeated { + return nil, interp.handleErrorf(interp.nodeInfo(val), "%vvalue is an array but field is not repeated", mc) + } + origPath := mc.OptAggPath + defer func() { + mc.OptAggPath = origPath + }() + childVals := make([]sourceinfo.OptionSourceInfo, len(sl)) + var firstIndex int + if fld.IsMap() { + firstIndex = msg.Get(fld).Map().Len() + } else { + firstIndex = msg.Get(fld).List().Len() + } + for index, item := range sl { + mc.OptAggPath = fmt.Sprintf("%s[%d]", origPath, index) + value, srcInfo, err := interp.fieldValue(targetType, mc, msg, fld, item, insideMsgLiteral, append(pathPrefix, int32(firstIndex+index))) + if err != nil || !value.IsValid() { + return nil, err + } + if fld.IsMap() { + mv := msg.Mutable(fld).Map() + setMapEntry(fld, msg, mv, value.Message()) + } else { + lv := msg.Mutable(fld).List() + lv.Append(value) + } + childVals[index] = srcInfo + } + srcInfo := newSrcInfo(append(pathPrefix, int32(firstIndex)), &sourceinfo.ArrayLiteralSourceInfo{Elements: childVals}) + return &srcInfo, nil + } + + if fld.IsMap() { + pathPrefix = append(pathPrefix, int32(msg.Get(fld).Map().Len())) + } else if fld.IsList() { + pathPrefix = append(pathPrefix, int32(msg.Get(fld).List().Len())) + } + + value, srcInfo, err := interp.fieldValue(targetType, mc, msg, fld, val, insideMsgLiteral, pathPrefix) + if err != nil || !value.IsValid() { + return nil, err + } + + if ood := fld.ContainingOneof(); ood != nil { + existingFld := msg.WhichOneof(ood) + if existingFld != nil && existingFld.Number() != fld.Number() { + return nil, interp.handleErrorf(interp.nodeInfo(name), "%voneof %q already has field %q set", mc, ood.Name(), fieldName(existingFld)) + } + } + + switch { + case fld.IsMap(): + mv := msg.Mutable(fld).Map() + setMapEntry(fld, msg, mv, value.Message()) + case fld.IsList(): + lv := msg.Mutable(fld).List() + lv.Append(value) + default: + if msg.Has(fld) { + return nil, interp.handleErrorf(interp.nodeInfo(name), "%vnon-repeated option field %s already set", mc, fieldName(fld)) + } + msg.Set(fld, value) + } + return &srcInfo, nil +} + +// setOptionFieldFromProto sets the value for field fld in the given message msg to the value +// represented by the given uninterpreted option. The given ast.Node, if non-nil, will be used +// to report source positions in error messages. On success, it returns additional metadata +// about the field that was set. +func (interp *interpreter) setOptionFieldFromProto( + targetType descriptorpb.FieldOptions_OptionTargetType, + mc *internal.MessageContext, + msg protoreflect.Message, + fld protoreflect.FieldDescriptor, + name ast.Node, + opt *descriptorpb.UninterpretedOption, + node ast.Node, +) error { + k := fld.Kind() + var value protoreflect.Value + switch k { + case protoreflect.EnumKind: + num, _, err := interp.enumFieldValueFromProto(mc, fld.Enum(), opt, node) + if err != nil { + return interp.handleError(err) + } + value = protoreflect.ValueOfEnum(num) + + case protoreflect.MessageKind, protoreflect.GroupKind: + if opt.AggregateValue == nil { + return interp.handleErrorf(interp.nodeInfo(node), "%vexpecting message, got %s", mc, optionValueKind(opt)) + } + // We must parse the text format from the aggregate value string + var elem protoreflect.Message + switch { + case fld.IsMap(): + elem = dynamicpb.NewMessage(fld.Message()) + case fld.IsList(): + elem = msg.Get(fld).List().NewElement().Message() + default: + elem = msg.NewField(fld).Message() + } + err := prototext.UnmarshalOptions{ + Resolver: &msgLiteralResolver{interp: interp, pkg: fld.ParentFile().Package()}, + AllowPartial: true, + }.Unmarshal([]byte(opt.GetAggregateValue()), elem.Interface()) + if err != nil { + return interp.handleErrorf(interp.nodeInfo(node), "%vfailed to parse message literal %w", mc, err) + } + if err := interp.checkFieldUsagesInMessage(targetType, elem, node); err != nil { + return err + } + value = protoreflect.ValueOfMessage(elem) + + default: + v, err := interp.scalarFieldValueFromProto(mc, descriptorpb.FieldDescriptorProto_Type(k), opt, node) + if err != nil { + return interp.handleError(err) + } + value = protoreflect.ValueOf(v) + } + + if ood := fld.ContainingOneof(); ood != nil { + existingFld := msg.WhichOneof(ood) + if existingFld != nil && existingFld.Number() != fld.Number() { + return interp.handleErrorf(interp.nodeInfo(name), "%voneof %q already has field %q set", mc, ood.Name(), fieldName(existingFld)) + } + } + + switch { + case fld.IsMap(): + mv := msg.Mutable(fld).Map() + setMapEntry(fld, msg, mv, value.Message()) + case fld.IsList(): + msg.Mutable(fld).List().Append(value) + default: + if msg.Has(fld) { + return interp.handleErrorf(interp.nodeInfo(name), "%vnon-repeated option field %s already set", mc, fieldName(fld)) + } + msg.Set(fld, value) + } + return nil +} + +// checkFieldUsagesInMessage verifies that all fields present in the given +// message can be used for the given target type. When an AST is +// present, we validate each field as it is processed. But without +// an AST, we unmarshal a message from an uninterpreted option's +// aggregate value string, and then must make sure that all fields +// set in that message are valid. This reports an error for each +// invalid field it encounters and returns a non-nil error if/when +// the handler returns a non-nil error. +func (interp *interpreter) checkFieldUsagesInMessage( + targetType descriptorpb.FieldOptions_OptionTargetType, + msg protoreflect.Message, + node ast.Node, +) error { + var err error + msg.Range(func(fld protoreflect.FieldDescriptor, val protoreflect.Value) bool { + err = interp.checkFieldUsage(targetType, fld, node) + if err != nil { + return false + } + switch { + case fld.IsList() && fld.Message() != nil: + listVal := val.List() + for i, length := 0, listVal.Len(); i < length; i++ { + err = interp.checkFieldUsagesInMessage(targetType, listVal.Get(i).Message(), node) + if err != nil { + return false + } + } + case fld.IsMap() && fld.MapValue().Message() != nil: + mapVal := val.Map() + mapVal.Range(func(_ protoreflect.MapKey, val protoreflect.Value) bool { + err = interp.checkFieldUsagesInMessage(targetType, val.Message(), node) + return err == nil + }) + case !fld.IsMap() && fld.Message() != nil: + err = interp.checkFieldUsagesInMessage(targetType, val.Message(), node) + } + return err == nil + }) + return err +} + +func (interp *interpreter) enableLenience(enable bool) { + if !interp.lenient { + return // nothing to do + } + if enable { + // reset the flag that tracks if an error has been reported + interp.lenientErrReported = false + } + interp.lenienceEnabled = enable +} + +func setMapEntry( + fld protoreflect.FieldDescriptor, + msg protoreflect.Message, + mapVal protoreflect.Map, + entry protoreflect.Message, +) { + keyFld, valFld := fld.MapKey(), fld.MapValue() + key := entry.Get(keyFld) + val := entry.Get(valFld) + if fld.MapValue().Kind() == protoreflect.MessageKind { + // Replace any nil/invalid values with an empty message + dm, valIsDynamic := val.Interface().(*dynamicpb.Message) + if (valIsDynamic && dm == nil) || !val.Message().IsValid() { + val = protoreflect.ValueOfMessage(dynamicpb.NewMessage(valFld.Message())) + } + _, containerIsDynamic := msg.Interface().(*dynamicpb.Message) + if valIsDynamic && !containerIsDynamic { + // This happens because we create dynamic messages to represent map entries, + // but the container of the map may expect a non-dynamic, generated type. + dest := mapVal.NewValue() + _, destIsDynamic := dest.Message().Interface().(*dynamicpb.Message) + if !destIsDynamic { + // reflection Set methods do not support cases where destination is + // generated but source is dynamic (or vice versa). But proto.Merge + // *DOES* support that, as long as dest and source use the same + // descriptor. + proto.Merge(dest.Message().Interface(), val.Message().Interface()) + val = dest + } + } + } + // TODO: error if key is already present + mapVal.Set(key.MapKey(), val) +} + +type msgLiteralResolver struct { + interp *interpreter + pkg protoreflect.FullName +} + +func (r *msgLiteralResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) { + if r.interp.resolver == nil { + return nil, protoregistry.NotFound + } + return r.interp.resolver.FindMessageByName(message) +} + +func (r *msgLiteralResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) { + // In a message literal, we don't allow arbitrary URL prefixes + pos := strings.LastIndexByte(url, '/') + var urlPrefix string + if pos > 0 { + urlPrefix = url[:pos] + } + if urlPrefix != "type.googleapis.com" && urlPrefix != "type.googleprod.com" { + return nil, fmt.Errorf("could not resolve type reference %s", url) + } + return r.FindMessageByName(protoreflect.FullName(url[pos+1:])) +} + +func (r *msgLiteralResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) { + if r.interp.resolver == nil { + return nil, protoregistry.NotFound + } + // In a message literal, extension name may be partially qualified, relative to package. + // So we have to search through package scopes. + pkg := r.pkg + for { + // TODO: This does not *fully* implement the insane logic of protoc with regards + // to resolving relative references. + // https://protobuf.com/docs/language-spec#reference-resolution + name := pkg.Append(protoreflect.Name(field)) + ext, err := r.interp.resolver.FindExtensionByName(name) + if err == nil { + return ext, nil + } + if pkg == "" { + // no more namespaces to check + return nil, err + } + pkg = pkg.Parent() + } +} + +func (r *msgLiteralResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { + if r.interp.resolver == nil { + return nil, protoregistry.NotFound + } + return r.interp.resolver.FindExtensionByNumber(message, field) +} + +func fieldName(fld protoreflect.FieldDescriptor) string { + if fld.IsExtension() { + return fmt.Sprintf("(%s)", fld.FullName()) + } + return string(fld.Name()) +} + +func valueKind(val interface{}) string { + switch val := val.(type) { + case ast.Identifier: + return "identifier" + case bool: + return "bool" + case int64: + if val < 0 { + return "negative integer" + } + return "integer" + case uint64: + return "integer" + case float64: + return "double" + case string, []byte: + return "string" + case []*ast.MessageFieldNode: + return "message" + case []ast.ValueNode: + return "array" + default: + return fmt.Sprintf("%T", val) + } +} + +func optionValueKind(opt *descriptorpb.UninterpretedOption) string { + switch { + case opt.IdentifierValue != nil: + return "identifier" + case opt.PositiveIntValue != nil: + return "integer" + case opt.NegativeIntValue != nil: + return "negative integer" + case opt.DoubleValue != nil: + return "double" + case opt.StringValue != nil: + return "string" + case opt.AggregateValue != nil: + return "message" + default: + // should not be possible + return "<nil>" + } +} + +// fieldValue computes a compile-time value (constant or list or message literal) for the given +// AST node val. The value in val must be assignable to the field fld. +// +// If the returned value is not valid, then an error occurred during processing. +// The returned err may be nil, however, as any errors will already have been +// handled (so the resulting error could be nil if the handler returned nil). +func (interp *interpreter) fieldValue( + targetType descriptorpb.FieldOptions_OptionTargetType, + mc *internal.MessageContext, + msg protoreflect.Message, + fld protoreflect.FieldDescriptor, + val ast.ValueNode, + insideMsgLiteral bool, + pathPrefix []int32, +) (protoreflect.Value, sourceinfo.OptionSourceInfo, error) { + k := fld.Kind() + switch k { + case protoreflect.EnumKind: + num, _, err := interp.enumFieldValue(mc, fld.Enum(), val, insideMsgLiteral) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, interp.handleError(err) + } + return protoreflect.ValueOfEnum(num), newSrcInfo(pathPrefix, nil), nil + + case protoreflect.MessageKind, protoreflect.GroupKind: + v := val.Value() + if aggs, ok := v.([]*ast.MessageFieldNode); ok { + var childMsg protoreflect.Message + switch { + case fld.IsList(): + // List of messages + val := msg.NewField(fld) + childMsg = val.List().NewElement().Message() + case fld.IsMap(): + // No generated type for map entries, so we use a dynamic type + childMsg = dynamicpb.NewMessage(fld.Message()) + default: + // Normal message field + childMsg = msg.NewField(fld).Message() + } + return interp.messageLiteralValue(targetType, mc, aggs, childMsg, pathPrefix) + } + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, + interp.handleErrorf(interp.nodeInfo(val), "%vexpecting message, got %s", mc, valueKind(v)) + + default: + v, err := interp.scalarFieldValue(mc, descriptorpb.FieldDescriptorProto_Type(k), val, insideMsgLiteral) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, interp.handleError(err) + } + return protoreflect.ValueOf(v), newSrcInfo(pathPrefix, nil), nil + } +} + +// enumFieldValue resolves the given AST node val as an enum value descriptor. If the given +// value is not a valid identifier (or number if allowed), an error is returned instead. +func (interp *interpreter) enumFieldValue( + mc *internal.MessageContext, + ed protoreflect.EnumDescriptor, + val ast.ValueNode, + allowNumber bool, +) (protoreflect.EnumNumber, protoreflect.Name, error) { + v := val.Value() + var num protoreflect.EnumNumber + switch v := v.(type) { + case ast.Identifier: + name := protoreflect.Name(v) + ev := ed.Values().ByName(name) + if ev == nil { + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%venum %s has no value named %s", mc, ed.FullName(), v) + } + return ev.Number(), name, nil + case int64: + if !allowNumber { + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%vexpecting enum name, got %s", mc, valueKind(v)) + } + if v > math.MaxInt32 || v < math.MinInt32 { + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for an enum", mc, v) + } + num = protoreflect.EnumNumber(v) + case uint64: + if !allowNumber { + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%vexpecting enum name, got %s", mc, valueKind(v)) + } + if v > math.MaxInt32 { + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for an enum", mc, v) + } + num = protoreflect.EnumNumber(v) + default: + return 0, "", reporter.Errorf(interp.nodeInfo(val), "%vexpecting enum, got %s", mc, valueKind(v)) + } + ev := ed.Values().ByNumber(num) + if ev != nil { + return num, ev.Name(), nil + } + if ed.IsClosed() { + return num, "", reporter.Errorf(interp.nodeInfo(val), "%vclosed enum %s has no value with number %d", mc, ed.FullName(), num) + } + // unknown value, but enum is open, so we allow it and return blank name + return num, "", nil +} + +// enumFieldValueFromProto resolves the given uninterpreted option value as an enum value descriptor. +// If the given value is not a valid identifier, an error is returned instead. +func (interp *interpreter) enumFieldValueFromProto( + mc *internal.MessageContext, + ed protoreflect.EnumDescriptor, + opt *descriptorpb.UninterpretedOption, + node ast.Node, +) (protoreflect.EnumNumber, protoreflect.Name, error) { + // We don't have to worry about allowing numbers because numbers are never allowed + // in uninterpreted values; they are only allowed inside aggregate values (i.e. + // message literals). + switch { + case opt.IdentifierValue != nil: + name := protoreflect.Name(opt.GetIdentifierValue()) + ev := ed.Values().ByName(name) + if ev == nil { + return 0, "", reporter.Errorf(interp.nodeInfo(node), "%venum %s has no value named %s", mc, ed.FullName(), name) + } + return ev.Number(), name, nil + default: + return 0, "", reporter.Errorf(interp.nodeInfo(node), "%vexpecting enum, got %s", mc, optionValueKind(opt)) + } +} + +// scalarFieldValue resolves the given AST node val as a value whose type is assignable to a +// field with the given fldType. +func (interp *interpreter) scalarFieldValue( + mc *internal.MessageContext, + fldType descriptorpb.FieldDescriptorProto_Type, + val ast.ValueNode, + insideMsgLiteral bool, +) (interface{}, error) { + v := val.Value() + switch fldType { + case descriptorpb.FieldDescriptorProto_TYPE_BOOL: + if b, ok := v.(bool); ok { + return b, nil + } + if id, ok := v.(ast.Identifier); ok { + if insideMsgLiteral { + // inside a message literal, values use the protobuf text format, + // which is lenient in that it accepts "t" and "f" or "True" and "False" + switch id { + case "t", "true", "True": + return true, nil + case "f", "false", "False": + return false, nil + } + } else { + // options with simple scalar values (no message literal) are stricter + switch id { + case "true": + return true, nil + case "false": + return false, nil + } + } + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting bool, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_BYTES: + if str, ok := v.(string); ok { + return []byte(str), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting bytes, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_STRING: + if str, ok := v.(string); ok { + return str, nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting string, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: + if i, ok := v.(int64); ok { + if i > math.MaxInt32 || i < math.MinInt32 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for int32", mc, i) + } + return int32(i), nil + } + if ui, ok := v.(uint64); ok { + if ui > math.MaxInt32 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for int32", mc, ui) + } + return int32(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting int32, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: + if i, ok := v.(int64); ok { + if i > math.MaxUint32 || i < 0 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for uint32", mc, i) + } + return uint32(i), nil + } + if ui, ok := v.(uint64); ok { + if ui > math.MaxUint32 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for uint32", mc, ui) + } + return uint32(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting uint32, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: + if i, ok := v.(int64); ok { + return i, nil + } + if ui, ok := v.(uint64); ok { + if ui > math.MaxInt64 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for int64", mc, ui) + } + return int64(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting int64, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: + if i, ok := v.(int64); ok { + if i < 0 { + return nil, reporter.Errorf(interp.nodeInfo(val), "%vvalue %d is out of range for uint64", mc, i) + } + return uint64(i), nil + } + if ui, ok := v.(uint64); ok { + return ui, nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting uint64, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE: + if id, ok := v.(ast.Identifier); ok { + switch id { + case "inf": + return math.Inf(1), nil + case "nan": + return math.NaN(), nil + } + } + if d, ok := v.(float64); ok { + return d, nil + } + if i, ok := v.(int64); ok { + return float64(i), nil + } + if u, ok := v.(uint64); ok { + return float64(u), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting double, got %s", mc, valueKind(v)) + case descriptorpb.FieldDescriptorProto_TYPE_FLOAT: + if id, ok := v.(ast.Identifier); ok { + switch id { + case "inf": + return float32(math.Inf(1)), nil + case "nan": + return float32(math.NaN()), nil + } + } + if d, ok := v.(float64); ok { + return float32(d), nil + } + if i, ok := v.(int64); ok { + return float32(i), nil + } + if u, ok := v.(uint64); ok { + return float32(u), nil + } + return nil, reporter.Errorf(interp.nodeInfo(val), "%vexpecting float, got %s", mc, valueKind(v)) + default: + return nil, reporter.Errorf(interp.nodeInfo(val), "%vunrecognized field type: %s", mc, fldType) + } +} + +// scalarFieldValue resolves the given uninterpreted option value as a value whose type is +// assignable to a field with the given fldType. +func (interp *interpreter) scalarFieldValueFromProto( + mc *internal.MessageContext, + fldType descriptorpb.FieldDescriptorProto_Type, + opt *descriptorpb.UninterpretedOption, + node ast.Node, +) (interface{}, error) { + switch fldType { + case descriptorpb.FieldDescriptorProto_TYPE_BOOL: + if opt.IdentifierValue != nil { + switch opt.GetIdentifierValue() { + case "true": + return true, nil + case "false": + return false, nil + } + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting bool, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_BYTES: + if opt.StringValue != nil { + return opt.GetStringValue(), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting bytes, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_STRING: + if opt.StringValue != nil { + return string(opt.GetStringValue()), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting string, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: + if opt.NegativeIntValue != nil { + i := opt.GetNegativeIntValue() + if i > math.MaxInt32 || i < math.MinInt32 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for int32", mc, i) + } + return int32(i), nil + } + if opt.PositiveIntValue != nil { + ui := opt.GetPositiveIntValue() + if ui > math.MaxInt32 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for int32", mc, ui) + } + return int32(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting int32, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: + if opt.NegativeIntValue != nil { + i := opt.GetNegativeIntValue() + if i > math.MaxUint32 || i < 0 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for uint32", mc, i) + } + return uint32(i), nil + } + if opt.PositiveIntValue != nil { + ui := opt.GetPositiveIntValue() + if ui > math.MaxUint32 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for uint32", mc, ui) + } + return uint32(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting uint32, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: + if opt.NegativeIntValue != nil { + return opt.GetNegativeIntValue(), nil + } + if opt.PositiveIntValue != nil { + ui := opt.GetPositiveIntValue() + if ui > math.MaxInt64 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for int64", mc, ui) + } + return int64(ui), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting int64, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: + if opt.NegativeIntValue != nil { + i := opt.GetNegativeIntValue() + if i < 0 { + return nil, reporter.Errorf(interp.nodeInfo(node), "%vvalue %d is out of range for uint64", mc, i) + } + // should not be possible since i should always be negative... + return uint64(i), nil + } + if opt.PositiveIntValue != nil { + return opt.GetPositiveIntValue(), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting uint64, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE: + if opt.IdentifierValue != nil { + switch opt.GetIdentifierValue() { + case "inf": + return math.Inf(1), nil + case "nan": + return math.NaN(), nil + } + } + if opt.DoubleValue != nil { + return opt.GetDoubleValue(), nil + } + if opt.NegativeIntValue != nil { + return float64(opt.GetNegativeIntValue()), nil + } + if opt.PositiveIntValue != nil { + return float64(opt.GetPositiveIntValue()), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting double, got %s", mc, optionValueKind(opt)) + case descriptorpb.FieldDescriptorProto_TYPE_FLOAT: + if opt.IdentifierValue != nil { + switch opt.GetIdentifierValue() { + case "inf": + return float32(math.Inf(1)), nil + case "nan": + return float32(math.NaN()), nil + } + } + if opt.DoubleValue != nil { + return float32(opt.GetDoubleValue()), nil + } + if opt.NegativeIntValue != nil { + return float32(opt.GetNegativeIntValue()), nil + } + if opt.PositiveIntValue != nil { + return float32(opt.GetPositiveIntValue()), nil + } + return nil, reporter.Errorf(interp.nodeInfo(node), "%vexpecting float, got %s", mc, optionValueKind(opt)) + default: + return nil, reporter.Errorf(interp.nodeInfo(node), "%vunrecognized field type: %s", mc, fldType) + } +} + +func descriptorType(m proto.Message) string { + switch m := m.(type) { + case *descriptorpb.DescriptorProto: + return "message" + case *descriptorpb.DescriptorProto_ExtensionRange: + return "extension range" + case *descriptorpb.FieldDescriptorProto: + if m.GetExtendee() == "" { + return "field" + } + return "extension" + case *descriptorpb.EnumDescriptorProto: + return "enum" + case *descriptorpb.EnumValueDescriptorProto: + return "enum value" + case *descriptorpb.ServiceDescriptorProto: + return "service" + case *descriptorpb.MethodDescriptorProto: + return "method" + case *descriptorpb.FileDescriptorProto: + return "file" + default: + // shouldn't be possible + return fmt.Sprintf("%T", m) + } +} + +// messageLiteralValue processes a message literal value. +// +// If the returned value is not valid, then an error occurred during processing. +// The returned err may be nil, however, as any errors will already have been +// handled (so the resulting error could be nil if the handler returned nil). +func (interp *interpreter) messageLiteralValue( + targetType descriptorpb.FieldOptions_OptionTargetType, + mc *internal.MessageContext, + fieldNodes []*ast.MessageFieldNode, + msg protoreflect.Message, + pathPrefix []int32, +) (protoreflect.Value, sourceinfo.OptionSourceInfo, error) { + fmd := msg.Descriptor() + origPath := mc.OptAggPath + defer func() { + mc.OptAggPath = origPath + }() + flds := make(map[*ast.MessageFieldNode]*sourceinfo.OptionSourceInfo, len(fieldNodes)) + var hadError bool + for _, fieldNode := range fieldNodes { + if origPath == "" { + mc.OptAggPath = fieldNode.Name.Value() + } else { + mc.OptAggPath = origPath + "." + fieldNode.Name.Value() + } + if fieldNode.Name.IsAnyTypeReference() { + if len(fieldNodes) > 1 { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name.URLPrefix), "%vany type references cannot be repeated or mixed with other fields", mc) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + } + + if fmd.FullName() != "google.protobuf.Any" { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name.URLPrefix), "%vtype references are only allowed for google.protobuf.Any, but this type is %s", mc, fmd.FullName()) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + typeURLDescriptor := fmd.Fields().ByNumber(internal.AnyTypeURLTag) + var err error + switch { + case typeURLDescriptor == nil: + err = fmt.Errorf("message schema is missing type_url field (number %d)", internal.AnyTypeURLTag) + case typeURLDescriptor.IsList(): + err = fmt.Errorf("message schema has type_url field (number %d) that is a list but should be singular", internal.AnyTypeURLTag) + case typeURLDescriptor.Kind() != protoreflect.StringKind: + err = fmt.Errorf("message schema has type_url field (number %d) that is %s but should be string", internal.AnyTypeURLTag, typeURLDescriptor.Kind()) + } + if err != nil { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name), "%v%w", mc, err) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + valueDescriptor := fmd.Fields().ByNumber(internal.AnyValueTag) + switch { + case valueDescriptor == nil: + err = fmt.Errorf("message schema is missing value field (number %d)", internal.AnyValueTag) + case valueDescriptor.IsList(): + err = fmt.Errorf("message schema has value field (number %d) that is a list but should be singular", internal.AnyValueTag) + case valueDescriptor.Kind() != protoreflect.BytesKind: + err = fmt.Errorf("message schema has value field (number %d) that is %s but should be bytes", internal.AnyValueTag, valueDescriptor.Kind()) + } + if err != nil { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name), "%v%w", mc, err) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + + urlPrefix := fieldNode.Name.URLPrefix.AsIdentifier() + msgName := fieldNode.Name.Name.AsIdentifier() + fullURL := fmt.Sprintf("%s/%s", urlPrefix, msgName) + // TODO: Support other URLs dynamically -- the caller of protocompile + // should be able to provide a custom resolver that can resolve type + // URLs into message descriptors. The default resolver would be + // implemented as below, only accepting "type.googleapis.com" and + // "type.googleprod.com" as hosts/prefixes and using the compiled + // file's transitive closure to find the named message, since that + // is what protoc does. + if urlPrefix != "type.googleapis.com" && urlPrefix != "type.googleprod.com" { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name.URLPrefix), "%vcould not resolve type reference %s", mc, fullURL) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + anyFields, ok := fieldNode.Val.Value().([]*ast.MessageFieldNode) + if !ok { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Val), "%vtype references for google.protobuf.Any must have message literal value", mc) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + anyMd := resolveDescriptor[protoreflect.MessageDescriptor](interp.resolver, string(msgName)) + if anyMd == nil { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name.URLPrefix), "%vcould not resolve type reference %s", mc, fullURL) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + // parse the message value + msgVal, valueSrcInfo, err := interp.messageLiteralValue(targetType, mc, anyFields, dynamicpb.NewMessage(anyMd), append(pathPrefix, internal.AnyValueTag)) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } else if !msgVal.IsValid() { + hadError = true + continue + } + + b, err := (proto.MarshalOptions{Deterministic: true}).Marshal(msgVal.Message().Interface()) + if err != nil { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Val), "%vfailed to serialize message value: %w", mc, err) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + + // Success! + if !hadError { + msg.Set(typeURLDescriptor, protoreflect.ValueOfString(fullURL)) + msg.Set(valueDescriptor, protoreflect.ValueOfBytes(b)) + flds[fieldNode] = &valueSrcInfo + } + continue + } + + // Not expanded Any syntax; handle normal field. + var ffld protoreflect.FieldDescriptor + var err error + if fieldNode.Name.IsExtension() { + n := interp.file.ResolveMessageLiteralExtensionName(fieldNode.Name.Name) + if n == "" { + // this should not be possible! + n = string(fieldNode.Name.Name.AsIdentifier()) + } + ffld, err = interp.resolveExtensionType(n) + if errors.Is(err, protoregistry.NotFound) { + // may need to qualify with package name + // (this should not be necessary!) + pkg := mc.File.FileDescriptorProto().GetPackage() + if pkg != "" { + ffld, err = interp.resolveExtensionType(pkg + "." + n) + } + } + } else { + ffld = fmd.Fields().ByName(protoreflect.Name(fieldNode.Name.Value())) + if ffld == nil { + err = protoregistry.NotFound + // It could be a proto2 group, where the text format refers to the group type + // name, and the field name is the lower-cased form of that. + ffld = fmd.Fields().ByName(protoreflect.Name(strings.ToLower(fieldNode.Name.Value()))) + if ffld != nil { + // In editions, we support using the group type name only for fields that + // "look like" proto2 groups. + if protoreflect.Name(fieldNode.Name.Value()) == ffld.Message().Name() && // text format uses type name + ffld.Message().FullName().Parent() == ffld.FullName().Parent() && // message and field declared in same scope + ffld.Kind() == protoreflect.GroupKind /* uses delimited encoding */ { + // This one looks like a proto2 group, so it's a keeper. + err = nil + } else { + // It doesn't look like a proto2 group, so this is not a match. + ffld = nil + } + } + } + } + if errors.Is(err, protoregistry.NotFound) { + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Name), "%vfield %s not found", mc, string(fieldNode.Name.Name.AsIdentifier())) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } else if err != nil { + err := interp.handleErrorWithPos(interp.nodeInfo(fieldNode.Name), err) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + if err := interp.checkFieldUsage(targetType, ffld, fieldNode.Name); err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + if fieldNode.Sep == nil && ffld.Message() == nil { + // If there is no separator, the field type should be a message. + // Otherwise, it is an error in the text format. + err := interp.handleErrorf(interp.nodeInfo(fieldNode.Val), "syntax error: unexpected value, expecting ':'") + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + hadError = true + continue + } + srcInfo, err := interp.setOptionField(targetType, mc, msg, ffld, fieldNode.Name, fieldNode.Val, true, append(pathPrefix, int32(ffld.Number()))) + if err != nil { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, err + } + if srcInfo != nil { + flds[fieldNode] = srcInfo + } + } + if hadError { + return protoreflect.Value{}, sourceinfo.OptionSourceInfo{}, nil + } + return protoreflect.ValueOfMessage(msg), + newSrcInfo(pathPrefix, &sourceinfo.MessageLiteralSourceInfo{Fields: flds}), + nil +} + +func newSrcInfo(path []int32, children sourceinfo.OptionChildrenSourceInfo) sourceinfo.OptionSourceInfo { + return sourceinfo.OptionSourceInfo{ + Path: internal.ClonePath(path), + Children: children, + } +} diff --git a/vendor/github.com/bufbuild/protocompile/options/source_retention_options.go b/vendor/github.com/bufbuild/protocompile/options/source_retention_options.go new file mode 100644 index 0000000..05c3e29 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/options/source_retention_options.go @@ -0,0 +1,539 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" + + "github.com/bufbuild/protocompile/internal" +) + +// StripSourceRetentionOptionsFromFile returns a file descriptor proto that omits any +// options in file that are defined to be retained only in source. If file has no +// such options, then it is returned as is. If it does have such options, a copy is +// made; the given file will not be mutated. +// +// Even when a copy is returned, it is not a deep copy: it may share data with the +// original file. So callers should not mutate the returned file unless mutating the +// input file is also safe. +func StripSourceRetentionOptionsFromFile(file *descriptorpb.FileDescriptorProto) (*descriptorpb.FileDescriptorProto, error) { + var path sourcePath + var removedPaths *sourcePathTrie + if file.SourceCodeInfo != nil && len(file.SourceCodeInfo.Location) > 0 { + path = make(sourcePath, 0, 16) + removedPaths = &sourcePathTrie{} + } + var dirty bool + optionsPath := path.push(internal.FileOptionsTag) + newOpts, err := stripSourceRetentionOptions(file.GetOptions(), optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts != file.GetOptions() { + dirty = true + } + msgsPath := path.push(internal.FileMessagesTag) + newMsgs, changed, err := stripOptionsFromAll(file.GetMessageType(), stripSourceRetentionOptionsFromMessage, msgsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + enumsPath := path.push(internal.FileEnumsTag) + newEnums, changed, err := stripOptionsFromAll(file.GetEnumType(), stripSourceRetentionOptionsFromEnum, enumsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + extsPath := path.push(internal.FileExtensionsTag) + newExts, changed, err := stripOptionsFromAll(file.GetExtension(), stripSourceRetentionOptionsFromField, extsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + svcsPath := path.push(internal.FileServicesTag) + newSvcs, changed, err := stripOptionsFromAll(file.GetService(), stripSourceRetentionOptionsFromService, svcsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + + if !dirty { + return file, nil + } + + newFile, err := shallowCopy(file) + if err != nil { + return nil, err + } + newFile.Options = newOpts + newFile.MessageType = newMsgs + newFile.EnumType = newEnums + newFile.Extension = newExts + newFile.Service = newSvcs + newFile.SourceCodeInfo = stripSourcePathsForSourceRetentionOptions(newFile.SourceCodeInfo, removedPaths) + return newFile, nil +} + +type sourcePath protoreflect.SourcePath + +func (p sourcePath) push(element int32) sourcePath { + if p == nil { + return nil + } + return append(p, element) +} + +type sourcePathTrie struct { + removed bool + children map[int32]*sourcePathTrie +} + +func (t *sourcePathTrie) addPath(p sourcePath) { + if t == nil { + return + } + if len(p) == 0 { + t.removed = true + return + } + child := t.children[p[0]] + if child == nil { + if t.children == nil { + t.children = map[int32]*sourcePathTrie{} + } + child = &sourcePathTrie{} + t.children[p[0]] = child + } + child.addPath(p[1:]) +} + +func (t *sourcePathTrie) isRemoved(p []int32) bool { + if t == nil { + return false + } + if t.removed { + return true + } + if len(p) == 0 { + return false + } + child := t.children[p[0]] + if child == nil { + return false + } + return child.isRemoved(p[1:]) +} + +func stripSourceRetentionOptions[M proto.Message]( + options M, + path sourcePath, + removedPaths *sourcePathTrie, +) (M, error) { + optionsRef := options.ProtoReflect() + // See if there are any options to strip. + var hasFieldToStrip bool + var numFieldsToKeep int + var err error + optionsRef.Range(func(field protoreflect.FieldDescriptor, _ protoreflect.Value) bool { + fieldOpts, ok := field.Options().(*descriptorpb.FieldOptions) + if !ok { + err = fmt.Errorf("field options is unexpected type: got %T, want %T", field.Options(), fieldOpts) + return false + } + if fieldOpts.GetRetention() == descriptorpb.FieldOptions_RETENTION_SOURCE { + hasFieldToStrip = true + } else { + numFieldsToKeep++ + } + return true + }) + var zero M + if err != nil { + return zero, err + } + if !hasFieldToStrip { + return options, nil + } + + if numFieldsToKeep == 0 { + // Stripping the message would remove *all* options. In that case, + // we'll clear out the options by returning the zero value (i.e. nil). + removedPaths.addPath(path) // clear out all source locations, too + return zero, nil + } + + // There is at least one option to remove. So we need to make a copy that does not have those options. + newOptions := optionsRef.New() + ret, ok := newOptions.Interface().(M) + if !ok { + return zero, fmt.Errorf("creating new message of same type resulted in unexpected type; got %T, want %T", newOptions.Interface(), zero) + } + optionsRef.Range(func(field protoreflect.FieldDescriptor, val protoreflect.Value) bool { + fieldOpts, ok := field.Options().(*descriptorpb.FieldOptions) + if !ok { + err = fmt.Errorf("field options is unexpected type: got %T, want %T", field.Options(), fieldOpts) + return false + } + if fieldOpts.GetRetention() != descriptorpb.FieldOptions_RETENTION_SOURCE { + newOptions.Set(field, val) + } else { + removedPaths.addPath(path.push(int32(field.Number()))) + } + return true + }) + if err != nil { + return zero, err + } + return ret, nil +} + +func stripSourceRetentionOptionsFromMessage( + msg *descriptorpb.DescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.DescriptorProto, error) { + var dirty bool + optionsPath := path.push(internal.MessageOptionsTag) + newOpts, err := stripSourceRetentionOptions(msg.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts != msg.Options { + dirty = true + } + fieldsPath := path.push(internal.MessageFieldsTag) + newFields, changed, err := stripOptionsFromAll(msg.Field, stripSourceRetentionOptionsFromField, fieldsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + oneofsPath := path.push(internal.MessageOneofsTag) + newOneofs, changed, err := stripOptionsFromAll(msg.OneofDecl, stripSourceRetentionOptionsFromOneof, oneofsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + extRangesPath := path.push(internal.MessageExtensionRangesTag) + newExtRanges, changed, err := stripOptionsFromAll(msg.ExtensionRange, stripSourceRetentionOptionsFromExtensionRange, extRangesPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + msgsPath := path.push(internal.MessageNestedMessagesTag) + newMsgs, changed, err := stripOptionsFromAll(msg.NestedType, stripSourceRetentionOptionsFromMessage, msgsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + enumsPath := path.push(internal.MessageEnumsTag) + newEnums, changed, err := stripOptionsFromAll(msg.EnumType, stripSourceRetentionOptionsFromEnum, enumsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + extsPath := path.push(internal.MessageExtensionsTag) + newExts, changed, err := stripOptionsFromAll(msg.Extension, stripSourceRetentionOptionsFromField, extsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + + if !dirty { + return msg, nil + } + + newMsg, err := shallowCopy(msg) + if err != nil { + return nil, err + } + newMsg.Options = newOpts + newMsg.Field = newFields + newMsg.OneofDecl = newOneofs + newMsg.ExtensionRange = newExtRanges + newMsg.NestedType = newMsgs + newMsg.EnumType = newEnums + newMsg.Extension = newExts + return newMsg, nil +} + +func stripSourceRetentionOptionsFromField( + field *descriptorpb.FieldDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.FieldDescriptorProto, error) { + optionsPath := path.push(internal.FieldOptionsTag) + newOpts, err := stripSourceRetentionOptions(field.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts == field.Options { + return field, nil + } + newField, err := shallowCopy(field) + if err != nil { + return nil, err + } + newField.Options = newOpts + return newField, nil +} + +func stripSourceRetentionOptionsFromOneof( + oneof *descriptorpb.OneofDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.OneofDescriptorProto, error) { + optionsPath := path.push(internal.OneofOptionsTag) + newOpts, err := stripSourceRetentionOptions(oneof.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts == oneof.Options { + return oneof, nil + } + newOneof, err := shallowCopy(oneof) + if err != nil { + return nil, err + } + newOneof.Options = newOpts + return newOneof, nil +} + +func stripSourceRetentionOptionsFromExtensionRange( + extRange *descriptorpb.DescriptorProto_ExtensionRange, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.DescriptorProto_ExtensionRange, error) { + optionsPath := path.push(internal.ExtensionRangeOptionsTag) + newOpts, err := stripSourceRetentionOptions(extRange.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts == extRange.Options { + return extRange, nil + } + newExtRange, err := shallowCopy(extRange) + if err != nil { + return nil, err + } + newExtRange.Options = newOpts + return newExtRange, nil +} + +func stripSourceRetentionOptionsFromEnum( + enum *descriptorpb.EnumDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.EnumDescriptorProto, error) { + var dirty bool + optionsPath := path.push(internal.EnumOptionsTag) + newOpts, err := stripSourceRetentionOptions(enum.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts != enum.Options { + dirty = true + } + valsPath := path.push(internal.EnumValuesTag) + newVals, changed, err := stripOptionsFromAll(enum.Value, stripSourceRetentionOptionsFromEnumValue, valsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + + if !dirty { + return enum, nil + } + + newEnum, err := shallowCopy(enum) + if err != nil { + return nil, err + } + newEnum.Options = newOpts + newEnum.Value = newVals + return newEnum, nil +} + +func stripSourceRetentionOptionsFromEnumValue( + enumVal *descriptorpb.EnumValueDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.EnumValueDescriptorProto, error) { + optionsPath := path.push(internal.EnumValOptionsTag) + newOpts, err := stripSourceRetentionOptions(enumVal.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts == enumVal.Options { + return enumVal, nil + } + newEnumVal, err := shallowCopy(enumVal) + if err != nil { + return nil, err + } + newEnumVal.Options = newOpts + return newEnumVal, nil +} + +func stripSourceRetentionOptionsFromService( + svc *descriptorpb.ServiceDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.ServiceDescriptorProto, error) { + var dirty bool + optionsPath := path.push(internal.ServiceOptionsTag) + newOpts, err := stripSourceRetentionOptions(svc.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts != svc.Options { + dirty = true + } + methodsPath := path.push(internal.ServiceMethodsTag) + newMethods, changed, err := stripOptionsFromAll(svc.Method, stripSourceRetentionOptionsFromMethod, methodsPath, removedPaths) + if err != nil { + return nil, err + } + if changed { + dirty = true + } + + if !dirty { + return svc, nil + } + + newSvc, err := shallowCopy(svc) + if err != nil { + return nil, err + } + newSvc.Options = newOpts + newSvc.Method = newMethods + return newSvc, nil +} + +func stripSourceRetentionOptionsFromMethod( + method *descriptorpb.MethodDescriptorProto, + path sourcePath, + removedPaths *sourcePathTrie, +) (*descriptorpb.MethodDescriptorProto, error) { + optionsPath := path.push(internal.MethodOptionsTag) + newOpts, err := stripSourceRetentionOptions(method.Options, optionsPath, removedPaths) + if err != nil { + return nil, err + } + if newOpts == method.Options { + return method, nil + } + newMethod, err := shallowCopy(method) + if err != nil { + return nil, err + } + newMethod.Options = newOpts + return newMethod, nil +} + +func stripSourcePathsForSourceRetentionOptions( + sourceInfo *descriptorpb.SourceCodeInfo, + removedPaths *sourcePathTrie, +) *descriptorpb.SourceCodeInfo { + if sourceInfo == nil || len(sourceInfo.Location) == 0 || removedPaths == nil { + // nothing to do + return sourceInfo + } + newLocations := make([]*descriptorpb.SourceCodeInfo_Location, len(sourceInfo.Location)) + var i int + for _, loc := range sourceInfo.Location { + if removedPaths.isRemoved(loc.Path) { + continue + } + newLocations[i] = loc + i++ + } + newLocations = newLocations[:i] + return &descriptorpb.SourceCodeInfo{Location: newLocations} +} + +func shallowCopy[M proto.Message](msg M) (M, error) { + msgRef := msg.ProtoReflect() + other := msgRef.New() + ret, ok := other.Interface().(M) + if !ok { + return ret, fmt.Errorf("creating new message of same type resulted in unexpected type; got %T, want %T", other.Interface(), ret) + } + msgRef.Range(func(field protoreflect.FieldDescriptor, val protoreflect.Value) bool { + other.Set(field, val) + return true + }) + return ret, nil +} + +// stripOptionsFromAll applies the given function to each element in the given +// slice in order to remove source-retention options from it. It returns the new +// slice and a bool indicating whether anything was actually changed. If the +// second value is false, then the returned slice is the same slice as the input +// slice. Usually, T is a pointer type, in which case the given updateFunc should +// NOT mutate the input value. Instead, it should return the input value if only +// if there is no update needed. If a mutation is needed, it should return a new +// value. +func stripOptionsFromAll[T comparable]( + slice []T, + updateFunc func(T, sourcePath, *sourcePathTrie) (T, error), + path sourcePath, + removedPaths *sourcePathTrie, +) ([]T, bool, error) { + var updated []T // initialized lazily, only when/if a copy is needed + for i, item := range slice { + newItem, err := updateFunc(item, path.push(int32(i)), removedPaths) + if err != nil { + return nil, false, err + } + if updated != nil { + updated[i] = newItem + } else if newItem != item { + updated = make([]T, len(slice)) + copy(updated[:i], slice) + updated[i] = newItem + } + } + if updated != nil { + return updated, true, nil + } + return slice, false, nil +} diff --git a/vendor/github.com/bufbuild/protocompile/options/target_types.go b/vendor/github.com/bufbuild/protocompile/options/target_types.go new file mode 100644 index 0000000..0d78075 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/options/target_types.go @@ -0,0 +1,152 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options + +import ( + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" +) + +type optionsType[T any] interface { + *T + proto.Message + GetFeatures() *descriptorpb.FeatureSet + GetUninterpretedOption() []*descriptorpb.UninterpretedOption +} + +type elementType[OptsStruct any, Opts optionsType[OptsStruct]] interface { + proto.Message + GetOptions() Opts +} + +type targetType[Elem elementType[OptsStruct, Opts], OptsStruct any, Opts optionsType[OptsStruct]] struct { + t descriptorpb.FieldOptions_OptionTargetType + setUninterpretedOptions func(opts Opts, uninterpreted []*descriptorpb.UninterpretedOption) + setOptions func(elem Elem, opts Opts) +} + +var ( + targetTypeFile = newTargetType[*descriptorpb.FileDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_FILE, setUninterpretedFileOptions, setFileOptions, + ) + targetTypeMessage = newTargetType[*descriptorpb.DescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_MESSAGE, setUninterpretedMessageOptions, setMessageOptions, + ) + targetTypeField = newTargetType[*descriptorpb.FieldDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_FIELD, setUninterpretedFieldOptions, setFieldOptions, + ) + targetTypeOneof = newTargetType[*descriptorpb.OneofDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_ONEOF, setUninterpretedOneofOptions, setOneofOptions, + ) + targetTypeExtensionRange = newTargetType[*descriptorpb.DescriptorProto_ExtensionRange]( + descriptorpb.FieldOptions_TARGET_TYPE_EXTENSION_RANGE, setUninterpretedExtensionRangeOptions, setExtensionRangeOptions, + ) + targetTypeEnum = newTargetType[*descriptorpb.EnumDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_ENUM, setUninterpretedEnumOptions, setEnumOptions, + ) + targetTypeEnumValue = newTargetType[*descriptorpb.EnumValueDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_ENUM_ENTRY, setUninterpretedEnumValueOptions, setEnumValueOptions, + ) + targetTypeService = newTargetType[*descriptorpb.ServiceDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_SERVICE, setUninterpretedServiceOptions, setServiceOptions, + ) + targetTypeMethod = newTargetType[*descriptorpb.MethodDescriptorProto]( + descriptorpb.FieldOptions_TARGET_TYPE_METHOD, setUninterpretedMethodOptions, setMethodOptions, + ) +) + +func newTargetType[Elem elementType[OptsStruct, Opts], OptsStruct any, Opts optionsType[OptsStruct]]( + t descriptorpb.FieldOptions_OptionTargetType, + setUninterpretedOptions func(opts Opts, uninterpreted []*descriptorpb.UninterpretedOption), + setOptions func(elem Elem, opts Opts), +) *targetType[Elem, OptsStruct, Opts] { + return &targetType[Elem, OptsStruct, Opts]{ + t: t, + setUninterpretedOptions: setUninterpretedOptions, + setOptions: setOptions, + } +} + +func setUninterpretedFileOptions(opts *descriptorpb.FileOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedMessageOptions(opts *descriptorpb.MessageOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedFieldOptions(opts *descriptorpb.FieldOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedOneofOptions(opts *descriptorpb.OneofOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedExtensionRangeOptions(opts *descriptorpb.ExtensionRangeOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedEnumOptions(opts *descriptorpb.EnumOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedEnumValueOptions(opts *descriptorpb.EnumValueOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedServiceOptions(opts *descriptorpb.ServiceOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setUninterpretedMethodOptions(opts *descriptorpb.MethodOptions, uninterpreted []*descriptorpb.UninterpretedOption) { + opts.UninterpretedOption = uninterpreted +} + +func setFileOptions(desc *descriptorpb.FileDescriptorProto, opts *descriptorpb.FileOptions) { + desc.Options = opts +} + +func setMessageOptions(desc *descriptorpb.DescriptorProto, opts *descriptorpb.MessageOptions) { + desc.Options = opts +} + +func setFieldOptions(desc *descriptorpb.FieldDescriptorProto, opts *descriptorpb.FieldOptions) { + desc.Options = opts +} + +func setOneofOptions(desc *descriptorpb.OneofDescriptorProto, opts *descriptorpb.OneofOptions) { + desc.Options = opts +} + +func setExtensionRangeOptions(desc *descriptorpb.DescriptorProto_ExtensionRange, opts *descriptorpb.ExtensionRangeOptions) { + desc.Options = opts +} + +func setEnumOptions(desc *descriptorpb.EnumDescriptorProto, opts *descriptorpb.EnumOptions) { + desc.Options = opts +} + +func setEnumValueOptions(desc *descriptorpb.EnumValueDescriptorProto, opts *descriptorpb.EnumValueOptions) { + desc.Options = opts +} + +func setServiceOptions(desc *descriptorpb.ServiceDescriptorProto, opts *descriptorpb.ServiceOptions) { + desc.Options = opts +} + +func setMethodOptions(desc *descriptorpb.MethodDescriptorProto, opts *descriptorpb.MethodOptions) { + desc.Options = opts +} |
