all: Release the Opaque API

For golang/protobuf#1657

Change-Id: I7b2b0c30506706015ce278e6054439c9ad9ef727
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/634815
TryBot-Bypass: Michael Stapelberg <stapelberg@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/builder_test/builder_test.go b/cmd/protoc-gen-go/builder_test/builder_test.go
new file mode 100644
index 0000000..029bfa4
--- /dev/null
+++ b/cmd/protoc-gen-go/builder_test/builder_test.go
@@ -0,0 +1,744 @@
+// 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.
+
+// Tests the opaque builders.
+package builder_test
+
+import (
+	"testing"
+
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+)
+
+var enableLazy = proto.UnmarshalOptions{}
+var disableLazy = proto.UnmarshalOptions{
+	NoLazyDecoding: true,
+}
+
+func roundtrip(t *testing.T, m proto.Message, unmarshalOpts proto.UnmarshalOptions) {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatalf("unable to Marshal proto: %v", err)
+	}
+	if err := unmarshalOpts.Unmarshal(b, m); err != nil {
+		t.Fatalf("roundtrip: unable to unmarshal proto: %v", err)
+	}
+}
+
+func TestOpaqueBuilderLazy(t *testing.T) {
+	testLazyOptionalBuilder(t, enableLazy)
+}
+
+func TestOpaqueBuilderEager(t *testing.T) {
+	testLazyOptionalBuilder(t, disableLazy)
+}
+
+// testLazyOptionalBuilder exercises all optional fields in the testall_opaque_optional3_go_proto builder
+func testLazyOptionalBuilder(t *testing.T, unmarshalOpts proto.UnmarshalOptions) {
+	// Create empty proto from builder
+	m := testopaquepb.TestAllTypes_builder{}.Build()
+
+	roundtrip(t, m, unmarshalOpts)
+
+	// Check lazy message field
+	m = testopaquepb.TestAllTypes_builder{
+		OptionalLazyNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1147),
+		}.Build(),
+		RepeatedNestedMessage: []*testopaquepb.TestAllTypes_NestedMessage{
+			testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(1247),
+			}.Build(),
+		},
+		OneofNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1347),
+		}.Build(),
+		MapStringNestedMessage: map[string]*testopaquepb.TestAllTypes_NestedMessage{
+			"a": testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(5),
+			}.Build(),
+		},
+	}.Build()
+
+	roundtrip(t, m, unmarshalOpts)
+
+	if got, want := m.HasOptionalLazyNestedMessage(), true; got != want {
+		t.Errorf("Builder for field NestedMessage did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalLazyNestedMessage().GetA(), int32(1147); got != want {
+		t.Errorf("Builder for field NestedMessage did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := len(m.GetRepeatedNestedMessage()), 1; got != want {
+		t.Errorf("Builder for field RepeatedNestedMessage did not set a field of expected length, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedNestedMessage()[0].GetA(), int32(1247); got != want {
+		t.Errorf("Builder for field RepetedNestedMessage did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOneofNestedMessage(), true; got != want {
+		t.Errorf("Builder for field OneofNestedMessage did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOneofNestedMessage().GetA(), int32(1347); got != want {
+		t.Errorf("Builder for field OneofNestedMessage did not work, got %v, wanted %v", got, want)
+	}
+	// Check map field
+	{
+		if got, want := len(m.GetMapStringNestedMessage()), 1; got != want {
+			t.Errorf("Builder for field MapStringNestedMessage did not work, got len %v, wanted len %v", got, want)
+		}
+		if got, want := m.GetMapStringNestedMessage()["a"].GetA(), int32(5); got != want {
+			t.Errorf("Builder for field MapStringNestedMessage did not work, got %v, wanted %v", got, want)
+		}
+	}
+}
+
+// TestHybridOptionalBuilder exercises all optional fields in the testall_opaque_optional3_go_proto builder
+func TestHybridOptionalBuilder(t *testing.T) {
+	// Create empty proto from builder
+	m := testhybridpb.TestAllTypes_builder{}.Build()
+
+	// Check that no optional fields are present
+	// Check presence of each field
+	if got, want := m.HasOptionalInt32(), false; got != want {
+		t.Errorf("Builder for field OptionalInt32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalInt64(), false; got != want {
+		t.Errorf("Builder for field OptionalInt64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalUint32(), false; got != want {
+		t.Errorf("Builder for field OptionalUint32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalUint64(), false; got != want {
+		t.Errorf("Builder for field OptionalUint64 did not set presence, got %v, wanted %v", got, want)
+	}
+
+	if got, want := m.HasOptionalSint32(), false; got != want {
+		t.Errorf("Builder for field OptionalSint32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSint64(), false; got != want {
+		t.Errorf("Builder for field OptionalSint64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFixed32(), false; got != want {
+		t.Errorf("Builder for field OptionalFixed32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFixed64(), false; got != want {
+		t.Errorf("Builder for field OptionalFixed64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSfixed32(), false; got != want {
+		t.Errorf("Builder for field OptionalSfixed32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSfixed64(), false; got != want {
+		t.Errorf("Builder for field OptionalSfixed64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFloat(), false; got != want {
+		t.Errorf("Builder for field OptionalFloat did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalDouble(), false; got != want {
+		t.Errorf("Builder for field OptionalDouble did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalBool(), false; got != want {
+		t.Errorf("Builder for field OptionalBool did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalString(), false; got != want {
+		t.Errorf("Builder for field OptionalString did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalBytes(), false; got != want {
+		t.Errorf("Builder for field OptionalBytes did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalNestedEnum(), false; got != want {
+		t.Errorf("Builder for field OptionalNestedEnum did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalNestedMessage(), false; got != want {
+		t.Errorf("Builder for field OptionalNestedMessage did not set presence, got %v, wanted %v", got, want)
+	}
+
+	// Create builder with every optional field filled in
+	b := testhybridpb.TestAllTypes_builder{
+		// Scalar fields (including bytes)
+		OptionalInt32:      proto.Int32(3),
+		OptionalInt64:      proto.Int64(64),
+		OptionalUint32:     proto.Uint32(32),
+		OptionalUint64:     proto.Uint64(4711),
+		OptionalSint32:     proto.Int32(-23),
+		OptionalSint64:     proto.Int64(-123132),
+		OptionalFixed32:    proto.Uint32(6798421),
+		OptionalFixed64:    proto.Uint64(876555776),
+		OptionalSfixed32:   proto.Int32(-909038),
+		OptionalSfixed64:   proto.Int64(-63728193629),
+		OptionalFloat:      proto.Float32(781.0),
+		OptionalDouble:     proto.Float64(-3456.3),
+		OptionalBool:       proto.Bool(true),
+		OptionalString:     proto.String("hello"),
+		OptionalBytes:      []byte("goodbye"),
+		OptionalNestedEnum: testhybridpb.TestAllTypes_FOO.Enum(),
+		OptionalNestedMessage: testhybridpb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1147),
+		}.Build(),
+	}
+
+	m = b.Build()
+
+	// Check presence of each optional field
+	if got, want := m.HasOptionalInt32(), true; got != want {
+		t.Errorf("Builder for field OptionalInt32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalInt64(), true; got != want {
+		t.Errorf("Builder for field OptionalInt64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalUint32(), true; got != want {
+		t.Errorf("Builder for field OptionalUint32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalUint64(), true; got != want {
+		t.Errorf("Builder for field OptionalUint64 did not set presence, got %v, wanted %v", got, want)
+	}
+
+	if got, want := m.HasOptionalSint32(), true; got != want {
+		t.Errorf("Builder for field OptionalSint32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSint64(), true; got != want {
+		t.Errorf("Builder for field OptionalSint64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFixed32(), true; got != want {
+		t.Errorf("Builder for field OptionalFixed32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFixed64(), true; got != want {
+		t.Errorf("Builder for field OptionalFixed64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSfixed32(), true; got != want {
+		t.Errorf("Builder for field OptionalSfixed32 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalSfixed64(), true; got != want {
+		t.Errorf("Builder for field OptionalSfixed64 did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalFloat(), true; got != want {
+		t.Errorf("Builder for field OptionalFloat did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalDouble(), true; got != want {
+		t.Errorf("Builder for field OptionalDouble did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalBool(), true; got != want {
+		t.Errorf("Builder for field OptionalBool did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalString(), true; got != want {
+		t.Errorf("Builder for field OptionalString did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalBytes(), true; got != want {
+		t.Errorf("Builder for field OptionalBytes did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalNestedEnum(), true; got != want {
+		t.Errorf("Builder for field OptionalNestedEnum did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalNestedMessage(), true; got != want {
+		t.Errorf("Builder for field OptionalNestedMessage did not set presence, got %v, wanted %v", got, want)
+	}
+
+	// Check each optional field against the corresponding field in the builder
+	if got, want := m.GetOptionalInt32(), *b.OptionalInt32; got != want {
+		t.Errorf("Builder for field OptionalInt32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalInt64(), *b.OptionalInt64; got != want {
+		t.Errorf("Builder for field OptionalInt64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalUint32(), *b.OptionalUint32; got != want {
+		t.Errorf("Builder for field OptionalUint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalUint64(), *b.OptionalUint64; got != want {
+		t.Errorf("Builder for field OptionalUint64 did not work, got %v, wanted %v", got, want)
+	}
+
+	if got, want := m.GetOptionalSint32(), *b.OptionalSint32; got != want {
+		t.Errorf("Builder for field OptionalSint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalSint64(), *b.OptionalSint64; got != want {
+		t.Errorf("Builder for field OptionalSint64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalFixed32(), *b.OptionalFixed32; got != want {
+		t.Errorf("Builder for field OptionalFixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalFixed64(), *b.OptionalFixed64; got != want {
+		t.Errorf("Builder for field OptionalFixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalSfixed32(), *b.OptionalSfixed32; got != want {
+		t.Errorf("Builder for field OptionalSfixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalSfixed64(), *b.OptionalSfixed64; got != want {
+		t.Errorf("Builder for field OptionalSfixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalFloat(), *b.OptionalFloat; got != want {
+		t.Errorf("Builder for field OptionalFloat did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalDouble(), *b.OptionalDouble; got != want {
+		t.Errorf("Builder for field OptionalDouble did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalBool(), *b.OptionalBool; got != want {
+		t.Errorf("Builder for field OptionalBool did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalString(), *b.OptionalString; got != want {
+		t.Errorf("Builder for field OptionalString did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalBytes(), b.OptionalBytes; string(got) != string(want) {
+		t.Errorf("Builder for field OptionalBytes did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalNestedEnum(), *b.OptionalNestedEnum; got != want {
+		t.Errorf("Builder for field OptionalNestedEnum did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalNestedMessage().GetA(), int32(1147); got != want {
+		t.Errorf("Builder for field OptionalNestedMessage did not work, got %v, wanted %v", got, want)
+	}
+
+}
+
+// TestOpaqueBuilder exercises all non-oneof fields in the testall_opaque3_go_proto builder
+func TestOpaqueBuilder(t *testing.T) {
+	// Create builder with every possible field filled in
+	b := testopaquepb.TestAllTypes_builder{
+		// Scalar fields (including bytes)
+		SingularInt32:      3,
+		SingularInt64:      64,
+		SingularUint32:     32,
+		SingularUint64:     4711,
+		SingularSint32:     -23,
+		SingularSint64:     -123132,
+		SingularFixed32:    6798421,
+		SingularFixed64:    876555776,
+		SingularSfixed32:   -909038,
+		SingularSfixed64:   -63728193629,
+		SingularFloat:      781.0,
+		SingularDouble:     -3456.3,
+		SingularBool:       true,
+		SingularString:     "hello",
+		SingularBytes:      []byte("goodbye"),
+		OptionalNestedEnum: testopaquepb.TestAllTypes_FOO.Enum(),
+		OptionalNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1147),
+		}.Build(),
+		RepeatedInt32:      []int32{4},
+		RepeatedInt64:      []int64{65},
+		RepeatedUint32:     []uint32{33},
+		RepeatedUint64:     []uint64{4712},
+		RepeatedSint32:     []int32{-24},
+		RepeatedSint64:     []int64{-123133},
+		RepeatedFixed32:    []uint32{6798422},
+		RepeatedFixed64:    []uint64{876555777},
+		RepeatedSfixed32:   []int32{-909039},
+		RepeatedSfixed64:   []int64{-63728193630},
+		RepeatedFloat:      []float32{782.0},
+		RepeatedDouble:     []float64{-3457.3},
+		RepeatedBool:       []bool{false},
+		RepeatedString:     []string{"hello!"},
+		RepeatedBytes:      [][]byte{[]byte("goodbye!")},
+		RepeatedNestedEnum: []testopaquepb.TestAllTypes_NestedEnum{testopaquepb.TestAllTypes_BAZ},
+		RepeatedNestedMessage: []*testopaquepb.TestAllTypes_NestedMessage{testopaquepb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1148),
+		}.Build()},
+		MapInt32Int32: map[int32]int32{
+			89: 87,
+			87: 89,
+		},
+		MapInt64Int64: map[int64]int64{
+			345:  678,
+			2121: 5432,
+		},
+		MapUint32Uint32: map[uint32]uint32{
+			765476: 87658,
+			4324:   6543,
+		},
+		MapUint64Uint64: map[uint64]uint64{
+			2324:    543534,
+			7657654: 675,
+		},
+		MapSint32Sint32: map[int32]int32{
+			-45243: -543353,
+			-54343: -33,
+		},
+		MapSint64Sint64: map[int64]int64{
+			-6754389: 34,
+			467382:   -676743,
+		},
+		MapFixed32Fixed32: map[uint32]uint32{
+			43432:   4444,
+			5555555: 666666,
+		},
+		MapFixed64Fixed64: map[uint64]uint64{
+			777777: 888888,
+			999999: 111111,
+		},
+		MapSfixed32Sfixed32: map[int32]int32{
+			-778989: -543,
+			-9999:   98765,
+		},
+		MapSfixed64Sfixed64: map[int64]int64{
+			65486723:  89,
+			-76843592: -33,
+		},
+		MapInt32Float: map[int32]float32{
+			543433:  7.5,
+			3434333: 3.14,
+		},
+		MapInt32Double: map[int32]float64{
+			876876: 34.34,
+			987650: 35.35,
+		},
+		MapBoolBool: map[bool]bool{
+			true:  true,
+			false: true,
+		},
+		MapStringString: map[string]string{
+			"hello?": "goodbye?",
+			"hi":     "bye",
+		},
+		MapStringBytes: map[string][]byte{
+			"hi?":  []byte("bye!"),
+			"bye?": []byte("hi!"),
+		},
+		MapStringNestedMessage: map[string]*testopaquepb.TestAllTypes_NestedMessage{
+			"nest": testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(99),
+			}.Build(),
+			"mess": testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(100),
+			}.Build(),
+		},
+		MapStringNestedEnum: map[string]testopaquepb.TestAllTypes_NestedEnum{
+			"bar": testopaquepb.TestAllTypes_BAR,
+			"baz": testopaquepb.TestAllTypes_BAZ,
+		},
+		OneofUint32: proto.Uint32(77665544),
+	}
+	m := b.Build()
+
+	// Check each field against the corresponding field in the builder
+	if got, want := m.GetSingularInt32(), b.SingularInt32; got != want {
+		t.Errorf("Builder for field FInt32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularInt64(), b.SingularInt64; got != want {
+		t.Errorf("Builder for field FInt64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularUint32(), b.SingularUint32; got != want {
+		t.Errorf("Builder for field FUint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularUint64(), b.SingularUint64; got != want {
+		t.Errorf("Builder for field FUint64 did not work, got %v, wanted %v", got, want)
+	}
+
+	if got, want := m.GetSingularSint32(), b.SingularSint32; got != want {
+		t.Errorf("Builder for field FSint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularSint64(), b.SingularSint64; got != want {
+		t.Errorf("Builder for field FSint64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularFixed32(), b.SingularFixed32; got != want {
+		t.Errorf("Builder for field FFixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularFixed64(), b.SingularFixed64; got != want {
+		t.Errorf("Builder for field FFixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularSfixed32(), b.SingularSfixed32; got != want {
+		t.Errorf("Builder for field FSfixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularSfixed64(), b.SingularSfixed64; got != want {
+		t.Errorf("Builder for field FSfixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularFloat(), b.SingularFloat; got != want {
+		t.Errorf("Builder for field FFloat did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularDouble(), b.SingularDouble; got != want {
+		t.Errorf("Builder for field FDouble did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularBool(), b.SingularBool; got != want {
+		t.Errorf("Builder for field FBool did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularString(), b.SingularString; got != want {
+		t.Errorf("Builder for field FString did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetSingularBytes(), b.SingularBytes; string(got) != string(want) {
+		t.Errorf("Builder for field FBytes did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalNestedEnum(), *b.OptionalNestedEnum; got != want {
+		t.Errorf("Builder for field FNestedEnum did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.HasOptionalNestedMessage(), true; got != want {
+		t.Errorf("Builder for field FNestedMessage did not set presence, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetOptionalNestedMessage().GetA(), int32(1147); got != want {
+		t.Errorf("Builder for field FNestedMessage did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedInt32()[0], b.RepeatedInt32[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedInt32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedInt64()[0], b.RepeatedInt64[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedInt64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedUint32()[0], b.RepeatedUint32[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedUint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedUint64()[0], b.RepeatedUint64[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedUint64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedSint32()[0], b.RepeatedSint32[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedSint32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedSint64()[0], b.RepeatedSint64[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedSint64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedFixed32()[0], b.RepeatedFixed32[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedFixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedFixed64()[0], b.RepeatedFixed64[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedFixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedSfixed32()[0], b.RepeatedSfixed32[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedSfixed32 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedSfixed64()[0], b.RepeatedSfixed64[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedSfixed64 did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedFloat()[0], b.RepeatedFloat[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedFloat did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedDouble()[0], b.RepeatedDouble[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedDouble did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedBool()[0], b.RepeatedBool[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedBool did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedString()[0], b.RepeatedString[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedString did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedBytes()[0], b.RepeatedBytes[0]; string(got) != string(want) {
+		t.Errorf("Builder for repeated field RepeatedBytes did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedNestedEnum()[0], b.RepeatedNestedEnum[0]; got != want {
+		t.Errorf("Builder for repeated field RepeatedNestedEnum did not work, got %v, wanted %v", got, want)
+	}
+	if got, want := m.GetRepeatedNestedMessage()[0].GetA(), int32(1148); got != want {
+		t.Errorf("Builder for repeated field RepeatedNestedMessage did not work, got %v, wanted %v", got, want)
+	}
+
+	for key, want := range b.MapInt32Int32 {
+		if got := m.GetMapInt32Int32()[key]; got != want {
+			t.Errorf("Builder for map field MapInt32Int32[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+
+	for key, want := range b.MapInt64Int64 {
+		if got := m.GetMapInt64Int64()[key]; got != want {
+			t.Errorf("Builder for map field MapInt64Int64[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapUint32Uint32 {
+		if got := m.GetMapUint32Uint32()[key]; got != want {
+			t.Errorf("Builder for map field MapUint32Uint32[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapUint64Uint64 {
+		if got := m.GetMapUint64Uint64()[key]; got != want {
+			t.Errorf("Builder for map field MapUint64Uint64[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapSint32Sint32 {
+		if got := m.GetMapSint32Sint32()[key]; got != want {
+			t.Errorf("Builder for map field MapSint32Sint32[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapSint64Sint64 {
+		if got := m.GetMapSint64Sint64()[key]; got != want {
+			t.Errorf("Builder for map field MapSint64Sint64[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapFixed32Fixed32 {
+		if got := m.GetMapFixed32Fixed32()[key]; got != want {
+			t.Errorf("Builder for map field MapFixed32Fixed32[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapFixed64Fixed64 {
+		if got := m.GetMapFixed64Fixed64()[key]; got != want {
+			t.Errorf("Builder for map field MapFixed64Fixed64[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapSfixed32Sfixed32 {
+		if got := m.GetMapSfixed32Sfixed32()[key]; got != want {
+			t.Errorf("Builder for map field MapSfixed32Sfixed32[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapSfixed64Sfixed64 {
+		if got := m.GetMapSfixed64Sfixed64()[key]; got != want {
+			t.Errorf("Builder for map field MapSfixed64Sfixed64[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapInt32Float {
+		if got := m.GetMapInt32Float()[key]; got != want {
+			t.Errorf("Builder for map field MapInt32Float[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapInt32Double {
+		if got := m.GetMapInt32Double()[key]; got != want {
+			t.Errorf("Builder for map field MapInt32Double[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapBoolBool {
+		if got := m.GetMapBoolBool()[key]; got != want {
+			t.Errorf("Builder for map field MapBoolBool[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapStringString {
+		if got := m.GetMapStringString()[key]; got != want {
+			t.Errorf("Builder for map field MapStringString[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapStringBytes {
+		if got := m.GetMapStringBytes()[key]; string(got) != string(want) {
+			t.Errorf("Builder for map field MapStringBytes[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapStringNestedMessage {
+		if got := m.GetMapStringNestedMessage()[key]; got.GetA() != want.GetA() {
+			t.Errorf("Builder for map field MapStringNestedMessage[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	for key, want := range b.MapStringNestedEnum {
+		if got := m.GetMapStringNestedEnum()[key]; got != want {
+			t.Errorf("Builder for map field MapStringNestedEnum[%v] did not work, got %v, wanted %v", key, got, want)
+		}
+	}
+	if got, want := m.GetOneofUint32(), *b.OneofUint32; got != want {
+		t.Errorf("Builder for field OneofUint32 did not work, got %v, wanted %v", got, want)
+	}
+}
+
+func TestOpaqueBuilderOneofsLazy(t *testing.T) {
+	testOpaqueBuilderOneofs(t, enableLazy)
+}
+
+func TestOpaqueBuilderOneofsEager(t *testing.T) {
+	testOpaqueBuilderOneofs(t, disableLazy)
+}
+
+// TestOpaqueBuilderOneofs test each oneof option in the builder separately
+func testOpaqueBuilderOneofs(t *testing.T, unmarshalOpts proto.UnmarshalOptions) {
+	for _, task := range []struct {
+		set   func() (any, int, *testopaquepb.TestAllTypes)
+		check func(any, *testopaquepb.TestAllTypes) (bool, any)
+	}{
+		{
+			// uint32
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := uint32(6754)
+				return val, int(testopaquepb.TestAllTypes_OneofUint32_case), testopaquepb.TestAllTypes_builder{OneofUint32: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(uint32)
+				got := m.GetOneofUint32()
+				return want == got, got
+			},
+		},
+		{
+			// message
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := testopaquepb.TestAllTypes_NestedMessage_builder{A: proto.Int32(5432678)}.Build()
+				return val, int(testopaquepb.TestAllTypes_OneofNestedMessage_case), testopaquepb.TestAllTypes_builder{OneofNestedMessage: val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(*testopaquepb.TestAllTypes_NestedMessage)
+				got := m.GetOneofNestedMessage()
+				return want.GetA() == got.GetA(), got
+			},
+		},
+		{
+			// string
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := "random"
+				return val, int(testopaquepb.TestAllTypes_OneofString_case), testopaquepb.TestAllTypes_builder{OneofString: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(string)
+				got := m.GetOneofString()
+				return want == got, got
+			},
+		},
+		{
+			// bytes
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := []byte("randombytes")
+				return val, int(testopaquepb.TestAllTypes_OneofBytes_case), testopaquepb.TestAllTypes_builder{OneofBytes: val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.([]byte)
+				got := m.GetOneofBytes()
+				return string(want) == string(got), got
+			},
+		},
+		{
+			// uint64
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := uint64(65934287653)
+				return val, int(testopaquepb.TestAllTypes_OneofUint64_case), testopaquepb.TestAllTypes_builder{OneofUint64: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(uint64)
+				got := m.GetOneofUint64()
+				return want == got, got
+			},
+		},
+		{
+			// bool
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := true
+				return val, int(testopaquepb.TestAllTypes_OneofBool_case), testopaquepb.TestAllTypes_builder{OneofBool: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(bool)
+				got := m.GetOneofBool()
+				return want == got, got
+			},
+		},
+		{
+			// float
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := float32(-54.45)
+				return val, int(testopaquepb.TestAllTypes_OneofFloat_case), testopaquepb.TestAllTypes_builder{OneofFloat: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(float32)
+				got := m.GetOneofFloat()
+				return want == got, got
+			},
+		},
+		{
+			// double
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := float64(-45.54)
+				return val, int(testopaquepb.TestAllTypes_OneofDouble_case), testopaquepb.TestAllTypes_builder{OneofDouble: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(float64)
+				got := m.GetOneofDouble()
+				return want == got, got
+			},
+		},
+		{
+			// enum
+			set: func() (any, int, *testopaquepb.TestAllTypes) {
+				val := testopaquepb.TestAllTypes_BAR
+				return val, int(testopaquepb.TestAllTypes_OneofEnum_case), testopaquepb.TestAllTypes_builder{OneofEnum: &val}.Build()
+			},
+			check: func(x any, m *testopaquepb.TestAllTypes) (bool, any) {
+				want := x.(testopaquepb.TestAllTypes_NestedEnum)
+				got := m.GetOneofEnum()
+				return want == got, got
+			},
+		},
+	} {
+		want, cas, m := task.set()
+		gotCase := int(m.WhichOneofField())
+		if gotCase != cas {
+			t.Errorf("Builder did not make which function return correct value, got %v, wanted %v for type %T", gotCase, cas, want)
+		}
+		ok, got := task.check(want, m)
+		if !ok {
+			t.Errorf("Builder did not set oneof field correctly, got %v, wanted %v for type %T", got, want, want)
+		}
+	}
+}
diff --git a/cmd/protoc-gen-go/descriptor_test/descriptor_test.go b/cmd/protoc-gen-go/descriptor_test/descriptor_test.go
new file mode 100644
index 0000000..70c1bb4
--- /dev/null
+++ b/cmd/protoc-gen-go/descriptor_test/descriptor_test.go
@@ -0,0 +1,43 @@
+// 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 descriptor_test
+
+import (
+	"testing"
+
+	testopenpb "google.golang.org/protobuf/internal/testprotos/test"
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+func TestFileModeEnum(t *testing.T) {
+	var e any = testopenpb.ForeignEnum_FOREIGN_FOO
+	if _, ok := e.(interface{ EnumDescriptor() ([]byte, []int) }); !ok {
+		t.Errorf("Open V1 proto did not have deprecated method EnumDescriptor")
+	}
+	var oe any = testopaquepb.ForeignEnum_FOREIGN_FOO
+	if _, ok := oe.(interface{ EnumDescriptor() ([]byte, []int) }); ok {
+		t.Errorf("Opaque V0 proto did have deprecated method EnumDescriptor")
+	}
+	var he any = testhybridpb.ForeignEnum_FOREIGN_FOO
+	if _, ok := he.(interface{ EnumDescriptor() ([]byte, []int) }); ok {
+		t.Errorf("Hybrid proto did have deprecated method EnumDescriptor")
+	}
+}
+
+func TestFileModeMessage(t *testing.T) {
+	var p any = &testopenpb.TestAllTypes{}
+	if _, ok := p.(interface{ Descriptor() ([]byte, []int) }); !ok {
+		t.Errorf("Open V1 proto did not have deprecated method Descriptor")
+	}
+	var op any = &testopaquepb.TestAllTypes{}
+	if _, ok := op.(interface{ Descriptor() ([]byte, []int) }); ok {
+		t.Errorf("Opaque V0 mode proto unexpectedly has deprecated Descriptor() method")
+	}
+	var hp any = &testhybridpb.TestAllTypes{}
+	if _, ok := hp.(interface{ EnumDescriptor() ([]byte, []int) }); ok {
+		t.Errorf("Hybrid proto did have deprecated method EnumDescriptor")
+	}
+}
diff --git a/cmd/protoc-gen-go/internal_gengo/init.go b/cmd/protoc-gen-go/internal_gengo/init.go
index 369df13..62de8bb 100644
--- a/cmd/protoc-gen-go/internal_gengo/init.go
+++ b/cmd/protoc-gen-go/internal_gengo/init.go
@@ -114,6 +114,7 @@
 	e := &enumInfo{Enum: enum}
 	e.genJSONMethod = true
 	e.genRawDescMethod = true
+	opaqueNewEnumInfoHook(f, e)
 	return e
 }
 
@@ -123,8 +124,9 @@
 	genRawDescMethod  bool
 	genExtRangeMethod bool
 
-	isTracked bool
-	hasWeak   bool
+	isTracked   bool
+	noInterface bool
+	hasWeak     bool
 }
 
 func newMessageInfo(f *fileInfo, message *protogen.Message) *messageInfo {
@@ -135,6 +137,7 @@
 	for _, field := range m.Fields {
 		m.hasWeak = m.hasWeak || field.Desc.IsWeak()
 	}
+	opaqueNewMessageInfoHook(f, m)
 	return m
 }
 
diff --git a/cmd/protoc-gen-go/internal_gengo/init_opaque.go b/cmd/protoc-gen-go/internal_gengo/init_opaque.go
new file mode 100644
index 0000000..221176a
--- /dev/null
+++ b/cmd/protoc-gen-go/internal_gengo/init_opaque.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 internal_gengo
+
+import "google.golang.org/protobuf/types/gofeaturespb"
+
+func (m *messageInfo) isOpen() bool {
+	return m.Message.APILevel == gofeaturespb.GoFeatures_API_OPEN
+}
+
+func (m *messageInfo) isHybrid() bool {
+	return m.Message.APILevel == gofeaturespb.GoFeatures_API_HYBRID
+}
+
+func (m *messageInfo) isOpaque() bool {
+	return m.Message.APILevel == gofeaturespb.GoFeatures_API_OPAQUE
+}
+
+func opaqueNewEnumInfoHook(f *fileInfo, e *enumInfo) {
+	if f.File.APILevel != gofeaturespb.GoFeatures_API_OPEN {
+		e.genJSONMethod = false
+		e.genRawDescMethod = false
+	}
+}
+
+func opaqueNewMessageInfoHook(f *fileInfo, m *messageInfo) {
+	if !m.isOpen() {
+		m.genRawDescMethod = false
+		m.genExtRangeMethod = false
+	}
+}
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index a4c4595..e493308 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -367,6 +367,9 @@
 	if m.Desc.IsMapEntry() {
 		return
 	}
+	if opaqueGenMessageHook(g, f, m) {
+		return
+	}
 
 	// Message type declaration.
 	g.AnnotateSymbol(m.GoIdent.GoName, protogen.Annotation{Location: m.Location})
@@ -657,7 +660,7 @@
 			continue
 		}
 
-		genNoInterfacePragma(g, m.isTracked)
+		genNoInterfacePragma(g, m.noInterface)
 
 		g.AnnotateSymbol(m.GoIdent.GoName+".Set"+field.GoName, protogen.Annotation{
 			Location: field.Location,
diff --git a/cmd/protoc-gen-go/internal_gengo/opaque.go b/cmd/protoc-gen-go/internal_gengo/opaque.go
new file mode 100644
index 0000000..dafa095
--- /dev/null
+++ b/cmd/protoc-gen-go/internal_gengo/opaque.go
@@ -0,0 +1,1306 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal_gengo
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/internal/genid"
+	"google.golang.org/protobuf/reflect/protoreflect"
+
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+func opaqueGenMessageHook(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) bool {
+	opaqueGenMessage(g, f, message)
+	return true
+}
+
+func opaqueGenMessage(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
+	// Message type declaration.
+	g.AnnotateSymbol(message.GoIdent.GoName, protogen.Annotation{Location: message.Location})
+	leadingComments := appendDeprecationSuffix(message.Comments.Leading,
+		message.Desc.ParentFile(),
+		message.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
+	g.P(leadingComments,
+		"type ", message.GoIdent, " struct {")
+
+	sf := f.allMessageFieldsByPtr[message]
+	if sf == nil {
+		sf = new(structFields)
+		f.allMessageFieldsByPtr[message] = sf
+	}
+
+	var tags structTags
+	switch {
+	case message.isOpen():
+		tags = structTags{{"protogen", "open.v1"}}
+	case message.isHybrid():
+		tags = structTags{{"protogen", "hybrid.v1"}}
+	case message.isOpaque():
+		tags = structTags{{"protogen", "opaque.v1"}}
+	}
+
+	g.P(genid.State_goname, " ", protoimplPackage.Ident("MessageState"), tags)
+	sf.append(genid.State_goname)
+	fields := message.Fields
+	for _, field := range fields {
+		opaqueGenMessageField(g, f, message, field, sf)
+	}
+	opaqueGenMessageInternalFields(g, f, message, sf)
+	g.P("}")
+	g.P()
+
+	genMessageKnownFunctions(g, f, message)
+	genMessageDefaultDecls(g, f, message)
+	opaqueGenMessageMethods(g, f, message)
+	opaqueGenMessageBuilder(g, f, message)
+	opaqueGenOneofWrapperTypes(g, f, message)
+}
+
+// opaqueGenMessageField generates a struct field.
+func opaqueGenMessageField(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field, sf *structFields) {
+	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
+		// It would be a bit simpler to iterate over the oneofs below,
+		// but generating the field here keeps the contents of the Go
+		// struct in the same order as the contents of the source
+		// .proto file.
+		if field != oneof.Fields[0] {
+			return
+		}
+		opaqueGenOneofFields(g, f, message, oneof, sf)
+		return
+	}
+
+	goType, pointer := opaqueFieldGoType(g, f, message, field)
+	if pointer {
+		goType = "*" + goType
+	}
+	protobufTagValue := fieldProtobufTagValue(field)
+	jsonTagValue := fieldJSONTagValue(field)
+	if g.InternalStripForEditionsDiff() {
+		if field.Desc.ContainingOneof() != nil && field.Desc.ContainingOneof().IsSynthetic() {
+			protobufTagValue = strings.ReplaceAll(protobufTagValue, ",oneof", "")
+		}
+		protobufTagValue = strings.ReplaceAll(protobufTagValue, ",proto3", "")
+	}
+	tags := structTags{
+		{"protobuf", protobufTagValue},
+		{"json", jsonTagValue},
+	}
+	if field.Desc.IsMap() {
+		keyTagValue := fieldProtobufTagValue(field.Message.Fields[0])
+		valTagValue := fieldProtobufTagValue(field.Message.Fields[1])
+		keyTagValue = strings.ReplaceAll(keyTagValue, ",proto3", "")
+		valTagValue = strings.ReplaceAll(valTagValue, ",proto3", "")
+		tags = append(tags, structTags{
+			{"protobuf_key", keyTagValue},
+			{"protobuf_val", valTagValue},
+		}...)
+	}
+
+	name := field.GoName
+	if field.Desc.IsWeak() {
+		g.P("// Deprecated: Do not use. This will be deleted in the near future.")
+		name = genid.WeakFieldPrefix_goname + name
+	} else if message.isOpaque() {
+		name = "xxx_hidden_" + name
+	}
+
+	if message.isOpaque() && !field.Desc.IsWeak() {
+		g.P(name, " ", goType, tags)
+		sf.append(name)
+		if message.isTracked {
+			g.P("// Deprecated: Do not use. This will be deleted in the near future.")
+			g.P("XXX_ft_", field.GoName, " struct{} `go:\"track\"`")
+			sf.append("XXX_ft_" + field.GoName)
+		}
+	} else {
+		if message.isTracked {
+			tags = append(tags, structTags{
+				{"go", "track"},
+			}...)
+		}
+		g.AnnotateSymbol(field.Parent.GoIdent.GoName+"."+name, protogen.Annotation{Location: field.Location})
+		leadingComments := appendDeprecationSuffix(field.Comments.Leading,
+			field.Desc.ParentFile(),
+			field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+		g.P(leadingComments,
+			name, " ", goType, tags,
+			trailingComment(field.Comments.Trailing))
+		sf.append(name)
+	}
+}
+
+// opaqueGenOneofFields generates the message fields for a oneof.
+func opaqueGenOneofFields(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof, sf *structFields) {
+	tags := structTags{
+		{"protobuf_oneof", string(oneof.Desc.Name())},
+	}
+	if message.isTracked {
+		tags = append(tags, structTags{
+			{"go", "track"},
+		}...)
+	}
+
+	oneofName := opaqueOneofFieldName(oneof, message.isOpaque())
+	goType := opaqueOneofInterfaceName(oneof)
+
+	if message.isOpaque() {
+		g.P(oneofName, " ", goType, tags)
+		sf.append(oneofName)
+		if message.isTracked {
+			g.P("// Deprecated: Do not use. This will be deleted in the near future.")
+			g.P("XXX_ft_", oneof.GoName, " struct{} `go:\"track\"`")
+			sf.append("XXX_ft_" + oneof.GoName)
+		}
+		return
+	}
+
+	leadingComments := oneof.Comments.Leading
+	if leadingComments != "" {
+		leadingComments += "\n"
+	}
+	// NOTE(rsc): The extra \n here is working around #52605,
+	// making the comment be in Go 1.19 doc comment format
+	// even though it's not really a doc comment.
+	ss := []string{" Types that are valid to be assigned to ", oneofName, ":\n\n"}
+	for _, field := range oneof.Fields {
+		ss = append(ss, "\t*"+opaqueFieldOneofType(field, message.isOpaque()).GoName+"\n")
+	}
+	leadingComments += protogen.Comments(strings.Join(ss, ""))
+	g.P(leadingComments, oneofName, " ", goType, tags)
+	sf.append(oneofName)
+}
+
+// opaqueGenMessageInternalFields adds additional XXX_ fields to a message struct.
+func opaqueGenMessageInternalFields(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, sf *structFields) {
+	if opaqueNeedsPresenceArray(message) {
+		if opaqueNeedsLazyStruct(message) {
+			g.P("// Deprecated: Do not use. This will be deleted in the near future.")
+			g.P("XXX_lazyUnmarshalInfo ", protoimplPackage.Ident("LazyUnmarshalInfo"))
+			sf.append("XXX_lazyUnmarshalInfo")
+		}
+		g.P("XXX_raceDetectHookData ", protoimplPackage.Ident("RaceDetectHookData"))
+		sf.append("XXX_raceDetectHookData")
+
+		// Presence must be stored in a data type no larger than 32 bit:
+		//
+		// Presence used to be a uint64, accessed with atomic.LoadUint64, but it
+		// turns out that on 32-bit platforms like GOARCH=arm, the struct field
+		// was 32-bit aligned (not 64-bit aligned) and hence atomic accesses
+		// failed.
+		//
+		// The easiest solution was to switch to a uint32 on all platforms,
+		// which did not come with a performance penalty.
+		g.P("XXX_presence [", (opaqueNumPresenceFields(message)+31)/32, "]uint32")
+		sf.append("XXX_presence")
+	}
+	if message.hasWeak {
+		g.P(genid.WeakFields_goname, " ", protoimplPackage.Ident("WeakFields"))
+		sf.append(genid.WeakFields_goname)
+	}
+	if message.Desc.ExtensionRanges().Len() > 0 {
+		g.P(genid.ExtensionFields_goname, " ", protoimplPackage.Ident("ExtensionFields"))
+		sf.append(genid.ExtensionFields_goname)
+	}
+	g.P(genid.UnknownFields_goname, " ", protoimplPackage.Ident("UnknownFields"))
+	sf.append(genid.UnknownFields_goname)
+	g.P(genid.SizeCache_goname, " ", protoimplPackage.Ident("SizeCache"))
+	sf.append(genid.SizeCache_goname)
+}
+
+func opaqueGenMessageMethods(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
+	genMessageBaseMethods(g, f, message)
+
+	isRepeated := func(field *protogen.Field) bool {
+		return field.Desc.Cardinality() == protoreflect.Repeated
+	}
+
+	for _, field := range message.Fields {
+		if isFirstOneofField(field) && !message.isOpaque() {
+			opaqueGenGetOneof(g, f, message, field.Oneof)
+		}
+		opaqueGenGet(g, f, message, field)
+	}
+	for _, field := range message.Fields {
+		// For the plain open mode, we only have set methods for weak fields.
+		if message.isOpen() && !field.Desc.IsWeak() {
+			continue
+		}
+		opaqueGenSet(g, f, message, field)
+	}
+	for _, field := range message.Fields {
+		// Open API does not have Has method.
+		// Repeated (includes map) fields do not have Has method.
+		if message.isOpen() || isRepeated(field) {
+			continue
+		}
+
+		if !field.Desc.HasPresence() {
+			continue
+		}
+
+		if isFirstOneofField(field) {
+			opaqueGenHasOneof(g, f, message, field.Oneof)
+		}
+		opaqueGenHas(g, f, message, field)
+	}
+	for _, field := range message.Fields {
+		// Open API does not have Clear method.
+		// Repeated (includes map) fields do not have Clear method.
+		if message.isOpen() || isRepeated(field) {
+			continue
+		}
+		if !field.Desc.HasPresence() {
+			continue
+		}
+
+		if isFirstOneofField(field) {
+			opaqueGenClearOneof(g, f, message, field.Oneof)
+		}
+		opaqueGenClear(g, f, message, field)
+	}
+	// Plain open protos do not have which methods.
+	if !message.isOpen() {
+		opaqueGenWhichOneof(g, f, message)
+	}
+
+	if g.InternalStripForEditionsDiff() {
+		return
+	}
+}
+
+func isLazy(field *protogen.Field) bool {
+	// Prerequisite: field is of kind message
+	if field.Message == nil {
+		return false
+	}
+
+	// Was the field marked as [lazy = true] in the .proto file?
+	fopts := field.Desc.Options().(*descriptorpb.FieldOptions)
+	return fopts.GetLazy()
+}
+
+// opaqueGenGet generates a Get method for a field.
+func opaqueGenGet(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
+	goType, pointer := opaqueFieldGoType(g, f, message, field)
+	getterName, bcName := field.MethodName("Get")
+
+	// If we need a backwards compatible getter name, we add it now.
+	if bcName != "" {
+		defer func() {
+			g.P("// Deprecated: Use ", getterName, " instead.")
+			g.P("func (x *", message.GoIdent, ") ", bcName, "() ", goType, " {")
+			g.P("return x.", getterName, "()")
+			g.P("}")
+			g.P()
+		}()
+	}
+
+	leadingComments := appendDeprecationSuffix("",
+		field.Desc.ParentFile(),
+		field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+	fieldtrackNoInterface(g, message.isTracked)
+	g.AnnotateSymbol(message.GoIdent.GoName+"."+getterName, protogen.Annotation{Location: field.Location})
+
+	// Weak field.
+	if field.Desc.IsWeak() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", protoPackage.Ident("Message"), "{")
+		g.P("var w ", protoimplPackage.Ident("WeakFields"))
+		g.P("if x != nil {")
+		g.P("w = x.", genid.WeakFields_goname)
+		if message.isTracked {
+			g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
+		}
+		g.P("}")
+		g.P("return ", protoimplPackage.Ident("X"), ".GetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ")")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	defaultValue := fieldDefaultValue(g, f, message, field)
+
+	// Oneof field.
+	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
+		structPtr := "x"
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, " {")
+		g.P("if x != nil {")
+		if message.isOpaque() && message.isTracked {
+			g.P("_ = ", structPtr, ".XXX_ft_", field.Oneof.GoName)
+		}
+		g.P("if x, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), "); ok {")
+		g.P("return x.", field.GoName)
+		g.P("}")
+		// End if m != nil {.
+		g.P("}")
+		g.P("return ", defaultValue)
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field for open type message.
+	if !message.isOpaque() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, " {")
+		if !field.Desc.HasPresence() || defaultValue == "nil" {
+			g.P("if x != nil {")
+		} else {
+			g.P("if x != nil && x.", field.GoName, " != nil {")
+		}
+		star := ""
+		if pointer {
+			star = "*"
+		}
+		g.P("return ", star, " x.", field.GoName)
+		g.P("}")
+		g.P("return ", defaultValue)
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field for opaque type message.
+	g.P(leadingComments, "func (x *", message.GoIdent, ") ", getterName, "() ", goType, "{")
+	structPtr := "x"
+	g.P("if x != nil {")
+	if message.isTracked {
+		g.P("_ = ", structPtr, ".XXX_ft_", field.GoName)
+	}
+	if usePresence(message, field) {
+		pi := opaqueFieldPresenceIndex(field)
+		ai := pi / 32
+		// For
+		//
+		//  1. Message fields of lazy messages (unmarshalled lazily),
+		//  2. Fields with a default value,
+		//  3. Closed enums
+		//
+		// ...we check presence, but for other fields using presence, we can return
+		// whatever is there and it should be correct regardless of presence, which
+		// saves us an atomic operation.
+		isEnum := field.Desc.Kind() == protoreflect.EnumKind
+		usePresenceForRead := (isLazy(field)) ||
+			field.Desc.HasDefault() || isEnum
+
+		if usePresenceForRead {
+			g.P("if ", protoimplPackage.Ident("X"), ".Present(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ") {")
+		}
+		// For lazy, check if pointer is nil and optionally unmarshal
+		if isLazy(field) {
+			// Since pointer to lazily unmarshaled sub-message can be written during a conceptual
+			// "read" operation, all read/write accesses to the pointer must be atomic.  This
+			// function gets inlined on x86 as just a simple get and compare. Still need to make the
+			// slice accesses be atomic.
+			g.P("if ", protoimplPackage.Ident("X"), ".AtomicCheckPointerIsNil(&", structPtr, ".xxx_hidden_", field.GoName, ") {")
+			g.P(protoimplPackage.Ident("X"), ".UnmarshalField(", structPtr, ", ", field.Desc.Number(), ")")
+			g.P("}")
+		}
+		if field.Message == nil || field.Desc.IsMap() {
+			star := ""
+			if pointer {
+				star = "*"
+			}
+			if pointer {
+				g.P("if ", structPtr, ".xxx_hidden_", field.GoName, "!= nil {")
+			}
+
+			g.P("return ", star, structPtr, ".xxx_hidden_", field.GoName)
+			if pointer {
+				g.P("}")
+				g.P("return ", defaultValue)
+			}
+		} else {
+			// We need to do an atomic load of the msg pointer field, but cannot explicitly use
+			// unsafe pointers here.  We load the value and store into rv, via protoimpl.Pointer,
+			// which is aliased to unsafe.Pointer in pointer_unsafe.go, but is aliased to
+			// interface{} in pointer_reflect.go
+			star := ""
+			if pointer {
+				star = "*"
+			}
+			if isLazy(field) {
+				g.P("var rv ", star, goType)
+				g.P(protoimplPackage.Ident("X"), ".AtomicLoadPointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&rv))")
+				g.P("return ", star, "rv")
+			} else {
+				if pointer {
+					g.P("if ", structPtr, ".xxx_hidden_", field.GoName, "!= nil {")
+				}
+				g.P("return ", star, structPtr, ".xxx_hidden_", field.GoName)
+				if pointer {
+					g.P("}")
+				}
+			}
+		}
+		if usePresenceForRead {
+			g.P("}")
+		}
+	} else if pointer {
+		g.P("if ", structPtr, ".xxx_hidden_", field.GoName, " != nil {")
+		g.P("return *", structPtr, ".xxx_hidden_", field.GoName)
+		g.P("}")
+	} else {
+		g.P("return ", structPtr, ".xxx_hidden_", field.GoName)
+	}
+	// End if m != nil {.
+	g.P("}")
+	g.P("return ", defaultValue)
+	g.P("}")
+	g.P()
+}
+
+// opaqueGenSet generates a Set method for a field.
+func opaqueGenSet(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
+	goType, pointer := opaqueFieldGoType(g, f, message, field)
+	setterName, bcName := field.MethodName("Set")
+
+	// If we need a backwards compatible setter name, we add it now.
+	if bcName != "" {
+		defer func() {
+			g.P("// Deprecated: Use ", setterName, " instead.")
+			g.P("func (x *", message.GoIdent, ") ", bcName, "(v ", goType, ") {")
+			g.P("x.", setterName, "(v)")
+			g.P("}")
+			g.P()
+		}()
+	}
+
+	leadingComments := appendDeprecationSuffix("",
+		field.Desc.ParentFile(),
+		field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+	g.AnnotateSymbol(message.GoIdent.GoName+"."+setterName, protogen.Annotation{
+		Location: field.Location,
+		Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+	})
+	fieldtrackNoInterface(g, message.noInterface)
+
+	// Weak field.
+	if field.Desc.IsWeak() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", protoPackage.Ident("Message"), ") {")
+		g.P("var w *", protoimplPackage.Ident("WeakFields"))
+		g.P("if x != nil {")
+		g.P("w = &x.", genid.WeakFields_goname)
+		if message.isTracked {
+			g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
+		}
+		g.P("}")
+		g.P(protoimplPackage.Ident("X"), ".SetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ", v)")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Oneof field.
+	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
+		structPtr := "x"
+		if message.isOpaque() && message.isTracked {
+			// Add access to zero field for tracking
+			g.P(structPtr, ".XXX_ft_", oneof.GoName, " = struct{}{}")
+		}
+		if field.Desc.Kind() == protoreflect.BytesKind {
+			g.P("if v == nil { v = []byte{} }")
+		} else if field.Message != nil {
+			g.P("if v == nil {")
+			g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), "= nil")
+			g.P("return")
+			g.P("}")
+		}
+		g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), "= &", opaqueFieldOneofType(field, message.isOpaque()), "{v}")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field for open type message.
+	if !message.isOpaque() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
+		if field.Desc.Cardinality() != protoreflect.Repeated && field.Desc.Kind() == protoreflect.BytesKind {
+			g.P("if v == nil { v = []byte{} }")
+		}
+		amp := ""
+		if pointer {
+			amp = "&"
+		}
+
+		v := "v"
+		g.P("x.", field.GoName, " = ", amp, v)
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field for opaque type message.
+	g.P(leadingComments, "func (x *", message.GoIdent, ") ", setterName, "(v ", goType, ") {")
+	structPtr := "x"
+	if message.isTracked {
+		// Add access to zero field for tracking
+		g.P(structPtr, ".XXX_ft_", field.GoName, " = struct{}{}")
+	}
+	if field.Desc.Cardinality() != protoreflect.Repeated && field.Desc.Kind() == protoreflect.BytesKind {
+		g.P("if v == nil { v = []byte{} }")
+	}
+	amp := ""
+	if pointer {
+		amp = "&"
+	}
+	if usePresence(message, field) {
+		pi := opaqueFieldPresenceIndex(field)
+		ai := pi / 32
+
+		if field.Message != nil && field.Desc.IsList() {
+			g.P("var sv *", goType)
+			g.P(protoimplPackage.Ident("X"), ".AtomicLoadPointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&sv))")
+			g.P("if sv == nil {")
+			g.P("sv = &", goType, "{}")
+			g.P(protoimplPackage.Ident("X"), ".AtomicInitializePointer(", protoimplPackage.Ident("Pointer"), "(&", structPtr, ".xxx_hidden_", field.GoName, "), ", protoimplPackage.Ident("Pointer"), "(&sv))")
+			g.P("}")
+			g.P("*sv = v")
+			g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
+		} else if field.Message != nil && !field.Desc.IsMap() {
+			// Only for lazy messages do we need to set pointers atomically
+			if isLazy(field) {
+				g.P(protoimplPackage.Ident("X"), ".AtomicSetPointer(&", structPtr, ".xxx_hidden_", field.GoName, ", ", amp, "v)")
+			} else {
+				g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
+			}
+			// When setting a message or slice of messages to a nil
+			// value, we must clear the presence bit, else we will
+			// later think that this field still needs to be lazily decoded.
+			g.P("if v == nil {")
+			g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
+			g.P("} else {")
+			g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
+			g.P("}")
+		} else {
+			// Any map or non-message, possibly repeated, field that uses presence (proto2 only)
+			g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
+			// For consistent behaviour with lazy fields, non-map repeated fields should be cleared when
+			// the last object is removed. Maps are cleared when set to a nil map.
+			if field.Desc.Cardinality() == protoreflect.Repeated { // Includes maps.
+				g.P("if v == nil {")
+				g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
+				g.P("} else {")
+			}
+			g.P(protoimplPackage.Ident("X"), ".SetPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
+			if field.Desc.Cardinality() == protoreflect.Repeated {
+				g.P("}")
+			}
+		}
+	} else {
+		// proto3 non-lazy fields
+		g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", amp, "v")
+	}
+	g.P("}")
+	g.P()
+}
+
+// usePresence returns true if the presence map should be used for a field. It
+// is always true for lazy message types. It is also true for all scalar fields.
+// repeated, map or message fields are not using the presence map.
+func usePresence(message *messageInfo, field *protogen.Field) bool {
+	if !message.isOpaque() || field.Desc.IsWeak() {
+		return false
+	}
+	return opaqueFieldNeedsPresenceArray(message, field)
+}
+
+func opaqueFieldNeedsPresenceArray(message *messageInfo, field *protogen.Field) bool {
+	// Non optional fields need presence if truly lazy field, i.e. are message fields.
+	if isLazy(field) {
+		return true
+	}
+	isNotOneof := field.Desc.ContainingOneof() == nil || field.Desc.ContainingOneof().IsSynthetic()
+	return field.Desc.HasPresence() && field.Message == nil && isNotOneof
+}
+
+// opaqueGenHas generates a Has method for a field.
+func opaqueGenHas(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
+	hasserName, _ := field.MethodName("Has")
+
+	leadingComments := appendDeprecationSuffix("",
+		field.Desc.ParentFile(),
+		field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+	g.AnnotateSymbol(message.GoIdent.GoName+"."+hasserName, protogen.Annotation{Location: field.Location})
+	fieldtrackNoInterface(g, message.noInterface)
+
+	// Weak field.
+	if field.Desc.IsWeak() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
+		g.P("var w ", protoimplPackage.Ident("WeakFields"))
+		g.P("if x != nil {")
+		g.P("w = x.", genid.WeakFields_goname)
+		if message.isTracked {
+			g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
+		}
+		g.P("}")
+		g.P("return ", protoimplPackage.Ident("X"), ".HasWeak(w, ", field.Desc.Number(), ")")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Oneof field.
+	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
+		structPtr := "x"
+		g.P("if ", structPtr, " == nil {")
+		g.P("return false")
+		g.P("}")
+		if message.isOpaque() && message.isTracked {
+			// Add access to zero field for tracking
+			g.P("_ = ", structPtr, ".", "XXX_ft_", oneof.GoName)
+		}
+		g.P("_, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), ")")
+		g.P("return ok")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field in open message.
+	if !message.isOpaque() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
+		g.P("if x == nil {")
+		g.P("return false")
+		g.P("}")
+		g.P("return ", "x.", field.GoName, " != nil")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field in opaque message.
+	g.P(leadingComments, "func (x *", message.GoIdent, ") ", hasserName, "() bool {")
+	g.P("if x == nil {")
+	g.P("return false")
+	g.P("}")
+	structPtr := "x"
+	if message.isTracked {
+		// Add access to zero field for tracking
+		g.P("_ = ", structPtr, ".", "XXX_ft_"+field.GoName)
+	}
+	if usePresence(message, field) {
+		pi := opaqueFieldPresenceIndex(field)
+		ai := pi / 32
+		g.P("return ", protoimplPackage.Ident("X"), ".Present(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
+	} else {
+		// Has for proto3 message without presence
+		g.P("return ", structPtr, ".xxx_hidden_", field.GoName, " != nil")
+	}
+
+	g.P("}")
+	g.P()
+}
+
+// opaqueGenClear generates a Clear method for a field.
+func opaqueGenClear(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) {
+	clearerName, _ := field.MethodName("Clear")
+	pi := opaqueFieldPresenceIndex(field)
+	ai := pi / 32
+
+	leadingComments := appendDeprecationSuffix("",
+		field.Desc.ParentFile(),
+		field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+	g.AnnotateSymbol(message.GoIdent.GoName+"."+clearerName, protogen.Annotation{
+		Location: field.Location,
+		Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+	})
+	fieldtrackNoInterface(g, message.noInterface)
+
+	// Weak field.
+	if field.Desc.IsWeak() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
+		g.P("var w *", protoimplPackage.Ident("WeakFields"))
+		g.P("if x != nil {")
+		g.P("w = &x.", genid.WeakFields_goname)
+		if message.isTracked {
+			g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName)
+		}
+		g.P("}")
+		g.P(protoimplPackage.Ident("X"), ".ClearWeak(w, ", field.Desc.Number(), ")")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Oneof field.
+	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
+		structPtr := "x"
+		if message.isOpaque() && message.isTracked {
+			// Add access to zero field for tracking
+			g.P(structPtr, ".", "XXX_ft_", oneof.GoName, " = struct{}{}")
+		}
+		g.P("if _, ok := ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), ".(*", opaqueFieldOneofType(field, message.isOpaque()), "); ok {")
+		g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " = nil")
+		g.P("}")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field in open message.
+	if !message.isOpaque() {
+		g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
+		g.P("x.", field.GoName, " = nil")
+		g.P("}")
+		g.P()
+		return
+	}
+
+	// Non-oneof field in opaque message.
+	g.P(leadingComments, "func (x *", message.GoIdent, ") ", clearerName, "() {")
+	structPtr := "x"
+	if message.isTracked {
+		// Add access to zero field for tracking
+		g.P(structPtr, ".", "XXX_ft_", field.GoName, " = struct{}{}")
+	}
+
+	if usePresence(message, field) {
+		g.P(protoimplPackage.Ident("X"), ".ClearPresent(&(", structPtr, ".XXX_presence[", ai, "]),", pi, ")")
+	}
+
+	// Avoid needing to read the presence value in Get by ensuring that we set the
+	// right zero value (unless we have an explicit default, in which case we
+	// revert to presence checking in Get). Rationale: Get is called far more
+	// frequently than Clear, it should be as lean as possible.
+	zv := opaqueZeroValueForField(g, field)
+	// For lazy, (repeated) message fields are unmarshalled lazily. Hence they are
+	// assigned atomically in Getters (which are allowed to be called
+	// concurrently). Due to this, historically, the code generator would use
+	// atomic operations everywhere.
+	//
+	// TODO(b/291588964): Stop using atomic operations for non-presence fields in
+	//                    write calls (Set/Clear). Concurrent reads are allowed,
+	//                    but concurrent read/write or write/write are not, we
+	//                    shouldn't cater to it.
+	if isLazy(field) {
+		goType, _ := opaqueFieldGoType(g, f, message, field)
+		g.P(protoimplPackage.Ident("X"), ".AtomicSetPointer(&", structPtr, ".xxx_hidden_", field.GoName, ",(", goType, ")(", zv, "))")
+	} else if !field.Desc.HasDefault() {
+		g.P(structPtr, ".xxx_hidden_", field.GoName, " = ", zv)
+	}
+	g.P("}")
+	g.P()
+}
+
+// Determine what value to set a cleared field to.
+func opaqueZeroValueForField(g *protogen.GeneratedFile, field *protogen.Field) string {
+	if field.Desc.Cardinality() == protoreflect.Repeated {
+		return "nil"
+	}
+	switch field.Desc.Kind() {
+	case protoreflect.StringKind:
+		return "nil"
+	case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind:
+		return "nil"
+	case protoreflect.BoolKind:
+		return "false"
+	case protoreflect.EnumKind:
+		return g.QualifiedGoIdent(field.Enum.Values[0].GoIdent)
+	default:
+		return "0"
+	}
+}
+
+// opaqueGenGetOneof generates a Get function for a oneof union.
+func opaqueGenGetOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
+	ifName := opaqueOneofInterfaceName(oneof)
+	g.AnnotateSymbol(message.GoIdent.GoName+".Get"+oneof.GoName, protogen.Annotation{Location: oneof.Location})
+	fieldtrackNoInterface(g, message.isTracked)
+	g.P("func (x *", message.GoIdent.GoName, ") Get", oneof.GoName, "() ", ifName, " {")
+	g.P("if x != nil {")
+	g.P("return x.", opaqueOneofFieldName(oneof, message.isOpaque()))
+	g.P("}")
+	g.P("return nil")
+	g.P("}")
+	g.P()
+}
+
+// opaqueGenHasOneof generates a Has function for a oneof union.
+func opaqueGenHasOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
+	fieldtrackNoInterface(g, message.noInterface)
+	hasserName := oneof.MethodName("Has")
+	g.P("func (x *", message.GoIdent, ") ", hasserName, "() bool {")
+	g.P("if x == nil {")
+	g.P("return false")
+	g.P("}")
+	structPtr := "x"
+	if message.isOpaque() && message.isTracked {
+		// Add access to zero field for tracking
+		g.P("_ = ", structPtr, ".XXX_ft_", oneof.GoName)
+	}
+	g.P("return ", structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " != nil")
+	g.P("}")
+	g.P()
+}
+
+// opaqueGenClearOneof generates a Clear function for a oneof union.
+func opaqueGenClearOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, oneof *protogen.Oneof) {
+	fieldtrackNoInterface(g, message.noInterface)
+	clearerName := oneof.MethodName("Clear")
+	g.P("func (x *", message.GoIdent, ") ", clearerName, "() {")
+	structPtr := "x"
+	if message.isOpaque() && message.isTracked {
+		// Add access to zero field for tracking
+		g.P(structPtr, ".", "XXX_ft_", oneof.GoName, " = struct{}{}")
+	}
+	g.P(structPtr, ".", opaqueOneofFieldName(oneof, message.isOpaque()), " = nil")
+	g.P("}")
+	g.P()
+}
+
+// opaqueGenWhichOneof generates the Which method for each oneof union, as well as the case values for each member
+// of that union.
+func opaqueGenWhichOneof(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
+	// Go through the message, and for each field that is the first of a oneof field, dig down
+	// and generate constants + the actual which method.
+	oneofIndex := 0
+	for _, field := range message.Fields {
+		if oneof := field.Oneof; oneof != nil {
+			if !isFirstOneofField(field) {
+				continue
+			}
+			caseType := opaqueOneofCaseTypeName(oneof)
+			g.P("const ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ", caseType, " = ", 0)
+			for _, f := range oneof.Fields {
+				g.P("const ", message.GoIdent.GoName, "_", f.GoName, "_case ", caseType, " = ", f.Desc.Number())
+			}
+			fieldtrackNoInterface(g, message.noInterface)
+			whicherName := oneof.MethodName("Which")
+			g.P("func (x *", message.GoIdent, ") ", whicherName, "() ", caseType, " {")
+			g.P("if x == nil {")
+			g.P("return ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ")
+			g.P("}")
+			g.P("switch x.", opaqueOneofFieldName(oneof, message.isOpaque()), ".(type) {")
+			for _, f := range oneof.Fields {
+				g.P("case *", opaqueFieldOneofType(f, message.isOpaque()), ":")
+				g.P("return ", message.GoIdent.GoName, "_", f.GoName, "_case")
+			}
+			g.P("default", ":")
+			g.P("return ", message.GoIdent.GoName, "_", oneof.GoName, "_not_set_case ")
+			g.P("}")
+			g.P("}")
+			g.P()
+			oneofIndex++
+		}
+	}
+}
+
+func opaqueNeedsPresenceArray(message *messageInfo) bool {
+	if !message.isOpaque() {
+		return false
+	}
+	for _, field := range message.Fields {
+		if opaqueFieldNeedsPresenceArray(message, field) {
+			return true
+		}
+	}
+	return false
+}
+
+func opaqueNeedsLazyStruct(message *messageInfo) bool {
+	for _, field := range message.Fields {
+		if isLazy(field) {
+			return true
+		}
+	}
+	return false
+}
+
+// opaqueGenMessageBuilder generates a Builder type for a message.
+func opaqueGenMessageBuilder(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
+	if message.isOpen() {
+		return
+	}
+	// Builder type.
+	bName := g.QualifiedGoIdent(message.GoIdent) + genid.BuilderSuffix_goname
+	g.AnnotateSymbol(message.GoIdent.GoName+genid.BuilderSuffix_goname, protogen.Annotation{Location: message.Location})
+
+	leadingComments := appendDeprecationSuffix("",
+		message.Desc.ParentFile(),
+		message.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
+	g.P(leadingComments, "type ", bName, " struct {")
+	g.P("_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.")
+	g.P()
+	for _, field := range message.Fields {
+		oneof := field.Oneof
+		if oneof == nil && field.Desc.IsWeak() {
+			continue
+		}
+
+		goType, pointer := opaqueBuilderFieldGoType(g, f, message, field)
+		if pointer {
+			goType = "*" + goType
+		} else if oneof != nil && fieldDefaultValue(g, f, message, field) != "nil" {
+			goType = "*" + goType
+		}
+		// Track all non-oneof fields. Note: synthetic oneofs are an
+		// implementation detail of proto3 optional fields:
+		// go/proto-proposals/proto3-presence.md, which should be tracked.
+		tag := ""
+		if (oneof == nil || oneof.Desc.IsSynthetic()) && message.isTracked {
+			tag = "`go:\"track\"`"
+		}
+		if oneof != nil && oneof.Fields[0] == field && !oneof.Desc.IsSynthetic() {
+			if oneof.Comments.Leading != "" {
+				g.P(oneof.Comments.Leading)
+				g.P()
+			}
+			g.P("// Fields of oneof ", opaqueOneofFieldName(oneof, message.isOpaque()), ":")
+		}
+		g.AnnotateSymbol(field.Parent.GoIdent.GoName+genid.BuilderSuffix_goname+"."+field.BuilderFieldName(), protogen.Annotation{Location: field.Location})
+		leadingComments := appendDeprecationSuffix(field.Comments.Leading,
+			field.Desc.ParentFile(),
+			field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+		g.P(leadingComments,
+			field.BuilderFieldName(), " ", goType, " ", tag)
+		if oneof != nil && oneof.Fields[len(oneof.Fields)-1] == field && !oneof.Desc.IsSynthetic() {
+			g.P("// -- end of ", opaqueOneofFieldName(oneof, message.isOpaque()))
+		}
+	}
+	g.P("}")
+	g.P()
+
+	opaqueGenBuildMethod(g, f, message, bName)
+}
+
+// opaqueGenBuildMethod generates the actual Build method for the builder
+func opaqueGenBuildMethod(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, bName string) {
+	// Build method on the builder type.
+	fieldtrackNoInterface(g, message.noInterface)
+	g.P("func (b0 ", bName, ") Build() *", message.GoIdent, " {")
+	g.P("m0 := &", message.GoIdent, "{}")
+
+	if message.isTracked {
+		// Redeclare the builder and message types as local
+		// defined types, so that field tracking records the
+		// field uses against these types instead of the
+		// original struct types.
+		//
+		// TODO: Actually redeclare the struct types
+		// without `go:"track"` tags?
+		g.P("type (notrackB ", bName, "; notrackM ", message.GoIdent, ")")
+		g.P("b, x := (*notrackB)(&b0), (*notrackM)(m0)")
+	} else {
+		g.P("b, x := &b0, m0")
+	}
+	g.P("_, _ = b, x")
+
+	for _, field := range message.Fields {
+		oneof := field.Oneof
+		if oneof == nil && field.Desc.IsWeak() {
+			continue
+		}
+		if oneof != nil && !oneof.Desc.IsSynthetic() {
+			qual := ""
+			if fieldDefaultValue(g, f, message, field) != "nil" {
+				qual = "*"
+			}
+
+			g.P("if b.", field.BuilderFieldName(), " != nil {")
+			oneofName := opaqueOneofFieldName(oneof, message.isOpaque())
+			oneofType := opaqueFieldOneofType(field, message.isOpaque())
+			g.P("x.", oneofName, " = &", oneofType, "{", qual, "b.", field.BuilderFieldName(), "}")
+			g.P("}")
+		} else { // proto3 optional ends up here (synthetic oneof)
+			qual := ""
+			_, pointer := opaqueBuilderFieldGoType(g, f, message, field)
+			if pointer && message.isOpaque() && !field.Desc.IsList() && field.Desc.Kind() != protoreflect.StringKind {
+				qual = "*"
+			} else if message.isOpaque() && field.Desc.IsList() && field.Desc.Message() != nil {
+				qual = "&"
+			}
+			presence := usePresence(message, field)
+			if presence {
+				g.P("if b.", field.BuilderFieldName(), " != nil {")
+			}
+			if presence {
+				pi := opaqueFieldPresenceIndex(field)
+				g.P(protoimplPackage.Ident("X"), ".SetPresentNonAtomic(&(x.XXX_presence[", pi/32, "]),", pi, ",", opaqueNumPresenceFields(message), ")")
+			}
+			goName := field.GoName
+			if message.isOpaque() {
+				goName = "xxx_hidden_" + goName
+			}
+			g.P("x.", goName, " = ", qual, "b.", field.BuilderFieldName())
+			if presence {
+				g.P("}")
+			}
+		}
+	}
+
+	g.P("return m0")
+	g.P("}")
+	g.P()
+}
+
+// opaqueBuilderFieldGoType does the same as opaqueFieldGoType, but corrects for
+// types that are different in a builder
+func opaqueBuilderFieldGoType(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) (goType string, pointer bool) {
+	goType, pointer = opaqueFieldGoType(g, f, message, field)
+	kind := field.Desc.Kind()
+
+	// Use []T instead of *[]T for opaque repeated lists.
+	if message.isOpaque() && field.Desc.IsList() {
+		pointer = false
+	}
+
+	// Use *T for optional fields.
+	optional := field.Desc.HasPresence()
+	if optional &&
+		kind != protoreflect.GroupKind &&
+		kind != protoreflect.MessageKind &&
+		kind != protoreflect.BytesKind &&
+		field.Desc.Cardinality() != protoreflect.Repeated {
+		pointer = true
+	}
+
+	return goType, pointer
+}
+
+func opaqueGenOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo) {
+	// TODO: We should avoid generating these wrapper types in pure-opaque mode.
+	if !message.isOpen() {
+		for _, oneof := range message.Oneofs {
+			if oneof.Desc.IsSynthetic() {
+				continue
+			}
+			caseTypeName := opaqueOneofCaseTypeName(oneof)
+			g.P("type ", caseTypeName, " ", protoreflectPackage.Ident("FieldNumber"))
+			g.P("")
+
+			idx := f.allMessagesByPtr[message]
+			typesVar := messageTypesVarName(f)
+			g.P("func (x ", caseTypeName, ") String() string {")
+			g.P("md := ", typesVar, "[", idx, "].Descriptor()")
+			g.P("if x == 0 {")
+			g.P(`return "not set"`)
+			g.P("}")
+			g.P("return ", protoimplPackage.Ident("X"), ".MessageFieldStringOf(md, ", protoreflectPackage.Ident("FieldNumber"), "(x))")
+			g.P("}")
+			g.P()
+		}
+	}
+	for _, oneof := range message.Oneofs {
+		if oneof.Desc.IsSynthetic() {
+			continue
+		}
+		ifName := opaqueOneofInterfaceName(oneof)
+		g.P("type ", ifName, " interface {")
+		g.P(ifName, "()")
+		g.P("}")
+		g.P()
+		for _, field := range oneof.Fields {
+			name := opaqueFieldOneofType(field, message.isOpaque())
+			g.AnnotateSymbol(name.GoName, protogen.Annotation{Location: field.Location})
+			g.AnnotateSymbol(name.GoName+"."+field.GoName, protogen.Annotation{Location: field.Location})
+			g.P("type ", name, " struct {")
+			goType, _ := opaqueFieldGoType(g, f, message, field)
+			protobufTagValue := fieldProtobufTagValue(field)
+			if g.InternalStripForEditionsDiff() {
+				protobufTagValue = strings.ReplaceAll(protobufTagValue, ",proto3", "")
+			}
+			tags := structTags{
+				{"protobuf", protobufTagValue},
+			}
+			leadingComments := appendDeprecationSuffix(field.Comments.Leading,
+				field.Desc.ParentFile(),
+				field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
+			g.P(leadingComments,
+				field.GoName, " ", goType, tags,
+				trailingComment(field.Comments.Trailing))
+			g.P("}")
+			g.P()
+		}
+		for _, field := range oneof.Fields {
+			g.P("func (*", opaqueFieldOneofType(field, message.isOpaque()), ") ", ifName, "() {}")
+			g.P()
+		}
+	}
+}
+
+// opaqueFieldGoType returns the Go type used for a field.
+//
+// If it returns pointer=true, the struct field is a pointer to the type.
+func opaqueFieldGoType(g *protogen.GeneratedFile, f *fileInfo, message *messageInfo, field *protogen.Field) (goType string, pointer bool) {
+	if field.Desc.IsWeak() {
+		return "struct{}", false
+	}
+
+	pointer = true
+	switch field.Desc.Kind() {
+	case protoreflect.BoolKind:
+		goType = "bool"
+	case protoreflect.EnumKind:
+		goType = g.QualifiedGoIdent(field.Enum.GoIdent)
+	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
+		goType = "int32"
+	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
+		goType = "uint32"
+	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
+		goType = "int64"
+	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
+		goType = "uint64"
+	case protoreflect.FloatKind:
+		goType = "float32"
+	case protoreflect.DoubleKind:
+		goType = "float64"
+	case protoreflect.StringKind:
+		goType = "string"
+	case protoreflect.BytesKind:
+		goType = "[]byte"
+		pointer = false
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		goType = opaqueMessageFieldGoType(g, f, field, message.isOpaque())
+		pointer = false
+	}
+	switch {
+	case field.Desc.IsList():
+		goType = "[]" + goType
+		pointer = false
+	case field.Desc.IsMap():
+		keyType, _ := opaqueFieldGoType(g, f, message, field.Message.Fields[0])
+		valType, _ := opaqueFieldGoType(g, f, message, field.Message.Fields[1])
+		return fmt.Sprintf("map[%v]%v", keyType, valType), false
+	}
+
+	// Extension fields always have pointer type, even when defined in a proto3 file.
+	if !field.Desc.IsExtension() && !field.Desc.HasPresence() {
+		pointer = false
+	}
+
+	if message.isOpaque() {
+		switch {
+		case field.Desc.IsList() && field.Desc.Message() != nil:
+			pointer = true
+		case !field.Desc.IsList() && field.Desc.Kind() == protoreflect.StringKind:
+			switch {
+			case field.Desc.HasPresence():
+				pointer = true
+			default:
+				pointer = false
+			}
+		default:
+			pointer = false
+		}
+	}
+
+	return goType, pointer
+}
+
+func opaqueMessageFieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field, isOpaque bool) string {
+	return "*" + g.QualifiedGoIdent(field.Message.GoIdent)
+}
+
+// opaqueFieldPresenceIndex returns the index to pass to presence functions.
+//
+// TODO: field.Desc.Index() would be simpler, and would give space to record the presence of oneof fields.
+func opaqueFieldPresenceIndex(field *protogen.Field) int {
+	structFieldIndex := 0
+	for _, f := range field.Parent.Fields {
+		if field == f {
+			break
+		}
+		if f.Oneof == nil || isLastOneofField(f) {
+			structFieldIndex++
+		}
+	}
+	return structFieldIndex
+}
+
+// opaqueNumPresenceFields returns the number of fields that may be passed to presence functions.
+//
+// Since all fields in a oneof currently share a single entry in the presence bitmap,
+// this is not just len(message.Fields).
+func opaqueNumPresenceFields(message *messageInfo) int {
+	if len(message.Fields) == 0 {
+		return 0
+	}
+	return opaqueFieldPresenceIndex(message.Fields[len(message.Fields)-1]) + 1
+}
+
+func fieldtrackNoInterface(g *protogen.GeneratedFile, isTracked bool) {
+	if isTracked {
+		g.P("//go:nointerface")
+	}
+}
+
+// opaqueOneofFieldName returns the name of the struct field that holds
+// the value of a oneof.
+func opaqueOneofFieldName(oneof *protogen.Oneof, isOpaque bool) string {
+	if isOpaque {
+		return "xxx_hidden_" + oneof.GoName
+	}
+	return oneof.GoName
+}
+
+func opaqueFieldOneofType(field *protogen.Field, isOpaque bool) protogen.GoIdent {
+	ident := protogen.GoIdent{
+		GoImportPath: field.Parent.GoIdent.GoImportPath,
+		GoName:       field.Parent.GoIdent.GoName + "_" + field.GoName,
+	}
+	// Check for collisions with nested messages or enums.
+	//
+	// This conflict resolution is incomplete: Among other things, it
+	// does not consider collisions with other oneof field types.
+Loop:
+	for {
+		for _, message := range field.Parent.Messages {
+			if message.GoIdent == ident {
+				ident.GoName += "_"
+				continue Loop
+			}
+		}
+		for _, enum := range field.Parent.Enums {
+			if enum.GoIdent == ident {
+				ident.GoName += "_"
+				continue Loop
+			}
+		}
+		return unexportIdent(ident, isOpaque)
+	}
+}
+
+// unexportIdent turns id into its unexported version (by lower-casing), but
+// only if isOpaque is set. This function is used for oneof wrapper types,
+// which remain exported in the non-opaque API for now.
+func unexportIdent(id protogen.GoIdent, isOpaque bool) protogen.GoIdent {
+	if !isOpaque {
+		return id
+	}
+	r, sz := utf8.DecodeRuneInString(id.GoName)
+	if r == utf8.RuneError {
+		panic(fmt.Sprintf("Go identifier %q contains invalid UTF8?!", id.GoName))
+	}
+	r = unicode.ToLower(r)
+	id.GoName = string(r) + id.GoName[sz:]
+	return id
+}
+
+func opaqueOneofInterfaceName(oneof *protogen.Oneof) string {
+	return fmt.Sprintf("is%s_%s", oneof.Parent.GoIdent.GoName, oneof.GoName)
+}
+func opaqueOneofCaseTypeName(oneof *protogen.Oneof) string {
+	return fmt.Sprintf("case_%s_%s", oneof.Parent.GoIdent.GoName, oneof.GoName)
+}
+
+// isFirstOneofField reports whether this is the first field in a oneof.
+func isFirstOneofField(field *protogen.Field) bool {
+	return field.Oneof != nil && field == field.Oneof.Fields[0] && !field.Oneof.Desc.IsSynthetic()
+}
+
+// isLastOneofField returns true if this is the last field in a oneof.
+func isLastOneofField(field *protogen.Field) bool {
+	return field.Oneof != nil && field == field.Oneof.Fields[len(field.Oneof.Fields)-1]
+}
diff --git a/cmd/protoc-gen-go/internal_gengo/reflect.go b/cmd/protoc-gen-go/internal_gengo/reflect.go
index 75939d9..a3f91a8 100644
--- a/cmd/protoc-gen-go/internal_gengo/reflect.go
+++ b/cmd/protoc-gen-go/internal_gengo/reflect.go
@@ -174,7 +174,7 @@
 				for _, oneof := range message.Oneofs {
 					if !oneof.Desc.IsSynthetic() {
 						for _, field := range oneof.Fields {
-							g.P("(*", field.GoIdent, ")(nil),")
+							g.P("(*", unexportIdent(field.GoIdent, message.isOpaque()), ")(nil),")
 						}
 					}
 				}
diff --git a/cmd/protoc-gen-go/name_clash_test/name_clash_proto3_test.go b/cmd/protoc-gen-go/name_clash_test/name_clash_proto3_test.go
new file mode 100644
index 0000000..bd85f09
--- /dev/null
+++ b/cmd/protoc-gen-go/name_clash_test/name_clash_proto3_test.go
@@ -0,0 +1,810 @@
+// 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 name_clash_test
+
+import (
+	"testing"
+
+	"google.golang.org/protobuf/proto"
+
+	hpb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3"
+	opb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3"
+	pb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3"
+)
+
+// TestOpenMangling3 tests the backwards compatible mangling of fields
+// who clashes with the getters. The expected behavior, which is
+// somewhat surprising, is documented in the proto
+// test_name_clash_open.proto itself.
+func TestOpenMangling3(t *testing.T) {
+	m1 := &pb.M1{
+		Foo:       makeOpenM0(1),
+		GetFoo_:   makeOpenM0(2),
+		GetGetFoo: makeOpenM0(3),
+	}
+	if m1.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo().GetI1(), m1)
+	}
+	if m1.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_().GetI1(), m1)
+	}
+	if m1.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo().GetI1(), m1)
+	}
+	m2 := &pb.M2{
+		Foo:       makeOpenM0(1),
+		GetFoo_:   makeOpenM0(2),
+		GetGetFoo: makeOpenM0(3),
+	}
+	if m2.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo().GetI1(), m2)
+	}
+	if m2.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_().GetI1(), m2)
+	}
+	if m2.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo().GetI1(), m2)
+	}
+	m3 := &pb.M3{
+		Foo_:       makeOpenM0(1),
+		GetFoo:     makeOpenM0(2),
+		GetGetFoo_: makeOpenM0(3),
+	}
+	if m3.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_().GetI1(), m3)
+	}
+	if m3.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo().GetI1(), m3)
+	}
+	if m3.GetGetGetFoo_().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo_().GetI1(), m3)
+	}
+
+	m4 := &pb.M4{
+		GetFoo:     makeOpenM0(2),
+		GetGetFoo_: &pb.M4_GetGetGetFoo{GetGetGetFoo: 3},
+		Foo_:       makeOpenM0(1),
+	}
+	if m4.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo_().GetI1(), m4)
+	}
+	if m4.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo().GetI1(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+
+	m5 := &pb.M5{
+		GetFoo:       makeOpenM0(2),
+		GetGetGetFoo: &pb.M5_GetGetFoo_{GetGetFoo_: 3},
+		Foo_:         makeOpenM0(1),
+	}
+	if m5.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo_().GetI1(), m5)
+	}
+	if m5.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo().GetI1(), m5)
+	}
+	if m5.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo_(), m5)
+	}
+
+	m6 := &pb.M6{
+		GetGetFoo: &pb.M6_GetGetGetFoo{GetGetGetFoo: 3},
+		GetFoo_:   makeOpenM0(2),
+		Foo:       makeOpenM0(1),
+	}
+	if m6.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo().GetI1(), m6)
+	}
+	if m6.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo_().GetI1(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+
+	m7 := &pb.M7{
+		GetGetFoo: &pb.M7_GetFoo_{GetFoo_: 3},
+		Foo:       makeOpenM0(1),
+	}
+	if m7.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo().GetI1(), m7)
+	}
+	if m7.GetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m7.GetGetFoo_(), m7)
+	}
+	m7.GetGetFoo = &pb.M7_Bar{Bar: true}
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected 3)", m7.GetBar(), m7)
+	}
+
+	m8 := &pb.M8{
+		GetGetGetFoo_: &pb.M8_GetGetFoo{GetGetFoo: 3},
+		GetFoo_:       makeOpenM0(2),
+		Foo:           makeOpenM0(1),
+	}
+	if m8.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo().GetI1(), m8)
+	}
+	if m8.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo_().GetI1(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+
+	m9 := &pb.M9{
+		GetGetGetFoo_: &pb.M9_GetGetFoo{GetGetFoo: 3},
+		Foo:           makeOpenM0(1),
+	}
+	if m9.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	m9.GetGetGetFoo_ = &pb.M9_GetFoo_{GetFoo_: 2}
+	if m9.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo_(), m9)
+	}
+
+}
+
+// TestHybridMangling3 tests the backwards compatible mangling as well
+// as new style mangling of fields who clashes with the getters. The
+// expected behavior, which is somewhat surprising, is documented in
+// the proto test_name_clash_hybrid.proto itself.
+func TestHybridMangling3(t *testing.T) {
+	m1 := hpb.M1_builder{
+		Foo:       makeHybridM0(1),
+		GetFoo:    makeHybridM0(2),
+		GetGetFoo: makeHybridM0(3),
+	}.Build()
+	if m1.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo().GetI1(), m1)
+	}
+	if m1.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo().GetI1(), m1)
+	}
+	if m1.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_().GetI1(), m1)
+	}
+	if m1.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_().GetI1(), m1)
+	}
+	if m1.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo().GetI1(), m1)
+	}
+	checkNameConsistency(t, m1)
+	m2 := hpb.M2_builder{
+		Foo:       makeHybridM0(1),
+		GetFoo:    makeHybridM0(2),
+		GetGetFoo: makeHybridM0(3),
+	}.Build()
+	if m2.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo().GetI1(), m2)
+	}
+	if m2.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo().GetI1(), m2)
+	}
+	if m2.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_().GetI1(), m2)
+	}
+	if m2.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_().GetI1(), m2)
+	}
+	if m2.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo().GetI1(), m2)
+	}
+	checkNameConsistency(t, m2)
+	m3 := hpb.M3_builder{
+		Foo:       makeHybridM0(1),
+		GetFoo:    makeHybridM0(2),
+		GetGetFoo: makeHybridM0(3),
+	}.Build()
+	if m3.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_().GetI1(), m3)
+	}
+	if m3.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_().GetI1(), m3)
+	}
+	if m3.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo().GetI1(), m3)
+	}
+	if m3.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo().GetI1(), m3)
+	}
+	if m3.GetGetGetFoo_().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo_().GetI1(), m3)
+	}
+	checkNameConsistency(t, m3)
+
+	m4 := hpb.M4_builder{
+		GetFoo:       makeHybridM0(2),
+		GetGetGetFoo: proto.Int32(3),
+		Foo:          makeHybridM0(1),
+	}.Build()
+	if m4.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo_().GetI1(), m4)
+	}
+	if m4.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.Get_Foo().GetI1(), m4)
+	}
+	if m4.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo().GetI1(), m4)
+	}
+	if m4.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.Get_GetFoo().GetI1(), m4)
+	}
+	if m4.GetGetGetFoo_().(*hpb.M4_GetGetGetFoo).GetGetGetFoo != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetFoo_(), m4)
+	}
+	if !m4.HasGetGetFoo() {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected true)", m4.HasGetGetFoo(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+	checkNameConsistency(t, m4)
+
+	m5 := hpb.M5_builder{
+		GetFoo:    makeHybridM0(2),
+		GetGetFoo: proto.Int32(3),
+		Foo:       makeHybridM0(1),
+	}.Build()
+	if m5.GetFoo_().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo_().GetI1(), m5)
+	}
+	if m5.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.Get_Foo().GetI1(), m4)
+	}
+	if m5.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo().GetI1(), m5)
+	}
+	if m5.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.Get_GetFoo().GetI1(), m4)
+	}
+	if m5.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo_(), m5)
+	}
+	if m5.Get_GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.Get_GetGetFoo(), m5)
+	}
+	checkNameConsistency(t, m5)
+
+	m6 := hpb.M6_builder{
+		GetGetGetFoo: proto.Int32(3),
+		GetFoo:       makeHybridM0(2),
+		Foo:          makeHybridM0(1),
+	}.Build()
+	if m6.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo().GetI1(), m6)
+	}
+	if m6.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.Get_Foo().GetI1(), m6)
+	}
+	if m6.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo_().GetI1(), m6)
+	}
+	if m6.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.Get_GetFoo().GetI1(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+	checkNameConsistency(t, m6)
+
+	m7 := hpb.M7_builder{
+		GetFoo: proto.Int32(3),
+		Foo:    makeHybridM0(1),
+	}.Build()
+	if m7.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo().GetI1(), m7)
+	}
+	if m7.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.Get_Foo().GetI1(), m7)
+	}
+	if m7.GetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.GetGetFoo_(), m7)
+	}
+	if m7.Get_GetFoo() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.Get_GetFoo(), m7)
+	}
+	m7.SetBar(true)
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected 3)", m7.GetBar(), m7)
+	}
+	checkNameConsistency(t, m7)
+
+	m8 := hpb.M8_builder{
+		GetGetFoo: proto.Int32(3),
+		GetFoo:    makeHybridM0(2),
+		Foo:       makeHybridM0(1),
+	}.Build()
+	if m8.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo().GetI1(), m8)
+	}
+	if m8.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.Get_Foo().GetI1(), m8)
+	}
+	if m8.GetGetFoo_().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo_().GetI1(), m8)
+	}
+	if m8.Get_GetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.Get_GetFoo().GetI1(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+	checkNameConsistency(t, m8)
+
+	m9 := hpb.M9_builder{
+		GetGetFoo: proto.Int32(3),
+		Foo:       makeHybridM0(1),
+	}.Build()
+	if m9.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo().GetI1(), m9)
+	}
+	if m9.Get_Foo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.Get_Foo().GetI1(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	if m9.Get_GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.Get_GetGetFoo(), m9)
+	}
+	m9.Set_GetFoo(2)
+	if m9.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo_(), m9)
+	}
+	if m9.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.Get_GetFoo(), m9)
+	}
+	checkNameConsistency(t, m9)
+	m10 := hpb.M10_builder{
+		Foo:    makeHybridM0(1),
+		SetFoo: makeHybridM0(2),
+	}.Build()
+	m10.Set_Foo(makeHybridM0(47))
+	if m10.Get_Foo().GetI1() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m10.Get_Foo().GetI1(), m10)
+	}
+	m10.SetSetFoo(makeHybridM0(11))
+	if m10.GetSetFoo().GetI1() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m10.GetSetFoo().GetI1(), m10)
+	}
+	checkNameConsistency(t, m10)
+	m11 := hpb.M11_builder{
+		Foo:       makeHybridM0(1),
+		SetSetFoo: proto.Int32(2),
+	}.Build()
+	m11.Set_Foo(makeHybridM0(47))
+	if m11.Get_Foo().GetI1() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m11.Get_Foo().GetI1(), m11)
+	}
+	m11.SetSetSetFoo(11)
+	if m11.GetSetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m11.GetSetSetFoo(), m11)
+	}
+	checkNameConsistency(t, m11)
+	m12 := hpb.M12_builder{
+		Foo:    makeHybridM0(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m12.Set_Foo(makeHybridM0(47))
+	if m12.Get_Foo().GetI1() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m12.Get_Foo().GetI1(), m12)
+	}
+	m12.Set_SetFoo(11)
+	if m12.Get_SetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m12.Get_SetFoo(), m12)
+	}
+	checkNameConsistency(t, m12)
+	m13 := hpb.M13_builder{
+		Foo:    makeHybridM0(1),
+		HasFoo: makeHybridM0(2),
+	}.Build()
+	if !m13.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.Has_Foo(), m13)
+	}
+	if !m13.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasHasFoo(), m13)
+	}
+	checkNameConsistency(t, m13)
+	m14 := hpb.M14_builder{
+		Foo:       makeHybridM0(1),
+		HasHasFoo: proto.Int32(2),
+	}.Build()
+	if !m14.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.Has_Foo(), m14)
+	}
+	if !m14.Has_HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.Has_HasFoo(), m14)
+	}
+	if !m14.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasHasFoo(), m14)
+	}
+	checkNameConsistency(t, m14)
+	m15 := hpb.M15_builder{
+		Foo:    makeHybridM0(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m15.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.Has_Foo(), m15)
+	}
+	if !m15.Has_HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.Has_HasFoo(), m15)
+	}
+	if !m15.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasHasFoo(), m15)
+	}
+	checkNameConsistency(t, m15)
+	m16 := hpb.M16_builder{
+		Foo:      makeHybridM0(1),
+		ClearFoo: makeHybridM0(2),
+	}.Build()
+	m16.Clear_Foo()
+	if m16.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.Has_Foo(), m16)
+	}
+	m16.ClearClearFoo()
+	if m16.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasClearFoo(), m16)
+	}
+	checkNameConsistency(t, m16)
+	m17 := hpb.M17_builder{
+		Foo:           makeHybridM0(1),
+		ClearClearFoo: proto.Int32(2),
+	}.Build()
+	m17.Clear_Foo()
+	if m17.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.Has_Foo(), m17)
+	}
+	m17.ClearClearClearFoo()
+	if m17.HasClearClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasClearClearFoo(), m17)
+	}
+	checkNameConsistency(t, m17)
+	m18 := hpb.M18_builder{
+		Foo:      makeHybridM0(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m18.Clear_Foo()
+	if m18.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.Has_Foo(), m18)
+	}
+	m18.Clear_ClearFoo()
+	if m18.Has_ClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.Has_ClearFoo(), m18)
+	}
+	checkNameConsistency(t, m18)
+	m19 := hpb.M19_builder{
+		Foo:      makeHybridM0(1),
+		WhichFoo: proto.Int32(2),
+	}.Build()
+	if m19.WhichWhichWhichFoo() != hpb.M19_WhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M19_ClearFoo_case)", m19.WhichWhichWhichFoo(), m19)
+	}
+	checkNameConsistency(t, m19)
+	m20 := hpb.M20_builder{
+		Foo:           makeHybridM0(1),
+		WhichWhichFoo: proto.Int32(2),
+	}.Build()
+	if m20.Which_WhichFoo() != hpb.M20_WhichWhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M20_WhichWhichFoo_case)", m20.Which_WhichFoo(), m20)
+	}
+	checkNameConsistency(t, m20)
+
+}
+
+// TestOpaqueMangling3 tests the backwards compatible mangling as well
+// as new style mangling of fields who clashes with the getters. The
+// expected behavior, which is somewhat surprising, is documented in
+// the proto test_name_clash_opaque.proto itself.
+func TestOpaqueMangling3(t *testing.T) {
+	m1 := opb.M1_builder{
+		Foo:       makeOpaqueM0(1),
+		GetFoo:    makeOpaqueM0(2),
+		GetGetFoo: makeOpaqueM0(3),
+	}.Build()
+	if m1.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo().GetI1(), m1)
+	}
+	if m1.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo().GetI1(), m1)
+	}
+	if m1.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo().GetI1(), m1)
+	}
+	checkNameConsistency(t, m1)
+	m2 := opb.M2_builder{
+		Foo:       makeOpaqueM0(1),
+		GetFoo:    makeOpaqueM0(2),
+		GetGetFoo: makeOpaqueM0(3),
+	}.Build()
+	if m2.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo().GetI1(), m2)
+	}
+	if m2.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo().GetI1(), m2)
+	}
+	if m2.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo().GetI1(), m2)
+	}
+	checkNameConsistency(t, m2)
+	m3 := opb.M3_builder{
+		Foo:       makeOpaqueM0(1),
+		GetFoo:    makeOpaqueM0(2),
+		GetGetFoo: makeOpaqueM0(3),
+	}.Build()
+	if m3.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo().GetI1(), m3)
+	}
+	if m3.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo().GetI1(), m3)
+	}
+	if m3.GetGetGetFoo().GetI1() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo().GetI1(), m3)
+	}
+	checkNameConsistency(t, m3)
+
+	m4 := opb.M4_builder{
+		GetFoo:       makeOpaqueM0(2),
+		GetGetGetFoo: proto.Int32(3),
+		Foo:          makeOpaqueM0(1),
+	}.Build()
+	if m4.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo().GetI1(), m4)
+	}
+	if m4.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo().GetI1(), m4)
+	}
+	if !m4.HasGetGetFoo() {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected true)", m4.HasGetGetFoo(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+	checkNameConsistency(t, m4)
+
+	m5 := opb.M5_builder{
+		GetFoo:    makeOpaqueM0(2),
+		GetGetFoo: proto.Int32(3),
+		Foo:       makeOpaqueM0(1),
+	}.Build()
+	if m5.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo().GetI1(), m5)
+	}
+	if m5.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo().GetI1(), m5)
+	}
+	if m5.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo(), m5)
+	}
+	checkNameConsistency(t, m5)
+
+	m6 := opb.M6_builder{
+		GetGetGetFoo: proto.Int32(3),
+		GetFoo:       makeOpaqueM0(2),
+		Foo:          makeOpaqueM0(1),
+	}.Build()
+	if m6.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo().GetI1(), m6)
+	}
+	if m6.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo().GetI1(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+	checkNameConsistency(t, m6)
+
+	m7 := opb.M7_builder{
+		GetFoo: proto.Int32(3),
+		Foo:    makeOpaqueM0(1),
+	}.Build()
+	if m7.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo().GetI1(), m7)
+	}
+	if m7.GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.GetGetFoo(), m7)
+	}
+	m7.SetBar(true)
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected true)", m7.GetBar(), m7)
+	}
+	checkNameConsistency(t, m7)
+
+	m8 := opb.M8_builder{
+		GetGetFoo: proto.Int32(3),
+		GetFoo:    makeOpaqueM0(2),
+		Foo:       makeOpaqueM0(1),
+	}.Build()
+	if m8.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo().GetI1(), m8)
+	}
+	if m8.GetGetFoo().GetI1() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo().GetI1(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+	checkNameConsistency(t, m8)
+
+	m9 := opb.M9_builder{
+		GetGetFoo: proto.Int32(3),
+		Foo:       makeOpaqueM0(1),
+	}.Build()
+	if m9.GetFoo().GetI1() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo().GetI1(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	m9.SetGetFoo(2)
+	if m9.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo(), m9)
+	}
+	checkNameConsistency(t, m9)
+	m10 := opb.M10_builder{
+		Foo:    makeOpaqueM0(1),
+		SetFoo: makeOpaqueM0(2),
+	}.Build()
+	m10.SetFoo(makeOpaqueM0(48))
+	if m10.GetFoo().GetI1() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m10.GetFoo().GetI1(), m10)
+	}
+	m10.SetSetFoo(makeOpaqueM0(11))
+	if m10.GetSetFoo().GetI1() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m10.GetSetFoo().GetI1(), m10)
+	}
+	checkNameConsistency(t, m10)
+	m11 := opb.M11_builder{
+		Foo:       makeOpaqueM0(1),
+		SetSetFoo: proto.Int32(2),
+	}.Build()
+	m11.SetFoo(makeOpaqueM0(48))
+	if m11.GetFoo().GetI1() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m11.GetFoo().GetI1(), m11)
+	}
+	m11.SetSetSetFoo(11)
+	if m11.GetSetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m11.GetSetSetFoo(), m11)
+	}
+	checkNameConsistency(t, m11)
+	m12 := opb.M12_builder{
+		Foo:    makeOpaqueM0(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m12.SetFoo(makeOpaqueM0(48))
+	if m12.GetFoo().GetI1() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m12.GetFoo().GetI1(), m12)
+	}
+	m12.SetSetFoo(12)
+	if m12.GetSetFoo() != 12 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 12)", m12.GetSetFoo(), m12)
+	}
+	checkNameConsistency(t, m12)
+	m13 := opb.M13_builder{
+		Foo:    makeOpaqueM0(1),
+		HasFoo: makeOpaqueM0(2),
+	}.Build()
+	if !m13.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasFoo(), m13)
+	}
+	if !m13.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasHasFoo(), m13)
+	}
+	checkNameConsistency(t, m13)
+	m14 := opb.M14_builder{
+		Foo:       makeOpaqueM0(1),
+		HasHasFoo: proto.Int32(2),
+	}.Build()
+	if !m14.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasFoo(), m14)
+	}
+	if !m14.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasFoo(), m14)
+	}
+	if !m14.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasHasFoo(), m14)
+	}
+	checkNameConsistency(t, m14)
+	m15 := opb.M15_builder{
+		Foo:    makeOpaqueM0(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m15.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasFoo(), m15)
+	}
+	if !m15.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasFoo(), m15)
+	}
+	if !m15.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasHasFoo(), m15)
+	}
+	checkNameConsistency(t, m15)
+	m16 := opb.M16_builder{
+		Foo:      makeOpaqueM0(1),
+		ClearFoo: makeOpaqueM0(2),
+	}.Build()
+	m16.SetFoo(makeOpaqueM0(4711))
+	m16.ClearFoo()
+	if m16.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasFoo(), m16)
+	}
+	m16.ClearClearFoo()
+	if m16.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasClearFoo(), m16)
+	}
+	checkNameConsistency(t, m16)
+	m17 := opb.M17_builder{
+		Foo:           makeOpaqueM0(1),
+		ClearClearFoo: proto.Int32(2),
+	}.Build()
+	m17.SetFoo(makeOpaqueM0(4711))
+	m17.ClearFoo()
+	if m17.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasFoo(), m17)
+	}
+	m17.ClearClearClearFoo()
+	if m17.HasClearClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasClearClearFoo(), m17)
+	}
+	checkNameConsistency(t, m17)
+	m18 := opb.M18_builder{
+		Foo:      makeOpaqueM0(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m18.SetFoo(makeOpaqueM0(4711))
+	m18.ClearFoo()
+	if m18.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.HasFoo(), m18)
+	}
+	m18.SetClearFoo(13)
+	m18.ClearClearFoo()
+	if m18.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.HasClearFoo(), m18)
+	}
+	checkNameConsistency(t, m18)
+	m19 := opb.M19_builder{
+		Foo:      makeOpaqueM0(1),
+		WhichFoo: proto.Int32(2),
+	}.Build()
+	if m19.WhichWhichWhichFoo() != opb.M19_WhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M19_ClearFoo_case)", m19.WhichWhichWhichFoo(), m19)
+	}
+	checkNameConsistency(t, m19)
+	m20 := opb.M20_builder{
+		Foo:           makeOpaqueM0(1),
+		WhichWhichFoo: proto.Int32(2),
+	}.Build()
+	if m20.WhichWhichFoo() != opb.M20_WhichWhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M20_WhichWhichFoo_case)", m20.WhichWhichFoo(), m20)
+	}
+	checkNameConsistency(t, m20)
+
+}
+
+func makeOpenM0(x int32) *pb.M0 {
+	return &pb.M0{
+		I1: x,
+	}
+}
+
+func makeHybridM0(x int32) *hpb.M0 {
+	return hpb.M0_builder{
+		I1: x,
+	}.Build()
+}
+
+func makeOpaqueM0(x int32) *opb.M0 {
+	return opb.M0_builder{
+		I1: x,
+	}.Build()
+}
diff --git a/cmd/protoc-gen-go/name_clash_test/name_clash_test.go b/cmd/protoc-gen-go/name_clash_test/name_clash_test.go
new file mode 100644
index 0000000..6062c63
--- /dev/null
+++ b/cmd/protoc-gen-go/name_clash_test/name_clash_test.go
@@ -0,0 +1,898 @@
+// 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 name_clash_test
+
+import (
+	"reflect"
+	"testing"
+
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/internal/genid"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protodesc"
+	descpb "google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/gofeaturespb"
+	"google.golang.org/protobuf/types/pluginpb"
+
+	hpb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid"
+	opb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque"
+	pb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open"
+)
+
+// TestOpenMangling tests the backwards compatible mangling of fields
+// who clashes with the getters. The expected behavior, which is
+// somewhat surprising, is documented in the proto
+// test_name_clash_open.proto itself.
+func TestOpenMangling(t *testing.T) {
+	m1 := &pb.M1{
+		Foo:       proto.Int32(1),
+		GetFoo_:   proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}
+	if m1.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo(), m1)
+	}
+	if m1.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_(), m1)
+	}
+	if m1.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo(), m1)
+	}
+	m2 := &pb.M2{
+		Foo:       proto.Int32(1),
+		GetFoo_:   proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}
+	if m2.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo(), m2)
+	}
+	if m2.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_(), m2)
+	}
+	if m2.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo(), m2)
+	}
+	m3 := &pb.M3{
+		Foo_:       proto.Int32(1),
+		GetFoo:     proto.Int32(2),
+		GetGetFoo_: proto.Int32(3),
+	}
+	if m3.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_(), m3)
+	}
+	if m3.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo(), m3)
+	}
+	if m3.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo_(), m3)
+	}
+
+	m4 := &pb.M4{
+		GetFoo:     proto.Int32(2),
+		GetGetFoo_: &pb.M4_GetGetGetFoo{GetGetGetFoo: 3},
+		Foo_:       proto.Int32(1),
+	}
+	if m4.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo_(), m4)
+	}
+	if m4.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+
+	m5 := &pb.M5{
+		GetFoo:       proto.Int32(2),
+		GetGetGetFoo: &pb.M5_GetGetFoo_{GetGetFoo_: 3},
+		Foo_:         proto.Int32(1),
+	}
+	if m5.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo_(), m5)
+	}
+	if m5.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo(), m5)
+	}
+	if m5.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo_(), m5)
+	}
+
+	m6 := &pb.M6{
+		GetGetFoo: &pb.M6_GetGetGetFoo{GetGetGetFoo: 3},
+		GetFoo_:   proto.Int32(2),
+		Foo:       proto.Int32(1),
+	}
+	if m6.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo(), m6)
+	}
+	if m6.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo_(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+
+	m7 := &pb.M7{
+		GetGetFoo: &pb.M7_GetFoo_{GetFoo_: 3},
+		Foo:       proto.Int32(1),
+	}
+	if m7.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo(), m7)
+	}
+	if m7.GetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m7.GetGetFoo_(), m7)
+	}
+	m7.GetGetFoo = &pb.M7_Bar{Bar: true}
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected 3)", m7.GetBar(), m7)
+	}
+
+	m8 := &pb.M8{
+		GetGetGetFoo_: &pb.M8_GetGetFoo{GetGetFoo: 3},
+		GetFoo_:       proto.Int32(2),
+		Foo:           proto.Int32(1),
+	}
+	if m8.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo(), m8)
+	}
+	if m8.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo_(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+
+	m9 := &pb.M9{
+		GetGetGetFoo_: &pb.M9_GetGetFoo{GetGetFoo: 3},
+		Foo:           proto.Int32(1),
+	}
+	if m9.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	m9.GetGetGetFoo_ = &pb.M9_GetFoo_{GetFoo_: 2}
+	if m9.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo_(), m9)
+	}
+
+}
+
+// TestHybridMangling tests the backwards compatible mangling as well
+// as new style mangling of fields who clashes with the getters. The
+// expected behavior, which is somewhat surprising, is documented in
+// the proto test_name_clash_hybrid.proto itself.
+func TestHybridMangling(t *testing.T) {
+	m1 := hpb.M1_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m1.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo(), m1)
+	}
+	if m1.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo(), m1)
+	}
+	if m1.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_(), m1)
+	}
+	if m1.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo_(), m1)
+	}
+	if m1.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo(), m1)
+	}
+	checkNameConsistency(t, m1)
+	m2 := hpb.M2_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m2.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo(), m2)
+	}
+	if m2.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo(), m2)
+	}
+	if m2.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_(), m2)
+	}
+	if m2.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo_(), m2)
+	}
+	if m2.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo(), m2)
+	}
+	checkNameConsistency(t, m2)
+	m3 := hpb.M3_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m3.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_(), m3)
+	}
+	if m3.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo_(), m3)
+	}
+	if m3.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo(), m3)
+	}
+	if m3.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo(), m3)
+	}
+	if m3.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo_(), m3)
+	}
+	checkNameConsistency(t, m3)
+
+	m4 := hpb.M4_builder{
+		GetFoo:       proto.Int32(2),
+		GetGetGetFoo: proto.Int32(3),
+		Foo:          proto.Int32(1),
+	}.Build()
+	if m4.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo_(), m4)
+	}
+	if m4.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.Get_Foo(), m4)
+	}
+	if m4.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo(), m4)
+	}
+	if m4.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.Get_GetFoo(), m4)
+	}
+	if m4.GetGetGetFoo_().(*hpb.M4_GetGetGetFoo).GetGetGetFoo != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetFoo_(), m4)
+	}
+	if !m4.HasGetGetFoo() {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected true)", m4.HasGetGetFoo(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+	checkNameConsistency(t, m4)
+
+	m5 := hpb.M5_builder{
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m5.GetFoo_() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo_(), m5)
+	}
+	if m5.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.Get_Foo(), m4)
+	}
+	if m5.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo(), m5)
+	}
+	if m5.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.Get_GetFoo(), m4)
+	}
+	if m5.GetGetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo_(), m5)
+	}
+	if m5.Get_GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.Get_GetGetFoo(), m5)
+	}
+	checkNameConsistency(t, m5)
+
+	m6 := hpb.M6_builder{
+		GetGetGetFoo: proto.Int32(3),
+		GetFoo:       proto.Int32(2),
+		Foo:          proto.Int32(1),
+	}.Build()
+	if m6.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo(), m6)
+	}
+	if m6.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.Get_Foo(), m6)
+	}
+	if m6.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo_(), m6)
+	}
+	if m6.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.Get_GetFoo(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+	checkNameConsistency(t, m6)
+
+	m7 := hpb.M7_builder{
+		GetFoo: proto.Int32(3),
+		Foo:    proto.Int32(1),
+	}.Build()
+	if m7.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo(), m7)
+	}
+	if m7.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.Get_Foo(), m7)
+	}
+	if m7.GetGetFoo_() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.GetGetFoo_(), m7)
+	}
+	if m7.Get_GetFoo() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.Get_GetFoo(), m7)
+	}
+	m7.SetBar(true)
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected 3)", m7.GetBar(), m7)
+	}
+	checkNameConsistency(t, m7)
+
+	m8 := hpb.M8_builder{
+		GetGetFoo: proto.Int32(3),
+		GetFoo:    proto.Int32(2),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m8.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo(), m8)
+	}
+	if m8.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.Get_Foo(), m8)
+	}
+	if m8.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo_(), m8)
+	}
+	if m8.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.Get_GetFoo(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+	checkNameConsistency(t, m8)
+
+	m9 := hpb.M9_builder{
+		GetGetFoo: proto.Int32(3),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m9.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo(), m9)
+	}
+	if m9.Get_Foo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.Get_Foo(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	if m9.Get_GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.Get_GetGetFoo(), m9)
+	}
+	m9.Set_GetFoo(2)
+	if m9.GetGetFoo_() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo_(), m9)
+	}
+	if m9.Get_GetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.Get_GetFoo(), m9)
+	}
+	checkNameConsistency(t, m9)
+	m10 := hpb.M10_builder{
+		Foo:    proto.Int32(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m10.Set_Foo(47)
+	if m10.Get_Foo() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m10.Get_Foo(), m10)
+	}
+	m10.SetSetFoo(11)
+	if m10.GetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m10.GetSetFoo(), m10)
+	}
+	checkNameConsistency(t, m10)
+	m11 := hpb.M11_builder{
+		Foo:       proto.Int32(1),
+		SetSetFoo: proto.Int32(2),
+	}.Build()
+	m11.Set_Foo(47)
+	if m11.Get_Foo() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m11.Get_Foo(), m11)
+	}
+	m11.SetSetSetFoo(11)
+	if m11.GetSetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m11.GetSetSetFoo(), m11)
+	}
+	checkNameConsistency(t, m11)
+	m12 := hpb.M12_builder{
+		Foo:    proto.Int32(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m12.Set_Foo(47)
+	if m12.Get_Foo() != 47 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 47)", m12.Get_Foo(), m12)
+	}
+	m12.Set_SetFoo(11)
+	if m12.Get_SetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m12.Get_SetFoo(), m12)
+	}
+	checkNameConsistency(t, m12)
+	m13 := hpb.M13_builder{
+		Foo:    proto.Int32(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m13.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.Has_Foo(), m13)
+	}
+	if !m13.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasHasFoo(), m13)
+	}
+	checkNameConsistency(t, m13)
+	m14 := hpb.M14_builder{
+		Foo:       proto.Int32(1),
+		HasHasFoo: proto.Int32(2),
+	}.Build()
+	if !m14.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.Has_Foo(), m14)
+	}
+	if !m14.Has_HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.Has_HasFoo(), m14)
+	}
+	if !m14.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasHasFoo(), m14)
+	}
+	checkNameConsistency(t, m14)
+	m15 := hpb.M15_builder{
+		Foo:    proto.Int32(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m15.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.Has_Foo(), m15)
+	}
+	if !m15.Has_HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.Has_HasFoo(), m15)
+	}
+	if !m15.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasHasFoo(), m15)
+	}
+	checkNameConsistency(t, m15)
+	m16 := hpb.M16_builder{
+		Foo:      proto.Int32(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m16.Clear_Foo()
+	if m16.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.Has_Foo(), m16)
+	}
+	m16.ClearClearFoo()
+	if m16.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasClearFoo(), m16)
+	}
+	checkNameConsistency(t, m16)
+	m17 := hpb.M17_builder{
+		Foo:           proto.Int32(1),
+		ClearClearFoo: proto.Int32(2),
+	}.Build()
+	m17.Clear_Foo()
+	if m17.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.Has_Foo(), m17)
+	}
+	m17.ClearClearClearFoo()
+	if m17.HasClearClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasClearClearFoo(), m17)
+	}
+	checkNameConsistency(t, m17)
+	m18 := hpb.M18_builder{
+		Foo:      proto.Int32(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m18.Clear_Foo()
+	if m18.Has_Foo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.Has_Foo(), m18)
+	}
+	m18.Clear_ClearFoo()
+	if m18.Has_ClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.Has_ClearFoo(), m18)
+	}
+	checkNameConsistency(t, m18)
+	m19 := hpb.M19_builder{
+		Foo:      proto.Int32(1),
+		WhichFoo: proto.Int32(2),
+	}.Build()
+	if m19.WhichWhichWhichFoo() != hpb.M19_WhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M19_ClearFoo_case)", m19.WhichWhichWhichFoo(), m19)
+	}
+	checkNameConsistency(t, m19)
+	m20 := hpb.M20_builder{
+		Foo:           proto.Int32(1),
+		WhichWhichFoo: proto.Int32(2),
+	}.Build()
+	if m20.Which_WhichFoo() != hpb.M20_WhichWhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M20_WhichWhichFoo_case)", m20.Which_WhichFoo(), m20)
+	}
+	checkNameConsistency(t, m20)
+
+}
+
+// TestOpaqueMangling tests the backwards compatible mangling as well
+// as new style mangling of fields who clashes with the getters. The
+// expected behavior, which is somewhat surprising, is documented in
+// the proto test_name_clash_opaque.proto itself.
+func TestOpaqueMangling(t *testing.T) {
+	m1 := opb.M1_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m1.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m1.GetFoo(), m1)
+	}
+	if m1.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m1.GetGetFoo(), m1)
+	}
+	if m1.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m1.GetGetGetFoo(), m1)
+	}
+	checkNameConsistency(t, m1)
+	m2 := opb.M2_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m2.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m2.GetFoo(), m2)
+	}
+	if m2.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m2.GetGetFoo(), m2)
+	}
+	if m2.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m2.GetGetGetFoo(), m2)
+	}
+	checkNameConsistency(t, m2)
+	m3 := opb.M3_builder{
+		Foo:       proto.Int32(1),
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+	}.Build()
+	if m3.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m3.GetFoo(), m3)
+	}
+	if m3.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m3.GetGetFoo(), m3)
+	}
+	if m3.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m3.GetGetGetFoo(), m3)
+	}
+	checkNameConsistency(t, m3)
+
+	m4 := opb.M4_builder{
+		GetFoo:       proto.Int32(2),
+		GetGetGetFoo: proto.Int32(3),
+		Foo:          proto.Int32(1),
+	}.Build()
+	if m4.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m4.GetFoo(), m4)
+	}
+	if m4.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m4.GetGetFoo(), m4)
+	}
+	if !m4.HasGetGetFoo() {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected true)", m4.HasGetGetFoo(), m4)
+	}
+	if m4.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m4.GetGetGetGetFoo(), m4)
+	}
+	checkNameConsistency(t, m4)
+
+	m5 := opb.M5_builder{
+		GetFoo:    proto.Int32(2),
+		GetGetFoo: proto.Int32(3),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m5.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m5.GetFoo(), m5)
+	}
+	if m5.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m5.GetGetFoo(), m5)
+	}
+	if m5.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m5.GetGetGetFoo(), m5)
+	}
+	checkNameConsistency(t, m5)
+
+	m6 := opb.M6_builder{
+		GetGetGetFoo: proto.Int32(3),
+		GetFoo:       proto.Int32(2),
+		Foo:          proto.Int32(1),
+	}.Build()
+	if m6.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m6.GetFoo(), m6)
+	}
+	if m6.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m6.GetGetFoo(), m6)
+	}
+	if m6.GetGetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_get_foo' has unexpected value %v for %T (expected 3)", m6.GetGetGetGetFoo(), m6)
+	}
+	checkNameConsistency(t, m6)
+
+	m7 := opb.M7_builder{
+		GetFoo: proto.Int32(3),
+		Foo:    proto.Int32(1),
+	}.Build()
+	if m7.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m7.GetFoo(), m7)
+	}
+	if m7.GetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 3)", m7.GetGetFoo(), m7)
+	}
+	m7.SetBar(true)
+	if !m7.GetBar() {
+		t.Errorf("Proto field 'bar' has unexpected value %v for %T (expected true)", m7.GetBar(), m7)
+	}
+	checkNameConsistency(t, m7)
+
+	m8 := opb.M8_builder{
+		GetGetFoo: proto.Int32(3),
+		GetFoo:    proto.Int32(2),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m8.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m8.GetFoo(), m8)
+	}
+	if m8.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m8.GetGetFoo(), m8)
+	}
+	if m8.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m8.GetGetGetFoo(), m8)
+	}
+	checkNameConsistency(t, m8)
+
+	m9 := opb.M9_builder{
+		GetGetFoo: proto.Int32(3),
+		Foo:       proto.Int32(1),
+	}.Build()
+	if m9.GetFoo() != 1 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 1)", m9.GetFoo(), m9)
+	}
+	if m9.GetGetGetFoo() != 3 {
+		t.Errorf("Proto field 'get_get_foo' has unexpected value %v for %T (expected 3)", m9.GetGetGetFoo(), m9)
+	}
+	m9.SetGetFoo(2)
+	if m9.GetGetFoo() != 2 {
+		t.Errorf("Proto field 'get_foo' has unexpected value %v for %T (expected 2)", m9.GetGetFoo(), m9)
+	}
+	checkNameConsistency(t, m9)
+	m10 := opb.M10_builder{
+		Foo:    proto.Int32(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m10.SetFoo(48)
+	if m10.GetFoo() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m10.GetFoo(), m10)
+	}
+	m10.SetSetFoo(11)
+	if m10.GetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m10.GetSetFoo(), m10)
+	}
+	checkNameConsistency(t, m10)
+	m11 := opb.M11_builder{
+		Foo:       proto.Int32(1),
+		SetSetFoo: proto.Int32(2),
+	}.Build()
+	m11.SetFoo(48)
+	if m11.GetFoo() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m11.GetFoo(), m11)
+	}
+	m11.SetSetSetFoo(11)
+	if m11.GetSetSetFoo() != 11 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 11)", m11.GetSetSetFoo(), m11)
+	}
+	checkNameConsistency(t, m11)
+	m12 := opb.M12_builder{
+		Foo:    proto.Int32(1),
+		SetFoo: proto.Int32(2),
+	}.Build()
+	m12.SetFoo(48)
+	if m12.GetFoo() != 48 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 48)", m12.GetFoo(), m12)
+	}
+	m12.SetSetFoo(12)
+	if m12.GetSetFoo() != 12 {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected 12)", m12.GetSetFoo(), m12)
+	}
+	checkNameConsistency(t, m12)
+	m13 := opb.M13_builder{
+		Foo:    proto.Int32(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m13.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasFoo(), m13)
+	}
+	if !m13.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m13.HasHasFoo(), m13)
+	}
+	checkNameConsistency(t, m13)
+	m14 := opb.M14_builder{
+		Foo:       proto.Int32(1),
+		HasHasFoo: proto.Int32(2),
+	}.Build()
+	if !m14.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasFoo(), m14)
+	}
+	if !m14.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasFoo(), m14)
+	}
+	if !m14.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m14.HasHasHasFoo(), m14)
+	}
+	checkNameConsistency(t, m14)
+	m15 := opb.M15_builder{
+		Foo:    proto.Int32(1),
+		HasFoo: proto.Int32(2),
+	}.Build()
+	if !m15.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasFoo(), m15)
+	}
+	if !m15.HasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasFoo(), m15)
+	}
+	if !m15.HasHasHasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected true)", m15.HasHasHasFoo(), m15)
+	}
+	checkNameConsistency(t, m15)
+	m16 := opb.M16_builder{
+		Foo:      proto.Int32(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m16.SetFoo(4711)
+	m16.ClearFoo()
+	if m16.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasFoo(), m16)
+	}
+	m16.ClearClearFoo()
+	if m16.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m16.HasClearFoo(), m16)
+	}
+	checkNameConsistency(t, m16)
+	m17 := opb.M17_builder{
+		Foo:           proto.Int32(1),
+		ClearClearFoo: proto.Int32(2),
+	}.Build()
+	m17.SetFoo(4711)
+	m17.ClearFoo()
+	if m17.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasFoo(), m17)
+	}
+	m17.ClearClearClearFoo()
+	if m17.HasClearClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m17.HasClearClearFoo(), m17)
+	}
+	checkNameConsistency(t, m17)
+	m18 := opb.M18_builder{
+		Foo:      proto.Int32(1),
+		ClearFoo: proto.Int32(2),
+	}.Build()
+	m18.SetFoo(4711)
+	m18.ClearFoo()
+	if m18.HasFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.HasFoo(), m18)
+	}
+	m18.SetClearFoo(13)
+	m18.ClearClearFoo()
+	if m18.HasClearFoo() {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected false)", m18.HasClearFoo(), m18)
+	}
+	checkNameConsistency(t, m18)
+	m19 := opb.M19_builder{
+		Foo:      proto.Int32(1),
+		WhichFoo: proto.Int32(2),
+	}.Build()
+	if m19.WhichWhichWhichFoo() != opb.M19_WhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M19_ClearFoo_case)", m19.WhichWhichWhichFoo(), m19)
+	}
+	checkNameConsistency(t, m19)
+	m20 := opb.M20_builder{
+		Foo:           proto.Int32(1),
+		WhichWhichFoo: proto.Int32(2),
+	}.Build()
+	if m20.WhichWhichFoo() != opb.M20_WhichWhichFoo_case {
+		t.Errorf("Proto field 'foo' has unexpected value %v for %T (expected M20_WhichWhichFoo_case)", m20.WhichWhichFoo(), m20)
+	}
+	checkNameConsistency(t, m20)
+
+}
+
+func protogenFor(t *testing.T, m proto.Message) *protogen.Message {
+	t.Helper()
+
+	md := m.ProtoReflect().Descriptor()
+
+	// Construct a Protobuf plugin code generation request based on the
+	// transitive closure of dependencies of message m.
+	req := &pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descpb.FileDescriptorProto{
+			protodesc.ToFileDescriptorProto(descpb.File_google_protobuf_descriptor_proto),
+			protodesc.ToFileDescriptorProto(gofeaturespb.File_google_protobuf_go_features_proto),
+			protodesc.ToFileDescriptorProto(md.ParentFile()),
+		},
+	}
+	plugin, err := protogen.Options{}.New(req)
+	if err != nil {
+		t.Fatalf("protogen.Options.New: %v", err)
+	}
+	if got, want := len(plugin.Files), len(req.ProtoFile); got != want {
+		t.Fatalf("protogen returned %d plugin.Files entries, expected %d", got, want)
+	}
+	file := plugin.Files[len(plugin.Files)-1]
+	for _, msg := range file.Messages {
+		if msg.Desc.FullName() != md.FullName() {
+			continue
+		}
+		return msg
+	}
+	t.Fatalf("BUG: message %q not found in protogen response", md.FullName())
+	return nil
+}
+
+// checkNameConsistency will go through the fields (deliberately avoiding
+// protoreflect; querying protogen instead), and for each field check that one
+// of the two naming schemes is used consistently, so that if you find
+// e.g. Has_Foo, the setter will be Set_Foo and not SetFoo. It also checks that
+// at least one of the naming schemes apply.
+func checkNameConsistency(t *testing.T, m proto.Message) {
+	t.Helper()
+	// It's wrong to use Go reflection on a Message, but in this
+	// case, we're specifically looking at the implementation
+	typ := reflect.TypeOf(m)
+	// The info we need for one field
+	type fi struct {
+		name     string
+		prefixes []string
+	}
+	// The method prefixes for different kinds of fields
+	repeatedPrefixes := []string{"Get", "Set"}
+	oneofPrefixes := []string{"Has", "Clear", "Which"}
+	optionalPrefixes := []string{"Get", "Set", "Has", "Clear"}
+
+	fields := []fi{}
+	msg := protogenFor(t, m)
+	for _, f := range msg.Fields {
+		prefixes := optionalPrefixes
+		if f.Desc.Cardinality() == genid.Field_CARDINALITY_REPEATED_enum_value {
+			prefixes = repeatedPrefixes
+		}
+		fields = append(fields, fi{name: f.GoName, prefixes: prefixes})
+	}
+	for _, o := range msg.Oneofs {
+		fields = append(fields, fi{name: o.GoName, prefixes: oneofPrefixes})
+	}
+	if len(fields) == 0 {
+		t.Errorf("Message %v checked for consistency has no fields to check", reflect.TypeOf(m))
+	}
+	// Check method names for all fields
+	for _, f := range fields {
+		// Remove trailing underscores added by old name mangling algorithm
+		for f.name[len(f.name)-1] == '_' {
+			f.name = f.name[:len(f.name)-1]
+		}
+		// Check consistency of either "underscored" methods or "non underscored"
+		found := ""
+		for _, infix := range []string{"_", ""} {
+			for _, prefix := range f.prefixes {
+				if m, ok := typ.MethodByName(prefix + infix + f.name); ok {
+					found = m.Name
+					break
+				}
+			}
+			if found != "" {
+				for _, prefix := range f.prefixes {
+					if _, ok := typ.MethodByName(prefix + infix + f.name); !ok {
+						t.Errorf("Field %s has inconsistent method names - found %s, but not %s", f.name, found, prefix+infix+f.name)
+					}
+				}
+				break
+			}
+		}
+		// If we found neither, something is wrong
+		if found == "" {
+			t.Errorf("Field %s has neither plain nor underscored methods.", f.name)
+		}
+
+	}
+}
diff --git a/cmd/protoc-gen-go/opaque_default_test/opaque_default_test.go b/cmd/protoc-gen-go/opaque_default_test/opaque_default_test.go
new file mode 100644
index 0000000..fb9349b
--- /dev/null
+++ b/cmd/protoc-gen-go/opaque_default_test/opaque_default_test.go
@@ -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.
+
+package opaque_default_test
+
+import (
+	"testing"
+
+	enumopaquepb "google.golang.org/protobuf/internal/testprotos/enums/enums_opaque"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+// From the spec: "Proto2 enums use the first syntactic entry in the enum
+// declaration as the default value where it is otherwise unspecified."
+func TestOpaqueEnumDefaults(t *testing.T) {
+	m := &testopaquepb.RemoteDefault{}
+	if got, want := m.GetDefault(), enumopaquepb.Enum_DEFAULT; got != want {
+		t.Errorf("default enum value: got %v, expected %v", got, want)
+	}
+}
diff --git a/cmd/protoc-gen-go/opaque_map_test/opaque_map_test.go b/cmd/protoc-gen-go/opaque_map_test/opaque_map_test.go
new file mode 100644
index 0000000..a856df2
--- /dev/null
+++ b/cmd/protoc-gen-go/opaque_map_test/opaque_map_test.go
@@ -0,0 +1,35 @@
+// 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 opaque_map_test
+
+import (
+	"testing"
+
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+func TestOpaqueMap(t *testing.T) {
+	m := &testopaquepb.TestAllTypes{}
+
+	m.SetMapStringString(map[string]string{"one": "eins"})
+	if got, want := len(m.GetMapStringString()), 1; got != want {
+		t.Errorf("after setting map_string_string to a non-empty map: len(m.GetMapStringString()) = %v, want %v", got, want)
+	}
+	delete(m.GetMapStringString(), "one")
+	if got, want := len(m.GetMapStringString()), 0; got != want {
+		t.Errorf("after removing all elements from m_one: len(m.GetMapStringString()) = %v, want %v", got, want)
+	}
+	if got := m.GetMapStringString(); got == nil {
+		t.Errorf("after removing all elements from m_one: m.GetMapStringString() = nil, want non-nil map")
+	}
+	m.GetMapStringString()["two"] = "zwei"
+	if got, want := len(m.GetMapStringString()), 1; got != want {
+		t.Errorf("after adding new element to m_one: len(m.GetMapStringString()) = %v, want %v", got, want)
+	}
+	m.SetMapStringString(map[string]string{})
+	if got := m.GetMapStringString(); got == nil {
+		t.Errorf("after setting m_one to an empty map: m.GetMapStringString() = nil, want non-nil map")
+	}
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/nameclash.go b/cmd/protoc-gen-go/testdata/nameclash/nameclash.go
new file mode 100644
index 0000000..dc63532
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/nameclash.go
@@ -0,0 +1,16 @@
+// Copyright 2019 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 nameclash is imported by gen_test.go and itself imports the various
+// nameclash proto variants.
+package nameclash
+
+import (
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid"
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3"
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque"
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3"
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open"
+	_ "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3"
+)
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid.proto
new file mode 100644
index 0000000..d15394c
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid.proto
@@ -0,0 +1,333 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashhybrid;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid";
+
+option features.(pb.go).api_level = API_HYBRID;
+
+message M1 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 foo = 1;
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+}
+
+message M2 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 get_get_foo = 3;
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M3 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+  int32 foo = 1;
+}
+
+message M4 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  int32 get_foo = 2;
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M5 {
+  // Old Scheme:
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  int32 get_foo = 2;
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // Bar                    | -                  | -    | GetBar
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+  int32 foo = 1;
+}
+
+message M10 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | -                  | SetSetFoo
+  int32 foo = 1;
+  int32 set_foo = 2;
+}
+
+message M11 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetSetFoo              | -                  | SetSetSetFoo
+  int32 foo = 1;
+  oneof set_foo {
+    int32 set_set_foo = 2;
+  }
+}
+
+message M12 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | set_set_foo        | Set_SetFoo
+  int32 foo = 1;
+  oneof set_set_foo {
+    int32 set_foo = 2;
+  }
+}
+
+message M13 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | -                  | HasHasFoo
+  int32 foo = 1;
+  int32 has_foo = 2;
+}
+
+message M14 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  int32 foo = 1;
+  oneof has_foo {
+    int32 has_has_foo = 2;
+  }
+}
+
+message M15 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  int32 foo = 1;
+  oneof has_has_foo {
+    int32 has_foo = 2;
+  }
+}
+
+message M16 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo            | Clear_Foo
+  // ClearFoo               | -                  | ClearClearFoo
+  int32 foo = 1;
+  int32 clear_foo = 2;
+}
+
+message M17 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  int32 foo = 1;
+  oneof clear_foo {
+    int32 clear_clear_foo = 2;
+  }
+}
+
+message M18 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  int32 foo = 1;
+  oneof clear_clear_foo {
+    int32 clear_foo = 2;
+  }
+}
+
+message M19 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | -                  | -
+  // WhichWhichFoo          | -                  | WhichWhichWhichFoo
+  int32 foo = 1;
+  oneof which_which_foo {
+    int32 which_foo = 2;
+  }
+}
+
+message M20 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | which_which_foo    | Which_WhichFoo
+  // WhichWhichFoo          | -                  | -
+  int32 foo = 1;
+  oneof which_foo {
+    int32 which_which_foo = 2;
+  }
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3.proto
new file mode 100644
index 0000000..8be92f6
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3.proto
@@ -0,0 +1,338 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashhybrid3;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_hybrid3";
+
+option features.field_presence = IMPLICIT;
+option features.(pb.go).api_level = API_HYBRID;
+
+message M0 {
+  int32 i1 = 1;
+}
+
+message M1 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 foo = 1;
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+}
+
+message M2 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 get_get_foo = 3;
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M3 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+  M0 foo = 1;
+}
+
+message M4 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  M0 get_foo = 2;
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M5 {
+  // Old Scheme:
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  M0 get_foo = 2;
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // Bar                    | -                  | -    | GetBar
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+  M0 foo = 1;
+}
+
+message M10 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | -                  | SetSetFoo
+  M0 foo = 1;
+  M0 set_foo = 2;
+}
+
+message M11 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetSetFoo              | -                  | SetSetSetFoo
+  M0 foo = 1;
+  oneof set_foo {
+    int32 set_set_foo = 2;
+  }
+}
+
+message M12 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | set_set_foo        | Set_SetFoo
+  M0 foo = 1;
+  oneof set_set_foo {
+    int32 set_foo = 2;
+  }
+}
+
+message M13 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | -                  | HasHasFoo
+  M0 foo = 1;
+  M0 has_foo = 2;
+}
+
+message M14 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  M0 foo = 1;
+  oneof has_foo {
+    int32 has_has_foo = 2;
+  }
+}
+
+message M15 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  M0 foo = 1;
+  oneof has_has_foo {
+    int32 has_foo = 2;
+  }
+}
+
+message M16 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo            | Clear_Foo
+  // ClearFoo               | -                  | ClearClearFoo
+  M0 foo = 1;
+  M0 clear_foo = 2;
+}
+
+message M17 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  M0 foo = 1;
+  oneof clear_foo {
+    int32 clear_clear_foo = 2;
+  }
+}
+
+message M18 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  M0 foo = 1;
+  oneof clear_clear_foo {
+    int32 clear_foo = 2;
+  }
+}
+
+message M19 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | -                  | -
+  // WhichWhichFoo          | -                  | WhichWhichWhichFoo
+  M0 foo = 1;
+  oneof which_which_foo {
+    int32 which_foo = 2;
+  }
+}
+
+message M20 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | which_which_foo    | Which_WhichFoo
+  // WhichWhichFoo          | -                  | -
+  M0 foo = 1;
+  oneof which_foo {
+    int32 which_which_foo = 2;
+  }
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque.proto
new file mode 100644
index 0000000..e23674c
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque.proto
@@ -0,0 +1,333 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashopaque;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque";
+
+option features.(pb.go).api_level = API_OPAQUE;
+
+message M1 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 foo = 1;
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+}
+
+message M2 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 get_get_foo = 3;
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M3 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+  int32 foo = 1;
+}
+
+message M4 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  int32 get_foo = 2;
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M5 {
+  // Old Scheme:
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  int32 get_foo = 2;
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // Bar                    | -                  | -    | GetBar
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+  int32 foo = 1;
+}
+
+message M10 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | -                  | SetSetFoo
+  int32 foo = 1;
+  int32 set_foo = 2;
+}
+
+message M11 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetSetFoo              | -                  | SetSetSetFoo
+  int32 foo = 1;
+  oneof set_foo {
+    int32 set_set_foo = 2;
+  }
+}
+
+message M12 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | set_set_foo        | Set_SetFoo
+  int32 foo = 1;
+  oneof set_set_foo {
+    int32 set_foo = 2;
+  }
+}
+
+message M13 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | -                  | HasHasFoo
+  int32 foo = 1;
+  int32 has_foo = 2;
+}
+
+message M14 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  int32 foo = 1;
+  oneof has_foo {
+    int32 has_has_foo = 2;
+  }
+}
+
+message M15 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  int32 foo = 1;
+  oneof has_has_foo {
+    int32 has_foo = 2;
+  }
+}
+
+message M16 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo            | Clear_Foo
+  // ClearFoo               | -                  | ClearClearFoo
+  int32 foo = 1;
+  int32 clear_foo = 2;
+}
+
+message M17 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  int32 foo = 1;
+  oneof clear_foo {
+    int32 clear_clear_foo = 2;
+  }
+}
+
+message M18 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  int32 foo = 1;
+  oneof clear_clear_foo {
+    int32 clear_foo = 2;
+  }
+}
+
+message M19 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | -                  | -
+  // WhichWhichFoo          | -                  | WhichWhichWhichFoo
+  int32 foo = 1;
+  oneof which_which_foo {
+    int32 which_foo = 2;
+  }
+}
+
+message M20 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | which_which_foo    | Which_WhichFoo
+  // WhichWhichFoo          | -                  | -
+  int32 foo = 1;
+  oneof which_foo {
+    int32 which_which_foo = 2;
+  }
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3.proto
new file mode 100644
index 0000000..fdafeee
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3.proto
@@ -0,0 +1,338 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashopaque3;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_opaque3";
+
+option features.field_presence = IMPLICIT;
+option features.(pb.go).api_level = API_OPAQUE;
+
+message M0 {
+  int32 i1 = 1;
+}
+
+message M1 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 foo = 1;
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+}
+
+message M2 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 get_get_foo = 3;
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M3 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | -                  | -    | GetGetGetFoo
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+  M0 foo = 1;
+}
+
+message M4 {
+  // Old Scheme:
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  M0 get_foo = 2;
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M5 {
+  // Old Scheme:
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  M0 get_foo = 2;
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetGetFoo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // Bar                    | -                  | -    | GetBar
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+  M0 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  // New Scheme:
+  // initial name in Go     | Clashes with field | type | Getter name
+  // Foo                    | get_foo            | G    | Get_Foo
+  // GetFoo                 | get_get_foo        | G    | Get_GetFoo
+  // GetGetFoo              | get_get_get_foo    | G    | Get_GetGetFoo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+  M0 foo = 1;
+}
+
+message M10 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | -                  | SetSetFoo
+  M0 foo = 1;
+  M0 set_foo = 2;
+}
+
+message M11 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetSetFoo              | -                  | SetSetSetFoo
+  M0 foo = 1;
+  oneof set_foo {
+    int32 set_set_foo = 2;
+  }
+}
+
+message M12 {
+  // Set Clashes - no concerns with get-mangling as legacy open struct
+  // does not have setters except for weak fields:
+  // initial name in Go     | Clashes with field | Setter name
+  // Foo                    | set_foo            | Set_Foo
+  // SetFoo                 | set_set_foo        | Set_SetFoo
+  M0 foo = 1;
+  oneof set_set_foo {
+    int32 set_foo = 2;
+  }
+}
+
+message M13 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | -                  | HasHasFoo
+  M0 foo = 1;
+  M0 has_foo = 2;
+}
+
+message M14 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  M0 foo = 1;
+  oneof has_foo {
+    int32 has_has_foo = 2;
+  }
+}
+
+message M15 {
+  // Has Clashes - no concerns with get-mangling as legacy open struct
+  // does not have hassers except for weak fields:
+  // initial name in Go     | Clashes with field | Hasser name
+  // Foo                    | has_foo            | Has_Foo
+  // HasFoo                 | has_has_foo        | Has_HasFoo
+  // HasHasFoo              | -                  | HasHasHasFoo
+  M0 foo = 1;
+  oneof has_has_foo {
+    int32 has_foo = 2;
+  }
+}
+
+message M16 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo            | Clear_Foo
+  // ClearFoo               | -                  | ClearClearFoo
+  M0 foo = 1;
+  M0 clear_foo = 2;
+}
+
+message M17 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  M0 foo = 1;
+  oneof clear_foo {
+    int32 clear_clear_foo = 2;
+  }
+}
+
+message M18 {
+  // Clear Clashes - no concerns with get-mangling as legacy open
+  // struct does not have clearers except for weak fields:
+  // initial name in Go     | Clashes with field | Clearer name
+  // Foo                    | clear_foo          | Clear_Foo
+  // ClearFoo               | clear_clear_foo    | Clear_ClearFoo
+  // ClearClearFoo          | -                  | ClearClearClearFoo
+  M0 foo = 1;
+  oneof clear_clear_foo {
+    int32 clear_foo = 2;
+  }
+}
+
+message M19 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | -                  | -
+  // WhichWhichFoo          | -                  | WhichWhichWhichFoo
+  M0 foo = 1;
+  oneof which_which_foo {
+    int32 which_foo = 2;
+  }
+}
+
+message M20 {
+  // Which Clashes - no concerns with get-mangling as legacy open
+  // struct does not have whichers except for weak fields:
+  // initial name in Go     | Clashes with field | Whicher name
+  // Foo                    | -                  | -
+  // WhichFoo               | which_which_foo    | Which_WhichFoo
+  // WhichWhichFoo          | -                  | -
+  M0 foo = 1;
+  oneof which_foo {
+    int32 which_which_foo = 2;
+  }
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open.proto
new file mode 100644
index 0000000..10775a9
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open.proto
@@ -0,0 +1,151 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashopen;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open";
+
+option features.(pb.go).api_level = API_OPEN;
+
+message M1 {
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  int32 foo = 1;
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+}
+
+message M2 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  int32 get_get_foo = 3;
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M3 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  int32 get_foo = 2;
+  int32 get_get_foo = 3;
+  int32 foo = 1;
+}
+
+message M4 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  int32 get_foo = 2;
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M5 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  int32 get_foo = 2;
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+  int32 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+  int32 get_foo = 2;
+  int32 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+  int32 foo = 1;
+}
diff --git a/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3.proto b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3.proto
new file mode 100644
index 0000000..442c046
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3.proto
@@ -0,0 +1,164 @@
+// 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.
+
+// This proto verifies that we keep the name mangling algorithm (which is
+// position dependent) intact in the protoc_gen_go generator. The field names
+// and the getter names have to be kept intact over time, both in the OPEN and
+// in the HYBRID API. How fields are "mangled" is described in a comment per
+// field.
+
+// The order of "evaluation" of fields is important. Fields are evaluated in
+// order of appearance, except the oneof union names, that are evaluated after
+// their first member. For each field, check if there is a previous field name
+// or getter name that clashes with this field or it's getter. In case there is
+// a clash, add an _ to the field name and repeat. In the case of oneof's, the
+// union will be renamed if it clashes with it's first member, but not if it
+// clashes with it's second.
+
+// This scheme is here for backwards compatibility.
+// The type of clashes that can be are the following:
+// 1 - My field name clashes with their getter name
+// 2 - My getter name clashes with their field name
+
+edition = "2023";
+
+package net.proto2.go.testdata.nameclashopen3;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/nameclash/test_name_clash_open3";
+
+option features.field_presence = IMPLICIT;
+option features.(pb.go).api_level = API_OPEN;
+
+message M0 {
+  int32 i1 = 1;
+}
+
+message M1 {
+  // initial name in Go     | Clashes with field | type | final name
+  // Foo                    | -                  | -    | Foo
+  // GetFoo                 | foo                | 1    | GetFoo_
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  M0 foo = 1;
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+}
+
+message M2 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  M0 get_get_foo = 3;
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M3 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  // Foo                    | get_foo            | 2    | Foo_
+  M0 get_foo = 2;
+  M0 get_get_foo = 3;
+  M0 foo = 1;
+}
+
+message M4 {
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetFoo              | get_foo            | 1    | GetGetFoo_
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  M0 get_foo = 2;
+
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+
+  M0 foo = 1;
+}
+
+message M5 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetFoo                 | -                  | -    | GetFoo
+  // GetGetGetFoo           | -                  | -    | GetGetGetFoo
+  //  GetGetFoo             | get_foo            | 1    | GetGetFoo_
+  //                        |                    |      |
+  // Foo                    | get_foo            | 2    | Foo_
+  M0 get_foo = 2;
+
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+
+  M0 foo = 1;
+}
+
+message M6 {
+  // Note evaluation order - get_get_get_foo before get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  GetGetGetFoo          | -                  | -    | GetGetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_foo {
+    int32 get_get_get_foo = 3;
+  }
+
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M7 {
+  // Note evaluation order - bar before get_get_foo, then get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetFoo              | -                  | -    | GetGetFoo
+  //  Bar                   | -                  | -    | Bar
+  //  GetFoo                | foo                | 1    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_foo {
+    bool bar = 4;
+    int32 get_foo = 3;
+  }
+
+  M0 foo = 1;
+}
+
+message M8 {
+  // Note evaluation order - get_get_foo before get_get_get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //                        |                    |      |
+  // GetFoo                 | get_get_foo        | 2    | GetFoo_
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+  }
+
+  M0 get_foo = 2;
+  M0 foo = 1;
+}
+
+message M9 {
+  // Note evaluation order - get_get_foo before get_get_get_foo, then get_foo
+  // initial name in Go     | Clashes with field | type | final name
+  // GetGetGetFoo           | get_get_foo        | 1    | GetGetGetFoo_
+  //  GetGetFoo             | -                  | -    | GetGetFoo
+  //  GetFoo                | get_get_foo        | 2    | GetFoo_
+  //                        |                    |      |
+  // Foo                    | -                  | -    | Foo
+  oneof get_get_get_foo {
+    int32 get_get_foo = 3;
+    int32 get_foo = 2;
+  }
+
+  M0 foo = 1;
+}
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index c2c9e9d..0bff637 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -35,9 +35,9 @@
 	"google.golang.org/protobuf/reflect/protodesc"
 	"google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/types/dynamicpb"
 
 	"google.golang.org/protobuf/types/descriptorpb"
-	"google.golang.org/protobuf/types/dynamicpb"
 	"google.golang.org/protobuf/types/gofeaturespb"
 	"google.golang.org/protobuf/types/pluginpb"
 )
@@ -166,6 +166,10 @@
 	// This struct field is for internal use by Go Protobuf only. Do not use it,
 	// we might remove it at any time.
 	InternalStripForEditionsDiff *bool
+
+	// DefaultAPILevel overrides which API to generate by default (despite what
+	// the editions feature default specifies). One of OPEN, HYBRID or OPAQUE.
+	DefaultAPILevel gofeaturespb.GoFeatures_APILevel
 }
 
 // New returns a new Plugin.
@@ -250,9 +254,9 @@
 	// Alternatively, build systems which want to exert full control over
 	// import paths may specify M<filename>=<import_path> flags.
 	for _, fdesc := range gen.Request.ProtoFile {
+		filename := fdesc.GetName()
 		// The "M" command-line flags take precedence over
 		// the "go_package" option in the .proto source file.
-		filename := fdesc.GetName()
 		impPath, pkgName := splitImportPathAndPackageName(fdesc.GetOptions().GetGoPackage())
 		if importPaths[filename] == "" && impPath != "" {
 			importPaths[filename] = impPath
@@ -460,6 +464,9 @@
 	GeneratedFilenamePrefix string
 
 	location Location
+
+	// APILevel specifies which API to generate. One of OPEN, HYBRID or OPAQUE.
+	APILevel gofeaturespb.GoFeatures_APILevel
 }
 
 func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) {
@@ -476,6 +483,8 @@
 		GoPackageName: packageName,
 		GoImportPath:  importPath,
 		location:      Location{SourceFile: desc.Path()},
+
+		APILevel: fileAPILevel(desc, gen.defaultAPILevel()),
 	}
 
 	// Determine the prefix for generated Go files.
@@ -655,6 +664,9 @@
 
 	Location Location   // location of this message
 	Comments CommentSet // comments associated with this message
+
+	// APILevel specifies which API to generate. One of OPEN, HYBRID or OPAQUE.
+	APILevel gofeaturespb.GoFeatures_APILevel
 }
 
 func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
@@ -664,11 +676,20 @@
 	} else {
 		loc = f.location.appendPath(genid.FileDescriptorProto_MessageType_field_number, desc.Index())
 	}
+
+	def := f.APILevel
+	if parent != nil {
+		// editions feature semantics: applies to nested messages.
+		def = parent.APILevel
+	}
+
 	message := &Message{
 		Desc:     desc,
 		GoIdent:  newGoIdent(f, desc),
 		Location: loc,
 		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
+
+		APILevel: messageAPILevel(desc, def),
 	}
 	gen.messagesByName[desc.FullName()] = message
 	for i, eds := 0, desc.Enums(); i < eds.Len(); i++ {
@@ -766,6 +787,8 @@
 		}
 	}
 
+	opaqueNewMessageHook(message)
+
 	return message
 }
 
@@ -812,6 +835,18 @@
 
 	Location Location   // location of this field
 	Comments CommentSet // comments associated with this field
+
+	// camelCase is the same as GoName, but without the name
+	// mangling.  This is used in builders, where only the single
+	// name "Build" needs to be mangled.
+	camelCase string
+
+	// hasConflictHybrid tells us if we are to insert an '_' into
+	// the method names, (e.g. SetFoo becomes Set_Foo).  This will
+	// be set even if we generate opaque protos, as we will want
+	// to potentially generate these method names anyway
+	// (opaque-v0).
+	hasConflictHybrid bool
 }
 
 func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
@@ -840,6 +875,9 @@
 		Location: loc,
 		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
+
+	opaqueNewFieldHook(desc, field)
+
 	return field
 }
 
@@ -890,13 +928,24 @@
 
 	Location Location   // location of this oneof
 	Comments CommentSet // comments associated with this oneof
+
+	// camelCase is the same as GoName, but without the name mangling.
+	// This is used in builders, which never have their names mangled
+	camelCase string
+
+	// hasConflictHybrid tells us if we are to insert an '_' into
+	// the method names, (e.g. SetFoo becomes Set_Foo).  This will
+	// be set even if we generate opaque protos, as we will want
+	// to potentially generate these method names anyway
+	// (opaque-v0).
+	hasConflictHybrid bool
 }
 
 func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof {
 	loc := message.Location.appendPath(genid.DescriptorProto_OneofDecl_field_number, desc.Index())
 	camelCased := strs.GoCamelCase(string(desc.Name()))
 	parentPrefix := message.GoIdent.GoName + "_"
-	return &Oneof{
+	oneof := &Oneof{
 		Desc:   desc,
 		Parent: message,
 		GoName: camelCased,
@@ -907,6 +956,10 @@
 		Location: loc,
 		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
+
+	opaqueNewOneofHook(desc, oneof)
+
+	return oneof
 }
 
 // Extension is an alias of [Field] for documentation.
diff --git a/compiler/protogen/protogen_apilevel.go b/compiler/protogen/protogen_apilevel.go
new file mode 100644
index 0000000..27276fa
--- /dev/null
+++ b/compiler/protogen/protogen_apilevel.go
@@ -0,0 +1,192 @@
+// 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 protogen
+
+import (
+	"fmt"
+
+	"google.golang.org/protobuf/internal/filedesc"
+	"google.golang.org/protobuf/internal/genid"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/types/gofeaturespb"
+)
+
+func fileAPILevel(fd protoreflect.FileDescriptor, def gofeaturespb.GoFeatures_APILevel) gofeaturespb.GoFeatures_APILevel {
+	level := gofeaturespb.GoFeatures_API_OPEN
+	level = def
+	if fd, ok := fd.(*filedesc.File); ok {
+		al := fd.L1.EditionFeatures.APILevel
+		if al != genid.GoFeatures_API_LEVEL_UNSPECIFIED_enum_value {
+			level = gofeaturespb.GoFeatures_APILevel(al)
+		}
+	}
+
+	return level
+}
+
+func messageAPILevel(md protoreflect.MessageDescriptor, def gofeaturespb.GoFeatures_APILevel) gofeaturespb.GoFeatures_APILevel {
+	level := def
+	if md, ok := md.(*filedesc.Message); ok {
+		al := md.L1.EditionFeatures.APILevel
+		if al != genid.GoFeatures_API_LEVEL_UNSPECIFIED_enum_value {
+			level = gofeaturespb.GoFeatures_APILevel(al)
+		}
+	}
+
+	return level
+}
+
+func (p *Plugin) defaultAPILevel() gofeaturespb.GoFeatures_APILevel {
+	if p.opts.DefaultAPILevel != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
+		return p.opts.DefaultAPILevel
+	}
+
+	return gofeaturespb.GoFeatures_API_OPEN
+}
+
+// MethodName returns the (possibly mangled) name of the generated accessor
+// method, along with the backwards-compatible name (if needed).
+//
+// method must be one of Get, Set, Has, Clear. MethodName panics otherwise.
+func (field *Field) MethodName(method string) (name, compat string) {
+	switch method {
+	case "Get":
+		return field.getterName()
+
+	case "Set":
+		return field.setterName()
+
+	case "Has", "Clear":
+		return field.methodName(method), ""
+
+	default:
+		panic(fmt.Sprintf("Field.MethodName called for unknown method %q", method))
+	}
+}
+
+// methodName returns the (possibly mangled) name of the generated method with
+// the given prefix.
+//
+// For the Open API, the return value is "".
+func (field *Field) methodName(prefix string) string {
+	switch field.Parent.APILevel {
+	case gofeaturespb.GoFeatures_API_OPEN:
+		// In the Open API, only generate getters (no Has or Clear methods).
+		return ""
+
+	case gofeaturespb.GoFeatures_API_HYBRID:
+		var infix string
+		if field.hasConflictHybrid {
+			infix = "_"
+		}
+		return prefix + infix + field.camelCase
+
+	case gofeaturespb.GoFeatures_API_OPAQUE:
+		return prefix + field.camelCase
+
+	default:
+		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
+	}
+}
+
+// getterName returns the (possibly mangled) name of the generated Get method,
+// along with the backwards-compatible name (if needed).
+func (field *Field) getterName() (getter, compat string) {
+	switch field.Parent.APILevel {
+	case gofeaturespb.GoFeatures_API_OPEN:
+		// In the Open API, only generate a getter with the old style mangled name.
+		return "Get" + field.GoName, ""
+
+	case gofeaturespb.GoFeatures_API_HYBRID:
+		// In the Hybrid API, return the mangled getter name and the old style
+		// name if needed, for backwards compatibility with the Open API.
+		var infix string
+		if field.hasConflictHybrid {
+			infix = "_"
+		}
+		orig := "Get" + infix + field.camelCase
+		mangled := "Get" + field.GoName
+		if mangled == orig {
+			mangled = ""
+		}
+		return orig, mangled
+
+	case gofeaturespb.GoFeatures_API_OPAQUE:
+		return field.methodName("Get"), ""
+
+	default:
+		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
+	}
+}
+
+// setterName returns the (possibly mangled) name of the generated Set method,
+// along with the backwards-compatible name (if needed).
+func (field *Field) setterName() (setter, compat string) {
+	// TODO(b/359846588): remove weak field support?
+	if field.Desc.IsWeak() && field.Parent.APILevel != gofeaturespb.GoFeatures_API_OPAQUE {
+		switch field.Parent.APILevel {
+		case gofeaturespb.GoFeatures_API_OPEN:
+			return "Set" + field.GoName, ""
+
+		default:
+			var infix string
+			if field.hasConflictHybrid {
+				infix = "_"
+			}
+			orig := "Set" + infix + field.camelCase
+			mangled := "Set" + field.GoName
+			if mangled == orig {
+				mangled = ""
+			}
+			return orig, mangled
+		}
+	}
+	return field.methodName("Set"), ""
+}
+
+// BuilderFieldName returns the name of this field in the corresponding _builder
+// struct.
+func (field *Field) BuilderFieldName() string {
+	return field.camelCase
+}
+
+// MethodName returns the (possibly mangled) name of the generated accessor
+// method.
+//
+// method must be one of Has, Clear, Which. MethodName panics otherwise.
+func (oneof *Oneof) MethodName(method string) string {
+	switch method {
+	case "Has", "Clear", "Which":
+		return oneof.methodName(method)
+
+	default:
+		panic(fmt.Sprintf("Oneof.MethodName called for unknown method %q", method))
+	}
+}
+
+// methodName returns the (possibly mangled) name of the generated method with
+// the given prefix.
+//
+// For the Open API, the return value is "".
+func (oneof *Oneof) methodName(prefix string) string {
+	switch oneof.Parent.APILevel {
+	case gofeaturespb.GoFeatures_API_OPEN:
+		// In the Open API, only generate getters.
+		return ""
+
+	case gofeaturespb.GoFeatures_API_HYBRID:
+		var infix string
+		if oneof.hasConflictHybrid {
+			infix = "_"
+		}
+		return prefix + infix + oneof.camelCase
+
+	case gofeaturespb.GoFeatures_API_OPAQUE:
+		return prefix + oneof.camelCase
+
+	default:
+		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
+	}
+}
diff --git a/compiler/protogen/protogen_opaque.go b/compiler/protogen/protogen_opaque.go
new file mode 100644
index 0000000..8b11cdb
--- /dev/null
+++ b/compiler/protogen/protogen_opaque.go
@@ -0,0 +1,79 @@
+// 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 protogen
+
+import (
+	"google.golang.org/protobuf/internal/strs"
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func opaqueNewFieldHook(desc protoreflect.FieldDescriptor, field *Field) {
+	field.camelCase = strs.GoCamelCase(string(desc.Name()))
+}
+
+func opaqueNewOneofHook(desc protoreflect.OneofDescriptor, oneof *Oneof) {
+	oneof.camelCase = strs.GoCamelCase(string(desc.Name()))
+}
+
+func opaqueNewMessageHook(message *Message) {
+	// New name mangling scheme: Add a '_' between method base
+	// name (Get, Set, Clear etc) and original field name if
+	// needed.  As a special case, there is one globally reserved
+	// name, e.g. "Build" thet still results in actual renaming of
+	// the builder field like in the old scheme.  We begin by
+	// taking care of this special case.
+	for _, field := range message.Fields {
+		if field.camelCase == "Build" {
+			field.camelCase += "_"
+		}
+	}
+
+	// Then find all names of the original field names, we do not want the old scheme to affect
+	// how we name things.
+
+	camelCases := map[string]bool{}
+	for _, field := range message.Fields {
+		if field.Oneof != nil {
+			// We add the name of the union here (potentially many times).
+			camelCases[field.Oneof.camelCase] = true
+		}
+		// The member fields of the oneof are considered fields in the struct although
+		// they are not technically there. This is to allow changing a proto2 optional
+		// to a oneof with source code compatibility.
+		camelCases[field.camelCase] = true
+
+	}
+	// For each field, check if any of it's methods would clash with an original field name
+	for _, field := range message.Fields {
+		// Every field (except the union fields, that are taken care of separately) has
+		// a Get and a Set method.
+		methods := []string{"Set", "Get"}
+		// For explicit presence fields, we also have Has and Clear.
+		if field.Desc.HasPresence() {
+			methods = append(methods, "Has", "Clear")
+		}
+		for _, method := range methods {
+			// If any method name clashes with a field name, all methods get a
+			// "_" inserted between the operation and the field name.
+			if camelCases[method+field.camelCase] {
+				field.hasConflictHybrid = true
+			}
+		}
+	}
+	// The union names for oneofs need only have a methods prefix if there is a clash with Has, Clear or Which in
+	// hybrid and opaque-v0.
+	for _, field := range message.Fields {
+		if field.Oneof == nil {
+			continue
+		}
+		for _, method := range []string{"Has", "Clear", "Which"} {
+			// Same logic as for regular fields - all methods get the "_" if one needs it.
+			if camelCases[method+field.Oneof.camelCase] {
+				field.Oneof.hasConflictHybrid = true
+			}
+		}
+	}
+
+}
diff --git a/encoding/prototext/testmessages_opaque_test.go b/encoding/prototext/testmessages_opaque_test.go
new file mode 100644
index 0000000..9f2a7a7
--- /dev/null
+++ b/encoding/prototext/testmessages_opaque_test.go
@@ -0,0 +1,34 @@
+// Copyright 2018 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 prototext_test
+
+import (
+	"fmt"
+	"strings"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoregistry"
+
+	_ "google.golang.org/protobuf/internal/testprotos/textpbeditions"
+	_ "google.golang.org/protobuf/internal/testprotos/textpbeditions/textpbeditions_opaque"
+)
+
+var relatedMessages = func() map[protoreflect.MessageType][]protoreflect.MessageType {
+	related := map[protoreflect.MessageType][]protoreflect.MessageType{}
+	const opaqueNamePrefix = "opaque."
+	protoregistry.GlobalTypes.RangeMessages(func(mt protoreflect.MessageType) bool {
+		name := mt.Descriptor().FullName()
+		if !strings.HasPrefix(string(name), opaqueNamePrefix) {
+			return true
+		}
+		mt1, err := protoregistry.GlobalTypes.FindMessageByName(name[len(opaqueNamePrefix):])
+		if err != nil {
+			panic(fmt.Sprintf("%v: can't find related message", name))
+		}
+		related[mt1] = append(related[mt1], mt)
+		return true
+	})
+	return related
+}()
diff --git a/encoding/prototext/testmessages_test.go b/encoding/prototext/testmessages_test.go
index 4681db7..50da60f 100644
--- a/encoding/prototext/testmessages_test.go
+++ b/encoding/prototext/testmessages_test.go
@@ -10,6 +10,13 @@
 )
 
 func makeMessages(in protobuild.Message, messages ...proto.Message) []proto.Message {
+
+	for _, m := range messages {
+		for _, mt := range relatedMessages[m.ProtoReflect().Type()] {
+			messages = append(messages, mt.New().Interface())
+		}
+	}
+
 	for _, m := range messages {
 		in.Build(m.ProtoReflect())
 	}
diff --git a/integration_test.go b/integration_test.go
index b05cdab..0ec126f 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -140,6 +140,7 @@
 		}
 
 		runGo("Normal", command{}, "go", "test", "-race", "./...")
+		runGo("LazyDecoding", command{}, "go", "test", "./proto", "-test_lazy_unmarshal")
 		runGo("Reflect", command{}, "go", "test", "-race", "-tags", "protoreflect", "./...")
 		if goVersion == golangLatest {
 			runGo("ProtoLegacyRace", command{}, "go", "test", "-race", "-tags", "protolegacy", "./...")
diff --git a/internal/cmd/generate-protos/main.go b/internal/cmd/generate-protos/main.go
index 5d352cf..5d95201 100644
--- a/internal/cmd/generate-protos/main.go
+++ b/internal/cmd/generate-protos/main.go
@@ -101,10 +101,190 @@
 	// editions_default.binpb was not yet updated.
 	generateEditionsDefaults()
 
+	// Generate versions of each testproto .proto file which use the Hybrid and
+	// Opaque API. This step needs to come first so that the next step will
+	// generate the .pb.go files for these extra .proto files.
+	generateOpaqueTestprotos()
+
 	generateLocalProtos()
 	generateRemoteProtos()
 }
 
+// gsed works roughly like sed(1), in that it processes a file with a list of
+// replacement functions that are applied in order to each line.
+func gsed(outFn, inFn string, repls ...func(line string) string) error {
+	if err := os.MkdirAll(filepath.Dir(outFn), 0755); err != nil {
+		return err
+	}
+	out, err := os.Create(outFn)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+	b, err := os.ReadFile(inFn)
+	if err != nil {
+		return err
+	}
+	lines := strings.Split(strings.TrimSpace(string(b)), "\n")
+	for idx, line := range lines {
+		for _, repl := range repls {
+			line = repl(line)
+		}
+		lines[idx] = line
+	}
+	if _, err := out.Write([]byte(strings.Join(lines, "\n"))); err != nil {
+		return err
+	}
+	return out.Close()
+}
+
+// variantFn turns a relative path like
+// internal/testprotos/annotation/annotation.proto into its corresponding
+// Hybrid/Opaque API variant file name,
+// e.g. internal/testprotos/annotation/annotation_hybrid/annotation.hybrid.proto
+func variantFn(relPath, variant string) string {
+	base := strings.TrimSuffix(filepath.Base(relPath), ".proto")
+	dir := filepath.Dir(relPath)
+	return filepath.Join(dir, filepath.Base(dir)+"_"+variant, base) + "." + variant + ".proto"
+}
+
+var (
+	testProtoRe = regexp.MustCompile(`(internal/testprotos/.*[.]proto)`)
+	goPackageRe = regexp.MustCompile(`option go_package = "([^"]+)";`)
+	extRe       = regexp.MustCompile(`_ext = ([0-9]+);`)
+)
+
+func generateOpaqueDotProto(repoRoot, tmpDir, relPath string) {
+	// relPath is e.g. internal/testprotos/annotation/annotation.proto
+	ignored := func(p string) bool {
+		return strings.HasPrefix(p, "internal/testprotos/irregular")
+	}
+	inFn := filepath.Join(repoRoot, relPath)
+
+	// create .hybrid.proto variant
+	hybridFn := variantFn(relPath, "hybrid")
+	outFn := filepath.Join(tmpDir, hybridFn)
+	check(gsed(outFn, inFn, []func(line string) string{
+		func(line string) string {
+			if strings.HasPrefix(line, "package ") {
+				return strings.ReplaceAll(line, "package ", "package hybrid.")
+			}
+			return line
+		},
+		func(line string) string {
+			if testProtoPath := testProtoRe.FindString(line); testProtoPath != "" && !ignored(testProtoPath) {
+				hybridFn := variantFn(testProtoPath, "hybrid")
+				return strings.ReplaceAll(line, testProtoPath, hybridFn)
+			}
+			return line
+		},
+		func(line string) string {
+			if matches := goPackageRe.FindStringSubmatch(line); matches != nil {
+				goPkg := matches[1]
+				hybridGoPkg := strings.TrimSuffix(goPkg, "/") + "/" + filepath.Base(goPkg) + "_hybrid"
+				return `option go_package = "` + hybridGoPkg + `";` + "\n" +
+					`import "google/protobuf/go_features.proto";` + "\n" +
+					`option features.(pb.go).api_level = API_HYBRID;`
+			}
+			return line
+		},
+	}...))
+
+	// create .opaque.proto variant
+	opaqueFn := variantFn(relPath, "opaque")
+	outFn = filepath.Join(tmpDir, opaqueFn)
+	check(gsed(outFn, inFn, []func(line string) string{
+		func(line string) string {
+			if strings.HasPrefix(line, "package ") {
+				return strings.ReplaceAll(line, "package ", "package opaque.")
+			}
+			return line
+		},
+		func(line string) string {
+			if testProtoPath := testProtoRe.FindString(line); testProtoPath != "" && !ignored(testProtoPath) {
+				hybridFn := variantFn(testProtoPath, "opaque")
+				return strings.ReplaceAll(line, testProtoPath, hybridFn)
+			}
+			return line
+		},
+		func(line string) string {
+			if matches := goPackageRe.FindStringSubmatch(line); matches != nil {
+				goPkg := matches[1]
+				opaqueGoPkg := strings.TrimSuffix(goPkg, "/") + "/" + filepath.Base(goPkg) + "_opaque"
+				return `option go_package = "` + opaqueGoPkg + `";` + "\n" +
+					`import "google/protobuf/go_features.proto";` + "\n" +
+					`option features.(pb.go).api_level = API_OPAQUE;`
+			}
+			return line
+		},
+		func(line string) string {
+			return strings.ReplaceAll(line, `full_name: ".goproto`, `full_name: ".opaque.goproto`)
+		},
+		func(line string) string {
+			return strings.ReplaceAll(line, `type: ".goproto`, `type: ".opaque.goproto`)
+		},
+		func(line string) string {
+			if matches := extRe.FindStringSubmatch(line); matches != nil {
+				trimmed := strings.TrimSuffix(matches[0], ";")
+				return strings.ReplaceAll(line, trimmed, trimmed+"0")
+			}
+			return line
+		},
+	}...))
+}
+
+func generateOpaqueTestprotos() {
+	tmpDir, err := os.MkdirTemp(repoRoot, "tmp")
+	check(err)
+	defer os.RemoveAll(tmpDir)
+
+	// Generate variants using the Hybrid and Opaque API for all local proto
+	// files (except version-locked files).
+	dirs := []struct {
+		path     string
+		pkgPaths map[string]string // mapping of .proto path to Go package path
+		annotate map[string]bool   // .proto files to annotate
+		exclude  map[string]bool   // .proto files to exclude from generation
+	}{
+		{path: "internal/testprotos/required"},
+		{path: "internal/testprotos/testeditions"},
+		{path: "internal/testprotos/enums"},
+		{path: "internal/testprotos/textpbeditions"},
+		{path: "internal/testprotos/messageset"},
+		{
+			path: "internal/testprotos/lazy",
+			exclude: map[string]bool{
+				"internal/testprotos/lazy/lazy_extension_normalized_wire_test.proto": true,
+				"internal/testprotos/lazy/lazy_normalized_wire_test.proto":           true,
+				"internal/testprotos/lazy/lazy_extension_test.proto":                 true,
+			},
+		},
+	}
+	excludeRx := regexp.MustCompile(`legacy/.*/`)
+	for _, d := range dirs {
+		srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path))
+		filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
+			if !strings.HasSuffix(srcPath, ".proto") || excludeRx.MatchString(srcPath) {
+				return nil
+			}
+			if strings.HasSuffix(srcPath, ".opaque.proto") || strings.HasSuffix(srcPath, ".hybrid.proto") {
+				return nil
+			}
+			relPath, err := filepath.Rel(repoRoot, srcPath)
+			check(err)
+
+			if d.exclude[filepath.ToSlash(relPath)] {
+				return nil
+			}
+
+			generateOpaqueDotProto(repoRoot, tmpDir, relPath)
+			return nil
+		})
+	}
+
+	syncOutput(repoRoot, tmpDir)
+}
+
 func generateEditionsDefaults() {
 	dest := filepath.Join(repoRoot, "internal", "editiondefaults", "editions_defaults.binpb")
 	srcDescriptorProto := filepath.Join(protoRoot, "src", "google", "protobuf", "descriptor.proto")
@@ -472,7 +652,9 @@
 
 func syncOutput(dstDir, srcDir string) {
 	filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
-		if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
+		if !strings.HasSuffix(srcPath, ".go") &&
+			!strings.HasSuffix(srcPath, ".meta") &&
+			!strings.HasSuffix(srcPath, ".proto") {
 			return nil
 		}
 		relPath, err := filepath.Rel(srcDir, srcPath)
diff --git a/internal/cmd/generate-types/impl.go b/internal/cmd/generate-types/impl.go
index 97bd251..66aa69b 100644
--- a/internal/cmd/generate-types/impl.go
+++ b/internal/cmd/generate-types/impl.go
@@ -894,3 +894,127 @@
 {{end}}
 {{end}}
 `))
+
+func generateImplField() string {
+	return mustExecute(implFieldTemplate, GoTypes)
+}
+
+var implFieldTemplate = template.Must(template.New("").Parse(`
+func getterForNullableScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if ft.Kind() == reflect.Ptr {
+		ft = ft.Elem()
+	}
+	if fd.Kind() == protoreflect.EnumKind {
+		elemType := fs.Type.Elem()
+		// Enums for nullable types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).Elem().AsValueOf(elemType)
+			if rv.IsNil() {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv.Elem())
+		}
+	}
+	switch ft.Kind() {
+{{range . }}
+{{- if eq . "string"}}	case reflect.String:
+{{- /* Handle string GoType -> bytes proto type specially */}}
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).StringPtr()
+				if *x == nil {
+					return conv.Zero()
+				}
+				if len(**x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(**x))
+			}
+		}
+{{else if eq . "[]byte" }}	case reflect.Slice:
+{{- /* Handle []byte GoType -> string proto type specially */}}
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				if len(*x) == 0 {
+					return conv.Zero()
+				}
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+{{else}}	case {{.Kind }}:
+{{end}}		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).{{.NullablePointerMethod}}()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOf{{.PointerMethod}}({{.NullableStar}}*x)
+		}
+{{end}}	}
+	panic("unexpected protobuf kind: "+ft.Kind().String())
+}
+
+func getterForDirectScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if fd.Kind() == protoreflect.EnumKind {
+		// Enums for non nullable types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return conv.PBValueOf(rv)
+		}
+	}
+	switch ft.Kind() {
+{{range . }}
+{{- if eq . "string"}}	case reflect.String:
+{{- /* Handle string GoType -> bytes proto type specially */}}
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).String()
+				if len(*x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(*x))
+			}
+		}
+{{else if eq . "[]byte" }}	case reflect.Slice:
+{{- /* Handle []byte GoType -> string proto type specially */}}
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+{{else}}	case {{.Kind}}:
+{{end}}		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).{{.PointerMethod}}()
+			return protoreflect.ValueOf{{.PointerMethod}}(*x)
+		}
+{{end}}	}
+	panic("unexpected protobuf kind: "+ft.Kind().String())
+}
+`))
diff --git a/internal/cmd/generate-types/impl_opaque.go b/internal/cmd/generate-types/impl_opaque.go
new file mode 100644
index 0000000..cb405de
--- /dev/null
+++ b/internal/cmd/generate-types/impl_opaque.go
@@ -0,0 +1,77 @@
+// 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 (
+	"text/template"
+)
+
+func generateImplMessageOpaque() string {
+	return mustExecute(messageOpaqueTemplate, GoTypes)
+}
+
+var messageOpaqueTemplate = template.Must(template.New("").Parse(`
+func getterForOpaqueNullableScalar(mi *MessageInfo, index uint32, fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if ft.Kind() == reflect.Ptr {
+		ft = ft.Elem()
+	}
+	if fd.Kind() == protoreflect.EnumKind {
+		// Enums for nullable opaque types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return conv.PBValueOf(rv)
+		}
+	}
+	switch ft.Kind() {
+{{range . }}
+{{- if eq . "string"}}	case reflect.String:
+{{- /* Handle string GoType -> bytes proto type specially */}}
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() || !mi.present(p, index) {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).StringPtr()
+				if *x == nil {
+					return conv.Zero()
+				}
+				if len(**x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(**x))
+			}
+		}
+{{else if eq . "[]byte" }}	case reflect.Slice:
+{{- /* Handle []byte GoType -> string proto type specially */}}
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() || !mi.present(p, index) {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+{{else}}	case {{.Kind}}:
+{{end}}		       return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).{{.OpaqueNullablePointerMethod}}()
+{{- if eq . "string"}}
+			if *x == nil {
+				return conv.Zero()
+			}
+{{- end}}
+			return protoreflect.ValueOf{{.PointerMethod}}({{.OpaqueNullableStar}}*x)
+		}
+{{end}}	}
+	panic("unexpected protobuf kind: "+ft.Kind().String())
+}
+`))
diff --git a/internal/cmd/generate-types/main.go b/internal/cmd/generate-types/main.go
index de829f2..597bd64 100644
--- a/internal/cmd/generate-types/main.go
+++ b/internal/cmd/generate-types/main.go
@@ -44,6 +44,8 @@
 	writeSource("internal/impl/codec_gen.go", generateImplCodec())
 	writeSource("internal/impl/message_reflect_gen.go", generateImplMessage())
 	writeSource("internal/impl/merge_gen.go", generateImplMerge())
+	writeSource("internal/impl/message_reflect_field_gen.go", generateImplField())
+	writeSource("internal/impl/message_opaque_gen.go", generateImplMessageOpaque())
 	writeSource("proto/decode_gen.go", generateProtoDecode())
 	writeSource("proto/encode_gen.go", generateProtoEncode())
 	writeSource("proto/size_gen.go", generateProtoSize())
diff --git a/internal/cmd/generate-types/proto.go b/internal/cmd/generate-types/proto.go
index c70cc21..94b5d97 100644
--- a/internal/cmd/generate-types/proto.go
+++ b/internal/cmd/generate-types/proto.go
@@ -88,6 +88,43 @@
 	return Expr(strings.ToUpper(string(g[:1])) + string(g[1:]))
 }
 
+// NullablePointerMethod is the "internal/impl".pointer method used to access a nullable pointer to this type.
+func (g GoType) NullablePointerMethod() Expr {
+	if g == GoBytes {
+		return "Bytes" // Bytes are already nullable
+	}
+	return Expr(strings.ToUpper(string(g[:1])) + string(g[1:]) + "Ptr")
+}
+
+// NullableStar is the prefix for dereferencing a nullable value of this type "*" or "".
+func (g GoType) NullableStar() Expr {
+	if g == GoBytes {
+		return "" // bytes are stored as a slice even when nullable
+	}
+	return "*"
+}
+
+// OpaqueNullablePointerMethod is the "internal/impl".pointer method used to access a opaque nullable pointer to this type.
+func (g GoType) OpaqueNullablePointerMethod() Expr {
+	switch g {
+	case GoString:
+		return "StringPtr" // Strings have indirection even in opaque
+	case GoBytes:
+		return "Bytes"
+	default:
+		return Expr(strings.ToUpper(string(g[:1])) + string(g[1:]))
+	}
+
+}
+
+// OpaqueNullableStar is the prefix for dereferencing a opaque nullable value of this type.
+func (g GoType) OpaqueNullableStar() Expr {
+	if g == GoString {
+		return "*" // Strings have indirection even in opaque
+	}
+	return ""
+}
+
 type ProtoKind struct {
 	Name     string
 	WireType WireType
diff --git a/internal/filedesc/build_test.go b/internal/filedesc/build_test.go
index 8dd7323..60e7152 100644
--- a/internal/filedesc/build_test.go
+++ b/internal/filedesc/build_test.go
@@ -54,7 +54,9 @@
 		descPkg.Append("FileDescriptorProto.source_code_info"): true,
 		descPkg.Append("FileDescriptorProto.syntax"):           true,
 		// Nothing is using edition yet.
-		descPkg.Append("FileDescriptorProto.edition"): true,
+		descPkg.Append("FileDescriptorProto.edition"):            true,
+		descPkg.Append("FileDescriptorProto.edition_enum"):       true,
+		descPkg.Append("FileDescriptorProto.edition_deprecated"): true,
 
 		// Impossible to test proto3 optional in a proto2 file.
 		descPkg.Append("FieldDescriptorProto.proto3_optional"): true,
diff --git a/internal/filedesc/desc.go b/internal/filedesc/desc.go
index f325298..378b826 100644
--- a/internal/filedesc/desc.go
+++ b/internal/filedesc/desc.go
@@ -117,6 +117,9 @@
 		// GenerateLegacyUnmarshalJSON determines if the plugin generates the
 		// UnmarshalJSON([]byte) error method for enums.
 		GenerateLegacyUnmarshalJSON bool
+		// APILevel controls which API (Open, Hybrid or Opaque) should be used
+		// for generated code (.pb.go files).
+		APILevel int
 	}
 )
 
diff --git a/internal/filedesc/editions.go b/internal/filedesc/editions.go
index 7611796..10132c9 100644
--- a/internal/filedesc/editions.go
+++ b/internal/filedesc/editions.go
@@ -32,6 +32,10 @@
 			v, m := protowire.ConsumeVarint(b)
 			b = b[m:]
 			parent.GenerateLegacyUnmarshalJSON = protowire.DecodeBool(v)
+		case genid.GoFeatures_ApiLevel_field_number:
+			v, m := protowire.ConsumeVarint(b)
+			b = b[m:]
+			parent.APILevel = int(v)
 		case genid.GoFeatures_StripEnumPrefix_field_number:
 			v, m := protowire.ConsumeVarint(b)
 			b = b[m:]
diff --git a/internal/genid/descriptor_gen.go b/internal/genid/descriptor_gen.go
index f30ab6b..30a2fa6 100644
--- a/internal/genid/descriptor_gen.go
+++ b/internal/genid/descriptor_gen.go
@@ -1120,20 +1120,26 @@
 
 // Field names for google.protobuf.FeatureSetDefaults.
 const (
-	FeatureSetDefaults_Defaults_field_name       protoreflect.Name = "defaults"
-	FeatureSetDefaults_MinimumEdition_field_name protoreflect.Name = "minimum_edition"
-	FeatureSetDefaults_MaximumEdition_field_name protoreflect.Name = "maximum_edition"
+	FeatureSetDefaults_Defaults_field_name                 protoreflect.Name = "defaults"
+	FeatureSetDefaults_MinimumEditionDeprecated_field_name protoreflect.Name = "minimum_edition"
+	FeatureSetDefaults_MaximumEditionDeprecated_field_name protoreflect.Name = "maximum_edition"
+	FeatureSetDefaults_MinimumEdition_field_name           protoreflect.Name = "minimum_edition"
+	FeatureSetDefaults_MaximumEdition_field_name           protoreflect.Name = "maximum_edition"
 
-	FeatureSetDefaults_Defaults_field_fullname       protoreflect.FullName = "google.protobuf.FeatureSetDefaults.defaults"
-	FeatureSetDefaults_MinimumEdition_field_fullname protoreflect.FullName = "google.protobuf.FeatureSetDefaults.minimum_edition"
-	FeatureSetDefaults_MaximumEdition_field_fullname protoreflect.FullName = "google.protobuf.FeatureSetDefaults.maximum_edition"
+	FeatureSetDefaults_Defaults_field_fullname                 protoreflect.FullName = "google.protobuf.FeatureSetDefaults.defaults"
+	FeatureSetDefaults_MinimumEditionDeprecated_field_fullname protoreflect.FullName = "google.protobuf.FeatureSetDefaults.minimum_edition"
+	FeatureSetDefaults_MaximumEditionDeprecated_field_fullname protoreflect.FullName = "google.protobuf.FeatureSetDefaults.maximum_edition"
+	FeatureSetDefaults_MinimumEdition_field_fullname           protoreflect.FullName = "google.protobuf.FeatureSetDefaults.minimum_edition"
+	FeatureSetDefaults_MaximumEdition_field_fullname           protoreflect.FullName = "google.protobuf.FeatureSetDefaults.maximum_edition"
 )
 
 // Field numbers for google.protobuf.FeatureSetDefaults.
 const (
-	FeatureSetDefaults_Defaults_field_number       protoreflect.FieldNumber = 1
-	FeatureSetDefaults_MinimumEdition_field_number protoreflect.FieldNumber = 4
-	FeatureSetDefaults_MaximumEdition_field_number protoreflect.FieldNumber = 5
+	FeatureSetDefaults_Defaults_field_number                 protoreflect.FieldNumber = 1
+	FeatureSetDefaults_MinimumEditionDeprecated_field_number protoreflect.FieldNumber = 2
+	FeatureSetDefaults_MaximumEditionDeprecated_field_number protoreflect.FieldNumber = 3
+	FeatureSetDefaults_MinimumEdition_field_number           protoreflect.FieldNumber = 4
+	FeatureSetDefaults_MaximumEdition_field_number           protoreflect.FieldNumber = 5
 )
 
 // Names for google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.
@@ -1147,10 +1153,12 @@
 	FeatureSetDefaults_FeatureSetEditionDefault_Edition_field_name             protoreflect.Name = "edition"
 	FeatureSetDefaults_FeatureSetEditionDefault_OverridableFeatures_field_name protoreflect.Name = "overridable_features"
 	FeatureSetDefaults_FeatureSetEditionDefault_FixedFeatures_field_name       protoreflect.Name = "fixed_features"
+	FeatureSetDefaults_FeatureSetEditionDefault_Features_field_name            protoreflect.Name = "features"
 
 	FeatureSetDefaults_FeatureSetEditionDefault_Edition_field_fullname             protoreflect.FullName = "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.edition"
 	FeatureSetDefaults_FeatureSetEditionDefault_OverridableFeatures_field_fullname protoreflect.FullName = "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.overridable_features"
 	FeatureSetDefaults_FeatureSetEditionDefault_FixedFeatures_field_fullname       protoreflect.FullName = "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.fixed_features"
+	FeatureSetDefaults_FeatureSetEditionDefault_Features_field_fullname            protoreflect.FullName = "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.features"
 )
 
 // Field numbers for google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.
@@ -1158,6 +1166,7 @@
 	FeatureSetDefaults_FeatureSetEditionDefault_Edition_field_number             protoreflect.FieldNumber = 3
 	FeatureSetDefaults_FeatureSetEditionDefault_OverridableFeatures_field_number protoreflect.FieldNumber = 4
 	FeatureSetDefaults_FeatureSetEditionDefault_FixedFeatures_field_number       protoreflect.FieldNumber = 5
+	FeatureSetDefaults_FeatureSetEditionDefault_Features_field_number            protoreflect.FieldNumber = 2
 )
 
 // Names for google.protobuf.SourceCodeInfo.
diff --git a/internal/genid/go_features_gen.go b/internal/genid/go_features_gen.go
index 09792d9..f5ee7f5 100644
--- a/internal/genid/go_features_gen.go
+++ b/internal/genid/go_features_gen.go
@@ -21,18 +21,35 @@
 // Field names for pb.GoFeatures.
 const (
 	GoFeatures_LegacyUnmarshalJsonEnum_field_name protoreflect.Name = "legacy_unmarshal_json_enum"
+	GoFeatures_ApiLevel_field_name                protoreflect.Name = "api_level"
 	GoFeatures_StripEnumPrefix_field_name         protoreflect.Name = "strip_enum_prefix"
 
 	GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "pb.GoFeatures.legacy_unmarshal_json_enum"
+	GoFeatures_ApiLevel_field_fullname                protoreflect.FullName = "pb.GoFeatures.api_level"
 	GoFeatures_StripEnumPrefix_field_fullname         protoreflect.FullName = "pb.GoFeatures.strip_enum_prefix"
 )
 
 // Field numbers for pb.GoFeatures.
 const (
 	GoFeatures_LegacyUnmarshalJsonEnum_field_number protoreflect.FieldNumber = 1
+	GoFeatures_ApiLevel_field_number                protoreflect.FieldNumber = 2
 	GoFeatures_StripEnumPrefix_field_number         protoreflect.FieldNumber = 3
 )
 
+// Full and short names for pb.GoFeatures.APILevel.
+const (
+	GoFeatures_APILevel_enum_fullname = "pb.GoFeatures.APILevel"
+	GoFeatures_APILevel_enum_name     = "APILevel"
+)
+
+// Enum values for pb.GoFeatures.APILevel.
+const (
+	GoFeatures_API_LEVEL_UNSPECIFIED_enum_value = 0
+	GoFeatures_API_OPEN_enum_value              = 1
+	GoFeatures_API_HYBRID_enum_value            = 2
+	GoFeatures_API_OPAQUE_enum_value            = 3
+)
+
 // Full and short names for pb.GoFeatures.StripEnumPrefix.
 const (
 	GoFeatures_StripEnumPrefix_enum_fullname = "pb.GoFeatures.StripEnumPrefix"
diff --git a/internal/genid/name.go b/internal/genid/name.go
new file mode 100644
index 0000000..224f339
--- /dev/null
+++ b/internal/genid/name.go
@@ -0,0 +1,12 @@
+// 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 genid
+
+const (
+	NoUnkeyedLiteral_goname  = "noUnkeyedLiteral"
+	NoUnkeyedLiteralA_goname = "XXX_NoUnkeyedLiteral"
+
+	BuilderSuffix_goname = "_builder"
+)
diff --git a/internal/impl/api_export_opaque.go b/internal/impl/api_export_opaque.go
new file mode 100644
index 0000000..6075d6f
--- /dev/null
+++ b/internal/impl/api_export_opaque.go
@@ -0,0 +1,128 @@
+// 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 impl
+
+import (
+	"strconv"
+	"sync/atomic"
+	"unsafe"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func (Export) UnmarshalField(msg any, fieldNum int32) {
+	UnmarshalField(msg.(protoreflect.ProtoMessage).ProtoReflect(), protoreflect.FieldNumber(fieldNum))
+}
+
+// Present checks the presence set for a certain field number (zero
+// based, ordered by appearance in original proto file). part is
+// a pointer to the correct element in the bitmask array, num is the
+// field number unaltered.  Example (field number 70 -> part =
+// &m.XXX_presence[1], num = 70)
+func (Export) Present(part *uint32, num uint32) bool {
+	// This hook will read an unprotected shadow presence set if
+	// we're unning under the race detector
+	raceDetectHookPresent(part, num)
+	return atomic.LoadUint32(part)&(1<<(num%32)) > 0
+}
+
+// SetPresent adds a field to the presence set. part is a pointer to
+// the relevant element in the array and num is the field number
+// unaltered.  size is the number of fields in the protocol
+// buffer.
+func (Export) SetPresent(part *uint32, num uint32, size uint32) {
+	// This hook will mutate an unprotected shadow presence set if
+	// we're running under the race detector
+	raceDetectHookSetPresent(part, num, presenceSize(size))
+	for {
+		old := atomic.LoadUint32(part)
+		if atomic.CompareAndSwapUint32(part, old, old|(1<<(num%32))) {
+			return
+		}
+	}
+}
+
+// SetPresentNonAtomic is like SetPresent, but operates non-atomically.
+// It is meant for use by builder methods, where the message is known not
+// to be accessible yet by other goroutines.
+func (Export) SetPresentNonAtomic(part *uint32, num uint32, size uint32) {
+	// This hook will mutate an unprotected shadow presence set if
+	// we're running under the race detector
+	raceDetectHookSetPresent(part, num, presenceSize(size))
+	*part |= 1 << (num % 32)
+}
+
+// ClearPresence removes a field from the presence set. part is a
+// pointer to the relevant element in the presence array and num is
+// the field number unaltered.
+func (Export) ClearPresent(part *uint32, num uint32) {
+	// This hook will mutate an unprotected shadow presence set if
+	// we're running under the race detector
+	raceDetectHookClearPresent(part, num)
+	for {
+		old := atomic.LoadUint32(part)
+		if atomic.CompareAndSwapUint32(part, old, old&^(1<<(num%32))) {
+			return
+		}
+	}
+}
+
+// interfaceToPointer takes a pointer to an empty interface whose value is a
+// pointer type, and converts it into a "pointer" that points to the same
+// target
+func interfaceToPointer(i *any) pointer {
+	return pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
+}
+
+func (p pointer) atomicGetPointer() pointer {
+	return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
+}
+
+func (p pointer) atomicSetPointer(q pointer) {
+	atomic.StorePointer((*unsafe.Pointer)(p.p), q.p)
+}
+
+// AtomicCheckPointerIsNil takes an interface (which is a pointer to a
+// pointer) and returns true if the pointed-to pointer is nil (using an
+// atomic load).  This function is inlineable and, on x86, just becomes a
+// simple load and compare.
+func (Export) AtomicCheckPointerIsNil(ptr any) bool {
+	return interfaceToPointer(&ptr).atomicGetPointer().IsNil()
+}
+
+// AtomicSetPointer takes two interfaces (first is a pointer to a pointer,
+// second is a pointer) and atomically sets the second pointer into location
+// referenced by first pointer.  Unfortunately, atomicSetPointer() does not inline
+// (even on x86), so this does not become a simple store on x86.
+func (Export) AtomicSetPointer(dstPtr, valPtr any) {
+	interfaceToPointer(&dstPtr).atomicSetPointer(interfaceToPointer(&valPtr))
+}
+
+// AtomicLoadPointer loads the pointer at the location pointed at by src,
+// and stores that pointer value into the location pointed at by dst.
+func (Export) AtomicLoadPointer(ptr Pointer, dst Pointer) {
+	*(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr)))
+}
+
+// AtomicInitializePointer makes ptr and dst point to the same value.
+//
+// If *ptr is a nil pointer, it sets *ptr = *dst.
+//
+// If *ptr is a non-nil pointer, it sets *dst = *ptr.
+func (Export) AtomicInitializePointer(ptr Pointer, dst Pointer) {
+	if !atomic.CompareAndSwapPointer((*unsafe.Pointer)(ptr), unsafe.Pointer(nil), *(*unsafe.Pointer)(dst)) {
+		*(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr)))
+	}
+}
+
+// MessageFieldStringOf returns the field formatted as a string,
+// either as the field name if resolvable otherwise as a decimal string.
+func (Export) MessageFieldStringOf(md protoreflect.MessageDescriptor, n protoreflect.FieldNumber) string {
+	fd := md.Fields().ByNumber(n)
+	if fd != nil {
+		return string(fd.Name())
+	}
+	return strconv.Itoa(int(n))
+}
diff --git a/internal/impl/bitmap.go b/internal/impl/bitmap.go
new file mode 100644
index 0000000..ea27654
--- /dev/null
+++ b/internal/impl/bitmap.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.
+
+//go:build !race
+
+package impl
+
+// There is no additional data as we're not running under race detector.
+type RaceDetectHookData struct{}
+
+// Empty stubs for when not using the race detector. Calls to these from index.go should be optimized away.
+func (presence) raceDetectHookPresent(num uint32)                       {}
+func (presence) raceDetectHookSetPresent(num uint32, size presenceSize) {}
+func (presence) raceDetectHookClearPresent(num uint32)                  {}
+func (presence) raceDetectHookAllocAndCopy(src presence)                {}
+
+// raceDetectHookPresent is called by the generated file interface
+// (*proto.internalFuncs) Present to optionally read an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookPresent(field *uint32, num uint32) {}
+
+// raceDetectHookSetPresent is called by the generated file interface
+// (*proto.internalFuncs) SetPresent to optionally write an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) {}
+
+// raceDetectHookClearPresent is called by the generated file interface
+// (*proto.internalFuncs) ClearPresent to optionally write an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookClearPresent(field *uint32, num uint32) {}
diff --git a/internal/impl/bitmap_race.go b/internal/impl/bitmap_race.go
new file mode 100644
index 0000000..e9a2758
--- /dev/null
+++ b/internal/impl/bitmap_race.go
@@ -0,0 +1,126 @@
+// 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 race
+
+package impl
+
+// When running under race detector, we add a presence map of bytes, that we can access
+// in the hook functions so that we trigger the race detection whenever we have concurrent
+// Read-Writes or Write-Writes. The race detector does not otherwise detect invalid concurrent
+// access to lazy fields as all updates of bitmaps and pointers are done using atomic operations.
+type RaceDetectHookData struct {
+	shadowPresence *[]byte
+}
+
+// Hooks for presence bitmap operations that allocate, read and write the shadowPresence
+// using non-atomic operations.
+func (data *RaceDetectHookData) raceDetectHookAlloc(size presenceSize) {
+	sp := make([]byte, size)
+	atomicStoreShadowPresence(&data.shadowPresence, &sp)
+}
+
+func (p presence) raceDetectHookPresent(num uint32) {
+	data := p.toRaceDetectData()
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp != nil {
+		_ = (*sp)[num]
+	}
+}
+
+func (p presence) raceDetectHookSetPresent(num uint32, size presenceSize) {
+	data := p.toRaceDetectData()
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp == nil {
+		data.raceDetectHookAlloc(size)
+		sp = atomicLoadShadowPresence(&data.shadowPresence)
+	}
+	(*sp)[num] = 1
+}
+
+func (p presence) raceDetectHookClearPresent(num uint32) {
+	data := p.toRaceDetectData()
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp != nil {
+		(*sp)[num] = 0
+
+	}
+}
+
+// raceDetectHookAllocAndCopy allocates a new shadowPresence slice at lazy and copies
+// shadowPresence bytes from src to lazy.
+func (p presence) raceDetectHookAllocAndCopy(q presence) {
+	sData := q.toRaceDetectData()
+	dData := p.toRaceDetectData()
+	if sData == nil {
+		return
+	}
+	srcSp := atomicLoadShadowPresence(&sData.shadowPresence)
+	if srcSp == nil {
+		atomicStoreShadowPresence(&dData.shadowPresence, nil)
+		return
+	}
+	n := len(*srcSp)
+	dSlice := make([]byte, n)
+	atomicStoreShadowPresence(&dData.shadowPresence, &dSlice)
+	for i := 0; i < n; i++ {
+		dSlice[i] = (*srcSp)[i]
+	}
+}
+
+// raceDetectHookPresent is called by the generated file interface
+// (*proto.internalFuncs) Present to optionally read an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookPresent(field *uint32, num uint32) {
+	data := findPointerToRaceDetectData(field, num)
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp != nil {
+		_ = (*sp)[num]
+	}
+}
+
+// raceDetectHookSetPresent is called by the generated file interface
+// (*proto.internalFuncs) SetPresent to optionally write an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) {
+	data := findPointerToRaceDetectData(field, num)
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp == nil {
+		data.raceDetectHookAlloc(size)
+		sp = atomicLoadShadowPresence(&data.shadowPresence)
+	}
+	(*sp)[num] = 1
+}
+
+// raceDetectHookClearPresent is called by the generated file interface
+// (*proto.internalFuncs) ClearPresent to optionally write an unprotected
+// shadow bitmap when race detection is enabled. In regular code it is
+// a noop.
+func raceDetectHookClearPresent(field *uint32, num uint32) {
+	data := findPointerToRaceDetectData(field, num)
+	if data == nil {
+		return
+	}
+	sp := atomicLoadShadowPresence(&data.shadowPresence)
+	if sp != nil {
+		(*sp)[num] = 0
+	}
+}
diff --git a/internal/impl/checkinit.go b/internal/impl/checkinit.go
index f29e6a8..fe2c719 100644
--- a/internal/impl/checkinit.go
+++ b/internal/impl/checkinit.go
@@ -35,6 +35,12 @@
 		}
 		return nil
 	}
+
+	var presence presence
+	if mi.presenceOffset.IsValid() {
+		presence = p.Apply(mi.presenceOffset).PresenceInfo()
+	}
+
 	if mi.extensionOffset.IsValid() {
 		e := p.Apply(mi.extensionOffset).Extensions()
 		if err := mi.isInitExtensions(e); err != nil {
@@ -45,6 +51,33 @@
 		if !f.isRequired && f.funcs.isInit == nil {
 			continue
 		}
+
+		if f.presenceIndex != noPresence {
+			if !presence.Present(f.presenceIndex) {
+				if f.isRequired {
+					return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
+				}
+				continue
+			}
+			if f.funcs.isInit != nil {
+				f.mi.init()
+				if f.mi.needsInitCheck {
+					if f.isLazy && p.Apply(f.offset).AtomicGetPointer().IsNil() {
+						lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr()
+						if !lazy.AllowedPartial() {
+							// Nothing to see here, it was checked on unmarshal
+							continue
+						}
+						mi.lazyUnmarshal(p, f.num)
+					}
+					if err := f.funcs.isInit(p.Apply(f.offset), f); err != nil {
+						return err
+					}
+				}
+			}
+			continue
+		}
+
 		fptr := p.Apply(f.offset)
 		if f.isPointer && fptr.Elem().IsNil() {
 			if f.isRequired {
diff --git a/internal/impl/codec_field_opaque.go b/internal/impl/codec_field_opaque.go
new file mode 100644
index 0000000..76818ea
--- /dev/null
+++ b/internal/impl/codec_field_opaque.go
@@ -0,0 +1,264 @@
+// 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 impl
+
+import (
+	"fmt"
+	"reflect"
+
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/internal/errors"
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func makeOpaqueMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) {
+	mi := getMessageInfo(ft)
+	if mi == nil {
+		panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), ft))
+	}
+	switch fd.Kind() {
+	case protoreflect.MessageKind:
+		return mi, pointerCoderFuncs{
+			size:      sizeOpaqueMessage,
+			marshal:   appendOpaqueMessage,
+			unmarshal: consumeOpaqueMessage,
+			isInit:    isInitOpaqueMessage,
+			merge:     mergeOpaqueMessage,
+		}
+	case protoreflect.GroupKind:
+		return mi, pointerCoderFuncs{
+			size:      sizeOpaqueGroup,
+			marshal:   appendOpaqueGroup,
+			unmarshal: consumeOpaqueGroup,
+			isInit:    isInitOpaqueMessage,
+			merge:     mergeOpaqueMessage,
+		}
+	}
+	panic("unexpected field kind")
+}
+
+func sizeOpaqueMessage(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
+	return protowire.SizeBytes(f.mi.sizePointer(p.AtomicGetPointer(), opts)) + f.tagsize
+}
+
+func appendOpaqueMessage(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
+	mp := p.AtomicGetPointer()
+	calculatedSize := f.mi.sizePointer(mp, opts)
+	b = protowire.AppendVarint(b, f.wiretag)
+	b = protowire.AppendVarint(b, uint64(calculatedSize))
+	before := len(b)
+	b, err := f.mi.marshalAppendPointer(b, mp, opts)
+	if measuredSize := len(b) - before; calculatedSize != measuredSize && err == nil {
+		return nil, errors.MismatchedSizeCalculation(calculatedSize, measuredSize)
+	}
+	return b, err
+}
+
+func consumeOpaqueMessage(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
+	if wtyp != protowire.BytesType {
+		return out, errUnknown
+	}
+	v, n := protowire.ConsumeBytes(b)
+	if n < 0 {
+		return out, errDecode
+	}
+	mp := p.AtomicGetPointer()
+	if mp.IsNil() {
+		mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
+	}
+	o, err := f.mi.unmarshalPointer(v, mp, 0, opts)
+	if err != nil {
+		return out, err
+	}
+	out.n = n
+	out.initialized = o.initialized
+	return out, nil
+}
+
+func isInitOpaqueMessage(p pointer, f *coderFieldInfo) error {
+	mp := p.AtomicGetPointer()
+	if mp.IsNil() {
+		return nil
+	}
+	return f.mi.checkInitializedPointer(mp)
+}
+
+func mergeOpaqueMessage(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
+	dstmp := dst.AtomicGetPointer()
+	if dstmp.IsNil() {
+		dstmp = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
+	}
+	f.mi.mergePointer(dstmp, src.AtomicGetPointer(), opts)
+}
+
+func sizeOpaqueGroup(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
+	return 2*f.tagsize + f.mi.sizePointer(p.AtomicGetPointer(), opts)
+}
+
+func appendOpaqueGroup(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
+	b = protowire.AppendVarint(b, f.wiretag) // start group
+	b, err := f.mi.marshalAppendPointer(b, p.AtomicGetPointer(), opts)
+	b = protowire.AppendVarint(b, f.wiretag+1) // end group
+	return b, err
+}
+
+func consumeOpaqueGroup(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
+	if wtyp != protowire.StartGroupType {
+		return out, errUnknown
+	}
+	mp := p.AtomicGetPointer()
+	if mp.IsNil() {
+		mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
+	}
+	o, e := f.mi.unmarshalPointer(b, mp, f.num, opts)
+	return o, e
+}
+
+func makeOpaqueRepeatedMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) {
+	if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
+		panic(fmt.Sprintf("invalid field: %v: unsupported type for opaque repeated message: %v", fd.FullName(), ft))
+	}
+	mt := ft.Elem().Elem() // *[]*T -> *T
+	mi := getMessageInfo(mt)
+	if mi == nil {
+		panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), mt))
+	}
+	switch fd.Kind() {
+	case protoreflect.MessageKind:
+		return mi, pointerCoderFuncs{
+			size:      sizeOpaqueMessageSlice,
+			marshal:   appendOpaqueMessageSlice,
+			unmarshal: consumeOpaqueMessageSlice,
+			isInit:    isInitOpaqueMessageSlice,
+			merge:     mergeOpaqueMessageSlice,
+		}
+	case protoreflect.GroupKind:
+		return mi, pointerCoderFuncs{
+			size:      sizeOpaqueGroupSlice,
+			marshal:   appendOpaqueGroupSlice,
+			unmarshal: consumeOpaqueGroupSlice,
+			isInit:    isInitOpaqueMessageSlice,
+			merge:     mergeOpaqueMessageSlice,
+		}
+	}
+	panic("unexpected field kind")
+}
+
+func sizeOpaqueMessageSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
+	s := p.AtomicGetPointer().PointerSlice()
+	n := 0
+	for _, v := range s {
+		n += protowire.SizeBytes(f.mi.sizePointer(v, opts)) + f.tagsize
+	}
+	return n
+}
+
+func appendOpaqueMessageSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
+	s := p.AtomicGetPointer().PointerSlice()
+	var err error
+	for _, v := range s {
+		b = protowire.AppendVarint(b, f.wiretag)
+		siz := f.mi.sizePointer(v, opts)
+		b = protowire.AppendVarint(b, uint64(siz))
+		before := len(b)
+		b, err = f.mi.marshalAppendPointer(b, v, opts)
+		if err != nil {
+			return b, err
+		}
+		if measuredSize := len(b) - before; siz != measuredSize {
+			return nil, errors.MismatchedSizeCalculation(siz, measuredSize)
+		}
+	}
+	return b, nil
+}
+
+func consumeOpaqueMessageSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
+	if wtyp != protowire.BytesType {
+		return out, errUnknown
+	}
+	v, n := protowire.ConsumeBytes(b)
+	if n < 0 {
+		return out, errDecode
+	}
+	mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
+	o, err := f.mi.unmarshalPointer(v, mp, 0, opts)
+	if err != nil {
+		return out, err
+	}
+	sp := p.AtomicGetPointer()
+	if sp.IsNil() {
+		sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
+	}
+	sp.AppendPointerSlice(mp)
+	out.n = n
+	out.initialized = o.initialized
+	return out, nil
+}
+
+func isInitOpaqueMessageSlice(p pointer, f *coderFieldInfo) error {
+	sp := p.AtomicGetPointer()
+	if sp.IsNil() {
+		return nil
+	}
+	s := sp.PointerSlice()
+	for _, v := range s {
+		if err := f.mi.checkInitializedPointer(v); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func mergeOpaqueMessageSlice(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
+	ds := dst.AtomicGetPointer()
+	if ds.IsNil() {
+		ds = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
+	}
+	for _, sp := range src.AtomicGetPointer().PointerSlice() {
+		dm := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
+		f.mi.mergePointer(dm, sp, opts)
+		ds.AppendPointerSlice(dm)
+	}
+}
+
+func sizeOpaqueGroupSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
+	s := p.AtomicGetPointer().PointerSlice()
+	n := 0
+	for _, v := range s {
+		n += 2*f.tagsize + f.mi.sizePointer(v, opts)
+	}
+	return n
+}
+
+func appendOpaqueGroupSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
+	s := p.AtomicGetPointer().PointerSlice()
+	var err error
+	for _, v := range s {
+		b = protowire.AppendVarint(b, f.wiretag) // start group
+		b, err = f.mi.marshalAppendPointer(b, v, opts)
+		if err != nil {
+			return b, err
+		}
+		b = protowire.AppendVarint(b, f.wiretag+1) // end group
+	}
+	return b, nil
+}
+
+func consumeOpaqueGroupSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
+	if wtyp != protowire.StartGroupType {
+		return out, errUnknown
+	}
+	mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
+	out, err = f.mi.unmarshalPointer(b, mp, f.num, opts)
+	if err != nil {
+		return out, err
+	}
+	sp := p.AtomicGetPointer()
+	if sp.IsNil() {
+		sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
+	}
+	sp.AppendPointerSlice(mp)
+	return out, err
+}
diff --git a/internal/impl/codec_message.go b/internal/impl/codec_message.go
index 78be9df..2f7b363 100644
--- a/internal/impl/codec_message.go
+++ b/internal/impl/codec_message.go
@@ -32,6 +32,10 @@
 	needsInitCheck     bool
 	isMessageSet       bool
 	numRequiredFields  uint8
+
+	lazyOffset     offset
+	presenceOffset offset
+	presenceSize   presenceSize
 }
 
 type coderFieldInfo struct {
@@ -45,12 +49,19 @@
 	tagsize    int                      // size of the varint-encoded tag
 	isPointer  bool                     // true if IsNil may be called on the struct field
 	isRequired bool                     // true if field is required
+
+	isLazy        bool
+	presenceIndex uint32
 }
 
+const noPresence = 0xffffffff
+
 func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) {
 	mi.sizecacheOffset = invalidOffset
 	mi.unknownOffset = invalidOffset
 	mi.extensionOffset = invalidOffset
+	mi.lazyOffset = invalidOffset
+	mi.presenceOffset = si.presenceOffset
 
 	if si.sizecacheOffset.IsValid() && si.sizecacheType == sizecacheType {
 		mi.sizecacheOffset = si.sizecacheOffset
@@ -127,6 +138,8 @@
 			validation: newFieldValidationInfo(mi, si, fd, ft),
 			isPointer:  fd.Cardinality() == protoreflect.Repeated || fd.HasPresence(),
 			isRequired: fd.Cardinality() == protoreflect.Required,
+
+			presenceIndex: noPresence,
 		}
 		mi.orderedCoderFields = append(mi.orderedCoderFields, cf)
 		mi.coderFields[cf.num] = cf
diff --git a/internal/impl/codec_message_opaque.go b/internal/impl/codec_message_opaque.go
new file mode 100644
index 0000000..88c16ae
--- /dev/null
+++ b/internal/impl/codec_message_opaque.go
@@ -0,0 +1,156 @@
+// 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 impl
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/internal/encoding/messageset"
+	"google.golang.org/protobuf/internal/order"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	piface "google.golang.org/protobuf/runtime/protoiface"
+)
+
+func (mi *MessageInfo) makeOpaqueCoderMethods(t reflect.Type, si opaqueStructInfo) {
+	mi.sizecacheOffset = si.sizecacheOffset
+	mi.unknownOffset = si.unknownOffset
+	mi.unknownPtrKind = si.unknownType.Kind() == reflect.Ptr
+	mi.extensionOffset = si.extensionOffset
+	mi.lazyOffset = si.lazyOffset
+	mi.presenceOffset = si.presenceOffset
+
+	mi.coderFields = make(map[protowire.Number]*coderFieldInfo)
+	fields := mi.Desc.Fields()
+	for i := 0; i < fields.Len(); i++ {
+		fd := fields.Get(i)
+
+		fs := si.fieldsByNumber[fd.Number()]
+		if fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic() {
+			fs = si.oneofsByName[fd.ContainingOneof().Name()]
+		}
+		ft := fs.Type
+		var wiretag uint64
+		if !fd.IsPacked() {
+			wiretag = protowire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
+		} else {
+			wiretag = protowire.EncodeTag(fd.Number(), protowire.BytesType)
+		}
+		var fieldOffset offset
+		var funcs pointerCoderFuncs
+		var childMessage *MessageInfo
+		switch {
+		case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
+			fieldOffset = offsetOf(fs, mi.Exporter)
+		case fd.IsWeak():
+			fieldOffset = si.weakOffset
+			funcs = makeWeakMessageFieldCoder(fd)
+		case fd.Message() != nil && !fd.IsMap():
+			fieldOffset = offsetOf(fs, mi.Exporter)
+			if fd.IsList() {
+				childMessage, funcs = makeOpaqueRepeatedMessageFieldCoder(fd, ft)
+			} else {
+				childMessage, funcs = makeOpaqueMessageFieldCoder(fd, ft)
+			}
+		default:
+			fieldOffset = offsetOf(fs, mi.Exporter)
+			childMessage, funcs = fieldCoder(fd, ft)
+		}
+		cf := &coderFieldInfo{
+			num:        fd.Number(),
+			offset:     fieldOffset,
+			wiretag:    wiretag,
+			ft:         ft,
+			tagsize:    protowire.SizeVarint(wiretag),
+			funcs:      funcs,
+			mi:         childMessage,
+			validation: newFieldValidationInfo(mi, si.structInfo, fd, ft),
+			isPointer: (fd.Cardinality() == protoreflect.Repeated ||
+				fd.Kind() == protoreflect.MessageKind ||
+				fd.Kind() == protoreflect.GroupKind),
+			isRequired:    fd.Cardinality() == protoreflect.Required,
+			presenceIndex: noPresence,
+		}
+
+		// TODO: Use presence for all fields.
+		//
+		// In some cases, such as maps, presence means only "might be set" rather
+		// than "is definitely set", but every field should have a presence bit to
+		// permit us to skip over definitely-unset fields at marshal time.
+
+		var hasPresence bool
+		hasPresence, cf.isLazy = usePresenceForField(si, fd)
+
+		if hasPresence {
+			cf.presenceIndex, mi.presenceSize = presenceIndex(mi.Desc, fd)
+		}
+
+		mi.orderedCoderFields = append(mi.orderedCoderFields, cf)
+		mi.coderFields[cf.num] = cf
+	}
+	for i, oneofs := 0, mi.Desc.Oneofs(); i < oneofs.Len(); i++ {
+		if od := oneofs.Get(i); !od.IsSynthetic() {
+			mi.initOneofFieldCoders(od, si.structInfo)
+		}
+	}
+	if messageset.IsMessageSet(mi.Desc) {
+		if !mi.extensionOffset.IsValid() {
+			panic(fmt.Sprintf("%v: MessageSet with no extensions field", mi.Desc.FullName()))
+		}
+		if !mi.unknownOffset.IsValid() {
+			panic(fmt.Sprintf("%v: MessageSet with no unknown field", mi.Desc.FullName()))
+		}
+		mi.isMessageSet = true
+	}
+	sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
+		return mi.orderedCoderFields[i].num < mi.orderedCoderFields[j].num
+	})
+
+	var maxDense protoreflect.FieldNumber
+	for _, cf := range mi.orderedCoderFields {
+		if cf.num >= 16 && cf.num >= 2*maxDense {
+			break
+		}
+		maxDense = cf.num
+	}
+	mi.denseCoderFields = make([]*coderFieldInfo, maxDense+1)
+	for _, cf := range mi.orderedCoderFields {
+		if int(cf.num) > len(mi.denseCoderFields) {
+			break
+		}
+		mi.denseCoderFields[cf.num] = cf
+	}
+
+	// To preserve compatibility with historic wire output, marshal oneofs last.
+	if mi.Desc.Oneofs().Len() > 0 {
+		sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
+			fi := fields.ByNumber(mi.orderedCoderFields[i].num)
+			fj := fields.ByNumber(mi.orderedCoderFields[j].num)
+			return order.LegacyFieldOrder(fi, fj)
+		})
+	}
+
+	mi.needsInitCheck = needsInitCheck(mi.Desc)
+	if mi.methods.Marshal == nil && mi.methods.Size == nil {
+		mi.methods.Flags |= piface.SupportMarshalDeterministic
+		mi.methods.Marshal = mi.marshal
+		mi.methods.Size = mi.size
+	}
+	if mi.methods.Unmarshal == nil {
+		mi.methods.Flags |= piface.SupportUnmarshalDiscardUnknown
+		mi.methods.Unmarshal = mi.unmarshal
+	}
+	if mi.methods.CheckInitialized == nil {
+		mi.methods.CheckInitialized = mi.checkInitialized
+	}
+	if mi.methods.Merge == nil {
+		mi.methods.Merge = mi.merge
+	}
+	if mi.methods.Equal == nil {
+		mi.methods.Equal = equal
+	}
+}
diff --git a/internal/impl/decode.go b/internal/impl/decode.go
index cda0520..e0dd21f 100644
--- a/internal/impl/decode.go
+++ b/internal/impl/decode.go
@@ -34,6 +34,8 @@
 		AllowPartial:   true,
 		DiscardUnknown: o.DiscardUnknown(),
 		Resolver:       o.resolver,
+
+		NoLazyDecoding: o.NoLazyDecoding(),
 	}
 }
 
@@ -41,13 +43,26 @@
 	return o.flags&protoiface.UnmarshalDiscardUnknown != 0
 }
 
-func (o unmarshalOptions) IsDefault() bool {
-	return o.flags == 0 && o.resolver == protoregistry.GlobalTypes
+func (o unmarshalOptions) AliasBuffer() bool { return o.flags&protoiface.UnmarshalAliasBuffer != 0 }
+func (o unmarshalOptions) Validated() bool   { return o.flags&protoiface.UnmarshalValidated != 0 }
+func (o unmarshalOptions) NoLazyDecoding() bool {
+	return o.flags&protoiface.UnmarshalNoLazyDecoding != 0
+}
+
+func (o unmarshalOptions) CanBeLazy() bool {
+	if o.resolver != protoregistry.GlobalTypes {
+		return false
+	}
+	// We ignore the UnmarshalInvalidateSizeCache even though it's not in the default set
+	return (o.flags & ^(protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated | protoiface.UnmarshalCheckRequired)) == 0
 }
 
 var lazyUnmarshalOptions = unmarshalOptions{
 	resolver: protoregistry.GlobalTypes,
-	depth:    protowire.DefaultRecursionLimit,
+
+	flags: protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated,
+
+	depth: protowire.DefaultRecursionLimit,
 }
 
 type unmarshalOutput struct {
@@ -94,9 +109,30 @@
 	if flags.ProtoLegacy && mi.isMessageSet {
 		return unmarshalMessageSet(mi, b, p, opts)
 	}
+
+	lazyDecoding := LazyEnabled() // default
+	if opts.NoLazyDecoding() {
+		lazyDecoding = false // explicitly disabled
+	}
+	if mi.lazyOffset.IsValid() && lazyDecoding {
+		return mi.unmarshalPointerLazy(b, p, groupTag, opts)
+	}
+	return mi.unmarshalPointerEager(b, p, groupTag, opts)
+}
+
+// unmarshalPointerEager is the message unmarshalling function for all messages that are not lazy.
+// The corresponding function for Lazy is in google_lazy.go.
+func (mi *MessageInfo) unmarshalPointerEager(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
+
 	initialized := true
 	var requiredMask uint64
 	var exts *map[int32]ExtensionField
+
+	var presence presence
+	if mi.presenceOffset.IsValid() {
+		presence = p.Apply(mi.presenceOffset).PresenceInfo()
+	}
+
 	start := len(b)
 	for len(b) > 0 {
 		// Parse the tag (field number and wire type).
@@ -154,6 +190,11 @@
 			if f.funcs.isInit != nil && !o.initialized {
 				initialized = false
 			}
+
+			if f.presenceIndex != noPresence {
+				presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
+			}
+
 		default:
 			// Possible extension.
 			if exts == nil && mi.extensionOffset.IsValid() {
@@ -222,7 +263,7 @@
 		return out, errUnknown
 	}
 	if flags.LazyUnmarshalExtensions {
-		if opts.IsDefault() && x.canLazy(xt) {
+		if opts.CanBeLazy() && x.canLazy(xt) {
 			out, valid := skipExtension(b, xi, num, wtyp, opts)
 			switch valid {
 			case ValidationValid:
@@ -270,6 +311,13 @@
 		if n < 0 {
 			return out, ValidationUnknown
 		}
+
+		if opts.Validated() {
+			out.initialized = true
+			out.n = n
+			return out, ValidationValid
+		}
+
 		out, st := xi.validation.mi.validate(v, 0, opts)
 		out.n = n
 		return out, st
diff --git a/internal/impl/encode.go b/internal/impl/encode.go
index 6254f5d..b2e2122 100644
--- a/internal/impl/encode.go
+++ b/internal/impl/encode.go
@@ -10,6 +10,7 @@
 	"sync/atomic"
 
 	"google.golang.org/protobuf/internal/flags"
+	"google.golang.org/protobuf/internal/protolazy"
 	"google.golang.org/protobuf/proto"
 	piface "google.golang.org/protobuf/runtime/protoiface"
 )
@@ -71,11 +72,39 @@
 		e := p.Apply(mi.extensionOffset).Extensions()
 		size += mi.sizeExtensions(e, opts)
 	}
+
+	var lazy **protolazy.XXX_lazyUnmarshalInfo
+	var presence presence
+	if mi.presenceOffset.IsValid() {
+		presence = p.Apply(mi.presenceOffset).PresenceInfo()
+		if mi.lazyOffset.IsValid() {
+			lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
+		}
+	}
+
 	for _, f := range mi.orderedCoderFields {
 		if f.funcs.size == nil {
 			continue
 		}
 		fptr := p.Apply(f.offset)
+
+		if f.presenceIndex != noPresence {
+			if !presence.Present(f.presenceIndex) {
+				continue
+			}
+
+			if f.isLazy && fptr.AtomicGetPointer().IsNil() {
+				if lazyFields(opts) {
+					size += (*lazy).SizeField(uint32(f.num))
+					continue
+				} else {
+					mi.lazyUnmarshal(p, f.num)
+				}
+			}
+			size += f.funcs.size(fptr, f, opts)
+			continue
+		}
+
 		if f.isPointer && fptr.Elem().IsNil() {
 			continue
 		}
@@ -134,11 +163,52 @@
 			return b, err
 		}
 	}
+
+	var lazy **protolazy.XXX_lazyUnmarshalInfo
+	var presence presence
+	if mi.presenceOffset.IsValid() {
+		presence = p.Apply(mi.presenceOffset).PresenceInfo()
+		if mi.lazyOffset.IsValid() {
+			lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
+		}
+	}
+
 	for _, f := range mi.orderedCoderFields {
 		if f.funcs.marshal == nil {
 			continue
 		}
 		fptr := p.Apply(f.offset)
+
+		if f.presenceIndex != noPresence {
+			if !presence.Present(f.presenceIndex) {
+				continue
+			}
+			if f.isLazy {
+				// Be careful, this field needs to be read atomically, like for a get
+				if f.isPointer && fptr.AtomicGetPointer().IsNil() {
+					if lazyFields(opts) {
+						b, _ = (*lazy).AppendField(b, uint32(f.num))
+						continue
+					} else {
+						mi.lazyUnmarshal(p, f.num)
+					}
+				}
+
+				b, err = f.funcs.marshal(b, fptr, f, opts)
+				if err != nil {
+					return b, err
+				}
+				continue
+			} else if f.isPointer && fptr.Elem().IsNil() {
+				continue
+			}
+			b, err = f.funcs.marshal(b, fptr, f, opts)
+			if err != nil {
+				return b, err
+			}
+			continue
+		}
+
 		if f.isPointer && fptr.Elem().IsNil() {
 			continue
 		}
@@ -163,6 +233,14 @@
 	return opts.flags&piface.MarshalDeterministic == 0
 }
 
+// lazyFields returns true if we should attempt to keep fields lazy over size and marshal.
+func lazyFields(opts marshalOptions) bool {
+	// When deterministic marshaling is requested, force an unmarshal for lazy
+	// fields to produce a deterministic result, instead of passing through
+	// bytes lazily that may or may not match what Go Protobuf would produce.
+	return opts.flags&piface.MarshalDeterministic == 0
+}
+
 func (mi *MessageInfo) sizeExtensions(ext *map[int32]ExtensionField, opts marshalOptions) (n int) {
 	if ext == nil {
 		return 0
diff --git a/internal/impl/lazy.go b/internal/impl/lazy.go
new file mode 100644
index 0000000..e8fb6c3
--- /dev/null
+++ b/internal/impl/lazy.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 impl
+
+import (
+	"fmt"
+	"math/bits"
+	"os"
+	"reflect"
+	"sort"
+	"sync/atomic"
+
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/internal/errors"
+	"google.golang.org/protobuf/internal/protolazy"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	preg "google.golang.org/protobuf/reflect/protoregistry"
+	piface "google.golang.org/protobuf/runtime/protoiface"
+)
+
+var enableLazy int32 = func() int32 {
+	if os.Getenv("GOPROTODEBUG") == "nolazy" {
+		return 0
+	}
+	return 1
+}()
+
+// EnableLazyUnmarshal enables lazy unmarshaling.
+func EnableLazyUnmarshal(enable bool) {
+	if enable {
+		atomic.StoreInt32(&enableLazy, 1)
+		return
+	}
+	atomic.StoreInt32(&enableLazy, 0)
+}
+
+// LazyEnabled reports whether lazy unmarshalling is currently enabled.
+func LazyEnabled() bool {
+	return atomic.LoadInt32(&enableLazy) != 0
+}
+
+// UnmarshalField unmarshals a field in a message.
+func UnmarshalField(m interface{}, num protowire.Number) {
+	switch m := m.(type) {
+	case *messageState:
+		m.messageInfo().lazyUnmarshal(m.pointer(), num)
+	case *messageReflectWrapper:
+		m.messageInfo().lazyUnmarshal(m.pointer(), num)
+	default:
+		panic(fmt.Sprintf("unsupported wrapper type %T", m))
+	}
+}
+
+func (mi *MessageInfo) lazyUnmarshal(p pointer, num protoreflect.FieldNumber) {
+	var f *coderFieldInfo
+	if int(num) < len(mi.denseCoderFields) {
+		f = mi.denseCoderFields[num]
+	} else {
+		f = mi.coderFields[num]
+	}
+	if f == nil {
+		panic(fmt.Sprintf("lazyUnmarshal: field info for %v.%v", mi.Desc.FullName(), num))
+	}
+	lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr()
+	start, end, found, _, multipleEntries := lazy.FindFieldInProto(uint32(num))
+	if !found && multipleEntries == nil {
+		panic(fmt.Sprintf("lazyUnmarshal: can't find field data for %v.%v", mi.Desc.FullName(), num))
+	}
+	// The actual pointer in the message can not be set until the whole struct is filled in, otherwise we will have races.
+	// Create another pointer and set it atomically, if we won the race and the pointer in the original message is still nil.
+	fp := pointerOfValue(reflect.New(f.ft))
+	if multipleEntries != nil {
+		for _, entry := range multipleEntries {
+			mi.unmarshalField(lazy.Buffer()[entry.Start:entry.End], fp, f, lazy, lazy.UnmarshalFlags())
+		}
+	} else {
+		mi.unmarshalField(lazy.Buffer()[start:end], fp, f, lazy, lazy.UnmarshalFlags())
+	}
+	p.Apply(f.offset).AtomicSetPointerIfNil(fp.Elem())
+}
+
+func (mi *MessageInfo) unmarshalField(b []byte, p pointer, f *coderFieldInfo, lazyInfo *protolazy.XXX_lazyUnmarshalInfo, flags piface.UnmarshalInputFlags) error {
+	opts := lazyUnmarshalOptions
+	opts.flags |= flags
+	for len(b) > 0 {
+		// Parse the tag (field number and wire type).
+		var tag uint64
+		if b[0] < 0x80 {
+			tag = uint64(b[0])
+			b = b[1:]
+		} else if len(b) >= 2 && b[1] < 128 {
+			tag = uint64(b[0]&0x7f) + uint64(b[1])<<7
+			b = b[2:]
+		} else {
+			var n int
+			tag, n = protowire.ConsumeVarint(b)
+			if n < 0 {
+				return errors.New("invalid wire data")
+			}
+			b = b[n:]
+		}
+		var num protowire.Number
+		if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) {
+			return errors.New("invalid wire data")
+		} else {
+			num = protowire.Number(n)
+		}
+		wtyp := protowire.Type(tag & 7)
+		if num == f.num {
+			o, err := f.funcs.unmarshal(b, p, wtyp, f, opts)
+			if err == nil {
+				b = b[o.n:]
+				continue
+			}
+			if err != errUnknown {
+				return err
+			}
+		}
+		n := protowire.ConsumeFieldValue(num, wtyp, b)
+		if n < 0 {
+			return errors.New("invalid wire data")
+		}
+		b = b[n:]
+	}
+	return nil
+}
+
+func (mi *MessageInfo) skipField(b []byte, f *coderFieldInfo, wtyp protowire.Type, opts unmarshalOptions) (out unmarshalOutput, _ ValidationStatus) {
+	fmi := f.validation.mi
+	if fmi == nil {
+		fd := mi.Desc.Fields().ByNumber(f.num)
+		if fd == nil || !fd.IsWeak() {
+			return out, ValidationUnknown
+		}
+		messageName := fd.Message().FullName()
+		messageType, err := preg.GlobalTypes.FindMessageByName(messageName)
+		if err != nil {
+			return out, ValidationUnknown
+		}
+		var ok bool
+		fmi, ok = messageType.(*MessageInfo)
+		if !ok {
+			return out, ValidationUnknown
+		}
+	}
+	fmi.init()
+	switch f.validation.typ {
+	case validationTypeMessage:
+		if wtyp != protowire.BytesType {
+			return out, ValidationWrongWireType
+		}
+		v, n := protowire.ConsumeBytes(b)
+		if n < 0 {
+			return out, ValidationInvalid
+		}
+		out, st := fmi.validate(v, 0, opts)
+		out.n = n
+		return out, st
+	case validationTypeGroup:
+		if wtyp != protowire.StartGroupType {
+			return out, ValidationWrongWireType
+		}
+		out, st := fmi.validate(b, f.num, opts)
+		return out, st
+	default:
+		return out, ValidationUnknown
+	}
+}
+
+// unmarshalPointerLazy is similar to unmarshalPointerEager, but it
+// specifically handles lazy unmarshalling.  it expects lazyOffset and
+// presenceOffset to both be valid.
+func (mi *MessageInfo) unmarshalPointerLazy(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
+	initialized := true
+	var requiredMask uint64
+	var lazy **protolazy.XXX_lazyUnmarshalInfo
+	var presence presence
+	var lazyIndex []protolazy.IndexEntry
+	var lastNum protowire.Number
+	outOfOrder := false
+	lazyDecode := false
+	presence = p.Apply(mi.presenceOffset).PresenceInfo()
+	lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
+	if !presence.AnyPresent(mi.presenceSize) {
+		if opts.CanBeLazy() {
+			// If the message contains existing data, we need to merge into it.
+			// Lazy unmarshaling doesn't merge, so only enable it when the
+			// message is empty (has no presence bitmap).
+			lazyDecode = true
+			if *lazy == nil {
+				*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
+			}
+			(*lazy).SetUnmarshalFlags(opts.flags)
+			if !opts.AliasBuffer() {
+				// Make a copy of the buffer for lazy unmarshaling.
+				// Set the AliasBuffer flag so recursive unmarshal
+				// operations reuse the copy.
+				b = append([]byte{}, b...)
+				opts.flags |= piface.UnmarshalAliasBuffer
+			}
+			(*lazy).SetBuffer(b)
+		}
+	}
+	// Track special handling of lazy fields.
+	//
+	// In the common case, all fields are lazyValidateOnly (and lazyFields remains nil).
+	// In the event that validation for a field fails, this map tracks handling of the field.
+	type lazyAction uint8
+	const (
+		lazyValidateOnly   lazyAction = iota // validate the field only
+		lazyUnmarshalNow                     // eagerly unmarshal the field
+		lazyUnmarshalLater                   // unmarshal the field after the message is fully processed
+	)
+	var lazyFields map[*coderFieldInfo]lazyAction
+	var exts *map[int32]ExtensionField
+	start := len(b)
+	pos := 0
+	for len(b) > 0 {
+		// Parse the tag (field number and wire type).
+		var tag uint64
+		if b[0] < 0x80 {
+			tag = uint64(b[0])
+			b = b[1:]
+		} else if len(b) >= 2 && b[1] < 128 {
+			tag = uint64(b[0]&0x7f) + uint64(b[1])<<7
+			b = b[2:]
+		} else {
+			var n int
+			tag, n = protowire.ConsumeVarint(b)
+			if n < 0 {
+				return out, errDecode
+			}
+			b = b[n:]
+		}
+		var num protowire.Number
+		if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) {
+			return out, errors.New("invalid field number")
+		} else {
+			num = protowire.Number(n)
+		}
+		wtyp := protowire.Type(tag & 7)
+
+		if wtyp == protowire.EndGroupType {
+			if num != groupTag {
+				return out, errors.New("mismatching end group marker")
+			}
+			groupTag = 0
+			break
+		}
+
+		var f *coderFieldInfo
+		if int(num) < len(mi.denseCoderFields) {
+			f = mi.denseCoderFields[num]
+		} else {
+			f = mi.coderFields[num]
+		}
+		var n int
+		err := errUnknown
+		discardUnknown := false
+	Field:
+		switch {
+		case f != nil:
+			if f.funcs.unmarshal == nil {
+				break
+			}
+			if f.isLazy && lazyDecode {
+				switch {
+				case lazyFields == nil || lazyFields[f] == lazyValidateOnly:
+					// Attempt to validate this field and leave it for later lazy unmarshaling.
+					o, valid := mi.skipField(b, f, wtyp, opts)
+					switch valid {
+					case ValidationValid:
+						// Skip over the valid field and continue.
+						err = nil
+						presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
+						requiredMask |= f.validation.requiredBit
+						if !o.initialized {
+							initialized = false
+						}
+						n = o.n
+						break Field
+					case ValidationInvalid:
+						return out, errors.New("invalid proto wire format")
+					case ValidationWrongWireType:
+						break Field
+					case ValidationUnknown:
+						if lazyFields == nil {
+							lazyFields = make(map[*coderFieldInfo]lazyAction)
+						}
+						if presence.Present(f.presenceIndex) {
+							// We were unable to determine if the field is valid or not,
+							// and we've already skipped over at least one instance of this
+							// field. Clear the presence bit (so if we stop decoding early,
+							// we don't leave a partially-initialized field around) and flag
+							// the field for unmarshaling before we return.
+							presence.ClearPresent(f.presenceIndex)
+							lazyFields[f] = lazyUnmarshalLater
+							discardUnknown = true
+							break Field
+						} else {
+							// We were unable to determine if the field is valid or not,
+							// but this is the first time we've seen it. Flag it as needing
+							// eager unmarshaling and fall through to the eager unmarshal case below.
+							lazyFields[f] = lazyUnmarshalNow
+						}
+					}
+				case lazyFields[f] == lazyUnmarshalLater:
+					// This field will be unmarshaled in a separate pass below.
+					// Skip over it here.
+					discardUnknown = true
+					break Field
+				default:
+					// Eagerly unmarshal the field.
+				}
+			}
+			if f.isLazy && !lazyDecode && presence.Present(f.presenceIndex) {
+				if p.Apply(f.offset).AtomicGetPointer().IsNil() {
+					mi.lazyUnmarshal(p, f.num)
+				}
+			}
+			var o unmarshalOutput
+			o, err = f.funcs.unmarshal(b, p.Apply(f.offset), wtyp, f, opts)
+			n = o.n
+			if err != nil {
+				break
+			}
+			requiredMask |= f.validation.requiredBit
+			if f.funcs.isInit != nil && !o.initialized {
+				initialized = false
+			}
+			if f.presenceIndex != noPresence {
+				presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
+			}
+		default:
+			// Possible extension.
+			if exts == nil && mi.extensionOffset.IsValid() {
+				exts = p.Apply(mi.extensionOffset).Extensions()
+				if *exts == nil {
+					*exts = make(map[int32]ExtensionField)
+				}
+			}
+			if exts == nil {
+				break
+			}
+			var o unmarshalOutput
+			o, err = mi.unmarshalExtension(b, num, wtyp, *exts, opts)
+			if err != nil {
+				break
+			}
+			n = o.n
+			if !o.initialized {
+				initialized = false
+			}
+		}
+		if err != nil {
+			if err != errUnknown {
+				return out, err
+			}
+			n = protowire.ConsumeFieldValue(num, wtyp, b)
+			if n < 0 {
+				return out, errDecode
+			}
+			if !discardUnknown && !opts.DiscardUnknown() && mi.unknownOffset.IsValid() {
+				u := mi.mutableUnknownBytes(p)
+				*u = protowire.AppendTag(*u, num, wtyp)
+				*u = append(*u, b[:n]...)
+			}
+		}
+		b = b[n:]
+		end := start - len(b)
+		if lazyDecode && f != nil && f.isLazy {
+			if num != lastNum {
+				lazyIndex = append(lazyIndex, protolazy.IndexEntry{
+					FieldNum: uint32(num),
+					Start:    uint32(pos),
+					End:      uint32(end),
+				})
+			} else {
+				i := len(lazyIndex) - 1
+				lazyIndex[i].End = uint32(end)
+				lazyIndex[i].MultipleContiguous = true
+			}
+		}
+		if num < lastNum {
+			outOfOrder = true
+		}
+		pos = end
+		lastNum = num
+	}
+	if groupTag != 0 {
+		return out, errors.New("missing end group marker")
+	}
+	if lazyFields != nil {
+		// Some fields failed validation, and now need to be unmarshaled.
+		for f, action := range lazyFields {
+			if action != lazyUnmarshalLater {
+				continue
+			}
+			initialized = false
+			if *lazy == nil {
+				*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
+			}
+			if err := mi.unmarshalField((*lazy).Buffer(), p.Apply(f.offset), f, *lazy, opts.flags); err != nil {
+				return out, err
+			}
+			presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
+		}
+	}
+	if lazyDecode {
+		if outOfOrder {
+			sort.Slice(lazyIndex, func(i, j int) bool {
+				return lazyIndex[i].FieldNum < lazyIndex[j].FieldNum ||
+					(lazyIndex[i].FieldNum == lazyIndex[j].FieldNum &&
+						lazyIndex[i].Start < lazyIndex[j].Start)
+			})
+		}
+		if *lazy == nil {
+			*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
+		}
+
+		(*lazy).SetIndex(lazyIndex)
+	}
+	if mi.numRequiredFields > 0 && bits.OnesCount64(requiredMask) != int(mi.numRequiredFields) {
+		initialized = false
+	}
+	if initialized {
+		out.initialized = true
+	}
+	out.n = start - len(b)
+	return out, nil
+}
diff --git a/internal/impl/lazy_buffersharing_test.go b/internal/impl/lazy_buffersharing_test.go
new file mode 100644
index 0000000..15a7b04
--- /dev/null
+++ b/internal/impl/lazy_buffersharing_test.go
@@ -0,0 +1,151 @@
+// 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 impl_test
+
+import (
+	"testing"
+
+	mixedpb "google.golang.org/protobuf/internal/testprotos/mixed"
+	"google.golang.org/protobuf/proto"
+)
+
+var enableLazy = proto.UnmarshalOptions{}
+var disableLazy = proto.UnmarshalOptions{
+	NoLazyDecoding: true,
+}
+
+func TestCopyTopLevelLazy(t *testing.T) {
+	testCopyTopLevel(t, enableLazy)
+}
+
+func TestCopyTopLevelEager(t *testing.T) {
+	testCopyTopLevel(t, disableLazy)
+}
+
+// testCopyTopLevel tests that the buffer is copied to a safe location
+// when the opaque proto is the top level proto
+func testCopyTopLevel(t *testing.T, unmarshalOpts proto.UnmarshalOptions) {
+	m := mixedpb.OpaqueLazy_builder{
+		Opaque: mixedpb.OpaqueLazy_builder{
+			OptionalInt32: proto.Int32(23),
+		}.Build(),
+	}.Build()
+	if got, want := m.GetOpaque().GetOptionalInt32(), int32(23); got != want {
+		t.Errorf("Build(): unexpected optional_int32: got %v, want %v", got, want)
+	}
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatalf("Could not marshal healthy proto %v.", m)
+	}
+	m2 := &mixedpb.OpaqueLazy{}
+	if err := unmarshalOpts.Unmarshal(b, m2); err != nil {
+		t.Fatalf("Could not unmarshal healthy proto buffer: %v.", b)
+	}
+	for i := 0; i < len(b); i++ {
+		b[i] = byte(0xFF)
+	}
+	if got, want := m2.GetOpaque().GetOptionalInt32(), int32(23); got != want {
+		t.Errorf("Mixed proto referred to shared buffer: got %v, want %v", got, want)
+	}
+}
+
+func TestCopyWhenContainedInOpenLazy(t *testing.T) {
+	testCopyWhenContainedInOpen(t, enableLazy)
+}
+
+func TestCopyWhenContainedInOpenEager(t *testing.T) {
+	testCopyWhenContainedInOpen(t, disableLazy)
+}
+
+// testCopyWhenContainedInOpen tests that the buffer is copied
+// for opaque messages that are not on the top level
+func testCopyWhenContainedInOpen(t *testing.T, unmarshalOpts proto.UnmarshalOptions) {
+	m := &mixedpb.OpenLazy{
+		Opaque: mixedpb.OpaqueLazy_builder{
+			Opaque: mixedpb.OpaqueLazy_builder{
+				OptionalInt32: proto.Int32(23),
+			}.Build(),
+		}.Build(),
+	}
+	if got, want := m.GetOpaque().GetOpaque().GetOptionalInt32(), int32(23); got != want {
+		t.Errorf("Build(): unexpected optional_int32: got %v, want %v", got, want)
+	}
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatalf("Could not marshal healthy proto %v.", m)
+	}
+	m2 := &mixedpb.OpenLazy{}
+	if err := unmarshalOpts.Unmarshal(b, m2); err != nil {
+		t.Fatalf("Could not unmarshal healthy proto buffer: %v.", b)
+	}
+	for i := 0; i < len(b); i++ {
+		b[i] = byte(0xFF)
+	}
+	if got, want := m2.GetOpaque().GetOpaque().GetOptionalInt32(), int32(23); got != want {
+		t.Errorf("Build(): unexpected optional_int32: got %v, want %v", got, want)
+	}
+}
+
+func TestNoExcessiveCopyLazy(t *testing.T) {
+	testNoExcessiveCopy(t, enableLazy)
+}
+
+func TestNoExcessiveCopyEager(t *testing.T) {
+	testNoExcessiveCopy(t, disableLazy)
+}
+
+// testNoExcessiveCopy tests that an opaque submessage does share the buffer
+// if the message above already got it copied
+func testNoExcessiveCopy(t *testing.T, unmarshalOpts proto.UnmarshalOptions) {
+	m := &mixedpb.OpenLazy{
+		Opaque: mixedpb.OpaqueLazy_builder{
+			Opaque: mixedpb.OpaqueLazy_builder{
+				OptionalInt32: proto.Int32(23),
+			}.Build(),
+		}.Build(),
+	}
+	if got, want := m.GetOpaque().GetOpaque().GetOptionalInt32(), int32(23); got != want {
+		t.Errorf("Build(): unexpected optional_int32: got %v, want %v", got, want)
+	}
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatalf("Could not marshal healthy proto %v.", m)
+	}
+	mm := &mixedpb.OpenLazy{}
+	if err := unmarshalOpts.Unmarshal(b, mm); err != nil {
+		t.Fatalf("Could not unmarshal healthy proto buffer: %v.", b)
+	}
+	m2 := mm.GetOpaque()
+	m3 := mm.GetOpaque().GetOpaque()
+	// Now, if we deliberately destroy the OpaqueM2 buffer, the OpaqueM3 buffer should
+	// be destroyed as well
+	if m2.XXX_lazyUnmarshalInfo == nil {
+		if m3.XXX_lazyUnmarshalInfo != nil {
+			t.Errorf("Inconsistent lazyUnmarshalInfo for subprotos")
+		}
+		// nothing to check, we don't have backing store
+		return
+	}
+
+	if m3.XXX_lazyUnmarshalInfo == nil {
+		t.Errorf("Inconsistent lazyUnmarshalInfo for subprotos (2)")
+		return
+	}
+	b = (*m2.XXX_lazyUnmarshalInfo).Protobuf
+	m2len := len(b)
+	for i := 0; i < len(b); i++ {
+		b[i] = byte(0xFF)
+	}
+	b = (*m3.XXX_lazyUnmarshalInfo).Protobuf
+	if m2len != 0 && len(b) == 0 {
+		t.Errorf("The lazy backing store for submessage is empty when it is not for the surronding message: %v.", m2len)
+	}
+	for i, x := range b {
+		if x != byte(0xFF) {
+			t.Errorf("Backing store for protocol buffer is not shared (index = %d, x = 0x%x)", i, x)
+		}
+	}
+
+}
diff --git a/internal/impl/lazy_field_normalized_test.go b/internal/impl/lazy_field_normalized_test.go
new file mode 100644
index 0000000..02c9ebe
--- /dev/null
+++ b/internal/impl/lazy_field_normalized_test.go
@@ -0,0 +1,156 @@
+// 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 impl_test
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protopack"
+
+	lnwtpb "google.golang.org/protobuf/internal/testprotos/lazy"
+)
+
+func unmarshalsTheSame(b []byte, expected *lnwtpb.FTop) error {
+	unmarshaledTop := &lnwtpb.FTop{}
+	if err := proto.Unmarshal(b, unmarshaledTop); err != nil {
+		return err
+	}
+	if !proto.Equal(unmarshaledTop, expected) {
+		return fmt.Errorf("!proto.Equal")
+	}
+	return nil
+}
+
+func bytesTag(num protowire.Number) protopack.Tag {
+	return protopack.Tag{
+		Number: num,
+		Type:   protopack.BytesType,
+	}
+}
+
+func varintTag(num protowire.Number) protopack.Tag {
+	return protopack.Tag{
+		Number: num,
+		Type:   protopack.VarintType,
+	}
+}
+
+// Constructs a message encoded in denormalized (non-minimal) wire format, but
+// using two levels of nesting: A top-level message with a child message which
+// in turn has a grandchild message.
+func denormalizedTwoLevelField() ([]byte, *lnwtpb.FTop, error) {
+	expectedMessage := &lnwtpb.FTop{
+		A: proto.Uint32(2342),
+		Child: &lnwtpb.FSub{
+			Grandchild: &lnwtpb.FSub{
+				B: proto.Uint32(1337),
+			},
+		},
+	}
+
+	fullMessage := protopack.Message{
+		varintTag(1), protopack.Varint(2342),
+		// Child
+		bytesTag(2), protopack.LengthPrefix(protopack.Message{
+			// Grandchild
+			bytesTag(4), protopack.LengthPrefix(protopack.Message{
+				// The first occurrence of B matches expectedMessage:
+				varintTag(2), protopack.Varint(1337),
+				// This second duplicative occurrence of B is spec'd in Protobuf:
+				// https://github.com/protocolbuffers/protobuf/issues/9257
+				varintTag(2), protopack.Varint(1337),
+			}),
+		}),
+	}.Marshal()
+
+	return fullMessage, expectedMessage, nil
+}
+
+func TestInvalidWireFormat(t *testing.T) {
+	fullMessage, expectedMessage, err := denormalizedTwoLevelField()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	top := &lnwtpb.FTop{}
+	if err := proto.Unmarshal(fullMessage, top); err != nil {
+		t.Fatal(err)
+	}
+
+	// Access the top-level submessage, but not the grandchild.
+	// This populates the size cache in the top-level message.
+	top.GetChild()
+
+	marshal1, err := proto.MarshalOptions{
+		UseCachedSize: true,
+	}.Marshal(top)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := unmarshalsTheSame(marshal1, expectedMessage); err != nil {
+		t.Error(err)
+	}
+
+	// Call top.GetChild().GetGrandchild() to unmarshal the lazy message,
+	// which will normalize it: the size cache shrinks from 6 bytes to 3.
+	// Notably, top.GetChild()’s size cache is not updated!
+	top.GetChild().GetGrandchild()
+	marshal2, err := proto.MarshalOptions{
+		// GetGrandchild+UseCachedSize is one way to trigger this bug.
+		// The other way is to call GetGrandchild in another goroutine,
+		// after proto.Marshal has called proto.Size but
+		// before proto.Marshal started encoding.
+		UseCachedSize: true,
+	}.Marshal(top)
+	if err != nil {
+		if strings.Contains(err.Error(), "size mismatch") {
+			// This is the expected failure mode: proto.Marshal() detects the
+			// combination of non-minimal wire format and lazy decoding and
+			// returns an error, prompting the user to disable lazy decoding.
+			return
+		}
+		t.Fatal(err)
+	}
+	if err := unmarshalsTheSame(marshal2, expectedMessage); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestIdenticalOverAccessWhenDeterministic(t *testing.T) {
+	fullMessage, _, err := denormalizedTwoLevelField()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	top := &lnwtpb.FTop{}
+	if err := proto.Unmarshal(fullMessage, top); err != nil {
+		t.Fatal(err)
+	}
+
+	deterministic := proto.MarshalOptions{
+		Deterministic: true,
+	}
+	marshal1, err := deterministic.Marshal(top)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Call top.GetChild().GetGrandchild() to unmarshal the lazy message,
+	// which will normalize it.
+	top.GetChild().GetGrandchild()
+
+	marshal2, err := deterministic.Marshal(top)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(marshal1, marshal2) {
+		t.Errorf("MarshalOptions{Deterministic: true}.Marshal() not identical over accessing a non-minimal wire format lazy message:\nbefore:\n%x\nafter:\n%x", marshal1, marshal2)
+	}
+}
diff --git a/internal/impl/merge.go b/internal/impl/merge.go
index 7e65f64..8ffdce6 100644
--- a/internal/impl/merge.go
+++ b/internal/impl/merge.go
@@ -41,11 +41,38 @@
 	if src.IsNil() {
 		return
 	}
+
+	var presenceSrc presence
+	var presenceDst presence
+	if mi.presenceOffset.IsValid() {
+		presenceSrc = src.Apply(mi.presenceOffset).PresenceInfo()
+		presenceDst = dst.Apply(mi.presenceOffset).PresenceInfo()
+	}
+
 	for _, f := range mi.orderedCoderFields {
 		if f.funcs.merge == nil {
 			continue
 		}
 		sfptr := src.Apply(f.offset)
+
+		if f.presenceIndex != noPresence {
+			if !presenceSrc.Present(f.presenceIndex) {
+				continue
+			}
+			dfptr := dst.Apply(f.offset)
+			if f.isLazy {
+				if sfptr.AtomicGetPointer().IsNil() {
+					mi.lazyUnmarshal(src, f.num)
+				}
+				if presenceDst.Present(f.presenceIndex) && dfptr.AtomicGetPointer().IsNil() {
+					mi.lazyUnmarshal(dst, f.num)
+				}
+			}
+			f.funcs.merge(dst.Apply(f.offset), sfptr, f, opts)
+			presenceDst.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
+			continue
+		}
+
 		if f.isPointer && sfptr.Elem().IsNil() {
 			continue
 		}
diff --git a/internal/impl/message.go b/internal/impl/message.go
index 741b5ed..fa10a0f 100644
--- a/internal/impl/message.go
+++ b/internal/impl/message.go
@@ -79,6 +79,9 @@
 	if mi.initDone == 1 {
 		return
 	}
+	if opaqueInitHook(mi) {
+		return
+	}
 
 	t := mi.GoReflectType
 	if t.Kind() != reflect.Ptr && t.Elem().Kind() != reflect.Struct {
@@ -133,6 +136,9 @@
 	extensionOffset offset
 	extensionType   reflect.Type
 
+	lazyOffset     offset
+	presenceOffset offset
+
 	fieldsByNumber        map[protoreflect.FieldNumber]reflect.StructField
 	oneofsByName          map[protoreflect.Name]reflect.StructField
 	oneofWrappersByType   map[reflect.Type]protoreflect.FieldNumber
@@ -145,6 +151,8 @@
 		weakOffset:      invalidOffset,
 		unknownOffset:   invalidOffset,
 		extensionOffset: invalidOffset,
+		lazyOffset:      invalidOffset,
+		presenceOffset:  invalidOffset,
 
 		fieldsByNumber:        map[protoreflect.FieldNumber]reflect.StructField{},
 		oneofsByName:          map[protoreflect.Name]reflect.StructField{},
@@ -175,6 +183,10 @@
 				si.extensionOffset = offsetOf(f, mi.Exporter)
 				si.extensionType = f.Type
 			}
+		case "lazyFields", "XXX_lazyUnmarshalInfo":
+			si.lazyOffset = offsetOf(f, mi.Exporter)
+		case "XXX_presence":
+			si.presenceOffset = offsetOf(f, mi.Exporter)
 		default:
 			for _, s := range strings.Split(f.Tag.Get("protobuf"), ",") {
 				if len(s) > 0 && strings.Trim(s, "0123456789") == "" {
diff --git a/internal/impl/message_opaque.go b/internal/impl/message_opaque.go
new file mode 100644
index 0000000..d407dd7
--- /dev/null
+++ b/internal/impl/message_opaque.go
@@ -0,0 +1,614 @@
+// 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 impl
+
+import (
+	"fmt"
+	"math"
+	"reflect"
+	"strings"
+	"sync/atomic"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+type opaqueStructInfo struct {
+	structInfo
+}
+
+// isOpaque determines whether a protobuf message type is on the Opaque API.  It
+// checks whether the type is a Go struct that protoc-gen-go would generate.
+//
+// This function only detects newly generated messages from the v2
+// implementation of protoc-gen-go. It is unable to classify generated messages
+// that are too old or those that are generated by a different generator
+// such as protoc-gen-gogo.
+func isOpaque(t reflect.Type) bool {
+	// The current detection mechanism is to simply check the first field
+	// for a struct tag with the "protogen" key.
+	if t.Kind() == reflect.Struct && t.NumField() > 0 {
+		pgt := t.Field(0).Tag.Get("protogen")
+		return strings.HasPrefix(pgt, "opaque.")
+	}
+	return false
+}
+
+func opaqueInitHook(mi *MessageInfo) bool {
+	mt := mi.GoReflectType.Elem()
+	si := opaqueStructInfo{
+		structInfo: mi.makeStructInfo(mt),
+	}
+
+	if !isOpaque(mt) {
+		return false
+	}
+
+	defer atomic.StoreUint32(&mi.initDone, 1)
+
+	mi.fields = map[protoreflect.FieldNumber]*fieldInfo{}
+	fds := mi.Desc.Fields()
+	for i := 0; i < fds.Len(); i++ {
+		fd := fds.Get(i)
+		fs := si.fieldsByNumber[fd.Number()]
+		var fi fieldInfo
+		usePresence, _ := usePresenceForField(si, fd)
+
+		switch {
+		case fd.IsWeak():
+			// Weak fields are no different for opaque.
+			fi = fieldInfoForWeakMessage(fd, si.weakOffset)
+		case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
+			// Oneofs are no different for opaque.
+			fi = fieldInfoForOneof(fd, si.oneofsByName[fd.ContainingOneof().Name()], mi.Exporter, si.oneofWrappersByNumber[fd.Number()])
+		case fd.IsMap():
+			fi = mi.fieldInfoForMapOpaque(si, fd, fs)
+		case fd.IsList() && fd.Message() == nil && usePresence:
+			fi = mi.fieldInfoForScalarListOpaque(si, fd, fs)
+		case fd.IsList() && fd.Message() == nil:
+			// Proto3 lists without presence can use same access methods as open
+			fi = fieldInfoForList(fd, fs, mi.Exporter)
+		case fd.IsList() && usePresence:
+			fi = mi.fieldInfoForMessageListOpaque(si, fd, fs)
+		case fd.IsList():
+			// Proto3 opaque messages that does not need presence bitmap.
+			// Different representation than open struct, but same logic
+			fi = mi.fieldInfoForMessageListOpaqueNoPresence(si, fd, fs)
+		case fd.Message() != nil && usePresence:
+			fi = mi.fieldInfoForMessageOpaque(si, fd, fs)
+		case fd.Message() != nil:
+			// Proto3 messages without presence can use same access methods as open
+			fi = fieldInfoForMessage(fd, fs, mi.Exporter)
+		default:
+			fi = mi.fieldInfoForScalarOpaque(si, fd, fs)
+		}
+		mi.fields[fd.Number()] = &fi
+	}
+	mi.oneofs = map[protoreflect.Name]*oneofInfo{}
+	for i := 0; i < mi.Desc.Oneofs().Len(); i++ {
+		od := mi.Desc.Oneofs().Get(i)
+		if !od.IsSynthetic() {
+			mi.oneofs[od.Name()] = makeOneofInfo(od, si.structInfo, mi.Exporter)
+		}
+	}
+
+	mi.denseFields = make([]*fieldInfo, fds.Len()*2)
+	for i := 0; i < fds.Len(); i++ {
+		if fd := fds.Get(i); int(fd.Number()) < len(mi.denseFields) {
+			mi.denseFields[fd.Number()] = mi.fields[fd.Number()]
+		}
+	}
+
+	for i := 0; i < fds.Len(); {
+		fd := fds.Get(i)
+		if od := fd.ContainingOneof(); od != nil && !fd.ContainingOneof().IsSynthetic() {
+			mi.rangeInfos = append(mi.rangeInfos, mi.oneofs[od.Name()])
+			i += od.Fields().Len()
+		} else {
+			mi.rangeInfos = append(mi.rangeInfos, mi.fields[fd.Number()])
+			i++
+		}
+	}
+
+	mi.makeExtensionFieldsFunc(mt, si.structInfo)
+	mi.makeUnknownFieldsFunc(mt, si.structInfo)
+	mi.makeOpaqueCoderMethods(mt, si)
+	mi.makeFieldTypes(si.structInfo)
+
+	return true
+}
+
+func (mi *MessageInfo) fieldInfoForMapOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	if ft.Kind() != reflect.Map {
+		panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
+	}
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	conv := NewConverter(ft, fd)
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			// Don't bother checking presence bits, since we need to
+			// look at the map length even if the presence bit is set.
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return rv.Len() > 0
+		},
+		clear: func(p pointer) {
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			rv.Set(reflect.Zero(rv.Type()))
+		},
+		get: func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if rv.Len() == 0 {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv)
+		},
+		set: func(p pointer, v protoreflect.Value) {
+			pv := conv.GoValueOf(v)
+			if pv.IsNil() {
+				panic(fmt.Sprintf("invalid value: setting map field to read-only value"))
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			rv.Set(pv)
+		},
+		mutable: func(p pointer) protoreflect.Value {
+			v := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if v.IsNil() {
+				v.Set(reflect.MakeMap(fs.Type))
+			}
+			return conv.PBValueOf(v)
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+func (mi *MessageInfo) fieldInfoForScalarListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	if ft.Kind() != reflect.Slice {
+		panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
+	}
+	conv := NewConverter(reflect.PtrTo(ft), fd)
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	index, _ := presenceIndex(mi.Desc, fd)
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return rv.Len() > 0
+		},
+		clear: func(p pointer) {
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			rv.Set(reflect.Zero(rv.Type()))
+		},
+		get: func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type)
+			if rv.Elem().Len() == 0 {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv)
+		},
+		set: func(p pointer, v protoreflect.Value) {
+			pv := conv.GoValueOf(v)
+			if pv.IsNil() {
+				panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
+			}
+			mi.setPresent(p, index)
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			rv.Set(pv.Elem())
+		},
+		mutable: func(p pointer) protoreflect.Value {
+			mi.setPresent(p, index)
+			return conv.PBValueOf(p.Apply(fieldOffset).AsValueOf(fs.Type))
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+func (mi *MessageInfo) fieldInfoForMessageListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
+		panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
+	}
+	conv := NewConverter(ft, fd)
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	index, _ := presenceIndex(mi.Desc, fd)
+	fieldNumber := fd.Number()
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			if !mi.present(p, index) {
+				return false
+			}
+			sp := p.Apply(fieldOffset).AtomicGetPointer()
+			if sp.IsNil() {
+				// Lazily unmarshal this field.
+				mi.lazyUnmarshal(p, fieldNumber)
+				sp = p.Apply(fieldOffset).AtomicGetPointer()
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			return rv.Elem().Len() > 0
+		},
+		clear: func(p pointer) {
+			fp := p.Apply(fieldOffset)
+			sp := fp.AtomicGetPointer()
+			if sp.IsNil() {
+				sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
+				mi.setPresent(p, index)
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			rv.Elem().Set(reflect.Zero(rv.Type().Elem()))
+		},
+		get: func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			if !mi.present(p, index) {
+				return conv.Zero()
+			}
+			sp := p.Apply(fieldOffset).AtomicGetPointer()
+			if sp.IsNil() {
+				// Lazily unmarshal this field.
+				mi.lazyUnmarshal(p, fieldNumber)
+				sp = p.Apply(fieldOffset).AtomicGetPointer()
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			if rv.Elem().Len() == 0 {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv)
+		},
+		set: func(p pointer, v protoreflect.Value) {
+			fp := p.Apply(fieldOffset)
+			sp := fp.AtomicGetPointer()
+			if sp.IsNil() {
+				sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
+				mi.setPresent(p, index)
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			val := conv.GoValueOf(v)
+			if val.IsNil() {
+				panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
+			} else {
+				rv.Elem().Set(val.Elem())
+			}
+		},
+		mutable: func(p pointer) protoreflect.Value {
+			fp := p.Apply(fieldOffset)
+			sp := fp.AtomicGetPointer()
+			if sp.IsNil() {
+				if mi.present(p, index) {
+					// Lazily unmarshal this field.
+					mi.lazyUnmarshal(p, fieldNumber)
+					sp = p.Apply(fieldOffset).AtomicGetPointer()
+				} else {
+					sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
+					mi.setPresent(p, index)
+				}
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			return conv.PBValueOf(rv)
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+func (mi *MessageInfo) fieldInfoForMessageListOpaqueNoPresence(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
+		panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
+	}
+	conv := NewConverter(ft, fd)
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			sp := p.Apply(fieldOffset).AtomicGetPointer()
+			if sp.IsNil() {
+				return false
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			return rv.Elem().Len() > 0
+		},
+		clear: func(p pointer) {
+			sp := p.Apply(fieldOffset).AtomicGetPointer()
+			if !sp.IsNil() {
+				rv := sp.AsValueOf(fs.Type.Elem())
+				rv.Elem().Set(reflect.Zero(rv.Type().Elem()))
+			}
+		},
+		get: func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			sp := p.Apply(fieldOffset).AtomicGetPointer()
+			if sp.IsNil() {
+				return conv.Zero()
+			}
+			rv := sp.AsValueOf(fs.Type.Elem())
+			if rv.Elem().Len() == 0 {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv)
+		},
+		set: func(p pointer, v protoreflect.Value) {
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if rv.IsNil() {
+				rv.Set(reflect.New(fs.Type.Elem()))
+			}
+			val := conv.GoValueOf(v)
+			if val.IsNil() {
+				panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
+			} else {
+				rv.Elem().Set(val.Elem())
+			}
+		},
+		mutable: func(p pointer) protoreflect.Value {
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if rv.IsNil() {
+				rv.Set(reflect.New(fs.Type.Elem()))
+			}
+			return conv.PBValueOf(rv)
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+func (mi *MessageInfo) fieldInfoForScalarOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	nullable := fd.HasPresence()
+	if oneof := fd.ContainingOneof(); oneof != nil && oneof.IsSynthetic() {
+		nullable = true
+	}
+	deref := false
+	if nullable && ft.Kind() == reflect.Ptr {
+		ft = ft.Elem()
+		deref = true
+	}
+	conv := NewConverter(ft, fd)
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	index, _ := presenceIndex(mi.Desc, fd)
+	var getter func(p pointer) protoreflect.Value
+	if !nullable {
+		getter = getterForDirectScalar(fd, fs, conv, fieldOffset)
+	} else {
+		getter = getterForOpaqueNullableScalar(mi, index, fd, fs, conv, fieldOffset)
+	}
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			if nullable {
+				return mi.present(p, index)
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			switch rv.Kind() {
+			case reflect.Bool:
+				return rv.Bool()
+			case reflect.Int32, reflect.Int64:
+				return rv.Int() != 0
+			case reflect.Uint32, reflect.Uint64:
+				return rv.Uint() != 0
+			case reflect.Float32, reflect.Float64:
+				return rv.Float() != 0 || math.Signbit(rv.Float())
+			case reflect.String, reflect.Slice:
+				return rv.Len() > 0
+			default:
+				panic(fmt.Sprintf("invalid type: %v", rv.Type())) // should never happen
+			}
+		},
+		clear: func(p pointer) {
+			if nullable {
+				mi.clearPresent(p, index)
+			}
+			// This is only valuable for bytes and strings, but we do it unconditionally.
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			rv.Set(reflect.Zero(rv.Type()))
+		},
+		get: getter,
+		// TODO: Implement unsafe fast path for set?
+		set: func(p pointer, v protoreflect.Value) {
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if deref {
+				if rv.IsNil() {
+					rv.Set(reflect.New(ft))
+				}
+				rv = rv.Elem()
+			}
+
+			rv.Set(conv.GoValueOf(v))
+			if nullable && rv.Kind() == reflect.Slice && rv.IsNil() {
+				rv.Set(emptyBytes)
+			}
+			if nullable {
+				mi.setPresent(p, index)
+			}
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+func (mi *MessageInfo) fieldInfoForMessageOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
+	ft := fs.Type
+	conv := NewConverter(ft, fd)
+	fieldOffset := offsetOf(fs, mi.Exporter)
+	index, _ := presenceIndex(mi.Desc, fd)
+	fieldNumber := fd.Number()
+	elemType := fs.Type.Elem()
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			if p.IsNil() {
+				return false
+			}
+			return mi.present(p, index)
+		},
+		clear: func(p pointer) {
+			mi.clearPresent(p, index)
+			p.Apply(fieldOffset).AtomicSetNilPointer()
+		},
+		get: func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			fp := p.Apply(fieldOffset)
+			mp := fp.AtomicGetPointer()
+			if mp.IsNil() {
+				// Lazily unmarshal this field.
+				mi.lazyUnmarshal(p, fieldNumber)
+				mp = fp.AtomicGetPointer()
+			}
+			rv := mp.AsValueOf(elemType)
+			return conv.PBValueOf(rv)
+		},
+		set: func(p pointer, v protoreflect.Value) {
+			val := pointerOfValue(conv.GoValueOf(v))
+			if val.IsNil() {
+				panic("invalid nil pointer")
+			}
+			p.Apply(fieldOffset).AtomicSetPointer(val)
+			mi.setPresent(p, index)
+		},
+		mutable: func(p pointer) protoreflect.Value {
+			fp := p.Apply(fieldOffset)
+			mp := fp.AtomicGetPointer()
+			if mp.IsNil() {
+				if mi.present(p, index) {
+					// Lazily unmarshal this field.
+					mi.lazyUnmarshal(p, fieldNumber)
+					mp = fp.AtomicGetPointer()
+				} else {
+					mp = pointerOfValue(conv.GoValueOf(conv.New()))
+					fp.AtomicSetPointer(mp)
+					mi.setPresent(p, index)
+				}
+			}
+			return conv.PBValueOf(mp.AsValueOf(fs.Type.Elem()))
+		},
+		newMessage: func() protoreflect.Message {
+			return conv.New().Message()
+		},
+		newField: func() protoreflect.Value {
+			return conv.New()
+		},
+	}
+}
+
+// A presenceList wraps a List, updating presence bits as necessary when the
+// list contents change.
+type presenceList struct {
+	pvalueList
+	setPresence func(bool)
+}
+type pvalueList interface {
+	protoreflect.List
+	//Unwrapper
+}
+
+func (list presenceList) Append(v protoreflect.Value) {
+	list.pvalueList.Append(v)
+	list.setPresence(true)
+}
+func (list presenceList) Truncate(i int) {
+	list.pvalueList.Truncate(i)
+	list.setPresence(i > 0)
+}
+
+// presenceIndex returns the index to pass to presence functions.
+//
+// TODO: field.Desc.Index() would be simpler, and would give space to record the presence of oneof fields.
+func presenceIndex(md protoreflect.MessageDescriptor, fd protoreflect.FieldDescriptor) (uint32, presenceSize) {
+	found := false
+	var index, numIndices uint32
+	for i := 0; i < md.Fields().Len(); i++ {
+		f := md.Fields().Get(i)
+		if f == fd {
+			found = true
+			index = numIndices
+		}
+		if f.ContainingOneof() == nil || isLastOneofField(f) {
+			numIndices++
+		}
+	}
+	if !found {
+		panic(fmt.Sprintf("BUG: %v not in %v", fd.Name(), md.FullName()))
+	}
+	return index, presenceSize(numIndices)
+}
+
+func isLastOneofField(fd protoreflect.FieldDescriptor) bool {
+	fields := fd.ContainingOneof().Fields()
+	return fields.Get(fields.Len()-1) == fd
+}
+
+func (mi *MessageInfo) setPresent(p pointer, index uint32) {
+	p.Apply(mi.presenceOffset).PresenceInfo().SetPresent(index, mi.presenceSize)
+}
+
+func (mi *MessageInfo) clearPresent(p pointer, index uint32) {
+	p.Apply(mi.presenceOffset).PresenceInfo().ClearPresent(index)
+}
+
+func (mi *MessageInfo) present(p pointer, index uint32) bool {
+	return p.Apply(mi.presenceOffset).PresenceInfo().Present(index)
+}
+
+// usePresenceForField implements the somewhat intricate logic of when
+// the presence bitmap is used for a field.  The main logic is that a
+// field that is optional or that can be lazy will use the presence
+// bit, but for proto2, also maps have a presence bit. It also records
+// if the field can ever be lazy, which is true if we have a
+// lazyOffset and the field is a message or a slice of messages. A
+// field that is lazy will always need a presence bit.  Oneofs are not
+// lazy and do not use presence, unless they are a synthetic oneof,
+// which is a proto3 optional field. For proto3 optionals, we use the
+// presence and they can also be lazy when applicable (a message).
+func usePresenceForField(si opaqueStructInfo, fd protoreflect.FieldDescriptor) (usePresence, canBeLazy bool) {
+	hasLazyField := fd.(interface{ IsLazy() bool }).IsLazy()
+
+	// Non-oneof scalar fields with explicit field presence use the presence array.
+	usesPresenceArray := fd.HasPresence() && fd.Message() == nil && (fd.ContainingOneof() == nil || fd.ContainingOneof().IsSynthetic())
+	switch {
+	case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
+		return false, false
+	case fd.IsWeak():
+		return false, false
+	case fd.IsMap():
+		return false, false
+	case fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind:
+		return hasLazyField, hasLazyField
+	default:
+		return usesPresenceArray || (hasLazyField && fd.HasPresence()), false
+	}
+}
diff --git a/internal/impl/message_opaque_gen.go b/internal/impl/message_opaque_gen.go
new file mode 100644
index 0000000..a698256
--- /dev/null
+++ b/internal/impl/message_opaque_gen.go
@@ -0,0 +1,132 @@
+// Copyright 2018 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 generate-types. DO NOT EDIT.
+
+package impl
+
+import (
+	"reflect"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func getterForOpaqueNullableScalar(mi *MessageInfo, index uint32, fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if ft.Kind() == reflect.Ptr {
+		ft = ft.Elem()
+	}
+	if fd.Kind() == protoreflect.EnumKind {
+		// Enums for nullable opaque types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return conv.PBValueOf(rv)
+		}
+	}
+	switch ft.Kind() {
+	case reflect.Bool:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Bool()
+			return protoreflect.ValueOfBool(*x)
+		}
+	case reflect.Int32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int32()
+			return protoreflect.ValueOfInt32(*x)
+		}
+	case reflect.Uint32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint32()
+			return protoreflect.ValueOfUint32(*x)
+		}
+	case reflect.Int64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int64()
+			return protoreflect.ValueOfInt64(*x)
+		}
+	case reflect.Uint64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint64()
+			return protoreflect.ValueOfUint64(*x)
+		}
+	case reflect.Float32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float32()
+			return protoreflect.ValueOfFloat32(*x)
+		}
+	case reflect.Float64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float64()
+			return protoreflect.ValueOfFloat64(*x)
+		}
+	case reflect.String:
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() || !mi.present(p, index) {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).StringPtr()
+				if *x == nil {
+					return conv.Zero()
+				}
+				if len(**x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(**x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).StringPtr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfString(**x)
+		}
+	case reflect.Slice:
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() || !mi.present(p, index) {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() || !mi.present(p, index) {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Bytes()
+			return protoreflect.ValueOfBytes(*x)
+		}
+	}
+	panic("unexpected protobuf kind: " + ft.Kind().String())
+}
diff --git a/internal/impl/message_reflect.go b/internal/impl/message_reflect.go
index 98ab94a..1b9b16a 100644
--- a/internal/impl/message_reflect.go
+++ b/internal/impl/message_reflect.go
@@ -207,6 +207,11 @@
 		case fd.IsList():
 			if fd.Enum() != nil || fd.Message() != nil {
 				ft = fs.Type.Elem()
+
+				if ft.Kind() == reflect.Slice {
+					ft = ft.Elem()
+				}
+
 			}
 			isMessage = fd.Message() != nil
 		case fd.Enum() != nil:
diff --git a/internal/impl/message_reflect_field.go b/internal/impl/message_reflect_field.go
index 986322b..a740646 100644
--- a/internal/impl/message_reflect_field.go
+++ b/internal/impl/message_reflect_field.go
@@ -256,6 +256,7 @@
 	ft := fs.Type
 	nullable := fd.HasPresence()
 	isBytes := ft.Kind() == reflect.Slice && ft.Elem().Kind() == reflect.Uint8
+	var getter func(p pointer) protoreflect.Value
 	if nullable {
 		if ft.Kind() != reflect.Ptr && ft.Kind() != reflect.Slice {
 			// This never occurs for generated message types.
@@ -268,19 +269,25 @@
 		}
 	}
 	conv := NewConverter(ft, fd)
-
-	// TODO: Implement unsafe fast path?
 	fieldOffset := offsetOf(fs, x)
+
+	// Generate specialized getter functions to avoid going through reflect.Value
+	if nullable {
+		getter = getterForNullableScalar(fd, fs, conv, fieldOffset)
+	} else {
+		getter = getterForDirectScalar(fd, fs, conv, fieldOffset)
+	}
+
 	return fieldInfo{
 		fieldDesc: fd,
 		has: func(p pointer) bool {
 			if p.IsNil() {
 				return false
 			}
-			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
 			if nullable {
-				return !rv.IsNil()
+				return !p.Apply(fieldOffset).Elem().IsNil()
 			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
 			switch rv.Kind() {
 			case reflect.Bool:
 				return rv.Bool()
@@ -300,21 +307,8 @@
 			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
 			rv.Set(reflect.Zero(rv.Type()))
 		},
-		get: func(p pointer) protoreflect.Value {
-			if p.IsNil() {
-				return conv.Zero()
-			}
-			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
-			if nullable {
-				if rv.IsNil() {
-					return conv.Zero()
-				}
-				if rv.Kind() == reflect.Ptr {
-					rv = rv.Elem()
-				}
-			}
-			return conv.PBValueOf(rv)
-		},
+		get: getter,
+		// TODO: Implement unsafe fast path for set?
 		set: func(p pointer, v protoreflect.Value) {
 			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
 			if nullable && rv.Kind() == reflect.Ptr {
diff --git a/internal/impl/message_reflect_field_gen.go b/internal/impl/message_reflect_field_gen.go
new file mode 100644
index 0000000..af5e063
--- /dev/null
+++ b/internal/impl/message_reflect_field_gen.go
@@ -0,0 +1,273 @@
+// Copyright 2018 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 generate-types. DO NOT EDIT.
+
+package impl
+
+import (
+	"reflect"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func getterForNullableScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if ft.Kind() == reflect.Ptr {
+		ft = ft.Elem()
+	}
+	if fd.Kind() == protoreflect.EnumKind {
+		elemType := fs.Type.Elem()
+		// Enums for nullable types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).Elem().AsValueOf(elemType)
+			if rv.IsNil() {
+				return conv.Zero()
+			}
+			return conv.PBValueOf(rv.Elem())
+		}
+	}
+	switch ft.Kind() {
+	case reflect.Bool:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).BoolPtr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfBool(**x)
+		}
+	case reflect.Int32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int32Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfInt32(**x)
+		}
+	case reflect.Uint32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint32Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfUint32(**x)
+		}
+	case reflect.Int64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int64Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfInt64(**x)
+		}
+	case reflect.Uint64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint64Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfUint64(**x)
+		}
+	case reflect.Float32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float32Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfFloat32(**x)
+		}
+	case reflect.Float64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float64Ptr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfFloat64(**x)
+		}
+	case reflect.String:
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).StringPtr()
+				if *x == nil {
+					return conv.Zero()
+				}
+				if len(**x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(**x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).StringPtr()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfString(**x)
+		}
+	case reflect.Slice:
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				if len(*x) == 0 {
+					return conv.Zero()
+				}
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Bytes()
+			if *x == nil {
+				return conv.Zero()
+			}
+			return protoreflect.ValueOfBytes(*x)
+		}
+	}
+	panic("unexpected protobuf kind: " + ft.Kind().String())
+}
+
+func getterForDirectScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
+	ft := fs.Type
+	if fd.Kind() == protoreflect.EnumKind {
+		// Enums for non nullable types.
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			return conv.PBValueOf(rv)
+		}
+	}
+	switch ft.Kind() {
+	case reflect.Bool:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Bool()
+			return protoreflect.ValueOfBool(*x)
+		}
+	case reflect.Int32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int32()
+			return protoreflect.ValueOfInt32(*x)
+		}
+	case reflect.Uint32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint32()
+			return protoreflect.ValueOfUint32(*x)
+		}
+	case reflect.Int64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Int64()
+			return protoreflect.ValueOfInt64(*x)
+		}
+	case reflect.Uint64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Uint64()
+			return protoreflect.ValueOfUint64(*x)
+		}
+	case reflect.Float32:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float32()
+			return protoreflect.ValueOfFloat32(*x)
+		}
+	case reflect.Float64:
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Float64()
+			return protoreflect.ValueOfFloat64(*x)
+		}
+	case reflect.String:
+		if fd.Kind() == protoreflect.BytesKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).String()
+				if len(*x) == 0 {
+					return protoreflect.ValueOfBytes(nil)
+				}
+				return protoreflect.ValueOfBytes([]byte(*x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).String()
+			return protoreflect.ValueOfString(*x)
+		}
+	case reflect.Slice:
+		if fd.Kind() == protoreflect.StringKind {
+			return func(p pointer) protoreflect.Value {
+				if p.IsNil() {
+					return conv.Zero()
+				}
+				x := p.Apply(fieldOffset).Bytes()
+				return protoreflect.ValueOfString(string(*x))
+			}
+		}
+		return func(p pointer) protoreflect.Value {
+			if p.IsNil() {
+				return conv.Zero()
+			}
+			x := p.Apply(fieldOffset).Bytes()
+			return protoreflect.ValueOfBytes(*x)
+		}
+	}
+	panic("unexpected protobuf kind: " + ft.Kind().String())
+}
diff --git a/internal/impl/pointer_unsafe.go b/internal/impl/pointer_unsafe.go
index 79e1866..041ebde 100644
--- a/internal/impl/pointer_unsafe.go
+++ b/internal/impl/pointer_unsafe.go
@@ -8,6 +8,8 @@
 	"reflect"
 	"sync/atomic"
 	"unsafe"
+
+	"google.golang.org/protobuf/internal/protolazy"
 )
 
 const UnsafeEnabled = true
@@ -111,6 +113,13 @@
 func (p pointer) BytesSlice() *[][]byte                 { return (*[][]byte)(p.p) }
 func (p pointer) WeakFields() *weakFields               { return (*weakFields)(p.p) }
 func (p pointer) Extensions() *map[int32]ExtensionField { return (*map[int32]ExtensionField)(p.p) }
+func (p pointer) LazyInfoPtr() **protolazy.XXX_lazyUnmarshalInfo {
+	return (**protolazy.XXX_lazyUnmarshalInfo)(p.p)
+}
+
+func (p pointer) PresenceInfo() presence {
+	return presence{P: p.p}
+}
 
 func (p pointer) Elem() pointer {
 	return pointer{p: *(*unsafe.Pointer)(p.p)}
diff --git a/internal/impl/pointer_unsafe_opaque.go b/internal/impl/pointer_unsafe_opaque.go
new file mode 100644
index 0000000..38aa7b7
--- /dev/null
+++ b/internal/impl/pointer_unsafe_opaque.go
@@ -0,0 +1,42 @@
+// 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 impl
+
+import (
+	"sync/atomic"
+	"unsafe"
+)
+
+func (p pointer) AtomicGetPointer() pointer {
+	return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
+}
+
+func (p pointer) AtomicSetPointer(v pointer) {
+	atomic.StorePointer((*unsafe.Pointer)(p.p), v.p)
+}
+
+func (p pointer) AtomicSetNilPointer() {
+	atomic.StorePointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil))
+}
+
+func (p pointer) AtomicSetPointerIfNil(v pointer) pointer {
+	if atomic.CompareAndSwapPointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil), v.p) {
+		return v
+	}
+	return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
+}
+
+type atomicV1MessageInfo struct{ p Pointer }
+
+func (mi *atomicV1MessageInfo) Get() Pointer {
+	return Pointer(atomic.LoadPointer((*unsafe.Pointer)(&mi.p)))
+}
+
+func (mi *atomicV1MessageInfo) SetIfNil(p Pointer) Pointer {
+	if atomic.CompareAndSwapPointer((*unsafe.Pointer)(&mi.p), nil, unsafe.Pointer(p)) {
+		return p
+	}
+	return mi.Get()
+}
diff --git a/internal/impl/presence.go b/internal/impl/presence.go
new file mode 100644
index 0000000..914cb1d
--- /dev/null
+++ b/internal/impl/presence.go
@@ -0,0 +1,142 @@
+// 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 impl
+
+import (
+	"sync/atomic"
+	"unsafe"
+)
+
+// presenceSize represents the size of a presence set, which should be the largest index of the set+1
+type presenceSize uint32
+
+// presence is the internal representation of the bitmap array in a generated protobuf
+type presence struct {
+	// This is a pointer to the beginning of an array of uint32
+	P unsafe.Pointer
+}
+
+func (p presence) toElem(num uint32) (ret *uint32) {
+	const (
+		bitsPerByte = 8
+		siz         = unsafe.Sizeof(*ret)
+	)
+	// p.P points to an array of uint32, num is the bit in this array that the
+	// caller wants to check/manipulate. Calculate the index in the array that
+	// contains this specific bit. E.g.: 76 / 32 = 2 (integer division).
+	offset := uintptr(num) / (siz * bitsPerByte) * siz
+	return (*uint32)(unsafe.Pointer(uintptr(p.P) + offset))
+}
+
+// Present checks for the presence of a specific field number in a presence set.
+func (p presence) Present(num uint32) bool {
+	if p.P == nil {
+		return false
+	}
+	return Export{}.Present(p.toElem(num), num)
+}
+
+// SetPresent adds presence for a specific field number in a presence set.
+func (p presence) SetPresent(num uint32, size presenceSize) {
+	Export{}.SetPresent(p.toElem(num), num, uint32(size))
+}
+
+// SetPresentUnatomic adds presence for a specific field number in a presence set without using
+// atomic operations. Only to be called during unmarshaling.
+func (p presence) SetPresentUnatomic(num uint32, size presenceSize) {
+	Export{}.SetPresentNonAtomic(p.toElem(num), num, uint32(size))
+}
+
+// ClearPresent removes presence for a specific field number in a presence set.
+func (p presence) ClearPresent(num uint32) {
+	Export{}.ClearPresent(p.toElem(num), num)
+}
+
+// LoadPresenceCache (together with PresentInCache) allows for a
+// cached version of checking for presence without re-reading the word
+// for every field. It is optimized for efficiency and assumes no
+// simltaneous mutation of the presence set (or at least does not have
+// a problem with simultaneous mutation giving inconsistent results).
+func (p presence) LoadPresenceCache() (current uint32) {
+	if p.P == nil {
+		return 0
+	}
+	return atomic.LoadUint32((*uint32)(p.P))
+}
+
+// PresentInCache reads presence from a cached word in the presence
+// bitmap. It caches up a new word if the bit is outside the
+// word. This is for really fast iteration through bitmaps in cases
+// where we either know that the bitmap will not be altered, or we
+// don't care about inconsistencies caused by simultaneous writes.
+func (p presence) PresentInCache(num uint32, cachedElement *uint32, current *uint32) bool {
+	if num/32 != *cachedElement {
+		o := uintptr(num/32) * unsafe.Sizeof(uint32(0))
+		q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o))
+		*current = atomic.LoadUint32(q)
+		*cachedElement = num / 32
+	}
+	return (*current & (1 << (num % 32))) > 0
+}
+
+// AnyPresent checks if any field is marked as present in the bitmap.
+func (p presence) AnyPresent(size presenceSize) bool {
+	n := uintptr((size + 31) / 32)
+	for j := uintptr(0); j < n; j++ {
+		o := j * unsafe.Sizeof(uint32(0))
+		q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o))
+		b := atomic.LoadUint32(q)
+		if b > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// toRaceDetectData finds the preceding RaceDetectHookData in a
+// message by using pointer arithmetic. As the type of the presence
+// set (bitmap) varies with the number of fields in the protobuf, we
+// can not have a struct type containing the array and the
+// RaceDetectHookData.  instead the RaceDetectHookData is placed
+// immediately before the bitmap array, and we find it by walking
+// backwards in the struct.
+//
+// This method is only called from the race-detect version of the code,
+// so RaceDetectHookData is never an empty struct.
+func (p presence) toRaceDetectData() *RaceDetectHookData {
+	var template struct {
+		d RaceDetectHookData
+		a [1]uint32
+	}
+	o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d)))
+	return (*RaceDetectHookData)(unsafe.Pointer(uintptr(p.P) - o))
+}
+
+func atomicLoadShadowPresence(p **[]byte) *[]byte {
+	return (*[]byte)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
+}
+func atomicStoreShadowPresence(p **[]byte, v *[]byte) {
+	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(p)), nil, unsafe.Pointer(v))
+}
+
+// findPointerToRaceDetectData finds the preceding RaceDetectHookData
+// in a message by using pointer arithmetic. For the methods called
+// directy from generated code, we don't have a pointer to the
+// beginning of the presence set, but a pointer inside the array. As
+// we know the index of the bit we're manipulating (num), we can
+// calculate which element of the array ptr is pointing to. With that
+// information we find the preceding RaceDetectHookData and can
+// manipulate the shadow bitmap.
+//
+// This method is only called from the race-detect version of the
+// code, so RaceDetectHookData is never an empty struct.
+func findPointerToRaceDetectData(ptr *uint32, num uint32) *RaceDetectHookData {
+	var template struct {
+		d RaceDetectHookData
+		a [1]uint32
+	}
+	o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d))) + uintptr(num/32)*unsafe.Sizeof(uint32(0))
+	return (*RaceDetectHookData)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) - o))
+}
diff --git a/internal/impl/validate.go b/internal/impl/validate.go
index a24e6bb..b534a3d 100644
--- a/internal/impl/validate.go
+++ b/internal/impl/validate.go
@@ -37,6 +37,10 @@
 
 	// ValidationValid indicates that unmarshaling the message will succeed.
 	ValidationValid
+
+	// ValidationWrongWireType indicates that a validated field does not have
+	// the expected wire type.
+	ValidationWrongWireType
 )
 
 func (v ValidationStatus) String() string {
@@ -149,11 +153,23 @@
 		switch fd.Kind() {
 		case protoreflect.MessageKind:
 			vi.typ = validationTypeMessage
+
+			if ft.Kind() == reflect.Ptr {
+				// Repeated opaque message fields are *[]*T.
+				ft = ft.Elem()
+			}
+
 			if ft.Kind() == reflect.Slice {
 				vi.mi = getMessageInfo(ft.Elem())
 			}
 		case protoreflect.GroupKind:
 			vi.typ = validationTypeGroup
+
+			if ft.Kind() == reflect.Ptr {
+				// Repeated opaque message fields are *[]*T.
+				ft = ft.Elem()
+			}
+
 			if ft.Kind() == reflect.Slice {
 				vi.mi = getMessageInfo(ft.Elem())
 			}
diff --git a/internal/protolazy/bufferreader.go b/internal/protolazy/bufferreader.go
new file mode 100644
index 0000000..82e5cab
--- /dev/null
+++ b/internal/protolazy/bufferreader.go
@@ -0,0 +1,364 @@
+// 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.
+
+// Helper code for parsing a protocol buffer
+
+package protolazy
+
+import (
+	"errors"
+	"fmt"
+	"io"
+
+	"google.golang.org/protobuf/encoding/protowire"
+)
+
+// BufferReader is a structure encapsulating a protobuf and a current position
+type BufferReader struct {
+	Buf []byte
+	Pos int
+}
+
+// NewBufferReader creates a new BufferRead from a protobuf
+func NewBufferReader(buf []byte) BufferReader {
+	return BufferReader{Buf: buf, Pos: 0}
+}
+
+var errOutOfBounds = errors.New("protobuf decoding: out of bounds")
+var errOverflow = errors.New("proto: integer overflow")
+
+func (b *BufferReader) DecodeVarintSlow() (x uint64, err error) {
+	i := b.Pos
+	l := len(b.Buf)
+
+	for shift := uint(0); shift < 64; shift += 7 {
+		if i >= l {
+			err = io.ErrUnexpectedEOF
+			return
+		}
+		v := b.Buf[i]
+		i++
+		x |= (uint64(v) & 0x7F) << shift
+		if v < 0x80 {
+			b.Pos = i
+			return
+		}
+	}
+
+	// The number is too large to represent in a 64-bit value.
+	err = errOverflow
+	return
+}
+
+// decodeVarint decodes a varint at the current position
+func (b *BufferReader) DecodeVarint() (x uint64, err error) {
+	i := b.Pos
+	buf := b.Buf
+
+	if i >= len(buf) {
+		return 0, io.ErrUnexpectedEOF
+	} else if buf[i] < 0x80 {
+		b.Pos++
+		return uint64(buf[i]), nil
+	} else if len(buf)-i < 10 {
+		return b.DecodeVarintSlow()
+	}
+
+	var v uint64
+	// we already checked the first byte
+	x = uint64(buf[i]) & 127
+	i++
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 7
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 14
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 21
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 28
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 35
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 42
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 49
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 56
+	if v < 128 {
+		goto done
+	}
+
+	v = uint64(buf[i])
+	i++
+	x |= (v & 127) << 63
+	if v < 128 {
+		goto done
+	}
+
+	return 0, errOverflow
+
+done:
+	b.Pos = i
+	return
+}
+
+// decodeVarint32 decodes a varint32 at the current position
+func (b *BufferReader) DecodeVarint32() (x uint32, err error) {
+	i := b.Pos
+	buf := b.Buf
+
+	if i >= len(buf) {
+		return 0, io.ErrUnexpectedEOF
+	} else if buf[i] < 0x80 {
+		b.Pos++
+		return uint32(buf[i]), nil
+	} else if len(buf)-i < 5 {
+		v, err := b.DecodeVarintSlow()
+		return uint32(v), err
+	}
+
+	var v uint32
+	// we already checked the first byte
+	x = uint32(buf[i]) & 127
+	i++
+
+	v = uint32(buf[i])
+	i++
+	x |= (v & 127) << 7
+	if v < 128 {
+		goto done
+	}
+
+	v = uint32(buf[i])
+	i++
+	x |= (v & 127) << 14
+	if v < 128 {
+		goto done
+	}
+
+	v = uint32(buf[i])
+	i++
+	x |= (v & 127) << 21
+	if v < 128 {
+		goto done
+	}
+
+	v = uint32(buf[i])
+	i++
+	x |= (v & 127) << 28
+	if v < 128 {
+		goto done
+	}
+
+	return 0, errOverflow
+
+done:
+	b.Pos = i
+	return
+}
+
+// skipValue skips a value in the protobuf, based on the specified tag
+func (b *BufferReader) SkipValue(tag uint32) (err error) {
+	wireType := tag & 0x7
+	switch protowire.Type(wireType) {
+	case protowire.VarintType:
+		err = b.SkipVarint()
+	case protowire.Fixed64Type:
+		err = b.SkipFixed64()
+	case protowire.BytesType:
+		var n uint32
+		n, err = b.DecodeVarint32()
+		if err == nil {
+			err = b.Skip(int(n))
+		}
+	case protowire.StartGroupType:
+		err = b.SkipGroup(tag)
+	case protowire.Fixed32Type:
+		err = b.SkipFixed32()
+	default:
+		err = fmt.Errorf("Unexpected wire type (%d)", wireType)
+	}
+	return
+}
+
+// skipGroup skips a group with the specified tag.  It executes efficiently using a tag stack
+func (b *BufferReader) SkipGroup(tag uint32) (err error) {
+	tagStack := make([]uint32, 0, 16)
+	tagStack = append(tagStack, tag)
+	var n uint32
+	for len(tagStack) > 0 {
+		tag, err = b.DecodeVarint32()
+		if err != nil {
+			return err
+		}
+		switch protowire.Type(tag & 0x7) {
+		case protowire.VarintType:
+			err = b.SkipVarint()
+		case protowire.Fixed64Type:
+			err = b.Skip(8)
+		case protowire.BytesType:
+			n, err = b.DecodeVarint32()
+			if err == nil {
+				err = b.Skip(int(n))
+			}
+		case protowire.StartGroupType:
+			tagStack = append(tagStack, tag)
+		case protowire.Fixed32Type:
+			err = b.SkipFixed32()
+		case protowire.EndGroupType:
+			if protoFieldNumber(tagStack[len(tagStack)-1]) == protoFieldNumber(tag) {
+				tagStack = tagStack[:len(tagStack)-1]
+			} else {
+				err = fmt.Errorf("end group tag %d does not match begin group tag %d at pos %d",
+					protoFieldNumber(tag), protoFieldNumber(tagStack[len(tagStack)-1]), b.Pos)
+			}
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// skipVarint effiently skips a varint
+func (b *BufferReader) SkipVarint() (err error) {
+	i := b.Pos
+
+	if len(b.Buf)-i < 10 {
+		// Use DecodeVarintSlow() to check for buffer overflow, but ignore result
+		if _, err := b.DecodeVarintSlow(); err != nil {
+			return err
+		}
+		return nil
+	}
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	i++
+
+	if b.Buf[i] < 0x80 {
+		goto out
+	}
+	return errOverflow
+
+out:
+	b.Pos = i + 1
+	return nil
+}
+
+// skip skips the specified number of bytes
+func (b *BufferReader) Skip(n int) (err error) {
+	if len(b.Buf) < b.Pos+n {
+		return io.ErrUnexpectedEOF
+	}
+	b.Pos += n
+	return
+}
+
+// skipFixed64 skips a fixed64
+func (b *BufferReader) SkipFixed64() (err error) {
+	return b.Skip(8)
+}
+
+// skipFixed32 skips a fixed32
+func (b *BufferReader) SkipFixed32() (err error) {
+	return b.Skip(4)
+}
+
+// skipBytes skips a set of bytes
+func (b *BufferReader) SkipBytes() (err error) {
+	n, err := b.DecodeVarint32()
+	if err != nil {
+		return err
+	}
+	return b.Skip(int(n))
+}
+
+// Done returns whether we are at the end of the protobuf
+func (b *BufferReader) Done() bool {
+	return b.Pos == len(b.Buf)
+}
+
+// Remaining returns how many bytes remain
+func (b *BufferReader) Remaining() int {
+	return len(b.Buf) - b.Pos
+}
diff --git a/internal/protolazy/lazy.go b/internal/protolazy/lazy.go
new file mode 100644
index 0000000..ff4d483
--- /dev/null
+++ b/internal/protolazy/lazy.go
@@ -0,0 +1,359 @@
+// 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 protolazy contains internal data structures for lazy message decoding.
+package protolazy
+
+import (
+	"fmt"
+	"sort"
+
+	"google.golang.org/protobuf/encoding/protowire"
+	piface "google.golang.org/protobuf/runtime/protoiface"
+)
+
+// IndexEntry is the structure for an index of the fields in a message of a
+// proto (not descending to sub-messages)
+type IndexEntry struct {
+	FieldNum uint32
+	// first byte of this tag/field
+	Start uint32
+	// first byte after a contiguous sequence of bytes for this tag/field, which could
+	// include a single encoding of the field, or multiple encodings for the field
+	End uint32
+	// True if this protobuf segment includes multiple encodings of the field
+	MultipleContiguous bool
+}
+
+// XXX_lazyUnmarshalInfo has information about a particular lazily decoded message
+//
+// Deprecated: Do not use. This will be deleted in the near future.
+type XXX_lazyUnmarshalInfo struct {
+	// Index of fields and their positions in the protobuf for this
+	// message.  Make index be a pointer to a slice so it can be updated
+	// atomically.  The index pointer is only set once (lazily when/if
+	// the index is first needed), and must always be SET and LOADED
+	// ATOMICALLY.
+	index *[]IndexEntry
+	// The protobuf associated with this lazily decoded message.  It is
+	// only set during proto.Unmarshal().  It doesn't need to be set and
+	// loaded atomically, since any simultaneous set (Unmarshal) and read
+	// (during a get) would already be a race in the app code.
+	Protobuf []byte
+	// The flags present when Unmarshal was originally called for this particular message
+	unmarshalFlags piface.UnmarshalInputFlags
+}
+
+// The Buffer and SetBuffer methods let v2/internal/impl interact with
+// XXX_lazyUnmarshalInfo via an interface, to avoid an import cycle.
+
+// Buffer returns the lazy unmarshal buffer.
+//
+// Deprecated: Do not use. This will be deleted in the near future.
+func (lazy *XXX_lazyUnmarshalInfo) Buffer() []byte {
+	return lazy.Protobuf
+}
+
+// SetBuffer sets the lazy unmarshal buffer.
+//
+// Deprecated: Do not use. This will be deleted in the near future.
+func (lazy *XXX_lazyUnmarshalInfo) SetBuffer(b []byte) {
+	lazy.Protobuf = b
+}
+
+// SetUnmarshalFlags is called to set a copy of the original unmarshalInputFlags.
+// The flags should reflect how Unmarshal was called.
+func (lazy *XXX_lazyUnmarshalInfo) SetUnmarshalFlags(f piface.UnmarshalInputFlags) {
+	lazy.unmarshalFlags = f
+}
+
+// UnmarshalFlags returns the original unmarshalInputFlags.
+func (lazy *XXX_lazyUnmarshalInfo) UnmarshalFlags() piface.UnmarshalInputFlags {
+	return lazy.unmarshalFlags
+}
+
+// AllowedPartial returns true if the user originally unmarshalled this message with
+// AllowPartial set to true
+func (lazy *XXX_lazyUnmarshalInfo) AllowedPartial() bool {
+	return (lazy.unmarshalFlags & piface.UnmarshalCheckRequired) == 0
+}
+
+func protoFieldNumber(tag uint32) uint32 {
+	return tag >> 3
+}
+
+// buildIndex builds an index of the specified protobuf, return the index
+// array and an error.
+func buildIndex(buf []byte) ([]IndexEntry, error) {
+	index := make([]IndexEntry, 0, 16)
+	var lastProtoFieldNum uint32
+	var outOfOrder bool
+
+	var r BufferReader = NewBufferReader(buf)
+
+	for !r.Done() {
+		var tag uint32
+		var err error
+		var curPos = r.Pos
+		// INLINED: tag, err = r.DecodeVarint32()
+		{
+			i := r.Pos
+			buf := r.Buf
+
+			if i >= len(buf) {
+				return nil, errOutOfBounds
+			} else if buf[i] < 0x80 {
+				r.Pos++
+				tag = uint32(buf[i])
+			} else if r.Remaining() < 5 {
+				var v uint64
+				v, err = r.DecodeVarintSlow()
+				tag = uint32(v)
+			} else {
+				var v uint32
+				// we already checked the first byte
+				tag = uint32(buf[i]) & 127
+				i++
+
+				v = uint32(buf[i])
+				i++
+				tag |= (v & 127) << 7
+				if v < 128 {
+					goto done
+				}
+
+				v = uint32(buf[i])
+				i++
+				tag |= (v & 127) << 14
+				if v < 128 {
+					goto done
+				}
+
+				v = uint32(buf[i])
+				i++
+				tag |= (v & 127) << 21
+				if v < 128 {
+					goto done
+				}
+
+				v = uint32(buf[i])
+				i++
+				tag |= (v & 127) << 28
+				if v < 128 {
+					goto done
+				}
+
+				return nil, errOutOfBounds
+
+			done:
+				r.Pos = i
+			}
+		}
+		// DONE: tag, err = r.DecodeVarint32()
+
+		fieldNum := protoFieldNumber(tag)
+		if fieldNum < lastProtoFieldNum {
+			outOfOrder = true
+		}
+
+		// Skip the current value -- will skip over an entire group as well.
+		// INLINED: err = r.SkipValue(tag)
+		wireType := tag & 0x7
+		switch protowire.Type(wireType) {
+		case protowire.VarintType:
+			// INLINED: err = r.SkipVarint()
+			i := r.Pos
+
+			if len(r.Buf)-i < 10 {
+				// Use DecodeVarintSlow() to skip while
+				// checking for buffer overflow, but ignore result
+				_, err = r.DecodeVarintSlow()
+				goto out2
+			}
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			i++
+
+			if r.Buf[i] < 0x80 {
+				goto out
+			}
+			return nil, errOverflow
+		out:
+			r.Pos = i + 1
+			// DONE: err = r.SkipVarint()
+		case protowire.Fixed64Type:
+			err = r.SkipFixed64()
+		case protowire.BytesType:
+			var n uint32
+			n, err = r.DecodeVarint32()
+			if err == nil {
+				err = r.Skip(int(n))
+			}
+		case protowire.StartGroupType:
+			err = r.SkipGroup(tag)
+		case protowire.Fixed32Type:
+			err = r.SkipFixed32()
+		default:
+			err = fmt.Errorf("Unexpected wire type (%d)", wireType)
+		}
+		// DONE: err = r.SkipValue(tag)
+
+	out2:
+		if err != nil {
+			return nil, err
+		}
+		if fieldNum != lastProtoFieldNum {
+			index = append(index, IndexEntry{FieldNum: fieldNum,
+				Start: uint32(curPos),
+				End:   uint32(r.Pos)},
+			)
+		} else {
+			index[len(index)-1].End = uint32(r.Pos)
+			index[len(index)-1].MultipleContiguous = true
+		}
+		lastProtoFieldNum = fieldNum
+	}
+	if outOfOrder {
+		sort.Slice(index, func(i, j int) bool {
+			return index[i].FieldNum < index[j].FieldNum ||
+				(index[i].FieldNum == index[j].FieldNum &&
+					index[i].Start < index[j].Start)
+		})
+	}
+	return index, nil
+}
+
+func (lazy *XXX_lazyUnmarshalInfo) SizeField(num uint32) (size int) {
+	start, end, found, _, multipleEntries := lazy.FindFieldInProto(num)
+	if multipleEntries != nil {
+		for _, entry := range multipleEntries {
+			size += int(entry.End - entry.Start)
+		}
+		return size
+	}
+	if !found {
+		return 0
+	}
+	return int(end - start)
+}
+
+func (lazy *XXX_lazyUnmarshalInfo) AppendField(b []byte, num uint32) ([]byte, bool) {
+	start, end, found, _, multipleEntries := lazy.FindFieldInProto(num)
+	if multipleEntries != nil {
+		for _, entry := range multipleEntries {
+			b = append(b, lazy.Protobuf[entry.Start:entry.End]...)
+		}
+		return b, true
+	}
+	if !found {
+		return nil, false
+	}
+	b = append(b, lazy.Protobuf[start:end]...)
+	return b, true
+}
+
+func (lazy *XXX_lazyUnmarshalInfo) SetIndex(index []IndexEntry) {
+	atomicStoreIndex(&lazy.index, &index)
+}
+
+// FindFieldInProto looks for field fieldNum in lazyUnmarshalInfo information
+// (including protobuf), returns startOffset/endOffset/found.
+func (lazy *XXX_lazyUnmarshalInfo) FindFieldInProto(fieldNum uint32) (start, end uint32, found, multipleContiguous bool, multipleEntries []IndexEntry) {
+	if lazy.Protobuf == nil {
+		// There is no backing protobuf for this message -- it was made from a builder
+		return 0, 0, false, false, nil
+	}
+	index := atomicLoadIndex(&lazy.index)
+	if index == nil {
+		r, err := buildIndex(lazy.Protobuf)
+		if err != nil {
+			panic(fmt.Sprintf("findFieldInfo: error building index when looking for field %d: %v", fieldNum, err))
+		}
+		// lazy.index is a pointer to the slice returned by BuildIndex
+		index = &r
+		atomicStoreIndex(&lazy.index, index)
+	}
+	return lookupField(index, fieldNum)
+}
+
+// lookupField returns the offset at which the indicated field starts using
+// the index, offset immediately after field ends (including all instances of
+// a repeated field), and bools indicating if field was found and if there
+// are multiple encodings of the field in the byte range.
+//
+// To hande the uncommon case where there are repeated encodings for the same
+// field which are not consecutive in the protobuf (so we need to returns
+// multiple start/end offsets), we also return a slice multipleEntries.  If
+// multipleEntries is non-nil, then multiple entries were found, and the
+// values in the slice should be used, rather than start/end/found.
+func lookupField(indexp *[]IndexEntry, fieldNum uint32) (start, end uint32, found bool, multipleContiguous bool, multipleEntries []IndexEntry) {
+	// The pointer indexp to the index was already loaded atomically.
+	// The slice is uniquely associated with the pointer, so it doesn't
+	// need to be loaded atomically.
+	index := *indexp
+	for i, entry := range index {
+		if fieldNum == entry.FieldNum {
+			if i < len(index)-1 && entry.FieldNum == index[i+1].FieldNum {
+				// Handle the uncommon case where there are
+				// repeated entries for the same field which
+				// are not contiguous in the protobuf.
+				multiple := make([]IndexEntry, 1, 2)
+				multiple[0] = IndexEntry{fieldNum, entry.Start, entry.End, entry.MultipleContiguous}
+				i++
+				for i < len(index) && index[i].FieldNum == fieldNum {
+					multiple = append(multiple, IndexEntry{fieldNum, index[i].Start, index[i].End, index[i].MultipleContiguous})
+					i++
+				}
+				return 0, 0, false, false, multiple
+
+			}
+			return entry.Start, entry.End, true, entry.MultipleContiguous, nil
+		}
+		if fieldNum < entry.FieldNum {
+			return 0, 0, false, false, nil
+		}
+	}
+	return 0, 0, false, false, nil
+}
diff --git a/internal/protolazy/pointer_unsafe.go b/internal/protolazy/pointer_unsafe.go
new file mode 100644
index 0000000..dc2a64c
--- /dev/null
+++ b/internal/protolazy/pointer_unsafe.go
@@ -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.
+
+package protolazy
+
+import (
+	"sync/atomic"
+	"unsafe"
+)
+
+func atomicLoadIndex(p **[]IndexEntry) *[]IndexEntry {
+	return (*[]IndexEntry)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
+}
+func atomicStoreIndex(p **[]IndexEntry, v *[]IndexEntry) {
+	atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
+}
diff --git a/internal/race_test/lazy/lazy_race_test.go b/internal/race_test/lazy/lazy_race_test.go
new file mode 100644
index 0000000..7d38683
--- /dev/null
+++ b/internal/race_test/lazy/lazy_race_test.go
@@ -0,0 +1,494 @@
+// 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.
+
+// This test tests that races on lazy fields in opaque protos are detected by the race detector,
+// even though the plain code uses atomic variables in a manner that would hide data races.
+// This is essential, as concurrent writes or read-writes on a lazy field can cause undefined
+// behaviours.
+//
+// Using exectest with the race detector to check that the code fails did not work,
+// as the race error got propagated from the subprocess and failed the test case in the parent process.
+// Instead we create the subprocess where the test is supposed to fail by ourselves.
+
+// Lazy decoding is only available in the fast path, which the protoreflect tag disables.
+//go:build !protoreflect
+
+package lazy_race_test
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"reflect"
+	"sync"
+	"testing"
+	"unsafe"
+
+	"google.golang.org/protobuf/internal/test/race"
+	mixedpb "google.golang.org/protobuf/internal/testprotos/mixed"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+)
+
+// To get some output from the subprocess, set this to true
+const debug = false
+
+func makeM2() *testopaquepb.TestAllTypes {
+	return testopaquepb.TestAllTypes_builder{
+		OptionalLazyNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{
+			A: proto.Int32(1),
+			Corecursive: testopaquepb.TestAllTypes_builder{
+				OptionalBool: proto.Bool(true),
+			}.Build(),
+		}.Build(),
+		RepeatedNestedMessage: []*testopaquepb.TestAllTypes_NestedMessage{
+			testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(2),
+				Corecursive: testopaquepb.TestAllTypes_builder{
+					OptionalInt32: proto.Int32(32),
+				}.Build(),
+			}.Build(),
+		},
+	}.Build()
+}
+
+type testC struct {
+	name string
+	l1   func()
+	l2   func()
+}
+
+const envVar = "GO_TESTING_IN_SUBPROCESS"
+
+// TestRaceDetectionOnWrite tests that any combination involving concurrent
+// read-write or write-write will trigger the race detector.
+func TestRaceDetectionOnWrite(t *testing.T) {
+	var x *testopaquepb.TestAllTypes
+	var y *testopaquepb.TestAllTypes_NestedMessage
+	var z int32
+	// A table of test cases to expose to the race detector.
+	// The name will be set in an environment variable, so don't use special characters or spaces.
+	// Each entry in the table will be spawned into a sub process, where the actual execution will happen.
+	cases := []testC{
+		{
+			name: "TestSetSet",
+			l1:   func() { x.SetOptionalLazyNestedMessage(y) },
+			l2:   func() { x.SetOptionalLazyNestedMessage(y) },
+		},
+		{
+			name: "TestClearClear",
+			l1:   func() { x.ClearOptionalLazyNestedMessage() },
+			l2:   func() { x.ClearOptionalLazyNestedMessage() },
+		},
+		{
+			name: "TestSetClear",
+			l1:   func() { x.SetOptionalLazyNestedMessage(y) },
+			l2:   func() { x.ClearOptionalLazyNestedMessage() },
+		},
+		{
+			name: "TestSetGet",
+			l1:   func() { x.SetOptionalLazyNestedMessage(y) },
+			l2: func() {
+				if x.GetOptionalLazyNestedMessage().GetCorecursive().GetOptionalBool() {
+					z++
+				}
+			},
+		},
+		{
+			name: "TestSetHas",
+			l1:   func() { x.SetOptionalLazyNestedMessage(y) },
+			l2: func() {
+				if x.HasOptionalLazyNestedMessage() {
+					z++
+				}
+			},
+		},
+		{
+			name: "TestClearGet",
+			l1:   func() { x.ClearOptionalLazyNestedMessage() },
+			l2: func() {
+				if x.GetOptionalLazyNestedMessage().GetCorecursive().GetOptionalBool() {
+					z++
+				}
+			},
+		},
+		{
+			name: "TestClearHas",
+			l1:   func() { x.ClearOptionalLazyNestedMessage() },
+			l2: func() {
+				if x.HasOptionalLazyNestedMessage() {
+					z++
+				}
+			},
+		},
+	}
+	e := os.Getenv(envVar)
+	if e != "" {
+		// We're in the subprocess. As spawnCase will add filter for the subtest,
+		// we will actually only execute one test in this subprocess even though
+		// we call t.Run for all cases.
+		for _, tc := range cases {
+			t.Run(tc.name, func(t *testing.T) {
+				x = makeM2()
+				y = x.GetOptionalLazyNestedMessage()
+				z = 0
+				execCase(t, tc)
+				return
+			})
+		}
+		return
+	}
+	// If we're not in a subprocess, spawn and check one for each entry in the table
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			spawnCase(t)
+		})
+	}
+}
+
+// execCase actually executes the testcase when we're in a subprocess, it executes
+// the two operations of tc in parallel and make sure tsan sees this as parallel
+// execution.
+func execCase(t *testing.T, tc testC) {
+	t.Helper()
+	c1 := make(chan struct{})
+	wg := sync.WaitGroup{}
+	wg.Add(2)
+	// This is a very complicated but stable way of telling tsan that the
+	// two operations are executed in parallel. I can only guess why this
+	// works so I'll leave my speculations out of the comment but
+	// experiments suggest that it works reliably.
+	go func() {
+		c1 <- struct{}{}
+		tc.l1()
+		<-c1
+		tc.l1()
+		wg.Done()
+	}()
+	go func() {
+		<-c1
+		tc.l2()
+		c1 <- struct{}{}
+		tc.l2()
+		wg.Done()
+	}()
+	wg.Wait()
+}
+
+// spawnCase reruns this executable to execute t.Name() with the sub-case tn in the environment variable
+func spawnCase(t *testing.T) {
+	// If we get here, we are in the parent process and should execute ourselves, but filter on the test that called us.
+	ep, err := os.Executable()
+	if err != nil {
+		t.Fatalf("Failed to find my own executable: %v", err)
+	}
+	c := exec.Command(ep, "--test.run="+t.Name())
+	// Set the environment variable so that we know we're in a subproceess when re-executed
+	c.Env = append(c.Env, envVar+"=true")
+	out, err := c.CombinedOutput()
+	// If we do not get an error, we fail in the parent process, otherwise we're good
+	if race.Enabled && err == nil {
+		t.Errorf("Got success, want error under race detector:\n-----------\n%s\n-------------\n", string(out))
+	}
+	if !race.Enabled && err != nil {
+		t.Errorf("Got error, want success without race detector:\n-----------\n%s\n-------------\n", string(out))
+	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "Subprocess output:\n-----------\n%s\n-------------\n", string(out))
+	}
+}
+
+// TestNoRaceDetection should not fail under race detector (or otherwise)
+func TestNoRaceDetection(t *testing.T) {
+	x := makeM2()
+	var y int32
+	var z int32
+	c := make(chan struct{})
+	go func() {
+		for i := 0; i < 10000; i++ {
+			y += x.GetRepeatedNestedMessage()[0].GetA()
+		}
+		close(c)
+	}()
+	for i := 0; i < 10000; i++ {
+		z += x.GetRepeatedNestedMessage()[0].GetA()
+	}
+	<-c
+	if z != y {
+		t.Errorf("The two go-routines did not calculate the same: %d != %d", z, y)
+	}
+}
+
+func TestNoRaceOnGetsOfSlices(t *testing.T) {
+	x := makeM2()
+	b, err := proto.Marshal(x)
+	if err != nil {
+		t.Fatalf("Error while marshaling: %v", err)
+	}
+
+	var y int32
+	var z int32
+	d := make(chan int)
+
+	// Check that there are no races when we do concurrent lazy gets of a field
+	// containing a slice of message pointers.
+	for i := 0; i < 10000; i++ {
+		err := proto.Unmarshal(b, x)
+		if err != nil {
+			t.Fatalf("Error while unmarshaling: %v", err)
+		}
+		go func() {
+			y += x.GetRepeatedNestedMessage()[0].GetA()
+			d <- 1
+		}()
+		go func() {
+			z += x.GetRepeatedNestedMessage()[0].GetA()
+			d <- 1
+		}()
+		<-d
+		<-d
+	}
+	if z != y {
+		t.Errorf("The two go-routines did not calculate the same: %d != %d", z, y)
+	}
+	close(d)
+}
+
+func TestNoRaceOnGetsOfMessages(t *testing.T) {
+	x := makeM2()
+	b, err := proto.Marshal(x)
+	if err != nil {
+		t.Fatalf("Error while marshaling: %v", err)
+	}
+
+	var y int32
+	var z int32
+	d := make(chan int)
+
+	// Check that there is no race when we do concurrent lazy gets of a field
+	// pointing to a sub-message.
+	for i := 0; i < 10000; i++ {
+		err := proto.Unmarshal(b, x)
+		if err != nil {
+			t.Fatalf("Error while unmarshaling: %v", err)
+		}
+		go func() {
+			if x.GetOptionalLazyNestedMessage().GetA() > 0 {
+				y++
+			}
+			d <- 1
+		}()
+		go func() {
+			if x.GetOptionalLazyNestedMessage().GetA() > 0 {
+				z++
+			}
+			d <- 1
+		}()
+		<-d
+		<-d
+	}
+	if z != y {
+		t.Errorf("The two go-routines did not calculate the same: %d != %d", z, y)
+	}
+
+	close(d)
+}
+
+func fillRequiredLazy() *testopaquepb.TestRequiredLazy {
+	return testopaquepb.TestRequiredLazy_builder{
+		OptionalLazyMessage: testopaquepb.TestRequired_builder{
+			RequiredField: proto.Int32(23),
+		}.Build(),
+	}.Build()
+}
+
+func expandedLazy(m *testopaquepb.TestRequiredLazy) bool {
+	v := reflect.ValueOf(m).Elem()
+	rf := v.FieldByName("xxx_hidden_OptionalLazyMessage")
+	rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
+	return rf.Pointer() != 0
+}
+
+// This test verifies all assumptions of TestParallellMarshalWithRequired
+// are (still) valid, to prevent the test from becoming a no-op (again).
+func TestParallellMarshalWithRequiredAssumptions(t *testing.T) {
+	b, err := proto.Marshal(fillRequiredLazy())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ml := &testopaquepb.TestRequiredLazy{}
+	// Specifying AllowPartial: true at unmarshal time is required, otherwise
+	// the Marshal call will skip the required field check.
+	if err := (proto.UnmarshalOptions{AllowPartial: true}).Unmarshal(b, ml); err != nil {
+		t.Fatal(err)
+	}
+	if expandedLazy(ml) {
+		t.Fatalf("lazy message unexpectedly decoded")
+	}
+
+	// Marshaling with AllowPartial: true means the no decoding is needed,
+	// because no required field checks are done.
+	if _, err := (proto.MarshalOptions{AllowPartial: true}).Marshal(ml); err != nil {
+		t.Fatal(err)
+	}
+	if expandedLazy(ml) {
+		t.Fatalf("lazy message unexpectedly decoded")
+	}
+
+	// Whereas marshaling with AllowPartial: false (default) means the message
+	// will be decoded to check if any required fields are not set.
+	if _, err := (proto.MarshalOptions{AllowPartial: false}).Marshal(ml); err != nil {
+		t.Fatal(err)
+	}
+	if !expandedLazy(ml) {
+		t.Fatalf("lazy message unexpectedly not decoded")
+	}
+}
+
+// TestParallellMarshalWithRequired runs two goroutines that marshal the same
+// message. Marshaling a message can result in lazily decoding said message,
+// provided the message contains any required fields. This test ensures that
+// said lazy decoding can happen without causing races in the other goroutine
+// that marshals the same message.
+func TestParallellMarshalWithRequired(t *testing.T) {
+	m := fillRequiredLazy()
+	b, err := proto.MarshalOptions{}.Marshal(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	partial := false
+	for i := 0; i < 1000; i++ {
+		partial = !partial
+		ml := &testopaquepb.TestRequiredLazy{}
+		d := make(chan bool)
+		err := proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(b, ml)
+		if err != nil {
+			t.Fatalf("Error while unmarshaling: %v", err)
+		}
+
+		go func() {
+			b2, err := proto.MarshalOptions{AllowPartial: partial}.Marshal(ml)
+			if err != nil {
+				t.Errorf("Marshal error: %v", err)
+				d <- false
+				return
+			}
+			m := &testopaquepb.TestRequiredLazy{}
+			if err := (proto.UnmarshalOptions{}).Unmarshal(b2, m); err != nil {
+				t.Errorf("Unmarshal error: %v", err)
+				d <- false
+				return
+			}
+			if !proto.Equal(ml, m) {
+				t.Errorf("Unmarshal roundtrip - protos not equal")
+				d <- false
+				return
+			}
+			d <- true
+		}()
+		go func() {
+			b2, err := proto.MarshalOptions{AllowPartial: partial}.Marshal(ml)
+			if err != nil {
+				t.Errorf("Marshal error: %v", err)
+				d <- false
+				return
+			}
+			m := &testopaquepb.TestRequiredLazy{}
+			if err := (proto.UnmarshalOptions{}).Unmarshal(b2, m); err != nil {
+				if !proto.Equal(ml, m) {
+					t.Errorf("Unmarshal roundtrip - protos not equal")
+					d <- false
+					return
+				}
+				if !proto.Equal(ml, m) {
+					t.Errorf("Unmarshal roundtrip - protos not equal")
+					d <- false
+					return
+				}
+			}
+			d <- true
+		}()
+		x := <-d
+		y := <-d
+		if !x || !y {
+			t.Fatalf("Worker reported error")
+		}
+	}
+}
+
+func fillMixedOpaqueLazy() *mixedpb.OpaqueLazy {
+	return mixedpb.OpaqueLazy_builder{
+		Opaque: mixedpb.OpaqueLazy_builder{
+			OptionalInt32: proto.Int32(23),
+			Hybrid: mixedpb.HybridLazy_builder{
+				OptionalInt32: proto.Int32(42),
+			}.Build(),
+		}.Build(),
+		Hybrid: mixedpb.HybridLazy_builder{
+			OptionalInt32: proto.Int32(5),
+		}.Build(),
+	}.Build()
+}
+
+func TestParallellMarshalMixed(t *testing.T) {
+	m := fillMixedOpaqueLazy()
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i := 0; i < 10000; i++ {
+		ml := &mixedpb.OpaqueLazy{}
+		d := make(chan bool)
+		if err := proto.Unmarshal(b, ml); err != nil {
+			t.Fatalf("Error while unmarshaling: %v", err)
+		}
+
+		go func() {
+			b2, err := proto.Marshal(ml)
+			if err != nil {
+				t.Errorf("Marshal error: %v", err)
+				d <- false
+				return
+			}
+			m := &mixedpb.OpaqueLazy{}
+			if err := proto.Unmarshal(b2, m); err != nil {
+				t.Errorf("Unmarshal error: %v", err)
+				d <- false
+				return
+			}
+			if !proto.Equal(ml, m) { // This is what expands all fields of ml
+				t.Errorf("Unmarshal roundtrip - protos not equal")
+				d <- false
+				return
+			}
+			d <- true
+		}()
+		go func() {
+			b2, err := proto.Marshal(ml)
+			if err != nil {
+				t.Errorf("Marshal error: %v", err)
+				d <- false
+				return
+			}
+			m := &mixedpb.OpaqueLazy{}
+			if err := proto.Unmarshal(b2, m); err != nil {
+				t.Errorf("Unmarshal error: %v", err)
+				d <- false
+				return
+			}
+			if !proto.Equal(ml, m) { // This is what expands all fields of ml
+				t.Errorf("Unmarshal roundtrip - protos not equal")
+				d <- false
+				return
+			}
+			d <- true
+		}()
+		x := <-d
+		y := <-d
+		if !x || !y {
+			t.Fatalf("Worker reported error")
+		}
+	}
+}
diff --git a/internal/reflection_test/reflection_hybrid_test.go b/internal/reflection_test/reflection_hybrid_test.go
new file mode 100644
index 0000000..4230ab8
--- /dev/null
+++ b/internal/reflection_test/reflection_hybrid_test.go
@@ -0,0 +1,1003 @@
+// 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 reflection_test
+
+import (
+	"fmt"
+	"math"
+	"testing"
+
+	testpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/prototest"
+)
+
+func TestOpen3Concrete(t *testing.T) {
+
+	prototest.Message{}.Test(t, newTestMessageOpen3(nil).ProtoReflect().Type())
+}
+
+func TestOpen3Reflection(t *testing.T) {
+	prototest.Message{}.Test(t, (*testpb.TestAllTypes)(nil).ProtoReflect().Type())
+}
+
+func TestOpen3Shadow_GetConcrete_SetReflection(t *testing.T) {
+	prototest.Message{}.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+		m := &testpb.TestAllTypes{}
+		return newTestMessageOpen3(m), m
+	}).ProtoReflect().Type())
+}
+
+func TestOpen3Shadow_GetReflection_SetConcrete(t *testing.T) {
+	prototest.Message{}.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+		m := &testpb.TestAllTypes{}
+		return m, newTestMessageOpen3(m)
+	}).ProtoReflect().Type())
+}
+
+func newTestMessageOpen3(m *testpb.TestAllTypes) protoreflect.ProtoMessage {
+	return &testProtoMessage{
+		m:  m,
+		md: m.ProtoReflect().Descriptor(),
+		new: func() protoreflect.Message {
+			return newTestMessageOpen3(&testpb.TestAllTypes{}).ProtoReflect()
+		},
+		has: func(num protoreflect.FieldNumber) bool {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32() != 0
+			case fieldSingularInt64:
+				return m.GetSingularInt64() != 0
+			case fieldSingularUint32:
+				return m.GetSingularUint32() != 0
+			case fieldSingularUint64:
+				return m.GetSingularUint64() != 0
+			case fieldSingularSint32:
+				return m.GetSingularSint32() != 0
+			case fieldSingularSint64:
+				return m.GetSingularSint64() != 0
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32() != 0
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64() != 0
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32() != 0
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64() != 0
+			case fieldSingularFloat:
+				return m.GetSingularFloat() != 0 || math.Signbit(float64(m.GetSingularFloat()))
+			case fieldSingularDouble:
+				return m.GetSingularDouble() != 0 || math.Signbit(m.GetSingularDouble())
+			case fieldSingularBool:
+				return m.GetSingularBool() != false
+			case fieldSingularString:
+				return m.GetSingularString() != ""
+			case fieldSingularBytes:
+				return len(m.GetSingularBytes()) != 0
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum() != testpb.TestAllTypes_FOO
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum() != testpb.ForeignEnum_FOREIGN_ZERO
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum() != testpb.ImportEnum_IMPORT_ZERO
+
+			case fieldOptionalInt32:
+				return m.HasOptionalInt32()
+			case fieldOptionalInt64:
+				return m.HasOptionalInt64()
+			case fieldOptionalUint32:
+				return m.HasOptionalUint32()
+			case fieldOptionalUint64:
+				return m.HasOptionalUint64()
+			case fieldOptionalSint32:
+				return m.HasOptionalSint32()
+			case fieldOptionalSint64:
+				return m.HasOptionalSint64()
+			case fieldOptionalFixed32:
+				return m.HasOptionalFixed32()
+			case fieldOptionalFixed64:
+				return m.HasOptionalFixed64()
+			case fieldOptionalSfixed32:
+				return m.HasOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				return m.HasOptionalSfixed64()
+			case fieldOptionalFloat:
+				return m.HasOptionalFloat()
+			case fieldOptionalDouble:
+				return m.HasOptionalDouble()
+			case fieldOptionalBool:
+				return m.HasOptionalBool()
+			case fieldOptionalString:
+				return m.HasOptionalString()
+			case fieldOptionalBytes:
+				return m.HasOptionalBytes()
+			case fieldOptionalGroup:
+				return m.HasOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				return m.HasNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				return m.HasOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				return m.HasOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				return m.HasOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				return m.HasOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				return m.HasOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				return m.HasOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				return m.HasOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				return len(m.GetRepeatedInt32()) > 0
+			case fieldRepeatedInt64:
+				return len(m.GetRepeatedInt64()) > 0
+			case fieldRepeatedUint32:
+				return len(m.GetRepeatedUint32()) > 0
+			case fieldRepeatedUint64:
+				return len(m.GetRepeatedUint64()) > 0
+			case fieldRepeatedSint32:
+				return len(m.GetRepeatedSint32()) > 0
+			case fieldRepeatedSint64:
+				return len(m.GetRepeatedSint64()) > 0
+			case fieldRepeatedFixed32:
+				return len(m.GetRepeatedFixed32()) > 0
+			case fieldRepeatedFixed64:
+				return len(m.GetRepeatedFixed64()) > 0
+			case fieldRepeatedSfixed32:
+				return len(m.GetRepeatedSfixed32()) > 0
+			case fieldRepeatedSfixed64:
+				return len(m.GetRepeatedSfixed64()) > 0
+			case fieldRepeatedFloat:
+				return len(m.GetRepeatedFloat()) > 0
+			case fieldRepeatedDouble:
+				return len(m.GetRepeatedDouble()) > 0
+			case fieldRepeatedBool:
+				return len(m.GetRepeatedBool()) > 0
+			case fieldRepeatedString:
+				return len(m.GetRepeatedString()) > 0
+			case fieldRepeatedBytes:
+				return len(m.GetRepeatedBytes()) > 0
+			case fieldRepeatedGroup:
+				return len(m.GetRepeatedgroup()) > 0
+			case fieldRepeatedNestedMessage:
+				return len(m.GetRepeatedNestedMessage()) > 0
+			case fieldRepeatedForeignMessage:
+				return len(m.GetRepeatedForeignMessage()) > 0
+			case fieldRepeatedImportMessage:
+				return len(m.GetRepeatedImportmessage()) > 0
+			case fieldRepeatedNestedEnum:
+				return len(m.GetRepeatedNestedEnum()) > 0
+			case fieldRepeatedForeignEnum:
+				return len(m.GetRepeatedForeignEnum()) > 0
+			case fieldRepeatedImportEnum:
+				return len(m.GetRepeatedImportenum()) > 0
+
+			case fieldMapInt32Int32:
+				return len(m.GetMapInt32Int32()) > 0
+			case fieldMapInt64Int64:
+				return len(m.GetMapInt64Int64()) > 0
+			case fieldMapUint32Uint32:
+				return len(m.GetMapUint32Uint32()) > 0
+			case fieldMapUint64Uint64:
+				return len(m.GetMapUint64Uint64()) > 0
+			case fieldMapSint32Sint32:
+				return len(m.GetMapSint32Sint32()) > 0
+			case fieldMapSint64Sint64:
+				return len(m.GetMapSint64Sint64()) > 0
+			case fieldMapFixed32Fixed32:
+				return len(m.GetMapFixed32Fixed32()) > 0
+			case fieldMapFixed64Fixed64:
+				return len(m.GetMapFixed64Fixed64()) > 0
+			case fieldMapSfixed32Sfixed32:
+				return len(m.GetMapSfixed32Sfixed32()) > 0
+			case fieldMapSfixed64Sfixed64:
+				return len(m.GetMapSfixed64Sfixed64()) > 0
+			case fieldMapInt32Float:
+				return len(m.GetMapInt32Float()) > 0
+			case fieldMapInt32Double:
+				return len(m.GetMapInt32Double()) > 0
+			case fieldMapBoolBool:
+				return len(m.GetMapBoolBool()) > 0
+			case fieldMapStringString:
+				return len(m.GetMapStringString()) > 0
+			case fieldMapStringBytes:
+				return len(m.GetMapStringBytes()) > 0
+			case fieldMapStringNestedMessage:
+				return len(m.GetMapStringNestedMessage()) > 0
+			case fieldMapStringNestedEnum:
+				return len(m.GetMapStringNestedEnum()) > 0
+
+			case fieldDefaultInt32:
+				return m.HasDefaultInt32()
+			case fieldDefaultInt64:
+				return m.HasDefaultInt64()
+			case fieldDefaultUint32:
+				return m.HasDefaultUint32()
+			case fieldDefaultUint64:
+				return m.HasDefaultUint64()
+			case fieldDefaultSint32:
+				return m.HasDefaultSint32()
+			case fieldDefaultSint64:
+				return m.HasDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.HasDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.HasDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.HasDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.HasDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.HasDefaultFloat()
+			case fieldDefaultDouble:
+				return m.HasDefaultDouble()
+			case fieldDefaultBool:
+				return m.HasDefaultBool()
+			case fieldDefaultString:
+				return m.HasDefaultString()
+			case fieldDefaultBytes:
+				return m.HasDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.HasDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.HasDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				return m.HasOneofUint32()
+			case fieldOneofNestedMessage:
+				return m.HasOneofNestedMessage()
+			case fieldOneofString:
+				return m.HasOneofString()
+			case fieldOneofBytes:
+				return m.HasOneofBytes()
+			case fieldOneofBool:
+				return m.HasOneofBool()
+			case fieldOneofUint64:
+				return m.HasOneofUint64()
+			case fieldOneofFloat:
+				return m.HasOneofFloat()
+			case fieldOneofDouble:
+				return m.HasOneofDouble()
+			case fieldOneofEnum:
+				return m.HasOneofEnum()
+			case fieldOneofGroup:
+				return m.HasOneofgroup()
+			case fieldOneofOptionalUint32:
+				return m.HasOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("has: unknown field %d", num))
+			}
+		},
+		get: func(num protoreflect.FieldNumber) any {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32()
+			case fieldSingularInt64:
+				return m.GetSingularInt64()
+			case fieldSingularUint32:
+				return m.GetSingularUint32()
+			case fieldSingularUint64:
+				return m.GetSingularUint64()
+			case fieldSingularSint32:
+				return m.GetSingularSint32()
+			case fieldSingularSint64:
+				return m.GetSingularSint64()
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32()
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64()
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32()
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64()
+			case fieldSingularFloat:
+				return m.GetSingularFloat()
+			case fieldSingularDouble:
+				return m.GetSingularDouble()
+			case fieldSingularBool:
+				return m.GetSingularBool()
+			case fieldSingularString:
+				return m.GetSingularString()
+			case fieldSingularBytes:
+				return m.GetSingularBytes()
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum()
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum()
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum()
+
+			case fieldOptionalInt32:
+				return m.GetOptionalInt32()
+			case fieldOptionalInt64:
+				return m.GetOptionalInt64()
+			case fieldOptionalUint32:
+				return m.GetOptionalUint32()
+			case fieldOptionalUint64:
+				return m.GetOptionalUint64()
+			case fieldOptionalSint32:
+				return m.GetOptionalSint32()
+			case fieldOptionalSint64:
+				return m.GetOptionalSint64()
+			case fieldOptionalFixed32:
+				return m.GetOptionalFixed32()
+			case fieldOptionalFixed64:
+				return m.GetOptionalFixed64()
+			case fieldOptionalSfixed32:
+				return m.GetOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				return m.GetOptionalSfixed64()
+			case fieldOptionalFloat:
+				return m.GetOptionalFloat()
+			case fieldOptionalDouble:
+				return m.GetOptionalDouble()
+			case fieldOptionalBool:
+				return m.GetOptionalBool()
+			case fieldOptionalString:
+				return m.GetOptionalString()
+			case fieldOptionalBytes:
+				return m.GetOptionalBytes()
+			case fieldOptionalGroup:
+				return m.GetOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				return m.GetNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				return m.GetOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				return m.GetOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				return m.GetOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				return m.GetOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				return m.GetOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				return m.GetOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				return m.GetOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				return m.GetRepeatedInt32()
+			case fieldRepeatedInt64:
+				return m.GetRepeatedInt64()
+			case fieldRepeatedUint32:
+				return m.GetRepeatedUint32()
+			case fieldRepeatedUint64:
+				return m.GetRepeatedUint64()
+			case fieldRepeatedSint32:
+				return m.GetRepeatedSint32()
+			case fieldRepeatedSint64:
+				return m.GetRepeatedSint64()
+			case fieldRepeatedFixed32:
+				return m.GetRepeatedFixed32()
+			case fieldRepeatedFixed64:
+				return m.GetRepeatedFixed64()
+			case fieldRepeatedSfixed32:
+				return m.GetRepeatedSfixed32()
+			case fieldRepeatedSfixed64:
+				return m.GetRepeatedSfixed64()
+			case fieldRepeatedFloat:
+				return m.GetRepeatedFloat()
+			case fieldRepeatedDouble:
+				return m.GetRepeatedDouble()
+			case fieldRepeatedBool:
+				return m.GetRepeatedBool()
+			case fieldRepeatedString:
+				return m.GetRepeatedString()
+			case fieldRepeatedBytes:
+				return m.GetRepeatedBytes()
+			case fieldRepeatedGroup:
+				return m.GetRepeatedgroup()
+			case fieldRepeatedNestedMessage:
+				return m.GetRepeatedNestedMessage()
+			case fieldRepeatedForeignMessage:
+				return m.GetRepeatedForeignMessage()
+			case fieldRepeatedImportMessage:
+				return m.GetRepeatedImportmessage()
+			case fieldRepeatedNestedEnum:
+				return m.GetRepeatedNestedEnum()
+			case fieldRepeatedForeignEnum:
+				return m.GetRepeatedForeignEnum()
+			case fieldRepeatedImportEnum:
+				return m.GetRepeatedImportenum()
+
+			case fieldMapInt32Int32:
+				return m.GetMapInt32Int32()
+			case fieldMapInt64Int64:
+				return m.GetMapInt64Int64()
+			case fieldMapUint32Uint32:
+				return m.GetMapUint32Uint32()
+			case fieldMapUint64Uint64:
+				return m.GetMapUint64Uint64()
+			case fieldMapSint32Sint32:
+				return m.GetMapSint32Sint32()
+			case fieldMapSint64Sint64:
+				return m.GetMapSint64Sint64()
+			case fieldMapFixed32Fixed32:
+				return m.GetMapFixed32Fixed32()
+			case fieldMapFixed64Fixed64:
+				return m.GetMapFixed64Fixed64()
+			case fieldMapSfixed32Sfixed32:
+				return m.GetMapSfixed32Sfixed32()
+			case fieldMapSfixed64Sfixed64:
+				return m.GetMapSfixed64Sfixed64()
+			case fieldMapInt32Float:
+				return m.GetMapInt32Float()
+			case fieldMapInt32Double:
+				return m.GetMapInt32Double()
+			case fieldMapBoolBool:
+				return m.GetMapBoolBool()
+			case fieldMapStringString:
+				return m.GetMapStringString()
+			case fieldMapStringBytes:
+				return m.GetMapStringBytes()
+			case fieldMapStringNestedMessage:
+				return m.GetMapStringNestedMessage()
+			case fieldMapStringNestedEnum:
+				return m.GetMapStringNestedEnum()
+
+			case fieldDefaultInt32:
+				return m.GetDefaultInt32()
+			case fieldDefaultInt64:
+				return m.GetDefaultInt64()
+			case fieldDefaultUint32:
+				return m.GetDefaultUint32()
+			case fieldDefaultUint64:
+				return m.GetDefaultUint64()
+			case fieldDefaultSint32:
+				return m.GetDefaultSint32()
+			case fieldDefaultSint64:
+				return m.GetDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.GetDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.GetDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.GetDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.GetDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.GetDefaultFloat()
+			case fieldDefaultDouble:
+				return m.GetDefaultDouble()
+			case fieldDefaultBool:
+				return m.GetDefaultBool()
+			case fieldDefaultString:
+				return m.GetDefaultString()
+			case fieldDefaultBytes:
+				return m.GetDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.GetDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.GetDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				return m.GetOneofUint32()
+			case fieldOneofNestedMessage:
+				return m.GetOneofNestedMessage()
+			case fieldOneofString:
+				return m.GetOneofString()
+			case fieldOneofBytes:
+				return m.GetOneofBytes()
+			case fieldOneofBool:
+				return m.GetOneofBool()
+			case fieldOneofUint64:
+				return m.GetOneofUint64()
+			case fieldOneofFloat:
+				return m.GetOneofFloat()
+			case fieldOneofDouble:
+				return m.GetOneofDouble()
+			case fieldOneofEnum:
+				return m.GetOneofEnum()
+			case fieldOneofGroup:
+				return m.GetOneofgroup()
+			case fieldOneofOptionalUint32:
+				return m.GetOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("get: unknown field %d", num))
+			}
+		},
+		set: func(num protoreflect.FieldNumber, v any) {
+			switch num {
+			case fieldSingularInt32:
+				m.SetSingularInt32(v.(int32))
+			case fieldSingularInt64:
+				m.SetSingularInt64(v.(int64))
+			case fieldSingularUint32:
+				m.SetSingularUint32(v.(uint32))
+			case fieldSingularUint64:
+				m.SetSingularUint64(v.(uint64))
+			case fieldSingularSint32:
+				m.SetSingularSint32(v.(int32))
+			case fieldSingularSint64:
+				m.SetSingularSint64(v.(int64))
+			case fieldSingularFixed32:
+				m.SetSingularFixed32(v.(uint32))
+			case fieldSingularFixed64:
+				m.SetSingularFixed64(v.(uint64))
+			case fieldSingularSfixed32:
+				m.SetSingularSfixed32(v.(int32))
+			case fieldSingularSfixed64:
+				m.SetSingularSfixed64(v.(int64))
+			case fieldSingularFloat:
+				m.SetSingularFloat(v.(float32))
+			case fieldSingularDouble:
+				m.SetSingularDouble(v.(float64))
+			case fieldSingularBool:
+				m.SetSingularBool(v.(bool))
+			case fieldSingularString:
+				m.SetSingularString(v.(string))
+			case fieldSingularBytes:
+				m.SetSingularBytes(v.([]byte))
+			case fieldSingularNestedEnum:
+				m.SetSingularNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldSingularForeignEnum:
+				m.SetSingularForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+			case fieldSingularImportEnum:
+				m.SetSingularImportEnum(testpb.ImportEnum(v.(protoreflect.EnumNumber)))
+
+			case fieldOptionalInt32:
+				m.SetOptionalInt32(v.(int32))
+			case fieldOptionalInt64:
+				m.SetOptionalInt64(v.(int64))
+			case fieldOptionalUint32:
+				m.SetOptionalUint32(v.(uint32))
+			case fieldOptionalUint64:
+				m.SetOptionalUint64(v.(uint64))
+			case fieldOptionalSint32:
+				m.SetOptionalSint32(v.(int32))
+			case fieldOptionalSint64:
+				m.SetOptionalSint64(v.(int64))
+			case fieldOptionalFixed32:
+				m.SetOptionalFixed32(v.(uint32))
+			case fieldOptionalFixed64:
+				m.SetOptionalFixed64(v.(uint64))
+			case fieldOptionalSfixed32:
+				m.SetOptionalSfixed32(v.(int32))
+			case fieldOptionalSfixed64:
+				m.SetOptionalSfixed64(v.(int64))
+			case fieldOptionalFloat:
+				m.SetOptionalFloat(v.(float32))
+			case fieldOptionalDouble:
+				m.SetOptionalDouble(v.(float64))
+			case fieldOptionalBool:
+				m.SetOptionalBool(v.(bool))
+			case fieldOptionalString:
+				m.SetOptionalString(v.(string))
+			case fieldOptionalBytes:
+				m.SetOptionalBytes(v.([]byte))
+			case fieldOptionalGroup:
+				m.SetOptionalgroup(v.(*testpb.TestAllTypes_OptionalGroup))
+			case fieldNotGroupLikeDelimited:
+				m.SetNotGroupLikeDelimited(v.(*testpb.TestAllTypes_OptionalGroup))
+			case fieldOptionalNestedMessage:
+				m.SetOptionalNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+			case fieldOptionalForeignMessage:
+				m.SetOptionalForeignMessage(v.(*testpb.ForeignMessage))
+			case fieldOptionalImportMessage:
+				m.SetOptionalImportMessage(v.(*testpb.ImportMessage))
+			case fieldOptionalNestedEnum:
+				m.SetOptionalNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalForeignEnum:
+				m.SetOptionalForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalImportEnum:
+				m.SetOptionalImportEnum(testpb.ImportEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalLazyNestedMessage:
+				m.SetOptionalLazyNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+
+			case fieldRepeatedInt32:
+				m.SetRepeatedInt32(v.([]int32))
+			case fieldRepeatedInt64:
+				m.SetRepeatedInt64(v.([]int64))
+			case fieldRepeatedUint32:
+				m.SetRepeatedUint32(v.([]uint32))
+			case fieldRepeatedUint64:
+				m.SetRepeatedUint64(v.([]uint64))
+			case fieldRepeatedSint32:
+				m.SetRepeatedSint32(v.([]int32))
+			case fieldRepeatedSint64:
+				m.SetRepeatedSint64(v.([]int64))
+			case fieldRepeatedFixed32:
+				m.SetRepeatedFixed32(v.([]uint32))
+			case fieldRepeatedFixed64:
+				m.SetRepeatedFixed64(v.([]uint64))
+			case fieldRepeatedSfixed32:
+				m.SetRepeatedSfixed32(v.([]int32))
+			case fieldRepeatedSfixed64:
+				m.SetRepeatedSfixed64(v.([]int64))
+			case fieldRepeatedFloat:
+				m.SetRepeatedFloat(v.([]float32))
+			case fieldRepeatedDouble:
+				m.SetRepeatedDouble(v.([]float64))
+			case fieldRepeatedBool:
+				m.SetRepeatedBool(v.([]bool))
+			case fieldRepeatedString:
+				m.SetRepeatedString(v.([]string))
+			case fieldRepeatedBytes:
+				m.SetRepeatedBytes(v.([][]byte))
+			case fieldRepeatedGroup:
+				m.SetRepeatedgroup(v.([]*testpb.TestAllTypes_RepeatedGroup))
+			case fieldRepeatedNestedMessage:
+				m.SetRepeatedNestedMessage(v.([]*testpb.TestAllTypes_NestedMessage))
+			case fieldRepeatedForeignMessage:
+				m.SetRepeatedForeignMessage(v.([]*testpb.ForeignMessage))
+			case fieldRepeatedImportMessage:
+				m.SetRepeatedImportmessage(v.([]*testpb.ImportMessage))
+			case fieldRepeatedNestedEnum:
+				m.SetRepeatedNestedEnum(v.([]testpb.TestAllTypes_NestedEnum))
+			case fieldRepeatedForeignEnum:
+				m.SetRepeatedForeignEnum(v.([]testpb.ForeignEnum))
+			case fieldRepeatedImportEnum:
+				m.SetRepeatedImportenum(v.([]testpb.ImportEnum))
+
+			case fieldMapInt32Int32:
+				m.SetMapInt32Int32(v.(map[int32]int32))
+			case fieldMapInt64Int64:
+				m.SetMapInt64Int64(v.(map[int64]int64))
+			case fieldMapUint32Uint32:
+				m.SetMapUint32Uint32(v.(map[uint32]uint32))
+			case fieldMapUint64Uint64:
+				m.SetMapUint64Uint64(v.(map[uint64]uint64))
+			case fieldMapSint32Sint32:
+				m.SetMapSint32Sint32(v.(map[int32]int32))
+			case fieldMapSint64Sint64:
+				m.SetMapSint64Sint64(v.(map[int64]int64))
+			case fieldMapFixed32Fixed32:
+				m.SetMapFixed32Fixed32(v.(map[uint32]uint32))
+			case fieldMapFixed64Fixed64:
+				m.SetMapFixed64Fixed64(v.(map[uint64]uint64))
+			case fieldMapSfixed32Sfixed32:
+				m.SetMapSfixed32Sfixed32(v.(map[int32]int32))
+			case fieldMapSfixed64Sfixed64:
+				m.SetMapSfixed64Sfixed64(v.(map[int64]int64))
+			case fieldMapInt32Float:
+				m.SetMapInt32Float(v.(map[int32]float32))
+			case fieldMapInt32Double:
+				m.SetMapInt32Double(v.(map[int32]float64))
+			case fieldMapBoolBool:
+				m.SetMapBoolBool(v.(map[bool]bool))
+			case fieldMapStringString:
+				m.SetMapStringString(v.(map[string]string))
+			case fieldMapStringBytes:
+				m.SetMapStringBytes(v.(map[string][]byte))
+			case fieldMapStringNestedMessage:
+				m.SetMapStringNestedMessage(v.(map[string]*testpb.TestAllTypes_NestedMessage))
+			case fieldMapStringNestedEnum:
+				m.SetMapStringNestedEnum(v.(map[string]testpb.TestAllTypes_NestedEnum))
+
+			case fieldDefaultInt32:
+				m.SetDefaultInt32(v.(int32))
+			case fieldDefaultInt64:
+				m.SetDefaultInt64(v.(int64))
+			case fieldDefaultUint32:
+				m.SetDefaultUint32(v.(uint32))
+			case fieldDefaultUint64:
+				m.SetDefaultUint64(v.(uint64))
+			case fieldDefaultSint32:
+				m.SetDefaultSint32(v.(int32))
+			case fieldDefaultSint64:
+				m.SetDefaultSint64(v.(int64))
+			case fieldDefaultFixed32:
+				m.SetDefaultFixed32(v.(uint32))
+			case fieldDefaultFixed64:
+				m.SetDefaultFixed64(v.(uint64))
+			case fieldDefaultSfixed32:
+				m.SetDefaultSfixed32(v.(int32))
+			case fieldDefaultSfixed64:
+				m.SetDefaultSfixed64(v.(int64))
+			case fieldDefaultFloat:
+				m.SetDefaultFloat(v.(float32))
+			case fieldDefaultDouble:
+				m.SetDefaultDouble(v.(float64))
+			case fieldDefaultBool:
+				m.SetDefaultBool(v.(bool))
+			case fieldDefaultString:
+				m.SetDefaultString(v.(string))
+			case fieldDefaultBytes:
+				m.SetDefaultBytes(v.([]byte))
+			case fieldDefaultNestedEnum:
+				m.SetDefaultNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldDefaultForeignEnum:
+				m.SetDefaultForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+
+			case fieldDefaultInt32:
+				m.SetDefaultInt32(v.(int32))
+			case fieldDefaultInt64:
+				m.SetDefaultInt64(v.(int64))
+			case fieldDefaultUint32:
+				m.SetDefaultUint32(v.(uint32))
+			case fieldDefaultUint64:
+				m.SetDefaultUint64(v.(uint64))
+			case fieldDefaultSint32:
+				m.SetDefaultSint32(v.(int32))
+			case fieldDefaultSint64:
+				m.SetDefaultSint64(v.(int64))
+			case fieldDefaultFixed32:
+				m.SetDefaultFixed32(v.(uint32))
+			case fieldDefaultFixed64:
+				m.SetDefaultFixed64(v.(uint64))
+			case fieldDefaultSfixed32:
+				m.SetDefaultSfixed32(v.(int32))
+			case fieldDefaultSfixed64:
+				m.SetDefaultSfixed64(v.(int64))
+			case fieldDefaultFloat:
+				m.SetDefaultFloat(v.(float32))
+			case fieldDefaultDouble:
+				m.SetDefaultDouble(v.(float64))
+			case fieldDefaultBool:
+				m.SetDefaultBool(v.(bool))
+			case fieldDefaultString:
+				m.SetDefaultString(v.(string))
+			case fieldDefaultBytes:
+				m.SetDefaultBytes(v.([]byte))
+			case fieldDefaultNestedEnum:
+				m.SetDefaultNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldDefaultForeignEnum:
+				m.SetDefaultForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+
+			case fieldOneofUint32:
+				m.SetOneofUint32(v.(uint32))
+			case fieldOneofNestedMessage:
+				m.SetOneofNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+			case fieldOneofString:
+				m.SetOneofString(v.(string))
+			case fieldOneofBytes:
+				m.SetOneofBytes(v.([]byte))
+			case fieldOneofBool:
+				m.SetOneofBool(v.(bool))
+			case fieldOneofUint64:
+				m.SetOneofUint64(v.(uint64))
+			case fieldOneofFloat:
+				m.SetOneofFloat(v.(float32))
+			case fieldOneofDouble:
+				m.SetOneofDouble(v.(float64))
+			case fieldOneofEnum:
+				m.SetOneofEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldOneofGroup:
+				m.SetOneofgroup(v.(*testpb.TestAllTypes_OneofGroup))
+			case fieldOneofOptionalUint32:
+				m.SetOneofOptionalUint32(v.(uint32))
+
+			default:
+				panic(fmt.Sprintf("set: unknown field %d", num))
+			}
+		},
+		clear: func(num protoreflect.FieldNumber) {
+			switch num {
+			case fieldSingularInt32:
+				m.SetSingularInt32(0)
+			case fieldSingularInt64:
+				m.SetSingularInt64(0)
+			case fieldSingularUint32:
+				m.SetSingularUint32(0)
+			case fieldSingularUint64:
+				m.SetSingularUint64(0)
+			case fieldSingularSint32:
+				m.SetSingularSint32(0)
+			case fieldSingularSint64:
+				m.SetSingularSint64(0)
+			case fieldSingularFixed32:
+				m.SetSingularFixed32(0)
+			case fieldSingularFixed64:
+				m.SetSingularFixed64(0)
+			case fieldSingularSfixed32:
+				m.SetSingularSfixed32(0)
+			case fieldSingularSfixed64:
+				m.SetSingularSfixed64(0)
+			case fieldSingularFloat:
+				m.SetSingularFloat(0)
+			case fieldSingularDouble:
+				m.SetSingularDouble(0)
+			case fieldSingularBool:
+				m.SetSingularBool(false)
+			case fieldSingularString:
+				m.SetSingularString("")
+			case fieldSingularBytes:
+				m.SetSingularBytes(nil)
+			case fieldSingularNestedEnum:
+				m.SetSingularNestedEnum(testpb.TestAllTypes_FOO)
+			case fieldSingularForeignEnum:
+				m.SetSingularForeignEnum(testpb.ForeignEnum_FOREIGN_ZERO)
+			case fieldSingularImportEnum:
+				m.SetSingularImportEnum(testpb.ImportEnum_IMPORT_ZERO)
+
+			case fieldOptionalInt32:
+				m.ClearOptionalInt32()
+			case fieldOptionalInt64:
+				m.ClearOptionalInt64()
+			case fieldOptionalUint32:
+				m.ClearOptionalUint32()
+			case fieldOptionalUint64:
+				m.ClearOptionalUint64()
+			case fieldOptionalSint32:
+				m.ClearOptionalSint32()
+			case fieldOptionalSint64:
+				m.ClearOptionalSint64()
+			case fieldOptionalFixed32:
+				m.ClearOptionalFixed32()
+			case fieldOptionalFixed64:
+				m.ClearOptionalFixed64()
+			case fieldOptionalSfixed32:
+				m.ClearOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				m.ClearOptionalSfixed64()
+			case fieldOptionalFloat:
+				m.ClearOptionalFloat()
+			case fieldOptionalDouble:
+				m.ClearOptionalDouble()
+			case fieldOptionalBool:
+				m.ClearOptionalBool()
+			case fieldOptionalString:
+				m.ClearOptionalString()
+			case fieldOptionalBytes:
+				m.ClearOptionalBytes()
+			case fieldOptionalGroup:
+				m.ClearOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				m.ClearNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				m.ClearOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				m.ClearOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				m.ClearOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				m.ClearOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				m.ClearOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				m.ClearOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				m.ClearOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				m.SetRepeatedInt32(nil)
+			case fieldRepeatedInt64:
+				m.SetRepeatedInt64(nil)
+			case fieldRepeatedUint32:
+				m.SetRepeatedUint32(nil)
+			case fieldRepeatedUint64:
+				m.SetRepeatedUint64(nil)
+			case fieldRepeatedSint32:
+				m.SetRepeatedSint32(nil)
+			case fieldRepeatedSint64:
+				m.SetRepeatedSint64(nil)
+			case fieldRepeatedFixed32:
+				m.SetRepeatedFixed32(nil)
+			case fieldRepeatedFixed64:
+				m.SetRepeatedFixed64(nil)
+			case fieldRepeatedSfixed32:
+				m.SetRepeatedSfixed32(nil)
+			case fieldRepeatedSfixed64:
+				m.SetRepeatedSfixed64(nil)
+			case fieldRepeatedFloat:
+				m.SetRepeatedFloat(nil)
+			case fieldRepeatedDouble:
+				m.SetRepeatedDouble(nil)
+			case fieldRepeatedBool:
+				m.SetRepeatedBool(nil)
+			case fieldRepeatedString:
+				m.SetRepeatedString(nil)
+			case fieldRepeatedBytes:
+				m.SetRepeatedBytes(nil)
+			case fieldRepeatedGroup:
+				m.SetRepeatedgroup(nil)
+			case fieldRepeatedNestedMessage:
+				m.SetRepeatedNestedMessage(nil)
+			case fieldRepeatedForeignMessage:
+				m.SetRepeatedForeignMessage(nil)
+			case fieldRepeatedImportMessage:
+				m.SetRepeatedImportmessage(nil)
+			case fieldRepeatedNestedEnum:
+				m.SetRepeatedNestedEnum(nil)
+			case fieldRepeatedForeignEnum:
+				m.SetRepeatedForeignEnum(nil)
+			case fieldRepeatedImportEnum:
+				m.SetRepeatedImportenum(nil)
+
+			case fieldMapInt32Int32:
+				m.SetMapInt32Int32(nil)
+			case fieldMapInt64Int64:
+				m.SetMapInt64Int64(nil)
+			case fieldMapUint32Uint32:
+				m.SetMapUint32Uint32(nil)
+			case fieldMapUint64Uint64:
+				m.SetMapUint64Uint64(nil)
+			case fieldMapSint32Sint32:
+				m.SetMapSint32Sint32(nil)
+			case fieldMapSint64Sint64:
+				m.SetMapSint64Sint64(nil)
+			case fieldMapFixed32Fixed32:
+				m.SetMapFixed32Fixed32(nil)
+			case fieldMapFixed64Fixed64:
+				m.SetMapFixed64Fixed64(nil)
+			case fieldMapSfixed32Sfixed32:
+				m.SetMapSfixed32Sfixed32(nil)
+			case fieldMapSfixed64Sfixed64:
+				m.SetMapSfixed64Sfixed64(nil)
+			case fieldMapInt32Float:
+				m.SetMapInt32Float(nil)
+			case fieldMapInt32Double:
+				m.SetMapInt32Double(nil)
+			case fieldMapBoolBool:
+				m.SetMapBoolBool(nil)
+			case fieldMapStringString:
+				m.SetMapStringString(nil)
+			case fieldMapStringBytes:
+				m.SetMapStringBytes(nil)
+			case fieldMapStringNestedMessage:
+				m.SetMapStringNestedMessage(nil)
+			case fieldMapStringNestedEnum:
+				m.SetMapStringNestedEnum(nil)
+
+			case fieldDefaultInt32:
+				m.ClearDefaultInt32()
+			case fieldDefaultInt64:
+				m.ClearDefaultInt64()
+			case fieldDefaultUint32:
+				m.ClearDefaultUint32()
+			case fieldDefaultUint64:
+				m.ClearDefaultUint64()
+			case fieldDefaultSint32:
+				m.ClearDefaultSint32()
+			case fieldDefaultSint64:
+				m.ClearDefaultSint64()
+			case fieldDefaultFixed32:
+				m.ClearDefaultFixed32()
+			case fieldDefaultFixed64:
+				m.ClearDefaultFixed64()
+			case fieldDefaultSfixed32:
+				m.ClearDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				m.ClearDefaultSfixed64()
+			case fieldDefaultFloat:
+				m.ClearDefaultFloat()
+			case fieldDefaultDouble:
+				m.ClearDefaultDouble()
+			case fieldDefaultBool:
+				m.ClearDefaultBool()
+			case fieldDefaultString:
+				m.ClearDefaultString()
+			case fieldDefaultBytes:
+				m.ClearDefaultBytes()
+			case fieldDefaultNestedEnum:
+				m.ClearDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				m.ClearDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				m.ClearOneofUint32()
+			case fieldOneofNestedMessage:
+				m.ClearOneofNestedMessage()
+			case fieldOneofString:
+				m.ClearOneofString()
+			case fieldOneofBytes:
+				m.ClearOneofBytes()
+			case fieldOneofBool:
+				m.ClearOneofBool()
+			case fieldOneofUint64:
+				m.ClearOneofUint64()
+			case fieldOneofFloat:
+				m.ClearOneofFloat()
+			case fieldOneofDouble:
+				m.ClearOneofDouble()
+			case fieldOneofEnum:
+				m.ClearOneofEnum()
+			case fieldOneofGroup:
+				m.ClearOneofgroup()
+			case fieldOneofOptionalUint32:
+				m.ClearOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("clear: unknown field %d", num))
+			}
+		},
+	}
+}
diff --git a/internal/reflection_test/reflection_large_opaque_test.go b/internal/reflection_test/reflection_large_opaque_test.go
new file mode 100644
index 0000000..fc88620
--- /dev/null
+++ b/internal/reflection_test/reflection_large_opaque_test.go
@@ -0,0 +1,893 @@
+// 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 reflection_test
+
+import (
+	"fmt"
+	"testing"
+
+	testpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func TestLargeOpaqueConcrete(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newTestMessageLargeOpaque(nil).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestLargeOpaqueReflection(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, (*testpb.TestManyMessageFieldsMessage)(nil).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestLargeOpaqueShadow_GetConcrete_SetReflection(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+				m := &testpb.TestManyMessageFieldsMessage{}
+				return newTestMessageLargeOpaque(m), m
+			}).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestLargeOpaqueShadow_GetReflection_SetConcrete(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+				m := &testpb.TestManyMessageFieldsMessage{}
+				return m, newTestMessageLargeOpaque(m)
+			}).ProtoReflect().Type())
+		})
+	}
+}
+
+func newTestMessageLargeOpaque(m *testpb.TestManyMessageFieldsMessage) protoreflect.ProtoMessage {
+	return &testProtoMessage{
+		m:  m,
+		md: m.ProtoReflect().Descriptor(),
+		new: func() protoreflect.Message {
+			return newTestMessageLargeOpaque(&testpb.TestManyMessageFieldsMessage{}).ProtoReflect()
+		},
+		has: func(num protoreflect.FieldNumber) bool {
+			switch num {
+			case largeFieldF1:
+				return m.HasF1()
+			case largeFieldF2:
+				return m.HasF2()
+			case largeFieldF3:
+				return m.HasF3()
+			case largeFieldF4:
+				return m.HasF4()
+			case largeFieldF5:
+				return m.HasF5()
+			case largeFieldF6:
+				return m.HasF6()
+			case largeFieldF7:
+				return m.HasF7()
+			case largeFieldF8:
+				return m.HasF8()
+			case largeFieldF9:
+				return m.HasF9()
+			case largeFieldF10:
+				return m.HasF10()
+			case largeFieldF11:
+				return m.HasF11()
+			case largeFieldF12:
+				return m.HasF12()
+			case largeFieldF13:
+				return m.HasF13()
+			case largeFieldF14:
+				return m.HasF14()
+			case largeFieldF15:
+				return m.HasF15()
+			case largeFieldF16:
+				return m.HasF16()
+			case largeFieldF17:
+				return m.HasF17()
+			case largeFieldF18:
+				return m.HasF18()
+			case largeFieldF19:
+				return m.HasF19()
+			case largeFieldF20:
+				return m.HasF20()
+			case largeFieldF21:
+				return m.HasF21()
+			case largeFieldF22:
+				return m.HasF22()
+			case largeFieldF23:
+				return m.HasF23()
+			case largeFieldF24:
+				return m.HasF24()
+			case largeFieldF25:
+				return m.HasF25()
+			case largeFieldF26:
+				return m.HasF26()
+			case largeFieldF27:
+				return m.HasF27()
+			case largeFieldF28:
+				return m.HasF28()
+			case largeFieldF29:
+				return m.HasF29()
+			case largeFieldF30:
+				return m.HasF30()
+			case largeFieldF31:
+				return m.HasF31()
+			case largeFieldF32:
+				return m.HasF32()
+			case largeFieldF33:
+				return m.HasF33()
+			case largeFieldF34:
+				return m.HasF34()
+			case largeFieldF35:
+				return m.HasF35()
+			case largeFieldF36:
+				return m.HasF36()
+			case largeFieldF37:
+				return m.HasF37()
+			case largeFieldF38:
+				return m.HasF38()
+			case largeFieldF39:
+				return m.HasF39()
+			case largeFieldF40:
+				return m.HasF40()
+			case largeFieldF41:
+				return m.HasF41()
+			case largeFieldF42:
+				return m.HasF42()
+			case largeFieldF43:
+				return m.HasF43()
+			case largeFieldF44:
+				return m.HasF44()
+			case largeFieldF45:
+				return m.HasF45()
+			case largeFieldF46:
+				return m.HasF46()
+			case largeFieldF47:
+				return m.HasF47()
+			case largeFieldF48:
+				return m.HasF48()
+			case largeFieldF49:
+				return m.HasF49()
+			case largeFieldF50:
+				return m.HasF50()
+			case largeFieldF51:
+				return m.HasF51()
+			case largeFieldF52:
+				return m.HasF52()
+			case largeFieldF53:
+				return m.HasF53()
+			case largeFieldF54:
+				return m.HasF54()
+			case largeFieldF55:
+				return m.HasF55()
+			case largeFieldF56:
+				return m.HasF56()
+			case largeFieldF57:
+				return m.HasF57()
+			case largeFieldF58:
+				return m.HasF58()
+			case largeFieldF59:
+				return m.HasF59()
+			case largeFieldF60:
+				return m.HasF60()
+			case largeFieldF60:
+				return m.HasF60()
+			case largeFieldF61:
+				return m.HasF61()
+			case largeFieldF62:
+				return m.HasF62()
+			case largeFieldF63:
+				return m.HasF63()
+			case largeFieldF64:
+				return m.HasF64()
+			case largeFieldF65:
+				return m.HasF65()
+			case largeFieldF66:
+				return m.HasF66()
+			case largeFieldF67:
+				return m.HasF67()
+			case largeFieldF68:
+				return m.HasF68()
+			case largeFieldF69:
+				return m.HasF69()
+			case largeFieldF70:
+				return m.HasF70()
+			case largeFieldF71:
+				return m.HasF71()
+			case largeFieldF72:
+				return m.HasF72()
+			case largeFieldF73:
+				return m.HasF73()
+			case largeFieldF74:
+				return m.HasF74()
+			case largeFieldF75:
+				return m.HasF75()
+			case largeFieldF76:
+				return m.HasF76()
+			case largeFieldF77:
+				return m.HasF77()
+			case largeFieldF78:
+				return m.HasF78()
+			case largeFieldF79:
+				return m.HasF79()
+			case largeFieldF80:
+				return m.HasF80()
+			case largeFieldF81:
+				return m.HasF81()
+			case largeFieldF82:
+				return m.HasF82()
+			case largeFieldF83:
+				return m.HasF83()
+			case largeFieldF84:
+				return m.HasF84()
+			case largeFieldF85:
+				return m.HasF85()
+			case largeFieldF86:
+				return m.HasF86()
+			case largeFieldF87:
+				return m.HasF87()
+			case largeFieldF88:
+				return m.HasF88()
+			case largeFieldF89:
+				return m.HasF89()
+			case largeFieldF90:
+				return m.HasF90()
+			case largeFieldF91:
+				return m.HasF91()
+			case largeFieldF92:
+				return m.HasF92()
+			case largeFieldF93:
+				return m.HasF93()
+			case largeFieldF94:
+				return m.HasF94()
+			case largeFieldF95:
+				return m.HasF95()
+			case largeFieldF96:
+				return m.HasF96()
+			case largeFieldF97:
+				return m.HasF97()
+			case largeFieldF98:
+				return m.HasF98()
+			case largeFieldF99:
+				return m.HasF99()
+			case largeFieldF100:
+				return m.HasF100()
+
+			default:
+				panic(fmt.Sprintf("has: unknown field %d", num))
+			}
+		},
+		get: func(num protoreflect.FieldNumber) any {
+			switch num {
+			case largeFieldF1:
+				return m.GetF1()
+			case largeFieldF2:
+				return m.GetF2()
+			case largeFieldF3:
+				return m.GetF3()
+			case largeFieldF4:
+				return m.GetF4()
+			case largeFieldF5:
+				return m.GetF5()
+			case largeFieldF6:
+				return m.GetF6()
+			case largeFieldF7:
+				return m.GetF7()
+			case largeFieldF8:
+				return m.GetF8()
+			case largeFieldF9:
+				return m.GetF9()
+			case largeFieldF10:
+				return m.GetF10()
+			case largeFieldF11:
+				return m.GetF11()
+			case largeFieldF12:
+				return m.GetF12()
+			case largeFieldF13:
+				return m.GetF13()
+			case largeFieldF14:
+				return m.GetF14()
+			case largeFieldF15:
+				return m.GetF15()
+			case largeFieldF16:
+				return m.GetF16()
+			case largeFieldF17:
+				return m.GetF17()
+			case largeFieldF18:
+				return m.GetF18()
+			case largeFieldF19:
+				return m.GetF19()
+			case largeFieldF20:
+				return m.GetF20()
+			case largeFieldF21:
+				return m.GetF21()
+			case largeFieldF22:
+				return m.GetF22()
+			case largeFieldF23:
+				return m.GetF23()
+			case largeFieldF24:
+				return m.GetF24()
+			case largeFieldF25:
+				return m.GetF25()
+			case largeFieldF26:
+				return m.GetF26()
+			case largeFieldF27:
+				return m.GetF27()
+			case largeFieldF28:
+				return m.GetF28()
+			case largeFieldF29:
+				return m.GetF29()
+			case largeFieldF30:
+				return m.GetF30()
+			case largeFieldF31:
+				return m.GetF31()
+			case largeFieldF32:
+				return m.GetF32()
+			case largeFieldF33:
+				return m.GetF33()
+			case largeFieldF34:
+				return m.GetF34()
+			case largeFieldF35:
+				return m.GetF35()
+			case largeFieldF36:
+				return m.GetF36()
+			case largeFieldF37:
+				return m.GetF37()
+			case largeFieldF38:
+				return m.GetF38()
+			case largeFieldF39:
+				return m.GetF39()
+			case largeFieldF40:
+				return m.GetF40()
+			case largeFieldF41:
+				return m.GetF41()
+			case largeFieldF42:
+				return m.GetF42()
+			case largeFieldF43:
+				return m.GetF43()
+			case largeFieldF44:
+				return m.GetF44()
+			case largeFieldF45:
+				return m.GetF45()
+			case largeFieldF46:
+				return m.GetF46()
+			case largeFieldF47:
+				return m.GetF47()
+			case largeFieldF48:
+				return m.GetF48()
+			case largeFieldF49:
+				return m.GetF49()
+			case largeFieldF50:
+				return m.GetF50()
+			case largeFieldF51:
+				return m.GetF51()
+			case largeFieldF52:
+				return m.GetF52()
+			case largeFieldF53:
+				return m.GetF53()
+			case largeFieldF54:
+				return m.GetF54()
+			case largeFieldF55:
+				return m.GetF55()
+			case largeFieldF56:
+				return m.GetF56()
+			case largeFieldF57:
+				return m.GetF57()
+			case largeFieldF58:
+				return m.GetF58()
+			case largeFieldF59:
+				return m.GetF59()
+			case largeFieldF60:
+				return m.GetF60()
+			case largeFieldF61:
+				return m.GetF61()
+			case largeFieldF62:
+				return m.GetF62()
+			case largeFieldF63:
+				return m.GetF63()
+			case largeFieldF64:
+				return m.GetF64()
+			case largeFieldF65:
+				return m.GetF65()
+			case largeFieldF66:
+				return m.GetF66()
+			case largeFieldF67:
+				return m.GetF67()
+			case largeFieldF68:
+				return m.GetF68()
+			case largeFieldF69:
+				return m.GetF69()
+			case largeFieldF70:
+				return m.GetF70()
+			case largeFieldF71:
+				return m.GetF71()
+			case largeFieldF72:
+				return m.GetF72()
+			case largeFieldF73:
+				return m.GetF73()
+			case largeFieldF74:
+				return m.GetF74()
+			case largeFieldF75:
+				return m.GetF75()
+			case largeFieldF76:
+				return m.GetF76()
+			case largeFieldF77:
+				return m.GetF77()
+			case largeFieldF78:
+				return m.GetF78()
+			case largeFieldF79:
+				return m.GetF79()
+			case largeFieldF80:
+				return m.GetF80()
+			case largeFieldF81:
+				return m.GetF81()
+			case largeFieldF82:
+				return m.GetF82()
+			case largeFieldF83:
+				return m.GetF83()
+			case largeFieldF84:
+				return m.GetF84()
+			case largeFieldF85:
+				return m.GetF85()
+			case largeFieldF86:
+				return m.GetF86()
+			case largeFieldF87:
+				return m.GetF87()
+			case largeFieldF88:
+				return m.GetF88()
+			case largeFieldF89:
+				return m.GetF89()
+			case largeFieldF90:
+				return m.GetF90()
+			case largeFieldF91:
+				return m.GetF91()
+			case largeFieldF92:
+				return m.GetF92()
+			case largeFieldF93:
+				return m.GetF93()
+			case largeFieldF94:
+				return m.GetF94()
+			case largeFieldF95:
+				return m.GetF95()
+			case largeFieldF96:
+				return m.GetF96()
+			case largeFieldF97:
+				return m.GetF97()
+			case largeFieldF98:
+				return m.GetF98()
+			case largeFieldF99:
+				return m.GetF99()
+			case largeFieldF100:
+				return m.GetF100()
+
+			default:
+				panic(fmt.Sprintf("get: unknown field %d", num))
+			}
+		},
+		set: func(num protoreflect.FieldNumber, v any) {
+			switch num {
+			case largeFieldF1:
+				m.SetF1(v.(*testpb.TestAllTypes))
+			case largeFieldF2:
+				m.SetF2(v.(*testpb.TestAllTypes))
+			case largeFieldF3:
+				m.SetF3(v.(*testpb.TestAllTypes))
+			case largeFieldF4:
+				m.SetF4(v.(*testpb.TestAllTypes))
+			case largeFieldF5:
+				m.SetF5(v.(*testpb.TestAllTypes))
+			case largeFieldF6:
+				m.SetF6(v.(*testpb.TestAllTypes))
+			case largeFieldF7:
+				m.SetF7(v.(*testpb.TestAllTypes))
+			case largeFieldF8:
+				m.SetF8(v.(*testpb.TestAllTypes))
+			case largeFieldF9:
+				m.SetF9(v.(*testpb.TestAllTypes))
+			case largeFieldF10:
+				m.SetF10(v.(*testpb.TestAllTypes))
+			case largeFieldF11:
+				m.SetF11(v.(*testpb.TestAllTypes))
+			case largeFieldF12:
+				m.SetF12(v.(*testpb.TestAllTypes))
+			case largeFieldF13:
+				m.SetF13(v.(*testpb.TestAllTypes))
+			case largeFieldF14:
+				m.SetF14(v.(*testpb.TestAllTypes))
+			case largeFieldF15:
+				m.SetF15(v.(*testpb.TestAllTypes))
+			case largeFieldF16:
+				m.SetF16(v.(*testpb.TestAllTypes))
+			case largeFieldF17:
+				m.SetF17(v.(*testpb.TestAllTypes))
+			case largeFieldF18:
+				m.SetF18(v.(*testpb.TestAllTypes))
+			case largeFieldF19:
+				m.SetF19(v.(*testpb.TestAllTypes))
+			case largeFieldF20:
+				m.SetF20(v.(*testpb.TestAllTypes))
+			case largeFieldF21:
+				m.SetF21(v.(*testpb.TestAllTypes))
+			case largeFieldF22:
+				m.SetF22(v.(*testpb.TestAllTypes))
+			case largeFieldF23:
+				m.SetF23(v.(*testpb.TestAllTypes))
+			case largeFieldF24:
+				m.SetF24(v.(*testpb.TestAllTypes))
+			case largeFieldF25:
+				m.SetF25(v.(*testpb.TestAllTypes))
+			case largeFieldF26:
+				m.SetF26(v.(*testpb.TestAllTypes))
+			case largeFieldF27:
+				m.SetF27(v.(*testpb.TestAllTypes))
+			case largeFieldF28:
+				m.SetF28(v.(*testpb.TestAllTypes))
+			case largeFieldF29:
+				m.SetF29(v.(*testpb.TestAllTypes))
+			case largeFieldF30:
+				m.SetF30(v.(*testpb.TestAllTypes))
+			case largeFieldF31:
+				m.SetF31(v.(*testpb.TestAllTypes))
+			case largeFieldF32:
+				m.SetF32(v.(*testpb.TestAllTypes))
+			case largeFieldF33:
+				m.SetF33(v.(*testpb.TestAllTypes))
+			case largeFieldF34:
+				m.SetF34(v.(*testpb.TestAllTypes))
+			case largeFieldF35:
+				m.SetF35(v.(*testpb.TestAllTypes))
+			case largeFieldF36:
+				m.SetF36(v.(*testpb.TestAllTypes))
+			case largeFieldF37:
+				m.SetF37(v.(*testpb.TestAllTypes))
+			case largeFieldF38:
+				m.SetF38(v.(*testpb.TestAllTypes))
+			case largeFieldF39:
+				m.SetF39(v.(*testpb.TestAllTypes))
+			case largeFieldF40:
+				m.SetF40(v.(*testpb.TestAllTypes))
+			case largeFieldF41:
+				m.SetF41(v.(*testpb.TestAllTypes))
+			case largeFieldF42:
+				m.SetF42(v.(*testpb.TestAllTypes))
+			case largeFieldF43:
+				m.SetF43(v.(*testpb.TestAllTypes))
+			case largeFieldF44:
+				m.SetF44(v.(*testpb.TestAllTypes))
+			case largeFieldF45:
+				m.SetF45(v.(*testpb.TestAllTypes))
+			case largeFieldF46:
+				m.SetF46(v.(*testpb.TestAllTypes))
+			case largeFieldF47:
+				m.SetF47(v.(*testpb.TestAllTypes))
+			case largeFieldF48:
+				m.SetF48(v.(*testpb.TestAllTypes))
+			case largeFieldF49:
+				m.SetF49(v.(*testpb.TestAllTypes))
+			case largeFieldF50:
+				m.SetF50(v.(*testpb.TestAllTypes))
+			case largeFieldF51:
+				m.SetF51(v.(*testpb.TestAllTypes))
+			case largeFieldF52:
+				m.SetF52(v.(*testpb.TestAllTypes))
+			case largeFieldF53:
+				m.SetF53(v.(*testpb.TestAllTypes))
+			case largeFieldF54:
+				m.SetF54(v.(*testpb.TestAllTypes))
+			case largeFieldF55:
+				m.SetF55(v.(*testpb.TestAllTypes))
+			case largeFieldF56:
+				m.SetF56(v.(*testpb.TestAllTypes))
+			case largeFieldF57:
+				m.SetF57(v.(*testpb.TestAllTypes))
+			case largeFieldF58:
+				m.SetF58(v.(*testpb.TestAllTypes))
+			case largeFieldF59:
+				m.SetF59(v.(*testpb.TestAllTypes))
+			case largeFieldF60:
+				m.SetF60(v.(*testpb.TestAllTypes))
+			case largeFieldF61:
+				m.SetF61(v.(*testpb.TestAllTypes))
+			case largeFieldF62:
+				m.SetF62(v.(*testpb.TestAllTypes))
+			case largeFieldF63:
+				m.SetF63(v.(*testpb.TestAllTypes))
+			case largeFieldF64:
+				m.SetF64(v.(*testpb.TestAllTypes))
+			case largeFieldF65:
+				m.SetF65(v.(*testpb.TestAllTypes))
+			case largeFieldF66:
+				m.SetF66(v.(*testpb.TestAllTypes))
+			case largeFieldF67:
+				m.SetF67(v.(*testpb.TestAllTypes))
+			case largeFieldF68:
+				m.SetF68(v.(*testpb.TestAllTypes))
+			case largeFieldF69:
+				m.SetF69(v.(*testpb.TestAllTypes))
+			case largeFieldF70:
+				m.SetF70(v.(*testpb.TestAllTypes))
+			case largeFieldF71:
+				m.SetF71(v.(*testpb.TestAllTypes))
+			case largeFieldF72:
+				m.SetF72(v.(*testpb.TestAllTypes))
+			case largeFieldF73:
+				m.SetF73(v.(*testpb.TestAllTypes))
+			case largeFieldF74:
+				m.SetF74(v.(*testpb.TestAllTypes))
+			case largeFieldF75:
+				m.SetF75(v.(*testpb.TestAllTypes))
+			case largeFieldF76:
+				m.SetF76(v.(*testpb.TestAllTypes))
+			case largeFieldF77:
+				m.SetF77(v.(*testpb.TestAllTypes))
+			case largeFieldF78:
+				m.SetF78(v.(*testpb.TestAllTypes))
+			case largeFieldF79:
+				m.SetF79(v.(*testpb.TestAllTypes))
+			case largeFieldF80:
+				m.SetF80(v.(*testpb.TestAllTypes))
+			case largeFieldF81:
+				m.SetF81(v.(*testpb.TestAllTypes))
+			case largeFieldF82:
+				m.SetF82(v.(*testpb.TestAllTypes))
+			case largeFieldF83:
+				m.SetF83(v.(*testpb.TestAllTypes))
+			case largeFieldF84:
+				m.SetF84(v.(*testpb.TestAllTypes))
+			case largeFieldF85:
+				m.SetF85(v.(*testpb.TestAllTypes))
+			case largeFieldF86:
+				m.SetF86(v.(*testpb.TestAllTypes))
+			case largeFieldF87:
+				m.SetF87(v.(*testpb.TestAllTypes))
+			case largeFieldF88:
+				m.SetF88(v.(*testpb.TestAllTypes))
+			case largeFieldF89:
+				m.SetF89(v.(*testpb.TestAllTypes))
+			case largeFieldF90:
+				m.SetF90(v.(*testpb.TestAllTypes))
+			case largeFieldF91:
+				m.SetF91(v.(*testpb.TestAllTypes))
+			case largeFieldF92:
+				m.SetF92(v.(*testpb.TestAllTypes))
+			case largeFieldF93:
+				m.SetF93(v.(*testpb.TestAllTypes))
+			case largeFieldF94:
+				m.SetF94(v.(*testpb.TestAllTypes))
+			case largeFieldF95:
+				m.SetF95(v.(*testpb.TestAllTypes))
+			case largeFieldF96:
+				m.SetF96(v.(*testpb.TestAllTypes))
+			case largeFieldF97:
+				m.SetF97(v.(*testpb.TestAllTypes))
+			case largeFieldF98:
+				m.SetF98(v.(*testpb.TestAllTypes))
+			case largeFieldF99:
+				m.SetF99(v.(*testpb.TestAllTypes))
+			case largeFieldF100:
+				m.SetF100(v.(*testpb.TestAllTypes))
+
+			default:
+				panic(fmt.Sprintf("set: unknown field %d", num))
+			}
+		},
+		clear: func(num protoreflect.FieldNumber) {
+			switch num {
+			case largeFieldF1:
+				m.ClearF1()
+			case largeFieldF2:
+				m.ClearF2()
+			case largeFieldF3:
+				m.ClearF3()
+			case largeFieldF4:
+				m.ClearF4()
+			case largeFieldF5:
+				m.ClearF5()
+			case largeFieldF6:
+				m.ClearF6()
+			case largeFieldF7:
+				m.ClearF7()
+			case largeFieldF8:
+				m.ClearF8()
+			case largeFieldF9:
+				m.ClearF9()
+			case largeFieldF10:
+				m.ClearF10()
+			case largeFieldF11:
+				m.ClearF11()
+			case largeFieldF12:
+				m.ClearF12()
+			case largeFieldF13:
+				m.ClearF13()
+			case largeFieldF14:
+				m.ClearF14()
+			case largeFieldF15:
+				m.ClearF15()
+			case largeFieldF16:
+				m.ClearF16()
+			case largeFieldF17:
+				m.ClearF17()
+			case largeFieldF18:
+				m.ClearF18()
+			case largeFieldF19:
+				m.ClearF19()
+			case largeFieldF20:
+				m.ClearF20()
+			case largeFieldF21:
+				m.ClearF21()
+			case largeFieldF22:
+				m.ClearF22()
+			case largeFieldF23:
+				m.ClearF23()
+			case largeFieldF24:
+				m.ClearF24()
+			case largeFieldF25:
+				m.ClearF25()
+			case largeFieldF26:
+				m.ClearF26()
+			case largeFieldF27:
+				m.ClearF27()
+			case largeFieldF28:
+				m.ClearF28()
+			case largeFieldF29:
+				m.ClearF29()
+			case largeFieldF30:
+				m.ClearF30()
+			case largeFieldF31:
+				m.ClearF31()
+			case largeFieldF32:
+				m.ClearF32()
+			case largeFieldF33:
+				m.ClearF33()
+			case largeFieldF34:
+				m.ClearF34()
+			case largeFieldF35:
+				m.ClearF35()
+			case largeFieldF36:
+				m.ClearF36()
+			case largeFieldF37:
+				m.ClearF37()
+			case largeFieldF38:
+				m.ClearF38()
+			case largeFieldF39:
+				m.ClearF39()
+			case largeFieldF40:
+				m.ClearF40()
+			case largeFieldF41:
+				m.ClearF41()
+			case largeFieldF42:
+				m.ClearF42()
+			case largeFieldF43:
+				m.ClearF43()
+			case largeFieldF44:
+				m.ClearF44()
+			case largeFieldF45:
+				m.ClearF45()
+			case largeFieldF46:
+				m.ClearF46()
+			case largeFieldF47:
+				m.ClearF47()
+			case largeFieldF48:
+				m.ClearF48()
+			case largeFieldF49:
+				m.ClearF49()
+			case largeFieldF50:
+				m.ClearF50()
+			case largeFieldF51:
+				m.ClearF51()
+			case largeFieldF52:
+				m.ClearF52()
+			case largeFieldF53:
+				m.ClearF53()
+			case largeFieldF54:
+				m.ClearF54()
+			case largeFieldF55:
+				m.ClearF55()
+			case largeFieldF56:
+				m.ClearF56()
+			case largeFieldF57:
+				m.ClearF57()
+			case largeFieldF58:
+				m.ClearF58()
+			case largeFieldF59:
+				m.ClearF59()
+			case largeFieldF60:
+				m.ClearF60()
+			case largeFieldF60:
+				m.ClearF60()
+			case largeFieldF61:
+				m.ClearF61()
+			case largeFieldF62:
+				m.ClearF62()
+			case largeFieldF63:
+				m.ClearF63()
+			case largeFieldF64:
+				m.ClearF64()
+			case largeFieldF65:
+				m.ClearF65()
+			case largeFieldF66:
+				m.ClearF66()
+			case largeFieldF67:
+				m.ClearF67()
+			case largeFieldF68:
+				m.ClearF68()
+			case largeFieldF69:
+				m.ClearF69()
+			case largeFieldF70:
+				m.ClearF70()
+			case largeFieldF71:
+				m.ClearF71()
+			case largeFieldF72:
+				m.ClearF72()
+			case largeFieldF73:
+				m.ClearF73()
+			case largeFieldF74:
+				m.ClearF74()
+			case largeFieldF75:
+				m.ClearF75()
+			case largeFieldF76:
+				m.ClearF76()
+			case largeFieldF77:
+				m.ClearF77()
+			case largeFieldF78:
+				m.ClearF78()
+			case largeFieldF79:
+				m.ClearF79()
+			case largeFieldF80:
+				m.ClearF80()
+			case largeFieldF81:
+				m.ClearF81()
+			case largeFieldF82:
+				m.ClearF82()
+			case largeFieldF83:
+				m.ClearF83()
+			case largeFieldF84:
+				m.ClearF84()
+			case largeFieldF85:
+				m.ClearF85()
+			case largeFieldF86:
+				m.ClearF86()
+			case largeFieldF87:
+				m.ClearF87()
+			case largeFieldF88:
+				m.ClearF88()
+			case largeFieldF89:
+				m.ClearF89()
+			case largeFieldF90:
+				m.ClearF90()
+			case largeFieldF91:
+				m.ClearF91()
+			case largeFieldF92:
+				m.ClearF92()
+			case largeFieldF93:
+				m.ClearF93()
+			case largeFieldF94:
+				m.ClearF94()
+			case largeFieldF95:
+				m.ClearF95()
+			case largeFieldF96:
+				m.ClearF96()
+			case largeFieldF97:
+				m.ClearF97()
+			case largeFieldF98:
+				m.ClearF98()
+			case largeFieldF99:
+				m.ClearF99()
+			case largeFieldF100:
+				m.ClearF100()
+
+			default:
+				panic(fmt.Sprintf("clear: unknown field %d", num))
+			}
+		},
+	}
+}
diff --git a/internal/reflection_test/reflection_opaque_test.go b/internal/reflection_test/reflection_opaque_test.go
new file mode 100644
index 0000000..7bfaa0d
--- /dev/null
+++ b/internal/reflection_test/reflection_opaque_test.go
@@ -0,0 +1,1045 @@
+// 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 reflection_test
+
+import (
+	"fmt"
+	"math"
+	"testing"
+
+	testpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/prototest"
+)
+
+var enableLazy = proto.UnmarshalOptions{}
+var disableLazy = proto.UnmarshalOptions{
+	NoLazyDecoding: true,
+}
+
+var lazyCombinations = []struct {
+	desc string
+	ptm  prototest.Message
+}{
+	{
+		desc: "lazy decoding",
+		ptm: prototest.Message{
+			UnmarshalOptions: enableLazy,
+		},
+	},
+
+	{
+		desc: "no lazy decoding",
+		ptm: prototest.Message{
+			UnmarshalOptions: disableLazy,
+		},
+	},
+}
+
+func TestOpaqueConcrete(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newTestMessageOpaque(nil).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestOpaqueReflection(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, (*testpb.TestAllTypes)(nil).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestOpaqueShadow_GetConcrete_SetReflection(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+				m := &testpb.TestAllTypes{}
+				return newTestMessageOpaque(m), m
+			}).ProtoReflect().Type())
+		})
+	}
+}
+
+func TestOpaqueShadow_GetReflection_SetConcrete(t *testing.T) {
+	for _, tt := range lazyCombinations {
+		t.Run(tt.desc, func(t *testing.T) {
+			tt.ptm.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+				m := &testpb.TestAllTypes{}
+				return m, newTestMessageOpaque(m)
+			}).ProtoReflect().Type())
+		})
+	}
+}
+
+func newTestMessageOpaque(m *testpb.TestAllTypes) protoreflect.ProtoMessage {
+	return &testProtoMessage{
+		m:  m,
+		md: m.ProtoReflect().Descriptor(),
+		new: func() protoreflect.Message {
+			return newTestMessageOpaque(&testpb.TestAllTypes{}).ProtoReflect()
+		},
+		has: func(num protoreflect.FieldNumber) bool {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32() != 0
+			case fieldSingularInt64:
+				return m.GetSingularInt64() != 0
+			case fieldSingularUint32:
+				return m.GetSingularUint32() != 0
+			case fieldSingularUint64:
+				return m.GetSingularUint64() != 0
+			case fieldSingularSint32:
+				return m.GetSingularSint32() != 0
+			case fieldSingularSint64:
+				return m.GetSingularSint64() != 0
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32() != 0
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64() != 0
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32() != 0
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64() != 0
+			case fieldSingularFloat:
+				return m.GetSingularFloat() != 0 || math.Signbit(float64(m.GetSingularFloat()))
+			case fieldSingularDouble:
+				return m.GetSingularDouble() != 0 || math.Signbit(m.GetSingularDouble())
+			case fieldSingularBool:
+				return m.GetSingularBool() != false
+			case fieldSingularString:
+				return m.GetSingularString() != ""
+			case fieldSingularBytes:
+				return len(m.GetSingularBytes()) != 0
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum() != testpb.TestAllTypes_FOO
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum() != testpb.ForeignEnum_FOREIGN_ZERO
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum() != testpb.ImportEnum_IMPORT_ZERO
+
+			case fieldOptionalInt32:
+				return m.HasOptionalInt32()
+			case fieldOptionalInt64:
+				return m.HasOptionalInt64()
+			case fieldOptionalUint32:
+				return m.HasOptionalUint32()
+			case fieldOptionalUint64:
+				return m.HasOptionalUint64()
+			case fieldOptionalSint32:
+				return m.HasOptionalSint32()
+			case fieldOptionalSint64:
+				return m.HasOptionalSint64()
+			case fieldOptionalFixed32:
+				return m.HasOptionalFixed32()
+			case fieldOptionalFixed64:
+				return m.HasOptionalFixed64()
+			case fieldOptionalSfixed32:
+				return m.HasOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				return m.HasOptionalSfixed64()
+			case fieldOptionalFloat:
+				return m.HasOptionalFloat()
+			case fieldOptionalDouble:
+				return m.HasOptionalDouble()
+			case fieldOptionalBool:
+				return m.HasOptionalBool()
+			case fieldOptionalString:
+				return m.HasOptionalString()
+			case fieldOptionalBytes:
+				return m.HasOptionalBytes()
+			case fieldOptionalGroup:
+				return m.HasOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				return m.HasNotGroupLikeDelimited()
+			case fieldOptionalGroup:
+				return m.HasOptionalgroup()
+			case fieldOptionalNestedMessage:
+				return m.HasOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				return m.HasOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				return m.HasOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				return m.HasOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				return m.HasOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				return m.HasOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				return m.HasOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				return len(m.GetRepeatedInt32()) > 0
+			case fieldRepeatedInt64:
+				return len(m.GetRepeatedInt64()) > 0
+			case fieldRepeatedUint32:
+				return len(m.GetRepeatedUint32()) > 0
+			case fieldRepeatedUint64:
+				return len(m.GetRepeatedUint64()) > 0
+			case fieldRepeatedSint32:
+				return len(m.GetRepeatedSint32()) > 0
+			case fieldRepeatedSint64:
+				return len(m.GetRepeatedSint64()) > 0
+			case fieldRepeatedFixed32:
+				return len(m.GetRepeatedFixed32()) > 0
+			case fieldRepeatedFixed64:
+				return len(m.GetRepeatedFixed64()) > 0
+			case fieldRepeatedSfixed32:
+				return len(m.GetRepeatedSfixed32()) > 0
+			case fieldRepeatedSfixed64:
+				return len(m.GetRepeatedSfixed64()) > 0
+			case fieldRepeatedFloat:
+				return len(m.GetRepeatedFloat()) > 0
+			case fieldRepeatedDouble:
+				return len(m.GetRepeatedDouble()) > 0
+			case fieldRepeatedBool:
+				return len(m.GetRepeatedBool()) > 0
+			case fieldRepeatedString:
+				return len(m.GetRepeatedString()) > 0
+			case fieldRepeatedBytes:
+				return len(m.GetRepeatedBytes()) > 0
+			case fieldRepeatedGroup:
+				return len(m.GetRepeatedgroup()) > 0
+			case fieldRepeatedNestedMessage:
+				return len(m.GetRepeatedNestedMessage()) > 0
+			case fieldRepeatedForeignMessage:
+				return len(m.GetRepeatedForeignMessage()) > 0
+			case fieldRepeatedImportMessage:
+				return len(m.GetRepeatedImportmessage()) > 0
+			case fieldRepeatedNestedEnum:
+				return len(m.GetRepeatedNestedEnum()) > 0
+			case fieldRepeatedForeignEnum:
+				return len(m.GetRepeatedForeignEnum()) > 0
+			case fieldRepeatedImportEnum:
+				return len(m.GetRepeatedImportenum()) > 0
+
+			case fieldMapInt32Int32:
+				return len(m.GetMapInt32Int32()) > 0
+			case fieldMapInt64Int64:
+				return len(m.GetMapInt64Int64()) > 0
+			case fieldMapUint32Uint32:
+				return len(m.GetMapUint32Uint32()) > 0
+			case fieldMapUint64Uint64:
+				return len(m.GetMapUint64Uint64()) > 0
+			case fieldMapSint32Sint32:
+				return len(m.GetMapSint32Sint32()) > 0
+			case fieldMapSint64Sint64:
+				return len(m.GetMapSint64Sint64()) > 0
+			case fieldMapFixed32Fixed32:
+				return len(m.GetMapFixed32Fixed32()) > 0
+			case fieldMapFixed64Fixed64:
+				return len(m.GetMapFixed64Fixed64()) > 0
+			case fieldMapSfixed32Sfixed32:
+				return len(m.GetMapSfixed32Sfixed32()) > 0
+			case fieldMapSfixed64Sfixed64:
+				return len(m.GetMapSfixed64Sfixed64()) > 0
+			case fieldMapInt32Float:
+				return len(m.GetMapInt32Float()) > 0
+			case fieldMapInt32Double:
+				return len(m.GetMapInt32Double()) > 0
+			case fieldMapBoolBool:
+				return len(m.GetMapBoolBool()) > 0
+			case fieldMapStringString:
+				return len(m.GetMapStringString()) > 0
+			case fieldMapStringBytes:
+				return len(m.GetMapStringBytes()) > 0
+			case fieldMapStringNestedMessage:
+				return len(m.GetMapStringNestedMessage()) > 0
+			case fieldMapStringNestedEnum:
+				return len(m.GetMapStringNestedEnum()) > 0
+
+			case fieldDefaultInt32:
+				return m.HasDefaultInt32()
+			case fieldDefaultInt64:
+				return m.HasDefaultInt64()
+			case fieldDefaultUint32:
+				return m.HasDefaultUint32()
+			case fieldDefaultUint64:
+				return m.HasDefaultUint64()
+			case fieldDefaultSint32:
+				return m.HasDefaultSint32()
+			case fieldDefaultSint64:
+				return m.HasDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.HasDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.HasDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.HasDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.HasDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.HasDefaultFloat()
+			case fieldDefaultDouble:
+				return m.HasDefaultDouble()
+			case fieldDefaultBool:
+				return m.HasDefaultBool()
+			case fieldDefaultString:
+				return m.HasDefaultString()
+			case fieldDefaultBytes:
+				return m.HasDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.HasDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.HasDefaultForeignEnum()
+
+			case fieldDefaultInt32:
+				return m.HasDefaultInt32()
+			case fieldDefaultInt64:
+				return m.HasDefaultInt64()
+			case fieldDefaultUint32:
+				return m.HasDefaultUint32()
+			case fieldDefaultUint64:
+				return m.HasDefaultUint64()
+			case fieldDefaultSint32:
+				return m.HasDefaultSint32()
+			case fieldDefaultSint64:
+				return m.HasDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.HasDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.HasDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.HasDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.HasDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.HasDefaultFloat()
+			case fieldDefaultDouble:
+				return m.HasDefaultDouble()
+			case fieldDefaultBool:
+				return m.HasDefaultBool()
+			case fieldDefaultString:
+				return m.HasDefaultString()
+			case fieldDefaultBytes:
+				return m.HasDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.HasDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.HasDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				return m.HasOneofUint32()
+			case fieldOneofNestedMessage:
+				return m.HasOneofNestedMessage()
+			case fieldOneofString:
+				return m.HasOneofString()
+			case fieldOneofBytes:
+				return m.HasOneofBytes()
+			case fieldOneofBool:
+				return m.HasOneofBool()
+			case fieldOneofUint64:
+				return m.HasOneofUint64()
+			case fieldOneofFloat:
+				return m.HasOneofFloat()
+			case fieldOneofDouble:
+				return m.HasOneofDouble()
+			case fieldOneofEnum:
+				return m.HasOneofEnum()
+			case fieldOneofGroup:
+				return m.HasOneofgroup()
+			case fieldOneofOptionalUint32:
+				return m.HasOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("has: unknown field %d", num))
+			}
+		},
+		get: func(num protoreflect.FieldNumber) any {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32()
+			case fieldSingularInt64:
+				return m.GetSingularInt64()
+			case fieldSingularUint32:
+				return m.GetSingularUint32()
+			case fieldSingularUint64:
+				return m.GetSingularUint64()
+			case fieldSingularSint32:
+				return m.GetSingularSint32()
+			case fieldSingularSint64:
+				return m.GetSingularSint64()
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32()
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64()
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32()
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64()
+			case fieldSingularFloat:
+				return m.GetSingularFloat()
+			case fieldSingularDouble:
+				return m.GetSingularDouble()
+			case fieldSingularBool:
+				return m.GetSingularBool()
+			case fieldSingularString:
+				return m.GetSingularString()
+			case fieldSingularBytes:
+				return m.GetSingularBytes()
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum()
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum()
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum()
+
+			case fieldOptionalInt32:
+				return m.GetOptionalInt32()
+			case fieldOptionalInt64:
+				return m.GetOptionalInt64()
+			case fieldOptionalUint32:
+				return m.GetOptionalUint32()
+			case fieldOptionalUint64:
+				return m.GetOptionalUint64()
+			case fieldOptionalSint32:
+				return m.GetOptionalSint32()
+			case fieldOptionalSint64:
+				return m.GetOptionalSint64()
+			case fieldOptionalFixed32:
+				return m.GetOptionalFixed32()
+			case fieldOptionalFixed64:
+				return m.GetOptionalFixed64()
+			case fieldOptionalSfixed32:
+				return m.GetOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				return m.GetOptionalSfixed64()
+			case fieldOptionalFloat:
+				return m.GetOptionalFloat()
+			case fieldOptionalDouble:
+				return m.GetOptionalDouble()
+			case fieldOptionalBool:
+				return m.GetOptionalBool()
+			case fieldOptionalString:
+				return m.GetOptionalString()
+			case fieldOptionalBytes:
+				return m.GetOptionalBytes()
+			case fieldOptionalGroup:
+				return m.GetOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				return m.GetNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				return m.GetOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				return m.GetOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				return m.GetOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				return m.GetOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				return m.GetOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				return m.GetOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				return m.GetOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				return m.GetRepeatedInt32()
+			case fieldRepeatedInt64:
+				return m.GetRepeatedInt64()
+			case fieldRepeatedUint32:
+				return m.GetRepeatedUint32()
+			case fieldRepeatedUint64:
+				return m.GetRepeatedUint64()
+			case fieldRepeatedSint32:
+				return m.GetRepeatedSint32()
+			case fieldRepeatedSint64:
+				return m.GetRepeatedSint64()
+			case fieldRepeatedFixed32:
+				return m.GetRepeatedFixed32()
+			case fieldRepeatedFixed64:
+				return m.GetRepeatedFixed64()
+			case fieldRepeatedSfixed32:
+				return m.GetRepeatedSfixed32()
+			case fieldRepeatedSfixed64:
+				return m.GetRepeatedSfixed64()
+			case fieldRepeatedFloat:
+				return m.GetRepeatedFloat()
+			case fieldRepeatedDouble:
+				return m.GetRepeatedDouble()
+			case fieldRepeatedBool:
+				return m.GetRepeatedBool()
+			case fieldRepeatedString:
+				return m.GetRepeatedString()
+			case fieldRepeatedBytes:
+				return m.GetRepeatedBytes()
+			case fieldRepeatedGroup:
+				return m.GetRepeatedgroup()
+			case fieldRepeatedNestedMessage:
+				return m.GetRepeatedNestedMessage()
+			case fieldRepeatedForeignMessage:
+				return m.GetRepeatedForeignMessage()
+			case fieldRepeatedImportMessage:
+				return m.GetRepeatedImportmessage()
+			case fieldRepeatedNestedEnum:
+				return m.GetRepeatedNestedEnum()
+			case fieldRepeatedForeignEnum:
+				return m.GetRepeatedForeignEnum()
+			case fieldRepeatedImportEnum:
+				return m.GetRepeatedImportenum()
+
+			case fieldMapInt32Int32:
+				return m.GetMapInt32Int32()
+			case fieldMapInt64Int64:
+				return m.GetMapInt64Int64()
+			case fieldMapUint32Uint32:
+				return m.GetMapUint32Uint32()
+			case fieldMapUint64Uint64:
+				return m.GetMapUint64Uint64()
+			case fieldMapSint32Sint32:
+				return m.GetMapSint32Sint32()
+			case fieldMapSint64Sint64:
+				return m.GetMapSint64Sint64()
+			case fieldMapFixed32Fixed32:
+				return m.GetMapFixed32Fixed32()
+			case fieldMapFixed64Fixed64:
+				return m.GetMapFixed64Fixed64()
+			case fieldMapSfixed32Sfixed32:
+				return m.GetMapSfixed32Sfixed32()
+			case fieldMapSfixed64Sfixed64:
+				return m.GetMapSfixed64Sfixed64()
+			case fieldMapInt32Float:
+				return m.GetMapInt32Float()
+			case fieldMapInt32Double:
+				return m.GetMapInt32Double()
+			case fieldMapBoolBool:
+				return m.GetMapBoolBool()
+			case fieldMapStringString:
+				return m.GetMapStringString()
+			case fieldMapStringBytes:
+				return m.GetMapStringBytes()
+			case fieldMapStringNestedMessage:
+				return m.GetMapStringNestedMessage()
+			case fieldMapStringNestedEnum:
+				return m.GetMapStringNestedEnum()
+
+			case fieldDefaultInt32:
+				return m.GetDefaultInt32()
+			case fieldDefaultInt64:
+				return m.GetDefaultInt64()
+			case fieldDefaultUint32:
+				return m.GetDefaultUint32()
+			case fieldDefaultUint64:
+				return m.GetDefaultUint64()
+			case fieldDefaultSint32:
+				return m.GetDefaultSint32()
+			case fieldDefaultSint64:
+				return m.GetDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.GetDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.GetDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.GetDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.GetDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.GetDefaultFloat()
+			case fieldDefaultDouble:
+				return m.GetDefaultDouble()
+			case fieldDefaultBool:
+				return m.GetDefaultBool()
+			case fieldDefaultString:
+				return m.GetDefaultString()
+			case fieldDefaultBytes:
+				return m.GetDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.GetDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.GetDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				return m.GetOneofUint32()
+			case fieldOneofNestedMessage:
+				return m.GetOneofNestedMessage()
+			case fieldOneofString:
+				return m.GetOneofString()
+			case fieldOneofBytes:
+				return m.GetOneofBytes()
+			case fieldOneofBool:
+				return m.GetOneofBool()
+			case fieldOneofUint64:
+				return m.GetOneofUint64()
+			case fieldOneofFloat:
+				return m.GetOneofFloat()
+			case fieldOneofDouble:
+				return m.GetOneofDouble()
+			case fieldOneofEnum:
+				return m.GetOneofEnum()
+			case fieldOneofGroup:
+				return m.GetOneofgroup()
+			case fieldOneofOptionalUint32:
+				return m.GetOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("get: unknown field %d", num))
+			}
+		},
+		set: func(num protoreflect.FieldNumber, v any) {
+			switch num {
+			case fieldSingularInt32:
+				m.SetSingularInt32(v.(int32))
+			case fieldSingularInt64:
+				m.SetSingularInt64(v.(int64))
+			case fieldSingularUint32:
+				m.SetSingularUint32(v.(uint32))
+			case fieldSingularUint64:
+				m.SetSingularUint64(v.(uint64))
+			case fieldSingularSint32:
+				m.SetSingularSint32(v.(int32))
+			case fieldSingularSint64:
+				m.SetSingularSint64(v.(int64))
+			case fieldSingularFixed32:
+				m.SetSingularFixed32(v.(uint32))
+			case fieldSingularFixed64:
+				m.SetSingularFixed64(v.(uint64))
+			case fieldSingularSfixed32:
+				m.SetSingularSfixed32(v.(int32))
+			case fieldSingularSfixed64:
+				m.SetSingularSfixed64(v.(int64))
+			case fieldSingularFloat:
+				m.SetSingularFloat(v.(float32))
+			case fieldSingularDouble:
+				m.SetSingularDouble(v.(float64))
+			case fieldSingularBool:
+				m.SetSingularBool(v.(bool))
+			case fieldSingularString:
+				m.SetSingularString(v.(string))
+			case fieldSingularBytes:
+				m.SetSingularBytes(v.([]byte))
+			case fieldSingularNestedEnum:
+				m.SetSingularNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldSingularForeignEnum:
+				m.SetSingularForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+			case fieldSingularImportEnum:
+				m.SetSingularImportEnum(testpb.ImportEnum(v.(protoreflect.EnumNumber)))
+
+			case fieldOptionalInt32:
+				m.SetOptionalInt32(v.(int32))
+			case fieldOptionalInt64:
+				m.SetOptionalInt64(v.(int64))
+			case fieldOptionalUint32:
+				m.SetOptionalUint32(v.(uint32))
+			case fieldOptionalUint64:
+				m.SetOptionalUint64(v.(uint64))
+			case fieldOptionalSint32:
+				m.SetOptionalSint32(v.(int32))
+			case fieldOptionalSint64:
+				m.SetOptionalSint64(v.(int64))
+			case fieldOptionalFixed32:
+				m.SetOptionalFixed32(v.(uint32))
+			case fieldOptionalFixed64:
+				m.SetOptionalFixed64(v.(uint64))
+			case fieldOptionalSfixed32:
+				m.SetOptionalSfixed32(v.(int32))
+			case fieldOptionalSfixed64:
+				m.SetOptionalSfixed64(v.(int64))
+			case fieldOptionalFloat:
+				m.SetOptionalFloat(v.(float32))
+			case fieldOptionalDouble:
+				m.SetOptionalDouble(v.(float64))
+			case fieldOptionalBool:
+				m.SetOptionalBool(v.(bool))
+			case fieldOptionalString:
+				m.SetOptionalString(v.(string))
+			case fieldOptionalBytes:
+				m.SetOptionalBytes(v.([]byte))
+			case fieldOptionalGroup:
+				m.SetOptionalgroup(v.(*testpb.TestAllTypes_OptionalGroup))
+			case fieldNotGroupLikeDelimited:
+				m.SetNotGroupLikeDelimited(v.(*testpb.TestAllTypes_OptionalGroup))
+			case fieldOptionalNestedMessage:
+				m.SetOptionalNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+			case fieldOptionalForeignMessage:
+				m.SetOptionalForeignMessage(v.(*testpb.ForeignMessage))
+			case fieldOptionalImportMessage:
+				m.SetOptionalImportMessage(v.(*testpb.ImportMessage))
+			case fieldOptionalNestedEnum:
+				m.SetOptionalNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalForeignEnum:
+				m.SetOptionalForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalImportEnum:
+				m.SetOptionalImportEnum(testpb.ImportEnum(v.(protoreflect.EnumNumber)))
+			case fieldOptionalLazyNestedMessage:
+				m.SetOptionalLazyNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+
+			case fieldRepeatedInt32:
+				m.SetRepeatedInt32(v.([]int32))
+			case fieldRepeatedInt64:
+				m.SetRepeatedInt64(v.([]int64))
+			case fieldRepeatedUint32:
+				m.SetRepeatedUint32(v.([]uint32))
+			case fieldRepeatedUint64:
+				m.SetRepeatedUint64(v.([]uint64))
+			case fieldRepeatedSint32:
+				m.SetRepeatedSint32(v.([]int32))
+			case fieldRepeatedSint64:
+				m.SetRepeatedSint64(v.([]int64))
+			case fieldRepeatedFixed32:
+				m.SetRepeatedFixed32(v.([]uint32))
+			case fieldRepeatedFixed64:
+				m.SetRepeatedFixed64(v.([]uint64))
+			case fieldRepeatedSfixed32:
+				m.SetRepeatedSfixed32(v.([]int32))
+			case fieldRepeatedSfixed64:
+				m.SetRepeatedSfixed64(v.([]int64))
+			case fieldRepeatedFloat:
+				m.SetRepeatedFloat(v.([]float32))
+			case fieldRepeatedDouble:
+				m.SetRepeatedDouble(v.([]float64))
+			case fieldRepeatedBool:
+				m.SetRepeatedBool(v.([]bool))
+			case fieldRepeatedString:
+				m.SetRepeatedString(v.([]string))
+			case fieldRepeatedBytes:
+				m.SetRepeatedBytes(v.([][]byte))
+			case fieldRepeatedGroup:
+				m.SetRepeatedgroup(v.([]*testpb.TestAllTypes_RepeatedGroup))
+			case fieldRepeatedNestedMessage:
+				m.SetRepeatedNestedMessage(v.([]*testpb.TestAllTypes_NestedMessage))
+			case fieldRepeatedForeignMessage:
+				m.SetRepeatedForeignMessage(v.([]*testpb.ForeignMessage))
+			case fieldRepeatedImportMessage:
+				m.SetRepeatedImportmessage(v.([]*testpb.ImportMessage))
+			case fieldRepeatedNestedEnum:
+				m.SetRepeatedNestedEnum(v.([]testpb.TestAllTypes_NestedEnum))
+			case fieldRepeatedForeignEnum:
+				m.SetRepeatedForeignEnum(v.([]testpb.ForeignEnum))
+			case fieldRepeatedImportEnum:
+				m.SetRepeatedImportenum(v.([]testpb.ImportEnum))
+
+			case fieldMapInt32Int32:
+				m.SetMapInt32Int32(v.(map[int32]int32))
+			case fieldMapInt64Int64:
+				m.SetMapInt64Int64(v.(map[int64]int64))
+			case fieldMapUint32Uint32:
+				m.SetMapUint32Uint32(v.(map[uint32]uint32))
+			case fieldMapUint64Uint64:
+				m.SetMapUint64Uint64(v.(map[uint64]uint64))
+			case fieldMapSint32Sint32:
+				m.SetMapSint32Sint32(v.(map[int32]int32))
+			case fieldMapSint64Sint64:
+				m.SetMapSint64Sint64(v.(map[int64]int64))
+			case fieldMapFixed32Fixed32:
+				m.SetMapFixed32Fixed32(v.(map[uint32]uint32))
+			case fieldMapFixed64Fixed64:
+				m.SetMapFixed64Fixed64(v.(map[uint64]uint64))
+			case fieldMapSfixed32Sfixed32:
+				m.SetMapSfixed32Sfixed32(v.(map[int32]int32))
+			case fieldMapSfixed64Sfixed64:
+				m.SetMapSfixed64Sfixed64(v.(map[int64]int64))
+			case fieldMapInt32Float:
+				m.SetMapInt32Float(v.(map[int32]float32))
+			case fieldMapInt32Double:
+				m.SetMapInt32Double(v.(map[int32]float64))
+			case fieldMapBoolBool:
+				m.SetMapBoolBool(v.(map[bool]bool))
+			case fieldMapStringString:
+				m.SetMapStringString(v.(map[string]string))
+			case fieldMapStringBytes:
+				m.SetMapStringBytes(v.(map[string][]byte))
+			case fieldMapStringNestedMessage:
+				m.SetMapStringNestedMessage(v.(map[string]*testpb.TestAllTypes_NestedMessage))
+			case fieldMapStringNestedEnum:
+				m.SetMapStringNestedEnum(v.(map[string]testpb.TestAllTypes_NestedEnum))
+
+			case fieldDefaultInt32:
+				m.SetDefaultInt32(v.(int32))
+			case fieldDefaultInt64:
+				m.SetDefaultInt64(v.(int64))
+			case fieldDefaultUint32:
+				m.SetDefaultUint32(v.(uint32))
+			case fieldDefaultUint64:
+				m.SetDefaultUint64(v.(uint64))
+			case fieldDefaultSint32:
+				m.SetDefaultSint32(v.(int32))
+			case fieldDefaultSint64:
+				m.SetDefaultSint64(v.(int64))
+			case fieldDefaultFixed32:
+				m.SetDefaultFixed32(v.(uint32))
+			case fieldDefaultFixed64:
+				m.SetDefaultFixed64(v.(uint64))
+			case fieldDefaultSfixed32:
+				m.SetDefaultSfixed32(v.(int32))
+			case fieldDefaultSfixed64:
+				m.SetDefaultSfixed64(v.(int64))
+			case fieldDefaultFloat:
+				m.SetDefaultFloat(v.(float32))
+			case fieldDefaultDouble:
+				m.SetDefaultDouble(v.(float64))
+			case fieldDefaultBool:
+				m.SetDefaultBool(v.(bool))
+			case fieldDefaultString:
+				m.SetDefaultString(v.(string))
+			case fieldDefaultBytes:
+				m.SetDefaultBytes(v.([]byte))
+			case fieldDefaultNestedEnum:
+				m.SetDefaultNestedEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldDefaultForeignEnum:
+				m.SetDefaultForeignEnum(testpb.ForeignEnum(v.(protoreflect.EnumNumber)))
+
+			case fieldOneofUint32:
+				m.SetOneofUint32(v.(uint32))
+			case fieldOneofNestedMessage:
+				m.SetOneofNestedMessage(v.(*testpb.TestAllTypes_NestedMessage))
+			case fieldOneofString:
+				m.SetOneofString(v.(string))
+			case fieldOneofBytes:
+				m.SetOneofBytes(v.([]byte))
+			case fieldOneofBool:
+				m.SetOneofBool(v.(bool))
+			case fieldOneofUint64:
+				m.SetOneofUint64(v.(uint64))
+			case fieldOneofFloat:
+				m.SetOneofFloat(v.(float32))
+			case fieldOneofDouble:
+				m.SetOneofDouble(v.(float64))
+			case fieldOneofEnum:
+				m.SetOneofEnum(testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)))
+			case fieldOneofGroup:
+				m.SetOneofgroup(v.(*testpb.TestAllTypes_OneofGroup))
+			case fieldOneofOptionalUint32:
+				m.SetOneofOptionalUint32(v.(uint32))
+
+			default:
+				panic(fmt.Sprintf("set: unknown field %d", num))
+			}
+		},
+		clear: func(num protoreflect.FieldNumber) {
+			switch num {
+			case fieldSingularInt32:
+				m.SetSingularInt32(0)
+			case fieldSingularInt64:
+				m.SetSingularInt64(0)
+			case fieldSingularUint32:
+				m.SetSingularUint32(0)
+			case fieldSingularUint64:
+				m.SetSingularUint64(0)
+			case fieldSingularSint32:
+				m.SetSingularSint32(0)
+			case fieldSingularSint64:
+				m.SetSingularSint64(0)
+			case fieldSingularFixed32:
+				m.SetSingularFixed32(0)
+			case fieldSingularFixed64:
+				m.SetSingularFixed64(0)
+			case fieldSingularSfixed32:
+				m.SetSingularSfixed32(0)
+			case fieldSingularSfixed64:
+				m.SetSingularSfixed64(0)
+			case fieldSingularFloat:
+				m.SetSingularFloat(0)
+			case fieldSingularDouble:
+				m.SetSingularDouble(0)
+			case fieldSingularBool:
+				m.SetSingularBool(false)
+			case fieldSingularString:
+				m.SetSingularString("")
+			case fieldSingularBytes:
+				m.SetSingularBytes(nil)
+			case fieldSingularNestedEnum:
+				m.SetSingularNestedEnum(testpb.TestAllTypes_FOO)
+			case fieldSingularForeignEnum:
+				m.SetSingularForeignEnum(testpb.ForeignEnum_FOREIGN_ZERO)
+			case fieldSingularImportEnum:
+				m.SetSingularImportEnum(testpb.ImportEnum_IMPORT_ZERO)
+
+			case fieldOptionalInt32:
+				m.ClearOptionalInt32()
+			case fieldOptionalInt64:
+				m.ClearOptionalInt64()
+			case fieldOptionalUint32:
+				m.ClearOptionalUint32()
+			case fieldOptionalUint64:
+				m.ClearOptionalUint64()
+			case fieldOptionalSint32:
+				m.ClearOptionalSint32()
+			case fieldOptionalSint64:
+				m.ClearOptionalSint64()
+			case fieldOptionalFixed32:
+				m.ClearOptionalFixed32()
+			case fieldOptionalFixed64:
+				m.ClearOptionalFixed64()
+			case fieldOptionalSfixed32:
+				m.ClearOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				m.ClearOptionalSfixed64()
+			case fieldOptionalFloat:
+				m.ClearOptionalFloat()
+			case fieldOptionalDouble:
+				m.ClearOptionalDouble()
+			case fieldOptionalBool:
+				m.ClearOptionalBool()
+			case fieldOptionalString:
+				m.ClearOptionalString()
+			case fieldOptionalBytes:
+				m.ClearOptionalBytes()
+			case fieldOptionalGroup:
+				m.ClearOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				m.ClearNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				m.ClearOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				m.ClearOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				m.ClearOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				m.ClearOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				m.ClearOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				m.ClearOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				m.ClearOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				m.SetRepeatedInt32(nil)
+			case fieldRepeatedInt64:
+				m.SetRepeatedInt64(nil)
+			case fieldRepeatedUint32:
+				m.SetRepeatedUint32(nil)
+			case fieldRepeatedUint64:
+				m.SetRepeatedUint64(nil)
+			case fieldRepeatedSint32:
+				m.SetRepeatedSint32(nil)
+			case fieldRepeatedSint64:
+				m.SetRepeatedSint64(nil)
+			case fieldRepeatedFixed32:
+				m.SetRepeatedFixed32(nil)
+			case fieldRepeatedFixed64:
+				m.SetRepeatedFixed64(nil)
+			case fieldRepeatedSfixed32:
+				m.SetRepeatedSfixed32(nil)
+			case fieldRepeatedSfixed64:
+				m.SetRepeatedSfixed64(nil)
+			case fieldRepeatedFloat:
+				m.SetRepeatedFloat(nil)
+			case fieldRepeatedDouble:
+				m.SetRepeatedDouble(nil)
+			case fieldRepeatedBool:
+				m.SetRepeatedBool(nil)
+			case fieldRepeatedString:
+				m.SetRepeatedString(nil)
+			case fieldRepeatedBytes:
+				m.SetRepeatedBytes(nil)
+			case fieldRepeatedGroup:
+				m.SetRepeatedgroup(nil)
+			case fieldRepeatedNestedMessage:
+				m.SetRepeatedNestedMessage(nil)
+			case fieldRepeatedForeignMessage:
+				m.SetRepeatedForeignMessage(nil)
+			case fieldRepeatedImportMessage:
+				m.SetRepeatedImportmessage(nil)
+			case fieldRepeatedNestedEnum:
+				m.SetRepeatedNestedEnum(nil)
+			case fieldRepeatedForeignEnum:
+				m.SetRepeatedForeignEnum(nil)
+			case fieldRepeatedImportEnum:
+				m.SetRepeatedImportenum(nil)
+
+			case fieldMapInt32Int32:
+				m.SetMapInt32Int32(nil)
+			case fieldMapInt64Int64:
+				m.SetMapInt64Int64(nil)
+			case fieldMapUint32Uint32:
+				m.SetMapUint32Uint32(nil)
+			case fieldMapUint64Uint64:
+				m.SetMapUint64Uint64(nil)
+			case fieldMapSint32Sint32:
+				m.SetMapSint32Sint32(nil)
+			case fieldMapSint64Sint64:
+				m.SetMapSint64Sint64(nil)
+			case fieldMapFixed32Fixed32:
+				m.SetMapFixed32Fixed32(nil)
+			case fieldMapFixed64Fixed64:
+				m.SetMapFixed64Fixed64(nil)
+			case fieldMapSfixed32Sfixed32:
+				m.SetMapSfixed32Sfixed32(nil)
+			case fieldMapSfixed64Sfixed64:
+				m.SetMapSfixed64Sfixed64(nil)
+			case fieldMapInt32Float:
+				m.SetMapInt32Float(nil)
+			case fieldMapInt32Double:
+				m.SetMapInt32Double(nil)
+			case fieldMapBoolBool:
+				m.SetMapBoolBool(nil)
+			case fieldMapStringString:
+				m.SetMapStringString(nil)
+			case fieldMapStringBytes:
+				m.SetMapStringBytes(nil)
+			case fieldMapStringNestedMessage:
+				m.SetMapStringNestedMessage(nil)
+			case fieldMapStringNestedEnum:
+				m.SetMapStringNestedEnum(nil)
+
+			case fieldDefaultInt32:
+				m.ClearDefaultInt32()
+			case fieldDefaultInt64:
+				m.ClearDefaultInt64()
+			case fieldDefaultUint32:
+				m.ClearDefaultUint32()
+			case fieldDefaultUint64:
+				m.ClearDefaultUint64()
+			case fieldDefaultSint32:
+				m.ClearDefaultSint32()
+			case fieldDefaultSint64:
+				m.ClearDefaultSint64()
+			case fieldDefaultFixed32:
+				m.ClearDefaultFixed32()
+			case fieldDefaultFixed64:
+				m.ClearDefaultFixed64()
+			case fieldDefaultSfixed32:
+				m.ClearDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				m.ClearDefaultSfixed64()
+			case fieldDefaultFloat:
+				m.ClearDefaultFloat()
+			case fieldDefaultDouble:
+				m.ClearDefaultDouble()
+			case fieldDefaultBool:
+				m.ClearDefaultBool()
+			case fieldDefaultString:
+				m.ClearDefaultString()
+			case fieldDefaultBytes:
+				m.ClearDefaultBytes()
+			case fieldDefaultNestedEnum:
+				m.ClearDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				m.ClearDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				m.ClearOneofUint32()
+			case fieldOneofNestedMessage:
+				m.ClearOneofNestedMessage()
+			case fieldOneofString:
+				m.ClearOneofString()
+			case fieldOneofBytes:
+				m.ClearOneofBytes()
+			case fieldOneofBool:
+				m.ClearOneofBool()
+			case fieldOneofUint64:
+				m.ClearOneofUint64()
+			case fieldOneofFloat:
+				m.ClearOneofFloat()
+			case fieldOneofDouble:
+				m.ClearOneofDouble()
+			case fieldOneofEnum:
+				m.ClearOneofEnum()
+			case fieldOneofGroup:
+				m.ClearOneofgroup()
+			case fieldOneofOptionalUint32:
+				m.ClearOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("clear: unknown field %d", num))
+			}
+		},
+	}
+}
diff --git a/internal/reflection_test/reflection_open_test.go b/internal/reflection_test/reflection_open_test.go
new file mode 100644
index 0000000..228dd20
--- /dev/null
+++ b/internal/reflection_test/reflection_open_test.go
@@ -0,0 +1,985 @@
+// 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 reflection_test
+
+import (
+	"fmt"
+	"math"
+	"testing"
+
+	testpb "google.golang.org/protobuf/internal/testprotos/testeditions"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/prototest"
+)
+
+func TestOpenConcrete(t *testing.T) {
+	prototest.Message{}.Test(t, newTestMessageOpen(nil).ProtoReflect().Type())
+}
+
+func TestOpenReflection(t *testing.T) {
+	prototest.Message{}.Test(t, (*testpb.TestAllTypes)(nil).ProtoReflect().Type())
+}
+
+func TestOpenShadow_GetConcrete_SetReflection(t *testing.T) {
+	prototest.Message{}.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+		m := &testpb.TestAllTypes{}
+		return newTestMessageOpen(m), m
+	}).ProtoReflect().Type())
+}
+
+func TestOpenShadow_GetReflection_SetConcrete(t *testing.T) {
+	prototest.Message{}.Test(t, newShadow(func() (get, set protoreflect.ProtoMessage) {
+		m := &testpb.TestAllTypes{}
+		return m, newTestMessageOpen(m)
+	}).ProtoReflect().Type())
+}
+
+func newTestMessageOpen(m *testpb.TestAllTypes) protoreflect.ProtoMessage {
+	return &testProtoMessage{
+		m:  m,
+		md: m.ProtoReflect().Descriptor(),
+		new: func() protoreflect.Message {
+			return newTestMessageOpen(&testpb.TestAllTypes{}).ProtoReflect()
+		},
+		has: func(num protoreflect.FieldNumber) bool {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32() != 0
+			case fieldSingularInt64:
+				return m.GetSingularInt64() != 0
+			case fieldSingularUint32:
+				return m.GetSingularUint32() != 0
+			case fieldSingularUint64:
+				return m.GetSingularUint64() != 0
+			case fieldSingularSint32:
+				return m.GetSingularSint32() != 0
+			case fieldSingularSint64:
+				return m.GetSingularSint64() != 0
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32() != 0
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64() != 0
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32() != 0
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64() != 0
+			case fieldSingularFloat:
+				return m.GetSingularFloat() != 0 || math.Signbit(float64(m.GetSingularFloat()))
+			case fieldSingularDouble:
+				return m.GetSingularDouble() != 0 || math.Signbit(m.GetSingularDouble())
+			case fieldSingularBool:
+				return m.GetSingularBool() != false
+			case fieldSingularString:
+				return m.GetSingularString() != ""
+			case fieldSingularBytes:
+				return m.SingularBytes != nil
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum() != testpb.TestAllTypes_FOO
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum() != testpb.ForeignEnum_FOREIGN_ZERO
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum() != testpb.ImportEnum_IMPORT_ZERO
+
+			case fieldOptionalInt32:
+				return m.OptionalInt32 != nil
+			case fieldOptionalInt64:
+				return m.OptionalInt64 != nil
+			case fieldOptionalUint32:
+				return m.OptionalUint32 != nil
+			case fieldOptionalUint64:
+				return m.OptionalUint64 != nil
+			case fieldOptionalSint32:
+				return m.OptionalSint32 != nil
+			case fieldOptionalSint64:
+				return m.OptionalSint64 != nil
+			case fieldOptionalFixed32:
+				return m.OptionalFixed32 != nil
+			case fieldOptionalFixed64:
+				return m.OptionalFixed64 != nil
+			case fieldOptionalSfixed32:
+				return m.OptionalSfixed32 != nil
+			case fieldOptionalSfixed64:
+				return m.OptionalSfixed64 != nil
+			case fieldOptionalFloat:
+				return m.OptionalFloat != nil
+			case fieldOptionalDouble:
+				return m.OptionalDouble != nil
+			case fieldOptionalBool:
+				return m.OptionalBool != nil
+			case fieldOptionalString:
+				return m.OptionalString != nil
+			case fieldOptionalBytes:
+				return m.OptionalBytes != nil
+			case fieldOptionalGroup:
+				return m.Optionalgroup != nil
+			case fieldNotGroupLikeDelimited:
+				return m.NotGroupLikeDelimited != nil
+			case fieldOptionalNestedMessage:
+				return m.OptionalNestedMessage != nil
+			case fieldOptionalForeignMessage:
+				return m.OptionalForeignMessage != nil
+			case fieldOptionalImportMessage:
+				return m.OptionalImportMessage != nil
+			case fieldOptionalNestedEnum:
+				return m.OptionalNestedEnum != nil
+			case fieldOptionalForeignEnum:
+				return m.OptionalForeignEnum != nil
+			case fieldOptionalImportEnum:
+				return m.OptionalImportEnum != nil
+			case fieldOptionalLazyNestedMessage:
+				return m.OptionalLazyNestedMessage != nil
+
+			case fieldRepeatedInt32:
+				return len(m.GetRepeatedInt32()) > 0
+			case fieldRepeatedInt64:
+				return len(m.GetRepeatedInt64()) > 0
+			case fieldRepeatedUint32:
+				return len(m.GetRepeatedUint32()) > 0
+			case fieldRepeatedUint64:
+				return len(m.GetRepeatedUint64()) > 0
+			case fieldRepeatedSint32:
+				return len(m.GetRepeatedSint32()) > 0
+			case fieldRepeatedSint64:
+				return len(m.GetRepeatedSint64()) > 0
+			case fieldRepeatedFixed32:
+				return len(m.GetRepeatedFixed32()) > 0
+			case fieldRepeatedFixed64:
+				return len(m.GetRepeatedFixed64()) > 0
+			case fieldRepeatedSfixed32:
+				return len(m.GetRepeatedSfixed32()) > 0
+			case fieldRepeatedSfixed64:
+				return len(m.GetRepeatedSfixed64()) > 0
+			case fieldRepeatedFloat:
+				return len(m.GetRepeatedFloat()) > 0
+			case fieldRepeatedDouble:
+				return len(m.GetRepeatedDouble()) > 0
+			case fieldRepeatedBool:
+				return len(m.GetRepeatedBool()) > 0
+			case fieldRepeatedString:
+				return len(m.GetRepeatedString()) > 0
+			case fieldRepeatedBytes:
+				return len(m.GetRepeatedBytes()) > 0
+			case fieldRepeatedGroup:
+				return len(m.GetRepeatedgroup()) > 0
+			case fieldRepeatedNestedMessage:
+				return len(m.GetRepeatedNestedMessage()) > 0
+			case fieldRepeatedForeignMessage:
+				return len(m.GetRepeatedForeignMessage()) > 0
+			case fieldRepeatedImportMessage:
+				return len(m.GetRepeatedImportmessage()) > 0
+			case fieldRepeatedNestedEnum:
+				return len(m.GetRepeatedNestedEnum()) > 0
+			case fieldRepeatedForeignEnum:
+				return len(m.GetRepeatedForeignEnum()) > 0
+			case fieldRepeatedImportEnum:
+				return len(m.GetRepeatedImportenum()) > 0
+
+			case fieldMapInt32Int32:
+				return len(m.GetMapInt32Int32()) > 0
+			case fieldMapInt64Int64:
+				return len(m.GetMapInt64Int64()) > 0
+			case fieldMapUint32Uint32:
+				return len(m.GetMapUint32Uint32()) > 0
+			case fieldMapUint64Uint64:
+				return len(m.GetMapUint64Uint64()) > 0
+			case fieldMapSint32Sint32:
+				return len(m.GetMapSint32Sint32()) > 0
+			case fieldMapSint64Sint64:
+				return len(m.GetMapSint64Sint64()) > 0
+			case fieldMapFixed32Fixed32:
+				return len(m.GetMapFixed32Fixed32()) > 0
+			case fieldMapFixed64Fixed64:
+				return len(m.GetMapFixed64Fixed64()) > 0
+			case fieldMapSfixed32Sfixed32:
+				return len(m.GetMapSfixed32Sfixed32()) > 0
+			case fieldMapSfixed64Sfixed64:
+				return len(m.GetMapSfixed64Sfixed64()) > 0
+			case fieldMapInt32Float:
+				return len(m.GetMapInt32Float()) > 0
+			case fieldMapInt32Double:
+				return len(m.GetMapInt32Double()) > 0
+			case fieldMapBoolBool:
+				return len(m.GetMapBoolBool()) > 0
+			case fieldMapStringString:
+				return len(m.GetMapStringString()) > 0
+			case fieldMapStringBytes:
+				return len(m.GetMapStringBytes()) > 0
+			case fieldMapStringNestedMessage:
+				return len(m.GetMapStringNestedMessage()) > 0
+			case fieldMapStringNestedEnum:
+				return len(m.GetMapStringNestedEnum()) > 0
+
+			case fieldDefaultInt32:
+				return m.DefaultInt32 != nil
+			case fieldDefaultInt64:
+				return m.DefaultInt64 != nil
+			case fieldDefaultUint32:
+				return m.DefaultUint32 != nil
+			case fieldDefaultUint64:
+				return m.DefaultUint64 != nil
+			case fieldDefaultSint32:
+				return m.DefaultSint32 != nil
+			case fieldDefaultSint64:
+				return m.DefaultSint64 != nil
+			case fieldDefaultFixed32:
+				return m.DefaultFixed32 != nil
+			case fieldDefaultFixed64:
+				return m.DefaultFixed64 != nil
+			case fieldDefaultSfixed32:
+				return m.DefaultSfixed32 != nil
+			case fieldDefaultSfixed64:
+				return m.DefaultSfixed64 != nil
+			case fieldDefaultFloat:
+				return m.DefaultFloat != nil
+			case fieldDefaultDouble:
+				return m.DefaultDouble != nil
+			case fieldDefaultBool:
+				return m.DefaultBool != nil
+			case fieldDefaultString:
+				return m.DefaultString != nil
+			case fieldDefaultBytes:
+				return m.DefaultBytes != nil
+			case fieldDefaultNestedEnum:
+				return m.DefaultNestedEnum != nil
+			case fieldDefaultForeignEnum:
+				return m.DefaultForeignEnum != nil
+
+			case fieldOneofUint32:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofUint32)
+				return ok
+			case fieldOneofNestedMessage:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofNestedMessage)
+				return ok
+			case fieldOneofString:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofString)
+				return ok
+			case fieldOneofBytes:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofBytes)
+				return ok
+			case fieldOneofBool:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofBool)
+				return ok
+			case fieldOneofUint64:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofUint64)
+				return ok
+			case fieldOneofFloat:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofFloat)
+				return ok
+			case fieldOneofDouble:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofDouble)
+				return ok
+			case fieldOneofEnum:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_OneofEnum)
+				return ok
+			case fieldOneofGroup:
+				_, ok := m.OneofField.(*testpb.TestAllTypes_Oneofgroup)
+				return ok
+			case fieldOneofOptionalUint32:
+				_, ok := m.OneofOptional.(*testpb.TestAllTypes_OneofOptionalUint32)
+				return ok
+
+			default:
+				panic(fmt.Sprintf("has: unknown field %d", num))
+			}
+		},
+		get: func(num protoreflect.FieldNumber) any {
+			switch num {
+			case fieldSingularInt32:
+				return m.GetSingularInt32()
+			case fieldSingularInt64:
+				return m.GetSingularInt64()
+			case fieldSingularUint32:
+				return m.GetSingularUint32()
+			case fieldSingularUint64:
+				return m.GetSingularUint64()
+			case fieldSingularSint32:
+				return m.GetSingularSint32()
+			case fieldSingularSint64:
+				return m.GetSingularSint64()
+			case fieldSingularFixed32:
+				return m.GetSingularFixed32()
+			case fieldSingularFixed64:
+				return m.GetSingularFixed64()
+			case fieldSingularSfixed32:
+				return m.GetSingularSfixed32()
+			case fieldSingularSfixed64:
+				return m.GetSingularSfixed64()
+			case fieldSingularFloat:
+				return m.GetSingularFloat()
+			case fieldSingularDouble:
+				return m.GetSingularDouble()
+			case fieldSingularBool:
+				return m.GetSingularBool()
+			case fieldSingularString:
+				return m.GetSingularString()
+			case fieldSingularBytes:
+				return m.GetSingularBytes()
+			case fieldSingularNestedEnum:
+				return m.GetSingularNestedEnum()
+			case fieldSingularForeignEnum:
+				return m.GetSingularForeignEnum()
+			case fieldSingularImportEnum:
+				return m.GetSingularImportEnum()
+
+			case fieldOptionalInt32:
+				return m.GetOptionalInt32()
+			case fieldOptionalInt64:
+				return m.GetOptionalInt64()
+			case fieldOptionalUint32:
+				return m.GetOptionalUint32()
+			case fieldOptionalUint64:
+				return m.GetOptionalUint64()
+			case fieldOptionalSint32:
+				return m.GetOptionalSint32()
+			case fieldOptionalSint64:
+				return m.GetOptionalSint64()
+			case fieldOptionalFixed32:
+				return m.GetOptionalFixed32()
+			case fieldOptionalFixed64:
+				return m.GetOptionalFixed64()
+			case fieldOptionalSfixed32:
+				return m.GetOptionalSfixed32()
+			case fieldOptionalSfixed64:
+				return m.GetOptionalSfixed64()
+			case fieldOptionalFloat:
+				return m.GetOptionalFloat()
+			case fieldOptionalDouble:
+				return m.GetOptionalDouble()
+			case fieldOptionalBool:
+				return m.GetOptionalBool()
+			case fieldOptionalString:
+				return m.GetOptionalString()
+			case fieldOptionalBytes:
+				return m.GetOptionalBytes()
+			case fieldOptionalGroup:
+				return m.GetOptionalgroup()
+			case fieldNotGroupLikeDelimited:
+				return m.GetNotGroupLikeDelimited()
+			case fieldOptionalNestedMessage:
+				return m.GetOptionalNestedMessage()
+			case fieldOptionalForeignMessage:
+				return m.GetOptionalForeignMessage()
+			case fieldOptionalImportMessage:
+				return m.GetOptionalImportMessage()
+			case fieldOptionalNestedEnum:
+				return m.GetOptionalNestedEnum()
+			case fieldOptionalForeignEnum:
+				return m.GetOptionalForeignEnum()
+			case fieldOptionalImportEnum:
+				return m.GetOptionalImportEnum()
+			case fieldOptionalLazyNestedMessage:
+				return m.GetOptionalLazyNestedMessage()
+
+			case fieldRepeatedInt32:
+				return m.GetRepeatedInt32()
+			case fieldRepeatedInt64:
+				return m.GetRepeatedInt64()
+			case fieldRepeatedUint32:
+				return m.GetRepeatedUint32()
+			case fieldRepeatedUint64:
+				return m.GetRepeatedUint64()
+			case fieldRepeatedSint32:
+				return m.GetRepeatedSint32()
+			case fieldRepeatedSint64:
+				return m.GetRepeatedSint64()
+			case fieldRepeatedFixed32:
+				return m.GetRepeatedFixed32()
+			case fieldRepeatedFixed64:
+				return m.GetRepeatedFixed64()
+			case fieldRepeatedSfixed32:
+				return m.GetRepeatedSfixed32()
+			case fieldRepeatedSfixed64:
+				return m.GetRepeatedSfixed64()
+			case fieldRepeatedFloat:
+				return m.GetRepeatedFloat()
+			case fieldRepeatedDouble:
+				return m.GetRepeatedDouble()
+			case fieldRepeatedBool:
+				return m.GetRepeatedBool()
+			case fieldRepeatedString:
+				return m.GetRepeatedString()
+			case fieldRepeatedBytes:
+				return m.GetRepeatedBytes()
+			case fieldRepeatedGroup:
+				return m.GetRepeatedgroup()
+			case fieldRepeatedNestedMessage:
+				return m.GetRepeatedNestedMessage()
+			case fieldRepeatedForeignMessage:
+				return m.GetRepeatedForeignMessage()
+			case fieldRepeatedImportMessage:
+				return m.GetRepeatedImportmessage()
+			case fieldRepeatedNestedEnum:
+				return m.GetRepeatedNestedEnum()
+			case fieldRepeatedForeignEnum:
+				return m.GetRepeatedForeignEnum()
+			case fieldRepeatedImportEnum:
+				return m.GetRepeatedImportenum()
+
+			case fieldMapInt32Int32:
+				return m.GetMapInt32Int32()
+			case fieldMapInt64Int64:
+				return m.GetMapInt64Int64()
+			case fieldMapUint32Uint32:
+				return m.GetMapUint32Uint32()
+			case fieldMapUint64Uint64:
+				return m.GetMapUint64Uint64()
+			case fieldMapSint32Sint32:
+				return m.GetMapSint32Sint32()
+			case fieldMapSint64Sint64:
+				return m.GetMapSint64Sint64()
+			case fieldMapFixed32Fixed32:
+				return m.GetMapFixed32Fixed32()
+			case fieldMapFixed64Fixed64:
+				return m.GetMapFixed64Fixed64()
+			case fieldMapSfixed32Sfixed32:
+				return m.GetMapSfixed32Sfixed32()
+			case fieldMapSfixed64Sfixed64:
+				return m.GetMapSfixed64Sfixed64()
+			case fieldMapInt32Float:
+				return m.GetMapInt32Float()
+			case fieldMapInt32Double:
+				return m.GetMapInt32Double()
+			case fieldMapBoolBool:
+				return m.GetMapBoolBool()
+			case fieldMapStringString:
+				return m.GetMapStringString()
+			case fieldMapStringBytes:
+				return m.GetMapStringBytes()
+			case fieldMapStringNestedMessage:
+				return m.GetMapStringNestedMessage()
+			case fieldMapStringNestedEnum:
+				return m.GetMapStringNestedEnum()
+
+			case fieldDefaultInt32:
+				return m.GetDefaultInt32()
+			case fieldDefaultInt64:
+				return m.GetDefaultInt64()
+			case fieldDefaultUint32:
+				return m.GetDefaultUint32()
+			case fieldDefaultUint64:
+				return m.GetDefaultUint64()
+			case fieldDefaultSint32:
+				return m.GetDefaultSint32()
+			case fieldDefaultSint64:
+				return m.GetDefaultSint64()
+			case fieldDefaultFixed32:
+				return m.GetDefaultFixed32()
+			case fieldDefaultFixed64:
+				return m.GetDefaultFixed64()
+			case fieldDefaultSfixed32:
+				return m.GetDefaultSfixed32()
+			case fieldDefaultSfixed64:
+				return m.GetDefaultSfixed64()
+			case fieldDefaultFloat:
+				return m.GetDefaultFloat()
+			case fieldDefaultDouble:
+				return m.GetDefaultDouble()
+			case fieldDefaultBool:
+				return m.GetDefaultBool()
+			case fieldDefaultString:
+				return m.GetDefaultString()
+			case fieldDefaultBytes:
+				return m.GetDefaultBytes()
+			case fieldDefaultNestedEnum:
+				return m.GetDefaultNestedEnum()
+			case fieldDefaultForeignEnum:
+				return m.GetDefaultForeignEnum()
+
+			case fieldOneofUint32:
+				return m.GetOneofUint32()
+			case fieldOneofNestedMessage:
+				return m.GetOneofNestedMessage()
+			case fieldOneofString:
+				return m.GetOneofString()
+			case fieldOneofBytes:
+				return m.GetOneofBytes()
+			case fieldOneofBool:
+				return m.GetOneofBool()
+			case fieldOneofUint64:
+				return m.GetOneofUint64()
+			case fieldOneofFloat:
+				return m.GetOneofFloat()
+			case fieldOneofDouble:
+				return m.GetOneofDouble()
+			case fieldOneofEnum:
+				return protoreflect.EnumNumber(m.GetOneofEnum())
+			case fieldOneofGroup:
+				return m.GetOneofgroup()
+			case fieldOneofOptionalUint32:
+				return m.GetOneofOptionalUint32()
+
+			default:
+				panic(fmt.Sprintf("get: unknown field %d", num))
+			}
+		},
+		set: func(num protoreflect.FieldNumber, v any) {
+			switch num {
+			case fieldSingularInt32:
+				m.SingularInt32 = v.(int32)
+			case fieldSingularInt64:
+				m.SingularInt64 = v.(int64)
+			case fieldSingularUint32:
+				m.SingularUint32 = v.(uint32)
+			case fieldSingularUint64:
+				m.SingularUint64 = v.(uint64)
+			case fieldSingularSint32:
+				m.SingularSint32 = v.(int32)
+			case fieldSingularSint64:
+				m.SingularSint64 = v.(int64)
+			case fieldSingularFixed32:
+				m.SingularFixed32 = v.(uint32)
+			case fieldSingularFixed64:
+				m.SingularFixed64 = v.(uint64)
+			case fieldSingularSfixed32:
+				m.SingularSfixed32 = v.(int32)
+			case fieldSingularSfixed64:
+				m.SingularSfixed64 = v.(int64)
+			case fieldSingularFloat:
+				m.SingularFloat = v.(float32)
+			case fieldSingularDouble:
+				m.SingularDouble = v.(float64)
+			case fieldSingularBool:
+				m.SingularBool = v.(bool)
+			case fieldSingularString:
+				m.SingularString = v.(string)
+			case fieldSingularBytes:
+				m.SingularBytes = v.([]byte)
+			case fieldSingularNestedEnum:
+				m.SingularNestedEnum = testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber))
+			case fieldSingularForeignEnum:
+				m.SingularForeignEnum = testpb.ForeignEnum(v.(protoreflect.EnumNumber))
+			case fieldSingularImportEnum:
+				m.SingularImportEnum = testpb.ImportEnum(v.(protoreflect.EnumNumber))
+
+			case fieldOptionalInt32:
+				m.OptionalInt32 = proto.Int32(v.(int32))
+			case fieldOptionalInt64:
+				m.OptionalInt64 = proto.Int64(v.(int64))
+			case fieldOptionalUint32:
+				m.OptionalUint32 = proto.Uint32(v.(uint32))
+			case fieldOptionalUint64:
+				m.OptionalUint64 = proto.Uint64(v.(uint64))
+			case fieldOptionalSint32:
+				m.OptionalSint32 = proto.Int32(v.(int32))
+			case fieldOptionalSint64:
+				m.OptionalSint64 = proto.Int64(v.(int64))
+			case fieldOptionalFixed32:
+				m.OptionalFixed32 = proto.Uint32(v.(uint32))
+			case fieldOptionalFixed64:
+				m.OptionalFixed64 = proto.Uint64(v.(uint64))
+			case fieldOptionalSfixed32:
+				m.OptionalSfixed32 = proto.Int32(v.(int32))
+			case fieldOptionalSfixed64:
+				m.OptionalSfixed64 = proto.Int64(v.(int64))
+			case fieldOptionalFloat:
+				m.OptionalFloat = proto.Float32(v.(float32))
+			case fieldOptionalDouble:
+				m.OptionalDouble = proto.Float64(v.(float64))
+			case fieldOptionalBool:
+				m.OptionalBool = proto.Bool(v.(bool))
+			case fieldOptionalString:
+				m.OptionalString = proto.String(v.(string))
+			case fieldOptionalBytes:
+				if v.([]byte) == nil {
+					v = []byte{}
+				}
+				m.OptionalBytes = v.([]byte)
+			case fieldNotGroupLikeDelimited:
+				m.NotGroupLikeDelimited = v.(*testpb.TestAllTypes_OptionalGroup)
+			case fieldOptionalGroup:
+				m.Optionalgroup = v.(*testpb.TestAllTypes_OptionalGroup)
+			case fieldOptionalNestedMessage:
+				m.OptionalNestedMessage = v.(*testpb.TestAllTypes_NestedMessage)
+			case fieldOptionalForeignMessage:
+				m.OptionalForeignMessage = v.(*testpb.ForeignMessage)
+			case fieldOptionalImportMessage:
+				m.OptionalImportMessage = v.(*testpb.ImportMessage)
+			case fieldOptionalNestedEnum:
+				m.OptionalNestedEnum = testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)).Enum()
+			case fieldOptionalForeignEnum:
+				m.OptionalForeignEnum = testpb.ForeignEnum(v.(protoreflect.EnumNumber)).Enum()
+			case fieldOptionalImportEnum:
+				m.OptionalImportEnum = testpb.ImportEnum(v.(protoreflect.EnumNumber)).Enum()
+			case fieldOptionalLazyNestedMessage:
+				m.OptionalLazyNestedMessage = v.(*testpb.TestAllTypes_NestedMessage)
+
+			case fieldRepeatedInt32:
+				m.RepeatedInt32 = v.([]int32)
+			case fieldRepeatedInt64:
+				m.RepeatedInt64 = v.([]int64)
+			case fieldRepeatedUint32:
+				m.RepeatedUint32 = v.([]uint32)
+			case fieldRepeatedUint64:
+				m.RepeatedUint64 = v.([]uint64)
+			case fieldRepeatedSint32:
+				m.RepeatedSint32 = v.([]int32)
+			case fieldRepeatedSint64:
+				m.RepeatedSint64 = v.([]int64)
+			case fieldRepeatedFixed32:
+				m.RepeatedFixed32 = v.([]uint32)
+			case fieldRepeatedFixed64:
+				m.RepeatedFixed64 = v.([]uint64)
+			case fieldRepeatedSfixed32:
+				m.RepeatedSfixed32 = v.([]int32)
+			case fieldRepeatedSfixed64:
+				m.RepeatedSfixed64 = v.([]int64)
+			case fieldRepeatedFloat:
+				m.RepeatedFloat = v.([]float32)
+			case fieldRepeatedDouble:
+				m.RepeatedDouble = v.([]float64)
+			case fieldRepeatedBool:
+				m.RepeatedBool = v.([]bool)
+			case fieldRepeatedString:
+				m.RepeatedString = v.([]string)
+			case fieldRepeatedBytes:
+				m.RepeatedBytes = v.([][]byte)
+			case fieldRepeatedGroup:
+				m.Repeatedgroup = v.([]*testpb.TestAllTypes_RepeatedGroup)
+			case fieldRepeatedNestedMessage:
+				m.RepeatedNestedMessage = v.([]*testpb.TestAllTypes_NestedMessage)
+			case fieldRepeatedForeignMessage:
+				m.RepeatedForeignMessage = v.([]*testpb.ForeignMessage)
+			case fieldRepeatedImportMessage:
+				m.RepeatedImportmessage = v.([]*testpb.ImportMessage)
+			case fieldRepeatedNestedEnum:
+				m.RepeatedNestedEnum = v.([]testpb.TestAllTypes_NestedEnum)
+			case fieldRepeatedForeignEnum:
+				m.RepeatedForeignEnum = v.([]testpb.ForeignEnum)
+			case fieldRepeatedImportEnum:
+				m.RepeatedImportenum = v.([]testpb.ImportEnum)
+
+			case fieldMapInt32Int32:
+				m.MapInt32Int32 = v.(map[int32]int32)
+			case fieldMapInt64Int64:
+				m.MapInt64Int64 = v.(map[int64]int64)
+			case fieldMapUint32Uint32:
+				m.MapUint32Uint32 = v.(map[uint32]uint32)
+			case fieldMapUint64Uint64:
+				m.MapUint64Uint64 = v.(map[uint64]uint64)
+			case fieldMapSint32Sint32:
+				m.MapSint32Sint32 = v.(map[int32]int32)
+			case fieldMapSint64Sint64:
+				m.MapSint64Sint64 = v.(map[int64]int64)
+			case fieldMapFixed32Fixed32:
+				m.MapFixed32Fixed32 = v.(map[uint32]uint32)
+			case fieldMapFixed64Fixed64:
+				m.MapFixed64Fixed64 = v.(map[uint64]uint64)
+			case fieldMapSfixed32Sfixed32:
+				m.MapSfixed32Sfixed32 = v.(map[int32]int32)
+			case fieldMapSfixed64Sfixed64:
+				m.MapSfixed64Sfixed64 = v.(map[int64]int64)
+			case fieldMapInt32Float:
+				m.MapInt32Float = v.(map[int32]float32)
+			case fieldMapInt32Double:
+				m.MapInt32Double = v.(map[int32]float64)
+			case fieldMapBoolBool:
+				m.MapBoolBool = v.(map[bool]bool)
+			case fieldMapStringString:
+				m.MapStringString = v.(map[string]string)
+			case fieldMapStringBytes:
+				m.MapStringBytes = v.(map[string][]byte)
+			case fieldMapStringNestedMessage:
+				m.MapStringNestedMessage = v.(map[string]*testpb.TestAllTypes_NestedMessage)
+			case fieldMapStringNestedEnum:
+				m.MapStringNestedEnum = v.(map[string]testpb.TestAllTypes_NestedEnum)
+
+			case fieldDefaultInt32:
+				m.DefaultInt32 = proto.Int32(v.(int32))
+			case fieldDefaultInt64:
+				m.DefaultInt64 = proto.Int64(v.(int64))
+			case fieldDefaultUint32:
+				m.DefaultUint32 = proto.Uint32(v.(uint32))
+			case fieldDefaultUint64:
+				m.DefaultUint64 = proto.Uint64(v.(uint64))
+			case fieldDefaultSint32:
+				m.DefaultSint32 = proto.Int32(v.(int32))
+			case fieldDefaultSint64:
+				m.DefaultSint64 = proto.Int64(v.(int64))
+			case fieldDefaultFixed32:
+				m.DefaultFixed32 = proto.Uint32(v.(uint32))
+			case fieldDefaultFixed64:
+				m.DefaultFixed64 = proto.Uint64(v.(uint64))
+			case fieldDefaultSfixed32:
+				m.DefaultSfixed32 = proto.Int32(v.(int32))
+			case fieldDefaultSfixed64:
+				m.DefaultSfixed64 = proto.Int64(v.(int64))
+			case fieldDefaultFloat:
+				m.DefaultFloat = proto.Float32(v.(float32))
+			case fieldDefaultDouble:
+				m.DefaultDouble = proto.Float64(v.(float64))
+			case fieldDefaultBool:
+				m.DefaultBool = proto.Bool(v.(bool))
+			case fieldDefaultString:
+				m.DefaultString = proto.String(v.(string))
+			case fieldDefaultBytes:
+				if v.([]byte) == nil {
+					v = []byte{}
+				}
+				m.DefaultBytes = v.([]byte)
+			case fieldDefaultNestedEnum:
+				m.DefaultNestedEnum = testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber)).Enum()
+			case fieldDefaultForeignEnum:
+				m.DefaultForeignEnum = testpb.ForeignEnum(v.(protoreflect.EnumNumber)).Enum()
+
+			case fieldOneofUint32:
+				m.OneofField = &testpb.TestAllTypes_OneofUint32{v.(uint32)}
+			case fieldOneofNestedMessage:
+				m.OneofField = &testpb.TestAllTypes_OneofNestedMessage{v.(*testpb.TestAllTypes_NestedMessage)}
+			case fieldOneofString:
+				m.OneofField = &testpb.TestAllTypes_OneofString{v.(string)}
+			case fieldOneofBytes:
+				m.OneofField = &testpb.TestAllTypes_OneofBytes{v.([]byte)}
+			case fieldOneofBool:
+				m.OneofField = &testpb.TestAllTypes_OneofBool{v.(bool)}
+			case fieldOneofUint64:
+				m.OneofField = &testpb.TestAllTypes_OneofUint64{v.(uint64)}
+			case fieldOneofFloat:
+				m.OneofField = &testpb.TestAllTypes_OneofFloat{v.(float32)}
+			case fieldOneofDouble:
+				m.OneofField = &testpb.TestAllTypes_OneofDouble{v.(float64)}
+			case fieldOneofEnum:
+				m.OneofField = &testpb.TestAllTypes_OneofEnum{testpb.TestAllTypes_NestedEnum(v.(protoreflect.EnumNumber))}
+			case fieldOneofGroup:
+				m.OneofField = &testpb.TestAllTypes_Oneofgroup{v.(*testpb.TestAllTypes_OneofGroup)}
+			case fieldOneofOptionalUint32:
+				m.OneofOptional = &testpb.TestAllTypes_OneofOptionalUint32{v.(uint32)}
+
+			default:
+				panic(fmt.Sprintf("set: unknown field %d", num))
+			}
+		},
+		clear: func(num protoreflect.FieldNumber) {
+			switch num {
+			case fieldSingularInt32:
+				m.SingularInt32 = 0
+			case fieldSingularInt64:
+				m.SingularInt64 = 0
+			case fieldSingularUint32:
+				m.SingularUint32 = 0
+			case fieldSingularUint64:
+				m.SingularUint64 = 0
+			case fieldSingularSint32:
+				m.SingularSint32 = 0
+			case fieldSingularSint64:
+				m.SingularSint64 = 0
+			case fieldSingularFixed32:
+				m.SingularFixed32 = 0
+			case fieldSingularFixed64:
+				m.SingularFixed64 = 0
+			case fieldSingularSfixed32:
+				m.SingularSfixed32 = 0
+			case fieldSingularSfixed64:
+				m.SingularSfixed64 = 0
+			case fieldSingularFloat:
+				m.SingularFloat = 0
+			case fieldSingularDouble:
+				m.SingularDouble = 0
+			case fieldSingularBool:
+				m.SingularBool = false
+			case fieldSingularString:
+				m.SingularString = ""
+			case fieldSingularBytes:
+				m.SingularBytes = nil
+			case fieldSingularNestedEnum:
+				m.SingularNestedEnum = testpb.TestAllTypes_FOO
+			case fieldSingularForeignEnum:
+				m.SingularForeignEnum = testpb.ForeignEnum_FOREIGN_ZERO
+			case fieldSingularImportEnum:
+				m.SingularImportEnum = testpb.ImportEnum_IMPORT_ZERO
+
+			case fieldOptionalInt32:
+				m.OptionalInt32 = nil
+			case fieldOptionalInt64:
+				m.OptionalInt64 = nil
+			case fieldOptionalUint32:
+				m.OptionalUint32 = nil
+			case fieldOptionalUint64:
+				m.OptionalUint64 = nil
+			case fieldOptionalSint32:
+				m.OptionalSint32 = nil
+			case fieldOptionalSint64:
+				m.OptionalSint64 = nil
+			case fieldOptionalFixed32:
+				m.OptionalFixed32 = nil
+			case fieldOptionalFixed64:
+				m.OptionalFixed64 = nil
+			case fieldOptionalSfixed32:
+				m.OptionalSfixed32 = nil
+			case fieldOptionalSfixed64:
+				m.OptionalSfixed64 = nil
+			case fieldOptionalFloat:
+				m.OptionalFloat = nil
+			case fieldOptionalDouble:
+				m.OptionalDouble = nil
+			case fieldOptionalBool:
+				m.OptionalBool = nil
+			case fieldOptionalString:
+				m.OptionalString = nil
+			case fieldOptionalBytes:
+				m.OptionalBytes = nil
+			case fieldOptionalGroup:
+				m.Optionalgroup = nil
+			case fieldNotGroupLikeDelimited:
+				m.NotGroupLikeDelimited = nil
+			case fieldOptionalNestedMessage:
+				m.OptionalNestedMessage = nil
+			case fieldOptionalForeignMessage:
+				m.OptionalForeignMessage = nil
+			case fieldOptionalImportMessage:
+				m.OptionalImportMessage = nil
+			case fieldOptionalNestedEnum:
+				m.OptionalNestedEnum = nil
+			case fieldOptionalForeignEnum:
+				m.OptionalForeignEnum = nil
+			case fieldOptionalImportEnum:
+				m.OptionalImportEnum = nil
+			case fieldOptionalLazyNestedMessage:
+				m.OptionalLazyNestedMessage = nil
+
+			case fieldRepeatedInt32:
+				m.RepeatedInt32 = nil
+			case fieldRepeatedInt64:
+				m.RepeatedInt64 = nil
+			case fieldRepeatedUint32:
+				m.RepeatedUint32 = nil
+			case fieldRepeatedUint64:
+				m.RepeatedUint64 = nil
+			case fieldRepeatedSint32:
+				m.RepeatedSint32 = nil
+			case fieldRepeatedSint64:
+				m.RepeatedSint64 = nil
+			case fieldRepeatedFixed32:
+				m.RepeatedFixed32 = nil
+			case fieldRepeatedFixed64:
+				m.RepeatedFixed64 = nil
+			case fieldRepeatedSfixed32:
+				m.RepeatedSfixed32 = nil
+			case fieldRepeatedSfixed64:
+				m.RepeatedSfixed64 = nil
+			case fieldRepeatedFloat:
+				m.RepeatedFloat = nil
+			case fieldRepeatedDouble:
+				m.RepeatedDouble = nil
+			case fieldRepeatedBool:
+				m.RepeatedBool = nil
+			case fieldRepeatedString:
+				m.RepeatedString = nil
+			case fieldRepeatedBytes:
+				m.RepeatedBytes = nil
+			case fieldRepeatedGroup:
+				m.Repeatedgroup = nil
+			case fieldRepeatedNestedMessage:
+				m.RepeatedNestedMessage = nil
+			case fieldRepeatedForeignMessage:
+				m.RepeatedForeignMessage = nil
+			case fieldRepeatedImportMessage:
+				m.RepeatedImportmessage = nil
+			case fieldRepeatedNestedEnum:
+				m.RepeatedNestedEnum = nil
+			case fieldRepeatedForeignEnum:
+				m.RepeatedForeignEnum = nil
+			case fieldRepeatedImportEnum:
+				m.RepeatedImportenum = nil
+
+			case fieldMapInt32Int32:
+				m.MapInt32Int32 = nil
+			case fieldMapInt64Int64:
+				m.MapInt64Int64 = nil
+			case fieldMapUint32Uint32:
+				m.MapUint32Uint32 = nil
+			case fieldMapUint64Uint64:
+				m.MapUint64Uint64 = nil
+			case fieldMapSint32Sint32:
+				m.MapSint32Sint32 = nil
+			case fieldMapSint64Sint64:
+				m.MapSint64Sint64 = nil
+			case fieldMapFixed32Fixed32:
+				m.MapFixed32Fixed32 = nil
+			case fieldMapFixed64Fixed64:
+				m.MapFixed64Fixed64 = nil
+			case fieldMapSfixed32Sfixed32:
+				m.MapSfixed32Sfixed32 = nil
+			case fieldMapSfixed64Sfixed64:
+				m.MapSfixed64Sfixed64 = nil
+			case fieldMapInt32Float:
+				m.MapInt32Float = nil
+			case fieldMapInt32Double:
+				m.MapInt32Double = nil
+			case fieldMapBoolBool:
+				m.MapBoolBool = nil
+			case fieldMapStringString:
+				m.MapStringString = nil
+			case fieldMapStringBytes:
+				m.MapStringBytes = nil
+			case fieldMapStringNestedMessage:
+				m.MapStringNestedMessage = nil
+			case fieldMapStringNestedEnum:
+				m.MapStringNestedEnum = nil
+
+			case fieldDefaultInt32:
+				m.DefaultInt32 = nil
+			case fieldDefaultInt64:
+				m.DefaultInt64 = nil
+			case fieldDefaultUint32:
+				m.DefaultUint32 = nil
+			case fieldDefaultUint64:
+				m.DefaultUint64 = nil
+			case fieldDefaultSint32:
+				m.DefaultSint32 = nil
+			case fieldDefaultSint64:
+				m.DefaultSint64 = nil
+			case fieldDefaultFixed32:
+				m.DefaultFixed32 = nil
+			case fieldDefaultFixed64:
+				m.DefaultFixed64 = nil
+			case fieldDefaultSfixed32:
+				m.DefaultSfixed32 = nil
+			case fieldDefaultSfixed64:
+				m.DefaultSfixed64 = nil
+			case fieldDefaultFloat:
+				m.DefaultFloat = nil
+			case fieldDefaultDouble:
+				m.DefaultDouble = nil
+			case fieldDefaultBool:
+				m.DefaultBool = nil
+			case fieldDefaultString:
+				m.DefaultString = nil
+			case fieldDefaultBytes:
+				m.DefaultBytes = nil
+			case fieldDefaultNestedEnum:
+				m.DefaultNestedEnum = nil
+			case fieldDefaultForeignEnum:
+				m.DefaultForeignEnum = nil
+
+			case fieldOneofUint32:
+				m.OneofField = nil
+			case fieldOneofNestedMessage:
+				m.OneofField = nil
+			case fieldOneofString:
+				m.OneofField = nil
+			case fieldOneofBytes:
+				m.OneofField = nil
+			case fieldOneofBool:
+				m.OneofField = nil
+			case fieldOneofUint64:
+				m.OneofField = nil
+			case fieldOneofFloat:
+				m.OneofField = nil
+			case fieldOneofDouble:
+				m.OneofField = nil
+			case fieldOneofEnum:
+				m.OneofField = nil
+			case fieldOneofGroup:
+				m.OneofField = nil
+			case fieldOneofOptionalUint32:
+				m.OneofOptional = nil
+
+			default:
+				panic(fmt.Sprintf("clear: unknown field %d", num))
+			}
+		},
+	}
+}
diff --git a/internal/reflection_test/reflection_repeated_test.go b/internal/reflection_test/reflection_repeated_test.go
new file mode 100644
index 0000000..9f4f320
--- /dev/null
+++ b/internal/reflection_test/reflection_repeated_test.go
@@ -0,0 +1,43 @@
+// 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 reflection_test
+
+import (
+	"testing"
+
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+)
+
+func TestOpaqueRepeated(t *testing.T) {
+	m := testopaquepb.TestAllTypes_builder{
+		RepeatedNestedMessage: []*testopaquepb.TestAllTypes_NestedMessage{
+			testopaquepb.TestAllTypes_NestedMessage_builder{
+				A: proto.Int32(42),
+			}.Build(),
+		},
+	}.Build()
+
+	// Clear the repeated_nested_message field. This should not clear the presence bit.
+	mr := m.ProtoReflect()
+	fd := mr.Descriptor().Fields().ByNumber(48)
+	mr.Clear(fd)
+	if len(m.GetRepeatedNestedMessage()) != 0 {
+		t.Errorf("protoreflect Clear did not empty the repeated field: got %v, expected []", m.GetRepeatedNestedMessage())
+	}
+
+	// Append a new submessage to the input field and set its A field to 23.
+	dst := mr.Mutable(fd).List()
+	v := dst.NewElement()
+	dst.Append(v)
+	if len(m.GetRepeatedNestedMessage()) != 1 {
+		t.Fatalf("unexpected number of elements in repeated field: got %v, expected 1", len(m.GetRepeatedNestedMessage()))
+	}
+	m.GetRepeatedNestedMessage()[0].SetA(23)
+
+	if mr.Get(fd).List().Len() != 1 {
+		t.Fatalf("presence bit (incorrectly) cleared")
+	}
+}
diff --git a/internal/reflection_test/reflection_test.go b/internal/reflection_test/reflection_test.go
new file mode 100644
index 0000000..53f6491
--- /dev/null
+++ b/internal/reflection_test/reflection_test.go
@@ -0,0 +1,768 @@
+// 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 reflection_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	testopenpb "google.golang.org/protobuf/internal/testprotos/testeditions"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/runtime/protoiface"
+	"google.golang.org/protobuf/testing/prototest"
+)
+
+func Test(t *testing.T) {
+	t.Skip()
+	for _, m := range []protoreflect.ProtoMessage{
+		&testopenpb.TestAllTypes{},
+	} {
+		t.Run(fmt.Sprintf("%T", m), func(t *testing.T) {
+			prototest.Message{}.Test(t, m.ProtoReflect().Type())
+		})
+	}
+}
+
+// What follows is infrastructure for a complicated but useful set of tests
+// of different views of a message.
+//
+// Every Protobuf message can be accessed in at least two ways:
+//
+//   - m:                a concrete open, hybrid or opaque message
+//   - m.ProtoReflect(): reflective view of the message
+//
+// A mutation to one representation must be reflected in the others.
+//
+// To test the various views of a message, we construct an implementations of
+// the protoreflect.Message interface for each. The simplest is the canonical
+// reflective view provided by the ProtoReflect method. In addition, for each
+// concrete representation we create another view backed by that concrete API.
+// (i.e., m.ProtoReflect().KnownFields().Get(1) directly translates to a call
+// to m.GetFieldOne().)
+//
+// Finally, we construct a "shadow" view in which read operations are backed
+// by one implementation and write operations by another.
+//
+// Each of these various views may then be passed to the prototest package
+// for validation.
+//
+// This approach separates the decision of what behaviors to test from the
+// implementations being tested; new validation tests added to prototest
+// apply to all the various views without additional effort. The disadvantage
+// is that there is quite a bit of per-message boilerplate required.
+//
+// We could attempt to reduce that boilerplate by use of reflection or code
+// generation, but both approaches replace simple-but-repetitive code with
+// something quite complex. Since the purpose of all this is to test the
+// complex, general-purpose canonical implementation, the simple approach
+// is safer.
+
+// Field numbers for the test messages.
+var (
+	largeMessageDesc protoreflect.MessageDescriptor = (&testopenpb.TestManyMessageFieldsMessage{}).ProtoReflect().Descriptor()
+
+	largeFieldF1   = largeMessageDesc.Fields().ByName("f1").Number()
+	largeFieldF2   = largeMessageDesc.Fields().ByName("f2").Number()
+	largeFieldF3   = largeMessageDesc.Fields().ByName("f3").Number()
+	largeFieldF4   = largeMessageDesc.Fields().ByName("f4").Number()
+	largeFieldF5   = largeMessageDesc.Fields().ByName("f5").Number()
+	largeFieldF6   = largeMessageDesc.Fields().ByName("f6").Number()
+	largeFieldF7   = largeMessageDesc.Fields().ByName("f7").Number()
+	largeFieldF8   = largeMessageDesc.Fields().ByName("f8").Number()
+	largeFieldF9   = largeMessageDesc.Fields().ByName("f9").Number()
+	largeFieldF10  = largeMessageDesc.Fields().ByName("f10").Number()
+	largeFieldF11  = largeMessageDesc.Fields().ByName("f11").Number()
+	largeFieldF12  = largeMessageDesc.Fields().ByName("f12").Number()
+	largeFieldF13  = largeMessageDesc.Fields().ByName("f13").Number()
+	largeFieldF14  = largeMessageDesc.Fields().ByName("f14").Number()
+	largeFieldF15  = largeMessageDesc.Fields().ByName("f15").Number()
+	largeFieldF16  = largeMessageDesc.Fields().ByName("f16").Number()
+	largeFieldF17  = largeMessageDesc.Fields().ByName("f17").Number()
+	largeFieldF18  = largeMessageDesc.Fields().ByName("f18").Number()
+	largeFieldF19  = largeMessageDesc.Fields().ByName("f19").Number()
+	largeFieldF20  = largeMessageDesc.Fields().ByName("f20").Number()
+	largeFieldF21  = largeMessageDesc.Fields().ByName("f21").Number()
+	largeFieldF22  = largeMessageDesc.Fields().ByName("f22").Number()
+	largeFieldF23  = largeMessageDesc.Fields().ByName("f23").Number()
+	largeFieldF24  = largeMessageDesc.Fields().ByName("f24").Number()
+	largeFieldF25  = largeMessageDesc.Fields().ByName("f25").Number()
+	largeFieldF26  = largeMessageDesc.Fields().ByName("f26").Number()
+	largeFieldF27  = largeMessageDesc.Fields().ByName("f27").Number()
+	largeFieldF28  = largeMessageDesc.Fields().ByName("f28").Number()
+	largeFieldF29  = largeMessageDesc.Fields().ByName("f29").Number()
+	largeFieldF30  = largeMessageDesc.Fields().ByName("f30").Number()
+	largeFieldF31  = largeMessageDesc.Fields().ByName("f31").Number()
+	largeFieldF32  = largeMessageDesc.Fields().ByName("f32").Number()
+	largeFieldF33  = largeMessageDesc.Fields().ByName("f33").Number()
+	largeFieldF34  = largeMessageDesc.Fields().ByName("f34").Number()
+	largeFieldF35  = largeMessageDesc.Fields().ByName("f35").Number()
+	largeFieldF36  = largeMessageDesc.Fields().ByName("f36").Number()
+	largeFieldF37  = largeMessageDesc.Fields().ByName("f37").Number()
+	largeFieldF38  = largeMessageDesc.Fields().ByName("f38").Number()
+	largeFieldF39  = largeMessageDesc.Fields().ByName("f39").Number()
+	largeFieldF40  = largeMessageDesc.Fields().ByName("f40").Number()
+	largeFieldF41  = largeMessageDesc.Fields().ByName("f41").Number()
+	largeFieldF42  = largeMessageDesc.Fields().ByName("f42").Number()
+	largeFieldF43  = largeMessageDesc.Fields().ByName("f43").Number()
+	largeFieldF44  = largeMessageDesc.Fields().ByName("f44").Number()
+	largeFieldF45  = largeMessageDesc.Fields().ByName("f45").Number()
+	largeFieldF46  = largeMessageDesc.Fields().ByName("f46").Number()
+	largeFieldF47  = largeMessageDesc.Fields().ByName("f47").Number()
+	largeFieldF48  = largeMessageDesc.Fields().ByName("f48").Number()
+	largeFieldF49  = largeMessageDesc.Fields().ByName("f49").Number()
+	largeFieldF50  = largeMessageDesc.Fields().ByName("f50").Number()
+	largeFieldF51  = largeMessageDesc.Fields().ByName("f51").Number()
+	largeFieldF52  = largeMessageDesc.Fields().ByName("f52").Number()
+	largeFieldF53  = largeMessageDesc.Fields().ByName("f53").Number()
+	largeFieldF54  = largeMessageDesc.Fields().ByName("f54").Number()
+	largeFieldF55  = largeMessageDesc.Fields().ByName("f55").Number()
+	largeFieldF56  = largeMessageDesc.Fields().ByName("f56").Number()
+	largeFieldF57  = largeMessageDesc.Fields().ByName("f57").Number()
+	largeFieldF58  = largeMessageDesc.Fields().ByName("f58").Number()
+	largeFieldF59  = largeMessageDesc.Fields().ByName("f59").Number()
+	largeFieldF60  = largeMessageDesc.Fields().ByName("f60").Number()
+	largeFieldF61  = largeMessageDesc.Fields().ByName("f61").Number()
+	largeFieldF62  = largeMessageDesc.Fields().ByName("f62").Number()
+	largeFieldF63  = largeMessageDesc.Fields().ByName("f63").Number()
+	largeFieldF64  = largeMessageDesc.Fields().ByName("f64").Number()
+	largeFieldF65  = largeMessageDesc.Fields().ByName("f65").Number()
+	largeFieldF66  = largeMessageDesc.Fields().ByName("f66").Number()
+	largeFieldF67  = largeMessageDesc.Fields().ByName("f67").Number()
+	largeFieldF68  = largeMessageDesc.Fields().ByName("f68").Number()
+	largeFieldF69  = largeMessageDesc.Fields().ByName("f69").Number()
+	largeFieldF70  = largeMessageDesc.Fields().ByName("f70").Number()
+	largeFieldF71  = largeMessageDesc.Fields().ByName("f71").Number()
+	largeFieldF72  = largeMessageDesc.Fields().ByName("f72").Number()
+	largeFieldF73  = largeMessageDesc.Fields().ByName("f73").Number()
+	largeFieldF74  = largeMessageDesc.Fields().ByName("f74").Number()
+	largeFieldF75  = largeMessageDesc.Fields().ByName("f75").Number()
+	largeFieldF76  = largeMessageDesc.Fields().ByName("f76").Number()
+	largeFieldF77  = largeMessageDesc.Fields().ByName("f77").Number()
+	largeFieldF78  = largeMessageDesc.Fields().ByName("f78").Number()
+	largeFieldF79  = largeMessageDesc.Fields().ByName("f79").Number()
+	largeFieldF80  = largeMessageDesc.Fields().ByName("f80").Number()
+	largeFieldF81  = largeMessageDesc.Fields().ByName("f81").Number()
+	largeFieldF82  = largeMessageDesc.Fields().ByName("f82").Number()
+	largeFieldF83  = largeMessageDesc.Fields().ByName("f83").Number()
+	largeFieldF84  = largeMessageDesc.Fields().ByName("f84").Number()
+	largeFieldF85  = largeMessageDesc.Fields().ByName("f85").Number()
+	largeFieldF86  = largeMessageDesc.Fields().ByName("f86").Number()
+	largeFieldF87  = largeMessageDesc.Fields().ByName("f87").Number()
+	largeFieldF88  = largeMessageDesc.Fields().ByName("f88").Number()
+	largeFieldF89  = largeMessageDesc.Fields().ByName("f89").Number()
+	largeFieldF90  = largeMessageDesc.Fields().ByName("f90").Number()
+	largeFieldF91  = largeMessageDesc.Fields().ByName("f91").Number()
+	largeFieldF92  = largeMessageDesc.Fields().ByName("f92").Number()
+	largeFieldF93  = largeMessageDesc.Fields().ByName("f93").Number()
+	largeFieldF94  = largeMessageDesc.Fields().ByName("f94").Number()
+	largeFieldF95  = largeMessageDesc.Fields().ByName("f95").Number()
+	largeFieldF96  = largeMessageDesc.Fields().ByName("f96").Number()
+	largeFieldF97  = largeMessageDesc.Fields().ByName("f97").Number()
+	largeFieldF98  = largeMessageDesc.Fields().ByName("f98").Number()
+	largeFieldF99  = largeMessageDesc.Fields().ByName("f99").Number()
+	largeFieldF100 = largeMessageDesc.Fields().ByName("f100").Number()
+)
+
+var (
+	messageDesc protoreflect.MessageDescriptor = (&testopenpb.TestAllTypes{}).ProtoReflect().Descriptor()
+
+	fieldSingularInt32       = messageDesc.Fields().ByName("singular_int32").Number()
+	fieldSingularInt64       = messageDesc.Fields().ByName("singular_int64").Number()
+	fieldSingularUint32      = messageDesc.Fields().ByName("singular_uint32").Number()
+	fieldSingularUint64      = messageDesc.Fields().ByName("singular_uint64").Number()
+	fieldSingularSint32      = messageDesc.Fields().ByName("singular_sint32").Number()
+	fieldSingularSint64      = messageDesc.Fields().ByName("singular_sint64").Number()
+	fieldSingularFixed32     = messageDesc.Fields().ByName("singular_fixed32").Number()
+	fieldSingularFixed64     = messageDesc.Fields().ByName("singular_fixed64").Number()
+	fieldSingularSfixed32    = messageDesc.Fields().ByName("singular_sfixed32").Number()
+	fieldSingularSfixed64    = messageDesc.Fields().ByName("singular_sfixed64").Number()
+	fieldSingularFloat       = messageDesc.Fields().ByName("singular_float").Number()
+	fieldSingularDouble      = messageDesc.Fields().ByName("singular_double").Number()
+	fieldSingularBool        = messageDesc.Fields().ByName("singular_bool").Number()
+	fieldSingularString      = messageDesc.Fields().ByName("singular_string").Number()
+	fieldSingularBytes       = messageDesc.Fields().ByName("singular_bytes").Number()
+	fieldSingularNestedEnum  = messageDesc.Fields().ByName("singular_nested_enum").Number()
+	fieldSingularForeignEnum = messageDesc.Fields().ByName("singular_foreign_enum").Number()
+	fieldSingularImportEnum  = messageDesc.Fields().ByName("singular_import_enum").Number()
+
+	fieldOptionalInt32             = messageDesc.Fields().ByName("optional_int32").Number()
+	fieldOptionalInt64             = messageDesc.Fields().ByName("optional_int64").Number()
+	fieldOptionalUint32            = messageDesc.Fields().ByName("optional_uint32").Number()
+	fieldOptionalUint64            = messageDesc.Fields().ByName("optional_uint64").Number()
+	fieldOptionalSint32            = messageDesc.Fields().ByName("optional_sint32").Number()
+	fieldOptionalSint64            = messageDesc.Fields().ByName("optional_sint64").Number()
+	fieldOptionalFixed32           = messageDesc.Fields().ByName("optional_fixed32").Number()
+	fieldOptionalFixed64           = messageDesc.Fields().ByName("optional_fixed64").Number()
+	fieldOptionalSfixed32          = messageDesc.Fields().ByName("optional_sfixed32").Number()
+	fieldOptionalSfixed64          = messageDesc.Fields().ByName("optional_sfixed64").Number()
+	fieldOptionalFloat             = messageDesc.Fields().ByName("optional_float").Number()
+	fieldOptionalDouble            = messageDesc.Fields().ByName("optional_double").Number()
+	fieldOptionalBool              = messageDesc.Fields().ByName("optional_bool").Number()
+	fieldOptionalString            = messageDesc.Fields().ByName("optional_string").Number()
+	fieldOptionalBytes             = messageDesc.Fields().ByName("optional_bytes").Number()
+	fieldOptionalGroup             = messageDesc.Fields().ByName("optionalgroup").Number()
+	fieldOptionalNestedMessage     = messageDesc.Fields().ByName("optional_nested_message").Number()
+	fieldOptionalForeignMessage    = messageDesc.Fields().ByName("optional_foreign_message").Number()
+	fieldOptionalImportMessage     = messageDesc.Fields().ByName("optional_import_message").Number()
+	fieldOptionalNestedEnum        = messageDesc.Fields().ByName("optional_nested_enum").Number()
+	fieldOptionalForeignEnum       = messageDesc.Fields().ByName("optional_foreign_enum").Number()
+	fieldOptionalImportEnum        = messageDesc.Fields().ByName("optional_import_enum").Number()
+	fieldOptionalLazyNestedMessage = messageDesc.Fields().ByName("optional_lazy_nested_message").Number()
+	fieldNotGroupLikeDelimited     = messageDesc.Fields().ByName("not_group_like_delimited").Number()
+
+	fieldRepeatedInt32          = messageDesc.Fields().ByName("repeated_int32").Number()
+	fieldRepeatedInt64          = messageDesc.Fields().ByName("repeated_int64").Number()
+	fieldRepeatedUint32         = messageDesc.Fields().ByName("repeated_uint32").Number()
+	fieldRepeatedUint64         = messageDesc.Fields().ByName("repeated_uint64").Number()
+	fieldRepeatedSint32         = messageDesc.Fields().ByName("repeated_sint32").Number()
+	fieldRepeatedSint64         = messageDesc.Fields().ByName("repeated_sint64").Number()
+	fieldRepeatedFixed32        = messageDesc.Fields().ByName("repeated_fixed32").Number()
+	fieldRepeatedFixed64        = messageDesc.Fields().ByName("repeated_fixed64").Number()
+	fieldRepeatedSfixed32       = messageDesc.Fields().ByName("repeated_sfixed32").Number()
+	fieldRepeatedSfixed64       = messageDesc.Fields().ByName("repeated_sfixed64").Number()
+	fieldRepeatedFloat          = messageDesc.Fields().ByName("repeated_float").Number()
+	fieldRepeatedDouble         = messageDesc.Fields().ByName("repeated_double").Number()
+	fieldRepeatedBool           = messageDesc.Fields().ByName("repeated_bool").Number()
+	fieldRepeatedString         = messageDesc.Fields().ByName("repeated_string").Number()
+	fieldRepeatedBytes          = messageDesc.Fields().ByName("repeated_bytes").Number()
+	fieldRepeatedGroup          = messageDesc.Fields().ByName("repeatedgroup").Number()
+	fieldRepeatedNestedMessage  = messageDesc.Fields().ByName("repeated_nested_message").Number()
+	fieldRepeatedForeignMessage = messageDesc.Fields().ByName("repeated_foreign_message").Number()
+	fieldRepeatedImportMessage  = messageDesc.Fields().ByName("repeated_importmessage").Number()
+	fieldRepeatedNestedEnum     = messageDesc.Fields().ByName("repeated_nested_enum").Number()
+	fieldRepeatedForeignEnum    = messageDesc.Fields().ByName("repeated_foreign_enum").Number()
+	fieldRepeatedImportEnum     = messageDesc.Fields().ByName("repeated_importenum").Number()
+
+	fieldMapInt32Int32          = messageDesc.Fields().ByName("map_int32_int32").Number()
+	fieldMapInt64Int64          = messageDesc.Fields().ByName("map_int64_int64").Number()
+	fieldMapUint32Uint32        = messageDesc.Fields().ByName("map_uint32_uint32").Number()
+	fieldMapUint64Uint64        = messageDesc.Fields().ByName("map_uint64_uint64").Number()
+	fieldMapSint32Sint32        = messageDesc.Fields().ByName("map_sint32_sint32").Number()
+	fieldMapSint64Sint64        = messageDesc.Fields().ByName("map_sint64_sint64").Number()
+	fieldMapFixed32Fixed32      = messageDesc.Fields().ByName("map_fixed32_fixed32").Number()
+	fieldMapFixed64Fixed64      = messageDesc.Fields().ByName("map_fixed64_fixed64").Number()
+	fieldMapSfixed32Sfixed32    = messageDesc.Fields().ByName("map_sfixed32_sfixed32").Number()
+	fieldMapSfixed64Sfixed64    = messageDesc.Fields().ByName("map_sfixed64_sfixed64").Number()
+	fieldMapInt32Float          = messageDesc.Fields().ByName("map_int32_float").Number()
+	fieldMapInt32Double         = messageDesc.Fields().ByName("map_int32_double").Number()
+	fieldMapBoolBool            = messageDesc.Fields().ByName("map_bool_bool").Number()
+	fieldMapStringString        = messageDesc.Fields().ByName("map_string_string").Number()
+	fieldMapStringBytes         = messageDesc.Fields().ByName("map_string_bytes").Number()
+	fieldMapStringNestedMessage = messageDesc.Fields().ByName("map_string_nested_message").Number()
+	fieldMapStringNestedEnum    = messageDesc.Fields().ByName("map_string_nested_enum").Number()
+
+	fieldDefaultInt32       = messageDesc.Fields().ByName("default_int32").Number()
+	fieldDefaultInt64       = messageDesc.Fields().ByName("default_int64").Number()
+	fieldDefaultUint32      = messageDesc.Fields().ByName("default_uint32").Number()
+	fieldDefaultUint64      = messageDesc.Fields().ByName("default_uint64").Number()
+	fieldDefaultSint32      = messageDesc.Fields().ByName("default_sint32").Number()
+	fieldDefaultSint64      = messageDesc.Fields().ByName("default_sint64").Number()
+	fieldDefaultFixed32     = messageDesc.Fields().ByName("default_fixed32").Number()
+	fieldDefaultFixed64     = messageDesc.Fields().ByName("default_fixed64").Number()
+	fieldDefaultSfixed32    = messageDesc.Fields().ByName("default_sfixed32").Number()
+	fieldDefaultSfixed64    = messageDesc.Fields().ByName("default_sfixed64").Number()
+	fieldDefaultFloat       = messageDesc.Fields().ByName("default_float").Number()
+	fieldDefaultDouble      = messageDesc.Fields().ByName("default_double").Number()
+	fieldDefaultBool        = messageDesc.Fields().ByName("default_bool").Number()
+	fieldDefaultString      = messageDesc.Fields().ByName("default_string").Number()
+	fieldDefaultBytes       = messageDesc.Fields().ByName("default_bytes").Number()
+	fieldDefaultNestedEnum  = messageDesc.Fields().ByName("default_nested_enum").Number()
+	fieldDefaultForeignEnum = messageDesc.Fields().ByName("default_foreign_enum").Number()
+
+	fieldOneofUint32         = messageDesc.Fields().ByName("oneof_uint32").Number()
+	fieldOneofNestedMessage  = messageDesc.Fields().ByName("oneof_nested_message").Number()
+	fieldOneofString         = messageDesc.Fields().ByName("oneof_string").Number()
+	fieldOneofBytes          = messageDesc.Fields().ByName("oneof_bytes").Number()
+	fieldOneofBool           = messageDesc.Fields().ByName("oneof_bool").Number()
+	fieldOneofUint64         = messageDesc.Fields().ByName("oneof_uint64").Number()
+	fieldOneofFloat          = messageDesc.Fields().ByName("oneof_float").Number()
+	fieldOneofDouble         = messageDesc.Fields().ByName("oneof_double").Number()
+	fieldOneofEnum           = messageDesc.Fields().ByName("oneof_enum").Number()
+	fieldOneofGroup          = messageDesc.Fields().ByName("oneofgroup").Number()
+	fieldOneofOptionalUint32 = messageDesc.Fields().ByName("oneof_optional_uint32").Number()
+)
+
+// testMessageType is an implementation of protoreflect.MessageType.
+type testMessageType struct {
+	protoreflect.MessageDescriptor
+	new func() protoreflect.Message
+}
+
+func (m *testMessageType) New() protoreflect.Message                  { return m.new() }
+func (m *testMessageType) Zero() protoreflect.Message                 { return m.new() }
+func (m *testMessageType) GoType() reflect.Type                       { panic("unimplemented") }
+func (m *testMessageType) Descriptor() protoreflect.MessageDescriptor { return m.MessageDescriptor }
+
+// testProtoMessage adapts the concrete API for a message to the ProtoReflect interface.
+type testProtoMessage struct {
+	m     protoreflect.ProtoMessage
+	md    protoreflect.MessageDescriptor
+	new   func() protoreflect.Message
+	has   func(protoreflect.FieldNumber) bool
+	get   func(protoreflect.FieldNumber) any
+	set   func(protoreflect.FieldNumber, any)
+	clear func(protoreflect.FieldNumber)
+}
+
+func (m *testProtoMessage) ProtoReflect() protoreflect.Message { return (*testMessage)(m) }
+
+// testMessage implements protoreflect.Message.
+type testMessage testProtoMessage
+
+func (m *testMessage) Interface() protoreflect.ProtoMessage       { return (*testProtoMessage)(m) }
+func (m *testMessage) ProtoMethods() *protoiface.Methods          { return nil }
+func (m *testMessage) Descriptor() protoreflect.MessageDescriptor { return m.md }
+func (m *testMessage) Type() protoreflect.MessageType             { return &testMessageType{m.md, m.new} }
+func (m *testMessage) New() protoreflect.Message                  { return m.new() }
+func (m *testMessage) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
+	fields := m.md.Fields()
+	for i := 0; i < fields.Len(); i++ {
+		fd := fields.Get(i)
+		if !m.Has(fd) {
+			continue
+		}
+		if !f(fd, m.Get(fd)) {
+			break
+		}
+	}
+}
+func (m *testMessage) Has(fd protoreflect.FieldDescriptor) bool {
+	return m.has(fd.Number())
+}
+func (m *testMessage) Clear(fd protoreflect.FieldDescriptor) {
+	m.clear(fd.Number())
+}
+func (m *testMessage) Get(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	num := fd.Number()
+	switch {
+	case fd.IsMap():
+		if !m.has(num) {
+			return protoreflect.ValueOfMap(&testMap{reflect.Zero(reflect.TypeOf(m.get(num))), fd})
+		}
+		return protoreflect.ValueOfMap(&testMap{reflect.ValueOf(m.get(num)), fd})
+	case fd.IsList():
+		if !m.has(num) {
+			return protoreflect.ValueOfList(&zeroList{reflect.TypeOf(m.get(num)).Elem(), fd})
+		}
+		return protoreflect.ValueOfList(&testList{m: (*testProtoMessage)(m), fd: fd})
+	case fd.Message() != nil:
+		if !m.has(fd.Number()) {
+			return protoreflect.Value{}
+		}
+	}
+	return singularValueOf(m.get(num))
+}
+func (m *testMessage) Set(fd protoreflect.FieldDescriptor, v protoreflect.Value) {
+	num := fd.Number()
+	switch {
+	case fd.IsMap():
+		if !v.Map().IsValid() {
+			panic("set with invalid map")
+		}
+		m.set(num, v.Map().(*testMap).val.Interface())
+	case fd.IsList():
+		if !v.List().IsValid() {
+			panic("set with invalid list")
+		}
+		m.set(num, v.List().(*testList).field().Interface())
+	case fd.Message() != nil:
+		i := v.Message().Interface()
+		if p, ok := i.(*testProtoMessage); ok {
+			i = p.m
+		}
+		m.set(num, i)
+	default:
+		m.set(num, v.Interface())
+	}
+}
+func (m *testMessage) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	num := fd.Number()
+	if !m.Has(fd) && (fd.IsMap() || fd.IsList() || fd.Message() != nil) {
+		switch {
+		case fd.IsMap():
+			typ := reflect.ValueOf(m.get(num)).Type()
+			m.set(num, reflect.MakeMap(typ).Interface())
+			return protoreflect.ValueOfMap(&testMap{reflect.ValueOf(m.get(num)), fd})
+		case fd.IsList():
+			return protoreflect.ValueOfList(&testList{m: (*testProtoMessage)(m), fd: fd})
+		case fd.Message() != nil:
+			typ := reflect.ValueOf(m.get(num)).Type()
+			m.set(num, reflect.New(typ.Elem()).Interface())
+		}
+	}
+	return m.Get(fd)
+}
+func (m *testMessage) NewMessage(fd protoreflect.FieldDescriptor) protoreflect.Message {
+	return singularValueOf(m.NewField(fd)).Message()
+}
+func (m *testMessage) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	num := fd.Number()
+	switch {
+	case fd.IsMap():
+		typ := reflect.ValueOf(m.get(num)).Type()
+		return protoreflect.ValueOf(&testMap{reflect.MakeMap(typ), fd})
+	case fd.IsList():
+		typ := reflect.ValueOf(m.get(num)).Type()
+		return protoreflect.ValueOf(&testList{val: reflect.Zero(typ), fd: fd})
+	case fd.Message() != nil:
+		typ := reflect.ValueOf(m.get(num)).Type()
+		return singularValueOf(reflect.New(typ.Elem()).Interface())
+	default:
+		// Obtain the default value of the field by creating an empty message
+		// and calling the getter.
+		n := m.new().(*testMessage)
+		return singularValueOf(n.get(num))
+	}
+}
+func (m *testMessage) WhichOneof(od protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
+	for i := 0; i < od.Fields().Len(); i++ {
+		fd := od.Fields().Get(i)
+		if m.has(fd.Number()) {
+			return fd
+		}
+	}
+	return nil
+}
+func (m *testMessage) GetUnknown() protoreflect.RawFields {
+	return m.m.ProtoReflect().GetUnknown()
+}
+func (m *testMessage) SetUnknown(raw protoreflect.RawFields) {
+	m.m.ProtoReflect().SetUnknown(raw)
+}
+func (m *testMessage) IsValid() bool {
+	return !reflect.ValueOf(m.m).IsNil()
+}
+
+func singularValueOf(v any) protoreflect.Value {
+	switch v := v.(type) {
+	case protoreflect.ProtoMessage:
+		return protoreflect.ValueOf(v.ProtoReflect())
+	case protoreflect.Enum:
+		return protoreflect.ValueOf(v.Number())
+	default:
+		return protoreflect.ValueOf(v)
+	}
+}
+
+// testList implements protoreflect.List over a concrete slice of values.
+type testList struct {
+	m   *testProtoMessage
+	val reflect.Value
+	fd  protoreflect.FieldDescriptor
+}
+
+func (x *testList) field() reflect.Value {
+	if x.m == nil {
+		return x.val
+	}
+	return reflect.ValueOf(x.m.get(x.fd.Number()))
+}
+func (x *testList) setField(v reflect.Value) {
+	if x.m == nil {
+		x.val = v
+		return
+	}
+	x.m.set(x.fd.Number(), v.Interface())
+}
+func (x *testList) Len() int { return x.field().Len() }
+func (x *testList) Get(n int) protoreflect.Value {
+	return singularValueOf(x.field().Index(n).Interface())
+}
+func (x *testList) Set(n int, v protoreflect.Value) {
+	switch x.fd.Kind() {
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		x.field().Index(n).Set(reflect.ValueOf(v.Message().Interface()))
+	case protoreflect.EnumKind:
+		x.field().Index(n).SetInt(int64(v.Enum()))
+	default:
+		x.field().Index(n).Set(reflect.ValueOf(v.Interface()))
+	}
+}
+func (x *testList) Append(v protoreflect.Value) {
+	f := x.field()
+	x.setField(reflect.Append(f, reflect.Zero(f.Type().Elem())))
+	x.Set(f.Len(), v)
+}
+func (x *testList) AppendMutable() protoreflect.Value {
+	if x.fd.Message() == nil {
+		panic("invalid AppendMutable on list with non-message value type")
+	}
+	v := x.NewElement()
+	x.Append(v)
+	return v
+}
+func (x *testList) Truncate(n int) {
+	x.setField(x.field().Slice(0, n))
+}
+func (x *testList) NewMessage() protoreflect.Message {
+	return x.NewElement().Message()
+}
+func (x *testList) NewElement() protoreflect.Value {
+	// For enums, List.NewElement returns the first enum value.
+	if ee := newEnumElement(x.fd); ee.IsValid() {
+		return ee
+	}
+	var v reflect.Value
+	typ := x.field().Type().Elem()
+	if typ.Kind() == reflect.Ptr {
+		v = reflect.New(typ.Elem())
+	} else {
+		v = reflect.Zero(typ)
+	}
+	return singularValueOf(v.Interface())
+}
+func (x *testList) IsValid() bool {
+	return true
+}
+
+func newEnumElement(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	if fd.Kind() != protoreflect.EnumKind {
+		return protoreflect.Value{}
+	}
+	if val := fd.Enum().Values(); val.Len() > 0 {
+		return protoreflect.ValueOfEnum(val.Get(0).Number())
+	}
+	return protoreflect.Value{}
+}
+
+// testList implements protoreflect.List over a concrete slice of values.
+type zeroList struct {
+	typ reflect.Type
+	fd  protoreflect.FieldDescriptor
+}
+
+func (x *zeroList) Len() int                          { return 0 }
+func (x *zeroList) Get(n int) protoreflect.Value      { panic("get on zero list") }
+func (x *zeroList) Set(n int, v protoreflect.Value)   { panic("set on zero list") }
+func (x *zeroList) Append(v protoreflect.Value)       { panic("append on zero list") }
+func (x *zeroList) AppendMutable() protoreflect.Value { panic("append on zero list") }
+func (x *zeroList) Truncate(n int)                    { panic("truncate on zero list") }
+func (x *zeroList) NewMessage() protoreflect.Message {
+	return x.NewElement().Message()
+}
+func (x *zeroList) NewElement() protoreflect.Value {
+	// For enums, List.NewElement returns the first enum value.
+	if ee := newEnumElement(x.fd); ee.IsValid() {
+		return ee
+	}
+	var v reflect.Value
+	if x.typ.Kind() == reflect.Ptr {
+		v = reflect.New(x.typ.Elem())
+	} else {
+		v = reflect.Zero(x.typ)
+	}
+	return singularValueOf(v.Interface())
+}
+func (x *zeroList) IsValid() bool {
+	return false
+}
+
+// testMap implements a protoreflect.Map over a concrete map.
+type testMap struct {
+	val reflect.Value
+	fd  protoreflect.FieldDescriptor
+}
+
+func (x *testMap) key(k protoreflect.MapKey) reflect.Value { return reflect.ValueOf(k.Interface()) }
+func (x *testMap) valueToProto(v reflect.Value) protoreflect.Value {
+	if !v.IsValid() {
+		return protoreflect.Value{}
+	}
+	switch x.fd.Message().Fields().ByNumber(2).Kind() {
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		return protoreflect.ValueOf(v.Interface().(protoreflect.ProtoMessage).ProtoReflect())
+	case protoreflect.EnumKind:
+		return protoreflect.ValueOf(protoreflect.EnumNumber(v.Int()))
+	default:
+		return protoreflect.ValueOf(v.Interface())
+	}
+}
+func (x *testMap) Len() int                       { return x.val.Len() }
+func (x *testMap) Has(k protoreflect.MapKey) bool { return x.val.MapIndex(x.key(k)).IsValid() }
+func (x *testMap) Get(k protoreflect.MapKey) protoreflect.Value {
+	return x.valueToProto(x.val.MapIndex(x.key(k)))
+}
+func (x *testMap) Set(k protoreflect.MapKey, v protoreflect.Value) {
+	f := x.val
+	switch x.fd.MapValue().Kind() {
+	case protoreflect.MessageKind, protoreflect.GroupKind:
+		f.SetMapIndex(x.key(k), reflect.ValueOf(v.Message().Interface()))
+	case protoreflect.EnumKind:
+		rv := reflect.New(f.Type().Elem()).Elem()
+		rv.SetInt(int64(v.Enum()))
+		f.SetMapIndex(x.key(k), rv)
+	default:
+		f.SetMapIndex(x.key(k), reflect.ValueOf(v.Interface()))
+	}
+}
+func (x *testMap) Mutable(k protoreflect.MapKey) protoreflect.Value {
+	if x.fd.MapValue().Message() == nil {
+		panic("invalid Mutable on map with non-message value type")
+	}
+	v := x.Get(k)
+	if !v.IsValid() {
+		v = x.NewValue()
+		x.Set(k, v)
+	}
+	return v
+}
+func (x *testMap) Clear(k protoreflect.MapKey) { x.val.SetMapIndex(x.key(k), reflect.Value{}) }
+func (x *testMap) Range(f func(protoreflect.MapKey, protoreflect.Value) bool) {
+	iter := x.val.MapRange()
+	for iter.Next() {
+		if !f(protoreflect.ValueOf(iter.Key().Interface()).MapKey(), x.valueToProto(iter.Value())) {
+			return
+		}
+	}
+}
+func (x *testMap) NewMessage() protoreflect.Message {
+	return x.NewValue().Message()
+}
+func (x *testMap) NewValue() protoreflect.Value {
+	var v reflect.Value
+	if x.fd.MapValue().Message() != nil {
+		v = reflect.New(x.val.Type().Elem().Elem())
+	} else {
+		v = reflect.Zero(x.val.Type().Elem())
+	}
+	return singularValueOf(v.Interface())
+}
+func (x *testMap) IsValid() bool {
+	return !x.val.IsNil()
+}
+
+// A shadow message is a wrapper around two protoreflect.Message implementations
+// presenting different views of the same underlying data. Read operations
+// are directed to one implementation and write operations to the other.
+
+// shadowProtoMessage implements protoreflect.ProtoMessage as a shadow.
+type shadowProtoMessage struct {
+	get, set protoreflect.Message
+	new      func() (get, set protoreflect.ProtoMessage)
+}
+
+func newShadow(newf func() (get, set protoreflect.ProtoMessage)) protoreflect.ProtoMessage {
+	get, set := newf()
+	return &shadowProtoMessage{
+		get.ProtoReflect(),
+		set.ProtoReflect(),
+		newf,
+	}
+}
+
+func (m *shadowProtoMessage) ProtoReflect() protoreflect.Message { return (*shadowMessage)(m) }
+
+// shadowMessage implements protoreflect.Message as a shadow.
+type shadowMessage shadowProtoMessage
+
+func (m *shadowMessage) Interface() protoreflect.ProtoMessage       { return (*shadowProtoMessage)(m) }
+func (m *shadowMessage) ProtoMethods() *protoiface.Methods          { return nil }
+func (m *shadowMessage) Descriptor() protoreflect.MessageDescriptor { return m.get.Descriptor() }
+func (m *shadowMessage) Type() protoreflect.MessageType {
+	return &testMessageType{m.Descriptor(), m.New}
+}
+func (m *shadowMessage) New() protoreflect.Message {
+	get, set := m.new()
+	return &shadowMessage{
+		get: get.ProtoReflect(),
+		set: set.ProtoReflect(),
+		new: m.new,
+	}
+}
+
+// TODO: Implement these.
+func (m *shadowMessage) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
+	m.get.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
+		return f(fd, m.Get(fd))
+	})
+}
+func (m *shadowMessage) Has(fd protoreflect.FieldDescriptor) bool {
+	return m.get.Has(fd)
+}
+func (m *shadowMessage) Clear(fd protoreflect.FieldDescriptor) {
+	m.set.Clear(fd)
+}
+func (m *shadowMessage) Get(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	v := m.get.Get(fd)
+	switch {
+	case fd.IsList():
+		return protoreflect.ValueOfList(&shadowList{v.List(), m.set.Get(fd).List()})
+	case fd.IsMap():
+		return protoreflect.ValueOfMap(&shadowMap{v.Map(), m.set.Get(fd).Map()})
+	default:
+		return v
+	}
+}
+func (m *shadowMessage) Set(fd protoreflect.FieldDescriptor, v protoreflect.Value) {
+	switch x := v.Interface().(type) {
+	case *shadowList:
+		m.set.Set(fd, protoreflect.ValueOf(x.set))
+	case *shadowMap:
+		m.set.Set(fd, protoreflect.ValueOf(x.set))
+	default:
+		m.set.Set(fd, v)
+	}
+}
+func (m *shadowMessage) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	v := m.get.Mutable(fd)
+	switch {
+	case fd.IsList():
+		return protoreflect.ValueOf(&shadowList{v.List(), m.set.Mutable(fd).List()})
+	case fd.IsMap():
+		return protoreflect.ValueOf(&shadowMap{v.Map(), m.set.Mutable(fd).Map()})
+	default:
+		return v
+	}
+}
+func (m *shadowMessage) NewMessage(fd protoreflect.FieldDescriptor) protoreflect.Message {
+	return m.NewField(fd).Message()
+}
+func (m *shadowMessage) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value {
+	return m.set.NewField(fd)
+}
+func (m *shadowMessage) WhichOneof(od protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
+	return m.get.WhichOneof(od)
+}
+func (m *shadowMessage) GetUnknown() protoreflect.RawFields {
+	return m.get.GetUnknown()
+}
+func (m *shadowMessage) SetUnknown(raw protoreflect.RawFields) {
+	m.set.SetUnknown(raw)
+}
+func (m *shadowMessage) IsValid() bool {
+	return m.get.IsValid()
+}
+
+// shadowList implements protoreflect.List as a shadow.
+type shadowList struct {
+	get, set protoreflect.List
+}
+
+func (x *shadowList) Len() int                          { return x.get.Len() }
+func (x *shadowList) Get(n int) protoreflect.Value      { return x.get.Get(n) }
+func (x *shadowList) Set(n int, v protoreflect.Value)   { x.set.Set(n, v) }
+func (x *shadowList) Append(v protoreflect.Value)       { x.set.Append(v) }
+func (x *shadowList) AppendMutable() protoreflect.Value { return x.set.AppendMutable() }
+func (x *shadowList) Truncate(n int)                    { x.set.Truncate(n) }
+func (x *shadowList) NewMessage() protoreflect.Message  { return x.set.NewElement().Message() }
+func (x *shadowList) NewElement() protoreflect.Value    { return x.set.NewElement() }
+func (x *shadowList) IsValid() bool                     { return x.get.IsValid() }
+
+// shadowMap implements protoreflect.Map as a shadow.
+type shadowMap struct {
+	get, set protoreflect.Map
+}
+
+func (x *shadowMap) Len() int                                                   { return x.get.Len() }
+func (x *shadowMap) Has(k protoreflect.MapKey) bool                             { return x.get.Has(k) }
+func (x *shadowMap) Get(k protoreflect.MapKey) protoreflect.Value               { return x.get.Get(k) }
+func (x *shadowMap) Set(k protoreflect.MapKey, v protoreflect.Value)            { x.set.Set(k, v) }
+func (x *shadowMap) Mutable(k protoreflect.MapKey) protoreflect.Value           { return x.set.Mutable(k) }
+func (x *shadowMap) Clear(k protoreflect.MapKey)                                { x.set.Clear(k) }
+func (x *shadowMap) Range(f func(protoreflect.MapKey, protoreflect.Value) bool) { x.get.Range(f) }
+func (x *shadowMap) NewMessage() protoreflect.Message                           { return x.set.NewValue().Message() }
+func (x *shadowMap) NewValue() protoreflect.Value                               { return x.set.NewValue() }
+func (x *shadowMap) IsValid() bool                                              { return x.get.IsValid() }
diff --git a/internal/testprotos/enums/enums.proto b/internal/testprotos/enums/enums.proto
index b8452d4..2921ad0 100644
--- a/internal/testprotos/enums/enums.proto
+++ b/internal/testprotos/enums/enums.proto
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-syntax = "proto2";
+edition = "2023";
 
 package goproto.proto.enums;
 
 option go_package = "google.golang.org/protobuf/internal/testprotos/enums";
 
+option features.enum_type = CLOSED;
+
 enum Enum {
   DEFAULT = 1337;
   ZERO = 0;
diff --git a/internal/testprotos/irregular/test.proto b/internal/testprotos/irregular/test.proto
index af8c59d..7a29297 100644
--- a/internal/testprotos/irregular/test.proto
+++ b/internal/testprotos/irregular/test.proto
@@ -15,17 +15,17 @@
 option go_package = "google.golang.org/protobuf/internal/testprotos/irregular";
 
 message Message {
-  optional IrregularMessage optional_message = 1;
-  repeated IrregularMessage repeated_message = 2;
-  required IrregularMessage required_message = 3;
-  map<string, IrregularMessage> map_message = 4;
+  optional .goproto.proto.irregular.IrregularMessage optional_message = 1;
+  repeated .goproto.proto.irregular.IrregularMessage repeated_message = 2;
+  required .goproto.proto.irregular.IrregularMessage required_message = 3;
+  map<string, .goproto.proto.irregular.IrregularMessage> map_message = 4;
   oneof union {
-    IrregularMessage oneof_message = 5;
-    AberrantMessage oneof_aberrant_message = 6;
+    .goproto.proto.irregular.IrregularMessage oneof_message = 5;
+    .goproto.proto.irregular.AberrantMessage oneof_aberrant_message = 6;
   }
 
-  optional AberrantMessage optional_aberrant_message = 7;
-  repeated AberrantMessage repeated_aberrant_message = 8;
-  required AberrantMessage required_aberrant_message = 9;
-  map<string, AberrantMessage> map_aberrant_message = 10;
+  optional .goproto.proto.irregular.AberrantMessage optional_aberrant_message = 7;
+  repeated .goproto.proto.irregular.AberrantMessage repeated_aberrant_message = 8;
+  required .goproto.proto.irregular.AberrantMessage required_aberrant_message = 9;
+  map<string, .goproto.proto.irregular.AberrantMessage> map_aberrant_message = 10;
 }
diff --git a/internal/testprotos/lazy/lazy_normalized_wire_test.proto b/internal/testprotos/lazy/lazy_normalized_wire_test.proto
new file mode 100644
index 0000000..f335601
--- /dev/null
+++ b/internal/testprotos/lazy/lazy_normalized_wire_test.proto
@@ -0,0 +1,20 @@
+// 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 = "proto2";
+
+package lazy_normalized_wire_test;
+
+option go_package = "google.golang.org/protobuf/internal/testprotos/lazy";
+
+message FSub {
+  optional uint32 b = 2;
+  optional uint32 c = 3;
+  optional FSub grandchild = 4 [lazy = true];
+}
+
+message FTop {
+  optional uint32 a = 1;
+  optional FSub child = 2;
+}
diff --git a/internal/testprotos/lazy/lazy_tree.proto b/internal/testprotos/lazy/lazy_tree.proto
new file mode 100644
index 0000000..47641dc
--- /dev/null
+++ b/internal/testprotos/lazy/lazy_tree.proto
@@ -0,0 +1,29 @@
+// 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 lazy_tree;
+
+option go_package = "google.golang.org/protobuf/internal/testprotos/lazy";
+
+message Node {
+  Node nested = 99 [lazy = true];
+
+  int32 int32 = 1;
+  int64 int64 = 2;
+  uint32 uint32 = 3;
+  uint64 uint64 = 4;
+  sint32 sint32 = 5;
+  sint64 sint64 = 6;
+  fixed32 fixed32 = 7;
+  fixed64 fixed64 = 8;
+  sfixed32 sfixed32 = 9;
+  sfixed64 sfixed64 = 10;
+  float float = 11;
+  double double = 12;
+  bool bool = 13;
+  string string = 14;
+  bytes bytes = 15;
+}
diff --git a/internal/testprotos/messageset/messagesetpb/message_set.proto b/internal/testprotos/messageset/messagesetpb/message_set.proto
index 0e408e0..93cfc14 100644
--- a/internal/testprotos/messageset/messagesetpb/message_set.proto
+++ b/internal/testprotos/messageset/messagesetpb/message_set.proto
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-syntax = "proto2";
+edition = "2023";
 
 package goproto.proto.messageset;
 
@@ -11,15 +11,9 @@
 message MessageSet {
   option message_set_wire_format = true;
 
-  extensions 4 to 529999999;
-  extensions 530000000 to max
-  [declaration = {
-    number: 536870912
-    full_name: ".goproto.proto.messageset.ExtLargeNumber.message_set_extlarge"
-    type: ".goproto.proto.messageset.ExtLargeNumber"
-  }];
+  extensions 4 to max;
 }
 
 message MessageSetContainer {
-  optional MessageSet message_set = 1;
+  MessageSet message_set = 1;
 }
diff --git a/internal/testprotos/messageset/msetextpb/msetextpb.proto b/internal/testprotos/messageset/msetextpb/msetextpb.proto
index c723b3a..ed7127b 100644
--- a/internal/testprotos/messageset/msetextpb/msetextpb.proto
+++ b/internal/testprotos/messageset/msetextpb/msetextpb.proto
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-syntax = "proto2";
+edition = "2023";
 
 package goproto.proto.messageset;
 
@@ -12,28 +12,28 @@
 
 message Ext1 {
   extend MessageSet {
-    optional Ext1 message_set_ext1 = 1000;
+    Ext1 message_set_ext1 = 1000;
   }
-  optional int32 ext1_field1 = 1;
-  optional int32 ext1_field2 = 2;
+  int32 ext1_field1 = 1;
+  int32 ext1_field2 = 2;
 }
 
 message Ext2 {
   extend MessageSet {
-    optional Ext2 message_set_ext2 = 1001;
+    Ext2 message_set_ext2 = 1001;
   }
-  optional int32 ext2_field1 = 1;
+  int32 ext2_field1 = 1;
 }
 
 message ExtRequired {
   extend MessageSet {
-    optional ExtRequired message_set_extrequired = 1002;
+    ExtRequired message_set_extrequired = 1002;
   }
-  required int32 required_field1 = 1;
+  int32 required_field1 = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message ExtLargeNumber {
   extend MessageSet {
-    optional ExtLargeNumber message_set_extlarge = 536870912;  // 1<<29
+    ExtLargeNumber message_set_extlarge = 536870912;  // 1<<29
   }
 }
diff --git a/internal/testprotos/mixed/mixed.proto b/internal/testprotos/mixed/mixed.proto
new file mode 100644
index 0000000..81cb039
--- /dev/null
+++ b/internal/testprotos/mixed/mixed.proto
@@ -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.
+
+// This testproto explicitly configures the API level of each message.
+//
+// This allows creating mixed trees of proto messages on different API levels.
+
+edition = "2023";
+
+package goproto.proto.test;
+
+import "google/protobuf/go_features.proto";
+
+option go_package = "google.golang.org/protobuf/internal/testprotos/mixed";
+
+message Open {
+  option features.(pb.go).api_level = API_OPEN;
+
+  // These fields allow for arbitrary mixing.
+  Open open = 1;
+  Hybrid hybrid = 2;
+  Opaque opaque = 3;
+
+  int32 optional_int32 = 4;
+}
+
+message Hybrid {
+  option features.(pb.go).api_level = API_HYBRID;
+
+  // These fields allow for arbitrary mixing.
+  Open open = 1;
+  Hybrid hybrid = 2;
+  Opaque opaque = 3;
+
+  int32 optional_int32 = 4;
+}
+
+message Opaque {
+  option features.(pb.go).api_level = API_OPAQUE;
+
+  // These fields allow for arbitrary mixing.
+  Open open = 1;
+  Hybrid hybrid = 2;
+  Opaque opaque = 3;
+
+  int32 optional_int32 = 4;
+}
+
+message OpenLazy {
+  option features.(pb.go).api_level = API_OPEN;
+
+  // These fields allow for arbitrary mixing.
+  OpenLazy open = 1 [lazy = true];
+  HybridLazy hybrid = 2 [lazy = true];
+  OpaqueLazy opaque = 3 [lazy = true];
+
+  int32 optional_int32 = 4;
+}
+
+message HybridLazy {
+  option features.(pb.go).api_level = API_HYBRID;
+
+  // These fields allow for arbitrary mixing.
+  OpenLazy open = 1 [lazy = true];
+  HybridLazy hybrid = 2 [lazy = true];
+  OpaqueLazy opaque = 3 [lazy = true];
+
+  int32 optional_int32 = 4;
+}
+
+message OpaqueLazy {
+  option features.(pb.go).api_level = API_OPAQUE;
+
+  // These fields allow for arbitrary mixing.
+  OpenLazy open = 1 [lazy = true];
+  HybridLazy hybrid = 2 [lazy = true];
+  OpaqueLazy opaque = 3 [lazy = true];
+
+  int32 optional_int32 = 4;
+}
diff --git a/internal/testprotos/news/news.proto b/internal/testprotos/news/news.proto
index 774c949..0f5031f 100644
--- a/internal/testprotos/news/news.proto
+++ b/internal/testprotos/news/news.proto
@@ -19,12 +19,12 @@
   }
 
   string author = 1;
-  google.protobuf.Timestamp date = 2;
+  .google.protobuf.Timestamp date = 2;
   string title = 3;
   string content = 4;
   Status status = 8;
   repeated string tags = 7;
-  repeated google.protobuf.Any attachments = 6;
+  repeated .google.protobuf.Any attachments = 6;
 }
 
 message BinaryAttachment {
diff --git a/internal/testprotos/required/required.proto b/internal/testprotos/required/required.proto
index 2837008..d0625d3 100644
--- a/internal/testprotos/required/required.proto
+++ b/internal/testprotos/required/required.proto
@@ -2,71 +2,76 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-syntax = "proto2";
+edition = "2023";
 
 package goproto.proto.testrequired;
 
 option go_package = "google.golang.org/protobuf/internal/testprotos/required";
 
 message Int32 {
-  required int32 v = 1;
+  int32 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Int64 {
-  required int64 v = 1;
+  int64 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Uint32 {
-  required uint32 v = 1;
+  uint32 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Uint64 {
-  required uint64 v = 1;
+  uint64 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Sint32 {
-  required sint32 v = 1;
+  sint32 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Sint64 {
-  required sint64 v = 1;
+  sint64 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Fixed32 {
-  required fixed32 v = 1;
+  fixed32 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Fixed64 {
-  required fixed64 v = 1;
+  fixed64 v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Float {
-  required float v = 1;
+  float v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Double {
-  required double v = 1;
+  double v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Bool {
-  required bool v = 1;
+  bool v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message String {
-  required string v = 1;
+  string v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Bytes {
-  required bytes v = 1;
+  bytes v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Message {
   message M {}
-  required M v = 1;
+  M v = 1 [features.field_presence = LEGACY_REQUIRED];
 }
 
 message Group {
-  required group Group = 1 {
-    optional int32 v = 1;
+  message Group {
+    int32 v = 1;
   }
+
+  Group group = 1 [
+    features.field_presence = LEGACY_REQUIRED,
+    features.message_encoding = DELIMITED
+  ];
 }
diff --git a/internal/testprotos/test/test.proto b/internal/testprotos/test/test.proto
index ab0557b..7bb4a82 100644
--- a/internal/testprotos/test/test.proto
+++ b/internal/testprotos/test/test.proto
@@ -398,6 +398,10 @@
   }
 }
 
+message TestRequiredLazy {
+  optional TestRequired optional_lazy_message = 1 [lazy = true];
+}
+
 message TestWeak {
   optional goproto.proto.test.weak.WeakImportMessage1 weak_message1 = 1
       [weak = true];
diff --git a/internal/testprotos/testeditions/test.proto b/internal/testprotos/testeditions/test.proto
index a850d42..420279b 100644
--- a/internal/testprotos/testeditions/test.proto
+++ b/internal/testprotos/testeditions/test.proto
@@ -6,6 +6,9 @@
 
 package goproto.proto.testeditions;
 
+import "internal/testprotos/enums/enums.proto";
+import "internal/testprotos/testeditions/test_import.proto";
+
 option go_package = "google.golang.org/protobuf/internal/testprotos/testeditions";
 
 message TestAllTypes {
@@ -36,6 +39,10 @@
   bool singular_bool = 136 [features.field_presence = IMPLICIT];
   string singular_string = 137 [features.field_presence = IMPLICIT];
   bytes singular_bytes = 138 [features.field_presence = IMPLICIT];
+  // message-typed fields elided, as they cannot specify implicit presence.
+  NestedEnum singular_nested_enum = 142 [features.field_presence = IMPLICIT];
+  ForeignEnum singular_foreign_enum = 143 [features.field_presence = IMPLICIT];
+  ImportEnum singular_import_enum = 144 [features.field_presence = IMPLICIT];
 
   int32 optional_int32 = 1;
   int64 optional_int64 = 2;
@@ -62,8 +69,11 @@
       [features.message_encoding = DELIMITED];
   NestedMessage optional_nested_message = 18;
   ForeignMessage optional_foreign_message = 19;
+  ImportMessage optional_import_message = 20;
   NestedEnum optional_nested_enum = 21;
   ForeignEnum optional_foreign_enum = 22;
+  ImportEnum optional_import_enum = 23;
+  NestedMessage optional_lazy_nested_message = 24 [lazy = true];
 
   repeated int32 repeated_int32 = 31;
   repeated int64 repeated_int64 = 32;
@@ -91,8 +101,10 @@
   ];
   repeated NestedMessage repeated_nested_message = 48;
   repeated ForeignMessage repeated_foreign_message = 49;
+  repeated ImportMessage repeated_importmessage = 50;
   repeated NestedEnum repeated_nested_enum = 51;
   repeated ForeignEnum repeated_foreign_enum = 52;
+  repeated ImportEnum repeated_importenum = 53;
 
   map<int32, int32> map_int32_int32 = 56;
   map<int64, int64> map_int64_int64 = 57;
@@ -154,6 +166,109 @@
   }
 }
 
+message TestManyMessageFieldsMessage {
+  TestAllTypes f1 = 1;
+  TestAllTypes f2 = 2;
+  TestAllTypes f3 = 3;
+  TestAllTypes f4 = 4;
+  TestAllTypes f5 = 5;
+  TestAllTypes f6 = 6;
+  TestAllTypes f7 = 7;
+  TestAllTypes f8 = 8;
+  TestAllTypes f9 = 9;
+  TestAllTypes f10 = 10;
+  TestAllTypes f11 = 11;
+  TestAllTypes f12 = 12;
+  TestAllTypes f13 = 13;
+  TestAllTypes f14 = 14;
+  TestAllTypes f15 = 15;
+  TestAllTypes f16 = 16;
+  TestAllTypes f17 = 17;
+  TestAllTypes f18 = 18;
+  TestAllTypes f19 = 19;
+  TestAllTypes f20 = 20;
+  TestAllTypes f21 = 21;
+  TestAllTypes f22 = 22;
+  TestAllTypes f23 = 23;
+  TestAllTypes f24 = 24;
+  TestAllTypes f25 = 25;
+  TestAllTypes f26 = 26;
+  TestAllTypes f27 = 27;
+  TestAllTypes f28 = 28;
+  TestAllTypes f29 = 29;
+  TestAllTypes f30 = 30;
+  TestAllTypes f31 = 31;
+  TestAllTypes f32 = 32;
+  TestAllTypes f33 = 33;
+  TestAllTypes f34 = 34;
+  TestAllTypes f35 = 35;
+  TestAllTypes f36 = 36;
+  TestAllTypes f37 = 37;
+  TestAllTypes f38 = 38;
+  TestAllTypes f39 = 39;
+  TestAllTypes f40 = 40;
+  TestAllTypes f41 = 41;
+  TestAllTypes f42 = 42;
+  TestAllTypes f43 = 43;
+  TestAllTypes f44 = 44;
+  TestAllTypes f45 = 45;
+  TestAllTypes f46 = 46;
+  TestAllTypes f47 = 47;
+  TestAllTypes f48 = 48;
+  TestAllTypes f49 = 49;
+  TestAllTypes f50 = 50;
+  TestAllTypes f51 = 51;
+  TestAllTypes f52 = 52;
+  TestAllTypes f53 = 53;
+  TestAllTypes f54 = 54;
+  TestAllTypes f55 = 55;
+  TestAllTypes f56 = 56;
+  TestAllTypes f57 = 57;
+  TestAllTypes f58 = 58;
+  TestAllTypes f59 = 59;
+  TestAllTypes f60 = 60;
+  TestAllTypes f61 = 61;
+  TestAllTypes f62 = 62;
+  TestAllTypes f63 = 63;
+  TestAllTypes f64 = 64;
+  TestAllTypes f65 = 65;
+  TestAllTypes f66 = 66;
+  TestAllTypes f67 = 67;
+  TestAllTypes f68 = 68;
+  TestAllTypes f69 = 69;
+  TestAllTypes f70 = 70;
+  TestAllTypes f71 = 71;
+  TestAllTypes f72 = 72;
+  TestAllTypes f73 = 73;
+  TestAllTypes f74 = 74;
+  TestAllTypes f75 = 75;
+  TestAllTypes f76 = 76;
+  TestAllTypes f77 = 77;
+  TestAllTypes f78 = 78;
+  TestAllTypes f79 = 79;
+  TestAllTypes f80 = 80;
+  TestAllTypes f81 = 81;
+  TestAllTypes f82 = 82;
+  TestAllTypes f83 = 83;
+  TestAllTypes f84 = 84;
+  TestAllTypes f85 = 85;
+  TestAllTypes f86 = 86;
+  TestAllTypes f87 = 87;
+  TestAllTypes f88 = 88;
+  TestAllTypes f89 = 89;
+  TestAllTypes f90 = 90;
+  TestAllTypes f91 = 91;
+  TestAllTypes f92 = 92;
+  TestAllTypes f93 = 93;
+  TestAllTypes f94 = 94;
+  TestAllTypes f95 = 95;
+  TestAllTypes f96 = 96;
+  TestAllTypes f97 = 97;
+  TestAllTypes f98 = 98;
+  TestAllTypes f99 = 99;
+  TestAllTypes f100 = 100;
+}
+
 message ForeignMessage {
   int32 c = 1;
   int32 d = 2;
@@ -191,6 +306,10 @@
       [features.message_encoding = DELIMITED];
 }
 
+message TestRequiredLazy {
+  TestRequired optional_lazy_message = 1 [lazy = true];
+}
+
 message TestPackedTypes {
   repeated int32 packed_int32 = 90 [features.repeated_field_encoding = PACKED];
   repeated int64 packed_int64 = 91 [features.repeated_field_encoding = PACKED];
@@ -248,3 +367,14 @@
   repeated ForeignEnum packed_enum = 103
       [features.repeated_field_encoding = PACKED];
 }
+
+message RemoteDefault {
+  goproto.proto.enums.Enum default = 1;
+  goproto.proto.enums.Enum zero = 2 [default = ZERO];
+  goproto.proto.enums.Enum one = 3 [default = ONE];
+  goproto.proto.enums.Enum elevent = 4 [default = ELEVENT];
+  goproto.proto.enums.Enum seventeen = 5 [default = SEVENTEEN];
+  goproto.proto.enums.Enum thirtyseven = 6 [default = THIRTYSEVEN];
+  goproto.proto.enums.Enum sixtyseven = 7 [default = SIXTYSEVEN];
+  goproto.proto.enums.Enum negative = 8 [default = NEGATIVE];
+}
diff --git a/internal/testprotos/testeditions/test_import.proto b/internal/testprotos/testeditions/test_import.proto
new file mode 100644
index 0000000..e4d3c2e
--- /dev/null
+++ b/internal/testprotos/testeditions/test_import.proto
@@ -0,0 +1,15 @@
+// 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 goproto.proto.testeditions;
+
+option go_package = "google.golang.org/protobuf/internal/testprotos/testeditions";
+
+message ImportMessage {}
+
+enum ImportEnum {
+  IMPORT_ZERO = 0;
+}
diff --git a/internal/testprotos/textpbeditions/test2.proto b/internal/testprotos/textpbeditions/test2.proto
index efd8caf..4a5f6fe 100644
--- a/internal/testprotos/textpbeditions/test2.proto
+++ b/internal/testprotos/textpbeditions/test2.proto
@@ -196,7 +196,7 @@
 }
 
 message IndirectRequired {
-  NestedWithRequired opt_nested = 1 [features.field_presence = LEGACY_REQUIRED];
+  NestedWithRequired opt_nested = 1;
   repeated NestedWithRequired rpt_nested = 2;
   map<string, NestedWithRequired> str_to_nested = 3;
 
diff --git a/proto/decode.go b/proto/decode.go
index d75a653..a3b5e14 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -47,6 +47,12 @@
 	// RecursionLimit limits how deeply messages may be nested.
 	// If zero, a default limit is applied.
 	RecursionLimit int
+
+	//
+	// NoLazyDecoding turns off lazy decoding, which otherwise is enabled by
+	// default. Lazy decoding only affects submessages (annotated with [lazy =
+	// true] in the .proto file) within messages that use the Opaque API.
+	NoLazyDecoding bool
 }
 
 // Unmarshal parses the wire-format message in b and places the result in m.
@@ -104,6 +110,16 @@
 		if o.DiscardUnknown {
 			in.Flags |= protoiface.UnmarshalDiscardUnknown
 		}
+
+		if !allowPartial {
+			// This does not affect how current unmarshal functions work, it just allows them
+			// to record this for lazy the decoding case.
+			in.Flags |= protoiface.UnmarshalCheckRequired
+		}
+		if o.NoLazyDecoding {
+			in.Flags |= protoiface.UnmarshalNoLazyDecoding
+		}
+
 		out, err = methods.Unmarshal(in)
 	} else {
 		o.RecursionLimit--
diff --git a/proto/encode.go b/proto/encode.go
index 1f847bc..f0473c5 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -63,7 +63,8 @@
 	// options (except for UseCachedSize itself).
 	//
 	// 2. The message and all its submessages have not changed in any
-	// way since the Size call.
+	// way since the Size call. For lazily decoded messages, accessing
+	// a message results in decoding the message, which is a change.
 	//
 	// If either of these invariants is violated,
 	// the results are undefined and may include panics or corrupted output.
diff --git a/proto/lazy_bench_test.go b/proto/lazy_bench_test.go
new file mode 100644
index 0000000..e405aa1
--- /dev/null
+++ b/proto/lazy_bench_test.go
@@ -0,0 +1,92 @@
+// 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 proto_test
+
+import (
+	"testing"
+
+	"google.golang.org/protobuf/proto"
+
+	lazyopaquepb "google.golang.org/protobuf/internal/testprotos/lazy/lazy_opaque"
+)
+
+// testMessageLinked returns a test message with a few fields of various
+// possible types filled in that nests more messages like a linked list.
+func testMessageLinked(nesting int) *lazyopaquepb.Node {
+	const (
+		shortVarint = 23              // encodes into 1 byte
+		longVarint  = 562949953421312 // encodes into 8 bytes
+	)
+	msg := lazyopaquepb.Node_builder{
+		Int32:    proto.Int32(shortVarint),
+		Int64:    proto.Int64(longVarint),
+		Uint32:   proto.Uint32(shortVarint),
+		Uint64:   proto.Uint64(longVarint),
+		Sint32:   proto.Int32(shortVarint),
+		Sint64:   proto.Int64(longVarint),
+		Fixed32:  proto.Uint32(shortVarint),
+		Fixed64:  proto.Uint64(longVarint),
+		Sfixed32: proto.Int32(shortVarint),
+		Sfixed64: proto.Int64(longVarint),
+		Float:    proto.Float32(23.42),
+		Double:   proto.Float64(23.42),
+		Bool:     proto.Bool(true),
+		String:   proto.String("hello"),
+		Bytes:    []byte("world"),
+	}.Build()
+	if nesting > 0 {
+		msg.SetNested(testMessageLinked(nesting - 1))
+	}
+	return msg
+}
+
+// A higher nesting level than 15 messages deep does not result in (relative)
+// performance changes. In other words, the full effect of lazy decoding is
+// visible with a nesting level of 15 messages deep. Lower nesting levels (like
+// 5 messages deep) also show significant improvement.
+const nesting = 15
+
+func BenchmarkUnmarshal(b *testing.B) {
+	encoded, err := proto.Marshal(testMessageLinked(nesting))
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	for _, tt := range []struct {
+		desc  string
+		uopts proto.UnmarshalOptions
+	}{
+		{
+			desc:  "lazy",
+			uopts: proto.UnmarshalOptions{},
+		},
+
+		// When running the benchmark directly, print lazy vs. nolazy in the
+		// same run. When using the benchstat tool, you can compare lazy
+		// vs. nolazy by running only the lazy variant and disabling lazy
+		// decoding with the -test_lazy_unmarshal command-line flag:
+		//
+		// benchstat \
+		//   nolazy=<(go test -run=^$ -bench=Unmarshal/^lazy -count=6) \
+		//   lazy=<(go test -run=^$ -bench=Unmarshal/^lazy -count=6 -test_lazy_unmarshal)
+		{
+			desc: "nolazy",
+			uopts: proto.UnmarshalOptions{
+				NoLazyDecoding: true,
+			},
+		},
+	} {
+		b.Run(tt.desc, func(b *testing.B) {
+			b.ResetTimer()
+			b.ReportAllocs()
+			for i := 0; i < b.N; i++ {
+				out := &lazyopaquepb.Node{}
+				if err := tt.uopts.Unmarshal(encoded, out); err != nil {
+					b.Fatalf("can't unmarshal message: %v", err)
+				}
+			}
+		})
+	}
+}
diff --git a/proto/lazy_roundtrip_test.go b/proto/lazy_roundtrip_test.go
new file mode 100644
index 0000000..08f42c3
--- /dev/null
+++ b/proto/lazy_roundtrip_test.go
@@ -0,0 +1,125 @@
+// 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 proto_test
+
+import (
+	"reflect"
+	"testing"
+	"unsafe"
+
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/internal/impl"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protocmp"
+)
+
+func fillLazyRequiredMessage() *testopaquepb.TestRequiredLazy {
+	return testopaquepb.TestRequiredLazy_builder{
+		OptionalLazyMessage: testopaquepb.TestRequired_builder{
+			RequiredField: proto.Int32(12),
+		}.Build(),
+	}.Build()
+}
+
+func expandedLazy(m *testopaquepb.TestRequiredLazy) bool {
+	v := reflect.ValueOf(m).Elem()
+	rf := v.FieldByName("xxx_hidden_OptionalLazyMessage")
+	rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
+	return rf.Pointer() != 0
+}
+
+// This test ensures that a lazy field keeps being lazy when marshalling
+// even if it has required fields (as they have already been checked on
+// unmarshal)
+func TestLazyRequiredRoundtrip(t *testing.T) {
+	if !impl.LazyEnabled() {
+		t.Skipf("this test requires lazy decoding to be enabled")
+	}
+	m := fillLazyRequiredMessage()
+	b, _ := proto.MarshalOptions{}.Marshal(m)
+	ml := &testopaquepb.TestRequiredLazy{}
+	err := proto.UnmarshalOptions{}.Unmarshal(b, ml)
+	if err != nil {
+		t.Fatalf("Error while unmarshaling: %v", err)
+	}
+	// Sanity check, we should have all unexpanded fields in the proto
+	if expandedLazy(ml) {
+		t.Fatalf("Proto is not lazy: %#v", ml)
+	}
+	// Now we marshal the lazy field. It should still be unexpanded
+	_, _ = proto.MarshalOptions{}.Marshal(ml)
+
+	if expandedLazy(ml) {
+		t.Errorf("Proto got expanded by marshal: %#v", ml)
+	}
+
+	// The following tests the current behavior for cases where we
+	// cannot guarantee the integrity of the lazy unmarshalled buffer
+	// because of required fields. This would have to be updated if
+	// we find another way to check required fields than simply
+	// unmarshalling everything that has them when we're not sure.
+
+	ml = &testopaquepb.TestRequiredLazy{}
+	err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(b, ml)
+	if err != nil {
+		t.Fatalf("Error while unmarshaling: %v", err)
+	}
+	// Sanity check, we should have all unexpanded fields in the proto.
+	if expandedLazy(ml) {
+		t.Fatalf("Proto is not lazy: %#v", ml)
+	}
+	// Now we marshal the proto. The lazy fields will be expanded to
+	// check required fields.
+	_, _ = proto.MarshalOptions{}.Marshal(ml)
+
+	if !expandedLazy(ml) {
+		t.Errorf("Proto did not get expanded by marshal: %#v", ml)
+	}
+
+	// Finally, we test to see that the fields to not get expanded
+	// if we are consistently using AllowPartial both for marshal
+	// and unmarshal.
+	ml = &testopaquepb.TestRequiredLazy{}
+	err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(b, ml)
+	if err != nil {
+		t.Fatalf("Error while unmarshaling: %v", err)
+	}
+	// Sanity check, we should have all unexpanded fields in the proto.
+	if expandedLazy(ml) {
+		t.Fatalf("Proto is not lazy: %#v", ml)
+	}
+	// Now we marshal the proto. The lazy fields will be expanded to
+	// check required fields.
+	_, _ = proto.MarshalOptions{AllowPartial: true}.Marshal(ml)
+
+	if expandedLazy(ml) {
+		t.Errorf("Proto did not get expanded by marshal: %#v", ml)
+	}
+
+}
+
+func TestRoundtripMap(t *testing.T) {
+	m := testopaquepb.TestAllTypes_builder{
+		OptionalLazyNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{
+			Corecursive: testopaquepb.TestAllTypes_builder{
+				MapStringString: map[string]string{
+					"a": "b",
+				},
+			}.Build(),
+		}.Build(),
+	}.Build()
+	b, err := proto.Marshal(m)
+	if err != nil {
+		t.Fatalf("proto.Marshal: %v", err)
+	}
+	got := &testopaquepb.TestAllTypes{}
+	if err := proto.Unmarshal(b, got); err != nil {
+		t.Fatalf("proto.Unmarshal: %v", err)
+	}
+	if diff := cmp.Diff(m, got, protocmp.Transform()); diff != "" {
+		t.Errorf("not the same: diff (-want +got):\n%s", diff)
+	}
+}
diff --git a/proto/messageset_test.go b/proto/messageset_test.go
index 7b76714..d1cad21 100644
--- a/proto/messageset_test.go
+++ b/proto/messageset_test.go
@@ -12,7 +12,9 @@
 	"google.golang.org/protobuf/testing/protopack"
 
 	"google.golang.org/protobuf/internal/testprotos/messageset/messagesetpb"
+	_ "google.golang.org/protobuf/internal/testprotos/messageset/messagesetpb/messagesetpb_opaque"
 	_ "google.golang.org/protobuf/internal/testprotos/messageset/msetextpb"
+	_ "google.golang.org/protobuf/internal/testprotos/messageset/msetextpb/msetextpb_opaque"
 )
 
 func init() {
diff --git a/proto/oneof_get_test.go b/proto/oneof_get_test.go
new file mode 100644
index 0000000..d60015c
--- /dev/null
+++ b/proto/oneof_get_test.go
@@ -0,0 +1,273 @@
+// 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 proto_test
+
+import (
+	"testing"
+
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+func expectPanic(t *testing.T, f func(), fmt string, a ...any) {
+	t.Helper()
+	defer func() {
+		t.Helper()
+		if r := recover(); r == nil {
+			t.Errorf(fmt, a...)
+		}
+	}()
+	f()
+}
+
+func TestOpenGet(t *testing.T) {
+	x := &testhybridpb.TestAllTypes{}
+
+	tab := []struct {
+		fName  string      // Field name (in proto)
+		set    func()      // Set the field
+		clear  func()      // Clear the field
+		has    func() bool // Has for the field
+		isZero func() bool // Get and return true if zero value
+	}{
+		{
+			fName:  "oneof_uint32",
+			set:    func() { x.SetOneofUint32(47) },
+			clear:  func() { x.ClearOneofUint32() },
+			has:    func() bool { return x.HasOneofUint32() },
+			isZero: func() bool { return x.GetOneofUint32() == 0 },
+		},
+		{
+			fName:  "oneof_nested_message",
+			set:    func() { x.SetOneofNestedMessage(&testhybridpb.TestAllTypes_NestedMessage{}) },
+			clear:  func() { x.ClearOneofNestedMessage() },
+			has:    func() bool { return x.HasOneofNestedMessage() },
+			isZero: func() bool { return x.GetOneofNestedMessage() == nil },
+		},
+		{
+			fName:  "oneof_string",
+			set:    func() { x.SetOneofString("test") },
+			clear:  func() { x.ClearOneofString() },
+			has:    func() bool { return x.HasOneofString() },
+			isZero: func() bool { return x.GetOneofString() == "" },
+		},
+		{
+			fName:  "oneof_bytes",
+			set:    func() { x.SetOneofBytes([]byte("test")) },
+			clear:  func() { x.ClearOneofBytes() },
+			has:    func() bool { return x.HasOneofBytes() },
+			isZero: func() bool { return len(x.GetOneofBytes()) == 0 },
+		},
+		{
+			fName:  "oneof_bool",
+			set:    func() { x.SetOneofBool(true) },
+			clear:  func() { x.ClearOneofBool() },
+			has:    func() bool { return x.HasOneofBool() },
+			isZero: func() bool { return x.GetOneofBool() == false },
+		},
+		{
+			fName:  "oneof_uint64",
+			set:    func() { x.SetOneofUint64(7438109473104) },
+			clear:  func() { x.ClearOneofUint64() },
+			has:    func() bool { return x.HasOneofUint64() },
+			isZero: func() bool { return x.GetOneofUint64() == 0 },
+		},
+		{
+			fName:  "oneof_float",
+			set:    func() { x.SetOneofFloat(3.1415) },
+			clear:  func() { x.ClearOneofFloat() },
+			has:    func() bool { return x.HasOneofFloat() },
+			isZero: func() bool { return x.GetOneofFloat() == 0.0 },
+		},
+		{
+			fName:  "oneof_double",
+			set:    func() { x.SetOneofDouble(3e+8) },
+			clear:  func() { x.ClearOneofDouble() },
+			has:    func() bool { return x.HasOneofDouble() },
+			isZero: func() bool { return x.GetOneofDouble() == 0.0 },
+		},
+		{
+			fName:  "oneof_enum",
+			set:    func() { x.SetOneofEnum(testhybridpb.TestAllTypes_BAZ) },
+			clear:  func() { x.ClearOneofEnum() },
+			has:    func() bool { return x.HasOneofEnum() },
+			isZero: func() bool { return x.GetOneofEnum() == 0 },
+		},
+	}
+
+	for i, mv := range tab {
+		x.ClearOneofField()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v", got, want)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on empty oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		mv.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), false; got != want {
+			t.Errorf("Get on non-empty oneof member did return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+
+		mv.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on empty oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other := tab[(i+1)%len(tab)]
+		mv.set()
+		other.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on wrong oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+	x = nil
+	for _, mv := range tab {
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on nil receiver did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on nil receiver failed, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+}
+
+func TestOpaqueGet(t *testing.T) {
+	x := &testopaquepb.TestAllTypes{}
+
+	tab := []struct {
+		fName  string      // Field name (in proto)
+		set    func()      // Set the field
+		clear  func()      // Clear the field
+		has    func() bool // Has for the field
+		isZero func() bool // Get and return true if zero value
+	}{
+		{
+			fName:  "oneof_uint32",
+			set:    func() { x.SetOneofUint32(47) },
+			clear:  func() { x.ClearOneofUint32() },
+			has:    func() bool { return x.HasOneofUint32() },
+			isZero: func() bool { return x.GetOneofUint32() == 0 },
+		},
+		{
+			fName:  "oneof_nested_message",
+			set:    func() { x.SetOneofNestedMessage(&testopaquepb.TestAllTypes_NestedMessage{}) },
+			clear:  func() { x.ClearOneofNestedMessage() },
+			has:    func() bool { return x.HasOneofNestedMessage() },
+			isZero: func() bool { return x.GetOneofNestedMessage() == nil },
+		},
+		{
+			fName:  "oneof_string",
+			set:    func() { x.SetOneofString("test") },
+			clear:  func() { x.ClearOneofString() },
+			has:    func() bool { return x.HasOneofString() },
+			isZero: func() bool { return x.GetOneofString() == "" },
+		},
+		{
+			fName:  "oneof_bytes",
+			set:    func() { x.SetOneofBytes([]byte("test")) },
+			clear:  func() { x.ClearOneofBytes() },
+			has:    func() bool { return x.HasOneofBytes() },
+			isZero: func() bool { return len(x.GetOneofBytes()) == 0 },
+		},
+		{
+			fName:  "oneof_bool",
+			set:    func() { x.SetOneofBool(true) },
+			clear:  func() { x.ClearOneofBool() },
+			has:    func() bool { return x.HasOneofBool() },
+			isZero: func() bool { return x.GetOneofBool() == false },
+		},
+		{
+			fName:  "oneof_uint64",
+			set:    func() { x.SetOneofUint64(7438109473104) },
+			clear:  func() { x.ClearOneofUint64() },
+			has:    func() bool { return x.HasOneofUint64() },
+			isZero: func() bool { return x.GetOneofUint64() == 0 },
+		},
+		{
+			fName:  "oneof_float",
+			set:    func() { x.SetOneofFloat(3.1415) },
+			clear:  func() { x.ClearOneofFloat() },
+			has:    func() bool { return x.HasOneofFloat() },
+			isZero: func() bool { return x.GetOneofFloat() == 0.0 },
+		},
+		{
+			fName:  "oneof_double",
+			set:    func() { x.SetOneofDouble(3e+8) },
+			clear:  func() { x.ClearOneofDouble() },
+			has:    func() bool { return x.HasOneofDouble() },
+			isZero: func() bool { return x.GetOneofDouble() == 0.0 },
+		},
+		{
+			fName:  "oneof_enum",
+			set:    func() { x.SetOneofEnum(testopaquepb.TestAllTypes_BAZ) },
+			clear:  func() { x.ClearOneofEnum() },
+			has:    func() bool { return x.HasOneofEnum() },
+			isZero: func() bool { return x.GetOneofEnum() == 0 },
+		},
+	}
+
+	for i, mv := range tab {
+		x.ClearOneofField()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v", got, want)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on empty oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		mv.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), false; got != want {
+			t.Errorf("Get on non-empty oneof member did return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+
+		mv.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on empty oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other := tab[(i+1)%len(tab)]
+		mv.set()
+		other.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on wrong oneof member did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+	x = nil
+	for _, mv := range tab {
+		if got, want := mv.isZero(), true; got != want {
+			t.Errorf("Get on nil receiver did not return zero value, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on nil receiver failed, got %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+}
diff --git a/proto/oneof_set_test.go b/proto/oneof_set_test.go
new file mode 100644
index 0000000..265063e
--- /dev/null
+++ b/proto/oneof_set_test.go
@@ -0,0 +1,312 @@
+// 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 proto_test
+
+import (
+	"testing"
+
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+func TestOpenSetNilReceiver(t *testing.T) {
+	var x *testhybridpb.TestAllTypes
+	expectPanic(t, func() {
+		x.SetOneofUint32(24)
+	}, "Setting of oneof member on nil receiver did not panic.")
+	expectPanic(t, func() {
+		x.ClearOneofUint32()
+	}, "Clearing of oneof member on nil receiver did not panic.")
+	expectPanic(t, func() {
+		x.ClearOneofField()
+	}, "Clearing of oneof union on nil receiver did not panic.")
+}
+
+func TestOpenSet(t *testing.T) {
+	x := &testhybridpb.TestAllTypes{}
+
+	tab := []struct {
+		fName string      // Field name (in proto)
+		set   func()      // Set the field
+		clear func()      // Clear the field
+		has   func() bool // Has for the field
+	}{
+		{
+			fName: "oneof_uint32",
+			set:   func() { x.SetOneofUint32(47) },
+			clear: func() { x.ClearOneofUint32() },
+			has:   func() bool { return x.HasOneofUint32() },
+		},
+		{
+			fName: "oneof_nested_message",
+			set:   func() { x.SetOneofNestedMessage(&testhybridpb.TestAllTypes_NestedMessage{}) },
+			clear: func() { x.ClearOneofNestedMessage() },
+			has:   func() bool { return x.HasOneofNestedMessage() },
+		},
+		{
+			fName: "oneof_string",
+			set:   func() { x.SetOneofString("test") },
+			clear: func() { x.ClearOneofString() },
+			has:   func() bool { return x.HasOneofString() },
+		},
+		{
+			fName: "oneof_bytes",
+			set:   func() { x.SetOneofBytes([]byte("test")) },
+			clear: func() { x.ClearOneofBytes() },
+			has:   func() bool { return x.HasOneofBytes() },
+		},
+		{
+			fName: "oneof_bool",
+			set:   func() { x.SetOneofBool(true) },
+			clear: func() { x.ClearOneofBool() },
+			has:   func() bool { return x.HasOneofBool() },
+		},
+		{
+			fName: "oneof_uint64",
+			set:   func() { x.SetOneofUint64(7438109473104) },
+			clear: func() { x.ClearOneofUint64() },
+			has:   func() bool { return x.HasOneofUint64() },
+		},
+		{
+			fName: "oneof_float",
+			set:   func() { x.SetOneofFloat(3.1415) },
+			clear: func() { x.ClearOneofFloat() },
+			has:   func() bool { return x.HasOneofFloat() },
+		},
+		{
+			fName: "oneof_double",
+			set:   func() { x.SetOneofDouble(3e+8) },
+			clear: func() { x.ClearOneofDouble() },
+			has:   func() bool { return x.HasOneofDouble() },
+		},
+		{
+			fName: "oneof_enum",
+			set:   func() { x.SetOneofEnum(testhybridpb.TestAllTypes_BAZ) },
+			clear: func() { x.ClearOneofEnum() },
+			has:   func() bool { return x.HasOneofEnum() },
+		},
+	}
+
+	for i, mv := range tab {
+		x.ClearOneofField()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v", got, want)
+		}
+		mv.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), true; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+
+		mv.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other := tab[(i+1)%len(tab)]
+		mv.set()
+		other.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+	x.SetOneofUint32(47)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	x.SetOneofNestedMessage(nil)
+	if got, want := x.HasOneofField(), false; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), false; got != want {
+		t.Errorf("HasOneofUint32 returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofNestedMessage(), false; got != want {
+		t.Errorf("HasOneofNestedMessage returned %v, expected %v", got, want)
+	}
+	x.SetOneofUint32(47)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	x.SetOneofBytes(nil)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), false; got != want {
+		t.Errorf("HasOneofUint32 returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofBytes(), true; got != want {
+		t.Errorf("HasOneofNestedMessage returned %v, expected %v", got, want)
+	}
+}
+
+func TestOpaqueSetNilReceiver(t *testing.T) {
+	var x *testopaquepb.TestAllTypes
+	expectPanic(t, func() {
+		x.SetOneofUint32(24)
+	}, "Setting of oneof member on nil receiver did not panic.")
+	expectPanic(t, func() {
+		x.ClearOneofUint32()
+	}, "Clearing of oneof member on nil receiver did not panic.")
+	expectPanic(t, func() {
+		x.ClearOneofField()
+	}, "Clearing of oneof union on nil receiver did not panic.")
+}
+
+func TestOpaqueSet(t *testing.T) {
+	x := &testopaquepb.TestAllTypes{}
+
+	tab := []struct {
+		fName string      // Field name (in proto)
+		set   func()      // Set the field
+		clear func()      // Clear the field
+		has   func() bool // Has for the field
+	}{
+		{
+			fName: "oneof_uint32",
+			set:   func() { x.SetOneofUint32(47) },
+			clear: func() { x.ClearOneofUint32() },
+			has:   func() bool { return x.HasOneofUint32() },
+		},
+		{
+			fName: "oneof_nested_message",
+			set:   func() { x.SetOneofNestedMessage(&testopaquepb.TestAllTypes_NestedMessage{}) },
+			clear: func() { x.ClearOneofNestedMessage() },
+			has:   func() bool { return x.HasOneofNestedMessage() },
+		},
+		{
+			fName: "oneof_string",
+			set:   func() { x.SetOneofString("test") },
+			clear: func() { x.ClearOneofString() },
+			has:   func() bool { return x.HasOneofString() },
+		},
+		{
+			fName: "oneof_bytes",
+			set:   func() { x.SetOneofBytes([]byte("test")) },
+			clear: func() { x.ClearOneofBytes() },
+			has:   func() bool { return x.HasOneofBytes() },
+		},
+		{
+			fName: "oneof_bool",
+			set:   func() { x.SetOneofBool(true) },
+			clear: func() { x.ClearOneofBool() },
+			has:   func() bool { return x.HasOneofBool() },
+		},
+		{
+			fName: "oneof_uint64",
+			set:   func() { x.SetOneofUint64(7438109473104) },
+			clear: func() { x.ClearOneofUint64() },
+			has:   func() bool { return x.HasOneofUint64() },
+		},
+		{
+			fName: "oneof_float",
+			set:   func() { x.SetOneofFloat(3.1415) },
+			clear: func() { x.ClearOneofFloat() },
+			has:   func() bool { return x.HasOneofFloat() },
+		},
+		{
+			fName: "oneof_double",
+			set:   func() { x.SetOneofDouble(3e+8) },
+			clear: func() { x.ClearOneofDouble() },
+			has:   func() bool { return x.HasOneofDouble() },
+		},
+		{
+			fName: "oneof_enum",
+			set:   func() { x.SetOneofEnum(testopaquepb.TestAllTypes_BAZ) },
+			clear: func() { x.ClearOneofEnum() },
+			has:   func() bool { return x.HasOneofEnum() },
+		},
+	}
+
+	for i, mv := range tab {
+		x.ClearOneofField()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v", got, want)
+		}
+		mv.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), true; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+
+		mv.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other := tab[(i+1)%len(tab)]
+		mv.set()
+		other.set()
+
+		if got, want := x.HasOneofField(), true; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		if got, want := mv.has(), false; got != want {
+			t.Errorf("Has on oneof member returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+		other.clear()
+		if got, want := x.HasOneofField(), false; got != want {
+			t.Errorf("HasOneofField returned %v, expected %v (%s)", got, want, mv.fName)
+		}
+	}
+	x.SetOneofUint32(47)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	x.SetOneofNestedMessage(nil)
+	if got, want := x.HasOneofField(), false; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), false; got != want {
+		t.Errorf("HasOneofUint32 returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofNestedMessage(), false; got != want {
+		t.Errorf("HasOneofNestedMessage returned %v, expected %v", got, want)
+	}
+	x.SetOneofUint32(47)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	x.SetOneofBytes(nil)
+	if got, want := x.HasOneofField(), true; got != want {
+		t.Errorf("HasOneofField returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofUint32(), false; got != want {
+		t.Errorf("HasOneofUint32 returned %v, expected %v", got, want)
+	}
+	if got, want := x.HasOneofBytes(), true; got != want {
+		t.Errorf("HasOneofNestedMessage returned %v, expected %v", got, want)
+	}
+}
diff --git a/proto/oneof_which_test.go b/proto/oneof_which_test.go
new file mode 100644
index 0000000..bdf10bd
--- /dev/null
+++ b/proto/oneof_which_test.go
@@ -0,0 +1,189 @@
+// 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 proto_test
+
+import (
+	"testing"
+
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func TestOpenWhich(t *testing.T) {
+	var x *testhybridpb.TestAllTypes
+	if x.WhichOneofField() != 0 {
+		t.Errorf("WhichOneofField on nil returned %d, expected %d", x.WhichOneofField(), 0)
+	}
+	x = &testhybridpb.TestAllTypes{}
+	if x.WhichOneofField() != 0 {
+		t.Errorf("WhichOneofField returned %d, expected %d", x.WhichOneofField(), 0)
+	}
+	tab := []struct {
+		m *testhybridpb.TestAllTypes
+		v protoreflect.FieldNumber
+	}{
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofUint32: proto.Uint32(46),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofUint32_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofNestedMessage: testhybridpb.TestAllTypes_NestedMessage_builder{A: proto.Int32(46)}.Build(),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofNestedMessage_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofString: proto.String("foo"),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofString_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofBytes: []byte("foo"),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofBytes_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofBool: proto.Bool(true),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofBool_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofUint64: proto.Uint64(0),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofUint64_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofFloat: proto.Float32(0.0),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofFloat_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofDouble: proto.Float64(1.1),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofDouble_case),
+		},
+		{
+			m: testhybridpb.TestAllTypes_builder{
+				OneofEnum: testhybridpb.TestAllTypes_BAZ.Enum(),
+			}.Build(),
+			v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofEnum_case),
+		},
+	}
+
+	for _, mv := range tab {
+		if protoreflect.FieldNumber(mv.m.WhichOneofField()) != mv.v {
+			t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), mv.v)
+		}
+		if !mv.m.HasOneofField() {
+			t.Errorf("HasOneofField returned %t, expected true", mv.m.HasOneofField())
+
+		}
+		mv.m.ClearOneofField()
+		if mv.m.WhichOneofField() != 0 {
+			t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), 0)
+		}
+		if mv.m.HasOneofField() {
+			t.Errorf("HasOneofField returned %t, expected false", mv.m.HasOneofField())
+		}
+	}
+}
+
+func TestOpaqueWhich(t *testing.T) {
+	var x *testopaquepb.TestAllTypes
+	if x.WhichOneofField() != 0 {
+		t.Errorf("WhichOneofField on nil returned %d, expected %d", x.WhichOneofField(), 0)
+	}
+	x = &testopaquepb.TestAllTypes{}
+	if x.WhichOneofField() != 0 {
+		t.Errorf("WhichOneofField returned %d, expected %d", x.WhichOneofField(), 0)
+	}
+	en := testopaquepb.TestAllTypes_BAZ
+	tab := []struct {
+		m *testopaquepb.TestAllTypes
+		v protoreflect.FieldNumber
+	}{
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofUint32: proto.Uint32(46),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofUint32_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{A: proto.Int32(46)}.Build(),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofNestedMessage_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofString: proto.String("foo"),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofString_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofBytes: []byte("foo"),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofBytes_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofBool: proto.Bool(true),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofBool_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofUint64: proto.Uint64(0),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofUint64_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofFloat: proto.Float32(0.0),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofFloat_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofDouble: proto.Float64(1.1),
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofDouble_case),
+		},
+		{
+			m: testopaquepb.TestAllTypes_builder{
+				OneofEnum: &en,
+			}.Build(),
+			v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofEnum_case),
+		},
+	}
+
+	for _, mv := range tab {
+		if protoreflect.FieldNumber(mv.m.WhichOneofField()) != mv.v {
+			t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), mv.v)
+		}
+		if !mv.m.HasOneofField() {
+			t.Errorf("HasOneofField returned %t, expected true", mv.m.HasOneofField())
+
+		}
+		mv.m.ClearOneofField()
+		if mv.m.WhichOneofField() != 0 {
+			t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), 0)
+		}
+		if mv.m.HasOneofField() {
+			t.Errorf("HasOneofField returned %t, expected false", mv.m.HasOneofField())
+		}
+	}
+}
diff --git a/proto/repeated_test.go b/proto/repeated_test.go
new file mode 100644
index 0000000..9edb2c6
--- /dev/null
+++ b/proto/repeated_test.go
@@ -0,0 +1,560 @@
+// 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 proto_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+	"unsafe"
+
+	"google.golang.org/protobuf/internal/impl"
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+	"google.golang.org/protobuf/proto"
+)
+
+func TestOpenSetRepeatedNilReceiver(t *testing.T) {
+	var x *testhybridpb.TestAllTypes
+	expectPanic(t, func() {
+		x.SetRepeatedUint32(nil)
+	}, "Setting repeated field on nil receiver did not panic.")
+}
+
+func TestOpenSetRepeated(t *testing.T) {
+	x := &testhybridpb.TestAllTypes{}
+
+	tab := []struct {
+		fName  string     // Field name (in proto)
+		set    func()     // Set the field to empty slice
+		setNil func()     // Set the field to nil
+		len    func() int // length of field, -1 if nil
+	}{
+		{
+			fName:  "repeated_int32",
+			set:    func() { x.SetRepeatedInt32([]int32{}) },
+			setNil: func() { x.SetRepeatedInt32(nil) },
+			len: func() int {
+				if x.GetRepeatedInt32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedInt32())
+			},
+		},
+		{
+			fName:  "repeated_int64",
+			set:    func() { x.SetRepeatedInt64([]int64{}) },
+			setNil: func() { x.SetRepeatedInt64(nil) },
+			len: func() int {
+				if x.GetRepeatedInt64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedInt64())
+			},
+		},
+		{
+			fName:  "repeated_uint32",
+			set:    func() { x.SetRepeatedUint32([]uint32{}) },
+			setNil: func() { x.SetRepeatedUint32(nil) },
+			len: func() int {
+				if x.GetRepeatedUint32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedUint32())
+			},
+		},
+		{
+			fName:  "repeated_uint64",
+			set:    func() { x.SetRepeatedUint64([]uint64{}) },
+			setNil: func() { x.SetRepeatedUint64(nil) },
+			len: func() int {
+				if x.GetRepeatedUint64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedUint64())
+			},
+		},
+		{
+			fName:  "repeated_sint32",
+			set:    func() { x.SetRepeatedSint32([]int32{}) },
+			setNil: func() { x.SetRepeatedSint32(nil) },
+			len: func() int {
+				if x.GetRepeatedSint32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSint32())
+			},
+		},
+		{
+			fName:  "repeated_sint64",
+			set:    func() { x.SetRepeatedSint64([]int64{}) },
+			setNil: func() { x.SetRepeatedSint64(nil) },
+			len: func() int {
+				if x.GetRepeatedSint64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSint64())
+			},
+		},
+		{
+			fName:  "repeated_fixed32",
+			set:    func() { x.SetRepeatedFixed32([]uint32{}) },
+			setNil: func() { x.SetRepeatedFixed32(nil) },
+			len: func() int {
+				if x.GetRepeatedFixed32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFixed32())
+			},
+		},
+		{
+			fName:  "repeated_fixed64",
+			set:    func() { x.SetRepeatedFixed64([]uint64{}) },
+			setNil: func() { x.SetRepeatedFixed64(nil) },
+			len: func() int {
+				if x.GetRepeatedFixed64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFixed64())
+			},
+		},
+		{
+			fName:  "repeated_sfixed32",
+			set:    func() { x.SetRepeatedSfixed32([]int32{}) },
+			setNil: func() { x.SetRepeatedSfixed32(nil) },
+			len: func() int {
+				if x.GetRepeatedSfixed32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSfixed32())
+			},
+		},
+		{
+			fName:  "repeated_sfixed64",
+			set:    func() { x.SetRepeatedSfixed64([]int64{}) },
+			setNil: func() { x.SetRepeatedSfixed64(nil) },
+			len: func() int {
+				if x.GetRepeatedSfixed64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSfixed64())
+			},
+		},
+		{
+			fName:  "repeated_float",
+			set:    func() { x.SetRepeatedFloat([]float32{}) },
+			setNil: func() { x.SetRepeatedFloat(nil) },
+			len: func() int {
+				if x.GetRepeatedFloat() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFloat())
+			},
+		},
+		{
+			fName:  "repeated_double",
+			set:    func() { x.SetRepeatedDouble([]float64{}) },
+			setNil: func() { x.SetRepeatedDouble(nil) },
+			len: func() int {
+				if x.GetRepeatedDouble() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedDouble())
+			},
+		},
+		{
+			fName:  "repeated_bool",
+			set:    func() { x.SetRepeatedBool([]bool{}) },
+			setNil: func() { x.SetRepeatedBool(nil) },
+			len: func() int {
+				if x.GetRepeatedBool() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedBool())
+			},
+		},
+		{
+			fName:  "repeated_string",
+			set:    func() { x.SetRepeatedString([]string{}) },
+			setNil: func() { x.SetRepeatedString(nil) },
+			len: func() int {
+				if x.GetRepeatedString() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedString())
+			},
+		},
+		{
+			fName:  "repeated_bytes",
+			set:    func() { x.SetRepeatedBytes([][]byte{}) },
+			setNil: func() { x.SetRepeatedBytes(nil) },
+			len: func() int {
+				if x.GetRepeatedBytes() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedBytes())
+			},
+		},
+		{
+			fName:  "RepeatedGroup",
+			set:    func() { x.SetRepeatedgroup([]*testhybridpb.TestAllTypes_RepeatedGroup{}) },
+			setNil: func() { x.SetRepeatedgroup(nil) },
+			len: func() int {
+				if x.GetRepeatedgroup() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedgroup())
+			},
+		},
+		{
+			fName:  "repeated_nested_message",
+			set:    func() { x.SetRepeatedNestedMessage([]*testhybridpb.TestAllTypes_NestedMessage{}) },
+			setNil: func() { x.SetRepeatedNestedMessage(nil) },
+			len: func() int {
+				if x.GetRepeatedNestedMessage() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedNestedMessage())
+			},
+		},
+		{
+			fName:  "repeated_nested_enum",
+			set:    func() { x.SetRepeatedNestedEnum([]testhybridpb.TestAllTypes_NestedEnum{}) },
+			setNil: func() { x.SetRepeatedNestedEnum(nil) },
+			len: func() int {
+				if x.GetRepeatedNestedEnum() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedNestedEnum())
+			},
+		},
+	}
+
+	for _, mv := range tab {
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil to start with ", mv.fName)
+		}
+		mv.set()
+		if mv.len() != 0 {
+			t.Errorf("Repeated field %s did not retain empty slice ", mv.fName)
+		}
+		b, err := proto.Marshal(x)
+		if err != nil {
+			t.Fatalf("Failed to marshal message, err = %v", err)
+		}
+		proto.Unmarshal(b, x)
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil to start with ", mv.fName)
+		}
+		mv.set()
+		mv.setNil()
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil event though we set it to ", mv.fName)
+		}
+
+	}
+
+	// Check that we actually retain the same slice
+	s := make([]testhybridpb.TestAllTypes_NestedEnum, 0, 455)
+	x.SetRepeatedNestedEnum(s)
+	if got, want := cap(x.GetRepeatedNestedEnum()), 455; got != want {
+		t.Errorf("cap(x.GetRepeatedNestedEnum()) returned %v, expected %v", got, want)
+	}
+	// Do this for a message too
+	s2 := make([]*testhybridpb.TestAllTypes_NestedMessage, 0, 544)
+	x.SetRepeatedNestedMessage(s2)
+	if got, want := cap(x.GetRepeatedNestedMessage()), 544; got != want {
+		t.Errorf("cap(x.GetRepeatedNestedMessage()) returned %v, expected %v", got, want)
+	}
+	// Check special bytes behavior
+	x.SetOptionalBytes(nil)
+	if got, want := x.HasOptionalBytes(), true; got != want {
+		t.Errorf("HasOptionalBytes after setting to nil returned %v, expected %v", got, want)
+	}
+	if got := x.GetOptionalBytes(); got == nil || len(got) != 0 {
+		t.Errorf("GetOptionalBytes after setting to nil returned %v, expected %v", got, []byte{})
+	}
+
+}
+
+func TestOpaqueSetRepeatedNilReceiver(t *testing.T) {
+	var x *testopaquepb.TestAllTypes
+	expectPanic(t, func() {
+		x.SetRepeatedUint32(nil)
+	}, "Setting repeated field on nil receiver did not panic.")
+}
+
+func TestOpaqueSetRepeated(t *testing.T) {
+	for _, mode := range []bool{true, false} {
+		impl.EnableLazyUnmarshal(mode)
+		t.Run(fmt.Sprintf("LazyUnmarshal_%t", mode), testOpaqueSetRepeatedSub)
+	}
+}
+
+func testOpaqueSetRepeatedSub(t *testing.T) {
+	x := &testopaquepb.TestAllTypes{}
+
+	tab := []struct {
+		fName  string     // Field name (in proto)
+		set    func()     // Set the field to empty slice
+		setNil func()     // Set the field to nil
+		len    func() int // length of field, -1 if nil
+	}{
+		{
+			fName:  "repeated_int32",
+			set:    func() { x.SetRepeatedInt32([]int32{}) },
+			setNil: func() { x.SetRepeatedInt32(nil) },
+			len: func() int {
+				if x.GetRepeatedInt32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedInt32())
+			},
+		},
+		{
+			fName:  "repeated_int64",
+			set:    func() { x.SetRepeatedInt64([]int64{}) },
+			setNil: func() { x.SetRepeatedInt64(nil) },
+			len: func() int {
+				if x.GetRepeatedInt64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedInt64())
+			},
+		},
+		{
+			fName:  "repeated_uint32",
+			set:    func() { x.SetRepeatedUint32([]uint32{}) },
+			setNil: func() { x.SetRepeatedUint32(nil) },
+			len: func() int {
+				if x.GetRepeatedUint32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedUint32())
+			},
+		},
+		{
+			fName:  "repeated_uint64",
+			set:    func() { x.SetRepeatedUint64([]uint64{}) },
+			setNil: func() { x.SetRepeatedUint64(nil) },
+			len: func() int {
+				if x.GetRepeatedUint64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedUint64())
+			},
+		},
+		{
+			fName:  "repeated_sint32",
+			set:    func() { x.SetRepeatedSint32([]int32{}) },
+			setNil: func() { x.SetRepeatedSint32(nil) },
+			len: func() int {
+				if x.GetRepeatedSint32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSint32())
+			},
+		},
+		{
+			fName:  "repeated_sint64",
+			set:    func() { x.SetRepeatedSint64([]int64{}) },
+			setNil: func() { x.SetRepeatedSint64(nil) },
+			len: func() int {
+				if x.GetRepeatedSint64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSint64())
+			},
+		},
+		{
+			fName:  "repeated_fixed32",
+			set:    func() { x.SetRepeatedFixed32([]uint32{}) },
+			setNil: func() { x.SetRepeatedFixed32(nil) },
+			len: func() int {
+				if x.GetRepeatedFixed32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFixed32())
+			},
+		},
+		{
+			fName:  "repeated_fixed64",
+			set:    func() { x.SetRepeatedFixed64([]uint64{}) },
+			setNil: func() { x.SetRepeatedFixed64(nil) },
+			len: func() int {
+				if x.GetRepeatedFixed64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFixed64())
+			},
+		},
+		{
+			fName:  "repeated_sfixed32",
+			set:    func() { x.SetRepeatedSfixed32([]int32{}) },
+			setNil: func() { x.SetRepeatedSfixed32(nil) },
+			len: func() int {
+				if x.GetRepeatedSfixed32() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSfixed32())
+			},
+		},
+		{
+			fName:  "repeated_sfixed64",
+			set:    func() { x.SetRepeatedSfixed64([]int64{}) },
+			setNil: func() { x.SetRepeatedSfixed64(nil) },
+			len: func() int {
+				if x.GetRepeatedSfixed64() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedSfixed64())
+			},
+		},
+		{
+			fName:  "repeated_float",
+			set:    func() { x.SetRepeatedFloat([]float32{}) },
+			setNil: func() { x.SetRepeatedFloat(nil) },
+			len: func() int {
+				if x.GetRepeatedFloat() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedFloat())
+			},
+		},
+		{
+			fName:  "repeated_double",
+			set:    func() { x.SetRepeatedDouble([]float64{}) },
+			setNil: func() { x.SetRepeatedDouble(nil) },
+			len: func() int {
+				if x.GetRepeatedDouble() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedDouble())
+			},
+		},
+		{
+			fName:  "repeated_bool",
+			set:    func() { x.SetRepeatedBool([]bool{}) },
+			setNil: func() { x.SetRepeatedBool(nil) },
+			len: func() int {
+				if x.GetRepeatedBool() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedBool())
+			},
+		},
+		{
+			fName:  "repeated_string",
+			set:    func() { x.SetRepeatedString([]string{}) },
+			setNil: func() { x.SetRepeatedString(nil) },
+			len: func() int {
+				if x.GetRepeatedString() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedString())
+			},
+		},
+		{
+			fName:  "repeated_bytes",
+			set:    func() { x.SetRepeatedBytes([][]byte{}) },
+			setNil: func() { x.SetRepeatedBytes(nil) },
+			len: func() int {
+				if x.GetRepeatedBytes() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedBytes())
+			},
+		},
+		{
+			fName:  "RepeatedGroup",
+			set:    func() { x.SetRepeatedgroup([]*testopaquepb.TestAllTypes_RepeatedGroup{}) },
+			setNil: func() { x.SetRepeatedgroup(nil) },
+			len: func() int {
+				if x.GetRepeatedgroup() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedgroup())
+			},
+		},
+		{
+			fName:  "repeated_nested_message",
+			set:    func() { x.SetRepeatedNestedMessage([]*testopaquepb.TestAllTypes_NestedMessage{}) },
+			setNil: func() { x.SetRepeatedNestedMessage(nil) },
+			len: func() int {
+				if x.GetRepeatedNestedMessage() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedNestedMessage())
+			},
+		},
+		{
+			fName:  "repeated_nested_enum",
+			set:    func() { x.SetRepeatedNestedEnum([]testopaquepb.TestAllTypes_NestedEnum{}) },
+			setNil: func() { x.SetRepeatedNestedEnum(nil) },
+			len: func() int {
+				if x.GetRepeatedNestedEnum() == nil {
+					return -1
+				}
+				return len(x.GetRepeatedNestedEnum())
+			},
+		},
+	}
+
+	for _, mv := range tab {
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil to start with ", mv.fName)
+		}
+		mv.set()
+		if mv.len() != 0 {
+			t.Errorf("Repeated field %s did not retain empty slice ", mv.fName)
+		}
+		b, err := proto.Marshal(x)
+		if err != nil {
+			t.Fatalf("Failed to marshal message, err = %v", err)
+		}
+		proto.Unmarshal(b, x)
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil to start with ", mv.fName)
+		}
+		mv.set()
+		mv.setNil()
+		if mv.len() != -1 {
+			t.Errorf("Repeated field %s was not nil event though we set it to ", mv.fName)
+		}
+
+	}
+
+	// Check that we actually retain the same slice
+	s := make([]testopaquepb.TestAllTypes_NestedEnum, 0, 455)
+	x.SetRepeatedNestedEnum(s)
+	if got, want := cap(x.GetRepeatedNestedEnum()), 455; got != want {
+		t.Errorf("cap(x.GetRepeatedNestedEnum()) returned %v, expected %v", got, want)
+	}
+	// Do this for a message too
+	s2 := make([]*testopaquepb.TestAllTypes_NestedMessage, 0, 544)
+	x.SetRepeatedNestedMessage(s2)
+	if got, want := cap(x.GetRepeatedNestedMessage()), 544; got != want {
+		t.Errorf("cap(x.GetRepeatedNestedMessage()) returned %v, expected %v", got, want)
+		t.Errorf("present: %v, isNilen: %v", checkPresent(x, 34), x.GetRepeatedNestedMessage())
+	}
+	// Check special bytes behavior
+	x.SetOptionalBytes(nil)
+	if got, want := x.HasOptionalBytes(), true; got != want {
+		t.Errorf("HasOptionalBytes after setting to nil returned %v, expected %v", got, want)
+	}
+	if got := x.GetOptionalBytes(); got == nil || len(got) != 0 {
+		t.Errorf("GetOptionalBytes after setting to nil returned %v, expected %v", got, []byte{})
+	}
+}
+
+func checkPresent(m proto.Message, fn uint32) bool {
+	vv := reflect.ValueOf(m).Elem()
+	rf := vv.FieldByName("XXX_presence")
+	rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
+	ai := int(fn) / 32
+	bit := fn % 32
+	ptr := rf.Index(ai).Addr().Interface().(*uint32)
+	return (*ptr & (1 << bit)) > 0
+}
diff --git a/proto/testmessages_opaque_test.go b/proto/testmessages_opaque_test.go
new file mode 100644
index 0000000..2cc51f1
--- /dev/null
+++ b/proto/testmessages_opaque_test.go
@@ -0,0 +1,97 @@
+// 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 proto_test
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"google.golang.org/protobuf/internal/impl"
+	"google.golang.org/protobuf/internal/protobuild"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/testing/protopack"
+
+	_ "google.golang.org/protobuf/internal/testprotos/lazy"
+	_ "google.golang.org/protobuf/internal/testprotos/lazy/lazy_opaque"
+	_ "google.golang.org/protobuf/internal/testprotos/required"
+	_ "google.golang.org/protobuf/internal/testprotos/required/required_opaque"
+	_ "google.golang.org/protobuf/internal/testprotos/test"
+	_ "google.golang.org/protobuf/internal/testprotos/test/weak1"
+	_ "google.golang.org/protobuf/internal/testprotos/test3"
+	_ "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	_ "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
+)
+
+var testLazyUnmarshal = flag.Bool("test_lazy_unmarshal", false, "test lazy proto.Unmarshal")
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+	impl.EnableLazyUnmarshal(*testLazyUnmarshal)
+	os.Exit(m.Run())
+}
+
+var relatedMessages = func() map[protoreflect.MessageType][]protoreflect.MessageType {
+	related := map[protoreflect.MessageType][]protoreflect.MessageType{}
+	const opaqueNamePrefix = "opaque."
+	protoregistry.GlobalTypes.RangeMessages(func(mt protoreflect.MessageType) bool {
+		name := mt.Descriptor().FullName()
+		if !strings.HasPrefix(string(name), opaqueNamePrefix) {
+			return true
+		}
+		mt1, err := protoregistry.GlobalTypes.FindMessageByName(name[len(opaqueNamePrefix):])
+		if err != nil {
+			panic(fmt.Sprintf("%v: can't find related message", name))
+		}
+		related[mt1] = append(related[mt1], mt)
+		return true
+	})
+	return related
+}()
+
+func init() {
+	testValidMessages = append(testValidMessages, []testProto{
+		{
+			desc:          "lazy field contains wrong wire type",
+			checkFastInit: true,
+			decodeTo: makeMessages(protobuild.Message{
+				"optional_nested_message": protobuild.Message{
+					protobuild.Unknown: protopack.Message{
+						protopack.Tag{2, protopack.VarintType}, protopack.Varint(3),
+					}.Marshal(),
+				},
+			}),
+			wire: protopack.Message{
+				protopack.Tag{18, protopack.BytesType}, protopack.LengthPrefix(protopack.Message{
+					protopack.Tag{2, protopack.VarintType}, protopack.Varint(3),
+				}),
+			}.Marshal(),
+		}, {
+			desc:          "lazy field contains right and wrong wire type",
+			checkFastInit: true,
+			decodeTo: makeMessages(protobuild.Message{
+				"optional_nested_message": protobuild.Message{
+					"corecursive": protobuild.Message{
+						"optional_int32": 2,
+					},
+					protobuild.Unknown: protopack.Message{
+						protopack.Tag{2, protopack.VarintType}, protopack.Varint(3),
+					}.Marshal(),
+				},
+			}),
+			wire: protopack.Message{
+				protopack.Tag{18, protopack.BytesType}, protopack.LengthPrefix(protopack.Message{
+					protopack.Tag{2, protopack.BytesType}, protopack.LengthPrefix(protopack.Message{
+						protopack.Tag{1, protopack.VarintType}, protopack.Varint(2),
+					}),
+					protopack.Tag{2, protopack.VarintType}, protopack.Varint(3),
+				}),
+			}.Marshal(),
+		},
+	}...)
+}
diff --git a/proto/testmessages_test.go b/proto/testmessages_test.go
index b1fccf8..4fb0e61 100644
--- a/proto/testmessages_test.go
+++ b/proto/testmessages_test.go
@@ -41,6 +41,13 @@
 			&testeditionspb.TestAllTypes{},
 		}
 	}
+
+	for _, m := range messages {
+		for _, mt := range relatedMessages[m.ProtoReflect().Type()] {
+			messages = append(messages, mt.New().Interface())
+		}
+	}
+
 	for _, m := range messages {
 		in.Build(m.ProtoReflect())
 	}
@@ -56,6 +63,13 @@
 			(*testeditionspb.TestAllTypes)(nil),
 		}
 	}
+
+	for _, m := range messages {
+		for _, mt := range relatedMessages[m.ProtoReflect().Type()] {
+			messages = append(messages, mt.New().Interface())
+		}
+	}
+
 	var out []protoreflect.MessageType
 	for _, m := range messages {
 		out = append(out, m.ProtoReflect().Type())
diff --git a/proto/wrapperopaque.go b/proto/wrapperopaque.go
new file mode 100644
index 0000000..267fd0f
--- /dev/null
+++ b/proto/wrapperopaque.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 proto
+
+// ValueOrNil returns nil if has is false, or a pointer to a new variable
+// containing the value returned by the specified getter.
+//
+// This function is similar to the wrappers (proto.Int32(), proto.String(),
+// etc.), but is generic (works for any field type) and works with the hasser
+// and getter of a field, as opposed to a value.
+//
+// This is convenient when populating builder fields.
+//
+// Example:
+//
+//	hop := attr.GetDirectHop()
+//	injectedRoute := ripb.InjectedRoute_builder{
+//	  Prefixes: route.GetPrefixes(),
+//	  NextHop:  proto.ValueOrNil(hop.HasAddress(), hop.GetAddress),
+//	}
+func ValueOrNil[T any](has bool, getter func() T) *T {
+	if !has {
+		return nil
+	}
+	v := getter()
+	return &v
+}
+
+// ValueOrDefault returns the protobuf message val if val is not nil, otherwise
+// it returns a pointer to an empty val message.
+//
+// This function allows for translating code from the old Open Struct API to the
+// new Opaque API.
+//
+// The old Open Struct API represented oneof fields with a wrapper struct:
+//
+//	var signedImg *accountpb.SignedImage
+//	profile := &accountpb.Profile{
+//		// The Avatar oneof will be set, with an empty SignedImage.
+//		Avatar: &accountpb.Profile_SignedImage{signedImg},
+//	}
+//
+// The new Opaque API treats oneof fields like regular fields, there are no more
+// wrapper structs:
+//
+//	var signedImg *accountpb.SignedImage
+//	profile := &accountpb.Profile{}
+//	profile.SetSignedImage(signedImg)
+//
+// For convenience, the Opaque API also offers Builders, which allow for a
+// direct translation of struct initialization. However, because Builders use
+// nilness to represent field presence (but there is no non-nil wrapper struct
+// anymore), Builders cannot distinguish between an unset oneof and a set oneof
+// with nil message. The above code would need to be translated with help of the
+// ValueOrDefault function to retain the same behavior:
+//
+//	var signedImg *accountpb.SignedImage
+//	return &accountpb.Profile_builder{
+//		SignedImage: proto.ValueOrDefault(signedImg),
+//	}.Build()
+func ValueOrDefault[T interface {
+	*P
+	Message
+}, P any](val T) T {
+	if val == nil {
+		return T(new(P))
+	}
+	return val
+}
+
+// ValueOrDefaultBytes is like ValueOrDefault but for working with fields of
+// type []byte.
+func ValueOrDefaultBytes(val []byte) []byte {
+	if val == nil {
+		return []byte{}
+	}
+	return val
+}
diff --git a/proto/wrapperopaque_test.go b/proto/wrapperopaque_test.go
new file mode 100644
index 0000000..0fdd4a0
--- /dev/null
+++ b/proto/wrapperopaque_test.go
@@ -0,0 +1,173 @@
+// 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 proto_test
+
+import (
+	"bytes"
+	"testing"
+
+	testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
+	"google.golang.org/protobuf/proto"
+)
+
+func TestOneofOrDefault(t *testing.T) {
+	for _, tt := range []struct {
+		desc  string
+		input func() *testhybridpb.TestAllTypes
+	}{
+		{
+			desc: "struct literal with nil nested message",
+			input: func() *testhybridpb.TestAllTypes {
+				return &testhybridpb.TestAllTypes{
+					OneofField: &testhybridpb.TestAllTypes_OneofNestedMessage{
+						OneofNestedMessage: nil,
+					},
+				}
+			},
+		},
+
+		{
+			desc: "struct literal with non-nil nested message",
+			input: func() *testhybridpb.TestAllTypes {
+				return &testhybridpb.TestAllTypes{
+					OneofField: &testhybridpb.TestAllTypes_OneofNestedMessage{
+						OneofNestedMessage: &testhybridpb.TestAllTypes_NestedMessage{},
+					},
+				}
+			},
+		},
+
+		{
+			desc: "opaque setter with ValueOrDefault",
+			input: func() *testhybridpb.TestAllTypes {
+				msg := &testhybridpb.TestAllTypes{}
+				msg.ClearOneofString()
+				var val *testhybridpb.TestAllTypes_NestedMessage
+				msg.SetOneofNestedMessage(proto.ValueOrDefault(val))
+				return msg
+			},
+		},
+
+		{
+			desc: "opaque builder with ValueOrDefault",
+			input: func() *testhybridpb.TestAllTypes {
+				var val *testhybridpb.TestAllTypes_NestedMessage
+				return testhybridpb.TestAllTypes_builder{
+					OneofNestedMessage: proto.ValueOrDefault(val),
+				}.Build()
+			},
+		},
+	} {
+		t.Run(tt.desc, func(t *testing.T) {
+			msg := tt.input()
+			b, err := proto.Marshal(msg)
+			if err != nil {
+				t.Fatal(err)
+			}
+			want := []byte{130, 7, 0}
+			if !bytes.Equal(b, want) {
+				t.Fatalf("Marshal: got %x, want %x", b, want)
+			}
+			if !msg.HasOneofField() {
+				t.Fatalf("HasOneofField was false, want true")
+			}
+			if got, want := msg.WhichOneofField(), testhybridpb.TestAllTypes_OneofNestedMessage_case; got != want {
+				t.Fatalf("WhichOneofField: got %v, want %v", got, want)
+			}
+			if !msg.HasOneofNestedMessage() {
+				t.Fatalf("HasOneofNestedMessage was false, want true")
+			}
+			if msg.HasOneofString() {
+				t.Fatalf("HasOneofString was true, want false")
+			}
+		})
+	}
+}
+
+func TestOneofOrDefaultBytes(t *testing.T) {
+	for _, tt := range []struct {
+		desc     string
+		input    func() *testhybridpb.TestAllTypes
+		wantWire []byte
+	}{
+		{
+			desc: "struct literal with nil bytes",
+			input: func() *testhybridpb.TestAllTypes {
+				return &testhybridpb.TestAllTypes{
+					OneofField: &testhybridpb.TestAllTypes_OneofBytes{
+						OneofBytes: nil,
+					},
+				}
+			},
+		},
+
+		{
+			desc: "struct literal with non-nil bytes",
+			input: func() *testhybridpb.TestAllTypes {
+				return &testhybridpb.TestAllTypes{
+					OneofField: &testhybridpb.TestAllTypes_OneofBytes{
+						OneofBytes: []byte{},
+					},
+				}
+			},
+		},
+
+		{
+			desc: "opaque setter with ValueOrDefaultBytes",
+			input: func() *testhybridpb.TestAllTypes {
+				msg := &testhybridpb.TestAllTypes{}
+				msg.ClearOneofString()
+				var val []byte
+				msg.SetOneofBytes(proto.ValueOrDefaultBytes(val))
+				return msg
+			},
+		},
+
+		{
+			desc: "opaque setter",
+			input: func() *testhybridpb.TestAllTypes {
+				msg := &testhybridpb.TestAllTypes{}
+				msg.ClearOneofString()
+				var val []byte
+				msg.SetOneofBytes(val)
+				return msg
+			},
+		},
+
+		{
+			desc: "opaque builder with ValueOrDefaultBytes",
+			input: func() *testhybridpb.TestAllTypes {
+				var val []byte
+				return testhybridpb.TestAllTypes_builder{
+					OneofBytes: proto.ValueOrDefaultBytes(val),
+				}.Build()
+			},
+		},
+	} {
+		t.Run(tt.desc, func(t *testing.T) {
+			msg := tt.input()
+			b, err := proto.Marshal(msg)
+			if err != nil {
+				t.Fatal(err)
+			}
+			want := []byte{146, 7, 0}
+			if !bytes.Equal(b, want) {
+				t.Fatalf("Marshal: got %x, want %x", b, want)
+			}
+			if !msg.HasOneofField() {
+				t.Fatalf("HasOneofField was false, want true")
+			}
+			if got, want := msg.WhichOneofField(), testhybridpb.TestAllTypes_OneofBytes_case; got != want {
+				t.Fatalf("WhichOneofField: got %v, want %v", got, want)
+			}
+			if !msg.HasOneofBytes() {
+				t.Fatalf("HasOneofBytes was false, want true")
+			}
+			if msg.HasOneofString() {
+				t.Fatalf("HasOneofString was true, want false")
+			}
+		})
+	}
+}
diff --git a/reflect/protodesc/editions.go b/reflect/protodesc/editions.go
index d0aeab9..bf0a0cc 100644
--- a/reflect/protodesc/editions.go
+++ b/reflect/protodesc/editions.go
@@ -132,6 +132,9 @@
 		if sep := goFeatures.StripEnumPrefix; sep != nil {
 			parentFS.StripEnumPrefix = int(*sep)
 		}
+		if al := goFeatures.ApiLevel; al != nil {
+			parentFS.APILevel = int(*al)
+		}
 	}
 
 	return parentFS
diff --git a/runtime/protoiface/methods.go b/runtime/protoiface/methods.go
index 2461565..28e9e9f 100644
--- a/runtime/protoiface/methods.go
+++ b/runtime/protoiface/methods.go
@@ -122,6 +122,22 @@
 
 const (
 	UnmarshalDiscardUnknown UnmarshalInputFlags = 1 << iota
+
+	// UnmarshalAliasBuffer permits unmarshal operations to alias the input buffer.
+	// The unmarshaller must not modify the contents of the buffer.
+	UnmarshalAliasBuffer
+
+	// UnmarshalValidated indicates that validation has already been
+	// performed on the input buffer.
+	UnmarshalValidated
+
+	// UnmarshalCheckRequired is set if this unmarshal operation ultimately will care if required fields are
+	// initialized.
+	UnmarshalCheckRequired
+
+	// UnmarshalNoLazyDecoding is set if this unmarshal operation should not use
+	// lazy decoding, even when otherwise available.
+	UnmarshalNoLazyDecoding
 )
 
 // UnmarshalOutputFlags are output from the Unmarshal method.
diff --git a/runtime/protoimpl/impl.go b/runtime/protoimpl/impl.go
index 4a1ab7f..93df1b5 100644
--- a/runtime/protoimpl/impl.go
+++ b/runtime/protoimpl/impl.go
@@ -15,6 +15,7 @@
 	"google.golang.org/protobuf/internal/filedesc"
 	"google.golang.org/protobuf/internal/filetype"
 	"google.golang.org/protobuf/internal/impl"
+	"google.golang.org/protobuf/internal/protolazy"
 )
 
 // UnsafeEnabled specifies whether package unsafe can be used.
@@ -39,6 +40,9 @@
 	ExtensionFieldV1 = impl.ExtensionField
 
 	Pointer = impl.Pointer
+
+	LazyUnmarshalInfo  = *protolazy.XXX_lazyUnmarshalInfo
+	RaceDetectHookData = impl.RaceDetectHookData
 )
 
 var X impl.Export
diff --git a/runtime/protolazy/protolazy.go b/runtime/protolazy/protolazy.go
new file mode 100644
index 0000000..cae330d
--- /dev/null
+++ b/runtime/protolazy/protolazy.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 protolazy controls the lazy implementation in the protobuf runtime.
+//
+// The following logic determines whether lazy decoding is enabled:
+//  1. Lazy decoding is enabled by default, unless the environment variable
+//     GOPROTODEBUG=nolazy is set.
+//  2. If still on, calling protolazy.Disable() turns off lazy decoding.
+//  3. If still on, proto.UnmarshalOptions's NoLazyDecoding turns off
+//     lazy decoding for this Unmarshal operation only.
+package protolazy
+
+import (
+	"google.golang.org/protobuf/internal/impl"
+)
+
+// Disable disables lazy unmarshaling of opaque messages.
+//
+// Messages which are still on the OPEN or HYBRID API level (see
+// http://go/go-opaque-proto) are never lazily unmarshalled.
+//
+// Fields must be annotated with [lazy = true] in their .proto file to become
+// eligible for lazy unmarshaling.
+func Disable() (reenable func()) {
+	impl.EnableLazyUnmarshal(false)
+	return func() {
+		impl.EnableLazyUnmarshal(true)
+	}
+}
diff --git a/src/google/protobuf/go_features.proto b/src/google/protobuf/go_features.proto
index 7ab74f5..a7e14f8 100644
--- a/src/google/protobuf/go_features.proto
+++ b/src/google/protobuf/go_features.proto
@@ -33,6 +33,28 @@
     edition_defaults = { edition: EDITION_PROTO3, value: "false" }
   ];
 
+  enum APILevel {
+    // API_LEVEL_UNSPECIFIED results in selecting the OPEN API,
+    // but needs to be a separate value to distinguish between
+    // an explicitly set api level or a missing api level.
+    API_LEVEL_UNSPECIFIED = 0;
+    API_OPEN = 1;
+    API_HYBRID = 2;
+    API_OPAQUE = 3;
+  }
+
+  // One of OPEN, HYBRID or OPAQUE.
+  optional APILevel api_level = 2 [
+    retention = RETENTION_RUNTIME,
+    targets = TARGET_TYPE_MESSAGE,
+    targets = TARGET_TYPE_FILE,
+    feature_support = {
+      edition_introduced: EDITION_2023,
+    },
+    edition_defaults = { edition: EDITION_LEGACY, value: "API_LEVEL_UNSPECIFIED" },
+    edition_defaults = { edition: EDITION_2024, value: "API_OPAQUE" }
+   ];
+
   enum StripEnumPrefix {
     STRIP_ENUM_PREFIX_UNSPECIFIED = 0;
     STRIP_ENUM_PREFIX_KEEP = 1;
diff --git a/testing/prototest/message.go b/testing/prototest/message.go
index eaf53cf..def37bf 100644
--- a/testing/prototest/message.go
+++ b/testing/prototest/message.go
@@ -33,6 +33,10 @@
 		FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
 		RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
 	}
+
+	// UnmarshalOptions are respected for every Unmarshal call this package
+	// does. The Resolver and AllowPartial fields are overridden.
+	UnmarshalOptions proto.UnmarshalOptions
 }
 
 // Test performs tests on a [protoreflect.MessageType] implementation.
@@ -74,10 +78,10 @@
 		t.Errorf("Marshal() = %v, want nil\n%v", err, prototext.Format(m2))
 	}
 	m3 := mt.New().Interface()
-	if err := (proto.UnmarshalOptions{
-		AllowPartial: true,
-		Resolver:     test.Resolver,
-	}.Unmarshal(b, m3)); err != nil {
+	unmarshalOpts := test.UnmarshalOptions
+	unmarshalOpts.AllowPartial = true
+	unmarshalOpts.Resolver = test.Resolver
+	if err := unmarshalOpts.Unmarshal(b, m3); err != nil {
 		t.Errorf("Unmarshal() = %v, want nil\n%v", err, prototext.Format(m2))
 	}
 	if !proto.Equal(m2, m3) {
diff --git a/types/gofeaturespb/go_features.pb.go b/types/gofeaturespb/go_features.pb.go
index 5067b89..61e3f76 100644
--- a/types/gofeaturespb/go_features.pb.go
+++ b/types/gofeaturespb/go_features.pb.go
@@ -18,6 +18,71 @@
 	sync "sync"
 )
 
+type GoFeatures_APILevel int32
+
+const (
+	// API_LEVEL_UNSPECIFIED results in selecting the OPEN API,
+	// but needs to be a separate value to distinguish between
+	// an explicitly set api level or a missing api level.
+	GoFeatures_API_LEVEL_UNSPECIFIED GoFeatures_APILevel = 0
+	GoFeatures_API_OPEN              GoFeatures_APILevel = 1
+	GoFeatures_API_HYBRID            GoFeatures_APILevel = 2
+	GoFeatures_API_OPAQUE            GoFeatures_APILevel = 3
+)
+
+// Enum value maps for GoFeatures_APILevel.
+var (
+	GoFeatures_APILevel_name = map[int32]string{
+		0: "API_LEVEL_UNSPECIFIED",
+		1: "API_OPEN",
+		2: "API_HYBRID",
+		3: "API_OPAQUE",
+	}
+	GoFeatures_APILevel_value = map[string]int32{
+		"API_LEVEL_UNSPECIFIED": 0,
+		"API_OPEN":              1,
+		"API_HYBRID":            2,
+		"API_OPAQUE":            3,
+	}
+)
+
+func (x GoFeatures_APILevel) Enum() *GoFeatures_APILevel {
+	p := new(GoFeatures_APILevel)
+	*p = x
+	return p
+}
+
+func (x GoFeatures_APILevel) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (GoFeatures_APILevel) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_go_features_proto_enumTypes[0].Descriptor()
+}
+
+func (GoFeatures_APILevel) Type() protoreflect.EnumType {
+	return &file_google_protobuf_go_features_proto_enumTypes[0]
+}
+
+func (x GoFeatures_APILevel) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *GoFeatures_APILevel) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = GoFeatures_APILevel(num)
+	return nil
+}
+
+// Deprecated: Use GoFeatures_APILevel.Descriptor instead.
+func (GoFeatures_APILevel) EnumDescriptor() ([]byte, []int) {
+	return file_google_protobuf_go_features_proto_rawDescGZIP(), []int{0, 0}
+}
+
 type GoFeatures_StripEnumPrefix int32
 
 const (
@@ -54,11 +119,11 @@
 }
 
 func (GoFeatures_StripEnumPrefix) Descriptor() protoreflect.EnumDescriptor {
-	return file_google_protobuf_go_features_proto_enumTypes[0].Descriptor()
+	return file_google_protobuf_go_features_proto_enumTypes[1].Descriptor()
 }
 
 func (GoFeatures_StripEnumPrefix) Type() protoreflect.EnumType {
-	return &file_google_protobuf_go_features_proto_enumTypes[0]
+	return &file_google_protobuf_go_features_proto_enumTypes[1]
 }
 
 func (x GoFeatures_StripEnumPrefix) Number() protoreflect.EnumNumber {
@@ -77,17 +142,18 @@
 
 // Deprecated: Use GoFeatures_StripEnumPrefix.Descriptor instead.
 func (GoFeatures_StripEnumPrefix) EnumDescriptor() ([]byte, []int) {
-	return file_google_protobuf_go_features_proto_rawDescGZIP(), []int{0, 0}
+	return file_google_protobuf_go_features_proto_rawDescGZIP(), []int{0, 1}
 }
 
 type GoFeatures struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
+	state protoimpl.MessageState `protogen:"open.v1"`
 	// Whether or not to generate the deprecated UnmarshalJSON method for enums.
-	LegacyUnmarshalJsonEnum *bool                       `protobuf:"varint,1,opt,name=legacy_unmarshal_json_enum,json=legacyUnmarshalJsonEnum" json:"legacy_unmarshal_json_enum,omitempty"`
-	StripEnumPrefix         *GoFeatures_StripEnumPrefix `protobuf:"varint,3,opt,name=strip_enum_prefix,json=stripEnumPrefix,enum=pb.GoFeatures_StripEnumPrefix" json:"strip_enum_prefix,omitempty"`
+	LegacyUnmarshalJsonEnum *bool `protobuf:"varint,1,opt,name=legacy_unmarshal_json_enum,json=legacyUnmarshalJsonEnum" json:"legacy_unmarshal_json_enum,omitempty"`
+	// One of OPEN, HYBRID or OPAQUE.
+	ApiLevel        *GoFeatures_APILevel        `protobuf:"varint,2,opt,name=api_level,json=apiLevel,enum=pb.GoFeatures_APILevel" json:"api_level,omitempty"`
+	StripEnumPrefix *GoFeatures_StripEnumPrefix `protobuf:"varint,3,opt,name=strip_enum_prefix,json=stripEnumPrefix,enum=pb.GoFeatures_StripEnumPrefix" json:"strip_enum_prefix,omitempty"`
+	unknownFields   protoimpl.UnknownFields
+	sizeCache       protoimpl.SizeCache
 }
 
 func (x *GoFeatures) Reset() {
@@ -127,6 +193,13 @@
 	return false
 }
 
+func (x *GoFeatures) GetApiLevel() GoFeatures_APILevel {
+	if x != nil && x.ApiLevel != nil {
+		return *x.ApiLevel
+	}
+	return GoFeatures_API_LEVEL_UNSPECIFIED
+}
+
 func (x *GoFeatures) GetStripEnumPrefix() GoFeatures_StripEnumPrefix {
 	if x != nil && x.StripEnumPrefix != nil {
 		return *x.StripEnumPrefix
@@ -158,7 +231,7 @@
 	0x66, 0x2f, 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
-	0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x03, 0x0a, 0x0a, 0x47, 0x6f,
+	0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xab, 0x05, 0x0a, 0x0a, 0x47, 0x6f,
 	0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0xbe, 0x01, 0x0a, 0x1a, 0x6c, 0x65, 0x67,
 	0x61, 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x5f, 0x6a, 0x73,
 	0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x80, 0x01,
@@ -171,31 +244,44 @@
 	0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61,
 	0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x20, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
 	0x52, 0x17, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61,
-	0x6c, 0x4a, 0x73, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x7c, 0x0a, 0x11, 0x73, 0x74, 0x72,
-	0x69, 0x70, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x6f, 0x46, 0x65, 0x61, 0x74,
-	0x75, 0x72, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x70, 0x45, 0x6e, 0x75, 0x6d, 0x50, 0x72,
-	0x65, 0x66, 0x69, 0x78, 0x42, 0x30, 0x88, 0x01, 0x01, 0x98, 0x01, 0x06, 0x98, 0x01, 0x07, 0x98,
-	0x01, 0x01, 0xa2, 0x01, 0x1b, 0x12, 0x16, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55,
-	0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f, 0x4b, 0x45, 0x45, 0x50, 0x18, 0x84, 0x07,
-	0xb2, 0x01, 0x03, 0x08, 0xe9, 0x07, 0x52, 0x0f, 0x73, 0x74, 0x72, 0x69, 0x70, 0x45, 0x6e, 0x75,
-	0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x92, 0x01, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x69,
-	0x70, 0x45, 0x6e, 0x75, 0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x21, 0x0a, 0x1d, 0x53,
-	0x54, 0x52, 0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58,
-	0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a,
-	0x0a, 0x16, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45,
-	0x46, 0x49, 0x58, 0x5f, 0x4b, 0x45, 0x45, 0x50, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x54,
+	0x6c, 0x4a, 0x73, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x74, 0x0a, 0x09, 0x61, 0x70, 0x69,
+	0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70,
+	0x62, 0x2e, 0x47, 0x6f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x41, 0x50, 0x49,
+	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x3e, 0x88, 0x01, 0x01, 0x98, 0x01, 0x03, 0x98, 0x01, 0x01,
+	0xa2, 0x01, 0x1a, 0x12, 0x15, 0x41, 0x50, 0x49, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55,
+	0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x0f,
+	0x12, 0x0a, 0x41, 0x50, 0x49, 0x5f, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45, 0x18, 0xe9, 0x07, 0xb2,
+	0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x08, 0x61, 0x70, 0x69, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12,
+	0x7c, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x69, 0x70, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x72,
+	0x65, 0x66, 0x69, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x70, 0x62, 0x2e,
+	0x47, 0x6f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x70,
+	0x45, 0x6e, 0x75, 0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x42, 0x30, 0x88, 0x01, 0x01, 0x98,
+	0x01, 0x06, 0x98, 0x01, 0x07, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x1b, 0x12, 0x16, 0x53, 0x54, 0x52,
+	0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f, 0x4b,
+	0x45, 0x45, 0x50, 0x18, 0x84, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe9, 0x07, 0x52, 0x0f, 0x73, 0x74,
+	0x72, 0x69, 0x70, 0x45, 0x6e, 0x75, 0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x53, 0x0a,
+	0x08, 0x41, 0x50, 0x49, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x50, 0x49,
+	0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+	0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50, 0x49, 0x5f, 0x4f, 0x50, 0x45, 0x4e,
+	0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x50, 0x49, 0x5f, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44,
+	0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x50, 0x49, 0x5f, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45,
+	0x10, 0x03, 0x22, 0x92, 0x01, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x69, 0x70, 0x45, 0x6e, 0x75, 0x6d,
+	0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f,
+	0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+	0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x52,
+	0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f, 0x4b,
+	0x45, 0x45, 0x50, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f, 0x45,
+	0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52,
+	0x41, 0x54, 0x45, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54,
 	0x52, 0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x5f,
-	0x47, 0x45, 0x4e, 0x45, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x02, 0x12,
-	0x1b, 0x0a, 0x17, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x50, 0x52,
-	0x45, 0x46, 0x49, 0x58, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x50, 0x10, 0x03, 0x3a, 0x3c, 0x0a, 0x02,
-	0x67, 0x6f, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x18,
-	0xea, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x6f, 0x46, 0x65,
-	0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x02, 0x67, 0x6f, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x67,
-	0x6f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x62,
+	0x53, 0x54, 0x52, 0x49, 0x50, 0x10, 0x03, 0x3a, 0x3c, 0x0a, 0x02, 0x67, 0x6f, 0x12, 0x1b, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x18, 0xea, 0x07, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x6f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
+	0x73, 0x52, 0x02, 0x67, 0x6f, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x67, 0x6f, 0x66, 0x65, 0x61, 0x74,
+	0x75, 0x72, 0x65, 0x73, 0x70, 0x62,
 }
 
 var (
@@ -210,22 +296,24 @@
 	return file_google_protobuf_go_features_proto_rawDescData
 }
 
-var file_google_protobuf_go_features_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_google_protobuf_go_features_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_google_protobuf_go_features_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
 var file_google_protobuf_go_features_proto_goTypes = []any{
-	(GoFeatures_StripEnumPrefix)(0), // 0: pb.GoFeatures.StripEnumPrefix
-	(*GoFeatures)(nil),              // 1: pb.GoFeatures
-	(*descriptorpb.FeatureSet)(nil), // 2: google.protobuf.FeatureSet
+	(GoFeatures_APILevel)(0),        // 0: pb.GoFeatures.APILevel
+	(GoFeatures_StripEnumPrefix)(0), // 1: pb.GoFeatures.StripEnumPrefix
+	(*GoFeatures)(nil),              // 2: pb.GoFeatures
+	(*descriptorpb.FeatureSet)(nil), // 3: google.protobuf.FeatureSet
 }
 var file_google_protobuf_go_features_proto_depIdxs = []int32{
-	0, // 0: pb.GoFeatures.strip_enum_prefix:type_name -> pb.GoFeatures.StripEnumPrefix
-	2, // 1: pb.go:extendee -> google.protobuf.FeatureSet
-	1, // 2: pb.go:type_name -> pb.GoFeatures
-	3, // [3:3] is the sub-list for method output_type
-	3, // [3:3] is the sub-list for method input_type
-	2, // [2:3] is the sub-list for extension type_name
-	1, // [1:2] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
+	0, // 0: pb.GoFeatures.api_level:type_name -> pb.GoFeatures.APILevel
+	1, // 1: pb.GoFeatures.strip_enum_prefix:type_name -> pb.GoFeatures.StripEnumPrefix
+	3, // 2: pb.go:extendee -> google.protobuf.FeatureSet
+	2, // 3: pb.go:type_name -> pb.GoFeatures
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	3, // [3:4] is the sub-list for extension type_name
+	2, // [2:3] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
 }
 
 func init() { file_google_protobuf_go_features_proto_init() }
@@ -238,7 +326,7 @@
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_google_protobuf_go_features_proto_rawDesc,
-			NumEnums:      1,
+			NumEnums:      2,
 			NumMessages:   1,
 			NumExtensions: 1,
 			NumServices:   0,