Initial commit
Change-Id: I5f41c4cc0135d6efad2d01aeafb4a6954421c082
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b16bd94
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# How to contribute
+
+We'd love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit <https://cla.developers.google.com/> to see your current agreements or to
+sign a new one.
+
+### Review our community guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9fdd966
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2024 Google LLC
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ac98bc6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# open2opaque
+
+The program open2opaque migrates Go code using Go Protobuf from the Open API
+to the Opaque API.
+
+See https://opaque-preview.stapelberg.ch/go.dev/blog/protobuf-opaque for context.
diff --git a/gen.go b/gen.go
new file mode 100644
index 0000000..272bc7d
--- /dev/null
+++ b/gen.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+//go:generate go run genprotos.go
+//go:generate go run gentestdata.go
diff --git a/genprotos.go b/genprotos.go
new file mode 100644
index 0000000..c3b3aac
--- /dev/null
+++ b/genprotos.go
@@ -0,0 +1,70 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+func genProtos() error {
+ for _, pb := range []struct {
+ Dir string
+ Basename string
+ Package string
+ }{
+ {
+ Dir: "internal/apiflagdata",
+ Basename: "go_api_enum.proto",
+ Package: "google.golang.org/open2opaque/internal/apiflagdata",
+ },
+
+ {
+ Dir: "internal/dashboard",
+ Basename: "stats.proto",
+ Package: "google.golang.org/open2opaque/internal/dashboard",
+ },
+
+ {
+ Dir: "internal/fix/testdata/proto2test_go_proto",
+ Basename: "proto2test.proto",
+ Package: "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto",
+ },
+
+ {
+ Dir: "internal/fix/testdata/proto3test_go_proto",
+ Basename: "proto3test.proto",
+ Package: "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto",
+ },
+ } {
+ log.Printf("protoc %s/%s", pb.Dir, pb.Basename)
+ protoc := exec.Command("protoc",
+ "-I=.",
+ "--go_out=.",
+ "--go_opt=M"+pb.Basename+"="+pb.Package,
+ "--go_opt=paths=source_relative")
+ if strings.HasSuffix(pb.Dir, "/proto3test_go_proto") {
+ protoc.Args = append(protoc.Args, "--go_opt=default_api_level=API_HYBRID")
+ }
+ protoc.Args = append(protoc.Args, pb.Basename)
+ protoc.Dir = pb.Dir
+ protoc.Stderr = os.Stderr
+ if err := protoc.Run(); err != nil {
+ return fmt.Errorf("%v: %v", protoc.Args, err)
+ }
+ }
+ return nil
+}
+
+func main() {
+ if err := genProtos(); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/gentestdata.go b/gentestdata.go
new file mode 100644
index 0000000..05d8291
--- /dev/null
+++ b/gentestdata.go
@@ -0,0 +1,32 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+ "log"
+ "os"
+
+ "google.golang.org/open2opaque/internal/fix"
+)
+
+func genTestdataFake() error {
+ log.Printf("generating testdata/fake")
+ content := fix.NewSrc("", "")
+ if err := os.MkdirAll("internal/fix/testdata/fake", 0777); err != nil {
+ return err
+ }
+ if err := os.WriteFile("internal/fix/testdata/fake/fake.go", []byte(content), 0666); err != nil {
+ return err
+ }
+ return nil
+}
+
+func main() {
+ if err := genTestdataFake(); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0cdd9dd
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,22 @@
+module google.golang.org/open2opaque
+
+go 1.23
+
+require (
+ github.com/dave/dst v0.27.3
+ github.com/golang/glog v1.2.2
+ github.com/google/go-cmp v0.6.0
+ github.com/google/subcommands v1.2.0
+ github.com/jhump/protoreflect v1.17.0
+ github.com/kylelemons/godebug v1.1.0
+ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
+ golang.org/x/sync v0.8.0
+ golang.org/x/tools v0.25.0
+ google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74
+)
+
+require (
+ github.com/bufbuild/protocompile v0.14.1 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
+ golang.org/x/mod v0.21.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..9d316b3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,48 @@
+github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
+github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
+github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
+github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
+github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg=
+github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
+github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
+github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
+github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
+github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
+golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
+golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
+google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
+google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
+google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74 h1:lM8vZMBh1xiYK60RmQQEO8xOdsMF+SdeT3BJk1+xMpI=
+google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/apiflagdata/go_api_enum.pb.go b/internal/apiflagdata/go_api_enum.pb.go
new file mode 100644
index 0000000..f425e28
--- /dev/null
+++ b/internal/apiflagdata/go_api_enum.pb.go
@@ -0,0 +1,146 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: go_api_enum.proto
+
+package apiflagdata
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GoAPI int32
+
+const (
+ GoAPI_GO_API_UNSPECIFIED GoAPI = 0
+ GoAPI_INVALID GoAPI = 1
+ GoAPI_OPEN_V1 GoAPI = 2
+ GoAPI_OPEN_TO_OPAQUE_HYBRID GoAPI = 3
+ GoAPI_OPAQUE_V0 GoAPI = 4
+)
+
+// Enum value maps for GoAPI.
+var (
+ GoAPI_name = map[int32]string{
+ 0: "GO_API_UNSPECIFIED",
+ 1: "INVALID",
+ 2: "OPEN_V1",
+ 3: "OPEN_TO_OPAQUE_HYBRID",
+ 4: "OPAQUE_V0",
+ }
+ GoAPI_value = map[string]int32{
+ "GO_API_UNSPECIFIED": 0,
+ "INVALID": 1,
+ "OPEN_V1": 2,
+ "OPEN_TO_OPAQUE_HYBRID": 3,
+ "OPAQUE_V0": 4,
+ }
+)
+
+func (x GoAPI) Enum() *GoAPI {
+ p := new(GoAPI)
+ *p = x
+ return p
+}
+
+func (x GoAPI) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (GoAPI) Descriptor() protoreflect.EnumDescriptor {
+ return file_go_api_enum_proto_enumTypes[0].Descriptor()
+}
+
+func (GoAPI) Type() protoreflect.EnumType {
+ return &file_go_api_enum_proto_enumTypes[0]
+}
+
+func (x GoAPI) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use GoAPI.Descriptor instead.
+func (GoAPI) EnumDescriptor() ([]byte, []int) {
+ return file_go_api_enum_proto_rawDescGZIP(), []int{0}
+}
+
+var File_go_api_enum_proto protoreflect.FileDescriptor
+
+var file_go_api_enum_proto_rawDesc = []byte{
+ 0x0a, 0x11, 0x67, 0x6f, 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x12, 0x25, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x61,
+ 0x70, 0x69, 0x66, 0x6c, 0x61, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x69, 0x0a, 0x05, 0x47, 0x6f,
+ 0x41, 0x50, 0x49, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x55, 0x4e,
+ 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49,
+ 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x50, 0x45, 0x4e,
+ 0x5f, 0x56, 0x31, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x54, 0x4f,
+ 0x5f, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45, 0x5f, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x10, 0x03,
+ 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45, 0x5f, 0x56, 0x30, 0x10, 0x04, 0x22,
+ 0x04, 0x08, 0x05, 0x10, 0x05, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70,
+ 0xe8, 0x07,
+}
+
+var (
+ file_go_api_enum_proto_rawDescOnce sync.Once
+ file_go_api_enum_proto_rawDescData = file_go_api_enum_proto_rawDesc
+)
+
+func file_go_api_enum_proto_rawDescGZIP() []byte {
+ file_go_api_enum_proto_rawDescOnce.Do(func() {
+ file_go_api_enum_proto_rawDescData = protoimpl.X.CompressGZIP(file_go_api_enum_proto_rawDescData)
+ })
+ return file_go_api_enum_proto_rawDescData
+}
+
+var file_go_api_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_go_api_enum_proto_goTypes = []any{
+ (GoAPI)(0), // 0: net.proto2.go.open2opaque.apiflagdata.GoAPI
+}
+var file_go_api_enum_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_go_api_enum_proto_init() }
+func file_go_api_enum_proto_init() {
+ if File_go_api_enum_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_go_api_enum_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 0,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_go_api_enum_proto_goTypes,
+ DependencyIndexes: file_go_api_enum_proto_depIdxs,
+ EnumInfos: file_go_api_enum_proto_enumTypes,
+ }.Build()
+ File_go_api_enum_proto = out.File
+ file_go_api_enum_proto_rawDesc = nil
+ file_go_api_enum_proto_goTypes = nil
+ file_go_api_enum_proto_depIdxs = nil
+}
diff --git a/internal/apiflagdata/go_api_enum.proto b/internal/apiflagdata/go_api_enum.proto
new file mode 100644
index 0000000..7464612
--- /dev/null
+++ b/internal/apiflagdata/go_api_enum.proto
@@ -0,0 +1,17 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.apiflagdata;
+
+enum GoAPI {
+ reserved 5;
+
+ GO_API_UNSPECIFIED = 0;
+ INVALID = 1;
+ OPEN_V1 = 2;
+ OPEN_TO_OPAQUE_HYBRID = 3;
+ OPAQUE_V0 = 4;
+}
diff --git a/internal/apiflagdata/set/set.go b/internal/apiflagdata/set/set.go
new file mode 100644
index 0000000..5db1cfa
--- /dev/null
+++ b/internal/apiflagdata/set/set.go
@@ -0,0 +1,50 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package set provides set data structures.
+package set
+
+// Strings is a set of strings.
+type Strings map[string]struct{}
+
+// NewStrings constructs Strings with given strs.
+func NewStrings(strs ...string) Strings {
+ ret := Strings{}
+ for _, s := range strs {
+ ret.Add(s)
+ }
+ return ret
+}
+
+// Add adds given string.
+func (s Strings) Add(str string) {
+ s[str] = struct{}{}
+}
+
+// AddSet adds values from another Strings object.
+func (s Strings) AddSet(strs Strings) {
+ for str := range strs {
+ s.Add(str)
+ }
+}
+
+// Len returns the size of the set.
+func (s Strings) Len() int {
+ return len(s)
+}
+
+// Contains returns true if given string is in set, else false.
+func (s Strings) Contains(str string) bool {
+ _, ok := s[str]
+ return ok
+}
+
+// ToSlice returns a slice of values.
+func (s Strings) ToSlice() []string {
+ ret := make([]string, 0, len(s))
+ for k := range s {
+ ret = append(ret, k)
+ }
+ return ret
+}
diff --git a/internal/apiflagdata/set/set_test.go b/internal/apiflagdata/set/set_test.go
new file mode 100644
index 0000000..33f3324
--- /dev/null
+++ b/internal/apiflagdata/set/set_test.go
@@ -0,0 +1,206 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package set_test
+
+import (
+ "sort"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "google.golang.org/open2opaque/internal/apiflagdata/set"
+)
+
+var empty = struct{}{}
+
+func TestNewStrings(t *testing.T) {
+ testcases := []struct {
+ input []string
+ want set.Strings
+ }{
+ {
+ input: nil,
+ want: set.Strings{},
+ },
+ {
+ input: []string{"hello"},
+ want: set.Strings{
+ "hello": empty,
+ },
+ },
+ {
+ input: []string{"foo", "bar"},
+ want: set.Strings{
+ "bar": empty,
+ "foo": empty,
+ },
+ },
+ {
+ input: []string{"foo", "bar", "foo"},
+ want: set.Strings{
+ "bar": empty,
+ "foo": empty,
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run("", func(t *testing.T) {
+ got := set.NewStrings(tc.input...)
+ if diff := cmp.Diff(tc.want, got); diff != "" {
+ t.Errorf("diff -want +got\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestStringsAdd(t *testing.T) {
+ testcases := []struct {
+ s set.Strings
+ input string
+ want set.Strings
+ }{
+ {
+ s: set.NewStrings(),
+ input: "foo",
+ want: set.Strings{
+ "foo": empty,
+ },
+ },
+ {
+ s: set.NewStrings("foo"),
+ input: "bar",
+ want: set.Strings{
+ "bar": empty,
+ "foo": empty,
+ },
+ },
+ {
+ s: set.NewStrings("foo"),
+ input: "foo",
+ want: set.Strings{
+ "foo": empty,
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run("", func(t *testing.T) {
+ tc.s.Add(tc.input)
+ if diff := cmp.Diff(tc.want, tc.s); diff != "" {
+ t.Errorf("diff -want +got\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestStringsAddSet(t *testing.T) {
+ testcases := []struct {
+ s1 set.Strings
+ s2 set.Strings
+ want set.Strings
+ }{
+ {
+ s1: set.NewStrings(),
+ s2: set.NewStrings("a", "b"),
+ want: set.Strings{
+ "a": empty,
+ "b": empty,
+ },
+ },
+ {
+ s1: set.NewStrings("a", "b"),
+ s2: set.NewStrings(),
+ want: set.Strings{
+ "a": empty,
+ "b": empty,
+ },
+ },
+ {
+ s1: set.NewStrings("a"),
+ s2: set.NewStrings("b"),
+ want: set.Strings{
+ "a": empty,
+ "b": empty,
+ },
+ },
+ {
+ s1: set.NewStrings("a", "b"),
+ s2: set.NewStrings("b", "c"),
+ want: set.Strings{
+ "a": empty,
+ "b": empty,
+ "c": empty,
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run("", func(t *testing.T) {
+ tc.s1.AddSet(tc.s2)
+ if diff := cmp.Diff(tc.want, tc.s1); diff != "" {
+ t.Errorf("diff -want +got\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestStringsContains(t *testing.T) {
+ testcases := []struct {
+ s set.Strings
+ input string
+ want bool
+ }{
+ {
+ s: set.NewStrings(),
+ input: "bar",
+ want: false,
+ },
+ {
+ s: set.NewStrings("foo"),
+ input: "foo",
+ want: true,
+ },
+ {
+ s: set.NewStrings("bar"),
+ input: "qux",
+ want: false,
+ },
+ {
+ s: set.NewStrings("foo", "bar"),
+ input: "foo",
+ want: true,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run("", func(t *testing.T) {
+ got := tc.s.Contains(tc.input)
+ if got != tc.want {
+ t.Errorf("got %v, want %v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestStringsSlice(t *testing.T) {
+ testcases := [][]string{
+ {},
+ {"a"},
+ {"z", "a", "j"},
+ }
+
+ for _, input := range testcases {
+ t.Run("", func(t *testing.T) {
+ s := set.NewStrings(input...)
+ got := s.ToSlice()
+ // Sort the returned slice and input before doing the comparison.
+ sort.Strings(got)
+ sort.Strings(input)
+ if diff := cmp.Diff(input, got); diff != "" {
+ t.Errorf("diff -want +got\n%s", diff)
+ }
+ })
+ }
+}
diff --git a/internal/dashboard/stats.pb.go b/internal/dashboard/stats.pb.go
new file mode 100644
index 0000000..08010a4
--- /dev/null
+++ b/internal/dashboard/stats.pb.go
@@ -0,0 +1,2858 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: stats.proto
+
+//go:build !protoopaque
+
+package dashboard
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ _ "google.golang.org/protobuf/types/gofeaturespb"
+ reflect "reflect"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// RewriteLevel represents rewrite level after which entries are
+// collected. For example, GREEN means that the tool does a green
+// rewrite and analyzes the result to create entries.
+type RewriteLevel int32
+
+const (
+ RewriteLevel_REWRITE_LEVEL_UNSPECIFIED RewriteLevel = 0
+ RewriteLevel_NONE RewriteLevel = 1
+ RewriteLevel_GREEN RewriteLevel = 2
+ RewriteLevel_YELLOW RewriteLevel = 3
+ RewriteLevel_RED RewriteLevel = 4
+)
+
+// Enum value maps for RewriteLevel.
+var (
+ RewriteLevel_name = map[int32]string{
+ 0: "REWRITE_LEVEL_UNSPECIFIED",
+ 1: "NONE",
+ 2: "GREEN",
+ 3: "YELLOW",
+ 4: "RED",
+ }
+ RewriteLevel_value = map[string]int32{
+ "REWRITE_LEVEL_UNSPECIFIED": 0,
+ "NONE": 1,
+ "GREEN": 2,
+ "YELLOW": 3,
+ "RED": 4,
+ }
+)
+
+func (x RewriteLevel) Enum() *RewriteLevel {
+ p := new(RewriteLevel)
+ *p = x
+ return p
+}
+
+func (x RewriteLevel) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (RewriteLevel) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[0].Descriptor()
+}
+
+func (RewriteLevel) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[0]
+}
+
+func (x RewriteLevel) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Status_Type int32
+
+const (
+ Status_UNSPECIFIED_TYPE Status_Type = 0
+ Status_OK Status_Type = 1
+ Status_SKIP Status_Type = 2
+ Status_FAIL Status_Type = 3
+)
+
+// Enum value maps for Status_Type.
+var (
+ Status_Type_name = map[int32]string{
+ 0: "UNSPECIFIED_TYPE",
+ 1: "OK",
+ 2: "SKIP",
+ 3: "FAIL",
+ }
+ Status_Type_value = map[string]int32{
+ "UNSPECIFIED_TYPE": 0,
+ "OK": 1,
+ "SKIP": 2,
+ "FAIL": 3,
+ }
+)
+
+func (x Status_Type) Enum() *Status_Type {
+ p := new(Status_Type)
+ *p = x
+ return p
+}
+
+func (x Status_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Status_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[1].Descriptor()
+}
+
+func (Status_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[1]
+}
+
+func (x Status_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Use_Type int32
+
+const (
+ Use_TYPE_UNSPECIFIED Use_Type = 0
+ Use_DIRECT_FIELD_ACCESS Use_Type = 1
+ Use_METHOD_CALL Use_Type = 2
+ Use_CONSTRUCTOR Use_Type = 3
+ Use_CONVERSION Use_Type = 4
+ Use_TYPE_ASSERTION Use_Type = 5
+ Use_TYPE_DEFINITION Use_Type = 6
+ Use_EMBEDDING Use_Type = 7
+ Use_INTERNAL_FIELD_ACCESS Use_Type = 8
+ Use_REFLECT_CALL Use_Type = 9
+ Use_SHALLOW_COPY Use_Type = 10
+ Use_BUILD_DEPENDENCY Use_Type = 11
+)
+
+// Enum value maps for Use_Type.
+var (
+ Use_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "DIRECT_FIELD_ACCESS",
+ 2: "METHOD_CALL",
+ 3: "CONSTRUCTOR",
+ 4: "CONVERSION",
+ 5: "TYPE_ASSERTION",
+ 6: "TYPE_DEFINITION",
+ 7: "EMBEDDING",
+ 8: "INTERNAL_FIELD_ACCESS",
+ 9: "REFLECT_CALL",
+ 10: "SHALLOW_COPY",
+ 11: "BUILD_DEPENDENCY",
+ }
+ Use_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "DIRECT_FIELD_ACCESS": 1,
+ "METHOD_CALL": 2,
+ "CONSTRUCTOR": 3,
+ "CONVERSION": 4,
+ "TYPE_ASSERTION": 5,
+ "TYPE_DEFINITION": 6,
+ "EMBEDDING": 7,
+ "INTERNAL_FIELD_ACCESS": 8,
+ "REFLECT_CALL": 9,
+ "SHALLOW_COPY": 10,
+ "BUILD_DEPENDENCY": 11,
+ }
+)
+
+func (x Use_Type) Enum() *Use_Type {
+ p := new(Use_Type)
+ *p = x
+ return p
+}
+
+func (x Use_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Use_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[2].Descriptor()
+}
+
+func (Use_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[2]
+}
+
+func (x Use_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type MethodCall_Type int32
+
+const (
+ MethodCall_INVALID MethodCall_Type = 0
+ MethodCall_GET_ONEOF MethodCall_Type = 1
+ MethodCall_GET_BUILD MethodCall_Type = 2
+)
+
+// Enum value maps for MethodCall_Type.
+var (
+ MethodCall_Type_name = map[int32]string{
+ 0: "INVALID",
+ 1: "GET_ONEOF",
+ 2: "GET_BUILD",
+ }
+ MethodCall_Type_value = map[string]int32{
+ "INVALID": 0,
+ "GET_ONEOF": 1,
+ "GET_BUILD": 2,
+ }
+)
+
+func (x MethodCall_Type) Enum() *MethodCall_Type {
+ p := new(MethodCall_Type)
+ *p = x
+ return p
+}
+
+func (x MethodCall_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (MethodCall_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[3].Descriptor()
+}
+
+func (MethodCall_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[3]
+}
+
+func (x MethodCall_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Constructor_Type int32
+
+const (
+ Constructor_TYPE_UNSPECIFIED Constructor_Type = 0
+ Constructor_EMPTY_LITERAL Constructor_Type = 1
+ Constructor_NONEMPTY_LITERAL Constructor_Type = 2
+ Constructor_BUILDER Constructor_Type = 3
+)
+
+// Enum value maps for Constructor_Type.
+var (
+ Constructor_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "EMPTY_LITERAL",
+ 2: "NONEMPTY_LITERAL",
+ 3: "BUILDER",
+ }
+ Constructor_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "EMPTY_LITERAL": 1,
+ "NONEMPTY_LITERAL": 2,
+ "BUILDER": 3,
+ }
+)
+
+func (x Constructor_Type) Enum() *Constructor_Type {
+ p := new(Constructor_Type)
+ *p = x
+ return p
+}
+
+func (x Constructor_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Constructor_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[4].Descriptor()
+}
+
+func (Constructor_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[4]
+}
+
+func (x Constructor_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Conversion_Context int32
+
+const (
+ Conversion_CONTEXT_UNSPECIFIED Conversion_Context = 0
+ Conversion_CALL_ARGUMENT Conversion_Context = 1
+ Conversion_RETURN_VALUE Conversion_Context = 2
+ Conversion_ASSIGNMENT Conversion_Context = 3
+ Conversion_EXPLICIT Conversion_Context = 4
+ Conversion_COMPOSITE_LITERAL_ELEMENT Conversion_Context = 5
+ Conversion_CHAN_SEND Conversion_Context = 6
+ Conversion_FUNC_RET Conversion_Context = 7
+)
+
+// Enum value maps for Conversion_Context.
+var (
+ Conversion_Context_name = map[int32]string{
+ 0: "CONTEXT_UNSPECIFIED",
+ 1: "CALL_ARGUMENT",
+ 2: "RETURN_VALUE",
+ 3: "ASSIGNMENT",
+ 4: "EXPLICIT",
+ 5: "COMPOSITE_LITERAL_ELEMENT",
+ 6: "CHAN_SEND",
+ 7: "FUNC_RET",
+ }
+ Conversion_Context_value = map[string]int32{
+ "CONTEXT_UNSPECIFIED": 0,
+ "CALL_ARGUMENT": 1,
+ "RETURN_VALUE": 2,
+ "ASSIGNMENT": 3,
+ "EXPLICIT": 4,
+ "COMPOSITE_LITERAL_ELEMENT": 5,
+ "CHAN_SEND": 6,
+ "FUNC_RET": 7,
+ }
+)
+
+func (x Conversion_Context) Enum() *Conversion_Context {
+ p := new(Conversion_Context)
+ *p = x
+ return p
+}
+
+func (x Conversion_Context) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Conversion_Context) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[5].Descriptor()
+}
+
+func (Conversion_Context) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[5]
+}
+
+func (x Conversion_Context) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type ShallowCopy_Type int32
+
+const (
+ ShallowCopy_TYPE_UNSPECIFIED ShallowCopy_Type = 0
+ ShallowCopy_ASSIGN ShallowCopy_Type = 1
+ ShallowCopy_CALL_ARGUMENT ShallowCopy_Type = 2
+ ShallowCopy_FUNC_RET ShallowCopy_Type = 3
+ ShallowCopy_COMPOSITE_LITERAL_ELEMENT ShallowCopy_Type = 4
+ ShallowCopy_CHAN_SEND ShallowCopy_Type = 5
+)
+
+// Enum value maps for ShallowCopy_Type.
+var (
+ ShallowCopy_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "ASSIGN",
+ 2: "CALL_ARGUMENT",
+ 3: "FUNC_RET",
+ 4: "COMPOSITE_LITERAL_ELEMENT",
+ 5: "CHAN_SEND",
+ }
+ ShallowCopy_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "ASSIGN": 1,
+ "CALL_ARGUMENT": 2,
+ "FUNC_RET": 3,
+ "COMPOSITE_LITERAL_ELEMENT": 4,
+ "CHAN_SEND": 5,
+ }
+)
+
+func (x ShallowCopy_Type) Enum() *ShallowCopy_Type {
+ p := new(ShallowCopy_Type)
+ *p = x
+ return p
+}
+
+func (x ShallowCopy_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ShallowCopy_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[6].Descriptor()
+}
+
+func (ShallowCopy_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[6]
+}
+
+func (x ShallowCopy_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Entry represents a single usage of a proto type. For example, a
+// single field access, method call, use as a type, etc. We collect
+// entries and analyze them offline to generate various statistics
+// (e.g. number of direct field accesses per package) about the
+// migration to opaque Go protocol buffer API.
+type Entry struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // If status is not set or empty then all other fields should be set
+ // and describe a single usage of a protocol buffer type in Go.
+ //
+ // If status is not empty then:
+ // - status.error says what went wrong, and
+ // - location.package says which package couldn't be processed
+ Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
+ // Location in Go code. Always set. Location.package is always
+ // non-empty.
+ Location *Location `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"`
+ // Rewrite level after which this entry was captured.
+ Level RewriteLevel `protobuf:"varint,3,opt,name=level,enum=net.proto2.go.open2opaque.stats.RewriteLevel" json:"level,omitempty"`
+ // Go type representing a protocol buffer type.
+ Type *Type `protobuf:"bytes,4,opt,name=type" json:"type,omitempty"`
+ // Go expression which leads to this entry.
+ Expr *Expression `protobuf:"bytes,5,opt,name=expr" json:"expr,omitempty"`
+ // Describes how a protocol buffer is used (e.g. direct field access).
+ Use *Use `protobuf:"bytes,6,opt,name=use" json:"use,omitempty"`
+ // Source of the information. For debugging purposes.
+ Source *Source `protobuf:"bytes,7,opt,name=source" json:"source,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Entry) Reset() {
+ *x = Entry{}
+ mi := &file_stats_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Entry) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Entry) ProtoMessage() {}
+
+func (x *Entry) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Entry) GetStatus() *Status {
+ if x != nil {
+ return x.Status
+ }
+ return nil
+}
+
+func (x *Entry) GetLocation() *Location {
+ if x != nil {
+ return x.Location
+ }
+ return nil
+}
+
+func (x *Entry) GetLevel() RewriteLevel {
+ if x != nil {
+ return x.Level
+ }
+ return RewriteLevel_REWRITE_LEVEL_UNSPECIFIED
+}
+
+func (x *Entry) GetType() *Type {
+ if x != nil {
+ return x.Type
+ }
+ return nil
+}
+
+func (x *Entry) GetExpr() *Expression {
+ if x != nil {
+ return x.Expr
+ }
+ return nil
+}
+
+func (x *Entry) GetUse() *Use {
+ if x != nil {
+ return x.Use
+ }
+ return nil
+}
+
+func (x *Entry) GetSource() *Source {
+ if x != nil {
+ return x.Source
+ }
+ return nil
+}
+
+func (x *Entry) SetStatus(v *Status) {
+ x.Status = v
+}
+
+func (x *Entry) SetLocation(v *Location) {
+ x.Location = v
+}
+
+func (x *Entry) SetLevel(v RewriteLevel) {
+ x.Level = v
+}
+
+func (x *Entry) SetType(v *Type) {
+ x.Type = v
+}
+
+func (x *Entry) SetExpr(v *Expression) {
+ x.Expr = v
+}
+
+func (x *Entry) SetUse(v *Use) {
+ x.Use = v
+}
+
+func (x *Entry) SetSource(v *Source) {
+ x.Source = v
+}
+
+func (x *Entry) HasStatus() bool {
+ if x == nil {
+ return false
+ }
+ return x.Status != nil
+}
+
+func (x *Entry) HasLocation() bool {
+ if x == nil {
+ return false
+ }
+ return x.Location != nil
+}
+
+func (x *Entry) HasType() bool {
+ if x == nil {
+ return false
+ }
+ return x.Type != nil
+}
+
+func (x *Entry) HasExpr() bool {
+ if x == nil {
+ return false
+ }
+ return x.Expr != nil
+}
+
+func (x *Entry) HasUse() bool {
+ if x == nil {
+ return false
+ }
+ return x.Use != nil
+}
+
+func (x *Entry) HasSource() bool {
+ if x == nil {
+ return false
+ }
+ return x.Source != nil
+}
+
+func (x *Entry) ClearStatus() {
+ x.Status = nil
+}
+
+func (x *Entry) ClearLocation() {
+ x.Location = nil
+}
+
+func (x *Entry) ClearType() {
+ x.Type = nil
+}
+
+func (x *Entry) ClearExpr() {
+ x.Expr = nil
+}
+
+func (x *Entry) ClearUse() {
+ x.Use = nil
+}
+
+func (x *Entry) ClearSource() {
+ x.Source = nil
+}
+
+type Entry_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // If status is not set or empty then all other fields should be set
+ // and describe a single usage of a protocol buffer type in Go.
+ //
+ // If status is not empty then:
+ // - status.error says what went wrong, and
+ // - location.package says which package couldn't be processed
+ Status *Status
+ // Location in Go code. Always set. Location.package is always
+ // non-empty.
+ Location *Location
+ // Rewrite level after which this entry was captured.
+ Level RewriteLevel
+ // Go type representing a protocol buffer type.
+ Type *Type
+ // Go expression which leads to this entry.
+ Expr *Expression
+ // Describes how a protocol buffer is used (e.g. direct field access).
+ Use *Use
+ // Source of the information. For debugging purposes.
+ Source *Source
+}
+
+func (b0 Entry_builder) Build() *Entry {
+ m0 := &Entry{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Status = b.Status
+ x.Location = b.Location
+ x.Level = b.Level
+ x.Type = b.Type
+ x.Expr = b.Expr
+ x.Use = b.Use
+ x.Source = b.Source
+ return m0
+}
+
+// Location represents location of an expression in a Go file.
+type Location struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Full name of a Go package
+ Package string `protobuf:"bytes,1,opt,name=package" json:"package,omitempty"`
+ // path to a Go file
+ File string `protobuf:"bytes,2,opt,name=file" json:"file,omitempty"`
+ // Whether the file is a generated file.
+ IsGeneratedFile bool `protobuf:"varint,3,opt,name=is_generated_file,json=isGeneratedFile" json:"is_generated_file,omitempty"`
+ // Start of the expression.
+ Start *Position `protobuf:"bytes,4,opt,name=start" json:"start,omitempty"`
+ // End of the expression.
+ End *Position `protobuf:"bytes,5,opt,name=end" json:"end,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Location) Reset() {
+ *x = Location{}
+ mi := &file_stats_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Location) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Location) ProtoMessage() {}
+
+func (x *Location) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Location) GetPackage() string {
+ if x != nil {
+ return x.Package
+ }
+ return ""
+}
+
+func (x *Location) GetFile() string {
+ if x != nil {
+ return x.File
+ }
+ return ""
+}
+
+func (x *Location) GetIsGeneratedFile() bool {
+ if x != nil {
+ return x.IsGeneratedFile
+ }
+ return false
+}
+
+func (x *Location) GetStart() *Position {
+ if x != nil {
+ return x.Start
+ }
+ return nil
+}
+
+func (x *Location) GetEnd() *Position {
+ if x != nil {
+ return x.End
+ }
+ return nil
+}
+
+func (x *Location) SetPackage(v string) {
+ x.Package = v
+}
+
+func (x *Location) SetFile(v string) {
+ x.File = v
+}
+
+func (x *Location) SetIsGeneratedFile(v bool) {
+ x.IsGeneratedFile = v
+}
+
+func (x *Location) SetStart(v *Position) {
+ x.Start = v
+}
+
+func (x *Location) SetEnd(v *Position) {
+ x.End = v
+}
+
+func (x *Location) HasStart() bool {
+ if x == nil {
+ return false
+ }
+ return x.Start != nil
+}
+
+func (x *Location) HasEnd() bool {
+ if x == nil {
+ return false
+ }
+ return x.End != nil
+}
+
+func (x *Location) ClearStart() {
+ x.Start = nil
+}
+
+func (x *Location) ClearEnd() {
+ x.End = nil
+}
+
+type Location_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Full name of a Go package
+ Package string
+ // path to a Go file
+ File string
+ // Whether the file is a generated file.
+ IsGeneratedFile bool
+ // Start of the expression.
+ Start *Position
+ // End of the expression.
+ End *Position
+}
+
+func (b0 Location_builder) Build() *Location {
+ m0 := &Location{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Package = b.Package
+ x.File = b.File
+ x.IsGeneratedFile = b.IsGeneratedFile
+ x.Start = b.Start
+ x.End = b.End
+ return m0
+}
+
+// Position describes a position in a Go file.
+type Position struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Line int64 `protobuf:"varint,1,opt,name=line" json:"line,omitempty"`
+ Column int64 `protobuf:"varint,2,opt,name=column" json:"column,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Position) Reset() {
+ *x = Position{}
+ mi := &file_stats_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Position) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Position) ProtoMessage() {}
+
+func (x *Position) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Position) GetLine() int64 {
+ if x != nil {
+ return x.Line
+ }
+ return 0
+}
+
+func (x *Position) GetColumn() int64 {
+ if x != nil {
+ return x.Column
+ }
+ return 0
+}
+
+func (x *Position) SetLine(v int64) {
+ x.Line = v
+}
+
+func (x *Position) SetColumn(v int64) {
+ x.Column = v
+}
+
+type Position_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Line int64
+ Column int64
+}
+
+func (b0 Position_builder) Build() *Position {
+ m0 := &Position{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Line = b.Line
+ x.Column = b.Column
+ return m0
+}
+
+// Status specifies an error that occurred. Empty error indicates
+// success.
+type Status struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Type Status_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Status_Type" json:"type,omitempty"`
+ Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Status) Reset() {
+ *x = Status{}
+ mi := &file_stats_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Status) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status) ProtoMessage() {}
+
+func (x *Status) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Status) GetType() Status_Type {
+ if x != nil {
+ return x.Type
+ }
+ return Status_UNSPECIFIED_TYPE
+}
+
+func (x *Status) GetError() string {
+ if x != nil {
+ return x.Error
+ }
+ return ""
+}
+
+func (x *Status) SetType(v Status_Type) {
+ x.Type = v
+}
+
+func (x *Status) SetError(v string) {
+ x.Error = v
+}
+
+type Status_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Status_Type
+ Error string
+}
+
+func (b0 Status_builder) Build() *Status {
+ m0 := &Status{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Type = b.Type
+ x.Error = b.Error
+ return m0
+}
+
+// Type represents a Go name for a protocol buffer type.
+type Type struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // The short name of the Go type representing the protocol buffer
+ // type. For example: "qem_go_proto.QueryEventMessage".
+ ShortName string `protobuf:"bytes,1,opt,name=short_name,json=shortName" json:"short_name,omitempty"`
+ // A fully qualified name of the Go type representing the protocol
+ // buffer type.
+ LongName string `protobuf:"bytes,2,opt,name=long_name,json=longName" json:"long_name,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Type) Reset() {
+ *x = Type{}
+ mi := &file_stats_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Type) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Type) ProtoMessage() {}
+
+func (x *Type) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Type) GetShortName() string {
+ if x != nil {
+ return x.ShortName
+ }
+ return ""
+}
+
+func (x *Type) GetLongName() string {
+ if x != nil {
+ return x.LongName
+ }
+ return ""
+}
+
+func (x *Type) SetShortName(v string) {
+ x.ShortName = v
+}
+
+func (x *Type) SetLongName(v string) {
+ x.LongName = v
+}
+
+type Type_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The short name of the Go type representing the protocol buffer
+ // type. For example: "qem_go_proto.QueryEventMessage".
+ ShortName string
+ // A fully qualified name of the Go type representing the protocol
+ // buffer type.
+ LongName string
+}
+
+func (b0 Type_builder) Build() *Type {
+ m0 := &Type{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.ShortName = b.ShortName
+ x.LongName = b.LongName
+ return m0
+}
+
+// Expression describes a Go expression.
+type Expression struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // go/ast expression type (e.g. "*ast.Ident").
+ Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"`
+ // go/ast expression type of the parent.
+ ParentType string `protobuf:"bytes,2,opt,name=parent_type,json=parentType" json:"parent_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Expression) Reset() {
+ *x = Expression{}
+ mi := &file_stats_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Expression) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Expression) ProtoMessage() {}
+
+func (x *Expression) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Expression) GetType() string {
+ if x != nil {
+ return x.Type
+ }
+ return ""
+}
+
+func (x *Expression) GetParentType() string {
+ if x != nil {
+ return x.ParentType
+ }
+ return ""
+}
+
+func (x *Expression) SetType(v string) {
+ x.Type = v
+}
+
+func (x *Expression) SetParentType(v string) {
+ x.ParentType = v
+}
+
+type Expression_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // go/ast expression type (e.g. "*ast.Ident").
+ Type string
+ // go/ast expression type of the parent.
+ ParentType string
+}
+
+func (b0 Expression_builder) Build() *Expression {
+ m0 := &Expression{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Type = b.Type
+ x.ParentType = b.ParentType
+ return m0
+}
+
+// Use describes a use of a protocol buffer type in Go.
+type Use struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Type Use_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Use_Type" json:"type,omitempty"`
+ DirectFieldAccess *FieldAccess `protobuf:"bytes,2,opt,name=direct_field_access,json=directFieldAccess" json:"direct_field_access,omitempty"`
+ MethodCall *MethodCall `protobuf:"bytes,3,opt,name=method_call,json=methodCall" json:"method_call,omitempty"`
+ Constructor *Constructor `protobuf:"bytes,4,opt,name=constructor" json:"constructor,omitempty"`
+ Conversion *Conversion `protobuf:"bytes,5,opt,name=conversion" json:"conversion,omitempty"`
+ FuncArg *FuncArg `protobuf:"bytes,6,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"`
+ TypeAssertion *TypeAssertion `protobuf:"bytes,7,opt,name=type_assertion,json=typeAssertion" json:"type_assertion,omitempty"`
+ TypeDefinition *TypeDefinition `protobuf:"bytes,8,opt,name=type_definition,json=typeDefinition" json:"type_definition,omitempty"`
+ Embedding *Embedding `protobuf:"bytes,9,opt,name=embedding" json:"embedding,omitempty"`
+ InternalFieldAccess *FieldAccess `protobuf:"bytes,10,opt,name=internal_field_access,json=internalFieldAccess" json:"internal_field_access,omitempty"`
+ ReflectCall *ReflectCall `protobuf:"bytes,11,opt,name=reflect_call,json=reflectCall" json:"reflect_call,omitempty"`
+ ShallowCopy *ShallowCopy `protobuf:"bytes,12,opt,name=shallow_copy,json=shallowCopy" json:"shallow_copy,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Use) Reset() {
+ *x = Use{}
+ mi := &file_stats_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Use) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Use) ProtoMessage() {}
+
+func (x *Use) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Use) GetType() Use_Type {
+ if x != nil {
+ return x.Type
+ }
+ return Use_TYPE_UNSPECIFIED
+}
+
+func (x *Use) GetDirectFieldAccess() *FieldAccess {
+ if x != nil {
+ return x.DirectFieldAccess
+ }
+ return nil
+}
+
+func (x *Use) GetMethodCall() *MethodCall {
+ if x != nil {
+ return x.MethodCall
+ }
+ return nil
+}
+
+func (x *Use) GetConstructor() *Constructor {
+ if x != nil {
+ return x.Constructor
+ }
+ return nil
+}
+
+func (x *Use) GetConversion() *Conversion {
+ if x != nil {
+ return x.Conversion
+ }
+ return nil
+}
+
+func (x *Use) GetFuncArg() *FuncArg {
+ if x != nil {
+ return x.FuncArg
+ }
+ return nil
+}
+
+func (x *Use) GetTypeAssertion() *TypeAssertion {
+ if x != nil {
+ return x.TypeAssertion
+ }
+ return nil
+}
+
+func (x *Use) GetTypeDefinition() *TypeDefinition {
+ if x != nil {
+ return x.TypeDefinition
+ }
+ return nil
+}
+
+func (x *Use) GetEmbedding() *Embedding {
+ if x != nil {
+ return x.Embedding
+ }
+ return nil
+}
+
+func (x *Use) GetInternalFieldAccess() *FieldAccess {
+ if x != nil {
+ return x.InternalFieldAccess
+ }
+ return nil
+}
+
+func (x *Use) GetReflectCall() *ReflectCall {
+ if x != nil {
+ return x.ReflectCall
+ }
+ return nil
+}
+
+func (x *Use) GetShallowCopy() *ShallowCopy {
+ if x != nil {
+ return x.ShallowCopy
+ }
+ return nil
+}
+
+func (x *Use) SetType(v Use_Type) {
+ x.Type = v
+}
+
+func (x *Use) SetDirectFieldAccess(v *FieldAccess) {
+ x.DirectFieldAccess = v
+}
+
+func (x *Use) SetMethodCall(v *MethodCall) {
+ x.MethodCall = v
+}
+
+func (x *Use) SetConstructor(v *Constructor) {
+ x.Constructor = v
+}
+
+func (x *Use) SetConversion(v *Conversion) {
+ x.Conversion = v
+}
+
+func (x *Use) SetFuncArg(v *FuncArg) {
+ x.FuncArg = v
+}
+
+func (x *Use) SetTypeAssertion(v *TypeAssertion) {
+ x.TypeAssertion = v
+}
+
+func (x *Use) SetTypeDefinition(v *TypeDefinition) {
+ x.TypeDefinition = v
+}
+
+func (x *Use) SetEmbedding(v *Embedding) {
+ x.Embedding = v
+}
+
+func (x *Use) SetInternalFieldAccess(v *FieldAccess) {
+ x.InternalFieldAccess = v
+}
+
+func (x *Use) SetReflectCall(v *ReflectCall) {
+ x.ReflectCall = v
+}
+
+func (x *Use) SetShallowCopy(v *ShallowCopy) {
+ x.ShallowCopy = v
+}
+
+func (x *Use) HasDirectFieldAccess() bool {
+ if x == nil {
+ return false
+ }
+ return x.DirectFieldAccess != nil
+}
+
+func (x *Use) HasMethodCall() bool {
+ if x == nil {
+ return false
+ }
+ return x.MethodCall != nil
+}
+
+func (x *Use) HasConstructor() bool {
+ if x == nil {
+ return false
+ }
+ return x.Constructor != nil
+}
+
+func (x *Use) HasConversion() bool {
+ if x == nil {
+ return false
+ }
+ return x.Conversion != nil
+}
+
+func (x *Use) HasFuncArg() bool {
+ if x == nil {
+ return false
+ }
+ return x.FuncArg != nil
+}
+
+func (x *Use) HasTypeAssertion() bool {
+ if x == nil {
+ return false
+ }
+ return x.TypeAssertion != nil
+}
+
+func (x *Use) HasTypeDefinition() bool {
+ if x == nil {
+ return false
+ }
+ return x.TypeDefinition != nil
+}
+
+func (x *Use) HasEmbedding() bool {
+ if x == nil {
+ return false
+ }
+ return x.Embedding != nil
+}
+
+func (x *Use) HasInternalFieldAccess() bool {
+ if x == nil {
+ return false
+ }
+ return x.InternalFieldAccess != nil
+}
+
+func (x *Use) HasReflectCall() bool {
+ if x == nil {
+ return false
+ }
+ return x.ReflectCall != nil
+}
+
+func (x *Use) HasShallowCopy() bool {
+ if x == nil {
+ return false
+ }
+ return x.ShallowCopy != nil
+}
+
+func (x *Use) ClearDirectFieldAccess() {
+ x.DirectFieldAccess = nil
+}
+
+func (x *Use) ClearMethodCall() {
+ x.MethodCall = nil
+}
+
+func (x *Use) ClearConstructor() {
+ x.Constructor = nil
+}
+
+func (x *Use) ClearConversion() {
+ x.Conversion = nil
+}
+
+func (x *Use) ClearFuncArg() {
+ x.FuncArg = nil
+}
+
+func (x *Use) ClearTypeAssertion() {
+ x.TypeAssertion = nil
+}
+
+func (x *Use) ClearTypeDefinition() {
+ x.TypeDefinition = nil
+}
+
+func (x *Use) ClearEmbedding() {
+ x.Embedding = nil
+}
+
+func (x *Use) ClearInternalFieldAccess() {
+ x.InternalFieldAccess = nil
+}
+
+func (x *Use) ClearReflectCall() {
+ x.ReflectCall = nil
+}
+
+func (x *Use) ClearShallowCopy() {
+ x.ShallowCopy = nil
+}
+
+type Use_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Use_Type
+ DirectFieldAccess *FieldAccess
+ MethodCall *MethodCall
+ Constructor *Constructor
+ Conversion *Conversion
+ FuncArg *FuncArg
+ TypeAssertion *TypeAssertion
+ TypeDefinition *TypeDefinition
+ Embedding *Embedding
+ InternalFieldAccess *FieldAccess
+ ReflectCall *ReflectCall
+ ShallowCopy *ShallowCopy
+}
+
+func (b0 Use_builder) Build() *Use {
+ m0 := &Use{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Type = b.Type
+ x.DirectFieldAccess = b.DirectFieldAccess
+ x.MethodCall = b.MethodCall
+ x.Constructor = b.Constructor
+ x.Conversion = b.Conversion
+ x.FuncArg = b.FuncArg
+ x.TypeAssertion = b.TypeAssertion
+ x.TypeDefinition = b.TypeDefinition
+ x.Embedding = b.Embedding
+ x.InternalFieldAccess = b.InternalFieldAccess
+ x.ReflectCall = b.ReflectCall
+ x.ShallowCopy = b.ShallowCopy
+ return m0
+}
+
+// ReflectCall represents a call to the Go reflection API.
+type ReflectCall struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Information about all frames leading to the reflection call.
+ //
+ // A callstack usually looks like:
+ //
+ // reflect.Value.Field // the function triggering the log
+ // reflect.F1 // more functions
+ // ... // in the
+ // reflect.Fn // reflect package
+ // import/path.Caller // (1) fn: calls into the reflect package
+ // import/path.C1 // more functions
+ // ... // in the same package
+ // import/path.Cn1 // as fn
+ // ancestor/import.Ancestor // (2) caller: calls into fn's package
+ // ... // more frames
+ //
+ // The frames field has information about all frames but we also
+ // store a few selected frames separately to make it easier to write
+ // queries:
+ //
+ // (1) caller is the last function before the reflection package
+ // (2) ancestor is the last function from a different package than caller
+ //
+ // The frames field contains at least one frame (a function in the
+ // reflect package) but fn and caller may be unset.
+ Frames []*Frame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"`
+ Fn *Frame `protobuf:"bytes,2,opt,name=fn" json:"fn,omitempty"`
+ Caller *Frame `protobuf:"bytes,3,opt,name=caller" json:"caller,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ReflectCall) Reset() {
+ *x = ReflectCall{}
+ mi := &file_stats_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ReflectCall) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReflectCall) ProtoMessage() {}
+
+func (x *ReflectCall) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ReflectCall) GetFrames() []*Frame {
+ if x != nil {
+ return x.Frames
+ }
+ return nil
+}
+
+func (x *ReflectCall) GetFn() *Frame {
+ if x != nil {
+ return x.Fn
+ }
+ return nil
+}
+
+func (x *ReflectCall) GetCaller() *Frame {
+ if x != nil {
+ return x.Caller
+ }
+ return nil
+}
+
+func (x *ReflectCall) SetFrames(v []*Frame) {
+ x.Frames = v
+}
+
+func (x *ReflectCall) SetFn(v *Frame) {
+ x.Fn = v
+}
+
+func (x *ReflectCall) SetCaller(v *Frame) {
+ x.Caller = v
+}
+
+func (x *ReflectCall) HasFn() bool {
+ if x == nil {
+ return false
+ }
+ return x.Fn != nil
+}
+
+func (x *ReflectCall) HasCaller() bool {
+ if x == nil {
+ return false
+ }
+ return x.Caller != nil
+}
+
+func (x *ReflectCall) ClearFn() {
+ x.Fn = nil
+}
+
+func (x *ReflectCall) ClearCaller() {
+ x.Caller = nil
+}
+
+type ReflectCall_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Information about all frames leading to the reflection call.
+ //
+ // A callstack usually looks like:
+ //
+ // reflect.Value.Field // the function triggering the log
+ // reflect.F1 // more functions
+ // ... // in the
+ // reflect.Fn // reflect package
+ // import/path.Caller // (1) fn: calls into the reflect package
+ // import/path.C1 // more functions
+ // ... // in the same package
+ // import/path.Cn1 // as fn
+ // ancestor/import.Ancestor // (2) caller: calls into fn's package
+ // ... // more frames
+ //
+ // The frames field has information about all frames but we also
+ // store a few selected frames separately to make it easier to write
+ // queries:
+ //
+ // (1) caller is the last function before the reflection package
+ // (2) ancestor is the last function from a different package than caller
+ //
+ // The frames field contains at least one frame (a function in the
+ // reflect package) but fn and caller may be unset.
+ Frames []*Frame
+ Fn *Frame
+ Caller *Frame
+}
+
+func (b0 ReflectCall_builder) Build() *ReflectCall {
+ m0 := &ReflectCall{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Frames = b.Frames
+ x.Fn = b.Fn
+ x.Caller = b.Caller
+ return m0
+}
+
+// Frame represents information about a single frame in a Go
+// stacktrace.
+type Frame struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Fully qualified function name. For example:
+ //
+ // net/http.Error
+ Function string `protobuf:"bytes,1,opt,name=function" json:"function,omitempty"`
+ // true if the function is exported.
+ IsExported bool `protobuf:"varint,6,opt,name=is_exported,json=isExported" json:"is_exported,omitempty"`
+ // Packed in which the function is defined.
+ Package string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"`
+ // path to the source file defining the function.
+ //
+ // For standard-library, the path is relative to the src/
+ // directory (e.g. net/http/server.go).
+ File string `protobuf:"bytes,3,opt,name=file" json:"file,omitempty"`
+ // Line number, together with file, is the location where function
+ // is defined.
+ Line string `protobuf:"bytes,4,opt,name=line" json:"line,omitempty"`
+ // index of the frame in the reflect_call.frames repeated field.
+ //
+ // This exists to make SQL queries easier.
+ Index int64 `protobuf:"varint,5,opt,name=index" json:"index,omitempty"`
+ // index of the frame across consecutive frames in the same package.
+ //
+ // This exists to make SQL queries easier.
+ PkgIndex int64 `protobuf:"varint,7,opt,name=pkg_index,json=pkgIndex" json:"pkg_index,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Frame) Reset() {
+ *x = Frame{}
+ mi := &file_stats_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Frame) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Frame) ProtoMessage() {}
+
+func (x *Frame) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Frame) GetFunction() string {
+ if x != nil {
+ return x.Function
+ }
+ return ""
+}
+
+func (x *Frame) GetIsExported() bool {
+ if x != nil {
+ return x.IsExported
+ }
+ return false
+}
+
+func (x *Frame) GetPackage() string {
+ if x != nil {
+ return x.Package
+ }
+ return ""
+}
+
+func (x *Frame) GetFile() string {
+ if x != nil {
+ return x.File
+ }
+ return ""
+}
+
+func (x *Frame) GetLine() string {
+ if x != nil {
+ return x.Line
+ }
+ return ""
+}
+
+func (x *Frame) GetIndex() int64 {
+ if x != nil {
+ return x.Index
+ }
+ return 0
+}
+
+func (x *Frame) GetPkgIndex() int64 {
+ if x != nil {
+ return x.PkgIndex
+ }
+ return 0
+}
+
+func (x *Frame) SetFunction(v string) {
+ x.Function = v
+}
+
+func (x *Frame) SetIsExported(v bool) {
+ x.IsExported = v
+}
+
+func (x *Frame) SetPackage(v string) {
+ x.Package = v
+}
+
+func (x *Frame) SetFile(v string) {
+ x.File = v
+}
+
+func (x *Frame) SetLine(v string) {
+ x.Line = v
+}
+
+func (x *Frame) SetIndex(v int64) {
+ x.Index = v
+}
+
+func (x *Frame) SetPkgIndex(v int64) {
+ x.PkgIndex = v
+}
+
+type Frame_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fully qualified function name. For example:
+ //
+ // net/http.Error
+ Function string
+ // true if the function is exported.
+ IsExported bool
+ // Packed in which the function is defined.
+ Package string
+ // path to the source file defining the function.
+ //
+ // For standard-library, the path is relative to the src/
+ // directory (e.g. net/http/server.go).
+ File string
+ // Line number, together with file, is the location where function
+ // is defined.
+ Line string
+ // index of the frame in the reflect_call.frames repeated field.
+ //
+ // This exists to make SQL queries easier.
+ Index int64
+ // index of the frame across consecutive frames in the same package.
+ //
+ // This exists to make SQL queries easier.
+ PkgIndex int64
+}
+
+func (b0 Frame_builder) Build() *Frame {
+ m0 := &Frame{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Function = b.Function
+ x.IsExported = b.IsExported
+ x.Package = b.Package
+ x.File = b.File
+ x.Line = b.Line
+ x.Index = b.Index
+ x.PkgIndex = b.PkgIndex
+ return m0
+}
+
+type FieldAccess struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ FieldName string `protobuf:"bytes,1,opt,name=field_name,json=fieldName" json:"field_name,omitempty"`
+ FieldType *Type `protobuf:"bytes,2,opt,name=field_type,json=fieldType" json:"field_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *FieldAccess) Reset() {
+ *x = FieldAccess{}
+ mi := &file_stats_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *FieldAccess) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FieldAccess) ProtoMessage() {}
+
+func (x *FieldAccess) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *FieldAccess) GetFieldName() string {
+ if x != nil {
+ return x.FieldName
+ }
+ return ""
+}
+
+func (x *FieldAccess) GetFieldType() *Type {
+ if x != nil {
+ return x.FieldType
+ }
+ return nil
+}
+
+func (x *FieldAccess) SetFieldName(v string) {
+ x.FieldName = v
+}
+
+func (x *FieldAccess) SetFieldType(v *Type) {
+ x.FieldType = v
+}
+
+func (x *FieldAccess) HasFieldType() bool {
+ if x == nil {
+ return false
+ }
+ return x.FieldType != nil
+}
+
+func (x *FieldAccess) ClearFieldType() {
+ x.FieldType = nil
+}
+
+type FieldAccess_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ FieldName string
+ FieldType *Type
+}
+
+func (b0 FieldAccess_builder) Build() *FieldAccess {
+ m0 := &FieldAccess{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.FieldName = b.FieldName
+ x.FieldType = b.FieldType
+ return m0
+}
+
+type MethodCall struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method" json:"method,omitempty"`
+ Type MethodCall_Type `protobuf:"varint,2,opt,name=type,enum=net.proto2.go.open2opaque.stats.MethodCall_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MethodCall) Reset() {
+ *x = MethodCall{}
+ mi := &file_stats_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MethodCall) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MethodCall) ProtoMessage() {}
+
+func (x *MethodCall) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *MethodCall) GetMethod() string {
+ if x != nil {
+ return x.Method
+ }
+ return ""
+}
+
+func (x *MethodCall) GetType() MethodCall_Type {
+ if x != nil {
+ return x.Type
+ }
+ return MethodCall_INVALID
+}
+
+func (x *MethodCall) SetMethod(v string) {
+ x.Method = v
+}
+
+func (x *MethodCall) SetType(v MethodCall_Type) {
+ x.Type = v
+}
+
+type MethodCall_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Method string
+ Type MethodCall_Type
+}
+
+func (b0 MethodCall_builder) Build() *MethodCall {
+ m0 := &MethodCall{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Method = b.Method
+ x.Type = b.Type
+ return m0
+}
+
+type Constructor struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Type Constructor_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Constructor_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Constructor) Reset() {
+ *x = Constructor{}
+ mi := &file_stats_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Constructor) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Constructor) ProtoMessage() {}
+
+func (x *Constructor) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Constructor) GetType() Constructor_Type {
+ if x != nil {
+ return x.Type
+ }
+ return Constructor_TYPE_UNSPECIFIED
+}
+
+func (x *Constructor) SetType(v Constructor_Type) {
+ x.Type = v
+}
+
+type Constructor_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Constructor_Type
+}
+
+func (b0 Constructor_builder) Build() *Constructor {
+ m0 := &Constructor{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Type = b.Type
+ return m0
+}
+
+type Conversion struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // The type of the conversion. For example:
+ //
+ // interface{}
+ // proto.Message
+ // unsafe.Pointer
+ DestTypeName string `protobuf:"bytes,1,opt,name=dest_type_name,json=destTypeName" json:"dest_type_name,omitempty"`
+ // Describes the called function. It is set if context==CALL_ARGUMENT.
+ FuncArg *FuncArg `protobuf:"bytes,3,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"`
+ Context Conversion_Context `protobuf:"varint,2,opt,name=context,enum=net.proto2.go.open2opaque.stats.Conversion_Context" json:"context,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Conversion) Reset() {
+ *x = Conversion{}
+ mi := &file_stats_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Conversion) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Conversion) ProtoMessage() {}
+
+func (x *Conversion) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[12]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Conversion) GetDestTypeName() string {
+ if x != nil {
+ return x.DestTypeName
+ }
+ return ""
+}
+
+func (x *Conversion) GetFuncArg() *FuncArg {
+ if x != nil {
+ return x.FuncArg
+ }
+ return nil
+}
+
+func (x *Conversion) GetContext() Conversion_Context {
+ if x != nil {
+ return x.Context
+ }
+ return Conversion_CONTEXT_UNSPECIFIED
+}
+
+func (x *Conversion) SetDestTypeName(v string) {
+ x.DestTypeName = v
+}
+
+func (x *Conversion) SetFuncArg(v *FuncArg) {
+ x.FuncArg = v
+}
+
+func (x *Conversion) SetContext(v Conversion_Context) {
+ x.Context = v
+}
+
+func (x *Conversion) HasFuncArg() bool {
+ if x == nil {
+ return false
+ }
+ return x.FuncArg != nil
+}
+
+func (x *Conversion) ClearFuncArg() {
+ x.FuncArg = nil
+}
+
+type Conversion_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The type of the conversion. For example:
+ //
+ // interface{}
+ // proto.Message
+ // unsafe.Pointer
+ DestTypeName string
+ // Describes the called function. It is set if context==CALL_ARGUMENT.
+ FuncArg *FuncArg
+ Context Conversion_Context
+}
+
+func (b0 Conversion_builder) Build() *Conversion {
+ m0 := &Conversion{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.DestTypeName = b.DestTypeName
+ x.FuncArg = b.FuncArg
+ x.Context = b.Context
+ return m0
+}
+
+type FuncArg struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // The name of the called function.
+ // For example: "Clone".
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ FunctionName string `protobuf:"bytes,1,opt,name=function_name,json=functionName" json:"function_name,omitempty"`
+ // Full package path containing the called function.
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ PackagePath string `protobuf:"bytes,2,opt,name=package_path,json=packagePath" json:"package_path,omitempty"`
+ // Signature of the called function.
+ // For example: "func(m interface{}) interface{}".
+ Signature string `protobuf:"bytes,3,opt,name=signature" json:"signature,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *FuncArg) Reset() {
+ *x = FuncArg{}
+ mi := &file_stats_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *FuncArg) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FuncArg) ProtoMessage() {}
+
+func (x *FuncArg) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[13]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *FuncArg) GetFunctionName() string {
+ if x != nil {
+ return x.FunctionName
+ }
+ return ""
+}
+
+func (x *FuncArg) GetPackagePath() string {
+ if x != nil {
+ return x.PackagePath
+ }
+ return ""
+}
+
+func (x *FuncArg) GetSignature() string {
+ if x != nil {
+ return x.Signature
+ }
+ return ""
+}
+
+func (x *FuncArg) SetFunctionName(v string) {
+ x.FunctionName = v
+}
+
+func (x *FuncArg) SetPackagePath(v string) {
+ x.PackagePath = v
+}
+
+func (x *FuncArg) SetSignature(v string) {
+ x.Signature = v
+}
+
+type FuncArg_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The name of the called function.
+ // For example: "Clone".
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ FunctionName string
+ // Full package path containing the called function.
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ PackagePath string
+ // Signature of the called function.
+ // For example: "func(m interface{}) interface{}".
+ Signature string
+}
+
+func (b0 FuncArg_builder) Build() *FuncArg {
+ m0 := &FuncArg{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.FunctionName = b.FunctionName
+ x.PackagePath = b.PackagePath
+ x.Signature = b.Signature
+ return m0
+}
+
+type TypeAssertion struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // The type of the expression whose type is being asserted.
+ SrcType *Type `protobuf:"bytes,1,opt,name=src_type,json=srcType" json:"src_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TypeAssertion) Reset() {
+ *x = TypeAssertion{}
+ mi := &file_stats_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TypeAssertion) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TypeAssertion) ProtoMessage() {}
+
+func (x *TypeAssertion) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[14]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *TypeAssertion) GetSrcType() *Type {
+ if x != nil {
+ return x.SrcType
+ }
+ return nil
+}
+
+func (x *TypeAssertion) SetSrcType(v *Type) {
+ x.SrcType = v
+}
+
+func (x *TypeAssertion) HasSrcType() bool {
+ if x == nil {
+ return false
+ }
+ return x.SrcType != nil
+}
+
+func (x *TypeAssertion) ClearSrcType() {
+ x.SrcType = nil
+}
+
+type TypeAssertion_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The type of the expression whose type is being asserted.
+ SrcType *Type
+}
+
+func (b0 TypeAssertion_builder) Build() *TypeAssertion {
+ m0 := &TypeAssertion{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.SrcType = b.SrcType
+ return m0
+}
+
+type TypeDefinition struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // new_type describes the newly defined type.
+ NewType *Type `protobuf:"bytes,1,opt,name=new_type,json=newType" json:"new_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TypeDefinition) Reset() {
+ *x = TypeDefinition{}
+ mi := &file_stats_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TypeDefinition) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TypeDefinition) ProtoMessage() {}
+
+func (x *TypeDefinition) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[15]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *TypeDefinition) GetNewType() *Type {
+ if x != nil {
+ return x.NewType
+ }
+ return nil
+}
+
+func (x *TypeDefinition) SetNewType(v *Type) {
+ x.NewType = v
+}
+
+func (x *TypeDefinition) HasNewType() bool {
+ if x == nil {
+ return false
+ }
+ return x.NewType != nil
+}
+
+func (x *TypeDefinition) ClearNewType() {
+ x.NewType = nil
+}
+
+type TypeDefinition_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // new_type describes the newly defined type.
+ NewType *Type
+}
+
+func (b0 TypeDefinition_builder) Build() *TypeDefinition {
+ m0 := &TypeDefinition{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.NewType = b.NewType
+ return m0
+}
+
+type Embedding struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ FieldIndex int64 `protobuf:"varint,1,opt,name=field_index,json=fieldIndex" json:"field_index,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Embedding) Reset() {
+ *x = Embedding{}
+ mi := &file_stats_proto_msgTypes[16]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Embedding) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Embedding) ProtoMessage() {}
+
+func (x *Embedding) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[16]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Embedding) GetFieldIndex() int64 {
+ if x != nil {
+ return x.FieldIndex
+ }
+ return 0
+}
+
+func (x *Embedding) SetFieldIndex(v int64) {
+ x.FieldIndex = v
+}
+
+type Embedding_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ FieldIndex int64
+}
+
+func (b0 Embedding_builder) Build() *Embedding {
+ m0 := &Embedding{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.FieldIndex = b.FieldIndex
+ return m0
+}
+
+type Source struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ File string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Source) Reset() {
+ *x = Source{}
+ mi := &file_stats_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Source) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Source) ProtoMessage() {}
+
+func (x *Source) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[17]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Source) GetFile() string {
+ if x != nil {
+ return x.File
+ }
+ return ""
+}
+
+func (x *Source) SetFile(v string) {
+ x.File = v
+}
+
+type Source_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ File string
+}
+
+func (b0 Source_builder) Build() *Source {
+ m0 := &Source{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.File = b.File
+ return m0
+}
+
+// ShallowCopy represents a shallow copy of protocol buffers.
+//
+// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages
+// (pointers to Go structs generated by the proto generor).
+type ShallowCopy struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Type ShallowCopy_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.ShallowCopy_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ShallowCopy) Reset() {
+ *x = ShallowCopy{}
+ mi := &file_stats_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ShallowCopy) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ShallowCopy) ProtoMessage() {}
+
+func (x *ShallowCopy) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ShallowCopy) GetType() ShallowCopy_Type {
+ if x != nil {
+ return x.Type
+ }
+ return ShallowCopy_TYPE_UNSPECIFIED
+}
+
+func (x *ShallowCopy) SetType(v ShallowCopy_Type) {
+ x.Type = v
+}
+
+type ShallowCopy_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type ShallowCopy_Type
+}
+
+func (b0 ShallowCopy_builder) Build() *ShallowCopy {
+ m0 := &ShallowCopy{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Type = b.Type
+ return m0
+}
+
+var File_stats_proto protoreflect.FileDescriptor
+
+var file_stats_proto_rawDesc = []byte{
+ 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x6e,
+ 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
+ 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x21,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
+ 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x22, 0xc9, 0x03, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x06, 0x73,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74,
+ 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f,
+ 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
+ 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65,
+ 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
+ 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x04,
+ 0x65, 0x78, 0x70, 0x72, 0x12, 0x36, 0x0a, 0x03, 0x75, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x52, 0x03, 0x75, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06,
+ 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
+ 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53,
+ 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xe2, 0x01,
+ 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61,
+ 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63,
+ 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x73, 0x5f, 0x67,
+ 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64,
+ 0x46, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05,
+ 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65,
+ 0x6e, 0x64, 0x22, 0x36, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12,
+ 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69,
+ 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x03, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x06, 0x53,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x79, 0x70,
+ 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x38, 0x0a,
+ 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+ 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f,
+ 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4b, 0x49, 0x50, 0x10, 0x02, 0x12, 0x08, 0x0a,
+ 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x03, 0x22, 0x42, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
+ 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b,
+ 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0a, 0x45,
+ 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a,
+ 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc8,
+ 0x09, 0x0a, 0x03, 0x55, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65,
+ 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52,
+ 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
+ 0x52, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63,
+ 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x63, 0x61,
+ 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70,
+ 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f,
+ 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c,
+ 0x6c, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75,
+ 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f,
+ 0x72, 0x12, 0x4b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75,
+ 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69,
+ 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43,
+ 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f,
+ 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61,
+ 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, 0x75, 0x6e, 0x63,
+ 0x41, 0x72, 0x67, 0x12, 0x55, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65,
+ 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79,
+ 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x79, 0x70,
+ 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x0f, 0x74, 0x79,
+ 0x70, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69,
+ 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69,
+ 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e,
+ 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64,
+ 0x69, 0x6e, 0x67, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x60,
+ 0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64,
+ 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e,
+ 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x13, 0x69, 0x6e, 0x74,
+ 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
+ 0x12, 0x4f, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x61, 0x6c, 0x6c,
+ 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74,
+ 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c,
+ 0x6c, 0x12, 0x4f, 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x70,
+ 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f,
+ 0x77, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f,
+ 0x70, 0x79, 0x22, 0xf4, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54,
+ 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+ 0x00, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x46, 0x49, 0x45, 0x4c,
+ 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45,
+ 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x43,
+ 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a,
+ 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05,
+ 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x49, 0x54,
+ 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x4d, 0x42, 0x45, 0x44, 0x44, 0x49,
+ 0x4e, 0x47, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c,
+ 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x08, 0x12,
+ 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x4c, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10,
+ 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x50,
+ 0x59, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x44, 0x45, 0x50,
+ 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x0b, 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x52, 0x65,
+ 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x72, 0x61,
+ 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f,
+ 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d,
+ 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x02, 0x66, 0x6e, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75,
+ 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x02, 0x66,
+ 0x6e, 0x12, 0x3e, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65,
+ 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66,
+ 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66,
+ 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x65, 0x78,
+ 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73,
+ 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b,
+ 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61,
+ 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e,
+ 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78,
+ 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20,
+ 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x72, 0x0a,
+ 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74,
+ 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, 0x6c,
+ 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x44, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43,
+ 0x61, 0x6c, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x31,
+ 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
+ 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46,
+ 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10,
+ 0x02, 0x22, 0xa8, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f,
+ 0x72, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+ 0x31, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74,
+ 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79,
+ 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65,
+ 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+ 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f,
+ 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4e, 0x4f, 0x4e,
+ 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x02, 0x12,
+ 0x0b, 0x0a, 0x07, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x03, 0x22, 0xea, 0x02, 0x0a,
+ 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x64,
+ 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d,
+ 0x65, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66,
+ 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+ 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72,
+ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f,
+ 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+ 0x74, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53,
+ 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41,
+ 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a,
+ 0x0c, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12,
+ 0x0e, 0x0a, 0x0a, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12,
+ 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1d, 0x0a,
+ 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52,
+ 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09,
+ 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x46,
+ 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x07, 0x22, 0x6f, 0x0a, 0x07, 0x46, 0x75, 0x6e,
+ 0x63, 0x41, 0x72, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+ 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63,
+ 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09,
+ 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x51, 0x0a, 0x0d, 0x54, 0x79,
+ 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x73,
+ 0x72, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e,
+ 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x73, 0x72, 0x63, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a,
+ 0x0e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+ 0x40, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1f,
+ 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22,
+ 0x1c, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xcd, 0x01,
+ 0x0a, 0x0b, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x45, 0x0a,
+ 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68,
+ 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
+ 0x74, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+ 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x10, 0x01, 0x12, 0x11,
+ 0x0a, 0x0d, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10,
+ 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x03, 0x12,
+ 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54,
+ 0x45, 0x52, 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x0d,
+ 0x0a, 0x09, 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x05, 0x2a, 0x57, 0x0a,
+ 0x0c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a,
+ 0x19, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55,
+ 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,
+ 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x45, 0x45, 0x4e, 0x10,
+ 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x59, 0x45, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x12, 0x07, 0x0a,
+ 0x03, 0x52, 0x45, 0x44, 0x10, 0x04, 0x42, 0x0a, 0x92, 0x03, 0x07, 0xd2, 0x3e, 0x02, 0x10, 0x02,
+ 0x08, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07,
+}
+
+var file_stats_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
+var file_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
+var file_stats_proto_goTypes = []any{
+ (RewriteLevel)(0), // 0: net.proto2.go.open2opaque.stats.RewriteLevel
+ (Status_Type)(0), // 1: net.proto2.go.open2opaque.stats.Status.Type
+ (Use_Type)(0), // 2: net.proto2.go.open2opaque.stats.Use.Type
+ (MethodCall_Type)(0), // 3: net.proto2.go.open2opaque.stats.MethodCall.Type
+ (Constructor_Type)(0), // 4: net.proto2.go.open2opaque.stats.Constructor.Type
+ (Conversion_Context)(0), // 5: net.proto2.go.open2opaque.stats.Conversion.Context
+ (ShallowCopy_Type)(0), // 6: net.proto2.go.open2opaque.stats.ShallowCopy.Type
+ (*Entry)(nil), // 7: net.proto2.go.open2opaque.stats.Entry
+ (*Location)(nil), // 8: net.proto2.go.open2opaque.stats.Location
+ (*Position)(nil), // 9: net.proto2.go.open2opaque.stats.Position
+ (*Status)(nil), // 10: net.proto2.go.open2opaque.stats.Status
+ (*Type)(nil), // 11: net.proto2.go.open2opaque.stats.Type
+ (*Expression)(nil), // 12: net.proto2.go.open2opaque.stats.Expression
+ (*Use)(nil), // 13: net.proto2.go.open2opaque.stats.Use
+ (*ReflectCall)(nil), // 14: net.proto2.go.open2opaque.stats.ReflectCall
+ (*Frame)(nil), // 15: net.proto2.go.open2opaque.stats.Frame
+ (*FieldAccess)(nil), // 16: net.proto2.go.open2opaque.stats.FieldAccess
+ (*MethodCall)(nil), // 17: net.proto2.go.open2opaque.stats.MethodCall
+ (*Constructor)(nil), // 18: net.proto2.go.open2opaque.stats.Constructor
+ (*Conversion)(nil), // 19: net.proto2.go.open2opaque.stats.Conversion
+ (*FuncArg)(nil), // 20: net.proto2.go.open2opaque.stats.FuncArg
+ (*TypeAssertion)(nil), // 21: net.proto2.go.open2opaque.stats.TypeAssertion
+ (*TypeDefinition)(nil), // 22: net.proto2.go.open2opaque.stats.TypeDefinition
+ (*Embedding)(nil), // 23: net.proto2.go.open2opaque.stats.Embedding
+ (*Source)(nil), // 24: net.proto2.go.open2opaque.stats.Source
+ (*ShallowCopy)(nil), // 25: net.proto2.go.open2opaque.stats.ShallowCopy
+}
+var file_stats_proto_depIdxs = []int32{
+ 10, // 0: net.proto2.go.open2opaque.stats.Entry.status:type_name -> net.proto2.go.open2opaque.stats.Status
+ 8, // 1: net.proto2.go.open2opaque.stats.Entry.location:type_name -> net.proto2.go.open2opaque.stats.Location
+ 0, // 2: net.proto2.go.open2opaque.stats.Entry.level:type_name -> net.proto2.go.open2opaque.stats.RewriteLevel
+ 11, // 3: net.proto2.go.open2opaque.stats.Entry.type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 12, // 4: net.proto2.go.open2opaque.stats.Entry.expr:type_name -> net.proto2.go.open2opaque.stats.Expression
+ 13, // 5: net.proto2.go.open2opaque.stats.Entry.use:type_name -> net.proto2.go.open2opaque.stats.Use
+ 24, // 6: net.proto2.go.open2opaque.stats.Entry.source:type_name -> net.proto2.go.open2opaque.stats.Source
+ 9, // 7: net.proto2.go.open2opaque.stats.Location.start:type_name -> net.proto2.go.open2opaque.stats.Position
+ 9, // 8: net.proto2.go.open2opaque.stats.Location.end:type_name -> net.proto2.go.open2opaque.stats.Position
+ 1, // 9: net.proto2.go.open2opaque.stats.Status.type:type_name -> net.proto2.go.open2opaque.stats.Status.Type
+ 2, // 10: net.proto2.go.open2opaque.stats.Use.type:type_name -> net.proto2.go.open2opaque.stats.Use.Type
+ 16, // 11: net.proto2.go.open2opaque.stats.Use.direct_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess
+ 17, // 12: net.proto2.go.open2opaque.stats.Use.method_call:type_name -> net.proto2.go.open2opaque.stats.MethodCall
+ 18, // 13: net.proto2.go.open2opaque.stats.Use.constructor:type_name -> net.proto2.go.open2opaque.stats.Constructor
+ 19, // 14: net.proto2.go.open2opaque.stats.Use.conversion:type_name -> net.proto2.go.open2opaque.stats.Conversion
+ 20, // 15: net.proto2.go.open2opaque.stats.Use.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg
+ 21, // 16: net.proto2.go.open2opaque.stats.Use.type_assertion:type_name -> net.proto2.go.open2opaque.stats.TypeAssertion
+ 22, // 17: net.proto2.go.open2opaque.stats.Use.type_definition:type_name -> net.proto2.go.open2opaque.stats.TypeDefinition
+ 23, // 18: net.proto2.go.open2opaque.stats.Use.embedding:type_name -> net.proto2.go.open2opaque.stats.Embedding
+ 16, // 19: net.proto2.go.open2opaque.stats.Use.internal_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess
+ 14, // 20: net.proto2.go.open2opaque.stats.Use.reflect_call:type_name -> net.proto2.go.open2opaque.stats.ReflectCall
+ 25, // 21: net.proto2.go.open2opaque.stats.Use.shallow_copy:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy
+ 15, // 22: net.proto2.go.open2opaque.stats.ReflectCall.frames:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 15, // 23: net.proto2.go.open2opaque.stats.ReflectCall.fn:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 15, // 24: net.proto2.go.open2opaque.stats.ReflectCall.caller:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 11, // 25: net.proto2.go.open2opaque.stats.FieldAccess.field_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 3, // 26: net.proto2.go.open2opaque.stats.MethodCall.type:type_name -> net.proto2.go.open2opaque.stats.MethodCall.Type
+ 4, // 27: net.proto2.go.open2opaque.stats.Constructor.type:type_name -> net.proto2.go.open2opaque.stats.Constructor.Type
+ 20, // 28: net.proto2.go.open2opaque.stats.Conversion.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg
+ 5, // 29: net.proto2.go.open2opaque.stats.Conversion.context:type_name -> net.proto2.go.open2opaque.stats.Conversion.Context
+ 11, // 30: net.proto2.go.open2opaque.stats.TypeAssertion.src_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 11, // 31: net.proto2.go.open2opaque.stats.TypeDefinition.new_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 6, // 32: net.proto2.go.open2opaque.stats.ShallowCopy.type:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy.Type
+ 33, // [33:33] is the sub-list for method output_type
+ 33, // [33:33] is the sub-list for method input_type
+ 33, // [33:33] is the sub-list for extension type_name
+ 33, // [33:33] is the sub-list for extension extendee
+ 0, // [0:33] is the sub-list for field type_name
+}
+
+func init() { file_stats_proto_init() }
+func file_stats_proto_init() {
+ if File_stats_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_stats_proto_rawDesc,
+ NumEnums: 7,
+ NumMessages: 19,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_stats_proto_goTypes,
+ DependencyIndexes: file_stats_proto_depIdxs,
+ EnumInfos: file_stats_proto_enumTypes,
+ MessageInfos: file_stats_proto_msgTypes,
+ }.Build()
+ File_stats_proto = out.File
+ file_stats_proto_rawDesc = nil
+ file_stats_proto_goTypes = nil
+ file_stats_proto_depIdxs = nil
+}
diff --git a/internal/dashboard/stats.proto b/internal/dashboard/stats.proto
new file mode 100644
index 0000000..393ac94
--- /dev/null
+++ b/internal/dashboard/stats.proto
@@ -0,0 +1,318 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.stats;
+
+import "google/protobuf/go_features.proto";
+
+option features.field_presence = IMPLICIT;
+
+option features.(pb.go).api_level = API_HYBRID;
+
+// Entry represents a single usage of a proto type. For example, a
+// single field access, method call, use as a type, etc. We collect
+// entries and analyze them offline to generate various statistics
+// (e.g. number of direct field accesses per package) about the
+// migration to opaque Go protocol buffer API.
+message Entry {
+ // If status is not set or empty then all other fields should be set
+ // and describe a single usage of a protocol buffer type in Go.
+ //
+ // If status is not empty then:
+ // - status.error says what went wrong, and
+ // - location.package says which package couldn't be processed
+ Status status = 1;
+
+ // Location in Go code. Always set. Location.package is always
+ // non-empty.
+ Location location = 2;
+
+ // Rewrite level after which this entry was captured.
+ RewriteLevel level = 3;
+
+ // Go type representing a protocol buffer type.
+ Type type = 4;
+
+ // Go expression which leads to this entry.
+ Expression expr = 5;
+
+ // Describes how a protocol buffer is used (e.g. direct field access).
+ Use use = 6;
+
+ // Source of the information. For debugging purposes.
+ Source source = 7;
+}
+
+// Location represents location of an expression in a Go file.
+message Location {
+ // Full name of a Go package
+ string package = 1;
+
+ // path to a Go file
+ string file = 2;
+
+ // Whether the file is a generated file.
+ bool is_generated_file = 3;
+
+ // Start of the expression.
+ Position start = 4;
+
+ // End of the expression.
+ Position end = 5;
+}
+
+// Position describes a position in a Go file.
+message Position {
+ int64 line = 1;
+ int64 column = 2;
+}
+
+// Status specifies an error that occurred. Empty error indicates
+// success.
+message Status {
+ enum Type {
+ UNSPECIFIED_TYPE = 0;
+ OK = 1;
+ SKIP = 2;
+ FAIL = 3;
+ }
+
+ Type type = 1;
+ string error = 2;
+}
+
+// RewriteLevel represents rewrite level after which entries are
+// collected. For example, GREEN means that the tool does a green
+// rewrite and analyzes the result to create entries.
+enum RewriteLevel {
+ REWRITE_LEVEL_UNSPECIFIED = 0;
+ NONE = 1;
+ GREEN = 2;
+ YELLOW = 3;
+ RED = 4;
+}
+
+// Type represents a Go name for a protocol buffer type.
+message Type {
+ // The short name of the Go type representing the protocol buffer
+ // type. For example: "qem_go_proto.QueryEventMessage".
+ string short_name = 1;
+
+ // A fully qualified name of the Go type representing the protocol
+ // buffer type.
+ string long_name = 2;
+}
+
+// Expression describes a Go expression.
+message Expression {
+ // go/ast expression type (e.g. "*ast.Ident").
+ string type = 1;
+
+ // go/ast expression type of the parent.
+ string parent_type = 2;
+}
+
+// Use describes a use of a protocol buffer type in Go.
+message Use {
+ enum Type {
+ TYPE_UNSPECIFIED = 0;
+ DIRECT_FIELD_ACCESS = 1;
+ METHOD_CALL = 2;
+ CONSTRUCTOR = 3;
+ CONVERSION = 4;
+ TYPE_ASSERTION = 5;
+ TYPE_DEFINITION = 6;
+ EMBEDDING = 7;
+ INTERNAL_FIELD_ACCESS = 8;
+ REFLECT_CALL = 9;
+ SHALLOW_COPY = 10;
+ BUILD_DEPENDENCY = 11;
+ }
+
+ Type type = 1;
+ FieldAccess direct_field_access = 2;
+ MethodCall method_call = 3;
+ Constructor constructor = 4;
+ Conversion conversion = 5;
+ FuncArg func_arg = 6;
+ TypeAssertion type_assertion = 7;
+ TypeDefinition type_definition = 8;
+ Embedding embedding = 9;
+ FieldAccess internal_field_access = 10;
+ ReflectCall reflect_call = 11;
+ ShallowCopy shallow_copy = 12;
+}
+
+// ReflectCall represents a call to the Go reflection API.
+message ReflectCall {
+ // Information about all frames leading to the reflection call.
+ //
+ // A callstack usually looks like:
+ //
+ // reflect.Value.Field // the function triggering the log
+ // reflect.F1 // more functions
+ // ... // in the
+ // reflect.Fn // reflect package
+ // import/path.Caller // (1) fn: calls into the reflect package
+ // import/path.C1 // more functions
+ // ... // in the same package
+ // import/path.Cn1 // as fn
+ // ancestor/import.Ancestor // (2) caller: calls into fn's package
+ // ... // more frames
+ //
+ // The frames field has information about all frames but we also
+ // store a few selected frames separately to make it easier to write
+ // queries:
+ // (1) caller is the last function before the reflection package
+ // (2) ancestor is the last function from a different package than caller
+ //
+ // The frames field contains at least one frame (a function in the
+ // reflect package) but fn and caller may be unset.
+ repeated Frame frames = 1;
+ Frame fn = 2;
+ Frame caller = 3;
+}
+
+// Frame represents information about a single frame in a Go
+// stacktrace.
+message Frame {
+ // Fully qualified function name. For example:
+ // net/http.Error
+ string function = 1;
+
+ // true if the function is exported.
+ bool is_exported = 6;
+
+ // Packed in which the function is defined.
+ string package = 2;
+
+ // path to the source file defining the function.
+ //
+ // For standard-library, the path is relative to the src/
+ // directory (e.g. net/http/server.go).
+ //
+ string file = 3;
+
+ // Line number, together with file, is the location where function
+ // is defined.
+ string line = 4;
+
+ // index of the frame in the reflect_call.frames repeated field.
+ //
+ // This exists to make SQL queries easier.
+ int64 index = 5;
+
+ // index of the frame across consecutive frames in the same package.
+ //
+ // This exists to make SQL queries easier.
+ int64 pkg_index = 7;
+}
+
+message FieldAccess {
+ string field_name = 1;
+ Type field_type = 2;
+}
+
+message MethodCall {
+ string method = 1;
+
+ enum Type {
+ INVALID = 0;
+ GET_ONEOF = 1;
+ GET_BUILD = 2;
+ }
+
+ Type type = 2;
+}
+
+message Constructor {
+ enum Type {
+ TYPE_UNSPECIFIED = 0;
+ EMPTY_LITERAL = 1;
+ NONEMPTY_LITERAL = 2;
+ BUILDER = 3;
+ }
+
+ Type type = 1;
+}
+
+message Conversion {
+ // The type of the conversion. For example:
+ // interface{}
+ // proto.Message
+ // unsafe.Pointer
+ string dest_type_name = 1;
+
+ // Describes the called function. It is set if context==CALL_ARGUMENT.
+ FuncArg func_arg = 3;
+
+ enum Context {
+ CONTEXT_UNSPECIFIED = 0;
+ CALL_ARGUMENT = 1;
+ RETURN_VALUE = 2;
+ ASSIGNMENT = 3;
+ EXPLICIT = 4;
+ COMPOSITE_LITERAL_ELEMENT = 5;
+ CHAN_SEND = 6;
+ FUNC_RET = 7;
+ }
+
+ Context context = 2;
+}
+
+message FuncArg {
+ // The name of the called function.
+ // For example: "Clone".
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ string function_name = 1;
+
+ // Full package path containing the called function.
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ string package_path = 2;
+
+ // Signature of the called function.
+ // For example: "func(m interface{}) interface{}".
+ string signature = 3;
+}
+
+message TypeAssertion {
+ // The type of the expression whose type is being asserted.
+ Type src_type = 1;
+}
+
+message TypeDefinition {
+ // new_type describes the newly defined type.
+ Type new_type = 1;
+}
+
+message Embedding {
+ int64 field_index = 1;
+}
+
+message Source {
+ string file = 1;
+}
+
+// ShallowCopy represents a shallow copy of protocol buffers.
+//
+// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages
+// (pointers to Go structs generated by the proto generor).
+message ShallowCopy {
+ enum Type {
+ TYPE_UNSPECIFIED = 0;
+ ASSIGN = 1;
+ CALL_ARGUMENT = 2;
+ FUNC_RET = 3;
+ COMPOSITE_LITERAL_ELEMENT = 4;
+ CHAN_SEND = 5;
+ }
+
+ Type type = 1;
+}
diff --git a/internal/dashboard/stats_protoopaque.pb.go b/internal/dashboard/stats_protoopaque.pb.go
new file mode 100644
index 0000000..ab0fa59
--- /dev/null
+++ b/internal/dashboard/stats_protoopaque.pb.go
@@ -0,0 +1,2776 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: stats.proto
+
+//go:build protoopaque
+
+package dashboard
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ _ "google.golang.org/protobuf/types/gofeaturespb"
+ reflect "reflect"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// RewriteLevel represents rewrite level after which entries are
+// collected. For example, GREEN means that the tool does a green
+// rewrite and analyzes the result to create entries.
+type RewriteLevel int32
+
+const (
+ RewriteLevel_REWRITE_LEVEL_UNSPECIFIED RewriteLevel = 0
+ RewriteLevel_NONE RewriteLevel = 1
+ RewriteLevel_GREEN RewriteLevel = 2
+ RewriteLevel_YELLOW RewriteLevel = 3
+ RewriteLevel_RED RewriteLevel = 4
+)
+
+// Enum value maps for RewriteLevel.
+var (
+ RewriteLevel_name = map[int32]string{
+ 0: "REWRITE_LEVEL_UNSPECIFIED",
+ 1: "NONE",
+ 2: "GREEN",
+ 3: "YELLOW",
+ 4: "RED",
+ }
+ RewriteLevel_value = map[string]int32{
+ "REWRITE_LEVEL_UNSPECIFIED": 0,
+ "NONE": 1,
+ "GREEN": 2,
+ "YELLOW": 3,
+ "RED": 4,
+ }
+)
+
+func (x RewriteLevel) Enum() *RewriteLevel {
+ p := new(RewriteLevel)
+ *p = x
+ return p
+}
+
+func (x RewriteLevel) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (RewriteLevel) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[0].Descriptor()
+}
+
+func (RewriteLevel) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[0]
+}
+
+func (x RewriteLevel) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Status_Type int32
+
+const (
+ Status_UNSPECIFIED_TYPE Status_Type = 0
+ Status_OK Status_Type = 1
+ Status_SKIP Status_Type = 2
+ Status_FAIL Status_Type = 3
+)
+
+// Enum value maps for Status_Type.
+var (
+ Status_Type_name = map[int32]string{
+ 0: "UNSPECIFIED_TYPE",
+ 1: "OK",
+ 2: "SKIP",
+ 3: "FAIL",
+ }
+ Status_Type_value = map[string]int32{
+ "UNSPECIFIED_TYPE": 0,
+ "OK": 1,
+ "SKIP": 2,
+ "FAIL": 3,
+ }
+)
+
+func (x Status_Type) Enum() *Status_Type {
+ p := new(Status_Type)
+ *p = x
+ return p
+}
+
+func (x Status_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Status_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[1].Descriptor()
+}
+
+func (Status_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[1]
+}
+
+func (x Status_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Use_Type int32
+
+const (
+ Use_TYPE_UNSPECIFIED Use_Type = 0
+ Use_DIRECT_FIELD_ACCESS Use_Type = 1
+ Use_METHOD_CALL Use_Type = 2
+ Use_CONSTRUCTOR Use_Type = 3
+ Use_CONVERSION Use_Type = 4
+ Use_TYPE_ASSERTION Use_Type = 5
+ Use_TYPE_DEFINITION Use_Type = 6
+ Use_EMBEDDING Use_Type = 7
+ Use_INTERNAL_FIELD_ACCESS Use_Type = 8
+ Use_REFLECT_CALL Use_Type = 9
+ Use_SHALLOW_COPY Use_Type = 10
+ Use_BUILD_DEPENDENCY Use_Type = 11
+)
+
+// Enum value maps for Use_Type.
+var (
+ Use_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "DIRECT_FIELD_ACCESS",
+ 2: "METHOD_CALL",
+ 3: "CONSTRUCTOR",
+ 4: "CONVERSION",
+ 5: "TYPE_ASSERTION",
+ 6: "TYPE_DEFINITION",
+ 7: "EMBEDDING",
+ 8: "INTERNAL_FIELD_ACCESS",
+ 9: "REFLECT_CALL",
+ 10: "SHALLOW_COPY",
+ 11: "BUILD_DEPENDENCY",
+ }
+ Use_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "DIRECT_FIELD_ACCESS": 1,
+ "METHOD_CALL": 2,
+ "CONSTRUCTOR": 3,
+ "CONVERSION": 4,
+ "TYPE_ASSERTION": 5,
+ "TYPE_DEFINITION": 6,
+ "EMBEDDING": 7,
+ "INTERNAL_FIELD_ACCESS": 8,
+ "REFLECT_CALL": 9,
+ "SHALLOW_COPY": 10,
+ "BUILD_DEPENDENCY": 11,
+ }
+)
+
+func (x Use_Type) Enum() *Use_Type {
+ p := new(Use_Type)
+ *p = x
+ return p
+}
+
+func (x Use_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Use_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[2].Descriptor()
+}
+
+func (Use_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[2]
+}
+
+func (x Use_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type MethodCall_Type int32
+
+const (
+ MethodCall_INVALID MethodCall_Type = 0
+ MethodCall_GET_ONEOF MethodCall_Type = 1
+ MethodCall_GET_BUILD MethodCall_Type = 2
+)
+
+// Enum value maps for MethodCall_Type.
+var (
+ MethodCall_Type_name = map[int32]string{
+ 0: "INVALID",
+ 1: "GET_ONEOF",
+ 2: "GET_BUILD",
+ }
+ MethodCall_Type_value = map[string]int32{
+ "INVALID": 0,
+ "GET_ONEOF": 1,
+ "GET_BUILD": 2,
+ }
+)
+
+func (x MethodCall_Type) Enum() *MethodCall_Type {
+ p := new(MethodCall_Type)
+ *p = x
+ return p
+}
+
+func (x MethodCall_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (MethodCall_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[3].Descriptor()
+}
+
+func (MethodCall_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[3]
+}
+
+func (x MethodCall_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Constructor_Type int32
+
+const (
+ Constructor_TYPE_UNSPECIFIED Constructor_Type = 0
+ Constructor_EMPTY_LITERAL Constructor_Type = 1
+ Constructor_NONEMPTY_LITERAL Constructor_Type = 2
+ Constructor_BUILDER Constructor_Type = 3
+)
+
+// Enum value maps for Constructor_Type.
+var (
+ Constructor_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "EMPTY_LITERAL",
+ 2: "NONEMPTY_LITERAL",
+ 3: "BUILDER",
+ }
+ Constructor_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "EMPTY_LITERAL": 1,
+ "NONEMPTY_LITERAL": 2,
+ "BUILDER": 3,
+ }
+)
+
+func (x Constructor_Type) Enum() *Constructor_Type {
+ p := new(Constructor_Type)
+ *p = x
+ return p
+}
+
+func (x Constructor_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Constructor_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[4].Descriptor()
+}
+
+func (Constructor_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[4]
+}
+
+func (x Constructor_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type Conversion_Context int32
+
+const (
+ Conversion_CONTEXT_UNSPECIFIED Conversion_Context = 0
+ Conversion_CALL_ARGUMENT Conversion_Context = 1
+ Conversion_RETURN_VALUE Conversion_Context = 2
+ Conversion_ASSIGNMENT Conversion_Context = 3
+ Conversion_EXPLICIT Conversion_Context = 4
+ Conversion_COMPOSITE_LITERAL_ELEMENT Conversion_Context = 5
+ Conversion_CHAN_SEND Conversion_Context = 6
+ Conversion_FUNC_RET Conversion_Context = 7
+)
+
+// Enum value maps for Conversion_Context.
+var (
+ Conversion_Context_name = map[int32]string{
+ 0: "CONTEXT_UNSPECIFIED",
+ 1: "CALL_ARGUMENT",
+ 2: "RETURN_VALUE",
+ 3: "ASSIGNMENT",
+ 4: "EXPLICIT",
+ 5: "COMPOSITE_LITERAL_ELEMENT",
+ 6: "CHAN_SEND",
+ 7: "FUNC_RET",
+ }
+ Conversion_Context_value = map[string]int32{
+ "CONTEXT_UNSPECIFIED": 0,
+ "CALL_ARGUMENT": 1,
+ "RETURN_VALUE": 2,
+ "ASSIGNMENT": 3,
+ "EXPLICIT": 4,
+ "COMPOSITE_LITERAL_ELEMENT": 5,
+ "CHAN_SEND": 6,
+ "FUNC_RET": 7,
+ }
+)
+
+func (x Conversion_Context) Enum() *Conversion_Context {
+ p := new(Conversion_Context)
+ *p = x
+ return p
+}
+
+func (x Conversion_Context) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Conversion_Context) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[5].Descriptor()
+}
+
+func (Conversion_Context) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[5]
+}
+
+func (x Conversion_Context) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type ShallowCopy_Type int32
+
+const (
+ ShallowCopy_TYPE_UNSPECIFIED ShallowCopy_Type = 0
+ ShallowCopy_ASSIGN ShallowCopy_Type = 1
+ ShallowCopy_CALL_ARGUMENT ShallowCopy_Type = 2
+ ShallowCopy_FUNC_RET ShallowCopy_Type = 3
+ ShallowCopy_COMPOSITE_LITERAL_ELEMENT ShallowCopy_Type = 4
+ ShallowCopy_CHAN_SEND ShallowCopy_Type = 5
+)
+
+// Enum value maps for ShallowCopy_Type.
+var (
+ ShallowCopy_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "ASSIGN",
+ 2: "CALL_ARGUMENT",
+ 3: "FUNC_RET",
+ 4: "COMPOSITE_LITERAL_ELEMENT",
+ 5: "CHAN_SEND",
+ }
+ ShallowCopy_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "ASSIGN": 1,
+ "CALL_ARGUMENT": 2,
+ "FUNC_RET": 3,
+ "COMPOSITE_LITERAL_ELEMENT": 4,
+ "CHAN_SEND": 5,
+ }
+)
+
+func (x ShallowCopy_Type) Enum() *ShallowCopy_Type {
+ p := new(ShallowCopy_Type)
+ *p = x
+ return p
+}
+
+func (x ShallowCopy_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ShallowCopy_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_stats_proto_enumTypes[6].Descriptor()
+}
+
+func (ShallowCopy_Type) Type() protoreflect.EnumType {
+ return &file_stats_proto_enumTypes[6]
+}
+
+func (x ShallowCopy_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Entry represents a single usage of a proto type. For example, a
+// single field access, method call, use as a type, etc. We collect
+// entries and analyze them offline to generate various statistics
+// (e.g. number of direct field accesses per package) about the
+// migration to opaque Go protocol buffer API.
+type Entry struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
+ xxx_hidden_Location *Location `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"`
+ xxx_hidden_Level RewriteLevel `protobuf:"varint,3,opt,name=level,enum=net.proto2.go.open2opaque.stats.RewriteLevel" json:"level,omitempty"`
+ xxx_hidden_Type *Type `protobuf:"bytes,4,opt,name=type" json:"type,omitempty"`
+ xxx_hidden_Expr *Expression `protobuf:"bytes,5,opt,name=expr" json:"expr,omitempty"`
+ xxx_hidden_Use *Use `protobuf:"bytes,6,opt,name=use" json:"use,omitempty"`
+ xxx_hidden_Source *Source `protobuf:"bytes,7,opt,name=source" json:"source,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Entry) Reset() {
+ *x = Entry{}
+ mi := &file_stats_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Entry) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Entry) ProtoMessage() {}
+
+func (x *Entry) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Entry) GetStatus() *Status {
+ if x != nil {
+ return x.xxx_hidden_Status
+ }
+ return nil
+}
+
+func (x *Entry) GetLocation() *Location {
+ if x != nil {
+ return x.xxx_hidden_Location
+ }
+ return nil
+}
+
+func (x *Entry) GetLevel() RewriteLevel {
+ if x != nil {
+ return x.xxx_hidden_Level
+ }
+ return RewriteLevel_REWRITE_LEVEL_UNSPECIFIED
+}
+
+func (x *Entry) GetType() *Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return nil
+}
+
+func (x *Entry) GetExpr() *Expression {
+ if x != nil {
+ return x.xxx_hidden_Expr
+ }
+ return nil
+}
+
+func (x *Entry) GetUse() *Use {
+ if x != nil {
+ return x.xxx_hidden_Use
+ }
+ return nil
+}
+
+func (x *Entry) GetSource() *Source {
+ if x != nil {
+ return x.xxx_hidden_Source
+ }
+ return nil
+}
+
+func (x *Entry) SetStatus(v *Status) {
+ x.xxx_hidden_Status = v
+}
+
+func (x *Entry) SetLocation(v *Location) {
+ x.xxx_hidden_Location = v
+}
+
+func (x *Entry) SetLevel(v RewriteLevel) {
+ x.xxx_hidden_Level = v
+}
+
+func (x *Entry) SetType(v *Type) {
+ x.xxx_hidden_Type = v
+}
+
+func (x *Entry) SetExpr(v *Expression) {
+ x.xxx_hidden_Expr = v
+}
+
+func (x *Entry) SetUse(v *Use) {
+ x.xxx_hidden_Use = v
+}
+
+func (x *Entry) SetSource(v *Source) {
+ x.xxx_hidden_Source = v
+}
+
+func (x *Entry) HasStatus() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Status != nil
+}
+
+func (x *Entry) HasLocation() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Location != nil
+}
+
+func (x *Entry) HasType() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Type != nil
+}
+
+func (x *Entry) HasExpr() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Expr != nil
+}
+
+func (x *Entry) HasUse() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Use != nil
+}
+
+func (x *Entry) HasSource() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Source != nil
+}
+
+func (x *Entry) ClearStatus() {
+ x.xxx_hidden_Status = nil
+}
+
+func (x *Entry) ClearLocation() {
+ x.xxx_hidden_Location = nil
+}
+
+func (x *Entry) ClearType() {
+ x.xxx_hidden_Type = nil
+}
+
+func (x *Entry) ClearExpr() {
+ x.xxx_hidden_Expr = nil
+}
+
+func (x *Entry) ClearUse() {
+ x.xxx_hidden_Use = nil
+}
+
+func (x *Entry) ClearSource() {
+ x.xxx_hidden_Source = nil
+}
+
+type Entry_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // If status is not set or empty then all other fields should be set
+ // and describe a single usage of a protocol buffer type in Go.
+ //
+ // If status is not empty then:
+ // - status.error says what went wrong, and
+ // - location.package says which package couldn't be processed
+ Status *Status
+ // Location in Go code. Always set. Location.package is always
+ // non-empty.
+ Location *Location
+ // Rewrite level after which this entry was captured.
+ Level RewriteLevel
+ // Go type representing a protocol buffer type.
+ Type *Type
+ // Go expression which leads to this entry.
+ Expr *Expression
+ // Describes how a protocol buffer is used (e.g. direct field access).
+ Use *Use
+ // Source of the information. For debugging purposes.
+ Source *Source
+}
+
+func (b0 Entry_builder) Build() *Entry {
+ m0 := &Entry{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Status = b.Status
+ x.xxx_hidden_Location = b.Location
+ x.xxx_hidden_Level = b.Level
+ x.xxx_hidden_Type = b.Type
+ x.xxx_hidden_Expr = b.Expr
+ x.xxx_hidden_Use = b.Use
+ x.xxx_hidden_Source = b.Source
+ return m0
+}
+
+// Location represents location of an expression in a Go file.
+type Location struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Package string `protobuf:"bytes,1,opt,name=package" json:"package,omitempty"`
+ xxx_hidden_File string `protobuf:"bytes,2,opt,name=file" json:"file,omitempty"`
+ xxx_hidden_IsGeneratedFile bool `protobuf:"varint,3,opt,name=is_generated_file,json=isGeneratedFile" json:"is_generated_file,omitempty"`
+ xxx_hidden_Start *Position `protobuf:"bytes,4,opt,name=start" json:"start,omitempty"`
+ xxx_hidden_End *Position `protobuf:"bytes,5,opt,name=end" json:"end,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Location) Reset() {
+ *x = Location{}
+ mi := &file_stats_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Location) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Location) ProtoMessage() {}
+
+func (x *Location) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Location) GetPackage() string {
+ if x != nil {
+ return x.xxx_hidden_Package
+ }
+ return ""
+}
+
+func (x *Location) GetFile() string {
+ if x != nil {
+ return x.xxx_hidden_File
+ }
+ return ""
+}
+
+func (x *Location) GetIsGeneratedFile() bool {
+ if x != nil {
+ return x.xxx_hidden_IsGeneratedFile
+ }
+ return false
+}
+
+func (x *Location) GetStart() *Position {
+ if x != nil {
+ return x.xxx_hidden_Start
+ }
+ return nil
+}
+
+func (x *Location) GetEnd() *Position {
+ if x != nil {
+ return x.xxx_hidden_End
+ }
+ return nil
+}
+
+func (x *Location) SetPackage(v string) {
+ x.xxx_hidden_Package = v
+}
+
+func (x *Location) SetFile(v string) {
+ x.xxx_hidden_File = v
+}
+
+func (x *Location) SetIsGeneratedFile(v bool) {
+ x.xxx_hidden_IsGeneratedFile = v
+}
+
+func (x *Location) SetStart(v *Position) {
+ x.xxx_hidden_Start = v
+}
+
+func (x *Location) SetEnd(v *Position) {
+ x.xxx_hidden_End = v
+}
+
+func (x *Location) HasStart() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Start != nil
+}
+
+func (x *Location) HasEnd() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_End != nil
+}
+
+func (x *Location) ClearStart() {
+ x.xxx_hidden_Start = nil
+}
+
+func (x *Location) ClearEnd() {
+ x.xxx_hidden_End = nil
+}
+
+type Location_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Full name of a Go package
+ Package string
+ // path to a Go file
+ File string
+ // Whether the file is a generated file.
+ IsGeneratedFile bool
+ // Start of the expression.
+ Start *Position
+ // End of the expression.
+ End *Position
+}
+
+func (b0 Location_builder) Build() *Location {
+ m0 := &Location{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Package = b.Package
+ x.xxx_hidden_File = b.File
+ x.xxx_hidden_IsGeneratedFile = b.IsGeneratedFile
+ x.xxx_hidden_Start = b.Start
+ x.xxx_hidden_End = b.End
+ return m0
+}
+
+// Position describes a position in a Go file.
+type Position struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Line int64 `protobuf:"varint,1,opt,name=line" json:"line,omitempty"`
+ xxx_hidden_Column int64 `protobuf:"varint,2,opt,name=column" json:"column,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Position) Reset() {
+ *x = Position{}
+ mi := &file_stats_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Position) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Position) ProtoMessage() {}
+
+func (x *Position) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Position) GetLine() int64 {
+ if x != nil {
+ return x.xxx_hidden_Line
+ }
+ return 0
+}
+
+func (x *Position) GetColumn() int64 {
+ if x != nil {
+ return x.xxx_hidden_Column
+ }
+ return 0
+}
+
+func (x *Position) SetLine(v int64) {
+ x.xxx_hidden_Line = v
+}
+
+func (x *Position) SetColumn(v int64) {
+ x.xxx_hidden_Column = v
+}
+
+type Position_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Line int64
+ Column int64
+}
+
+func (b0 Position_builder) Build() *Position {
+ m0 := &Position{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Line = b.Line
+ x.xxx_hidden_Column = b.Column
+ return m0
+}
+
+// Status specifies an error that occurred. Empty error indicates
+// success.
+type Status struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Type Status_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Status_Type" json:"type,omitempty"`
+ xxx_hidden_Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Status) Reset() {
+ *x = Status{}
+ mi := &file_stats_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Status) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status) ProtoMessage() {}
+
+func (x *Status) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Status) GetType() Status_Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return Status_UNSPECIFIED_TYPE
+}
+
+func (x *Status) GetError() string {
+ if x != nil {
+ return x.xxx_hidden_Error
+ }
+ return ""
+}
+
+func (x *Status) SetType(v Status_Type) {
+ x.xxx_hidden_Type = v
+}
+
+func (x *Status) SetError(v string) {
+ x.xxx_hidden_Error = v
+}
+
+type Status_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Status_Type
+ Error string
+}
+
+func (b0 Status_builder) Build() *Status {
+ m0 := &Status{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Type = b.Type
+ x.xxx_hidden_Error = b.Error
+ return m0
+}
+
+// Type represents a Go name for a protocol buffer type.
+type Type struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_ShortName string `protobuf:"bytes,1,opt,name=short_name,json=shortName" json:"short_name,omitempty"`
+ xxx_hidden_LongName string `protobuf:"bytes,2,opt,name=long_name,json=longName" json:"long_name,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Type) Reset() {
+ *x = Type{}
+ mi := &file_stats_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Type) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Type) ProtoMessage() {}
+
+func (x *Type) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Type) GetShortName() string {
+ if x != nil {
+ return x.xxx_hidden_ShortName
+ }
+ return ""
+}
+
+func (x *Type) GetLongName() string {
+ if x != nil {
+ return x.xxx_hidden_LongName
+ }
+ return ""
+}
+
+func (x *Type) SetShortName(v string) {
+ x.xxx_hidden_ShortName = v
+}
+
+func (x *Type) SetLongName(v string) {
+ x.xxx_hidden_LongName = v
+}
+
+type Type_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The short name of the Go type representing the protocol buffer
+ // type. For example: "qem_go_proto.QueryEventMessage".
+ ShortName string
+ // A fully qualified name of the Go type representing the protocol
+ // buffer type.
+ LongName string
+}
+
+func (b0 Type_builder) Build() *Type {
+ m0 := &Type{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_ShortName = b.ShortName
+ x.xxx_hidden_LongName = b.LongName
+ return m0
+}
+
+// Expression describes a Go expression.
+type Expression struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"`
+ xxx_hidden_ParentType string `protobuf:"bytes,2,opt,name=parent_type,json=parentType" json:"parent_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Expression) Reset() {
+ *x = Expression{}
+ mi := &file_stats_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Expression) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Expression) ProtoMessage() {}
+
+func (x *Expression) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Expression) GetType() string {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return ""
+}
+
+func (x *Expression) GetParentType() string {
+ if x != nil {
+ return x.xxx_hidden_ParentType
+ }
+ return ""
+}
+
+func (x *Expression) SetType(v string) {
+ x.xxx_hidden_Type = v
+}
+
+func (x *Expression) SetParentType(v string) {
+ x.xxx_hidden_ParentType = v
+}
+
+type Expression_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // go/ast expression type (e.g. "*ast.Ident").
+ Type string
+ // go/ast expression type of the parent.
+ ParentType string
+}
+
+func (b0 Expression_builder) Build() *Expression {
+ m0 := &Expression{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Type = b.Type
+ x.xxx_hidden_ParentType = b.ParentType
+ return m0
+}
+
+// Use describes a use of a protocol buffer type in Go.
+type Use struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Type Use_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Use_Type" json:"type,omitempty"`
+ xxx_hidden_DirectFieldAccess *FieldAccess `protobuf:"bytes,2,opt,name=direct_field_access,json=directFieldAccess" json:"direct_field_access,omitempty"`
+ xxx_hidden_MethodCall *MethodCall `protobuf:"bytes,3,opt,name=method_call,json=methodCall" json:"method_call,omitempty"`
+ xxx_hidden_Constructor *Constructor `protobuf:"bytes,4,opt,name=constructor" json:"constructor,omitempty"`
+ xxx_hidden_Conversion *Conversion `protobuf:"bytes,5,opt,name=conversion" json:"conversion,omitempty"`
+ xxx_hidden_FuncArg *FuncArg `protobuf:"bytes,6,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"`
+ xxx_hidden_TypeAssertion *TypeAssertion `protobuf:"bytes,7,opt,name=type_assertion,json=typeAssertion" json:"type_assertion,omitempty"`
+ xxx_hidden_TypeDefinition *TypeDefinition `protobuf:"bytes,8,opt,name=type_definition,json=typeDefinition" json:"type_definition,omitempty"`
+ xxx_hidden_Embedding *Embedding `protobuf:"bytes,9,opt,name=embedding" json:"embedding,omitempty"`
+ xxx_hidden_InternalFieldAccess *FieldAccess `protobuf:"bytes,10,opt,name=internal_field_access,json=internalFieldAccess" json:"internal_field_access,omitempty"`
+ xxx_hidden_ReflectCall *ReflectCall `protobuf:"bytes,11,opt,name=reflect_call,json=reflectCall" json:"reflect_call,omitempty"`
+ xxx_hidden_ShallowCopy *ShallowCopy `protobuf:"bytes,12,opt,name=shallow_copy,json=shallowCopy" json:"shallow_copy,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Use) Reset() {
+ *x = Use{}
+ mi := &file_stats_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Use) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Use) ProtoMessage() {}
+
+func (x *Use) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Use) GetType() Use_Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return Use_TYPE_UNSPECIFIED
+}
+
+func (x *Use) GetDirectFieldAccess() *FieldAccess {
+ if x != nil {
+ return x.xxx_hidden_DirectFieldAccess
+ }
+ return nil
+}
+
+func (x *Use) GetMethodCall() *MethodCall {
+ if x != nil {
+ return x.xxx_hidden_MethodCall
+ }
+ return nil
+}
+
+func (x *Use) GetConstructor() *Constructor {
+ if x != nil {
+ return x.xxx_hidden_Constructor
+ }
+ return nil
+}
+
+func (x *Use) GetConversion() *Conversion {
+ if x != nil {
+ return x.xxx_hidden_Conversion
+ }
+ return nil
+}
+
+func (x *Use) GetFuncArg() *FuncArg {
+ if x != nil {
+ return x.xxx_hidden_FuncArg
+ }
+ return nil
+}
+
+func (x *Use) GetTypeAssertion() *TypeAssertion {
+ if x != nil {
+ return x.xxx_hidden_TypeAssertion
+ }
+ return nil
+}
+
+func (x *Use) GetTypeDefinition() *TypeDefinition {
+ if x != nil {
+ return x.xxx_hidden_TypeDefinition
+ }
+ return nil
+}
+
+func (x *Use) GetEmbedding() *Embedding {
+ if x != nil {
+ return x.xxx_hidden_Embedding
+ }
+ return nil
+}
+
+func (x *Use) GetInternalFieldAccess() *FieldAccess {
+ if x != nil {
+ return x.xxx_hidden_InternalFieldAccess
+ }
+ return nil
+}
+
+func (x *Use) GetReflectCall() *ReflectCall {
+ if x != nil {
+ return x.xxx_hidden_ReflectCall
+ }
+ return nil
+}
+
+func (x *Use) GetShallowCopy() *ShallowCopy {
+ if x != nil {
+ return x.xxx_hidden_ShallowCopy
+ }
+ return nil
+}
+
+func (x *Use) SetType(v Use_Type) {
+ x.xxx_hidden_Type = v
+}
+
+func (x *Use) SetDirectFieldAccess(v *FieldAccess) {
+ x.xxx_hidden_DirectFieldAccess = v
+}
+
+func (x *Use) SetMethodCall(v *MethodCall) {
+ x.xxx_hidden_MethodCall = v
+}
+
+func (x *Use) SetConstructor(v *Constructor) {
+ x.xxx_hidden_Constructor = v
+}
+
+func (x *Use) SetConversion(v *Conversion) {
+ x.xxx_hidden_Conversion = v
+}
+
+func (x *Use) SetFuncArg(v *FuncArg) {
+ x.xxx_hidden_FuncArg = v
+}
+
+func (x *Use) SetTypeAssertion(v *TypeAssertion) {
+ x.xxx_hidden_TypeAssertion = v
+}
+
+func (x *Use) SetTypeDefinition(v *TypeDefinition) {
+ x.xxx_hidden_TypeDefinition = v
+}
+
+func (x *Use) SetEmbedding(v *Embedding) {
+ x.xxx_hidden_Embedding = v
+}
+
+func (x *Use) SetInternalFieldAccess(v *FieldAccess) {
+ x.xxx_hidden_InternalFieldAccess = v
+}
+
+func (x *Use) SetReflectCall(v *ReflectCall) {
+ x.xxx_hidden_ReflectCall = v
+}
+
+func (x *Use) SetShallowCopy(v *ShallowCopy) {
+ x.xxx_hidden_ShallowCopy = v
+}
+
+func (x *Use) HasDirectFieldAccess() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_DirectFieldAccess != nil
+}
+
+func (x *Use) HasMethodCall() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_MethodCall != nil
+}
+
+func (x *Use) HasConstructor() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Constructor != nil
+}
+
+func (x *Use) HasConversion() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Conversion != nil
+}
+
+func (x *Use) HasFuncArg() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_FuncArg != nil
+}
+
+func (x *Use) HasTypeAssertion() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_TypeAssertion != nil
+}
+
+func (x *Use) HasTypeDefinition() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_TypeDefinition != nil
+}
+
+func (x *Use) HasEmbedding() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Embedding != nil
+}
+
+func (x *Use) HasInternalFieldAccess() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_InternalFieldAccess != nil
+}
+
+func (x *Use) HasReflectCall() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_ReflectCall != nil
+}
+
+func (x *Use) HasShallowCopy() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_ShallowCopy != nil
+}
+
+func (x *Use) ClearDirectFieldAccess() {
+ x.xxx_hidden_DirectFieldAccess = nil
+}
+
+func (x *Use) ClearMethodCall() {
+ x.xxx_hidden_MethodCall = nil
+}
+
+func (x *Use) ClearConstructor() {
+ x.xxx_hidden_Constructor = nil
+}
+
+func (x *Use) ClearConversion() {
+ x.xxx_hidden_Conversion = nil
+}
+
+func (x *Use) ClearFuncArg() {
+ x.xxx_hidden_FuncArg = nil
+}
+
+func (x *Use) ClearTypeAssertion() {
+ x.xxx_hidden_TypeAssertion = nil
+}
+
+func (x *Use) ClearTypeDefinition() {
+ x.xxx_hidden_TypeDefinition = nil
+}
+
+func (x *Use) ClearEmbedding() {
+ x.xxx_hidden_Embedding = nil
+}
+
+func (x *Use) ClearInternalFieldAccess() {
+ x.xxx_hidden_InternalFieldAccess = nil
+}
+
+func (x *Use) ClearReflectCall() {
+ x.xxx_hidden_ReflectCall = nil
+}
+
+func (x *Use) ClearShallowCopy() {
+ x.xxx_hidden_ShallowCopy = nil
+}
+
+type Use_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Use_Type
+ DirectFieldAccess *FieldAccess
+ MethodCall *MethodCall
+ Constructor *Constructor
+ Conversion *Conversion
+ FuncArg *FuncArg
+ TypeAssertion *TypeAssertion
+ TypeDefinition *TypeDefinition
+ Embedding *Embedding
+ InternalFieldAccess *FieldAccess
+ ReflectCall *ReflectCall
+ ShallowCopy *ShallowCopy
+}
+
+func (b0 Use_builder) Build() *Use {
+ m0 := &Use{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Type = b.Type
+ x.xxx_hidden_DirectFieldAccess = b.DirectFieldAccess
+ x.xxx_hidden_MethodCall = b.MethodCall
+ x.xxx_hidden_Constructor = b.Constructor
+ x.xxx_hidden_Conversion = b.Conversion
+ x.xxx_hidden_FuncArg = b.FuncArg
+ x.xxx_hidden_TypeAssertion = b.TypeAssertion
+ x.xxx_hidden_TypeDefinition = b.TypeDefinition
+ x.xxx_hidden_Embedding = b.Embedding
+ x.xxx_hidden_InternalFieldAccess = b.InternalFieldAccess
+ x.xxx_hidden_ReflectCall = b.ReflectCall
+ x.xxx_hidden_ShallowCopy = b.ShallowCopy
+ return m0
+}
+
+// ReflectCall represents a call to the Go reflection API.
+type ReflectCall struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Frames *[]*Frame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"`
+ xxx_hidden_Fn *Frame `protobuf:"bytes,2,opt,name=fn" json:"fn,omitempty"`
+ xxx_hidden_Caller *Frame `protobuf:"bytes,3,opt,name=caller" json:"caller,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ReflectCall) Reset() {
+ *x = ReflectCall{}
+ mi := &file_stats_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ReflectCall) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReflectCall) ProtoMessage() {}
+
+func (x *ReflectCall) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ReflectCall) GetFrames() []*Frame {
+ if x != nil {
+ if x.xxx_hidden_Frames != nil {
+ return *x.xxx_hidden_Frames
+ }
+ }
+ return nil
+}
+
+func (x *ReflectCall) GetFn() *Frame {
+ if x != nil {
+ return x.xxx_hidden_Fn
+ }
+ return nil
+}
+
+func (x *ReflectCall) GetCaller() *Frame {
+ if x != nil {
+ return x.xxx_hidden_Caller
+ }
+ return nil
+}
+
+func (x *ReflectCall) SetFrames(v []*Frame) {
+ x.xxx_hidden_Frames = &v
+}
+
+func (x *ReflectCall) SetFn(v *Frame) {
+ x.xxx_hidden_Fn = v
+}
+
+func (x *ReflectCall) SetCaller(v *Frame) {
+ x.xxx_hidden_Caller = v
+}
+
+func (x *ReflectCall) HasFn() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Fn != nil
+}
+
+func (x *ReflectCall) HasCaller() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_Caller != nil
+}
+
+func (x *ReflectCall) ClearFn() {
+ x.xxx_hidden_Fn = nil
+}
+
+func (x *ReflectCall) ClearCaller() {
+ x.xxx_hidden_Caller = nil
+}
+
+type ReflectCall_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Information about all frames leading to the reflection call.
+ //
+ // A callstack usually looks like:
+ //
+ // reflect.Value.Field // the function triggering the log
+ // reflect.F1 // more functions
+ // ... // in the
+ // reflect.Fn // reflect package
+ // import/path.Caller // (1) fn: calls into the reflect package
+ // import/path.C1 // more functions
+ // ... // in the same package
+ // import/path.Cn1 // as fn
+ // ancestor/import.Ancestor // (2) caller: calls into fn's package
+ // ... // more frames
+ //
+ // The frames field has information about all frames but we also
+ // store a few selected frames separately to make it easier to write
+ // queries:
+ //
+ // (1) caller is the last function before the reflection package
+ // (2) ancestor is the last function from a different package than caller
+ //
+ // The frames field contains at least one frame (a function in the
+ // reflect package) but fn and caller may be unset.
+ Frames []*Frame
+ Fn *Frame
+ Caller *Frame
+}
+
+func (b0 ReflectCall_builder) Build() *ReflectCall {
+ m0 := &ReflectCall{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Frames = &b.Frames
+ x.xxx_hidden_Fn = b.Fn
+ x.xxx_hidden_Caller = b.Caller
+ return m0
+}
+
+// Frame represents information about a single frame in a Go
+// stacktrace.
+type Frame struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Function string `protobuf:"bytes,1,opt,name=function" json:"function,omitempty"`
+ xxx_hidden_IsExported bool `protobuf:"varint,6,opt,name=is_exported,json=isExported" json:"is_exported,omitempty"`
+ xxx_hidden_Package string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"`
+ xxx_hidden_File string `protobuf:"bytes,3,opt,name=file" json:"file,omitempty"`
+ xxx_hidden_Line string `protobuf:"bytes,4,opt,name=line" json:"line,omitempty"`
+ xxx_hidden_Index int64 `protobuf:"varint,5,opt,name=index" json:"index,omitempty"`
+ xxx_hidden_PkgIndex int64 `protobuf:"varint,7,opt,name=pkg_index,json=pkgIndex" json:"pkg_index,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Frame) Reset() {
+ *x = Frame{}
+ mi := &file_stats_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Frame) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Frame) ProtoMessage() {}
+
+func (x *Frame) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Frame) GetFunction() string {
+ if x != nil {
+ return x.xxx_hidden_Function
+ }
+ return ""
+}
+
+func (x *Frame) GetIsExported() bool {
+ if x != nil {
+ return x.xxx_hidden_IsExported
+ }
+ return false
+}
+
+func (x *Frame) GetPackage() string {
+ if x != nil {
+ return x.xxx_hidden_Package
+ }
+ return ""
+}
+
+func (x *Frame) GetFile() string {
+ if x != nil {
+ return x.xxx_hidden_File
+ }
+ return ""
+}
+
+func (x *Frame) GetLine() string {
+ if x != nil {
+ return x.xxx_hidden_Line
+ }
+ return ""
+}
+
+func (x *Frame) GetIndex() int64 {
+ if x != nil {
+ return x.xxx_hidden_Index
+ }
+ return 0
+}
+
+func (x *Frame) GetPkgIndex() int64 {
+ if x != nil {
+ return x.xxx_hidden_PkgIndex
+ }
+ return 0
+}
+
+func (x *Frame) SetFunction(v string) {
+ x.xxx_hidden_Function = v
+}
+
+func (x *Frame) SetIsExported(v bool) {
+ x.xxx_hidden_IsExported = v
+}
+
+func (x *Frame) SetPackage(v string) {
+ x.xxx_hidden_Package = v
+}
+
+func (x *Frame) SetFile(v string) {
+ x.xxx_hidden_File = v
+}
+
+func (x *Frame) SetLine(v string) {
+ x.xxx_hidden_Line = v
+}
+
+func (x *Frame) SetIndex(v int64) {
+ x.xxx_hidden_Index = v
+}
+
+func (x *Frame) SetPkgIndex(v int64) {
+ x.xxx_hidden_PkgIndex = v
+}
+
+type Frame_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fully qualified function name. For example:
+ //
+ // net/http.Error
+ Function string
+ // true if the function is exported.
+ IsExported bool
+ // Packed in which the function is defined.
+ Package string
+ // path to the source file defining the function.
+ //
+ // For standard-library, the path is relative to the src/
+ // directory (e.g. net/http/server.go).
+ File string
+ // Line number, together with file, is the location where function
+ // is defined.
+ Line string
+ // index of the frame in the reflect_call.frames repeated field.
+ //
+ // This exists to make SQL queries easier.
+ Index int64
+ // index of the frame across consecutive frames in the same package.
+ //
+ // This exists to make SQL queries easier.
+ PkgIndex int64
+}
+
+func (b0 Frame_builder) Build() *Frame {
+ m0 := &Frame{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Function = b.Function
+ x.xxx_hidden_IsExported = b.IsExported
+ x.xxx_hidden_Package = b.Package
+ x.xxx_hidden_File = b.File
+ x.xxx_hidden_Line = b.Line
+ x.xxx_hidden_Index = b.Index
+ x.xxx_hidden_PkgIndex = b.PkgIndex
+ return m0
+}
+
+type FieldAccess struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_FieldName string `protobuf:"bytes,1,opt,name=field_name,json=fieldName" json:"field_name,omitempty"`
+ xxx_hidden_FieldType *Type `protobuf:"bytes,2,opt,name=field_type,json=fieldType" json:"field_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *FieldAccess) Reset() {
+ *x = FieldAccess{}
+ mi := &file_stats_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *FieldAccess) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FieldAccess) ProtoMessage() {}
+
+func (x *FieldAccess) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *FieldAccess) GetFieldName() string {
+ if x != nil {
+ return x.xxx_hidden_FieldName
+ }
+ return ""
+}
+
+func (x *FieldAccess) GetFieldType() *Type {
+ if x != nil {
+ return x.xxx_hidden_FieldType
+ }
+ return nil
+}
+
+func (x *FieldAccess) SetFieldName(v string) {
+ x.xxx_hidden_FieldName = v
+}
+
+func (x *FieldAccess) SetFieldType(v *Type) {
+ x.xxx_hidden_FieldType = v
+}
+
+func (x *FieldAccess) HasFieldType() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_FieldType != nil
+}
+
+func (x *FieldAccess) ClearFieldType() {
+ x.xxx_hidden_FieldType = nil
+}
+
+type FieldAccess_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ FieldName string
+ FieldType *Type
+}
+
+func (b0 FieldAccess_builder) Build() *FieldAccess {
+ m0 := &FieldAccess{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_FieldName = b.FieldName
+ x.xxx_hidden_FieldType = b.FieldType
+ return m0
+}
+
+type MethodCall struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Method string `protobuf:"bytes,1,opt,name=method" json:"method,omitempty"`
+ xxx_hidden_Type MethodCall_Type `protobuf:"varint,2,opt,name=type,enum=net.proto2.go.open2opaque.stats.MethodCall_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MethodCall) Reset() {
+ *x = MethodCall{}
+ mi := &file_stats_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MethodCall) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MethodCall) ProtoMessage() {}
+
+func (x *MethodCall) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *MethodCall) GetMethod() string {
+ if x != nil {
+ return x.xxx_hidden_Method
+ }
+ return ""
+}
+
+func (x *MethodCall) GetType() MethodCall_Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return MethodCall_INVALID
+}
+
+func (x *MethodCall) SetMethod(v string) {
+ x.xxx_hidden_Method = v
+}
+
+func (x *MethodCall) SetType(v MethodCall_Type) {
+ x.xxx_hidden_Type = v
+}
+
+type MethodCall_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Method string
+ Type MethodCall_Type
+}
+
+func (b0 MethodCall_builder) Build() *MethodCall {
+ m0 := &MethodCall{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Method = b.Method
+ x.xxx_hidden_Type = b.Type
+ return m0
+}
+
+type Constructor struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Type Constructor_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Constructor_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Constructor) Reset() {
+ *x = Constructor{}
+ mi := &file_stats_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Constructor) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Constructor) ProtoMessage() {}
+
+func (x *Constructor) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Constructor) GetType() Constructor_Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return Constructor_TYPE_UNSPECIFIED
+}
+
+func (x *Constructor) SetType(v Constructor_Type) {
+ x.xxx_hidden_Type = v
+}
+
+type Constructor_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type Constructor_Type
+}
+
+func (b0 Constructor_builder) Build() *Constructor {
+ m0 := &Constructor{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Type = b.Type
+ return m0
+}
+
+type Conversion struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_DestTypeName string `protobuf:"bytes,1,opt,name=dest_type_name,json=destTypeName" json:"dest_type_name,omitempty"`
+ xxx_hidden_FuncArg *FuncArg `protobuf:"bytes,3,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"`
+ xxx_hidden_Context Conversion_Context `protobuf:"varint,2,opt,name=context,enum=net.proto2.go.open2opaque.stats.Conversion_Context" json:"context,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Conversion) Reset() {
+ *x = Conversion{}
+ mi := &file_stats_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Conversion) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Conversion) ProtoMessage() {}
+
+func (x *Conversion) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[12]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Conversion) GetDestTypeName() string {
+ if x != nil {
+ return x.xxx_hidden_DestTypeName
+ }
+ return ""
+}
+
+func (x *Conversion) GetFuncArg() *FuncArg {
+ if x != nil {
+ return x.xxx_hidden_FuncArg
+ }
+ return nil
+}
+
+func (x *Conversion) GetContext() Conversion_Context {
+ if x != nil {
+ return x.xxx_hidden_Context
+ }
+ return Conversion_CONTEXT_UNSPECIFIED
+}
+
+func (x *Conversion) SetDestTypeName(v string) {
+ x.xxx_hidden_DestTypeName = v
+}
+
+func (x *Conversion) SetFuncArg(v *FuncArg) {
+ x.xxx_hidden_FuncArg = v
+}
+
+func (x *Conversion) SetContext(v Conversion_Context) {
+ x.xxx_hidden_Context = v
+}
+
+func (x *Conversion) HasFuncArg() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_FuncArg != nil
+}
+
+func (x *Conversion) ClearFuncArg() {
+ x.xxx_hidden_FuncArg = nil
+}
+
+type Conversion_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The type of the conversion. For example:
+ //
+ // interface{}
+ // proto.Message
+ // unsafe.Pointer
+ DestTypeName string
+ // Describes the called function. It is set if context==CALL_ARGUMENT.
+ FuncArg *FuncArg
+ Context Conversion_Context
+}
+
+func (b0 Conversion_builder) Build() *Conversion {
+ m0 := &Conversion{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_DestTypeName = b.DestTypeName
+ x.xxx_hidden_FuncArg = b.FuncArg
+ x.xxx_hidden_Context = b.Context
+ return m0
+}
+
+type FuncArg struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_FunctionName string `protobuf:"bytes,1,opt,name=function_name,json=functionName" json:"function_name,omitempty"`
+ xxx_hidden_PackagePath string `protobuf:"bytes,2,opt,name=package_path,json=packagePath" json:"package_path,omitempty"`
+ xxx_hidden_Signature string `protobuf:"bytes,3,opt,name=signature" json:"signature,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *FuncArg) Reset() {
+ *x = FuncArg{}
+ mi := &file_stats_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *FuncArg) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FuncArg) ProtoMessage() {}
+
+func (x *FuncArg) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[13]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *FuncArg) GetFunctionName() string {
+ if x != nil {
+ return x.xxx_hidden_FunctionName
+ }
+ return ""
+}
+
+func (x *FuncArg) GetPackagePath() string {
+ if x != nil {
+ return x.xxx_hidden_PackagePath
+ }
+ return ""
+}
+
+func (x *FuncArg) GetSignature() string {
+ if x != nil {
+ return x.xxx_hidden_Signature
+ }
+ return ""
+}
+
+func (x *FuncArg) SetFunctionName(v string) {
+ x.xxx_hidden_FunctionName = v
+}
+
+func (x *FuncArg) SetPackagePath(v string) {
+ x.xxx_hidden_PackagePath = v
+}
+
+func (x *FuncArg) SetSignature(v string) {
+ x.xxx_hidden_Signature = v
+}
+
+type FuncArg_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The name of the called function.
+ // For example: "Clone".
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ FunctionName string
+ // Full package path containing the called function.
+ //
+ // An empty string means that analysis couldn't determine which
+ // function is called (this could happen for indirect calls).
+ PackagePath string
+ // Signature of the called function.
+ // For example: "func(m interface{}) interface{}".
+ Signature string
+}
+
+func (b0 FuncArg_builder) Build() *FuncArg {
+ m0 := &FuncArg{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_FunctionName = b.FunctionName
+ x.xxx_hidden_PackagePath = b.PackagePath
+ x.xxx_hidden_Signature = b.Signature
+ return m0
+}
+
+type TypeAssertion struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_SrcType *Type `protobuf:"bytes,1,opt,name=src_type,json=srcType" json:"src_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TypeAssertion) Reset() {
+ *x = TypeAssertion{}
+ mi := &file_stats_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TypeAssertion) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TypeAssertion) ProtoMessage() {}
+
+func (x *TypeAssertion) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[14]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *TypeAssertion) GetSrcType() *Type {
+ if x != nil {
+ return x.xxx_hidden_SrcType
+ }
+ return nil
+}
+
+func (x *TypeAssertion) SetSrcType(v *Type) {
+ x.xxx_hidden_SrcType = v
+}
+
+func (x *TypeAssertion) HasSrcType() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_SrcType != nil
+}
+
+func (x *TypeAssertion) ClearSrcType() {
+ x.xxx_hidden_SrcType = nil
+}
+
+type TypeAssertion_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // The type of the expression whose type is being asserted.
+ SrcType *Type
+}
+
+func (b0 TypeAssertion_builder) Build() *TypeAssertion {
+ m0 := &TypeAssertion{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_SrcType = b.SrcType
+ return m0
+}
+
+type TypeDefinition struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_NewType *Type `protobuf:"bytes,1,opt,name=new_type,json=newType" json:"new_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TypeDefinition) Reset() {
+ *x = TypeDefinition{}
+ mi := &file_stats_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TypeDefinition) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TypeDefinition) ProtoMessage() {}
+
+func (x *TypeDefinition) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[15]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *TypeDefinition) GetNewType() *Type {
+ if x != nil {
+ return x.xxx_hidden_NewType
+ }
+ return nil
+}
+
+func (x *TypeDefinition) SetNewType(v *Type) {
+ x.xxx_hidden_NewType = v
+}
+
+func (x *TypeDefinition) HasNewType() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_NewType != nil
+}
+
+func (x *TypeDefinition) ClearNewType() {
+ x.xxx_hidden_NewType = nil
+}
+
+type TypeDefinition_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // new_type describes the newly defined type.
+ NewType *Type
+}
+
+func (b0 TypeDefinition_builder) Build() *TypeDefinition {
+ m0 := &TypeDefinition{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_NewType = b.NewType
+ return m0
+}
+
+type Embedding struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_FieldIndex int64 `protobuf:"varint,1,opt,name=field_index,json=fieldIndex" json:"field_index,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Embedding) Reset() {
+ *x = Embedding{}
+ mi := &file_stats_proto_msgTypes[16]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Embedding) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Embedding) ProtoMessage() {}
+
+func (x *Embedding) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[16]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Embedding) GetFieldIndex() int64 {
+ if x != nil {
+ return x.xxx_hidden_FieldIndex
+ }
+ return 0
+}
+
+func (x *Embedding) SetFieldIndex(v int64) {
+ x.xxx_hidden_FieldIndex = v
+}
+
+type Embedding_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ FieldIndex int64
+}
+
+func (b0 Embedding_builder) Build() *Embedding {
+ m0 := &Embedding{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_FieldIndex = b.FieldIndex
+ return m0
+}
+
+type Source struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_File string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Source) Reset() {
+ *x = Source{}
+ mi := &file_stats_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Source) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Source) ProtoMessage() {}
+
+func (x *Source) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[17]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *Source) GetFile() string {
+ if x != nil {
+ return x.xxx_hidden_File
+ }
+ return ""
+}
+
+func (x *Source) SetFile(v string) {
+ x.xxx_hidden_File = v
+}
+
+type Source_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ File string
+}
+
+func (b0 Source_builder) Build() *Source {
+ m0 := &Source{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_File = b.File
+ return m0
+}
+
+// ShallowCopy represents a shallow copy of protocol buffers.
+//
+// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages
+// (pointers to Go structs generated by the proto generor).
+type ShallowCopy struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_Type ShallowCopy_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.ShallowCopy_Type" json:"type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ShallowCopy) Reset() {
+ *x = ShallowCopy{}
+ mi := &file_stats_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ShallowCopy) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ShallowCopy) ProtoMessage() {}
+
+func (x *ShallowCopy) ProtoReflect() protoreflect.Message {
+ mi := &file_stats_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ShallowCopy) GetType() ShallowCopy_Type {
+ if x != nil {
+ return x.xxx_hidden_Type
+ }
+ return ShallowCopy_TYPE_UNSPECIFIED
+}
+
+func (x *ShallowCopy) SetType(v ShallowCopy_Type) {
+ x.xxx_hidden_Type = v
+}
+
+type ShallowCopy_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Type ShallowCopy_Type
+}
+
+func (b0 ShallowCopy_builder) Build() *ShallowCopy {
+ m0 := &ShallowCopy{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_Type = b.Type
+ return m0
+}
+
+var File_stats_proto protoreflect.FileDescriptor
+
+var file_stats_proto_rawDesc = []byte{
+ 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x6e,
+ 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
+ 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x21,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
+ 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x22, 0xc9, 0x03, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x06, 0x73,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74,
+ 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f,
+ 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
+ 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65,
+ 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
+ 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x04,
+ 0x65, 0x78, 0x70, 0x72, 0x12, 0x36, 0x0a, 0x03, 0x75, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x52, 0x03, 0x75, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06,
+ 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
+ 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53,
+ 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xe2, 0x01,
+ 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61,
+ 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63,
+ 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x73, 0x5f, 0x67,
+ 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64,
+ 0x46, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05,
+ 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65,
+ 0x6e, 0x64, 0x22, 0x36, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12,
+ 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69,
+ 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x03, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x06, 0x53,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x79, 0x70,
+ 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x38, 0x0a,
+ 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+ 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f,
+ 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4b, 0x49, 0x50, 0x10, 0x02, 0x12, 0x08, 0x0a,
+ 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x03, 0x22, 0x42, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
+ 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b,
+ 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0a, 0x45,
+ 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a,
+ 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc8,
+ 0x09, 0x0a, 0x03, 0x55, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65,
+ 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52,
+ 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73,
+ 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
+ 0x52, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63,
+ 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x63, 0x61,
+ 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70,
+ 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f,
+ 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c,
+ 0x6c, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75,
+ 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f,
+ 0x72, 0x12, 0x4b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75,
+ 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69,
+ 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43,
+ 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f,
+ 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61,
+ 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, 0x75, 0x6e, 0x63,
+ 0x41, 0x72, 0x67, 0x12, 0x55, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65,
+ 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79,
+ 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x79, 0x70,
+ 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x0f, 0x74, 0x79,
+ 0x70, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69,
+ 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69,
+ 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e,
+ 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64,
+ 0x69, 0x6e, 0x67, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x60,
+ 0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64,
+ 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e,
+ 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x13, 0x69, 0x6e, 0x74,
+ 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
+ 0x12, 0x4f, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x61, 0x6c, 0x6c,
+ 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74,
+ 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c,
+ 0x6c, 0x12, 0x4f, 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x70,
+ 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f,
+ 0x77, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f,
+ 0x70, 0x79, 0x22, 0xf4, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54,
+ 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+ 0x00, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x46, 0x49, 0x45, 0x4c,
+ 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45,
+ 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x43,
+ 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a,
+ 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05,
+ 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x49, 0x54,
+ 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x4d, 0x42, 0x45, 0x44, 0x44, 0x49,
+ 0x4e, 0x47, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c,
+ 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x08, 0x12,
+ 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x4c, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10,
+ 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x50,
+ 0x59, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x44, 0x45, 0x50,
+ 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x0b, 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x52, 0x65,
+ 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x72, 0x61,
+ 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f,
+ 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d,
+ 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x02, 0x66, 0x6e, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75,
+ 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x02, 0x66,
+ 0x6e, 0x12, 0x3e, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65,
+ 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66,
+ 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66,
+ 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x65, 0x78,
+ 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73,
+ 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b,
+ 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61,
+ 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e,
+ 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78,
+ 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20,
+ 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x72, 0x0a,
+ 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74,
+ 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, 0x6c,
+ 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x44, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43,
+ 0x61, 0x6c, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x31,
+ 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
+ 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46,
+ 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10,
+ 0x02, 0x22, 0xa8, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f,
+ 0x72, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+ 0x31, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74,
+ 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79,
+ 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65,
+ 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+ 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f,
+ 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4e, 0x4f, 0x4e,
+ 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x02, 0x12,
+ 0x0b, 0x0a, 0x07, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x03, 0x22, 0xea, 0x02, 0x0a,
+ 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x64,
+ 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d,
+ 0x65, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66,
+ 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+ 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72,
+ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f,
+ 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+ 0x74, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53,
+ 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41,
+ 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a,
+ 0x0c, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12,
+ 0x0e, 0x0a, 0x0a, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12,
+ 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1d, 0x0a,
+ 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52,
+ 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09,
+ 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x46,
+ 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x07, 0x22, 0x6f, 0x0a, 0x07, 0x46, 0x75, 0x6e,
+ 0x63, 0x41, 0x72, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+ 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63,
+ 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09,
+ 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x51, 0x0a, 0x0d, 0x54, 0x79,
+ 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x73,
+ 0x72, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e,
+ 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x73, 0x72, 0x63, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a,
+ 0x0e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+ 0x40, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74,
+ 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1f,
+ 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22,
+ 0x1c, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xcd, 0x01,
+ 0x0a, 0x0b, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x45, 0x0a,
+ 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68,
+ 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
+ 0x74, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+ 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x10, 0x01, 0x12, 0x11,
+ 0x0a, 0x0d, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10,
+ 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x03, 0x12,
+ 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54,
+ 0x45, 0x52, 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x0d,
+ 0x0a, 0x09, 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x05, 0x2a, 0x57, 0x0a,
+ 0x0c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a,
+ 0x19, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55,
+ 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,
+ 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x45, 0x45, 0x4e, 0x10,
+ 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x59, 0x45, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x12, 0x07, 0x0a,
+ 0x03, 0x52, 0x45, 0x44, 0x10, 0x04, 0x42, 0x0a, 0x92, 0x03, 0x07, 0xd2, 0x3e, 0x02, 0x10, 0x02,
+ 0x08, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07,
+}
+
+var file_stats_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
+var file_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
+var file_stats_proto_goTypes = []any{
+ (RewriteLevel)(0), // 0: net.proto2.go.open2opaque.stats.RewriteLevel
+ (Status_Type)(0), // 1: net.proto2.go.open2opaque.stats.Status.Type
+ (Use_Type)(0), // 2: net.proto2.go.open2opaque.stats.Use.Type
+ (MethodCall_Type)(0), // 3: net.proto2.go.open2opaque.stats.MethodCall.Type
+ (Constructor_Type)(0), // 4: net.proto2.go.open2opaque.stats.Constructor.Type
+ (Conversion_Context)(0), // 5: net.proto2.go.open2opaque.stats.Conversion.Context
+ (ShallowCopy_Type)(0), // 6: net.proto2.go.open2opaque.stats.ShallowCopy.Type
+ (*Entry)(nil), // 7: net.proto2.go.open2opaque.stats.Entry
+ (*Location)(nil), // 8: net.proto2.go.open2opaque.stats.Location
+ (*Position)(nil), // 9: net.proto2.go.open2opaque.stats.Position
+ (*Status)(nil), // 10: net.proto2.go.open2opaque.stats.Status
+ (*Type)(nil), // 11: net.proto2.go.open2opaque.stats.Type
+ (*Expression)(nil), // 12: net.proto2.go.open2opaque.stats.Expression
+ (*Use)(nil), // 13: net.proto2.go.open2opaque.stats.Use
+ (*ReflectCall)(nil), // 14: net.proto2.go.open2opaque.stats.ReflectCall
+ (*Frame)(nil), // 15: net.proto2.go.open2opaque.stats.Frame
+ (*FieldAccess)(nil), // 16: net.proto2.go.open2opaque.stats.FieldAccess
+ (*MethodCall)(nil), // 17: net.proto2.go.open2opaque.stats.MethodCall
+ (*Constructor)(nil), // 18: net.proto2.go.open2opaque.stats.Constructor
+ (*Conversion)(nil), // 19: net.proto2.go.open2opaque.stats.Conversion
+ (*FuncArg)(nil), // 20: net.proto2.go.open2opaque.stats.FuncArg
+ (*TypeAssertion)(nil), // 21: net.proto2.go.open2opaque.stats.TypeAssertion
+ (*TypeDefinition)(nil), // 22: net.proto2.go.open2opaque.stats.TypeDefinition
+ (*Embedding)(nil), // 23: net.proto2.go.open2opaque.stats.Embedding
+ (*Source)(nil), // 24: net.proto2.go.open2opaque.stats.Source
+ (*ShallowCopy)(nil), // 25: net.proto2.go.open2opaque.stats.ShallowCopy
+}
+var file_stats_proto_depIdxs = []int32{
+ 10, // 0: net.proto2.go.open2opaque.stats.Entry.status:type_name -> net.proto2.go.open2opaque.stats.Status
+ 8, // 1: net.proto2.go.open2opaque.stats.Entry.location:type_name -> net.proto2.go.open2opaque.stats.Location
+ 0, // 2: net.proto2.go.open2opaque.stats.Entry.level:type_name -> net.proto2.go.open2opaque.stats.RewriteLevel
+ 11, // 3: net.proto2.go.open2opaque.stats.Entry.type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 12, // 4: net.proto2.go.open2opaque.stats.Entry.expr:type_name -> net.proto2.go.open2opaque.stats.Expression
+ 13, // 5: net.proto2.go.open2opaque.stats.Entry.use:type_name -> net.proto2.go.open2opaque.stats.Use
+ 24, // 6: net.proto2.go.open2opaque.stats.Entry.source:type_name -> net.proto2.go.open2opaque.stats.Source
+ 9, // 7: net.proto2.go.open2opaque.stats.Location.start:type_name -> net.proto2.go.open2opaque.stats.Position
+ 9, // 8: net.proto2.go.open2opaque.stats.Location.end:type_name -> net.proto2.go.open2opaque.stats.Position
+ 1, // 9: net.proto2.go.open2opaque.stats.Status.type:type_name -> net.proto2.go.open2opaque.stats.Status.Type
+ 2, // 10: net.proto2.go.open2opaque.stats.Use.type:type_name -> net.proto2.go.open2opaque.stats.Use.Type
+ 16, // 11: net.proto2.go.open2opaque.stats.Use.direct_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess
+ 17, // 12: net.proto2.go.open2opaque.stats.Use.method_call:type_name -> net.proto2.go.open2opaque.stats.MethodCall
+ 18, // 13: net.proto2.go.open2opaque.stats.Use.constructor:type_name -> net.proto2.go.open2opaque.stats.Constructor
+ 19, // 14: net.proto2.go.open2opaque.stats.Use.conversion:type_name -> net.proto2.go.open2opaque.stats.Conversion
+ 20, // 15: net.proto2.go.open2opaque.stats.Use.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg
+ 21, // 16: net.proto2.go.open2opaque.stats.Use.type_assertion:type_name -> net.proto2.go.open2opaque.stats.TypeAssertion
+ 22, // 17: net.proto2.go.open2opaque.stats.Use.type_definition:type_name -> net.proto2.go.open2opaque.stats.TypeDefinition
+ 23, // 18: net.proto2.go.open2opaque.stats.Use.embedding:type_name -> net.proto2.go.open2opaque.stats.Embedding
+ 16, // 19: net.proto2.go.open2opaque.stats.Use.internal_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess
+ 14, // 20: net.proto2.go.open2opaque.stats.Use.reflect_call:type_name -> net.proto2.go.open2opaque.stats.ReflectCall
+ 25, // 21: net.proto2.go.open2opaque.stats.Use.shallow_copy:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy
+ 15, // 22: net.proto2.go.open2opaque.stats.ReflectCall.frames:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 15, // 23: net.proto2.go.open2opaque.stats.ReflectCall.fn:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 15, // 24: net.proto2.go.open2opaque.stats.ReflectCall.caller:type_name -> net.proto2.go.open2opaque.stats.Frame
+ 11, // 25: net.proto2.go.open2opaque.stats.FieldAccess.field_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 3, // 26: net.proto2.go.open2opaque.stats.MethodCall.type:type_name -> net.proto2.go.open2opaque.stats.MethodCall.Type
+ 4, // 27: net.proto2.go.open2opaque.stats.Constructor.type:type_name -> net.proto2.go.open2opaque.stats.Constructor.Type
+ 20, // 28: net.proto2.go.open2opaque.stats.Conversion.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg
+ 5, // 29: net.proto2.go.open2opaque.stats.Conversion.context:type_name -> net.proto2.go.open2opaque.stats.Conversion.Context
+ 11, // 30: net.proto2.go.open2opaque.stats.TypeAssertion.src_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 11, // 31: net.proto2.go.open2opaque.stats.TypeDefinition.new_type:type_name -> net.proto2.go.open2opaque.stats.Type
+ 6, // 32: net.proto2.go.open2opaque.stats.ShallowCopy.type:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy.Type
+ 33, // [33:33] is the sub-list for method output_type
+ 33, // [33:33] is the sub-list for method input_type
+ 33, // [33:33] is the sub-list for extension type_name
+ 33, // [33:33] is the sub-list for extension extendee
+ 0, // [0:33] is the sub-list for field type_name
+}
+
+func init() { file_stats_proto_init() }
+func file_stats_proto_init() {
+ if File_stats_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_stats_proto_rawDesc,
+ NumEnums: 7,
+ NumMessages: 19,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_stats_proto_goTypes,
+ DependencyIndexes: file_stats_proto_depIdxs,
+ EnumInfos: file_stats_proto_enumTypes,
+ MessageInfos: file_stats_proto_msgTypes,
+ }.Build()
+ File_stats_proto = out.File
+ file_stats_proto_rawDesc = nil
+ file_stats_proto_goTypes = nil
+ file_stats_proto_depIdxs = nil
+}
diff --git a/internal/fix/appendprotos.go b/internal/fix/appendprotos.go
new file mode 100644
index 0000000..ab416f6
--- /dev/null
+++ b/internal/fix/appendprotos.go
@@ -0,0 +1,85 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+
+ "github.com/dave/dst"
+)
+
+func appendProtosPre(c *cursor) bool {
+ // Handle the following as a single unit:
+ //
+ // m.R = append(m.R, msg, ...)
+ // =>
+ // m.SetR(append(m.GetR(), msg, ...))
+ //
+ //
+ // Note that this rewrite can be handled as two independent rewrites:
+ //
+ // Step 1
+ // m.R = append(m.R, msg, ...)
+ // =>
+ // m.R = append(m.GetR(), msg, ...)
+ //
+ // Step2
+ // m.R = append(m.GetR(), msg, ...)
+ // =>
+ // m.SetR(append(m.GetR(), msg, ...))
+ //
+ // So this function may seem redundant at first. However, in practice,
+ // those multi-step rewrites are not very reliable in presence of
+ // failures and it's not uncommon to end up with partially updated
+ // pattern or a misplaced comment. Hence, we handle the pattern as a
+ // whole.
+ a, ok := c.Node().(*dst.AssignStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for AssignStmt)", c.Node())
+ return true
+ }
+ if a.Tok != token.ASSIGN || len(a.Lhs) != 1 || len(a.Rhs) != 1 {
+ c.Logf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", a.Tok)
+ return true
+ }
+ if len(a.Lhs) != 1 || len(a.Rhs) != 1 {
+ c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(a.Lhs), len(a.Rhs))
+ return true
+ }
+
+ appendCall, ok := a.Rhs[0].(*dst.CallExpr)
+ if !ok {
+ c.Logf("ignoring AssignStmt with rhs %T (looking for CallExpr)", a.Rhs[0])
+ return true
+ }
+ if len(appendCall.Args) == 0 {
+ c.Logf("ignoring rhs CallExpr with %d args (looking for 1)", len(appendCall.Args))
+ return true
+ }
+ ident, ok := appendCall.Fun.(*dst.Ident)
+ if !ok {
+ c.Logf("ignoring rhs CallExpr with Fun %T (looking for Ident)", appendCall.Fun)
+ return true
+ }
+ if obj := c.objectOf(ident); obj.Name() != "append" || obj.Pkg() != nil {
+ c.Logf("ignoring rhs CallExpr with Obj %v (looking for append())", obj)
+ return true
+ }
+
+ lsel, ok := c.trackedProtoFieldSelector(a.Lhs[0])
+ if !ok {
+ c.Logf("ignoring: lhs is not a proto field selector")
+ return true
+ }
+ rsel, ok := c.trackedProtoFieldSelector(appendCall.Args[0])
+ if !ok {
+ c.Logf("ignoring: rhs append() arg is not a proto field selector")
+ return true
+ }
+
+ appendCall.Args[0] = sel2call(c, "Get", rsel, nil, *rsel.Decorations())
+ c.Replace(c.expr2stmt(sel2call(c, "Set", lsel, appendCall, *c.Node().Decorations()), lsel))
+ return true
+}
diff --git a/internal/fix/appendprotos_test.go b/internal/fix/appendprotos_test.go
new file mode 100644
index 0000000..a6b63d1
--- /dev/null
+++ b/internal/fix/appendprotos_test.go
@@ -0,0 +1,46 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestAppend(t *testing.T) {
+ tt := test{
+ in: `
+m2.Ms = append(m2.Ms, nil)
+m2.Is = append(m2.Is, 1)
+m3.Ms = append(m3.Ms, nil)
+m3.Is = append(m3.Is, 1)
+
+m3.Is = append(m3.Is, 1, 2, 3)
+m3.Is = append(m3.Is, append(m3.Is, 1)...)
+
+// append with a comment
+m3.Is = append(m3.Is, 1)
+
+m3.Is = append(m3.Is)
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetMs(append(m2.GetMs(), nil))
+m2.SetIs(append(m2.GetIs(), 1))
+m3.SetMs(append(m3.GetMs(), nil))
+m3.SetIs(append(m3.GetIs(), 1))
+
+m3.SetIs(append(m3.GetIs(), 1, 2, 3))
+m3.SetIs(append(m3.GetIs(), append(m3.GetIs(), 1)...))
+
+// append with a comment
+m3.SetIs(append(m3.GetIs(), 1))
+
+m3.SetIs(append(m3.GetIs()))
+`,
+ },
+ }
+
+ runTableTest(t, tt)
+}
diff --git a/internal/fix/assign.go b/internal/fix/assign.go
new file mode 100644
index 0000000..9c766e3
--- /dev/null
+++ b/internal/fix/assign.go
@@ -0,0 +1,1004 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+)
+
+// assignPre rewrites assignments. This function is executed by traversing the tree in preorder.
+// Assignments with operations are handled by assignOpPre.
+func assignPre(c *cursor) bool {
+ stmt, ok := c.Node().(*dst.AssignStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for AssignStmt)", c.Node())
+ return true
+ }
+ if stmt.Tok != token.ASSIGN {
+ c.Logf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", stmt.Tok)
+ return true
+ }
+
+ // Not handled: shallow copy support.
+
+ if len(stmt.Lhs) != len(stmt.Rhs) {
+ c.Logf("ignoring AssignStmt with len(lhs)=%d != len(rhs)=%d", len(stmt.Lhs), len(stmt.Rhs))
+ // Not handled: assignments where len(lhs) != len(rhs):
+ // - calls
+ // - chan ops
+ // - map Access
+ // - type assertions
+ return true
+ }
+
+ // Handle the most common case: single assignment.
+ if len(stmt.Lhs) == 1 {
+ c.Logf("len(lhs) = 1")
+ lhs, rhs := stmt.Lhs[0], stmt.Rhs[0]
+
+ // *m.F = v => m.SetF(v)
+ if star, ok := lhs.(*dst.StarExpr); ok {
+ c.Logf("lhs is a StarExpr")
+ sel, ok := c.trackedProtoFieldSelector(star.X)
+ if !ok {
+ c.Logf("ignoring: lhs is not a proto field selector")
+ return true
+ }
+ c.Replace(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel))
+ c.Logf("rewriting AssignStmt")
+ return true
+ }
+
+ // Handle "m.F = v"
+ field, ok := c.protoFieldSelector(lhs)
+ if !ok {
+ c.Logf("ignoring: lhs is not a proto field selector")
+ return true
+ }
+ // Check if either side is a proto field selector in -types_to_update
+ // and update the whole assignment.
+ _, lhsOk := c.trackedProtoFieldSelector(lhs)
+ _, rhsOk := c.trackedProtoFieldSelector(rhs)
+ if !lhsOk && !rhsOk {
+ c.Logf("ignoring: neither lhs nor rhs are (tracked) proto field selectors")
+ return true
+ }
+ c.Logf("attempting to rewrite...")
+ if a, ok := rewriteFieldAssign(c, field, rhs, *stmt.Decorations()); ok {
+ c.Logf("...success")
+ c.Replace(a)
+ } else {
+ c.Logf("...failure")
+ }
+
+ return true
+ }
+
+ // Rewriting multi-assignment may change order of evaluation. Hence this is a yellow rewrite.
+ if c.lvl.le(Green) {
+ return true
+ }
+
+ if err := assignmentIsSwap(c, stmt); err == nil && !c.lvl.ge(Yellow) {
+ // Swaps are yellow rewrites because getPost only handles them in the yellow level.
+ return true
+ } else if err != nil {
+ c.Logf("%s", err.Error())
+ }
+
+ // Multi-assignment in simple statements is handled by assignPost. It requires updating
+ // grandparent (calling parent.InsertBefore) of the rewritten node and the dstutil.Cursor API
+ // doesn't provide a way to do that. Here, we only update statements in blocks because we can
+ // call c.InsertBefore.
+
+ if _, ok := c.Cursor.Parent().(*dst.BlockStmt); !ok {
+ c.Logf("ignoring: multi-assignment outside of BlockStmt (handled by assignPost)")
+ return true
+ }
+
+ // Don't change the multi-assignment structure if there are no proto-related
+ // rewrites in there.
+ var usesProtos bool
+ for _, lhs := range stmt.Lhs {
+ if star, ok := lhs.(*dst.StarExpr); ok {
+ if _, ok := c.trackedProtoFieldSelector(star.X); ok {
+ usesProtos = true
+ break
+ }
+ }
+ if _, ok := c.trackedProtoFieldSelector(lhs); ok {
+ usesProtos = true
+ break
+ }
+ }
+ if !usesProtos {
+ c.Logf("ignoring AssignStmt without protos")
+ return true
+ }
+
+ var decs dst.NodeDecs
+ lastIdx := len(stmt.Lhs) - 1
+ for i, lhs := range stmt.Lhs {
+ switch i {
+ case 0:
+ decs = dst.NodeDecs{
+ Before: stmt.Decorations().Before,
+ Start: stmt.Decorations().Start,
+ }
+ case lastIdx:
+ decs = dst.NodeDecs{
+ After: stmt.Decorations().After,
+ End: stmt.Decorations().End,
+ }
+ default:
+ decs = dst.NodeDecs{}
+ }
+ rhs := stmt.Rhs[i]
+
+ // rewrite "*m.F = v"
+ if star, ok := lhs.(*dst.StarExpr); ok {
+ if sel, ok := c.trackedProtoFieldSelector(star.X); ok {
+ c.Logf("rewriting %v = %v", sel.Sel.Name, rhs)
+ c.InsertBefore(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel))
+ } else {
+ c.Logf("ignoring: lhs is not a proto field selector")
+ }
+ continue
+ }
+
+ // rewrite "m.F = v"
+ if field, ok := c.trackedProtoFieldSelector(lhs); ok {
+ c.Logf("lhs %d is a proto field selector", i)
+ if a, ok := rewriteFieldAssign(c, field, rhs, decs); ok {
+ c.Logf("rewriting %v = %v", field.Sel.Name, rhs)
+ c.InsertBefore(a)
+ continue
+ }
+ }
+ as := &dst.AssignStmt{
+ Lhs: []dst.Expr{lhs},
+ Tok: token.ASSIGN,
+ Rhs: []dst.Expr{rhs},
+ }
+ as.Decs.NodeDecs = decs
+ c.Logf("rewriting %v = %v", lhs, rhs)
+ c.InsertBefore(as)
+ }
+ c.Delete()
+ return true
+}
+
+// assignPost rewrites multi-assignments in simple statements. This function is executed by
+// traversing the tree in postorder.
+func assignPost(c *cursor) bool {
+ // Splitting multi-assignments is a yellow rewrite because it changes order of evaluation.
+ if c.lvl.le(Green) {
+ return true
+ }
+
+ // If either expression a or b is a proto direct field access, rewrite:
+ //
+ // if a, b = f(), g(); cond { // Similar for init statements in "for" and "switch".
+ // =>
+ // a = f()
+ // b = g()
+ // if cond {
+ //
+ // and apply single-assignment rewrite rules for individual assign statements.
+ var initStmt *dst.AssignStmt
+ n := c.Node()
+ switch n := n.(type) {
+ case *dst.IfStmt:
+ if isMultiProtoAssign(c, n.Init) {
+ initStmt = n.Init.(*dst.AssignStmt)
+ n.Init = nil
+ }
+ case *dst.ForStmt:
+ if isMultiProtoAssign(c, n.Init) {
+ initStmt = n.Init.(*dst.AssignStmt)
+ n.Init = nil
+ }
+ case *dst.SwitchStmt:
+ if isMultiProtoAssign(c, n.Init) {
+ initStmt = n.Init.(*dst.AssignStmt)
+ n.Init = nil
+ }
+ case *dst.TypeSwitchStmt:
+ if isMultiProtoAssign(c, n.Init) {
+ initStmt = n.Init.(*dst.AssignStmt)
+ n.Init = nil
+ }
+ }
+ if initStmt == nil {
+ return true
+ }
+ for i, lhs := range initStmt.Lhs {
+ rhs := initStmt.Rhs[i]
+ if a, ok := rewriteFieldAssign(c, lhs, rhs, dst.NodeDecs{}); ok {
+ c.InsertBefore(a)
+ continue
+ }
+ c.InsertBefore(&dst.AssignStmt{
+ Lhs: []dst.Expr{lhs},
+ Tok: token.ASSIGN,
+ Rhs: []dst.Expr{rhs},
+ })
+ }
+ c.numUnsafeRewritesByReason[EvalOrderChange]++
+ return true
+}
+
+// assignOpPre rewrites assignment operations (x op= y).
+// This function is executed by traversing the tree in preorder.
+func assignOpPre(c *cursor) bool {
+ stmt, ok := c.Node().(*dst.AssignStmt)
+ if !ok {
+ return true
+ }
+ if stmt.Tok == token.ASSIGN || stmt.Tok == token.DEFINE {
+ return false
+ }
+
+ // Assignment operations must have exactly one lhs and one rhs value,
+ // see https://go.dev/ref/spec#Assignment_statements.
+ lhs, rhs := stmt.Lhs[0], stmt.Rhs[0]
+
+ if star, ok := lhs.(*dst.StarExpr); ok {
+ lhs = star.X
+ }
+ sel, ok := c.trackedProtoFieldSelector(lhs)
+ if !ok {
+ return false
+ }
+
+ tok := stmt.Tok
+ switch stmt.Tok {
+ case token.ADD_ASSIGN:
+ tok = token.ADD
+ case token.SUB_ASSIGN:
+ tok = token.SUB
+ case token.MUL_ASSIGN:
+ tok = token.MUL
+ case token.QUO_ASSIGN:
+ tok = token.QUO
+ case token.REM_ASSIGN:
+ tok = token.REM
+ case token.AND_ASSIGN:
+ tok = token.AND
+ case token.OR_ASSIGN:
+ tok = token.OR
+ case token.XOR_ASSIGN:
+ tok = token.XOR
+ case token.SHL_ASSIGN:
+ tok = token.SHL
+ case token.SHR_ASSIGN:
+ tok = token.SHR
+ case token.AND_NOT_ASSIGN:
+ tok = token.AND_NOT
+ default:
+ c.Logf("unexpected token: %v", stmt.Tok)
+ return false
+ }
+ binExpr := &dst.BinaryExpr{
+ X: sel2call(c, "Get", sel, nil, dst.NodeDecs{}),
+ Op: tok,
+ Y: rhs,
+ }
+ c.setType(binExpr, c.typeOf(lhs))
+
+ startEndDec := dst.NodeDecs{
+ Start: stmt.Decorations().Start,
+ End: stmt.Decorations().End,
+ }
+ selClone := cloneSelectorExpr(c, sel)
+ c.Replace(c.expr2stmt(sel2call(c, "Set", selClone, binExpr, startEndDec), selClone))
+
+ return false
+}
+
+func isNeverNilSliceExpr(c *cursor, e dst.Expr) bool {
+ // Is this a string to byte conversion?
+ if ce, ok := e.(*dst.CallExpr); ok && len(ce.Args) == 1 {
+ if bt, ok := c.typeOf(ce.Args[0]).(*types.Basic); ok && bt.Kind() == types.String {
+ if at, ok := ce.Fun.(*dst.ArrayType); ok {
+ if id, ok := at.Elt.(*dst.Ident); ok && id.Name == "byte" {
+ return true
+ }
+ }
+ }
+ }
+ if _, ok := e.(*dst.CompositeLit); ok {
+ if _, ok := c.typeOf(e).(*types.Slice); ok {
+ return true
+ }
+ }
+ se, ok := e.(*dst.SliceExpr)
+ if !ok {
+ return false
+ }
+ if bl, ok := se.Low.(*dst.BasicLit); ok && bl.Value != "0" {
+ return true
+ }
+ if bl, ok := se.High.(*dst.BasicLit); ok && bl.Value != "0" {
+ return true
+ }
+ return false
+}
+
+// rewriteFieldAssign rewrites a direct field assignment
+//
+// lhs = rhs where lhs is "m.F"
+//
+// to a form that works in the opaque proto API world.
+func rewriteFieldAssign(c *cursor, lhs, rhs dst.Expr, decs dst.NodeDecs) (dst.Stmt, bool) {
+ lhsSel, ok := c.protoFieldSelector(lhs)
+ if !ok {
+ c.Logf("ignoring: lhs is not a proto field selector")
+ return nil, false
+ }
+
+ // Drop parens around rhs, if any. Rhs becomes an argument to a function call. It's never
+ // necessary to keep it in parenthesis. One situation where rhs is a ParenExpr happens when it
+ // used to be a composite literal in a simple statement. For example:
+ //
+ // if _, M.F = nil, (&pb.M{}); {
+ if pe, ok := rhs.(*dst.ParenExpr); ok {
+ rhs = pe.X
+ }
+
+ // m.F = proto.{String, Int, ...}(V) => m.SetF(V)
+ if arg, ok := protoHelperCall(c, rhs); ok {
+ c.Logf("rewriting proto helper call")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true
+ }
+
+ // m.F = pb.EnumValue.Enum() => m.SetF(pb.EnumValue)
+ if enumVal, ok := enumHelperCall(c, rhs); ok {
+ c.Logf("rewriting Enum() call")
+
+ if t := c.typeOfOrNil(enumVal); t != nil {
+ if pt, ok := t.(*types.Pointer); ok {
+ enumVal = &dst.StarExpr{X: enumVal}
+ c.setType(enumVal, pt.Elem())
+ }
+ }
+
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, enumVal, decs), lhsSel), true
+ }
+
+ // m.F = new(MsgType) => m.SetF(new(MsgType))
+ // m.F = new(BasicType) => m.SetF(ZeroValueOf(BasicType))
+ // m.F = new(EnumType) => m.SetF(EnumType(0))
+ if arg, ok := newConstructorCall(c, rhs); ok {
+ c.Logf("rewriting constructor")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true
+ }
+
+ // m.F = nil => m.ClearF()
+ if ident, ok := rhs.(*dst.Ident); ok && ident.Name == "nil" {
+ c.Logf("rewriting nil assignment...")
+ if c.useClearOrHas(lhsSel) {
+ c.Logf("...with Clear()")
+ return c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, decs), lhsSel), true
+ }
+ c.Logf("...with Set()")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
+ }
+
+ // This condition is intentionally placed after the ClearF condition just
+ // above so that we do not need to handle the nil assignment case.
+ //
+ // m.F = ptrToEnum
+ // =>
+ // if ptrToEnum != nil {
+ // m1.SetF(*ptrToEnum)
+ // } else {
+ // m1.ClearF()
+ // }
+ if t := c.typeOfOrNil(lhsSel); t != nil && isPtr(t) {
+ et := t.Underlying().(*types.Pointer).Elem()
+ _, fieldCopy := c.trackedProtoFieldSelector(rhs)
+ c.Logf("isEnum(%v) = %v, fieldCopy = %v", et, isEnum(et), fieldCopy)
+ if !fieldCopy && isEnum(et) {
+ // special case for m.E = &eVal => m.SetE(eVal)
+ if ue, ok := rhs.(*dst.UnaryExpr); ok && ue.Op == token.AND {
+ stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, ue.X, decs), lhsSel)
+ return stmt, true
+ }
+ lhs2 := cloneSelectorExpr(c, lhsSel)
+ ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
+ ifStmt.Body.List = []dst.Stmt{
+ c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2),
+ }
+ return ifStmt, true
+ }
+ }
+
+ // m.F = []byte(v) => m.SetF([]byte(v))
+ // m.F = []byte("...") => m.SetF([]byte("..."))
+ if isBytesConversion(c, rhs) {
+ c.Logf("rewriting []byte conversion")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
+ }
+
+ // For proto2 bytes field, rewrite statement
+ // m.F = Expr
+ // =>
+ // if x := Expr; x != nil {
+ // m.SetF(x)
+ // }
+ //
+ // Or, if we cannot determine whether Clear() is safe to omit:
+ //
+ // if x := Expr; x != nil {
+ // m.SetF(x)
+ // } else {
+ // m.ClearF()
+ // }
+ //
+ // Can only rewrite for statements and not for expressions.
+ if _, ok := c.Parent().(*dst.BlockStmt); ok {
+ c.Logf("rewriting assignment with rhs Expr")
+ f := c.objectOf(lhsSel.Sel).(*types.Var)
+ isProto2 := !isProto3Field(c.typeOf(lhsSel.X), f.Name())
+ if slice, ok := f.Type().(*types.Slice); ok && isProto2 {
+ if basic, ok := slice.Elem().(*types.Basic); ok && basic.Kind() == types.Uint8 {
+ if isNeverNilSliceExpr(c, rhs) {
+ stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel)
+ return stmt, true
+ }
+ // Duplicate LHS so that it can be used in both "then" and "else" blocks.
+ // It's ok, regardless of the exact details of LHS, because:
+ // - before this change, LHS was evaluated exactly once, and
+ // - after this change, LHS is still evaluated exactly once
+ lhsSelClone := cloneSelectorExpr(c, lhsSel)
+ ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
+ ifStmt.Body.List = []dst.Stmt{
+ c.expr2stmt(sel2call(c, "Set", lhsSelClone, cloneExpr(c, v), dst.NodeDecs{}), lhsSelClone),
+ }
+ return ifStmt, true
+ }
+ }
+ }
+
+ // m.Oneof = &pb.M_Oneof{K: V} => m.SetK(V)
+ // m.Oneof = &pb.M_Oneof{V} => m.SetK(V)
+ // m.Oneof = &pb.M_Oneof{} => m.SetK(ZeroValueOf(BasicType))
+ //
+ // Where K is the field name of the sole field in M_Oneof.
+ if isOneof(c.typeOf(lhsSel.Sel)) {
+ c.Logf("rewriting oneof wrapper")
+ name, typ, val, oneofDecs, ok := destructureOneofWrapper(c, rhs)
+ if !ok {
+ c.Logf("ignoring: destructuring oneof wrapper failed")
+ if c.lvl.ge(Red) {
+ c.numUnsafeRewritesByReason[OneofFieldAccess]++
+ addCommentAbove(c.Node(), lhsSel.X, "// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).")
+ }
+ return nil, false
+ }
+ if val == nil {
+ if !isScalar(typ) {
+ c.Logf("...failed because lhs is not a scalar type")
+ return nil, false
+ }
+ val = scalarTypeZeroExpr(c, typ)
+ }
+ lhsSel.Sel.Name = name
+ c.Logf("...success")
+ if oneofDecs != nil {
+ decs.Start = append(decs.Start, (*oneofDecs).Start...)
+ decs.End = append(decs.End, (*oneofDecs).End...)
+ }
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, val, decs), lhsSel), true
+ }
+
+ // m1.F = m2.F
+ //
+ // [proto2]
+ // if m2.HasF() {
+ // m1.SetF(m2.GetF())
+ // } else {
+ // m1.ClearF()
+ // }
+ // (or variations based on existence of side effects when evaluating m1 or m2)
+ //
+ // [proto3]
+ // m1.SetF(m2.GetF())
+ isProto2 := isPtrToBasic(c.underlyingTypeOf(lhsSel)) // Bytes fields don't behave like other proto2 scalars.
+ if rhsSel, ok := c.trackedProtoFieldSelector(rhs); ok {
+ c.Logf("rewriting proto field to proto field assignment")
+ if !isProto2 {
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, sel2call(c, "Get", rhsSel, nil, dst.NodeDecs{}), decs), lhsSel), true
+ }
+
+ // If RHS has no side effects then just evaluate it inline. However, if it
+ // is not known to be side-effect free then evaluate it once, in the init
+ // statement.
+ var rhs1 *dst.SelectorExpr // We need two copies of rhs. They are identical.
+ var initStmt dst.Stmt
+ if c.isSideEffectFree(rhsSel) {
+ rhs1 = rhsSel
+ } else {
+ v := &dst.Ident{Name: "x"}
+ c.setType(v, c.typeOf(rhsSel.X))
+
+ initStmt = &dst.AssignStmt{
+ Lhs: []dst.Expr{v},
+ Tok: token.DEFINE,
+ Rhs: []dst.Expr{rhsSel.X},
+ }
+
+ rhs1 = &dst.SelectorExpr{
+ X: &dst.Ident{Name: v.Name},
+ Sel: rhsSel.Sel,
+ }
+ c.setType(rhs1, c.typeOf(rhsSel))
+ c.setType(rhs1.X, c.typeOf(v))
+ }
+ rhs2 := cloneSelectorExpr(c, rhs1)
+
+ lhs1, lhs2 := lhsSel, cloneSelectorExpr(c, lhsSel) // We need two copies of LHS. They are identical.
+ var elseStmt dst.Stmt
+ if clearNeeded(c, lhsSel) {
+ c.Logf("Clear() statement is needed")
+ elseStmt = c.expr2stmt(sel2call(c, "Clear", lhs1, nil, dst.NodeDecs{}), lhs1)
+ }
+
+ // Move end-of-line comments to above the if conditional.
+ if len(decs.End) > 0 {
+ decs.Start = append(decs.Start, decs.End...)
+ decs.End = nil
+ }
+ var stmt dst.Stmt = c.expr2stmt(sel2call(c, "Set", lhs2, sel2call(c, "Get", rhs2, nil, dst.NodeDecs{}), dst.NodeDecs{}), lhs2)
+ if hasNeeded(c, rhsSel) {
+ stmt = &dst.IfStmt{
+ Init: initStmt,
+ Cond: sel2call(c, "Has", rhs1, nil, dst.NodeDecs{}),
+ Body: &dst.BlockStmt{
+ List: []dst.Stmt{
+ stmt,
+ },
+ },
+ Else: elseStmt,
+ Decs: dst.IfStmtDecorations{NodeDecs: decs},
+ }
+ }
+ return stmt, true
+ }
+
+ // m.F = V => m.SetF(V)
+ if !isProto2 {
+ c.Logf("rewriting direct field access to setter")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
+ }
+
+ // m.F = &V => m.SetF(V)
+ if isAddr(rhs) {
+ c.Logf("rewriting direct field access to setter (rhs &Expr)")
+ return c.expr2stmt(sel2call(c, "Set", lhsSel, deref(c, rhs), decs), lhsSel), true
+ }
+
+ // m.F = V => m.SetF(*V) (red rewrite: loses aliasing)
+ if c.lvl.ge(Red) {
+ c.Logf("rewriting direct field access to setter (losing pointer aliasing)")
+ lhs2 := cloneSelectorExpr(c, lhsSel)
+ ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
+ ifStmt.Body.List = []dst.Stmt{
+ c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2),
+ }
+ c.numUnsafeRewritesByReason[PointerAlias]++
+ return ifStmt, true
+ }
+
+ c.Logf("no applicable rewrite found")
+ return nil, false
+}
+
+// clearNeeded figures out if for the specified SelectorExpr, a Clear() call
+// needs to be inserted before or not.
+//
+// clearNeeded looks at the AST of the current scope, considering all statements
+// between the initial assignment and the SelectorExpr:
+//
+// … // (not checked yet)
+// mm2 := &mypb.Message{} // initial assignment
+// mm2.SetBytes([]byte("hello world")) // checked by clearNeeded()
+// … // checked by clearNeeded()
+// mm2.I32 = 23 // SelectorExpr
+// proto.Merge(mm2, src) // (not checked anymore)
+// …
+func clearNeeded(c *cursor, sel *dst.SelectorExpr) bool {
+ // sel is something like dst.SelectorExpr{
+ // X: &dst.Ident{"mm2"},
+ // Sel: &dst.Ident{"I32"},
+ // }
+ if _, ok := sel.X.(*dst.Ident); !ok {
+ return true
+ }
+
+ innerMost, _ := c.enclosingASTStmt(sel.X)
+ if innerMost == nil {
+ return true
+ }
+ enclosing, ok := c.typesInfo.dstMap[innerMost]
+ if !ok {
+ c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost)
+ return true
+ }
+ first := compositLiteralInitialer(enclosing, sel.X.(*dst.Ident).Name)
+ if first == nil {
+ // The variable we are looking for is not initialized
+ // unconditionally in this scope.
+ return true
+ }
+ firstSeen := false
+ lastSeen := false
+ usageFound := false
+ var visit visitorFunc
+ xObj := c.objectOf(sel.X.(*dst.Ident))
+ selObj := c.objectOf(sel.Sel)
+ selName := fixConflictingNames(c.typeOf(sel.X), "Set", sel.Sel.Name)
+
+ visit = func(n dst.Node) dst.Visitor {
+ if n == first {
+ firstSeen = true
+ // Don't check the children of the definition because
+ // it would look like a usage.
+ return nil
+ }
+ if !firstSeen {
+ // As long as we have not seen the first node we don't
+ // need to look at any of the statements because they
+ // cannot influence first.
+ return visit
+ }
+ if lastSeen {
+ return nil
+ }
+
+ if as, ok := n.(*dst.AssignStmt); ok {
+ for _, lhs := range as.Lhs {
+ if lhs == sel {
+ lastSeen = true
+ // Skip recursing into children; all subsequent visit() calls
+ // will return immediately.
+ return nil
+ }
+
+ // Is the field assigned to?
+ if usesObject(c, lhs, xObj) && usesObject(c, lhs, selObj) {
+ usageFound = true
+ return nil
+ }
+ }
+ }
+
+ // Access is okay if it's not a setter for the field
+ // and if it is not assigned to (checked above).
+ if doesNotModifyField(c, n, selName) {
+ return nil
+ }
+
+ if id, ok := n.(*dst.Ident); ok && id.Name == sel.X.(*dst.Ident).Name {
+ c.Logf("found non-proto-field-selector usage of %q", id.Name)
+ usageFound = true
+ }
+
+ return visit // recurse into children
+ }
+ dst.Walk(visit, enclosing)
+ // Clear() calls are definitely needed when:
+ //
+ // 1. !firstSeen — we couldn’t find the declaration of <sel> in the
+ // innermost scope.
+ //
+ // 2. or !lastSeen — we couldn’t find the usage of <sel> (bug?)
+ //
+ // 3. or usageFound — we did find a usage that we didn’t expect.
+ return !firstSeen || !lastSeen || usageFound
+}
+
+// isMultiProtoAssign returns true if stmt is a multi-assignment that assigns at least one protocol
+// buffer field. Note that irregular assignments (e.g. "a,b := m[c]") are not considered to be
+// multi-assignments.
+func isMultiProtoAssign(c *cursor, stmt dst.Stmt) bool {
+ a, ok := stmt.(*dst.AssignStmt)
+ if !ok || len(a.Lhs) < 2 || len(a.Lhs) != len(a.Rhs) {
+ return false
+ }
+ for _, lhs := range a.Lhs {
+ if _, ok := c.trackedProtoFieldSelector(lhs); ok {
+ return true
+ }
+ }
+ return false
+}
+
+// isBytesConversion returns true if x is an explicit conversion to a slice of
+// bytes. That is, when x has the form "[]byte(...)".
+func isBytesConversion(c *cursor, x dst.Expr) bool {
+ call, ok := x.(*dst.CallExpr)
+ if !ok {
+ return false
+ }
+ fun, ok := call.Fun.(*dst.ArrayType)
+ if !ok {
+ return false
+ }
+ ident, ok := fun.Elt.(*dst.Ident)
+ if !ok {
+ return false
+ }
+ return c.objectOf(ident) == types.Universe.Lookup("byte")
+}
+
+// newConstructorCall returns true if x is a 'new' call. It also returns an
+// argument that should be provided to a corresponding proto setter if the new
+// call was to be replaced by a set call.
+func newConstructorCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
+ call, ok := expr.(*dst.CallExpr)
+ if !ok || len(call.Args) != 1 {
+ return nil, false
+ }
+ ident, ok := call.Fun.(*dst.Ident)
+ if !ok {
+ return nil, false
+ }
+ if c.objectOf(ident) != types.Universe.Lookup("new") {
+ return nil, false
+ }
+
+ t := c.typeOf(call.Args[0])
+ if t, ok := t.(*types.Basic); ok {
+ return scalarTypeZeroExpr(c, t), true
+ }
+
+ if _, ok := t.(*types.Named); !ok {
+ return nil, false
+ }
+
+ // Message
+ if _, ok := t.Underlying().(*types.Struct); ok {
+ return call, true // new(M) is fine
+ }
+
+ // Enum
+ if !isBasic(t.Underlying()) {
+ return nil, false
+ }
+ zero := &dst.Ident{Name: "0"}
+ c.setType(zero, types.Typ[types.UntypedInt])
+ conv := &dst.CallExpr{
+ Fun: call.Args[0],
+ Args: []dst.Expr{zero},
+ }
+ c.setType(conv, t)
+ return conv, true
+}
+
+// enumHelperCall returns true if expr is a enum helper call (e.g.
+// "pb.MyMessage_MyEnumVal.Enum()"). If so, it also returns the enum value
+// ("MyEnumVal" in the previous example).
+func enumHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
+ call, ok := expr.(*dst.CallExpr)
+ if !ok {
+ return nil, false
+ }
+ sel, ok := call.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return nil, false
+ }
+ if sel.Sel.Name != "Enum" {
+ return nil, false
+ }
+ var res dst.Expr = sel.X
+ // It is possible to use methods enums as if they were free functions by
+ // passing the receiver as first argument. We know that the Enum method
+ // does not have any parameters. This means if it is called with an argument
+ // it must be used as free function and the argument is the receiver, e.g.:
+ // (e.g. "pb.MyMessage_MyEnum.Enum(pb.MyMessage_MyEnumVal)").
+ //
+ // Note: we cannot use the type system to determine whether the free function
+ // or method is used because these are two are the same from the type
+ // systems's point of view.
+ if len(call.Args) == 1 {
+ res = call.Args[0]
+ }
+ return res, true
+}
+
+// isSelectorExprWithIdent return true if e is a simple selector expression where
+// X is of type *dst.Ident.
+func isSelectorExprWithIdent(e dst.Expr) bool {
+ rhsSel, ok := e.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ if _, ok := rhsSel.X.(*dst.Ident); !ok {
+ return false
+ }
+ return true
+}
+
+// ifNonNil returns a *dst.IfStmt that checks if rhs is not nil (if rhs is nil,
+// Clear() is called). If needed, a temporary variable (x) is introduced to only
+// evaluate rhs once. The returned *dst.Ident refers either to the temporary
+// variable (if needed) or to rhs. If possible, the Clear() call will be elided.
+func ifNonNil(c *cursor, lhsSel *dst.SelectorExpr, rhs dst.Expr, decs dst.NodeDecs) (*dst.IfStmt, dst.Expr) {
+ var elseStmt dst.Stmt
+ if clearNeeded(c, lhsSel) {
+ c.Logf("Clear() statement is needed")
+ elseStmt = c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, dst.NodeDecs{}), lhsSel)
+ }
+
+ var v dst.Expr
+ var initAssign dst.Stmt
+ if rhsIdent, ok := rhs.(*dst.Ident); ok {
+ // If RHS is already an identifier, we skip generating an
+ // initializer in the if statement (x := rhs) and just use
+ // the RHS directly.
+ v = rhsIdent
+ } else if isSelectorExprWithIdent(rhs) {
+ v = rhs
+ } else {
+ v = &dst.Ident{Name: "x"}
+ c.setType(v, c.typeOf(rhs))
+ initAssign = &dst.AssignStmt{
+ Lhs: []dst.Expr{cloneIdent(c, v.(*dst.Ident))},
+ Tok: token.DEFINE,
+ Rhs: []dst.Expr{rhs},
+ }
+ }
+
+ untypedNil := &dst.Ident{Name: "nil"}
+ c.setType(untypedNil, types.Typ[types.UntypedNil])
+ cond := &dst.BinaryExpr{
+ X: v,
+ Op: token.NEQ,
+ Y: untypedNil,
+ }
+ c.setType(cond, types.Typ[types.Bool])
+
+ // Move end-of-line comments to above the if conditional.
+ if len(decs.End) > 0 {
+ decs.Start = append(decs.Start, decs.End...)
+ decs.End = nil
+ }
+
+ return &dst.IfStmt{
+ Init: initAssign,
+ Cond: cond,
+ Body: &dst.BlockStmt{},
+ Else: elseStmt,
+ Decs: dst.IfStmtDecorations{NodeDecs: decs},
+ }, v
+}
+
+// doesNotModifyField returns true if n does not modify a proto field named name.
+func doesNotModifyField(c *cursor, n dst.Node, name string) bool {
+ if selFun, sig, ok := c.protoFieldSelectorOrAccessor(n); ok {
+ if sig == nil || selFun.Sel.Name != "Set"+name {
+ // Skip recursing into children: proto field selector usages are
+ // okay; we could still skip the Clear() methods.
+ return true
+ }
+ }
+ return false
+}
+
+// compositLiteralInitialer return the node that is a direct child of enclosing
+// and initialized a variabled named `name` unconditionally with a composite
+// literal. Returns nil if there is no such node.
+func compositLiteralInitialer(enclosing dst.Node, name string) dst.Node {
+ for _, n := range directChildren(enclosing) {
+ as, ok := n.(*dst.AssignStmt)
+ if !ok {
+ continue
+ }
+ if len(as.Lhs) != len(as.Rhs) {
+ continue
+ }
+ for i, lhs := range as.Lhs {
+ if id, ok := lhs.(*dst.Ident); ok && id.Name == name {
+ // We consider everything but composite literal
+ // initialization as unknown and thus unsafe.
+ if _, ok := isCompositeLit(as.Rhs[i], as); ok {
+ return n
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func usesObject(c *cursor, expr dst.Expr, obj types.Object) bool {
+ found := false
+ var visit visitorFunc
+ visit = func(n dst.Node) dst.Visitor {
+ id, ok := n.(*dst.Ident)
+ if !ok {
+ return visit
+ }
+
+ if c.objectOf(id) == obj {
+ found = true
+ return nil
+ }
+ return visit
+ }
+ dst.Walk(visit, expr)
+ return found
+}
+
+// directChildren returns a list of dst.Stmts if the n is a node opens a scope.
+func directChildren(n dst.Node) []dst.Stmt {
+ switch t := n.(type) {
+ case *dst.BlockStmt:
+ return t.List
+ case *dst.CaseClause:
+ return t.Body
+ case *dst.CommClause:
+ return t.Body
+ }
+ return nil
+}
+
+func deref(c *cursor, expr dst.Expr) dst.Expr {
+ ue, ok := expr.(*dst.UnaryExpr)
+ if ok {
+ return ue.X
+ }
+ out := &dst.StarExpr{X: expr}
+ c.setType(out, c.underlyingTypeOf(expr).(*types.Pointer).Elem())
+ return out
+}
+
+func stringIn(s string, ss []string) bool {
+ for _, v := range ss {
+ if s == v {
+ return true
+ }
+ }
+ return false
+}
+
+// protoHelperCall returns true if expr is a proto helper call (e.g. "proto.String(s)"). If so, it
+// also returns argument to the helper.
+func protoHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
+ call, ok := expr.(*dst.CallExpr)
+ if !ok {
+ return nil, false
+ }
+ sel, ok := call.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return nil, false
+ }
+ if sel.Sel.Name == "Int" && !isBasicLit(call.Args[0]) {
+ // "m.F = proto.Int(v)" => "m.SetF(int32(v))"
+ x := &dst.CallExpr{
+ Fun: dst.NewIdent("int32"),
+ Args: []dst.Expr{call.Args[0]},
+ }
+ c.setType(x, types.Universe.Lookup("int32").Type())
+ c.setType(x.Fun, types.Universe.Lookup("int32").Type())
+ return x, true
+ }
+ if !stringIn(sel.Sel.Name, []string{"Bool", "Float32", "Float64", "Int", "Int32", "Int64", "String", "Uint32", "Uint64"}) {
+ return nil, false
+ }
+ if c.objectOf(sel.Sel).Pkg().Path() != protoImport {
+ return nil, false
+ }
+ return call.Args[0], true
+}
+
+func isBasicLit(x dst.Expr) bool {
+ _, ok := x.(*dst.BasicLit)
+ return ok
+}
diff --git a/internal/fix/assign_test.go b/internal/fix/assign_test.go
new file mode 100644
index 0000000..7f72167
--- /dev/null
+++ b/internal/fix/assign_test.go
@@ -0,0 +1,1585 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestEliminateClearer(t *testing.T) {
+ tests := []test{
+ {
+ desc: "basic has/set without clearer",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{
+ I32: m2.I32,
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "helper and has/set without clearer",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{
+ M: &pb2.M2{
+ I32: m2.I32,
+ },
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+if m2.HasI32() {
+ m2h2.SetI32(m2.GetI32())
+}
+mypb := &pb2.M2{}
+mypb.SetM(m2h2)
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "clearer not safe: function call instead of empty composite literal",
+ srcfiles: []string{"pkg.go"},
+ extra: `
+func preparedProto() *pb2.M2 {
+ result := &pb2.M2{}
+ result.SetI32(42)
+ return result
+}
+`,
+ in: `
+mypb := preparedProto()
+mypb.I32 = m2.I32
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := preparedProto()
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "scope: clearer not safe: function call outside of scope",
+ srcfiles: []string{"pkg.go"},
+ extra: `
+func preparedProto() *pb2.M2 {
+ result := &pb2.M2{}
+ result.SetI32(42)
+ return result
+}
+`,
+ in: `
+mypb := preparedProto()
+if mypb.GetI32() > 0 {
+ mypb.I32 = m2.I32
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := preparedProto()
+if mypb.GetI32() > 0 {
+ if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+ } else {
+ mypb.ClearI32()
+ }
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "scope: message used inside condition body",
+ srcfiles: []string{"pkg.go"},
+ extra: `
+func externalCondition() bool { return true }
+`,
+ in: `
+mypb := &pb2.M2{}
+if externalCondition() {
+ mypb.I32 = m2.I32
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+if externalCondition() {
+ if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+ } else {
+ mypb.ClearI32()
+ }
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "scope: clearer not safe due to intermediate proto.Merge",
+ srcfiles: []string{"pkg.go"},
+ extra: `
+func externalCondition() bool { return true }
+`,
+ in: `
+mypb := &pb2.M2{}
+if externalCondition() {
+ proto.Merge(mypb, m2)
+}
+mypb.I32 = m2.I32
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+if externalCondition() {
+ proto.Merge(mypb, m2)
+}
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "scope: shadowing",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{}
+proto.Merge(mypb, m2)
+mypb.I32 = m2.I32
+{
+ mypb := &pb2.M2{}
+ mypb.I32 = m2.I32
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+proto.Merge(mypb, m2)
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+{
+ mypb := &pb2.M2{}
+ if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+ }
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "plain usage",
+ extra: `func f() *int32 {return nil }`,
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{}
+mypb.I32 = f()
+mypb.I32 = m2.I32
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+mypb.I32 = f()
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "setter",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{}
+mypb.SetI32(int32(42))
+mypb.I32 = m2.I32
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+mypb.SetI32(int32(42))
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "direct field assignment",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{}
+mypb.I32 = proto.Int32(int32(42))
+mypb.I32 = m2.I32
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+mypb.SetI32(int32(42))
+if m2.HasI32() {
+ mypb.SetI32(m2.GetI32())
+} else {
+ mypb.ClearI32()
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "conditional initialization",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = func(m *pb2.M2) {
+ if m == nil {
+ m = &pb2.M2{}
+ }
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = func(m *pb2.M2) {
+ if m == nil {
+ m = &pb2.M2{}
+ }
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ } else {
+ m.ClearI32()
+ }
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestProtoToProtoAssignWhenUpdatingOnlyOne(t *testing.T) {
+ // Before b/266919153, open2opaque would incorrectly not apply some of its
+ // rewrites when only part of an expression was matched by
+ // -types_to_update. For example, an assignment m2.I32 = other.I32 would
+ // only get rewritten correctly if the left *and* right side were in
+ // -types_to_update.
+
+ tests := []test{
+ {
+ desc: "int32",
+ typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.OtherProto2": true},
+ in: `
+other := new(pb2.OtherProto2)
+m2.I32 = other.I32
+`,
+ want: map[Level]string{
+ Red: `
+other := new(pb2.OtherProto2)
+if other.HasI32() {
+ m2.SetI32(other.GetI32())
+} else {
+ m2.ClearI32()
+}
+`,
+ },
+ },
+
+ {
+ desc: "int32, sides reversed",
+ typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.OtherProto2": true},
+ in: `
+other := new(pb2.OtherProto2)
+other.I32 = m2.I32
+`,
+ want: map[Level]string{
+ Red: `
+other := new(pb2.OtherProto2)
+if m2.I32 != nil {
+ other.SetI32(*m2.I32)
+} else {
+ other.ClearI32()
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestRemoveLinebreakForShortLines(t *testing.T) {
+ tests := []test{
+ {
+ desc: "short line",
+ srcfiles: []string{"pkg.go"},
+ extra: `func shortName(*pb2.M2) {}`,
+ in: `
+shortName(
+ &pb2.M2{
+ I32: proto.Int32(42),
+ },
+)
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+shortName(m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "short line, multiple parameters",
+ srcfiles: []string{"pkg.go"},
+ extra: `func shortName(*pb2.M2, *pb2.M2) {}`,
+ in: `
+shortName(
+ &pb2.M2{
+ I32: proto.Int32(42),
+ },
+ &pb2.M2{
+ I32: proto.Int32(42),
+ },
+)
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+m2h3 := &pb2.M2{}
+m2h3.SetI32(42)
+shortName(m2h2, m2h3)
+`,
+ },
+ },
+
+ {
+ desc: "short line, append",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var result []*pb2.M2
+result = append(result,
+ &pb2.M2{
+ I32: proto.Int32(42),
+ })
+`,
+ want: map[Level]string{
+ Green: `
+var result []*pb2.M2
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+result = append(result, m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "long line",
+ srcfiles: []string{"pkg.go"},
+ extra: `func veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine(*pb2.M2) {}`,
+ in: `
+veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine(
+ &pb2.M2{
+ I32: proto.Int32(42),
+ },
+)
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine(
+ m2h2,
+)
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestMultiAssign(t *testing.T) {
+ tests := []test{{
+ desc: "no rewrite when there are no protos involved",
+ extra: `
+type NotAProto struct {
+ S *string
+ Field struct{}
+}
+var a, b *NotAProto
+func g() *string { return nil }
+`,
+ in: "a.S, b.S = nil, g()",
+ want: map[Level]string{
+ Red: "a.S, b.S = nil, g()",
+ },
+ }, {
+ desc: "multi-clear",
+ in: `
+m2.B, m2.S, m2.Is, m2.Ms, m2.M, m2.Map = nil, nil, nil, nil, nil, nil
+m3.Is, m3.Ms, m3.M, m3.Map = nil, nil, nil, nil
+`,
+ want: map[Level]string{
+ Green: `
+m2.B, m2.S, m2.Is, m2.Ms, m2.M, m2.Map = nil, nil, nil, nil, nil, nil
+m3.Is, m3.Ms, m3.M, m3.Map = nil, nil, nil, nil
+`,
+ Yellow: `
+m2.ClearB()
+m2.ClearS()
+m2.SetIs(nil)
+m2.SetMs(nil)
+m2.ClearM()
+m2.SetMap(nil)
+m3.SetIs(nil)
+m3.SetMs(nil)
+m3.ClearM()
+m3.SetMap(nil)
+`,
+ },
+ }, {
+ desc: "multi-assign",
+ in: `
+m2.B, m2.S, m2.Is, m2.Ms, m2.M = proto.Bool(true), proto.String("s"), []int32{1}, []*pb2.M2{{},{}}, &pb2.M2{}
+m3.Is, m3.Ms, m3.M = []int32{1}, []*pb3.M3{{},{}}, &pb3.M3{}
+`,
+ want: map[Level]string{
+ Green: `
+m2.B, m2.S, m2.Is, m2.Ms, m2.M = proto.Bool(true), proto.String("s"), []int32{1}, []*pb2.M2{{}, {}}, &pb2.M2{}
+m3.Is, m3.Ms, m3.M = []int32{1}, []*pb3.M3{{}, {}}, &pb3.M3{}
+`,
+ Yellow: `
+m2.SetB(true)
+m2.SetS("s")
+m2.SetIs([]int32{1})
+m2.SetMs([]*pb2.M2{{}, {}})
+m2.SetM(&pb2.M2{})
+m3.SetIs([]int32{1})
+m3.SetMs([]*pb3.M3{{}, {}})
+m3.SetM(&pb3.M3{})
+`,
+ },
+ }, {
+ desc: "multi-assign mixed with non-proto",
+ in: `
+var n int
+_ = n
+m2.S, m2.S, n, m2.M = proto.String("s"), nil, 42, &pb2.M2{}
+`,
+ want: map[Level]string{
+ Green: `
+var n int
+_ = n
+m2.S, m2.S, n, m2.M = proto.String("s"), nil, 42, &pb2.M2{}
+`,
+ Yellow: `
+var n int
+_ = n
+m2.SetS("s")
+m2.ClearS()
+n = 42
+m2.SetM(&pb2.M2{})
+`,
+ },
+ }, {
+ desc: "set bytes field",
+ in: `
+var b []byte
+var x bool
+m2.Bytes, x = b, true
+_ = x
+`,
+ want: map[Level]string{
+ Green: `
+var b []byte
+var x bool
+m2.Bytes, x = b, true
+_ = x
+`,
+ Yellow: `
+var b []byte
+var x bool
+if b != nil {
+ m2.SetBytes(b)
+} else {
+ m2.ClearBytes()
+}
+x = true
+_ = x
+`,
+ },
+ }, {
+ desc: "proto3",
+ in: `
+m3.S, m3.M, m3.Is = "", &pb3.M3{}, []int32{1}
+`,
+ want: map[Level]string{
+ Green: `m3.S, m3.M, m3.Is = "", &pb3.M3{}, []int32{1}`,
+ Yellow: `
+m3.SetS("")
+m3.SetM(&pb3.M3{})
+m3.SetIs([]int32{1})
+`,
+ },
+ }, {
+ // Skipped: single multi-valued expressions are not supported yet
+ desc: "single multi-valued expression, maps",
+ in: `
+m := map[int]string{}
+
+m3.S, m3.B = m[1]
+
+var ok bool
+_ = ok
+m3.S, ok = m[1]
+`,
+ want: map[Level]string{
+ Red: `
+m := map[int]string{}
+
+m3.S, m3.B = m[1]
+
+var ok bool
+_ = ok
+m3.S, ok = m[1]
+`,
+ },
+ }, {
+ // Skipped: single multi-valued expressions are not supported yet
+ desc: "single multi-valued expression, maps",
+ in: `
+var s interface{} = "s"
+
+m3.S, m3.B = s.(string)
+
+var ok bool
+_ = ok
+m3.S, ok = s.(string)
+`,
+ want: map[Level]string{
+ Red: `
+var s interface{} = "s"
+
+m3.S, m3.B = s.(string)
+
+var ok bool
+_ = ok
+m3.S, ok = s.(string)
+`,
+ },
+ }, {
+ // Skipped: single multi-valued expressions are not supported yet
+ desc: "single multi-valued expression, maps",
+ extra: `func g() (string, *bool, bool) { return "", nil, false } `,
+ in: `
+var ok bool
+m3.S, m2.B, ok = g()
+_ = ok
+`,
+ want: map[Level]string{
+ Red: `
+var ok bool
+m3.S, m2.B, ok = g()
+_ = ok
+`,
+ },
+ }, {
+ desc: "no rewrite for init simple statement when there are no protos involved",
+ extra: `
+type NotAProto struct {
+ S *string
+ Field struct{}
+}
+var a, b *NotAProto
+func g() *string { return nil }
+`,
+ in: `if a.S, b.S = nil, g(); true {
+}
+`,
+ want: map[Level]string{
+ Red: `if a.S, b.S = nil, g(); true {
+}
+`,
+ },
+ }, {
+ desc: "if init simple statement",
+ in: `
+if m3.S, m3.M = "", (&pb3.M3{}); m3.B {
+ m3.B, m3.Is = true, nil
+}
+`,
+ want: map[Level]string{
+ Red: `
+m3.SetS("")
+m3.SetM(&pb3.M3{})
+
+if m3.GetB() {
+ m3.SetB(true)
+ m3.SetIs(nil)
+}
+`,
+ },
+ }, {
+ desc: "for init simple statement",
+ in: `
+for m3.S, m3.M = "", (&pb3.M3{}); m3.B; {
+ m3.B, m3.Is = true, nil
+}
+`,
+ want: map[Level]string{
+ Green: `
+for m3.S, m3.M = "", (&pb3.M3{}); m3.GetB(); {
+ m3.B, m3.Is = true, nil
+}
+`,
+ Yellow: `
+m3.SetS("")
+m3.SetM(&pb3.M3{})
+
+for m3.GetB() {
+ m3.SetB(true)
+ m3.SetIs(nil)
+}
+`,
+ },
+ }, {
+ desc: "simple for post statement",
+ skip: "support multi-assignment in post-statements",
+ in: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+}
+`,
+ want: map[Level]string{
+ Green: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+}
+`,
+ Yellow: `
+var n int
+for {
+ n++
+ m2.SetS("s")
+ m3.SetS("s")
+
+}
+`,
+ },
+ }, {
+ desc: "for post statement + continue",
+ skip: "support multi-assignment in post-statements",
+ in: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+ if n % 2==0 {
+ continue
+ }
+}
+`,
+ want: map[Level]string{
+ Green: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+ if n%2 == 0 {
+ continue
+ }
+}
+`,
+ Yellow: `
+var n int
+for {
+ n++
+ if n%2 == 0 {
+ goto postStmt
+
+ }
+postStmt:
+ m2.SetS("s")
+ m3.SetS("s")
+
+}
+`,
+ },
+ }, {
+ desc: "nested loops",
+ skip: "support multi-assignment in post-statements",
+ in: `
+for ; ; m3.S, m3.B = "", false {
+ continue
+ for {
+ continue
+ }
+}
+`,
+ want: map[Level]string{
+ Green: `
+for ; ; m3.S, m3.B = "", false {
+ continue
+ for {
+ continue
+ }
+}
+`,
+ Yellow: `
+for {
+ goto postStmt
+
+ for {
+ continue
+ }
+postStmt:
+ m3.SetS("")
+ m3.SetB(false)
+
+}
+`,
+ },
+ }, {
+ skip: "support nested rewritten loops with multi-assignment post-stmt",
+ desc: "nested rewritten loops",
+ in: `
+for ; ; m3.S, m3.B = "", false {
+ if true {
+ continue
+ }
+ for ; ; m3.S, m3.B = "", false{
+ continue
+ }
+}
+`,
+ want: map[Level]string{
+ Green: `
+for ; ; m3.S, m3.B = "", false {
+ if true {
+ continue
+ }
+ for ; ; m3.S, m3.B = "", false {
+ continue
+ }
+}
+`,
+ Yellow: `
+for {
+ if true {
+ goto postStmt
+ }
+ for {
+ goto postStmt2
+ poststmt:
+ m3.SetS("")
+ m3.SetB(false)
+ }
+postStmt:
+ m3.SetS("")
+ m3.SetB(false)
+}
+`,
+ },
+ }, {
+ // goto can't jump over declarations
+ desc: "for post statement + continue + declarations",
+ skip: "support multi-assignment in post-statements",
+ in: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+ if n%2==0 {
+ continue
+ }
+ m := 1
+ _ = m
+}
+`,
+ want: map[Level]string{
+ Yellow: `
+var n int
+for ; ; m2.S, m3.S = proto.String("s"), "s" {
+ n++
+ if n%2 == 0 {
+ continue
+ }
+ m := 1
+ _ = m
+}
+`,
+ // This is red because the resulting code is not very
+ // readable. It's better if it's manually inspected and
+ // rewritten.
+ Red: `
+var n int
+for ; ; func() {
+ m2.SetS("s")
+ m3.SetS("s")
+}() {
+ n++
+ if n%2 == 0 {
+ continue
+ }
+ m := 1
+ _ = m
+}
+`,
+ },
+ }, {
+ desc: "multi-assign field deref",
+ in: `
+var v int
+*m2.S, *m2.I32, v = "hello", 1, 42
+_ = v
+`,
+ want: map[Level]string{
+ Red: `
+var v int
+m2.SetS("hello")
+m2.SetI32(1)
+v = 42
+_ = v
+`,
+ },
+ }}
+
+ runTableTests(t, tests)
+}
+
+func TestBuildToSetRewrite(t *testing.T) {
+ const vars = `
+var bytes []byte
+var is []int32
+var m2s []*pb2.M2
+var m3s []*pb3.M3
+var m map[string]bool
+
+var b bool
+var f32 float32
+var f64 float64
+var i32 int32
+var i64 int64
+var ui32 uint32
+var ui64 uint64
+var s string
+var e2 pb2.M2_Enum
+var e3 pb3.M3_Enum
+
+var bPtr *bool
+var f32Ptr *float32
+var f64Ptr *float64
+var i32Ptr *int32
+var i64Ptr *int64
+var ui32Ptr *uint32
+var ui64Ptr *uint64
+var sPtr *string
+var e2Ptr *pb2.M2_Enum
+`
+
+ tests := []test{
+ {
+ desc: "use builders in tests",
+ srcfiles: []string{"code_test.go"},
+ in: `
+a := []*pb2.M2{
+ &pb2.M2{S: nil},
+}
+_ = a
+
+b := &pb2.M2{
+ M: &pb2.M2{S: nil},
+}
+_ = b
+
+c := &pb2.M2{
+ M: &pb2.M2{
+ S: nil,
+ },
+}
+_ = c
+`,
+ want: map[Level]string{
+ Yellow: `
+a := []*pb2.M2{
+ pb2.M2_builder{S: nil}.Build(),
+}
+_ = a
+
+b := pb2.M2_builder{
+ M: pb2.M2_builder{S: nil}.Build(),
+}.Build()
+_ = b
+
+c := pb2.M2_builder{
+ M: pb2.M2_builder{
+ S: nil,
+ }.Build(),
+}.Build()
+_ = c
+`,
+ },
+ },
+
+ {
+ desc: "use builders in codelabs",
+ srcfiles: []string{"spanner_codelab.go"},
+ in: `
+a := []*pb2.M2{
+ &pb2.M2{S: nil},
+}
+_ = a
+
+b := &pb2.M2{
+ M: &pb2.M2{S: nil},
+}
+_ = b
+
+c := &pb2.M2{
+ M: &pb2.M2{
+ S: nil,
+ },
+}
+_ = c
+`,
+ want: map[Level]string{
+ Yellow: `
+a := []*pb2.M2{
+ pb2.M2_builder{S: nil}.Build(),
+}
+_ = a
+
+b := pb2.M2_builder{
+ M: pb2.M2_builder{S: nil}.Build(),
+}.Build()
+_ = b
+
+c := pb2.M2_builder{
+ M: pb2.M2_builder{
+ S: nil,
+ }.Build(),
+}.Build()
+_ = c
+`,
+ },
+ },
+
+ {
+ desc: "use builders if configured",
+ srcfiles: []string{"code.go"},
+ builderTypes: map[string]bool{
+ "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2": true,
+ },
+ in: `
+a := []*pb2.M2{
+ &pb2.M2{S: nil},
+}
+_ = a
+`,
+ want: map[Level]string{
+ Green: `
+a := []*pb2.M2{
+ pb2.M2_builder{S: nil}.Build(),
+}
+_ = a
+`,
+ },
+ },
+
+ {
+ desc: "use builders if too deeply nested",
+ srcfiles: []string{"code.go"},
+ in: `
+_ = &pb2.M2{
+ M: &pb2.M2{
+ M: &pb2.M2{
+ M: &pb2.M2{
+ }, // 4 levels of nesting
+ },
+ },
+}
+ `,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ M: pb2.M2_builder{
+ M: pb2.M2_builder{
+ M: &pb2.M2{}, // 4 levels of nesting
+ }.Build(),
+ }.Build(),
+}.Build()
+`,
+ },
+ },
+
+ {
+ desc: "use builders (shallow nesting)",
+ srcfiles: []string{"code.go"},
+ in: `
+_ = &pb2.M2{
+ M: &pb2.M2{
+ M: &pb2.M2{
+ M: &pb2.M2{
+ }, // 4 levels of nesting
+ },
+ },
+ Ms: []*pb2.M2{
+ &pb2.M2{
+ I32: proto.Int32(23),
+ }, // 3 levels of nesting
+ },
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ M: pb2.M2_builder{
+ M: pb2.M2_builder{
+ M: &pb2.M2{}, // 4 levels of nesting
+ }.Build(),
+ }.Build(),
+ Ms: []*pb2.M2{
+ pb2.M2_builder{
+ I32: proto.Int32(23),
+ }.Build(), // 3 levels of nesting
+ },
+}.Build()
+`,
+ },
+ },
+
+ {
+ desc: "use builders if too many messages are involved",
+ srcfiles: []string{"code.go"},
+ in: `
+_ = &pb2.M2{
+ Ms: []*pb2.M2{
+ // four proto messages involved in the literal:
+ &pb2.M2{I32: proto.Int32(23)},
+ &pb2.M2{I32: proto.Int32(23)},
+ &pb2.M2{I32: proto.Int32(23)},
+ &pb2.M2{I32: proto.Int32(23)},
+ },
+}
+ `,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ Ms: []*pb2.M2{
+ // four proto messages involved in the literal:
+ pb2.M2_builder{I32: proto.Int32(23)}.Build(),
+ pb2.M2_builder{I32: proto.Int32(23)}.Build(),
+ pb2.M2_builder{I32: proto.Int32(23)}.Build(),
+ pb2.M2_builder{I32: proto.Int32(23)}.Build(),
+ },
+}.Build()
+`,
+ },
+ },
+
+ {
+ desc: "builders for one-liners in tests",
+ srcfiles: []string{"code_test.go"},
+ in: `
+a := &pb2.M2{S: nil}
+_ = a
+
+b := &pb2.M2{
+ S: nil,
+}
+_ = b
+`,
+ want: map[Level]string{
+ Yellow: `
+a := pb2.M2_builder{S: nil}.Build()
+_ = a
+
+b := pb2.M2_builder{
+ S: nil,
+}.Build()
+_ = b
+`,
+ },
+ },
+
+ {
+ desc: "setters for one-liners outside tests",
+ srcfiles: []string{"code.go"},
+ in: `
+a := &pb2.M2{S:nil}
+_ = a
+
+b := &pb2.M2{
+ S:nil,
+}
+_ = b
+`,
+ want: map[Level]string{
+ Yellow: `
+a := &pb2.M2{}
+a.ClearS()
+_ = a
+
+b := &pb2.M2{}
+b.ClearS()
+_ = b
+`,
+ },
+ },
+
+ {
+ desc: "clit to non-builder: proto2: new objs",
+ srcfiles: []string{"code.go"},
+ in: `
+mm2 := &pb2.M2{
+ B: proto.Bool(true),
+ Bytes: []byte("hello"),
+ F32: proto.Float32(1),
+ F64: proto.Float64(2),
+ I32: proto.Int32(3),
+ I64: proto.Int64(4),
+ Ui32: proto.Uint32(5),
+ Ui64: proto.Uint64(6),
+ S: proto.String("world"),
+ M: &pb2.M2{},
+ Is: []int32{10, 11},
+ Ms: []*pb2.M2{{}, {}},
+ Map: map[string]bool{"a": true},
+ E: pb2.M2_E_VAL.Enum(),
+}
+_ = mm2`,
+ want: map[Level]string{
+ Yellow: `
+mm2 := &pb2.M2{}
+mm2.SetB(true)
+mm2.SetBytes([]byte("hello"))
+mm2.SetF32(1)
+mm2.SetF64(2)
+mm2.SetI32(3)
+mm2.SetI64(4)
+mm2.SetUi32(5)
+mm2.SetUi64(6)
+mm2.SetS("world")
+mm2.SetM(&pb2.M2{})
+mm2.SetIs([]int32{10, 11})
+mm2.SetMs([]*pb2.M2{{}, {}})
+mm2.SetMap(map[string]bool{"a": true})
+mm2.SetE(pb2.M2_E_VAL)
+_ = mm2
+`,
+ },
+ },
+
+ {
+ desc: "clit to non-builder: proto3: new objs",
+ srcfiles: []string{"code.go"},
+ in: `
+mm3 := &pb3.M3{
+ B: true,
+ Bytes: []byte("hello"),
+ F32: 1,
+ F64: 2,
+ I32: 3,
+ I64: 4,
+ Ui32: 5,
+ Ui64: 6,
+ S: "world",
+ M: &pb3.M3{},
+ Is: []int32{10, 11},
+ Ms: []*pb3.M3{{}, {}},
+ Map: map[string]bool{"a": true},
+ E: pb3.M3_E_VAL,
+}
+_ = mm3
+`,
+ want: map[Level]string{
+ Yellow: `
+mm3 := &pb3.M3{}
+mm3.SetB(true)
+mm3.SetBytes([]byte("hello"))
+mm3.SetF32(1)
+mm3.SetF64(2)
+mm3.SetI32(3)
+mm3.SetI64(4)
+mm3.SetUi32(5)
+mm3.SetUi64(6)
+mm3.SetS("world")
+mm3.SetM(&pb3.M3{})
+mm3.SetIs([]int32{10, 11})
+mm3.SetMs([]*pb3.M3{{}, {}})
+mm3.SetMap(map[string]bool{"a": true})
+mm3.SetE(pb3.M3_E_VAL)
+_ = mm3
+`,
+ }},
+
+ {
+ desc: "clit to non-builder: proto2 vars",
+ srcfiles: []string{"code.go"},
+ extra: vars,
+ in: `
+mm2 := &pb2.M2{
+ M: m2,
+ Is: is,
+ Ms: m2s,
+ Map: m,
+}
+_ = mm2
+`,
+ want: map[Level]string{
+ Yellow: `
+mm2 := &pb2.M2{}
+mm2.SetM(m2)
+mm2.SetIs(is)
+mm2.SetMs(m2s)
+mm2.SetMap(m)
+_ = mm2
+`,
+ },
+ },
+
+ {
+ desc: "clit to non-builder: proto3 vars",
+ srcfiles: []string{"code.go"},
+ extra: vars,
+ in: `
+mm3 := &pb3.M3{
+ B: b,
+ F32: f32,
+ F64: f64,
+ I32: i32,
+ I64: i64,
+ Ui32: ui32,
+ Ui64: ui64,
+ S: s,
+ M: &pb3.M3{},
+ Is: is,
+ Ms: m3s,
+ Map: m,
+ E: e3,
+}
+_ = mm3
+`,
+ want: map[Level]string{
+ Yellow: `
+mm3 := &pb3.M3{}
+mm3.SetB(b)
+mm3.SetF32(f32)
+mm3.SetF64(f64)
+mm3.SetI32(i32)
+mm3.SetI64(i64)
+mm3.SetUi32(ui32)
+mm3.SetUi64(ui64)
+mm3.SetS(s)
+mm3.SetM(&pb3.M3{})
+mm3.SetIs(is)
+mm3.SetMs(m3s)
+mm3.SetMap(m)
+mm3.SetE(e3)
+_ = mm3
+`}}, {
+ desc: "clit to non-builder: preserve proto2 presence from message",
+ srcfiles: []string{"code.go"},
+ extra: vars,
+ in: `
+mm2 := &pb2.M2{
+ Bytes: m2.Bytes, // eol comment
+ B: m2.B,
+ F32: m2.F32,
+ F64: m2.F64,
+ I32: m2.I32,
+ I64: m2.I64,
+ Ui32: m2.Ui32,
+ Ui64: m2.Ui64,
+ S: m2.S,
+ E: m2.E,
+}
+_ = mm2
+`,
+ want: map[Level]string{
+ Yellow: `
+mm2 := &pb2.M2{}
+// eol comment
+if x := m2.GetBytes(); x != nil {
+ mm2.SetBytes(x)
+}
+if m2.HasB() {
+ mm2.SetB(m2.GetB())
+}
+if m2.HasF32() {
+ mm2.SetF32(m2.GetF32())
+}
+if m2.HasF64() {
+ mm2.SetF64(m2.GetF64())
+}
+if m2.HasI32() {
+ mm2.SetI32(m2.GetI32())
+}
+if m2.HasI64() {
+ mm2.SetI64(m2.GetI64())
+}
+if m2.HasUi32() {
+ mm2.SetUi32(m2.GetUi32())
+}
+if m2.HasUi64() {
+ mm2.SetUi64(m2.GetUi64())
+}
+if m2.HasS() {
+ mm2.SetS(m2.GetS())
+}
+if m2.HasE() {
+ mm2.SetE(m2.GetE())
+}
+_ = mm2
+`},
+ },
+
+ {
+ desc: "clit to non-builder: preserve proto2 presence from var",
+ srcfiles: []string{"code.go"},
+ extra: vars,
+ in: `
+mm2 := &pb2.M2{
+ Bytes: bytes,
+ B: bPtr,
+ F32: f32Ptr,
+ F64: f64Ptr,
+ I32: i32Ptr,
+ I64: i64Ptr,
+ Ui32: ui32Ptr,
+ Ui64: ui64Ptr,
+ S: sPtr,
+ E: e2Ptr,
+}
+_ = mm2
+`,
+ want: map[Level]string{
+ Yellow: `
+mm2 := &pb2.M2{}
+if bytes != nil {
+ mm2.SetBytes(bytes)
+}
+mm2.B = bPtr
+mm2.F32 = f32Ptr
+mm2.F64 = f64Ptr
+mm2.I32 = i32Ptr
+mm2.I64 = i64Ptr
+mm2.Ui32 = ui32Ptr
+mm2.Ui64 = ui64Ptr
+mm2.S = sPtr
+if e2Ptr != nil {
+ mm2.SetE(*e2Ptr)
+}
+_ = mm2
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestBuildersAreGreenInTests(t *testing.T) {
+ tt := test{
+ in: `
+_ = []*pb2.M2{
+ &pb2.M2{S: nil},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = []*pb2.M2{
+ pb2.M2_builder{S: nil}.Build(),
+}
+`,
+ },
+ }
+
+ runTableTest(t, tt)
+}
+
+// TestAssignOperations tests that assignment operations like token.ADD_ASSIGN (+=) are rewritten.
+func TestAssignOperations(t *testing.T) {
+ tests := []test{
+ {
+ desc: "ADD_ASSIGN proto2 int32",
+ in: `
+*m2.I32 += 5
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetI32(m2.GetI32() + 5)
+`,
+ },
+ },
+ {
+ desc: "SUB_ASSIGN proto3 int64",
+ in: `
+m3.I64 -= 5
+`,
+ want: map[Level]string{
+ Green: `
+m3.SetI64(m3.GetI64() - 5)
+`,
+ },
+ },
+ {
+ desc: "never nil enum",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var b pb2.M2_Enum
+m2.E = &b
+`,
+ want: map[Level]string{
+ Green: `
+var b pb2.M2_Enum
+m2.SetE(b)
+`,
+ },
+ },
+ {
+ desc: "QUO_ASSIGN proto2 float64 end-of-line-comment",
+ in: `
+*m2.F64 /= 5. // comment
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetF64(m2.GetF64() / 5.) // comment
+`,
+ },
+ },
+ {
+ desc: "AND_ASSIGN proto2 uint32 newline-before",
+ in: `
+_ = "something"
+
+*m2.Ui32 &= 42
+`,
+ want: map[Level]string{
+ Green: `
+_ = "something"
+
+m2.SetUi32(m2.GetUi32() & 42)
+`,
+ },
+ },
+ {
+ desc: "SHL_ASSIGN proto3 uint64 newline-after",
+ in: `
+m3.Ui64 <<= 2
+
+_ = "something"
+`,
+ want: map[Level]string{
+ Green: `
+m3.SetUi64(m3.GetUi64() << 2)
+
+_ = "something"
+`,
+ },
+ },
+ {
+ desc: "string-concatenation proto2",
+ in: `
+*m2.S = "hello "
+*m2.S += "world!"
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetS("hello ")
+m2.SetS(m2.GetS() + "world!")
+`,
+ },
+ },
+ {
+ desc: "non-proto DEFINE AssignStmt no-rewrite",
+ in: `
+x := 5
+_ = x
+`,
+ want: map[Level]string{
+ Green: `
+x := 5
+_ = x
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestNilCheck(t *testing.T) {
+ tt := test{
+ desc: "side effect free expression",
+ extra: `type mytype struct { e *pb2.M2_Enum }`,
+ in: `
+mt := mytype{}
+m := &pb2.M2{}
+m.E = mt.e
+_ = m
+`,
+ want: map[Level]string{
+ Green: `
+mt := mytype{}
+m := &pb2.M2{}
+if mt.e != nil {
+ m.SetE(*mt.e)
+}
+_ = m
+`,
+ },
+ }
+
+ runTableTest(t, tt)
+}
diff --git a/internal/fix/assignswap.go b/internal/fix/assignswap.go
new file mode 100644
index 0000000..0dee837
--- /dev/null
+++ b/internal/fix/assignswap.go
@@ -0,0 +1,149 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "go/token"
+ "strings"
+
+ "github.com/dave/dst"
+)
+
+// invariant: selFromExpr() must only be called on expressions for which
+// c.protoFieldSelectorOrAccessor() returns ok. assignmentIsSwap() ensures that
+// for all lhs/rhs expressions.
+func selFromExpr(c *cursor, expr dst.Expr) *dst.SelectorExpr {
+ if ce, ok := expr.(*dst.CallExpr); ok {
+ sel, _, _ := c.protoFieldSelectorOrAccessor(ce.Fun)
+ return sel
+ }
+ return expr.(*dst.SelectorExpr)
+}
+
+func idFromExpr(expr dst.Expr) *dst.Ident {
+ if expr == nil {
+ return nil
+ }
+ switch x := expr.(type) {
+ case *dst.Ident:
+ return x
+ case *dst.CallExpr:
+ return idFromExpr(x.Fun)
+ case *dst.SelectorExpr:
+ return idFromExpr(x.Sel)
+ default:
+ return nil
+ }
+}
+
+func assignmentIsSwap(c *cursor, stmt *dst.AssignStmt) error {
+ if stmt.Tok != token.ASSIGN {
+ return fmt.Errorf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", stmt.Tok)
+ }
+
+ if len(stmt.Lhs) != len(stmt.Rhs) {
+ return fmt.Errorf("ignoring AssignStmt with len(lhs)=%d != len(rhs)=%d (cannot be a swap)", len(stmt.Lhs), len(stmt.Rhs))
+ }
+
+ if len(stmt.Lhs) == 1 {
+ return fmt.Errorf("ignoring AssignStmt with 1 lhs/rhs (assignment, not swap)")
+ }
+
+ for _, expr := range append(append([]dst.Expr(nil), stmt.Lhs...), stmt.Rhs...) {
+ if ce, ok := expr.(*dst.CallExpr); ok {
+ expr = ce.Fun
+ }
+ se, ok := expr.(*dst.SelectorExpr)
+ if !ok {
+ return fmt.Errorf("ignoring AssignStmt: %T is not a SelectorExpr (cannot be a swap)", expr)
+ }
+ if idFromExpr(se.X) == nil {
+ return fmt.Errorf("ignoring AssignStmt: could not find Ident in SelectorExpr.X %T", expr)
+ }
+ sel, _, ok := c.protoFieldSelectorOrAccessor(expr)
+ if !ok {
+ return fmt.Errorf("ignoring AssignStmt: %T is not a proto field selector", expr)
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ continue // no type info (silo'ed?), assume tracked
+ }
+ if !c.shouldUpdateType(t) {
+ return fmt.Errorf("should not update type %v", t)
+ }
+ }
+
+ c.Logf("rewriting swap")
+
+ // As soon as we find any repetition among the left and right hand side, we
+ // treat the assignment as a swap.
+ for _, lhs := range stmt.Lhs {
+ lhsSel := selFromExpr(c, lhs)
+ lhsXObj := c.objectOf(idFromExpr(lhsSel.X))
+ lhsSelName := c.objectOf(lhsSel.Sel).Name()
+ for _, rhs := range stmt.Rhs {
+ rhsSel := selFromExpr(c, rhs)
+ rhsXObj := c.objectOf(idFromExpr(rhsSel.X))
+ rhsSelName := c.objectOf(rhsSel.Sel).Name()
+ // If the RHS is a function call (LHS of an assignment cannot be a
+ // function call), it must be a getter (the other accessors do not
+ // return a value), so remove the Get prefix to match the LHS name.
+ if _, ok := rhs.(*dst.CallExpr); ok {
+ rhsSelName = strings.TrimPrefix(rhsSelName, "Get")
+ }
+
+ if lhsXObj == rhsXObj && lhsSelName == rhsSelName {
+ return nil
+ }
+ }
+ }
+ return fmt.Errorf("ignoring AssignStmt: no repetitions among lhs/rhs")
+}
+
+// assignSwapPre splits swaps (m.F1, m.F2 = m.F2, m.F1) into two separate assign
+// statements which can then be rewritten by subsequent rewrite stages.
+func assignSwapPre(c *cursor) bool {
+ // NOTE(stapelberg): This stage is only yellow level because the subsequent
+ // getPost and assignPre stages only deal with pointer-typed variables in
+ // the yellow level. But, safety-wise, this could go into the green level.
+ if !c.lvl.ge(Yellow) {
+ return true
+ }
+
+ stmt, ok := c.Node().(*dst.AssignStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for AssignStmt)", c.Node())
+ return true
+ }
+ if err := assignmentIsSwap(c, stmt); err != nil {
+ c.Logf("%s", err.Error())
+ return true
+ }
+
+ assign2 := &dst.AssignStmt{
+ Lhs: stmt.Lhs,
+ Tok: token.ASSIGN,
+ Rhs: nil, // will be filled with helper variable names
+ }
+ stmt.Lhs = nil // will be filled with helper variable names
+ for _, rhs := range stmt.Rhs {
+ rhsSel := selFromExpr(c, rhs)
+ helperName := c.helperNameFor(rhs, c.typeOf(rhsSel.X))
+ helperIdent := &dst.Ident{Name: helperName}
+ updateASTMap(c, rhs, helperIdent)
+ c.setType(helperIdent, c.typeOf(rhs))
+ stmt.Lhs = append(stmt.Lhs, helperIdent)
+ assign2.Rhs = append(assign2.Rhs, cloneIdent(c, helperIdent))
+ }
+ stmt.Tok = token.DEFINE
+ assign2.Decorations().After = stmt.Decorations().After
+ assign2.Decorations().End = stmt.Decorations().End
+ stmt.Decorations().After = dst.None
+ stmt.Decorations().End = nil
+ c.InsertAfter(assign2)
+
+ return true
+}
diff --git a/internal/fix/assignswap_test.go b/internal/fix/assignswap_test.go
new file mode 100644
index 0000000..689b540
--- /dev/null
+++ b/internal/fix/assignswap_test.go
@@ -0,0 +1,230 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import "testing"
+
+func TestSetterSwap(t *testing.T) {
+ tests := []test{
+ {
+ desc: "int32 swap",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{
+ I32: proto.Int32(23),
+ Build: proto.Int32(42),
+}
+mypb.I32, mypb.Build = mypb.Build, mypb.I32
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+mypb.SetI32(23)
+mypb.SetBuild_(42)
+mypb.I32, mypb.Build = mypb.Build, mypb.I32
+`,
+ Yellow: `
+mypb := &pb2.M2{}
+mypb.SetI32(23)
+mypb.SetBuild_(42)
+m2h2, m2h3 := proto.ValueOrNil(mypb.HasBuild_(), mypb.GetBuild_), proto.ValueOrNil(mypb.HasI32(), mypb.GetI32)
+mypb.I32 = m2h2
+mypb.Build = m2h3
+`,
+ Red: `
+mypb := &pb2.M2{}
+mypb.SetI32(23)
+mypb.SetBuild_(42)
+m2h2, m2h3 := proto.ValueOrNil(mypb.HasBuild_(), mypb.GetBuild_), proto.ValueOrNil(mypb.HasI32(), mypb.GetI32)
+if m2h2 != nil {
+ mypb.SetI32(*m2h2)
+} else {
+ mypb.ClearI32()
+}
+if m2h3 != nil {
+ mypb.SetBuild_(*m2h3)
+} else {
+ mypb.ClearBuild_()
+}
+`,
+ },
+ },
+
+ {
+ desc: "int32 oneof copy",
+ srcfiles: []string{"pkg.go"},
+ extra: `func defaultVal() *pb2.M2 { return nil }`,
+ in: `
+mypb := &pb2.M2{
+ M: &pb2.M2{
+ OneofField: &pb2.M2_IntOneof{
+ IntOneof: 23,
+ },
+ },
+}
+mypb.M.OneofField = defaultVal().M.OneofField
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetIntOneof(23)
+mypb := &pb2.M2{}
+mypb.SetM(m2h2)
+mypb.GetM().OneofField = defaultVal().GetM().OneofField
+`,
+ Yellow: `
+m2h2 := &pb2.M2{}
+m2h2.SetIntOneof(23)
+mypb := &pb2.M2{}
+mypb.SetM(m2h2)
+mypb.GetM().OneofField = defaultVal().GetM().OneofField
+`,
+ },
+ },
+
+ {
+ desc: "int32 proto3 getter swap",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+}.Build()
+mypb.I32, mypb.SecondI32 = mypb.GetSecondI32(), mypb.GetI32()
+`,
+ want: map[Level]string{
+ Green: `
+mypb := pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+}.Build()
+mypb.I32, mypb.SecondI32 = mypb.GetSecondI32(), mypb.GetI32()
+`,
+ Yellow: `
+mypb := pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+}.Build()
+m3h2, m3h3 := mypb.GetSecondI32(), mypb.GetI32()
+mypb.SetI32(m3h2)
+mypb.SetSecondI32(m3h3)
+`,
+ Red: `
+mypb := pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+}.Build()
+m3h2, m3h3 := mypb.GetSecondI32(), mypb.GetI32()
+mypb.SetI32(m3h2)
+mypb.SetSecondI32(m3h3)
+`,
+ },
+ },
+
+ {
+ desc: "int32 nested proto3 getter swap",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+}.Build()
+mypb.M.I32, mypb.M.SecondI32 = mypb.M.GetSecondI32(), mypb.M.GetI32()
+ `,
+ want: map[Level]string{
+ Green: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+}.Build()
+mypb.GetM().I32, mypb.GetM().SecondI32 = mypb.GetM().GetSecondI32(), mypb.GetM().GetI32()
+`,
+ Yellow: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+}.Build()
+m3h2, m3h3 := mypb.GetM().GetSecondI32(), mypb.GetM().GetI32()
+mypb.GetM().SetI32(m3h2)
+mypb.GetM().SetSecondI32(m3h3)
+`,
+ Red: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+}.Build()
+m3h2, m3h3 := mypb.GetM().GetSecondI32(), mypb.GetM().GetI32()
+mypb.GetM().SetI32(m3h2)
+mypb.GetM().SetSecondI32(m3h3)
+`,
+ },
+ },
+
+ {
+ desc: "int32 double-nested proto3 getter swap",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+ }.Build(),
+}.Build()
+mypb.M.M.I32, mypb.M.M.SecondI32 = mypb.M.M.GetSecondI32(), mypb.M.M.GetI32()
+`,
+ want: map[Level]string{
+ Green: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+ }.Build(),
+}.Build()
+mypb.GetM().GetM().I32, mypb.GetM().GetM().SecondI32 = mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32()
+`,
+ Yellow: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+ }.Build(),
+}.Build()
+m3h2, m3h3 := mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32()
+mypb.GetM().GetM().SetI32(m3h2)
+mypb.GetM().GetM().SetSecondI32(m3h3)
+`,
+ Red: `
+mypb := pb3.M3_builder{
+ M: pb3.M3_builder{
+ M: pb3.M3_builder{
+ I32: 23,
+ SecondI32: 42,
+ }.Build(),
+ }.Build(),
+}.Build()
+m3h2, m3h3 := mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32()
+mypb.GetM().GetM().SetI32(m3h2)
+mypb.GetM().GetM().SetSecondI32(m3h3)
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/build.go b/internal/fix/build.go
new file mode 100644
index 0000000..19688ba
--- /dev/null
+++ b/internal/fix/build.go
@@ -0,0 +1,433 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+)
+
+// buildPost rewrites the code to use builders for object construction. This
+// function is executed by traversing the tree in postorder. It visits every
+// node (always returns true to continue the traversal) because Builder calls can
+// be nested.
+func buildPost(c *cursor) bool {
+ // &pb.M{F: V} => pb.M_builder{F: V}.Build()
+ if _, ok := c.Node().(*dst.UnaryExpr); ok {
+ if lit, ok := c.builderCLit(c.Node(), c.Parent()); ok {
+ if !c.useBuilder(lit) {
+ c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit))
+ return true
+ }
+ expr := c.Node().(dst.Expr)
+ incompleteRewrite := !updateBuilderElements(c, lit)
+ if incompleteRewrite && !c.lvl.ge(Red) {
+ c.Logf("returning, no builder elements updated")
+ return true
+ }
+ if call, ok := newBuildCall(c, c.typeOf(expr), lit.Type, lit, *expr.Decorations()); ok {
+ if incompleteRewrite {
+ c.ReplaceUnsafe(call, IncompleteRewrite)
+ } else {
+ c.Replace(call)
+ }
+ c.Logf("successfully generated builder")
+ }
+ return true
+ }
+ }
+
+ // K: {F: V} => K: pb.M_builder{F: V}.Build() for map KVs and slice/array KVs
+ //
+ // We assert on the key value here and not on the composite literal because:
+ // - we can only access the parent of the node, but not its grandparent
+ // - the relevant container, in case of KV composite literal values, is a grandparent as the KV is the parent.
+ // An alternative implementation would track parents of all nodes.
+ if kv, ok := c.Node().(*dst.KeyValueExpr); ok {
+ lit, ok := kv.Value.(*dst.CompositeLit)
+ if !ok || lit.Type != nil || len(lit.Elts) == 0 || !c.shouldUpdateType(c.typeOf(lit)) || !isPtr(c.typeOf(lit)) {
+ return true
+ }
+ if !c.useBuilder(lit) {
+ c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit))
+ return true
+ }
+ typ, ok := parentValPBType(c, kv)
+ if !ok {
+ return true
+ }
+ incompleteRewrite := !updateBuilderElements(c, lit)
+ if incompleteRewrite && !c.lvl.ge(Red) {
+ return true
+ }
+ if call, ok := newBuildCall(c, c.typeOf(lit), typ, lit, dst.NodeDecs{}); ok {
+ kv.Value = call
+ if incompleteRewrite {
+ c.ReplaceUnsafe(kv, IncompleteRewrite)
+ } else {
+ c.Replace(kv)
+ }
+ }
+ return true
+ }
+
+ // {F: V} => pb.M_builder{F: V}.Build() when {F: V} is a protobuf (e.g. "[]*pb.M{{F0: V0}, {F1:V1}}")
+ lit, ok := c.Node().(*dst.CompositeLit)
+ if !ok || lit.Type != nil || len(lit.Elts) == 0 || !c.shouldUpdateType(c.typeOf(lit)) || !isPtr(c.typeOf(lit)) {
+ return true
+ }
+ if !c.useBuilder(lit) {
+ c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit))
+ return true
+ }
+ typ, ok := parentValPBType(c, lit)
+ if !ok {
+ return true
+ }
+ incompleteRewrite := !updateBuilderElements(c, lit)
+ if incompleteRewrite && !c.lvl.ge(Red) {
+ return true
+ }
+ if call, ok := newBuildCall(c, c.typeOf(lit), typ, lit, dst.NodeDecs{}); ok {
+ if incompleteRewrite {
+ c.ReplaceUnsafe(call, IncompleteRewrite)
+ } else {
+ c.Replace(call)
+ }
+ }
+ return true
+}
+
+func isNeverNilExpr(c *cursor, e dst.Expr) bool {
+ // Is this taking the address of something?
+ if ue, ok := e.(*dst.UnaryExpr); ok && ue.Op == token.AND {
+ return true
+ }
+ // Is this a builder call?
+ if ce, ok := e.(*dst.CallExpr); ok && len(ce.Args) == 0 {
+ sel, ok := ce.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ if !c.isBuilderType(c.typeOf(sel.X)) {
+ return false
+ }
+ // As of 2023-06-29 there is only one function defined on the builder.
+ // Thus, technically this check is not needed but it is a second layer
+ // of defense.
+ if sel.Sel.Name != "Build" {
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+// updateBuilderElements does necessary rewrites to elements when changing a
+// composite literal into a builder.
+//
+// Returns ok==true if all elements could be handled and ok==false if there was
+// a case that we can't handle yet and hence didn't rewrite anything.
+func updateBuilderElements(c *cursor, lit *dst.CompositeLit) (ok bool) {
+ // A list of updates to execute if there are no hard cases.
+ var updates []func()
+
+ // Handle oneof fields in builders.
+ //
+ // pb.M{F: pb.M_Oneof{K: V}}
+ // pb.M{F: pb.M_Oneof{V}}
+ // pb.M{F: pb.M_Oneof{}}
+ // =>
+ // pb.M_builder{K: V'}.Build()
+ //
+ // Where
+ //
+ // F used to be the made up "oneof field"
+ // K is the name of the only field in the oneof wrapper for the field
+ // V' is V made into a pointer for basic types (it's a pointer already for
+ // other types). If V is not present then this is a pointer to the zero
+ // value for basic types and no rewrite for enums/messages.
+ for _, e := range lit.Elts {
+ c.Logf("updating composite literal element")
+ kv, ok := e.(*dst.KeyValueExpr)
+ if !ok {
+ c.Logf("skipping %T (looking for KeyValueExpr)", e)
+ continue
+ }
+
+ // Skip over fields that are not oneof.
+ if _, ok := c.underlyingTypeOf(kv.Key).(*types.Interface); !ok {
+ c.Logf("skipping none oneof field", e)
+ continue
+ }
+
+ // Check that the value is a oneof that we can rewrite: address of a
+ // composite literal with exactly one field that has a "oneof" tag
+ fieldName, fieldType, fieldValue, decs, ok := destructureOneofWrapper(c, kv.Value)
+ if !ok {
+ // RHS is not a oneof wrapper but a oneof field itself.
+ // Try generating an exhaustive list covering all cases.
+ updates, ok = generateOneofBuilderCases(c, updates, lit, kv)
+ if !ok {
+ c.Logf("failed to generate exhaustive list of oneof cases")
+ return false
+ }
+ // At this point we know that we can replace the oneof field with
+ // key value pairs for its cases.
+ e := e
+ updates = append(updates, func() {
+ var idx int
+ // We know that this always find the element because there is
+ // exactly one attempt to rewrite this KeyValueExpr.
+ for i, ne := range lit.Elts {
+ if e == ne {
+ idx = i
+ }
+ }
+ // Remove the KeyValueExpr for the oneof field since it was
+ // replaced by KeyValueExpr's for all cases in
+ // generateOneofBuilderCases().
+ lit.Elts = append(lit.Elts[:idx], lit.Elts[idx+1:]...)
+ })
+ c.Logf("generated exhaustive list of oneof cases")
+ continue
+ }
+
+ // Don't rewrite the oneof in
+ //
+ // &pb.M2{OneofField: &pb.M2_MsgOneof{}}
+ // &pb.M2{OneofField: &pb.M2_EnumOneof{}}
+ //
+ // It's a tricky case and should be very rare because "oneof
+ // with a type but without a value" is not a valid protocol buffers
+ // concept.
+ //
+ // It happens due to a mismatch between what's allowed in protocol buffers
+ // and the Go structs representing protocol buffers in the open API.
+ // This information will not be marshalled to the wire and the field
+ // is effectively discarded during marshalling when it does not have
+ // a value.
+ //
+ // The opaque API does not allow this. While the struct can technically
+ // represent this, you cannot use the API to bring the struct into
+ // this state.
+ //
+ // For enums: this should be rare and we don't want to guess the default
+ // value.
+ if fieldValue == nil && !isBasic(fieldType) {
+ c.Logf("returning: RHS is nil of Type %T (looking for types.Basic)", fieldType)
+ return false
+ }
+
+ // If the wrapped value can be nil, it is generally not safe to
+ // rewrite because this changes behavior from a set oneof field with
+ // type but no value to a completely unset oneof.
+ unsafeRewrite := false
+ if !isNeverNilExpr(c, fieldValue) && !isNeverNilSliceExpr(c, fieldValue) && !isBasic(fieldType) && !isEnum(fieldType) {
+ if !c.lvl.ge(Yellow) {
+ c.Logf("returning: RHS is nil of Type %T (looking for types.Basic)", fieldType)
+ return false
+ }
+ unsafeRewrite = true
+ }
+
+ // Handle `M{Oneof: OneofBasicField{}}`
+ if fieldValue == nil {
+ updates = append(updates, func() {
+ kv.Key.(*dst.Ident).Name = fieldName // Rename the key to field name from the oneof wrapper.
+ kv.Value = c.newProtoHelperCall(nil, fieldType.(*types.Basic))
+ })
+ c.Logf("generated RHS for field %v", fieldName)
+ continue
+ }
+
+ // We don't handle assigning literal integers to enum fields:
+ //
+ // 1. This is a rare way to set enums (most code uses enum
+ // constants, not integer literals)
+ //
+ // 2. Handling this case requires a lot of extra machinery. We
+ // must be able to construct AST by knowing only the type
+ // that we want. This requires inspecting imports, ensuring
+ // that they are not shadowed, and potentially adding new
+ // imports.
+ t := c.typeOf(fieldValue)
+ if _, ok := fieldValue.(*dst.BasicLit); ok && isEnum(t) {
+ c.Logf("returning: assignment of int literal to enum")
+ return false
+ }
+
+ // If it's not a pointer and not []byte then make it a pointer
+ // because everything in the builder is a pointer.
+ //
+ // In practice, isPtr checks if the type is a message because
+ // non-pointer, non-[]byte types in oneof wrappers are either
+ // enums (*types.Named) or basic types (*types.Basic).
+ if !isPtr(t) && !isSlice(t) {
+ fieldValue = c.newProtoHelperCall(fieldValue, t)
+ }
+
+ updates = append(updates, func() {
+ if decs != nil {
+ kv.Decorations().Start = append(kv.Decorations().Start, decs.Start...)
+ kv.Decorations().End = append(kv.Decorations().End, decs.End...)
+ }
+ kv.Key.(*dst.Ident).Name = fieldName // Rename the key to field name from the oneof wrapper.
+ kv.Value = fieldValue
+ if unsafeRewrite {
+ c.numUnsafeRewritesByReason[MaybeOneofChange]++
+ }
+ })
+ }
+
+ // If we are here then we're confident that we can rewrite the composite
+ // literal to a builder.
+
+ for _, u := range updates {
+ u()
+ }
+
+ // Rename fields to deal with naming conflicts:
+ for _, e := range lit.Elts {
+ kv, ok := e.(*dst.KeyValueExpr)
+ if !ok {
+ continue
+ }
+ sel, ok := kv.Key.(*dst.Ident)
+ if !ok {
+ continue
+ }
+ sel.Name = fixConflictingNames(c.typeOf(lit), "", sel.Name)
+ }
+
+ c.Logf("updated of expressions of composite literal")
+ return true
+}
+
+// parentValPBType returns the expression representing the type of x in the parent. For example:
+//
+// In:
+// []*pb.M{ // [1]
+// {M:nil}, // [2]
+// }
+// the return value for composite literal "{M:nil}" from line [2] is "pb.M" from line [1].
+//
+// In:
+//
+// map[int]*pb.M{ // [1]
+// 0: {M:nil}, // [2]
+// }
+// the return value for key-value expression "0: {M:nil}" on line [2] is "pb.M" from line [1].
+func parentValPBType(c *cursor, x dst.Expr) (dst.Expr, bool) {
+ plit, ok := c.Parent().(*dst.CompositeLit)
+ if !ok {
+ return nil, false
+ }
+ var typ dst.Expr
+ switch t := plit.Type.(type) {
+ case *dst.ArrayType:
+ typ = t.Elt
+ case *dst.MapType:
+ typ = t.Value
+ default:
+ return nil, false
+ }
+ se, ok := typ.(*dst.StarExpr)
+ if !ok {
+ return nil, false
+ }
+ return se.X, true
+}
+
+// newBuildCall wraps the provided elements in builder Build call for the provided type.
+// t is the type of Build() result (pointer to a message struct).
+// typ is DST representing the protobuf struct type (e.g. selector "pb.M").
+// lit is the source composite literal (e.g. "pb.M{F: V}"). It has a non-zero number of elements.
+func newBuildCall(c *cursor, t types.Type, typ dst.Expr, lit *dst.CompositeLit, parentDecs dst.NodeDecs) (dst.Expr, bool) {
+ sel, ok := typ.(*dst.SelectorExpr)
+ if !ok {
+ // Could happen if someone creates a new named type in their package. For example:
+ // type MyMsg pb.M
+ return nil, false
+ }
+
+ msgType := types.NewPointer(c.typeOf(sel.Sel)) // *pb.M
+ builder := &dst.SelectorExpr{
+ // Clone the selector in case we might duplicate it when we rewrite:
+ //
+ // []*pb.M{{F:nil}, {F:nil}}
+ //
+ // to:
+ //
+ // []*pb.M{
+ // pb.M_builder{F:nil}.Build(),
+ // pb.M_builder{F:nil}.Build(),
+ // }
+ X: cloneSelectorExpr(c, sel).X,
+ Sel: &dst.Ident{Name: sel.Sel.Name + "_builder"},
+ }
+ pkg := c.objectOf(sel.Sel).Pkg()
+ builderType := types.NewNamed( // pb.M_builder in the same package as pb.M
+ types.NewTypeName(token.NoPos, pkg, sel.Sel.Name+"_builder", nil),
+ types.NewStruct(nil, nil),
+ nil)
+ builderType.AddMethod(types.NewFunc(token.NoPos, pkg, "Build", types.NewSignature( // func (pb.M_Builder) Build() *pb.M
+ types.NewParam(token.NoPos, pkg, "_", builderType),
+ types.NewTuple(),
+ types.NewTuple(types.NewParam(token.NoPos, pkg, "_", msgType)),
+ false)))
+ c.setType(builder, builderType)
+ updateASTMap(c, typ, builder)
+ c.setType(builder.Sel, builderType)
+
+ builderLit := &dst.CompositeLit{
+ Type: builder,
+ Elts: lit.Elts,
+ }
+ c.setType(builderLit, builderType) // pb.M_builder{...} has the same type as pb.M_builder
+ updateASTMap(c, typ, builderLit)
+
+ // pb.M_builder{...}.Build is the only pb.M_builder method.
+ fun := &dst.SelectorExpr{
+ X: builderLit,
+ Sel: &dst.Ident{Name: "Build"},
+ }
+ c.setType(fun, builderType.Method(0).Type())
+ c.setType(fun.Sel, builderType.Method(0).Type())
+ updateASTMap(c, typ, fun)
+
+ // pb.M_builder{...}.Build() returns *pb.M
+ buildCall := &dst.CallExpr{Fun: fun}
+ c.setType(buildCall, msgType)
+ updateASTMap(c, typ, buildCall)
+
+ // Update decorations (comments and whitespace).
+ builderLit.Decs = lit.Decs
+ builderLit.Decs.After = dst.None
+ builderLit.Decs.End = nil
+
+ decs := dst.NodeDecs{
+ After: lit.Decs.After,
+ End: lit.Decs.End,
+ }
+ if b := parentDecs.Before; b != dst.None {
+ decs.Before = b
+ }
+ decs.Start = append(parentDecs.Start, decs.Start...)
+ decs.End = append(decs.End, parentDecs.End...)
+ if a := parentDecs.After; a != dst.None {
+ decs.After = a
+ }
+ buildCall.Decs.NodeDecs = decs
+
+ return buildCall, true
+}
+
+func isSlice(t types.Type) bool {
+ _, ok := t.Underlying().(*types.Slice)
+ return ok
+}
diff --git a/internal/fix/build_test.go b/internal/fix/build_test.go
new file mode 100644
index 0000000..c1b8768
--- /dev/null
+++ b/internal/fix/build_test.go
@@ -0,0 +1,660 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestBuild(t *testing.T) {
+ tests := []test{{
+ desc: "proto2: empty composite literal",
+ in: `
+msg := &pb2.M2{}
+msg = &pb2.M2{}
+msg.Ms = []*pb2.M2{{},{}}
+msg = func() *pb2.M2 {
+ return &pb2.M2{}
+}()
+func(*pb2.M2) { }(&pb2.M2{})
+_=msg
+`,
+ want: map[Level]string{
+ Green: `
+msg := &pb2.M2{}
+msg = &pb2.M2{}
+msg.SetMs([]*pb2.M2{{}, {}})
+msg = func() *pb2.M2 {
+ return &pb2.M2{}
+}()
+func(*pb2.M2) {}(&pb2.M2{})
+_ = msg
+`,
+ },
+ }, {
+ desc: "proto2: non-empty composite literal",
+ in: `
+msg := &pb2.M2{S:nil}
+msg = &pb2.M2{S:nil}
+msg.Ms = []*pb2.M2{{S:nil},{S:nil}}
+msg = func() *pb2.M2 {
+ return &pb2.M2{S:nil}
+}()
+func(*pb2.M2) {}(&pb2.M2{S:nil})
+_ = msg
+`,
+ want: map[Level]string{
+ Green: `
+msg := pb2.M2_builder{S: nil}.Build()
+msg = pb2.M2_builder{S: nil}.Build()
+msg.SetMs([]*pb2.M2{pb2.M2_builder{S: nil}.Build(), pb2.M2_builder{S: nil}.Build()})
+msg = func() *pb2.M2 {
+ return pb2.M2_builder{S: nil}.Build()
+}()
+func(*pb2.M2) {}(pb2.M2_builder{S: nil}.Build())
+_ = msg
+`,
+ },
+ }, {
+ desc: "proto3: empty composite literal",
+ in: `
+msg := &pb3.M3{}
+msg = &pb3.M3{}
+msg.Ms = []*pb3.M3{{},{}}
+msg = func() *pb3.M3 {
+ return &pb3.M3{}
+}()
+func(*pb3.M3) { }(&pb3.M3{})
+_=msg
+`,
+ want: map[Level]string{
+ Green: `
+msg := &pb3.M3{}
+msg = &pb3.M3{}
+msg.SetMs([]*pb3.M3{{}, {}})
+msg = func() *pb3.M3 {
+ return &pb3.M3{}
+}()
+func(*pb3.M3) {}(&pb3.M3{})
+_ = msg
+`,
+ },
+ }, {
+ desc: "proto3: non-empty composite literal",
+ in: `
+msg := &pb3.M3{M:nil}
+msg = &pb3.M3{M:nil}
+msg.Ms = []*pb3.M3{{M:nil},{M:nil}}
+msg = func() *pb3.M3 {
+ return &pb3.M3{M:nil}
+}()
+func(*pb3.M3) { }(&pb3.M3{M:nil})
+_=msg
+`,
+ want: map[Level]string{
+ Green: `
+msg := pb3.M3_builder{M: nil}.Build()
+msg = pb3.M3_builder{M: nil}.Build()
+msg.SetMs([]*pb3.M3{pb3.M3_builder{M: nil}.Build(), pb3.M3_builder{M: nil}.Build()})
+msg = func() *pb3.M3 {
+ return pb3.M3_builder{M: nil}.Build()
+}()
+func(*pb3.M3) {}(pb3.M3_builder{M: nil}.Build())
+_ = msg
+`,
+ },
+ }, {
+ desc: "builder naming conflict",
+ in: `
+_ = &pb2.M2{
+ Build: proto.Int32(1),
+}
+_ = []*pb2.M2{{Build: proto.Int32(1)}}
+_ = map[int]*pb2.M2{
+ 0: {Build: proto.Int32(1)},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ Build_: proto.Int32(1),
+}.Build()
+_ = []*pb2.M2{pb2.M2_builder{Build_: proto.Int32(1)}.Build()}
+_ = map[int]*pb2.M2{
+ 0: pb2.M2_builder{Build_: proto.Int32(1)}.Build(),
+}
+`,
+ },
+ }, {
+ desc: "proto2: scalars",
+ in: `
+_ = &pb2.M2{
+ B: proto.Bool(true),
+ F32: proto.Float32(1),
+ F64: proto.Float64(1),
+ I32: proto.Int32(1),
+ I64: proto.Int64(1),
+ Ui32: proto.Uint32(1),
+ Ui64: proto.Uint64(1),
+ S: proto.String("hello"),
+ E: pb2.M2_E_VAL.Enum(),
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ B: proto.Bool(true),
+ F32: proto.Float32(1),
+ F64: proto.Float64(1),
+ I32: proto.Int32(1),
+ I64: proto.Int64(1),
+ Ui32: proto.Uint32(1),
+ Ui64: proto.Uint64(1),
+ S: proto.String("hello"),
+ E: pb2.M2_E_VAL.Enum(),
+}.Build()
+`,
+ },
+ }, {
+ desc: "proto3: scalars",
+ in: `_ = &pb3.M3{
+ B: true,
+ F32: 1,
+ F64: 1,
+ I32: 1,
+ I64: 1,
+ Ui32: 1,
+ Ui64: 1,
+ S: "hello",
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb3.M3_builder{
+ B: true,
+ F32: 1,
+ F64: 1,
+ I32: 1,
+ I64: 1,
+ Ui32: 1,
+ Ui64: 1,
+ S: "hello",
+}.Build()
+`,
+ },
+ }, {
+ desc: "scalar slices",
+ in: `
+_ = &pb2.M2{Is: []int32{1, 2, 3}}
+_ = &pb3.M3{Is: []int32{1, 2, 3}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{Is: []int32{1, 2, 3}}.Build()
+_ = pb3.M3_builder{Is: []int32{1, 2, 3}}.Build()
+`,
+ },
+ }, {
+ desc: "msg slices",
+ in: `
+_ = &pb2.M2{Ms: []*pb2.M2{{}, &pb2.M2{}}}
+_ = &pb2.M2{Ms: []*pb2.M2{{M: nil}, {M: nil}}}
+_ = &pb3.M3{Ms: []*pb3.M3{{}, &pb3.M3{}}}
+_ = &pb3.M3{Ms: []*pb3.M3{{M: nil}, {M: nil}}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{Ms: []*pb2.M2{{}, &pb2.M2{}}}.Build()
+_ = pb2.M2_builder{Ms: []*pb2.M2{pb2.M2_builder{M: nil}.Build(), pb2.M2_builder{M: nil}.Build()}}.Build()
+_ = pb3.M3_builder{Ms: []*pb3.M3{{}, &pb3.M3{}}}.Build()
+_ = pb3.M3_builder{Ms: []*pb3.M3{pb3.M3_builder{M: nil}.Build(), pb3.M3_builder{M: nil}.Build()}}.Build()
+`,
+ },
+ }, {
+ desc: "key-value builders",
+ in: `
+_ = map[int]*pb2.M2{
+ 1: {},
+ 2: {M:nil},
+ 3: &pb2.M2{},
+ 4: &pb2.M2{M: nil},
+ 5: &pb2.M2{
+ Ms: []*pb2.M2{{},{M:nil},&pb2.M2{},&pb2.M2{M:nil}},
+ },
+}
+_ = [...]*pb2.M2 {
+ 1: {},
+ 2: {M:nil},
+ 3: &pb2.M2{},
+ 4: &pb2.M2{M: nil},
+ 5: &pb2.M2{
+ Ms: []*pb2.M2{{},{M:nil},&pb2.M2{},&pb2.M2{M:nil}},
+ },
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = map[int]*pb2.M2{
+ 1: {},
+ 2: pb2.M2_builder{M: nil}.Build(),
+ 3: &pb2.M2{},
+ 4: pb2.M2_builder{M: nil}.Build(),
+ 5: pb2.M2_builder{
+ Ms: []*pb2.M2{{}, pb2.M2_builder{M: nil}.Build(), &pb2.M2{}, pb2.M2_builder{M: nil}.Build()},
+ }.Build(),
+}
+_ = [...]*pb2.M2{
+ 1: {},
+ 2: pb2.M2_builder{M: nil}.Build(),
+ 3: &pb2.M2{},
+ 4: pb2.M2_builder{M: nil}.Build(),
+ 5: pb2.M2_builder{
+ Ms: []*pb2.M2{{}, pb2.M2_builder{M: nil}.Build(), &pb2.M2{}, pb2.M2_builder{M: nil}.Build()},
+ }.Build(),
+}
+`,
+ },
+ }, {
+ desc: "composite lit msg in slice",
+ in: `
+_ = []*pb2.M2{
+ &pb2.M2{S: nil},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = []*pb2.M2{
+ pb2.M2_builder{S: nil}.Build(),
+}
+`,
+ },
+ }, {
+ desc: "composite lit msg single line in slice",
+ in: `
+_ = []*pb2.M2{ &pb2.M2{S: nil} }
+`,
+ want: map[Level]string{
+ Green: `
+_ = []*pb2.M2{pb2.M2_builder{S: nil}.Build()}
+`,
+ },
+ }, {
+ desc: "multi-line msg slices",
+ in: `
+_ = []*pb2.M2{
+ &pb2.M2{},
+ &pb2.M2{M: nil},
+ &pb2.M2{
+ M: nil,
+ },
+}
+_ = []*pb3.M3{
+ {},
+ {B: true},
+ {
+ S: "hello",
+ },
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = []*pb2.M2{
+ &pb2.M2{},
+ pb2.M2_builder{M: nil}.Build(),
+ pb2.M2_builder{
+ M: nil,
+ }.Build(),
+}
+_ = []*pb3.M3{
+ {},
+ pb3.M3_builder{B: true}.Build(),
+ pb3.M3_builder{
+ S: "hello",
+ }.Build(),
+}
+`,
+ },
+ }, {
+ desc: "oneofs: easy cases",
+ in: `
+_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{"hello"}}
+_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{StringOneof: "hello"}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{[]byte("hello")}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte("hello")}}
+_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{pb2.M2_E_VAL}}
+_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{EnumOneof: pb2.M2_E_VAL}}
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{&pb2.M2{}}}
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{}}}
+_ = &pb3.M3{OneofField: &pb3.M3_StringOneof{"hello"}}
+_ = &pb3.M3{OneofField: &pb3.M3_StringOneof{StringOneof: "hello"}}
+_ = &pb3.M3{OneofField: &pb3.M3_BytesOneof{[]byte("hello")}}
+_ = &pb3.M3{OneofField: &pb3.M3_BytesOneof{BytesOneof: []byte("hello")}}
+_ = &pb3.M3{OneofField: &pb3.M3_EnumOneof{pb3.M3_E_VAL}}
+_ = &pb3.M3{OneofField: &pb3.M3_EnumOneof{EnumOneof: pb3.M3_E_VAL}}
+_ = &pb3.M3{OneofField: &pb3.M3_MsgOneof{&pb3.M3{}}}
+_ = &pb3.M3{OneofField: &pb3.M3_MsgOneof{MsgOneof: &pb3.M3{}}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{StringOneof: proto.String("hello")}.Build()
+_ = pb2.M2_builder{StringOneof: proto.String("hello")}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb2.M2_builder{EnumOneof: pb2.M2_E_VAL.Enum()}.Build()
+_ = pb2.M2_builder{EnumOneof: pb2.M2_E_VAL.Enum()}.Build()
+_ = pb2.M2_builder{MsgOneof: &pb2.M2{}}.Build()
+_ = pb2.M2_builder{MsgOneof: &pb2.M2{}}.Build()
+_ = pb3.M3_builder{StringOneof: proto.String("hello")}.Build()
+_ = pb3.M3_builder{StringOneof: proto.String("hello")}.Build()
+_ = pb3.M3_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb3.M3_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb3.M3_builder{EnumOneof: pb3.M3_E_VAL.Enum()}.Build()
+_ = pb3.M3_builder{EnumOneof: pb3.M3_E_VAL.Enum()}.Build()
+_ = pb3.M3_builder{MsgOneof: &pb3.M3{}}.Build()
+_ = pb3.M3_builder{MsgOneof: &pb3.M3{}}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: messages",
+ in: `
+_ = &pb2.M2{
+ OneofField: &pb2.M2_MsgOneof{
+ MsgOneof: &pb2.M2{M: nil},
+ },
+}
+
+_ = &pb2.M2{
+ OneofField: &pb2.M2_MsgOneof{&pb2.M2{M: nil}},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ MsgOneof: pb2.M2_builder{M: nil}.Build(),
+}.Build()
+
+_ = pb2.M2_builder{
+ MsgOneof: pb2.M2_builder{M: nil}.Build(),
+}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: literal int enums",
+ in: `
+_ = &pb3.M3{
+ OneofField: &pb3.M3_EnumOneof{42},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = &pb3.M3{
+ OneofField: &pb3.M3_EnumOneof{42},
+}
+`,
+ },
+ }, {
+ desc: "oneof: basic-type zero value",
+ in: `
+_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{}}
+_ = &pb2.M2{OneofField: &pb2.M2_IntOneof{}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{StringOneof: proto.String("")}.Build()
+_ = pb2.M2_builder{IntOneof: proto.Int64(0)}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: nested messages",
+ extra: `var mOuter2 pb2.M2Outer`,
+ in: `
+_ = &pb2.M2Outer{
+ OuterOneof: mOuter2.OuterOneof,
+ }
+
+_ = &pb2.M2Outer{
+ OuterOneof: &pb2.M2Outer_InnerMsg{
+ InnerMsg: &pb2.M2Outer_MInner{
+ InnerOneof: &pb2.M2Outer_MInner_StringInner{
+ StringInner: "Hello World!",
+ },
+ },
+ },
+ }
+`,
+ want: map[Level]string{
+ Red: `
+_ = pb2.M2Outer_builder{
+ InnerMsg: mOuter2.GetInnerMsg(),
+ StringOneof: proto.ValueOrNil(mOuter2.HasStringOneof(), mOuter2.GetStringOneof),
+}.Build()
+
+_ = pb2.M2Outer_builder{
+ InnerMsg: pb2.M2Outer_MInner_builder{
+ StringInner: proto.String("Hello World!"),
+ }.Build(),
+}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: preserve comments",
+ in: `
+_ = &pb2.M2{
+ // comment before
+ OneofField: /*comment1*/ m2.GetOneofField(), // eol comment
+ }
+`,
+ want: map[Level]string{
+ Yellow: `
+_ = pb2.M2_builder{
+ // comment before
+ BytesOneof:/*comment1*/ m2.GetBytesOneof(), // eol comment
+ EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof),
+ IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof),
+ MsgOneof: m2.GetMsgOneof(),
+ StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof),
+}.Build()
+`,
+ Red: `
+_ = pb2.M2_builder{
+ // comment before
+ BytesOneof:/*comment1*/ m2.GetBytesOneof(), // eol comment
+ EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof),
+ IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof),
+ MsgOneof: m2.GetMsgOneof(),
+ StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof),
+}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: preserve comments when using setters",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = &pb2.M2{
+ // comment before
+ OneofField: /*comment1*/ &pb2.M2_StringOneof{"hello"}, // eol comment
+ }
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+// comment before
+m2h2.SetStringOneof("hello") // eol comment
+_ = m2h2
+`,
+ },
+ }, {
+
+ desc: "oneofs: propagate oneof",
+ extra: `func F() *pb2.M2_MsgOneof {return nil}`,
+ in: `
+var scalarOneof *pb2.M2_StringOneof
+_ = &pb2.M2{OneofField: scalarOneof}
+
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+
+_ = &pb2.M2{OneofField: F()}
+
+ifaceOneof := m2.GetOneofField()
+_ = &pb2.M2{OneofField: ifaceOneof}
+
+_ = &pb2.M2{
+ OneofField: m2.GetOneofField(),
+ S: proto.String("42"),
+ }
+`,
+ want: map[Level]string{
+ Yellow: `
+var scalarOneof *pb2.M2_StringOneof
+_ = &pb2.M2{OneofField: scalarOneof}
+
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+
+_ = &pb2.M2{OneofField: F()}
+
+ifaceOneof := m2.GetOneofField()
+_ = &pb2.M2{OneofField: ifaceOneof}
+
+_ = pb2.M2_builder{
+ S: proto.String("42"),
+ BytesOneof: m2.GetBytesOneof(),
+ EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof),
+ IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof),
+ MsgOneof: m2.GetMsgOneof(),
+ StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof),
+}.Build()
+`,
+ Red: `
+var scalarOneof *pb2.M2_StringOneof
+_ = pb2.M2_builder{StringOneof: proto.String(scalarOneof.StringOneof)}.Build()
+
+var msgOneof *pb2.M2_MsgOneof
+_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msgOneof.MsgOneof)}.Build()
+
+_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(F().MsgOneof)}.Build()
+
+ifaceOneof := m2.GetOneofField()
+_ = pb2.M2_builder{OneofField: ifaceOneof}.Build()
+
+_ = pb2.M2_builder{
+ S: proto.String("42"),
+ BytesOneof: m2.GetBytesOneof(),
+ EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof),
+ IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof),
+ MsgOneof: m2.GetMsgOneof(),
+ StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof),
+}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: potentially nil message",
+ in: `
+msg := &pb2.M2{}
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}}
+`,
+ want: map[Level]string{
+ Green: `
+msg := &pb2.M2{}
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}}
+`,
+ Red: `
+msg := &pb2.M2{}
+_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msg)}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: msg zero value",
+ in: `
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{}}
+`,
+ Red: `
+_ = pb2.M2_builder{OneofField: &pb2.M2_MsgOneof{}}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: enum zero value", // no rewrite (see comments in rules.go)
+ in: `
+_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{}}
+`,
+ Red: `
+_ = pb2.M2_builder{OneofField: &pb2.M2_EnumOneof{}}.Build()
+`,
+ },
+ }, {
+ desc: "oneofs: oneofs builder rewrite",
+ extra: `func F() *pb2.M2_MsgOneof2 {return nil}`,
+ in: `
+msg := &pb2.M2{}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_EnumOneof2{},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_MsgOneof2{msg},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_MsgOneof2{},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: F(),
+}
+`,
+ want: map[Level]string{
+ Green: `
+msg := &pb2.M2{}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_EnumOneof2{},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_MsgOneof2{msg},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_MsgOneof2{},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: F(),
+}
+`,
+ Yellow: `
+msg := &pb2.M2{}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_EnumOneof2{},
+}
+_ = pb2.M2_builder{
+ StringOneof: proto.String("hello"),
+ MsgOneof2: protooneofdefault.ValueOrDefault(msg),
+}.Build()
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: &pb2.M2_MsgOneof2{},
+}
+_ = &pb2.M2{
+ OneofField: &pb2.M2_StringOneof{"hello"},
+ OneofField2: F(),
+}
+`,
+ },
+ }}
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/clone.go b/internal/fix/clone.go
new file mode 100644
index 0000000..9f54522
--- /dev/null
+++ b/internal/fix/clone.go
@@ -0,0 +1,176 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+func cloneSelectorExpr(c *cursor, src *dst.SelectorExpr) *dst.SelectorExpr {
+ out := dst.Clone(src).(*dst.SelectorExpr)
+
+ // Match type information for all subexpressions to corresponding type
+ // information from the source.
+ //
+ // Call c.setType for all dst.Expr.
+ // Call c.setUse for all *dst.Ident.
+ var cloneTypes func(from, to reflect.Value)
+ cloneTypes = func(from, to reflect.Value) {
+ if !from.IsValid() || !to.IsValid() || from.IsZero() || to.IsZero() || isNil(from) || isNil(to) {
+ return
+ }
+ // from and to are clones so we assume that they are structurally
+ // similar. Unfortunately, they are not actually the same as dst.Clone
+ // loses (for example) information about Object references.
+ if from.Kind() != to.Kind() { // A cheap sanity check.
+ panic(fmt.Sprintf("bad kind %s vs %s (%+v vs %+v)", from.Kind(), to.Kind(), from, to))
+ }
+ switch from.Kind() {
+ case reflect.Array, reflect.Slice:
+ for i := 0; i < from.Len(); i++ {
+ cloneTypes(from.Index(i), to.Index(i))
+ }
+ case reflect.Interface:
+ if from.IsNil() {
+ return
+ }
+ cloneTypes(from.Elem(), to.Elem())
+ case reflect.Map:
+ // The DST package doesn't use maps for anything significant here.
+ panic("map is not supported")
+ case reflect.Ptr:
+ if from.IsNil() {
+ return
+ }
+ cloneTypes(from.Elem(), to.Elem())
+ case reflect.Struct:
+ for i := 0; i < from.NumField(); i++ {
+ cloneTypes(from.Field(i), to.Field(i))
+ }
+ }
+ if from.Type().AssignableTo(dstExprType) && from.Kind() != reflect.Interface {
+ c.setType(to.Interface().(dst.Expr), c.typeOf(from.Interface().(dst.Expr)))
+ }
+ if from.Type() == dstIdentType {
+ // If the source identifier has an unknown use then don't try to
+ // propagate it to the new identifier. This could happen for manually
+ // created dst.Ident and we are currently really bad at maintaining
+ // that use information.
+ if use := c.objectOf(from.Interface().(*dst.Ident)); use != nil {
+ c.setUse(to.Interface().(*dst.Ident), use)
+ }
+ }
+ }
+ cloneTypes(reflect.ValueOf(src), reflect.ValueOf(out))
+ updateAstMapTree(c, src, out)
+ return out
+}
+
+func cloneIdent(c *cursor, ident *dst.Ident) *dst.Ident {
+ out := &dst.Ident{
+ Name: ident.Name,
+ Path: ident.Path,
+ }
+ c.setType(out, c.typeOf(ident))
+ if use := c.objectOf(ident); use != nil {
+ c.setUse(out, use)
+ }
+ updateASTMap(c, ident, out)
+ return out
+}
+
+// cloneSelectorCallExpr clones call expression of the form "m.F()"
+func cloneSelectorCallExpr(c *cursor, src *dst.CallExpr) *dst.CallExpr {
+ sel, ok := src.Fun.(*dst.SelectorExpr)
+ if !ok {
+ panic("invalid cloneSelectorCallExpr call: src.Fun must be a selector")
+ }
+ call := &dst.CallExpr{
+ Fun: cloneSelectorExpr(c, sel),
+ }
+ c.setType(call, c.typeOf(src))
+ return call
+}
+
+// cloneIndexExpr clones call expression of the form "m.F()"
+func cloneIndexExpr(c *cursor, src *dst.IndexExpr) *dst.IndexExpr {
+ idx := &dst.IndexExpr{
+ X: cloneExpr(c, src.X),
+ Index: cloneExpr(c, src.Index),
+ }
+ c.setType(idx.X, c.typeOf(src.X))
+ c.setType(idx.Index, c.typeOf(src.Index))
+ c.setType(idx, c.typeOf(src))
+ return idx
+}
+
+// cloneIndexExpr clones call expression of the form "m.F()"
+func cloneBasicLit(c *cursor, src *dst.BasicLit) *dst.BasicLit {
+ bl := &dst.BasicLit{
+ Kind: src.Kind,
+ Value: src.Value,
+ }
+ c.setType(bl, c.typeOf(src))
+ return bl
+}
+
+// cloneExpr dispatches to any of the functions above.
+func cloneExpr(c *cursor, src dst.Expr) dst.Expr {
+ switch v := src.(type) {
+ case *dst.Ident:
+ return cloneIdent(c, v)
+ case *dst.IndexExpr:
+ return cloneIndexExpr(c, v)
+ case *dst.BasicLit:
+ return cloneBasicLit(c, v)
+ case *dst.CallExpr:
+ return cloneSelectorCallExpr(c, v)
+ case *dst.SelectorExpr:
+ return cloneSelectorExpr(c, v)
+ default:
+ panic(fmt.Sprintf("unhandled type for cloneExpr %T", src))
+ }
+}
+
+func isNil(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ return v.IsNil()
+ default:
+ return false
+ }
+}
+
+// updateAstMapTree updates a the astMap for a cloned dst tree. This function
+// assumes that source and copy are exact copies of each other. After this
+// function each node in copy will be mapped to the same ast node as the
+// associated node in source.
+func updateAstMapTree(c *cursor, source, copy dst.Node) {
+ var nodes []dst.Node
+ dstutil.Apply(source, func(cur *dstutil.Cursor) bool {
+ nodes = append(nodes, cur.Node())
+ return true
+ }, nil)
+ cnt := 0
+ dstutil.Apply(copy, func(cur *dstutil.Cursor) bool {
+ updateASTMap(c, nodes[cnt], cur.Node())
+ cnt++
+ return true
+ }, nil)
+}
+
+func updateASTMap(c *cursor, source dst.Node, copy dst.Node) {
+ // Retain the mapping between dave/dst and go/ast nodes.
+ // This is necessary for looking up position information.
+ // The cloned ident will not be at the same position as
+ // its source, but it will be close enough for our purposes.
+ if n, ok := c.typesInfo.astMap[source]; ok {
+ c.typesInfo.astMap[copy] = n
+ }
+}
diff --git a/internal/fix/conflictingname.go b/internal/fix/conflictingname.go
new file mode 100644
index 0000000..c0cae08
--- /dev/null
+++ b/internal/fix/conflictingname.go
@@ -0,0 +1,53 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/types"
+ "strings"
+)
+
+func fixConflictingNames(t types.Type, prefix, name string) string {
+ if prefix != "" {
+ if pt, ok := t.(*types.Pointer); ok {
+ t = pt.Elem()
+ }
+ st := t.(*types.Named).Underlying().(*types.Struct)
+ for i := 0; i < st.NumFields(); i++ {
+ if st.Field(i).Name() == prefix+name {
+ return "_" + name
+ }
+ }
+ }
+ // A "build" field in the protocol buffer results in a "Build_" field in
+ // the builder (the "_" is added to avoid conflicts with the "Build"
+ // method). As a result, the generator renames all "Build" accessor
+ // methods in the message (?!).
+ if name == "Build" {
+ name = "Build_"
+ }
+ // Before the migration, the following names conflicted with methods on
+ // the message struct. The corresponding accessor methods are deprecated
+ // and we should use versions without the "_" suffix.
+ for _, s := range []string{"Reset_", "String_", "ProtoMessage_", "Descriptor_"} {
+ if name == s {
+ name = strings.TrimSuffix(name, "_")
+ }
+ }
+ return name
+}
+
+// See usedNames in
+// https://go.googlesource.com/protobuf/+/refs/heads/master/compiler/protogen/protogen.go
+var conflictingNames = map[string]bool{
+ "Reset": true,
+ "String": true,
+ "ProtoMessage": true,
+ "Marshal": true,
+ "Unmarshal": true,
+ "ExtensionRangeArray": true,
+ "ExtensionMap": true,
+ "Descriptor": true,
+}
diff --git a/internal/fix/conflictingname_test.go b/internal/fix/conflictingname_test.go
new file mode 100644
index 0000000..22ed204
--- /dev/null
+++ b/internal/fix/conflictingname_test.go
@@ -0,0 +1,139 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestConflictingName(t *testing.T) {
+ tests := []test{
+ {
+ desc: "resolve conflict with Build",
+ in: `
+_ = &pb2.M2{
+ Build: proto.Int32(1),
+}
+
+_ = *m2.Build
+_ = m2.Build != nil
+
+m2.Build = proto.Int32(1)
+m2.Build = nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ Build_: proto.Int32(1),
+}.Build()
+
+_ = m2.GetBuild_()
+_ = m2.HasBuild_()
+
+m2.SetBuild_(1)
+m2.ClearBuild_()
+`,
+ },
+ },
+
+ {
+ desc: "resolve conflict with message methods",
+ in: `
+_ = &pb2.M2{
+ ProtoMessage_: proto.Int32(1),
+ Reset_: proto.Int32(1),
+ String_: proto.Int32(1),
+ Descriptor_: proto.Int32(1),
+}
+
+_ = *m2.ProtoMessage_
+_ = m2.ProtoMessage_ != nil
+m2.ProtoMessage_ = nil
+m2.ProtoMessage_ = proto.Int32(1)
+
+_ = *m2.Reset_
+_ = m2.Reset_ != nil
+m2.Reset_ = nil
+m2.Reset_ = proto.Int32(1)
+
+_ = *m2.String_
+_ = m2.String_ != nil
+m2.String_ = nil
+m2.String_ = proto.Int32(1)
+
+_ = *m2.Descriptor_
+_ = m2.Descriptor_ != nil
+m2.Descriptor_ = nil
+m2.Descriptor_ = proto.Int32(1)
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ ProtoMessage: proto.Int32(1),
+ Reset: proto.Int32(1),
+ String: proto.Int32(1),
+ Descriptor: proto.Int32(1),
+}.Build()
+
+_ = m2.GetProtoMessage()
+_ = m2.HasProtoMessage()
+m2.ClearProtoMessage()
+m2.SetProtoMessage(1)
+
+_ = m2.GetReset()
+_ = m2.HasReset()
+m2.ClearReset()
+m2.SetReset(1)
+
+_ = m2.GetString()
+_ = m2.HasString()
+m2.ClearString()
+m2.SetString(1)
+
+_ = m2.GetDescriptor()
+_ = m2.HasDescriptor()
+m2.ClearDescriptor()
+m2.SetDescriptor(1)
+`,
+ },
+ },
+
+ {
+ desc: "resolve conflict with message methods due to oneofs",
+ in: `
+_ = &pb3.M3{
+ OneofField: &pb3.M3_ProtoMessage_{""},
+}
+_ = &pb3.M3{
+ OneofField: &pb3.M3_Reset_{""},
+}
+_ = &pb3.M3{
+ OneofField: &pb3.M3_String_{""},
+}
+_ = &pb3.M3{
+ OneofField: &pb3.M3_Descriptor_{""},
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb3.M3_builder{
+ ProtoMessage: proto.String(""),
+}.Build()
+_ = pb3.M3_builder{
+ Reset: proto.String(""),
+}.Build()
+_ = pb3.M3_builder{
+ String: proto.String(""),
+}.Build()
+_ = pb3.M3_builder{
+ Descriptor: proto.String(""),
+}.Build()
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/converttosetter.go b/internal/fix/converttosetter.go
new file mode 100644
index 0000000..bc81283
--- /dev/null
+++ b/internal/fix/converttosetter.go
@@ -0,0 +1,374 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+// convertToSetterPost rewrites all non-empty composite literals into setters.
+// For example:
+//
+// m := &pb.M{
+// StringField: proto.String("Hello"),
+// }
+//
+// =>
+//
+// m := &pb.M{}
+// m.SetStringField("Hello")
+//
+// That is, we DO NOT do the following:
+//
+// m := pb.M_builder{
+// StringField: proto.String("Hello"),
+// }.Build()
+//
+// We use setters instead of builders to avoid performance regressions,
+// see the Opaque API FAQ: https://protobuf.dev/reference/go/opaque-faq/
+func convertToSetterPost(c *cursor) bool {
+ n := c.Node()
+ // We need to work on the statement level, because we need to insert a new
+ // statement (new variable declaration).
+ if _, ok := n.(dst.Stmt); !ok {
+ return true
+ }
+
+ // Ensure that the current statement is part of a slice so that we can
+ // prepend additional statements using c.InsertBefore().
+ switch p := c.Parent().(type) {
+ case *dst.BlockStmt:
+ // This statement is part of the BlockStmt.List slice
+ case *dst.CaseClause:
+ // This statement is part of the CaseClause.Body slice
+ case *dst.CommClause:
+ if p.Comm == n {
+ return true
+ }
+ // This statement is part of the CommClause.Body slice
+ default:
+ return true
+ }
+
+ var assignmentReused *dst.AssignStmt
+
+ var labelName *dst.Ident
+ if ls, ok := n.(*dst.LabeledStmt); ok {
+ labelName = ls.Label
+ }
+
+ // Find proto struct literals within the current statement and extract them.
+ dstutil.Apply(n,
+ nil,
+ func(cur *dstutil.Cursor) bool {
+ // Extract the builder composite literal into a helper variable and
+ // modify it by calling the corresponding setter functions.
+ lit, ok := c.builderCLit(cur.Node(), cur.Parent())
+ if !ok {
+ return true
+ }
+ if c.useBuilder(lit) {
+ c.Logf("requested to use builders for this file or type %v", c.typeOf(lit))
+ return true
+ }
+
+ // builderCLit returning ok means cur.Node() is a UnaryExpr (&{…})
+ // or a CompositeLit ({…}), both of which implement Expr.
+ litExpr := cur.Node().(dst.Expr)
+
+ var exName string // name of the extracted part of the literal
+ var exSource *dst.Ident
+ shallowCopies := 0
+ if as, ok := cur.Parent().(*dst.AssignStmt); ok {
+ // Only rewrite shallow copies in red level, because it requires
+ // follow-up changes to the code (e.g. changing the shallow copy
+ // to use proto.Merge()).
+ for _, lhs := range as.Lhs {
+ if _, ok := lhs.(*dst.StarExpr); ok {
+ if c.lvl.le(Yellow) {
+ c.Logf("shallow copy detected, skipping")
+ return true
+ }
+ shallowCopies++
+ }
+ }
+ }
+ if as, ok := n.(*dst.AssignStmt); ok && n == cur.Parent() {
+ // For assignments (result := &pb.M2{…}) we reuse the variable
+ // name (result) instead of introducing a helper only to
+ // ultimately assign result := helper.
+ for idx, rhs := range as.Rhs {
+ if rhs != cur.Node() {
+ continue
+ }
+ if as.Tok == token.ASSIGN && !types.Identical(c.typeOf(as.Lhs[idx]), c.typeOf(rhs)) {
+ // If the static type is different then we might not be able
+ // to call methods on it, e.g.:
+ //
+ // var myMsg proto.Message
+ // myMsg = &pb2.M2{S: proto.String("Hello")}
+ //
+ // If we translate this as is, it would fail type checking:
+ //
+ // var myMsg proto.Message
+ // myMsg = &pb2.M2{}
+ // myMsg.SetS("Hello") // compile-time error
+ break
+ }
+ if id, ok := as.Lhs[idx].(*dst.Ident); ok && id.Name != "_" {
+ exSource = id
+ exName = id.Name
+ assignmentReused = as
+ }
+ }
+ }
+ if exName == "" {
+ exName = c.helperNameFor(c.Node(), c.typeOf(litExpr))
+ }
+
+ qualifiedLitExpr := litExpr
+ if cl, ok := qualifiedLitExpr.(*dst.CompositeLit); ok {
+ // The expression is a CompositeLit without explicit type, which
+ // is valid within a slice initializer, but not outside, so we
+ // need to wrap it in a UnaryExpr and assign a type identifier.
+ typ := c.typeOf(litExpr)
+ qualifiedLitExpr = &dst.UnaryExpr{
+ Op: token.AND,
+ X: litExpr,
+ }
+ updateASTMap(c, litExpr, qualifiedLitExpr)
+ c.setType(qualifiedLitExpr, typ)
+ cl.Type = c.selectorForProtoMessageType(typ)
+ }
+
+ exIdent := &dst.Ident{Name: exName}
+ if exSource != nil {
+ updateASTMap(c, exSource, exIdent)
+ } else {
+ updateASTMap(c, lit, exIdent)
+ }
+ c.setType(exIdent, c.typeOf(qualifiedLitExpr))
+ tok := token.DEFINE
+ if assignmentReused != nil {
+ tok = assignmentReused.Tok
+ }
+ assign := (dst.Stmt)(&dst.AssignStmt{
+ Lhs: []dst.Expr{exIdent},
+ Tok: tok,
+ Rhs: []dst.Expr{qualifiedLitExpr},
+ })
+
+ replacement := cloneIdent(c, exIdent)
+ // Move line break decorations from the literal to its replacement.
+ replacement.Decorations().Before = litExpr.Decorations().Before
+ replacement.Decorations().After = litExpr.Decorations().After
+
+ if ce, ok := cur.Parent().(*dst.CallExpr); ok {
+ for idx, arg := range ce.Args {
+ if arg != cur.Node() {
+ continue
+ }
+ if !fitsOnSameLine(ce, replacement) {
+ continue
+ }
+ replacement.Decorations().Before = dst.None
+ replacement.Decorations().After = dst.None
+ if idx == 0 {
+ continue
+ }
+ previous := ce.Args[idx-1]
+ previous.Decorations().After = dst.None
+ }
+ }
+
+ // Move end-of-line comments from the literal to its replacement.
+ replacement.Decorations().End = litExpr.Decorations().End
+
+ // Move decorations (line break and comments) of the containing
+ // statement to the first inserted helper variable.
+ assign.Decorations().Before = n.Decorations().Before
+ assign.Decorations().Start = n.Decorations().Start
+ n.Decorations().Before = dst.None
+ n.Decorations().Start = nil
+ // Move decorations (line break and comments) of the literal to its
+ // corresponding helper variable.
+ if assign.Decorations().Before == dst.None {
+ assign.Decorations().Before = litExpr.Decorations().Before
+ }
+ assign.Decorations().Start = append(assign.Decorations().Start, litExpr.Decorations().Start...)
+
+ // Remove all decorations from the composite literal to ensure that
+ // there is no line break within the new AssignStmt (would fail to
+ // compile). Comments have been retained in the lines above.
+ litExpr.Decorations().Before = dst.None
+ litExpr.Decorations().After = dst.None
+ litExpr.Decorations().Start = nil
+ litExpr.Decorations().End = nil
+
+ // If the current node is a LabeledStmt, move the label to the first
+ // inserted helper variable assignment.
+ if labelName != nil {
+ // Turn a Stmt with a label into just the Stmt, without a label.
+ c.Replace(n.(*dst.LabeledStmt).Stmt)
+ // Add the label to the assignment we are inserting.
+ assign = &dst.LabeledStmt{
+ Label: labelName,
+ Stmt: assign,
+ }
+ labelName = nil
+ }
+
+ // Rewrite the composite literal to assignments.
+ elts := lit.Elts
+ lit.Elts = nil
+ // Replace references to exIdent.Name in the right-hand side:
+ // now that we have cleared the RHS, references to exIdent.Name
+ // refer to the new struct literal! b/277902682
+ shadows := false
+ for _, e := range elts {
+ kv := e.(*dst.KeyValueExpr)
+ dstutil.Apply(kv.Value,
+ func(cur *dstutil.Cursor) bool {
+ if _, ok := cur.Node().(*dst.SelectorExpr); ok {
+ // skip over SelectorExprs to avoid false positives
+ // when part of the selector (e.g. cmd.zone) happens
+ // to match the identifier we are looking for
+ // (e.g. zone).
+ return false
+ }
+
+ id, ok := cur.Node().(*dst.Ident)
+ if !ok {
+ return true
+ }
+ if id.Name == exIdent.Name {
+ shadows = true
+ }
+ return true
+ },
+ nil)
+ }
+ if shadows {
+ // The right-hand side references exIdent. Introduce a helper
+ // variable (e.g. cri2 := cri) and update all RHS references to
+ // use the helper variable:
+ helperName := c.helperNameFor(cur.Node(), c.typeOf(litExpr))
+ helperIdent := &dst.Ident{Name: helperName}
+ updateASTMap(c, lit, helperIdent)
+ c.setType(helperIdent, c.typeOf(litExpr))
+
+ helperAssign := &dst.AssignStmt{
+ Lhs: []dst.Expr{helperIdent},
+ Tok: token.DEFINE,
+ Rhs: []dst.Expr{cloneIdent(c, exIdent)},
+ }
+ // Move decorations (line break and comments) from assign to
+ // helperAssign, which just became the first inserted variable.
+ helperAssign.Decorations().Before = assign.Decorations().Before
+ helperAssign.Decorations().Start = assign.Decorations().Start
+ assign.Decorations().Before = dst.None
+ assign.Decorations().Start = nil
+ c.InsertBefore(helperAssign)
+
+ for _, e := range elts {
+ kv := e.(*dst.KeyValueExpr)
+ dstutil.Apply(kv.Value,
+ func(cur *dstutil.Cursor) bool {
+ if _, ok := cur.Node().(*dst.SelectorExpr); ok {
+ // skip over SelectorExprs to avoid false positives
+ // when part of the selector (e.g. cmd.zone) happens
+ // to match the identifier we are looking for
+ // (e.g. zone).
+ return false
+ }
+
+ id, ok := cur.Node().(*dst.Ident)
+ if !ok {
+ return true
+ }
+ if id.Name == exIdent.Name {
+ id.Name = helperIdent.Name
+ }
+ return true
+ },
+ nil)
+ }
+ }
+ c.InsertBefore(assign)
+ for _, e := range elts {
+ kv := e.(*dst.KeyValueExpr)
+ lhs := &dst.SelectorExpr{
+ X: cloneIdent(c, exIdent),
+ Sel: cloneIdent(c, kv.Key.(*dst.Ident)),
+ }
+ c.setType(lhs, c.typeOf(lhs.Sel))
+ a := &dst.AssignStmt{
+ Lhs: []dst.Expr{lhs},
+ Tok: token.ASSIGN,
+ Rhs: []dst.Expr{kv.Value},
+ }
+ *a.Decorations() = *kv.Decorations()
+ c.InsertBefore(a)
+ }
+
+ c.numUnsafeRewritesByReason[ShallowCopy] += shallowCopies
+ cur.Replace(replacement)
+
+ return true
+ })
+
+ if assignmentReused != nil && isIdenticalAssignment(n.(*dst.AssignStmt)) {
+ c.Delete()
+ }
+
+ return true
+}
+
+func fitsOnSameLine(call *dst.CallExpr, replacement *dst.Ident) bool {
+ combinedLen := len(replacement.Name)
+ dstutil.Apply(call,
+ func(cur *dstutil.Cursor) bool {
+ if cur.Node() == call.Args[len(call.Args)-1] {
+ // Skip the last argument, because we are about to replace it.
+ return false // skip children
+ }
+ if id, ok := cur.Node().(*dst.Ident); ok {
+ combinedLen += len(id.Name)
+ }
+ return true
+ },
+ nil)
+ return combinedLen < 80
+}
+
+// isIdenticalAssignment reports whether the provided assign statement has the
+// same names on the left hand side and right hand side, e.g. “foo := foo” or
+// “foo, bar := foo, bar”. This can happen as part of convertToSetter() and
+// results in the deletion of the now-unnecessary assignment.
+func isIdenticalAssignment(as *dst.AssignStmt) bool {
+ if len(as.Lhs) != len(as.Rhs) {
+ return false
+ }
+
+ for idx := range as.Lhs {
+ lhs, ok := as.Lhs[idx].(*dst.Ident)
+ if !ok {
+ return false
+ }
+ rhs, ok := as.Rhs[idx].(*dst.Ident)
+ if !ok {
+ return false
+ }
+ if lhs.Name != rhs.Name {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/internal/fix/converttosetter_test.go b/internal/fix/converttosetter_test.go
new file mode 100644
index 0000000..7ecb0b7
--- /dev/null
+++ b/internal/fix/converttosetter_test.go
@@ -0,0 +1,884 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestConvertToSetter(t *testing.T) {
+ tests := []test{
+ {
+ desc: "assignment",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{
+ I32: proto.Int32(23),
+ M: &pb2.M2{
+ I32: proto.Int32(42),
+ },
+}
+_ = mypb
+for idx, val := range []int32{123, 456} {
+ idx, val := idx, val
+ go func() { println(idx, val) }()
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+mypb := &pb2.M2{}
+mypb.SetI32(23)
+mypb.SetM(m2h2)
+_ = mypb
+for idx, val := range []int32{123, 456} {
+ idx, val := idx, val
+ go func() { println(idx, val) }()
+}
+`,
+ },
+ },
+
+ {
+ desc: "multi assignment",
+ srcfiles: []string{"pkg.go"},
+ in: `
+my1, my2 := &pb2.M2{
+ I32: proto.Int32(23),
+}, &pb2.M2{
+ I64: proto.Int64(12345),
+}
+_, _ = my1, my2
+`,
+ want: map[Level]string{
+ Red: `
+my1 := &pb2.M2{}
+my1.SetI32(23)
+my2 := &pb2.M2{}
+my2.SetI64(12345)
+_, _ = my1, my2
+`,
+ },
+ },
+
+ {
+ desc: "struct literal field",
+ srcfiles: []string{"pkg.go"},
+ in: `
+func() *pb2.M2 {
+ return &pb2.M2{
+ I32: proto.Int32(23),
+ }
+}()
+`,
+ want: map[Level]string{
+ Red: `
+func() *pb2.M2 {
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ return m2h2
+}()
+`,
+ },
+ },
+
+ {
+ desc: "nested builder",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = &pb2.M2{
+ I32: proto.Int32(23),
+ M: &pb2.M2{
+ I32: proto.Int32(42),
+ },
+ Is: []int32{10, 11},
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+m2h3 := &pb2.M2{}
+m2h3.SetI32(23)
+m2h3.SetM(m2h2)
+m2h3.SetIs([]int32{10, 11})
+_ = m2h3
+`,
+ },
+ },
+
+ {
+ desc: "if conditional",
+ srcfiles: []string{"pkg.go"},
+ extra: `func validateProto(msg *pb2.M2) bool { return false }`,
+ in: `
+if msg := (&pb2.M2{
+ I32: proto.Int32(23),
+ M: &pb2.M2{
+ I32: proto.Int32(42),
+ },
+ Is: []int32{10, 11},
+}); validateProto(msg) {
+ println("validated")
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+m2h3 := &pb2.M2{}
+m2h3.SetI32(23)
+m2h3.SetM(m2h2)
+m2h3.SetIs([]int32{10, 11})
+if msg := (m2h3); validateProto(msg) {
+ println("validated")
+}
+`,
+ },
+ },
+
+ {
+ desc: "case clause",
+ srcfiles: []string{"pkg.go"},
+ extra: `func validateProto(msg *pb2.M2) bool { return false }`,
+ in: `
+switch {
+case validateProto(nil):
+_ = &pb2.M2{
+ I32: proto.Int32(23),
+}
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch {
+case validateProto(nil):
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ _ = m2h2
+}
+`,
+ },
+ },
+
+ {
+ desc: "select clause",
+ srcfiles: []string{"pkg.go"},
+ in: `
+dummy := make(chan bool)
+select {
+case <-dummy:
+_ = &pb2.M2{
+ I32: proto.Int32(23),
+}
+}
+`,
+ want: map[Level]string{
+ Red: `
+dummy := make(chan bool)
+select {
+case <-dummy:
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ _ = m2h2
+}
+`,
+ },
+ },
+
+ {
+ desc: "never nil byte expression",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var b []byte
+m2.Bytes = b[2:3]
+m2.Bytes = b[:3]
+m2.Bytes = b[2:]
+m2.Bytes = b[:]
+m2.Bytes = b[0:]
+m2.Bytes = b[:0]
+m2.Bytes = b[0:0]
+`,
+ want: map[Level]string{
+ Green: `
+var b []byte
+m2.SetBytes(b[2:3])
+m2.SetBytes(b[:3])
+m2.SetBytes(b[2:])
+if x := b[:]; x != nil {
+ m2.SetBytes(x)
+} else {
+ m2.ClearBytes()
+}
+if x := b[0:]; x != nil {
+ m2.SetBytes(x)
+} else {
+ m2.ClearBytes()
+}
+if x := b[:0]; x != nil {
+ m2.SetBytes(x)
+} else {
+ m2.ClearBytes()
+}
+if x := b[0:0]; x != nil {
+ m2.SetBytes(x)
+} else {
+ m2.ClearBytes()
+}
+`,
+ },
+ },
+
+ {
+ desc: "variable declaration block",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var (
+_ = &pb2.M2{
+ I32: proto.Int32(23),
+}
+)
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23)
+var (
+ _ = m2h2
+)
+`,
+ },
+ },
+
+ {
+ desc: "defer statement",
+ srcfiles: []string{"pkg.go"},
+ extra: `func validateProto(msg *pb2.M2) bool { return false }`,
+ in: `
+defer validateProto(&pb2.M2{
+ I32: proto.Int32(23),
+})
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23)
+defer validateProto(m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "go statement",
+ srcfiles: []string{"pkg.go"},
+ extra: `func validateProto(msg *pb2.M2) bool { return false }`,
+ in: `
+go validateProto(&pb2.M2{
+ I32: proto.Int32(23),
+})
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23)
+go validateProto(m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "for loop",
+ srcfiles: []string{"pkg.go"},
+ extra: `func validateProto(msg *pb2.M2) bool { return false }`,
+ in: `
+for m2 := (&pb2.M2{
+ I32: proto.Int32(23),
+}); m2 != nil; m2 = nil {
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23)
+for m2 := (m2h2); m2 != nil; m2 = nil {
+}
+`,
+ },
+ },
+
+ {
+ desc: "range statement",
+ srcfiles: []string{"pkg.go"},
+ in: `
+for _, idx := range []int{1, 2, 3} {
+ _ = idx
+ _ = &pb2.M2{
+ I32: proto.Int32(23),
+ }
+}
+`,
+ want: map[Level]string{
+ Red: `
+for _, idx := range []int{1, 2, 3} {
+ _ = idx
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ _ = m2h2
+}
+`,
+ },
+ },
+
+ {
+ desc: "labeled statement",
+ srcfiles: []string{"pkg.go"},
+ in: `
+goto validate
+println("this line is skipped")
+validate:
+println(&pb2.M2{
+ I32: proto.Int32(23),
+})
+`,
+ want: map[Level]string{
+ Red: `
+ goto validate
+ println("this line is skipped")
+validate:
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ println(m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "slice",
+ srcfiles: []string{"pkg.go"},
+ in: `
+for _, msg := range []*pb2.M2{
+ {
+ I32: proto.Int32(23),
+ },
+ &pb2.M2{
+ I64: proto.Int64(123),
+ },
+} {
+ println(msg)
+}
+ `,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23)
+m2h3 := &pb2.M2{}
+m2h3.SetI64(123)
+for _, msg := range []*pb2.M2{
+ m2h2,
+ m2h3,
+} {
+ println(msg)
+}
+`,
+ },
+ },
+
+ {
+ desc: "conditional assignment",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var mypb *pb2.M2
+if true {
+ mypb = &pb2.M2{
+ I32: proto.Int32(23),
+ }
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Red: `
+var mypb *pb2.M2
+if true {
+ mypb = &pb2.M2{}
+ mypb.SetI32(23)
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "assignment with different type",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var mypb proto.Message
+if true {
+ mypb = &pb2.M2{
+ I32: proto.Int32(23),
+ }
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+var mypb proto.Message
+if true {
+ m2h2 := &pb2.M2{}
+ m2h2.SetI32(23)
+ mypb = m2h2
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "slice with comments",
+ srcfiles: []string{"pkg.go"},
+ in: `
+for _, msg := range []*pb2.M2{
+ // Comment above first literal
+ {
+ I32: proto.Int32(23), // End-of-line comment for I32
+ },
+ // Comment above second literal
+ &pb2.M2{
+ // Comment above I64
+ I64: proto.Int64(/* before 123 */ 123 /* after 123 */),
+ // Comment at the end of the second literal
+ }, // End-of-line comment after second literal
+} {
+ println(msg)
+}
+ `,
+ want: map[Level]string{
+ Red: `
+// Comment above first literal
+m2h2 := &pb2.M2{}
+m2h2.SetI32(23) // End-of-line comment for I32
+// Comment above second literal
+m2h3 := &pb2.M2{}
+// Comment above I64
+m2h3.SetI64(123 /* after 123 */)
+// Comment at the end of the second literal
+for _, msg := range []*pb2.M2{
+ m2h2,
+ m2h3, // End-of-line comment after second literal
+} {
+ println(msg)
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestPointerDereference(t *testing.T) {
+
+ tests := []test{
+ {
+ desc: "assignment, lhs proto field dereference",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if val := m2.I32; val != nil {
+ *val = int32(42)
+}
+`,
+ want: map[Level]string{
+ Red: `
+if m2.HasI32() {
+ m2.SetI32(int32(42))
+}
+`,
+ },
+ },
+
+ {
+ desc: "multi-assign, lhs proto field dereference",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var i int
+if val := m2.I32; val != nil {
+ i, *val, i = 5, int32(42), 6
+}
+_ = i
+`,
+ want: map[Level]string{
+ Red: `
+var i int
+if m2.HasI32() {
+ i = 5
+ m2.SetI32(int32(42))
+ i = 6
+}
+_ = i
+`,
+ },
+ },
+
+ {
+ desc: "multiple assignments, lhs proto field dereference",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var i int
+if val := m2.I32; val != nil {
+ *val, i = int32(42), 5
+ i, *val = 6, int32(43)
+}
+_ = i
+`,
+ want: map[Level]string{
+ Red: `
+var i int
+if m2.HasI32() {
+ m2.SetI32(int32(42))
+ i = 5
+ i = 6
+ m2.SetI32(int32(43))
+}
+_ = i
+`,
+ },
+ },
+
+ {
+ desc: "multi-assign, lhs and rhs",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var i int
+if val := m2.I32; val != nil {
+ *val, i = *val+2, 5
+ i, *val = 6, *val+5
+}
+_ = i
+`,
+ want: map[Level]string{
+ Red: `
+var i int
+if m2.HasI32() {
+ m2.SetI32(m2.GetI32() + 2)
+ i = 5
+ i = 6
+ m2.SetI32(m2.GetI32() + 5)
+}
+_ = i
+`,
+ },
+ },
+
+ {
+ desc: "ifstmt: potential side effect initializer",
+ srcfiles: []string{"pkg.go"},
+ extra: "func f(m *pb2.M2) *int32 { return m.I32 }",
+ in: `
+if val := f(m2); val != nil {
+ *val = int32(42)
+}
+`,
+ want: map[Level]string{
+ Red: `
+if val := f(m2); val != nil {
+ *val = int32(42)
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestEnum(t *testing.T) {
+ tests := []test{
+ {
+ desc: "basic case",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = &pb2.M2{
+ E: pb2.M2_E_VAL.Enum(),
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetE(pb2.M2_E_VAL)
+_ = m2h2
+`,
+ },
+ },
+
+ {
+ desc: "free function with explicit receiver",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = &pb2.M2{
+ E: pb2.M2_Enum.Enum(pb2.M2_E_VAL),
+}
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := &pb2.M2{}
+m2h2.SetE(pb2.M2_E_VAL)
+_ = m2h2
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestNameGeneration(t *testing.T) {
+ tests := []test{
+ {
+ desc: "basic case",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m2h2 := 5
+{
+ _ = &pb2.M2{
+ S: proto.String("Hello World!"),
+ }
+}
+_ = m2h2
+`,
+ want: map[Level]string{
+ Red: `
+m2h2 := 5
+{
+ m2h3 := &pb2.M2{}
+ m2h3.SetS("Hello World!")
+ _ = m2h3
+}
+_ = m2h2
+`,
+ },
+ },
+
+ {
+ desc: "local definition",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if m2h2 := int32(5); m2h2 < 5 {
+ _ = &pb2.M2{
+ S: proto.String("Hello World!"),
+ I32: proto.Int32(m2h2),
+ }
+}
+`,
+ want: map[Level]string{
+ Red: `
+if m2h2 := int32(5); m2h2 < 5 {
+ m2h3 := &pb2.M2{}
+ m2h3.SetS("Hello World!")
+ m2h3.SetI32(m2h2)
+ _ = m2h3
+}
+`,
+ },
+ },
+
+ {
+ desc: "sub message",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if op2 := (&pb2.OtherProto2{}); op2 != nil {
+ _ = &pb2.OtherProto2{
+ M: &pb2.OtherProto2{},
+ Ms: []*pb2.OtherProto2{op2},
+ }
+}
+`,
+ want: map[Level]string{
+ Red: `
+if op2 := (&pb2.OtherProto2{}); op2 != nil {
+ op2h2 := &pb2.OtherProto2{}
+ op2h2.SetM(&pb2.OtherProto2{})
+ op2h2.SetMs([]*pb2.OtherProto2{op2})
+ _ = op2h2
+}
+`,
+ },
+ },
+
+ {
+ desc: "within if statement",
+ srcfiles: []string{"pkg.go"},
+ extra: "func extra(*pb2.M2) bool { return true }",
+ in: `
+if extra(&pb2.M2{I32: proto.Int32(42)}) {
+}
+
+if extra(&pb2.M2{I32: proto.Int32(42)}) {
+}
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetI32(42)
+if extra(m2h2) {
+}
+
+m2h3 := &pb2.M2{}
+m2h3.SetI32(42)
+if extra(m2h3) {
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestParentheses(t *testing.T) {
+ tests := []test{
+ {
+ desc: "basic case",
+ srcfiles: []string{"pkg.go"},
+ in: `
+p := int32(42)
+m2.I32 = &((p))
+`,
+ want: map[Level]string{
+ Red: `
+p := int32(42)
+m2.SetI32(p)
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestUnshadow(t *testing.T) {
+ tests := []test{
+ {
+ desc: "struct literal",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m2 = &pb2.M2{M: m2}
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := m2
+m2 = &pb2.M2{}
+m2.SetM(m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "selectorexpr",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var unrelated struct{
+ zone string
+}
+zone := &pb2.M2{S: proto.String(unrelated.zone)}
+_ = zone
+`,
+ want: map[Level]string{
+ Green: `
+var unrelated struct {
+ zone string
+}
+zone := &pb2.M2{}
+zone.SetS(unrelated.zone)
+_ = zone
+`,
+ },
+ },
+
+ {
+ desc: "struct literal, define",
+ srcfiles: []string{"pkg.go"},
+ in: `
+{
+ m2 := &pb2.M2{M: m2}
+ _ = m2
+}
+`,
+ want: map[Level]string{
+ Green: `
+{
+ m2h2 := m2
+ m2 := &pb2.M2{}
+ m2.SetM(m2h2)
+ _ = m2
+}
+`,
+ },
+ },
+
+ {
+ desc: "append",
+ srcfiles: []string{"pkg.go"},
+ in: `
+all := &pb2.M2{}
+all.Ms = append(all.Ms, m2)
+`,
+ want: map[Level]string{
+ Green: `
+all := &pb2.M2{}
+all.SetMs(append(all.GetMs(), m2))
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestConflictingNames(t *testing.T) {
+ tests := []test{
+ {
+ desc: "struct literal",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.SetterNameConflict{
+ Stat: proto.Int32(int32(5)),
+ SetStat: proto.Int32(int32(42)),
+ GetStat_: proto.Int32(int32(42)),
+ HasStat: proto.Int32(int32(42)),
+ ClearStat: proto.Int32(int32(42)),
+}
+_ = m
+_ = *m.Stat
+if m.Stat != nil || m.Stat == nil {
+ m.Stat = nil
+}
+`,
+ want: map[Level]string{
+ // The SetGetStat_ is wrong. It should be SetGetStat.
+ // Fixing it requires the proto descriptor and thus,
+ // is not worth it.
+ // Technically we should also generate Get_Stat()
+ // instead of GetStat() but again it requires the
+ // proto descriptor.
+ // Both of these cases should be extremely rare.
+ Green: `
+m := &pb2.SetterNameConflict{}
+m.Set_Stat(int32(5))
+m.SetSetStat(int32(42))
+m.SetGetStat_(int32(42))
+m.SetHasStat(int32(42))
+m.SetClearStat(int32(42))
+_ = m
+_ = m.GetStat()
+if m.Has_Stat() || !m.Has_Stat() {
+ m.Clear_Stat()
+}
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/debug.go b/internal/fix/debug.go
new file mode 100644
index 0000000..479e150
--- /dev/null
+++ b/internal/fix/debug.go
@@ -0,0 +1,74 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "bytes"
+ "fmt"
+ "go/types"
+ "reflect"
+
+ "github.com/dave/dst"
+)
+
+// dump exists for debugging purposes. It prints information about provided objects. Don't delete it.
+func (c *cursor) dump(args ...any) {
+ toString := func(n dst.Node) string {
+ buf := new(bytes.Buffer)
+ err := dst.Fprint(buf, n, func(name string, val reflect.Value) bool {
+ return dst.NotNilFilter(name, val) && name != "Obj" && name != "Decs"
+ })
+ if err != nil {
+ buf = bytes.NewBufferString("<can't print the expression>")
+ }
+ return buf.String()
+ }
+ defer fmt.Println("------------------------------")
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Recovered", r)
+ }
+ }()
+ fmt.Println("==============================")
+ for _, a := range args {
+ switch a := a.(type) {
+ case nil:
+ fmt.Println("<nil>")
+ case dst.Expr:
+ fmt.Print("EXPR: `")
+ fmt.Printf("DST TYPE: %T\n", a)
+ fmt.Println(toString(a))
+ fmt.Println("`")
+ fmt.Println("EXPR TYPE: ", c.typeOf(a))
+ case *dst.FieldList, *dst.Field:
+ fmt.Printf("DST TYPE: %T\n", a)
+ fmt.Println("*dst.FieldList and *dst.Field can't be printed")
+ case dst.Node:
+ fmt.Printf("DST TYPE: %T\n", a)
+ if _, ok := a.(*dst.FieldList); ok {
+ fmt.Print("*dst.FieldList can't be printed")
+ } else {
+ fmt.Println(toString(a))
+ }
+ fmt.Println("")
+ case string:
+ fmt.Println("label:", a)
+ case bool, int:
+ fmt.Println("value:", a)
+ case types.Type:
+ if a == nil {
+ fmt.Println("<no type information>")
+ } else {
+ fmt.Printf("%T\n", a)
+ }
+ case types.TypeAndValue:
+ fmt.Printf("TypeAndValue%+v\n", a)
+ case types.Object:
+ fmt.Printf("Object: .Package=%s .Name=%s .Id=%s\n", a.Pkg(), a.Name(), a.Id())
+ default:
+ fmt.Printf("unrecognized argument of type %T\n", a)
+ }
+ }
+}
diff --git a/internal/fix/diff.go b/internal/fix/diff.go
new file mode 100644
index 0000000..054f11c
--- /dev/null
+++ b/internal/fix/diff.go
@@ -0,0 +1,70 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+)
+
+func udiff(x, y []byte) ([]byte, error) {
+ if bytes.Equal(x, y) {
+ return nil, nil
+ }
+ xp, err := pipe(x)
+ if err != nil {
+ return nil, err
+ }
+ defer xp.Close()
+ yp, err := pipe(y)
+ if err != nil {
+ return nil, err
+ }
+ defer yp.Close()
+
+ var stderr bytes.Buffer
+ cmd := exec.Command("diff", "-u", "/dev/fd/3", "/dev/fd/4")
+ cmd.ExtraFiles = []*os.File{xp, yp}
+ cmd.Stderr = &stderr
+ stdout, err := cmd.Output()
+ if ee, ok := err.(*exec.ExitError); ok {
+ if ws, ok := ee.Sys().(syscall.WaitStatus); ok && ws.ExitStatus() == 1 {
+ // exit status 1 means there's a diff, but no other failure
+ err = nil
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ if stderr.Len() != 0 {
+ return nil, fmt.Errorf("diff: %s", &stderr)
+ }
+ nl := []byte("\n")
+ lines := bytes.Split(stdout, nl)
+ if len(lines) < 2 {
+ return stdout, nil
+ }
+ if strings.HasPrefix(string(lines[0]), "--- /dev/fd/3\t") &&
+ strings.HasPrefix(string(lines[1]), "+++ /dev/fd/4\t") {
+ stdout = bytes.Join(lines[2:], nl)
+ }
+ return stdout, nil
+}
+
+func pipe(data []byte) (*os.File, error) {
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, fmt.Errorf("os.Pipe: %v", err)
+ }
+ go func() {
+ pw.Write(data)
+ pw.Close()
+ }()
+ return pr, nil
+}
diff --git a/internal/fix/fix.go b/internal/fix/fix.go
new file mode 100644
index 0000000..17256b5
--- /dev/null
+++ b/internal/fix/fix.go
@@ -0,0 +1,519 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package fix rewrites Go packages to use opaque version of the Go protocol
+// buffer API.
+package fix
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/token"
+ "go/types"
+ "os"
+ "reflect"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+ "github.com/dave/dst/dstutil"
+ log "github.com/golang/glog"
+ "golang.org/x/tools/go/types/typeutil"
+ spb "google.golang.org/open2opaque/internal/dashboard"
+ "google.golang.org/open2opaque/internal/ignore"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/o2o/syncset"
+)
+
+// Level represents the riskiness of a fix ranging from "safe to submit" to
+// "needs fixes by humans".
+type Level string
+
+const (
+ // None means that no transforms to the code are applied. Useful for
+ // gathering statistics about unmodified code.
+ None = Level("none")
+
+ // Green fixes are considered safe to submit without human review. Those
+ // fixes preserve the behavior of the program.
+ Green = Level("green")
+
+ // Yellow fixes are safe to submit except for programs that depend on
+ // unspecified behavior, internal details, code that goes against the
+ // good coding style or guidelines, etc.
+ // Yellow fixes should be reviewed.
+ Yellow = Level("yellow")
+
+ // Red fixes can change behavior of the program. Red fixes are proposed
+ // when we can't prove that the fix is safe or when the fix results in
+ // code that we don't consider readable.
+ // Red fixes should go through extensive review and analysis.
+ Red = Level("red")
+)
+
+// ge returns true if lvl is greater or equal to rhs.
+func (lvl Level) ge(rhs Level) bool {
+ switch lvl {
+ case None:
+ return rhs == None
+ case Green:
+ return rhs == None || rhs == Green
+ case Yellow:
+ return rhs == None || rhs == Green || rhs == Yellow
+ case Red:
+ return true
+ }
+ panic("unreachable; lvl = '" + lvl + "'")
+}
+
+// le returns true if lvl is less or equal to rhs.
+func (lvl Level) le(rhs Level) bool {
+ return lvl == rhs || !lvl.ge(rhs)
+}
+
+// FixedFile represents a single file after applying fixes.
+type FixedFile struct {
+ // Path to the file
+ Path string
+ OriginalCode string // Code before applying fixes.
+ Code string // Code after applying fixes.
+ Modified bool // Whether the file was modified by this tool.
+ Generated bool // Whether the file is a generated file.
+ Stats []*spb.Entry // List of proto accesses in Code (i.e. after applying rewrites).
+ Drifted bool // Whether the file has drifted between CBT and HEAD.
+ RedFixes map[unsafeReason]int // Number of fixes per unsafe category.
+}
+
+func (f *FixedFile) String() string {
+ return fmt.Sprintf("[FixedFile %s Code=<redacted,len=%d> Modified=%t Generated=%t Stats=<redacted,len=%d>]",
+ f.Path, len(f.Code), f.Modified, f.Generated, len(f.Stats))
+}
+
+// Result describes what was fixed. For all change levels.
+type Result map[Level][]*FixedFile
+
+// AllStats returns all of the generated stats entries.
+func (r Result) AllStats() []*spb.Entry {
+ var stats []*spb.Entry
+ for _, lvl := range []Level{None, Green, Yellow, Red} {
+ for _, f := range r[lvl] {
+ if lvl > None && !f.Modified {
+ continue
+ }
+ stats = append(stats, f.Stats...)
+ }
+ }
+ return stats
+}
+
+// ReportStats calls report on each given stats entry after setting
+// its Status field based on err. If err is non-nil, it also reports
+// an empty entry for pkgPath.
+func ReportStats(stats []*spb.Entry, pkgPath string, err error, report func(*spb.Entry)) {
+ st := &spb.Status{Type: spb.Status_OK}
+ if err != nil {
+ st = &spb.Status{
+ Type: spb.Status_FAIL,
+ Error: strings.TrimSpace(err.Error()),
+ }
+ report(&spb.Entry{
+ Status: st,
+ Location: &spb.Location{
+ Package: pkgPath,
+ },
+ })
+ }
+
+ for _, e := range stats {
+ if e.Status == nil {
+ e.Status = st
+ }
+ report(e)
+ }
+}
+
+// rewrite describes transformations done on a DST with a pre- and post-
+// traversal. Exactly one of the pre/post must be set.
+type rewrite struct {
+ name string
+ pre func(c *cursor) bool
+ post func(c *cursor) bool
+}
+
+var rewrites []rewrite
+
+// typesInfo contains type information for a type-checked package similar to types.Info but with
+// dst.Node instead of ast.Node.
+type typesInfo struct {
+ types map[dst.Expr]types.TypeAndValue
+ uses map[*dst.Ident]types.Object
+ defs map[*dst.Ident]types.Object
+ astMap map[dst.Node]ast.Node
+ dstMap map[ast.Node]dst.Node
+}
+
+// typeOf returns the types.Type of the given expression or nil if not found. It is equivalent to
+// types.Info.TypeOf.
+func (info *typesInfo) typeOf(e dst.Expr) types.Type {
+ if t, ok := info.types[e]; ok {
+ return t.Type
+ }
+ if id, _ := e.(*dst.Ident); id != nil {
+ if obj := info.objectOf(id); obj != nil {
+ return obj.Type()
+ }
+ }
+ return nil
+}
+
+// objectOf returns the types.Object denoted by the given id, or nil if not found. It is equivalent
+// to types.Info.TypeOf.
+func (info *typesInfo) objectOf(id *dst.Ident) types.Object {
+ if obj := info.defs[id]; obj != nil {
+ return obj
+ }
+ return info.uses[id]
+}
+
+// A cacheEntry stores a proto type (if any). Presence of a cacheEntry indicates
+// that the type in question has been processed before, no matter whether
+// protoType is nil or non-nil.
+type cacheEntry struct {
+ protoType types.Type
+}
+
+func init() {
+ t, err := types.Eval(token.NewFileSet(), nil, token.NoPos, "func(){}()")
+ if err != nil {
+ panic("can't initialize void type: " + err.Error())
+ }
+ if !t.IsVoid() {
+ panic("can't initialize the void type")
+ }
+ voidType = t
+}
+
+// BuilderUseType categorizes when builders instead of setters are used to
+// rewrite struct literal initialization.
+type BuilderUseType int
+
+const (
+ // BuildersNowhere means never use builders
+ BuildersNowhere BuilderUseType = 0
+ // BuildersEverywhere means always use builders
+ BuildersEverywhere BuilderUseType = 1
+ // BuildersTestsOnly means use builders only in tests
+ BuildersTestsOnly BuilderUseType = 2
+ // BuildersEverywhereExceptPromising means always use builders, except for
+ // .go files that touch promising protos (many fleet-wide unmarshals).
+ BuildersEverywhereExceptPromising BuilderUseType = 3
+)
+
+// ConfiguredPackage contains a package and all configuration necessary to
+// rewrite the package.
+type ConfiguredPackage struct {
+ Loader loader.Loader
+ Pkg *loader.Package
+ TypesToUpdate map[string]bool
+ BuilderTypes map[string]bool
+ BuilderLocations *ignore.List
+ Levels []Level
+ ProcessedFiles *syncset.Set
+ ShowWork bool
+ Testonly bool
+ UseBuilders BuilderUseType
+}
+
+// Fix fixes a Go package.
+func (cpkg *ConfiguredPackage) Fix() (Result, error) {
+ defer func() {
+ if r := recover(); r != nil {
+ panic(fmt.Sprintf("can't process package: %v", r))
+ }
+ }()
+
+ // Pairing of loader.File with associated dst.File.
+ type filePair struct {
+ loaderFile *loader.File
+ dstFile *dst.File
+ }
+ files := []filePair{}
+
+ // Convert AST to DST and also produce corresponding typesInfo.
+ dec := decorator.NewDecorator(cpkg.Pkg.Fileset)
+ for _, f := range cpkg.Pkg.Files {
+ dstFile, err := dec.DecorateFile(f.AST)
+ if err != nil {
+ return nil, err
+ }
+ files = append(files, filePair{f, dstFile})
+ }
+ info := dstTypesInfo(cpkg.Pkg.TypeInfo, dec)
+
+ // Only check for file drift (between Compilations Bigtable and Piper HEAD)
+ // when working in a CitC client, not when running as FlumeGo job in prod.
+ driftCheck := false
+ if wd, err := os.Getwd(); err == nil {
+ driftCheck = strings.HasPrefix(wd, "/google/src/cloud/")
+ }
+
+ out := make(Result)
+ for _, rec := range files {
+ f := rec.loaderFile
+ if f.LibraryUnderTest {
+ // Skip library code: non-test code uses setters instead of
+ // builders, so it is important to skip library code when processing
+ // the _test target.
+ log.Infof("skipping library file %s", f.Path)
+ continue
+ }
+ if !cpkg.ProcessedFiles.Add(f.Path) {
+ continue
+ }
+
+ dstFile := rec.dstFile
+ fmtSource := func() string {
+ var buf bytes.Buffer
+ if err := decorator.Fprint(&buf, dstFile); err != nil {
+ log.Fatalf("BUG: decorator.Fprint: %v", err)
+ }
+ return buf.String()
+ }
+ c := &cursor{
+ pkg: cpkg.Pkg,
+ curFile: f,
+ curFileDST: dstFile,
+ imports: newImports(cpkg.Pkg.TypePkg, f.AST),
+ typesInfo: info,
+ loader: cpkg.Loader,
+ lvl: None,
+ typesToUpdate: cpkg.TypesToUpdate,
+ builderTypes: cpkg.BuilderTypes,
+ builderLocations: cpkg.BuilderLocations,
+ shouldLogCompositeTypeCache: new(typeutil.Map),
+ shouldLogCompositeTypeCacheNoPtr: new(typeutil.Map),
+ debugLog: make(map[string][]string),
+ builderUseType: cpkg.UseBuilders,
+ testonly: cpkg.Testonly,
+ helperVariableNames: make(map[string]bool),
+ numUnsafeRewritesByReason: map[unsafeReason]int{},
+ }
+ knownNoType := exprsWithNoType(c, dstFile)
+ out[None] = append(out[None], &FixedFile{
+ Path: f.Path,
+ Code: f.Code,
+ Generated: f.Generated,
+ Stats: stats(c, dstFile, f.Generated),
+ })
+ for _, lvl := range cpkg.Levels {
+ if lvl == None {
+ continue
+ }
+ if cpkg.ShowWork {
+ log.Infof("----- LEVEL %s -----", lvl)
+ }
+ c.imports.importsToAdd = nil
+ for _, r := range rewrites {
+ before := ""
+ if cpkg.ShowWork {
+ before = fmtSource()
+ }
+
+ c.lvl = lvl
+ if (r.pre != nil) == (r.post != nil) {
+ // We enforce this so that it's easier to accurately detect
+ // which DST transformation loses type information.
+ panic(fmt.Sprintf("exactly one rewrite.pre or rewrite.post must be set; r.pre set: %t; r.post set: %t", r.pre != nil, r.post != nil))
+ }
+ if r.pre != nil {
+ dstutil.Apply(dstFile, makeApplyFn(r.name, cpkg.ShowWork, r.pre, c), nil)
+ }
+ if r.post != nil {
+ dstutil.Apply(dstFile, nil, makeApplyFn(r.name, cpkg.ShowWork, r.post, c))
+ }
+ // Walk the dst and verify that all expressions that should have
+ // the type set, have the type set. The idea is that we can run
+ // this over all our code and identify type bugs in
+ // open2opaque rewrites.
+ //
+ //
+ // We've considered the following alternative:
+ //
+ // for each transformation (e.g. green 'hasPre')
+ // repeat until there are no changes:
+ // type-check
+ // apply the transformation
+ //
+ // We've discarded this approach because it prevents doing
+ // transformation that can't be type checked. For example:
+ // introducing builders. The problem is that:
+ // - not all protos are on the open_struct API
+ // - we use an offline job to provide type information for
+ // dependencies and can't easily make it generate the new API
+ dstutil.Apply(dstFile, func(cur *dstutil.Cursor) bool {
+ x, ok := cur.Node().(dst.Expr)
+ if !ok {
+ return true
+ }
+ if knownNoType[x] {
+ return true
+ }
+ if _, ok := c.typesInfo.types[x]; !ok {
+ buf := new(bytes.Buffer)
+ err := dst.Fprint(buf, x, func(name string, v reflect.Value) bool {
+ return name != "Decs" && name != "Obj" && name != "Path"
+ })
+ if err != nil {
+ buf = bytes.NewBufferString("<can't print the expression>")
+ }
+ panic(fmt.Sprintf("BUG: can't determine type of expression after a rewrite; level: %s; file: %s; rewrite %s; expr:\n%s",
+ c.lvl, rec.loaderFile.Path, r.name, buf))
+ }
+ return true
+ }, nil)
+
+ if cpkg.ShowWork {
+ after := fmtSource()
+ // We are intentionally using udiff instead of
+ // cmp.Diff here, because it is too cumbersome to get a
+ // line-based diff out of cmp.Diff.
+ //
+ // While udiff calling out to diff(1) is not the most
+ // efficient arrangement, at least the output format is
+ // familiar to readers.
+ diff, err := udiff([]byte(before), []byte(after))
+ if err != nil {
+ return nil, err
+ }
+ if diff != nil {
+ log.Infof("rewrite %s changed:\n%s", r.name, string(diff))
+ }
+ }
+ }
+
+ if len(c.imports.importsToAdd) > 0 {
+ dstutil.Apply(dstFile, nil, func(cur *dstutil.Cursor) bool {
+ if _, ok := cur.Node().(*dst.ImportSpec); !ok {
+ return true // skip node, looking for ImportSpecs only
+ }
+ decl, ok := cur.Parent().(*dst.GenDecl)
+ if !ok {
+ panic(fmt.Sprintf("BUG: parent of ImportSpec is type %T, wanted GenDecl", cur.Parent()))
+ }
+ if cur.Index() < len(decl.Specs)-1 {
+ return true // skip import, waiting for the last one
+ }
+ // This is the last import, so we add to the very end of
+ // the import list.
+ for _, imp := range c.imports.importsToAdd {
+ cur.InsertAfter(imp)
+ c.setType(imp.Path, types.Typ[types.Invalid])
+ }
+ return false // import added, abort traversal
+ })
+ }
+
+ var buf bytes.Buffer
+ if err := decorator.Fprint(&buf, dstFile); err != nil {
+ return nil, err
+ }
+ code := buf.String()
+ modified := f.Code != code
+ drifted := false
+ if modified && !f.Generated && driftCheck &&
+ // The paths that our unit tests use (test/pkg/...) do not refer
+ // to actual files and hence cannot be read.
+ !strings.HasPrefix(f.Path, "test/pkg/") {
+ // Check whether the source has changed between the local CitC
+ // client and reading it from the go/compilations-bigtable
+ // loader.
+ b, err := os.ReadFile(f.Path)
+ if err != nil {
+ return nil, err
+ }
+ // Our loader formats the source when loading from
+ // go/compilations-bigtable, so we need to format here, too.
+ formattedContents, err := format.Source(b)
+ if err != nil {
+ return nil, err
+ }
+ drifted = f.Code != string(formattedContents)
+ }
+ out[lvl] = append(out[lvl], &FixedFile{
+ Path: f.Path,
+ OriginalCode: f.Code,
+ Code: code,
+ Modified: modified,
+ Generated: f.Generated,
+ Drifted: drifted,
+ Stats: stats(c, dstFile, f.Generated),
+ RedFixes: c.numUnsafeRewritesByReason,
+ })
+ }
+ }
+ return out, nil
+}
+
+func exprsWithNoType(cur *cursor, f *dst.File) map[dst.Expr]bool {
+ out := map[dst.Expr]bool{}
+ dstutil.Apply(f, func(c *dstutil.Cursor) bool {
+ x, ok := c.Node().(dst.Expr)
+ if !ok {
+ return true
+ }
+ if _, ok := cur.typesInfo.types[x]; !ok {
+ out[x] = true
+ }
+ return true
+ }, nil)
+ return out
+}
+
+// dstTypesInfo generates typesInfo from given types.Info and dst mapping.
+func dstTypesInfo(orig *types.Info, dec *decorator.Decorator) *typesInfo {
+ dstMap := dec.Dst
+ info := &typesInfo{
+ types: map[dst.Expr]types.TypeAndValue{},
+ defs: map[*dst.Ident]types.Object{},
+ uses: map[*dst.Ident]types.Object{},
+ astMap: dec.Ast.Nodes,
+ dstMap: dec.Dst.Nodes,
+ }
+
+ for astExpr, tav := range orig.Types {
+ if dstExpr, ok := dstMap.Nodes[astExpr]; ok {
+ info.types[dstExpr.(dst.Expr)] = tav
+ }
+ }
+
+ for astIdent, obj := range orig.Defs {
+ if dstIdent, ok := dstMap.Nodes[astIdent]; ok {
+ info.defs[dstIdent.(*dst.Ident)] = obj
+ }
+ }
+
+ for astIdent, obj := range orig.Uses {
+ if dstIdent, ok := dstMap.Nodes[astIdent]; ok {
+ info.uses[dstIdent.(*dst.Ident)] = obj
+ }
+ }
+
+ return info
+}
+
+// makeApplyFn adapts a rewrite to work with dstutil.Apply package.
+func makeApplyFn(name string, showWork bool, f func(c *cursor) bool, cur *cursor) dstutil.ApplyFunc {
+ if f == nil {
+ return nil
+ }
+ cur.rewriteName = name
+ return func(c *dstutil.Cursor) bool {
+ cur.Logf("entering")
+ defer cur.Logf("leaving")
+ cur.Cursor = c
+ return f(cur)
+ }
+}
diff --git a/internal/fix/fix_test.go b/internal/fix/fix_test.go
new file mode 100644
index 0000000..3a63eed
--- /dev/null
+++ b/internal/fix/fix_test.go
@@ -0,0 +1,120 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "context"
+ "sort"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "google.golang.org/open2opaque/internal/o2o/fakeloader"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/o2o/syncset"
+)
+
+func TestLevelsComparison(t *testing.T) {
+ all := []Level{None, Green, Yellow, Red}
+
+ var got []Level
+ for i := 0; i < 10; i++ {
+ got = append(got, all...)
+ }
+ sort.Slice(got, func(i, j int) bool {
+ return got[i].le(got[j])
+ })
+
+ var want []Level
+ for _, lvl := range all {
+ for i := 0; i < 10; i++ {
+ want = append(want, lvl)
+ }
+ }
+
+ if d := cmp.Diff(want, got); d != "" {
+ t.Fatalf("Sort returned %v want %v; diff:\n%s", got, want, d)
+ }
+}
+
+func TestPopulatesModifiedAndGeneratedFlags(t *testing.T) {
+ ruleName := "fake"
+ prefix := ruleName + "/"
+ pathPrefix := prefix
+
+ l := fakeloader.NewFakeLoader(
+ map[string][]string{ruleName: []string{
+ prefix + "updated.go",
+ prefix + "nochange.go",
+ prefix + "generated_updated.go",
+ prefix + "generated_nochange.go",
+ }},
+ map[string]string{
+ prefix + "updated.go": `package test
+
+type MessageState struct{}
+
+type M struct {
+ state MessageState ` + "`" + `protogen:"hybrid.v1"` + "`" + `
+ S *string
+}
+
+func (*M) Reset() { }
+func (*M) String() string { return "" }
+func (*M) ProtoMessage() { }
+
+func f(m *M) {
+ _ = m.S != nil
+}
+`,
+ prefix + "nochange.go": "package test\n",
+ },
+ map[string]string{
+ prefix + "generated_updated.go": `package test
+
+func g(m *M) {
+ _ = m.S != nil
+}
+`,
+ prefix + "generated_nochange.go": "package test\n",
+ },
+ nil)
+
+ ctx := context.Background()
+ for _, lvl := range []Level{None, Green, Yellow, Red} {
+ t.Run(string(lvl), func(t *testing.T) {
+ pkg, err := loader.LoadOne(ctx, l, &loader.Target{ID: ruleName})
+ if err != nil {
+ t.Fatalf("Can't load %q: %v:", ruleName, err)
+ }
+
+ cPkg := ConfiguredPackage{
+ Pkg: pkg,
+ Levels: []Level{lvl},
+ ProcessedFiles: syncset.New(),
+ UseBuilders: BuildersTestsOnly,
+ }
+ got, err := cPkg.Fix()
+ if err != nil {
+ t.Fatalf("Can't fix %q: %v", ruleName, err)
+ }
+
+ want := []*FixedFile{
+ {Path: pathPrefix + "updated.go", Modified: true, Generated: false},
+ {Path: pathPrefix + "nochange.go", Modified: false, Generated: false},
+ {Path: pathPrefix + "generated_updated.go", Modified: true, Generated: true},
+ {Path: pathPrefix + "generated_nochange.go", Modified: false, Generated: true},
+ }
+ ignored := []string{"Code", "Stats", "OriginalCode", "RedFixes"}
+ if lvl == None {
+ ignored = append(ignored, "Modified")
+ }
+
+ if d := cmp.Diff(want, got[lvl], cmpopts.IgnoreFields(FixedFile{}, ignored...)); d != "" {
+ t.Fatalf("Package() = %s, want %s; diff:\n%s", got[lvl], want, d)
+ }
+ })
+ }
+}
diff --git a/internal/fix/fixcursor.go b/internal/fix/fixcursor.go
new file mode 100644
index 0000000..0269d76
--- /dev/null
+++ b/internal/fix/fixcursor.go
@@ -0,0 +1,859 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "reflect"
+ "strings"
+ "unicode"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+ log "github.com/golang/glog"
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/types/typeutil"
+ "google.golang.org/open2opaque/internal/ignore"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/protodetecttypes"
+)
+
+// cursor is an argument to rewrite transformations. Rewrites can modify this state to share
+// information with other DST nodes in the same package.
+type cursor struct {
+ *dstutil.Cursor // Describes node to rewrite (referred as "current node" below). See dstutil.Cursor.
+
+ pkg *loader.Package // Package containing the current node.
+ curFile *loader.File
+ curFileDST *dst.File
+ imports *imports // Imports defined in the file containing the current node.
+ typesInfo *typesInfo
+ loader loader.Loader
+
+ lvl Level // Specifies level of rewrites to apply to the current node.
+
+ // A set of types to consider when updating code. Empty set means "update all".
+ typesToUpdate map[string]bool
+
+ // A set of types for which to always use builders, not setters.
+ // (e.g. "google.golang.org/protobuf/types/known/timestamppb").
+ //
+ // An empty (or nil) builderTypes means: use setters or builders for
+ // production/test code respectively (or follow the -use_builders flag if
+ // set).
+ builderTypes map[string]bool
+
+ // A list of files for which to always use builders, not setters.
+ builderLocations *ignore.List
+
+ // A cache of shouldLogCompositeType results. The value for a given key can be:
+ // - missing: no information for that type
+ // - nil: either:
+ // - the type is currently being processed (e.g. struct fields can refer
+ // to the struct type), or
+ // - the type was processed and it does not depend on protos
+ //
+ // - non-nil: cached result for a type that should be processed
+ // The cache serves as both: an optimization and a way to address cycles (see TestStats/recursive_type).
+ //
+ // The NoPtr version is used for calls with followPointers=false
+ shouldLogCompositeTypeCache *typeutil.Map
+ shouldLogCompositeTypeCacheNoPtr *typeutil.Map
+
+ // rewriteName is the function name of the open2opaque rewrite step that is
+ // currently running, e.g. appendProtosPre.
+ rewriteName string
+
+ // ASTID is the astv.ASTID() for the current DST node.
+ ASTID string
+
+ // debugLog will be passed as astv.File.DebugLog
+ debugLog map[string][]string
+
+ // Where should the tool use builders?
+ builderUseType BuilderUseType
+
+ testonly bool // does this file belong to a testonly library?
+
+ helperVariableNames map[string]bool
+
+ numUnsafeRewritesByReason map[unsafeReason]int
+}
+
+func (c *cursor) Logf(format string, a ...any) {
+ if c.ASTID == "" {
+ return
+ }
+ msg := fmt.Sprintf(string(c.lvl)+"/"+c.rewriteName+": "+format, a...)
+ c.debugLog[c.ASTID] = append(c.debugLog[c.ASTID], msg)
+}
+
+func (c *cursor) Replace(n dst.Node) {
+ c.Cursor.Replace(n)
+}
+
+type unsafeReason int
+
+const (
+ // Unknown means the reason not nown or ambiguous
+ Unknown unsafeReason = iota
+ // PointerAlias means the rewrite removes pointer aliasing
+ PointerAlias
+ // SliceAlias means the rewrite removes slice aliasing
+ SliceAlias
+ // InexpressibleAPIUsage means the rewrite changes the behavior because
+ // the original behavior cannot be expressed in the opaque API (this
+ // usually leads to build failures).
+ InexpressibleAPIUsage
+ // PotentialBuildBreakage means the rewrite might induce a build breakage.
+ PotentialBuildBreakage
+ // EvalOrderChange means the evaluation order changes by the rewrite
+ // (e.g. multi-assignments are rewritten into multiple single
+ // assignments).
+ EvalOrderChange
+ // IncompleteRewrite means the rewrite is incomplete and further manual
+ // changes are needed. In most cases a comment is left in the code.
+ IncompleteRewrite
+ // OneofFieldAccess means a oneof field is directly accessed in the
+ // hybrid/open API. This cannot be expressed in the opaque API.
+ OneofFieldAccess
+ // ShallowCopy means the rewrite contains a shallow copy (before and
+ // after) but shallow copies are unsafe in the opaque API.
+ ShallowCopy
+ // MaybeOneofChange means the rewrite produces code that might unset an
+ // oneof field that was previously set to an invalid state (type set
+ // but value not set).
+ MaybeOneofChange
+ // MaybeSemanticChange means the rewrite might produce invalid code
+ // because identifier refer to different objects than before.
+ MaybeSemanticChange
+ // MaybeNilPointerDeref means the rewrite might produce code that leads
+ // to nil pointer references that were not in the code before.
+ MaybeNilPointerDeref
+)
+
+func (c *cursor) ReplaceUnsafe(n dst.Node, rt unsafeReason) {
+ c.numUnsafeRewritesByReason[rt]++
+ c.Replace(n)
+}
+
+func (c *cursor) InsertBefore(n dst.Node) {
+ c.Cursor.InsertBefore(n)
+}
+
+func (c *cursor) underlyingTypeOf(expr dst.Expr) types.Type {
+ return c.typeOf(expr).Underlying()
+}
+
+func (c *cursor) underlyingTypeOfOrNil(expr dst.Expr) types.Type {
+ t := c.typeOfOrNil(expr)
+ if t == nil {
+ return nil
+ }
+ return t.Underlying()
+}
+
+func (c *cursor) isBuiltin(expr dst.Expr) bool {
+ tv, ok := c.typesInfo.types[expr]
+ return ok && tv.IsBuiltin()
+}
+
+func (c *cursor) hasType(expr dst.Expr) bool {
+ return c.typesInfo.typeOf(expr) != nil
+}
+
+func (c *cursor) typeAndValueOf(expr dst.Expr) (types.TypeAndValue, bool) {
+ tv, ok := c.typesInfo.types[expr]
+ return tv, ok
+}
+
+func (c *cursor) typeOfOrNil(expr dst.Expr) types.Type {
+ t := c.typesInfo.typeOf(expr)
+ if t != nil {
+ return t
+ }
+ // We don't know the type of "_" (it doesn't have one). Don't panic
+ // because this is a known possibility and using "invalid" type here
+ // makes writing rules using c.typeOf easier.
+ if ident, ok := expr.(*dst.Ident); ok && ident.Name == "_" {
+ return types.Typ[types.Invalid]
+ }
+ return nil
+}
+
+func (c *cursor) typeOf(expr dst.Expr) types.Type {
+ if t := c.typeOfOrNil(expr); t != nil {
+ return t
+ }
+ // The following code is unreachable. It exists to provide better error messages when there are
+ // bugs in how we handle type information (e.g. forget to update type info map). This is
+ // technically dead code but please don't delete it. It is very helpful during development.
+ buf := new(bytes.Buffer)
+ if err := dst.Fprint(buf, expr, dst.NotNilFilter); err != nil {
+ buf = bytes.NewBufferString("<can't print the expression>")
+ }
+ panic(fmt.Sprintf("don't know type for expression %T %s", expr, buf.String()))
+}
+
+func (c *cursor) objectOf(ident *dst.Ident) types.Object {
+ return c.typesInfo.objectOf(ident)
+}
+
+func (c *cursor) setType(expr dst.Expr, t types.Type) {
+ c.typesInfo.types[expr] = types.TypeAndValue{Type: t}
+}
+
+func (c *cursor) setUse(ident *dst.Ident, o types.Object) {
+ c.typesInfo.uses[ident] = o
+ c.setType(ident, o.Type())
+}
+
+func (c *cursor) setVoidType(expr dst.Expr) {
+ c.typesInfo.types[expr] = voidType
+}
+
+var voidType types.TypeAndValue
+
+func (c *cursor) isTest() bool {
+ return strings.HasSuffix(c.curFile.Path, "_test.go") || c.testonly
+}
+
+func findEnclosingLiteral(file *dst.File, needle *dst.CompositeLit) *dst.CompositeLit {
+ var enclosing *dst.CompositeLit
+ dstutil.Apply(file,
+ func(cur *dstutil.Cursor) bool {
+ if enclosing == nil {
+ if cl, ok := cur.Node().(*dst.CompositeLit); ok {
+ enclosing = cl
+ }
+ }
+
+ return true
+ },
+ func(cur *dstutil.Cursor) bool {
+ if cur.Node() == needle {
+ return false // found the needle, stop traversal
+ }
+
+ if cur.Node() == enclosing {
+ // Leaving the top-level *dst.CompositeLit, the needle was not
+ // found.
+ enclosing = nil
+ return true
+ }
+
+ return true
+ })
+ return enclosing
+}
+
+func deepestNesting(lit *dst.CompositeLit) int {
+ var nesting, deepest int
+ dstutil.Apply(lit,
+ func(cur *dstutil.Cursor) bool {
+ if _, ok := cur.Node().(*dst.CompositeLit); ok {
+ nesting++
+ if nesting > deepest {
+ deepest = nesting
+ }
+ }
+ return true
+ },
+ func(cur *dstutil.Cursor) bool {
+ if _, ok := cur.Node().(*dst.CompositeLit); ok {
+ nesting--
+ }
+ return true
+ })
+ return deepest
+}
+
+func (c *cursor) messagesInvolved(lit *dst.CompositeLit) int {
+ var involved int
+ dstutil.Apply(lit,
+ func(cur *dstutil.Cursor) bool {
+ if cur.Node() == lit {
+ // Do not count the top-level composite literal itself.
+ return true
+ }
+ if cl, ok := cur.Node().(*dst.CompositeLit); ok {
+ // Verify this composite literal is a proto message (builder or
+ // actual message), as opposed to a []int32{…}, for example.
+ t := c.typeOf(cl)
+ if strings.HasSuffix(t.String(), "_builder") {
+ involved++
+ return true
+ }
+ if _, ok := c.messageTypeName(t); ok {
+ involved++
+ }
+ }
+ return true
+ },
+ nil)
+ return involved
+}
+
+func (c *cursor) useBuilder(lit *dst.CompositeLit) bool {
+ if c.builderUseType == BuildersEverywhere {
+ return true
+ }
+
+ if c.builderLocations.Contains(c.curFile.Path) {
+ return true
+ }
+
+ // We treat codelabs like test code: performance cannot be a concern, so
+ // prefer readability.
+ isTestOrCodelab := c.isTest() || strings.Contains(c.curFile.Path, "codelab")
+ if c.builderUseType == BuildersTestsOnly && isTestOrCodelab {
+ return true
+ }
+
+ elem := c.typeOf(lit)
+ if ptr, ok := elem.(*types.Pointer); ok {
+ elem = ptr.Elem()
+ }
+ if named, ok := elem.(*types.Named); ok {
+ obj := named.Obj()
+ typeName := obj.Pkg().Path() + "." + obj.Name()
+ c.Logf("always use builders for %q? %v", typeName, c.builderTypes[typeName])
+ if c.builderTypes[typeName] {
+ return true
+ }
+ }
+
+ // Check for the nesting level: too deeply nested literals cannot be
+ // converted to setters without a loss of readability, so stick to builders
+ // for these.
+ enclosing := findEnclosingLiteral(c.curFileDST, lit)
+ if enclosing == nil {
+ c.Logf("BUG?! No enclosing literal found for %p / %v", lit, lit)
+ return false // use setters
+ }
+ messagesInvolved := c.messagesInvolved(enclosing)
+ deepestNesting := deepestNesting(enclosing)
+
+ c.Logf("CompositeLit nesting: %d", deepestNesting)
+ c.Logf("Messages involved: %d", messagesInvolved)
+ // NOTE(stapelberg): The deepestNesting condition might seem irrelevant
+ // thanks to the messagesInvolved condition, but keeping both allows us to
+ // adjust the number of either threshold without having to disable/re-enable
+ // the relevant code.
+ if deepestNesting >= 4 || messagesInvolved >= 4 {
+ return true // use builders
+ }
+
+ return false // use setters
+}
+
+// grabNameInScope finds and returns a free name (starting with prefix) in the
+// provided scope, reserving it in the scope to ensure subsequent calls return a
+// different name.
+func grabNameInScope(pkg *types.Package, s *types.Scope, prefix string) string {
+ // Find a free name
+ name := prefix
+ cnt := 2
+ for _, obj := s.LookupParent(name, token.NoPos); obj != nil; _, obj = s.LookupParent(name, token.NoPos) {
+ middle := ""
+ if unicode.IsNumber(rune(prefix[len(prefix)-1])) {
+ // Inject an extra h (stands for helper) if the prefix ends in a
+ // number: Generate m2h2 instead of m22, which might be misleading.
+ middle = "h"
+ }
+ name = fmt.Sprintf("%s%s%d", prefix, middle, cnt)
+ cnt++
+ }
+
+ // Insert an object with this name in the scope so that subsequent calls of
+ // grabNameInScope() cannot return the same name.
+ s.Insert(types.NewTypeName(token.NoPos, pkg, name, nil))
+
+ return name
+}
+
+func (c *cursor) helperNameFor(n dst.Node, t types.Type) string {
+ // dst.Node objects do not have position information, so we need to
+ // look up the corresponding ast.Node to get to the scope.
+ astNode, ok := c.typesInfo.astMap[n]
+ if !ok {
+ log.Fatalf("BUG: %s: no corresponding go/ast node for dave/dst node %T / %p / %v", c.pkg.TypePkg.Path(), n, n, n)
+ }
+ inner := c.pkg.TypePkg.Scope().Innermost(astNode.Pos())
+ if _, ok := astNode.(*ast.IfStmt); ok {
+ // An *ast.IfStmt creates a new scope. For inserting a helper before the
+ // *ast.IfStmt, we are interested in the parent scope, i.e. the scope
+ // that contains the *ast.IfStmt, and our helper variable.
+ inner = inner.Parent()
+ }
+ helperName := grabNameInScope(c.pkg.TypePkg, inner, helperVarNameForType(t))
+ c.helperVariableNames[helperName] = true
+ return helperName
+}
+
+func isCompositeLit(n, parent dst.Node) (*dst.CompositeLit, bool) {
+ // CompositeLits are often wrapped in a UnaryExpr (&pb.M2{…}), but not
+ // always: when defining a slice, the type is inferred, e.g. []*pb.M2{{…}}.
+ expr, ok := n.(dst.Expr)
+ if !ok {
+ return nil, false
+ }
+ if ue, ok := expr.(*dst.UnaryExpr); ok {
+ if ue.Op != token.AND {
+ return nil, false
+ }
+ expr = ue.X
+ } else {
+ // Ensure the parent is not a UnaryExpr to avoid triggering twice for
+ // UnaryExprs (once on the UnaryExpr, once on the contained
+ // CompositeLit).
+ if _, ok := parent.(*dst.UnaryExpr); ok {
+ return nil, false
+ }
+ }
+ lit, ok := expr.(*dst.CompositeLit)
+ return lit, ok
+}
+
+// builderCLit returns a composite literal that is a non-empty one representing
+// a protocol buffer.
+func (c *cursor) builderCLit(n dst.Node, parent dst.Node) (*dst.CompositeLit, bool) {
+ lit, ok := isCompositeLit(n, parent)
+ if !ok {
+ return nil, false
+ }
+ if len(lit.Elts) == 0 { // Don't use builders for constructing zero values.
+ return nil, false
+ }
+ if !c.shouldUpdateType(c.typeOf(lit)) {
+ return nil, false
+ }
+ for _, e := range lit.Elts {
+ // This shouldn't be possible because of noUnkeyedLiteral (or
+ // XXX_NoUnkeyedLiterals) field included in all structs representing
+ // protocol buffers. We handle this just in case we run into a very old
+ // proto.
+ if _, ok := e.(*dst.KeyValueExpr); !ok {
+ return nil, false
+ }
+ }
+ return lit, true
+}
+
+func (c *cursor) selectorForProtoMessageType(t types.Type) *dst.SelectorExpr {
+ // Get to the elementary type (pb.M2) if this is a pointer type (*pb.M2).
+ elem := t
+ if ptr, ok := elem.(*types.Pointer); ok {
+ elem = ptr.Elem()
+ }
+ named, ok := elem.(*types.Named)
+ if !ok {
+ log.Fatalf("BUG: proto message unexpectedly not a named type (but %T)?!", elem)
+ }
+ obj := named.Obj()
+
+ sel := &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(obj.Pkg().Path())},
+ Sel: &dst.Ident{Name: obj.Name()},
+ }
+ c.setType(sel, t)
+ c.setType(sel.X, types.Typ[types.Invalid])
+ c.setType(sel.Sel, types.Typ[types.Invalid])
+ return sel
+}
+
+// isSideEffectFree returns true if x can be safely called a different number of
+// times after the rewrite. It returns false if it can't say for sure that
+// that's the case.
+//
+// x must be one of "X", "X.F" or "X.GetF()" where any call is known to have no
+// relevant side effects (e.g. method calls on protocol buffers).
+func (c *cursor) isSideEffectFree(x dst.Expr) bool {
+ var sel *dst.SelectorExpr
+ switch x := x.(type) {
+ case *dst.Ident:
+ return true
+ case *dst.BasicLit:
+ return true
+ case *dst.IndexExpr:
+ return c.isSideEffectFree(x.Index) && c.isSideEffectFree(x.X)
+ case *dst.SelectorExpr:
+ sel = x
+ case *dst.CallExpr:
+ callSel, ok := x.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ sel = callSel
+ if _, ok := c.messageTypeName(c.typeOf(callSel.X)); !ok {
+ return false
+ }
+ default:
+ return false
+ }
+
+ var hasCall bool
+ dstutil.Apply(sel, func(cur *dstutil.Cursor) bool {
+ call, ok := cur.Node().(*dst.CallExpr)
+ if !ok {
+ return true
+ }
+ // If this is a method call on a proto then we don't report it as we know
+ // that those don't have relevant side effects.
+ if sel, ok := call.Fun.(*dst.SelectorExpr); ok {
+ if _, ok := c.messageTypeName(c.typeOf(sel.X)); ok {
+ return true
+ }
+ }
+ hasCall = true
+ return true
+ }, nil)
+ return !hasCall
+}
+
+func isInterfaceVararg(t types.Type) bool {
+ vararg, ok := t.(*types.Slice)
+ if !ok {
+ return false
+ }
+ _, ok = vararg.Elem().(*types.Interface)
+ return ok
+}
+
+func isString(t types.Type) bool {
+ b, ok := t.(*types.Basic)
+ return ok && b.Kind() == types.String
+}
+
+// looksLikePrintf returns true for any function that looks like a printing one. For example:
+// fmt.Print, log.Print, log.Info, (*testing.T).Error, etc.
+//
+// We can't enumerate all such functions so we use a heuristic that tries to classify a call
+// expression based on its type.
+func (c *cursor) looksLikePrintf(n dst.Node) bool {
+ // We say that a function/method is a printer if:
+ // - its first argument is a "string" (format) and it is followed by "...interface{}" argument (arguments), or
+ // - its sole argument is "...interface{}" (arguments)
+ call, ok := n.(*dst.CallExpr)
+ if !ok {
+ return false
+ }
+ if sel, ok := call.Fun.(*dst.SelectorExpr); ok {
+ if strings.HasSuffix(sel.Sel.Name, "Errorf") {
+ return true
+ }
+ }
+ sig, ok := c.typeOf(call.Fun).(*types.Signature)
+ if !ok {
+ return false
+ }
+ switch p := sig.Params(); p.Len() {
+ case 1:
+ return isInterfaceVararg(p.At(0).Type())
+ case 2:
+ return isString(p.At(0).Type()) && isInterfaceVararg(p.At(1).Type())
+ default:
+ return false
+ }
+}
+
+// shouldUpdateType returns true for types which we consider to represent protocol buffers generated
+// by the proto generator that the user requested to migrate. That is, types that should be
+// considered for a rewrite during the open2opaque protocol buffer migration.
+//
+// There's also a shouldTrackType function, which returns true for types that we
+// want to track for the migration purposes. For example, we want to track
+// operations on type T in
+//
+// type T pb.M
+//
+// but we don't want to rewrite accesses to type yet.
+func (c *cursor) shouldUpdateType(t types.Type) bool {
+ if !c.shouldTrackType(t) {
+ return false
+ }
+ _, ok := c.messageTypeName(t)
+ return ok
+}
+
+// messageTypeName returns the name of the protocol buffer message with type
+// t. It returns an empty string and false if t is not a protocol buffer message
+// type.
+func (c *cursor) messageTypeName(t types.Type) (name string, ok bool) {
+ name = t.String()
+
+ if nt, ok := t.(*types.Named); ok && isPtr(nt.Underlying()) {
+ // A non-pointer named type whose underlying type is a pointer can be
+ // neither proto struct nor pointer to a proto struct. If we were to
+ // return "true" for such type, it was most likely "type T *pb.M" for some
+ // proto type "pb.M".
+ return "", false
+ }
+
+ if p, ok := t.(*types.Pointer); ok {
+ t = p.Elem()
+ }
+
+ nt, ok := t.(*types.Named)
+ if !ok {
+ return "", false
+ }
+
+ // ProtoMessage exists on proto structs, but not on custom named types based
+ // on protos ("type T pb.M"). We don't want to rewrite code using such types
+ // with rules that use messageTypeName.
+ var hasProtoMessage bool
+ for i := 0; i < nt.NumMethods(); i++ {
+ if nt.Method(i).Name() == "ProtoMessage" {
+ hasProtoMessage = true
+ }
+ }
+ if !hasProtoMessage {
+ return "", false
+ }
+
+ if st := (protodetecttypes.Type{T: t}); !st.IsMessage() || st.MessageAPI() == protodetecttypes.OpenAPI {
+ return "", false
+ }
+
+ return strings.TrimPrefix(name, "*"), true
+}
+
+// canAddr returns true the expression if it's legal to take address of that
+// expression.
+func (c *cursor) canAddr(x dst.Expr) bool {
+ tv, ok := c.typeAndValueOf(x)
+ if !ok {
+ return false
+ }
+ return tv.Addressable()
+}
+
+// enclosingStmt returns the closest ast.Node that opens a scope (opener) and
+// the ast.Node representing this scope (scope). E.g. for
+//
+// if ... {
+// n
+// }
+//
+// it returns the ast.IfStmt and the ast.IfStmt.Body nodes.
+// Either both or none of the return values are nil.
+func (c *cursor) enclosingASTStmt(n dst.Node) (scope ast.Node, opener ast.Node) {
+ lhsAst, ok := c.typesInfo.astMap[n]
+ if !ok {
+ c.Logf("BUG: no corresponding go/ast node for dave/dst node %T / %+v (was c.typesInfo.astMap not updated across rewrites?)", n, n)
+ return nil, nil
+ }
+ path, _ := astutil.PathEnclosingInterval(c.curFile.AST, lhsAst.Pos(), lhsAst.End())
+ for i := 0; i < len(path)-1; i++ {
+ // Check for types that open a new scope.
+ switch path[i].(type) {
+ case *ast.BlockStmt,
+ *ast.CaseClause,
+ *ast.CommClause:
+ return path[i], path[i+1]
+ }
+ }
+ return nil, nil
+}
+
+// useClearOrHas returns true for field accesses (sel) that should be handled
+// with either Has or Clear call in the context of a comparison/assignment
+// with/to nil.
+func (c *cursor) useClearOrHas(sel *dst.SelectorExpr) bool {
+ if isOneof(c.typeOf(sel.Sel)) {
+ return true
+ }
+ f := c.objectOf(sel.Sel).(*types.Var)
+ // Handle messages (either proto2 or proto3) and proto2 enums.
+ if p, ok := f.Type().(*types.Pointer); ok {
+ if _, ok := p.Elem().(*types.Named); ok {
+ return true
+ }
+ }
+ // Handle non-enum proto2 scalars.
+ isProto2 := !isProto3Field(c.typeOf(sel.X), f.Name())
+ if isProto2 {
+ switch ft := f.Type().(type) {
+ case *types.Pointer:
+ if _, ok := ft.Elem().(*types.Basic); ok {
+ return true
+ }
+ case *types.Slice:
+ // Use Clear for bytes field, but not other repeated fields.
+ if basic, ok := ft.Elem().(*types.Basic); ok && basic.Kind() == types.Uint8 {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// isProto3Field returns true if given message is a proto3 message based on given field, else false.
+// It uses the "protobuf" struct tag on the field to determine if it contains "proto3" or not.
+//
+// This function considers both value and pointer types to be protocol buffer messages.
+func isProto3Field(m types.Type, field string) bool {
+ p, ok := m.Underlying().(*types.Pointer)
+ if ok {
+ m = p.Elem()
+ }
+ s, ok := m.Underlying().(*types.Struct)
+ if !ok {
+ return false
+ }
+
+ numFields := s.NumFields()
+ for i := 0; i < numFields; i++ {
+ if s.Field(i).Name() == field {
+ // Following relies on current generator behavior of having def value always being at
+ // the end of the tag if it exists because value of "def" may contain ",". It also
+ // relies on the proto3 text not being in the first position.
+ st := s.Tag(i)
+ pb := reflect.StructTag(st).Get("protobuf")
+ if i := strings.Index(pb, ",def="); i > -1 {
+ pb = pb[:i]
+ }
+ return strings.Contains(pb, ",proto3")
+ }
+ }
+ return false
+}
+
+// Exactly one of expr or t should be non-nil. expr can be nil only if t is *types.Basic
+func (c *cursor) newProtoHelperCall(expr dst.Expr, t types.Type) dst.Expr {
+ if _, ok := t.(*types.Basic); expr == nil && !ok {
+ panic(fmt.Sprintf("t must be *types.Basic if expr is nil, but it is %T", t))
+ }
+ if t == nil && expr == nil {
+ panic("t and expr can't be both nil")
+ }
+ if t == nil {
+ t = c.typeOf(expr)
+ }
+
+ // Enums are represented as named types in generated files.
+ if t, ok := t.(*types.Named); ok {
+ fun := &dst.SelectorExpr{
+ X: expr,
+ Sel: &dst.Ident{Name: "Enum"},
+ }
+ c.setType(fun, types.NewSignature(
+ types.NewParam(token.NoPos, nil, "_", t),
+ types.NewTuple(),
+ types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.NewPointer(t))),
+ false))
+ c.setType(fun.Sel, c.typeOf(fun))
+
+ out := &dst.CallExpr{Fun: fun}
+ c.setType(out, types.NewPointer(t))
+ return out
+ }
+ bt := t.(*types.Basic)
+ if expr == nil {
+ expr = scalarTypeZeroExpr(c, bt)
+ }
+ hname := basicTypeHelperName(bt)
+ helper := c.imports.lookup(protoImport, hname)
+ out := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(protoImport)},
+ Sel: &dst.Ident{Name: hname},
+ },
+ Args: []dst.Expr{expr},
+ }
+ c.setType(out, types.NewPointer(t))
+ if helper != nil {
+ c.setType(out.Fun, helper.Type())
+ c.setUse(out.Fun.(*dst.SelectorExpr).Sel, helper)
+ } else {
+ // The "proto" package was not imported, so we do not have an actual
+ // type to assign.
+ c.setType(out.Fun, types.Typ[types.Invalid])
+ c.setType(out.Fun.(*dst.SelectorExpr).Sel, types.Typ[types.Invalid])
+ }
+ // We set the type for "proto" identifier to Invalid because that's consistent with what the
+ // typechecker does on new code. We need to distinguish "invalid" type from "no type was
+ // set" as the code panics on the later in order to catch issues with missing type updates.
+ c.setType(out.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid])
+
+ return out
+}
+
+// trackedProtoFieldSelector is a wrapper around protoFieldSelector that only
+// returns the field selector if the underlying proto message type should be
+// updated (i.e. it was specified in -types_to_update).
+func (c *cursor) trackedProtoFieldSelector(expr dst.Node) (*dst.SelectorExpr, bool) {
+ sel, ok := c.protoFieldSelector(expr)
+ if !ok {
+ return nil, false
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return nil, false // skip over expression without type info (silo'ed?)
+ }
+ if !c.shouldUpdateType(t) {
+ return nil, false
+ }
+ return sel, true
+}
+
+// protoFieldSelector checks whether expr is of the form "m.F" where m is a
+// protocol buffer message and "F" is a field. It returns expr as DST selector
+// if that's the case and true. Returns false otherwise.
+func (c *cursor) protoFieldSelector(expr dst.Node) (*dst.SelectorExpr, bool) {
+ sel, ok := expr.(*dst.SelectorExpr)
+ if !ok {
+ return nil, false
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return nil, false // skip over expression without type info (silo'ed?)
+ }
+ if _, messageType := c.messageTypeName(t); !messageType {
+ return nil, false
+ }
+ if strings.HasPrefix(sel.Sel.Name, "XXX_") {
+ return nil, false
+ }
+ if _, ok := c.underlyingTypeOf(sel.Sel).(*types.Signature); ok {
+ return nil, false
+ }
+ return sel, true
+}
+
+// protoFieldSelectorOrAccessor is like protoFieldSelector, but also permits
+// accessor methods like GetX, HasX, ClearX, SetX. If the expression is an
+// accessor, the second return value contains its signature (nil otherwise).
+func (c *cursor) protoFieldSelectorOrAccessor(expr dst.Node) (*dst.SelectorExpr, *types.Signature, bool) {
+ sel, ok := expr.(*dst.SelectorExpr)
+ if !ok {
+ return nil, nil, false
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return nil, nil, false // skip over expression without type info (silo'ed?)
+ }
+ if _, messageType := c.messageTypeName(t); !messageType {
+ return nil, nil, false
+ }
+ if strings.HasPrefix(sel.Sel.Name, "XXX_") {
+ return nil, nil, false
+ }
+ if sig, ok := c.underlyingTypeOf(sel.Sel).(*types.Signature); ok {
+ if strings.HasPrefix(sel.Sel.Name, "Has") ||
+ strings.HasPrefix(sel.Sel.Name, "Clear") ||
+ strings.HasPrefix(sel.Sel.Name, "Set") ||
+ strings.HasPrefix(sel.Sel.Name, "Get") {
+ return sel, sig, true
+ }
+ return nil, nil, false
+ }
+ return sel, nil, true
+}
diff --git a/internal/fix/fiximports.go b/internal/fix/fiximports.go
new file mode 100644
index 0000000..9131c32
--- /dev/null
+++ b/internal/fix/fiximports.go
@@ -0,0 +1,158 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/dave/dst"
+ log "github.com/golang/glog"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+// imports provides an API to work with package imports. It is a convenience wrapper around
+// type and AST information.
+type imports struct {
+ path2pkg map[string]*types.Package
+ renameByPath map[string]string // import path -> name; for renamed packages
+ renameByName map[string]string // name -> import path
+ importsToAdd []*dst.ImportSpec
+}
+
+// newImports creates imports for the package.
+func newImports(pkg *types.Package, f *ast.File) *imports {
+ out := &imports{
+ path2pkg: make(map[string]*types.Package),
+ renameByPath: make(map[string]string),
+ renameByName: make(map[string]string),
+ }
+
+ // path2pkg maps from import path to *types.Package, but for the entire
+ // package-under-analysis, not just for the file-under-analysis.
+ path2pkg := make(map[string]*types.Package)
+ for _, imp := range pkg.Imports() {
+ path2pkg[imp.Path()] = imp
+ }
+
+ astutil.Apply(f, func(c *astutil.Cursor) bool {
+ s, ok := c.Node().(*ast.ImportSpec)
+ if !ok {
+ return true
+ }
+ path, err := strconv.Unquote(s.Path.Value)
+ if err != nil {
+ log.Errorf("malformed source: %v", err)
+ return false
+ }
+
+ if pkg, ok := path2pkg[path]; ok {
+ out.path2pkg[path] = pkg
+ }
+
+ if s.Name == nil { // no rename
+ return false
+ }
+ out.renameByPath[path] = s.Name.Name
+ out.renameByName[s.Name.Name] = path
+ return false
+ }, nil)
+
+ return out
+}
+
+// name returns the name of import with the given import path. For example:
+//
+// "google.golang.org/protobuf/proto" => "proto"
+// goproto "google.golang.org/protobuf/proto" => "goproto"
+//
+// In case the import does not yet exist, it will be queued for addition.
+func (imp *imports) name(path string) string {
+ // Is the package already imported by the input source code?
+ if v, ok := imp.renameByPath[path]; ok {
+ return v
+ }
+
+ // Check if we already tried to add the import.
+ for _, i := range imp.importsToAdd {
+ if s, err := strconv.Unquote(i.Path.Value); err == nil && s == path {
+ if i.Name != nil {
+ return i.Name.Name
+ }
+ return filepath.Base(path)
+ }
+ }
+
+ // Import doesn't exist and we didn't try to add it yet. Add a new import.
+ p := imp.path2pkg[path]
+ if p == nil {
+ // path is an import path that does not occur in the source file. There
+ // are two situations in which this can happen:
+ //
+ // 1. The proto package (from third_party/golang/protobuf) was not
+ // imported, but is now necessary because helper functions like
+ // proto.String() are used after the rewrite.
+ //
+ // 2. A proto message is referenced without a corresponding import. For
+ // example, mypb.GetSubmessage() could be defined in the separate
+ // package myextrapb.
+ //
+ // We find an available name and add the required import(s).
+ name := imp.findAvailableName(path)
+ spec := &dst.ImportSpec{
+ Path: &dst.BasicLit{Kind: token.STRING, Value: strconv.Quote(path)},
+ }
+ if strings.HasSuffix(name, "pb") {
+ // The third_party proto package is not renamed, but all generated
+ // proto packages are.
+ spec.Name = &dst.Ident{Name: name}
+ }
+ imp.importsToAdd = append(imp.importsToAdd, spec)
+ return name
+
+ }
+ return p.Name()
+}
+
+// lookup returns a objects with givne name from import identified by the provided import path or nil if it doesn't exist.
+func (imp *imports) lookup(path, name string) types.Object {
+ p := imp.path2pkg[path]
+ if p == nil {
+ return nil
+ }
+ return p.Scope().Lookup(name)
+}
+
+// findAvailableName returns an available name to import a generated proto
+// package as.
+//
+// We try xpb, x2pb, x3pb, etc. (x stands for expression protobuf, or extra
+// protobuf). This way, humans editing the source can recognize the placeholder
+// name and replace it with something more descriptive and more inline with the
+// respective team style.
+func (imp *imports) findAvailableName(path string) string {
+ if !strings.HasSuffix(path, "go_proto") {
+ // default name for non proto imports, assumed to be available
+ return filepath.Base(path)
+ }
+
+ name := "xpb"
+ cnt := 2
+ for {
+ if _, ok := imp.renameByName[name]; !ok {
+ break // name available
+ }
+ name = fmt.Sprintf("x%dpb", cnt)
+ cnt++
+ }
+ imp.renameByName[name] = path
+ imp.renameByPath[path] = name
+ return name
+}
diff --git a/internal/fix/get.go b/internal/fix/get.go
new file mode 100644
index 0000000..f635c5a
--- /dev/null
+++ b/internal/fix/get.go
@@ -0,0 +1,421 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+// assignGet rewrites a direct scalar field access on the rhs of variable
+// definitions, e.g.:
+//
+// v := m.Field
+// =>
+// var v *fieldType
+// if m.HasField() {
+// v = proto.Helper(m.GetField())
+// }
+func assignGet(c *cursor) {
+ n := c.Node()
+ as, ok := n.(*dst.AssignStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for AssignStmt)", n)
+ return
+ }
+ if len(as.Lhs) != 1 {
+ c.Logf("ignoring: len(Lhs) != 1")
+ return
+ }
+ lhsID, ok := as.Lhs[0].(*dst.Ident)
+ if !ok {
+ c.Logf("ignoring lhs %T (looking for Ident)", as.Lhs[0])
+ return
+ }
+ if len(as.Rhs) != 1 {
+ c.Logf("ignoring: len(Rhs) != 1")
+ return
+ }
+ if as.Tok != token.DEFINE {
+ c.Logf("ignoring %v (looking for token.DEFINE)", as.Tok)
+ return
+ }
+ rhs := as.Rhs[0]
+ if !isPtrToBasic(c.underlyingTypeOf(rhs)) {
+ c.Logf("ignoring: accessed field is not a scalar field")
+ return
+ }
+ if !c.isSideEffectFree(rhs) {
+ c.Logf("ignoring: accessor expression is not side effect free")
+ return
+ }
+ field, ok := c.trackedProtoFieldSelector(rhs)
+ if !ok {
+ c.Logf("ignoring: rhs is not a proto field selector")
+ return
+ }
+
+ lhsExpr := dst.Clone(lhsID).(*dst.Ident)
+ c.setType(lhsExpr, c.typeOf(lhsID))
+ field2 := cloneSelectorExpr(c, field) // for Get
+ asStmt := &dst.AssignStmt{
+ Tok: token.DEFINE,
+ Lhs: []dst.Expr{lhsExpr},
+ }
+
+ if hasNeeded(c, field) {
+ field3 := cloneSelectorExpr(c, field) // for Get
+ rhs := valueOrNil(c,
+ // Intentionally drop node decorations to avoid spurious line breaks
+ // inside a proto.ValueOrNil() call.
+ sel2call(c, "Has", field2, nil, dst.NodeDecs{}),
+ field3,
+ *n.Decorations())
+ // Avoid a line break between return and proto.ValueOrNil().
+ rhs.Decorations().Before = dst.None
+ asStmt.Rhs = append(asStmt.Rhs, rhs)
+ } else {
+ asStmt.Rhs = append(asStmt.Rhs, c.newProtoHelperCall(sel2call(c, "Get", field2, nil, *rhs.Decorations()), nil))
+ }
+ moveDecsBeforeStart(asStmt, asStmt.Rhs[0])
+ c.ReplaceUnsafe(asStmt, PointerAlias)
+}
+
+// assignGet rewrites a direct scalar field access in a return statement, e.g.:
+//
+// return m.Field
+// =>
+// if !m.HasField() {
+// return nil
+// }
+// return proto.Helper(m.GetField())
+func returnGet(c *cursor) {
+ n := c.Node()
+ rs, ok := n.(*dst.ReturnStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for ReturnStmt)", n)
+ return
+ }
+ // Technically we could handle this case but it is not very common and
+ // would require some work to properly clone all the nodes and keep
+ // the types up to date.
+ if len(rs.Results) != 1 {
+ c.Logf("ignoring: len(Results) != 1")
+ return
+ }
+ rhs := rs.Results[0]
+ if !isPtrToBasic(c.underlyingTypeOf(rhs)) {
+ c.Logf("ignoring: accessed field is not a scalar field")
+ return
+ }
+ if !c.isSideEffectFree(rhs) {
+ c.Logf("ignoring: accessor expression is not side effect free")
+ return
+ }
+ field, ok := c.trackedProtoFieldSelector(rhs)
+ if !ok {
+ c.Logf("ignoring: rhs is not a proto field selector")
+ return
+ }
+
+ ret := &dst.ReturnStmt{}
+ if hasNeeded(c, field) {
+ // return proto.ValueOrNil(m.HasField(), m.GetField)
+ field1 := cloneSelectorExpr(c, field) // for Has
+ field2 := cloneSelectorExpr(c, field) // for Get
+
+ ret.Results = append(ret.Results, valueOrNil(c,
+ // Intentionally drop node decorations to avoid spurious line breaks
+ // inside a proto.ValueOrNil() call.
+ sel2call(c, "Has", field1, nil, dst.NodeDecs{}),
+ field2,
+ *n.Decorations()))
+ } else {
+ // return proto.Helper(m.GetField())
+ field2 := cloneSelectorExpr(c, field) // for Get
+ ret.Results = append(ret.Results, c.newProtoHelperCall(sel2call(c, "Get", field2, nil, *rhs.Decorations()), nil))
+ }
+ moveDecsBeforeStart(ret, ret.Results[0])
+ c.ReplaceUnsafe(ret, PointerAlias)
+}
+
+// Move decorations (line breaks and comments) from src to dest.
+func moveDecsBeforeStart(dest, src dst.Node) {
+ dest.Decorations().Before = src.Decorations().Before
+ dest.Decorations().Start = src.Decorations().Start
+ src.Decorations().Before = dst.None
+ src.Decorations().Start = nil
+}
+
+// getPre rewrites the code to use Get methods. This function is executed by
+// traversing the tree in preorder. getPre rewrites assignment and return
+// statements that assign/return with exactly one direct scalar field access
+// expression. getPost handles all other cases of direct field access rewrites
+// that need getter.
+func getPre(c *cursor) bool {
+ if _, ok := c.Parent().(*dst.BlockStmt); !ok {
+ c.Logf("ignoring node with parent of type %T (looking for BlockStmt)", c.Parent())
+ return true
+ }
+ if !c.lvl.ge(Yellow) {
+ return true
+ }
+ assignGet(c)
+ returnGet(c)
+ return true
+}
+
+// getPost rewrites the code to use Get methods. This function is executed by
+// traversing the tree in postorder
+func getPost(c *cursor) bool {
+ // &m.F => proto.Helper(m.GetF()) // proto3 scalars
+ // &m.F => no rewrite // everything else
+ if ue, ok := c.Node().(*dst.UnaryExpr); ok && ue.Op == token.AND && c.lvl.ge(Red) {
+ field, ok := c.trackedProtoFieldSelector(ue.X)
+ if !ok {
+ return true
+ }
+ if t := c.typeOf(field); isScalar(t) && !isPtrToBasic(t) {
+ c.ReplaceUnsafe(c.newProtoHelperCall(sel2call(c, "Get", field, nil, *c.Node().Decorations()), t), PointerAlias)
+ return true
+ }
+ markMissingRewrite(field, "address of field")
+ return true
+ }
+ if ue, ok := c.Parent().(*dst.UnaryExpr); ok && ue.Op == token.AND {
+ return true
+ }
+
+ if isLValue(c) {
+ return true
+ }
+ n, ok := c.Node().(dst.Expr)
+ if !ok {
+ return true
+ }
+
+ if _, ok := c.Parent().(*dst.IncDecStmt); ok {
+ return true
+ }
+
+ // *m.F => m.GetF() for proto2 scalars
+ if isDeref(n) && isBasic(c.underlyingTypeOf(n)) {
+ field, ok := c.trackedProtoFieldSelector(dstutil.Unparen(addr(c, n)))
+ if !ok {
+ return true
+ }
+ c.Replace(sel2call(c, "Get", field, nil, *n.Decorations()))
+ return true
+ }
+
+ field, ok := c.trackedProtoFieldSelector(n)
+ if !ok {
+ return true
+ }
+
+ // Oneofs are not fields (members of the oneof union are fields) and should
+ // not have the Get method. In the open API, oneofs could be used as objects
+ // of their own which was incompatible with the proto spec.
+ //
+ // Hence we have to explicitly ignore those cases.
+ if isOneof(c.typeOf(field)) {
+ if c.lvl.ge(Red) {
+ c.numUnsafeRewritesByReason[OneofFieldAccess]++
+ addCommentAbove(c.Parent(), field, "// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).")
+ }
+ return true
+ }
+
+ // m.F => m.GetF() for all except proto2 scalar fields.
+ if !isPtrToBasic(c.underlyingTypeOf(n)) {
+ if isPtr(c.typeOf(field.X)) || c.canAddr(field.X) {
+ c.Replace(sel2call(c, "Get", field, nil, *n.Decorations()))
+ } else if c.lvl.ge(Red) {
+ c.ReplaceUnsafe(sel2call(c, "Get", field, nil, *n.Decorations()), InexpressibleAPIUsage)
+ }
+ return true
+ }
+
+ // for proto2 scalars:
+ // m.F => m.GetF().Enum() for enums
+ // m.F => proto.Helper(m.GetF()) otherwise
+ if c.lvl.ge(Yellow) { // for proto2 scalars we loose aliasing
+ // Don't do this rewrite:
+ // *m.F => *proto.Helper(m.GetF())
+ // as it rarely makes sense.
+ //
+ // We could get here if "*m.F" wasn't rewritten to "m.GetF()"
+ // for some reason (e.g. we don't rewrite "*m.F++" to "m.GetF()++").
+ if _, ok := c.Parent().(*dst.StarExpr); ok {
+ return true
+ }
+ if hasNeeded(c, field) {
+ c.ReplaceUnsafe(funcLiteralForHas(c, n, field), PointerAlias)
+ } else {
+ c.ReplaceUnsafe(sel2call(c, "Get", field, nil, *n.Decorations()), PointerAlias)
+ }
+ return true
+ }
+
+ return true
+}
+
+func funcLiteralForHas(c *cursor, n dst.Expr, field *dst.SelectorExpr) dst.Node {
+ nodeElemType := c.typeOf(n)
+ if ptr, ok := nodeElemType.(*types.Pointer); ok {
+ nodeElemType = ptr.Elem()
+ }
+ msgType := c.typeOf(field.X)
+
+ // We need two copies of field. They are identical.
+ field1 := cloneSelectorExpr(c, field) // for Has
+ field2 := cloneSelectorExpr(c, field) // for Get
+
+ if c.isSideEffectFree(field.X) {
+ // Call proto.ValueOrNil() directly, no function literal needed.
+ return valueOrNil(c,
+ sel2call(c, "Has", field1, nil, *n.Decorations()),
+ field2,
+ *n.Decorations())
+ }
+
+ var retElemType dst.Expr = &dst.Ident{Name: nodeElemType.String()}
+ if named, ok := nodeElemType.(*types.Named); ok {
+ pkgID := &dst.Ident{Name: c.imports.name(named.Obj().Pkg().Path())}
+ c.setType(pkgID, types.Typ[types.Invalid])
+ pkgSel := &dst.Ident{Name: named.Obj().Name()}
+ c.setType(pkgSel, types.Typ[types.Invalid])
+ retElemType = &dst.SelectorExpr{
+ X: pkgID,
+ Sel: pkgSel,
+ }
+ }
+ c.setType(retElemType, nodeElemType)
+ retType := &dst.StarExpr{X: retElemType}
+ c.setType(retType, types.NewPointer(nodeElemType))
+
+ msgParamSel := c.selectorForProtoMessageType(msgType)
+ msgParamType := &dst.StarExpr{X: msgParamSel}
+ c.setType(msgParamType, msgType)
+
+ msgParam := &dst.Ident{Name: "msg"}
+ c.setType(msgParam, msgType)
+ field1.X = &dst.Ident{Name: "msg"}
+ c.setType(field1.X, msgType)
+ field2.X = &dst.Ident{Name: "msg"}
+ c.setType(field2.X, msgType)
+
+ untypedNil := &dst.Ident{Name: "nil"}
+ c.setType(untypedNil, types.Typ[types.UntypedNil])
+
+ funcLit := &dst.FuncLit{
+ // func(msg *pb.M2) <type> {
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{
+ List: []*dst.Field{
+ &dst.Field{
+ Names: []*dst.Ident{msgParam},
+ Type: msgParamType,
+ },
+ },
+ },
+ Results: &dst.FieldList{
+ List: []*dst.Field{
+ &dst.Field{
+ Type: retType,
+ },
+ },
+ },
+ },
+ Body: &dst.BlockStmt{
+ List: []dst.Stmt{
+ // return proto.ValueOrNil(…)
+ &dst.ReturnStmt{
+ Results: []dst.Expr{
+ valueOrNil(c,
+ sel2call(c, "Has", field1, nil, *n.Decorations()),
+ field2,
+ *n.Decorations()),
+ },
+ },
+ },
+ },
+ }
+ // We do not know whether the proto package was imported, so we may not be
+ // able to construct the correct type signature. Set the type to invalid,
+ // like we do for any code involving the proto package.
+ c.setType(funcLit, types.Typ[types.Invalid])
+ c.setType(funcLit.Type, types.Typ[types.Invalid])
+
+ call := &dst.CallExpr{
+ Fun: funcLit,
+ Args: []dst.Expr{
+ field.X,
+ },
+ }
+ c.setType(call, c.typeOf(n))
+
+ return call
+}
+
+func valueOrNil(c *cursor, has dst.Expr, sel *dst.SelectorExpr, decs dst.NodeDecs) *dst.CallExpr {
+ fnsel := &dst.Ident{Name: "ValueOrNil"}
+ get := sel2call(c, "Get", sel, nil, decs)
+ fn := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(protoImport)},
+ Sel: fnsel,
+ },
+ Args: []dst.Expr{
+ has,
+ get.Fun,
+ },
+ }
+ fn.Decs.NodeDecs = decs
+
+ t := c.underlyingTypeOf(sel.Sel)
+ var pkg *types.Package
+ if use := c.objectOf(sel.Sel); use != nil {
+ pkg = use.Pkg()
+ }
+ value := types.NewParam(token.NoPos, pkg, "_", t)
+ recv := types.NewParam(token.NoPos, pkg, "_", c.underlyingTypeOf(sel.X))
+
+ getterType := types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false)
+ getterParam := types.NewParam(token.NoPos, pkg, "_", getterType)
+ boolParam := types.NewParam(token.NoPos, pkg, "_", types.Typ[types.Bool])
+ c.setType(fnsel, types.NewSignature(nil, types.NewTuple(boolParam, getterParam), types.NewTuple(value), false))
+ c.setType(fn, t)
+
+ c.setType(fn.Fun, c.typeOf(fnsel))
+
+ // We set the type for "proto" identifier to Invalid because that's consistent with what the
+ // typechecker does on new code. We need to distinguish "invalid" type from "no type was
+ // set" as the code panics on the later in order to catch issues with missing type updates.
+ c.setType(fn.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid])
+ return fn
+}
+
+func isDeref(n dst.Node) bool {
+ _, ok := n.(*dst.StarExpr)
+ return ok
+}
+
+// true if c.Node() is on left-hand side of an assignment
+func isLValue(c *cursor) bool {
+ p, ok := c.Parent().(*dst.AssignStmt)
+ if !ok {
+ return false
+ }
+ for _, ch := range p.Lhs {
+ if ch == c.Node() {
+ return true
+ }
+ }
+ return false
+}
diff --git a/internal/fix/get_test.go b/internal/fix/get_test.go
new file mode 100644
index 0000000..51384ca
--- /dev/null
+++ b/internal/fix/get_test.go
@@ -0,0 +1,329 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestGet(t *testing.T) {
+ tests := []test{{
+ desc: "proto2: get message ptr",
+ in: "_ = m2.M",
+ want: map[Level]string{
+ Green: "_ = m2.GetM()",
+ },
+ }, {
+ desc: "proto2: get message value",
+ in: "_ = *m2.M",
+ want: map[Level]string{
+ Green: "_ = *m2.GetM()",
+ },
+ }, {
+ desc: "proto2: get scalar ptr",
+ in: `_ = m2.S`,
+ want: map[Level]string{
+ Green: `_ = m2.S`,
+ Yellow: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`,
+ Red: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`,
+ },
+ }, {
+ desc: "proto2: get scalar ptr with comments",
+ in: `
+// before line
+extra := m2.S // end of line
+_ = extra
+`,
+ want: map[Level]string{
+ Green: `
+// before line
+extra := m2.S // end of line
+_ = extra
+`,
+ Yellow: `
+// before line
+extra := proto.ValueOrNil(m2.HasS(), m2.GetS) // end of line
+_ = extra
+`,
+ Red: `
+// before line
+extra := proto.ValueOrNil(m2.HasS(), m2.GetS) // end of line
+_ = extra
+`,
+ },
+ }, {
+ desc: "proto2: get enum ptr",
+ in: `_ = m2.E`,
+ want: map[Level]string{
+ Green: `_ = m2.E`,
+ Yellow: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`,
+ Red: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`,
+ },
+ }, {
+ desc: "proto2: get scalar value",
+ in: "_ = *m2.S",
+ want: map[Level]string{
+ Green: "_ = m2.GetS()",
+ },
+ }, {
+ desc: "proto2: scalar slice",
+ in: "_ = m2.Is",
+ want: map[Level]string{
+ Green: "_ = m2.GetIs()",
+ },
+ }, {
+ desc: "proto2: scalar slice and index",
+ in: "_ = m2.Is[0]",
+ want: map[Level]string{
+ Green: "_ = m2.GetIs()[0]",
+ },
+ }, {
+ desc: "proto2: message slice",
+ in: "_ = m2.Ms",
+ want: map[Level]string{
+ Green: "_ = m2.GetMs()",
+ },
+ }, {
+ desc: "proto2: message slice and index",
+ in: "_ = m2.Ms[0]",
+ want: map[Level]string{
+ Green: "_ = m2.GetMs()[0]",
+ },
+ }, {
+ desc: "proto2: get in function args",
+ extra: "func g2(*string, string, *pb2.M2, []int32, []*pb2.M2) { }",
+ in: "g2(m2.S, *m2.S, m2.M, m2.Is, m2.Ms)",
+ want: map[Level]string{
+ Green: "g2(m2.S, m2.GetS(), m2.GetM(), m2.GetIs(), m2.GetMs())",
+ },
+ }, {
+ desc: "proto3: get message ptr",
+ in: "_ = m3.M",
+ want: map[Level]string{
+ Green: "_ = m3.GetM()",
+ },
+ }, {
+ desc: "proto3: get message value",
+ in: "_ = *m3.M",
+ want: map[Level]string{
+ Green: "_ = *m3.GetM()",
+ },
+ }, {
+ desc: "proto3: get scalar",
+ in: "_ = m3.S",
+ want: map[Level]string{
+ Green: "_ = m3.GetS()",
+ },
+ }, {
+ desc: "field address",
+ in: `
+_ = &m2.S
+_ = &m2.Is
+_ = &m2.M
+_ = &m2.Ms
+
+_ = &m3.S
+_ = &m3.Is
+_ = &m3.M
+_ = &m3.Ms
+`,
+ want: map[Level]string{
+ Yellow: `
+_ = &m2.S
+_ = &m2.Is
+_ = &m2.M
+_ = &m2.Ms
+
+_ = &m3.S
+_ = &m3.Is
+_ = &m3.M
+_ = &m3.Ms
+`,
+ Red: `
+_ = &m2.S /* DO_NOT_SUBMIT: missing rewrite for address of field */
+_ = &m2.Is /* DO_NOT_SUBMIT: missing rewrite for address of field */
+_ = &m2.M /* DO_NOT_SUBMIT: missing rewrite for address of field */
+_ = &m2.Ms /* DO_NOT_SUBMIT: missing rewrite for address of field */
+
+_ = proto.String(m3.GetS())
+_ = &m3.Is /* DO_NOT_SUBMIT: missing rewrite for address of field */
+_ = &m3.M /* DO_NOT_SUBMIT: missing rewrite for address of field */
+_ = &m3.Ms /* DO_NOT_SUBMIT: missing rewrite for address of field */
+`,
+ },
+ }, {
+ desc: "proto3: scalar slice",
+ in: "_ = m3.Is",
+ want: map[Level]string{
+ Green: "_ = m3.GetIs()",
+ },
+ }, {
+ desc: "proto3: scalar slice and index",
+ in: "_ = m3.Is[0]",
+ want: map[Level]string{
+ Green: "_ = m3.GetIs()[0]",
+ },
+ }, {
+ desc: "proto3: message slice",
+ in: "_ = m3.Ms",
+ want: map[Level]string{
+ Green: "_ = m3.GetMs()",
+ },
+ }, {
+ desc: "proto3: message slice and index",
+ in: "_ = m3.Ms[0]",
+ want: map[Level]string{
+ Green: "_ = m3.GetMs()[0]",
+ },
+ }, {
+ desc: "proto3: get in function args",
+ extra: "func g3(string, *pb3.M3, []int32, []*pb3.M3) { }",
+ in: "g3(m3.S, m3.M, m3.Is, m3.Ms)",
+ want: map[Level]string{
+ Green: "g3(m3.GetS(), m3.GetM(), m3.GetIs(), m3.GetMs())",
+ },
+ }, {
+ desc: "rewriting Get only affects fields",
+ in: "_ = m2.GetS",
+ want: map[Level]string{
+ Green: "_ = m2.GetS",
+ Yellow: "_ = m2.GetS",
+ },
+ }, {
+ desc: "proto2: chained get",
+ in: "_ = *m2.M.M.M.Ms[0].M.S",
+ want: map[Level]string{
+ Green: "_ = m2.GetM().GetM().GetM().GetMs()[0].GetM().GetS()",
+ },
+ }, {
+ desc: "proto3: chained get",
+ in: "_ = m3.M.M.M.Ms[0].M.S",
+ want: map[Level]string{
+ Green: "_ = m3.GetM().GetM().GetM().GetMs()[0].GetM().GetS()",
+ },
+ }}
+
+ runTableTests(t, tests)
+}
+
+func TestNoFuncLiteral(t *testing.T) {
+ tt := []test{
+ {
+ desc: "return scalar field",
+ in: `
+_ = func() *float32 {
+ // before
+ return m2.F32 // end of line
+}
+`,
+ want: map[Level]string{
+ Red: `
+_ = func() *float32 {
+ // before
+ return proto.ValueOrNil(m2.HasF32(), m2.GetF32) // end of line
+}
+`,
+ },
+ },
+
+ {
+ desc: "return non-scalar fields",
+ in: `
+_ = func() any { // needs to be any because oneof field types are not exported
+ return m2.OneofField
+}
+
+_ = func() []byte {
+ return m2.Bytes
+}
+
+_ = func() *pb2.M2 {
+ return m2.M
+}
+`,
+ want: map[Level]string{
+ Red: `
+_ = func() any { // needs to be any because oneof field types are not exported
+ // DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+ return m2.OneofField
+}
+
+_ = func() []byte {
+ return m2.GetBytes()
+}
+
+_ = func() *pb2.M2 {
+ return m2.GetM()
+}
+`,
+ },
+ },
+
+ {
+ desc: "define var from field",
+ in: `
+f := m2.F32
+_ = f
+
+of := m2.OneofField
+_ = of
+`,
+ want: map[Level]string{
+ Red: `
+f := proto.ValueOrNil(m2.HasF32(), m2.GetF32)
+_ = f
+
+// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+of := m2.OneofField
+_ = of
+`,
+ },
+ },
+
+ {
+ desc: "define var from non-scalar fields",
+ in: `
+of := m2.OneofField
+_ = of
+
+b := m2.Bytes
+_ = b
+
+m := m2.M
+_ = m
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+of := m2.OneofField
+_ = of
+
+b := m2.GetBytes()
+_ = b
+
+m := m2.GetM()
+_ = m
+`,
+ },
+ },
+
+ {
+ desc: "derefence potential nil pointer",
+ in: `
+_ = *(m2.I32) + 35
+`,
+ want: map[Level]string{
+ Green: `
+_ = m2.GetI32() + 35
+`,
+ Red: `
+_ = m2.GetI32() + 35
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tt)
+}
diff --git a/internal/fix/has.go b/internal/fix/has.go
new file mode 100644
index 0000000..1809a33
--- /dev/null
+++ b/internal/fix/has.go
@@ -0,0 +1,257 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+// hasPre rewrites comparisons with nil in the following cases:
+// - for proto2 optional scalar fields, replace with "!m.HasF()" or "m.HasF()"
+// - for proto3 bytes fields, replace with "len(m.GetF()) == 0" or "len(m.GetF()) > 0"
+// - for simple conditionals (e.g. "if f := m.F; f != nil {") replace with "if m.HasF()"
+//
+// The function does not rewrite proto3 message fields, map fields, or repeated
+// fields. Those are handled by changing the direct field access to a Get call.
+//
+// This function is executed by traversing the tree in preorder.
+func hasPre(c *cursor) bool {
+ // Handle a special case that shows up frequently
+ //
+ // if f := m.F; f != nil { => if m.HasF() {
+ //
+ // This works for singular, scalar fields and avoids red, incorrect rewrites
+ // like the following:
+ //
+ // if f := proto.Helper(m.GetF()); f != nil {
+ if ifstmt, ok := c.Node().(*dst.IfStmt); ok {
+ if ifstmt.Init == nil {
+ return true
+ }
+ if ifstmt.Else != nil {
+ // For now, focus on the most common case. Perhaps we could add handling
+ // of "else" blocks one day.
+ return true
+ }
+ lhs, op, ok := comparisonWithNil(ifstmt.Cond)
+ if !ok {
+ return true
+ }
+ condIdent, ok := lhs.(*dst.Ident)
+ if !ok {
+ return true
+ }
+ // The init statement must define a new name that's used in the comparison
+ // with nil but is not used as a pointer otherwise. We only handle the
+ // common case of "f := m.F" for now.
+ def, ok := ifstmt.Init.(*dst.AssignStmt)
+ if !ok || def.Tok != token.DEFINE || len(def.Lhs) != 1 || len(def.Rhs) != 1 {
+ return true
+ }
+ rhsSel, ok := def.Rhs[0].(*dst.SelectorExpr)
+ if !ok {
+ return true
+ }
+ condObj := c.objectOf(condIdent)
+ if defIdent, ok := def.Lhs[0].(*dst.Ident); !ok || c.objectOf(defIdent) != condObj {
+ return true
+ }
+ if usesAsPointer(c, ifstmt.Body, condObj) {
+ return true
+ }
+
+ if hasCall, ok := hasCallForProtoField(c, rhsSel, op, dst.NodeDecs{}); ok {
+ ifstmt.Init = nil
+ ifstmt.Cond = hasCall
+ c.Replace(ifstmt)
+ dstutil.Apply(ifstmt.Body, nil, func(cur *dstutil.Cursor) bool {
+ star, ok := cur.Node().(*dst.StarExpr)
+ if !ok {
+ return true
+ }
+ ident, ok := star.X.(*dst.Ident)
+ if !ok {
+ return true
+ }
+ if c.objectOf(ident) == condObj {
+ // Is the pointee assigned to?
+
+ if as, ok := cur.Parent().(*dst.AssignStmt); ok {
+ var found bool
+ for _, l := range as.Lhs {
+ if l == cur.Node() {
+ // It is easier to replace the pointer
+ // dereference with a direct field access here
+ // and to rely on a later pass to rewrite it to
+ // a setter. The alternative is to replace it
+ // with a setter directly.
+ clone := cloneSelectorExpr(c, rhsSel)
+ star.X = clone
+ found = true
+ }
+ }
+ if found {
+ return true
+ }
+ }
+
+ // The pointee is used as value. It is safe to use the Getter.
+ cur.Replace(sel2call(c, "Get", cloneSelectorExpr(c, rhsSel), nil, *rhsSel.Decorations()))
+ }
+ return true
+ })
+ }
+ return true
+ }
+
+ // Handle conditionals that use a selector on the left-hand side:
+ //
+ // m.F != nil => m.HasF()
+ // m.F == nil => !m.HasF()
+ if _, _, ok := comparisonWithNil(c.Node()); !ok {
+ return true
+ }
+ expr := c.Node().(*dst.BinaryExpr)
+ if call, ok := hasCallForProtoField(c, expr.X, expr.Op, *expr.Decorations()); ok {
+ if sel := expr.X.(*dst.SelectorExpr); !ok || isPtr(c.typeOf(sel.X)) || c.canAddr(sel.X) {
+ c.Replace(call)
+ } else if c.lvl.ge(Red) {
+ c.ReplaceUnsafe(call, InexpressibleAPIUsage)
+ }
+ return false
+ }
+ field, ok := c.trackedProtoFieldSelector(expr.X)
+ if !ok {
+ return true
+ }
+ if s, ok := c.typeOf(field).(*types.Slice); ok {
+ // use "len" for proto3 bytes fields.
+ if bt, ok := s.Elem().(*types.Basic); ok && bt.Kind() == types.Byte {
+ // m.F == nil => len(m.GetF()) == 0
+ // m.F != nil => len(m.GetF()) != 0
+ var getVal dst.Expr = field
+ if isPtr(c.typeOf(field.X)) || c.canAddr(field.X) {
+ getVal = sel2call(c, "Get", field, nil, dst.NodeDecs{})
+ }
+ lenCall := &dst.CallExpr{
+ Fun: dst.NewIdent("len"),
+ Args: []dst.Expr{getVal},
+ }
+ c.setType(lenCall, types.Typ[types.Int])
+ c.setType(lenCall.Fun, types.Universe.Lookup("len").Type())
+ op := token.EQL
+ if expr.Op == token.NEQ {
+ op = token.NEQ
+ }
+ zero := &dst.BasicLit{Kind: token.INT, Value: "0"}
+ c.setType(zero, types.Typ[types.Int])
+ bop := &dst.BinaryExpr{
+ X: lenCall,
+ Op: op,
+ Y: zero,
+ Decs: expr.Decs,
+ }
+ c.setType(bop, types.Typ[types.Bool])
+ c.Replace(bop)
+ return true
+ }
+ }
+
+ // We don't handle repeated fields and maps explicitly here. We handle those
+ // cases by rewriting the code to use Get calls:
+ //
+ // m.F == nil => m.GetF() == nil
+ // m.F != nil => m.GetF() != nil
+ //
+ // We depend on the above and on the implementation detail that after:
+ //
+ // m.SetF(nil)
+ //
+ // we guarantee:
+ //
+ // m.GetF() == nil
+ //
+ // This works and preserves the old API behavior. However, it's
+ // a discouraged pattern in new code. It's better to check the
+ // length instead.
+ //
+ // We DO NOT do that as it couldn't be a green rewrite due to
+ // the difference between nil and zero-length slices.
+
+ return true
+}
+
+// usesAsPointer returns whether the pointer target is used without being
+// dereferenced.
+func usesAsPointer(c *cursor, b *dst.BlockStmt, target types.Object) bool {
+ var out bool
+ dstutil.Apply(b, nil, func(cur *dstutil.Cursor) bool {
+ // Is current node a usage of target without dereferencing it?
+ if ident, ok := cur.Node().(*dst.Ident); ok && c.objectOf(ident) == target && !isStarExpr(cur.Parent()) {
+ out = true
+ return false // terminate traversal immediately
+ }
+ return true
+ })
+ return out
+}
+
+// hasCallForProtoField returns a "has" call for the given proto field selector, x.
+//
+// For example, for "m.F", it returns "m.HasF()". The op determines the context
+// in which "m.F" is used. Only "==" and "!=" have an effect here, with the
+// expectation that "x" is used as "m.F OP nil"
+func hasCallForProtoField(c *cursor, x dst.Expr, op token.Token, decs dst.NodeDecs) (hasCall dst.Expr, ok bool) {
+ field, ok := c.trackedProtoFieldSelector(x)
+ if !ok {
+ return nil, false
+ }
+ if !c.useClearOrHas(field) {
+ return nil, false
+ }
+ call := sel2call(c, "Has", field, nil, decs)
+ if op == token.EQL {
+ // m.F == nil => !m.HasF()
+ return not(c, call), true
+ } else if op == token.NEQ {
+ // m.F != nil => m.HasF()
+ return call, true
+ }
+ return nil, false
+}
+
+// comparisonWithNil checks that n is a comparison with nil. If so, it returns
+// the left-hand side and the comparison operator. Otherwise, it returns false.
+func comparisonWithNil(n dst.Node) (lhs dst.Expr, op token.Token, ok bool) {
+ x, ok := n.(*dst.BinaryExpr)
+ if !ok {
+ return nil, 0, false
+ }
+ if x.Op != token.EQL && x.Op != token.NEQ {
+ return nil, 0, false
+ }
+ if ident, ok := x.Y.(*dst.Ident); !ok || ident.Name != "nil" {
+ return nil, 0, false
+ }
+ return x.X, x.Op, true
+}
+
+func isStarExpr(x dst.Node) bool {
+ _, ok := x.(*dst.StarExpr)
+ return ok
+}
+
+func not(c *cursor, expr dst.Expr) dst.Expr {
+ out := &dst.UnaryExpr{
+ Op: token.NOT,
+ X: expr,
+ }
+ c.setType(out, c.underlyingTypeOf(expr))
+ return out
+}
diff --git a/internal/fix/has_test.go b/internal/fix/has_test.go
new file mode 100644
index 0000000..22789fb
--- /dev/null
+++ b/internal/fix/has_test.go
@@ -0,0 +1,603 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestAvoidRedundantHaser(t *testing.T) {
+ tests := []test{
+ {
+ desc: "basic non-nil check",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.I32 != nil {
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+}
+`,
+ },
+ },
+
+ {
+ desc: "basic haser check",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+}
+`,
+ },
+ },
+
+ {
+ desc: "modification after has check",
+ extra: `func f() *int32 { return nil }`,
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.I32 = f()
+ m.I32 = m2.I32
+}
+
+if m2.I32 != nil {
+ m2.I32 = f()
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.I32 = f()
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ } else {
+ m.ClearI32()
+ }
+}
+
+if m2.HasI32() {
+ m2.I32 = f()
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ } else {
+ m.ClearI32()
+ }
+}
+`,
+ },
+ },
+
+ {
+ desc: "shadowing",
+ extra: `func f() *pb2.M2 { return nil }`,
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2 := f()
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2 := f()
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ } else {
+ m.ClearI32()
+ }
+}
+`,
+ },
+ },
+
+ {
+ desc: "usage in lhs of assignment",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.I32 = proto.Int32(int32(42))
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.SetI32(int32(42))
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ } else {
+ m.ClearI32()
+ }
+}
+`,
+ },
+ },
+
+ {
+ desc: "usage of different field",
+ srcfiles: []string{"pkg.go"},
+ in: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.S = proto.String("Hello")
+ m.I32 = m2.I32
+}
+
+if m2.HasI32() {
+ m2.SetS("Hello")
+ m.I32 = m2.I32
+}
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.M2{}
+if m2.HasI32() {
+ m2.SetS("Hello")
+ m.SetI32(m2.GetI32())
+}
+
+if m2.HasI32() {
+ m2.SetS("Hello")
+ m.SetI32(m2.GetI32())
+}
+`,
+ },
+ },
+
+ {
+ desc: "comp literal (non-test)",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if m2.HasI32() {
+ m := &pb2.M2{
+ I32: m2.I32,
+ }
+ _ = m
+}
+
+if m2.I32 != nil {
+ m := &pb2.M2{
+ I32: m2.I32,
+ }
+ _ = m
+}
+`,
+ want: map[Level]string{
+ Red: `
+if m2.HasI32() {
+ m := &pb2.M2{}
+ m.SetI32(m2.GetI32())
+ _ = m
+}
+
+if m2.HasI32() {
+ m := &pb2.M2{}
+ m.SetI32(m2.GetI32())
+ _ = m
+}
+`,
+ },
+ },
+
+ {
+ desc: "comp literal (test)",
+ srcfiles: []string{"pkg_test.go"},
+ in: `
+if m2.HasI32() {
+ m := &pb2.M2{
+ I32: m2.I32,
+ }
+ _ = m
+}
+
+if m2.I32 != nil {
+ m := &pb2.M2{
+ I32: m2.I32,
+ }
+ _ = m
+}
+`,
+ want: map[Level]string{
+ Red: `
+if m2.HasI32() {
+ m := pb2.M2_builder{
+ I32: m2.GetI32(),
+ }.Build()
+ _ = m
+}
+
+if m2.HasI32() {
+ m := pb2.M2_builder{
+ I32: m2.GetI32(),
+ }.Build()
+ _ = m
+}
+`,
+ },
+ },
+
+ {
+ desc: "return",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = func() *int32 {
+ if m2.HasI32() {
+ return m2.I32
+ }
+ return nil
+}
+
+_ = func() int32 {
+ if m2.HasI32() {
+ return *m2.I32
+ }
+ return int32(0)
+}
+`,
+ want: map[Level]string{
+ Red: `
+_ = func() *int32 {
+ if m2.HasI32() {
+ return proto.Int32(m2.GetI32())
+ }
+ return nil
+}
+
+_ = func() int32 {
+ if m2.HasI32() {
+ return m2.GetI32()
+ }
+ return int32(0)
+}
+`,
+ },
+ },
+
+ {
+ desc: "assign",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if m2.HasI64() {
+ i := m2.I64
+ _ = i
+}
+`,
+
+ want: map[Level]string{
+ Red: `
+if m2.HasI64() {
+ i := proto.Int64(m2.GetI64())
+ _ = i
+}
+`,
+ },
+ },
+
+ {
+ desc: "getter",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if m2.GetF32() != 0 {
+ m := &pb2.M2{}
+ m.F32 = m2.F32
+ _ = m
+}
+if *m2.F32 != 0 {
+ m := &pb2.M2{}
+ m.F32 = m2.F32
+ _ = m
+}
+`,
+
+ want: map[Level]string{
+ Green: `
+if m2.GetF32() != 0 {
+ m := &pb2.M2{}
+ m.SetF32(m2.GetF32())
+ _ = m
+}
+if m2.GetF32() != 0 {
+ m := &pb2.M2{}
+ m.SetF32(m2.GetF32())
+ _ = m
+}
+`,
+ },
+ },
+
+ {
+ desc: "getter: bytes",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if m2.GetBytes() != nil {
+ m := &pb2.M2{}
+ m.Bytes = m2.Bytes
+ _ = m
+}
+if nil != m2.Bytes {
+ m := &pb2.M2{}
+ m.Bytes = m2.Bytes
+ _ = m
+}
+`,
+
+ want: map[Level]string{
+ Green: `
+if m2.GetBytes() != nil {
+ m := &pb2.M2{}
+ if x := m2.GetBytes(); x != nil {
+ m.SetBytes(x)
+ }
+ _ = m
+}
+if nil != m2.GetBytes() {
+ m := &pb2.M2{}
+ if x := m2.GetBytes(); x != nil {
+ m.SetBytes(x)
+ }
+ _ = m
+}
+`,
+ },
+ },
+
+ {
+ desc: "getter: different field",
+ srcfiles: []string{"pkg.go"},
+ in: `
+if *m2.I64 != 0 {
+ m := &pb2.M2{}
+ m.I32 = m2.I32
+ _ = m
+}
+if 0 != m2.GetI64() {
+ m := &pb2.M2{}
+ m.I32 = m2.I32
+ _ = m
+}
+`,
+
+ want: map[Level]string{
+ Green: `
+if m2.GetI64() != 0 {
+ m := &pb2.M2{}
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ }
+ _ = m
+}
+if 0 != m2.GetI64() {
+ m := &pb2.M2{}
+ if m2.HasI32() {
+ m.SetI32(m2.GetI32())
+ }
+ _ = m
+}
+`,
+ },
+ },
+ }
+ runTableTests(t, tests)
+}
+
+func TestHas(t *testing.T) {
+ tests := []test{{
+ desc: "proto2: if-has",
+ in: `
+if e := m2.E; e != nil {
+ _ = *e
+ _ = *e
+ _ = *e
+}
+
+if e := m2.E; e == nil {
+ _ = *e
+}
+
+// New name must be used in the conditional.
+var f *int
+if e := m2.E; f != nil {
+ _ = *e
+}
+
+// We don't apply this rewrite when comparison with nil is ok.
+if m := m2.GetM(); m != nil {
+ _ = m
+}
+
+// The code can't use the pointer value.
+if e := m2.E; e != nil {
+ _ = e
+}
+`,
+ want: map[Level]string{
+ Green: `
+if m2.HasE() {
+ _ = m2.GetE()
+ _ = m2.GetE()
+ _ = m2.GetE()
+}
+
+if !m2.HasE() {
+ _ = m2.GetE()
+}
+
+// New name must be used in the conditional.
+var f *int
+if e := m2.E; f != nil {
+ _ = *e
+}
+
+// We don't apply this rewrite when comparison with nil is ok.
+if m := m2.GetM(); m != nil {
+ _ = m
+}
+
+// The code can't use the pointer value.
+if e := m2.E; e != nil {
+ _ = e
+}
+`,
+ }}, {
+ desc: "proto2: has",
+ in: `
+_ = m2.E != nil
+_ = m2.B != nil
+_ = m2.Bytes != nil
+_ = m2.F32 != nil
+_ = m2.F64 != nil
+_ = m2.I32 != nil
+_ = m2.I64 != nil
+_ = m2.Ui32 != nil
+_ = m2.Ui64 != nil
+_ = m2.M != nil
+_ = m2.Is != nil
+_ = m2.Ms != nil
+_ = m2.Map != nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = m2.HasE()
+_ = m2.HasB()
+_ = m2.HasBytes()
+_ = m2.HasF32()
+_ = m2.HasF64()
+_ = m2.HasI32()
+_ = m2.HasI64()
+_ = m2.HasUi32()
+_ = m2.HasUi64()
+_ = m2.HasM()
+_ = m2.GetIs() != nil
+_ = m2.GetMs() != nil
+_ = m2.GetMap() != nil
+`}}, {
+ desc: "proto2: doesn't have",
+ in: `
+_ = m2.E == nil
+_ = m2.B == nil
+_ = m2.Bytes == nil
+_ = m2.F32 == nil
+_ = m2.F64 == nil
+_ = m2.I32 == nil
+_ = m2.I64 == nil
+_ = m2.Ui32 == nil
+_ = m2.Ui64 == nil
+_ = m2.M == nil
+_ = m2.Is == nil
+_ = m2.Ms == nil
+_ = m2.Map == nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = !m2.HasE()
+_ = !m2.HasB()
+_ = !m2.HasBytes()
+_ = !m2.HasF32()
+_ = !m2.HasF64()
+_ = !m2.HasI32()
+_ = !m2.HasI64()
+_ = !m2.HasUi32()
+_ = !m2.HasUi64()
+_ = !m2.HasM()
+_ = m2.GetIs() == nil
+_ = m2.GetMs() == nil
+_ = m2.GetMap() == nil
+`}}, {
+ desc: "proto3: has",
+ in: `
+_ = m3.Bytes != nil
+_ = m3.M != nil
+_ = m3.Is != nil
+_ = m3.Ms != nil
+_ = m3.Map != nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = len(m3.GetBytes()) != 0
+_ = m3.HasM()
+_ = m3.GetIs() != nil
+_ = m3.GetMs() != nil
+_ = m3.GetMap() != nil
+`}}, {
+ desc: "proto3: doesn't have",
+ in: `
+_ = m3.Bytes == nil
+_ = m3.M == nil
+_ = m3.Is == nil
+_ = m3.Ms == nil
+_ = m3.Map == nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = len(m3.GetBytes()) == 0
+_ = !m3.HasM()
+_ = m3.GetIs() == nil
+_ = m3.GetMs() == nil
+_ = m3.GetMap() == nil
+`}}, {
+ desc: "proto3 value: has",
+ in: `
+var m3val pb3.M3
+_ = m3val.Bytes != nil
+_ = m3val.M != nil
+_ = m3val.Is != nil
+_ = m3val.Ms != nil
+_ = m3val.Map != nil
+`,
+ want: map[Level]string{
+ Green: `
+var m3val pb3.M3
+_ = len(m3val.GetBytes()) != 0
+_ = m3val.HasM()
+_ = m3val.GetIs() != nil
+_ = m3val.GetMs() != nil
+_ = m3val.GetMap() != nil
+`}}, {
+ desc: "proto3 value: doesn't have",
+ in: `
+var m3val pb3.M3
+_ = m3val.Bytes == nil
+_ = m3val.M == nil
+_ = m3val.Is == nil
+_ = m3val.Ms == nil
+_ = m3val.Map == nil
+`,
+ want: map[Level]string{
+ Green: `
+var m3val pb3.M3
+_ = len(m3val.GetBytes()) == 0
+_ = !m3val.HasM()
+_ = m3val.GetIs() == nil
+_ = m3val.GetMs() == nil
+_ = m3val.GetMap() == nil
+`}},
+ }
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/hasneeded.go b/internal/fix/hasneeded.go
new file mode 100644
index 0000000..4bc8e9e
--- /dev/null
+++ b/internal/fix/hasneeded.go
@@ -0,0 +1,271 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+)
+
+// isGetterFor return true if expr is a getter for the proto field sel
+func isGetterFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool {
+ call, ok := expr.(*dst.CallExpr)
+ if !ok {
+ return false
+ }
+ dstSel, ok := call.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ if id, ok := dstSel.X.(*dst.Ident); !ok || c.objectOf(id) != c.objectOf(sel.X.(*dst.Ident)) {
+ return false
+ }
+ if dstSel.Sel.Name != "Get"+sel.Sel.Name {
+ return false
+ }
+ return true
+}
+
+// isDirectFieldAccess return true if expr is a dereferencing direct field
+// access for the proto field sel
+func isDirectFieldAccess(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool {
+ se, ok := expr.(*dst.StarExpr)
+ if !ok {
+ return false
+ }
+ otherSel, ok := se.X.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ otherID, ok := otherSel.X.(*dst.Ident)
+ if !ok {
+ return false
+ }
+ if c.objectOf(otherID) != c.objectOf(sel.X.(*dst.Ident)) {
+ return false
+ }
+ if c.objectOf(otherSel.Sel) != c.objectOf(sel.Sel) {
+ return false
+ }
+ return true
+}
+
+// isFieldAccessFor returns true if expr is a field access for sel (either
+// getter of direct).
+func isFieldAccessFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool {
+ return isGetterFor(c, expr, sel) || isDirectFieldAccess(c, expr, sel)
+}
+
+// guaranteesExistenceOf return true if the expr is a boolean expression that
+// guarantees that sel is set, e.g.:
+//
+// if m.GetX() != "" {
+func guaranteesExistenceOf(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool {
+ _, ok := sel.X.(*dst.Ident)
+ if !ok {
+ return false
+ }
+ fieldType := c.typeOf(sel)
+ if pt, ok := fieldType.(*types.Pointer); ok {
+ fieldType = pt.Elem()
+ }
+ // If expr a binary condition we check if it is a comparison against nil
+ // or if it's conjunction and either of the branches is a haser.
+ if bin, ok := expr.(*dst.BinaryExpr); ok {
+ if bin.Op == token.LAND {
+ return guaranteesExistenceOf(c, bin.X, sel) || guaranteesExistenceOf(c, bin.Y, sel)
+ }
+ if bin.Op == token.NEQ {
+ // Is this `if m.GetX() != 0 {` ?
+ // (0 is representative for the type specific zero value)
+ foundZero := false
+ expr := bin.X
+ if isScalarTypeZeroExpr(c, fieldType, expr) {
+ foundZero = true
+ expr = bin.Y
+ }
+ if !foundZero && !isScalarTypeZeroExpr(c, fieldType, bin.Y) {
+ return false
+ }
+ return isFieldAccessFor(c, expr, sel)
+
+ }
+ return false
+
+ }
+ return false
+}
+
+// isHaserFor return true if the expr is a boolean expression that
+// implements a has-check for the message field specified by sel.
+func isHaserFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool {
+ xID, ok := sel.X.(*dst.Ident)
+ if !ok {
+ return false
+ }
+ xObj := c.objectOf(xID)
+ selObj := c.objectOf(sel.Sel)
+
+ // If expr a binary condition we check if it is a comparison against nil
+ // or if it's conjunction and either of the branches is a haser.
+ if bin, ok := expr.(*dst.BinaryExpr); ok {
+ if bin.Op == token.LAND {
+ return isHaserFor(c, bin.X, sel) || isHaserFor(c, bin.Y, sel)
+ }
+ if bin.Op == token.NEQ {
+ // Is this `if m.X != nil {` ?
+ // Or `if nil != m.X {` ?
+ foundNil := false
+ expr := bin.X
+ if c.typeOf(expr) == types.Typ[types.UntypedNil] {
+ foundNil = true
+ expr = bin.Y
+ }
+ if !foundNil && c.typeOf(bin.Y) != types.Typ[types.UntypedNil] {
+ return false
+ }
+ bSel, ok := expr.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ bID, ok := bSel.X.(*dst.Ident)
+ if !ok {
+ return false
+ }
+ if c.objectOf(bID) != xObj || c.objectOf(bSel.Sel) != selObj {
+ return false
+ }
+ return true
+ }
+ return false
+
+ }
+
+ // Is this `if m.HasX() {` ?
+ call, ok := expr.(*dst.CallExpr)
+ if !ok {
+ return false
+ }
+ dstSel, ok := call.Fun.(*dst.SelectorExpr)
+ if !ok {
+ return false
+ }
+ if id, ok := dstSel.X.(*dst.Ident); !ok || c.typesInfo.objectOf(id) != xObj {
+ return false
+ }
+ if dstSel.Sel.Name != "Has"+sel.Sel.Name {
+ return false
+ }
+
+ return true
+}
+
+// isScalarTypeZeroExpr returns true if e is a zero value for t
+func isScalarTypeZeroExpr(c *cursor, t types.Type, e dst.Expr) bool {
+ if _, ok := t.(*types.Basic); !isBytes(t) && !isEnum(t) && !ok {
+ return false
+ }
+ zeroExpr := scalarTypeZeroExpr(c, t)
+ if id0, ok := zeroExpr.(*dst.Ident); ok {
+ if id1, ok := e.(*dst.Ident); ok {
+ return id0.Name == id1.Name
+ }
+ }
+ if bl0, ok := zeroExpr.(*dst.BasicLit); ok {
+ if bl1, ok := e.(*dst.BasicLit); ok {
+ // floats can be compared to either 0 or 0.0
+ // scalarTypeZeroExpr generates the more specific 0.0
+ // but we would like to allow comparison against 0 as
+ // well.
+ if bl0.Kind == token.FLOAT && bl1.Value == "0" {
+ return true
+ }
+ return bl0.Value == bl1.Value && bl0.Kind == bl1.Kind
+ }
+ }
+ return false
+}
+
+// hasNeeded implements a basic dataflow analysis to find out if the scope
+// enclosing sel guarantees that sel is set (non-nil). We consider this
+// guaranteed if there is either a `sel != nil` or a `${sel.X}.Has${sel.Sel}()`
+// condition and the field is not modified afterwards.
+// In the absence of bugs, this check never produces false-positives but it may
+// produce false-negatives.
+func hasNeeded(c *cursor, sel *dst.SelectorExpr) bool {
+ selX, ok := sel.X.(*dst.Ident)
+ if !ok {
+ return true
+ }
+
+ innerMost, opener := c.enclosingASTStmt(sel)
+ if _, ok := opener.(*ast.IfStmt); !ok {
+ return true
+ }
+ dstIf, ok := c.typesInfo.dstMap[opener]
+ if !ok {
+ c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v (was c.typesInfo.dstMap not updated across rewrites?)", opener, opener)
+ return true
+ }
+
+ cond := dstIf.(*dst.IfStmt).Cond
+ if !isHaserFor(c, cond, sel) && !guaranteesExistenceOf(c, cond, sel) {
+ return true
+ }
+
+ enclosing, ok := c.typesInfo.dstMap[innerMost]
+ if !ok {
+ c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost)
+ return true
+ }
+ lastSeen := false
+ usageFound := false
+
+ xObj := c.objectOf(selX)
+ selObj := c.objectOf(sel.Sel)
+ var visit visitorFunc
+ visit = func(n dst.Node) dst.Visitor {
+ if lastSeen {
+ return nil
+ }
+
+ if se, ok := n.(*dst.SelectorExpr); ok && se == sel {
+ lastSeen = true
+ // Skip recursing into children; all subsequent visit() calls
+ // will return immediately.
+ return nil
+ }
+
+ if as, ok := n.(*dst.AssignStmt); ok {
+ // Is the field that was checked assigned to?
+ for _, lhs := range as.Lhs {
+ if usesObject(c, lhs, xObj) && usesObject(c, lhs, selObj) {
+ usageFound = true
+ return nil
+ }
+ }
+ }
+
+ // Access is okay if it's not a setter for the field
+ // and if it is not assigned to (checked above).
+ if doesNotModifyField(c, n, sel.Sel.Name) {
+ return nil
+ }
+
+ if id, ok := n.(*dst.Ident); ok && c.objectOf(id) == xObj {
+ c.Logf("found non-proto-field-selector usage of %q", id.Name)
+ usageFound = true
+ return nil
+ }
+
+ return visit // recurse into children
+ }
+ dst.Walk(visit, enclosing)
+
+ return !lastSeen || usageFound
+}
diff --git a/internal/fix/incdec.go b/internal/fix/incdec.go
new file mode 100644
index 0000000..524f02a
--- /dev/null
+++ b/internal/fix/incdec.go
@@ -0,0 +1,51 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+)
+
+func incDecPre(c *cursor) bool {
+ stmt, ok := c.Node().(*dst.IncDecStmt)
+ if !ok {
+ return true
+ }
+ x := stmt.X
+ if pe, ok := x.(*dst.ParenExpr); ok {
+ x = pe.X
+ }
+ if se, ok := x.(*dst.StarExpr); ok {
+ x = se.X
+ }
+ field, ok := c.trackedProtoFieldSelector(x)
+ if !ok {
+ return true
+ }
+ if !c.isSideEffectFree(field) {
+ markMissingRewrite(stmt, "inc/dec statement")
+ return true
+ }
+ val := &dst.BinaryExpr{
+ X: sel2call(c, "Get", cloneSelectorExpr(c, field), nil, *field.Decorations()),
+ Y: dst.NewIdent("1"),
+ }
+ c.setType(val.Y, types.Typ[types.UntypedInt])
+ c.setType(val, c.typeOf(field))
+ if stmt.Tok == token.INC {
+ val.Op = token.ADD
+ } else {
+ val.Op = token.SUB
+ }
+ // Not handled: decorations from the inner nodes (ParenExpr,
+ // StarExpr). While those are unlikely to be there, we would ideally not
+ // lose those. Perhaps we need a more general solution instead of handling
+ // decorations on a case-by-case basis.
+ c.Replace(c.expr2stmt(sel2call(c, "Set", field, val, *stmt.Decorations()), field))
+ return true
+}
diff --git a/internal/fix/incdec_test.go b/internal/fix/incdec_test.go
new file mode 100644
index 0000000..ae5c4de
--- /dev/null
+++ b/internal/fix/incdec_test.go
@@ -0,0 +1,103 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestIncDecUnary(t *testing.T) {
+ tests := []test{
+ {
+ desc: "unary expressions: contexts",
+ extra: `var x struct { M *pb2.M2 }`,
+ in: `
+*m2.I32++
+(*m2.I32)++
+*m2.M.I32++
+(*m2.M.I32)++
+
+*x.M.I32++
+
+*m2.I32--
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetI32(m2.GetI32() + 1)
+m2.SetI32(m2.GetI32() + 1)
+m2.GetM().SetI32(m2.GetM().GetI32() + 1)
+m2.GetM().SetI32(m2.GetM().GetI32() + 1)
+
+x.M.SetI32(x.M.GetI32() + 1)
+
+m2.SetI32(m2.GetI32() - 1)
+`,
+ },
+ },
+
+ {
+ desc: "unary expressions: proto3",
+ in: `
+m3.I32++
+(m3.I32)++
+m3.M.I32++
+(m3.M.I32)++
+m3.I32--
+`,
+ want: map[Level]string{
+ Green: `
+m3.SetI32(m3.GetI32() + 1)
+m3.SetI32(m3.GetI32() + 1)
+m3.GetM().SetI32(m3.GetM().GetI32() + 1)
+m3.GetM().SetI32(m3.GetM().GetI32() + 1)
+m3.SetI32(m3.GetI32() - 1)
+`,
+ },
+ },
+
+ {
+ desc: "unary expressions: decorations",
+ in: `
+// hello
+*m2.I32++ // world
+`,
+ want: map[Level]string{
+ Green: `
+// hello
+m2.SetI32(m2.GetI32() + 1) // world
+`,
+ },
+ },
+
+ {
+ desc: "unary expressions: no duplicated side-effects",
+ extra: `
+func f2() *pb2.M2 { return nil }
+func f3() *pb3.M3 { return nil }
+`,
+ in: `
+*f2().I32++
+(*f2().I32)++
+f3().I32++
+(f3().I32)++
+`,
+ want: map[Level]string{
+ Green: `
+*f2().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+(f2().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+f3().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+(f3().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+`,
+ Red: `
+*f2().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+(f2().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+f3().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+(f3().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */
+`,
+ },
+ }}
+
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/naming.go b/internal/fix/naming.go
new file mode 100644
index 0000000..f6717fb
--- /dev/null
+++ b/internal/fix/naming.go
@@ -0,0 +1,61 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/types"
+ "strings"
+ "unicode"
+
+ log "github.com/golang/glog"
+)
+
+func helperVarNameForType(t types.Type) string {
+ // Get to the elementary type (pb.M2) if this is a pointer type (*pb.M2).
+ elem := t
+ if ptr, ok := elem.(*types.Pointer); ok {
+ elem = ptr.Elem()
+ }
+ named, ok := elem.(*types.Named)
+ if !ok {
+ log.Fatalf("BUG: proto message unexpectedly not a named type (but %T)?!", elem)
+ }
+ return helperVarNameForName(named.Obj().Name())
+}
+
+// helperVarNameForName produces a name for a helper variable for the specified
+// package-local name.
+func helperVarNameForName(packageLocal string) string {
+ fullName := strings.ToLower(packageLocal[:1]) + packageLocal[1:]
+ if len(fullName) < 10 {
+ return fullName
+ }
+ // The name is too long for a helper variable. Abbreviate if possible.
+ if strings.Contains(fullName, "_") {
+ // Split along the underscores and use the first letter of each word,
+ // turning BigtableRowMutationArgs_Mod_SetCell into bms.
+ parts := strings.Split(fullName, "_")
+ abbrev := ""
+ for _, part := range parts {
+ abbrev += strings.ToLower(part[:1])
+ }
+ return abbrev
+ }
+ if parts := strings.FieldsFunc(packageLocal, unicode.IsLower); len(parts) > 1 {
+ // We split around the lowercase characters, leaving us with only the
+ // uppercase characters.
+ return strings.ToLower(strings.Join(parts, ""))
+ } else if len(parts) == 1 && len(parts[0]) > 1 {
+ // The name starts with multiple uppercase letters, but is followed by
+ // only lowercase letters (e.g. ESDimensions). Return all the uppercase
+ // letters we have.
+ return strings.ToLower(parts[0])
+ }
+ // The name is too long and cannot be abbreviated based on uppercase
+ // letters. Cut off at the closest vowel.
+ return strings.TrimRightFunc(fullName[:10], func(r rune) bool {
+ return r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u'
+ })
+}
diff --git a/internal/fix/naming_test.go b/internal/fix/naming_test.go
new file mode 100644
index 0000000..96722ca
--- /dev/null
+++ b/internal/fix/naming_test.go
@@ -0,0 +1,46 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import "testing"
+
+func TestHelperVarNameForName(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ want string
+ }{
+ {
+ name: "M2",
+ want: "m2",
+ },
+
+ {
+ name: "BigtableRowMutationArgs_Mod_SetCell",
+ want: "bms",
+ },
+
+ {
+ name: "BigtableRowMutationArgs",
+ want: "brma",
+ },
+
+ {
+ name: "Verylongonewordnamethatcannotbeabbreviated",
+ want: "verylongon",
+ },
+
+ {
+ name: "ESDimensions",
+ want: "esd",
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ got := helperVarNameForName(tt.name)
+ if got != tt.want {
+ t.Errorf("helperVarNameForName(%q) = %q, want %q", tt.name, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/internal/fix/oneof.go b/internal/fix/oneof.go
new file mode 100644
index 0000000..07c1fc2
--- /dev/null
+++ b/internal/fix/oneof.go
@@ -0,0 +1,497 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "context"
+ "go/token"
+ "go/types"
+ "sort"
+
+ "github.com/dave/dst"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/protodetecttypes"
+)
+
+// generateOneofBuilderCases transforms oneofs in composite literals to be
+// compatible with builders that don't have a field for the oneof but a field
+// for each case instead. The RHS is either a direct field access or getter
+// oneof field. The only other values that could be assigned to the oneof field
+// would be the wrapper types which must be handled before this function is
+// called.
+//
+// &pb.M{
+// OneofField: m2.OneofField,
+// }
+// =>
+// &pb.M{
+// OneofField: m2.OneofField,
+// BytesOneof: m2.GetBytesOneof(),
+// IntOneof: func(msg *pb2.M2) *int64 {
+// if !msg.HasIntOneof() {
+// return nil
+// }
+// return proto.Int64(msg.GetIntOneof())
+// }(m2),
+// }
+//
+// Removal of the oneof KeyValueExpr happens later.
+// Changing from &pb.M to pb.M_builder{...}.Build() happens in another step.
+//
+// Note: The exact results differ and depend on the valid cases for the oneof
+// field. For scalar fields, we have to use self invoking function literals to
+// be able to check if the case is unset and pass nil to the builder field if
+// so.
+func generateOneofBuilderCases(c *cursor, updates []func(), lit *dst.CompositeLit, kv *dst.KeyValueExpr) ([]func(), bool) {
+ t := c.typesInfo.typeOf(kv.Value)
+ if !isOneof(t) {
+ c.Logf("returning: not a oneof field")
+ return nil, false
+ }
+ c.Logf("try generating oneof cases...")
+ // First we need to collect an exhaustive list of alternatives. We do this
+ // by collecting all wrapper types for the given field that are defined in
+ // the generated proto package.
+ nt, ok := t.(*types.Named)
+ if !ok {
+ c.Logf("ignoring oneof of type %T (looking for *types.Named)", c.Node())
+ return nil, false
+ }
+ pkg := nt.Obj().Pkg()
+
+ targetID := pkg.Path()
+
+ p, err := loader.LoadOne(context.Background(), c.loader, &loader.Target{ID: targetID})
+ if err != nil {
+ c.Logf("failed to load proto package %q: %v", pkg.Path(), err)
+ return nil, false
+ }
+
+ // Get the message name from which the oneof field is taken
+ rhsSel, ok := kv.Value.(*dst.SelectorExpr)
+ if !ok {
+ // If it is not a direct field access, it must be a call expression,
+ // i.e. the getter for the oneof field.
+ // For now we don't support anything else (e.g. variables).
+ callExpr, ok := kv.Value.(*dst.CallExpr)
+ if !ok {
+ c.Logf("ignoring value %T (looking for SelectorExpr or CallExpr)", kv.Value)
+ return nil, false
+ }
+ rhsSel, ok = callExpr.Fun.(*dst.SelectorExpr)
+ if !ok {
+ c.Logf("ignoring value %T (looking for SelectorExpr)", callExpr.Fun)
+ return nil, false
+ }
+ }
+
+ // Oneofs are guaranteed to have *types.Interface as underlying type.
+ it := t.Underlying().(*types.Interface)
+ type typeAndName struct {
+ name string
+ typ *types.Struct
+ }
+ // Collect wrapper types to generate an exhaustive list of cases
+ var wrapperTypes []typeAndName
+ for _, idt := range p.TypeInfo.Defs {
+ // Some things (like `package p`) are toplevel definitions that don't
+ // have an associated object.
+ if idt == nil {
+ continue
+ }
+ // All wrapper types are exported *types.Named
+ if !idt.Exported() {
+ continue
+ }
+ nt, ok := idt.Type().(*types.Named)
+ if !ok {
+ continue
+ }
+ if !types.Implements(types.NewPointer(nt), it) {
+ continue
+ }
+ // All wrapper types are structs with exactly one field.
+ // The one field is named that same as the case itself.
+ st := nt.Underlying().(*types.Struct)
+ name := st.Field(0).Name()
+ wrapperTypes = append(wrapperTypes, typeAndName{name, st})
+ }
+
+ sort.Slice(wrapperTypes, func(i, j int) bool {
+ return wrapperTypes[i].name < wrapperTypes[j].name
+ })
+
+ first := true
+ for _, tan := range wrapperTypes {
+ caseName := tan.name
+
+ // Generate Value
+ st := tan.typ
+ if st.NumFields() != 1 {
+ c.Logf("wrapper type has %d fields (expected 1)", st.NumFields())
+ return nil, false
+ }
+ fieldType := st.Field(0).Type()
+
+ var rhsExpr dst.Expr
+ if _, ok = fieldType.Underlying().(*types.Basic); ok {
+ rhsExpr = funcLiteralForOneofField(c, rhsSel, caseName, fieldType)
+ } else {
+ rhsExpr = oneOfSelector(c, "Get", caseName, rhsSel.X, fieldType, nil, *rhsSel.Decorations())
+ }
+
+ // Generate KeyValueExpr
+ nKey := &dst.Ident{Name: caseName}
+ c.setType(nKey, types.NewPointer(fieldType))
+ nKeyVal := &dst.KeyValueExpr{
+ Key: nKey,
+ Value: rhsExpr,
+ }
+ // Duplicate the decorations to all children.
+ if first {
+ nKeyVal.Decs = kv.Decs
+ first = false
+ }
+ nKeyVal.Decorations().After = dst.NewLine
+ c.setType(nKeyVal, types.Typ[types.Invalid])
+ c.Logf("generated KeyValueExpr for %v", caseName)
+ updates = append(updates, func() {
+ lit.Elts = append(lit.Elts, nKeyVal)
+ })
+ }
+
+ c.Logf("generated %d oneof cases", len(wrapperTypes))
+ return updates, true
+}
+
+// funcLiteralForOneofField is similar to funcLiteralForHas but does not operate
+// on existing (Go struct) fields but on proto message fields. Such fields don't
+// exist in the Go type system and thus we cannot reuse funcLiteralForHas.
+func funcLiteralForOneofField(c *cursor, field *dst.SelectorExpr, fieldName string, fieldType types.Type) *dst.CallExpr {
+ msg := field.X.(*dst.Ident)
+ msgType := c.typeOf(msg)
+
+ getSel := &dst.Ident{
+ Name: "Get" + fixConflictingNames(msgType, "Get", fieldName),
+ }
+ getX := cloneIdent(c, msg)
+ getCall := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: getX,
+ Sel: getSel,
+ },
+ }
+ value := types.NewParam(token.NoPos, nil, "_", fieldType)
+ recv := types.NewParam(token.NoPos, nil, "_", c.underlyingTypeOf(msg))
+ c.setType(getSel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false))
+ c.setType(getCall.Fun, c.typeOf(getSel))
+ c.setType(getCall, fieldType)
+
+ hasSel := &dst.Ident{
+ Name: "Has" + fixConflictingNames(msgType, "Has", fieldName),
+ }
+ hasX := &dst.Ident{Name: "msg"}
+ c.setType(hasX, types.Typ[types.Invalid])
+ hasCall := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: hasX,
+ Sel: hasSel,
+ },
+ }
+ c.setType(hasSel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.Typ[types.Bool])), false))
+ c.setType(hasCall.Fun, c.typeOf(hasSel))
+ c.setType(hasCall, types.Typ[types.Bool])
+
+ // oneof fields are synthetically generated, so there are no node
+ // decorations to carry over.
+ var emptyDecs dst.NodeDecs
+ if c.isSideEffectFree(msg) {
+ // Call proto.ValueOrNil() directly, no function literal needed.
+ hasX.Name = msg.Name
+ field2 := cloneSelectorExpr(c, field)
+ field2.Sel.Name = fieldName
+ return valueOrNil(c, hasCall, field2, emptyDecs)
+ }
+
+ var retElemType dst.Expr = &dst.Ident{Name: fieldType.String()}
+ fieldTypePtr := types.NewPointer(fieldType)
+ c.setType(retElemType, fieldType)
+ retType := &dst.StarExpr{X: retElemType}
+ c.setType(retType, fieldTypePtr)
+
+ msgTypePtr := types.NewPointer(msgType)
+ msgParamSel := c.selectorForProtoMessageType(msgType)
+ msgParamType := &dst.StarExpr{X: msgParamSel}
+ c.setType(msgParamType, msgTypePtr)
+
+ msgParam := &dst.Ident{Name: "msg"}
+ c.setType(msgParam, types.Typ[types.Invalid])
+
+ field2 := cloneSelectorExpr(c, field)
+ field2.Sel.Name = fieldName
+ funcLit := &dst.FuncLit{
+ // func(msg *pb.M2) <type> {
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{
+ List: []*dst.Field{
+ &dst.Field{
+ Names: []*dst.Ident{msgParam},
+ Type: msgParamType,
+ },
+ },
+ },
+ Results: &dst.FieldList{
+ List: []*dst.Field{
+ &dst.Field{
+ Type: retType,
+ },
+ },
+ },
+ },
+ Body: &dst.BlockStmt{
+ List: []dst.Stmt{
+ // return proto.ValueOrNil(…)
+ &dst.ReturnStmt{
+ Results: []dst.Expr{
+ valueOrNil(c, hasCall, field2, emptyDecs),
+ },
+ },
+ },
+ },
+ }
+ // We do not know whether the proto package was imported, so we may not be
+ // able to construct the correct type signature. Set the type to invalid,
+ // like we do for any code involving the proto package.
+ c.setType(funcLit, types.Typ[types.Invalid])
+ c.setType(funcLit.Type, types.Typ[types.Invalid])
+
+ msgArg := dst.Clone(msg).(dst.Expr)
+ c.setType(msgArg, msgType)
+ call := &dst.CallExpr{
+ Fun: funcLit,
+ Args: []dst.Expr{
+ msgArg,
+ },
+ }
+ c.setType(call, fieldTypePtr)
+
+ return call
+}
+
+// This is like sel2call but for oneof fields which are not actual (Go struct)
+// fields in the generated Go struct and thus we cannot use sel2call directly
+func oneOfSelector(c *cursor, prefix, fieldName string, msg dst.Expr, fieldType types.Type, val dst.Expr, decs dst.NodeDecs) *dst.CallExpr {
+ name := fixConflictingNames(c.typeOf(msg), prefix, fieldName)
+ fnsel := &dst.Ident{
+ Name: prefix + name,
+ }
+ selX := dst.Clone(msg).(dst.Expr)
+ c.setType(selX, types.Typ[types.Invalid])
+ fn := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: selX,
+ Sel: fnsel,
+ },
+ }
+ if val != nil {
+ fn.Args = []dst.Expr{val}
+ }
+
+ value := types.NewParam(token.NoPos, nil, "_", fieldType)
+ recv := types.NewParam(token.NoPos, nil, "_", c.typeOf(msg))
+ switch prefix {
+ case "Get":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false))
+ c.setType(fn, fieldType)
+ case "Set":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(value), types.NewTuple(), false))
+ c.setVoidType(fn)
+ case "Clear":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(), false))
+ c.setVoidType(fn)
+ case "Has":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.Typ[types.Bool])), false))
+ c.setType(fn, types.Typ[types.Bool])
+ default:
+ panic("bad function name prefix '" + prefix + "'")
+ }
+ c.setType(fn.Fun, c.typeOf(fnsel))
+ return fn
+}
+
+// destructureOneofWrapper returns K (field name), V (assigned value), and
+// typeof(K) (type of the field) for a oneof wrapper expression
+//
+// "&OneofWrapper{K: V}"
+//
+// and equivalents that omit either K, or V, or both.
+func destructureOneofWrapper(c *cursor, x dst.Expr) (string, types.Type, dst.Expr, *dst.NodeDecs, bool) {
+ c.Logf("destructuring one of wrapper")
+ ue, ok := x.(*dst.UnaryExpr)
+ if !ok || ue.Op != token.AND {
+ return oneofWrapperSelector(c, x)
+ }
+ clit, ok := ue.X.(*dst.CompositeLit)
+ if !ok {
+ return oneofWrapperSelector(c, x)
+ }
+ s, ok := c.underlyingTypeOf(clit).(*types.Struct)
+ if !ok || s.NumFields() != 1 {
+ return oneofWrapperSelector(c, x)
+ }
+ if !isOneofWrapper(c, clit) {
+ return oneofWrapperSelector(c, x)
+ }
+ var decs *dst.NodeDecs
+ var val dst.Expr
+ switch {
+ case len(clit.Elts) > 1:
+ panic("oneof wrapper clit has multiple elements")
+ case len(clit.Elts) == 0: // &Oneof{}
+ val = nil
+ case isKV(clit.Elts[0]): // &Oneof{K: V}
+ // We are about to replace clit.Elts[0], so propagate its decorations.
+ decs = clit.Elts[0].Decorations()
+ val = clit.Elts[0].(*dst.KeyValueExpr).Value
+ default: // &Oneof{V}
+ val = clit.Elts[0]
+ // Propagate the decorations and clear them at the value level.
+ var decsCopy dst.NodeDecs
+ decsCopy = *val.Decorations()
+ decs = &decsCopy
+ val.Decorations().Before = dst.None
+ val.Decorations().After = dst.None
+ val.Decorations().Start = nil
+ val.Decorations().End = nil
+ }
+ if ident, ok := val.(*dst.Ident); ok && ident.Name == "nil" {
+ val = nil
+ }
+
+ valType := s.Field(0).Type()
+ if isBytes(valType) && !isNeverNilSliceExpr(c, val) {
+ if !c.lvl.ge(Yellow) {
+ c.numUnsafeRewritesByReason[IncompleteRewrite]++
+ c.Logf("ignoring: rewrite level smaller than Yellow")
+ return "", nil, nil, nil, false
+ }
+ if val == nil {
+ id := &dst.Ident{
+ Name: "byte",
+ }
+ c.setType(id, valType.(*types.Slice).Elem())
+ typ := &dst.ArrayType{
+ Elt: id,
+ }
+ c.setType(typ, valType)
+ val = &dst.CompositeLit{
+ Type: typ,
+ }
+ c.setType(val, valType)
+ return s.Field(0).Name(), valType, val, decs, true
+ }
+ c.numUnsafeRewritesByReason[MaybeOneofChange]++
+ // NOTE(lassefolger): This ValueOrDefaultBytes() call is only
+ // necessary in builders, but we don’t have enough context in
+ // this part of the code to omit it for setters.
+ return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefaultBytes", val), decs, true
+ }
+ isMsgOneof := false
+ if ptr, ok := valType.(*types.Pointer); ok && (protodetecttypes.Type{T: ptr.Elem()}.IsMessage()) {
+ isMsgOneof = true
+ }
+ if isMsgOneof && val != nil && !isNeverNilExpr(c, val) {
+ if !c.lvl.ge(Yellow) {
+ c.numUnsafeRewritesByReason[IncompleteRewrite]++
+ c.Logf("ignoring: rewrite level smaller than Yellow")
+ return "", nil, nil, nil, false
+ }
+ c.numUnsafeRewritesByReason[MaybeOneofChange]++
+ return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefault", val), decs, true
+ }
+
+ return s.Field(0).Name(), valType, val, decs, true
+}
+
+func oneofWrapperSelector(c *cursor, x dst.Expr) (string, types.Type, dst.Expr, *dst.NodeDecs, bool) {
+ var decs *dst.NodeDecs
+ if !c.lvl.ge(Yellow) {
+ c.Logf("ignoring: rewrite level smaller than Yellow")
+ return "", nil, nil, nil, false
+ }
+ if !isOneofWrapper(c, x) {
+ c.Logf("ignoring: not a one of wrapper")
+ return "", nil, nil, nil, false
+ }
+ if !isNeverNilExpr(c, x) {
+ if c.lvl.le(Yellow) {
+ c.Logf("ignoring: potential nil expression and rewrite level not Red")
+ // This could be handled with self calling func literals but
+ // we should only do so if there is a significant number of
+ // locations that need this.
+ c.numUnsafeRewritesByReason[IncompleteRewrite]++
+ return "", nil, nil, nil, false
+ }
+ c.numUnsafeRewritesByReason[MaybeNilPointerDeref]++
+ }
+ // It is possible that this rewrite unsets the oneof field where it was
+ // previously set to a type but without value (which is not a valid
+ // proto message), e.g.:
+ //
+ // m.OneofField = pb.OneofWrapper{nil}
+ c.numUnsafeRewritesByReason[MaybeOneofChange]++
+
+ t := c.underlyingTypeOf(x)
+ if p, ok := t.(*types.Pointer); ok {
+ t = p.Elem().Underlying()
+ }
+ s := t.(*types.Struct)
+ val := &dst.SelectorExpr{
+ X: x,
+ Sel: &dst.Ident{
+ Name: s.Field(0).Name(),
+ },
+ }
+ valType := s.Field(0).Type()
+ c.setType(val.Sel, valType)
+ c.setType(val, s.Field(0).Type())
+ if ptr, ok := valType.(*types.Pointer); ok && (protodetecttypes.Type{T: ptr.Elem()}.IsMessage()) {
+ return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefault", val), decs, true
+ }
+ return s.Field(0).Name(), s.Field(0).Type(), val, decs, true
+}
+
+func isKV(x dst.Expr) bool {
+ _, ok := x.(*dst.KeyValueExpr)
+ return ok
+}
+
+const protooneofdefaultImport = "google.golang.org/protobuf/protooneofdefault"
+
+func valueOrDefault(c *cursor, fun string, val dst.Expr) dst.Expr {
+ fnsel := &dst.Ident{Name: fun}
+ fn := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(protooneofdefaultImport)},
+ Sel: fnsel,
+ },
+ Args: []dst.Expr{
+ val,
+ },
+ }
+
+ t := c.underlyingTypeOf(val)
+ value := types.NewParam(token.NoPos, nil, "_", t)
+ c.setType(fnsel, types.NewSignature(nil, types.NewTuple(value), types.NewTuple(value), false))
+ c.setType(fn, t)
+
+ c.setType(fn.Fun, c.typeOf(fnsel))
+
+ // We set the type for "proto" identifier to Invalid because that's consistent with what the
+ // typechecker does on new code. We need to distinguish "invalid" type from "no type was
+ // set" as the code panics on the later in order to catch issues with missing type updates.
+ c.setType(fn.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid])
+ return fn
+}
diff --git a/internal/fix/oneof_test.go b/internal/fix/oneof_test.go
new file mode 100644
index 0000000..bc5f60b
--- /dev/null
+++ b/internal/fix/oneof_test.go
@@ -0,0 +1,1484 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestOneof(t *testing.T) {
+ tests := []test{{
+ desc: "clear",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+m2.OneofField = nil
+`,
+ want: map[Level]string{
+ Green: `
+m2.ClearOneofField()
+`,
+ },
+ }, {
+ desc: "multiassign",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `var ignored string`,
+ in: `
+m2.OneofField, ignored = &pb2.M2_StringOneof{StringOneof: "hello"}, ""
+`,
+ want: map[Level]string{
+ Green: `
+m2.OneofField, ignored = &pb2.M2_StringOneof{StringOneof: "hello"}, ""
+`,
+ },
+ }, {
+ desc: "ignore variable oneof",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+oneofField := m2.OneofField
+m2.OneofField = oneofField
+`,
+ want: map[Level]string{
+ Green: `
+oneofField := m2.OneofField
+m2.OneofField = oneofField
+`,
+ Red: `
+// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+oneofField := m2.OneofField
+// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+m2.OneofField = oneofField
+`,
+ },
+ }, {
+ desc: "has",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+_ = m2.OneofField != nil
+_ = m2.OneofField == nil
+_ = m3.OneofField != nil
+_ = m3.OneofField == nil
+if m2.OneofField != nil {
+}
+if m2.OneofField == nil {
+}
+if m3.OneofField != nil {
+}
+if m3.OneofField == nil {
+}
+if o := m2.OneofField; o != nil {
+}
+if o := m2.OneofField; o == nil {
+}
+if o := m3.OneofField; o != nil {
+}
+if o := m3.OneofField; o == nil {
+}
+`,
+ want: map[Level]string{
+ Red: `
+_ = m2.HasOneofField()
+_ = !m2.HasOneofField()
+_ = m3.HasOneofField()
+_ = !m3.HasOneofField()
+if m2.HasOneofField() {
+}
+if !m2.HasOneofField() {
+}
+if m3.HasOneofField() {
+}
+if !m3.HasOneofField() {
+}
+if m2.HasOneofField() {
+}
+if !m2.HasOneofField() {
+}
+if m3.HasOneofField() {
+}
+if !m3.HasOneofField() {
+}
+`,
+ },
+ }, {
+ desc: "proto3 works", // Basic smoke test; proto3 is handled together with proto2.
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+m3.OneofField = &pb3.M3_StringOneof{StringOneof: "hello"}
+m3.OneofField = &pb3.M3_EnumOneof{EnumOneof: pb3.M3_E_VAL}
+m3.OneofField = &pb3.M3_MsgOneof{MsgOneof: &pb3.M3{}}
+m3.OneofField = nil
+_ = m3.OneofField != nil
+`,
+ want: map[Level]string{
+ Green: `
+m3.SetStringOneof("hello")
+m3.SetEnumOneof(pb3.M3_E_VAL)
+m3.SetMsgOneof(&pb3.M3{})
+m3.ClearOneofField()
+_ = m3.HasOneofField()
+`,
+ },
+ }, {
+ desc: "build naming",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+m3.OneofField = &pb3.M3_Build{Build: 1}
+m3.OneofField = &pb3.M3_Build{1}
+m3.OneofField = &pb3.M3_Build{}
+`,
+ want: map[Level]string{
+ Green: `
+m3.SetBuild_(1)
+m3.SetBuild_(1)
+m3.SetBuild_(0)
+`,
+ },
+ }, {
+ desc: "oneof: simple type switch, field",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch m2.OneofField.(type) {
+case *pb2.M2_StringOneof,
+ *pb2.M2_IntOneof:
+case *pb2.M2_MsgOneof:
+case *pb2.M2_EnumOneof:
+case *pb2.M2_BytesOneof:
+case nil:
+default:
+}
+
+switch m3.OneofField.(type) {
+case *pb3.M3_StringOneof, *pb3.M3_IntOneof:
+case *pb3.M3_MsgOneof:
+case *pb3.M3_EnumOneof:
+case *pb3.M3_BytesOneof, nil:
+default:
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case,
+ pb2.M2_IntOneof_case:
+case pb2.M2_MsgOneof_case:
+case pb2.M2_EnumOneof_case:
+case pb2.M2_BytesOneof_case:
+case 0:
+default:
+}
+
+switch m3.WhichOneofField() {
+case pb3.M3_StringOneof_case, pb3.M3_IntOneof_case:
+case pb3.M3_MsgOneof_case:
+case pb3.M3_EnumOneof_case:
+case pb3.M3_BytesOneof_case, 0:
+default:
+}
+`,
+ },
+ }, {
+ desc: "oneof: type switch with naming conflicts",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch m3.OneofField.(type) {
+case *pb3.M3_String_:
+case *pb3.M3_Reset_:
+case *pb3.M3_ProtoMessage_:
+case *pb3.M3_Descriptor_:
+default:
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m3.WhichOneofField() {
+case pb3.M3_String__case:
+case pb3.M3_Reset__case:
+case pb3.M3_ProtoMessage__case:
+case pb3.M3_Descriptor__case:
+default:
+}
+`,
+ },
+ }, {
+ desc: "oneof: type switch with in-file naming conflicts",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+co := &pb2.ConflictingOneof{}
+switch co.Included.(type) {
+case *pb2.ConflictingOneof_Sub_:
+default:
+}
+`,
+ want: map[Level]string{
+ Green: `
+co := &pb2.ConflictingOneof{}
+switch co.WhichIncluded() {
+case pb2.ConflictingOneof_Sub_case:
+default:
+}
+`,
+ },
+ }, {
+ desc: "oneof: type switch with in-file naming conflicts in nested sub-message",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+co := &pb2.ConflictingOneof_DeepSub{}
+switch co.DeeplyIncluded.(type) {
+case *pb2.ConflictingOneof_DeepSub_Sub_:
+default:
+}
+`,
+ want: map[Level]string{
+ Green: `
+co := &pb2.ConflictingOneof_DeepSub{}
+switch co.WhichDeeplyIncluded() {
+case pb2.ConflictingOneof_DeepSub_Sub_case:
+default:
+}
+`,
+ },
+ }, {
+ desc: "oneof: simple type switch, method",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof, *pb2.M2_IntOneof:
+case *pb2.M2_MsgOneof:
+case *pb2.M2_EnumOneof:
+case *pb2.M2_BytesOneof:
+case nil:
+default:
+}
+
+switch m3.GetOneofField().(type) {
+case *pb3.M3_StringOneof, *pb3.M3_IntOneof:
+case *pb3.M3_MsgOneof:
+case *pb3.M3_EnumOneof:
+case *pb3.M3_BytesOneof:
+case nil:
+default:
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case, pb2.M2_IntOneof_case:
+case pb2.M2_MsgOneof_case:
+case pb2.M2_EnumOneof_case:
+case pb2.M2_BytesOneof_case:
+case 0:
+default:
+}
+
+switch m3.WhichOneofField() {
+case pb3.M3_StringOneof_case, pb3.M3_IntOneof_case:
+case pb3.M3_MsgOneof_case:
+case pb3.M3_EnumOneof_case:
+case pb3.M3_BytesOneof_case:
+case 0:
+default:
+}
+`,
+ },
+ }, {
+ desc: "oneof: type switch decorations",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch /* hello */ m2.OneofField.(type) /* world */ { // !
+case /* A */ *pb2.M2_StringOneof /* B */ : /* C */
+}
+
+switch /* hello */ m2.GetOneofField().(type) /* world */ { // !
+case /* A */ *pb2.M2_StringOneof /* B */ : /* C */
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch /* hello */ m2.WhichOneofField() /* world */ { // !
+case /* A */ pb2.M2_StringOneof_case /* B */ : /* C */
+}
+
+switch /* hello */ m2.WhichOneofField() /* world */ { // !
+case /* A */ pb2.M2_StringOneof_case /* B */ : /* C */
+}
+`,
+ },
+ }, {
+ desc: "oneof: default non printf like func",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func randomFunc(*pb2.M2, ...interface{}) { }`,
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+default:
+ randomFunc(m2, oneofField)
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+default:
+ randomFunc(m2, oneofField)
+}
+`,
+ Red: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+default:
+ randomFunc(m2, oneofField)
+}
+`,
+ },
+ }, {
+ desc: "oneof: default with T verb",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+default:
+ fmtErrorf("%T", oneofField)
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ Red: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ },
+ }, {
+ desc: "oneof: default with multiple verbs",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+default:
+ fmtErrorf("%v %T", m2, oneofField)
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+default:
+ fmtErrorf("%v %v", m2, oneofField)
+}
+`,
+ Red: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+default:
+ fmtErrorf("%v %v", m2, oneofField)
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access, proto3",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m3.OneofField.(type) {
+case *pb3.M3_MsgOneof:
+ _ = oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m3.WhichOneofField() {
+case pb3.M3_MsgOneof_case:
+ _ = m3.GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, getter access",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, getter access, in --types_to_update_file",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2": true},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.WhichOneofField() {
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, getter access, not in --types_to_update_file",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3": true},
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, shadowing, non-oneof",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `type O struct {StringOneof int}`,
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = m2.GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, shadowing, oneof, nop in green",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `
+type O struct {
+ StringOneof string ` + "`protobuf:\"oneof\"`" + `
+}`,
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = oneofField.MsgOneof.GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, shadowing, oneof, red is risky",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `
+type O struct {
+ StringOneof string ` + "`protobuf:\"oneof\"`" + `
+}`,
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ {
+ oneofField := &O{}
+ _ = oneofField.StringOneof
+ }
+ _ = m2.GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access, selector",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.M.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.GetM().WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetM().GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetM().GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access, call",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.GetM().OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.GetM().WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetM().GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetM().GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access, other expr; red marker",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `var msgs []*pb2.M2`,
+ in: `
+switch oneofField := msgs[0].OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch msgs[0].WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = msgs[0].GetStringOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, field access, other expr; green nop",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `var msgs []*pb2.M2`,
+ in: `
+switch oneofField := msgs[0].OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch msgs[0].WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = msgs[0].GetStringOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment, method call",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_MsgOneof:
+ _ = *oneofField.MsgOneof.S
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = m2.GetStringOneof()
+case pb2.M2_MsgOneof_case:
+ _ = m2.GetMsgOneof().GetS()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; avoid extra side effects",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() *pb2.M2 { return nil }`,
+ in: `
+switch oneofField := f().M.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch xmsg := f().GetM(); xmsg.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = xmsg.GetStringOneof()
+case pb2.M2_IntOneof_case:
+ _ = xmsg.GetIntOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; avoid extra side effects; call",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() *pb2.M2 { return nil }`,
+ in: `
+switch oneofField := f().GetM().GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch xmsg := f().GetM(); xmsg.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = xmsg.GetStringOneof()
+case pb2.M2_IntOneof_case:
+ _ = xmsg.GetIntOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; avoid extra side effects; f",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() *pb2.M2 { return nil }`,
+ in: `
+switch oneofField := f().GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch xmsg := f(); xmsg.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = xmsg.GetStringOneof()
+case pb2.M2_IntOneof_case:
+ _ = xmsg.GetIntOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; don't override the init",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() *pb2.M2 { return nil }`,
+ in: `
+switch x := 1; oneofField := f().M.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+ _ = x
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch x := 1; oneofField := f().GetM().GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+ _ = x
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+} /* DO_NOT_SUBMIT: missing rewrite for type switch with side effects and init statement */
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; don't override the init; green nop",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() *pb2.M2 { return nil }`,
+ in: `
+switch x := 1; oneofField := f().M.GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+ _ = x
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch x := 1; oneofField := f().GetM().GetOneofField().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+ _ = x
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; avoid extra side effects; no oneof",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func f() interface{} { return nil }`,
+ in: `
+switch oneofField := f().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+ `,
+ want: map[Level]string{
+ Red: `
+switch oneofField := f().(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField.StringOneof
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; don't rewrite unrelated statement",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+default:
+ var err error
+ _ = err.Error()
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = oneofField
+case pb2.M2_IntOneof_case:
+ _ = m2.GetIntOneof()
+default:
+ var err error
+ _ = err.Error()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; oneof refs in case",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; oneof refs in default",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+default:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.OneofField.(type) {
+default:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; oneof refs in case; red",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_StringOneof:
+ _ = oneofField
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch m2.WhichOneofField() {
+case pb2.M2_StringOneof_case:
+ _ = oneofField
+case pb2.M2_IntOneof_case:
+ _ = m2.GetIntOneof()
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; oneof refs in default; red",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_IntOneof:
+ _ = oneofField.IntOneof
+default:
+ _ = oneofField
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch m2.WhichOneofField() {
+case pb2.M2_IntOneof_case:
+ _ = m2.GetIntOneof()
+default:
+ _ = oneofField
+}
+`,
+ },
+ }, {
+ desc: "oneof: with assignment; default %v",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ in: `
+switch oneofField := m2.OneofField.(type) {
+default:
+ fmtErrorf("bad oneof %v", oneofField)
+ fmtErrorf("bad oneof %v", m2.GetOneofField())
+ fmtErrorf("bad oneof %v", m2.OneofField)
+}
+ `,
+ want: map[Level]string{
+ Green: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+default:
+ fmtErrorf("bad oneof %v", oneofField)
+ fmtErrorf("bad oneof %v", m2.WhichOneofField())
+ fmtErrorf("bad oneof %v", m2.WhichOneofField())
+}
+`,
+ },
+ }}
+
+ runTableTests(t, tests)
+}
+
+func TestOneofBuilder(t *testing.T) {
+ tests := []test{
+ {
+ desc: "bytes builder",
+ srcfiles: []string{"pkg_test.go"},
+ extra: `var bytes []byte`,
+ in: `
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte("hello")}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte{}}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: bytes}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{[]byte("hello")}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{bytes}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{nil}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: nil}}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte{}}.Build()
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: bytes}}
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{bytes}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{nil}}
+_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: nil}}
+`,
+ Red: `
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte{}}.Build()
+_ = pb2.M2_builder{BytesOneof: protooneofdefault.ValueOrDefaultBytes(bytes)}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build()
+_ = pb2.M2_builder{BytesOneof: protooneofdefault.ValueOrDefaultBytes(bytes)}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte{}}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte{}}.Build()
+_ = pb2.M2_builder{BytesOneof: []byte{}}.Build()
+`,
+ },
+ },
+ }
+ runTableTests(t, tests)
+}
+
+func TestOneofSetter(t *testing.T) {
+ tests := []test{{
+ desc: "basic",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `
+var str string
+var num int64
+`,
+ in: `
+m2.OneofField = &pb2.M2_StringOneof{StringOneof: "hello"}
+m2.OneofField = &pb2.M2_StringOneof{StringOneof: str}
+m2.OneofField = &pb2.M2_StringOneof{"hello"}
+m2.OneofField = &pb2.M2_StringOneof{str}
+m2.OneofField = &pb2.M2_StringOneof{}
+m2.OneofField = &pb2.M2_IntOneof{IntOneof: 42}
+m2.OneofField = &pb2.M2_IntOneof{IntOneof: num}
+m2.OneofField = &pb2.M2_IntOneof{42}
+m2.OneofField = &pb2.M2_IntOneof{num}
+m2.OneofField = &pb2.M2_IntOneof{}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetStringOneof("hello")
+m2.SetStringOneof(str)
+m2.SetStringOneof("hello")
+m2.SetStringOneof(str)
+m2.SetStringOneof("")
+m2.SetIntOneof(42)
+m2.SetIntOneof(num)
+m2.SetIntOneof(42)
+m2.SetIntOneof(num)
+m2.SetIntOneof(0)
+`,
+ },
+ }, {
+ desc: "enum",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `var enum pb2.M2_Enum`,
+ in: `
+m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: pb2.M2_E_VAL}
+m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: enum}
+m2.OneofField = &pb2.M2_EnumOneof{enum}
+m2.OneofField = &pb2.M2_EnumOneof{}
+m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: 0}
+m2.OneofField = &pb2.M2_EnumOneof{0}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetEnumOneof(pb2.M2_E_VAL)
+m2.SetEnumOneof(enum)
+m2.SetEnumOneof(enum)
+m2.SetEnumOneof(0)
+m2.SetEnumOneof(0)
+m2.SetEnumOneof(0)
+`,
+ },
+ }, {
+ desc: "bytes",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ extra: `var bytes []byte`,
+ in: `
+m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: []byte("hello")}
+m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: bytes}
+m2.OneofField = &pb2.M2_BytesOneof{[]byte("hello")}
+m2.OneofField = &pb2.M2_BytesOneof{bytes}
+m2.OneofField = &pb2.M2_BytesOneof{}
+m2.OneofField = &pb2.M2_BytesOneof{nil}
+m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: nil}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetBytesOneof([]byte("hello"))
+m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: bytes}
+m2.SetBytesOneof([]byte("hello"))
+m2.OneofField = &pb2.M2_BytesOneof{bytes}
+m2.OneofField = &pb2.M2_BytesOneof{}
+m2.OneofField = &pb2.M2_BytesOneof{nil}
+m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: nil}
+`,
+ Red: `
+m2.SetBytesOneof([]byte("hello"))
+m2.SetBytesOneof(protooneofdefault.ValueOrDefaultBytes(bytes))
+m2.SetBytesOneof([]byte("hello"))
+m2.SetBytesOneof(protooneofdefault.ValueOrDefaultBytes(bytes))
+m2.SetBytesOneof([]byte{})
+m2.SetBytesOneof([]byte{})
+m2.SetBytesOneof([]byte{})
+`,
+ },
+ }, {
+ desc: "nontest: message clit empty",
+ srcfiles: []string{"code.go"},
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{}}
+m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{}}
+m2.OneofField = &pb2.M2_MsgOneof{}
+m2.OneofField = &pb2.M2_MsgOneof{nil}
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: nil}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetMsgOneof(&pb2.M2{})
+m2.SetMsgOneof(&pb2.M2{})
+m2.OneofField = &pb2.M2_MsgOneof{}
+m2.OneofField = &pb2.M2_MsgOneof{nil}
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: nil}
+`,
+ },
+ }, {
+ desc: "nontest: message clit",
+ srcfiles: []string{"code.go"},
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{B: proto.Bool(true)}}
+m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{B: proto.Bool(true)}}
+`,
+ want: map[Level]string{
+ // We could detect that this is safe in theory but it
+ // requires data flow analysis to prove that m2h2 and
+ // m2h3 are never nil
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.SetB(true)
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: m2h2}
+m2h3 := &pb2.M2{}
+m2h3.SetB(true)
+m2.OneofField = &pb2.M2_MsgOneof{m2h3}
+`,
+ Yellow: `
+m2h2 := &pb2.M2{}
+m2h2.SetB(true)
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h2))
+m2h3 := &pb2.M2{}
+m2h3.SetB(true)
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h3))
+`,
+ },
+ }, {
+ desc: "test: message clit",
+ srcfiles: []string{"pkg_test.go"},
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{B: proto.Bool(true)}}
+m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{B: proto.Bool(true)}}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build())
+m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build())
+`,
+ },
+ }, {
+ desc: "test: message clit with decorations",
+ srcfiles: []string{"pkg_test.go"},
+ in: `
+_ = &pb2.M2{
+ OneofField: &pb2.M2_MsgOneof{
+ // Comment above MsgOneof
+ MsgOneof: &pb2.M2{
+ // Comment above B
+ B: proto.Bool(true), // end of B line
+ }, // end of MsgOneof line
+ },
+}
+
+_ = &pb2.M2{
+ OneofField: &pb2.M2_MsgOneof{
+ // Comment above MsgOneof
+ &pb2.M2{
+ // Comment above B
+ B: proto.Bool(true), // end of B line
+ }, // end of MsgOneof line
+ },
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{
+ // Comment above MsgOneof
+ MsgOneof: pb2.M2_builder{
+ // Comment above B
+ B: proto.Bool(true), // end of B line
+ }.Build(), // end of MsgOneof line
+}.Build()
+
+_ = pb2.M2_builder{
+ // Comment above MsgOneof
+ MsgOneof: pb2.M2_builder{
+ // Comment above B
+ B: proto.Bool(true), // end of B line
+ }.Build(), // end of MsgOneof line
+}.Build()
+`,
+ },
+ }, {
+ desc: "nontest: nested message clit with decorations",
+ srcfiles: []string{"pkg.go"},
+ in: `
+_ = &pb2.M2{
+ OneofField: &pb2.M2_MsgOneof{
+ // Comment above MsgOneof
+ MsgOneof: &pb2.M2{
+ // Comment above B
+ B: proto.Bool(true), // end of B line
+ }, // end of MsgOneof line
+ },
+}
+`,
+ want: map[Level]string{
+ // We could detect that this is safe in theory but it
+ // requires data flow analysis to prove that m2h2 is
+ // never nil
+ Green: `
+m2h2 := &pb2.M2{}
+// Comment above B
+m2h2.SetB(true) // end of B line
+m2h3 := &pb2.M2{}
+m2h3.OneofField = &pb2.M2_MsgOneof{
+ // Comment above MsgOneof
+ MsgOneof: m2h2, // end of MsgOneof line
+}
+_ = m2h3
+`,
+ Yellow: `
+m2h2 := &pb2.M2{}
+// Comment above B
+m2h2.SetB(true) // end of B line
+m2h3 := &pb2.M2{}
+// Comment above MsgOneof
+m2h3.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h2)) // end of MsgOneof line
+_ = m2h3
+`,
+ },
+ }, {
+ desc: "nontest message clit var",
+ srcfiles: []string{"code.go"},
+ extra: `var msg *pb2.M2`,
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg}
+m2.OneofField = &pb2.M2_MsgOneof{msg}
+`,
+ want: map[Level]string{
+ Green: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg}
+m2.OneofField = &pb2.M2_MsgOneof{msg}
+`,
+ Yellow: `
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg))
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg))
+`,
+ },
+ }, {
+ desc: "message clit var",
+ srcfiles: []string{"pkg_test.go"},
+ extra: `var msg *pb2.M2`,
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg}
+m2.OneofField = &pb2.M2_MsgOneof{msg}
+`,
+ want: map[Level]string{
+ Green: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg}
+m2.OneofField = &pb2.M2_MsgOneof{msg}
+`,
+ Yellow: `
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg))
+m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg))
+`,
+ },
+ }, {
+ desc: "message builder",
+ srcfiles: []string{"code.go", "pkg_test.go"},
+ in: `
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: pb2.M2_builder{B: proto.Bool(true)}.Build()}
+m2.OneofField = &pb2.M2_MsgOneof{pb2.M2_builder{B: proto.Bool(true)}.Build()}
+m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: pb2.M2_builder{}.Build()}
+m2.OneofField = &pb2.M2_MsgOneof{pb2.M2_builder{}.Build()}
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build())
+m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build())
+m2.SetMsgOneof(pb2.M2_builder{}.Build())
+m2.SetMsgOneof(pb2.M2_builder{}.Build())
+`,
+ },
+ }, {
+ desc: "variable of wrapper type",
+ srcfiles: []string{"code.go"},
+ in: `
+var scalarOneof *pb2.M2_StringOneof
+_ = &pb2.M2{OneofField: scalarOneof}
+`,
+ want: map[Level]string{
+ Green: `
+var scalarOneof *pb2.M2_StringOneof
+m2h2 := &pb2.M2{}
+m2h2.OneofField = scalarOneof
+_ = m2h2
+`,
+ Red: `
+var scalarOneof *pb2.M2_StringOneof
+m2h2 := &pb2.M2{}
+m2h2.SetStringOneof(scalarOneof.StringOneof)
+_ = m2h2
+`,
+ },
+ }, {
+ desc: "oneof field",
+ srcfiles: []string{"code.go"},
+ in: `
+_ = &pb2.M2{OneofField: m2.OneofField}
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+m2h2.OneofField = m2.OneofField
+_ = m2h2
+`,
+ Red: `
+m2h2 := &pb2.M2{}
+// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).
+m2h2.OneofField = m2.OneofField
+_ = m2h2
+`,
+ },
+ }}
+ runTableTests(t, tests)
+}
+
+func TestMaybeUnsafeOneof(t *testing.T) {
+ tests := []test{{
+ desc: "oneof message wrapper variable field",
+ srcfiles: []string{"code.go"},
+ in: `
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+`,
+ want: map[Level]string{
+ Green: `
+var msgOneof *pb2.M2_MsgOneof
+m2h2 := &pb2.M2{}
+m2h2.OneofField = msgOneof
+_ = m2h2
+`,
+ Yellow: `
+var msgOneof *pb2.M2_MsgOneof
+m2h2 := &pb2.M2{}
+m2h2.OneofField = msgOneof
+_ = m2h2
+`,
+ Red: `
+var msgOneof *pb2.M2_MsgOneof
+m2h2 := &pb2.M2{}
+m2h2.SetMsgOneof(protooneofdefault.ValueOrDefault(msgOneof.MsgOneof))
+_ = m2h2
+`,
+ },
+ },
+
+ {
+ desc: "oneof message wrapper variable in builder",
+ srcfiles: []string{"code_test.go"},
+ in: `
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+`,
+ want: map[Level]string{
+ Green: `
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+`,
+ Yellow: `
+var msgOneof *pb2.M2_MsgOneof
+_ = &pb2.M2{OneofField: msgOneof}
+`,
+ Red: `
+var msgOneof *pb2.M2_MsgOneof
+_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msgOneof.MsgOneof)}.Build()
+`,
+ },
+ },
+
+ {
+ desc: "oneof message field",
+ srcfiles: []string{"code.go"},
+ in: `
+var msg *pb2.M2
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}}
+`,
+ want: map[Level]string{
+ Green: `
+var msg *pb2.M2
+m2h2 := &pb2.M2{}
+m2h2.OneofField = &pb2.M2_MsgOneof{msg}
+_ = m2h2
+`,
+ Yellow: `
+var msg *pb2.M2
+m2h2 := &pb2.M2{}
+m2h2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg))
+_ = m2h2
+`,
+ },
+ },
+
+ {
+ desc: "oneof message field in builder",
+ srcfiles: []string{"code_test.go"},
+ in: `
+var msg *pb2.M2
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}}
+`,
+ want: map[Level]string{
+ Green: `
+var msg *pb2.M2
+_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}}
+`,
+ Yellow: `
+var msg *pb2.M2
+_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msg)}.Build()
+`,
+ },
+ },
+ }
+ runTableTests(t, tests)
+}
+
+func TestNestedSwitch(t *testing.T) {
+ tests := []test{
+ {
+ desc: "nested switch",
+ extra: `func fmtErrorf(format string, a ...interface{}) { }`,
+ srcfiles: []string{"code_test.go"},
+ in: `
+switch oneofField := m2.OneofField.(type) {
+case *pb2.M2_BytesOneof:
+ _ = oneofField.BytesOneof
+case *pb2.M2_MsgOneof:
+ switch nestedOneof := oneofField.MsgOneof.OneofField.(type) {
+ case *pb2.M2_StringOneof:
+ _ = nestedOneof.StringOneof
+ default:
+ fmtErrorf("%v", oneofField)
+ }
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ want: map[Level]string{
+ Red: `
+switch oneofField := m2.WhichOneofField(); oneofField {
+case pb2.M2_BytesOneof_case:
+ _ = m2.GetBytesOneof()
+case pb2.M2_MsgOneof_case:
+ switch m2.GetMsgOneof().WhichOneofField() {
+ case pb2.M2_StringOneof_case:
+ _ = m2.GetMsgOneof().GetStringOneof()
+ default:
+ fmtErrorf("%v", oneofField)
+ }
+default:
+ fmtErrorf("%v", oneofField)
+}
+`,
+ },
+ },
+ }
+ runTableTests(t, tests)
+}
diff --git a/internal/fix/oneofswitch.go b/internal/fix/oneofswitch.go
new file mode 100644
index 0000000..471d51e
--- /dev/null
+++ b/internal/fix/oneofswitch.go
@@ -0,0 +1,576 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "go/token"
+ "go/types"
+ "slices"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+// oneofSwitchPost handles type switches of protocol buffer oneofs.
+func oneofSwitchPost(c *cursor) bool {
+ stmt, ok := c.Node().(*dst.TypeSwitchStmt)
+ if !ok {
+ c.Logf("ignoring %T (looking for TypeSwitchStmt)", c.Node())
+ return true
+ }
+ initStmt := stmt.Init
+
+ var typeAssert *dst.TypeAssertExpr
+ // oneofIdent is the variable introduced for the expression under the type
+ // assertion (e.g. "x" in "x := m.F.(type)").
+ //
+ // We keep track of it so that we can replace it with accesses to m later on.
+ //
+ // This can be nil as "m.F.(type)" is also valid (type-switch without
+ // assignment), in which case we don't have to rewrite any references to the
+ // oneof in type-switch clauses.
+ var oneofIdent *dst.Ident
+
+ switch a := stmt.Assign.(type) {
+ case *dst.AssignStmt: // "switch x := m.F.(type) {"
+ if len(a.Lhs) != 1 || len(a.Rhs) != 1 {
+ c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(a.Lhs), len(a.Rhs))
+ return true
+ }
+ ident, ok := a.Lhs[0].(*dst.Ident)
+ if !ok {
+ c.Logf("ignoring Lhs=%T (looking for Ident)", a.Lhs[0])
+ return true
+ }
+ oneofIdent = ident
+ typeAssert, ok = a.Rhs[0].(*dst.TypeAssertExpr)
+ if !ok {
+ c.Logf("ignoring Rhs=%T (looking for TypeAssertExpr)", a.Rhs[0])
+ return true
+ }
+ case *dst.ExprStmt: // "switch m.F.(type) {"
+ typeAssert, ok = a.X.(*dst.TypeAssertExpr)
+ if !ok {
+ c.Logf("ignoring TypeSwitchStmt.Assign.X=%T (looking for TypeAssertExpr)", a.X)
+ return true
+ }
+ default:
+ c.Logf("ignoring TypeSwitchStmt.Assign=%T (looking for TypeAssertExpr or AssignStmt)", a)
+ return true
+ }
+ var oneofScope *types.Scope
+ if oneofIdent != nil {
+ ooi, ok := c.typesInfo.astMap[oneofIdent]
+ if !ok {
+ panic(fmt.Sprintf("failed to retrieve ast.Node for dst.Node: %v", oneofIdent))
+ }
+ oneofScope = c.pkg.TypePkg.Scope().Innermost(ooi.Pos())
+ }
+
+ // Below we assume how the "Which" method is named based on either the name of
+ // the corresponding field or "Get" method. This isn't necessarily sound in
+ // the presence of naming conflicts but we don't want to repeat that
+ // complicated logic here.
+ var whichSig *types.Signature
+ var whichName string
+ var recv dst.Expr // "X" in "X.F.(type)" or "X.GetF().(type)"
+
+ // We track oneof field and getter name here to keep the name-related logic
+ // in one place, at the top. We use those to rewrite oneof field access
+ // through field/getter later on. Ideally, we would use object identity
+ // instead of comparison by name, but we can't get that without messing with
+ // names, so we might as well got for name comaprisons.
+ var oneofFieldName string // "F" in "X.F.(type)" or "X.GetF().(type)"
+ var oneofGetName string // "GetF" corresponding to oneofFieldName
+
+ // Find the WhichF() method that should replace "m.F.(type)" in
+ // switch m.F.(type) {
+ // =>
+ // switch m.WhichF() {
+ if field, ok := c.trackedProtoFieldSelector(typeAssert.X); ok {
+ if !isOneof(c.typeOf(field)) {
+ c.Logf("ignoring field %v because it is not a oneof", field)
+ return true
+ }
+ oneofFieldName = field.Sel.Name
+ oneofGetName = "Get" + field.Sel.Name
+ whichName = "Which" + oneofFieldName
+ c.Logf("rewriting TypeAssertExpr on oneof field %s to %s CallExpr", oneofFieldName, whichName)
+ whichSig = whichOneofSignature(c.typeOf(field.X), whichName)
+ recv = field.X
+ }
+
+ // Find the WhichF() method that should replace "m.GetF().(type)" in
+ // switch m.GetF().(type) {
+ // =>
+ // switch m.WhichF() {
+ if call, ok := typeAssert.X.(*dst.CallExpr); ok {
+ sel, ok := call.Fun.(*dst.SelectorExpr)
+ if !ok || !isOneof(c.typeOf(call)) || !strings.HasPrefix(sel.Sel.Name, "Get") || !c.shouldUpdateType(c.typeOf(sel.X)) {
+ // Even if this is a call returning a oneof, we can't do anything about
+ // it. We don't add DO_NOT_SUBMIT tag because it's too easy to
+ // accidentally add it to unrelated type switches here.
+ c.Logf("ignoring: TypeAssertExpr recondition is not met")
+ return true
+ }
+ recv = sel.X
+ oneofGetName = sel.Sel.Name
+ oneofFieldName = oneofGetName[len("Get"):]
+ whichName = "Which" + oneofFieldName
+ c.Logf("rewriting TypeAssertExpr on oneof field %s to %s CallExpr", oneofFieldName, whichName)
+ whichSig = whichOneofSignature(c.typeOf(sel.X), whichName)
+ }
+
+ // Not a oneof access (call or field) or we couldn't find the "Which" method.
+ if whichSig == nil {
+ c.Logf("ignoring: cannot find Which${Field} method")
+ return true
+ }
+
+ // If recv may have side effects, then move it to the init statement if
+ // possible (bail otherwise) in order to avoid cloning side effects into
+ // the body of the type-switch.
+ if !c.isSideEffectFree(recv) {
+ if initStmt != nil {
+ if c.lvl.ge(Red) {
+ c.numUnsafeRewritesByReason[IncompleteRewrite]++
+ markMissingRewrite(stmt, "type switch with side effects and init statement")
+ }
+ c.Logf("ignoring: cannot move init statement with side effects")
+ return true
+ }
+ newVar := dst.NewIdent("xmsg")
+ c.setType(newVar, c.typeOf(recv))
+ initStmt = &dst.AssignStmt{
+ Lhs: []dst.Expr{newVar},
+ Tok: token.DEFINE,
+ Rhs: []dst.Expr{recv},
+ }
+ recv = cloneIdent(c, newVar)
+ }
+
+ // Construct the "recv.WhichOneofField()" method call.
+ whichMethod := &dst.SelectorExpr{
+ X: recv,
+ Sel: dst.NewIdent(whichName),
+ }
+ c.setType(whichMethod, whichSig)
+ c.setType(whichMethod.Sel, whichSig)
+ whichCall := &dst.CallExpr{Fun: whichMethod}
+ c.setType(whichCall, whichSig.Results().At(0).Type())
+
+ // First, verify that we can do necessary rewrites to the entire type switch
+ // statement.
+ //
+ // In "switch x := m.F.(type) {" and similar, we replace all references
+ // to "x" (the oneof object, "oneofIdent") with method calls on "m".
+ definedVarUsedInDefaultCase := false
+ if oneofIdent != nil {
+ var maybeUnsafe bool
+ if !c.lvl.ge(Red) {
+ // Unfortunately "x" in "x := X.(type)" has no identity, so we must
+ // rely on name matching to replace all instances of "x" with
+ // something else. We want to be careful: if any clause refers to
+ // more than one "x" ("x" has identity in non-default clauses) that
+ // could be a oneof then we don't do anything.
+ //
+ // If any non-default clause refers to "x" (that could be a oneof)
+ // not in the context of a selector (i.e. it's not "x.F") then we
+ // also don't do anything. Oneofs are not a "thing" and can't be
+ // referenced like this in the new API.
+ //
+ // The sole exception is printf-like calls in the default clause
+ // (see below), where we replace the oneof reference with a "Which"
+ // method call.
+ for _, clause := range stmt.Body.List {
+ var clauseIdent *dst.Ident
+ dstutil.Apply(clause, func(cur *dstutil.Cursor) bool {
+ ident, ok := cur.Node().(*dst.Ident)
+ if !ok || ident.Name != oneofIdent.Name {
+ return true
+ }
+ isDefaultClause := clause.(*dst.CaseClause).List == nil // Ident has no type in "default".
+ if !isDefaultClause && !isOneofWrapper(c, ident) {
+ return true
+ }
+ // Usually we don't allow references to the "oneof" itself, but
+ // we make an exception for print-like statements as
+ // it's comment to say: `fmt.Errorf("unknown case %v", oneofField)`.
+ if c.looksLikePrintf(cur.Parent()) {
+ return true
+ }
+ if _, ok := cur.Parent().(*dst.SelectorExpr); !ok {
+ maybeUnsafe = true
+ return false
+ }
+ if clauseIdent == nil {
+ clauseIdent = ident
+ return true
+ }
+ if c.objectOf(clauseIdent) == c.objectOf(ident) {
+ return true
+ }
+ maybeUnsafe = true
+ return false
+ }, nil)
+ if maybeUnsafe {
+ c.Logf("ignoring: cannot rewrite oneof usage safely")
+ return true
+ }
+ }
+ }
+
+ // Then do the necessary rewrites.
+ for _, clause := range stmt.Body.List {
+ dstutil.Apply(clause, func(cur *dstutil.Cursor) bool {
+ n := cur.Node()
+ // First, handle direct references to the oneof itself,
+ // in default clauses:
+ // fmt.Errorf("Unknown case %v", oneofField)
+ // =>
+ // fmt.Errorf("Unknown case %v", m.WhichOneofField())
+ //
+ // This works well, because oneof cases have String() method.
+ //
+ // Note that we do the rewrite regardless of what's in the format
+ // string (we don't want to get into the business of parsing format
+ // strings now), so we risk:
+ // fmt.Errorf("Unknown case %T", m.WhichOneofField())
+ // Which is not ideal, but not unreasonable.
+ if c.looksLikePrintf(cur.Parent()) {
+ // rewrite the formatting verb (if any) to %v:
+ // fmt.Errorf("%T", m2.OneofField) => fmt.Errorf("%v", m2.OneofField)
+ // The other part of the expression is rewritten below.
+ // This function does not handle escaped '%' character.
+ // Implementing a full formatting string parser is out of scope here.
+ rewriteVerb := func() {
+ call := cur.Parent().(*dst.CallExpr)
+ argIndex := slices.Index(call.Args, cur.Node().(dst.Expr))
+ if argIndex == -1 {
+ panic(fmt.Sprintf("could not find argument %v in call expression %v. Did you call rewriteVerb after replacing the Node?", cur.Node(), call))
+ }
+ for i, p := range call.Args {
+ slit, ok := p.(*dst.BasicLit)
+ if !ok {
+ continue
+ }
+ if slit.Kind != token.STRING {
+ continue
+ }
+ numFormattedArg := argIndex - i
+ idx := -1
+ for i, r := range slit.Value {
+ if r == '%' {
+ numFormattedArg--
+ if numFormattedArg == 0 {
+ idx = i
+ break
+ }
+ }
+ }
+ // There are not enough formatting verbs.
+ if idx == -1 {
+ return
+ }
+ if idx == len(slit.Value) || slit.Value[idx+1] == 'v' {
+ break
+ }
+ // No need to clone as a literal cannot be shared between Nodes.
+ slit.Value = slit.Value[:idx] + "%v" + slit.Value[idx+2:]
+ }
+ }
+ // switch m := f(); oneofField := m.OneofField.(type) {...
+ // default:
+ // return fmt.Errorf("Unknown case %v", oneofField)
+ // =>
+ // switch m := f(); oneofField := m.OneofField.(type) {...
+ // default:
+ // return fmt.Errorf("Unknown case %v", m.WhichOneofField())
+ if ident, ok := n.(*dst.Ident); ok && ident.Name == oneofIdent.Name {
+ rewriteVerb()
+ definedVarUsedInDefaultCase = true
+ if initStmt == nil {
+ return true
+ }
+ c.Logf("rewriting usage of %q", oneofIdent.Name)
+ if maybeUnsafe {
+ c.numUnsafeRewritesByReason[MaybeSemanticChange]++
+ }
+ cur.Replace(cloneSelectorCallExpr(c, whichCall))
+ return true
+ }
+ // fmt.Errorf("Unknown case %v", m.OneofField)
+ // =>
+ // fmt.Errorf("Unknown case %v", m.WhichOneofField())
+ if field, ok := c.trackedProtoFieldSelector(n); ok && isOneof(c.typeOf(field)) && field.Sel.Name == oneofFieldName {
+ c.Logf("rewriting usage of %q", oneofIdent.Name)
+ if maybeUnsafe {
+ c.numUnsafeRewritesByReason[MaybeSemanticChange]++
+ }
+ rewriteVerb()
+ cur.Replace(cloneSelectorCallExpr(c, whichCall))
+ return true
+ }
+ // fmt.Errorf("Unknown case %v", m.GetOneofField())
+ // =>
+ // fmt.Errorf("Unknown case %v", m.WhichOneofField())
+ if call, ok := n.(*dst.CallExpr); ok && isOneof(c.typeOf(call)) {
+ if sel, ok := call.Fun.(*dst.SelectorExpr); ok && sel.Sel.Name == oneofGetName {
+ c.Logf("rewriting usage of %q", oneofIdent.Name)
+ if maybeUnsafe {
+ c.numUnsafeRewritesByReason[MaybeSemanticChange]++
+ }
+ rewriteVerb()
+ cur.Replace(cloneSelectorCallExpr(c, whichCall))
+ return true
+ }
+ }
+ }
+
+ // Handle "oneofField.F" => "recv.F"
+ // Subsequent passes may then do more rewrites. For example:
+ // "recv.F" => "recv.{Get,Set,Has,Clear}F()"
+ sel, ok := n.(*dst.SelectorExpr)
+ if !ok {
+ c.Logf("ignoring usage %v of type %T (looking for SelectorExpr)", n, n)
+ return true
+ }
+ ident, ok := sel.X.(*dst.Ident)
+ if !ok {
+ c.Logf("ignoring usage %v of type SelectorExpr.X.%T (looking for SelectorExpr.X.Ident)", sel, sel.X)
+ return true
+ }
+ // We cannot do an object based comparison here
+ // because variable declared in the initializer
+ // of a switch don't have an associated object
+ // (or there is a bug in our tooling that removes
+ // this association).
+ if ident.Name != oneofIdent.Name {
+ c.Logf("ignoring %v because it is not using %v", sel, oneofIdent)
+ return true
+ }
+ isDefaultClause := clause.(*dst.CaseClause).List == nil
+ if !isDefaultClause && !isOneofWrapper(c, ident) {
+ c.Logf("ignoring usage %v: not in default clause and not a oneof wrapper", sel)
+ return true
+ }
+
+ // Check if the name is shadwed in a nested block:
+ //
+ // switch oneofField := m2.OneofField.(type) {
+ // case *pb.M2_StringOneof:
+ // {
+ // oneofField := &O{}
+ // _ = oneofField.StringOneof
+ // }
+ // }
+ //
+ // For switch-case statements there is one scope for the switch
+ // which has child scopes, one for each case clause. A variable
+ // defined in the switch condition (oneofField in the example)
+ // is not part of the parent (switch) scope but is defined in
+ // every child (case) scope.
+ // This means to check if an identifier references the variable
+ // defined by the switch condition, we must check if the
+ // referenced object is any of the child (case) scopes.
+ if oneofScope == nil {
+ c.Logf("ignoring usage %v: no scope for oneof found", n)
+ return true
+ }
+ astNode, ok := c.typesInfo.astMap[ident]
+ if !ok {
+ panic(fmt.Sprintf("failed to retrieve ast.Node for dst.Node: %p", ident))
+ }
+ scope := c.pkg.TypePkg.Scope().Innermost(astNode.Pos())
+ s, _ := scope.LookupParent(ident.Name, astNode.Pos())
+ if s == nil {
+ panic(fmt.Sprintf("invalid package: cannot resolve %v", astNode))
+ }
+ found := false
+ for i := 0; i < oneofScope.NumChildren(); i++ {
+ if oneofScope.Child(i) == s {
+ found = true
+ break
+ }
+ }
+ // The variable defined in the switch is shadowed by another
+ // definition.
+ if !found {
+ c.Logf("ignoring usage %v: does not reference oneof type switch variable", astNode)
+ return true
+ }
+
+ c.Logf("rewriting usage %v", recv)
+ switch recv := recv.(type) {
+ case *dst.Ident:
+ sel.X = cloneIdent(c, recv)
+ case *dst.IndexExpr:
+ sel.X = cloneIndexExpr(c, recv)
+ case *dst.SelectorExpr:
+ sel.X = cloneSelectorExpr(c, recv)
+ case *dst.CallExpr:
+ sel.X = cloneSelectorCallExpr(c, recv)
+ default:
+ panic(fmt.Sprintf("unsupported receiver AST type %T in oneof type switch", recv))
+ }
+ if maybeUnsafe {
+ c.numUnsafeRewritesByReason[MaybeSemanticChange]++
+ }
+ return true
+ }, nil)
+ }
+ }
+
+ // Rewrite clauses:
+ // case *pb.M_Oneof:
+ // =>
+ // case pb.M_Oneof_case:
+ for _, clause := range stmt.Body.List {
+ cl, ok := clause.(*dst.CaseClause)
+ if !ok {
+ continue
+ }
+ for i, typ := range cl.List {
+ if ident, ok := typ.(*dst.Ident); ok && ident.Name == "nil" {
+ zero := &dst.BasicLit{Kind: token.INT, Value: "0"}
+ c.setType(zero, types.Typ[types.Int32])
+ c.Logf("rewriting nil-case to 0-case in %v", cl)
+ cl.List[i] = zero
+ continue
+ }
+
+ se, ok := typ.(*dst.StarExpr)
+ if !ok {
+ c.Logf("skipping case %v of type %T (looking for StarExpr)", cl, typ)
+ continue
+ }
+ sel, ok := se.X.(*dst.SelectorExpr)
+ if !ok {
+ c.Logf("skipping case %v of type %T (looking for SelectorExpr)", cl, se)
+ continue
+ }
+
+ // Split the oneof wrapper type name into its parts:
+ parts := strings.Split(strings.TrimRight(sel.Sel.Name, "_"), "_")
+ // parts[0] is the parent of the oneof field
+ // parts[len(parts)-1] is the oneof field
+ if len(parts) < 2 {
+ c.Logf("skipping case %v, sel %q does not split into parent_field", cl, sel.Sel.Name)
+ continue
+ }
+ field := parts[len(parts)-1]
+ suffix := "_case"
+ // There are two cases in which the wrapper type name ends in an
+ // underscore:
+ //
+ // 1. The field name itself conflicts. This means the field was
+ // called reset, marshal, etc., conflicting with the proto
+ // generated code method names. In this case, the _case constant
+ // will use two underscores.
+ //
+ // 2. The field name conflicts with another name in the .proto file.
+ // In this case, the _case constant will not use two underscores.
+ if strings.HasSuffix(sel.Sel.Name, "_") && !conflictingNames[field] {
+ suffix = "case"
+ }
+
+ sel.Sel.Name += suffix
+ sel.Decs.Before = se.Decs.Before
+ sel.Decs.Start = append(sel.Decs.Start, se.Decs.Start...)
+ sel.Decs.End = append(sel.Decs.End, se.Decs.End...)
+ sel.Decs.After = se.Decs.After
+ c.Logf("rewriting case %+#v", cl.List[i])
+ cl.List[i] = sel
+ }
+ }
+
+ c.Logf("rewriting SwitchStmt")
+
+ // The TypeSwitchStmt initializes a local variable that we need to keep
+ // and there is no init statement.
+ // switch oneofField := m.OneofField.(type) {...
+ // default:
+ // return fmt.Errorf("Unknown case %v", oneofField)
+ // =>
+ // switch oneofField := m.WhichOneof() {...
+ // default:
+ // return fmt.Errorf("Unknown case %v", oneofField)
+ // If it were not used we would rewrite it to:
+ // switch oneofField := m.OneofField.(type) {
+ // ...
+ // default:
+ // return fmt.Errorf("Unknown case")
+ // =>
+ // switch m.WhichOneof() {...
+ // ...
+ // default:
+ // return fmt.Errorf("Unknown case")
+ var tag dst.Expr = cloneSelectorCallExpr(c, whichCall)
+ if definedVarUsedInDefaultCase && initStmt == nil {
+ if oneofIdent == nil {
+ panic("identNil")
+ }
+ c.setType(oneofIdent, types.Typ[types.Invalid])
+ initStmt = &dst.AssignStmt{
+ Lhs: []dst.Expr{
+ cloneIdent(c, oneofIdent),
+ },
+ Rhs: []dst.Expr{tag},
+ Tok: token.DEFINE,
+ }
+ tag = cloneIdent(c, oneofIdent)
+ c.setType(tag, c.typeOf(oneofIdent))
+ }
+ // Change the type from TypeSwitchStmt to SwitchStmt.
+ c.Replace(&dst.SwitchStmt{
+ Init: initStmt,
+ Tag: tag,
+ Body: stmt.Body,
+ Decs: dst.SwitchStmtDecorations{
+ NodeDecs: stmt.Decs.NodeDecs,
+ Switch: stmt.Decs.Switch,
+ Init: stmt.Decs.Init,
+ Tag: stmt.Decs.Assign,
+ },
+ })
+
+ return true
+}
+
+// whichOneofSignature returns the signature type of the method, of type t, with
+// the given name. It must have "Which" prefix (i.e. it is the method for
+// getting the oneof type). It returns an in-band nil (instead of a separate
+// "ok" bool) if such method does not exist in order to simplify the caller code
+// above.
+func whichOneofSignature(t types.Type, name string) *types.Signature {
+ if !strings.HasPrefix(name, "Which") {
+ panic(fmt.Sprintf("whichOneofSignature called with %q; must have 'Which' prefix", name))
+ }
+ ptr, ok := t.(*types.Pointer)
+ if !ok {
+ return nil
+ }
+ typ, ok := ptr.Elem().(*types.Named)
+ if !ok {
+ return nil
+ }
+ var m *types.Func
+ for i := 0; i < typ.NumMethods(); i++ {
+ m = typ.Method(i)
+ if m.Name() == name {
+ break
+ }
+ }
+ if m == nil {
+ return nil
+ }
+ sig := m.Type().(*types.Signature)
+ if sig.Results().Len() != 1 {
+ return nil
+ }
+ return sig
+}
diff --git a/internal/fix/outputparam.go b/internal/fix/outputparam.go
new file mode 100644
index 0000000..16af42d
--- /dev/null
+++ b/internal/fix/outputparam.go
@@ -0,0 +1,232 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+ "google.golang.org/open2opaque/internal/protodetecttypes"
+)
+
+// Recognize the *resp = <message> pattern and rewrite it to proto.Merge.
+func outputParamPre(c *cursor) bool {
+ as, ok := c.Node().(*dst.AssignStmt)
+ if !ok {
+ return true
+ }
+
+ if len(as.Lhs) != 1 || len(as.Rhs) != 1 {
+ c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(as.Lhs), len(as.Rhs))
+ return true
+ }
+
+ switch as.Rhs[0].(type) {
+ case *dst.Ident: // variable assignment
+ case *dst.CompositeLit: // struct literal
+ case *dst.CallExpr: // builder
+ default:
+ c.Logf("ignoring AssignStmt with rhs %T, want Ident, CompositeLit or CallExpr", as.Rhs[0])
+ return true
+ }
+
+ se, ok := as.Lhs[0].(*dst.StarExpr)
+ if !ok {
+ c.Logf("ignoring AssignStmt with lhs %T, want StarExpr", as.Lhs[0])
+ return true
+ }
+ if _, ok := se.X.(*dst.Ident); !ok {
+ c.Logf("ignoring AssignStmt with lhs.X %T, want Ident", se.X)
+ return true
+ }
+
+ T := c.typeOf(se)
+ if !(protodetecttypes.Type{T: T}).IsMessage() {
+ c.Logf("ignoring AssignStmt for non-proto message (%v)", T)
+ return true
+ }
+
+ if resetNeeded(c, as, se.X.(*dst.Ident)) {
+ resetFun := &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(protoImport)},
+ Sel: &dst.Ident{Name: "Reset"},
+ }
+ resetCall := &dst.CallExpr{
+ Fun: resetFun,
+ Args: []dst.Expr{
+ cloneIdent(c, se.X.(*dst.Ident)),
+ },
+ Decs: dst.CallExprDecorations{
+ NodeDecs: dst.NodeDecs{
+ Before: as.Decs.NodeDecs.Before,
+ Start: as.Decs.NodeDecs.Start,
+ },
+ },
+ }
+ as.Decs.NodeDecs.Before = dst.None
+ as.Decs.NodeDecs.Start = nil
+ c.setType(resetCall, types.Typ[types.Invalid])
+ c.setType(resetFun, types.Typ[types.Invalid])
+ c.setType(resetFun.X, types.Typ[types.Invalid])
+ c.setType(resetFun.Sel, types.Typ[types.Invalid])
+ c.InsertBefore(&dst.ExprStmt{X: resetCall})
+ }
+
+ // Replace the AssignStmt with a call to proto.Merge
+ mergeFun := &dst.SelectorExpr{
+ X: &dst.Ident{Name: c.imports.name(protoImport)},
+ Sel: &dst.Ident{Name: "Merge"},
+ }
+ unaryRHS := &dst.UnaryExpr{
+ Op: token.AND,
+ X: as.Rhs[0],
+ }
+ mergeCall := &dst.CallExpr{
+ Fun: mergeFun,
+ Args: []dst.Expr{
+ se.X,
+ unaryRHS,
+ },
+ Decs: dst.CallExprDecorations{
+ NodeDecs: as.Decs.NodeDecs,
+ },
+ }
+ c.setType(mergeCall, types.Typ[types.Invalid])
+ c.setType(mergeFun, types.Typ[types.Invalid])
+ c.setType(mergeFun.X, types.Typ[types.Invalid])
+ c.setType(mergeFun.Sel, types.Typ[types.Invalid])
+ c.setType(unaryRHS, types.NewPointer(T))
+ stmt := &dst.ExprStmt{X: mergeCall}
+ c.Replace(stmt)
+ updateASTMap(c, as, stmt)
+
+ return true
+}
+
+func isStubbyHandler(c *cursor, ft *ast.FuncType) bool {
+ if ft.Results == nil || len(ft.Results.List) != 1 {
+ c.Logf("not a stubby handler: not precisely one return value")
+ return false
+ }
+ if id, ok := ft.Results.List[0].Type.(*ast.Ident); !ok || id.Name != "error" {
+ c.Logf("not a stubby handler: not returning an error")
+ return false
+ }
+ if got, want := len(ft.Params.List), 3; got != want {
+ c.Logf("incorrect number of parameters for a stubby handler: got %d, want %d", got, want)
+ return false
+ }
+ params := ft.Params.List
+
+ se, ok := params[0].Type.(*ast.SelectorExpr)
+ if !ok {
+ c.Logf("first parameter is not context.Context")
+ return false
+ }
+ if id, ok := se.X.(*ast.Ident); !ok || id.Name != "context" {
+ c.Logf("first parameter is not context.Context")
+ return false
+ }
+ if se.Sel.Name != "Context" {
+ c.Logf("first parameter is not context.Context")
+ return false
+ }
+
+ // Both remaining parameters must be proto messages
+ for _, param := range params[1:] {
+ dstT, ok := c.typesInfo.dstMap[param.Type]
+ if !ok {
+ c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", param.Type, param.Type)
+ return false
+ }
+
+ T := c.typeOf(dstT.(dst.Expr))
+ ptr, ok := T.(*types.Pointer)
+ if !ok {
+ c.Logf("parameter %T is not a proto message", param.Type)
+ return false
+ }
+ T = ptr.Elem()
+ if !(protodetecttypes.Type{T: T}).IsMessage() {
+ c.Logf("parameter %T is not a proto message", param.Type)
+ return false
+ }
+ }
+
+ return true
+}
+
+// resetNeeded figures out if for the specified AssignStmt, a proto.Reset() call
+// needs to be inserted before or not.
+//
+// resetNeeded looks at the AST of the current scope, considering all statements
+// between the scope opener (function declaration) and the AssignStmt:
+//
+// func (*Srv) Handler(ctx context.Context, req *pb.Req, resp *pb.Resp) error {
+// … // checked by resetNeeded()
+// mm2.I32 = 23 // uses mm2, not resp
+// *resp = mm2 // AssignStmt
+// }
+func resetNeeded(c *cursor, as *dst.AssignStmt, id *dst.Ident) bool {
+ innerMost, opener := c.enclosingASTStmt(as)
+ if innerMost == nil {
+ return true
+ }
+ enclosing, ok := c.typesInfo.dstMap[innerMost]
+ if !ok {
+ c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost)
+ return true
+ }
+
+ var ft *ast.FuncType
+ switch x := opener.(type) {
+ case *ast.FuncDecl:
+ ft = x.Type
+ case *ast.FuncLit:
+ ft = x.Type
+ default:
+ c.Logf("scope opener is %T, not a FuncDecl or FuncLit", opener)
+ return true
+ }
+
+ if !isStubbyHandler(c, ft) {
+ c.Logf("scope opener is not a stubby handler")
+ return true
+ }
+
+ lastSeen := false
+ usageFound := false
+ var visit visitorFunc
+ xObj := c.objectOf(id)
+
+ visit = func(n dst.Node) dst.Visitor {
+ if n == as {
+ lastSeen = true
+ return nil
+ }
+ if lastSeen {
+ return nil
+ }
+
+ if id, ok := n.(*dst.Ident); ok {
+ // Is the variable ever used?
+ if usesObject(c, id, xObj) {
+ usageFound = true
+ return nil
+ }
+ }
+
+ return visit // recurse into children
+ }
+ dst.Walk(visit, enclosing)
+ // proto.Reset() calls are definitely needed when:
+ //
+ // 1. !lastSeen — we couldn’t find the usage of <sel> (bug?)
+ //
+ // 2. or usageFound — we did find a usage that we didn’t expect.
+ return !lastSeen || usageFound
+}
diff --git a/internal/fix/rules.go b/internal/fix/rules.go
new file mode 100644
index 0000000..85a5fcf
--- /dev/null
+++ b/internal/fix/rules.go
@@ -0,0 +1,411 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "go/token"
+ "go/types"
+ "reflect"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+ "golang.org/x/exp/slices"
+)
+
+func init() {
+ rewrites = []rewrite{
+ // outputparam.go
+ {name: "outputParamPre", pre: outputParamPre},
+ // usepointers.go
+ {name: "usePointersPre", pre: usePointersPre},
+ // incdec.go
+ {name: "incDecPre", pre: incDecPre},
+ // The hasPre stage needs to run before convertToSetterPost because it
+ // generates direct fields accesses on the lhs of assignments which
+ // convertToSetterPost rewrites to setters.
+ //
+ // has.go
+ {name: "hasPre", pre: hasPre},
+ // converttosetter.go
+ {name: "convertToSetterPost", post: convertToSetterPost},
+ // oneofswitch.go
+ {name: "oneofSwitchPost", pre: oneofSwitchPost},
+ // appendprotos.go
+ {name: "appendProtosPre", pre: appendProtosPre},
+ // The assignSwapPre stage needs to run before assignPre and getPost
+ // because it untangles swap assignments into two assignments, which
+ // will afterwards be rewritten into getters (getPost) and setters
+ // (assignPre).
+ //
+ // assignswap.go
+ {name: "assignSwapPre", pre: assignSwapPre},
+ // get.go
+ {name: "getPre", pre: getPre},
+ {name: "getPost", post: getPost},
+ // assign.go
+ {name: "assignPre", pre: assignPre},
+ {name: "assignOpPre", pre: assignOpPre},
+ {name: "assignPost", post: assignPost},
+ // build.go
+ {name: "buildPost", post: buildPost},
+ }
+}
+
+const protoImport = "google.golang.org/protobuf/proto"
+
+func markMissingRewrite(n dst.Node, what string) {
+ marker := fmt.Sprintf("/* DO_NOT_SUBMIT: missing rewrite for %s */", what)
+ decs := n.Decorations()
+ for _, d := range decs.End {
+ if d == marker {
+ return
+ }
+ }
+ decs.End = append([]string{marker}, decs.End...)
+}
+
+// visitorFunc is a convenience type to use a function literal as a dst.Visitor.
+type visitorFunc func(n dst.Node) dst.Visitor
+
+// Visit implements dst.Visitor.
+func (v visitorFunc) Visit(n dst.Node) dst.Visitor {
+ return v(n)
+}
+
+var (
+ dstExprType = reflect.TypeOf((*dst.Expr)(nil)).Elem()
+ dstIdentType = reflect.TypeOf((*dst.Ident)(nil))
+)
+
+func isStatementOrDeclaration(n dst.Node) bool {
+ switch n.(type) {
+ case dst.Stmt:
+ return true
+ case dst.Decl:
+ return true
+ default:
+ return false
+ }
+}
+
+// addCommentAbove locates the specified expression (can be part of a line),
+// walks up to the parent nodes until it encounters a statement or declaration
+// (full line) and adds the specified comment.
+func addCommentAbove(root dst.Node, expr dst.Expr, comment string) {
+ // Pre-allocate to avoid memory allocations up until 100 levels of nesting.
+ stack := make([]dst.Node, 0, 100)
+
+ dstutil.Apply(root,
+ func(cur *dstutil.Cursor) bool {
+ verdict := true // keep recursing
+ if cur.Node() == expr {
+ // Insert comment above the closest dst.Stmt or dst.Decl.
+ for i := len(stack) - 1; i >= 0; i-- {
+ if !isStatementOrDeclaration(stack[i]) {
+ continue
+ }
+ decs := stack[i].Decorations()
+ if slices.Contains(decs.Start, comment) {
+ // This statement contains multiple expressions, but the
+ // comment was already added. Skip so that we do not add
+ // the same comment multiple times.
+ break
+ }
+ decs.Start = append(decs.Start, comment)
+ decs.Before = dst.NewLine
+ break
+ }
+ verdict = false // stop recursing
+ }
+ stack = append(stack, cur.Node()) // push
+ return verdict
+ },
+ func(cur *dstutil.Cursor) bool {
+ stack = stack[:len(stack)-1] // pop
+ return true
+ })
+}
+
+// scalarTypeZeroExpr returns an expression for zero value of the given type
+// which must be a protocol buffer scalar type.
+func scalarTypeZeroExpr(c *cursor, t types.Type) dst.Expr {
+ out := &dst.Ident{}
+ c.setType(out, t)
+
+ if isBytes(t) {
+ out.Name = "nil"
+ return out
+ }
+
+ if isEnum(t) {
+ out.Name = "0"
+ return out
+ }
+
+ bt, ok := t.(*types.Basic)
+ if !ok {
+ panic(fmt.Sprintf("scalarTypeZeroExpr called with %T", t))
+ }
+ basicLit := &dst.BasicLit{}
+ c.setType(basicLit, bt)
+ switch bt.Kind() {
+ case types.UntypedBool, types.Bool:
+ out.Name = "false"
+ return out
+ case types.UntypedInt, types.Int, types.Int32, types.Int64, types.Uint32, types.Uint64:
+ basicLit.Kind = token.INT
+ basicLit.Value = "0"
+ case types.UntypedFloat, types.Float32, types.Float64:
+ basicLit.Kind = token.FLOAT
+ basicLit.Value = "0.0"
+ case types.UntypedString, types.String:
+ basicLit.Kind = token.STRING
+ basicLit.Value = `""`
+ default:
+ panic(fmt.Sprintf("unrecognized kind %d of type %s", bt.Kind(), t))
+ }
+ return basicLit
+}
+
+func basicTypeHelperName(t *types.Basic) string {
+ switch t.Kind() {
+ case types.Bool:
+ return "Bool"
+ case types.Int:
+ return "Int"
+ case types.Int32:
+ return "Int32"
+ case types.Int64:
+ return "Int64"
+ case types.Uint32:
+ return "Uint32"
+ case types.Uint64:
+ return "Uint64"
+ case types.Float32:
+ return "Float32"
+ case types.Float64:
+ return "Float64"
+ case types.String:
+ return "String"
+ case types.UntypedBool:
+ return "Bool"
+ case types.UntypedInt:
+ return "Int"
+ case types.UntypedFloat:
+ return "Float"
+ case types.UntypedString:
+ return "String"
+ default:
+ panic(fmt.Sprintf("unrecognized kind %d of type %s", t.Kind(), t))
+ }
+}
+
+func addr(c *cursor, expr dst.Expr) dst.Expr {
+ if e, ok := expr.(*dst.StarExpr); ok {
+ return e.X
+ }
+ out := &dst.UnaryExpr{
+ Op: token.AND,
+ X: expr,
+ }
+ updateASTMap(c, expr, out)
+ c.setType(out, types.NewPointer(c.typeOf(expr)))
+ return out
+}
+
+func isAddr(expr dst.Node) bool {
+ ue, ok := expr.(*dst.UnaryExpr)
+ return ok && ue.Op == token.AND
+}
+
+// expr2stmt wraps an expression as a statement
+func (c *cursor) expr2stmt(expr dst.Expr, src dst.Node) *dst.ExprStmt {
+ stmt := &dst.ExprStmt{X: expr}
+ updateASTMap(c, src, stmt)
+ return stmt
+}
+
+func isPtr(t types.Type) bool {
+ _, ok := t.Underlying().(*types.Pointer)
+ return ok
+}
+
+func isBasic(t types.Type) bool {
+ _, ok := t.(*types.Basic)
+ return ok
+}
+
+func isBytes(t types.Type) bool {
+ s, ok := t.(*types.Slice)
+ if !ok {
+ return false
+ }
+ elem, ok := s.Elem().(*types.Basic)
+ return ok && elem.Kind() == types.Byte
+}
+
+func isEnum(t types.Type) bool {
+ n, ok := t.(*types.Named)
+ if !ok {
+ return false
+ }
+ _, ok = n.Underlying().(*types.Basic)
+ return ok
+}
+
+func isScalar(t types.Type) bool {
+ return isBasic(t) || isEnum(t) || isBytes(t)
+}
+
+func isMsg(t types.Type) bool {
+ p, ok := t.Underlying().(*types.Pointer)
+ if !ok {
+ return false
+ }
+ _, ok = p.Elem().Underlying().(*types.Struct)
+ return ok
+}
+
+func isOneof(t types.Type) bool {
+ _, ok := t.Underlying().(*types.Interface)
+ return ok
+}
+
+func isOneofWrapper(c *cursor, x dst.Expr) bool {
+ t := c.underlyingTypeOf(x)
+ if p, ok := t.(*types.Pointer); ok {
+ t = p.Elem().Underlying()
+ }
+ s, ok := t.(*types.Struct)
+ if !ok || s.NumFields() != 1 {
+ return false
+ }
+ for _, tag := range strings.Split(reflect.StructTag(s.Tag(0)).Get("protobuf"), ",") {
+ if tag == "oneof" {
+ return true
+ }
+ }
+ return false
+}
+
+func isPtrToBasic(t types.Type) bool {
+ p, ok := t.Underlying().(*types.Pointer)
+ if !ok {
+ return false
+ }
+ _, ok = p.Elem().Underlying().(*types.Basic)
+ return ok
+}
+
+// sel2call converts a selector expression to a call expression (a direct field access to a method
+// call). For example, "m.F = v" is replaced with "m.SetF(v)" where "m.F" is the selector
+// expression, "v" is the val, and "Set" is the prefix.
+//
+// If val is nil, it is not added as an argument to the call expression. Prefix can be one of "Get",
+// "Set", "Clear", "Has",
+//
+// This function does the necessary changes to field names to resolve conflicts.
+func sel2call(c *cursor, prefix string, sel *dst.SelectorExpr, val dst.Expr, decs dst.NodeDecs) *dst.CallExpr {
+ name := fixConflictingNames(c.typeOf(sel.X), prefix, sel.Sel.Name)
+ fnsel := &dst.Ident{
+ Name: prefix + name,
+ }
+ fn := &dst.CallExpr{
+ Fun: &dst.SelectorExpr{
+ X: sel.X,
+ Sel: fnsel,
+ },
+ }
+ fn.Decs.NodeDecs = decs
+ if val != nil {
+ val = dstutil.Unparen(val)
+ fn.Args = []dst.Expr{val}
+ }
+
+ t := c.underlyingTypeOf(sel.Sel)
+ if isPtrToBasic(t) {
+ t = t.(*types.Pointer).Elem()
+ }
+ var pkg *types.Package
+ if use := c.objectOf(sel.Sel); use != nil {
+ pkg = use.Pkg()
+ }
+ value := types.NewParam(token.NoPos, pkg, "_", t)
+ recv := types.NewParam(token.NoPos, pkg, "_", c.underlyingTypeOf(sel.X))
+ switch prefix {
+ case "Get":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false))
+ c.setType(fn, t)
+ case "Set":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(value), types.NewTuple(), false))
+ c.setVoidType(fn)
+ case "Clear":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(), false))
+ c.setVoidType(fn)
+ case "Has":
+ c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, pkg, "_", types.Typ[types.Bool])), false))
+ c.setType(fn, types.Typ[types.Bool])
+ default:
+ panic("bad function name prefix '" + prefix + "'")
+ }
+ c.setType(fn.Fun, c.typeOf(fnsel))
+ return fn
+}
+
+// NewSrc creates a test Go package for test examples.
+//
+// Tests can access:
+//
+// a fake version of the proto package, "proto"
+// a fake generated profile package, "pb"
+// a fake proto2 object, "m2"
+// a fake proto3 object, "m3"
+//
+// Note that there's only one instance of "m2" and "m3". We may need more
+// instances when the analysis is smart enough to do different rewrites for
+// operations on two different objects than on a single one. Currently, for
+// example:
+//
+// m2.S = m2.S
+//
+// is not recognized as having the same object on both sides. Hence we consider
+// it as losing aliasing and clear semantics when rewritten as:
+//
+// m2.SetS(m2.GetS())
+//
+// newSrc doesn't introduce new access patterns recognized by the migration tool
+// so that tests can rely on all returned accesses coming from code added in
+// those tests.
+func NewSrc(in, extra string) string {
+ return `// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto"
+import pb3 "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto"
+import proto "google.golang.org/protobuf/proto"
+import "unsafe"
+import "context"
+
+var _ unsafe.Pointer
+var _ = proto.String
+var _ = context.Background
+
+func test_function() {
+ m2 := new(pb2.M2)
+ m2a := new(pb2.M2)
+ _, _ = m2, m2a
+ m3 := new(pb3.M3)
+ _ = m3
+ _ = "TEST CODE STARTS HERE"
+` + in + `
+ _ = "TEST CODE ENDS HERE"
+}
+` + extra
+}
diff --git a/internal/fix/rules_common_test.go b/internal/fix/rules_common_test.go
new file mode 100644
index 0000000..53f88df
--- /dev/null
+++ b/internal/fix/rules_common_test.go
@@ -0,0 +1,354 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "context"
+ "fmt"
+ "sort"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/kylelemons/godebug/diff"
+ spb "google.golang.org/open2opaque/internal/dashboard"
+ "google.golang.org/open2opaque/internal/o2o/fakeloader"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/o2o/syncset"
+)
+
+const dumpSrcOnFail = false
+
+type fakeLoaderBase struct {
+ ImportPathToFiles map[string][]string
+ PathToContent map[string]string
+ ExportFor fakeloader.ExportForFunc
+}
+
+// flBase contains the fakeloader base values, i.e. the parts of the test which
+// do not change between each test (testdata protobuf packages and Go Protobuf
+// itself).
+var flBase = func() *fakeLoaderBase {
+
+ flb, err := goListBase()
+ if err != nil {
+ panic(err)
+ }
+ return flb
+}()
+
+func fixSource(ctx context.Context, src, srcfile string, cPkgSettings ConfiguredPackage, levels []Level) (lvl2src map[Level]string, lvl2stats map[Level][]*spb.Entry, err error) {
+ // Wrap the single source file in a package that can be loaded. Also add fake support
+ // packages (the proto library and compiled .proto file with messages).
+ ruleName := "google.golang.org/open2opaque/internal/fix/testdata/fake"
+ prefix := ruleName + "/"
+
+ srcfile = prefix + srcfile
+
+ // Add/overwrite the source file:
+ flBase.ImportPathToFiles[ruleName] = []string{srcfile}
+ flBase.PathToContent[srcfile] = src
+
+ l := fakeloader.NewFakeLoader(
+ flBase.ImportPathToFiles,
+ flBase.PathToContent,
+ nil,
+ flBase.ExportFor)
+ pkg, err := loader.LoadOne(ctx, l, &loader.Target{ID: ruleName})
+ if err != nil {
+ return nil, nil, fmt.Errorf("can't fix source: %v", err)
+ }
+
+ cPkg := ConfiguredPackage{
+ Loader: l,
+ Pkg: pkg,
+ TypesToUpdate: cPkgSettings.TypesToUpdate,
+ BuilderTypes: cPkgSettings.BuilderTypes,
+ Levels: levels,
+ ProcessedFiles: syncset.New(),
+ UseBuilders: BuildersTestsOnly,
+ }
+ fixed, err := cPkg.Fix()
+ if err != nil {
+ return nil, nil, fmt.Errorf("can't fix source: %v", err)
+ }
+ var lvls []Level
+ for lvl := range fixed {
+ lvls = append(lvls, lvl)
+ }
+ sort.Slice(lvls, func(i, j int) bool {
+ return !lvls[i].ge(lvls[j])
+ })
+ want := append([]Level{None}, levels...)
+ if d := cmp.Diff(want, lvls); d != "" {
+ return nil, nil, fmt.Errorf("Package() = %v;\nwant map with keys %v:\n%s\n", lvls, want, d)
+ }
+ for lvl, fs := range fixed {
+ if len(fs) != 1 {
+ return nil, nil, fmt.Errorf("Package(1 source) = %s/%d; want 1 for each None,Green,Yellow,Red", lvl, len(fs))
+ }
+ }
+
+ // Extract code that the test cares about. It is between
+ // _ = "TEST CODE STARTS HERE"
+ // and
+ // _ = "TEST CODE ENDS HERE"
+ // Everything else wraps that code in a package that can be loaded.
+ // Note that wrapped code can have different indentation levels than what the test
+ // expects. Fix that too.
+ const start = `_ = "TEST CODE STARTS HERE"`
+ lvl2src = map[Level]string{}
+ lvl2stats = map[Level][]*spb.Entry{}
+ for lvl, srcs := range fixed {
+ lvl2stats[lvl] = srcs[0].Stats
+ src := srcs[0].Code
+
+ sidx := strings.Index(src, start)
+ if sidx < 0 {
+ return nil, nil, fmt.Errorf("Fixed source doesn't contain start marker %q. Result:\n%s", start, src)
+ }
+ const end = `_ = "TEST CODE ENDS HERE"`
+ eidx := strings.Index(src, end)
+ if eidx < 0 {
+ return nil, nil, fmt.Errorf("Fixed source doesn't contain end marker %q. Result:\n%s", end, src)
+ }
+
+ raw := strings.Split(src[sidx+len(start):eidx], "\n")
+ first := 0
+ for ; first < len(raw); first++ {
+ if len(strings.TrimSpace(raw[first])) != 0 {
+ break
+ }
+ }
+ last := len(raw) - 1
+ for ; last > 0; last-- {
+ if len(strings.TrimSpace(raw[last])) != 0 {
+ break
+ }
+ }
+ if first > last {
+ return nil, nil, fmt.Errorf("all output lines are empty in %q", raw)
+ }
+ var lines []string
+ for i := first; i <= last; i++ {
+ lines = append(lines, raw[i])
+ }
+
+ mintab := -1
+ for i, ln := range lines {
+ if strings.TrimSpace(ln) == "" {
+ lines[i] = ""
+ continue
+ }
+ var w int
+ for _, ch := range ln {
+ if ch != '\t' {
+ break
+ }
+ w++
+ }
+ if mintab == -1 || w < mintab {
+ mintab = w
+ }
+ }
+ for i, ln := range lines {
+ if ln != "" {
+ ln = ln[mintab:]
+ }
+ lines[i] = ln
+ }
+
+ lvl2src[lvl] = strings.Join(lines, "\n")
+ }
+
+ // Update Location information for all statistics so that line 1 is the
+ // first line introduced by the user.
+ var offset int
+ for _, ln := range strings.Split(src, "\n") {
+ offset++
+ if strings.Contains(ln, "TEST CODE STARTS HERE") {
+ break
+ }
+ }
+ for _, stats := range lvl2stats {
+ for _, entry := range stats {
+ entry.GetLocation().GetStart().Line -= int64(offset)
+ entry.GetLocation().GetEnd().Line -= int64(offset)
+ }
+ }
+
+ return lvl2src, lvl2stats, nil
+}
+
+// A very simple test that verfies that fundamentals work:
+// - fake objects are setup
+// - test packages are setup
+// - packages are setup and loaded
+// - basic rewrites work
+func TestSmokeTest(t *testing.T) {
+ tt := test{
+ extra: `func g() string { return "" }`,
+ in: `m2.S = proto.String(g())`,
+ want: map[Level]string{
+ Green: `m2.SetS(g())`,
+ },
+ }
+ runTableTest(t, tt)
+}
+
+func TestDoesntRewriteNonProtos(t *testing.T) {
+ src := `notAProto.S = proto.String(g())`
+ tt := test{
+ extra: `
+type NotAProto struct {
+ S *string
+ Field struct{}
+}
+var notAProto *NotAProto
+func g() string { return "" }
+`,
+ in: src,
+ want: map[Level]string{
+ Green: src,
+ },
+ }
+ runTableTest(t, tt)
+}
+
+// skip marks tests as skipped.
+func skip(ts []test) []test {
+ for i := range ts {
+ ts[i].skip = "enable when fix.Rewrite is implemented"
+ }
+ return ts
+}
+
+type test struct {
+ skip string // A reason to skip the test. Useful for disabling test-cases that don't work yet.
+ desc string
+ // Code added after the function enclosing the input.
+ extra string
+ // Input is wrapped in a package-level function. The package
+ // defines M2 and M3 as proto2 and proto3 messages
+ // respectively. m2 and m3 are variables of types *M2 and *M3
+ // respectively.
+ in string
+
+ // Name of the source file(s) to test with. Defaults to
+ // []string{"pkg_test.go"} if empty.
+ srcfiles []string
+
+ typesToUpdate map[string]bool
+ builderTypes map[string]bool
+
+ // Each test uses either want or wantRed but not both.
+ want map[Level]string
+ wantRed string // Used in tests that only do Red rewrites.
+
+ // Asserts what expressions should be logged for gethering statistcs purposes. It may seem
+ // unnecessary to assert the result per level. This verifies that we don't lose type information
+ // necessary to calculate statistics.
+ wantStats map[Level][]*spb.Entry
+}
+
+func runTableTest(t *testing.T, tt test) {
+ t.Helper()
+
+ if tt.skip != "" {
+ t.Skip(tt.skip)
+ }
+
+ in := NewSrc(tt.in, tt.extra)
+ srcfiles := tt.srcfiles
+ if len(srcfiles) == 0 {
+ srcfiles = []string{"pkg_test.go"}
+ }
+ for _, srcfile := range srcfiles {
+ cpkg := ConfiguredPackage{
+ TypesToUpdate: tt.typesToUpdate,
+ BuilderTypes: tt.builderTypes,
+ }
+ got, _, err := fixSource(context.Background(), in, srcfile, cpkg, []Level{Green, Yellow, Red})
+ if err != nil {
+ t.Fatalf("fixSource(%q) failed: %v; Full input:\n%s", tt.in, err, in)
+ }
+ failSrc := "<redacted because dumpSrcOnFail==false>"
+ if dumpSrcOnFail {
+ failSrc = in
+ }
+
+ for _, lvl := range []Level{Green, Yellow, Red} {
+ want, ok := tt.want[lvl]
+ if !ok {
+ continue
+ }
+ want = trimNL(want)
+ if d := diff.Diff(want, got[lvl]); d != "" {
+ t.Errorf("fixSource(%q) = (%s) %q\nwant\n'%s'\ndiff:\n%s\nFull input source:\n------------------------------\n%s\n------------------------------\n", tt.in, lvl, got[lvl], want, d, failSrc)
+ }
+ }
+ }
+}
+
+func runTableTests(t *testing.T, tests []test) {
+ t.Helper()
+
+ for _, tt := range tests {
+ t.Run(tt.desc, func(t *testing.T) {
+ t.Helper()
+ runTableTest(t, tt)
+ })
+ }
+}
+
+// Verify that basic level calculation works. If this test fails than other tests are likely to be incorrect.
+func TestLevels(t *testing.T) {
+ tt := test{
+ extra: `
+func b() bool { return false }
+func s() string { return "" }
+func m() *pb2.M2 { return nil }
+`,
+ in: `
+m2.S = proto.String("s")
+m2.B, m2.S = proto.Bool(b()), proto.String(s())
+m2.S = m().S
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetS("s")
+m2.B, m2.S = proto.Bool(b()), proto.String(s())
+if x := m(); x.HasS() {
+ m2.SetS(x.GetS())
+} else {
+ m2.ClearS()
+}
+`,
+ // ignore evaluation order
+ Yellow: `
+m2.SetS("s")
+m2.SetB(b())
+m2.SetS(s())
+if x := m(); x.HasS() {
+ m2.SetS(x.GetS())
+} else {
+ m2.ClearS()
+}
+`,
+ Red: `
+m2.SetS("s")
+m2.SetB(b())
+m2.SetS(s())
+if x := m(); x.HasS() {
+ m2.SetS(x.GetS())
+} else {
+ m2.ClearS()
+}
+`,
+ },
+ }
+
+ runTableTest(t, tt)
+}
diff --git a/internal/fix/rules_commonload_test.go b/internal/fix/rules_commonload_test.go
new file mode 100644
index 0000000..86e4177
--- /dev/null
+++ b/internal/fix/rules_commonload_test.go
@@ -0,0 +1,95 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+type jsonPackage struct {
+ ImportPath string
+ Dir string
+ Name string
+ Export string
+ GoFiles []string
+
+ Error struct {
+ Err string
+ }
+}
+
+func goListBase() (*fakeLoaderBase, error) {
+ goList := exec.Command("go", "list", "-e",
+ "-json=ImportPath,Error,Dir,GoFiles,Export",
+ "-export=true",
+ "-deps=true",
+ // https://cs.opensource.google/go/x/tools/+/master:go/packages/golist.go;l=818-824;drc=977f6f71501a7b7b9b35d6125bf740401be8ce29
+ "-pgo=off",
+ "google.golang.org/open2opaque/internal/fix/testdata/fake")
+ goList.Stderr = os.Stderr
+ stdout, err := goList.Output()
+ if err != nil {
+ return nil, err
+ }
+
+ // Filter the packages for which we store data and load files. This reduces
+ // memory usage and makes the program easier to debug.
+ interestingPackages := map[string]bool{
+ // Imported by testdata/fake/fake.go:
+ "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto": true,
+ "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto": true,
+ "google.golang.org/protobuf/proto": true,
+ "google.golang.org/protobuf/types/gofeaturespb": true,
+ // Imported by .pb.go files:
+ "google.golang.org/protobuf/reflect/protoreflect": true,
+ "google.golang.org/protobuf/runtime/protoimpl": true,
+ }
+
+ importPathToFiles := make(map[string][]string)
+ importPathToExport := make(map[string][]byte)
+ for dec := json.NewDecoder(bytes.NewBuffer(stdout)); dec.More(); {
+ p := new(jsonPackage)
+ if err := dec.Decode(p); err != nil {
+ return nil, fmt.Errorf("JSON decoding failed: %v", err)
+ }
+ if !interestingPackages[p.ImportPath] {
+ continue
+ }
+ goFiles := make([]string, len(p.GoFiles))
+ for idx, fn := range p.GoFiles {
+ goFiles[idx] = filepath.Join(p.Dir, fn)
+ }
+ importPathToFiles[p.ImportPath] = goFiles
+ b, err := os.ReadFile(p.Export)
+ if err != nil {
+ return nil, err
+ }
+ importPathToExport[p.ImportPath] = b
+ }
+
+ pathToContent := make(map[string]string)
+ for _, goFiles := range importPathToFiles {
+ for _, fn := range goFiles {
+ b, err := os.ReadFile(fn)
+ if err != nil {
+ return nil, err
+ }
+ pathToContent[fn] = string(b)
+ }
+ }
+
+ return &fakeLoaderBase{
+ ImportPathToFiles: importPathToFiles,
+ PathToContent: pathToContent,
+ ExportFor: func(importPath string) []byte {
+ return importPathToExport[importPath]
+ },
+ }, nil
+}
diff --git a/internal/fix/rules_test.go b/internal/fix/rules_test.go
new file mode 100644
index 0000000..dc32b52
--- /dev/null
+++ b/internal/fix/rules_test.go
@@ -0,0 +1,1599 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "context"
+ "strings"
+ "testing"
+
+ "github.com/kylelemons/godebug/diff"
+)
+
+func TestCommon(t *testing.T) {
+ tests := []test{{
+ desc: "ignore messages that opted-out",
+ in: `
+m := &pb2.DoNotMigrateMe{B: proto.Bool(true)}
+_ = *m.B
+_ = m.B != nil
+m.B = nil
+`,
+ want: map[Level]string{
+ Green: `
+m := &pb2.DoNotMigrateMe{B: proto.Bool(true)}
+_ = *m.B
+_ = m.B != nil
+m.B = nil
+`,
+ },
+ }, {
+ desc: "proto2: set new empty message",
+ in: "m2.M = &pb2.M2{}",
+ want: map[Level]string{
+ Green: "m2.SetM(&pb2.M2{})",
+ },
+ }, {
+ desc: "ignore custom types",
+ extra: `
+type T1 pb2.M2
+type T2 *pb2.M2
+`,
+ in: `
+m := &T1{M: nil}
+_ = *m.I64
+_ = m.I64
+m.I64 = nil
+_ = m.I64 == nil
+
+var n T2
+_ = *n.I64
+_ = n.I64
+n.I64 = nil
+_ = n.I64 == nil
+`,
+ want: map[Level]string{
+ Red: `
+m := &T1{M: nil}
+_ = *m.I64
+_ = m.I64
+m.I64 = nil
+_ = m.I64 == nil
+
+var n T2
+_ = *n.I64
+_ = n.I64
+n.I64 = nil
+_ = n.I64 == nil
+`,
+ },
+ }, {
+ desc: "proto2: set new non-empty message",
+ in: `m2.M = &pb2.M2{S: proto.String("hello")}`,
+ want: map[Level]string{
+ Green: `m2.SetM(pb2.M2_builder{S: proto.String("hello")}.Build())`,
+ },
+ }, {
+ desc: "proto2: set existing message",
+ in: "m2.M = m2",
+ want: map[Level]string{
+ Green: "m2.SetM(m2)",
+ },
+ }, {
+ desc: "proto2: set message field",
+ in: "m2.M = m2.M",
+ want: map[Level]string{
+ Green: "m2.SetM(m2.GetM())",
+ },
+ }, {
+ desc: "proto2: set message function result",
+ extra: "func g2() *pb2.M2 { return nil }",
+ in: "m2.M = g2()",
+ want: map[Level]string{
+ Green: "m2.SetM(g2())",
+ },
+ }, {
+ desc: "proto2: set scalar slice",
+ in: "m2.Is = []int32{1,2,3}",
+ want: map[Level]string{
+ Green: "m2.SetIs([]int32{1, 2, 3})",
+ },
+ }, {
+ desc: "proto2: set scalar field, nohelper",
+ extra: `var s = new(string)`,
+ in: `m2.S = s // eol comment`,
+ want: map[Level]string{
+ Yellow: `m2.S = s // eol comment`,
+ Red: `// eol comment
+if s != nil {
+ m2.SetS(*s)
+} else {
+ m2.ClearS()
+}`,
+ },
+ }, {
+ desc: "proto2: set bytes field",
+ in: `
+var b []byte
+m2.Bytes = b
+if m2.Bytes = b; false {
+}
+`,
+ want: map[Level]string{
+ Green: `
+var b []byte
+if b != nil {
+ m2.SetBytes(b)
+} else {
+ m2.ClearBytes()
+}
+if m2.SetBytes(b); false {
+}
+`,
+ },
+ }, {
+ desc: "proto2: set bytes field with func call",
+ in: `
+m2.Bytes = returnBytes()
+`,
+ extra: `
+func returnBytes() []byte { return nil }
+`,
+ want: map[Level]string{
+ Green: `
+if x := returnBytes(); x != nil {
+ m2.SetBytes(x)
+} else {
+ m2.ClearBytes()
+}
+`,
+ },
+ }, {
+ desc: "proto3: set bytes field",
+ in: `
+var b []byte
+m3.Bytes = b
+if m3.Bytes = b; false {
+}
+`,
+ want: map[Level]string{
+ Green: `
+var b []byte
+m3.SetBytes(b)
+if m3.SetBytes(b); false {
+}
+`,
+ },
+ }, {
+ desc: "proto2: set scalar field, nohelper, addr",
+ extra: `var s = ""`,
+ in: `m2.S = &s`,
+ want: map[Level]string{
+ Green: `m2.SetS(s)`,
+ },
+ }, {
+ desc: "proto2: set proto.Bool",
+ in: `m2.B = proto.Bool(true)`,
+ want: map[Level]string{
+ Green: `m2.SetB(true)`,
+ },
+ }, {
+ desc: "proto2: set proto.Float32",
+ in: `m2.F32 = proto.Float32(42)`,
+ want: map[Level]string{
+ Green: `m2.SetF32(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.Float64",
+ in: `m2.F64 = proto.Float64(42)`,
+ want: map[Level]string{
+ Green: `m2.SetF64(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.Int",
+ in: `m2.I32 = proto.Int32(42)`,
+ want: map[Level]string{
+ Green: `m2.SetI32(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.Int val",
+ extra: `var v int`,
+ in: `m2.I32 = proto.Int32(int32(v))`,
+ want: map[Level]string{
+ Green: `m2.SetI32(int32(v))`,
+ },
+ }, {
+ desc: "proto2: set proto.Int new",
+ in: `m2.I32 = new(int32)`,
+ want: map[Level]string{
+ Green: `m2.SetI32(0)`,
+ },
+ }, {
+ desc: "proto2: set enum",
+ in: `m2.E = pb2.M2_E_VAL.Enum()`,
+ want: map[Level]string{
+ Green: `m2.SetE(pb2.M2_E_VAL)`,
+ },
+ }, {
+ desc: "proto2: set enum new",
+ in: `m2.E = new(pb2.M2_Enum)`,
+ want: map[Level]string{
+ Green: `m2.SetE(pb2.M2_Enum(0))`,
+ },
+ }, {
+ desc: "proto2: set proto.Int32",
+ in: `m2.I32 = proto.Int32(42)`,
+ want: map[Level]string{
+ Green: `m2.SetI32(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.Int64",
+ in: `m2.I64 = proto.Int64(42)`,
+ want: map[Level]string{
+ Green: `m2.SetI64(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.String",
+ in: `m2.S = proto.String("q")`,
+ want: map[Level]string{
+ Green: `m2.SetS("q")`,
+ },
+ }, {
+ desc: "proto2: set proto.Uint32",
+ in: `m2.Ui32 = proto.Uint32(42)`,
+ want: map[Level]string{
+ Green: `m2.SetUi32(42)`,
+ },
+ }, {
+ desc: "proto2: set proto.Uint64",
+ in: `m2.Ui64 = proto.Uint64(42)`,
+ want: map[Level]string{
+ Green: `m2.SetUi64(42)`,
+ },
+ }, {
+ desc: "proto2: scalar field copy",
+ in: `m2.S = m2a.S // eol comment`,
+ want: map[Level]string{
+ Green: `
+// eol comment
+if m2a.HasS() {
+ m2.SetS(m2a.GetS())
+} else {
+ m2.ClearS()
+}
+`,
+ },
+ }, {
+ desc: "proto2: scalar field copy, lhs/rhs identical",
+ in: `m2.S = m2.S // eol comment`,
+ want: map[Level]string{
+ Green: `
+// eol comment
+if m2.HasS() {
+ m2.SetS(m2.GetS())
+} else {
+ m2.ClearS()
+}
+`,
+ },
+ }, {
+ desc: "proto3: set new empty message",
+ in: `m3.M = &pb3.M3{}`,
+ want: map[Level]string{
+ Green: `m3.SetM(&pb3.M3{})`,
+ },
+ }, {
+ desc: "proto3: set new non-empty message",
+ in: `m3.M = &pb3.M3{S:"s"}`,
+ want: map[Level]string{
+ Green: `m3.SetM(pb3.M3_builder{S: "s"}.Build())`,
+ },
+ }, {
+ desc: "proto3: set existing message",
+ in: "m3.M = m3",
+ want: map[Level]string{
+ Green: "m3.SetM(m3)",
+ },
+ }, {
+ desc: "proto3: set message field",
+ in: "m3.M = m3.M",
+ want: map[Level]string{
+ Green: "m3.SetM(m3.GetM())",
+ },
+ }, {
+ desc: "proto3: set message field new",
+ in: "m3.M = new(pb3.M3)",
+ want: map[Level]string{
+ Green: "m3.SetM(new(pb3.M3))",
+ },
+ }, {
+ desc: "proto3: set message function result",
+ extra: "func g3() *pb3.M3 { return nil }",
+ in: "m3.M = g3()",
+ want: map[Level]string{
+ Green: "m3.SetM(g3())",
+ },
+ }, {
+ desc: "proto3: set scalar slice",
+ in: "m3.Is = []int32{1,2,3}",
+ want: map[Level]string{
+ Green: "m3.SetIs([]int32{1, 2, 3})",
+ },
+ }, {
+ desc: "deref set",
+ extra: `
+var s string
+var i32 int32
+type S struct {
+ Proto *pb2.M2
+}
+`,
+ in: `
+*m2.S = "hello"
+*m2.I32 = 1
+*m2.S = s
+*m2.I32 = i32
+
+*m2.M.S = "hello"
+*m2.M.I32 = 1
+*m2.M.S = s
+*m2.M.I32 = i32
+
+ss := &S{}
+*ss.Proto.M.S = "hello"
+*ss.Proto.M.I32 = 1
+*ss.Proto.M.S = s
+*ss.Proto.M.I32 = i32
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetS("hello")
+m2.SetI32(1)
+m2.SetS(s)
+m2.SetI32(i32)
+
+m2.GetM().SetS("hello")
+m2.GetM().SetI32(1)
+m2.GetM().SetS(s)
+m2.GetM().SetI32(i32)
+
+ss := &S{}
+ss.Proto.GetM().SetS("hello")
+ss.Proto.GetM().SetI32(1)
+ss.Proto.GetM().SetS(s)
+ss.Proto.GetM().SetI32(i32)
+`,
+ Red: `
+m2.SetS("hello")
+m2.SetI32(1)
+m2.SetS(s)
+m2.SetI32(i32)
+
+m2.GetM().SetS("hello")
+m2.GetM().SetI32(1)
+m2.GetM().SetS(s)
+m2.GetM().SetI32(i32)
+
+ss := &S{}
+ss.Proto.GetM().SetS("hello")
+ss.Proto.GetM().SetI32(1)
+ss.Proto.GetM().SetS(s)
+ss.Proto.GetM().SetI32(i32)
+`,
+ },
+ }, {
+ desc: "proto3: scalar field copy",
+ in: `m3.S = m3.S`,
+ want: map[Level]string{
+ Green: `m3.SetS(m3.GetS())`,
+ },
+ }, {
+ desc: "proto2: clear message",
+ in: "m2.M = nil",
+ want: map[Level]string{
+ Green: "m2.ClearM()",
+ },
+ }, {
+ desc: "proto2: clear scalar",
+ in: "m2.S = nil",
+ want: map[Level]string{
+ Green: "m2.ClearS()",
+ },
+ }, {
+ desc: "proto2: clear enum",
+ in: "m2.E = nil",
+ want: map[Level]string{
+ Green: "m2.ClearE()",
+ },
+ }, {
+ desc: "proto2: clear bytes",
+ in: "m2.Bytes = nil",
+ want: map[Level]string{
+ Green: "m2.ClearBytes()",
+ },
+ }, {
+ desc: "proto2: clear scalar slice",
+ in: "m2.Is = nil",
+ want: map[Level]string{
+ Green: "m2.SetIs(nil)",
+ },
+ }, {
+ desc: "proto2: clear message slice",
+ in: "m2.Ms = nil",
+ want: map[Level]string{
+ Green: "m2.SetMs(nil)",
+ },
+ }, {
+ desc: "proto2: clear map",
+ in: "m2.Map = nil",
+ want: map[Level]string{
+ Green: "m2.SetMap(nil)",
+ },
+ }, {
+ desc: "proto3: clear message",
+ in: "m3.M = nil",
+ want: map[Level]string{
+ Green: "m3.ClearM()",
+ },
+ }, {
+ desc: "proto3: clear bytes",
+ in: "m3.Bytes = nil",
+ want: map[Level]string{
+ Green: "m3.SetBytes(nil)",
+ },
+ }, {
+ desc: "proto3 value: clear bytes",
+ extra: "var m3val pb3.M3",
+ in: "m3val.Bytes = nil",
+ want: map[Level]string{
+ Green: "m3val.SetBytes(nil)",
+ },
+ }, {
+ desc: "proto3: clear scalar slice",
+ in: "m3.Is = nil",
+ want: map[Level]string{
+ Green: "m3.SetIs(nil)",
+ },
+ }, {
+ desc: "proto3: clear message slice",
+ in: "m3.Ms = nil",
+ want: map[Level]string{
+ Green: "m3.SetMs(nil)",
+ },
+ }, {
+ desc: "proto3: clear map",
+ in: "m3.Map = nil",
+ want: map[Level]string{
+ Green: "m3.SetMap(nil)",
+ },
+ }, {
+ desc: "proto2: get message ptr",
+ in: "_ = m2.M",
+ want: map[Level]string{
+ Green: "_ = m2.GetM()",
+ },
+ }, {
+ desc: "proto2: get message value",
+ in: "_ = *m2.M",
+ want: map[Level]string{
+ Green: "_ = *m2.GetM()",
+ },
+ }, {
+ desc: "proto2: get scalar ptr",
+ in: `_ = m2.S`,
+ want: map[Level]string{
+ Green: `_ = m2.S`,
+ Yellow: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`,
+ Red: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`,
+ },
+ }, {
+ desc: "proto2: get scalar ptr from side effect free expr",
+ in: `_ = m2.Ms[0].I32`,
+ want: map[Level]string{
+ Green: `_ = m2.GetMs()[0].I32`,
+ Yellow: `_ = proto.ValueOrNil(m2.GetMs()[0].HasI32(), m2.GetMs()[0].GetI32)`,
+ Red: `_ = proto.ValueOrNil(m2.GetMs()[0].HasI32(), m2.GetMs()[0].GetI32)`,
+ },
+ }, {
+ desc: "proto2: get scalar ptr from side effect expr (index)",
+ extra: `func f() int { return 0 }`,
+ in: `_ = m2.Ms[f()].I32`,
+ want: map[Level]string{
+ Green: `_ = m2.GetMs()[f()].I32`,
+ Yellow: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(m2.GetMs()[f()])`,
+ Red: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(m2.GetMs()[f()])`,
+ },
+ }, {
+ desc: "proto2: get scalar ptr from side effect expr (receiver)",
+ extra: `func f() []*pb2.M2 { return nil }`,
+ in: `_ = f()[0].I32`,
+ want: map[Level]string{
+ Green: `_ = f()[0].I32`,
+ Yellow: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f()[0])`,
+ Red: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f()[0])`,
+ },
+ }, {
+ desc: "proto2: get enum ptr",
+ in: `_ = m2.E`,
+ want: map[Level]string{
+ Green: `_ = m2.E`,
+ Yellow: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`,
+ Red: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`,
+ },
+ }, {
+ desc: "proto2: get scalar value",
+ in: "_ = *m2.S",
+ want: map[Level]string{
+ Green: "_ = m2.GetS()",
+ },
+ }, {
+ desc: "proto2: scalar slice",
+ in: "_ = m2.Is",
+ want: map[Level]string{
+ Green: "_ = m2.GetIs()",
+ },
+ }, {
+ desc: "proto2: scalar slice and index",
+ in: "_ = m2.Is[0]",
+ want: map[Level]string{
+ Green: "_ = m2.GetIs()[0]",
+ },
+ }, {
+ desc: "proto2: message slice",
+ in: "_ = m2.Ms",
+ want: map[Level]string{
+ Green: "_ = m2.GetMs()",
+ },
+ }, {
+ desc: "proto2: message slice and index",
+ in: "_ = m2.Ms[0]",
+ want: map[Level]string{
+ Green: "_ = m2.GetMs()[0]",
+ },
+ }, {
+ desc: "proto2: get in function args",
+ extra: "func g2(*string, string, *pb2.M2, []int32, []*pb2.M2) { }",
+ in: "g2(m2.S, *m2.S, m2.M, m2.Is, m2.Ms)",
+ want: map[Level]string{
+ Green: "g2(m2.S, m2.GetS(), m2.GetM(), m2.GetIs(), m2.GetMs())",
+ },
+ }, {
+ desc: "proto2 assignments: nested",
+ in: `
+_ = func() int {
+ m2.I32 = proto.Int32(23)
+ return 0
+}()
+`,
+ want: map[Level]string{
+ Red: `
+_ = func() int {
+ m2.SetI32(23)
+ return 0
+}()
+`,
+ },
+ }, {
+ desc: "proto2 assignments: no side-effects",
+ extra: `
+var cnt int
+func f2() *pb2.M2 {
+ cnt++
+ return nil
+}
+`,
+ in: `
+newM := f2()
+m2.B = newM.B
+m2.GetM().B = newM.GetM().B
+m2.M.B = newM.M.B
+
+type E struct {
+ Proto *pb2.M2
+}
+e := &E{}
+e.Proto.B = newM.B
+e.Proto.GetM().B = newM.GetM().B
+e.Proto.M.B = newM.M.B
+m2.B = e.Proto.B
+m2.GetM().B = e.Proto.GetM().B
+m2.M.B = e.Proto.M.B
+`,
+
+ want: map[Level]string{
+ Red: `
+newM := f2()
+if newM.HasB() {
+ m2.SetB(newM.GetB())
+} else {
+ m2.ClearB()
+}
+if newM.GetM().HasB() {
+ m2.GetM().SetB(newM.GetM().GetB())
+} else {
+ m2.GetM().ClearB()
+}
+if newM.GetM().HasB() {
+ m2.GetM().SetB(newM.GetM().GetB())
+} else {
+ m2.GetM().ClearB()
+}
+
+type E struct {
+ Proto *pb2.M2
+}
+e := &E{}
+if newM.HasB() {
+ e.Proto.SetB(newM.GetB())
+} else {
+ e.Proto.ClearB()
+}
+if newM.GetM().HasB() {
+ e.Proto.GetM().SetB(newM.GetM().GetB())
+} else {
+ e.Proto.GetM().ClearB()
+}
+if newM.GetM().HasB() {
+ e.Proto.GetM().SetB(newM.GetM().GetB())
+} else {
+ e.Proto.GetM().ClearB()
+}
+if e.Proto.HasB() {
+ m2.SetB(e.Proto.GetB())
+} else {
+ m2.ClearB()
+}
+if e.Proto.GetM().HasB() {
+ m2.GetM().SetB(e.Proto.GetM().GetB())
+} else {
+ m2.GetM().ClearB()
+}
+if e.Proto.GetM().HasB() {
+ m2.GetM().SetB(e.Proto.GetM().GetB())
+} else {
+ m2.GetM().ClearB()
+}
+`,
+ }}, {
+ desc: "proto2 assignments: side-effects rhs",
+ extra: `
+var cnt int
+func f2() *pb2.M2 {
+ cnt++
+ return nil
+}
+`,
+ in: `
+m2.B = f2().B // eol comment
+`,
+ want: map[Level]string{
+ Green: `
+// eol comment
+if x := f2(); x.HasB() {
+ m2.SetB(x.GetB())
+} else {
+ m2.ClearB()
+}
+`}}, {
+ desc: "proto2 assignments: side-effects lhs",
+ extra: `
+var cnt int
+func f2() *pb2.M2 {
+ cnt++
+ return nil
+}
+`,
+ in: `
+f2().B = m2.B
+`,
+ want: map[Level]string{
+ Green: `
+if m2.HasB() {
+ f2().SetB(m2.GetB())
+} else {
+ f2().ClearB()
+}
+`}}, {
+ desc: "proto2 assignments: side-effects lhs and rhs",
+ extra: `
+var cnt int
+func f2() *pb2.M2 {
+ cnt++
+ return nil
+}
+`,
+ in: `
+f2().B = f2().B
+`,
+ want: map[Level]string{
+ Green: `
+if x := f2(); x.HasB() {
+ f2().SetB(x.GetB())
+} else {
+ f2().ClearB()
+}
+`}}, {
+ desc: "assign []byte",
+ extra: "var v string",
+ in: `
+m2.Bytes = []byte("hello")
+m2.Bytes = []byte(v)
+`,
+ want: map[Level]string{
+ Green: `
+m2.SetBytes([]byte("hello"))
+m2.SetBytes([]byte(v))
+`,
+ },
+ }, {
+ desc: "increment non-proto",
+ in: `
+for i := 0; i < 10; i++ {
+}
+`,
+ want: map[Level]string{
+ Green: `
+for i := 0; i < 10; i++ {
+}
+`, Red: `
+for i := 0; i < 10; i++ {
+}
+`,
+ },
+ }, {
+ desc: "proto2: don't call methods on non-addressable receiver",
+ extra: `
+func f2() pb2.M2{ return pb2.M2{} }
+`,
+ in: `
+_ = f2().B
+_ = f2().Bytes
+_ = f2().F32
+_ = f2().F64
+_ = f2().I32
+_ = f2().I64
+_ = f2().Ui32
+_ = f2().Ui64
+_ = f2().S
+_ = f2().M
+_ = f2().Is
+_ = f2().Ms
+_ = f2().Map
+_ = f2().E
+
+_ = f2().B != nil
+_ = f2().Bytes != nil
+_ = f2().F32 != nil
+_ = f2().F64 != nil
+_ = f2().I32 != nil
+_ = f2().I64 != nil
+_ = f2().Ui32 != nil
+_ = f2().Ui64 != nil
+_ = f2().S != nil
+_ = f2().M != nil
+_ = f2().Is != nil
+_ = f2().Ms != nil
+_ = f2().Map != nil
+_ = f2().E != nil
+
+if f2().B != nil {
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = f2().B
+_ = f2().Bytes
+_ = f2().F32
+_ = f2().F64
+_ = f2().I32
+_ = f2().I64
+_ = f2().Ui32
+_ = f2().Ui64
+_ = f2().S
+_ = f2().M
+_ = f2().Is
+_ = f2().Ms
+_ = f2().Map
+_ = f2().E
+
+_ = f2().B != nil
+_ = f2().Bytes != nil
+_ = f2().F32 != nil
+_ = f2().F64 != nil
+_ = f2().I32 != nil
+_ = f2().I64 != nil
+_ = f2().Ui32 != nil
+_ = f2().Ui64 != nil
+_ = f2().S != nil
+_ = f2().M != nil
+_ = f2().Is != nil
+_ = f2().Ms != nil
+_ = f2().Map != nil
+_ = f2().E != nil
+
+if f2().B != nil {
+}
+`,
+ Red: `
+_ = func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2())
+_ = f2().GetBytes()
+_ = func(msg *pb2.M2) *float32 { return proto.ValueOrNil(msg.HasF32(), msg.GetF32) }(f2())
+_ = func(msg *pb2.M2) *float64 { return proto.ValueOrNil(msg.HasF64(), msg.GetF64) }(f2())
+_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f2())
+_ = func(msg *pb2.M2) *int64 { return proto.ValueOrNil(msg.HasI64(), msg.GetI64) }(f2())
+_ = func(msg *pb2.M2) *uint32 { return proto.ValueOrNil(msg.HasUi32(), msg.GetUi32) }(f2())
+_ = func(msg *pb2.M2) *uint64 { return proto.ValueOrNil(msg.HasUi64(), msg.GetUi64) }(f2())
+_ = func(msg *pb2.M2) *string { return proto.ValueOrNil(msg.HasS(), msg.GetS) }(f2())
+_ = f2().GetM()
+_ = f2().GetIs()
+_ = f2().GetMs()
+_ = f2().GetMap()
+_ = func(msg *pb2.M2) *pb2.M2_Enum { return proto.ValueOrNil(msg.HasE(), msg.GetE) }(f2())
+
+_ = func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2()) != nil
+_ = f2().HasBytes()
+_ = func(msg *pb2.M2) *float32 { return proto.ValueOrNil(msg.HasF32(), msg.GetF32) }(f2()) != nil
+_ = func(msg *pb2.M2) *float64 { return proto.ValueOrNil(msg.HasF64(), msg.GetF64) }(f2()) != nil
+_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f2()) != nil
+_ = func(msg *pb2.M2) *int64 { return proto.ValueOrNil(msg.HasI64(), msg.GetI64) }(f2()) != nil
+_ = func(msg *pb2.M2) *uint32 { return proto.ValueOrNil(msg.HasUi32(), msg.GetUi32) }(f2()) != nil
+_ = func(msg *pb2.M2) *uint64 { return proto.ValueOrNil(msg.HasUi64(), msg.GetUi64) }(f2()) != nil
+_ = func(msg *pb2.M2) *string { return proto.ValueOrNil(msg.HasS(), msg.GetS) }(f2()) != nil
+_ = f2().HasM()
+_ = f2().GetIs() != nil
+_ = f2().GetMs() != nil
+_ = f2().GetMap() != nil
+_ = func(msg *pb2.M2) *pb2.M2_Enum { return proto.ValueOrNil(msg.HasE(), msg.GetE) }(f2()) != nil
+
+if func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2()) != nil {
+}
+`,
+ },
+ }, {
+ desc: "proto3: don't call methods on non-addressable receiver",
+ extra: `
+func f3() pb3.M3{ return pb3.M3{} }
+`,
+ in: `
+_ = f3().B
+_ = f3().Bytes
+_ = f3().F32
+_ = f3().F64
+_ = f3().I32
+_ = f3().I64
+_ = f3().Ui32
+_ = f3().Ui64
+_ = f3().S
+_ = f3().M
+_ = f3().Is
+_ = f3().Ms
+_ = f3().Map
+
+_ = f3().Bytes != nil
+_ = f3().M != nil
+_ = f3().Is != nil
+_ = f3().Ms != nil
+_ = f3().Map != nil
+`,
+ want: map[Level]string{
+ Green: `
+_ = f3().B
+_ = f3().Bytes
+_ = f3().F32
+_ = f3().F64
+_ = f3().I32
+_ = f3().I64
+_ = f3().Ui32
+_ = f3().Ui64
+_ = f3().S
+_ = f3().M
+_ = f3().Is
+_ = f3().Ms
+_ = f3().Map
+
+_ = len(f3().Bytes) != 0
+_ = f3().M != nil
+_ = f3().Is != nil
+_ = f3().Ms != nil
+_ = f3().Map != nil
+`,
+ Red: `
+_ = f3().GetB()
+_ = f3().GetBytes()
+_ = f3().GetF32()
+_ = f3().GetF64()
+_ = f3().GetI32()
+_ = f3().GetI64()
+_ = f3().GetUi32()
+_ = f3().GetUi64()
+_ = f3().GetS()
+_ = f3().GetM()
+_ = f3().GetIs()
+_ = f3().GetMs()
+_ = f3().GetMap()
+
+_ = len(f3().GetBytes()) != 0
+_ = f3().HasM()
+_ = f3().GetIs() != nil
+_ = f3().GetMs() != nil
+_ = f3().GetMap() != nil
+`,
+ },
+ }}
+
+ runTableTests(t, tests)
+}
+
+func TestShallowCopies(t *testing.T) {
+ // Shallow copy rewrites lose scalar field aliasing. All are red rewrites.
+
+ // https://golang.org/ref/spec#Address_operators
+ // "For an operand x of type T, the address operation &x generates a
+ // pointer of type *T to x. The operand must be addressable, that is,
+ // either a variable, pointer indirection, or slice indexing operation;
+ // or a field selector of an addressable struct operand; or an array
+ // indexing operation of an addressable array."
+ tests := skip([]test{{
+ desc: `definition, rhs is addressable`,
+ in: `
+m := *m2
+_ = &m
+`,
+ wantRed: `
+var m pb2.M2
+proto.Assign(&m, m2)
+_ = &m
+`,
+ }, {
+ desc: `definition, rhs is not addressable`,
+ extra: `func g() pb2.M2 { return pb2.M2{} }`,
+ in: `
+m := g()
+_ = &m
+`,
+ wantRed: `
+m := g()
+_ = &m
+`,
+ }, { // Both lhs (left-hand side) and rhs (right-hand side) are addressable.
+ desc: `proto2: lhs is addressable, rhs is an empty composite literal`,
+ extra: `var m pb2.M2`,
+ in: `m = pb2.M2{}`,
+ wantRed: `proto.Assign(&m, &pb2.M2{})`,
+ }, {
+ desc: `proto3: lhs is addressable, rhs is an empty composite literal`,
+ extra: `var m pb3.M3`,
+ in: `m = pb3.M3{}`,
+ wantRed: `proto.Assign(&m, &pb3.M3{})`,
+ }, {
+ desc: `lhs is addressable, rhs is non-empty composite literal`,
+ extra: `var m pb2.M2`,
+ in: `m = pb2.M2{M: nil}`,
+ wantRed: `proto.Assign(&m, pb2.M2_builder{M: nil}.Build())`,
+ }, {
+ desc: `lhs and rhs are addressable: pointer indirections`,
+ in: `*m2 = *m2`,
+ wantRed: `proto.Assign(m2, m2)`,
+ }, {
+ desc: `lhs and rhs are addressable: pointer indirection 2`,
+ extra: `var m pb2.M2; func mp() *pb2.M2 { return nil }`,
+ in: `m = *mp()`,
+ wantRed: `proto.Assign(&m, mp())`,
+ }, {
+ desc: `lhs and rhs are addressable: variables`,
+ extra: `var m pb2.M2`,
+ in: `m = m`,
+ wantRed: `proto.Assign(&m, &m)`,
+ }, {
+ desc: `lhs and rhs are addressable: addressable expr in parens`,
+ extra: `var m pb2.M2`,
+ in: `(m) = (m)`,
+ want: map[Level]string{
+ Yellow: `m = m`,
+ Red: `proto.Assign(&m, &m)`,
+ },
+ }, {
+ desc: `lhs and rhs are addressable: addressable slice index`,
+ extra: `var ms = []pb2.M2{}`,
+ in: `ms[0] = ms[0]`,
+ wantRed: `proto.Assign(&ms[0], &ms[0])`,
+ }, {
+ desc: `lhs and rhs are addressable: non-addressable slice index`,
+ extra: `func s() []pb2.M2 { return nil }`,
+ in: `s()[0] = s()[0]`,
+ wantRed: `proto.Assign(&s()[0], &s()[0])`,
+ }, {
+ desc: `lhs and rhs are addressable: addressable array index`,
+ extra: `var ms [1]pb2.M2`,
+ in: `ms[0] = ms[0]`,
+ wantRed: `proto.Assign(&ms[0], &ms[0])`,
+ }, {
+ desc: `lhs and rhs are addressable: field selector`,
+ extra: `var t struct{m pb2.M2}`,
+ in: `t.m = t.m`,
+ wantRed: `proto.Assign(&t.m, &t.m)`,
+ }, { // Only rhs is addressable.
+ desc: `proto2: lhs is not addressable, rhs is an empty struct literal`,
+ extra: `var m = map[int]pb2.M2{}`,
+ in: `m[0] = pb2.M2{}`,
+ wantRed: `m[0] = pb2.M2{}`,
+ }, {
+ desc: `proto3: lhs is not addressable, rhs is an empty struct literal`,
+ extra: `var m = map[int]pb3.M3{}`,
+ in: `m[0] = pb3.M3{}`,
+ wantRed: `m[0] = pb3.M3{}`,
+ }, {
+ desc: `lhs is not addressable, rhs is a non-empty composite literal`,
+ extra: `var m = map[int]pb2.M2{}`,
+ in: `m[0] = pb2.M2{M: nil}`,
+ wantRed: `m[0] = *pb2.M2_builder{M: nil}.Build()`,
+ }, {
+ desc: `lhs is not addressable, rhs is addressable: pointer indirection`,
+ extra: `var m = map[int]pb2.M2{}`,
+ in: `m[0] = *m2`,
+ wantRed: `m[0] = *proto.Clone(m2).(*pb2.M2)`,
+ }, {
+ desc: `lhs is not addressable, rhs is addressable: addressable slice index`,
+ extra: `var m = map[int]pb2.M2{}; var ms []pb2.M2`,
+ in: `m[0] = ms[0]`,
+ wantRed: `m[0] = *proto.Clone(&ms[0]).(*pb2.M2)`,
+ }, {
+ desc: `lhs is not addressable, rhs is addressable: not-addressable slice index`,
+ extra: `var m = map[int]pb2.M2{}; func s() []pb2.M2{return nil}`,
+ in: `m[0] = s()[0]`,
+ wantRed: `m[0] = *proto.Clone(&s()[0]).(*pb2.M2)`,
+ }, {
+ desc: `lhs is not addressable, rhs is addressable: addressable array index`,
+ extra: `var m = map[int]pb2.M2{}; var ms [1]pb2.M2`,
+ in: `m[0] = ms[0]`,
+ wantRed: `m[0] = *proto.Clone(&ms[0]).(*pb2.M2)`,
+ }, {
+ desc: `lhs is not addressable, rhs is addressable: field selector`,
+ extra: `var m = map[int]pb2.M2{}; var t struct {m pb2.M2} `,
+ in: `m[0] = t.m`,
+ wantRed: `m[0] = *proto.Clone(&t.m).(*pb2.M2)`,
+ }, {
+ desc: `lhs is an underscore, rhs is addressable`,
+ in: `_ = *m2`,
+ wantRed: `_ = *proto.Clone(m2).(*pb2.M2)`,
+ }, { // Rhs is not addressable => no rewrite.
+ desc: `lhs is addressable, rhs is not addressable: map access`,
+ extra: `var m = map[int]pb2.M2{}`,
+ in: `*m2 = m[0]`,
+ wantRed: `*m2 = m[0]`,
+ }, {
+ desc: `lhs is addressable, rhs is not addressable: array index`,
+ extra: `func m() [1]pb2.M2 {return [1]pb2.M2{} }`,
+ in: `*m2 = m()[0]`,
+ wantRed: `*m2 = m()[0]`,
+ }, {
+ desc: `lhs is addressable, rhs is not addressable: func result`,
+ extra: `func m() pb2.M2 { return pb2.M2{} }`,
+ in: `*m2 = m()`,
+ wantRed: `*m2 = m()`,
+ }, {
+ desc: `lhs is addressable, rhs is not addressable: type conversion`,
+ in: `*m2 = pb2.M2(pb2.M2{})`,
+ wantRed: `*m2 = pb2.M2(pb2.M2{})`,
+ }, {
+ desc: `lhs is addressable, rhs is not addressable: chan receive`,
+ extra: `var ch chan pb2.M2`,
+ in: `*m2 = <-ch`,
+ wantRed: `*m2 = <-ch`,
+ }, { // Neither rhs nor lhs is addressable
+ desc: `lhs is not addressable, rhs is not addressable`,
+ extra: `var m = map[int]pb2.M2{}`,
+ in: `m[0] = m[0]`,
+ wantRed: `m[0] = m[0]`,
+ }, { // No lhs (not assignment context)
+ desc: `addressable function argument`,
+ extra: `func g(pb2.M2) {}; var m pb2.M2`,
+ in: `g(m)`,
+ wantRed: `g(*proto.Clone(&m).(*pb2.M2))`,
+ }, {
+ desc: `don't rewrite proto.Clone`,
+ extra: `func g(pb2.M2) {}; var m pb2.M2`,
+ in: `g(*proto.Clone(&m).(*pb2.M2))`,
+ wantRed: `g(*proto.Clone(&m).(*pb2.M2))`,
+ }, {
+ desc: `empty maker function argument`,
+ extra: `func g(pb2.M2) {}`,
+ in: `g(pb2.M2{})`,
+ wantRed: `g(pb2.M2{})`,
+ }, {
+ desc: `non-empty maker function argument`,
+ extra: `func g(pb2.M2) {}`,
+ in: `g(pb2.M2{M: nil})`,
+ wantRed: `g(*pb2.M2_builder{M: nil}.Build())`,
+ }, {
+ desc: `slice of values`,
+ in: `_ = []pb2.M2{{M: nil}, pb2.M2{M: nil}, pb2.M2{}, {}}`,
+ wantRed: `_ = []pb2.M2{*pb2.M2_builder{M: nil}.Build(), *pb2.M2_builder{M: nil}.Build(), pb2.M2{}, {}}`,
+ }, {
+ desc: `non-addressable function argument`,
+ extra: `func g(pb2.M2) {}; var m map[int]pb2.M2`,
+ in: `g(m[0])`,
+ wantRed: `g(m[0])`,
+ }})
+
+ runTableTests(t, tests)
+}
+
+func TestProto2ScalarAliasing(t *testing.T) {
+ tests := skip([]test{{
+ desc: "proto2: only def + alias",
+ skip: "make red rewrite work for scalar aliasing",
+ in: `
+s := "hello world"
+m2.S = &s`,
+ want: map[Level]string{
+ Yellow: `
+s := "hello world"
+m2.S = &s`,
+ Red: `
+m2.SetS(s)
+`,
+ }}, {
+ desc: "proto2: no access after alias",
+ skip: "make red rewrite work for scalar aliasing",
+ in: `
+s := "hello"
+s = "world"
+m2.S = &s`,
+ want: map[Level]string{
+ Yellow: `
+s := "hello"
+s = "world"
+m2.S = &s`,
+ Red: `
+s := "hello"
+s = "world"
+m2.SetS(s)
+`,
+ }}, {
+ desc: "proto2: only reads after alias",
+ skip: "make red rewrite work for scalar aliasing",
+ extra: "func g(string) { }", in: `
+s := "hello world"
+m2.S = &s
+g(s)
+`,
+ want: map[Level]string{
+ Yellow: `
+s := "hello world"
+m2.S = &s
+g(s)
+`,
+ Red: `
+s := "hello world"
+m2.SetS(s)
+g(s)
+`,
+ }}, {
+ extra: "var b = true",
+ desc: "proto2: conditionals prevent inlining",
+ skip: "make red rewrite work for scalar aliasing",
+ in: `
+s := "hello"
+if b {
+ s = "world"
+}
+m2.S = &s
+`,
+ want: map[Level]string{
+ Yellow: `
+s := "hello"
+if b {
+s = "world"
+}
+m2.S = &s
+`,
+ Red: `
+s := "hello"
+if b {
+s = "world"
+}
+m2.SetS(s)
+`,
+ }}, {
+ desc: "aliasing of message fields",
+ skip: "make red rewrite work for scalar aliasing",
+ in: `
+n2 := m()
+m2.S = n2.S
+`,
+ want: map[Level]string{
+ Yellow: `
+n2 := m()
+m2.S = n2.S
+`,
+ Red: `
+n2 := m()
+m2.SetS(n2.GetS())
+`,
+ },
+ }})
+
+ runTableTests(t, tests)
+}
+
+func trimNL(s string) string {
+ if len(s) != 0 && s[0] == '\n' {
+ s = s[1:]
+ }
+ if len(s) != 0 && s[len(s)-1] == '\n' {
+ s = s[:len(s)-1]
+ }
+ return s
+}
+
+func TestUnparentExpr(t *testing.T) {
+ tests := skip([]test{{
+ desc: "proto2: clear scalar",
+ in: "(m2.S) = nil",
+ want: map[Level]string{
+ Green: "m2.ClearS()",
+ }}, {
+ desc: "proto2: clear scalar in if",
+ in: `
+if true {
+ (m2.S) = nil
+}
+`,
+ want: map[Level]string{
+ Green: `
+if true {
+ m2.ClearS()
+}
+`,
+ }}, {
+ desc: "proto2: clear scalar 2",
+ in: "((m2.S)) = nil",
+ want: map[Level]string{
+ Green: "m2.ClearS()",
+ }}, {
+ desc: "proto2: clear message",
+ in: "(m2.M) = nil",
+ want: map[Level]string{
+ Green: "m2.ClearM()",
+ }}, {
+ desc: "proto2: clear message 2",
+ in: "((m2.M)) = nil",
+ want: map[Level]string{
+ Green: "m2.ClearM()",
+ }}, {
+ desc: "proto2: clear parenthesized scalar slice",
+ in: "(m2.Is) = nil",
+ want: map[Level]string{
+ Green: "m2.SetIs(nil)",
+ }}, {
+ desc: "proto2: clear parenthesized message slice",
+ in: "(m2.Ms) = nil",
+ want: map[Level]string{
+ Green: "m2.SetMs(nil)",
+ }}, {
+ desc: "proto2: clear parenthesized oneof",
+ in: "(m2.OneofField) = nil",
+ want: map[Level]string{
+ Green: "m2.ClearOneofField()",
+ }}, {
+ desc: "proto2: get on lhs used for indexing",
+ in: `
+var ns []int
+ns[*m2.I32] = 0
+ns[(*m2.I32)] = 0
+ns[*(m2.I32)] = 0
+`,
+ want: map[Level]string{
+ Green: `
+var ns []int
+ns[m2.GetI32()] = 0
+ns[m2.GetI32()] = 0
+ns[m2.GetI32()] = 0
+`,
+ }}, {
+ desc: "proto2: get on lhs used for indexing",
+ in: `
+var ns []int
+ns[m3.I32] = 0
+ns[(m3.I32)] = 0
+`,
+ want: map[Level]string{
+ Green: `
+var ns []int
+ns[m3.GetI32()] = 0
+ns[m3.GetI32()] = 0
+`,
+ }}, {
+ desc: "proto3: clear message",
+ in: "(m3.M) = nil",
+ want: map[Level]string{
+ Green: "m3.ClearM()",
+ }}, {
+ desc: "proto3: clear message 3",
+ in: "((m3.M)) = nil",
+ want: map[Level]string{
+ Green: "m3.ClearM()",
+ }}, {
+ desc: "proto3: clear parenthesized scalar slice",
+ in: "(m3.Is) = nil",
+ want: map[Level]string{
+ Green: "m3.SetIs(nil)",
+ }}, {
+ desc: "proto3: clear parenthesized message slice",
+ in: "(m3.Ms) = nil",
+ want: map[Level]string{
+ Green: "m3.SetMs(nil)",
+ }}, {
+ desc: "proto3: clear parenthesized oneof",
+ in: "(m3.OneofField) = nil",
+ want: map[Level]string{
+ Green: "m3.ClearOneofField()",
+ }},
+ })
+
+ runTableTests(t, tests)
+}
+
+func TestPreserveComments(t *testing.T) {
+ tests := []test{
+ {
+ desc: "comments around CompositeLit",
+ in: `
+// Comment above.
+_ = &pb2.M2{S:nil} // Inline comment.
+// Comment below.
+`,
+ want: map[Level]string{
+ Green: `
+// Comment above.
+_ = pb2.M2_builder{S: nil}.Build() // Inline comment.
+// Comment below.
+`,
+ },
+ },
+
+ {
+ desc: "comments in CompositeLit",
+ in: `
+_ = &pb2.M2{ // Comment here.
+ // Comment above field S
+ S: nil, // Inside literal.
+} // After.
+`,
+ want: map[Level]string{
+ Green: `
+_ = pb2.M2_builder{ // Comment here.
+ // Comment above field S
+ S: nil, // Inside literal.
+}.Build() // After.
+`,
+ },
+ },
+
+ {
+ desc: "CompositeLit to setters",
+ srcfiles: []string{"nontest.go"},
+ in: `
+_ = &pb2.M2{ // Comment here.
+ // Comment above field S
+ S: nil, // Inside literal.
+ // Comment above nested message M.
+ M: &pb2.M2{
+ // Comment above field I32 in nested M.
+ I32: proto.Int32(32),
+ },
+} // After.
+`,
+ want: map[Level]string{
+ Green: `
+m2h2 := &pb2.M2{}
+// Comment above field I32 in nested M.
+m2h2.SetI32(32)
+m2h3 := &pb2.M2{ // Comment here.
+}
+// Comment above field S
+m2h3.ClearS() // Inside literal.
+// Comment above nested message M.
+m2h3.SetM(m2h2)
+_ = m2h3 // After.
+`,
+ },
+ },
+
+ {
+ desc: "set clear has",
+ in: `
+// Comment 1
+// More comments
+m2.S = proto.String("hello") // Comment 2
+// Comment 3
+m2.S = nil // Comment 4
+// Comment 5
+_ = m2.S != nil // Comment 6
+// Comment 7
+_ = m2.S == nil // Comment 8
+// Comment 9
+`,
+ want: map[Level]string{
+ Green: `
+// Comment 1
+// More comments
+m2.SetS("hello") // Comment 2
+// Comment 3
+m2.ClearS() // Comment 4
+// Comment 5
+_ = m2.HasS() // Comment 6
+// Comment 7
+_ = !m2.HasS() // Comment 8
+// Comment 9
+`,
+ },
+ },
+
+ {
+ desc: "multi-assign",
+ in: `
+var n int
+_ = n
+// Comment 1
+n, m2.B, m2.S = 42, proto.Bool(true), proto.String("s") // Comment 2
+// Comment 3
+`,
+ want: map[Level]string{
+ Yellow: `
+var n int
+_ = n
+// Comment 1
+n = 42
+m2.SetB(true)
+m2.SetS("s") // Comment 2
+// Comment 3
+`,
+ },
+ },
+
+ {
+ desc: "multi-line msg slices",
+ in: `
+_ = []*pb2.M2{
+ // Comment 1
+ &pb2.M2{}, // Comment 2
+ &pb2.M2{M: nil}, // Comment 3
+ // Comment 4
+ &pb2.M2{ // Comment 5
+ // Comment 6
+ M: nil, // Comment 7
+ }, // Comment 8
+ // Comment 9
+}
+
+_ = []*pb3.M3{
+ // Comment 1
+ {}, // Comment 2
+ {B: true}, // Comment 3
+ // Comment 4
+ { // Comment 5
+ // Comment 6
+ S: "hello", // Comment 7
+ // Comment 8
+ }, // Comment 9
+}
+`,
+ want: map[Level]string{
+ Green: `
+_ = []*pb2.M2{
+ // Comment 1
+ &pb2.M2{}, // Comment 2
+ pb2.M2_builder{M: nil}.Build(), // Comment 3
+ // Comment 4
+ pb2.M2_builder{ // Comment 5
+ // Comment 6
+ M: nil, // Comment 7
+ }.Build(), // Comment 8
+ // Comment 9
+}
+
+_ = []*pb3.M3{
+ // Comment 1
+ {}, // Comment 2
+ pb3.M3_builder{B: true}.Build(), // Comment 3
+ // Comment 4
+ pb3.M3_builder{ // Comment 5
+ // Comment 6
+ S: "hello", // Comment 7
+ // Comment 8
+ }.Build(), // Comment 9
+}
+`,
+ },
+ },
+
+ {
+ desc: "if init simple statement",
+ in: `
+// Comment 1
+if m3.S, m3.M = "", (&pb3.M3{}); m3.B { // Comment 2
+ // Comment 3
+ m3.B, m3.Is = true, nil // Comment 4
+ // Comment 5
+}
+// Comment 6
+`,
+ want: map[Level]string{
+ Yellow: `
+m3.SetS("")
+m3.SetM(&pb3.M3{})
+
+// Comment 1
+if m3.GetB() { // Comment 2
+ // Comment 3
+ m3.SetB(true)
+ m3.SetIs(nil) // Comment 4
+ // Comment 5
+}
+// Comment 6
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestAllowRenamingProtoPackage(t *testing.T) {
+ in := `package p
+
+import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto"
+import protolib "google.golang.org/protobuf/proto"
+
+var _ = protolib.String
+
+func f() {
+ m := &pb2.M2{}
+ _ = "TEST CODE STARTS HERE"
+ _ = m.S
+ _ = "TEST CODE ENDS HERE"
+}
+`
+ want := `_ = protolib.ValueOrNil(m.HasS(), m.GetS)`
+
+ got, _, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, []Level{Green, Yellow, Red})
+ if err != nil {
+ t.Fatalf("fixSource() failed: %v\nFull source:\n%s\n------------------------------", err, in)
+ }
+ if d := diff.Diff(want, got[Red]); d != "" {
+ t.Errorf("fixSource(%q) = (red) %q; want %s\ndiff:\n%s\n", in, got, want, d)
+ }
+}
+
+func TestAddsProtoImport(t *testing.T) {
+ // The m2.M assignment will be rewritten to:
+ //
+ // m2.SetM(pb2.M2_builder{S: proto.String(m2.GetS())}.Build())
+ //
+ // Because the package does not currently import the proto package,
+ // a new proto import should be added.
+ const src = `// _ = "TEST CODE STARTS HERE"
+package p
+
+import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto"
+
+func test_function() {
+ m2 := new(pb2.M2)
+ m2.M = pb2.M2_builder{S: m2.S}.Build()
+ _ = "TEST CODE ENDS HERE"
+}
+`
+
+ gotAll, _, err := fixSource(context.Background(), src, "code.go", ConfiguredPackage{}, []Level{Green, Yellow, Red})
+ if err != nil {
+ t.Fatalf("fixSource() failed: %v; Full input:\n%s", err, src)
+ }
+ got := gotAll[Red]
+ if !strings.Contains(got, "google.golang.org/protobuf/proto") {
+ t.Fatalf("proto import not added: %q", got)
+ }
+}
diff --git a/internal/fix/stats.go b/internal/fix/stats.go
new file mode 100644
index 0000000..dc1e7a5
--- /dev/null
+++ b/internal/fix/stats.go
@@ -0,0 +1,1201 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "fmt"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+ log "github.com/golang/glog"
+ "google.golang.org/open2opaque/internal/o2o/statsutil"
+ "google.golang.org/open2opaque/internal/protodetecttypes"
+
+ spb "google.golang.org/open2opaque/internal/dashboard"
+)
+
+// stats lists uses of protocol buffer types that are interesting from analysis
+// standpoint. This function is called after all rewrites for level c.lvl are
+// applied on the file. If generated is true, then Location.IsGeneratedFile
+// will be set to true on the returned entries.
+func stats(c *cursor, f *dst.File, generated bool) []*spb.Entry {
+ // Temporarily disable stats after rewrites. Only calculate for code before our changes.
+ if c.lvl != None {
+ return nil
+ }
+ parents := map[dst.Node]dst.Node{}
+ var out []*spb.Entry
+ dstutil.Apply(f, func(cur *dstutil.Cursor) bool {
+ n := cur.Node()
+ parents[n] = cur.Parent()
+
+ switch x := n.(type) {
+ case nil:
+ // ApplyFunc can be called with a nil node, for example, when processing a
+ // function declaration
+ return false
+ case *dst.BadStmt, *dst.BadExpr, *dst.BadDecl, *dst.ImportSpec:
+ // Don't recurse into nodes that can't refer to proto types.
+ return false
+ case *dst.GenDecl:
+ // Only recurse into declarations that can reference protos.
+ return x.Tok != token.IMPORT
+ }
+
+ switch n := n.(type) {
+ case *dst.SelectorExpr:
+ out = append(out, selectorStats(c, n, cur.Parent())...)
+ case *dst.CallExpr:
+ out = append(out, callStats(c, n, cur.Parent())...)
+ case *dst.AssignStmt:
+ out = append(out, assignStats(c, n, cur.Parent())...)
+ case *dst.CompositeLit:
+ out = append(out, compositeLitStats(c, n, cur.Parent())...)
+ case *dst.SendStmt:
+ out = append(out, sendStats(c, n, cur.Parent())...)
+ case *dst.ReturnStmt:
+ out = append(out, returnStats(c, n, cur.Parent(), parents)...)
+ case *dst.TypeAssertExpr:
+ out = append(out, typeAssertStats(c, n, cur.Parent())...)
+ case *dst.TypeSpec:
+ out = append(out, typeSpecStats(c, n, cur.Parent())...)
+ case *dst.StructType:
+ out = append(out, structStats(c, n, cur.Parent())...)
+ }
+ return true
+ }, nil)
+ if generated {
+ for _, e := range out {
+ e.GetLocation().IsGeneratedFile = true
+ }
+ }
+ return out
+}
+
+func logSiloed(c *cursor, out []*spb.Entry, n, parent dst.Node) []*spb.Entry {
+ return append(out, &spb.Entry{
+ Status: &spb.Status{
+ Type: spb.Status_FAIL,
+ Error: "type information missing; are dependencies in a silo?",
+ },
+ Location: location(c, n),
+ Level: toRewriteLevel(c.lvl),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", n),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ })
+}
+
+// logConversion appends an entry to the out slice if the conversion is an
+// interesting one.
+//
+// Interesting conversions include all conversions that can make the open2opaque
+// migration harder (e.g. converting protos to interface{}, unsafe.Pointer,
+// etc.).
+//
+// Exactly one of srcExpr and src must be set. Those determine the
+// type/expression that is being converted to type dst.
+//
+// The nodes n and parent build the context for the conversion. Parent should be
+// the parent node of n in the AST.
+//
+// The use specifies the reason for the conversion (is it an implicit conversion
+// to a function argument? explicit conversion in assignment? etc.).
+func logConversion(c *cursor, out []*spb.Entry, srcExpr dst.Expr, src, dst types.Type, n, parent dst.Node, use *spb.Use) []*spb.Entry {
+ if (srcExpr != nil) == (src != nil) {
+ panic(fmt.Sprintf("logConversion: either srcExpr or src must set, but not both (srcExpr!=nil: %t, src!=nil: %t)", srcExpr != nil, src != nil))
+ }
+ if src == nil {
+ src = c.typeOfOrNil(srcExpr)
+ }
+ if src == nil {
+ return logSiloed(c, out, n, parent)
+ }
+ if !isInterfaceType(dst) {
+ return out
+ }
+ t, ok := c.shouldLogCompositeType(src, true)
+ if !ok {
+ return out
+ }
+ return append(out, &spb.Entry{
+ Location: location(c, n),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", n),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: use,
+ })
+}
+
+// logShallowCopy appends an entry to the out slice if the expression is a
+// shallow copy.
+//
+// Exactly one of srcExpr and src must be set. Those determine the
+// type/expression that is being converted to type dst.
+//
+// The nodes n and parent build the context for the conversion. Parent should be
+// the parent node of n in the AST.
+//
+// The use specifies the reason for the conversion (is it an implicit conversion
+// to a function argument? explicit conversion in assignment? etc.).
+func logShallowCopy(c *cursor, out []*spb.Entry, srcExpr dst.Expr, src types.Type, n, parent dst.Node, use *spb.Use) []*spb.Entry {
+ if (srcExpr != nil) == (src != nil) {
+ panic(fmt.Sprintf("logShallowCopy: either srcExpr or src must set, but not both (srcExpr!=nil: %t, src!=nil: %t)", srcExpr != nil, src != nil))
+ }
+ if src == nil {
+ src = c.typeOfOrNil(srcExpr)
+ }
+ if src == nil {
+ return logSiloed(c, out, n, parent)
+ }
+
+ if _, isPtr := src.Underlying().(*types.Pointer); isPtr {
+ return out
+ }
+ t, ok := c.shouldLogCompositeType(src, false)
+ if !ok {
+ return out
+ }
+
+ return append(out, &spb.Entry{
+ Location: location(c, n),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", n),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: use,
+ })
+}
+
+// shouldLogCompositeType returns true if an expression should be logged when
+// the composite type t is part of the expression.
+//
+// followPointers determines whether pointer types should be traversed when
+// looking for the result. This is mainly useful for finding shallow copies.
+//
+// If shouldLogCompositeType returns true, then it also returns the reason why
+// the type should be logged (i.e. the actual proto type referenced by the
+// composite type t).
+//
+// For example, the following expression:
+//
+// f(struct{ // assume: func f(interface{}) { }
+// m *pb.M2,
+// }{
+// m: m2,
+// })
+//
+// should be logged (if *pb.M2 is a proto type that should be tracked) because a
+// value of type *pb.M2 is converted to interface{}.
+//
+// The following expression should not be tracked:
+//
+// f(struct{ // assume: func f(interface{}) { }
+// ch chan *pb.M2,
+// }{
+// ch: make(chan *pb.M2),
+// })
+//
+// because it does not contain values of type *pb.M2. Only references to the
+// type. (what should/shouldn't be tracked is a somewhat arbitrary choice).
+//
+// This functionality exists to statically track potential Go reflect usage on
+// protos and shallow copies.
+//
+// NOTE: one could argue that it would be more accurate to track all references
+// to proto types, not only those associated with values in expressions
+// (e.g. the channel example above). We've tried that and the number of findings
+// (false positives) is so large that the statistic becomes meaningless.
+func (c *cursor) shouldLogCompositeType(t types.Type, followPointers bool) (out types.Type, _ bool) {
+ // We use a cache for two reasons:
+ // - (major) to handle cyclic data structures
+ // - (minor) to improve performance
+ //
+ // Consider type:
+ //
+ // type T struct {
+ // F *T
+ // }
+ //
+ // and shouldLogCompositeType call with T and followPointers=true. We
+ // don't want a recursive call shouldLogCompositeType for the field F
+ // after dereferencing the pointer as that would result in an infinite
+ // recursion.
+ //
+ // At the time we see T for the first time, we don't know if it will
+ // contain pointers or not, but we have to signal not to process T
+ // again. Our approach involves three states for T in the cache:
+ //
+ // 1. an empty cache for T means that we've never seen this type (and
+ // hence should process it)
+ //
+ // 2. a cache with a nil value for T means either that:
+ //
+ // - we're currently processing the type. We don't know whether it
+ // depends on protos or not, but we know that we shouldn't start
+ // processing it again; or
+ //
+ // - we've processed the type before and it doesn't reference
+ // protos. We can return nil as the result.
+ //
+ // 3. a cache with a non-nil value for T means that we've processed
+ // the type before and that we can simply return the result.
+ //
+ // The result of shouldLogCompositeType can be different for a single
+ // type, based on the followPointers value. Therefore, we have two
+ // caches: one for each possdible value of followPointers. Technically
+ // followPointers=false implies that there should be no infinite
+ // recursion (in the current version of shouldLogCompositeType) but we
+ // want to keep the code symmetric.
+ cache := c.shouldLogCompositeTypeCache
+ if followPointers {
+ cache = c.shouldLogCompositeTypeCacheNoPtr
+ }
+
+ if cached := cache.At(t); cached != nil {
+ ce := cached.(*cacheEntry)
+ return ce.protoType, ce.protoType != nil
+ }
+
+ cache.Set(t, &cacheEntry{})
+
+ defer func() {
+ cache.Set(t, &cacheEntry{protoType: out})
+ }()
+
+ if _, isPtr := t.Underlying().(*types.Pointer); !followPointers && isPtr {
+ return nil, false
+ }
+ if c.shouldTrackType(t) {
+ return t, true
+ }
+ switch t := t.(type) {
+ case *types.Tuple:
+ for i := 0; i < t.Len(); i++ {
+ if t, ok := c.shouldLogCompositeType(t.At(i).Type(), followPointers); ok {
+ return t, true
+ }
+ }
+ return nil, false
+ case *types.Interface:
+ // The interface could contain a proto, however we would've caught
+ // conversion to the interface type.
+ return nil, false
+ case *types.Named:
+ return c.shouldLogCompositeType(t.Underlying(), followPointers)
+ case *types.Pointer:
+ if !followPointers {
+ return nil, false
+ }
+ return c.shouldLogCompositeType(t.Elem(), followPointers)
+ case *types.Signature:
+ return nil, false
+ case *types.TypeParam:
+ return nil, false
+ case *types.Slice:
+ if !followPointers {
+ return nil, false
+ }
+ return c.shouldLogCompositeType(t.Elem(), followPointers)
+ case *types.Array:
+ return c.shouldLogCompositeType(t.Elem(), followPointers)
+ case *types.Basic:
+ return nil, false
+ case *types.Chan:
+ return nil, false
+ case *types.Map:
+ if !followPointers {
+ return nil, false
+ }
+ if t, ok := c.shouldLogCompositeType(t.Key(), followPointers); ok {
+ return t, true
+ }
+ if t, ok := c.shouldLogCompositeType(t.Elem(), followPointers); ok {
+ return t, true
+ }
+ return nil, false
+ case *types.Struct:
+ for i := 0; i < t.NumFields(); i++ {
+ if t, ok := c.shouldLogCompositeType(t.Field(i).Type(), followPointers); ok {
+ return t, true
+ }
+ }
+ return nil, false
+ case *types.Alias:
+ return c.shouldLogCompositeType(types.Unalias(t), followPointers)
+ default:
+ panic(fmt.Sprintf("unrecognized type %T", t))
+ }
+}
+
+// convToUnsafePointerArg returns the x expression in unsafe.Pointer(x)
+// conversion if expr has the form unsafe.Pointer(x).
+func convToUnsafePointerArg(c *cursor, expr dst.Expr) (dst.Expr, bool) {
+ call, ok := expr.(*dst.CallExpr)
+ if !ok {
+ return nil, false
+ }
+ if sel, ok := call.Fun.(*dst.SelectorExpr); !ok || c.objectOf(sel.Sel) != types.Unsafe.Scope().Lookup("Pointer") {
+ return nil, false
+ }
+ return call.Args[0], true
+}
+
+func typeName(t types.Type) string {
+ if iface, ok := t.(*types.Interface); ok && iface.Empty() {
+ return "interface{}"
+ }
+ return t.String()
+}
+
+func isInterfaceType(t types.Type) bool {
+ _, ok := t.Underlying().(*types.Interface)
+ return ok
+}
+
+func hasInterfaceType(c *cursor, expr dst.Expr) bool {
+ if ident, ok := expr.(*dst.Ident); ok && ident.Name == "_" {
+ return false
+ }
+ return isInterfaceType(c.typeOf(expr))
+}
+
+func location(c *cursor, n dst.Node) *spb.Location {
+ astNode := c.typesInfo.astMap[n]
+ if astNode == nil {
+ return nil
+ }
+ start := c.pkg.Fileset.Position(astNode.Pos())
+ end := c.pkg.Fileset.Position(astNode.End())
+ return &spb.Location{
+ Package: c.pkg.TypePkg.Path(),
+ File: start.Filename,
+ Start: &spb.Position{
+ Line: int64(start.Line),
+ Column: int64(start.Column),
+ },
+ End: &spb.Position{
+ Line: int64(end.Line),
+ Column: int64(end.Column),
+ },
+ }
+}
+
+func toRewriteLevel(lvl Level) spb.RewriteLevel {
+ switch lvl {
+ case None:
+ return spb.RewriteLevel_NONE
+ case Green:
+ return spb.RewriteLevel_GREEN
+ case Yellow:
+ return spb.RewriteLevel_YELLOW
+ case Red:
+ return spb.RewriteLevel_RED
+ default:
+ panic("unrecognized fix.Level %s" + lvl)
+ }
+}
+
+func toTypeProto(typ types.Type) *spb.Type {
+ return statsutil.ShortAndLongNameFrom(typ.String())
+}
+
+func selectorStats(c *cursor, sel *dst.SelectorExpr, parent dst.Node) []*spb.Entry {
+ if _, ok := c.trackedProtoFieldSelector(sel); ok {
+ return []*spb.Entry{&spb.Entry{
+ Location: location(c, sel),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(c.typeOf(sel.X)),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", sel),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_DIRECT_FIELD_ACCESS,
+ DirectFieldAccess: &spb.FieldAccess{
+ FieldName: sel.Sel.Name,
+ FieldType: toTypeProto(c.typeOf(sel.Sel)),
+ },
+ },
+ }}
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return logSiloed(c, nil, sel, parent)
+ }
+ if !c.shouldTrackType(t) || !strings.HasPrefix(sel.Sel.Name, "XXX_") {
+ return nil
+ }
+ return []*spb.Entry{&spb.Entry{
+ Location: location(c, sel),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", sel),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_INTERNAL_FIELD_ACCESS,
+ InternalFieldAccess: &spb.FieldAccess{
+ FieldName: sel.Sel.Name,
+ FieldType: toTypeProto(c.typeOf(sel.Sel)),
+ },
+ },
+ }}
+}
+
+// oneofGetterOrNil determines whether call is like msg.GetFoo(), where foo is a
+// oneof field of the message type of msg. It returns a corresponding stats
+// entry if this is the case, and nil otherwise.
+func oneofGetterOrNil(c *cursor, call *dst.CallExpr, parent dst.Node) *spb.Entry {
+ sel, sig, ok := c.protoFieldSelectorOrAccessor(call.Fun)
+ if !ok || sig == nil {
+ return nil
+ }
+ if !strings.HasPrefix(sel.Sel.Name, "Get") {
+ return nil
+ }
+ field := strings.TrimPrefix(sel.Sel.Name, "Get")
+
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return nil // skip over expression without type info (silo'ed?)
+ }
+ fullQual, isMsg := c.messageTypeName(t)
+ if !isMsg {
+ return nil
+ }
+ fullQual = strings.Trim(fullQual, `"`)
+ parts := strings.Split(fullQual, ".")
+ if len(parts) < 2 {
+ log.Errorf("not a fully qualified type name: %s", fullQual)
+ return nil
+ }
+ msg, pkg := parts[len(parts)-1], strings.Join(parts[:len(parts)-1], ".")
+ expectRetType := pkg + ".is" + msg + "_" + field
+
+ res := sig.Results()
+ if res.Len() != 1 {
+ return nil
+ }
+ if res.At(0).Type().String() != expectRetType {
+ return nil
+ }
+ return &spb.Entry{
+ Location: location(c, call),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", call),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_METHOD_CALL,
+ MethodCall: &spb.MethodCall{
+ Method: sel.Sel.Name,
+ Type: spb.MethodCall_GET_ONEOF,
+ },
+ },
+ }
+}
+
+// buildGetterEntryOrNil determines whether call is like msg.GetBuild() to get
+// the value of the field called build of the proto message msg. It returns a
+// corresponding stats Entry if this is the case, and nil otherwise.
+func buildGetterEntryOrNil(c *cursor, call *dst.CallExpr, parent dst.Node) *spb.Entry {
+ sel, sig, ok := c.protoFieldSelectorOrAccessor(call.Fun)
+ if !ok || sig == nil {
+ return nil
+ }
+ if sel.Sel.Name != "GetBuild" {
+ return nil
+ }
+ t := c.typeOfOrNil(sel.X)
+ if t == nil {
+ return nil // skip over expression without type info (silo'ed?)
+ }
+ _, isMsg := c.messageTypeName(t)
+ if !isMsg {
+ return nil
+ }
+ return &spb.Entry{
+ Location: location(c, call),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", call),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_METHOD_CALL,
+ MethodCall: &spb.MethodCall{
+ Method: sel.Sel.Name, Type: spb.MethodCall_GET_BUILD,
+ },
+ },
+ }
+}
+
+func callStats(c *cursor, call *dst.CallExpr, parent dst.Node) []*spb.Entry {
+ if c.isBuiltin(call.Fun) {
+ return nil
+ }
+
+ var f types.Object
+ switch expr := call.Fun.(type) {
+ case *dst.Ident:
+ f = c.objectOf(expr)
+ case *dst.SelectorExpr:
+ f = c.objectOf(expr.Sel)
+ }
+ var fname, fpkg string
+ if f != nil {
+ fname = f.Name()
+ if p := f.Pkg(); p != nil {
+ fpkg = p.Path()
+ }
+ } else if _, ok := call.Fun.(*dst.InterfaceType); !ok {
+ // We could probably drop this branch. After AST transformations we may
+ // have no knowledge of the actual function object/type (e.g. if we
+ // don't maintain it correctly). It does not matter because none of the
+ // rewritten functions have interfaces as arguments, and it's safe to
+ // skip them. However, for explicit conversions, there's no
+ // corresponding function object/type. Addressing this todo requires
+ // updating the rewriter to correctly object identity for all introduced
+ // function and method calls.
+ return nil
+ }
+
+ if entry := oneofGetterOrNil(c, call, parent); entry != nil {
+ return []*spb.Entry{entry}
+ }
+ if entry := buildGetterEntryOrNil(c, call, parent); entry != nil {
+ return []*spb.Entry{entry}
+ }
+
+ if len(call.Args) == 0 {
+ return nil
+ }
+
+ var out []*spb.Entry
+
+ if len(call.Args) == 1 {
+ ft := c.typeOf(call.Fun)
+ // explicit conversion: interface{}(m)
+ if hasInterfaceType(c, call.Fun) {
+ // should we consider this a shallow copy too?
+ return logConversion(c, out, call.Args[0], nil, c.typeOf(call.Fun), call, parent, &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_EXPLICIT,
+ DestTypeName: ft.String(),
+ FuncArg: &spb.FuncArg{
+ FunctionName: fname,
+ PackagePath: fpkg,
+ Signature: ft.String(),
+ },
+ },
+ })
+ }
+ // explicit conversion: unsafe.Pointer(m)
+ if arg, ok := convToUnsafePointerArg(c, call); ok {
+ if t, ok := c.shouldLogCompositeType(c.typeOf(arg), true); ok {
+ return append(out, &spb.Entry{
+ Location: location(c, call),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", call),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ DestTypeName: ft.String(),
+ Context: spb.Conversion_EXPLICIT,
+ },
+ },
+ })
+ }
+ }
+ }
+
+ // A function call. Look for arguments that are implicitly converted to an interface or shallow-copied.
+ // For example:
+ // "proto.Clone(m)"
+ // "f(*m)"
+ // "f(g())" where g() returns at least one protocol buffer message
+
+ ft, ok := c.typeOf(call.Fun).(*types.Signature)
+ if !ok {
+ return out
+ }
+
+ var argTypes []types.Type
+ arg0t := c.typeOfOrNil(call.Args[0])
+ if arg0t == nil {
+ return logSiloed(c, out, call.Args[0], parent)
+ }
+ if tuple, ok := arg0t.(*types.Tuple); ok {
+ // Handle "f(g())"-style calls where g() returns a tuple of results.
+ for i := 0; i < tuple.Len(); i++ {
+ argTypes = append(argTypes, tuple.At(i).Type())
+ }
+ } else {
+ // Handle "f(a,b,c)"-style calls.
+ for _, a := range call.Args {
+ t := c.typeOfOrNil(a)
+ if t == nil {
+ return logSiloed(c, out, a, parent)
+ }
+ argTypes = append(argTypes, t)
+ }
+ }
+
+ for i, argType := range argTypes {
+ var t types.Type
+ if ft.Variadic() {
+ if i < ft.Params().Len()-1 {
+ t = ft.Params().At(i).Type()
+ } else {
+ t = ft.Params().At(ft.Params().Len() - 1).Type().(*types.Slice).Elem()
+ }
+ } else {
+ t = ft.Params().At(i).Type()
+ }
+ out = logConversion(c, out, nil, argType, t, call, parent, &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_CALL_ARGUMENT,
+ DestTypeName: typeName(t),
+ FuncArg: &spb.FuncArg{
+ FunctionName: fname,
+ PackagePath: fpkg,
+ Signature: ft.String(),
+ },
+ },
+ })
+ out = logShallowCopy(c, out, nil, argType, call, parent, &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_CALL_ARGUMENT,
+ },
+ })
+ }
+
+ return out
+}
+
+func assignStats(c *cursor, as *dst.AssignStmt, parent dst.Node) []*spb.Entry {
+ // a!=b && b==1 happens when right-hand side returns a tuple (e.g. map access or a function call)
+ if a, b := len(as.Lhs), len(as.Rhs); a != b && b != 1 {
+ panic(fmt.Sprintf("invalid assignment: lhs has %d exprs, rhs has %d exprs", a, b))
+ }
+
+ var out []*spb.Entry
+ for i, lhs := range as.Lhs {
+ // Ignore valid situations where a dst.Ident may have no type. For example:
+ // n (*dst.Ident) has no known type in
+ // switch n := in.(type) {
+ if _, ok := parent.(*dst.TypeSwitchStmt); ok && !c.hasType(lhs) {
+ continue
+ }
+
+ lhst := c.typeOfOrNil(lhs)
+ if lhst == nil {
+ out = logSiloed(c, out, lhs, parent)
+ continue
+ }
+ conversion := &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_ASSIGNMENT,
+ DestTypeName: typeName(lhst),
+ },
+ }
+ shallowCopy := &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_ASSIGN,
+ },
+ }
+ if len(as.Lhs) == len(as.Rhs) {
+ out = logConversion(c, out, as.Rhs[i], nil, lhst, as, parent, conversion)
+ out = logShallowCopy(c, out, as.Rhs[i], nil, as, parent, shallowCopy)
+ } else {
+ rhst := c.typeOfOrNil(as.Rhs[0])
+ if rhst == nil {
+ out = logSiloed(c, out, as.Rhs[0], parent)
+ continue
+ }
+
+ if _, ok := rhst.(*types.Tuple); !ok {
+ continue
+ }
+
+ out = logConversion(c, out, nil, rhst.(*types.Tuple).At(i).Type(), lhst, as, parent, conversion)
+ out = logShallowCopy(c, out, nil, rhst.(*types.Tuple).At(i).Type(), as, parent, shallowCopy)
+ }
+ }
+ return out
+}
+
+func constructor(c *cursor, lit *dst.CompositeLit, parent dst.Node) *spb.Entry {
+ ct := &spb.Entry{
+ Location: location(c, lit),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(c.typeOf(lit)),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", lit),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_CONSTRUCTOR,
+ Constructor: &spb.Constructor{},
+ },
+ }
+ ctype := c.typeOf(lit).String()
+ switch {
+ case strings.HasSuffix(ctype, "_builder"):
+ // Consider an empty builder as builder type, not empty.
+ ct.GetUse().GetConstructor().Type = spb.Constructor_BUILDER
+ case len(lit.Elts) == 0:
+ ct.GetUse().GetConstructor().Type = spb.Constructor_EMPTY_LITERAL
+ default:
+ ct.GetUse().GetConstructor().Type = spb.Constructor_NONEMPTY_LITERAL
+ }
+ return ct
+}
+
+func compositeLitStats(c *cursor, lit *dst.CompositeLit, parent dst.Node) []*spb.Entry {
+ var out []*spb.Entry
+
+ t := c.typeOfOrNil(lit)
+ if t == nil {
+ return logSiloed(c, out, lit, parent)
+ }
+
+ // isBuilderType is cheaper.
+ if c.isBuilderType(t) || c.shouldTrackType(t) {
+ out = append(out, constructor(c, lit, parent))
+ }
+ if len(lit.Elts) == 0 {
+ return out
+ }
+
+ // Conversion in composite literal construction.
+Elt:
+ for i, e := range lit.Elts {
+ var val dst.Expr
+ if kv, ok := e.(*dst.KeyValueExpr); ok {
+ val = kv.Value
+ } else {
+ val = e
+ }
+ use := func(t types.Type) *spb.Use {
+ return &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_COMPOSITE_LITERAL_ELEMENT,
+ DestTypeName: typeName(t),
+ },
+ }
+ }
+ shallowCopyUse := &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT,
+ },
+ }
+ switch t := c.underlyingTypeOf(lit).(type) {
+ case *types.Pointer: // e.g. []*struct{m *pb.M}{{m2}}
+ if kv, ok := e.(*dst.KeyValueExpr); ok {
+ t := c.typeOfOrNil(kv.Key)
+ if t == nil {
+ out = logSiloed(c, out, lit, parent)
+ continue Elt
+ }
+ out = logConversion(c, out, val, nil, t, e, lit, use(t))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ } else {
+ if st, ok := t.Elem().Underlying().(*types.Struct); ok {
+ t := st.Field(i).Type()
+ out = logConversion(c, out, val, nil, t, e, lit, use(t))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ }
+ }
+ case *types.Struct: // e.g. []struct{m *pb.M}{{m2}}
+ if kv, ok := e.(*dst.KeyValueExpr); ok {
+ t := c.typeOfOrNil(kv.Key)
+ if t == nil {
+ out = logSiloed(c, out, lit, parent)
+ continue Elt
+ }
+ out = logConversion(c, out, val, nil, t, e, lit, use(t))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ } else {
+ t := t.Field(i).Type()
+ out = logConversion(c, out, val, nil, t, e, lit, use(t))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ }
+ case *types.Slice: // e.g. []*pb.M2{m2}
+ out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem()))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ case *types.Array: // e.g. [1]*pb.M2{m2}
+ out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem()))
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse)
+ case *types.Map: // e.g. map[*pb.M2]*pb.M2{m2: m2}
+ kv, ok := e.(*dst.KeyValueExpr)
+ if !ok {
+ ae := c.typesInfo.astMap[e]
+ panic("can't process a map element: not a key-value at " +
+ c.pkg.Fileset.Position(ae.Pos()).String())
+ }
+ out = logConversion(c, out, kv.Key, nil, t.Key(), kv, lit, use(t.Key()))
+ out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem()))
+ out = logShallowCopy(c, out, kv.Key, nil, kv, lit, shallowCopyUse) // impossible?
+ out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) // map[Key]pb.M{Key{}: *m2}
+ case *types.Basic:
+ if t.Kind() == types.Invalid {
+ out = logSiloed(c, out, lit, parent)
+ continue Elt
+ }
+ default:
+ ae := c.typesInfo.astMap[e]
+ panic(fmt.Sprintf("unrecognized composite literal type %T (%v) at %s at level %s",
+ t, t, c.pkg.Fileset.Position(ae.Pos()).String(), c.lvl))
+ }
+ }
+
+ // Write to an internal field.
+ if t := c.typeOf(lit); c.shouldTrackType(t) {
+ var s *types.Struct
+ if p, ok := t.Underlying().(*types.Pointer); ok {
+ s = p.Elem().Underlying().(*types.Struct)
+ } else {
+ s = t.Underlying().(*types.Struct)
+ }
+ for i, e := range lit.Elts {
+ var fname string
+ var ftype types.Type
+ if kv, ok := e.(*dst.KeyValueExpr); ok {
+ f := kv.Key.(*dst.Ident)
+ fname, ftype = f.Name, c.typeOf(f)
+ } else {
+ f := s.Field(i)
+ fname, ftype = f.Name(), f.Type()
+ }
+ if !strings.HasPrefix(fname, "XXX_") {
+ continue
+ }
+ out = append(out, &spb.Entry{
+ Location: location(c, e),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", e),
+ ParentType: fmt.Sprintf("%T", lit),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_INTERNAL_FIELD_ACCESS,
+ InternalFieldAccess: &spb.FieldAccess{
+ FieldName: fname,
+ FieldType: toTypeProto(ftype),
+ },
+ },
+ })
+ }
+ }
+
+ return out
+}
+
+func sendStats(c *cursor, send *dst.SendStmt, parent dst.Node) []*spb.Entry {
+ underlying := c.underlyingTypeOfOrNil(send.Chan)
+ if underlying == nil {
+ return logSiloed(c, nil, send, parent)
+ }
+ dst := underlying.(*types.Chan).Elem()
+ out := logConversion(c, nil, send.Value, nil, dst, send, parent, &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_CHAN_SEND,
+ DestTypeName: typeName(dst),
+ },
+ })
+ out = append(out, logShallowCopy(c, nil, send.Value, nil, send, parent, &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_CHAN_SEND,
+ },
+ })...)
+ return out
+}
+
+func returnStats(c *cursor, ret *dst.ReturnStmt, parent dst.Node, parents map[dst.Node]dst.Node) []*spb.Entry {
+ p := parent
+ for i := 0; ; i++ {
+ if i > 1000 {
+ panic("too many parent nodes; is there a cycle in the parent structure?")
+ }
+ _, isfunc := p.(*dst.FuncDecl)
+ _, isflit := p.(*dst.FuncLit)
+ if isfunc || isflit {
+ break
+ }
+ p = parents[p]
+ }
+ var sig *types.Signature
+ switch p := p.(type) {
+ case *dst.FuncDecl:
+ pt := c.typeOfOrNil(p.Name)
+ if pt == nil {
+ return logSiloed(c, nil, ret, parent)
+ }
+ sig = pt.(*types.Signature)
+ case *dst.FuncLit:
+ pt := c.typeOfOrNil(p)
+ if pt == nil {
+ return logSiloed(c, nil, ret, parent)
+ }
+ sig = pt.(*types.Signature)
+ default:
+ panic(fmt.Sprintf("invalid parent function type %T; must be *dst.FuncDecl or *dst.FuncLit", p))
+ }
+
+ // Naked returns: there's no conversion possible because return values
+ // already have the same type as the function specifies. Any conversion
+ // to that type was already captured in assignments before the return
+ // statement.
+ if len(ret.Results) == 0 {
+ return nil
+ }
+
+ // Handle the special case of returning the result of a function call as
+ // multiple results:
+ // func f() (a,b T) {
+ // return g()
+ // }
+ if len(ret.Results) == 1 && sig.Results().Len() > 1 {
+ rt0 := c.typeOfOrNil(ret.Results[0])
+ if rt0 == nil {
+ return logSiloed(c, nil, ret.Results[0], parent)
+ }
+ rt := rt0.(*types.Tuple)
+ if a, b := sig.Results().Len(), rt.Len(); a != b {
+ panic(fmt.Sprintf("number of function return value types (%d) doesn't match the number of returned values as a tuple (%d)", a, b))
+ }
+ var out []*spb.Entry
+ for i := 0; i < rt.Len(); i++ {
+ dst := sig.Results().At(i).Type()
+ out = logConversion(c, out, nil, rt.At(i).Type(), dst, ret, parent, &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_FUNC_RET,
+ DestTypeName: typeName(dst),
+ },
+ })
+ out = logShallowCopy(c, out, nil, rt.At(i).Type(), ret, parent, &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_FUNC_RET,
+ },
+ })
+ }
+ return out
+ }
+
+ // Handle the typical case: the return statement has one value for each
+ // return value in the function signature.
+ if a, b := sig.Results().Len(), len(ret.Results); a != b {
+ panic(fmt.Sprintf("number of function return value types (%d) doesn't match the number of returned values (%d)", a, b))
+ }
+ var out []*spb.Entry
+ for i, srcExpr := range ret.Results {
+ dst := sig.Results().At(i).Type()
+ out = logConversion(c, out, srcExpr, nil, dst, ret, parent, &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: &spb.Conversion{
+ Context: spb.Conversion_FUNC_RET,
+ DestTypeName: typeName(dst),
+ },
+ })
+ out = logShallowCopy(c, out, srcExpr, nil, ret, parent, &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: &spb.ShallowCopy{
+ Type: spb.ShallowCopy_FUNC_RET,
+ },
+ })
+ }
+ return out
+}
+
+func typeAssertStats(c *cursor, ta *dst.TypeAssertExpr, parent dst.Node) []*spb.Entry {
+ // Ignore type assertions that don't have known types. This could
+ // happen, for example, in a type switch. The expression:
+ // in.(type)
+ // has no known type in:
+ // switch n := in.(type) {
+ if !c.hasType(ta) {
+ // Not handled: case with a proto type.
+ return nil
+ }
+
+ t, ok := c.shouldLogCompositeType(c.typeOf(ta), true)
+ if !ok {
+ return nil
+ }
+ return []*spb.Entry{&spb.Entry{
+ Location: location(c, ta),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", ta),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_TYPE_ASSERTION,
+ TypeAssertion: &spb.TypeAssertion{
+ SrcType: toTypeProto(c.typeOf(ta.X)),
+ },
+ },
+ }}
+}
+
+func typeSpecStats(c *cursor, ts *dst.TypeSpec, parent dst.Node) []*spb.Entry {
+ if ts.Assign {
+ // Type aliases are not interesting.
+ return nil
+ }
+ t := c.typeOf(ts.Type)
+ if !c.shouldTrackType(t) {
+ return nil
+ }
+ if p, ok := t.Underlying().(*types.Pointer); ok {
+ // For U in:
+ // type T *pb.M3
+ // type U T
+ // we want t==*pb.M3, but for U in
+ // type U pb.M3
+ // we want t==pb.M3 (not the underlying struct)
+ t = p
+ }
+ return []*spb.Entry{&spb.Entry{
+ Location: location(c, ts),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", ts),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_TYPE_DEFINITION,
+ TypeDefinition: &spb.TypeDefinition{
+ NewType: toTypeProto(c.typeOf(ts.Name)),
+ },
+ },
+ }}
+}
+
+func structStats(c *cursor, st *dst.StructType, parent dst.Node) []*spb.Entry {
+ var idx int
+ var out []*spb.Entry
+ for _, f := range st.Fields.List {
+ idx += len(f.Names)
+ if len(f.Names) != 0 {
+ continue
+ }
+ t := c.typeOf(f.Type)
+ if !c.shouldTrackType(t) {
+ continue
+ }
+ out = append(out, &spb.Entry{
+ Location: location(c, f),
+ Level: toRewriteLevel(c.lvl),
+ Type: toTypeProto(t),
+ Expr: &spb.Expression{
+ Type: fmt.Sprintf("%T", st),
+ ParentType: fmt.Sprintf("%T", parent),
+ },
+ Use: &spb.Use{
+ Type: spb.Use_EMBEDDING,
+ Embedding: &spb.Embedding{
+ FieldIndex: int64(idx),
+ },
+ },
+ })
+ }
+ return out
+}
+
+// isBuilderType returns true for types which we consider to represent builder types generated
+// by the proto generator that builds protocol buffer messages.
+func (c *cursor) isBuilderType(t types.Type) bool {
+ if p, ok := t.Underlying().(*types.Pointer); ok {
+ t = p.Elem()
+ }
+ nt, ok := t.(*types.Named)
+ if !ok {
+ return false
+ }
+ if !strings.HasSuffix(nt.String(), "_builder") {
+ return false
+ }
+
+ // Check whether the type has a method called "Build" and it takes no argument
+ // and returns a proto.
+ for i := 0; i < nt.NumMethods(); i++ {
+ f := nt.Method(i)
+ if f.Name() != "Build" {
+ continue
+ }
+ sig, ok := f.Type().(*types.Signature)
+ if !ok {
+ return false
+ }
+ if sig.Params() != nil {
+ return false
+ }
+ res := sig.Results()
+ if res == nil || res.Len() != 1 {
+ return false
+ }
+ return c.shouldTrackType(res.At(0).Type())
+ }
+ return false
+}
+
+// shouldTrackType returns true for types which we consider to represent protocol buffers generated
+// by the proto generator that the user requested to migrate. That is, types that should be
+// considered for a rewrite during the open2opaque protocol buffer migration.
+//
+// The function returns true for all references to proto messages. Including
+// accesses that we are currently not rewriting (e.g. via custom named types
+// whose underlying type is a protocol buffer struct, or messages that
+// explicitly stay on the OPEN_V1 API).
+//
+// Also see the shouldUpdateType function which returns true for types that we
+// are currently rewriting.
+func (c *cursor) shouldTrackType(t types.Type) bool {
+ name := strings.TrimPrefix(t.String(), "*")
+
+ t = t.Underlying()
+ if p, ok := t.(*types.Pointer); ok {
+ t = p.Elem()
+ }
+ if !(protodetecttypes.Type{T: t}.IsMessage()) {
+ return false
+ }
+ name = strings.TrimPrefix(name, "*")
+ return len(c.typesToUpdate) == 0 || c.typesToUpdate[name]
+}
diff --git a/internal/fix/stats_test.go b/internal/fix/stats_test.go
new file mode 100644
index 0000000..90dbd97
--- /dev/null
+++ b/internal/fix/stats_test.go
@@ -0,0 +1,879 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/dave/dst"
+ "github.com/google/go-cmp/cmp"
+ spb "google.golang.org/open2opaque/internal/dashboard"
+ "google.golang.org/open2opaque/internal/o2o/statsutil"
+ "google.golang.org/protobuf/testing/protocmp"
+)
+
+func TestStats(t *testing.T) {
+ t.Setenv("GODEBUG", "gotypesalias=1")
+
+ const extra = `
+type NotAProto struct {
+ S *string
+ Field struct{}
+}
+var notAProto *NotAProto
+func g() string { return "" }
+`
+ loc := func(startLn, startCol, endLn, endCol int64) *spb.Location {
+ return &spb.Location{
+ Package: "google.golang.org/open2opaque/internal/fix/testdata/fake",
+ File: "google.golang.org/open2opaque/internal/fix/testdata/fake/pkg_test.go",
+ Start: &spb.Position{
+ Line: startLn,
+ Column: startCol,
+ },
+ End: &spb.Position{
+ Line: endLn,
+ Column: endCol,
+ },
+ }
+ }
+ const protoMsg = "google.golang.org/protobuf/proto.Message"
+ m2Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2")
+ m2 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2")
+ m3Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")
+ m3 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")
+ expr := func(node, parent any) *spb.Expression {
+ return &spb.Expression{
+ Type: fmt.Sprintf("%T", node),
+ ParentType: fmt.Sprintf("%T", parent),
+ }
+ }
+ entry := func(loc *spb.Location, typ *spb.Type, expr *spb.Expression, use any) *spb.Entry {
+ e := &spb.Entry{
+ Location: loc,
+ Level: toRewriteLevel(None),
+ Type: typ,
+ Expr: expr,
+ }
+ switch use := use.(type) {
+ case *spb.Use:
+ e.Use = use
+ case *spb.Constructor:
+ e.Use = &spb.Use{
+ Type: spb.Use_CONSTRUCTOR,
+ Constructor: use,
+ }
+ case *spb.Conversion:
+ e.Use = &spb.Use{
+ Type: spb.Use_CONVERSION,
+ Conversion: use,
+ }
+ case *spb.TypeAssertion:
+ e.Use = &spb.Use{
+ Type: spb.Use_TYPE_ASSERTION,
+ TypeAssertion: use,
+ }
+ case *spb.TypeDefinition:
+ e.Use = &spb.Use{
+ Type: spb.Use_TYPE_DEFINITION,
+ TypeDefinition: use,
+ }
+ case *spb.Embedding:
+ e.Use = &spb.Use{
+ Type: spb.Use_EMBEDDING,
+ Embedding: use,
+ }
+ case *spb.ShallowCopy:
+ e.Use = &spb.Use{
+ Type: spb.Use_SHALLOW_COPY,
+ ShallowCopy: use,
+ }
+ case *spb.MethodCall:
+ e.Use = spb.Use_builder{
+ Type: spb.Use_METHOD_CALL,
+ MethodCall: use,
+ }.Build()
+ default:
+ panic(fmt.Sprintf("Bad 'use' type: %T", use))
+ }
+ return e
+ }
+ directFieldAccess := func(fieldName, shortType, longType string) *spb.Use {
+ return &spb.Use{
+ Type: spb.Use_DIRECT_FIELD_ACCESS,
+ DirectFieldAccess: &spb.FieldAccess{
+ FieldName: fieldName,
+ FieldType: &spb.Type{
+ ShortName: shortType,
+ LongName: longType,
+ },
+ },
+ }
+ }
+ typeMissing := func(loc *spb.Location, expr *spb.Expression) *spb.Entry {
+ return spb.Entry_builder{
+ Status: spb.Status_builder{
+ Type: spb.Status_FAIL,
+ Error: "type information missing; are dependencies in a silo?",
+ }.Build(),
+ Location: loc,
+ Level: toRewriteLevel(None),
+ Expr: expr,
+ }.Build()
+ }
+
+ callStmt := expr(&dst.CallExpr{}, &dst.ExprStmt{})
+ callCall := expr(&dst.CallExpr{}, &dst.CallExpr{})
+ callConv := func(dstType, fname, fpkg, fsig string, c spb.Conversion_Context) *spb.Conversion {
+ return &spb.Conversion{
+ DestTypeName: dstType,
+ FuncArg: &spb.FuncArg{
+ FunctionName: fname,
+ PackagePath: fpkg,
+ Signature: fsig,
+ },
+ Context: c,
+ }
+ }
+ retStmt := expr(&dst.ReturnStmt{}, &dst.BlockStmt{})
+
+ assignStmt := expr(&dst.AssignStmt{}, &dst.BlockStmt{})
+ assignConv := func(dstType string) *spb.Conversion {
+ return &spb.Conversion{
+ DestTypeName: dstType,
+ Context: spb.Conversion_ASSIGNMENT,
+ }
+ }
+
+ kvCLit := expr(&dst.KeyValueExpr{}, &dst.CompositeLit{})
+ starCLit := expr(&dst.StarExpr{}, &dst.CompositeLit{})
+ identCLit := expr(&dst.Ident{}, &dst.CompositeLit{})
+ elemConv := func(dstType string) *spb.Conversion {
+ return &spb.Conversion{
+ DestTypeName: dstType,
+ Context: spb.Conversion_COMPOSITE_LITERAL_ELEMENT,
+ }
+ }
+
+ selAssign := expr(&dst.SelectorExpr{}, &dst.AssignStmt{})
+
+ cLitUnary := expr(&dst.CompositeLit{}, &dst.UnaryExpr{})
+ cLitCLit := expr(&dst.CompositeLit{}, &dst.CompositeLit{})
+ emptyLiteral := &spb.Constructor{Type: spb.Constructor_EMPTY_LITERAL}
+ nonEmptyLiteral := &spb.Constructor{Type: spb.Constructor_NONEMPTY_LITERAL}
+ builderLiteral := &spb.Constructor{Type: spb.Constructor_BUILDER}
+
+ tests := []test{{
+ desc: "direct field accesses",
+ extra: extra,
+ in: `
+// simple proto2/proto3 access
+_ = m2.S
+m2.S = nil
+_ = m3.S
+m3.S = ""
+
+// repeated fields
+m3.Ms[0] = nil
+m3.Ms = append(m3.Ms, m3)
+
+// multiple selector expressions
+m3.M.M.S = ""
+
+// accessing a method is not a direct field access
+_ = m3.GetS
+
+// direct field access in non-protos don't count
+var s NotAProto
+_ = s.S
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ // simple proto2/proto3 access
+ entry(loc(3, 5, 3, 9), m2, selAssign, directFieldAccess("S", "*string", "*string")),
+ entry(loc(4, 1, 4, 5), m2, selAssign, directFieldAccess("S", "*string", "*string")),
+ entry(loc(5, 5, 5, 9), m3, selAssign, directFieldAccess("S", "string", "string")),
+ entry(loc(6, 1, 6, 5), m3, selAssign, directFieldAccess("S", "string", "string")),
+ // repeated fields
+ entry(loc(9, 1, 9, 6), m3, expr(&dst.SelectorExpr{}, &dst.IndexExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
+ entry(loc(10, 1, 10, 6), m3, selAssign, directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
+ entry(loc(10, 16, 10, 21), m3, expr(&dst.SelectorExpr{}, &dst.CallExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
+ // multiple selector expressions
+ entry(loc(13, 1, 13, 9), m3, selAssign, directFieldAccess("S", "string", "string")),
+ entry(loc(13, 1, 13, 7), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
+ entry(loc(13, 1, 13, 5), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
+ },
+ },
+ }, {
+ desc: "conversion in call expression",
+ extra: extra + `
+func retProto() *pb2.M2 { return nil }
+func protoIn(*pb2.M2) { }
+func protoIn2(*pb2.M2, *pb2.M2) { }
+func efaceIn(interface{}) { }
+func efaceIn2(a,b interface{}) { }
+func efaceVararg(format string, args ...interface{}) { }
+func msgVararg(format string, args ...proto.Message) { }
+
+type T struct{}
+func (T) Method(interface{}) {}
+`,
+ in: `
+protoIn(m2) // ignored: no conversion
+protoIn(&pb2.M2{}) // ignored: no conversion
+
+efaceIn(notAProto) // ignored: not a proto
+
+efaceIn(m2)
+efaceIn2(m2, &pb2.M2{})
+
+proto.Clone(m2)
+proto.Marshal(m2)
+
+proto.Clone(proto.Message(m2))
+proto.Marshal(proto.Message(m2))
+
+efaceVararg("", m2, m2)
+msgVararg("", m2)
+
+T{}.Method(m2)
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(3, 10, 3, 18), m2Val, cLitUnary, emptyLiteral),
+ entry(loc(7, 1, 7, 12), m2, callStmt, callConv("interface{}", "efaceIn", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(8, 15, 8, 23), m2Val, cLitUnary, emptyLiteral),
+ entry(loc(10, 1, 10, 16), m2, callStmt, callConv(protoMsg, "Clone", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") "+protoMsg, spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(11, 1, 11, 18), m2, callStmt, callConv(protoMsg, "Marshal", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") ([]byte, error)", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(13, 13, 13, 30), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)),
+ entry(loc(14, 15, 14, 32), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)),
+ entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(17, 1, 17, 18), m2, callStmt, callConv(protoMsg, "msgVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ..."+protoMsg+")", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(19, 1, 19, 15), m2, callStmt, callConv("interface{}", "Method", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ },
+ },
+ }, {
+ desc: "conversion in assignment",
+ extra: `
+func multival() (interface{}, *pb2.M2, int, *pb2.M2) {
+ return nil, nil, 0, nil
+}
+`,
+ in: `
+x := m2 // ignored: no conversion
+var mm *pb2.M2 = m2 // ignored: no conversion
+
+var in interface{}
+in = m2
+
+var min proto.Message
+min = m3
+
+var n int
+n, in = 1, m2
+in, n2 := m2, 0
+in, in = m2, m3
+
+var m map[string]*pb2.M2
+in, ok := m[""]
+in, _ = m[""]
+
+in, in, n, in = multival() // eface->eface, *pb.M->eface, int->int, *pb.M->eface
+
+_, _, _, _, _, _, _ = mm, n, n2, ok, x, in, min
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(6, 1, 6, 8), m2, assignStmt, assignConv("interface{}")), // in = m2
+ entry(loc(9, 1, 9, 9), m3, assignStmt, assignConv(protoMsg)), // min = m3
+ entry(loc(12, 1, 12, 14), m2, assignStmt, assignConv("interface{}")), // n, in = 1, m2
+ entry(loc(13, 1, 13, 16), m2, assignStmt, assignConv("interface{}")), // in, n2 := m2, 0
+ entry(loc(14, 1, 14, 16), m2, assignStmt, assignConv("interface{}")), // in, in = m2, m3
+ entry(loc(14, 1, 14, 16), m3, assignStmt, assignConv("interface{}")), // in, in = m2, m3
+ entry(loc(17, 1, 17, 16), m2, assignStmt, assignConv("interface{}")), // in, ok := m[""]
+ entry(loc(18, 1, 18, 14), m2, assignStmt, assignConv("interface{}")), // in, _ = m[""]
+ entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : second arg
+ entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : last arg
+ },
+ },
+ }, {
+ desc: "conversion in construction",
+ in: `
+type t struct {
+ eface interface{}
+ msg proto.Message
+ m *pb2.M2
+}
+
+_ = t{
+ eface: m2,
+ msg: m3,
+ m: m2, // ignore: no conversion
+}
+
+_ = &t{
+ eface: m2,
+ msg: m3,
+ m: m2, // ignore: no conversion
+}
+
+_ = &t{m: m2} // ignore: no conversions
+_ = &t{eface: m2}
+
+_ = []struct{m interface{}}{{m: m2}}
+_ = []struct{m *pb2.M2}{{m: m2}} // ignore: no conversion
+
+_ = map[int]interface{}{0: m2}
+_ = map[interface{}]int{m2: 0}
+_ = map[interface{}]interface{}{m2: m2}
+
+_ = [...]interface{}{0:m2}
+_ = []interface{}{0:m2}
+
+_ = &t{m2,m2,m2} // 2 findings + 1 ignored (no conversion)
+_ = []interface{}{m2}
+_ = []struct{m interface{}}{{m2}}
+
+_ = []*t{{m2, m2, m2}}
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(9, 3, 9, 12), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(10, 3, 10, 10), m3, kvCLit, elemConv(protoMsg)),
+ entry(loc(15, 3, 15, 12), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(16, 3, 16, 10), m3, kvCLit, elemConv(protoMsg)),
+ entry(loc(21, 8, 21, 17), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(23, 30, 23, 35), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(26, 25, 26, 30), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(27, 25, 27, 30), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(30, 22, 30, 26), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(31, 19, 31, 23), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(33, 8, 33, 10), m2, identCLit, elemConv("interface{}")),
+ entry(loc(33, 11, 33, 13), m2, identCLit, elemConv(protoMsg)),
+ entry(loc(34, 19, 34, 21), m2, identCLit, elemConv("interface{}")),
+ entry(loc(35, 30, 35, 32), m2, identCLit, elemConv("interface{}")),
+ entry(loc(37, 11, 37, 13), m2, identCLit, elemConv("interface{}")),
+ entry(loc(37, 15, 37, 17), m2, identCLit, elemConv(protoMsg)),
+ },
+ },
+ }, {
+ desc: "conversions to unsafe.Pointer",
+ extra: `
+func f(unsafe.Pointer) {}
+func g(*pb2.M2) unsafe.Pointer{ return nil }
+`,
+ in: `
+_ = unsafe.Pointer(m2)
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 5, 2, 23), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.Conversion{
+ DestTypeName: "unsafe.Pointer",
+ Context: spb.Conversion_EXPLICIT,
+ }),
+ },
+ },
+ }, {
+ desc: "composite types: contexts",
+ extra: `
+type s struct {m *pb2.M2}
+func f(interface{}) {}
+func g() (int, uintptr, unsafe.Pointer) {
+ for {
+ return 0, 0, unsafe.Pointer(&pb2.M2{})
+ }
+ return 0, 0, nil
+}
+`,
+ in: `
+// Various contexts:
+f(&s{m: m2}) // conversion in call arg
+var in interface{}
+in = &s{m2} // conversion in assignment
+in = struct{s *s}{s: &s{m2}}
+_ = struct{s interface{}}{s: m2}
+_ = struct{s interface{}}{m2}
+f(unsafe.Pointer(&m2))
+in = <-make(chan *pb2.M2) // assignment from *pb.M to interface{} in assignment
+make(chan interface{}) <- m2
+_ = func() interface{} {
+ if true {
+ return m2
+ }
+ return nil
+}()
+
+type namedChan chan interface{}
+make(namedChan) <- m2
+
+_ = in
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(3, 1, 3, 13), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(5, 1, 5, 12), m2, assignStmt, assignConv("interface{}")),
+ entry(loc(6, 1, 6, 29), m2, assignStmt, assignConv("interface{}")),
+ entry(loc(7, 27, 7, 32), m2, kvCLit, elemConv("interface{}")),
+ entry(loc(8, 27, 8, 29), m2, expr(&dst.Ident{}, &dst.CompositeLit{}), elemConv("interface{}")),
+ entry(loc(9, 3, 9, 22), m2, expr(&dst.CallExpr{}, &dst.CallExpr{}), &spb.Conversion{
+ DestTypeName: "unsafe.Pointer",
+ Context: spb.Conversion_EXPLICIT,
+ }),
+ entry(loc(10, 1, 10, 26), m2, assignStmt, assignConv("interface{}")),
+ entry(loc(11, 1, 11, 29), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{
+ DestTypeName: "interface{}",
+ Context: spb.Conversion_CHAN_SEND,
+ }),
+ entry(loc(14, 3, 14, 12), m2, retStmt, &spb.Conversion{
+ DestTypeName: "interface{}",
+ Context: spb.Conversion_FUNC_RET,
+ }),
+ entry(loc(20, 1, 20, 22), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{
+ DestTypeName: "interface{}",
+ Context: spb.Conversion_CHAN_SEND,
+ }),
+ // This is for function "g" which is defined in "extra" (outside "in") and hence the line number is out of range.
+ entry(loc(31, 16, 31, 41), m2, expr(&dst.CallExpr{}, &dst.ReturnStmt{}), &spb.Conversion{
+ DestTypeName: "unsafe.Pointer",
+ Context: spb.Conversion_EXPLICIT,
+ }),
+ entry(loc(31, 32, 31, 40), m2Val, cLitUnary, emptyLiteral),
+ },
+ },
+ }, {
+ desc: "composite types: types",
+ extra: `
+func f(interface{}) {}
+func g(_, _ interface{}) {}
+`,
+ in: `
+f(&m2)
+f([]*pb2.M2{})
+f([1]*pb2.M2{{}})
+f(map[int]*pb2.M2{})
+f(map[*pb2.M2]int{})
+g(func() (_,_ *pb2.M2) { return }()) // generates two entries
+f(make(chan *pb2.M2)) // ignored: no proto value provided to reflection, only type
+f(func(*pb2.M2){}) // ignored: no proto value provided to reflection, only type
+f(func(a,b,c int, m *pb2.M2){}) // ignored: no proto value provided to reflection, only type
+
+type msg pb2.M2
+f(&msg{})
+
+f(struct{m *pb2.M2}{m: m2})
+
+_ = make(map[int]bool, len([]int{})) // make sure that builtins don't mess things up with variadic functions
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 1, 2, 7), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(3, 1, 3, 15), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(4, 1, 4, 18), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(4, 14, 4, 16), m2, cLitCLit, emptyLiteral),
+ entry(loc(5, 1, 5, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(6, 1, 6, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(12, 6, 12, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}),
+ entry(loc(13, 1, 13, 10), statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ entry(loc(13, 4, 13, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), cLitUnary, emptyLiteral),
+ entry(loc(15, 1, 15, 28), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ },
+ },
+ }, {
+ desc: "composite types: constructors",
+ in: `
+m2 = &pb2.M2{S: proto.String("s")}
+_ = pb2.M2{
+ S: proto.String("s"),
+ B: proto.Bool(true),
+}
+_ = pb2.M2_builder{
+ S: proto.String("builder"),
+}.Build()
+m3s := []*pb3.M3{
+ {S: "pointer"},
+ {},
+ pb3.M3_builder{}.Build(),
+}
+m3s = append(m3s, &pb3.M3{})
+
+_ = []pb3.M3{
+ {S: "shallow"},
+ {},
+}
+
+type NotMsg struct{ M *pb2.M2 }
+_ = &NotMsg{ M: &pb2.M2{} }
+_ = NotMsg{}
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 7, 2, 35), m2Val, cLitUnary, nonEmptyLiteral),
+ entry(loc(3, 1, 6, 2), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(3, 5, 6, 2), m2Val, expr(&dst.CompositeLit{}, &dst.AssignStmt{}), nonEmptyLiteral),
+ entry(loc(7, 5, 9, 2), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral),
+ entry(loc(11, 2, 11, 16), m3, cLitCLit, nonEmptyLiteral),
+ entry(loc(12, 2, 12, 4), m3, cLitCLit, emptyLiteral),
+ entry(loc(13, 2, 13, 18), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral),
+ entry(loc(15, 20, 15, 28), m3Val, cLitUnary, emptyLiteral),
+ entry(loc(18, 2, 18, 16), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(19, 2, 19, 4), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(18, 2, 18, 16), m3Val, cLitCLit, nonEmptyLiteral),
+ entry(loc(19, 2, 19, 4), m3Val, cLitCLit, emptyLiteral),
+ entry(loc(23, 18, 23, 26), m2Val, cLitUnary, emptyLiteral),
+ },
+ },
+ }, {
+ desc: "type assertions",
+ extra: `type NotAProto struct {
+ S *string
+ Field struct{}
+}`,
+ in: `
+var in interface{}
+_ = in.(*pb3.M3)
+_ = in.(*NotAProto)
+
+switch in.(type) {}
+switch n := in.(type) { case int: _ = n }
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(3, 5, 3, 17), m3, expr(&dst.TypeAssertExpr{}, &dst.AssignStmt{}), &spb.TypeAssertion{
+ SrcType: &spb.Type{
+ ShortName: "interface{}",
+ LongName: "interface{}",
+ },
+ }),
+ },
+ },
+ }, {
+ desc: "type defs",
+ extra: `type NotAProto struct {
+ S *string
+ Field struct{}
+}`,
+ in: `
+type alias = *pb3.M3 // ignored
+type notProto NotAProto // ignored
+type myProto pb2.M2
+type myProtoPtr *pb3.M3
+type myProtoPtr2 myProtoPtr
+type myProtoPtr3 *myProtoPtr // ignored: pointer to pointer
+
+// Composite types are not interesting to us.
+type ignored1 struct { _ *pb2.M2 }
+type ignored2 func(*pb3.M3)
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(4, 6, 4, 20), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProto")}),
+ entry(loc(5, 6, 5, 24), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr")}),
+ entry(loc(6, 6, 6, 28), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr2")}),
+ },
+ },
+ }, {
+ desc: "type embedding",
+ extra: `type NotAProto struct {
+ S *string
+ Field struct{}
+}`,
+ in: `
+type ignored struct {
+ NotAProto
+ Named *pb3.M3
+ _ *pb3.M3
+}
+
+type T struct {
+ n int
+ *pb3.M3
+}
+
+type U struct {
+ _, _ int
+ _ int
+ pb3.M3
+}
+
+type V struct {
+ _, _, _ int
+ T // ignored
+}
+
+type W struct {
+ _, _, _ int
+ _ func(*pb3.M3) // ignored
+}
+
+type named pb3.M3
+type X struct {
+ named
+}
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(10, 2, 10, 9), m3, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 1}),
+ entry(loc(16, 2, 16, 8), m3Val, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 3}),
+ entry(loc(29, 6, 29, 18), m3Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named")}),
+ entry(loc(31, 2, 31, 7), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named"), expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 0}),
+ },
+ },
+ }, {
+ desc: "recursive type",
+ extra: "func f(interface{}){}",
+ in: `
+type S struct {
+ S *S
+ M *pb2.M2
+}
+f(&S{})
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(6, 1, 6, 8), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
+ },
+ },
+ }, {
+ desc: "mismatched number of return values",
+ extra: `func f() (*pb2.M2, *pb2.M2) { return nil, nil }`,
+ in: `
+_ = func() (m interface{}) {
+ m = &pb2.M2{}
+ return
+}
+_ = func() (a,b interface{}) {
+ return f()
+}
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(3, 2, 3, 15), m2, assignStmt, assignConv("interface{}")),
+ entry(loc(3, 7, 3, 15), m2Val, cLitUnary, emptyLiteral),
+ entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{
+ DestTypeName: "interface{}",
+ Context: spb.Conversion_FUNC_RET,
+ }),
+ entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{
+ DestTypeName: "interface{}",
+ Context: spb.Conversion_FUNC_RET,
+ }),
+ },
+ },
+ }, {
+ // Copy a proto message by value. This test doesn't check for copying a
+ // composite type that results in a proto shallow copy).
+ desc: "direct shallow copies",
+ extra: `
+func args(int, pb2.M2) {}
+func ret() (_ int, _ pb2.M2) { return } // naked return so that we don't trigger analysis
+`,
+ in: `
+copy := *m2 // 0: assign-shallow-copy
+copy = *m2 // 1: assign-shallow-copy
+var n int
+n, copy = 0, *m2 // 2: assign-shallow-copy
+_,_ = copy,n // 3: assign-shallow-copy
+
+args(0, *m2) // 4: call-argument-shallow-copy
+
+args(ret()) // 5: call-argument-shallow-copy
+
+func() (int, pb2.M2) {
+ return 0, *m2 // 6: call-argument-return-shallow-copy
+}()
+
+func() (int, pb2.M2) {
+ return ret() // 7: call-argument-return-shallow-copy
+}()
+
+(*m2).GetS() // ignored: non-pointer receiver is fine
+
+m := map[string]pb2.M2{
+ "": *m2, // 8: composite-literal-shallow-copy
+}
+copy, _ = m[""] // 9: assign-shallow-copy
+
+s := &struct {
+ m pb2.M2
+} {
+ m: *m2, // 11: composite-literal-shallow-copy
+}
+_ = s
+
+ch := make(chan pb2.M2)
+ch <- *m2 // 12: chan-send-shallow-copy
+
+var in interface{} = *m2
+_ = in
+
+_ = []*struct{m pb2.M2}{
+ {*m2}, // 14: composite-literal-shallow-copy
+ {m: *m2}, // 16: composite-literal-shallow-copy
+}
+_ = []struct{m pb2.M2}{
+ {*m2}, // 17,18: composite-literal-shallow-copy of the entire struct '{*m2}' and of the message itself '*m2'
+ {m: *m2}, // 20,22: composite-literal-shallow-copy of the entire struct '{m: *m2}' and of the message itself '*m2'
+}
+_ = []pb2.M2{*m2} // 23: composite-literal-shallow-copy of the element '*m2'
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 1, 2, 12), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(3, 1, 3, 11), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(5, 1, 5, 17), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(6, 1, 6, 13), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(8, 1, 8, 13), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
+ entry(loc(10, 1, 10, 12), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
+ entry(loc(13, 2, 13, 15), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}),
+ entry(loc(17, 2, 17, 14), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}),
+ entry(loc(23, 2, 23, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(25, 1, 25, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(30, 2, 30, 8), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(35, 1, 35, 10), m2Val, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.ShallowCopy{Type: spb.ShallowCopy_CHAN_SEND}),
+ entry(loc(41, 3, 41, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(42, 3, 42, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(45, 2, 45, 7), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(46, 2, 46, 10), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(45, 3, 45, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(46, 3, 46, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ entry(loc(48, 14, 48, 17), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
+ },
+ },
+ }, {
+ // Copy a container with a proto struct.
+ desc: "indirect shallow copies",
+ extra: `
+type msg pb2.M2
+type S struct{m msg}
+func args(_, _ S){}
+func argsp(_, _ *S){}`,
+ in: `
+s := S{} // 0: shallow copy in definition
+_ = s // 2: shallow copy in assignment
+args(func() (_, _ S) { return }()) // 3,4: shallow via a tuple (twice because there are two values in the tuple)
+_ = [1]pb2.M2{} // 5: copy an array
+
+// Those are OK because of the indirection
+sp := &S{}
+_ = sp
+argsp(func() (_, _ *S) { return }())
+_ = [1]*pb2.M2{}
+
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 1, 2, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(3, 1, 3, 6), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+ entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
+ entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
+ entry(loc(5, 1, 5, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
+
+ // "type S struct{m msg}" definition:
+ entry(loc(17, 6, 17, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}),
+ },
+ },
+ }, {
+ desc: "type information missing",
+ in: `
+_ = siloedpb.Message{}
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ typeMissing(loc(2, 1, 2, 23), assignStmt),
+ typeMissing(loc(2, 1, 2, 23), assignStmt),
+ typeMissing(loc(2, 5, 2, 23), expr(&dst.CompositeLit{}, &dst.AssignStmt{})),
+ typeMissing(loc(2, 5, 2, 21), expr(&dst.SelectorExpr{}, &dst.CompositeLit{})),
+ typeMissing(loc(4, 2, 4, 27), assignStmt),
+ typeMissing(loc(4, 2, 4, 27), assignStmt),
+ },
+ },
+ }, {
+ // The method GetFoo for a oneof field foo only exists in the OPEN API.
+ desc: "GetOneof",
+ in: `
+switch x := m2.GetOneofField().(type) {
+ case *pb2.M2_StringOneof:
+ _ = x
+ default:
+}
+_ = m3.GetOneofField()
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 13, 2, 31), m2, expr(&dst.CallExpr{}, &dst.TypeAssertExpr{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}),
+ entry(loc(7, 5, 7, 23), m3, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}),
+ },
+ },
+ }, {
+ desc: "GetBuild",
+ in: `
+_ = m2.GetBuild()
+`,
+ wantStats: map[Level][]*spb.Entry{
+ None: []*spb.Entry{
+ entry(loc(2, 5, 2, 18), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetBuild", Type: spb.MethodCall_GET_BUILD}),
+ },
+ },
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.desc, func(t *testing.T) {
+ if tt.skip != "" {
+ t.Skip(tt.skip)
+ }
+ in := NewSrc(tt.in, tt.extra)
+ statsOnly := []Level{}
+ _, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, statsOnly)
+ if err != nil {
+ t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", tt.in, err, in)
+ }
+ for _, lvl := range []Level{None} {
+ want, ok := tt.wantStats[lvl]
+ if !ok {
+ continue
+ }
+ if len(want) != len(got[lvl]) {
+ t.Errorf("len(want)=%d != len(got[lvl])=%d", len(want), len(got[lvl]))
+ }
+ for idx := range want {
+ if diff := cmp.Diff(want[idx], got[lvl][idx], protocmp.Transform()); diff != "" {
+ // We don't print got/want because it's a lot of output and the diff is almost always enough.
+ t.Errorf("[%s] fixSources(level=%s, message %d) = diff:\n%s", tt.desc, lvl, idx, diff)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestSliceLiteral(t *testing.T) {
+ const src = `
+for _, _ = range map[*[]*pb2.M2]string{
+ {}: "UNKNOWN_NOTIFICATION_TYPE",
+ {
+ {
+ S: nil,
+ },
+ }: "UNKNOWN_NOTIFICATION_TYPE",
+} {
+ panic("irrelevant")
+}
+`
+ in := NewSrc(src, "")
+ _, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, []Level{Green, Yellow, Red})
+ if err != nil {
+ t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", src, err, in)
+ }
+ t.Logf("got: %v", got)
+}
diff --git a/internal/fix/testdata/fake/fake.go b/internal/fix/testdata/fake/fake.go
new file mode 100644
index 0000000..57ef425
--- /dev/null
+++ b/internal/fix/testdata/fake/fake.go
@@ -0,0 +1,26 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto"
+import pb3 "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto"
+import proto "google.golang.org/protobuf/proto"
+import "unsafe"
+import "context"
+
+var _ unsafe.Pointer
+var _ = proto.String
+var _ = context.Background
+
+func test_function() {
+ m2 := new(pb2.M2)
+ m2a := new(pb2.M2)
+ _, _ = m2, m2a
+ m3 := new(pb3.M3)
+ _ = m3
+ _ = "TEST CODE STARTS HERE"
+
+ _ = "TEST CODE ENDS HERE"
+}
diff --git a/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go b/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go
new file mode 100644
index 0000000..499c465
--- /dev/null
+++ b/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go
@@ -0,0 +1,3538 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: proto2test.proto
+
+package proto2test_go_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ _ "google.golang.org/protobuf/types/gofeaturespb"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type M2_Enum int32
+
+const (
+ M2_E_VAL M2_Enum = 0
+)
+
+// Enum value maps for M2_Enum.
+var (
+ M2_Enum_name = map[int32]string{
+ 0: "E_VAL",
+ }
+ M2_Enum_value = map[string]int32{
+ "E_VAL": 0,
+ }
+)
+
+func (x M2_Enum) Enum() *M2_Enum {
+ p := new(M2_Enum)
+ *p = x
+ return p
+}
+
+func (x M2_Enum) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (M2_Enum) Descriptor() protoreflect.EnumDescriptor {
+ return file_proto2test_proto_enumTypes[0].Descriptor()
+}
+
+func (M2_Enum) Type() protoreflect.EnumType {
+ return &file_proto2test_proto_enumTypes[0]
+}
+
+func (x M2_Enum) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use M2_Enum.Descriptor instead.
+func (M2_Enum) EnumDescriptor() ([]byte, []int) {
+ return file_proto2test_proto_rawDescGZIP(), []int{1, 0}
+}
+
+type OtherProto2_OtherEnum int32
+
+const (
+ OtherProto2_E_VAL OtherProto2_OtherEnum = 0
+)
+
+// Enum value maps for OtherProto2_OtherEnum.
+var (
+ OtherProto2_OtherEnum_name = map[int32]string{
+ 0: "E_VAL",
+ }
+ OtherProto2_OtherEnum_value = map[string]int32{
+ "E_VAL": 0,
+ }
+)
+
+func (x OtherProto2_OtherEnum) Enum() *OtherProto2_OtherEnum {
+ p := new(OtherProto2_OtherEnum)
+ *p = x
+ return p
+}
+
+func (x OtherProto2_OtherEnum) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (OtherProto2_OtherEnum) Descriptor() protoreflect.EnumDescriptor {
+ return file_proto2test_proto_enumTypes[1].Descriptor()
+}
+
+func (OtherProto2_OtherEnum) Type() protoreflect.EnumType {
+ return &file_proto2test_proto_enumTypes[1]
+}
+
+func (x OtherProto2_OtherEnum) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use OtherProto2_OtherEnum.Descriptor instead.
+func (OtherProto2_OtherEnum) EnumDescriptor() ([]byte, []int) {
+ return file_proto2test_proto_rawDescGZIP(), []int{2, 0}
+}
+
+type DoNotMigrateMe struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DoNotMigrateMe) Reset() {
+ *x = DoNotMigrateMe{}
+ mi := &file_proto2test_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DoNotMigrateMe) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DoNotMigrateMe) ProtoMessage() {}
+
+func (x *DoNotMigrateMe) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DoNotMigrateMe.ProtoReflect.Descriptor instead.
+func (*DoNotMigrateMe) Descriptor() ([]byte, []int) {
+ return file_proto2test_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *DoNotMigrateMe) GetB() bool {
+ if x != nil && x.B != nil {
+ return *x.B
+ }
+ return false
+}
+
+type M2 struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"`
+ Bytes []byte `protobuf:"bytes,2,opt,name=bytes" json:"bytes,omitempty"`
+ F32 *float32 `protobuf:"fixed32,3,opt,name=f32" json:"f32,omitempty"`
+ F64 *float64 `protobuf:"fixed64,4,opt,name=f64" json:"f64,omitempty"`
+ I32 *int32 `protobuf:"varint,5,opt,name=i32" json:"i32,omitempty"`
+ I64 *int64 `protobuf:"varint,6,opt,name=i64" json:"i64,omitempty"`
+ Ui32 *uint32 `protobuf:"varint,7,opt,name=ui32" json:"ui32,omitempty"`
+ Ui64 *uint64 `protobuf:"varint,8,opt,name=ui64" json:"ui64,omitempty"`
+ S *string `protobuf:"bytes,9,opt,name=s" json:"s,omitempty"`
+ M *M2 `protobuf:"bytes,10,opt,name=m" json:"m,omitempty"`
+ Is []int32 `protobuf:"varint,11,rep,packed,name=is" json:"is,omitempty"`
+ Ms []*M2 `protobuf:"bytes,12,rep,name=ms" json:"ms,omitempty"`
+ Map map[string]bool `protobuf:"bytes,29,rep,name=map" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+ E *M2_Enum `protobuf:"varint,13,opt,name=e,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum" json:"e,omitempty"`
+ // Types that are valid to be assigned to OneofField:
+ //
+ // *M2_StringOneof
+ // *M2_IntOneof
+ // *M2_MsgOneof
+ // *M2_EnumOneof
+ // *M2_BytesOneof
+ OneofField isM2_OneofField `protobuf_oneof:"oneof_field"`
+ // Types that are valid to be assigned to OneofField2:
+ //
+ // *M2_StringOneof2
+ // *M2_IntOneof2
+ // *M2_MsgOneof2
+ // *M2_EnumOneof2
+ // *M2_BytesOneof2
+ OneofField2 isM2_OneofField2 `protobuf_oneof:"oneof_field2"`
+ Build *int32 `protobuf:"varint,24,opt,name=build" json:"build,omitempty"`
+ ProtoMessage_ *int32 `protobuf:"varint,25,opt,name=proto_message,json=protoMessage" json:"proto_message,omitempty"`
+ Reset_ *int32 `protobuf:"varint,26,opt,name=reset" json:"reset,omitempty"`
+ String_ *int32 `protobuf:"varint,27,opt,name=string" json:"string,omitempty"`
+ Descriptor_ *int32 `protobuf:"varint,28,opt,name=descriptor" json:"descriptor,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *M2) Reset() {
+ *x = M2{}
+ mi := &file_proto2test_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *M2) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*M2) ProtoMessage() {}
+
+func (x *M2) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *M2) GetB() bool {
+ if x != nil && x.B != nil {
+ return *x.B
+ }
+ return false
+}
+
+func (x *M2) GetBytes() []byte {
+ if x != nil {
+ return x.Bytes
+ }
+ return nil
+}
+
+func (x *M2) GetF32() float32 {
+ if x != nil && x.F32 != nil {
+ return *x.F32
+ }
+ return 0
+}
+
+func (x *M2) GetF64() float64 {
+ if x != nil && x.F64 != nil {
+ return *x.F64
+ }
+ return 0
+}
+
+func (x *M2) GetI32() int32 {
+ if x != nil && x.I32 != nil {
+ return *x.I32
+ }
+ return 0
+}
+
+func (x *M2) GetI64() int64 {
+ if x != nil && x.I64 != nil {
+ return *x.I64
+ }
+ return 0
+}
+
+func (x *M2) GetUi32() uint32 {
+ if x != nil && x.Ui32 != nil {
+ return *x.Ui32
+ }
+ return 0
+}
+
+func (x *M2) GetUi64() uint64 {
+ if x != nil && x.Ui64 != nil {
+ return *x.Ui64
+ }
+ return 0
+}
+
+func (x *M2) GetS() string {
+ if x != nil && x.S != nil {
+ return *x.S
+ }
+ return ""
+}
+
+func (x *M2) GetM() *M2 {
+ if x != nil {
+ return x.M
+ }
+ return nil
+}
+
+func (x *M2) GetIs() []int32 {
+ if x != nil {
+ return x.Is
+ }
+ return nil
+}
+
+func (x *M2) GetMs() []*M2 {
+ if x != nil {
+ return x.Ms
+ }
+ return nil
+}
+
+func (x *M2) GetMap() map[string]bool {
+ if x != nil {
+ return x.Map
+ }
+ return nil
+}
+
+func (x *M2) GetE() M2_Enum {
+ if x != nil && x.E != nil {
+ return *x.E
+ }
+ return M2_E_VAL
+}
+
+func (x *M2) GetOneofField() isM2_OneofField {
+ if x != nil {
+ return x.OneofField
+ }
+ return nil
+}
+
+func (x *M2) GetStringOneof() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M2_StringOneof); ok {
+ return x.StringOneof
+ }
+ }
+ return ""
+}
+
+func (x *M2) GetIntOneof() int64 {
+ if x != nil {
+ if x, ok := x.OneofField.(*M2_IntOneof); ok {
+ return x.IntOneof
+ }
+ }
+ return 0
+}
+
+func (x *M2) GetMsgOneof() *M2 {
+ if x != nil {
+ if x, ok := x.OneofField.(*M2_MsgOneof); ok {
+ return x.MsgOneof
+ }
+ }
+ return nil
+}
+
+func (x *M2) GetEnumOneof() M2_Enum {
+ if x != nil {
+ if x, ok := x.OneofField.(*M2_EnumOneof); ok {
+ return x.EnumOneof
+ }
+ }
+ return M2_E_VAL
+}
+
+func (x *M2) GetBytesOneof() []byte {
+ if x != nil {
+ if x, ok := x.OneofField.(*M2_BytesOneof); ok {
+ return x.BytesOneof
+ }
+ }
+ return nil
+}
+
+func (x *M2) GetOneofField2() isM2_OneofField2 {
+ if x != nil {
+ return x.OneofField2
+ }
+ return nil
+}
+
+func (x *M2) GetStringOneof2() string {
+ if x != nil {
+ if x, ok := x.OneofField2.(*M2_StringOneof2); ok {
+ return x.StringOneof2
+ }
+ }
+ return ""
+}
+
+func (x *M2) GetIntOneof2() int64 {
+ if x != nil {
+ if x, ok := x.OneofField2.(*M2_IntOneof2); ok {
+ return x.IntOneof2
+ }
+ }
+ return 0
+}
+
+func (x *M2) GetMsgOneof2() *M2 {
+ if x != nil {
+ if x, ok := x.OneofField2.(*M2_MsgOneof2); ok {
+ return x.MsgOneof2
+ }
+ }
+ return nil
+}
+
+func (x *M2) GetEnumOneof2() M2_Enum {
+ if x != nil {
+ if x, ok := x.OneofField2.(*M2_EnumOneof2); ok {
+ return x.EnumOneof2
+ }
+ }
+ return M2_E_VAL
+}
+
+func (x *M2) GetBytesOneof2() []byte {
+ if x != nil {
+ if x, ok := x.OneofField2.(*M2_BytesOneof2); ok {
+ return x.BytesOneof2
+ }
+ }
+ return nil
+}
+
+func (x *M2) GetBuild_() int32 {
+ if x != nil && x.Build != nil {
+ return *x.Build
+ }
+ return 0
+}
+
+// Deprecated: Use GetBuild_ instead.
+func (x *M2) GetBuild() int32 {
+ return x.GetBuild_()
+}
+
+func (x *M2) GetProtoMessage() int32 {
+ if x != nil && x.ProtoMessage_ != nil {
+ return *x.ProtoMessage_
+ }
+ return 0
+}
+
+// Deprecated: Use GetProtoMessage instead.
+func (x *M2) GetProtoMessage_() int32 {
+ return x.GetProtoMessage()
+}
+
+func (x *M2) GetReset() int32 {
+ if x != nil && x.Reset_ != nil {
+ return *x.Reset_
+ }
+ return 0
+}
+
+// Deprecated: Use GetReset instead.
+func (x *M2) GetReset_() int32 {
+ return x.GetReset()
+}
+
+func (x *M2) GetString() int32 {
+ if x != nil && x.String_ != nil {
+ return *x.String_
+ }
+ return 0
+}
+
+// Deprecated: Use GetString instead.
+func (x *M2) GetString_() int32 {
+ return x.GetString()
+}
+
+func (x *M2) GetDescriptor() int32 {
+ if x != nil && x.Descriptor_ != nil {
+ return *x.Descriptor_
+ }
+ return 0
+}
+
+// Deprecated: Use GetDescriptor instead.
+func (x *M2) GetDescriptor_() int32 {
+ return x.GetDescriptor()
+}
+
+func (x *M2) SetB(v bool) {
+ x.B = &v
+}
+
+func (x *M2) SetBytes(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.Bytes = v
+}
+
+func (x *M2) SetF32(v float32) {
+ x.F32 = &v
+}
+
+func (x *M2) SetF64(v float64) {
+ x.F64 = &v
+}
+
+func (x *M2) SetI32(v int32) {
+ x.I32 = &v
+}
+
+func (x *M2) SetI64(v int64) {
+ x.I64 = &v
+}
+
+func (x *M2) SetUi32(v uint32) {
+ x.Ui32 = &v
+}
+
+func (x *M2) SetUi64(v uint64) {
+ x.Ui64 = &v
+}
+
+func (x *M2) SetS(v string) {
+ x.S = &v
+}
+
+func (x *M2) SetM(v *M2) {
+ x.M = v
+}
+
+func (x *M2) SetIs(v []int32) {
+ x.Is = v
+}
+
+func (x *M2) SetMs(v []*M2) {
+ x.Ms = v
+}
+
+func (x *M2) SetMap(v map[string]bool) {
+ x.Map = v
+}
+
+func (x *M2) SetE(v M2_Enum) {
+ x.E = &v
+}
+
+func (x *M2) SetStringOneof(v string) {
+ x.OneofField = &M2_StringOneof{v}
+}
+
+func (x *M2) SetIntOneof(v int64) {
+ x.OneofField = &M2_IntOneof{v}
+}
+
+func (x *M2) SetMsgOneof(v *M2) {
+ if v == nil {
+ x.OneofField = nil
+ return
+ }
+ x.OneofField = &M2_MsgOneof{v}
+}
+
+func (x *M2) SetEnumOneof(v M2_Enum) {
+ x.OneofField = &M2_EnumOneof{v}
+}
+
+func (x *M2) SetBytesOneof(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.OneofField = &M2_BytesOneof{v}
+}
+
+func (x *M2) SetStringOneof2(v string) {
+ x.OneofField2 = &M2_StringOneof2{v}
+}
+
+func (x *M2) SetIntOneof2(v int64) {
+ x.OneofField2 = &M2_IntOneof2{v}
+}
+
+func (x *M2) SetMsgOneof2(v *M2) {
+ if v == nil {
+ x.OneofField2 = nil
+ return
+ }
+ x.OneofField2 = &M2_MsgOneof2{v}
+}
+
+func (x *M2) SetEnumOneof2(v M2_Enum) {
+ x.OneofField2 = &M2_EnumOneof2{v}
+}
+
+func (x *M2) SetBytesOneof2(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.OneofField2 = &M2_BytesOneof2{v}
+}
+
+func (x *M2) SetBuild_(v int32) {
+ x.Build = &v
+}
+
+func (x *M2) SetProtoMessage(v int32) {
+ x.ProtoMessage_ = &v
+}
+
+func (x *M2) SetReset(v int32) {
+ x.Reset_ = &v
+}
+
+func (x *M2) SetString(v int32) {
+ x.String_ = &v
+}
+
+func (x *M2) SetDescriptor(v int32) {
+ x.Descriptor_ = &v
+}
+
+func (x *M2) HasB() bool {
+ if x == nil {
+ return false
+ }
+ return x.B != nil
+}
+
+func (x *M2) HasBytes() bool {
+ if x == nil {
+ return false
+ }
+ return x.Bytes != nil
+}
+
+func (x *M2) HasF32() bool {
+ if x == nil {
+ return false
+ }
+ return x.F32 != nil
+}
+
+func (x *M2) HasF64() bool {
+ if x == nil {
+ return false
+ }
+ return x.F64 != nil
+}
+
+func (x *M2) HasI32() bool {
+ if x == nil {
+ return false
+ }
+ return x.I32 != nil
+}
+
+func (x *M2) HasI64() bool {
+ if x == nil {
+ return false
+ }
+ return x.I64 != nil
+}
+
+func (x *M2) HasUi32() bool {
+ if x == nil {
+ return false
+ }
+ return x.Ui32 != nil
+}
+
+func (x *M2) HasUi64() bool {
+ if x == nil {
+ return false
+ }
+ return x.Ui64 != nil
+}
+
+func (x *M2) HasS() bool {
+ if x == nil {
+ return false
+ }
+ return x.S != nil
+}
+
+func (x *M2) HasM() bool {
+ if x == nil {
+ return false
+ }
+ return x.M != nil
+}
+
+func (x *M2) HasE() bool {
+ if x == nil {
+ return false
+ }
+ return x.E != nil
+}
+
+func (x *M2) HasOneofField() bool {
+ if x == nil {
+ return false
+ }
+ return x.OneofField != nil
+}
+
+func (x *M2) HasStringOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M2_StringOneof)
+ return ok
+}
+
+func (x *M2) HasIntOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M2_IntOneof)
+ return ok
+}
+
+func (x *M2) HasMsgOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M2_MsgOneof)
+ return ok
+}
+
+func (x *M2) HasEnumOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M2_EnumOneof)
+ return ok
+}
+
+func (x *M2) HasBytesOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M2_BytesOneof)
+ return ok
+}
+
+func (x *M2) HasOneofField2() bool {
+ if x == nil {
+ return false
+ }
+ return x.OneofField2 != nil
+}
+
+func (x *M2) HasStringOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*M2_StringOneof2)
+ return ok
+}
+
+func (x *M2) HasIntOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*M2_IntOneof2)
+ return ok
+}
+
+func (x *M2) HasMsgOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*M2_MsgOneof2)
+ return ok
+}
+
+func (x *M2) HasEnumOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*M2_EnumOneof2)
+ return ok
+}
+
+func (x *M2) HasBytesOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*M2_BytesOneof2)
+ return ok
+}
+
+func (x *M2) HasBuild_() bool {
+ if x == nil {
+ return false
+ }
+ return x.Build != nil
+}
+
+func (x *M2) HasProtoMessage() bool {
+ if x == nil {
+ return false
+ }
+ return x.ProtoMessage_ != nil
+}
+
+func (x *M2) HasReset() bool {
+ if x == nil {
+ return false
+ }
+ return x.Reset_ != nil
+}
+
+func (x *M2) HasString() bool {
+ if x == nil {
+ return false
+ }
+ return x.String_ != nil
+}
+
+func (x *M2) HasDescriptor() bool {
+ if x == nil {
+ return false
+ }
+ return x.Descriptor_ != nil
+}
+
+func (x *M2) ClearB() {
+ x.B = nil
+}
+
+func (x *M2) ClearBytes() {
+ x.Bytes = nil
+}
+
+func (x *M2) ClearF32() {
+ x.F32 = nil
+}
+
+func (x *M2) ClearF64() {
+ x.F64 = nil
+}
+
+func (x *M2) ClearI32() {
+ x.I32 = nil
+}
+
+func (x *M2) ClearI64() {
+ x.I64 = nil
+}
+
+func (x *M2) ClearUi32() {
+ x.Ui32 = nil
+}
+
+func (x *M2) ClearUi64() {
+ x.Ui64 = nil
+}
+
+func (x *M2) ClearS() {
+ x.S = nil
+}
+
+func (x *M2) ClearM() {
+ x.M = nil
+}
+
+func (x *M2) ClearE() {
+ x.E = nil
+}
+
+func (x *M2) ClearOneofField() {
+ x.OneofField = nil
+}
+
+func (x *M2) ClearStringOneof() {
+ if _, ok := x.OneofField.(*M2_StringOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M2) ClearIntOneof() {
+ if _, ok := x.OneofField.(*M2_IntOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M2) ClearMsgOneof() {
+ if _, ok := x.OneofField.(*M2_MsgOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M2) ClearEnumOneof() {
+ if _, ok := x.OneofField.(*M2_EnumOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M2) ClearBytesOneof() {
+ if _, ok := x.OneofField.(*M2_BytesOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M2) ClearOneofField2() {
+ x.OneofField2 = nil
+}
+
+func (x *M2) ClearStringOneof2() {
+ if _, ok := x.OneofField2.(*M2_StringOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *M2) ClearIntOneof2() {
+ if _, ok := x.OneofField2.(*M2_IntOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *M2) ClearMsgOneof2() {
+ if _, ok := x.OneofField2.(*M2_MsgOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *M2) ClearEnumOneof2() {
+ if _, ok := x.OneofField2.(*M2_EnumOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *M2) ClearBytesOneof2() {
+ if _, ok := x.OneofField2.(*M2_BytesOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *M2) ClearBuild_() {
+ x.Build = nil
+}
+
+func (x *M2) ClearProtoMessage() {
+ x.ProtoMessage_ = nil
+}
+
+func (x *M2) ClearReset() {
+ x.Reset_ = nil
+}
+
+func (x *M2) ClearString() {
+ x.String_ = nil
+}
+
+func (x *M2) ClearDescriptor() {
+ x.Descriptor_ = nil
+}
+
+const M2_OneofField_not_set_case case_M2_OneofField = 0
+const M2_StringOneof_case case_M2_OneofField = 14
+const M2_IntOneof_case case_M2_OneofField = 15
+const M2_MsgOneof_case case_M2_OneofField = 16
+const M2_EnumOneof_case case_M2_OneofField = 17
+const M2_BytesOneof_case case_M2_OneofField = 18
+
+func (x *M2) WhichOneofField() case_M2_OneofField {
+ if x == nil {
+ return M2_OneofField_not_set_case
+ }
+ switch x.OneofField.(type) {
+ case *M2_StringOneof:
+ return M2_StringOneof_case
+ case *M2_IntOneof:
+ return M2_IntOneof_case
+ case *M2_MsgOneof:
+ return M2_MsgOneof_case
+ case *M2_EnumOneof:
+ return M2_EnumOneof_case
+ case *M2_BytesOneof:
+ return M2_BytesOneof_case
+ default:
+ return M2_OneofField_not_set_case
+ }
+}
+
+const M2_OneofField2_not_set_case case_M2_OneofField2 = 0
+const M2_StringOneof2_case case_M2_OneofField2 = 19
+const M2_IntOneof2_case case_M2_OneofField2 = 20
+const M2_MsgOneof2_case case_M2_OneofField2 = 21
+const M2_EnumOneof2_case case_M2_OneofField2 = 22
+const M2_BytesOneof2_case case_M2_OneofField2 = 23
+
+func (x *M2) WhichOneofField2() case_M2_OneofField2 {
+ if x == nil {
+ return M2_OneofField2_not_set_case
+ }
+ switch x.OneofField2.(type) {
+ case *M2_StringOneof2:
+ return M2_StringOneof2_case
+ case *M2_IntOneof2:
+ return M2_IntOneof2_case
+ case *M2_MsgOneof2:
+ return M2_MsgOneof2_case
+ case *M2_EnumOneof2:
+ return M2_EnumOneof2_case
+ case *M2_BytesOneof2:
+ return M2_BytesOneof2_case
+ default:
+ return M2_OneofField2_not_set_case
+ }
+}
+
+type M2_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ B *bool
+ Bytes []byte
+ F32 *float32
+ F64 *float64
+ I32 *int32
+ I64 *int64
+ Ui32 *uint32
+ Ui64 *uint64
+ S *string
+ M *M2
+ Is []int32
+ Ms []*M2
+ Map map[string]bool
+ E *M2_Enum
+ // Fields of oneof OneofField:
+ StringOneof *string
+ IntOneof *int64
+ MsgOneof *M2
+ EnumOneof *M2_Enum
+ BytesOneof []byte
+ // -- end of OneofField
+ // Fields of oneof OneofField2:
+ StringOneof2 *string
+ IntOneof2 *int64
+ MsgOneof2 *M2
+ EnumOneof2 *M2_Enum
+ BytesOneof2 []byte
+ // -- end of OneofField2
+ Build_ *int32
+ ProtoMessage *int32
+ Reset *int32
+ String *int32
+ Descriptor *int32
+}
+
+func (b0 M2_builder) Build() *M2 {
+ m0 := &M2{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.B = b.B
+ x.Bytes = b.Bytes
+ x.F32 = b.F32
+ x.F64 = b.F64
+ x.I32 = b.I32
+ x.I64 = b.I64
+ x.Ui32 = b.Ui32
+ x.Ui64 = b.Ui64
+ x.S = b.S
+ x.M = b.M
+ x.Is = b.Is
+ x.Ms = b.Ms
+ x.Map = b.Map
+ x.E = b.E
+ if b.StringOneof != nil {
+ x.OneofField = &M2_StringOneof{*b.StringOneof}
+ }
+ if b.IntOneof != nil {
+ x.OneofField = &M2_IntOneof{*b.IntOneof}
+ }
+ if b.MsgOneof != nil {
+ x.OneofField = &M2_MsgOneof{b.MsgOneof}
+ }
+ if b.EnumOneof != nil {
+ x.OneofField = &M2_EnumOneof{*b.EnumOneof}
+ }
+ if b.BytesOneof != nil {
+ x.OneofField = &M2_BytesOneof{b.BytesOneof}
+ }
+ if b.StringOneof2 != nil {
+ x.OneofField2 = &M2_StringOneof2{*b.StringOneof2}
+ }
+ if b.IntOneof2 != nil {
+ x.OneofField2 = &M2_IntOneof2{*b.IntOneof2}
+ }
+ if b.MsgOneof2 != nil {
+ x.OneofField2 = &M2_MsgOneof2{b.MsgOneof2}
+ }
+ if b.EnumOneof2 != nil {
+ x.OneofField2 = &M2_EnumOneof2{*b.EnumOneof2}
+ }
+ if b.BytesOneof2 != nil {
+ x.OneofField2 = &M2_BytesOneof2{b.BytesOneof2}
+ }
+ x.Build = b.Build_
+ x.ProtoMessage_ = b.ProtoMessage
+ x.Reset_ = b.Reset
+ x.String_ = b.String
+ x.Descriptor_ = b.Descriptor
+ return m0
+}
+
+type case_M2_OneofField protoreflect.FieldNumber
+
+func (x case_M2_OneofField) String() string {
+ md := file_proto2test_proto_msgTypes[1].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type case_M2_OneofField2 protoreflect.FieldNumber
+
+func (x case_M2_OneofField2) String() string {
+ md := file_proto2test_proto_msgTypes[1].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isM2_OneofField interface {
+ isM2_OneofField()
+}
+
+type M2_StringOneof struct {
+ StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,oneof"`
+}
+
+type M2_IntOneof struct {
+ IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,oneof"`
+}
+
+type M2_MsgOneof struct {
+ MsgOneof *M2 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,oneof"`
+}
+
+type M2_EnumOneof struct {
+ EnumOneof M2_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum,oneof"`
+}
+
+type M2_BytesOneof struct {
+ BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,oneof"`
+}
+
+func (*M2_StringOneof) isM2_OneofField() {}
+
+func (*M2_IntOneof) isM2_OneofField() {}
+
+func (*M2_MsgOneof) isM2_OneofField() {}
+
+func (*M2_EnumOneof) isM2_OneofField() {}
+
+func (*M2_BytesOneof) isM2_OneofField() {}
+
+type isM2_OneofField2 interface {
+ isM2_OneofField2()
+}
+
+type M2_StringOneof2 struct {
+ StringOneof2 string `protobuf:"bytes,19,opt,name=string_oneof2,json=stringOneof2,oneof"`
+}
+
+type M2_IntOneof2 struct {
+ IntOneof2 int64 `protobuf:"varint,20,opt,name=int_oneof2,json=intOneof2,oneof"`
+}
+
+type M2_MsgOneof2 struct {
+ MsgOneof2 *M2 `protobuf:"bytes,21,opt,name=msg_oneof2,json=msgOneof2,oneof"`
+}
+
+type M2_EnumOneof2 struct {
+ EnumOneof2 M2_Enum `protobuf:"varint,22,opt,name=enum_oneof2,json=enumOneof2,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum,oneof"`
+}
+
+type M2_BytesOneof2 struct {
+ BytesOneof2 []byte `protobuf:"bytes,23,opt,name=bytes_oneof2,json=bytesOneof2,oneof"`
+}
+
+func (*M2_StringOneof2) isM2_OneofField2() {}
+
+func (*M2_IntOneof2) isM2_OneofField2() {}
+
+func (*M2_MsgOneof2) isM2_OneofField2() {}
+
+func (*M2_EnumOneof2) isM2_OneofField2() {}
+
+func (*M2_BytesOneof2) isM2_OneofField2() {}
+
+type OtherProto2 struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"`
+ Bytes []byte `protobuf:"bytes,2,opt,name=bytes" json:"bytes,omitempty"`
+ F32 *float32 `protobuf:"fixed32,3,opt,name=f32" json:"f32,omitempty"`
+ F64 *float64 `protobuf:"fixed64,4,opt,name=f64" json:"f64,omitempty"`
+ I32 *int32 `protobuf:"varint,5,opt,name=i32" json:"i32,omitempty"`
+ I64 *int64 `protobuf:"varint,6,opt,name=i64" json:"i64,omitempty"`
+ Ui32 *uint32 `protobuf:"varint,7,opt,name=ui32" json:"ui32,omitempty"`
+ Ui64 *uint64 `protobuf:"varint,8,opt,name=ui64" json:"ui64,omitempty"`
+ S *string `protobuf:"bytes,9,opt,name=s" json:"s,omitempty"`
+ M *OtherProto2 `protobuf:"bytes,10,opt,name=m" json:"m,omitempty"`
+ Is []int32 `protobuf:"varint,11,rep,packed,name=is" json:"is,omitempty"`
+ Ms []*OtherProto2 `protobuf:"bytes,12,rep,name=ms" json:"ms,omitempty"`
+ Map map[string]bool `protobuf:"bytes,29,rep,name=map" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+ E *OtherProto2_OtherEnum `protobuf:"varint,13,opt,name=e,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum" json:"e,omitempty"`
+ // Types that are valid to be assigned to OneofField:
+ //
+ // *OtherProto2_StringOneof
+ // *OtherProto2_IntOneof
+ // *OtherProto2_MsgOneof
+ // *OtherProto2_EnumOneof
+ // *OtherProto2_BytesOneof
+ OneofField isOtherProto2_OneofField `protobuf_oneof:"oneof_field"`
+ // Types that are valid to be assigned to OneofField2:
+ //
+ // *OtherProto2_StringOneof2
+ // *OtherProto2_IntOneof2
+ // *OtherProto2_MsgOneof2
+ // *OtherProto2_EnumOneof2
+ // *OtherProto2_BytesOneof2
+ OneofField2 isOtherProto2_OneofField2 `protobuf_oneof:"oneof_field2"`
+ Build *int32 `protobuf:"varint,24,opt,name=build" json:"build,omitempty"`
+ ProtoMessage_ *int32 `protobuf:"varint,25,opt,name=proto_message,json=protoMessage" json:"proto_message,omitempty"`
+ Reset_ *int32 `protobuf:"varint,26,opt,name=reset" json:"reset,omitempty"`
+ String_ *int32 `protobuf:"varint,27,opt,name=string" json:"string,omitempty"`
+ Descriptor_ *int32 `protobuf:"varint,28,opt,name=descriptor" json:"descriptor,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *OtherProto2) Reset() {
+ *x = OtherProto2{}
+ mi := &file_proto2test_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *OtherProto2) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OtherProto2) ProtoMessage() {}
+
+func (x *OtherProto2) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *OtherProto2) GetB() bool {
+ if x != nil && x.B != nil {
+ return *x.B
+ }
+ return false
+}
+
+func (x *OtherProto2) GetBytes() []byte {
+ if x != nil {
+ return x.Bytes
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetF32() float32 {
+ if x != nil && x.F32 != nil {
+ return *x.F32
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetF64() float64 {
+ if x != nil && x.F64 != nil {
+ return *x.F64
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetI32() int32 {
+ if x != nil && x.I32 != nil {
+ return *x.I32
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetI64() int64 {
+ if x != nil && x.I64 != nil {
+ return *x.I64
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetUi32() uint32 {
+ if x != nil && x.Ui32 != nil {
+ return *x.Ui32
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetUi64() uint64 {
+ if x != nil && x.Ui64 != nil {
+ return *x.Ui64
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetS() string {
+ if x != nil && x.S != nil {
+ return *x.S
+ }
+ return ""
+}
+
+func (x *OtherProto2) GetM() *OtherProto2 {
+ if x != nil {
+ return x.M
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetIs() []int32 {
+ if x != nil {
+ return x.Is
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetMs() []*OtherProto2 {
+ if x != nil {
+ return x.Ms
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetMap() map[string]bool {
+ if x != nil {
+ return x.Map
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetE() OtherProto2_OtherEnum {
+ if x != nil && x.E != nil {
+ return *x.E
+ }
+ return OtherProto2_E_VAL
+}
+
+func (x *OtherProto2) GetOneofField() isOtherProto2_OneofField {
+ if x != nil {
+ return x.OneofField
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetStringOneof() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*OtherProto2_StringOneof); ok {
+ return x.StringOneof
+ }
+ }
+ return ""
+}
+
+func (x *OtherProto2) GetIntOneof() int64 {
+ if x != nil {
+ if x, ok := x.OneofField.(*OtherProto2_IntOneof); ok {
+ return x.IntOneof
+ }
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetMsgOneof() *OtherProto2 {
+ if x != nil {
+ if x, ok := x.OneofField.(*OtherProto2_MsgOneof); ok {
+ return x.MsgOneof
+ }
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetEnumOneof() OtherProto2_OtherEnum {
+ if x != nil {
+ if x, ok := x.OneofField.(*OtherProto2_EnumOneof); ok {
+ return x.EnumOneof
+ }
+ }
+ return OtherProto2_E_VAL
+}
+
+func (x *OtherProto2) GetBytesOneof() []byte {
+ if x != nil {
+ if x, ok := x.OneofField.(*OtherProto2_BytesOneof); ok {
+ return x.BytesOneof
+ }
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetOneofField2() isOtherProto2_OneofField2 {
+ if x != nil {
+ return x.OneofField2
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetStringOneof2() string {
+ if x != nil {
+ if x, ok := x.OneofField2.(*OtherProto2_StringOneof2); ok {
+ return x.StringOneof2
+ }
+ }
+ return ""
+}
+
+func (x *OtherProto2) GetIntOneof2() int64 {
+ if x != nil {
+ if x, ok := x.OneofField2.(*OtherProto2_IntOneof2); ok {
+ return x.IntOneof2
+ }
+ }
+ return 0
+}
+
+func (x *OtherProto2) GetMsgOneof2() *OtherProto2 {
+ if x != nil {
+ if x, ok := x.OneofField2.(*OtherProto2_MsgOneof2); ok {
+ return x.MsgOneof2
+ }
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetEnumOneof2() OtherProto2_OtherEnum {
+ if x != nil {
+ if x, ok := x.OneofField2.(*OtherProto2_EnumOneof2); ok {
+ return x.EnumOneof2
+ }
+ }
+ return OtherProto2_E_VAL
+}
+
+func (x *OtherProto2) GetBytesOneof2() []byte {
+ if x != nil {
+ if x, ok := x.OneofField2.(*OtherProto2_BytesOneof2); ok {
+ return x.BytesOneof2
+ }
+ }
+ return nil
+}
+
+func (x *OtherProto2) GetBuild_() int32 {
+ if x != nil && x.Build != nil {
+ return *x.Build
+ }
+ return 0
+}
+
+// Deprecated: Use GetBuild_ instead.
+func (x *OtherProto2) GetBuild() int32 {
+ return x.GetBuild_()
+}
+
+func (x *OtherProto2) GetProtoMessage() int32 {
+ if x != nil && x.ProtoMessage_ != nil {
+ return *x.ProtoMessage_
+ }
+ return 0
+}
+
+// Deprecated: Use GetProtoMessage instead.
+func (x *OtherProto2) GetProtoMessage_() int32 {
+ return x.GetProtoMessage()
+}
+
+func (x *OtherProto2) GetReset() int32 {
+ if x != nil && x.Reset_ != nil {
+ return *x.Reset_
+ }
+ return 0
+}
+
+// Deprecated: Use GetReset instead.
+func (x *OtherProto2) GetReset_() int32 {
+ return x.GetReset()
+}
+
+func (x *OtherProto2) GetString() int32 {
+ if x != nil && x.String_ != nil {
+ return *x.String_
+ }
+ return 0
+}
+
+// Deprecated: Use GetString instead.
+func (x *OtherProto2) GetString_() int32 {
+ return x.GetString()
+}
+
+func (x *OtherProto2) GetDescriptor() int32 {
+ if x != nil && x.Descriptor_ != nil {
+ return *x.Descriptor_
+ }
+ return 0
+}
+
+// Deprecated: Use GetDescriptor instead.
+func (x *OtherProto2) GetDescriptor_() int32 {
+ return x.GetDescriptor()
+}
+
+func (x *OtherProto2) SetB(v bool) {
+ x.B = &v
+}
+
+func (x *OtherProto2) SetBytes(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.Bytes = v
+}
+
+func (x *OtherProto2) SetF32(v float32) {
+ x.F32 = &v
+}
+
+func (x *OtherProto2) SetF64(v float64) {
+ x.F64 = &v
+}
+
+func (x *OtherProto2) SetI32(v int32) {
+ x.I32 = &v
+}
+
+func (x *OtherProto2) SetI64(v int64) {
+ x.I64 = &v
+}
+
+func (x *OtherProto2) SetUi32(v uint32) {
+ x.Ui32 = &v
+}
+
+func (x *OtherProto2) SetUi64(v uint64) {
+ x.Ui64 = &v
+}
+
+func (x *OtherProto2) SetS(v string) {
+ x.S = &v
+}
+
+func (x *OtherProto2) SetM(v *OtherProto2) {
+ x.M = v
+}
+
+func (x *OtherProto2) SetIs(v []int32) {
+ x.Is = v
+}
+
+func (x *OtherProto2) SetMs(v []*OtherProto2) {
+ x.Ms = v
+}
+
+func (x *OtherProto2) SetMap(v map[string]bool) {
+ x.Map = v
+}
+
+func (x *OtherProto2) SetE(v OtherProto2_OtherEnum) {
+ x.E = &v
+}
+
+func (x *OtherProto2) SetStringOneof(v string) {
+ x.OneofField = &OtherProto2_StringOneof{v}
+}
+
+func (x *OtherProto2) SetIntOneof(v int64) {
+ x.OneofField = &OtherProto2_IntOneof{v}
+}
+
+func (x *OtherProto2) SetMsgOneof(v *OtherProto2) {
+ if v == nil {
+ x.OneofField = nil
+ return
+ }
+ x.OneofField = &OtherProto2_MsgOneof{v}
+}
+
+func (x *OtherProto2) SetEnumOneof(v OtherProto2_OtherEnum) {
+ x.OneofField = &OtherProto2_EnumOneof{v}
+}
+
+func (x *OtherProto2) SetBytesOneof(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.OneofField = &OtherProto2_BytesOneof{v}
+}
+
+func (x *OtherProto2) SetStringOneof2(v string) {
+ x.OneofField2 = &OtherProto2_StringOneof2{v}
+}
+
+func (x *OtherProto2) SetIntOneof2(v int64) {
+ x.OneofField2 = &OtherProto2_IntOneof2{v}
+}
+
+func (x *OtherProto2) SetMsgOneof2(v *OtherProto2) {
+ if v == nil {
+ x.OneofField2 = nil
+ return
+ }
+ x.OneofField2 = &OtherProto2_MsgOneof2{v}
+}
+
+func (x *OtherProto2) SetEnumOneof2(v OtherProto2_OtherEnum) {
+ x.OneofField2 = &OtherProto2_EnumOneof2{v}
+}
+
+func (x *OtherProto2) SetBytesOneof2(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.OneofField2 = &OtherProto2_BytesOneof2{v}
+}
+
+func (x *OtherProto2) SetBuild_(v int32) {
+ x.Build = &v
+}
+
+func (x *OtherProto2) SetProtoMessage(v int32) {
+ x.ProtoMessage_ = &v
+}
+
+func (x *OtherProto2) SetReset(v int32) {
+ x.Reset_ = &v
+}
+
+func (x *OtherProto2) SetString(v int32) {
+ x.String_ = &v
+}
+
+func (x *OtherProto2) SetDescriptor(v int32) {
+ x.Descriptor_ = &v
+}
+
+func (x *OtherProto2) HasB() bool {
+ if x == nil {
+ return false
+ }
+ return x.B != nil
+}
+
+func (x *OtherProto2) HasBytes() bool {
+ if x == nil {
+ return false
+ }
+ return x.Bytes != nil
+}
+
+func (x *OtherProto2) HasF32() bool {
+ if x == nil {
+ return false
+ }
+ return x.F32 != nil
+}
+
+func (x *OtherProto2) HasF64() bool {
+ if x == nil {
+ return false
+ }
+ return x.F64 != nil
+}
+
+func (x *OtherProto2) HasI32() bool {
+ if x == nil {
+ return false
+ }
+ return x.I32 != nil
+}
+
+func (x *OtherProto2) HasI64() bool {
+ if x == nil {
+ return false
+ }
+ return x.I64 != nil
+}
+
+func (x *OtherProto2) HasUi32() bool {
+ if x == nil {
+ return false
+ }
+ return x.Ui32 != nil
+}
+
+func (x *OtherProto2) HasUi64() bool {
+ if x == nil {
+ return false
+ }
+ return x.Ui64 != nil
+}
+
+func (x *OtherProto2) HasS() bool {
+ if x == nil {
+ return false
+ }
+ return x.S != nil
+}
+
+func (x *OtherProto2) HasM() bool {
+ if x == nil {
+ return false
+ }
+ return x.M != nil
+}
+
+func (x *OtherProto2) HasE() bool {
+ if x == nil {
+ return false
+ }
+ return x.E != nil
+}
+
+func (x *OtherProto2) HasOneofField() bool {
+ if x == nil {
+ return false
+ }
+ return x.OneofField != nil
+}
+
+func (x *OtherProto2) HasStringOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*OtherProto2_StringOneof)
+ return ok
+}
+
+func (x *OtherProto2) HasIntOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*OtherProto2_IntOneof)
+ return ok
+}
+
+func (x *OtherProto2) HasMsgOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*OtherProto2_MsgOneof)
+ return ok
+}
+
+func (x *OtherProto2) HasEnumOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*OtherProto2_EnumOneof)
+ return ok
+}
+
+func (x *OtherProto2) HasBytesOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*OtherProto2_BytesOneof)
+ return ok
+}
+
+func (x *OtherProto2) HasOneofField2() bool {
+ if x == nil {
+ return false
+ }
+ return x.OneofField2 != nil
+}
+
+func (x *OtherProto2) HasStringOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*OtherProto2_StringOneof2)
+ return ok
+}
+
+func (x *OtherProto2) HasIntOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*OtherProto2_IntOneof2)
+ return ok
+}
+
+func (x *OtherProto2) HasMsgOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*OtherProto2_MsgOneof2)
+ return ok
+}
+
+func (x *OtherProto2) HasEnumOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*OtherProto2_EnumOneof2)
+ return ok
+}
+
+func (x *OtherProto2) HasBytesOneof2() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField2.(*OtherProto2_BytesOneof2)
+ return ok
+}
+
+func (x *OtherProto2) HasBuild_() bool {
+ if x == nil {
+ return false
+ }
+ return x.Build != nil
+}
+
+func (x *OtherProto2) HasProtoMessage() bool {
+ if x == nil {
+ return false
+ }
+ return x.ProtoMessage_ != nil
+}
+
+func (x *OtherProto2) HasReset() bool {
+ if x == nil {
+ return false
+ }
+ return x.Reset_ != nil
+}
+
+func (x *OtherProto2) HasString() bool {
+ if x == nil {
+ return false
+ }
+ return x.String_ != nil
+}
+
+func (x *OtherProto2) HasDescriptor() bool {
+ if x == nil {
+ return false
+ }
+ return x.Descriptor_ != nil
+}
+
+func (x *OtherProto2) ClearB() {
+ x.B = nil
+}
+
+func (x *OtherProto2) ClearBytes() {
+ x.Bytes = nil
+}
+
+func (x *OtherProto2) ClearF32() {
+ x.F32 = nil
+}
+
+func (x *OtherProto2) ClearF64() {
+ x.F64 = nil
+}
+
+func (x *OtherProto2) ClearI32() {
+ x.I32 = nil
+}
+
+func (x *OtherProto2) ClearI64() {
+ x.I64 = nil
+}
+
+func (x *OtherProto2) ClearUi32() {
+ x.Ui32 = nil
+}
+
+func (x *OtherProto2) ClearUi64() {
+ x.Ui64 = nil
+}
+
+func (x *OtherProto2) ClearS() {
+ x.S = nil
+}
+
+func (x *OtherProto2) ClearM() {
+ x.M = nil
+}
+
+func (x *OtherProto2) ClearE() {
+ x.E = nil
+}
+
+func (x *OtherProto2) ClearOneofField() {
+ x.OneofField = nil
+}
+
+func (x *OtherProto2) ClearStringOneof() {
+ if _, ok := x.OneofField.(*OtherProto2_StringOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *OtherProto2) ClearIntOneof() {
+ if _, ok := x.OneofField.(*OtherProto2_IntOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *OtherProto2) ClearMsgOneof() {
+ if _, ok := x.OneofField.(*OtherProto2_MsgOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *OtherProto2) ClearEnumOneof() {
+ if _, ok := x.OneofField.(*OtherProto2_EnumOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *OtherProto2) ClearBytesOneof() {
+ if _, ok := x.OneofField.(*OtherProto2_BytesOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *OtherProto2) ClearOneofField2() {
+ x.OneofField2 = nil
+}
+
+func (x *OtherProto2) ClearStringOneof2() {
+ if _, ok := x.OneofField2.(*OtherProto2_StringOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *OtherProto2) ClearIntOneof2() {
+ if _, ok := x.OneofField2.(*OtherProto2_IntOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *OtherProto2) ClearMsgOneof2() {
+ if _, ok := x.OneofField2.(*OtherProto2_MsgOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *OtherProto2) ClearEnumOneof2() {
+ if _, ok := x.OneofField2.(*OtherProto2_EnumOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *OtherProto2) ClearBytesOneof2() {
+ if _, ok := x.OneofField2.(*OtherProto2_BytesOneof2); ok {
+ x.OneofField2 = nil
+ }
+}
+
+func (x *OtherProto2) ClearBuild_() {
+ x.Build = nil
+}
+
+func (x *OtherProto2) ClearProtoMessage() {
+ x.ProtoMessage_ = nil
+}
+
+func (x *OtherProto2) ClearReset() {
+ x.Reset_ = nil
+}
+
+func (x *OtherProto2) ClearString() {
+ x.String_ = nil
+}
+
+func (x *OtherProto2) ClearDescriptor() {
+ x.Descriptor_ = nil
+}
+
+const OtherProto2_OneofField_not_set_case case_OtherProto2_OneofField = 0
+const OtherProto2_StringOneof_case case_OtherProto2_OneofField = 14
+const OtherProto2_IntOneof_case case_OtherProto2_OneofField = 15
+const OtherProto2_MsgOneof_case case_OtherProto2_OneofField = 16
+const OtherProto2_EnumOneof_case case_OtherProto2_OneofField = 17
+const OtherProto2_BytesOneof_case case_OtherProto2_OneofField = 18
+
+func (x *OtherProto2) WhichOneofField() case_OtherProto2_OneofField {
+ if x == nil {
+ return OtherProto2_OneofField_not_set_case
+ }
+ switch x.OneofField.(type) {
+ case *OtherProto2_StringOneof:
+ return OtherProto2_StringOneof_case
+ case *OtherProto2_IntOneof:
+ return OtherProto2_IntOneof_case
+ case *OtherProto2_MsgOneof:
+ return OtherProto2_MsgOneof_case
+ case *OtherProto2_EnumOneof:
+ return OtherProto2_EnumOneof_case
+ case *OtherProto2_BytesOneof:
+ return OtherProto2_BytesOneof_case
+ default:
+ return OtherProto2_OneofField_not_set_case
+ }
+}
+
+const OtherProto2_OneofField2_not_set_case case_OtherProto2_OneofField2 = 0
+const OtherProto2_StringOneof2_case case_OtherProto2_OneofField2 = 19
+const OtherProto2_IntOneof2_case case_OtherProto2_OneofField2 = 20
+const OtherProto2_MsgOneof2_case case_OtherProto2_OneofField2 = 21
+const OtherProto2_EnumOneof2_case case_OtherProto2_OneofField2 = 22
+const OtherProto2_BytesOneof2_case case_OtherProto2_OneofField2 = 23
+
+func (x *OtherProto2) WhichOneofField2() case_OtherProto2_OneofField2 {
+ if x == nil {
+ return OtherProto2_OneofField2_not_set_case
+ }
+ switch x.OneofField2.(type) {
+ case *OtherProto2_StringOneof2:
+ return OtherProto2_StringOneof2_case
+ case *OtherProto2_IntOneof2:
+ return OtherProto2_IntOneof2_case
+ case *OtherProto2_MsgOneof2:
+ return OtherProto2_MsgOneof2_case
+ case *OtherProto2_EnumOneof2:
+ return OtherProto2_EnumOneof2_case
+ case *OtherProto2_BytesOneof2:
+ return OtherProto2_BytesOneof2_case
+ default:
+ return OtherProto2_OneofField2_not_set_case
+ }
+}
+
+type OtherProto2_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ B *bool
+ Bytes []byte
+ F32 *float32
+ F64 *float64
+ I32 *int32
+ I64 *int64
+ Ui32 *uint32
+ Ui64 *uint64
+ S *string
+ M *OtherProto2
+ Is []int32
+ Ms []*OtherProto2
+ Map map[string]bool
+ E *OtherProto2_OtherEnum
+ // Fields of oneof OneofField:
+ StringOneof *string
+ IntOneof *int64
+ MsgOneof *OtherProto2
+ EnumOneof *OtherProto2_OtherEnum
+ BytesOneof []byte
+ // -- end of OneofField
+ // Fields of oneof OneofField2:
+ StringOneof2 *string
+ IntOneof2 *int64
+ MsgOneof2 *OtherProto2
+ EnumOneof2 *OtherProto2_OtherEnum
+ BytesOneof2 []byte
+ // -- end of OneofField2
+ Build_ *int32
+ ProtoMessage *int32
+ Reset *int32
+ String *int32
+ Descriptor *int32
+}
+
+func (b0 OtherProto2_builder) Build() *OtherProto2 {
+ m0 := &OtherProto2{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.B = b.B
+ x.Bytes = b.Bytes
+ x.F32 = b.F32
+ x.F64 = b.F64
+ x.I32 = b.I32
+ x.I64 = b.I64
+ x.Ui32 = b.Ui32
+ x.Ui64 = b.Ui64
+ x.S = b.S
+ x.M = b.M
+ x.Is = b.Is
+ x.Ms = b.Ms
+ x.Map = b.Map
+ x.E = b.E
+ if b.StringOneof != nil {
+ x.OneofField = &OtherProto2_StringOneof{*b.StringOneof}
+ }
+ if b.IntOneof != nil {
+ x.OneofField = &OtherProto2_IntOneof{*b.IntOneof}
+ }
+ if b.MsgOneof != nil {
+ x.OneofField = &OtherProto2_MsgOneof{b.MsgOneof}
+ }
+ if b.EnumOneof != nil {
+ x.OneofField = &OtherProto2_EnumOneof{*b.EnumOneof}
+ }
+ if b.BytesOneof != nil {
+ x.OneofField = &OtherProto2_BytesOneof{b.BytesOneof}
+ }
+ if b.StringOneof2 != nil {
+ x.OneofField2 = &OtherProto2_StringOneof2{*b.StringOneof2}
+ }
+ if b.IntOneof2 != nil {
+ x.OneofField2 = &OtherProto2_IntOneof2{*b.IntOneof2}
+ }
+ if b.MsgOneof2 != nil {
+ x.OneofField2 = &OtherProto2_MsgOneof2{b.MsgOneof2}
+ }
+ if b.EnumOneof2 != nil {
+ x.OneofField2 = &OtherProto2_EnumOneof2{*b.EnumOneof2}
+ }
+ if b.BytesOneof2 != nil {
+ x.OneofField2 = &OtherProto2_BytesOneof2{b.BytesOneof2}
+ }
+ x.Build = b.Build_
+ x.ProtoMessage_ = b.ProtoMessage
+ x.Reset_ = b.Reset
+ x.String_ = b.String
+ x.Descriptor_ = b.Descriptor
+ return m0
+}
+
+type case_OtherProto2_OneofField protoreflect.FieldNumber
+
+func (x case_OtherProto2_OneofField) String() string {
+ md := file_proto2test_proto_msgTypes[2].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type case_OtherProto2_OneofField2 protoreflect.FieldNumber
+
+func (x case_OtherProto2_OneofField2) String() string {
+ md := file_proto2test_proto_msgTypes[2].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isOtherProto2_OneofField interface {
+ isOtherProto2_OneofField()
+}
+
+type OtherProto2_StringOneof struct {
+ StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,oneof"`
+}
+
+type OtherProto2_IntOneof struct {
+ IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,oneof"`
+}
+
+type OtherProto2_MsgOneof struct {
+ MsgOneof *OtherProto2 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,oneof"`
+}
+
+type OtherProto2_EnumOneof struct {
+ EnumOneof OtherProto2_OtherEnum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum,oneof"`
+}
+
+type OtherProto2_BytesOneof struct {
+ BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,oneof"`
+}
+
+func (*OtherProto2_StringOneof) isOtherProto2_OneofField() {}
+
+func (*OtherProto2_IntOneof) isOtherProto2_OneofField() {}
+
+func (*OtherProto2_MsgOneof) isOtherProto2_OneofField() {}
+
+func (*OtherProto2_EnumOneof) isOtherProto2_OneofField() {}
+
+func (*OtherProto2_BytesOneof) isOtherProto2_OneofField() {}
+
+type isOtherProto2_OneofField2 interface {
+ isOtherProto2_OneofField2()
+}
+
+type OtherProto2_StringOneof2 struct {
+ StringOneof2 string `protobuf:"bytes,19,opt,name=string_oneof2,json=stringOneof2,oneof"`
+}
+
+type OtherProto2_IntOneof2 struct {
+ IntOneof2 int64 `protobuf:"varint,20,opt,name=int_oneof2,json=intOneof2,oneof"`
+}
+
+type OtherProto2_MsgOneof2 struct {
+ MsgOneof2 *OtherProto2 `protobuf:"bytes,21,opt,name=msg_oneof2,json=msgOneof2,oneof"`
+}
+
+type OtherProto2_EnumOneof2 struct {
+ EnumOneof2 OtherProto2_OtherEnum `protobuf:"varint,22,opt,name=enum_oneof2,json=enumOneof2,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum,oneof"`
+}
+
+type OtherProto2_BytesOneof2 struct {
+ BytesOneof2 []byte `protobuf:"bytes,23,opt,name=bytes_oneof2,json=bytesOneof2,oneof"`
+}
+
+func (*OtherProto2_StringOneof2) isOtherProto2_OneofField2() {}
+
+func (*OtherProto2_IntOneof2) isOtherProto2_OneofField2() {}
+
+func (*OtherProto2_MsgOneof2) isOtherProto2_OneofField2() {}
+
+func (*OtherProto2_EnumOneof2) isOtherProto2_OneofField2() {}
+
+func (*OtherProto2_BytesOneof2) isOtherProto2_OneofField2() {}
+
+type M2Outer struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Types that are valid to be assigned to OuterOneof:
+ //
+ // *M2Outer_InnerMsg
+ // *M2Outer_StringOneof
+ OuterOneof isM2Outer_OuterOneof `protobuf_oneof:"outer_oneof"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *M2Outer) Reset() {
+ *x = M2Outer{}
+ mi := &file_proto2test_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *M2Outer) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*M2Outer) ProtoMessage() {}
+
+func (x *M2Outer) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *M2Outer) GetOuterOneof() isM2Outer_OuterOneof {
+ if x != nil {
+ return x.OuterOneof
+ }
+ return nil
+}
+
+func (x *M2Outer) GetInnerMsg() *M2Outer_MInner {
+ if x != nil {
+ if x, ok := x.OuterOneof.(*M2Outer_InnerMsg); ok {
+ return x.InnerMsg
+ }
+ }
+ return nil
+}
+
+func (x *M2Outer) GetStringOneof() string {
+ if x != nil {
+ if x, ok := x.OuterOneof.(*M2Outer_StringOneof); ok {
+ return x.StringOneof
+ }
+ }
+ return ""
+}
+
+func (x *M2Outer) SetInnerMsg(v *M2Outer_MInner) {
+ if v == nil {
+ x.OuterOneof = nil
+ return
+ }
+ x.OuterOneof = &M2Outer_InnerMsg{v}
+}
+
+func (x *M2Outer) SetStringOneof(v string) {
+ x.OuterOneof = &M2Outer_StringOneof{v}
+}
+
+func (x *M2Outer) HasOuterOneof() bool {
+ if x == nil {
+ return false
+ }
+ return x.OuterOneof != nil
+}
+
+func (x *M2Outer) HasInnerMsg() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OuterOneof.(*M2Outer_InnerMsg)
+ return ok
+}
+
+func (x *M2Outer) HasStringOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OuterOneof.(*M2Outer_StringOneof)
+ return ok
+}
+
+func (x *M2Outer) ClearOuterOneof() {
+ x.OuterOneof = nil
+}
+
+func (x *M2Outer) ClearInnerMsg() {
+ if _, ok := x.OuterOneof.(*M2Outer_InnerMsg); ok {
+ x.OuterOneof = nil
+ }
+}
+
+func (x *M2Outer) ClearStringOneof() {
+ if _, ok := x.OuterOneof.(*M2Outer_StringOneof); ok {
+ x.OuterOneof = nil
+ }
+}
+
+const M2Outer_OuterOneof_not_set_case case_M2Outer_OuterOneof = 0
+const M2Outer_InnerMsg_case case_M2Outer_OuterOneof = 1
+const M2Outer_StringOneof_case case_M2Outer_OuterOneof = 2
+
+func (x *M2Outer) WhichOuterOneof() case_M2Outer_OuterOneof {
+ if x == nil {
+ return M2Outer_OuterOneof_not_set_case
+ }
+ switch x.OuterOneof.(type) {
+ case *M2Outer_InnerMsg:
+ return M2Outer_InnerMsg_case
+ case *M2Outer_StringOneof:
+ return M2Outer_StringOneof_case
+ default:
+ return M2Outer_OuterOneof_not_set_case
+ }
+}
+
+type M2Outer_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fields of oneof OuterOneof:
+ InnerMsg *M2Outer_MInner
+ StringOneof *string
+ // -- end of OuterOneof
+}
+
+func (b0 M2Outer_builder) Build() *M2Outer {
+ m0 := &M2Outer{}
+ b, x := &b0, m0
+ _, _ = b, x
+ if b.InnerMsg != nil {
+ x.OuterOneof = &M2Outer_InnerMsg{b.InnerMsg}
+ }
+ if b.StringOneof != nil {
+ x.OuterOneof = &M2Outer_StringOneof{*b.StringOneof}
+ }
+ return m0
+}
+
+type case_M2Outer_OuterOneof protoreflect.FieldNumber
+
+func (x case_M2Outer_OuterOneof) String() string {
+ md := file_proto2test_proto_msgTypes[3].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isM2Outer_OuterOneof interface {
+ isM2Outer_OuterOneof()
+}
+
+type M2Outer_InnerMsg struct {
+ InnerMsg *M2Outer_MInner `protobuf:"bytes,1,opt,name=inner_msg,json=innerMsg,oneof"`
+}
+
+type M2Outer_StringOneof struct {
+ StringOneof string `protobuf:"bytes,2,opt,name=string_oneof,json=stringOneof,oneof"`
+}
+
+func (*M2Outer_InnerMsg) isM2Outer_OuterOneof() {}
+
+func (*M2Outer_StringOneof) isM2Outer_OuterOneof() {}
+
+type ConflictingOneof struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Types that are valid to be assigned to Included:
+ //
+ // *ConflictingOneof_Sub_
+ // *ConflictingOneof_Otherwise
+ Included isConflictingOneof_Included `protobuf_oneof:"included"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ConflictingOneof) Reset() {
+ *x = ConflictingOneof{}
+ mi := &file_proto2test_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ConflictingOneof) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConflictingOneof) ProtoMessage() {}
+
+func (x *ConflictingOneof) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ConflictingOneof) GetIncluded() isConflictingOneof_Included {
+ if x != nil {
+ return x.Included
+ }
+ return nil
+}
+
+func (x *ConflictingOneof) GetSub() *ConflictingOneof_Sub {
+ if x != nil {
+ if x, ok := x.Included.(*ConflictingOneof_Sub_); ok {
+ return x.Sub
+ }
+ }
+ return nil
+}
+
+func (x *ConflictingOneof) GetOtherwise() string {
+ if x != nil {
+ if x, ok := x.Included.(*ConflictingOneof_Otherwise); ok {
+ return x.Otherwise
+ }
+ }
+ return ""
+}
+
+func (x *ConflictingOneof) SetSub(v *ConflictingOneof_Sub) {
+ if v == nil {
+ x.Included = nil
+ return
+ }
+ x.Included = &ConflictingOneof_Sub_{v}
+}
+
+func (x *ConflictingOneof) SetOtherwise(v string) {
+ x.Included = &ConflictingOneof_Otherwise{v}
+}
+
+func (x *ConflictingOneof) HasIncluded() bool {
+ if x == nil {
+ return false
+ }
+ return x.Included != nil
+}
+
+func (x *ConflictingOneof) HasSub() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.Included.(*ConflictingOneof_Sub_)
+ return ok
+}
+
+func (x *ConflictingOneof) HasOtherwise() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.Included.(*ConflictingOneof_Otherwise)
+ return ok
+}
+
+func (x *ConflictingOneof) ClearIncluded() {
+ x.Included = nil
+}
+
+func (x *ConflictingOneof) ClearSub() {
+ if _, ok := x.Included.(*ConflictingOneof_Sub_); ok {
+ x.Included = nil
+ }
+}
+
+func (x *ConflictingOneof) ClearOtherwise() {
+ if _, ok := x.Included.(*ConflictingOneof_Otherwise); ok {
+ x.Included = nil
+ }
+}
+
+const ConflictingOneof_Included_not_set_case case_ConflictingOneof_Included = 0
+const ConflictingOneof_Sub_case case_ConflictingOneof_Included = 1
+const ConflictingOneof_Otherwise_case case_ConflictingOneof_Included = 2
+
+func (x *ConflictingOneof) WhichIncluded() case_ConflictingOneof_Included {
+ if x == nil {
+ return ConflictingOneof_Included_not_set_case
+ }
+ switch x.Included.(type) {
+ case *ConflictingOneof_Sub_:
+ return ConflictingOneof_Sub_case
+ case *ConflictingOneof_Otherwise:
+ return ConflictingOneof_Otherwise_case
+ default:
+ return ConflictingOneof_Included_not_set_case
+ }
+}
+
+type ConflictingOneof_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fields of oneof Included:
+ Sub *ConflictingOneof_Sub
+ Otherwise *string
+ // -- end of Included
+}
+
+func (b0 ConflictingOneof_builder) Build() *ConflictingOneof {
+ m0 := &ConflictingOneof{}
+ b, x := &b0, m0
+ _, _ = b, x
+ if b.Sub != nil {
+ x.Included = &ConflictingOneof_Sub_{b.Sub}
+ }
+ if b.Otherwise != nil {
+ x.Included = &ConflictingOneof_Otherwise{*b.Otherwise}
+ }
+ return m0
+}
+
+type case_ConflictingOneof_Included protoreflect.FieldNumber
+
+func (x case_ConflictingOneof_Included) String() string {
+ md := file_proto2test_proto_msgTypes[4].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isConflictingOneof_Included interface {
+ isConflictingOneof_Included()
+}
+
+type ConflictingOneof_Sub_ struct {
+ Sub *ConflictingOneof_Sub `protobuf:"bytes,1,opt,name=sub,oneof"`
+}
+
+type ConflictingOneof_Otherwise struct {
+ Otherwise string `protobuf:"bytes,2,opt,name=otherwise,oneof"`
+}
+
+func (*ConflictingOneof_Sub_) isConflictingOneof_Included() {}
+
+func (*ConflictingOneof_Otherwise) isConflictingOneof_Included() {}
+
+type SetterNameConflict struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ Stat *int32 `protobuf:"varint,1,opt,name=stat" json:"stat,omitempty"`
+ SetStat *int32 `protobuf:"varint,2,opt,name=set_stat,json=setStat" json:"set_stat,omitempty"`
+ GetStat_ *int32 `protobuf:"varint,3,opt,name=get_stat,json=getStat" json:"get_stat,omitempty"`
+ HasStat *int32 `protobuf:"varint,4,opt,name=has_stat,json=hasStat" json:"has_stat,omitempty"`
+ ClearStat *int32 `protobuf:"varint,5,opt,name=clear_stat,json=clearStat" json:"clear_stat,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SetterNameConflict) Reset() {
+ *x = SetterNameConflict{}
+ mi := &file_proto2test_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SetterNameConflict) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SetterNameConflict) ProtoMessage() {}
+
+func (x *SetterNameConflict) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *SetterNameConflict) Get_Stat() int32 {
+ if x != nil && x.Stat != nil {
+ return *x.Stat
+ }
+ return 0
+}
+
+// Deprecated: Use Get_Stat instead.
+func (x *SetterNameConflict) GetStat() int32 {
+ return x.Get_Stat()
+}
+
+func (x *SetterNameConflict) GetSetStat() int32 {
+ if x != nil && x.SetStat != nil {
+ return *x.SetStat
+ }
+ return 0
+}
+
+func (x *SetterNameConflict) GetGetStat() int32 {
+ if x != nil && x.GetStat_ != nil {
+ return *x.GetStat_
+ }
+ return 0
+}
+
+// Deprecated: Use GetGetStat instead.
+func (x *SetterNameConflict) GetGetStat_() int32 {
+ return x.GetGetStat()
+}
+
+func (x *SetterNameConflict) GetHasStat() int32 {
+ if x != nil && x.HasStat != nil {
+ return *x.HasStat
+ }
+ return 0
+}
+
+func (x *SetterNameConflict) GetClearStat() int32 {
+ if x != nil && x.ClearStat != nil {
+ return *x.ClearStat
+ }
+ return 0
+}
+
+func (x *SetterNameConflict) Set_Stat(v int32) {
+ x.Stat = &v
+}
+
+func (x *SetterNameConflict) SetSetStat(v int32) {
+ x.SetStat = &v
+}
+
+func (x *SetterNameConflict) SetGetStat(v int32) {
+ x.GetStat_ = &v
+}
+
+func (x *SetterNameConflict) SetHasStat(v int32) {
+ x.HasStat = &v
+}
+
+func (x *SetterNameConflict) SetClearStat(v int32) {
+ x.ClearStat = &v
+}
+
+func (x *SetterNameConflict) Has_Stat() bool {
+ if x == nil {
+ return false
+ }
+ return x.Stat != nil
+}
+
+func (x *SetterNameConflict) HasSetStat() bool {
+ if x == nil {
+ return false
+ }
+ return x.SetStat != nil
+}
+
+func (x *SetterNameConflict) HasGetStat() bool {
+ if x == nil {
+ return false
+ }
+ return x.GetStat_ != nil
+}
+
+func (x *SetterNameConflict) HasHasStat() bool {
+ if x == nil {
+ return false
+ }
+ return x.HasStat != nil
+}
+
+func (x *SetterNameConflict) HasClearStat() bool {
+ if x == nil {
+ return false
+ }
+ return x.ClearStat != nil
+}
+
+func (x *SetterNameConflict) Clear_Stat() {
+ x.Stat = nil
+}
+
+func (x *SetterNameConflict) ClearSetStat() {
+ x.SetStat = nil
+}
+
+func (x *SetterNameConflict) ClearGetStat() {
+ x.GetStat_ = nil
+}
+
+func (x *SetterNameConflict) ClearHasStat() {
+ x.HasStat = nil
+}
+
+func (x *SetterNameConflict) ClearClearStat() {
+ x.ClearStat = nil
+}
+
+type SetterNameConflict_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ Stat *int32
+ SetStat *int32
+ GetStat *int32
+ HasStat *int32
+ ClearStat *int32
+}
+
+func (b0 SetterNameConflict_builder) Build() *SetterNameConflict {
+ m0 := &SetterNameConflict{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.Stat = b.Stat
+ x.SetStat = b.SetStat
+ x.GetStat_ = b.GetStat
+ x.HasStat = b.HasStat
+ x.ClearStat = b.ClearStat
+ return m0
+}
+
+type M2Outer_MInner struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Types that are valid to be assigned to InnerOneof:
+ //
+ // *M2Outer_MInner_StringInner
+ // *M2Outer_MInner_IntInner
+ InnerOneof isM2Outer_MInner_InnerOneof `protobuf_oneof:"inner_oneof"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *M2Outer_MInner) Reset() {
+ *x = M2Outer_MInner{}
+ mi := &file_proto2test_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *M2Outer_MInner) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*M2Outer_MInner) ProtoMessage() {}
+
+func (x *M2Outer_MInner) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *M2Outer_MInner) GetInnerOneof() isM2Outer_MInner_InnerOneof {
+ if x != nil {
+ return x.InnerOneof
+ }
+ return nil
+}
+
+func (x *M2Outer_MInner) GetStringInner() string {
+ if x != nil {
+ if x, ok := x.InnerOneof.(*M2Outer_MInner_StringInner); ok {
+ return x.StringInner
+ }
+ }
+ return ""
+}
+
+func (x *M2Outer_MInner) GetIntInner() int64 {
+ if x != nil {
+ if x, ok := x.InnerOneof.(*M2Outer_MInner_IntInner); ok {
+ return x.IntInner
+ }
+ }
+ return 0
+}
+
+func (x *M2Outer_MInner) SetStringInner(v string) {
+ x.InnerOneof = &M2Outer_MInner_StringInner{v}
+}
+
+func (x *M2Outer_MInner) SetIntInner(v int64) {
+ x.InnerOneof = &M2Outer_MInner_IntInner{v}
+}
+
+func (x *M2Outer_MInner) HasInnerOneof() bool {
+ if x == nil {
+ return false
+ }
+ return x.InnerOneof != nil
+}
+
+func (x *M2Outer_MInner) HasStringInner() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.InnerOneof.(*M2Outer_MInner_StringInner)
+ return ok
+}
+
+func (x *M2Outer_MInner) HasIntInner() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.InnerOneof.(*M2Outer_MInner_IntInner)
+ return ok
+}
+
+func (x *M2Outer_MInner) ClearInnerOneof() {
+ x.InnerOneof = nil
+}
+
+func (x *M2Outer_MInner) ClearStringInner() {
+ if _, ok := x.InnerOneof.(*M2Outer_MInner_StringInner); ok {
+ x.InnerOneof = nil
+ }
+}
+
+func (x *M2Outer_MInner) ClearIntInner() {
+ if _, ok := x.InnerOneof.(*M2Outer_MInner_IntInner); ok {
+ x.InnerOneof = nil
+ }
+}
+
+const M2Outer_MInner_InnerOneof_not_set_case case_M2Outer_MInner_InnerOneof = 0
+const M2Outer_MInner_StringInner_case case_M2Outer_MInner_InnerOneof = 19
+const M2Outer_MInner_IntInner_case case_M2Outer_MInner_InnerOneof = 20
+
+func (x *M2Outer_MInner) WhichInnerOneof() case_M2Outer_MInner_InnerOneof {
+ if x == nil {
+ return M2Outer_MInner_InnerOneof_not_set_case
+ }
+ switch x.InnerOneof.(type) {
+ case *M2Outer_MInner_StringInner:
+ return M2Outer_MInner_StringInner_case
+ case *M2Outer_MInner_IntInner:
+ return M2Outer_MInner_IntInner_case
+ default:
+ return M2Outer_MInner_InnerOneof_not_set_case
+ }
+}
+
+type M2Outer_MInner_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fields of oneof InnerOneof:
+ StringInner *string
+ IntInner *int64
+ // -- end of InnerOneof
+}
+
+func (b0 M2Outer_MInner_builder) Build() *M2Outer_MInner {
+ m0 := &M2Outer_MInner{}
+ b, x := &b0, m0
+ _, _ = b, x
+ if b.StringInner != nil {
+ x.InnerOneof = &M2Outer_MInner_StringInner{*b.StringInner}
+ }
+ if b.IntInner != nil {
+ x.InnerOneof = &M2Outer_MInner_IntInner{*b.IntInner}
+ }
+ return m0
+}
+
+type case_M2Outer_MInner_InnerOneof protoreflect.FieldNumber
+
+func (x case_M2Outer_MInner_InnerOneof) String() string {
+ md := file_proto2test_proto_msgTypes[8].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isM2Outer_MInner_InnerOneof interface {
+ isM2Outer_MInner_InnerOneof()
+}
+
+type M2Outer_MInner_StringInner struct {
+ StringInner string `protobuf:"bytes,19,opt,name=string_inner,json=stringInner,oneof"`
+}
+
+type M2Outer_MInner_IntInner struct {
+ IntInner int64 `protobuf:"varint,20,opt,name=int_inner,json=intInner,oneof"`
+}
+
+func (*M2Outer_MInner_StringInner) isM2Outer_MInner_InnerOneof() {}
+
+func (*M2Outer_MInner_IntInner) isM2Outer_MInner_InnerOneof() {}
+
+type ConflictingOneof_Sub struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ConflictingOneof_Sub) Reset() {
+ *x = ConflictingOneof_Sub{}
+ mi := &file_proto2test_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ConflictingOneof_Sub) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConflictingOneof_Sub) ProtoMessage() {}
+
+func (x *ConflictingOneof_Sub) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+type ConflictingOneof_Sub_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+}
+
+func (b0 ConflictingOneof_Sub_builder) Build() *ConflictingOneof_Sub {
+ m0 := &ConflictingOneof_Sub{}
+ b, x := &b0, m0
+ _, _ = b, x
+ return m0
+}
+
+type ConflictingOneof_DeepSub struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ // Types that are valid to be assigned to DeeplyIncluded:
+ //
+ // *ConflictingOneof_DeepSub_Sub_
+ // *ConflictingOneof_DeepSub_Otherwise
+ DeeplyIncluded isConflictingOneof_DeepSub_DeeplyIncluded `protobuf_oneof:"deeply_included"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ConflictingOneof_DeepSub) Reset() {
+ *x = ConflictingOneof_DeepSub{}
+ mi := &file_proto2test_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ConflictingOneof_DeepSub) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConflictingOneof_DeepSub) ProtoMessage() {}
+
+func (x *ConflictingOneof_DeepSub) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *ConflictingOneof_DeepSub) GetDeeplyIncluded() isConflictingOneof_DeepSub_DeeplyIncluded {
+ if x != nil {
+ return x.DeeplyIncluded
+ }
+ return nil
+}
+
+func (x *ConflictingOneof_DeepSub) GetSub() *ConflictingOneof_DeepSub_Sub {
+ if x != nil {
+ if x, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_); ok {
+ return x.Sub
+ }
+ }
+ return nil
+}
+
+func (x *ConflictingOneof_DeepSub) GetOtherwise() string {
+ if x != nil {
+ if x, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise); ok {
+ return x.Otherwise
+ }
+ }
+ return ""
+}
+
+func (x *ConflictingOneof_DeepSub) SetSub(v *ConflictingOneof_DeepSub_Sub) {
+ if v == nil {
+ x.DeeplyIncluded = nil
+ return
+ }
+ x.DeeplyIncluded = &ConflictingOneof_DeepSub_Sub_{v}
+}
+
+func (x *ConflictingOneof_DeepSub) SetOtherwise(v string) {
+ x.DeeplyIncluded = &ConflictingOneof_DeepSub_Otherwise{v}
+}
+
+func (x *ConflictingOneof_DeepSub) HasDeeplyIncluded() bool {
+ if x == nil {
+ return false
+ }
+ return x.DeeplyIncluded != nil
+}
+
+func (x *ConflictingOneof_DeepSub) HasSub() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_)
+ return ok
+}
+
+func (x *ConflictingOneof_DeepSub) HasOtherwise() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise)
+ return ok
+}
+
+func (x *ConflictingOneof_DeepSub) ClearDeeplyIncluded() {
+ x.DeeplyIncluded = nil
+}
+
+func (x *ConflictingOneof_DeepSub) ClearSub() {
+ if _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_); ok {
+ x.DeeplyIncluded = nil
+ }
+}
+
+func (x *ConflictingOneof_DeepSub) ClearOtherwise() {
+ if _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise); ok {
+ x.DeeplyIncluded = nil
+ }
+}
+
+const ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 0
+const ConflictingOneof_DeepSub_Sub_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 1
+const ConflictingOneof_DeepSub_Otherwise_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 3
+
+func (x *ConflictingOneof_DeepSub) WhichDeeplyIncluded() case_ConflictingOneof_DeepSub_DeeplyIncluded {
+ if x == nil {
+ return ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case
+ }
+ switch x.DeeplyIncluded.(type) {
+ case *ConflictingOneof_DeepSub_Sub_:
+ return ConflictingOneof_DeepSub_Sub_case
+ case *ConflictingOneof_DeepSub_Otherwise:
+ return ConflictingOneof_DeepSub_Otherwise_case
+ default:
+ return ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case
+ }
+}
+
+type ConflictingOneof_DeepSub_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ // Fields of oneof DeeplyIncluded:
+ Sub *ConflictingOneof_DeepSub_Sub
+ Otherwise *string
+ // -- end of DeeplyIncluded
+}
+
+func (b0 ConflictingOneof_DeepSub_builder) Build() *ConflictingOneof_DeepSub {
+ m0 := &ConflictingOneof_DeepSub{}
+ b, x := &b0, m0
+ _, _ = b, x
+ if b.Sub != nil {
+ x.DeeplyIncluded = &ConflictingOneof_DeepSub_Sub_{b.Sub}
+ }
+ if b.Otherwise != nil {
+ x.DeeplyIncluded = &ConflictingOneof_DeepSub_Otherwise{*b.Otherwise}
+ }
+ return m0
+}
+
+type case_ConflictingOneof_DeepSub_DeeplyIncluded protoreflect.FieldNumber
+
+func (x case_ConflictingOneof_DeepSub_DeeplyIncluded) String() string {
+ md := file_proto2test_proto_msgTypes[10].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isConflictingOneof_DeepSub_DeeplyIncluded interface {
+ isConflictingOneof_DeepSub_DeeplyIncluded()
+}
+
+type ConflictingOneof_DeepSub_Sub_ struct {
+ Sub *ConflictingOneof_DeepSub_Sub `protobuf:"bytes,1,opt,name=sub,oneof"`
+}
+
+type ConflictingOneof_DeepSub_Otherwise struct {
+ Otherwise string `protobuf:"bytes,3,opt,name=otherwise,oneof"`
+}
+
+func (*ConflictingOneof_DeepSub_Sub_) isConflictingOneof_DeepSub_DeeplyIncluded() {}
+
+func (*ConflictingOneof_DeepSub_Otherwise) isConflictingOneof_DeepSub_DeeplyIncluded() {}
+
+type ConflictingOneof_DeepSub_Sub struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ConflictingOneof_DeepSub_Sub) Reset() {
+ *x = ConflictingOneof_DeepSub_Sub{}
+ mi := &file_proto2test_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ConflictingOneof_DeepSub_Sub) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConflictingOneof_DeepSub_Sub) ProtoMessage() {}
+
+func (x *ConflictingOneof_DeepSub_Sub) ProtoReflect() protoreflect.Message {
+ mi := &file_proto2test_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+type ConflictingOneof_DeepSub_Sub_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+}
+
+func (b0 ConflictingOneof_DeepSub_Sub_builder) Build() *ConflictingOneof_DeepSub_Sub {
+ m0 := &ConflictingOneof_DeepSub_Sub{}
+ b, x := &b0, m0
+ _, _ = b, x
+ return m0
+}
+
+var File_proto2test_proto protoreflect.FileDescriptor
+
+var file_proto2test_proto_rawDesc = []byte{
+ 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x12, 0x22, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32,
+ 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75,
+ 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x27, 0x0a, 0x0e, 0x44, 0x6f, 0x4e,
+ 0x6f, 0x74, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x62,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02,
+ 0x10, 0x01, 0x22, 0xa0, 0x09, 0x0a, 0x02, 0x4d, 0x32, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a,
+ 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x66, 0x33, 0x32, 0x12,
+ 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x36,
+ 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03,
+ 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
+ 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20,
+ 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x36,
+ 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, 0x36, 0x34, 0x12, 0x0c, 0x0a,
+ 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, 0x12, 0x34, 0x0a, 0x01, 0x6d,
+ 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x52, 0x01,
+ 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x02, 0x69,
+ 0x73, 0x12, 0x36, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65,
+ 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x41, 0x0a, 0x03, 0x6d, 0x61, 0x70,
+ 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e, 0x4d,
+ 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x12, 0x39, 0x0a, 0x01,
+ 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e,
+ 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e,
+ 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
+ 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09,
+ 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48,
+ 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x45, 0x0a, 0x09, 0x6d,
+ 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f,
+ 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74,
+ 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65,
+ 0x6f, 0x66, 0x12, 0x4c, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71,
+ 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e, 0x45,
+ 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18,
+ 0x12, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x12, 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x32, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x73, 0x74,
+ 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x1f, 0x0a, 0x0a, 0x69, 0x6e,
+ 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01,
+ 0x52, 0x09, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x47, 0x0a, 0x0a, 0x6d,
+ 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e,
+ 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x48, 0x01, 0x52, 0x09, 0x6d, 0x73, 0x67, 0x4f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x32, 0x12, 0x4e, 0x0a, 0x0b, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65,
+ 0x6f, 0x66, 0x32, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f,
+ 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d,
+ 0x32, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x01, 0x52, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x32, 0x12, 0x23, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x32, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x0b, 0x62, 0x79,
+ 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69,
+ 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12,
+ 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74,
+ 0x72, 0x69, 0x6e, 0x67, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69,
+ 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72,
+ 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+ 0x6f, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
+ 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+ 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e,
+ 0x75, 0x6d, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x3a, 0x07, 0x62,
+ 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d, 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0x85, 0x0a, 0x0a, 0x0b, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50,
+ 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
+ 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66,
+ 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a,
+ 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12,
+ 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36,
+ 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52,
+ 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20,
+ 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, 0x12, 0x3d, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f,
+ 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, 0x03,
+ 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x3f, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f,
+ 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f,
+ 0x74, 0x6f, 0x32, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x4a, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d,
+ 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65,
+ 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50,
+ 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03,
+ 0x6d, 0x61, 0x70, 0x12, 0x47, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f,
+ 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74,
+ 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, 0x12, 0x23, 0x0a, 0x0c,
+ 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0e, 0x20, 0x01,
+ 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f,
+ 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0f,
+ 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x12, 0x4e, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x10, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x12, 0x5a, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65,
+ 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50,
+ 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x48,
+ 0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28,
+ 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12,
+ 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32,
+ 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+ 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x1f, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e,
+ 0x65, 0x6f, 0x66, 0x32, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x09, 0x69, 0x6e,
+ 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x50, 0x0a, 0x0a, 0x6d, 0x73, 0x67, 0x5f, 0x6f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74,
+ 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x48, 0x01, 0x52, 0x09,
+ 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x5c, 0x0a, 0x0b, 0x65, 0x6e, 0x75,
+ 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f,
+ 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74,
+ 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e,
+ 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x01, 0x52, 0x0a, 0x65, 0x6e, 0x75,
+ 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x23, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73,
+ 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52,
+ 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x14, 0x0a, 0x05,
+ 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x75, 0x69,
+ 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x6d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74,
+ 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a,
+ 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73,
+ 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
+ 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72,
+ 0x69, 0x70, 0x74, 0x6f, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72,
+ 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+ 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x16, 0x0a,
+ 0x09, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f,
+ 0x56, 0x41, 0x4c, 0x10, 0x00, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d,
+ 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0e, 0x0a,
+ 0x0c, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0xff, 0x01,
+ 0x0a, 0x07, 0x4d, 0x32, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x51, 0x0a, 0x09, 0x69, 0x6e, 0x6e,
+ 0x65, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
+ 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73,
+ 0x74, 0x2e, 0x4d, 0x32, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x4d, 0x49, 0x6e, 0x6e, 0x65, 0x72,
+ 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 0x0c,
+ 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f,
+ 0x66, 0x1a, 0x64, 0x0a, 0x06, 0x4d, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0c, 0x73,
+ 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28,
+ 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x6e, 0x65, 0x72,
+ 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x14, 0x20,
+ 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x3a,
+ 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x6e, 0x65,
+ 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02,
+ 0x42, 0x0d, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x22,
+ 0xd3, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4c, 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x38, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32,
+ 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69,
+ 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x53, 0x75, 0x62, 0x48, 0x00, 0x52, 0x03, 0x73,
+ 0x75, 0x62, 0x12, 0x1e, 0x0a, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69,
+ 0x73, 0x65, 0x1a, 0x0e, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02,
+ 0x10, 0x02, 0x1a, 0xab, 0x01, 0x0a, 0x07, 0x44, 0x65, 0x65, 0x70, 0x53, 0x75, 0x62, 0x12, 0x54,
+ 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74,
+ 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f,
+ 0x66, 0x2e, 0x44, 0x65, 0x65, 0x70, 0x53, 0x75, 0x62, 0x2e, 0x53, 0x75, 0x62, 0x48, 0x00, 0x52,
+ 0x03, 0x73, 0x75, 0x62, 0x12, 0x1e, 0x0a, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73,
+ 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72,
+ 0x77, 0x69, 0x73, 0x65, 0x1a, 0x0e, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2,
+ 0x3e, 0x02, 0x10, 0x02, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x11, 0x0a,
+ 0x0f, 0x64, 0x65, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64,
+ 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0a, 0x0a, 0x08, 0x69, 0x6e, 0x63,
+ 0x6c, 0x75, 0x64, 0x65, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x74, 0x65, 0x72,
+ 0x4e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04,
+ 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74,
+ 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x05, 0x52, 0x07, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67,
+ 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x67,
+ 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x73, 0x74,
+ 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x68, 0x61, 0x73, 0x53, 0x74, 0x61,
+ 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74,
+ 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69,
+ 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07,
+}
+
+var (
+ file_proto2test_proto_rawDescOnce sync.Once
+ file_proto2test_proto_rawDescData = file_proto2test_proto_rawDesc
+)
+
+func file_proto2test_proto_rawDescGZIP() []byte {
+ file_proto2test_proto_rawDescOnce.Do(func() {
+ file_proto2test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto2test_proto_rawDescData)
+ })
+ return file_proto2test_proto_rawDescData
+}
+
+var file_proto2test_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_proto2test_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
+var file_proto2test_proto_goTypes = []any{
+ (M2_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test.M2.Enum
+ (OtherProto2_OtherEnum)(0), // 1: net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum
+ (*DoNotMigrateMe)(nil), // 2: net.proto2.go.open2opaque.o2o.test.DoNotMigrateMe
+ (*M2)(nil), // 3: net.proto2.go.open2opaque.o2o.test.M2
+ (*OtherProto2)(nil), // 4: net.proto2.go.open2opaque.o2o.test.OtherProto2
+ (*M2Outer)(nil), // 5: net.proto2.go.open2opaque.o2o.test.M2Outer
+ (*ConflictingOneof)(nil), // 6: net.proto2.go.open2opaque.o2o.test.ConflictingOneof
+ (*SetterNameConflict)(nil), // 7: net.proto2.go.open2opaque.o2o.test.SetterNameConflict
+ nil, // 8: net.proto2.go.open2opaque.o2o.test.M2.MapEntry
+ nil, // 9: net.proto2.go.open2opaque.o2o.test.OtherProto2.MapEntry
+ (*M2Outer_MInner)(nil), // 10: net.proto2.go.open2opaque.o2o.test.M2Outer.MInner
+ (*ConflictingOneof_Sub)(nil), // 11: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.Sub
+ (*ConflictingOneof_DeepSub)(nil), // 12: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub
+ (*ConflictingOneof_DeepSub_Sub)(nil), // 13: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.Sub
+}
+var file_proto2test_proto_depIdxs = []int32{
+ 3, // 0: net.proto2.go.open2opaque.o2o.test.M2.m:type_name -> net.proto2.go.open2opaque.o2o.test.M2
+ 3, // 1: net.proto2.go.open2opaque.o2o.test.M2.ms:type_name -> net.proto2.go.open2opaque.o2o.test.M2
+ 8, // 2: net.proto2.go.open2opaque.o2o.test.M2.map:type_name -> net.proto2.go.open2opaque.o2o.test.M2.MapEntry
+ 0, // 3: net.proto2.go.open2opaque.o2o.test.M2.e:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum
+ 3, // 4: net.proto2.go.open2opaque.o2o.test.M2.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.M2
+ 0, // 5: net.proto2.go.open2opaque.o2o.test.M2.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum
+ 3, // 6: net.proto2.go.open2opaque.o2o.test.M2.msg_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.M2
+ 0, // 7: net.proto2.go.open2opaque.o2o.test.M2.enum_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum
+ 4, // 8: net.proto2.go.open2opaque.o2o.test.OtherProto2.m:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2
+ 4, // 9: net.proto2.go.open2opaque.o2o.test.OtherProto2.ms:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2
+ 9, // 10: net.proto2.go.open2opaque.o2o.test.OtherProto2.map:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.MapEntry
+ 1, // 11: net.proto2.go.open2opaque.o2o.test.OtherProto2.e:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum
+ 4, // 12: net.proto2.go.open2opaque.o2o.test.OtherProto2.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2
+ 1, // 13: net.proto2.go.open2opaque.o2o.test.OtherProto2.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum
+ 4, // 14: net.proto2.go.open2opaque.o2o.test.OtherProto2.msg_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2
+ 1, // 15: net.proto2.go.open2opaque.o2o.test.OtherProto2.enum_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum
+ 10, // 16: net.proto2.go.open2opaque.o2o.test.M2Outer.inner_msg:type_name -> net.proto2.go.open2opaque.o2o.test.M2Outer.MInner
+ 11, // 17: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.sub:type_name -> net.proto2.go.open2opaque.o2o.test.ConflictingOneof.Sub
+ 13, // 18: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.sub:type_name -> net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.Sub
+ 19, // [19:19] is the sub-list for method output_type
+ 19, // [19:19] is the sub-list for method input_type
+ 19, // [19:19] is the sub-list for extension type_name
+ 19, // [19:19] is the sub-list for extension extendee
+ 0, // [0:19] is the sub-list for field type_name
+}
+
+func init() { file_proto2test_proto_init() }
+func file_proto2test_proto_init() {
+ if File_proto2test_proto != nil {
+ return
+ }
+ file_proto2test_proto_msgTypes[1].OneofWrappers = []any{
+ (*M2_StringOneof)(nil),
+ (*M2_IntOneof)(nil),
+ (*M2_MsgOneof)(nil),
+ (*M2_EnumOneof)(nil),
+ (*M2_BytesOneof)(nil),
+ (*M2_StringOneof2)(nil),
+ (*M2_IntOneof2)(nil),
+ (*M2_MsgOneof2)(nil),
+ (*M2_EnumOneof2)(nil),
+ (*M2_BytesOneof2)(nil),
+ }
+ file_proto2test_proto_msgTypes[2].OneofWrappers = []any{
+ (*OtherProto2_StringOneof)(nil),
+ (*OtherProto2_IntOneof)(nil),
+ (*OtherProto2_MsgOneof)(nil),
+ (*OtherProto2_EnumOneof)(nil),
+ (*OtherProto2_BytesOneof)(nil),
+ (*OtherProto2_StringOneof2)(nil),
+ (*OtherProto2_IntOneof2)(nil),
+ (*OtherProto2_MsgOneof2)(nil),
+ (*OtherProto2_EnumOneof2)(nil),
+ (*OtherProto2_BytesOneof2)(nil),
+ }
+ file_proto2test_proto_msgTypes[3].OneofWrappers = []any{
+ (*M2Outer_InnerMsg)(nil),
+ (*M2Outer_StringOneof)(nil),
+ }
+ file_proto2test_proto_msgTypes[4].OneofWrappers = []any{
+ (*ConflictingOneof_Sub_)(nil),
+ (*ConflictingOneof_Otherwise)(nil),
+ }
+ file_proto2test_proto_msgTypes[8].OneofWrappers = []any{
+ (*M2Outer_MInner_StringInner)(nil),
+ (*M2Outer_MInner_IntInner)(nil),
+ }
+ file_proto2test_proto_msgTypes[10].OneofWrappers = []any{
+ (*ConflictingOneof_DeepSub_Sub_)(nil),
+ (*ConflictingOneof_DeepSub_Otherwise)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_proto2test_proto_rawDesc,
+ NumEnums: 2,
+ NumMessages: 12,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_proto2test_proto_goTypes,
+ DependencyIndexes: file_proto2test_proto_depIdxs,
+ EnumInfos: file_proto2test_proto_enumTypes,
+ MessageInfos: file_proto2test_proto_msgTypes,
+ }.Build()
+ File_proto2test_proto = out.File
+ file_proto2test_proto_rawDesc = nil
+ file_proto2test_proto_goTypes = nil
+ file_proto2test_proto_depIdxs = nil
+}
diff --git a/internal/fix/testdata/proto2test_go_proto/proto2test.proto b/internal/fix/testdata/proto2test_go_proto/proto2test.proto
new file mode 100644
index 0000000..e4c93d3
--- /dev/null
+++ b/internal/fix/testdata/proto2test_go_proto/proto2test.proto
@@ -0,0 +1,150 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.o2o.test;
+
+import "google/protobuf/go_features.proto";
+
+message DoNotMigrateMe {
+ option features.(pb.go).api_level = API_OPEN;
+
+ bool b = 1;
+}
+
+message M2 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ bool b = 1;
+ bytes bytes = 2;
+ float f32 = 3;
+ double f64 = 4;
+ int32 i32 = 5;
+ int64 i64 = 6;
+ uint32 ui32 = 7;
+ uint64 ui64 = 8;
+ string s = 9;
+ M2 m = 10;
+ repeated int32 is = 11;
+ repeated M2 ms = 12;
+ map<string, bool> map = 29;
+ enum Enum {
+ E_VAL = 0;
+ }
+ Enum e = 13;
+ oneof oneof_field {
+ string string_oneof = 14;
+ int64 int_oneof = 15;
+ M2 msg_oneof = 16;
+ Enum enum_oneof = 17;
+ bytes bytes_oneof = 18;
+ }
+ oneof oneof_field2 {
+ string string_oneof2 = 19;
+ int64 int_oneof2 = 20;
+ M2 msg_oneof2 = 21;
+ Enum enum_oneof2 = 22;
+ bytes bytes_oneof2 = 23;
+ }
+ int32 build = 24;
+ int32 proto_message = 25;
+ int32 reset = 26;
+ int32 string = 27;
+ int32 descriptor = 28;
+}
+
+message OtherProto2 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ bool b = 1;
+ bytes bytes = 2;
+ float f32 = 3;
+ double f64 = 4;
+ int32 i32 = 5;
+ int64 i64 = 6;
+ uint32 ui32 = 7;
+ uint64 ui64 = 8;
+ string s = 9;
+ OtherProto2 m = 10;
+ repeated int32 is = 11;
+ repeated OtherProto2 ms = 12;
+ map<string, bool> map = 29;
+ enum OtherEnum {
+ E_VAL = 0;
+ }
+ OtherEnum e = 13;
+ oneof oneof_field {
+ string string_oneof = 14;
+ int64 int_oneof = 15;
+ OtherProto2 msg_oneof = 16;
+ OtherEnum enum_oneof = 17;
+ bytes bytes_oneof = 18;
+ }
+ oneof oneof_field2 {
+ string string_oneof2 = 19;
+ int64 int_oneof2 = 20;
+ OtherProto2 msg_oneof2 = 21;
+ OtherEnum enum_oneof2 = 22;
+ bytes bytes_oneof2 = 23;
+ }
+ int32 build = 24;
+ int32 proto_message = 25;
+ int32 reset = 26;
+ int32 string = 27;
+ int32 descriptor = 28;
+}
+
+message M2Outer {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message MInner {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ oneof inner_oneof {
+ string string_inner = 19;
+ int64 int_inner = 20;
+ }
+ }
+ oneof outer_oneof {
+ MInner inner_msg = 1;
+ string string_oneof = 2;
+ }
+}
+
+message ConflictingOneof {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Sub {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ oneof included {
+ Sub sub = 1;
+ string otherwise = 2;
+ }
+
+ message DeepSub {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Sub {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ oneof deeply_included {
+ Sub sub = 1;
+ string otherwise = 3;
+ }
+ }
+}
+
+message SetterNameConflict {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ int32 stat = 1;
+ int32 set_stat = 2;
+ int32 get_stat = 3;
+ int32 has_stat = 4;
+ int32 clear_stat = 5;
+}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go b/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go
new file mode 100644
index 0000000..95d9148
--- /dev/null
+++ b/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go
@@ -0,0 +1,950 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: proto3test.proto
+
+//go:build !protoopaque
+
+package proto3test_go_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type M3_Enum int32
+
+const (
+ M3_E_VAL M3_Enum = 0
+)
+
+// Enum value maps for M3_Enum.
+var (
+ M3_Enum_name = map[int32]string{
+ 0: "E_VAL",
+ }
+ M3_Enum_value = map[string]int32{
+ "E_VAL": 0,
+ }
+)
+
+func (x M3_Enum) Enum() *M3_Enum {
+ p := new(M3_Enum)
+ *p = x
+ return p
+}
+
+func (x M3_Enum) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (M3_Enum) Descriptor() protoreflect.EnumDescriptor {
+ return file_proto3test_proto_enumTypes[0].Descriptor()
+}
+
+func (M3_Enum) Type() protoreflect.EnumType {
+ return &file_proto3test_proto_enumTypes[0]
+}
+
+func (x M3_Enum) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type M3 struct {
+ state protoimpl.MessageState `protogen:"hybrid.v1"`
+ B bool `protobuf:"varint,1,opt,name=b,proto3" json:"b,omitempty"`
+ Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"`
+ F32 float32 `protobuf:"fixed32,3,opt,name=f32,proto3" json:"f32,omitempty"`
+ F64 float64 `protobuf:"fixed64,4,opt,name=f64,proto3" json:"f64,omitempty"`
+ I32 int32 `protobuf:"varint,5,opt,name=i32,proto3" json:"i32,omitempty"`
+ I64 int64 `protobuf:"varint,6,opt,name=i64,proto3" json:"i64,omitempty"`
+ Ui32 uint32 `protobuf:"varint,7,opt,name=ui32,proto3" json:"ui32,omitempty"`
+ Ui64 uint64 `protobuf:"varint,8,opt,name=ui64,proto3" json:"ui64,omitempty"`
+ S string `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
+ M *M3 `protobuf:"bytes,10,opt,name=m,proto3" json:"m,omitempty"`
+ Is []int32 `protobuf:"varint,11,rep,packed,name=is,proto3" json:"is,omitempty"`
+ Ms []*M3 `protobuf:"bytes,12,rep,name=ms,proto3" json:"ms,omitempty"`
+ Map map[string]bool `protobuf:"bytes,29,rep,name=map,proto3" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+ E M3_Enum `protobuf:"varint,13,opt,name=e,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum" json:"e,omitempty"`
+ // Types that are valid to be assigned to OneofField:
+ //
+ // *M3_StringOneof
+ // *M3_IntOneof
+ // *M3_MsgOneof
+ // *M3_EnumOneof
+ // *M3_BytesOneof
+ // *M3_Build
+ // *M3_ProtoMessage_
+ // *M3_Reset_
+ // *M3_String_
+ // *M3_Descriptor_
+ OneofField isM3_OneofField `protobuf_oneof:"oneof_field"`
+ SecondI32 int32 `protobuf:"varint,30,opt,name=second_i32,json=secondI32,proto3" json:"second_i32,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *M3) Reset() {
+ *x = M3{}
+ mi := &file_proto3test_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *M3) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*M3) ProtoMessage() {}
+
+func (x *M3) ProtoReflect() protoreflect.Message {
+ mi := &file_proto3test_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *M3) GetB() bool {
+ if x != nil {
+ return x.B
+ }
+ return false
+}
+
+func (x *M3) GetBytes() []byte {
+ if x != nil {
+ return x.Bytes
+ }
+ return nil
+}
+
+func (x *M3) GetF32() float32 {
+ if x != nil {
+ return x.F32
+ }
+ return 0
+}
+
+func (x *M3) GetF64() float64 {
+ if x != nil {
+ return x.F64
+ }
+ return 0
+}
+
+func (x *M3) GetI32() int32 {
+ if x != nil {
+ return x.I32
+ }
+ return 0
+}
+
+func (x *M3) GetI64() int64 {
+ if x != nil {
+ return x.I64
+ }
+ return 0
+}
+
+func (x *M3) GetUi32() uint32 {
+ if x != nil {
+ return x.Ui32
+ }
+ return 0
+}
+
+func (x *M3) GetUi64() uint64 {
+ if x != nil {
+ return x.Ui64
+ }
+ return 0
+}
+
+func (x *M3) GetS() string {
+ if x != nil {
+ return x.S
+ }
+ return ""
+}
+
+func (x *M3) GetM() *M3 {
+ if x != nil {
+ return x.M
+ }
+ return nil
+}
+
+func (x *M3) GetIs() []int32 {
+ if x != nil {
+ return x.Is
+ }
+ return nil
+}
+
+func (x *M3) GetMs() []*M3 {
+ if x != nil {
+ return x.Ms
+ }
+ return nil
+}
+
+func (x *M3) GetMap() map[string]bool {
+ if x != nil {
+ return x.Map
+ }
+ return nil
+}
+
+func (x *M3) GetE() M3_Enum {
+ if x != nil {
+ return x.E
+ }
+ return M3_E_VAL
+}
+
+func (x *M3) GetOneofField() isM3_OneofField {
+ if x != nil {
+ return x.OneofField
+ }
+ return nil
+}
+
+func (x *M3) GetStringOneof() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_StringOneof); ok {
+ return x.StringOneof
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetIntOneof() int64 {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_IntOneof); ok {
+ return x.IntOneof
+ }
+ }
+ return 0
+}
+
+func (x *M3) GetMsgOneof() *M3 {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_MsgOneof); ok {
+ return x.MsgOneof
+ }
+ }
+ return nil
+}
+
+func (x *M3) GetEnumOneof() M3_Enum {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_EnumOneof); ok {
+ return x.EnumOneof
+ }
+ }
+ return M3_E_VAL
+}
+
+func (x *M3) GetBytesOneof() []byte {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_BytesOneof); ok {
+ return x.BytesOneof
+ }
+ }
+ return nil
+}
+
+func (x *M3) GetBuild_() int32 {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_Build); ok {
+ return x.Build
+ }
+ }
+ return 0
+}
+
+// Deprecated: Use GetBuild_ instead.
+func (x *M3) GetBuild() int32 {
+ return x.GetBuild_()
+}
+
+func (x *M3) GetProtoMessage() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_ProtoMessage_); ok {
+ return x.ProtoMessage_
+ }
+ }
+ return ""
+}
+
+// Deprecated: Use GetProtoMessage instead.
+func (x *M3) GetProtoMessage_() string {
+ return x.GetProtoMessage()
+}
+
+func (x *M3) GetReset() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_Reset_); ok {
+ return x.Reset_
+ }
+ }
+ return ""
+}
+
+// Deprecated: Use GetReset instead.
+func (x *M3) GetReset_() string {
+ return x.GetReset()
+}
+
+func (x *M3) GetString() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_String_); ok {
+ return x.String_
+ }
+ }
+ return ""
+}
+
+// Deprecated: Use GetString instead.
+func (x *M3) GetString_() string {
+ return x.GetString()
+}
+
+func (x *M3) GetDescriptor() string {
+ if x != nil {
+ if x, ok := x.OneofField.(*M3_Descriptor_); ok {
+ return x.Descriptor_
+ }
+ }
+ return ""
+}
+
+// Deprecated: Use GetDescriptor instead.
+func (x *M3) GetDescriptor_() string {
+ return x.GetDescriptor()
+}
+
+func (x *M3) GetSecondI32() int32 {
+ if x != nil {
+ return x.SecondI32
+ }
+ return 0
+}
+
+func (x *M3) SetB(v bool) {
+ x.B = v
+}
+
+func (x *M3) SetBytes(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.Bytes = v
+}
+
+func (x *M3) SetF32(v float32) {
+ x.F32 = v
+}
+
+func (x *M3) SetF64(v float64) {
+ x.F64 = v
+}
+
+func (x *M3) SetI32(v int32) {
+ x.I32 = v
+}
+
+func (x *M3) SetI64(v int64) {
+ x.I64 = v
+}
+
+func (x *M3) SetUi32(v uint32) {
+ x.Ui32 = v
+}
+
+func (x *M3) SetUi64(v uint64) {
+ x.Ui64 = v
+}
+
+func (x *M3) SetS(v string) {
+ x.S = v
+}
+
+func (x *M3) SetM(v *M3) {
+ x.M = v
+}
+
+func (x *M3) SetIs(v []int32) {
+ x.Is = v
+}
+
+func (x *M3) SetMs(v []*M3) {
+ x.Ms = v
+}
+
+func (x *M3) SetMap(v map[string]bool) {
+ x.Map = v
+}
+
+func (x *M3) SetE(v M3_Enum) {
+ x.E = v
+}
+
+func (x *M3) SetStringOneof(v string) {
+ x.OneofField = &M3_StringOneof{v}
+}
+
+func (x *M3) SetIntOneof(v int64) {
+ x.OneofField = &M3_IntOneof{v}
+}
+
+func (x *M3) SetMsgOneof(v *M3) {
+ if v == nil {
+ x.OneofField = nil
+ return
+ }
+ x.OneofField = &M3_MsgOneof{v}
+}
+
+func (x *M3) SetEnumOneof(v M3_Enum) {
+ x.OneofField = &M3_EnumOneof{v}
+}
+
+func (x *M3) SetBytesOneof(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.OneofField = &M3_BytesOneof{v}
+}
+
+func (x *M3) SetBuild_(v int32) {
+ x.OneofField = &M3_Build{v}
+}
+
+func (x *M3) SetProtoMessage(v string) {
+ x.OneofField = &M3_ProtoMessage_{v}
+}
+
+func (x *M3) SetReset(v string) {
+ x.OneofField = &M3_Reset_{v}
+}
+
+func (x *M3) SetString(v string) {
+ x.OneofField = &M3_String_{v}
+}
+
+func (x *M3) SetDescriptor(v string) {
+ x.OneofField = &M3_Descriptor_{v}
+}
+
+func (x *M3) SetSecondI32(v int32) {
+ x.SecondI32 = v
+}
+
+func (x *M3) HasM() bool {
+ if x == nil {
+ return false
+ }
+ return x.M != nil
+}
+
+func (x *M3) HasOneofField() bool {
+ if x == nil {
+ return false
+ }
+ return x.OneofField != nil
+}
+
+func (x *M3) HasStringOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_StringOneof)
+ return ok
+}
+
+func (x *M3) HasIntOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_IntOneof)
+ return ok
+}
+
+func (x *M3) HasMsgOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_MsgOneof)
+ return ok
+}
+
+func (x *M3) HasEnumOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_EnumOneof)
+ return ok
+}
+
+func (x *M3) HasBytesOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_BytesOneof)
+ return ok
+}
+
+func (x *M3) HasBuild_() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_Build)
+ return ok
+}
+
+func (x *M3) HasProtoMessage() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_ProtoMessage_)
+ return ok
+}
+
+func (x *M3) HasReset() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_Reset_)
+ return ok
+}
+
+func (x *M3) HasString() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_String_)
+ return ok
+}
+
+func (x *M3) HasDescriptor() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.OneofField.(*M3_Descriptor_)
+ return ok
+}
+
+func (x *M3) ClearM() {
+ x.M = nil
+}
+
+func (x *M3) ClearOneofField() {
+ x.OneofField = nil
+}
+
+func (x *M3) ClearStringOneof() {
+ if _, ok := x.OneofField.(*M3_StringOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearIntOneof() {
+ if _, ok := x.OneofField.(*M3_IntOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearMsgOneof() {
+ if _, ok := x.OneofField.(*M3_MsgOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearEnumOneof() {
+ if _, ok := x.OneofField.(*M3_EnumOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearBytesOneof() {
+ if _, ok := x.OneofField.(*M3_BytesOneof); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearBuild_() {
+ if _, ok := x.OneofField.(*M3_Build); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearProtoMessage() {
+ if _, ok := x.OneofField.(*M3_ProtoMessage_); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearReset() {
+ if _, ok := x.OneofField.(*M3_Reset_); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearString() {
+ if _, ok := x.OneofField.(*M3_String_); ok {
+ x.OneofField = nil
+ }
+}
+
+func (x *M3) ClearDescriptor() {
+ if _, ok := x.OneofField.(*M3_Descriptor_); ok {
+ x.OneofField = nil
+ }
+}
+
+const M3_OneofField_not_set_case case_M3_OneofField = 0
+const M3_StringOneof_case case_M3_OneofField = 14
+const M3_IntOneof_case case_M3_OneofField = 15
+const M3_MsgOneof_case case_M3_OneofField = 16
+const M3_EnumOneof_case case_M3_OneofField = 17
+const M3_BytesOneof_case case_M3_OneofField = 18
+const M3_Build_case case_M3_OneofField = 24
+const M3_ProtoMessage__case case_M3_OneofField = 25
+const M3_Reset__case case_M3_OneofField = 26
+const M3_String__case case_M3_OneofField = 27
+const M3_Descriptor__case case_M3_OneofField = 28
+
+func (x *M3) WhichOneofField() case_M3_OneofField {
+ if x == nil {
+ return M3_OneofField_not_set_case
+ }
+ switch x.OneofField.(type) {
+ case *M3_StringOneof:
+ return M3_StringOneof_case
+ case *M3_IntOneof:
+ return M3_IntOneof_case
+ case *M3_MsgOneof:
+ return M3_MsgOneof_case
+ case *M3_EnumOneof:
+ return M3_EnumOneof_case
+ case *M3_BytesOneof:
+ return M3_BytesOneof_case
+ case *M3_Build:
+ return M3_Build_case
+ case *M3_ProtoMessage_:
+ return M3_ProtoMessage__case
+ case *M3_Reset_:
+ return M3_Reset__case
+ case *M3_String_:
+ return M3_String__case
+ case *M3_Descriptor_:
+ return M3_Descriptor__case
+ default:
+ return M3_OneofField_not_set_case
+ }
+}
+
+type M3_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ B bool
+ Bytes []byte
+ F32 float32
+ F64 float64
+ I32 int32
+ I64 int64
+ Ui32 uint32
+ Ui64 uint64
+ S string
+ M *M3
+ Is []int32
+ Ms []*M3
+ Map map[string]bool
+ E M3_Enum
+ // Fields of oneof OneofField:
+ StringOneof *string
+ IntOneof *int64
+ MsgOneof *M3
+ EnumOneof *M3_Enum
+ BytesOneof []byte
+ Build_ *int32
+ ProtoMessage *string
+ Reset *string
+ String *string
+ Descriptor *string
+ // -- end of OneofField
+ SecondI32 int32
+}
+
+func (b0 M3_builder) Build() *M3 {
+ m0 := &M3{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.B = b.B
+ x.Bytes = b.Bytes
+ x.F32 = b.F32
+ x.F64 = b.F64
+ x.I32 = b.I32
+ x.I64 = b.I64
+ x.Ui32 = b.Ui32
+ x.Ui64 = b.Ui64
+ x.S = b.S
+ x.M = b.M
+ x.Is = b.Is
+ x.Ms = b.Ms
+ x.Map = b.Map
+ x.E = b.E
+ if b.StringOneof != nil {
+ x.OneofField = &M3_StringOneof{*b.StringOneof}
+ }
+ if b.IntOneof != nil {
+ x.OneofField = &M3_IntOneof{*b.IntOneof}
+ }
+ if b.MsgOneof != nil {
+ x.OneofField = &M3_MsgOneof{b.MsgOneof}
+ }
+ if b.EnumOneof != nil {
+ x.OneofField = &M3_EnumOneof{*b.EnumOneof}
+ }
+ if b.BytesOneof != nil {
+ x.OneofField = &M3_BytesOneof{b.BytesOneof}
+ }
+ if b.Build_ != nil {
+ x.OneofField = &M3_Build{*b.Build_}
+ }
+ if b.ProtoMessage != nil {
+ x.OneofField = &M3_ProtoMessage_{*b.ProtoMessage}
+ }
+ if b.Reset != nil {
+ x.OneofField = &M3_Reset_{*b.Reset}
+ }
+ if b.String != nil {
+ x.OneofField = &M3_String_{*b.String}
+ }
+ if b.Descriptor != nil {
+ x.OneofField = &M3_Descriptor_{*b.Descriptor}
+ }
+ x.SecondI32 = b.SecondI32
+ return m0
+}
+
+type case_M3_OneofField protoreflect.FieldNumber
+
+func (x case_M3_OneofField) String() string {
+ md := file_proto3test_proto_msgTypes[0].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isM3_OneofField interface {
+ isM3_OneofField()
+}
+
+type M3_StringOneof struct {
+ StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,proto3,oneof"`
+}
+
+type M3_IntOneof struct {
+ IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,proto3,oneof"`
+}
+
+type M3_MsgOneof struct {
+ MsgOneof *M3 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,proto3,oneof"`
+}
+
+type M3_EnumOneof struct {
+ EnumOneof M3_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum,oneof"`
+}
+
+type M3_BytesOneof struct {
+ BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,proto3,oneof"`
+}
+
+type M3_Build struct {
+ Build int32 `protobuf:"varint,24,opt,name=build,proto3,oneof"`
+}
+
+type M3_ProtoMessage_ struct {
+ ProtoMessage_ string `protobuf:"bytes,25,opt,name=proto_message,json=protoMessage,proto3,oneof"`
+}
+
+type M3_Reset_ struct {
+ Reset_ string `protobuf:"bytes,26,opt,name=reset,proto3,oneof"`
+}
+
+type M3_String_ struct {
+ String_ string `protobuf:"bytes,27,opt,name=string,proto3,oneof"`
+}
+
+type M3_Descriptor_ struct {
+ Descriptor_ string `protobuf:"bytes,28,opt,name=descriptor,proto3,oneof"`
+}
+
+func (*M3_StringOneof) isM3_OneofField() {}
+
+func (*M3_IntOneof) isM3_OneofField() {}
+
+func (*M3_MsgOneof) isM3_OneofField() {}
+
+func (*M3_EnumOneof) isM3_OneofField() {}
+
+func (*M3_BytesOneof) isM3_OneofField() {}
+
+func (*M3_Build) isM3_OneofField() {}
+
+func (*M3_ProtoMessage_) isM3_OneofField() {}
+
+func (*M3_Reset_) isM3_OneofField() {}
+
+func (*M3_String_) isM3_OneofField() {}
+
+func (*M3_Descriptor_) isM3_OneofField() {}
+
+var File_proto3test_proto protoreflect.FileDescriptor
+
+var file_proto3test_proto_rawDesc = []byte{
+ 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x12, 0x23, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32,
+ 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x22, 0xb0, 0x07, 0x0a, 0x02, 0x4d, 0x33, 0x12, 0x0c,
+ 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74,
+ 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52,
+ 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18,
+ 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69,
+ 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12,
+ 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69,
+ 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73,
+ 0x12, 0x35, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74,
+ 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20,
+ 0x03, 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x37, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20,
+ 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x02, 0x6d, 0x73,
+ 0x12, 0x42, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65,
+ 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
+ 0x03, 0x6d, 0x61, 0x70, 0x12, 0x3a, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32,
+ 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e,
+ 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65,
+ 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+ 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65,
+ 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x46, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f,
+ 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33,
+ 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4d, 0x0a, 0x0a,
+ 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e,
+ 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f,
+ 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f,
+ 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00,
+ 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b, 0x62,
+ 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c,
+ 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x16,
+ 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52,
+ 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
+ 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a,
+ 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05,
+ 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18,
+ 0x1b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12,
+ 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20,
+ 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
+ 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x18,
+ 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x49, 0x33, 0x32,
+ 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+ 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+ 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76,
+ 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d,
+ 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x42, 0x0d, 0x0a, 0x0b, 0x6f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x33,
+}
+
+var file_proto3test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_proto3test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proto3test_proto_goTypes = []any{
+ (M3_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ (*M3)(nil), // 1: net.proto2.go.open2opaque.o2o.test3.M3
+ nil, // 2: net.proto2.go.open2opaque.o2o.test3.M3.MapEntry
+}
+var file_proto3test_proto_depIdxs = []int32{
+ 1, // 0: net.proto2.go.open2opaque.o2o.test3.M3.m:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 1, // 1: net.proto2.go.open2opaque.o2o.test3.M3.ms:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 2, // 2: net.proto2.go.open2opaque.o2o.test3.M3.map:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.MapEntry
+ 0, // 3: net.proto2.go.open2opaque.o2o.test3.M3.e:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ 1, // 4: net.proto2.go.open2opaque.o2o.test3.M3.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 0, // 5: net.proto2.go.open2opaque.o2o.test3.M3.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_proto3test_proto_init() }
+func file_proto3test_proto_init() {
+ if File_proto3test_proto != nil {
+ return
+ }
+ file_proto3test_proto_msgTypes[0].OneofWrappers = []any{
+ (*M3_StringOneof)(nil),
+ (*M3_IntOneof)(nil),
+ (*M3_MsgOneof)(nil),
+ (*M3_EnumOneof)(nil),
+ (*M3_BytesOneof)(nil),
+ (*M3_Build)(nil),
+ (*M3_ProtoMessage_)(nil),
+ (*M3_Reset_)(nil),
+ (*M3_String_)(nil),
+ (*M3_Descriptor_)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_proto3test_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_proto3test_proto_goTypes,
+ DependencyIndexes: file_proto3test_proto_depIdxs,
+ EnumInfos: file_proto3test_proto_enumTypes,
+ MessageInfos: file_proto3test_proto_msgTypes,
+ }.Build()
+ File_proto3test_proto = out.File
+ file_proto3test_proto_rawDesc = nil
+ file_proto3test_proto_goTypes = nil
+ file_proto3test_proto_depIdxs = nil
+}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test.proto b/internal/fix/testdata/proto3test_go_proto/proto3test.proto
new file mode 100644
index 0000000..414b4b6
--- /dev/null
+++ b/internal/fix/testdata/proto3test_go_proto/proto3test.proto
@@ -0,0 +1,41 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+syntax = "proto3";
+
+package net.proto2.go.open2opaque.o2o.test3;
+
+message M3 {
+
+ bool b = 1;
+ bytes bytes = 2;
+ float f32 = 3;
+ double f64 = 4;
+ int32 i32 = 5;
+ int64 i64 = 6;
+ uint32 ui32 = 7;
+ uint64 ui64 = 8;
+ string s = 9;
+ M3 m = 10;
+ repeated int32 is = 11;
+ repeated M3 ms = 12;
+ map<string, bool> map = 29;
+ enum Enum {
+ E_VAL = 0;
+ }
+ Enum e = 13;
+ oneof oneof_field {
+ string string_oneof = 14;
+ int64 int_oneof = 15;
+ M3 msg_oneof = 16;
+ Enum enum_oneof = 17;
+ bytes bytes_oneof = 18;
+ int32 build = 24;
+ string proto_message = 25;
+ string reset = 26;
+ string string = 27;
+ string descriptor = 28;
+ }
+ int32 second_i32 = 30;
+}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go b/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go
new file mode 100644
index 0000000..fef9755
--- /dev/null
+++ b/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go
@@ -0,0 +1,908 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.35.2-devel
+// protoc v5.29.1
+// source: proto3test.proto
+
+//go:build protoopaque
+
+package proto3test_go_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type M3_Enum int32
+
+const (
+ M3_E_VAL M3_Enum = 0
+)
+
+// Enum value maps for M3_Enum.
+var (
+ M3_Enum_name = map[int32]string{
+ 0: "E_VAL",
+ }
+ M3_Enum_value = map[string]int32{
+ "E_VAL": 0,
+ }
+)
+
+func (x M3_Enum) Enum() *M3_Enum {
+ p := new(M3_Enum)
+ *p = x
+ return p
+}
+
+func (x M3_Enum) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (M3_Enum) Descriptor() protoreflect.EnumDescriptor {
+ return file_proto3test_proto_enumTypes[0].Descriptor()
+}
+
+func (M3_Enum) Type() protoreflect.EnumType {
+ return &file_proto3test_proto_enumTypes[0]
+}
+
+func (x M3_Enum) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+type M3 struct {
+ state protoimpl.MessageState `protogen:"opaque.v1"`
+ xxx_hidden_B bool `protobuf:"varint,1,opt,name=b,proto3" json:"b,omitempty"`
+ xxx_hidden_Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"`
+ xxx_hidden_F32 float32 `protobuf:"fixed32,3,opt,name=f32,proto3" json:"f32,omitempty"`
+ xxx_hidden_F64 float64 `protobuf:"fixed64,4,opt,name=f64,proto3" json:"f64,omitempty"`
+ xxx_hidden_I32 int32 `protobuf:"varint,5,opt,name=i32,proto3" json:"i32,omitempty"`
+ xxx_hidden_I64 int64 `protobuf:"varint,6,opt,name=i64,proto3" json:"i64,omitempty"`
+ xxx_hidden_Ui32 uint32 `protobuf:"varint,7,opt,name=ui32,proto3" json:"ui32,omitempty"`
+ xxx_hidden_Ui64 uint64 `protobuf:"varint,8,opt,name=ui64,proto3" json:"ui64,omitempty"`
+ xxx_hidden_S string `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
+ xxx_hidden_M *M3 `protobuf:"bytes,10,opt,name=m,proto3" json:"m,omitempty"`
+ xxx_hidden_Is []int32 `protobuf:"varint,11,rep,packed,name=is,proto3" json:"is,omitempty"`
+ xxx_hidden_Ms *[]*M3 `protobuf:"bytes,12,rep,name=ms,proto3" json:"ms,omitempty"`
+ xxx_hidden_Map map[string]bool `protobuf:"bytes,29,rep,name=map,proto3" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+ xxx_hidden_E M3_Enum `protobuf:"varint,13,opt,name=e,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum" json:"e,omitempty"`
+ xxx_hidden_OneofField isM3_OneofField `protobuf_oneof:"oneof_field"`
+ xxx_hidden_SecondI32 int32 `protobuf:"varint,30,opt,name=second_i32,json=secondI32,proto3" json:"second_i32,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *M3) Reset() {
+ *x = M3{}
+ mi := &file_proto3test_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *M3) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*M3) ProtoMessage() {}
+
+func (x *M3) ProtoReflect() protoreflect.Message {
+ mi := &file_proto3test_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+func (x *M3) GetB() bool {
+ if x != nil {
+ return x.xxx_hidden_B
+ }
+ return false
+}
+
+func (x *M3) GetBytes() []byte {
+ if x != nil {
+ return x.xxx_hidden_Bytes
+ }
+ return nil
+}
+
+func (x *M3) GetF32() float32 {
+ if x != nil {
+ return x.xxx_hidden_F32
+ }
+ return 0
+}
+
+func (x *M3) GetF64() float64 {
+ if x != nil {
+ return x.xxx_hidden_F64
+ }
+ return 0
+}
+
+func (x *M3) GetI32() int32 {
+ if x != nil {
+ return x.xxx_hidden_I32
+ }
+ return 0
+}
+
+func (x *M3) GetI64() int64 {
+ if x != nil {
+ return x.xxx_hidden_I64
+ }
+ return 0
+}
+
+func (x *M3) GetUi32() uint32 {
+ if x != nil {
+ return x.xxx_hidden_Ui32
+ }
+ return 0
+}
+
+func (x *M3) GetUi64() uint64 {
+ if x != nil {
+ return x.xxx_hidden_Ui64
+ }
+ return 0
+}
+
+func (x *M3) GetS() string {
+ if x != nil {
+ return x.xxx_hidden_S
+ }
+ return ""
+}
+
+func (x *M3) GetM() *M3 {
+ if x != nil {
+ return x.xxx_hidden_M
+ }
+ return nil
+}
+
+func (x *M3) GetIs() []int32 {
+ if x != nil {
+ return x.xxx_hidden_Is
+ }
+ return nil
+}
+
+func (x *M3) GetMs() []*M3 {
+ if x != nil {
+ if x.xxx_hidden_Ms != nil {
+ return *x.xxx_hidden_Ms
+ }
+ }
+ return nil
+}
+
+func (x *M3) GetMap() map[string]bool {
+ if x != nil {
+ return x.xxx_hidden_Map
+ }
+ return nil
+}
+
+func (x *M3) GetE() M3_Enum {
+ if x != nil {
+ return x.xxx_hidden_E
+ }
+ return M3_E_VAL
+}
+
+func (x *M3) GetStringOneof() string {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_StringOneof); ok {
+ return x.StringOneof
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetIntOneof() int64 {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_IntOneof); ok {
+ return x.IntOneof
+ }
+ }
+ return 0
+}
+
+func (x *M3) GetMsgOneof() *M3 {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof); ok {
+ return x.MsgOneof
+ }
+ }
+ return nil
+}
+
+func (x *M3) GetEnumOneof() M3_Enum {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof); ok {
+ return x.EnumOneof
+ }
+ }
+ return M3_E_VAL
+}
+
+func (x *M3) GetBytesOneof() []byte {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof); ok {
+ return x.BytesOneof
+ }
+ }
+ return nil
+}
+
+func (x *M3) GetBuild_() int32 {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_Build); ok {
+ return x.Build
+ }
+ }
+ return 0
+}
+
+func (x *M3) GetProtoMessage() string {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_); ok {
+ return x.ProtoMessage_
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetReset() string {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_Reset_); ok {
+ return x.Reset_
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetString() string {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_String_); ok {
+ return x.String_
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetDescriptor() string {
+ if x != nil {
+ if x, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_); ok {
+ return x.Descriptor_
+ }
+ }
+ return ""
+}
+
+func (x *M3) GetSecondI32() int32 {
+ if x != nil {
+ return x.xxx_hidden_SecondI32
+ }
+ return 0
+}
+
+func (x *M3) SetB(v bool) {
+ x.xxx_hidden_B = v
+}
+
+func (x *M3) SetBytes(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.xxx_hidden_Bytes = v
+}
+
+func (x *M3) SetF32(v float32) {
+ x.xxx_hidden_F32 = v
+}
+
+func (x *M3) SetF64(v float64) {
+ x.xxx_hidden_F64 = v
+}
+
+func (x *M3) SetI32(v int32) {
+ x.xxx_hidden_I32 = v
+}
+
+func (x *M3) SetI64(v int64) {
+ x.xxx_hidden_I64 = v
+}
+
+func (x *M3) SetUi32(v uint32) {
+ x.xxx_hidden_Ui32 = v
+}
+
+func (x *M3) SetUi64(v uint64) {
+ x.xxx_hidden_Ui64 = v
+}
+
+func (x *M3) SetS(v string) {
+ x.xxx_hidden_S = v
+}
+
+func (x *M3) SetM(v *M3) {
+ x.xxx_hidden_M = v
+}
+
+func (x *M3) SetIs(v []int32) {
+ x.xxx_hidden_Is = v
+}
+
+func (x *M3) SetMs(v []*M3) {
+ x.xxx_hidden_Ms = &v
+}
+
+func (x *M3) SetMap(v map[string]bool) {
+ x.xxx_hidden_Map = v
+}
+
+func (x *M3) SetE(v M3_Enum) {
+ x.xxx_hidden_E = v
+}
+
+func (x *M3) SetStringOneof(v string) {
+ x.xxx_hidden_OneofField = &m3_StringOneof{v}
+}
+
+func (x *M3) SetIntOneof(v int64) {
+ x.xxx_hidden_OneofField = &m3_IntOneof{v}
+}
+
+func (x *M3) SetMsgOneof(v *M3) {
+ if v == nil {
+ x.xxx_hidden_OneofField = nil
+ return
+ }
+ x.xxx_hidden_OneofField = &m3_MsgOneof{v}
+}
+
+func (x *M3) SetEnumOneof(v M3_Enum) {
+ x.xxx_hidden_OneofField = &m3_EnumOneof{v}
+}
+
+func (x *M3) SetBytesOneof(v []byte) {
+ if v == nil {
+ v = []byte{}
+ }
+ x.xxx_hidden_OneofField = &m3_BytesOneof{v}
+}
+
+func (x *M3) SetBuild_(v int32) {
+ x.xxx_hidden_OneofField = &m3_Build{v}
+}
+
+func (x *M3) SetProtoMessage(v string) {
+ x.xxx_hidden_OneofField = &m3_ProtoMessage_{v}
+}
+
+func (x *M3) SetReset(v string) {
+ x.xxx_hidden_OneofField = &m3_Reset_{v}
+}
+
+func (x *M3) SetString(v string) {
+ x.xxx_hidden_OneofField = &m3_String_{v}
+}
+
+func (x *M3) SetDescriptor(v string) {
+ x.xxx_hidden_OneofField = &m3_Descriptor_{v}
+}
+
+func (x *M3) SetSecondI32(v int32) {
+ x.xxx_hidden_SecondI32 = v
+}
+
+func (x *M3) HasM() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_M != nil
+}
+
+func (x *M3) HasOneofField() bool {
+ if x == nil {
+ return false
+ }
+ return x.xxx_hidden_OneofField != nil
+}
+
+func (x *M3) HasStringOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_StringOneof)
+ return ok
+}
+
+func (x *M3) HasIntOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_IntOneof)
+ return ok
+}
+
+func (x *M3) HasMsgOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof)
+ return ok
+}
+
+func (x *M3) HasEnumOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof)
+ return ok
+}
+
+func (x *M3) HasBytesOneof() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof)
+ return ok
+}
+
+func (x *M3) HasBuild_() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_Build)
+ return ok
+}
+
+func (x *M3) HasProtoMessage() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_)
+ return ok
+}
+
+func (x *M3) HasReset() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_Reset_)
+ return ok
+}
+
+func (x *M3) HasString() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_String_)
+ return ok
+}
+
+func (x *M3) HasDescriptor() bool {
+ if x == nil {
+ return false
+ }
+ _, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_)
+ return ok
+}
+
+func (x *M3) ClearM() {
+ x.xxx_hidden_M = nil
+}
+
+func (x *M3) ClearOneofField() {
+ x.xxx_hidden_OneofField = nil
+}
+
+func (x *M3) ClearStringOneof() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_StringOneof); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearIntOneof() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_IntOneof); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearMsgOneof() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearEnumOneof() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearBytesOneof() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearBuild_() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_Build); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearProtoMessage() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearReset() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_Reset_); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearString() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_String_); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+func (x *M3) ClearDescriptor() {
+ if _, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_); ok {
+ x.xxx_hidden_OneofField = nil
+ }
+}
+
+const M3_OneofField_not_set_case case_M3_OneofField = 0
+const M3_StringOneof_case case_M3_OneofField = 14
+const M3_IntOneof_case case_M3_OneofField = 15
+const M3_MsgOneof_case case_M3_OneofField = 16
+const M3_EnumOneof_case case_M3_OneofField = 17
+const M3_BytesOneof_case case_M3_OneofField = 18
+const M3_Build_case case_M3_OneofField = 24
+const M3_ProtoMessage__case case_M3_OneofField = 25
+const M3_Reset__case case_M3_OneofField = 26
+const M3_String__case case_M3_OneofField = 27
+const M3_Descriptor__case case_M3_OneofField = 28
+
+func (x *M3) WhichOneofField() case_M3_OneofField {
+ if x == nil {
+ return M3_OneofField_not_set_case
+ }
+ switch x.xxx_hidden_OneofField.(type) {
+ case *m3_StringOneof:
+ return M3_StringOneof_case
+ case *m3_IntOneof:
+ return M3_IntOneof_case
+ case *m3_MsgOneof:
+ return M3_MsgOneof_case
+ case *m3_EnumOneof:
+ return M3_EnumOneof_case
+ case *m3_BytesOneof:
+ return M3_BytesOneof_case
+ case *m3_Build:
+ return M3_Build_case
+ case *m3_ProtoMessage_:
+ return M3_ProtoMessage__case
+ case *m3_Reset_:
+ return M3_Reset__case
+ case *m3_String_:
+ return M3_String__case
+ case *m3_Descriptor_:
+ return M3_Descriptor__case
+ default:
+ return M3_OneofField_not_set_case
+ }
+}
+
+type M3_builder struct {
+ _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
+
+ B bool
+ Bytes []byte
+ F32 float32
+ F64 float64
+ I32 int32
+ I64 int64
+ Ui32 uint32
+ Ui64 uint64
+ S string
+ M *M3
+ Is []int32
+ Ms []*M3
+ Map map[string]bool
+ E M3_Enum
+ // Fields of oneof xxx_hidden_OneofField:
+ StringOneof *string
+ IntOneof *int64
+ MsgOneof *M3
+ EnumOneof *M3_Enum
+ BytesOneof []byte
+ Build_ *int32
+ ProtoMessage *string
+ Reset *string
+ String *string
+ Descriptor *string
+ // -- end of xxx_hidden_OneofField
+ SecondI32 int32
+}
+
+func (b0 M3_builder) Build() *M3 {
+ m0 := &M3{}
+ b, x := &b0, m0
+ _, _ = b, x
+ x.xxx_hidden_B = b.B
+ x.xxx_hidden_Bytes = b.Bytes
+ x.xxx_hidden_F32 = b.F32
+ x.xxx_hidden_F64 = b.F64
+ x.xxx_hidden_I32 = b.I32
+ x.xxx_hidden_I64 = b.I64
+ x.xxx_hidden_Ui32 = b.Ui32
+ x.xxx_hidden_Ui64 = b.Ui64
+ x.xxx_hidden_S = b.S
+ x.xxx_hidden_M = b.M
+ x.xxx_hidden_Is = b.Is
+ x.xxx_hidden_Ms = &b.Ms
+ x.xxx_hidden_Map = b.Map
+ x.xxx_hidden_E = b.E
+ if b.StringOneof != nil {
+ x.xxx_hidden_OneofField = &m3_StringOneof{*b.StringOneof}
+ }
+ if b.IntOneof != nil {
+ x.xxx_hidden_OneofField = &m3_IntOneof{*b.IntOneof}
+ }
+ if b.MsgOneof != nil {
+ x.xxx_hidden_OneofField = &m3_MsgOneof{b.MsgOneof}
+ }
+ if b.EnumOneof != nil {
+ x.xxx_hidden_OneofField = &m3_EnumOneof{*b.EnumOneof}
+ }
+ if b.BytesOneof != nil {
+ x.xxx_hidden_OneofField = &m3_BytesOneof{b.BytesOneof}
+ }
+ if b.Build_ != nil {
+ x.xxx_hidden_OneofField = &m3_Build{*b.Build_}
+ }
+ if b.ProtoMessage != nil {
+ x.xxx_hidden_OneofField = &m3_ProtoMessage_{*b.ProtoMessage}
+ }
+ if b.Reset != nil {
+ x.xxx_hidden_OneofField = &m3_Reset_{*b.Reset}
+ }
+ if b.String != nil {
+ x.xxx_hidden_OneofField = &m3_String_{*b.String}
+ }
+ if b.Descriptor != nil {
+ x.xxx_hidden_OneofField = &m3_Descriptor_{*b.Descriptor}
+ }
+ x.xxx_hidden_SecondI32 = b.SecondI32
+ return m0
+}
+
+type case_M3_OneofField protoreflect.FieldNumber
+
+func (x case_M3_OneofField) String() string {
+ md := file_proto3test_proto_msgTypes[0].Descriptor()
+ if x == 0 {
+ return "not set"
+ }
+ return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))
+}
+
+type isM3_OneofField interface {
+ isM3_OneofField()
+}
+
+type m3_StringOneof struct {
+ StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,proto3,oneof"`
+}
+
+type m3_IntOneof struct {
+ IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,proto3,oneof"`
+}
+
+type m3_MsgOneof struct {
+ MsgOneof *M3 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,proto3,oneof"`
+}
+
+type m3_EnumOneof struct {
+ EnumOneof M3_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum,oneof"`
+}
+
+type m3_BytesOneof struct {
+ BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,proto3,oneof"`
+}
+
+type m3_Build struct {
+ Build int32 `protobuf:"varint,24,opt,name=build,proto3,oneof"`
+}
+
+type m3_ProtoMessage_ struct {
+ ProtoMessage_ string `protobuf:"bytes,25,opt,name=proto_message,json=protoMessage,proto3,oneof"`
+}
+
+type m3_Reset_ struct {
+ Reset_ string `protobuf:"bytes,26,opt,name=reset,proto3,oneof"`
+}
+
+type m3_String_ struct {
+ String_ string `protobuf:"bytes,27,opt,name=string,proto3,oneof"`
+}
+
+type m3_Descriptor_ struct {
+ Descriptor_ string `protobuf:"bytes,28,opt,name=descriptor,proto3,oneof"`
+}
+
+func (*m3_StringOneof) isM3_OneofField() {}
+
+func (*m3_IntOneof) isM3_OneofField() {}
+
+func (*m3_MsgOneof) isM3_OneofField() {}
+
+func (*m3_EnumOneof) isM3_OneofField() {}
+
+func (*m3_BytesOneof) isM3_OneofField() {}
+
+func (*m3_Build) isM3_OneofField() {}
+
+func (*m3_ProtoMessage_) isM3_OneofField() {}
+
+func (*m3_Reset_) isM3_OneofField() {}
+
+func (*m3_String_) isM3_OneofField() {}
+
+func (*m3_Descriptor_) isM3_OneofField() {}
+
+var File_proto3test_proto protoreflect.FileDescriptor
+
+var file_proto3test_proto_rawDesc = []byte{
+ 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x12, 0x23, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67,
+ 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32,
+ 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x22, 0xb0, 0x07, 0x0a, 0x02, 0x4d, 0x33, 0x12, 0x0c,
+ 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74,
+ 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52,
+ 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18,
+ 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69,
+ 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12,
+ 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69,
+ 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73,
+ 0x12, 0x35, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
+ 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74,
+ 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20,
+ 0x03, 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x37, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20,
+ 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+ 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e,
+ 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x02, 0x6d, 0x73,
+ 0x12, 0x42, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70,
+ 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65,
+ 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
+ 0x03, 0x6d, 0x61, 0x70, 0x12, 0x3a, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32,
+ 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e,
+ 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e,
+ 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65,
+ 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66,
+ 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+ 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65,
+ 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x46, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f,
+ 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61,
+ 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33,
+ 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4d, 0x0a, 0x0a,
+ 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e,
+ 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f,
+ 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f,
+ 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00,
+ 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b, 0x62,
+ 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c,
+ 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x16,
+ 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52,
+ 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
+ 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a,
+ 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05,
+ 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18,
+ 0x1b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12,
+ 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20,
+ 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
+ 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x18,
+ 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x49, 0x33, 0x32,
+ 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+ 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+ 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76,
+ 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d,
+ 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x42, 0x0d, 0x0a, 0x0b, 0x6f,
+ 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x33,
+}
+
+var file_proto3test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_proto3test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proto3test_proto_goTypes = []any{
+ (M3_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ (*M3)(nil), // 1: net.proto2.go.open2opaque.o2o.test3.M3
+ nil, // 2: net.proto2.go.open2opaque.o2o.test3.M3.MapEntry
+}
+var file_proto3test_proto_depIdxs = []int32{
+ 1, // 0: net.proto2.go.open2opaque.o2o.test3.M3.m:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 1, // 1: net.proto2.go.open2opaque.o2o.test3.M3.ms:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 2, // 2: net.proto2.go.open2opaque.o2o.test3.M3.map:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.MapEntry
+ 0, // 3: net.proto2.go.open2opaque.o2o.test3.M3.e:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ 1, // 4: net.proto2.go.open2opaque.o2o.test3.M3.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3
+ 0, // 5: net.proto2.go.open2opaque.o2o.test3.M3.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_proto3test_proto_init() }
+func file_proto3test_proto_init() {
+ if File_proto3test_proto != nil {
+ return
+ }
+ file_proto3test_proto_msgTypes[0].OneofWrappers = []any{
+ (*m3_StringOneof)(nil),
+ (*m3_IntOneof)(nil),
+ (*m3_MsgOneof)(nil),
+ (*m3_EnumOneof)(nil),
+ (*m3_BytesOneof)(nil),
+ (*m3_Build)(nil),
+ (*m3_ProtoMessage_)(nil),
+ (*m3_Reset_)(nil),
+ (*m3_String_)(nil),
+ (*m3_Descriptor_)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_proto3test_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_proto3test_proto_goTypes,
+ DependencyIndexes: file_proto3test_proto_depIdxs,
+ EnumInfos: file_proto3test_proto_enumTypes,
+ MessageInfos: file_proto3test_proto_msgTypes,
+ }.Build()
+ File_proto3test_proto = out.File
+ file_proto3test_proto_rawDesc = nil
+ file_proto3test_proto_goTypes = nil
+ file_proto3test_proto_depIdxs = nil
+}
diff --git a/internal/fix/usepointers.go b/internal/fix/usepointers.go
new file mode 100644
index 0000000..987a325
--- /dev/null
+++ b/internal/fix/usepointers.go
@@ -0,0 +1,218 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "go/token"
+ "go/types"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+ "google.golang.org/open2opaque/internal/protodetecttypes"
+)
+
+// usePointersPre replaces usage of Go proto structs as values with pointer to structs (i.e. the
+// only correct way to handle protos).
+//
+// NOTE: usePointersPre is called (once) for the *dst.File. Recursion aborts
+// because it returns false; the dstutil.Apply() calls within the function
+// traverse the entire file.
+func usePointersPre(c *cursor) bool {
+ // The rewrite below is only for the Red level.
+ if c.lvl.le(Yellow) {
+ return false
+ }
+
+ // x := pb.M{} => x := &pb.M{}
+ // x := pb.M{F: V} => x := &pb.M{F: V}
+ // This is ok if x is never copied (shallow copy).
+ // It could be ok in other cases too but we implement the common case for now.
+ // We only deal with the short-form definition for now (the long form hasn't showed up in practice).
+ dstutil.Apply(c.Node(), func(cur *dstutil.Cursor) bool {
+ switch n := cur.Node().(type) {
+ case *dst.BlockStmt:
+ for j, stmt := range n.List {
+ ident, ok := nonPtrProtoStructDef(c, stmt)
+ if !ok {
+ continue
+ }
+ if replaceWithPtr(c, ident, n.List[j+1:]) {
+ c.setType(ident, types.NewPointer(c.typeOf(ident)))
+ a := stmt.(*dst.AssignStmt)
+ a.Rhs[0] = addr(c, a.Rhs[0])
+ c.numUnsafeRewritesByReason[PotentialBuildBreakage]++
+ }
+ }
+
+ case *dst.Field:
+ T := c.typeOf(n.Type)
+ _, alreadyStar := n.Type.(*dst.StarExpr)
+ if (protodetecttypes.Type{T: T}).IsMessage() && !alreadyStar {
+ sexpr := &dst.StarExpr{X: n.Type}
+ c.setType(sexpr, types.NewPointer(T))
+ n.Type = sexpr
+ c.numUnsafeRewritesByReason[PotentialBuildBreakage]++
+ }
+
+ }
+ return true
+ }, nil)
+
+ // Rewrite all non-pointer composite literals, even though it breaks
+ // compilation. We annotate these rewrites with a FIXME comment. It is up to
+ // the user to change the usage of this literal to cope with it now being a
+ // pointer.
+ dstutil.Apply(c.Node(), func(cur *dstutil.Cursor) bool {
+ lit, ok := cur.Node().(*dst.CompositeLit)
+ if !ok {
+ return true
+ }
+ if isAddr(cur.Parent()) {
+ // The code already takes the address of this composite literal
+ // (e.g. &pb.M2{literal}), resulting in a pointer. Skip.
+ return true
+ }
+
+ typ := c.typeOf(lit)
+ if _, ok := typ.Underlying().(*types.Pointer); ok {
+ // The composite literal implicitly is already a pointer type
+ // (e.g. []*pb.M2{{literal}}). Skip.
+ return true
+ }
+
+ if !c.shouldUpdateType(typ) {
+ return true
+ }
+
+ addCommentAbove(c.Node(), lit, "// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)")
+
+ c.numUnsafeRewritesByReason[IncompleteRewrite]++
+ cur.Replace(addr(c, lit))
+
+ return true
+ }, nil)
+ return false
+}
+
+// nonPtrProtoStructDef returns true if stmt is of the form:
+//
+// x := pb.M{...}
+//
+// It also returns the assigned identifier ('x' in this example) if that's the case.
+func nonPtrProtoStructDef(c *cursor, stmt dst.Stmt) (*dst.Ident, bool) {
+ // Not handled: multi-assign
+ a, ok := stmt.(*dst.AssignStmt)
+ if !ok || a.Tok != token.DEFINE || len(a.Lhs) != 1 {
+ return nil, false
+ }
+ lit, ok := a.Rhs[0].(*dst.CompositeLit)
+ if !ok {
+ return nil, false
+ }
+ ident, ok := a.Lhs[0].(*dst.Ident)
+ if !ok {
+ // Shouldn't happen as only identifiers can be on LHS of a definition.
+ return nil, false
+ }
+ if !c.shouldUpdateType(c.typeOf(lit)) {
+ return nil, false
+ }
+ return ident, true
+}
+
+// replaceWithPtr modifies (if possible) stmts so that they work if ident was a pointer type (it
+// must be a non-pointer). For example, for identifier 'x' the following statements:
+//
+// x.S = nil
+// _ = x.GetS()
+// f(&x)
+//
+// would be changed to:
+//
+// x.S = nil
+// _ = x.GetS()
+// f(x) // notice dropped "&"
+//
+// Changes are only made if they are possible. Any usage of the identifier for shallow copies
+// prevents rewrites. For example, in the following example statements won't be changed:
+//
+// f(&x)
+// E
+//
+// where E is any of
+//
+// x = pb.M{}
+// y = x
+// *p = x
+// f(x)
+// []pb.M{x}
+//
+// or anything else that's not a field access, a method call, &x, or an argument to a printer function.
+//
+// replaceWithPtr returns true if it modifies stmts.
+func replaceWithPtr(c *cursor, ident *dst.Ident, stmts []dst.Stmt) bool {
+ b := &dst.BlockStmt{List: stmts}
+ canRewrite := true
+ dstutil.Apply(b, func(cur *dstutil.Cursor) bool {
+ n, ok := cur.Node().(*dst.Ident)
+ if !ok {
+ return true
+ }
+ if c.objectOf(n) != c.objectOf(ident) {
+ return false
+ }
+ if isAddr(cur.Parent()) {
+ return false
+ }
+
+ // If n's parent is a selector expression then we have one of those situations:
+ //
+ // n.Field
+ // n.Func()
+ //
+ // This works whether n is a pointer or a value.
+ if _, ok := cur.Parent().(*dst.SelectorExpr); ok {
+ return false
+ }
+
+ // Address expressions are ok. We have to drop "&" once ident becomes a pointer.
+ if e, ok := cur.Parent().(*dst.UnaryExpr); ok && e.Op == token.AND {
+ return false
+ }
+
+ // If n is an argument to t.Errorf or other function used for printing then it's ok to make
+ // n a pointer. That will change output format from:
+ // {F:...}
+ // to
+ // &{F:...}
+ // A shallow copy like this is incorrect. It's rare to depend on exact log statement though (I'm sure it happens).
+ if c.lvl.ge(Yellow) && c.looksLikePrintf(cur.Parent()) {
+ return false
+ }
+
+ // Otherwise we found a use of ident that can't be changed to a pointer.
+ canRewrite = false
+
+ return false
+ }, nil)
+ if !canRewrite {
+ return false
+ }
+
+ // We can change the type of ident to be a pointer. We must replace all "&ident" expressions with "ident".
+ dstutil.Apply(b, func(cur *dstutil.Cursor) bool {
+ ue, ok := cur.Node().(*dst.UnaryExpr)
+ if !ok || ue.Op != token.AND {
+ return true
+ }
+ v, ok := ue.X.(*dst.Ident)
+ if !ok || c.objectOf(ident) != c.objectOf(v) {
+ return true
+ }
+ cur.Replace(v)
+ return false
+ }, nil)
+ return true
+}
diff --git a/internal/fix/usepointers_test.go b/internal/fix/usepointers_test.go
new file mode 100644
index 0000000..5d1e757
--- /dev/null
+++ b/internal/fix/usepointers_test.go
@@ -0,0 +1,548 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fix
+
+import (
+ "testing"
+)
+
+func TestEnumPointer(t *testing.T) {
+ tests := []test{
+ {
+ desc: "enum (value)",
+ srcfiles: []string{"pkg.go"},
+ in: `
+mypb := &pb2.M2{
+ E: pb2.M2_E_VAL.Enum(),
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+mypb := &pb2.M2{}
+mypb.SetE(pb2.M2_E_VAL)
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "enum (pointer)",
+ srcfiles: []string{"pkg.go"},
+ in: `
+ptr := pb2.M2_E_VAL.Enum()
+mypb := &pb2.M2{
+ E: ptr,
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+ptr := pb2.M2_E_VAL.Enum()
+mypb := &pb2.M2{}
+if ptr != nil {
+ mypb.SetE(*ptr)
+}
+_ = mypb
+`,
+ },
+ },
+
+ {
+ desc: "enum (Enum() on pointer)",
+ srcfiles: []string{"pkg.go"},
+ in: `
+ptr := pb2.M2_E_VAL.Enum()
+mypb := &pb2.M2{
+ E: ptr.Enum(),
+}
+_ = mypb
+`,
+ want: map[Level]string{
+ Green: `
+ptr := pb2.M2_E_VAL.Enum()
+mypb := &pb2.M2{}
+mypb.SetE(*ptr)
+_ = mypb
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestUsePointersNotValues(t *testing.T) {
+ tests := []test{
+ {
+ desc: "simple example",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+m := pb2.M2{S:nil}
+f(&m)
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+f(&m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+f(m)
+`,
+ },
+ },
+
+ {
+ desc: "empty literal",
+ in: `
+m := pb2.M2{}
+_ = &m
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{}
+_ = &m
+`,
+ Red: `
+m := &pb2.M2{}
+_ = m
+`,
+ },
+ },
+
+ {
+ desc: "method calls",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+m := pb2.M2{S:nil}
+_ = m.GetS()
+f(&m)
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+_ = m.GetS()
+f(&m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+_ = m.GetS()
+f(m)
+`,
+ },
+ },
+
+ {
+ desc: "direct field accesses",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+m := pb2.M2{S: nil}
+m.S = proto.String("")
+m.S = nil
+f(&m)
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+m.SetS("")
+m.ClearS()
+f(&m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+m.SetS("")
+m.ClearS()
+f(m)
+`,
+ },
+ },
+
+ {
+ desc: "addr is stored",
+ in: `
+m := pb2.M2{S: nil}
+m.M = &m
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+m.SetM(&m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+m.SetM(m)
+`,
+ },
+ },
+
+ {
+ desc: "argument to function printer",
+ extra: `func fmtPrintln(string, ...interface{})`,
+ in: `
+m := pb2.M2{S: nil}
+fmtPrintln("", m)
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+fmtPrintln("", m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+fmtPrintln("", m)
+`,
+ },
+ },
+
+ {
+ desc: "argument to method printer",
+ extra: `
+type T struct{}
+func (*T) println(string, ...interface{})
+var t *T
+`,
+ in: `
+m := pb2.M2{S: nil}
+t.println("", m)
+`,
+ want: map[Level]string{
+ Green: `
+m := pb2.M2{S: nil}
+t.println("", m)
+`,
+ Red: `
+m := pb2.M2_builder{S: nil}.Build()
+t.println("", m)
+`,
+ },
+ },
+
+ {
+ desc: "array of values",
+ in: `
+ms := []pb2.M2{{S: nil}, pb2.M2{S: nil}, {}}
+_ = ms
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+ms := []pb2.M2{&{S: nil}, pb2.M2_builder{S: nil}.Build(), &{}}
+_ = ms
+`,
+ },
+ },
+
+ {
+ desc: "array value",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+m := pb2.M2{}
+ms := []pb2.M2{m}
+f(&m)
+_ = ms
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+m := &pb2.M2{}
+ms := []pb2.M2{m}
+f(&m)
+_ = ms
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, func call",
+ extra: `
+func f(*pb2.M2) {}
+func g(pb2.M2) {}`,
+ in: `
+m := pb2.M2{S: nil}
+f(&m)
+g(m)
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+m := pb2.M2_builder{S: nil}.Build()
+f(&m)
+g(m)
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, return value",
+ extra: `
+func f(*pb2.M2) {}
+func g(pb2.M2) {}`,
+ in: `
+m := func() pb2.M2 {
+ return pb2.M2{S: nil}
+}()
+f(&m)
+g(m)
+`,
+ want: map[Level]string{
+ Red: `
+m := func() *pb2.M2 {
+ // DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+ return pb2.M2_builder{S: nil}.Build()
+}()
+f(&m)
+g(m)
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, output arg",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+func(out *pb2.M2) {
+ m := pb2.M2{S: nil}
+ f(&m)
+ *out = m
+}(m2)
+`,
+ want: map[Level]string{
+ Red: `
+func(out *pb2.M2) {
+ m := pb2.M2_builder{S: nil}.Build()
+ f(m)
+ proto.Reset(out)
+ proto.Merge(out, m)
+}(m2)
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, direct assignment to output arg",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var out *pb2.M2
+*out = pb2.M2{S: nil}
+`,
+ want: map[Level]string{
+ Green: `
+var out *pb2.M2
+proto.Reset(out)
+m2h2 := &pb2.M2{}
+m2h2.ClearS()
+proto.Merge(out, m2h2)
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, conditional assignment to output arg",
+ srcfiles: []string{"pkg.go"},
+ in: `
+var out *pb2.M2
+if out != nil {
+ *out = pb2.M2{S: nil}
+}
+`,
+ want: map[Level]string{
+ Green: `
+var out *pb2.M2
+if out != nil {
+ proto.Reset(out)
+ m2h2 := &pb2.M2{}
+ m2h2.ClearS()
+ proto.Merge(out, m2h2)
+}
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, simple copy",
+ extra: `func f(*pb2.M2) {}`,
+ in: `
+m := pb2.M2{S: nil}
+var copy pb2.M2 = m
+_ = copy
+f(&m)
+f(©)
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+m := pb2.M2_builder{S: nil}.Build()
+var copy pb2.M2 = m
+_ = copy
+f(&m)
+f(©)
+`,
+ },
+ },
+
+ {
+ desc: "shallow copy, reassigned",
+ extra: `
+func f(*pb2.M2) { }
+func g() pb2.M2{ return pb2.M2{} }
+`,
+ in: `
+// existing comment to illustrate comment addition
+m := pb2.M2{S: nil}
+m = g()
+f(&m)
+`,
+ want: map[Level]string{
+ Red: `
+// existing comment to illustrate comment addition
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+m := pb2.M2_builder{S: nil}.Build()
+m = g()
+f(&m)
+`,
+ },
+ },
+
+ {
+ desc: "struct field definition",
+ in: `
+for _, tt := range []struct {
+ want pb2.M2
+}{
+ {
+ want: pb2.M2{S: nil},
+ },
+} {
+ _ = tt.want
+}
+`,
+ want: map[Level]string{
+ Red: `
+// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)
+for _, tt := range []struct {
+ want *pb2.M2
+}{
+ {
+ want: pb2.M2_builder{S: nil}.Build(),
+ },
+} {
+ _ = tt.want
+}
+`,
+ },
+ },
+
+ {
+ desc: "Stubby method handler response assignment",
+ in: `
+func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error {
+ *resp = pb2.M2{S: nil}
+ return nil
+}(context.Background(), nil, nil)
+`,
+ want: map[Level]string{
+ Red: `
+func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error {
+ proto.Merge(resp, pb2.M2_builder{S: nil}.Build())
+ return nil
+}(context.Background(), nil, nil)
+`,
+ },
+ },
+
+ {
+ desc: "Stubby method handler, mutating response assignment",
+ in: `
+func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error {
+ *resp = pb2.M2{I32: proto.Int32(42)}
+ if true {
+ *resp = pb2.M2{S: nil}
+ }
+ return nil
+}(context.Background(), nil, nil)
+`,
+ want: map[Level]string{
+ Red: `
+func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error {
+ proto.Merge(resp, pb2.M2_builder{I32: proto.Int32(42)}.Build())
+ if true {
+ proto.Reset(resp)
+ proto.Merge(resp, pb2.M2_builder{S: nil}.Build())
+ }
+ return nil
+}(context.Background(), nil, nil)
+`,
+ },
+ },
+
+ {
+ desc: "Stubby method handler response assignment with comments",
+ in: `
+var response *pb2.M2
+// above response assignment
+*response = pb2.M2{
+ // above field
+ S: nil, // end of field line
+} // end of message line
+`,
+ want: map[Level]string{
+ Red: `
+var response *pb2.M2
+// above response assignment
+proto.Reset(response)
+proto.Merge(response, pb2.M2_builder{
+ // above field
+ S: nil, // end of field line
+}.Build()) // end of message line
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
+
+func TestNoUnconditionalDereference(t *testing.T) {
+ // Before b/259702553, the open2opaque tool unconditionally rewrote code
+ // such that it de-referenced pointers.
+
+ tests := []test{
+ {
+ desc: "assignment",
+ extra: `func funcReturningIntPointer() *int32 { return nil }`,
+ in: `
+m2.I32 = funcReturningIntPointer()
+`,
+ want: map[Level]string{
+ Red: `
+if x := funcReturningIntPointer(); x != nil {
+ m2.SetI32(*x)
+} else {
+ m2.ClearI32()
+}
+`,
+ },
+ },
+
+ {
+ desc: "struct literal field",
+ in: `
+_ = &pb2.M2{
+ I32: m2a.I32,
+}
+`,
+ want: map[Level]string{
+ Red: `
+_ = pb2.M2_builder{
+ I32: proto.ValueOrNil(m2a.HasI32(), m2a.GetI32),
+}.Build()
+`,
+ },
+ },
+ }
+
+ runTableTests(t, tests)
+}
diff --git a/internal/ignore/ignore.go b/internal/ignore/ignore.go
new file mode 100644
index 0000000..3c02754
--- /dev/null
+++ b/internal/ignore/ignore.go
@@ -0,0 +1,80 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ignore implements checking if a certain file (typically .proto or
+// .go) should be ignored by the open2opaque pipeline.
+package ignore
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ log "github.com/golang/glog"
+)
+
+// List allows checking if a certain file (typically .proto or .go files) should
+// be ignored by the open2opaque pipeline.
+type List struct {
+ IgnoredFiles map[string]bool
+ IgnoredDirs []string
+}
+
+// Add adds the depot path to the ignore list.
+func (l *List) Add(path string) {
+ if strings.HasSuffix(path, "/") {
+ l.IgnoredDirs = append(l.IgnoredDirs, path)
+ return
+ }
+ l.IgnoredFiles[path] = true
+}
+
+// Contains returns true if the loaded ignorelist contains path.
+func (l *List) Contains(path string) bool {
+ if l == nil {
+ return false
+ }
+ for _, dir := range l.IgnoredDirs {
+ if strings.HasPrefix(path, dir) {
+ return true
+ }
+ }
+ return l.IgnoredFiles[path]
+}
+
+// LoadList loads an ignore list from files matching the provided glob pattern
+// (see http://godoc/3/file/base/go/file#Match for the syntax definition).
+func LoadList(pattern string) (*List, error) {
+ matches, err := glob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ var lines []string
+ for _, f := range matches {
+ b, err := os.ReadFile(f)
+ if err != nil {
+ return nil, err
+ }
+ lines = append(lines, strings.Split(strings.TrimSpace(string(b)), "\n")...)
+ }
+ l := &List{
+ IgnoredFiles: make(map[string]bool, len(lines)),
+ }
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue // skip comments
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue // skip empty lines
+ }
+ l.Add(line)
+ }
+ log.Infof("Loaded ignore list from pattern %q: %d files and %d directories.", pattern, len(l.IgnoredFiles), len(l.IgnoredDirs))
+ return l, nil
+}
+
+var glob = func(pattern string) ([]string, error) {
+ return filepath.Glob(pattern)
+}
diff --git a/internal/ignore/ignore_test.go b/internal/ignore/ignore_test.go
new file mode 100644
index 0000000..4ed6a09
--- /dev/null
+++ b/internal/ignore/ignore_test.go
@@ -0,0 +1,81 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ignore_test
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "google.golang.org/open2opaque/internal/ignore"
+)
+
+func TestIgnoreList(t *testing.T) {
+ dir := t.TempDir()
+
+ if err := os.WriteFile(filepath.Join(dir, "file1.txt"), []byte(`
+a/b/
+
+ `), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "file2.txt"), []byte(`
+c/d/e/some.proto
+ `), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ testCases := []struct {
+ loadPattern string
+ path string
+ want bool
+ }{
+ {
+ loadPattern: filepath.Join(dir, "file*.txt"),
+ path: "a/b/some.proto",
+ want: true,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file1*.txt"),
+ path: "a/b/some.proto",
+ want: true,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file2*.txt"),
+ path: "a/b/some.proto",
+ want: false,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file*.txt"),
+ path: "a/b/x/some.proto",
+ want: true,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file*.txt"),
+ path: "a/x/some.proto",
+ want: false,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file*.txt"),
+ path: "c/d/e/some.proto",
+ want: true,
+ },
+ {
+ loadPattern: filepath.Join(dir, "file1*.txt"),
+ path: "c/d/e/some.proto",
+ want: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ ignoreList, err := ignore.LoadList(tc.loadPattern)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := ignoreList.Contains(tc.path); got != tc.want {
+ t.Errorf("Using pattern %q, Contains(%s) = %v, want %v", tc.loadPattern, tc.path, got, tc.want)
+ }
+ }
+}
diff --git a/internal/o2o/args/args.go b/internal/o2o/args/args.go
new file mode 100644
index 0000000..81170f8
--- /dev/null
+++ b/internal/o2o/args/args.go
@@ -0,0 +1,23 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package args translates command-line arguments from proto message names or
+// prefixes to proto file names.
+package args
+
+import (
+ "context"
+ "strings"
+)
+
+// ToProtoFilename translates the specified arg into a (filename, symbol) pair
+// using go/global-protodb. If only a prefix is specified, symbol will be empty.
+func ToProtoFilename(ctx context.Context, arg, kind string) (detectedKind, filename, symbol string, _ error) {
+ if kind == "proto_filename" ||
+ (kind == "autodetect" && strings.HasSuffix(arg, ".proto")) {
+ return "proto_filename", arg, "", nil
+ }
+
+ return "", arg, "", nil
+}
diff --git a/internal/o2o/errutil/errutil.go b/internal/o2o/errutil/errutil.go
new file mode 100644
index 0000000..5bee60a
--- /dev/null
+++ b/internal/o2o/errutil/errutil.go
@@ -0,0 +1,37 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errutil provides utilities for easily annotating Go errors.
+package errutil
+
+import "fmt"
+
+// Annotatef annotates non-nil error with the given message.
+//
+// It's designed to be used in a defer, for example:
+//
+// func g(arg string) (err error) {
+// defer Annotate(&err, fmt.Sprintf("g(%s)")
+// return errors.New("my error")
+// }
+//
+// Calling g("hello") will result in error message:
+//
+// g(hello): my error
+//
+// Annotate allows using the above short form instead of the long form:
+//
+// func g(arg string) (err error) {
+// defer func() {
+// if err != nil {
+// err = fmt.Errorf("g(%s): %v", arg, err)
+// }
+// }()
+// return errors.New("my error")
+// }
+func Annotatef(err *error, format string, a ...any) {
+ if *err != nil {
+ *err = fmt.Errorf("%s: %v", fmt.Sprintf(format, a...), *err)
+ }
+}
diff --git a/internal/o2o/errutil/errutil_test.go b/internal/o2o/errutil/errutil_test.go
new file mode 100644
index 0000000..dab984e
--- /dev/null
+++ b/internal/o2o/errutil/errutil_test.go
@@ -0,0 +1,31 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errutil
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestAnnotateHasNoEffectOnNilError(t *testing.T) {
+ f := func() (err error) {
+ defer Annotatef(&err, "f")
+ return nil
+ }
+ if err := f(); err != nil {
+ t.Errorf("f() failed: %v", err)
+ }
+}
+
+func TestAnnotateAddsInfoToNonNilError(t *testing.T) {
+ f := func() (err error) {
+ defer Annotatef(&err, "g")
+ return errors.New("test error")
+ }
+ want := "g: test error"
+ if err := f(); err == nil || err.Error() != want {
+ t.Errorf("f(): %v; want %s", err, want)
+ }
+}
diff --git a/internal/o2o/fakeloader/fakeloader.go b/internal/o2o/fakeloader/fakeloader.go
new file mode 100644
index 0000000..928c351
--- /dev/null
+++ b/internal/o2o/fakeloader/fakeloader.go
@@ -0,0 +1,240 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package fakeloader contains a hermetic loader implementation that does not
+// depend on any other systems and can be used in tests.
+package fakeloader
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "strings"
+ "sync"
+
+ "golang.org/x/tools/go/gcexportdata"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+)
+
+// ExportForFunc is a caller-supplied function that returns the export data (.x
+// file) for the package with the specified import path.
+type ExportForFunc func(importPath string) []byte
+
+type fakeLoader struct {
+ // fake input from the test
+ pkgs map[string][]string
+ files map[string]string
+ generated map[string]string
+ exportFor ExportForFunc
+}
+
+// NewFakeLoader returns a Loader that can be used in tests. It works in the
+// same way as loader returned from NewLoader, except it fakes expensive/flaky
+// dependencies (e.g. executing commands).
+//
+// pkgs maps target rule names (e.g. "//base/go:flag") to list of files in that package
+// (e.g. "net/proto2/go/open2opaque/testdata/dummycmd.go"). File paths should be in the form
+// expected by blaze, e.g. "//net/proto2/go/open2opaque/testdata:dummy.go". Names of test packages
+// should have "_test" suffix.
+//
+// files maps all files (e.g. "//test/pkg:updated.go"), referenced by the pkgs map, to their content.
+// generated maps generated files (e.g. "//test/pkg:generated.go"), referenced by the pkgs map, to ther content.
+//
+// Unlike the loader returned by NewLoader, the fake loader is inexpensive. It's
+// intended that multiple fake loaders are created (perhaps one per test).
+func NewFakeLoader(pkgs map[string][]string, files, generated map[string]string, exportFor ExportForFunc) loader.Loader {
+ l := &fakeLoader{
+ pkgs: pkgs,
+ files: files,
+ generated: generated,
+ exportFor: exportFor,
+ }
+ return l
+}
+
+func (fl *fakeLoader) Close(context.Context) error { return nil }
+
+func (fl *fakeLoader) loadPackage(ctx context.Context, target *loader.Target) (_ *loader.Package, err error) {
+ // target.ID is e.g. google.golang.org/open2opaque/fix/testdata/dummy
+ files, ok := fl.pkgs[target.ID]
+ if !ok {
+ return nil, fmt.Errorf("no such fake package: %q", target.ID)
+ }
+
+ pkgPath := target.ID
+
+ pkg := &loader.Package{
+ Fileset: token.NewFileSet(),
+ }
+
+ var asts []*ast.File
+ for _, f := range files {
+ fname := f
+ generated := false
+ code, ok := fl.files[f]
+ if !ok {
+ if code, ok = fl.generated[f]; !ok {
+ return nil, errors.New("no source code")
+ }
+ generated = true
+ }
+ ast, err := parser.ParseFile(pkg.Fileset, fname, code, parser.ParseComments|parser.SpuriousErrors)
+ if err != nil {
+ return nil, err
+ }
+ asts = append(asts, ast)
+ pkg.Files = append(pkg.Files, &loader.File{
+ AST: ast,
+ Path: fname,
+ Generated: generated,
+ Code: code,
+ })
+ }
+
+ pkg.TypeInfo = &types.Info{
+ Types: make(map[ast.Expr]types.TypeAndValue),
+ Defs: make(map[*ast.Ident]types.Object),
+ Uses: make(map[*ast.Ident]types.Object),
+ Implicits: make(map[ast.Node]types.Object),
+ Selections: make(map[*ast.SelectorExpr]*types.Selection),
+ Scopes: make(map[ast.Node]*types.Scope),
+ }
+ imp, err := newFakeImporter(fl.files, fl.generated, fl.exportFor)
+ if err != nil {
+ return nil, err
+ }
+ cfg := types.Config{
+ Importer: imp,
+ FakeImportC: true,
+ DisableUnusedImportCheck: true,
+ }
+ if pkg.TypePkg, err = cfg.Check(pkgPath, pkg.Fileset, asts, pkg.TypeInfo); err != nil {
+ // Ignore loading errors for the siloedpb package, emulating the
+ // behavior of the GAP loader with DropSiloedFiles enabled.
+ if !strings.Contains(err.Error(), "siloedpb") {
+ return nil, err
+ }
+ }
+ return pkg, nil
+}
+
+// LoadPackages loads a batch of Go packages by concurrently calling
+// loadPackage() for all targets, as the fake loader does not require any
+// synchronization.
+func (fl *fakeLoader) LoadPackages(ctx context.Context, targets []*loader.Target, res chan loader.LoadResult) {
+ var wg sync.WaitGroup
+ wg.Add(len(targets))
+ for _, t := range targets {
+ t := t // copy
+ go func() {
+ defer wg.Done()
+ p, err := fl.loadPackage(ctx, t)
+ res <- loader.LoadResult{
+ Target: t,
+ Package: p,
+ Err: err,
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func newFakeImporter(files, generated map[string]string, exportFor ExportForFunc) (*fakeImporter, error) {
+ imp := &fakeImporter{
+ checked: make(map[string]*types.Package),
+ files: files,
+ generated: generated,
+ exportFor: exportFor,
+ }
+ return imp, nil
+}
+
+type fakeImporter struct {
+ checked map[string]*types.Package
+ files, generated map[string]string
+ exportFor ExportForFunc
+}
+
+func (imp *fakeImporter) readXFile(importPath string, xFile []byte) (*types.Package, error) {
+ // Load the package from the .x file if we have not yet loaded it.
+ r, err := gcexportdata.NewReader(bytes.NewReader(xFile))
+ if err != nil {
+ return nil, err
+ }
+ pkg, err := gcexportdata.Read(r, token.NewFileSet(), imp.checked, importPath)
+ if err != nil {
+ return nil, err
+ }
+ imp.checked[importPath] = pkg
+ return pkg, nil
+}
+
+const fakeSync = `
+package sync
+
+type Once struct {}
+
+func (*Once) Do(func()) {}
+`
+
+const fakeReflect = `
+package reflect
+
+type Type interface {
+ PkgPath() string
+}
+
+func TypeOf(any) Type { return nil }
+`
+
+const fakeContext = `package context
+
+type Context interface{}
+
+func Background() Context
+`
+
+func (imp *fakeImporter) parseAndTypeCheck(pkgPath, fileName, contents string) (*types.Package, error) {
+ fs := token.NewFileSet()
+ afile, err := parser.ParseFile(fs, fileName, contents, parser.ParseComments|parser.SpuriousErrors)
+ if err != nil {
+ return nil, err
+ }
+ cfg := types.Config{Importer: imp}
+ pkg, err := cfg.Check(pkgPath, fs, []*ast.File{afile}, nil)
+ if err != nil {
+ return nil, err
+ }
+ imp.checked[pkgPath] = pkg
+ return pkg, nil
+}
+
+func (imp *fakeImporter) Import(pkgPath string) (_ *types.Package, err error) {
+ if pkgPath == "unsafe" {
+ return types.Unsafe, nil
+ }
+ if p, ok := imp.checked[pkgPath]; ok && p.Complete() {
+ return p, nil
+ }
+ if pkgPath == "sync" {
+ return imp.parseAndTypeCheck(pkgPath, "sync.go", fakeSync)
+ }
+ if pkgPath == "reflect" {
+ return imp.parseAndTypeCheck(pkgPath, "reflect.go", fakeReflect)
+ }
+ if pkgPath == "context" {
+ return imp.parseAndTypeCheck(pkgPath, "context.go", fakeContext)
+ }
+
+ b := imp.exportFor(pkgPath)
+ if b == nil {
+ return nil, fmt.Errorf("tried to load package %q for which there is no export data", pkgPath)
+ }
+ return imp.readXFile(pkgPath, b)
+}
diff --git a/internal/o2o/loader/loader.go b/internal/o2o/loader/loader.go
new file mode 100644
index 0000000..5e1de27
--- /dev/null
+++ b/internal/o2o/loader/loader.go
@@ -0,0 +1,98 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package loader loads Go packages.
+package loader
+
+import (
+ "context"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+)
+
+// Package represents a loaded Go package.
+type Package struct {
+ Files []*File // Files in the package.
+ Fileset *token.FileSet // For translating between positions and locations in files.
+ TypeInfo *types.Info // Type information for the package (e.g. object identity, identifier uses/declarations, expression types).
+ TypePkg *types.Package // Describes the package (e.g. import objects, package scope).
+}
+
+func (p Package) String() string {
+ var paths []string
+ for _, f := range p.Files {
+ paths = append(paths, f.Path)
+ }
+ return fmt.Sprintf("Go Package %s with files:\n\t%s", p.TypePkg.Path(), strings.Join(paths, "\n\t"))
+}
+
+// File represents a single file in a loaded package.
+type File struct {
+ // Parsed file.
+ AST *ast.File
+
+ Path string
+
+ // For go_test targets, this field indicates whether the file belongs to the
+ // test code itself (one or more _test.go files), or whether the file
+ // belongs to the package-under-test (via the go_test target’s library
+ // attribute).
+ //
+ // For other targets (go_binary or go_library), this field is always false.
+ LibraryUnderTest bool
+
+ // Source code from the file.
+ Code string
+
+ // True if the file was generated (go_embed_data, genrule, etc.).
+ Generated bool
+}
+
+// Target represents a package to be loaded. It is identified by the opaque ID
+// field, which is interpreted by the loader (which, in turn, delegates to the
+// gopackagesdriver).
+type Target struct {
+ // ID is an opaque identifier for the package when using the packages loader.
+ ID string
+
+ // Testonly indicates that this package should be considered test code. This
+ // attribute needs to be passed in because go/packages does not have the
+ // concept of test only code, only Blaze does.
+ Testonly bool
+
+ LibrarySrcs map[string]bool
+}
+
+// LoadResult represents the result of loading an individual target. The Target
+// field is always set. If something went wrong, Err is non-nil and Package is
+// nil. Otherwise, Err is nil and Package is non-nil.
+type LoadResult struct {
+ Target *Target
+ Package *Package
+ Err error
+}
+
+// Loader loads Go packages.
+type Loader interface {
+ // LoadPackages loads a batch of Go packages.
+ //
+ // The method does not return a slice of results, but instead writes each
+ // result to the specified result channel as the result arrives. This allows
+ // for concurrency with loaders that support it, like the Compilations
+ // Bigtable loader (processing results while the load is still ongoing).
+ LoadPackages(context.Context, []*Target, chan LoadResult)
+ Close(context.Context) error
+}
+
+// LoadOne is a convenience function that loads precisely one target, saving the
+// caller the mechanics of having to work with a batch of targets.
+func LoadOne(ctx context.Context, l Loader, t *Target) (*Package, error) {
+ results := make(chan LoadResult, 1)
+ l.LoadPackages(context.Background(), []*Target{t}, results)
+ res := <-results
+ return res.Package, res.Err
+}
diff --git a/internal/o2o/loader/loader_test.go b/internal/o2o/loader/loader_test.go
new file mode 100644
index 0000000..187c292
--- /dev/null
+++ b/internal/o2o/loader/loader_test.go
@@ -0,0 +1,204 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package loader_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "sort"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "google.golang.org/open2opaque/internal/o2o/fakeloader"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+)
+
+// fakingBlaze is true when NewFakeLoader is used
+var fakingBlaze = true
+
+var prefix = func() string {
+ p := "google.golang.org/open2opaque/fake/"
+ return p
+}()
+
+var pathPrefix = func() string {
+ p := "google.golang.org/open2opaque/fake/"
+ return p
+}()
+
+var newLoader func(ctx context.Context, cfg *loader.Config) (loader.Loader, error)
+
+func init() {
+ testPkgs := map[string][]string{
+ prefix + "dummy": []string{
+ prefix + "dummy.go",
+ },
+ prefix + "dummy_test": []string{
+ prefix + "dummy.go",
+ prefix + "dummy_test.go",
+ },
+ prefix + "dummycmd": []string{
+ prefix + "dummycmd.go",
+ },
+ }
+ testFiles := map[string]string{
+ prefix + "dummy.go": `package dummy`,
+ prefix + "dummy_test.go": `package dummy`,
+ prefix + "dummycmd.go": `package main
+func main() {}`,
+ }
+ newLoader = func(ctx context.Context, cfg *loader.Config) (loader.Loader, error) {
+ return fakeloader.NewFakeLoader(testPkgs, testFiles, nil, nil), nil
+ }
+}
+
+var tests = []struct {
+ desc string
+ ruleName string
+ wantFiles []string
+}{{
+ desc: "library",
+ ruleName: prefix + "dummy",
+ wantFiles: []string{pathPrefix + "dummy.go"},
+}, {
+ desc: "test",
+ ruleName: prefix + "dummy_test",
+ wantFiles: []string{
+ pathPrefix + "dummy.go",
+ pathPrefix + "dummy_test.go",
+ },
+}, {
+ desc: "binary",
+ ruleName: prefix + "dummycmd",
+ wantFiles: []string{pathPrefix + "dummycmd.go"},
+}}
+
+func setup(t *testing.T) (dir string) {
+ t.Helper()
+ dir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("os.Getwd failed: %v", err)
+ }
+ return dir
+}
+
+// TestUsesRealBlaze exists so that the test output has a reminder that this
+// test can be run locally with a real blaze. It also verifies that we don't
+// accidentally fake blaze locally.
+func TestUsesRealBlaze(t *testing.T) {
+ if fakingBlaze {
+ t.Skip("Can't use real blaze on borg. All tests will use a fake one. Run this test locally to use real blaze.")
+ }
+}
+
+func testConfig(t *testing.T) *loader.Config {
+ t.Helper()
+
+ cfg := &loader.Config{}
+
+ return cfg
+}
+
+func TestDiscoversSourceFiles(t *testing.T) {
+ setup(t)
+ l, err := newLoader(context.Background(), testConfig(t))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, tt := range tests {
+ t.Run(tt.desc, func(t *testing.T) {
+ pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName})
+ if err != nil {
+ t.Fatalf("LoadPackage(%s) failed: %v", tt.ruleName, err)
+ }
+ var got []string
+ for _, f := range pkg.Files {
+ got = append(got, f.Path)
+ }
+ sort.Strings(got)
+ if d := cmp.Diff(tt.wantFiles, got); d != "" {
+ t.Errorf("LoadPackage(%s) = %v; want %v; diff:\n%s", tt.ruleName, got, tt.wantFiles, d)
+ }
+ })
+ }
+}
+
+func TestParallelQueries(t *testing.T) {
+ setup(t)
+ l, err := newLoader(context.Background(), testConfig(t))
+ if err != nil {
+ t.Fatal(err)
+ }
+ errc := make(chan error)
+ const runs = 3 // chosen arbitrarily
+ for i := 0; i < runs; i++ {
+ go func() {
+ for _, tt := range tests {
+ pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName})
+ if err != nil {
+ errc <- fmt.Errorf("%s: LoadPackage(%s) failed: %v", tt.desc, tt.ruleName, err)
+ continue
+ }
+ var got []string
+ for _, f := range pkg.Files {
+ got = append(got, f.Path)
+ }
+ sort.Strings(got)
+ if d := cmp.Diff(tt.wantFiles, got); d != "" {
+ errc <- fmt.Errorf("%s: LoadPackage(%s) = %v; want %v; diff:\n%s", tt.desc, tt.ruleName, got, tt.wantFiles, d)
+ }
+ errc <- nil
+ }
+ }()
+ }
+ for i := 0; i < runs*len(tests); i++ {
+ if err := <-errc; err != nil {
+ t.Error(err)
+ }
+ }
+}
+
+func TestSingleFailureDoesntAffectOtherTargets(t *testing.T) {
+ setup(t)
+ l, err := newLoader(context.Background(), testConfig(t))
+ if err != nil {
+ t.Fatal(err)
+ }
+ errc := make(chan error)
+ const runs = 5 // chosen arbitrarily
+ for i := 0; i < runs; i++ {
+ go func() {
+ for _, tt := range tests {
+ pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName})
+ if err != nil {
+ errc <- fmt.Errorf("%s: LoadPackage(%s) failed: %v", tt.desc, tt.ruleName, err)
+ continue
+ }
+ var got []string
+ for _, f := range pkg.Files {
+ got = append(got, f.Path)
+ }
+ sort.Strings(got)
+ if d := cmp.Diff(tt.wantFiles, got); d != "" {
+ errc <- fmt.Errorf("%s: LoadPackage(%s) = %v; want %v; diff:\n%s", tt.desc, tt.ruleName, got, tt.wantFiles, d)
+ }
+ errc <- nil
+ }
+ in := "google.golang.org/open2opaque/internal/fix/testdata/DOES_NOT_EXIST"
+ got, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: in})
+ if err == nil {
+ errc <- fmt.Errorf("LoadPackage(%s) succeeded (files: %v); want failure (package doesn't exist)", in, got.Files)
+ return
+ }
+ errc <- nil
+ }()
+ }
+ for i := 0; i < runs*(len(tests)+1); i++ {
+ if err := <-errc; err != nil {
+ t.Error(err)
+ }
+ }
+}
diff --git a/internal/o2o/loader/loaderblaze.go b/internal/o2o/loader/loaderblaze.go
new file mode 100644
index 0000000..2d6e0e0
--- /dev/null
+++ b/internal/o2o/loader/loaderblaze.go
@@ -0,0 +1,106 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package loader
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "golang.org/x/tools/go/packages"
+)
+
+type BlazeLoader struct {
+ dir string
+}
+
+func NewBlazeLoader(ctx context.Context, cfg *Config, dir string) (*BlazeLoader, error) {
+ return &BlazeLoader{
+ dir: dir,
+ }, nil
+}
+
+// Close frees all resources that NewBlazeLoader() created. The BlazeLoader must
+// not be used afterwards.
+func (l *BlazeLoader) Close(context.Context) error { return nil }
+
+func failBatch(targets []*Target, res chan LoadResult, err error) {
+ for _, t := range targets {
+ res <- LoadResult{
+ Target: t,
+ Err: err,
+ }
+ }
+}
+
+// LoadPackage loads a batch of Go packages.
+func (l *BlazeLoader) LoadPackages(ctx context.Context, targets []*Target, res chan LoadResult) {
+ targetByID := make(map[string]*Target)
+ patterns := make([]string, len(targets))
+ for idx, t := range targets {
+ patterns[idx] = t.ID
+ targetByID[t.ID] = t
+ }
+
+ cfg := &packages.Config{
+ Dir: l.dir,
+ Context: ctx,
+ Mode: packages.NeedCompiledGoFiles |
+ packages.NeedSyntax |
+ packages.NeedTypes |
+ packages.NeedTypesInfo,
+ }
+ pkgs, err := packages.Load(cfg, patterns...)
+ if err != nil {
+ failBatch(targets, res, err)
+ return
+ }
+
+ if got, want := len(pkgs), len(patterns); got != want {
+ failBatch(targets, res, fmt.Errorf("BUG: Load(%s) resulted in %d packages, want %d packages", patterns, got, want))
+ return
+ }
+
+ // Validate the response: ensure we can associate each returned package with
+ // a requested target, or fail the entire batch.
+ for _, pkg := range pkgs {
+ if _, ok := targetByID[pkg.ID]; !ok {
+ failBatch(targets, res, fmt.Errorf("BUG: Load() returned package %s, which was not requested", pkg.ID))
+ return
+ }
+ }
+
+LoadedPackage:
+ for _, pkg := range pkgs {
+ t := targetByID[pkg.ID]
+ result := &Package{
+ Fileset: pkg.Fset,
+ TypeInfo: pkg.TypesInfo,
+ TypePkg: pkg.Types,
+ }
+ for idx, absPath := range pkg.CompiledGoFiles {
+ b, err := os.ReadFile(absPath)
+ if err != nil {
+ res <- LoadResult{
+ Target: t,
+ Err: err,
+ }
+ continue LoadedPackage
+ }
+ relPath := absPath
+ f := &File{
+ AST: pkg.Syntax[idx],
+ Path: relPath,
+ LibraryUnderTest: t.LibrarySrcs[relPath],
+ Code: string(b),
+ }
+ result.Files = append(result.Files, f)
+ }
+ res <- LoadResult{
+ Target: t,
+ Package: result,
+ }
+ }
+}
diff --git a/internal/o2o/loader/loaderconfig.go b/internal/o2o/loader/loaderconfig.go
new file mode 100644
index 0000000..2f1f886
--- /dev/null
+++ b/internal/o2o/loader/loaderconfig.go
@@ -0,0 +1,9 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package loader
+
+// Config configures the loader.
+type Config struct {
+}
diff --git a/internal/o2o/profile/profile.go b/internal/o2o/profile/profile.go
new file mode 100644
index 0000000..306c5b7
--- /dev/null
+++ b/internal/o2o/profile/profile.go
@@ -0,0 +1,62 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package profile provides a simple way to gather timing statistics.
+package profile
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+)
+
+type profile struct {
+ records []record
+}
+
+type record struct {
+ name string
+ time time.Time
+}
+
+type key int
+
+const profileKey key = 0
+
+// NewContext returns a new context that has profiling information attached.
+func NewContext(parent context.Context) context.Context {
+ ctx := context.WithValue(parent, profileKey, &profile{})
+ Add(ctx, "start")
+ return ctx
+}
+
+// Add adds event and event with the given name to the profile attached to the context.
+func Add(ctx context.Context, name string) {
+ p, ok := ctx.Value(profileKey).(*profile)
+ if !ok {
+ return
+ }
+ p.records = append(p.records, record{
+ name: name,
+ time: time.Now(),
+ })
+}
+
+// Dump prints the profile attached to the context as string.
+func Dump(ctx context.Context) string {
+ p, ok := ctx.Value(profileKey).(*profile)
+ if !ok {
+ return "<no profile>"
+ }
+ if len(p.records) == 1 {
+ return "<empty profile>"
+ }
+ var b strings.Builder
+ fmt.Fprintf(&b, "TOTAL: %s | %s", p.records[len(p.records)-1].time.Sub(p.records[0].time), p.records[0].name)
+ for i := 1; i < len(p.records); i++ {
+ fmt.Fprintf(&b, " %s %s", p.records[i].time.Sub(p.records[i-1].time), p.records[i].name)
+ }
+ return b.String()
+}
diff --git a/internal/o2o/rewrite/rewrite.go b/internal/o2o/rewrite/rewrite.go
new file mode 100644
index 0000000..68163ea
--- /dev/null
+++ b/internal/o2o/rewrite/rewrite.go
@@ -0,0 +1,727 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package rewrite implements the main open2opaque functionality: rewriting Go
+// source code to use the opaque API.
+package rewrite
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "os"
+ "os/exec"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "flag"
+ log "github.com/golang/glog"
+ "github.com/google/subcommands"
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/tools/go/packages"
+ "google.golang.org/open2opaque/internal/fix"
+ "google.golang.org/open2opaque/internal/ignore"
+ "google.golang.org/open2opaque/internal/o2o/errutil"
+ "google.golang.org/open2opaque/internal/o2o/loader"
+ "google.golang.org/open2opaque/internal/o2o/profile"
+ "google.golang.org/open2opaque/internal/o2o/syncset"
+ "google.golang.org/open2opaque/internal/o2o/wd"
+ "google.golang.org/protobuf/proto"
+
+ statspb "google.golang.org/open2opaque/internal/dashboard"
+)
+
+const kindTypeUsages = "typeusages"
+
+// Cmd implements the rewrite subcommand of the open2opaque tool.
+type Cmd struct {
+ toUpdate string
+ toUpdateFile string
+ builderTypesFile string
+ builderLocationsFile string
+ levelsStr string
+ httpAddr string
+ outputFilterStr string
+ ignoreOutputFilterStr string
+ parallelJobs int
+ dryRun bool
+ showWork bool
+ useBuilders string
+}
+
+func (cmd *Cmd) levels() []string {
+ return strings.Split(cmd.levelsStr, ",")
+}
+
+// Name implements subcommand.Command.
+func (*Cmd) Name() string { return "rewrite" }
+
+// Synopsis implements subcommand.Command.
+func (*Cmd) Synopsis() string { return "Rewrite Go source code to use the Opaque API." }
+
+// Usage implements subcommand.Command.
+func (*Cmd) Usage() string {
+ return `Usage: open2opaque rewrite -levels=yellow <target> [<target>...]
+
+See http://godoc/3/net/proto2/go/open2opaque/open2opaque for documentation.
+
+Command-line flag documentation follows:
+`
+}
+
+// SetFlags implements subcommand.Command.
+func (cmd *Cmd) SetFlags(f *flag.FlagSet) {
+ const exampleMessageType = "google.golang.org/protobuf/types/known/timestamppb"
+
+ f.StringVar(&cmd.toUpdate,
+ "types_to_update",
+ "",
+ "Comma separated list of types to migrate. For example, '"+exampleMessageType+"'. Empty means 'all'. types_to_update_file overrides this flag.")
+
+ f.StringVar(&cmd.toUpdateFile,
+ "types_to_update_file",
+ "",
+ "Path to a file with one type to migrate per line. For example, '"+exampleMessageType+"'.")
+
+ workdirHelp := "relative to the current directory"
+
+ builderTypesFileDefault := ""
+ f.StringVar(&cmd.builderTypesFile,
+ "types_always_builders_file",
+ builderTypesFileDefault,
+ "Path to a file ("+workdirHelp+") with one type per line for which builders will always be used (instead of setters). For example, '"+exampleMessageType+"'.")
+
+ builderLocationsFile := ""
+ builderLocationsPath := "path"
+ f.StringVar(&cmd.builderLocationsFile,
+ "paths_always_builders_file",
+ builderLocationsFile,
+ "Path to a file ("+workdirHelp+") with one "+builderLocationsPath+" per line for which builders will always be used (instead of setters).")
+
+ levelsHelp := ""
+ f.StringVar(&cmd.levelsStr,
+ "levels",
+ "green",
+ "Comma separated list of rewrite levels"+levelsHelp+". Levels can be: green, yellow, red. Each level includes the preceding ones: -levels=red enables green, yellow and red rewrites. Empty list means that no rewrites are requested; only analysis.")
+
+ f.StringVar(&cmd.httpAddr,
+ "http",
+ "localhost:6060",
+ "Address (host:port) to serve the net/http/pprof handlers on (for profiling).")
+
+ f.StringVar(&cmd.outputFilterStr,
+ "output_filter",
+ ".",
+ "A regular expression that filters file names of files that should be written. This is useful, for example, to limit changes to '_test.go' files.")
+
+ f.StringVar(&cmd.ignoreOutputFilterStr,
+ "ignore_output_filter",
+ "",
+ "A regular expression that filters out file names of files that should be written. This is useful, for example, to limit changes to non-test files. It takes precedence over --output_filter.")
+
+ f.IntVar(&cmd.parallelJobs,
+ "parallel_jobs",
+ 20,
+ "How many packages are analyzed in parallel.")
+
+ f.BoolVar(&cmd.dryRun,
+ "dry_run",
+ false,
+ "Do not modify any files, but run all the logic.")
+
+ f.BoolVar(&cmd.showWork,
+ "show_work",
+ false,
+ "For debugging: show your work mode. Logs to the INFO log every rewrite step that causes a change, and the diff of its changes.")
+
+ useBuildersDefault := "everywhere"
+ useBuildersHelp := ""
+ useBuildersValues := "'tests', 'everywhere' and 'nowhere'"
+ f.StringVar(&cmd.useBuilders,
+ "use_builders",
+ useBuildersDefault,
+ "Determines where struct initialization rewrites will use builders instead of setters. Valid values are "+useBuildersValues+"."+useBuildersHelp)
+}
+
+// Execute implements subcommand.Command.
+func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus {
+ if err := cmd.rewrite(ctx, f); err != nil {
+ // Use fmt.Fprintf instead of log.Exit to generate a shorter error
+ // message: users do not care about the current date/time and the fact
+ // that our code lives in rewrite.go.
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return subcommands.ExitFailure
+ }
+
+ return subcommands.ExitSuccess
+}
+
+// Command returns an initialized Cmd for registration with the subcommands
+// package.
+func Command() *Cmd {
+ return &Cmd{}
+}
+
+func (cmd *Cmd) rewrite(ctx context.Context, f *flag.FlagSet) error {
+ subdir, err := wd.Adjust()
+ if err != nil {
+ return err
+ }
+
+ targets := f.Args()
+ _ = subdir
+
+ if len(targets) == 0 {
+ f.Usage()
+ return nil
+ }
+ return cmd.RewriteTargets(ctx, targets)
+}
+
+// RewriteTargets implements the rewrite functionality, and is called either
+// from within this same package (open2opaque rewrite) or from the
+// rewritepending package (open2opaque rewrite-pending wrapper).
+func (cmd *Cmd) RewriteTargets(ctx context.Context, targets []string) error {
+
+ targetsKind, err := verifyTargetsAreSameKind(targets)
+ if err != nil {
+ return err
+ }
+ if targetsKind == "unknown" {
+ return fmt.Errorf("could not detect target kind of %q - neither a blaze target, nor a .go file, nor a go package import path (see http://godoc/3/net/proto2/go/open2opaque/open2opaque for instructions)", targets[0])
+ }
+
+ inputTypeUses := targetsKind == kindTypeUsages
+
+ if inputTypeUses && len(targets) > 1 {
+ return fmt.Errorf("When specifying the special value %q as target, you must not specify more than one target", kindTypeUsages)
+ }
+
+ if inputTypeUses && cmd.toUpdate == "" && cmd.toUpdateFile == "" {
+ return fmt.Errorf("Please set either --types_to_update or --types_to_update_file to use %q", kindTypeUsages)
+ }
+ useSameClient := true
+
+ outputFilterRe, err := regexp.Compile(cmd.outputFilterStr)
+ if err != nil {
+ return err
+ }
+ ignoreOutputFilterRe, err := regexp.Compile(cmd.ignoreOutputFilterStr)
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Useful commands")
+ fmt.Fprintln(w, " go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30")
+ fmt.Fprintln(w, " go tool pprof http://localhost:6060/debug/pprof/heap")
+ fmt.Fprintln(w, " wget http://localhost:6060/debug/pprof/trace?seconds=5")
+ fmt.Fprintln(w, " wget http://localhost:6060/debug/pprof/goroutine?debug=2")
+ })
+ fmt.Println(http.ListenAndServe(cmd.httpAddr, nil))
+ }()
+
+ var lvls []fix.Level
+ for _, lvl := range cmd.levels() {
+ switch lvl {
+ case "":
+ case "green":
+ lvls = append(lvls, fix.Green)
+ case "yellow":
+ if useSameClient {
+ lvls = append(lvls, fix.Green)
+ }
+ lvls = append(lvls, fix.Yellow)
+ case "red":
+ if useSameClient {
+ lvls = append(lvls, fix.Green)
+ lvls = append(lvls, fix.Yellow)
+ }
+ lvls = append(lvls, fix.Red)
+ default:
+ return fmt.Errorf("unrecognized level name %q", lvl)
+ }
+ }
+
+ toUpdateParts := strings.Split(cmd.toUpdate, ",")
+ if len(toUpdateParts) == 1 && toUpdateParts[0] == "" {
+ toUpdateParts = nil
+ }
+ typesToUpdate := newSet(toUpdateParts)
+ if cmd.toUpdateFile != "" {
+ b, err := os.ReadFile(cmd.toUpdateFile)
+ if err != nil {
+ log.ExitContext(ctx, err)
+ }
+ typesToUpdate = newSet(strings.Split(strings.TrimSpace(string(b)), "\n"))
+ }
+
+ builderTypes := map[string]bool{}
+ if cmd.builderTypesFile != "" {
+ fn := cmd.builderTypesFile
+ b, err := os.ReadFile(fn)
+ if err != nil {
+ log.ExitContext(ctx, err)
+ }
+ builderTypes = newSet(strings.Split(strings.TrimSpace(string(b)), "\n"))
+ }
+ var builderLocations *ignore.List
+ if cmd.builderLocationsFile != "" {
+ fn := cmd.builderLocationsFile
+ l, err := ignore.LoadList(fn)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ // File not found: user is running a newer tool in a client synced
+ // to an older CL. Ignore and proceed without builderLocations.
+ } else {
+ builderLocations = l
+ }
+ }
+
+ var pkgs []string
+ switch targetsKind {
+
+ case "go package import path":
+ pkgs = targets
+
+ default:
+ return fmt.Errorf("BUG: unhandled targetsKind %q", targetsKind)
+ }
+
+ packagesToTargets := func(pkgs []string) ([]*loader.Target, error) {
+ fmt.Printf("Resolving Go package names...\n")
+ cfg := &packages.Config{
+ Context: ctx,
+ }
+ loaded, err := packages.Load(cfg, pkgs...)
+ if err != nil {
+ return nil, err
+ }
+ targets := make([]*loader.Target, len(loaded))
+ for idx, l := range loaded {
+ targets[idx] = &loader.Target{ID: l.ID}
+ }
+ return targets, nil
+ }
+
+ targetsToRewrite, err := packagesToTargets(pkgs)
+ if err != nil {
+ return fmt.Errorf("can't read the package list: %v", err)
+ }
+
+ var builderUseType fix.BuilderUseType
+ switch cmd.useBuilders {
+ case "everywhere":
+ builderUseType = fix.BuildersEverywhere
+ case "nowhere":
+ builderUseType = fix.BuildersNowhere
+ case "tests":
+ builderUseType = fix.BuildersTestsOnly
+ default:
+ return fmt.Errorf("invalid value for --use_builders flag. Valid values are 'tests', 'everywhere', 'everywhere-except-promising' and 'nowhere'")
+ }
+
+ cfg := &config{
+ targets: targetsToRewrite,
+ typesToUpdate: typesToUpdate,
+ builderTypes: builderTypes,
+ builderLocations: builderLocations,
+ levels: lvls,
+ outputFilterRe: outputFilterRe,
+ ignoreOutputFilterRe: ignoreOutputFilterRe,
+ useSameClient: useSameClient,
+ parallelJobs: cmd.parallelJobs,
+ dryRun: cmd.dryRun,
+ showWork: cmd.showWork,
+ useBuilder: builderUseType,
+ }
+
+ if err := rewrite(ctx, cfg); err != nil {
+ log.ExitContext(ctx, err)
+ }
+
+ return nil
+}
+
+// splitName splits a qualified Go declaration name into package path
+// and bare identifier.
+func splitName(name string) (pkgPath, ident string) {
+ i := strings.LastIndex(name, ".")
+ return name[:i], name[i+1:]
+}
+
+// keys returns the keys of set in sorted order.
+func keys(set map[string]bool) []string {
+ var res []string
+ for k := range set {
+ res = append(res, k)
+ }
+ sort.Strings(res)
+ return res
+}
+
+type config struct {
+ // pkgs is the list of Go packages to rewrite
+ targets []*loader.Target
+
+ // Name of the run. This ends up (for example) in client names.
+ runName string
+
+ // A set of types to consider when updating code
+ // (e.g. "google.golang.org/protobuf/types/known/timestamppb").
+ //
+ // An empty (or nil) typesToUpdate means "update all types".
+ typesToUpdate map[string]bool
+
+ // A set of types for which to always use builders, not setters.
+ // (e.g. "google.golang.org/protobuf/types/known/timestamppb").
+ //
+ // An empty (or nil) builderTypes means: use setters or builders for
+ // production/test code respectively (or follow the -use_builders flag if
+ // set).
+ builderTypes map[string]bool
+
+ builderLocations *ignore.List
+
+ levels []fix.Level
+
+ outputFilterRe, ignoreOutputFilterRe *regexp.Regexp
+
+ useSameClient bool
+
+ parallelJobs int
+
+ dryRun bool
+
+ showWork bool
+
+ useBuilder fix.BuilderUseType
+}
+
+func (c *config) createLoader(ctx context.Context, dir string) (_ loader.Loader, cl int64, _ error) {
+
+ fmt.Println("Starting the Blaze loader")
+ l, err := loader.NewBlazeLoader(ctx, &loader.Config{}, dir)
+ if err != nil {
+ return nil, 0, err
+ }
+ return l, 0, nil
+}
+
+func rewrite(ctx context.Context, cfg *config) (err error) {
+ defer errutil.Annotatef(&err, "rewrite() failed")
+
+ log.InfoContextf(ctx, "Configuration: %+v", cfg)
+
+ cutoff := ""
+ if len(cfg.targets) > 50 {
+ cutoff = " (listing first 50)"
+ }
+ fmt.Printf("rewriting %d packages:%s\n", len(cfg.targets), cutoff)
+ for idx, t := range cfg.targets {
+ fmt.Printf(" %s\n", t.ID)
+ if idx >= 50 {
+ break
+ }
+ }
+
+ wd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ // Start the loader before creating more clients. This allows loading typeinfo sstables while we wait on citc to setup clients.
+ l, loaderCL, err := cfg.createLoader(ctx, wd)
+ if err != nil {
+ return err
+ }
+ defer l.Close(ctx)
+
+ start := time.Now()
+ resc := make(chan fixResult)
+
+ pkgCfg := packageConfig{
+ loader: l,
+ outputFilterRe: cfg.outputFilterRe,
+ ignoreOutputFilterRe: cfg.ignoreOutputFilterRe,
+ dryRun: cfg.dryRun,
+ configuredPkg: fix.ConfiguredPackage{
+ ProcessedFiles: syncset.New(), // avoid processing files multiple times
+ ShowWork: cfg.showWork,
+ TypesToUpdate: cfg.typesToUpdate,
+ Levels: cfg.levels,
+ UseBuilders: cfg.useBuilder,
+ },
+ }
+
+ // Load and process targets in batches of up to cfg.parallelJobs
+ // packages. This happens in a separate goroutine; the main goroutine just
+ // collects and prints results.
+ go func() {
+ ln := len(cfg.targets)
+ for idx := 0; idx < ln; idx += cfg.parallelJobs {
+ end := idx + cfg.parallelJobs
+ if end > ln {
+ end = ln
+ }
+ fixPackageBatch(ctx, pkgCfg, cfg.targets[idx:end], resc)
+ }
+ }()
+
+ fmt.Printf("Loading packages (in batches of up to %d)...\n", cfg.parallelJobs)
+
+ writtenByPath := make(map[string]bool)
+ var total, fail int
+ for range cfg.targets {
+ res := <-resc
+ profile.Add(res.ctx, "main/gotresp")
+
+ total++
+ if res.err != nil {
+ fail++
+ }
+
+ for p := range res.written {
+ writtenByPath[p] = true
+ }
+
+ tused := time.Since(start)
+ tavg := tused / time.Duration(total)
+ tleft := time.Duration(len(cfg.targets)-total) * tavg
+ profile.Add(res.ctx, "done")
+
+ fmt.Printf(`PROCESSED %d/%d packages
+ Last package: %s
+ Total time: %s
+ Package profile: %s
+ Failures: %d (%.2f%%)
+ Average time: %s
+ Estimated until done: %s
+ Estimated done at: %s
+ Error: %v
+
+`, total, len(cfg.targets), res.ruleName, tused, profile.Dump(res.ctx), fail, 100.0*float64(fail)/float64(total), tavg, tleft, time.Now().Add(tleft), res.err)
+
+ }
+
+ _ = loaderCL // Used in Google-internal code.
+
+ writtenFiles := make([]string, 0, len(writtenByPath))
+ for fname := range writtenByPath {
+ writtenFiles = append(writtenFiles, fname)
+ }
+ sort.Strings(writtenFiles)
+
+ successful := total - fail
+ fmt.Printf("\nProcessed %d packages:\n", total)
+ fmt.Printf("\tsuccessfully analyzed: %d\n", successful)
+ fmt.Printf("\tfailed to load/rewrite: %d\n", fail)
+ fmt.Printf("\t.go files rewritten: %d\n", len(writtenFiles))
+ if len(writtenFiles) > 0 {
+ fmt.Println("\nYou should see the modified files.")
+ if err := fixBuilds("", writtenFiles); err != nil {
+ fmt.Fprintf(os.Stderr, "Can't fix builds: %v\n", err)
+ }
+ }
+ if fail > 0 {
+ return fmt.Errorf("%d packages could not be rewritten", fail)
+ }
+
+ return nil
+}
+
+func runGoimports(dir string, files []string) error {
+ fmt.Printf("\tRunning goimports on %d files\n", len(files))
+ // Limit concurrent processes.
+ parallelism := make(chan struct{}, 20)
+ eg, ctx := errgroup.WithContext(context.Background())
+ for _, f := range files {
+ if !strings.HasSuffix(f, ".go") {
+ continue
+ }
+ eg.Go(func() error {
+ parallelism <- struct{}{}
+ defer func() { <-parallelism }()
+ cmd := exec.CommandContext(ctx, "goimports", "-w", f)
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("goimports -w %s failed: %s\n%s", f, err, out)
+ }
+ return nil
+ })
+ }
+ return eg.Wait()
+}
+
+var fixBuilds = func(dir string, files []string) error {
+ return runGoimports(dir, files)
+}
+
+type fixResult struct {
+ ruleName string
+ err error
+ stats []*statspb.Entry
+ ctx context.Context
+ drifted []string
+ written map[string]bool
+}
+
+type packageConfig struct {
+ loader loader.Loader
+ outputFilterRe *regexp.Regexp
+ ignoreOutputFilterRe *regexp.Regexp
+ dryRun bool
+ configuredPkg fix.ConfiguredPackage
+}
+
+func fixPackageBatch(ctx context.Context, cfg packageConfig, targets []*loader.Target, resc chan fixResult) {
+ results := make(chan loader.LoadResult, len(targets))
+ var wg sync.WaitGroup
+ wg.Add(len(targets))
+ for range targets {
+ go func() {
+ defer wg.Done()
+ res, ok := <-results
+ if !ok {
+ return // channel closed
+ }
+ ctx := profile.NewContext(ctx)
+ if err := res.Err; err != nil {
+ resc <- fixResult{
+ ruleName: res.Target.ID,
+ err: err,
+ ctx: ctx,
+ }
+ return
+ }
+ profile.Add(ctx, "main/scheduled")
+
+ cfg.configuredPkg.Testonly = res.Target.Testonly
+ cfg.configuredPkg.Loader = cfg.loader
+ cfg.configuredPkg.Pkg = res.Package
+ stats, drifted, written, err := fixPackage(ctx, cfg)
+ profile.Add(ctx, "main/fixed")
+ resc <- fixResult{
+ ruleName: res.Target.ID,
+ err: err,
+ stats: stats,
+ ctx: ctx,
+ drifted: drifted,
+ written: written,
+ }
+ }()
+ }
+ cfg.loader.LoadPackages(ctx, targets, results)
+ close(results)
+ wg.Wait()
+}
+
+// fixPackage loads a Go package
+// from the input client, applies transformations to it, and writes results to
+// the output client.
+func fixPackage(ctx context.Context, cfg packageConfig) (stats []*statspb.Entry, drifted []string, written map[string]bool, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("panic: %s", r)
+ }
+ }()
+
+ fixed, err := cfg.configuredPkg.Fix()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ profile.Add(ctx, "fix/fixed")
+
+ written = make(map[string]bool)
+ for _, lvl := range cfg.configuredPkg.Levels {
+ for _, f := range fixed[lvl] {
+ fname := f.Path
+ if !f.Modified {
+ log.InfoContextf(ctx, "Skipping writing [NOT MODIFIED] %s %s to %s: not modified", lvl, f.Path, fname)
+ continue
+ }
+ if f.Generated {
+ log.InfoContextf(ctx, "Skipping writing [GENERATED FILE] %s %s to %s: generated files can't be overwritten", lvl, f.Path, fname)
+ continue
+ }
+ if strings.HasPrefix(f.Path, "net/proto2/go/") {
+ log.InfoContextf(ctx, "Skipping writing [IGNORED PATH] %s %s to %s: generated files can't be overwritten", lvl, f.Path, fname)
+ continue
+ }
+ if cfg.outputFilterRe.FindString(fname) == "" || cfg.ignoreOutputFilterRe.FindString(fname) != "" {
+ log.InfoContextf(ctx, "Skipping writing [OUTPUT FILTER] %s %s to %s", lvl, f.Path, fname)
+ continue
+ }
+ if cfg.dryRun {
+ log.InfoContextf(ctx, "Skipping writing [DRY RUN] %s %s to %s", lvl, f.Path, fname)
+ continue
+ }
+ if f.Drifted {
+ drifted = append(drifted, f.Path)
+ }
+ log.InfoContextf(ctx, "Writing %s %s to %s", lvl, f.Path, fname)
+ if err := os.WriteFile(fname, []byte(f.Code), 0644); err != nil {
+ return nil, nil, nil, err
+ }
+ written[fname] = true
+ }
+ }
+ profile.Add(ctx, "fix/wrotefiles")
+
+ stats = fixed.AllStats()
+ profile.Add(ctx, "fix/donestats")
+
+ return stats, drifted, written, nil
+}
+
+func newSet(ss []string) map[string]bool {
+ if len(ss) == 0 {
+ return nil
+ }
+ out := make(map[string]bool)
+ for _, s := range ss {
+ out[s] = true
+ }
+ return out
+}
+
+type rowAdder interface {
+ AddRow(context.Context, proto.Message) error
+}
+
+type nullRowAdder struct{}
+
+func (*nullRowAdder) AddRow(context.Context, proto.Message) error {
+ return nil
+}
+
+func targetKind(target string) string {
+ return "go package import path"
+}
+
+func verifyTargetsAreSameKind(targets []string) (string, error) {
+ counters := make(map[string]int)
+ targetKinds := make([]string, len(targets))
+ for idx, target := range targets {
+ kind := targetKind(target)
+ targetKinds[idx] = kind
+ counters[kind]++
+ }
+ if len(counters) > 1 {
+ firstKind := targetKinds[0]
+ otherIdx := 0
+ for otherIdx < len(targetKinds)-1 && targetKinds[otherIdx] == firstKind {
+ otherIdx++
+ }
+ return "", fmt.Errorf("target kinds unexpectedly not the same: target %q is of kind %q, but target %q is of kind %q", targets[0], firstKind, targets[otherIdx], targetKinds[otherIdx])
+ }
+ return targetKinds[0], nil
+}
diff --git a/internal/o2o/setapi/setapi.go b/internal/o2o/setapi/setapi.go
new file mode 100644
index 0000000..1143a8b
--- /dev/null
+++ b/internal/o2o/setapi/setapi.go
@@ -0,0 +1,816 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package setapi implements the setapi open2opaque subcommand, which sets the
+// go_api_flag file option in .proto files.
+package setapi
+
+import (
+ "bytes"
+ "cmp"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "os/exec"
+ "slices"
+ "strings"
+
+ "flag"
+ "github.com/google/subcommands"
+ "golang.org/x/sync/errgroup"
+ pb "google.golang.org/open2opaque/internal/apiflagdata"
+ "google.golang.org/open2opaque/internal/o2o/args"
+ "google.golang.org/open2opaque/internal/protodetect"
+ "google.golang.org/open2opaque/internal/protoparse"
+ descpb "google.golang.org/protobuf/types/descriptorpb"
+ gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
+)
+
+// Cmd implements the setapi subcommand of the open2opaque tool.
+type Cmd struct {
+ apiFlag string
+ inputFile string
+ skipCleanup bool
+ maxProcs uint
+ protoFmt string
+ kind string
+}
+
+// Name implements subcommand.Command.
+func (*Cmd) Name() string { return "setapi" }
+
+// Synopsis implements subcommand.Command.
+func (*Cmd) Synopsis() string {
+ return "Set the Go API level proto option."
+}
+
+// Usage implements subcommand.Command.
+func (*Cmd) Usage() string {
+ return `Usage: open2opaque setapi [-api=<` + strings.Join(validApis, "|") + `>] [-input_file=<input-list>] [<path/file.proto>] [<protopkg1>] [<protopkg2.MessageName>]
+
+The setapi subcommand adjusts the Go API level option on the specified proto file(s) / package(s) / message(s).
+
+The setapi subcommand can either read the proto file name(s) / package(s) / message(s)
+from a text file (-input_file) or from the command line arguments, or both.
+
+Command-line flag documentation follows:
+`
+}
+
+// SetFlags implements subcommand.Command.
+func (cmd *Cmd) SetFlags(f *flag.FlagSet) {
+ f.StringVar(&cmd.apiFlag, "api", "OPAQUE", "set Go API level value to this, valid values: OPEN, HYBRID, and OPAQUE")
+ f.StringVar(&cmd.inputFile, "input_file", "", "file containing list of proto source files / proto packages / proto messages to update (one per line)")
+ f.BoolVar(&cmd.skipCleanup, "skip_cleanup", false, "skip the cleanup step, which removes the file-level flag if it equals the default and removes message flags if they equal the file-level API")
+ f.UintVar(&cmd.maxProcs, "max_procs", 32, "max number of files concurrently processed")
+ protofmtDefault := ""
+ f.StringVar(&cmd.protoFmt, "protofmt", protofmtDefault, "if non-empty, a formatter program for .proto files")
+}
+
+// Execute implements subcommand.Command.
+func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus {
+ if err := cmd.setapi(ctx, f); err != nil {
+ // Use fmt.Fprintf instead of log.Exit to generate a shorter error
+ // message: users do not care about the current date/time and the fact
+ // that our code lives in setapi.go.
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return subcommands.ExitFailure
+ }
+ return subcommands.ExitSuccess
+}
+
+// Command returns an initialized Cmd for registration with the subcommands
+// package.
+func Command() *Cmd {
+ return &Cmd{}
+}
+
+// Task defines the modification of the Go API level of a proto file or a
+// particular message.
+type Task struct {
+ // Path of the proto file.
+ Path string
+ // The content of the proto file.
+ Content []byte
+ // If not empty, set the API level only for the message with this
+ // package-local or fully-qualified name, e.g. Msg.NestedMsg or
+ // pkgname.Msg.NestedMsg.
+ // If empty, set the file-level API level.
+ Symbol string
+
+ TargetAPI gofeaturespb.GoFeatures_APILevel
+ // If true, skip cleanup steps. If false, perform the following steps:
+ // - if all messages in a file are on the same API level, set the whole file
+ // to that level.
+ // - remove API flags from all messages that don't need it because they are
+ // on the same API level as the file (or of its parent message for nested
+ // messages in edition protos that use the new edition-feature API flag).
+ SkipCleanup bool
+ // A leading comment before the Go API flag prevents setapi from
+ // modifying it. If a modification was prevent by this mechanism and
+ // ErrorOnExempt is true, Process returns an error. Otherwise, the original
+ // content is returned.
+ ErrorOnExempt bool
+}
+
+func (cmd *Cmd) setapi(ctx context.Context, f *flag.FlagSet) error {
+ api, err := parseAPIFlag(cmd.apiFlag)
+ if err != nil {
+ return err
+ }
+
+ var inputs []string
+ if cmd.inputFile != "" {
+ var err error
+ if inputs, err = readInputFile(cmd.inputFile); err != nil {
+ return fmt.Errorf("error reading file: %v", err)
+ }
+ }
+ inputs = append(inputs, f.Args()...)
+
+ var tasks []Task
+ kind := cmd.kind
+ if kind == "" {
+ kind = "proto_filename"
+ }
+ for _, input := range inputs {
+ _, filename, symbol, err := args.ToProtoFilename(ctx, input, kind)
+ if err != nil {
+ return err
+ }
+ content, err := os.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ tasks = append(tasks, Task{
+ Path: filename,
+ Symbol: symbol,
+ Content: content,
+ TargetAPI: api,
+ SkipCleanup: cmd.skipCleanup,
+ ErrorOnExempt: true,
+ })
+ }
+
+ if len(tasks) == 0 {
+ return fmt.Errorf("missing inputs, use either -list (one input per line) and / or pass input file name(s) / package(s) / message(s) as non-flag arguments")
+ }
+
+ // Error out if there are non-unique paths to avoid conflicting inputs like
+ // editing the file-level flag of a file and a message flag in the same file.
+ if conflicts := conflictingTasks(tasks); len(conflicts) > 0 {
+ var l []string
+ for _, c := range conflicts {
+ l = append(l, c.Path)
+ }
+ return fmt.Errorf("conflicting, non-unique proto files in inputs: %s", strings.Join(l, ", "))
+ }
+
+ outputs := make([][]byte, len(tasks))
+
+ protofmt := cmd.protoFmt
+ if protofmt == "" {
+ protofmt = "cat"
+ }
+ eg, ctx := errgroup.WithContext(ctx)
+ eg.SetLimit(int(cmd.maxProcs))
+ for itask, task := range tasks {
+ itask, task := itask, task
+ eg.Go(func() error {
+ var err error
+ outputs[itask], err = Process(ctx, task, protofmt)
+ return err
+ })
+ }
+ if err := eg.Wait(); err != nil {
+ return fmt.Errorf("aborting without writing any proto files: %v", err)
+ }
+
+ // Write the outputs back to the input files.
+ eg, ctx = errgroup.WithContext(ctx)
+ eg.SetLimit(int(cmd.maxProcs))
+ for itask, task := range tasks {
+ itask, task := itask, task
+ eg.Go(func() error {
+ return os.WriteFile(task.Path, outputs[itask], 0644)
+ })
+ }
+ if err := eg.Wait(); err != nil {
+ return fmt.Errorf("error while writing proto files: %v", err)
+ }
+ return nil
+}
+
+var (
+ apiMap = map[string]gofeaturespb.GoFeatures_APILevel{
+ "OPEN": gofeaturespb.GoFeatures_API_OPEN,
+ "HYBRID": gofeaturespb.GoFeatures_API_HYBRID,
+ "OPAQUE": gofeaturespb.GoFeatures_API_OPAQUE,
+ }
+ // Don't use the keys of apiMap to report valid values to the user, we want
+ // in a specific order.
+ validApis = []string{"OPEN", "HYBRID", "OPAQUE"}
+)
+
+func parseAPIFlag(flag string) (gofeaturespb.GoFeatures_APILevel, error) {
+ if flag == "" {
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("missing --api flag value")
+ }
+ if api, ok := apiMap[flag]; ok {
+ return api, nil
+ }
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("invalid --api flag value: %v, valid values: %s", flag, strings.Join(validApis, ", "))
+}
+
+func readInputFile(name string) ([]string, error) {
+ b, err := os.ReadFile(name)
+ if err != nil {
+ return nil, err
+ }
+ var result []string
+ for _, line := range strings.Split(string(b), "\n") {
+ if line := strings.TrimSpace(line); line != "" {
+ result = append(result, line)
+ }
+ }
+ return result, nil
+}
+
+func conflictingTasks(tasks []Task) []Task {
+ var conflicts []Task
+ present := make(map[string]bool)
+ for _, task := range tasks {
+ if present[task.Path] {
+ conflicts = append(conflicts, task)
+ }
+ present[task.Path] = true
+ }
+ return conflicts
+}
+
+var logf = func(format string, a ...any) { fmt.Fprintf(os.Stderr, "[setapi] "+format+"\n", a...) }
+
+func parse(path string, content []byte, skipMessages bool) (*protoparse.FileOpt, error) {
+ parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) {
+ return io.NopCloser(bytes.NewReader(content)), nil
+ })
+ fopt, err := parser.ParseFile(path, skipMessages)
+ if err != nil {
+ return nil, fmt.Errorf("protoparse.ParseFile: %v", err)
+ }
+ return fopt, nil
+}
+
+func parentAPI(path string, content []byte, msgName string) (gofeaturespb.GoFeatures_APILevel, error) {
+ fopt, err := parse(path, content, false)
+ if err != nil {
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, err
+ }
+ msgName = strings.TrimPrefix(msgName, fopt.Package+".")
+ var mopt *protoparse.MessageOpt
+ for _, moptLoop := range fopt.MessageOpts {
+ if o := findMsg(moptLoop, msgName); o != nil {
+ mopt = o
+ break
+ }
+ }
+ if mopt == nil {
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("cannot find message %q", msgName)
+ }
+ result := fopt.GoAPI
+ if fopt.Syntax == "editions" && mopt.Parent != nil {
+ result = mopt.Parent.GoAPI
+ }
+ return result, nil
+}
+
+func traverseMsgTree(opt *protoparse.MessageOpt, f func(*protoparse.MessageOpt) error) error {
+ if err := f(opt); err != nil {
+ return err
+ }
+ for _, c := range opt.Children {
+ if err := traverseMsgTree(c, f); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Process modifies the API level of a proto file or of a particular message in
+// a proto file, see the doc comment of the type Task for more details. Before
+// returning the modified file content, the file is formatted by executing
+// formatter (use "cat" if you don't have a formatter handy). This function
+// doesn't modify the []byte task.Content.
+func Process(ctx context.Context, task Task, formatter string) ([]byte, error) {
+ if task.Path == "" {
+ return nil, fmt.Errorf("path is empty")
+ }
+ if len(task.Content) == 0 {
+ return nil, fmt.Errorf("content is empty")
+ }
+
+ // Clone the input file content in case the caller will use the slice after
+ // the call.
+ content := slices.Clone(task.Content)
+
+ var err error
+ if task.Symbol == "" {
+ content, err = setFileAPI(task.Path, content, task.TargetAPI, task.SkipCleanup, task.ErrorOnExempt)
+ if err != nil {
+ return nil, fmt.Errorf("setFileAPI: %v", err)
+ }
+ } else {
+ parentAPI, err := parentAPI(task.Path, content, task.Symbol)
+ if err != nil {
+ return nil, fmt.Errorf("parentAPI: %v", err)
+ }
+ content, err = setMsgAPI(task.Path, content, task.Symbol, parentAPI, task.TargetAPI, task.SkipCleanup)
+ if err != nil {
+ if errors.Is(err, ErrLeadingCommentPreventsEdit) && !task.ErrorOnExempt {
+ // Don't error out if leading comment exempted edit but ErrorOnExempt
+ // is false. Could write this a lot more compact, but this makes it easy
+ // to understand.
+ } else {
+ return nil, err
+ }
+ }
+ }
+
+ if !task.SkipCleanup {
+ content, err = cleanup(task.Path, content)
+ if err != nil {
+ return nil, fmt.Errorf("cleanup: %v", err)
+ }
+ }
+ content, err = FormatFile(ctx, content, formatter)
+ if err != nil {
+ return nil, fmt.Errorf("FormatFile: %v", err)
+ }
+ return content, nil
+}
+
+func fromFeatureToOld(apiLevel gofeaturespb.GoFeatures_APILevel) pb.GoAPI {
+ switch apiLevel {
+ case gofeaturespb.GoFeatures_API_OPEN:
+ return pb.GoAPI_OPEN_V1
+
+ case gofeaturespb.GoFeatures_API_HYBRID:
+ return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID
+
+ case gofeaturespb.GoFeatures_API_OPAQUE:
+ return pb.GoAPI_OPAQUE_V0
+
+ default:
+ panic(fmt.Sprintf("unknown apilevel %v", apiLevel))
+ }
+}
+
+func byteRangeWithEOLComment(in []byte, tr protoparse.TextRange) (beginByte, endByte int, err error) {
+ beginByte, endByte, err = tr.ToByteRange(in)
+ if err != nil {
+ return -1, -1, fmt.Errorf("tr.ToByteRange: %v", err)
+ }
+ if tr.BeginLine == tr.EndLine {
+ if nextNewlineByte := bytes.IndexByte(in[endByte:], '\n'); nextNewlineByte != -1 {
+ if lineSuffix := in[endByte : endByte+nextNewlineByte]; bytes.HasPrefix(bytes.TrimSpace(lineSuffix), []byte("//")) {
+ endByte += nextNewlineByte
+ }
+ }
+ }
+ return beginByte, endByte, nil
+}
+
+func replaceTextRange(in []byte, tr protoparse.TextRange, insert []byte) ([]byte, error) {
+ beginByte, endByte, err := tr.ToByteRange(in)
+ if err != nil {
+ return nil, fmt.Errorf("tr.ToByteRange: %v", err)
+ }
+ in = slices.Delete(in, beginByte, endByte)
+ return slices.Insert(in, beginByte, insert...), nil
+}
+
+func setFileAPI(path string, content []byte, targetAPI gofeaturespb.GoFeatures_APILevel, skipCleanup, errorOnExempt bool) ([]byte, error) {
+ fopt, err := parse(path, content, true)
+ if err != nil {
+ return nil, err
+ }
+ if fopt.IsExplicit && fopt.APIInfo == nil {
+ return nil, fmt.Errorf("BUG: fopt.APIInfo is nil")
+ }
+
+ if defaultAPI := protodetect.DefaultFileLevel(path); defaultAPI == targetAPI {
+ if !fopt.IsExplicit {
+ logf("File %s is already on the target API level by default, doing nothing", path)
+ return content, nil
+ }
+ if fopt.APIInfo.HasLeadingComment {
+ if errorOnExempt {
+ return nil, fmt.Errorf("API flag of file %s has a leading comment that prevents removing it", path)
+ }
+ logf("API flag of file %s has a leading comment that prevents removing it", path)
+ return content, nil
+ }
+ if fopt.GoAPI == targetAPI && skipCleanup {
+ logf("skipping cleanup: not removing the API flag of file %s although it's at the default API level", path)
+ return content, nil
+ }
+ from, to, err := byteRangeWithEOLComment(content, fopt.APIInfo.TextRange)
+ if err != nil {
+ return nil, fmt.Errorf("byteRangeWithEOLComment: %v", err)
+ }
+ logf("Removing the API flag of file %s to use the default API level", path)
+ return slices.Delete(content, from, to), nil
+ }
+
+ if !fopt.IsExplicit {
+ logf("Inserting API flag %q into file %s", targetAPI, path)
+
+ ln, err := fileOptionLineNumber(fopt.SourceCodeInfo)
+ if err != nil {
+ return nil, fmt.Errorf("fileOptionLineNumber: %v", err)
+ }
+ lines := bytes.Split(content, []byte{'\n'})
+ insertLine := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI))
+ if fopt.Syntax == "editions" {
+ insertLine = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI)
+ }
+ result := slices.Clone(lines[:ln])
+ result = append(result, []byte(insertLine))
+ result = append(result, lines[ln:]...)
+ return bytes.Join(result, []byte{'\n'}), nil
+ }
+
+ // File is currently explicitly set to API value and target API isn't the
+ // default API.
+ if fopt.GoAPI == targetAPI {
+ logf("File %s is already on the target API level, do nothing", path)
+ return content, nil
+ }
+ if fopt.APIInfo.HasLeadingComment {
+ if errorOnExempt {
+ return nil, fmt.Errorf("API flag of file %s has a leading comment that prevents replacing it", path)
+ }
+ logf("API flag of file %s has a leading comment that prevents replacing it", path)
+ return content, nil
+ }
+ logf("Replacing the API flag of file %s", path)
+ insert := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI))
+ if fopt.Syntax == "editions" {
+ insert = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI)
+ }
+ content, err = replaceTextRange(content, fopt.APIInfo.TextRange, []byte(insert))
+ if err != nil {
+ return nil, fmt.Errorf("replaceTextRange: %v", err)
+ }
+ return content, nil
+}
+
+func findMsg(opt *protoparse.MessageOpt, name string) *protoparse.MessageOpt {
+ if opt.Message == name {
+ return opt
+ }
+ for _, c := range opt.Children {
+ if childOpt := findMsg(c, name); childOpt != nil {
+ return childOpt
+ }
+ }
+ return nil
+}
+
+// ErrLeadingCommentPreventsEdit signals that no modifications were made because
+// a leading comment exempted an API flag from modification. This error is
+// ignored if Task.ErrorOnExempt is false.
+var ErrLeadingCommentPreventsEdit = errors.New("leading comment prevents edit, check the logs for more details")
+
+func setMsgAPI(path string, content []byte, msgName string, parentAPI, targetAPI gofeaturespb.GoFeatures_APILevel, skipCleanup bool) ([]byte, error) {
+ // This function is called recursively. Parse content every time because the
+ // position information changes when parent messages are manipulated.
+ fopt, err := parse(path, content, false)
+ if err != nil {
+ return nil, err
+ }
+ msgName = strings.TrimPrefix(msgName, fopt.Package+".")
+ var mopt *protoparse.MessageOpt
+ for _, moptLoop := range fopt.MessageOpts {
+ if o := findMsg(moptLoop, msgName); o != nil {
+ mopt = o
+ break
+ }
+ }
+ if mopt == nil {
+ return nil, fmt.Errorf("cannot find massage %q", msgName)
+ }
+ if mopt.IsExplicit && mopt.APIInfo == nil {
+ return nil, fmt.Errorf("BUG: mopt.APIInfo is nil")
+ }
+
+ if parentAPI == targetAPI {
+ if !mopt.IsExplicit {
+ logf("Message %q is already on the target API level, doing nothing", msgName)
+ return content, nil
+ }
+ if mopt.APIInfo.HasLeadingComment {
+ logf("Changing API level of message %q was prevented by a leading comment of the API flag", msgName)
+ if mopt.GoAPI != targetAPI {
+ // Report an error to abort the whole operation if we would have
+ // changed the API flag instead of just cleaning it up because it's the
+ // same as the parent. If we didn't, we might change nested messages to
+ // the wrong API level.
+ return nil, ErrLeadingCommentPreventsEdit
+ }
+ return content, nil
+ }
+ if mopt.GoAPI != targetAPI && fopt.Syntax == "editions" {
+ logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName)
+ for _, child := range mopt.Children {
+ content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ if mopt.GoAPI == targetAPI && skipCleanup {
+ logf("skipping cleanup: not removing the API flag of message %q although it's at the parent API level", msgName)
+ return content, nil
+ }
+ from, to, err := byteRangeWithEOLComment(content, mopt.APIInfo.TextRange)
+ if err != nil {
+ return nil, fmt.Errorf("byteRangeWithEOLComment: %v", err)
+ }
+ logf("Removing the API flag of message %q to use the parent API level", msgName)
+ return slices.Delete(content, from, to), nil
+ }
+
+ if !mopt.IsExplicit {
+ if fopt.Syntax == "editions" {
+ logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName)
+ for _, child := range mopt.Children {
+ content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ logf("Inserting API flag %q into message %q", targetAPI, msgName)
+ idx, err := msgOptionInsertionByteIdx(content, fopt.SourceCodeInfo, mopt.LocPath)
+ if err != nil {
+ return nil, fmt.Errorf("msgOptionInsertionByteIdx: %v", err)
+ }
+ insertion := fmt.Sprintf("\noption go_api_flag = %q;", fromFeatureToOld(targetAPI))
+ if fopt.Syntax == "editions" {
+ insertion = fmt.Sprintf("\noption features.(pb.go).api_level = %s;", targetAPI)
+ }
+ return slices.Concat(content[:idx], []byte(insertion), content[idx:]), nil
+ }
+
+ // Message is currently explicitly set to API value and target API isn't the
+ // parent API.
+ if mopt.GoAPI == targetAPI {
+ logf("Message %q is already on the target API level, do nothing", msgName)
+ return content, nil
+ }
+ if mopt.APIInfo.HasLeadingComment {
+ logf("Changing API level of message %q was prevented by a leading comment of the API flag", msgName)
+ return content, ErrLeadingCommentPreventsEdit
+ }
+ if fopt.Syntax == "editions" {
+ logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName)
+ for _, child := range mopt.Children {
+ content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ logf("Replacing the API flag of message %q", msgName)
+ insert := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI))
+ if fopt.Syntax == "editions" {
+ insert = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI)
+ }
+ content, err = replaceTextRange(content, mopt.APIInfo.TextRange, []byte(insert))
+ if err != nil {
+ return nil, fmt.Errorf("replaceTextRange: %v", err)
+ }
+ return content, nil
+}
+
+func cleanup(path string, content []byte) ([]byte, error) {
+ // Cleanup 1: If all messages are on the same API level, flip the file API
+ // level to that value. If said level is the default for the path, don't use
+ // an explicit flag.
+ fopt, err := parse(path, content, false)
+ if err != nil {
+ return nil, err
+ }
+ apiMap := map[gofeaturespb.GoFeatures_APILevel]struct{}{}
+ for _, moptLoop := range fopt.MessageOpts {
+ traverseMsgTree(moptLoop, func(mopt *protoparse.MessageOpt) error {
+ apiMap[mopt.GoAPI] = struct{}{}
+ return nil
+ })
+ }
+ if len(apiMap) == 1 {
+ // All messages have same API level.
+ // Consider cleanup as a nice to have, don't error out on leading-comment
+ // exemptions.
+ const errorOnExempt = false
+ const skipCleanup = false
+ content, err = setFileAPI(path, content, fopt.MessageOpts[0].GoAPI, skipCleanup, errorOnExempt)
+ if err != nil {
+ return nil, fmt.Errorf("setFileAPI: %v", err)
+ }
+ }
+
+ // Cleanup 2: Remove the API flag from all messages that have the same API
+ // level as their parents. The parent is usually the file API level, but in
+ // case of editions proto that use the new edition feature, the parent of
+ // a nested message is its parent message.
+ //
+ // Parse again after the previous cleanup might have modified conten.
+ fopt, err = parse(path, content, false)
+ if err != nil {
+ return nil, err
+ }
+ // Collect all byte ranges of API flags that should be removed.
+ type byteRange struct {
+ from, to int
+ }
+ var removeByteRanges []byteRange
+ for _, moptLoop := range fopt.MessageOpts {
+ if err := traverseMsgTree(moptLoop, func(mopt *protoparse.MessageOpt) error {
+ if !mopt.IsExplicit {
+ return nil
+ }
+ if mopt.APIInfo == nil {
+ return fmt.Errorf("BUG: mopt.APIInfo is nil")
+ }
+ if mopt.APIInfo.HasLeadingComment {
+ return nil
+ }
+ parentAPI := fopt.GoAPI
+ if fopt.Syntax == "editions" && mopt.Parent != nil {
+ parentAPI = mopt.Parent.GoAPI
+ }
+ if mopt.GoAPI != parentAPI {
+ return nil
+ }
+ // Message has same API level as parent (or file) and API flag is
+ // explicit.
+ from, to, err := byteRangeWithEOLComment(content, mopt.APIInfo.TextRange)
+ if err != nil {
+ return fmt.Errorf("TextRange.ToByteRange: %v", err)
+ }
+ removeByteRanges = append(removeByteRanges, byteRange{from: from, to: to})
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ }
+ if len(removeByteRanges) == 0 {
+ return content, nil
+ }
+
+ slices.SortFunc(removeByteRanges, func(a, b byteRange) int {
+ return cmp.Compare(a.from, b.from)
+ })
+ for i := 1; i < len(removeByteRanges); i++ {
+ if removeByteRanges[i].from < removeByteRanges[i-1].to {
+ return nil, fmt.Errorf("text ranges overlap")
+ }
+ }
+
+ for _, br := range slices.Backward(removeByteRanges) {
+ content = slices.Delete(content, br.from, br.to)
+ }
+ return content, nil
+}
+
+func spanEndLine(span []int32) int32 {
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1209
+ if len(span) == 4 {
+ return span[2]
+ }
+ return span[0]
+}
+
+// fileOptionLineNumber returns the line number to insert the file-level
+// go_api_flag option. It uses the following heuristics to determine the
+// line number:
+// If there are any file option settings, return the line number after the last
+// one.
+// If there are any import lines, return the line number after the last one.
+// If there is a package statement line, return the line number after it.
+// If there is a syntax statement line, return the line number after it.
+// Note that this func assumes that an earlier protoparser.Parser.ParseFile
+// invocation will return error already if there is no syntax statement.
+func fileOptionLineNumber(info *descpb.SourceCodeInfo) (int32, error) {
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L105-L137
+ syntaxFieldNum = 12
+ editionFieldNum = 14
+ editionDeprFieldNum = 13
+ packageFieldNum = 2
+ importFieldNum = 3
+ optionFieldNum = 8
+ )
+ // pos contains a mapping of proto field numbers to line numbers. For syntax
+ // and package constructs, the line numbers represent where they are declared.
+ // For import and options, the line number represents the last import/option
+ // if it exists.
+ pos := map[int32]int32{}
+ for _, loc := range info.GetLocation() {
+ path := loc.GetPath()
+ span := loc.GetSpan()
+ if len(path) < 1 {
+ continue
+ }
+ switch fieldNum := path[0]; fieldNum {
+ case syntaxFieldNum, editionFieldNum, editionDeprFieldNum, packageFieldNum, importFieldNum, optionFieldNum:
+ endLine := spanEndLine(span)
+ // If not set, map returns 0.
+ if endLine > pos[fieldNum] {
+ pos[fieldNum] = endLine
+ }
+ }
+ }
+
+ if lnum, ok := pos[optionFieldNum]; ok {
+ return lnum + 1, nil
+ }
+ if lnum, ok := pos[importFieldNum]; ok {
+ return lnum + 1, nil
+ }
+ if lnum, ok := pos[packageFieldNum]; ok {
+ return lnum + 1, nil
+ }
+ if lnum, ok := pos[syntaxFieldNum]; ok {
+ return lnum + 1, nil
+ }
+ if lnum, ok := pos[editionFieldNum]; ok {
+ return lnum + 1, nil
+ }
+ if lnum, ok := pos[editionDeprFieldNum]; ok {
+ return lnum + 1, nil
+ }
+
+ return 0, fmt.Errorf("cannot determine line number for file-level API flag")
+}
+
+func msgOptionInsertionByteIdx(content []byte, info *descpb.SourceCodeInfo, msgPath []int32) (int, error) {
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L105
+ nameFieldNum = 1
+ )
+ namePath := append(slices.Clone(msgPath), nameFieldNum)
+ nameEndIdx := math.MaxInt
+
+ for _, loc := range info.GetLocation() {
+ path := loc.GetPath()
+ if len(path) <= len(msgPath) {
+ // Paths in the message (name, fields, option, ...) have to be longer than
+ // the message path.
+ continue
+ }
+ if slices.Equal(path, namePath) {
+ var err error
+ _, nameEndIdx, err = protoparse.SpanToTextRange(loc.GetSpan()).ToByteRange(content)
+ if err != nil {
+ return -1, fmt.Errorf("TextRange.ToByteRange: %v", err)
+ }
+ break
+ }
+ }
+ if nameEndIdx == math.MaxInt {
+ return -1, fmt.Errorf("BUG: cannot find message name")
+ }
+
+ // Look for the next opening curly after the name. If this happens to be in a
+ // comment, this function will produce wrong results.
+ offset := bytes.IndexByte(content[nameEndIdx:], '{')
+ if offset == -1 {
+ return -1, fmt.Errorf(`cannot find "{"`)
+ }
+ return nameEndIdx + offset + 1, nil
+}
+
+// FormatFile runs formatter on input and returns the formatted result.
+func FormatFile(ctx context.Context, input []byte, formatter string) ([]byte, error) {
+ cmd := exec.CommandContext(ctx, formatter)
+ cmd.Stdin = bytes.NewReader(input)
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+ err := cmd.Run()
+ if err != nil {
+ return nil, fmt.Errorf("formatter error: %v", err)
+ }
+ if stderr.Len() > 0 {
+ return nil, fmt.Errorf("formatter stderr: %s", stderr.String())
+ }
+ return stdout.Bytes(), nil
+}
diff --git a/internal/o2o/setapi/setapi_test.go b/internal/o2o/setapi/setapi_test.go
new file mode 100644
index 0000000..b70ad49
--- /dev/null
+++ b/internal/o2o/setapi/setapi_test.go
@@ -0,0 +1,1245 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package setapi_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "slices"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "google.golang.org/open2opaque/internal/o2o/setapi"
+ gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
+)
+
+// We added new test functions for file modification below that cover the same
+// and more aspects of setapi. But we keep this test function around since it
+// still works and there is no point in taking the risk of reducing test
+// coverage. However, if this should become a maintenance burden in the future,
+// consider removing this function.
+func TestModificationOld(t *testing.T) {
+ const copyrightHeader = `// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+`
+ testcases := []struct {
+ desc string
+ input string
+ task setapi.Task
+ want string
+ }{
+ {
+ input: "../../../testdata/flag_edition_test1_go_proto/flag_edition_test1.proto",
+ desc: "replace_file_option_with_HYBRID_where_default_is_OPAQUE_and_clean_up_file_and_messages",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "",
+ TargetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test1;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test1_go_proto/flag_edition_test1.proto",
+ desc: "replace_file_option_with_HYBRID_where_default_is_OPAQUE_and_skip_cleanup",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ TargetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ SkipCleanup: true,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test1;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto",
+ desc: "insert_file_option_OPEN_where_default_is_OPEN_and_clean_up",
+ task: setapi.Task{
+ Path: "google/some.proto",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test2;
+
+import "google/protobuf/go_features.proto";
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+ option
+ /*multi-line*/
+ features.(pb.go)
+ .api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto",
+ desc: "insert_message_M2_option_OPEN_using_fully-qualified_name_and_clean_up",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test2.M2",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test2;
+
+import "google/protobuf/go_features.proto";
+
+message M1 {
+
+
+ message Nested1 {
+ option
+ /*multi-line*/
+ features.(pb.go)
+ .api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+option features.(pb.go).api_level = API_OPEN;
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {
+option features.(pb.go).api_level = API_OPAQUE;}
+
+ map<string, bool> map_field = 10;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test5_go_proto/flag_edition_test5.proto",
+ desc: "inserting_message_M1_option_OPAQUE_works_with_leading_comment",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test5.M1",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test5;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+option features.(pb.go).api_level = API_OPAQUE;
+ // Leading comment on message should not be misplaced
+ // when API flag is inserted.
+ int32 int_field = 1;
+}
+
+message M2 {
+ /**
+ * Leading block comment on message should not be misplaced
+ * when API flag is inserted.
+ */
+ int32 int_field = 1;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test5_go_proto/flag_edition_test5.proto",
+ desc: "inserting_message_M1_option_OPAQUE_works_with_leading_block_comment",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test5.M2",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test5;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ // Leading comment on message should not be misplaced
+ // when API flag is inserted.
+ int32 int_field = 1;
+}
+
+message M2 {
+option features.(pb.go).api_level = API_OPAQUE;
+ /**
+ * Leading block comment on message should not be misplaced
+ * when API flag is inserted.
+ */
+ int32 int_field = 1;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto",
+ desc: "delete_message_M1.Nested1_option_to_make_OPAQUE_and_skip_cleanup",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "M1.Nested1",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ SkipCleanup: true,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test2;
+
+import "google/protobuf/go_features.proto";
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+
+
+ message Nested2 {
+option features.(pb.go).api_level = API_HYBRID;}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ desc: "leading_comments_exempt_file-level_API_flag_changes_and_cleanup",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test3;
+
+import "google/protobuf/go_features.proto";
+
+// Leading comment exempts api_level from modifications.
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ // Exemption on message level.
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested1 {
+ // Exemption on nested-message level.
+ option features.(pb.go).api_level = API_OPAQUE;
+ }
+}
+
+message M2 {
+
+}
+`,
+ },
+ {
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ desc: "leading_comments_exempt_message_API_flag_changes_and_cleanup",
+ task: setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: "M1.Nested1",
+ TargetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ },
+ want: copyrightHeader + `edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test3;
+
+import "google/protobuf/go_features.proto";
+
+// Leading comment exempts api_level from modifications.
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ // Exemption on message level.
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested1 {
+ // Exemption on nested-message level.
+ option features.(pb.go).api_level = API_OPAQUE;
+ }
+}
+
+message M2 {
+
+}
+`,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ var err error
+ tc.task.Content, err = os.ReadFile(tc.input)
+ if err != nil {
+ t.Fatalf("runfiles.ReadFile: %v", err)
+ }
+ ctx := context.Background()
+ contentClone := slices.Clone(tc.task.Content)
+ got, err := setapi.Process(ctx, tc.task, "cat")
+ if err != nil {
+ t.Fatalf("setapi.Process: %v", err)
+ }
+ if !slices.Equal(tc.task.Content, contentClone) {
+ t.Fatalf("setapi.Process modified Task.Content")
+ }
+ if diff := cmp.Diff(tc.want, string(got)); diff != "" {
+ t.Fatalf("diff setapi.Process (-want +got):\n%v\n", diff)
+ }
+ })
+ }
+}
+
+func TestErrors(t *testing.T) {
+ testcases := []struct {
+ desc string
+ input string
+ symbol string
+ errorOnExempt bool
+ wantErr bool
+ }{
+ {
+ desc: "error_for_unknown_symbol",
+ input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto",
+ symbol: "UNKNOWN",
+ wantErr: true,
+ },
+ {
+ desc: "error_for_file-level_flag_with_leading_comment_with_errorOnExempt",
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ errorOnExempt: true,
+ wantErr: true,
+ },
+ {
+ desc: "no_error_for_file-level_flag_with_leading_comment_without_errorOnExempt",
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ },
+ {
+ desc: "error_for_message_flag_with_leading_comment_with_errorOnExempt",
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ symbol: "M1.Nested1",
+ errorOnExempt: true,
+ wantErr: true,
+ },
+ {
+ desc: "error_for_message_flag_with_leading_comment_with_errorOnExempt_on_cleanup_for_M1",
+ input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto",
+ symbol: "M1",
+ errorOnExempt: true,
+ wantErr: true,
+ },
+ }
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ task := setapi.Task{
+ Path: "testonly-opaque-default-dummy.proto",
+ Symbol: tc.symbol,
+ TargetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ ErrorOnExempt: tc.errorOnExempt,
+ }
+ var err error
+ task.Content, err = os.ReadFile(tc.input)
+ if err != nil {
+ t.Fatalf("runfiles.ReadFile: %v", err)
+ }
+ ctx := context.Background()
+ _, err = setapi.Process(ctx, task, "cat")
+ gotErr := err != nil
+ if gotErr != tc.wantErr {
+ t.Fatalf("setapi.Process: got err=%q, wantErr=%v", err, tc.wantErr)
+ }
+ })
+ }
+}
+
+const filenameWithOpaqueDefault = "testonly-opaque-default-dummy.proto"
+
+func TestSetFileAPI(t *testing.T) {
+ testCases := []struct {
+ name string
+ protoIn string
+ targetAPI gofeaturespb.GoFeatures_APILevel
+ skipCleanup bool
+ errorOnExempt bool
+ protoWant string
+ wantErr bool
+ }{
+ {
+ name: "edition_is_default_opaque_not_explicit__do_nothing",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_explicit__remove_flag_and_eol_comment",
+ protoIn: `
+edition = "2023";
+package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_explicit__skip_cleanup_dont_remove",
+ protoIn: `
+edition = "2023";
+package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ skipCleanup: true,
+ protoWant: `
+edition = "2023";
+package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_hybrid_explicit_want_opaque__remove_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_no_pkg_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_other_option_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+option java_package = "foo";
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+option java_package = "foo";
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_import_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+import "foo.proto";
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+import "foo.proto";
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_no_pkg_is_default_opaque_not_explicit_want_hybrid__insert_flag",
+ protoIn: `
+edition = "2023";
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_open_explicit_want_hybrid__replace_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_OPEN;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_opaque_explicit_want_hybrid__replace_flag",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_OPAQUE;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_open_explicit_want_open__do_nothing",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_OPEN;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ protoWant: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_OPEN;
+message M{}
+`,
+ },
+
+ {
+ name: "leading_comment_prevents_replacement_with_errorOnExempt",
+ protoIn: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ errorOnExempt: true,
+ wantErr: true,
+ },
+
+ {
+ name: "leading_comment_prevents_replacement_without_errorOnExempt",
+ protoIn: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ protoWant: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ },
+
+ {
+ name: "leading_comment_prevents_deletion_with_errorOnExempt",
+ protoIn: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_OPAQUE;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ errorOnExempt: true,
+ wantErr: true,
+ },
+
+ {
+ name: "leading_comment_prevents_deletion_without_errorOnExempt",
+ protoIn: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_OPAQUE;
+message M{}
+`,
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+// leading comment
+option features.(pb.go).api_level = API_OPAQUE;
+message M{}
+`,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ task := setapi.Task{
+ Path: filenameWithOpaqueDefault,
+ Content: []byte(tc.protoIn),
+ TargetAPI: tc.targetAPI,
+ SkipCleanup: tc.skipCleanup,
+ ErrorOnExempt: tc.errorOnExempt,
+ }
+ contentClone := slices.Clone(task.Content)
+ protoGot, err := setapi.Process(context.Background(), task, "cat")
+ switch {
+ case err != nil && tc.wantErr:
+ return
+ case err == nil && tc.wantErr:
+ t.Fatalf("wanted error but got nil")
+ case err != nil && !tc.wantErr:
+ t.Fatalf("didn't want error but got: %v", err)
+ }
+ if !slices.Equal(task.Content, contentClone) {
+ t.Fatalf("setapi.Process modified Task.Content")
+ }
+ if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" {
+ t.Errorf("setFileAPI: diff (-want +got):\n%v\n", diff)
+ }
+ })
+ }
+}
+
+func TestSetMsgAPI(t *testing.T) {
+ testCases := []struct {
+ name string
+ protoIn string
+ msgName string
+ targetAPI gofeaturespb.GoFeatures_APILevel
+ protoWant string
+ skipCleanup bool
+ wantErr bool
+ wantErrorLeadingCommentPreventsEdit bool
+ }{
+ {
+ name: "edition_file_default_opaque__message_non_explicit_opaque_set_to_opaque__do_nothing",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{
+string s = 1;
+}
+`,
+ msgName: "M",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{
+string s = 1;
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_explicit_opaque_set_to_opaque__remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{
+option features.(pb.go).api_level = API_OPAQUE;
+string s = 1;
+}
+`,
+ msgName: "M",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{
+
+string s = 1;
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__fully_qualified_message_explicit_opaque_set_to_opaque__remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{
+option features.(pb.go).api_level = API_OPAQUE;
+string s = 1;
+}
+`,
+ msgName: "pkg.M",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{
+
+string s = 1;
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_explicit_opaque_set_to_opaque__skip_cleanup_dont_remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{
+option features.(pb.go).api_level = API_OPAQUE;
+string s = 1;
+}
+`,
+ msgName: "M",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ skipCleanup: true,
+ protoWant: `
+edition = "2023";
+package pkg;
+message M{
+option features.(pb.go).api_level = API_OPAQUE;
+string s = 1;
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_opaque__remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_hybrid__do_nothing",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+
+ string s = 1;
+ }
+ }
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_open__replace",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_OPEN; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_opaque__remove_and_fix_children",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ message A2{
+ string s = 1;
+ message A3 {
+ string s = 1;
+ }
+ }
+ }
+}
+message B{
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+
+ string s = 1;
+ message A2{
+option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ message A3 {
+ string s = 1;
+ }
+ }
+ }
+}
+message B{
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_open__replace_and_fix_children",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ message A2{
+ string s = 1;
+ message A3 {
+ string s = 1;
+ }
+ }
+ }
+}
+message B{
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ option features.(pb.go).api_level = API_OPEN;
+ string s = 1;
+ message A2{
+option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ message A3 {
+ string s = 1;
+ }
+ }
+ }
+}
+message B{
+}
+`,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_opaque__removal_prevented_by_leading_comment",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ // leading comment prevents removal
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ wantErr: true,
+ wantErrorLeadingCommentPreventsEdit: true,
+ },
+
+ {
+ name: "edition_file_default_opaque__message_hybrid_set_to_open__replacement_prevented_by_leading_comment",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+ message A1{
+ // leading comment prevents replacement
+ option features.(pb.go).api_level = API_HYBRID; // end-of-line comment
+ string s = 1;
+ message A2{
+ option features.(pb.go).api_level = API_HYBRID;
+ string s = 1;
+ }
+ }
+}
+`,
+ msgName: "A.A1",
+ targetAPI: gofeaturespb.GoFeatures_API_OPEN,
+ wantErr: true,
+ wantErrorLeadingCommentPreventsEdit: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ task := setapi.Task{
+ Path: filenameWithOpaqueDefault,
+ Content: []byte(tc.protoIn),
+ Symbol: tc.msgName,
+ TargetAPI: tc.targetAPI,
+ SkipCleanup: tc.skipCleanup,
+ ErrorOnExempt: true,
+ }
+ protoGot, err := setapi.Process(context.Background(), task, "cat")
+ switch {
+ case err != nil && tc.wantErr:
+ if tc.wantErrorLeadingCommentPreventsEdit && !errors.Is(err, setapi.ErrLeadingCommentPreventsEdit) {
+ t.Fatalf("wanted wantErrorLeadingCommentPreventsEdit but got: %v", err)
+ }
+ return
+ case err == nil && tc.wantErr:
+ t.Fatalf("wanted error but got nil")
+ case err != nil && !tc.wantErr:
+ t.Fatalf("didn't want error but got: %v", err)
+ }
+ if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" {
+ t.Errorf("setMsgAPI: diff (-want +got):\n%v\n", diff)
+ }
+ })
+ }
+}
+
+func TestCleanup(t *testing.T) {
+ testCases := []struct {
+ name string
+ protoIn string
+ protoWant string
+ }{
+ {
+ name: "edition_is_default_opaque_with_explicit_opaque__remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment
+message M{}
+`,
+ protoWant: `
+edition = "2023";
+package pkg;
+
+message M{}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_some_messages_opaque__remove",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment
+message A1{
+option features.(pb.go).api_level = API_OPEN; // end-of-line comment
+message A2{
+option features.(pb.go).api_level = API_OPAQUE;
+}
+}
+}
+message B{
+// leading comment
+option features.(pb.go).api_level = API_OPAQUE;
+}
+`,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+
+message A1{
+option features.(pb.go).api_level = API_OPEN; // end-of-line comment
+message A2{
+option features.(pb.go).api_level = API_OPAQUE;
+}
+}
+}
+message B{
+// leading comment
+option features.(pb.go).api_level = API_OPAQUE;
+}
+`,
+ },
+
+ {
+ name: "edition_is_default_opaque_some_messages_opaque__remove_but_respect_api_inheritance",
+ protoIn: `
+edition = "2023";
+package pkg;
+message A{
+option features.(pb.go).api_level = API_OPAQUE;
+message A1{
+option features.(pb.go).api_level = API_OPEN;
+message A2{
+// cannot clean up, because A2 inherits OPEN from A1
+option features.(pb.go).api_level = API_OPAQUE;
+}
+}
+}
+message B{
+option features.(pb.go).api_level = API_OPAQUE;
+}
+`,
+ protoWant: `
+edition = "2023";
+package pkg;
+message A{
+
+message A1{
+option features.(pb.go).api_level = API_OPEN;
+message A2{
+// cannot clean up, because A2 inherits OPEN from A1
+option features.(pb.go).api_level = API_OPAQUE;
+}
+}
+}
+message B{
+
+}
+`,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ task := setapi.Task{
+ Path: filenameWithOpaqueDefault,
+ Content: []byte(tc.protoIn),
+ TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ }
+ contentClone := slices.Clone(task.Content)
+ protoGot, err := setapi.Process(context.Background(), task, "cat")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !slices.Equal(task.Content, contentClone) {
+ t.Fatalf("setapi.Process modified Task.Content")
+ }
+ if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" {
+ t.Errorf("setMsgAPI: diff (-want +got):\n%v\n", diff)
+ }
+ })
+ }
+}
diff --git a/internal/o2o/statsutil/statsutil.go b/internal/o2o/statsutil/statsutil.go
new file mode 100644
index 0000000..f492c31
--- /dev/null
+++ b/internal/o2o/statsutil/statsutil.go
@@ -0,0 +1,30 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package statsutil provides functions for working with the open2opaque stats
+// proto.
+package statsutil
+
+import (
+ "strings"
+
+ statspb "google.golang.org/open2opaque/internal/dashboard"
+)
+
+// ShortAndLongNameFrom takes a long name
+// and returns a combined short and long name statspb.Type.
+func ShortAndLongNameFrom(long string) *statspb.Type {
+ short := long
+ if idx := strings.LastIndex(long, "."); idx >= 0 {
+ short = long[idx+1:]
+ stars := strings.LastIndex(long, "*")
+ if stars != -1 {
+ short = long[:stars+1] + short
+ }
+ }
+ return &statspb.Type{
+ LongName: long,
+ ShortName: short,
+ }
+}
diff --git a/internal/o2o/statsutil/statsutil_test.go b/internal/o2o/statsutil/statsutil_test.go
new file mode 100644
index 0000000..93541e8
--- /dev/null
+++ b/internal/o2o/statsutil/statsutil_test.go
@@ -0,0 +1,33 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package statsutil_test
+
+import (
+ "testing"
+
+ "google.golang.org/open2opaque/internal/o2o/statsutil"
+)
+
+func TestShortAndLongNameFrom(t *testing.T) {
+ for _, tt := range []struct {
+ long string
+ wantLong string
+ wantShort string
+ }{
+ {
+ long: "google.golang.org/open2opaque/random_go_proto.MyMessage",
+ wantLong: "google.golang.org/open2opaque/random_go_proto.MyMessage",
+ wantShort: "MyMessage",
+ },
+ } {
+ typ := statsutil.ShortAndLongNameFrom(tt.long)
+ if got := typ.GetLongName(); got != tt.wantLong {
+ t.Errorf("ShortAndLongNameFrom(%s): unexpected long name: got %q, want %q", tt.long, got, tt.wantLong)
+ }
+ if got := typ.GetShortName(); got != tt.wantShort {
+ t.Errorf("ShortAndLongNameFrom(%s): unexpected short name: got %q, want %q", tt.long, got, tt.wantShort)
+ }
+ }
+}
diff --git a/internal/o2o/syncset/syncset.go b/internal/o2o/syncset/syncset.go
new file mode 100644
index 0000000..7a11711
--- /dev/null
+++ b/internal/o2o/syncset/syncset.go
@@ -0,0 +1,34 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package syncset implements sets that can be safely accessed concurrently.
+package syncset
+
+import "sync"
+
+// New returns a new, empty set.
+func New() *Set {
+ return &Set{
+ set: map[string]struct{}{},
+ }
+}
+
+// Set is a set of strings.
+type Set struct {
+ mu sync.Mutex
+ set map[string]struct{}
+}
+
+// Add adds the value v to the set and returns true if it wasn't in the set
+// before. It returns false otherwise.
+func (s *Set) Add(v string) bool {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ _, ok := s.set[v]
+ if !ok {
+ s.set[v] = struct{}{}
+ return true
+ }
+ return false
+}
diff --git a/internal/o2o/syncset/syncset_test.go b/internal/o2o/syncset/syncset_test.go
new file mode 100644
index 0000000..bdee154
--- /dev/null
+++ b/internal/o2o/syncset/syncset_test.go
@@ -0,0 +1,36 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package syncset
+
+import (
+ "sync"
+ "testing"
+)
+
+func TestSyncset(t *testing.T) {
+ s := New()
+ if !s.Add("hello") {
+ t.Error("AddNew() returned false for an empty set")
+ }
+ if s.Add("hello") {
+ t.Error("AddNew('hello') returned true for a set containing 'hello'")
+ }
+ if !s.Add("world") {
+ t.Error("AddNew('world') returned false for a set without 'world'")
+ }
+}
+
+func TestNoRace(t *testing.T) {
+ var wg sync.WaitGroup
+ s := New()
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ s.Add("hello")
+ }()
+ }
+ wg.Wait()
+}
diff --git a/internal/o2o/version/version.go b/internal/o2o/version/version.go
new file mode 100644
index 0000000..a35e817
--- /dev/null
+++ b/internal/o2o/version/version.go
@@ -0,0 +1,86 @@
+package version
+
+import (
+ "context"
+ "fmt"
+ "runtime/debug"
+ "time"
+
+ "flag"
+ "github.com/google/subcommands"
+)
+
+// Cmd implements the version subcommand of the open2opaque tool.
+type Cmd struct{}
+
+// Name implements subcommand.Command.
+func (*Cmd) Name() string { return "version" }
+
+// Synopsis implements subcommand.Command.
+func (*Cmd) Synopsis() string { return "print tool version" }
+
+// Usage implements subcommand.Command.
+func (*Cmd) Usage() string { return `Usage: open2opaque version` }
+
+// SetFlags implements subcommand.Command.
+func (*Cmd) SetFlags(*flag.FlagSet) {}
+
+func synthesizeVersion(info *debug.BuildInfo) string {
+ const fallback = "(devel)"
+ settings := make(map[string]string)
+ for _, s := range info.Settings {
+ settings[s.Key] = s.Value
+ }
+
+ rev, ok := settings["vcs.revision"]
+ if !ok {
+ return fallback
+ }
+
+ commitTime, err := time.Parse(time.RFC3339Nano, settings["vcs.time"])
+ if err != nil {
+ return fallback
+ }
+
+ modifiedSuffix := ""
+ if settings["vcs.modified"] == "true" {
+ modifiedSuffix += "+dirty"
+ }
+
+ // Go pseudo versions use 12 hex digits.
+ if len(rev) > 12 {
+ rev = rev[:12]
+ }
+
+ // Copied from x/mod/module/pseudo.go
+ const PseudoVersionTimestampFormat = "20060102150405"
+
+ return fmt.Sprintf("v?.?.?-%s-%s%s",
+ commitTime.UTC().Format(PseudoVersionTimestampFormat),
+ rev,
+ modifiedSuffix)
+}
+
+// Execute implements subcommand.Command.
+func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus {
+ info, ok := debug.ReadBuildInfo()
+ mainVersion := info.Main.Version
+ if !ok {
+ mainVersion = "<runtime/debug.ReadBuildInfo failed>"
+ }
+ // As of Go 1.24 (https://tip.golang.org/doc/go1.24#go-command),
+ // we get v0.0.0-20241211143045-0af77b971425+dirty for git builds.
+ if mainVersion == "(devel)" {
+ // Before Go 1.24, the main module version just contained "(devel)" when
+ // building from git. Try and find a git revision identifier.
+ mainVersion = synthesizeVersion(info)
+ }
+ fmt.Printf("open2opaque %s\n", mainVersion)
+ return subcommands.ExitSuccess
+}
+
+// Command returns an initialized Cmd for registration with the subcommands
+// package.
+func Command() *Cmd {
+ return &Cmd{}
+}
diff --git a/internal/o2o/wd/wd.go b/internal/o2o/wd/wd.go
new file mode 100644
index 0000000..8e5dde3
--- /dev/null
+++ b/internal/o2o/wd/wd.go
@@ -0,0 +1,11 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package wd deals with adjusting the working directory as needed
+package wd
+
+func Adjust() (subdir string, _ error) {
+
+ return "", nil
+}
diff --git a/internal/protodetect/protodetect.go b/internal/protodetect/protodetect.go
new file mode 100644
index 0000000..45ae144
--- /dev/null
+++ b/internal/protodetect/protodetect.go
@@ -0,0 +1,90 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package protodetect provides function to identify the go_api_flag
+// file and message options specified in the files.
+package protodetect
+
+import (
+ "fmt"
+
+ "google.golang.org/protobuf/compiler/protogen"
+ "google.golang.org/protobuf/proto"
+
+ pb "google.golang.org/open2opaque/internal/apiflagdata"
+ descpb "google.golang.org/protobuf/types/descriptorpb"
+ gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
+ pluginpb "google.golang.org/protobuf/types/pluginpb"
+)
+
+func fromFeatureToOld(apiLevel gofeaturespb.GoFeatures_APILevel) pb.GoAPI {
+ switch apiLevel {
+ case gofeaturespb.GoFeatures_API_OPEN:
+ return pb.GoAPI_OPEN_V1
+
+ case gofeaturespb.GoFeatures_API_HYBRID:
+ return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID
+
+ case gofeaturespb.GoFeatures_API_OPAQUE:
+ return pb.GoAPI_OPAQUE_V0
+
+ default:
+ panic(fmt.Sprintf("unknown apilevel %v", apiLevel))
+ }
+}
+
+func apiLevelForDescriptor(fd *descpb.FileDescriptorProto) gofeaturespb.GoFeatures_APILevel {
+ return apiLevelForDescriptorOpts(protogen.Options{}, fd)
+}
+
+func apiLevelForDescriptorOpts(opts protogen.Options, fd *descpb.FileDescriptorProto) gofeaturespb.GoFeatures_APILevel {
+ fopts := fd.Options
+ if fopts == nil {
+ fopts = &descpb.FileOptions{}
+ fd.Options = fopts
+ }
+ fopts.GoPackage = proto.String("dummy/package")
+
+ // Determine the API level by querying protogen using a stub plugin request.
+ plugin, err := opts.New(&pluginpb.CodeGeneratorRequest{
+ ProtoFile: []*descpb.FileDescriptorProto{fd},
+ })
+ if err != nil {
+ panic(err)
+ }
+ if got, want := len(plugin.Files), 1; got != want {
+ panic(fmt.Sprintf("protogen returned %d plugin.Files entries, expected %d", got, want))
+ }
+ return plugin.Files[0].APILevel
+}
+
+// DefaultFileLevel returns the default go_api_flag option for given path.
+func DefaultFileLevel(path string) gofeaturespb.GoFeatures_APILevel {
+ if path == "testonly-opaque-default-dummy.proto" {
+ // Magic filename to test the edition 2024+ behavior (Opaque API by
+ // default) from setapi_test.go
+ return gofeaturespb.GoFeatures_API_OPAQUE
+ }
+
+ fd := &descpb.FileDescriptorProto{Name: proto.String(path)}
+ return apiLevelForDescriptor(fd)
+}
+
+// MapGoAPIFlag maps current and old values of the go API flag to current values.
+func MapGoAPIFlag(val string) (pb.GoAPI, error) {
+ // Old go_api_flag values are still used here in order to parse proto files from
+ // older /google_src/files/... paths.
+ // LINT.IfChange(go_api_flag_parsing)
+ switch val {
+ case "OPEN_V1":
+ return pb.GoAPI_OPEN_V1, nil
+ case "OPEN_TO_OPAQUE_HYBRID":
+ return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID, nil
+ case "OPAQUE_V0":
+ return pb.GoAPI_OPAQUE_V0, nil
+ default:
+ return pb.GoAPI_INVALID, fmt.Errorf("invalid value: %q", val)
+ }
+ // LINT.ThenChange()
+}
diff --git a/internal/protodetecttypes/protodetecttypes.go b/internal/protodetecttypes/protodetecttypes.go
new file mode 100644
index 0000000..f02a2d7
--- /dev/null
+++ b/internal/protodetecttypes/protodetecttypes.go
@@ -0,0 +1,65 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package protodetecttypes identifies static types (go/types, typically
+// obtained at build time) generated by protoc-gen-go. The protodetectreflect
+// package is the counterpart for dynamic types (reflect, obtained at runtime).
+package protodetecttypes
+
+import (
+ "go/types"
+ "reflect"
+ "strings"
+)
+
+// Type represents a go/types.Type obtained at build time.
+type Type struct{ T types.Type }
+
+// IsMessage reports whether t is a generated message.
+func (t Type) IsMessage() bool {
+ return t.MessageAPI() != Invalid
+}
+
+// MessageAPI determines the API of the generated message.
+func (t Type) MessageAPI() MessageAPI {
+ if t, ok := t.T.Underlying().(*types.Struct); ok && t.NumFields() > 0 {
+ return determineAPI(reflect.StructTag(t.Tag(0)))
+ }
+ return Invalid
+}
+
+// MessageAPI determines which API is used for the generated message type.
+type MessageAPI int
+
+const (
+ // Invalid indicates the specified type is not a proto message.
+ Invalid MessageAPI = iota
+
+ // OpenAPI indicates a message generated in the open struct API,
+ // where the message representation is a Go struct with exported fields.
+ OpenAPI
+
+ // HybridAPI indicates a message generated in the hybrid API,
+ // where the message has properties of both OpenAPI and OpaqueAPI.
+ HybridAPI
+
+ // OpaqueAPI indicates a message generated in the opaque API,
+ // where the message can only be interacted with through accessor methods.
+ OpaqueAPI
+)
+
+// DetermineAPI determines the message API from a magic struct tag that
+// protoc-gen-go emits for all messages.
+func determineAPI(tag reflect.StructTag) MessageAPI {
+ switch s := tag.Get("protogen"); {
+ case strings.HasPrefix(s, "open."):
+ return OpenAPI
+ case strings.HasPrefix(s, "hybrid."):
+ return HybridAPI
+ case strings.HasPrefix(s, "opaque."):
+ return OpaqueAPI
+ default:
+ return Invalid
+ }
+}
diff --git a/internal/protoparse/protoparse.go b/internal/protoparse/protoparse.go
new file mode 100644
index 0000000..892a0b9
--- /dev/null
+++ b/internal/protoparse/protoparse.go
@@ -0,0 +1,385 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package protoparse provides function to parse proto source files and identify the go_api_flag
+// file and message options specified in the files.
+package protoparse
+
+import (
+ "bytes"
+ "fmt"
+ "slices"
+ "strings"
+
+ "github.com/jhump/protoreflect/desc/protoparse"
+ "google.golang.org/open2opaque/internal/protodetect"
+ "google.golang.org/protobuf/proto"
+
+ pb "google.golang.org/open2opaque/internal/apiflagdata"
+ descpb "google.golang.org/protobuf/types/descriptorpb"
+ gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
+)
+
+// TextRange describes a location in a proto file. Please note that the column
+// indices are code-point indices, not byte indices.
+type TextRange struct {
+ BeginLine int
+ BeginCol int
+ EndLine int
+ EndCol int
+}
+
+// SpanToTextRange converts a proto2.SourceCodeInfo.Location.span to a
+// TextRange.
+func SpanToTextRange(span []int32) TextRange {
+ if len(span) < 3 && len(span) > 4 {
+ panic(fmt.Sprintf("input %v isn't a proto2.SourceCodeInfo.Location.span", span))
+ }
+ if len(span) == 3 {
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1209
+ return TextRange{
+ BeginLine: int(span[0]),
+ BeginCol: int(span[1]),
+ EndLine: int(span[0]),
+ EndCol: int(span[2]),
+ }
+ }
+
+ return TextRange{
+ BeginLine: int(span[0]),
+ BeginCol: int(span[1]),
+ EndLine: int(span[2]),
+ EndCol: int(span[3]),
+ }
+}
+
+// ToByteRange converts line and column information to a byte range.
+func (tr TextRange) ToByteRange(content []byte) (beginByte, endByte int, err error) {
+ if tr.EndLine < tr.BeginLine {
+ return -1, -1, fmt.Errorf("EndLine %d < BeginLine %d", tr.EndLine, tr.BeginLine)
+ }
+ if tr.EndLine == tr.BeginLine && tr.EndCol < tr.BeginCol {
+ return -1, -1, fmt.Errorf("EndCol %d < BeginCol %d in the same line", tr.EndCol, tr.BeginCol)
+ }
+ {
+ lines := bytes.Split(content, []byte{'\n'})[tr.BeginLine : tr.EndLine+1]
+ if bytes.Contains(bytes.Join(lines, []byte{'\n'}), []byte{'\t'}) {
+ // The parser deals with tabs in a complicated manner, see
+ // https://github.com/bufbuild/protocompile/blob/c91057b816eb7f827dfa83ff5288b74ead9d4fe5/ast/file_info.go#L362-L364.
+ // We currently don't support this.
+ return -1, -1, fmt.Errorf("line range contains tabs")
+ }
+ }
+ beginByte = -1
+ endByte = -1
+ from := 0
+ newlineIdx := 0
+ lineNumber := 0
+ for newlineIdx >= 0 {
+ newlineIdx := bytes.IndexByte(content[from:], '\n')
+ to := from + newlineIdx + 1
+ if newlineIdx == -1 {
+ to = len(content) - 1
+ }
+ lineRunes := bytes.Runes(content[from:to])
+ if lineNumber == tr.BeginLine {
+ if tr.BeginCol > len(lineRunes) {
+ return -1, -1, fmt.Errorf("BeginCol %d is out of range, line is %q", tr.BeginCol, string(lineRunes))
+ }
+ beginByte = from + len(string(lineRunes[:tr.BeginCol]))
+ }
+ if lineNumber == tr.EndLine {
+ if tr.EndCol > len(lineRunes) {
+ return -1, -1, fmt.Errorf("EndCol %d is out of range, line is %q", tr.EndCol, string(lineRunes))
+ }
+ endByte = from + len(string(lineRunes[:tr.EndCol]))
+ break
+ }
+
+ from = to
+ lineNumber++
+ }
+ if endByte == -1 {
+ return -1, -1, fmt.Errorf("EndLine %d is out of range, number lines is %d", tr, lineNumber)
+ }
+ return beginByte, endByte, nil
+}
+
+// APIInfo contains information about an explicit API flag definition.
+type APIInfo struct {
+ TextRange TextRange
+ HasLeadingComment bool
+ path []int32
+}
+
+// FileOpt contains the Go API level info for a file along with other proto
+// info.
+type FileOpt struct {
+ // File name containing relative path
+ File string
+ // Proto package name.
+ Package string
+ // Go API level. This can be an implicit value via default.
+ GoAPI gofeaturespb.GoFeatures_APILevel
+ // Whether go_api_flag option is explicitly set in proto file or not.
+ IsExplicit bool
+ // APIInfo is nil if IsExplicit is false.
+ APIInfo *APIInfo
+ // Options of messages defined at the file level. Nested messages are stored
+ // as their children.
+ MessageOpts []*MessageOpt
+ // SourceCodeInfo is set if parsed results includes it.
+ SourceCodeInfo *descpb.SourceCodeInfo
+ // Proto syntax: "proto2", "proto3", "editions", or "editions_go_api_flag".
+ // The latter is set for editions protos that use the old go_api_flag
+ // explicitly.
+ Syntax string
+}
+
+// MessageOpt contains the Go API level info for a message.
+type MessageOpt struct {
+ // Proto message name. Includes parent name if nested, e.g. A.B for message
+ // B that is defined in body of A.
+ Message string
+ // Go API level. This can be an implicit value via file option or in case of
+ // editions features via the parent message.
+ GoAPI gofeaturespb.GoFeatures_APILevel
+ // Whether go_api_flag option is explicitly set in proto message or not.
+ IsExplicit bool
+ // APIInfo is nil if IsExplicit is false.
+ APIInfo *APIInfo
+ // FileDescriptorProto.source_code_info.location.path of this message:
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1202
+ // Example: The 1st nested message of the 6th message in the file is in path
+ // [4, 5, 3, 0]; 4 is the field number of FileDescriptorProto.message_type, 5
+ // is the index for the 6th message, 3 is DescriptorProto.nested_type, 0 is
+ // the index for the first nested message.
+ LocPath []int32
+ // Options of the parent message. If this is e.g. the message B which is
+ // defined in the body of message A, then A is the parent. Parent is nil for
+ // messages defined at the file level, i.e. non-nested messages.
+ Parent *MessageOpt
+ // Options of the child messages. If this is e.g. message A and messages
+ // B and C are defined in the body of message A, then B and C are the
+ // children.
+ Children []*MessageOpt
+}
+
+// Parser parses proto source files for go_api_flag values.
+type Parser struct {
+ parser protoparse.Parser
+}
+
+// NewParser constructs a Parser with default file accessor.
+func NewParser() *Parser {
+ return &Parser{protoparse.Parser{
+ InterpretOptionsInUnlinkedFiles: true,
+ IncludeSourceCodeInfo: true,
+ }}
+}
+
+// NewParserWithAccessor constructs a Parser with a custom file accessor.
+func NewParserWithAccessor(acc protoparse.FileAccessor) *Parser {
+ return &Parser{protoparse.Parser{
+ InterpretOptionsInUnlinkedFiles: true,
+ IncludeSourceCodeInfo: true,
+ Accessor: acc,
+ }}
+}
+
+func fromOldToFeature(apiLevel pb.GoAPI) (gofeaturespb.GoFeatures_APILevel, error) {
+ switch apiLevel {
+ case pb.GoAPI_OPEN_V1:
+ return gofeaturespb.GoFeatures_API_OPEN, nil
+
+ case pb.GoAPI_OPEN_TO_OPAQUE_HYBRID:
+ return gofeaturespb.GoFeatures_API_HYBRID, nil
+
+ case pb.GoAPI_OPAQUE_V0:
+ return gofeaturespb.GoFeatures_API_OPAQUE, nil
+
+ default:
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("unknown apilevel %v", apiLevel)
+ }
+}
+
+func uninterpretedGoAPIFeature(opts []*descpb.UninterpretedOption) (gofeaturespb.GoFeatures_APILevel, int) {
+ for i, opt := range opts {
+ nameParts := opt.GetName()
+ if len(nameParts) != 3 ||
+ nameParts[0].GetNamePart() != "features" ||
+ nameParts[1].GetNamePart() != "pb.go" ||
+ nameParts[2].GetNamePart() != "api_level" {
+ continue
+ }
+ v := string(opt.GetIdentifierValue())
+ switch v {
+ case "API_OPEN":
+ return gofeaturespb.GoFeatures_API_OPEN, i
+ case "API_HYBRID":
+ return gofeaturespb.GoFeatures_API_HYBRID, i
+ case "API_OPAQUE":
+ return gofeaturespb.GoFeatures_API_OPAQUE, i
+ default:
+ panic(fmt.Sprintf("unknown features.(pb.go).api_level value %v", v))
+ }
+ }
+ return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, -1
+}
+
+func fileGoAPIEditions(desc *descpb.FileDescriptorProto) (gofeaturespb.GoFeatures_APILevel, bool, []int32, error) {
+ if proto.HasExtension(desc.GetOptions().GetFeatures(), gofeaturespb.E_Go) {
+ panic("unimplemented: Go extension features are fully parsed in file options")
+ }
+ api, idx := uninterpretedGoAPIFeature(desc.GetOptions().GetUninterpretedOption())
+ if api == gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
+ return protodetect.DefaultFileLevel(desc.GetName()), false, nil, nil
+ }
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L122
+ fileOptionsField = 8
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L553
+ uninterpretedOptionField = 999
+ )
+ return api, true, []int32{fileOptionsField, uninterpretedOptionField, int32(idx)}, nil
+}
+
+func traverseMsgOpts(opt *MessageOpt, f func(*MessageOpt)) {
+ f(opt)
+ for _, c := range opt.Children {
+ traverseMsgOpts(c, f)
+ }
+}
+
+// ParseFile reads the given proto source file name
+// and determines the API level. If skipMessages is set to
+// true, return value will have nil MessageOpts field.
+func (p *Parser) ParseFile(name string, skipMessages bool) (*FileOpt, error) {
+ descs, err := p.parser.ParseFilesButDoNotLink(name)
+ if err != nil {
+ return nil, fmt.Errorf("error reading file %s: %w", name, err)
+ }
+ desc := descs[0]
+
+ var fileAPI gofeaturespb.GoFeatures_APILevel
+ var explicit bool
+ var sciPath []int32
+
+ syntax := desc.GetSyntax()
+ fileAPI, explicit, sciPath, err = fileGoAPIEditions(desc)
+ if err != nil {
+ return nil, fmt.Errorf("fileGoAPIEditions: %v", err)
+ }
+
+ var mopts []*MessageOpt
+ if !skipMessages {
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L117
+ descriptorProtoField = 4
+ )
+ for i, m := range desc.GetMessageType() {
+ var mopt *MessageOpt
+ mopt = readMessagesEditions(m, fileAPI, "", []int32{descriptorProtoField, int32(i)})
+ mopts = append(mopts, mopt)
+ }
+ }
+ var info *APIInfo
+ var allAPIInfos []*APIInfo
+ if explicit {
+ info = &APIInfo{path: sciPath}
+ allAPIInfos = append(allAPIInfos, info)
+ }
+
+ for _, mLoop := range mopts {
+ traverseMsgOpts(mLoop, func(m *MessageOpt) {
+ allAPIInfos = append(allAPIInfos, m.APIInfo)
+ })
+ }
+ // The APIInfos only contain SourceCodeInfo paths so far. Now, fill in the
+ // line and column information by directly modifying the pointee text ranges
+ // in allTextRanges.
+ fillAPIInfos(allAPIInfos, desc.GetSourceCodeInfo())
+
+ return &FileOpt{
+ File: name,
+ Package: desc.GetPackage(),
+ GoAPI: fileAPI,
+ IsExplicit: explicit,
+ APIInfo: info,
+ MessageOpts: mopts,
+ SourceCodeInfo: desc.GetSourceCodeInfo(),
+ Syntax: syntax,
+ }, nil
+}
+
+func fillAPIInfos(infos []*APIInfo, info *descpb.SourceCodeInfo) {
+ m := make(map[string]*APIInfo)
+ for _, info := range infos {
+ if info != nil {
+ m[fmt.Sprint(info.path)] = info
+ }
+ }
+ for _, loc := range info.GetLocation() {
+ if info, ok := m[fmt.Sprint(loc.GetPath())]; ok {
+ info.TextRange = SpanToTextRange(loc.GetSpan())
+ leading := strings.TrimSpace(loc.GetLeadingComments())
+ switch {
+ default:
+ info.HasLeadingComment = leading != ""
+ }
+ }
+ }
+}
+
+func readMessagesEditions(m *descpb.DescriptorProto, parentAPI gofeaturespb.GoFeatures_APILevel, namePrefix string, msgPath []int32) *MessageOpt {
+ if m.GetOptions().GetMapEntry() {
+ // Map-entry messages are auto-generated and their Go API level cannot
+ // be adjusted in the proto file.
+ return nil
+ }
+ name := m.GetName()
+ if namePrefix != "" {
+ name = namePrefix + "." + m.GetName()
+ }
+
+ // If not set, default to parent value. This is the file API for a message
+ // at the file level or the API of the parent message for a nested message.
+ msgAPI := parentAPI
+ var info *APIInfo
+ var isSet bool
+ if api, idx := uninterpretedGoAPIFeature(m.GetOptions().GetUninterpretedOption()); api != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
+ msgAPI = api
+ isSet = true
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L160
+ messageOptionsField = 7
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L638
+ uninterpretedOptionField = 999
+ )
+ info = &APIInfo{path: append(slices.Clone(msgPath), messageOptionsField, uninterpretedOptionField, int32(idx))}
+ }
+
+ mopt := &MessageOpt{
+ Message: name,
+ GoAPI: msgAPI,
+ IsExplicit: isSet,
+ APIInfo: info,
+ LocPath: slices.Clone(msgPath),
+ }
+
+ for i, n := range m.GetNestedType() {
+ const (
+ // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L147
+ nestedDescriptorProtoField = 3
+ )
+ // Pass msgAPI as parent API: edition features are inherited by nested messages.
+ nopt := readMessagesEditions(n, msgAPI, name, append(slices.Clone(msgPath), nestedDescriptorProtoField, int32(i)))
+ if nopt == nil {
+ continue
+ }
+ mopt.Children = append(mopt.Children, nopt)
+ nopt.Parent = mopt
+ }
+ return mopt
+}
diff --git a/internal/protoparse/protoparse_test.go b/internal/protoparse/protoparse_test.go
new file mode 100644
index 0000000..c77a84c
--- /dev/null
+++ b/internal/protoparse/protoparse_test.go
@@ -0,0 +1,315 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package protoparse_test
+
+import (
+ "io"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "google.golang.org/open2opaque/internal/protodetect"
+ "google.golang.org/open2opaque/internal/protoparse"
+ gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
+)
+
+const filenameWithOpaqueDefault = "foo.proto"
+
+func TestTextRangeToByteRange(t *testing.T) {
+ testCases := []struct {
+ in string
+ textRange protoparse.TextRange
+ want string
+ }{
+ {
+ in: "0123456789",
+ textRange: protoparse.TextRange{
+ BeginLine: 0,
+ BeginCol: 2,
+ EndLine: 0,
+ EndCol: 6,
+ },
+ want: "2345",
+ },
+ {
+ in: `0Hello, 世界0
+1Hello, 世界1
+2Hello, 世界2
+3Hello, 世界3`,
+ textRange: protoparse.TextRange{
+ BeginLine: 1,
+ BeginCol: 8,
+ EndLine: 3,
+ EndCol: 6,
+ },
+ want: `世界1
+2Hello, 世界2
+3Hello`,
+ },
+ }
+ for _, tc := range testCases {
+ from, to, err := tc.textRange.ToByteRange([]byte(tc.in))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(tc.want, string(tc.in[from:to])); diff != "" {
+ t.Errorf("TextRange.ToByteRange: diff (-want +got):\n%v\n", diff)
+ }
+ }
+}
+
+func TestFileOpt(t *testing.T) {
+ testCases := []struct {
+ name string
+ protoIn string
+ wantOpt *protoparse.FileOpt
+ wantAPIStr string
+ wantHasLeadingComment bool
+ }{
+ {
+ name: "proto3_default_opaque",
+ protoIn: `
+syntax = "proto3";
+package pkg;
+message M{}
+`,
+ wantOpt: &protoparse.FileOpt{
+ File: filenameWithOpaqueDefault,
+ Package: "pkg",
+ GoAPI: protodetect.DefaultFileLevel("dummy.proto"),
+ IsExplicit: false,
+ Syntax: "proto3",
+ },
+ },
+
+ {
+ name: "edition_default_opaque",
+ protoIn: `
+edition = "2023";
+package pkg;
+message M{}
+`,
+ wantOpt: &protoparse.FileOpt{
+ File: filenameWithOpaqueDefault,
+ Package: "pkg",
+ GoAPI: protodetect.DefaultFileLevel("dummy.proto"),
+ IsExplicit: false,
+ Syntax: "editions",
+ },
+ },
+
+ {
+ name: "edition_explicit_hybrid",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message M{}
+`,
+ wantOpt: &protoparse.FileOpt{
+ File: filenameWithOpaqueDefault,
+ Package: "pkg",
+ GoAPI: gofeaturespb.GoFeatures_API_HYBRID,
+ IsExplicit: true,
+ Syntax: "editions",
+ },
+ wantAPIStr: "option features.(pb.go).api_level = API_HYBRID;",
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) {
+ return io.NopCloser(strings.NewReader(tc.protoIn)), nil
+ })
+ got, err := parser.ParseFile(filenameWithOpaqueDefault, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ingores := cmp.Options{
+ cmpopts.IgnoreFields(protoparse.FileOpt{}, "APIInfo"),
+ cmpopts.IgnoreFields(protoparse.FileOpt{}, "SourceCodeInfo"),
+ cmpopts.IgnoreFields(protoparse.FileOpt{}, "MessageOpts"),
+ }
+ if diff := cmp.Diff(tc.wantOpt, got, ingores...); diff != "" {
+ t.Errorf("parser.ParseFile(): diff (-want +got):\n%v\n", diff)
+ }
+
+ if !tc.wantOpt.IsExplicit {
+ // File doesn't explicitly define API flag, don't check content of
+ // APIInfo
+ return
+ }
+ if got.APIInfo == nil {
+ t.Fatal("API flag is set explicitly, but APIInfo is nil")
+ }
+
+ if got, want := got.APIInfo.HasLeadingComment, tc.wantHasLeadingComment; got != want {
+ t.Fatalf("got APIInfo.HasLeadingComment %v, want %v", got, want)
+ }
+
+ from, to, err := got.APIInfo.TextRange.ToByteRange([]byte(tc.protoIn))
+ if err != nil {
+ t.Fatalf("TextRange.ToByteRange: %v", err)
+ }
+ gotAPIStr := string([]byte(tc.protoIn)[from:to])
+ if diff := cmp.Diff(tc.wantAPIStr, gotAPIStr); diff != "" {
+ t.Errorf("API string: diff (-want +got):\n%v\n", diff)
+ }
+ })
+ }
+}
+
+func traverseMsg(opt *protoparse.MessageOpt, f func(*protoparse.MessageOpt)) {
+ f(opt)
+ for _, c := range opt.Children {
+ traverseMsg(c, f)
+ }
+}
+
+func TestMessageOpt(t *testing.T) {
+ type msgInfo struct {
+ Opt *protoparse.MessageOpt
+ APIString string
+ HasLeadingComment bool
+ }
+
+ testCases := []struct {
+ name string
+ protoIn string
+ want []msgInfo
+ }{
+
+ {
+ name: "edition_file_hybrid_message_one_msg_open",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message A{
+ option features.(pb.go).api_level = API_OPEN;
+ message A1{}
+}
+`,
+ want: []msgInfo{
+ {
+ Opt: &protoparse.MessageOpt{
+ Message: "A",
+ GoAPI: gofeaturespb.GoFeatures_API_OPEN,
+ IsExplicit: true,
+ },
+ APIString: `option features.(pb.go).api_level = API_OPEN;`,
+ },
+ {
+ Opt: &protoparse.MessageOpt{
+ Message: "A.A1",
+ // API level of parent message is inherited.
+ GoAPI: gofeaturespb.GoFeatures_API_OPEN,
+ IsExplicit: false,
+ },
+ },
+ },
+ },
+
+ {
+ name: "edition_file_hybrid_message_one_msg_open_with_leading_comment",
+ protoIn: `
+edition = "2023";
+package pkg;
+option features.(pb.go).api_level = API_HYBRID;
+message A{
+ // leading comment
+ option features.(pb.go).api_level = API_OPEN;
+ message A1{
+ option features.(pb.go).api_level = API_OPAQUE;
+ }
+}
+`,
+ // https://github.com/bufbuild/protocompile/blob/main/ast/file_info.go#L362-L364
+ want: []msgInfo{
+ {
+ Opt: &protoparse.MessageOpt{
+ Message: "A",
+ GoAPI: gofeaturespb.GoFeatures_API_OPEN,
+ IsExplicit: true,
+ },
+ APIString: `option features.(pb.go).api_level = API_OPEN;`,
+ HasLeadingComment: true,
+ },
+ {
+ Opt: &protoparse.MessageOpt{
+ Message: "A.A1",
+ GoAPI: gofeaturespb.GoFeatures_API_OPAQUE,
+ IsExplicit: true,
+ },
+ APIString: `option features.(pb.go).api_level = API_OPAQUE;`,
+ HasLeadingComment: false,
+ },
+ },
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) {
+ return io.NopCloser(strings.NewReader(tc.protoIn)), nil
+ })
+ gotFopt, err := parser.ParseFile(filenameWithOpaqueDefault, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Flatten the tree of message options and wrap into []msgInfo to allow
+ // easy comparison. Check tree structure below.
+ var gotInfo []msgInfo
+ for _, optLoop := range gotFopt.MessageOpts {
+ traverseMsg(optLoop, func(opt *protoparse.MessageOpt) {
+ info := msgInfo{Opt: opt}
+ if opt.IsExplicit {
+ if opt.APIInfo == nil {
+ t.Fatalf("API flag is set explicitly, but APIInfo of message %q is nil", opt.Message)
+ }
+ info.HasLeadingComment = opt.APIInfo.HasLeadingComment
+ from, to, err := opt.APIInfo.TextRange.ToByteRange([]byte(tc.protoIn))
+ if err != nil {
+ t.Fatalf("TextRange.ToByteRange: %v", err)
+ }
+ info.APIString = string([]byte(tc.protoIn)[from:to])
+ }
+ gotInfo = append(gotInfo, info)
+ })
+ }
+
+ ingores := cmp.Options{
+ cmpopts.IgnoreFields(protoparse.MessageOpt{}, "APIInfo"),
+ cmpopts.IgnoreFields(protoparse.MessageOpt{}, "LocPath"),
+ cmpopts.IgnoreFields(protoparse.MessageOpt{}, "Parent"),
+ cmpopts.IgnoreFields(protoparse.MessageOpt{}, "Children"),
+ }
+ if diff := cmp.Diff(tc.want, gotInfo, ingores...); diff != "" {
+ t.Errorf("diff (-want +got):\n%v\n", diff)
+ }
+
+ // Check the tree structure of the message options. If I'm called A.A1.A2,
+ // is my parent called A.A1?
+ for _, optOuter := range gotFopt.MessageOpts {
+ traverseMsg(optOuter, func(opt *protoparse.MessageOpt) {
+ parts := strings.Split(opt.Message, ".")
+ if len(parts) == 1 {
+ if opt.Parent != nil {
+ t.Errorf("parent pointer of top-level message %q isn't nil", opt.Message)
+ }
+ return
+ }
+ if opt.Parent == nil {
+ t.Errorf("parent pointer of nested message %q is nil", opt.Message)
+ }
+ if got, want := opt.Parent.Message, strings.Join(parts[:len(parts)-1], "."); got != want {
+ t.Errorf("got name of parent message %q, want %q", got, want)
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/open2opaque.go b/open2opaque.go
new file mode 100644
index 0000000..bbc9444
--- /dev/null
+++ b/open2opaque.go
@@ -0,0 +1,66 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The program open2opaque migrates Go code using Go Protobuf from the Open API
+// to the Opaque API.
+//
+// See https://go.dev/blog/protobuf-opaque for context.
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ _ "net/http/pprof"
+ "os"
+ "path"
+
+ "flag"
+ "github.com/google/subcommands"
+ "google.golang.org/open2opaque/internal/o2o/rewrite"
+ "google.golang.org/open2opaque/internal/o2o/setapi"
+ "google.golang.org/open2opaque/internal/o2o/version"
+)
+
+const groupOther = "working with this tool"
+
+func registerVersion(commander *subcommands.Commander) {
+ commander.Register(version.Command(), groupOther)
+}
+
+func main() {
+ ctx := context.Background()
+
+ commander := subcommands.NewCommander(flag.CommandLine, path.Base(os.Args[0]))
+
+ // Prepend general documentation before the regular help output.
+ defaultExplain := commander.Explain
+ commander.Explain = func(w io.Writer) {
+ fmt.Fprintf(w, "The open2opaque tool migrates Go packages from the open protobuf API to the opaque protobuf API.\n\n")
+ fmt.Fprintf(w, "See http://godoc/3/third_party/golang/google_golang_org/open2opaque/v/v0/open2opaque for documentation.\n\n")
+ defaultExplain(w)
+ }
+
+ // Comes last in the help output (alphabetically)
+ commander.Register(commander.HelpCommand(), groupOther)
+ commander.Register(commander.FlagsCommand(), groupOther)
+ registerVersion(commander)
+
+ // Comes first in the help output (alphabetically)
+ const groupRewrite = "automatically rewriting Go code"
+ commander.Register(rewrite.Command(), groupRewrite)
+
+ const groupFlag = "managing go_api_flag"
+ commander.Register(setapi.Command(), groupFlag)
+
+ flag.Usage = func() {
+ commander.HelpCommand().Execute(ctx, flag.CommandLine)
+ }
+
+ flag.Parse()
+
+ code := int(commander.Execute(ctx))
+ logFlush()
+ os.Exit(code)
+}
diff --git a/open2opaque_flush.go b/open2opaque_flush.go
new file mode 100644
index 0000000..ec482b6
--- /dev/null
+++ b/open2opaque_flush.go
@@ -0,0 +1,13 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ log "github.com/golang/glog"
+)
+
+func logFlush() {
+ log.Flush()
+}
diff --git a/regenerate.bash b/regenerate.bash
new file mode 100755
index 0000000..6eb19f3
--- /dev/null
+++ b/regenerate.bash
@@ -0,0 +1,5 @@
+#!/bin/bash
+# Copyright 2024 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+go generate
diff --git a/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto b/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto
new file mode 100644
index 0000000..945ddeb
--- /dev/null
+++ b/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto
@@ -0,0 +1,33 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test1;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = /*unicode comment comment saying Hello, 世界*/ API_OPEN;
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
diff --git a/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto b/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto
new file mode 100644
index 0000000..99250db
--- /dev/null
+++ b/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto
@@ -0,0 +1,34 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test2;
+
+import "google/protobuf/go_features.proto";
+
+message M1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+
+ message Nested1 {
+ option
+ /*multi-line*/
+ features.(pb.go)
+ .api_level = API_HYBRID;
+
+ message Nested2 {}
+ }
+
+ map<string, bool> map_field = 10;
+}
+
+message M2 {
+ message Nested1 {
+ option features.(pb.go).api_level = API_HYBRID;
+ }
+
+ message Nested2 {}
+
+ map<string, bool> map_field = 10;
+}
diff --git a/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto b/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto
new file mode 100644
index 0000000..6115f77
--- /dev/null
+++ b/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto
@@ -0,0 +1,26 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test3;
+
+import "google/protobuf/go_features.proto";
+
+// Leading comment exempts api_level from modifications.
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ // Exemption on message level.
+ option features.(pb.go).api_level = API_HYBRID;
+
+ message Nested1 {
+ // Exemption on nested-message level.
+ option features.(pb.go).api_level = API_OPAQUE;
+ }
+}
+
+message M2 {
+ option features.(pb.go).api_level = API_HYBRID;
+}
diff --git a/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto b/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto
new file mode 100644
index 0000000..912e726
--- /dev/null
+++ b/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto
@@ -0,0 +1,23 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test4;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_OPAQUE;
+
+message M1 {
+ option features.(pb.go).api_level = API_HYBRID;
+
+ string s = 1;
+}
+
+message M2 {
+ option features.(pb.go).api_level = API_OPEN;
+
+ string s = 1;
+}
diff --git a/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto b/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto
new file mode 100644
index 0000000..228d8c7
--- /dev/null
+++ b/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto
@@ -0,0 +1,25 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test5;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+ // Leading comment on message should not be misplaced
+ // when API flag is inserted.
+ int32 int_field = 1;
+}
+
+message M2 {
+ /**
+ * Leading block comment on message should not be misplaced
+ * when API flag is inserted.
+ */
+ int32 int_field = 1;
+}
diff --git a/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto b/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto
new file mode 100644
index 0000000..3c48c6b
--- /dev/null
+++ b/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto
@@ -0,0 +1,21 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Something in the first line
+
+edition = "2023";
+
+package net.proto2.go.open2opaque.testdata.flag_edition_test6;
+
+import "google/protobuf/go_features.proto";
+
+option features.(pb.go).api_level = API_OPAQUE;
+
+message M1 {
+ message Nested1 {
+ message SubNested1 {
+ option features.(pb.go).api_level = API_OPAQUE;
+ }
+ }
+}
diff --git a/testdata/rewriteme_go_proto/rewriteme.proto b/testdata/rewriteme_go_proto/rewriteme.proto
new file mode 100644
index 0000000..5dbc4df
--- /dev/null
+++ b/testdata/rewriteme_go_proto/rewriteme.proto
@@ -0,0 +1,14 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Type information for this package is provided by an offline job. Hence
+// changes here require the job to run before it can be used in tests.
+syntax = "proto2";
+
+package rewriteme;
+
+message M {
+ optional string s1 = 1;
+ optional string s2 = 2;
+}
diff --git a/testdata/rewriteme_test_want_red_go b/testdata/rewriteme_test_want_red_go
new file mode 100644
index 0000000..dd335d2
--- /dev/null
+++ b/testdata/rewriteme_test_want_red_go
@@ -0,0 +1,12 @@
+package rewriteme
+
+import (
+ "testing"
+
+ rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto"
+ "google.golang.org/protobuf/proto"
+)
+
+func Test(t *testing.T) {
+ _ = rpb.MMaker{S1: proto.String("")}.Make()
+}
diff --git a/testdata/rewriteme_want_green_go b/testdata/rewriteme_want_green_go
new file mode 100644
index 0000000..e7fee8a
--- /dev/null
+++ b/testdata/rewriteme_want_green_go
@@ -0,0 +1,20 @@
+// Package rewriteme is used for end to end test of the open2opaque program.
+package rewriteme
+
+import (
+ rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto"
+
+ "google.golang.org/protobuf/proto"
+)
+
+func green() *rpb.M {
+ return rpb.MMaker{S1: proto.String("")}.Make()
+}
+
+func yellow(m *rpb.M) {
+ m.S1, m.S2 = proto.String(""), proto.String("")
+}
+
+func red(m1, m2 *rpb.M) {
+ m1.S1 = m2.S1
+}
diff --git a/testdata/rewriteme_want_red_go b/testdata/rewriteme_want_red_go
new file mode 100644
index 0000000..445e7ab
--- /dev/null
+++ b/testdata/rewriteme_want_red_go
@@ -0,0 +1,21 @@
+// Package rewriteme is used for end to end test of the open2opaque program.
+package rewriteme
+
+import (
+ rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto"
+
+ "google.golang.org/protobuf/proto"
+)
+
+func green() *rpb.M {
+ return rpb.MMaker{S1: proto.String("")}.Make()
+}
+
+func yellow(m *rpb.M) {
+ m.SetS1("")
+ m.SetS2("")
+}
+
+func red(m1, m2 *rpb.M) {
+ m1.SetS1(m2.GetS1())
+}
diff --git a/testdata/rewriteme_want_yellow_go b/testdata/rewriteme_want_yellow_go
new file mode 100644
index 0000000..3ff20b5
--- /dev/null
+++ b/testdata/rewriteme_want_yellow_go
@@ -0,0 +1,21 @@
+// Package rewriteme is used for end to end test of the open2opaque program.
+package rewriteme
+
+import (
+ rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto"
+
+ "google.golang.org/protobuf/proto"
+)
+
+func green() *rpb.M {
+ return rpb.MMaker{S1: proto.String("")}.Make()
+}
+
+func yellow(m *rpb.M) {
+ m.SetS1("")
+ m.SetS2("")
+}
+
+func red(m1, m2 *rpb.M) {
+ m1.S1 = m2.S1
+}