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(&copy)
+`,
+			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(&copy)
+`,
+			},
+		},
+
+		{
+			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
+}