internal/impl: add runtime support for aberrant messages

Implement support in the protobuf runtime to better understand
message types that are not generated by the official generator.

In particular:
* Add a best-effort implementation of protobuf reflection for
"non-nullable" fields which are supposed to be represented by *T,
but are instead represented by a T. "Non-nullable" message fields
report presence based on whether the message is the zero Go value.
* We do NOT implement support for "non-nullable" fields in the
table-driven implementation since we assume that the aberrant messages
that we care about have a Marshal and Unmarshal method.
* We better handle custom messages that implement Marshal and Unmarshal,
but do NOT implement Merge. In that case, we implement merge in terms of
a back-to-back marshal and unmarshal.
* We better tolerate the situations where a protobuf message field
cannot be mapped to a Go struct field since the latter is missing.
In such cases, reflection treats the field as if it were unpopulated.
Setting such fields will panic.

This change allows the runtime to handle all message types declared
in the "go.etcd.io/etcd" and "k8s.io" modules where protobuf reflection,
Marshal, Unmarshal, Reset, Merge, and Equal all work.

The only types that still do not fully work are:
	* "k8s.io/api/authentication/v1".ExtraValue
	* "k8s.io/api/authentication/v1beta1".ExtraValue
	* "k8s.io/api/authorization/v1".ExtraValue
	* "k8s.io/api/authorization/v1beta1".ExtraValue
	* "k8s.io/api/certificates/v1".ExtraValue
	* "k8s.io/api/certificates/v1beta1".ExtraValue
	* "k8s.io/apimachinery/pkg/apis/meta/v1".MicroTime
	* "k8s.io/apimachinery/pkg/apis/meta/v1".Time
	* "k8s.io/apimachinery/pkg/apis/meta/v1".Verbs
While Marshal, Unmarshal, Reset, and Merge continue to work,
protobuf reflection and any functionality that depends on it
(e.g., prototext, protojson, Equal, etc.) will not work.

Change-Id: I67a9d2f1bec35248045ad0c16220d02fc2e0e172
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/300869
Trust: Joe Tsai <joetsai@digital-static.net>
Trust: Joe Tsai <thebrokentoaster@gmail.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/codec_message.go b/internal/impl/codec_message.go
index e108642..cd40527 100644
--- a/internal/impl/codec_message.go
+++ b/internal/impl/codec_message.go
@@ -85,6 +85,27 @@
 		var funcs pointerCoderFuncs
 		var childMessage *MessageInfo
 		switch {
+		case ft == nil:
+			// This never occurs for generated message types.
+			// It implies that a hand-crafted type has missing Go fields
+			// for specific protobuf message fields.
+			funcs = pointerCoderFuncs{
+				size: func(p pointer, f *coderFieldInfo, opts marshalOptions) int {
+					return 0
+				},
+				marshal: func(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
+					return nil, nil
+				},
+				unmarshal: func(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (unmarshalOutput, error) {
+					panic("missing Go struct field for " + string(fd.FullName()))
+				},
+				isInit: func(p pointer, f *coderFieldInfo) error {
+					panic("missing Go struct field for " + string(fd.FullName()))
+				},
+				merge: func(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
+					panic("missing Go struct field for " + string(fd.FullName()))
+				},
+			}
 		case isOneof:
 			fieldOffset = offsetOf(fs, mi.Exporter)
 		case fd.IsWeak():
diff --git a/internal/impl/convert.go b/internal/impl/convert.go
index 36a90df..acd61bb 100644
--- a/internal/impl/convert.go
+++ b/internal/impl/convert.go
@@ -423,6 +423,13 @@
 	if v.Type() != c.goType {
 		panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), c.goType))
 	}
+	if c.isNonPointer() {
+		if v.CanAddr() {
+			v = v.Addr() // T => *T
+		} else {
+			v = reflect.Zero(reflect.PtrTo(v.Type()))
+		}
+	}
 	if m, ok := v.Interface().(pref.ProtoMessage); ok {
 		return pref.ValueOfMessage(m.ProtoReflect())
 	}
@@ -437,6 +444,16 @@
 	} else {
 		rv = reflect.ValueOf(m.Interface())
 	}
+	if c.isNonPointer() {
+		if rv.Type() != reflect.PtrTo(c.goType) {
+			panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), reflect.PtrTo(c.goType)))
+		}
+		if !rv.IsNil() {
+			rv = rv.Elem() // *T => T
+		} else {
+			rv = reflect.Zero(rv.Type().Elem())
+		}
+	}
 	if rv.Type() != c.goType {
 		panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), c.goType))
 	}
@@ -451,6 +468,9 @@
 	} else {
 		rv = reflect.ValueOf(m.Interface())
 	}
+	if c.isNonPointer() {
+		return rv.Type() == reflect.PtrTo(c.goType)
+	}
 	return rv.Type() == c.goType
 }
 
@@ -459,9 +479,18 @@
 }
 
 func (c *messageConverter) New() pref.Value {
+	if c.isNonPointer() {
+		return c.PBValueOf(reflect.New(c.goType).Elem())
+	}
 	return c.PBValueOf(reflect.New(c.goType.Elem()))
 }
 
 func (c *messageConverter) Zero() pref.Value {
 	return c.PBValueOf(reflect.Zero(c.goType))
 }
+
+// isNonPointer reports whether the type is a non-pointer type.
+// This never occurs for generated message types.
+func (c *messageConverter) isNonPointer() bool {
+	return c.goType.Kind() != reflect.Ptr
+}
diff --git a/internal/impl/legacy_message.go b/internal/impl/legacy_message.go
index b1d66c5..3759b01 100644
--- a/internal/impl/legacy_message.go
+++ b/internal/impl/legacy_message.go
@@ -24,11 +24,11 @@
 // legacyWrapMessage wraps v as a protoreflect.Message,
 // where v must be a *struct kind and not implement the v2 API already.
 func legacyWrapMessage(v reflect.Value) pref.Message {
-	typ := v.Type()
-	if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
+	t := v.Type()
+	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
 		return aberrantMessage{v: v}
 	}
-	mt := legacyLoadMessageInfo(typ, "")
+	mt := legacyLoadMessageInfo(t, "")
 	return mt.MessageOf(v.Interface())
 }
 
@@ -59,8 +59,9 @@
 		GoReflectType: t,
 	}
 
+	var hasMarshal, hasUnmarshal bool
 	v := reflect.Zero(t).Interface()
-	if _, ok := v.(legacyMarshaler); ok {
+	if _, hasMarshal = v.(legacyMarshaler); hasMarshal {
 		mi.methods.Marshal = legacyMarshal
 
 		// We have no way to tell whether the type's Marshal method
@@ -69,10 +70,10 @@
 		// calling Marshal methods when present.
 		mi.methods.Flags |= piface.SupportMarshalDeterministic
 	}
-	if _, ok := v.(legacyUnmarshaler); ok {
+	if _, hasUnmarshal = v.(legacyUnmarshaler); hasUnmarshal {
 		mi.methods.Unmarshal = legacyUnmarshal
 	}
-	if _, ok := v.(legacyMerger); ok {
+	if _, hasMerge := v.(legacyMerger); hasMerge || (hasMarshal && hasUnmarshal) {
 		mi.methods.Merge = legacyMerge
 	}
 
@@ -85,7 +86,7 @@
 var legacyMessageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
 
 // LegacyLoadMessageDesc returns an MessageDescriptor derived from the Go type,
-// which must be a *struct kind and not implement the v2 API already.
+// which should be a *struct kind and must not implement the v2 API already.
 //
 // This is exported for testing purposes.
 func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
@@ -124,17 +125,19 @@
 	// If the Go type has no fields, then this might be a proto3 empty message
 	// from before the size cache was added. If there are any fields, check to
 	// see that at least one of them looks like something we generated.
-	if nfield := t.Elem().NumField(); nfield > 0 {
-		hasProtoField := false
-		for i := 0; i < nfield; i++ {
-			f := t.Elem().Field(i)
-			if f.Tag.Get("protobuf") != "" || f.Tag.Get("protobuf_oneof") != "" || strings.HasPrefix(f.Name, "XXX_") {
-				hasProtoField = true
-				break
+	if t.Elem().Kind() == reflect.Struct {
+		if nfield := t.Elem().NumField(); nfield > 0 {
+			hasProtoField := false
+			for i := 0; i < nfield; i++ {
+				f := t.Elem().Field(i)
+				if f.Tag.Get("protobuf") != "" || f.Tag.Get("protobuf_oneof") != "" || strings.HasPrefix(f.Name, "XXX_") {
+					hasProtoField = true
+					break
+				}
 			}
-		}
-		if !hasProtoField {
-			return aberrantLoadMessageDesc(t, name)
+			if !hasProtoField {
+				return aberrantLoadMessageDesc(t, name)
+			}
 		}
 	}
 
@@ -411,18 +414,40 @@
 	v := in.Message.(unwrapper).protoUnwrap()
 	unmarshaler, ok := v.(legacyUnmarshaler)
 	if !ok {
-		return piface.UnmarshalOutput{}, errors.New("%T does not implement Marshal", v)
+		return piface.UnmarshalOutput{}, errors.New("%T does not implement Unmarshal", v)
 	}
 	return piface.UnmarshalOutput{}, unmarshaler.Unmarshal(in.Buf)
 }
 
 func legacyMerge(in piface.MergeInput) piface.MergeOutput {
+	// Check whether this supports the legacy merger.
 	dstv := in.Destination.(unwrapper).protoUnwrap()
 	merger, ok := dstv.(legacyMerger)
+	if ok {
+		merger.Merge(Export{}.ProtoMessageV1Of(in.Source))
+		return piface.MergeOutput{Flags: piface.MergeComplete}
+	}
+
+	// If legacy merger is unavailable, implement merge in terms of
+	// a marshal and unmarshal operation.
+	srcv := in.Source.(unwrapper).protoUnwrap()
+	marshaler, ok := srcv.(legacyMarshaler)
 	if !ok {
 		return piface.MergeOutput{}
 	}
-	merger.Merge(Export{}.ProtoMessageV1Of(in.Source))
+	dstv = in.Destination.(unwrapper).protoUnwrap()
+	unmarshaler, ok := dstv.(legacyUnmarshaler)
+	if !ok {
+		return piface.MergeOutput{}
+	}
+	b, err := marshaler.Marshal()
+	if err != nil {
+		return piface.MergeOutput{}
+	}
+	err = unmarshaler.Unmarshal(b)
+	if err != nil {
+		return piface.MergeOutput{}
+	}
 	return piface.MergeOutput{Flags: piface.MergeComplete}
 }
 
@@ -432,6 +457,9 @@
 }
 
 func (mt aberrantMessageType) New() pref.Message {
+	if mt.t.Kind() == reflect.Ptr {
+		return aberrantMessage{reflect.New(mt.t.Elem())}
+	}
 	return aberrantMessage{reflect.Zero(mt.t)}
 }
 func (mt aberrantMessageType) Zero() pref.Message {
@@ -453,6 +481,17 @@
 	v reflect.Value
 }
 
+// Reset implements the v1 proto.Message.Reset method.
+func (m aberrantMessage) Reset() {
+	if mr, ok := m.v.Interface().(interface{ Reset() }); ok {
+		mr.Reset()
+		return
+	}
+	if m.v.Kind() == reflect.Ptr && !m.v.IsNil() {
+		m.v.Elem().Set(reflect.Zero(m.v.Type().Elem()))
+	}
+}
+
 func (m aberrantMessage) ProtoReflect() pref.Message {
 	return m
 }
@@ -464,33 +503,40 @@
 	return aberrantMessageType{m.v.Type()}
 }
 func (m aberrantMessage) New() pref.Message {
+	if m.v.Type().Kind() == reflect.Ptr {
+		return aberrantMessage{reflect.New(m.v.Type().Elem())}
+	}
 	return aberrantMessage{reflect.Zero(m.v.Type())}
 }
 func (m aberrantMessage) Interface() pref.ProtoMessage {
 	return m
 }
 func (m aberrantMessage) Range(f func(pref.FieldDescriptor, pref.Value) bool) {
+	return
 }
 func (m aberrantMessage) Has(pref.FieldDescriptor) bool {
-	panic("invalid field descriptor")
+	return false
 }
 func (m aberrantMessage) Clear(pref.FieldDescriptor) {
-	panic("invalid field descriptor")
+	panic("invalid Message.Clear on " + string(m.Descriptor().FullName()))
 }
-func (m aberrantMessage) Get(pref.FieldDescriptor) pref.Value {
-	panic("invalid field descriptor")
+func (m aberrantMessage) Get(fd pref.FieldDescriptor) pref.Value {
+	if fd.Default().IsValid() {
+		return fd.Default()
+	}
+	panic("invalid Message.Get on " + string(m.Descriptor().FullName()))
 }
 func (m aberrantMessage) Set(pref.FieldDescriptor, pref.Value) {
-	panic("invalid field descriptor")
+	panic("invalid Message.Set on " + string(m.Descriptor().FullName()))
 }
 func (m aberrantMessage) Mutable(pref.FieldDescriptor) pref.Value {
-	panic("invalid field descriptor")
+	panic("invalid Message.Mutable on " + string(m.Descriptor().FullName()))
 }
 func (m aberrantMessage) NewField(pref.FieldDescriptor) pref.Value {
-	panic("invalid field descriptor")
+	panic("invalid Message.NewField on " + string(m.Descriptor().FullName()))
 }
 func (m aberrantMessage) WhichOneof(pref.OneofDescriptor) pref.FieldDescriptor {
-	panic("invalid oneof descriptor")
+	panic("invalid Message.WhichOneof descriptor on " + string(m.Descriptor().FullName()))
 }
 func (m aberrantMessage) GetUnknown() pref.RawFields {
 	return nil
@@ -499,10 +545,10 @@
 	// SetUnknown discards its input on messages which don't support unknown field storage.
 }
 func (m aberrantMessage) IsValid() bool {
-	// An invalid message is a read-only, empty message. Since we don't know anything
-	// about the alleged contents of this message, we can't say with confidence that
-	// it is invalid in this sense. Therefore, report it as valid.
-	return true
+	if m.v.Kind() == reflect.Ptr {
+		return !m.v.IsNil()
+	}
+	return false
 }
 func (m aberrantMessage) ProtoMethods() *piface.Methods {
 	return aberrantProtoMethods
diff --git a/internal/impl/message_reflect.go b/internal/impl/message_reflect.go
index 0c6f106..9488b72 100644
--- a/internal/impl/message_reflect.go
+++ b/internal/impl/message_reflect.go
@@ -58,10 +58,16 @@
 	for i := 0; i < fds.Len(); i++ {
 		fd := fds.Get(i)
 		fs := si.fieldsByNumber[fd.Number()]
+		isOneof := fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic()
+		if isOneof {
+			fs = si.oneofsByName[fd.ContainingOneof().Name()]
+		}
 		var fi fieldInfo
 		switch {
-		case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
-			fi = fieldInfoForOneof(fd, si.oneofsByName[fd.ContainingOneof().Name()], mi.Exporter, si.oneofWrappersByNumber[fd.Number()])
+		case fs.Type == nil:
+			fi = fieldInfoForMissing(fd) // never occurs for officially generated message types
+		case isOneof:
+			fi = fieldInfoForOneof(fd, fs, mi.Exporter, si.oneofWrappersByNumber[fd.Number()])
 		case fd.IsMap():
 			fi = fieldInfoForMap(fd, fs, mi.Exporter)
 		case fd.IsList():
@@ -179,8 +185,15 @@
 		var ft reflect.Type
 		fd := fds.Get(i)
 		fs := si.fieldsByNumber[fd.Number()]
+		isOneof := fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic()
+		if isOneof {
+			fs = si.oneofsByName[fd.ContainingOneof().Name()]
+		}
+		var isMessage bool
 		switch {
-		case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
+		case fs.Type == nil:
+			continue // never occurs for officially generated message types
+		case isOneof:
 			if fd.Enum() != nil || fd.Message() != nil {
 				ft = si.oneofWrappersByNumber[fd.Number()].Field(0).Type
 			}
@@ -188,13 +201,15 @@
 			if fd.MapValue().Enum() != nil || fd.MapValue().Message() != nil {
 				ft = fs.Type.Elem()
 			}
+			isMessage = fd.MapValue().Message() != nil
 		case fd.IsList():
 			if fd.Enum() != nil || fd.Message() != nil {
 				ft = fs.Type.Elem()
 			}
+			isMessage = fd.Message() != nil
 		case fd.Enum() != nil:
 			ft = fs.Type
-			if fd.HasPresence() {
+			if fd.HasPresence() && ft.Kind() == reflect.Ptr {
 				ft = ft.Elem()
 			}
 		case fd.Message() != nil:
@@ -202,6 +217,10 @@
 			if fd.IsWeak() {
 				ft = nil
 			}
+			isMessage = true
+		}
+		if isMessage && ft != nil && ft.Kind() != reflect.Ptr {
+			ft = reflect.PtrTo(ft) // never occurs for officially generated message types
 		}
 		if ft != nil {
 			if mi.fieldTypes == nil {
@@ -391,6 +410,17 @@
 func (m *messageReflectWrapper) pointer() pointer          { return m.p }
 func (m *messageReflectWrapper) messageInfo() *MessageInfo { return m.mi }
 
+// Reset implements the v1 proto.Message.Reset method.
+func (m *messageIfaceWrapper) Reset() {
+	if mr, ok := m.protoUnwrap().(interface{ Reset() }); ok {
+		mr.Reset()
+		return
+	}
+	rv := reflect.ValueOf(m.protoUnwrap())
+	if rv.Kind() == reflect.Ptr && !rv.IsNil() {
+		rv.Elem().Set(reflect.Zero(rv.Type().Elem()))
+	}
+}
 func (m *messageIfaceWrapper) ProtoReflect() pref.Message {
 	return (*messageReflectWrapper)(m)
 }
diff --git a/internal/impl/message_reflect_field.go b/internal/impl/message_reflect_field.go
index 23124a8..343cf87 100644
--- a/internal/impl/message_reflect_field.go
+++ b/internal/impl/message_reflect_field.go
@@ -28,6 +28,39 @@
 	newField   func() pref.Value
 }
 
+func fieldInfoForMissing(fd pref.FieldDescriptor) fieldInfo {
+	// This never occurs for generated message types.
+	// It implies that a hand-crafted type has missing Go fields
+	// for specific protobuf message fields.
+	return fieldInfo{
+		fieldDesc: fd,
+		has: func(p pointer) bool {
+			return false
+		},
+		clear: func(p pointer) {
+			panic("missing Go struct field for " + string(fd.FullName()))
+		},
+		get: func(p pointer) pref.Value {
+			return fd.Default()
+		},
+		set: func(p pointer, v pref.Value) {
+			panic("missing Go struct field for " + string(fd.FullName()))
+		},
+		mutable: func(p pointer) pref.Value {
+			panic("missing Go struct field for " + string(fd.FullName()))
+		},
+		newMessage: func() pref.Message {
+			panic("missing Go struct field for " + string(fd.FullName()))
+		},
+		newField: func() pref.Value {
+			if v := fd.Default(); v.IsValid() {
+				return v
+			}
+			panic("missing Go struct field for " + string(fd.FullName()))
+		},
+	}
+}
+
 func fieldInfoForOneof(fd pref.FieldDescriptor, fs reflect.StructField, x exporter, ot reflect.Type) fieldInfo {
 	ft := fs.Type
 	if ft.Kind() != reflect.Interface {
@@ -97,7 +130,7 @@
 				rv.Set(reflect.New(ot))
 			}
 			rv = rv.Elem().Elem().Field(0)
-			if rv.IsNil() {
+			if rv.Kind() == reflect.Ptr && rv.IsNil() {
 				rv.Set(conv.GoValueOf(pref.ValueOfMessage(conv.New().Message())))
 			}
 			return conv.PBValueOf(rv)
@@ -225,7 +258,10 @@
 	isBytes := ft.Kind() == reflect.Slice && ft.Elem().Kind() == reflect.Uint8
 	if nullable {
 		if ft.Kind() != reflect.Ptr && ft.Kind() != reflect.Slice {
-			panic(fmt.Sprintf("field %v has invalid type: got %v, want pointer", fd.FullName(), ft))
+			// This never occurs for generated message types.
+			// Despite the protobuf type system specifying presence,
+			// the Go field type cannot represent it.
+			nullable = false
 		}
 		if ft.Kind() == reflect.Ptr {
 			ft = ft.Elem()
@@ -388,6 +424,9 @@
 				return false
 			}
 			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
+			if fs.Type.Kind() != reflect.Ptr {
+				return !isZero(rv)
+			}
 			return !rv.IsNil()
 		},
 		clear: func(p pointer) {
@@ -404,13 +443,13 @@
 		set: func(p pointer, v pref.Value) {
 			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
 			rv.Set(conv.GoValueOf(v))
-			if rv.IsNil() {
+			if fs.Type.Kind() == reflect.Ptr && rv.IsNil() {
 				panic(fmt.Sprintf("field %v has invalid nil pointer", fd.FullName()))
 			}
 		},
 		mutable: func(p pointer) pref.Value {
 			rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
-			if rv.IsNil() {
+			if fs.Type.Kind() == reflect.Ptr && rv.IsNil() {
 				rv.Set(conv.GoValueOf(conv.New()))
 			}
 			return conv.PBValueOf(rv)
@@ -464,3 +503,41 @@
 	}
 	return oi
 }
+
+// isZero is identical to reflect.Value.IsZero.
+// TODO: Remove this when Go1.13 is the minimally supported Go version.
+func isZero(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return math.Float64bits(v.Float()) == 0
+	case reflect.Complex64, reflect.Complex128:
+		c := v.Complex()
+		return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
+	case reflect.Array:
+		for i := 0; i < v.Len(); i++ {
+			if !isZero(v.Index(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
+		return v.IsNil()
+	case reflect.String:
+		return v.Len() == 0
+	case reflect.Struct:
+		for i := 0; i < v.NumField(); i++ {
+			if !isZero(v.Field(i)) {
+				return false
+			}
+		}
+		return true
+	default:
+		panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()})
+	}
+}
diff --git a/internal/testprotos/nullable/methods_test.go b/internal/testprotos/nullable/methods_test.go
new file mode 100644
index 0000000..e272838
--- /dev/null
+++ b/internal/testprotos/nullable/methods_test.go
@@ -0,0 +1,46 @@
+// Copyright 2021 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.
+
+// Only test compatibility with the Marshal/Unmarshal functionality with
+// pure protobuf reflection since there is no support for nullable fields
+// in the table-driven implementation.
+// +build protoreflect
+
+package nullable
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/protocmp"
+)
+
+func init() {
+	testMethods = func(t *testing.T, mt protoreflect.MessageType) {
+		m1 := mt.New()
+		populated := testPopulateMessage(t, m1, 2)
+		b, err := proto.Marshal(m1.Interface())
+		if err != nil {
+			t.Errorf("proto.Marshal error: %v", err)
+		}
+		if populated && len(b) == 0 {
+			t.Errorf("len(proto.Marshal) = 0, want >0")
+		}
+		m2 := mt.New()
+		if err := proto.Unmarshal(b, m2.Interface()); err != nil {
+			t.Errorf("proto.Unmarshal error: %v", err)
+		}
+		if diff := cmp.Diff(m1.Interface(), m2.Interface(), protocmp.Transform()); diff != "" {
+			t.Errorf("message mismatch:\n%v", diff)
+		}
+		proto.Reset(m2.Interface())
+		testEmptyMessage(t, m2, true)
+		proto.Merge(m2.Interface(), m1.Interface())
+		if diff := cmp.Diff(m1.Interface(), m2.Interface(), protocmp.Transform()); diff != "" {
+			t.Errorf("message mismatch:\n%v", diff)
+		}
+	}
+}
diff --git a/internal/testprotos/nullable/nullable.go b/internal/testprotos/nullable/nullable.go
new file mode 100644
index 0000000..a291b45
--- /dev/null
+++ b/internal/testprotos/nullable/nullable.go
@@ -0,0 +1,225 @@
+// Copyright 2021 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 nullable
+
+import (
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/runtime/protoimpl"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+type Proto2 struct {
+	OptionalBool    bool                                   `protobuf:"varint,100,opt,name=optional_bool"`
+	OptionalInt32   int32                                  `protobuf:"varint,101,opt,name=optional_int32"`
+	OptionalInt64   int64                                  `protobuf:"varint,102,opt,name=optional_int64"`
+	OptionalUint32  uint32                                 `protobuf:"varint,103,opt,name=optional_uint32"`
+	OptionalUint64  uint64                                 `protobuf:"varint,104,opt,name=optional_uint64"`
+	OptionalFloat   float32                                `protobuf:"fixed32,105,opt,name=optional_float"`
+	OptionalDouble  float64                                `protobuf:"fixed64,106,opt,name=optional_double"`
+	OptionalString  string                                 `protobuf:"bytes,107,opt,name=optional_string"`
+	OptionalBytes   []byte                                 `protobuf:"bytes,108,opt,name=optional_bytes"`
+	OptionalEnum    descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,109,req,name=optional_enum"`
+	OptionalMessage descriptorpb.FieldOptions              `protobuf:"bytes,110,req,name=optional_message"`
+
+	RepeatedBool    []bool                                   `protobuf:"varint,200,rep,name=repeated_bool"`
+	RepeatedInt32   []int32                                  `protobuf:"varint,201,rep,name=repeated_int32"`
+	RepeatedInt64   []int64                                  `protobuf:"varint,202,rep,name=repeated_int64"`
+	RepeatedUint32  []uint32                                 `protobuf:"varint,203,rep,name=repeated_uint32"`
+	RepeatedUint64  []uint64                                 `protobuf:"varint,204,rep,name=repeated_uint64"`
+	RepeatedFloat   []float32                                `protobuf:"fixed32,205,rep,name=repeated_float"`
+	RepeatedDouble  []float64                                `protobuf:"fixed64,206,rep,name=repeated_double"`
+	RepeatedString  []string                                 `protobuf:"bytes,207,rep,name=repeated_string"`
+	RepeatedBytes   [][]byte                                 `protobuf:"bytes,208,rep,name=repeated_bytes"`
+	RepeatedEnum    []descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,209,rep,name=repeated_enum"`
+	RepeatedMessage []descriptorpb.FieldOptions              `protobuf:"bytes,210,rep,name=repeated_message"`
+
+	MapBool    map[string]bool                                   `protobuf:"bytes,300,rep,name=map_bool" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapInt32   map[string]int32                                  `protobuf:"bytes,301,rep,name=map_int32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapInt64   map[string]int64                                  `protobuf:"bytes,302,rep,name=map_int64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapUint32  map[string]uint32                                 `protobuf:"bytes,303,rep,name=map_uint32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapUint64  map[string]uint64                                 `protobuf:"bytes,304,rep,name=map_uint64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapFloat   map[string]float32                                `protobuf:"bytes,305,rep,name=map_float" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"`
+	MapDouble  map[string]float64                                `protobuf:"bytes,306,rep,name=map_double" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"`
+	MapString  map[string]string                                 `protobuf:"bytes,307,rep,name=map_string" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+	MapBytes   map[string][]byte                                 `protobuf:"bytes,308,rep,name=map_bytes" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+	MapEnum    map[string]descriptorpb.FieldDescriptorProto_Type `protobuf:"bytes,309,rep,name=map_enum" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
+	MapMessage map[string]descriptorpb.FieldOptions              `protobuf:"bytes,310,rep,name=map_message" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+
+	OneofUnion isProto2_OneofUnion `protobuf_oneof:"oneof_union"`
+}
+
+func (x *Proto2) ProtoMessage()  {}
+func (x *Proto2) Reset()         { *x = Proto2{} }
+func (x *Proto2) String() string { return prototext.Format(protoimpl.X.ProtoMessageV2Of(x)) }
+func (x *Proto2) XXX_OneofWrappers() []interface{} {
+	return []interface{}{
+		(*Proto2_OneofBool)(nil),
+		(*Proto2_OneofInt32)(nil),
+		(*Proto2_OneofInt64)(nil),
+		(*Proto2_OneofUint32)(nil),
+		(*Proto2_OneofUint64)(nil),
+		(*Proto2_OneofFloat)(nil),
+		(*Proto2_OneofDouble)(nil),
+		(*Proto2_OneofString)(nil),
+		(*Proto2_OneofBytes)(nil),
+		(*Proto2_OneofEnum)(nil),
+		(*Proto2_OneofMessage)(nil),
+	}
+}
+
+type isProto2_OneofUnion interface{ isProto2_OneofUnion() }
+
+type Proto2_OneofBool struct {
+	OneofBool bool `protobuf:"varint,400,opt,name=oneof_bool,oneof"`
+}
+type Proto2_OneofInt32 struct {
+	OneofInt32 int32 `protobuf:"varint,401,opt,name=oneof_int32,oneof"`
+}
+type Proto2_OneofInt64 struct {
+	OneofInt64 int64 `protobuf:"varint,402,opt,name=oneof_int64,oneof"`
+}
+type Proto2_OneofUint32 struct {
+	OneofUint32 uint32 `protobuf:"varint,403,opt,name=oneof_uint32,oneof"`
+}
+type Proto2_OneofUint64 struct {
+	OneofUint64 uint64 `protobuf:"varint,404,opt,name=oneof_uint64,oneof"`
+}
+type Proto2_OneofFloat struct {
+	OneofFloat float32 `protobuf:"fixed32,405,opt,name=oneof_float,oneof"`
+}
+type Proto2_OneofDouble struct {
+	OneofDouble float64 `protobuf:"fixed64,406,opt,name=oneof_double,oneof"`
+}
+type Proto2_OneofString struct {
+	OneofString string `protobuf:"bytes,407,opt,name=oneof_string,oneof"`
+}
+type Proto2_OneofBytes struct {
+	OneofBytes []byte `protobuf:"bytes,408,opt,name=oneof_bytes,oneof"`
+}
+type Proto2_OneofEnum struct {
+	OneofEnum descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,409,opt,name=oneof_enum,oneof"`
+}
+type Proto2_OneofMessage struct {
+	OneofMessage descriptorpb.FieldOptions `protobuf:"bytes,410,opt,name=oneof_message,oneof"`
+}
+
+func (*Proto2_OneofBool) isProto2_OneofUnion()    {}
+func (*Proto2_OneofInt32) isProto2_OneofUnion()   {}
+func (*Proto2_OneofInt64) isProto2_OneofUnion()   {}
+func (*Proto2_OneofUint32) isProto2_OneofUnion()  {}
+func (*Proto2_OneofUint64) isProto2_OneofUnion()  {}
+func (*Proto2_OneofFloat) isProto2_OneofUnion()   {}
+func (*Proto2_OneofDouble) isProto2_OneofUnion()  {}
+func (*Proto2_OneofString) isProto2_OneofUnion()  {}
+func (*Proto2_OneofBytes) isProto2_OneofUnion()   {}
+func (*Proto2_OneofEnum) isProto2_OneofUnion()    {}
+func (*Proto2_OneofMessage) isProto2_OneofUnion() {}
+
+type Proto3 struct {
+	OptionalBool    bool                                   `protobuf:"varint,100,opt,name=optional_bool,proto3"`
+	OptionalInt32   int32                                  `protobuf:"varint,101,opt,name=optional_int32,proto3"`
+	OptionalInt64   int64                                  `protobuf:"varint,102,opt,name=optional_int64,proto3"`
+	OptionalUint32  uint32                                 `protobuf:"varint,103,opt,name=optional_uint32,proto3"`
+	OptionalUint64  uint64                                 `protobuf:"varint,104,opt,name=optional_uint64,proto3"`
+	OptionalFloat   float32                                `protobuf:"fixed32,105,opt,name=optional_float,proto3"`
+	OptionalDouble  float64                                `protobuf:"fixed64,106,opt,name=optional_double,proto3"`
+	OptionalString  string                                 `protobuf:"bytes,107,opt,name=optional_string,proto3"`
+	OptionalBytes   []byte                                 `protobuf:"bytes,108,opt,name=optional_bytes,proto3"`
+	OptionalEnum    descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,109,req,name=optional_enum,proto3"`
+	OptionalMessage descriptorpb.FieldOptions              `protobuf:"bytes,110,req,name=optional_message,proto3"`
+
+	RepeatedBool    []bool                                   `protobuf:"varint,200,rep,name=repeated_bool,proto3"`
+	RepeatedInt32   []int32                                  `protobuf:"varint,201,rep,name=repeated_int32,proto3"`
+	RepeatedInt64   []int64                                  `protobuf:"varint,202,rep,name=repeated_int64,proto3"`
+	RepeatedUint32  []uint32                                 `protobuf:"varint,203,rep,name=repeated_uint32,proto3"`
+	RepeatedUint64  []uint64                                 `protobuf:"varint,204,rep,name=repeated_uint64,proto3"`
+	RepeatedFloat   []float32                                `protobuf:"fixed32,205,rep,name=repeated_float,proto3"`
+	RepeatedDouble  []float64                                `protobuf:"fixed64,206,rep,name=repeated_double,proto3"`
+	RepeatedString  []string                                 `protobuf:"bytes,207,rep,name=repeated_string,proto3"`
+	RepeatedBytes   [][]byte                                 `protobuf:"bytes,208,rep,name=repeated_bytes,proto3"`
+	RepeatedEnum    []descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,209,rep,name=repeated_enum,proto3"`
+	RepeatedMessage []descriptorpb.FieldOptions              `protobuf:"bytes,210,rep,name=repeated_message,proto3"`
+
+	MapBool    map[string]bool                                   `protobuf:"bytes,300,rep,name=map_bool,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapInt32   map[string]int32                                  `protobuf:"bytes,301,rep,name=map_int32,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapInt64   map[string]int64                                  `protobuf:"bytes,302,rep,name=map_int64,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapUint32  map[string]uint32                                 `protobuf:"bytes,303,rep,name=map_uint32,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapUint64  map[string]uint64                                 `protobuf:"bytes,304,rep,name=map_uint64,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapFloat   map[string]float32                                `protobuf:"bytes,305,rep,name=map_float,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed32,2,opt,name=value,proto3"`
+	MapDouble  map[string]float64                                `protobuf:"bytes,306,rep,name=map_double,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"`
+	MapString  map[string]string                                 `protobuf:"bytes,307,rep,name=map_string,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	MapBytes   map[string][]byte                                 `protobuf:"bytes,308,rep,name=map_bytes,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	MapEnum    map[string]descriptorpb.FieldDescriptorProto_Type `protobuf:"bytes,309,rep,name=map_enum,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	MapMessage map[string]descriptorpb.FieldOptions              `protobuf:"bytes,310,rep,name=map_message,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+
+	OneofUnion isProto3_OneofUnion `protobuf_oneof:"oneof_union"`
+}
+
+func (x *Proto3) ProtoMessage()  {}
+func (x *Proto3) Reset()         { *x = Proto3{} }
+func (x *Proto3) String() string { return prototext.Format(protoimpl.X.ProtoMessageV2Of(x)) }
+func (x *Proto3) XXX_OneofWrappers() []interface{} {
+	return []interface{}{
+		(*Proto3_OneofBool)(nil),
+		(*Proto3_OneofInt32)(nil),
+		(*Proto3_OneofInt64)(nil),
+		(*Proto3_OneofUint32)(nil),
+		(*Proto3_OneofUint64)(nil),
+		(*Proto3_OneofFloat)(nil),
+		(*Proto3_OneofDouble)(nil),
+		(*Proto3_OneofString)(nil),
+		(*Proto3_OneofBytes)(nil),
+		(*Proto3_OneofEnum)(nil),
+		(*Proto3_OneofMessage)(nil),
+	}
+}
+
+type isProto3_OneofUnion interface{ isProto3_OneofUnion() }
+
+type Proto3_OneofBool struct {
+	OneofBool bool `protobuf:"varint,400,opt,name=oneof_bool,proto3,oneof"`
+}
+type Proto3_OneofInt32 struct {
+	OneofInt32 int32 `protobuf:"varint,401,opt,name=oneof_int32,proto3,oneof"`
+}
+type Proto3_OneofInt64 struct {
+	OneofInt64 int64 `protobuf:"varint,402,opt,name=oneof_int64,proto3,oneof"`
+}
+type Proto3_OneofUint32 struct {
+	OneofUint32 uint32 `protobuf:"varint,403,opt,name=oneof_uint32,proto3,oneof"`
+}
+type Proto3_OneofUint64 struct {
+	OneofUint64 uint64 `protobuf:"varint,404,opt,name=oneof_uint64,proto3,oneof"`
+}
+type Proto3_OneofFloat struct {
+	OneofFloat float32 `protobuf:"fixed32,405,opt,name=oneof_float,proto3,oneof"`
+}
+type Proto3_OneofDouble struct {
+	OneofDouble float64 `protobuf:"fixed64,406,opt,name=oneof_double,proto3,oneof"`
+}
+type Proto3_OneofString struct {
+	OneofString string `protobuf:"bytes,407,opt,name=oneof_string,proto3,oneof"`
+}
+type Proto3_OneofBytes struct {
+	OneofBytes []byte `protobuf:"bytes,408,opt,name=oneof_bytes,proto3,oneof"`
+}
+type Proto3_OneofEnum struct {
+	OneofEnum descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,409,opt,name=oneof_enum,proto3,oneof"`
+}
+type Proto3_OneofMessage struct {
+	OneofMessage descriptorpb.FieldOptions `protobuf:"bytes,410,opt,name=oneof_message,proto3,oneof"`
+}
+
+func (*Proto3_OneofBool) isProto3_OneofUnion()    {}
+func (*Proto3_OneofInt32) isProto3_OneofUnion()   {}
+func (*Proto3_OneofInt64) isProto3_OneofUnion()   {}
+func (*Proto3_OneofUint32) isProto3_OneofUnion()  {}
+func (*Proto3_OneofUint64) isProto3_OneofUnion()  {}
+func (*Proto3_OneofFloat) isProto3_OneofUnion()   {}
+func (*Proto3_OneofDouble) isProto3_OneofUnion()  {}
+func (*Proto3_OneofString) isProto3_OneofUnion()  {}
+func (*Proto3_OneofBytes) isProto3_OneofUnion()   {}
+func (*Proto3_OneofEnum) isProto3_OneofUnion()    {}
+func (*Proto3_OneofMessage) isProto3_OneofUnion() {}
diff --git a/internal/testprotos/nullable/nullable_test.go b/internal/testprotos/nullable/nullable_test.go
new file mode 100644
index 0000000..6994e34
--- /dev/null
+++ b/internal/testprotos/nullable/nullable_test.go
@@ -0,0 +1,175 @@
+// Copyright 2021 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 nullable
+
+import (
+	"reflect"
+	"testing"
+
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/runtime/protoimpl"
+)
+
+func Test(t *testing.T) {
+	for _, mt := range []protoreflect.MessageType{
+		protoimpl.X.ProtoMessageV2Of((*Proto2)(nil)).ProtoReflect().Type(),
+		protoimpl.X.ProtoMessageV2Of((*Proto3)(nil)).ProtoReflect().Type(),
+	} {
+		t.Run(string(mt.Descriptor().FullName()), func(t *testing.T) {
+			testEmptyMessage(t, mt.Zero(), false)
+			testEmptyMessage(t, mt.New(), true)
+			testMethods(t, mt)
+		})
+	}
+}
+
+var testMethods = func(*testing.T, protoreflect.MessageType) {}
+
+func testEmptyMessage(t *testing.T, m protoreflect.Message, wantValid bool) {
+	numFields := func(m protoreflect.Message) (n int) {
+		m.Range(func(protoreflect.FieldDescriptor, protoreflect.Value) bool {
+			n++
+			return true
+		})
+		return n
+	}
+
+	md := m.Descriptor()
+	if gotValid := m.IsValid(); gotValid != wantValid {
+		t.Errorf("%v.IsValid = %v, want %v", md.FullName(), gotValid, wantValid)
+	}
+	m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
+		t.Errorf("%v.Range iterated over field %v, want no iteration", md.FullName(), fd.Name())
+		return true
+	})
+	fds := md.Fields()
+	for i := 0; i < fds.Len(); i++ {
+		fd := fds.Get(i)
+		if m.Has(fd) {
+			t.Errorf("%v.Has(%v) = true, want false", md.FullName(), fd.Name())
+		}
+		v := m.Get(fd)
+		switch {
+		case fd.IsList():
+			if n := v.List().Len(); n > 0 {
+				t.Errorf("%v.Get(%v).List().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+			}
+			ls := m.NewField(fd).List()
+			if fd.Message() != nil {
+				if n := numFields(ls.NewElement().Message()); n > 0 {
+					t.Errorf("%v.NewField(%v).List().NewElement().Message().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+				}
+			}
+		case fd.IsMap():
+			if n := v.Map().Len(); n > 0 {
+				t.Errorf("%v.Get(%v).Map().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+			}
+			ms := m.NewField(fd).Map()
+			if fd.MapValue().Message() != nil {
+				if n := numFields(ms.NewValue().Message()); n > 0 {
+					t.Errorf("%v.NewField(%v).Map().NewValue().Message().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+				}
+			}
+		case fd.Message() != nil:
+			if n := numFields(v.Message()); n > 0 {
+				t.Errorf("%v.Get(%v).Message().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+			}
+			if n := numFields(m.NewField(fd).Message()); n > 0 {
+				t.Errorf("%v.NewField(%v).Message().Len() = %v, want 0", md.FullName(), fd.Name(), n)
+			}
+		default:
+			if !reflect.DeepEqual(v.Interface(), fd.Default().Interface()) {
+				t.Errorf("%v.Get(%v) = %v, want %v", md.FullName(), fd.Name(), v, fd.Default())
+			}
+			m.NewField(fd) // should not panic
+		}
+	}
+	ods := md.Oneofs()
+	for i := 0; i < ods.Len(); i++ {
+		od := ods.Get(i)
+		if fd := m.WhichOneof(od); fd != nil {
+			t.Errorf("%v.WhichOneof(%v) = %v, want nil", md.FullName(), od.Name(), fd.Name())
+		}
+	}
+	if b := m.GetUnknown(); b != nil {
+		t.Errorf("%v.GetUnknown() = %v, want nil", md.FullName(), b)
+	}
+}
+
+func testPopulateMessage(t *testing.T, m protoreflect.Message, depth int) bool {
+	if depth == 0 {
+		return false
+	}
+	md := m.Descriptor()
+	fds := md.Fields()
+	var populatedMessage bool
+	for i := 0; i < fds.Len(); i++ {
+		populatedField := true
+		fd := fds.Get(i)
+		m.Clear(fd) // should not panic
+		switch {
+		case fd.IsList():
+			ls := m.Mutable(fd).List()
+			if fd.Message() == nil {
+				ls.Append(scalarValue(fd.Kind()))
+			} else {
+				populatedField = testPopulateMessage(t, ls.AppendMutable().Message(), depth-1)
+			}
+		case fd.IsMap():
+			ms := m.Mutable(fd).Map()
+			if fd.MapValue().Message() == nil {
+				ms.Set(
+					scalarValue(fd.MapKey().Kind()).MapKey(),
+					scalarValue(fd.MapValue().Kind()),
+				)
+			} else {
+				// NOTE: Map.Mutable does not work with non-nullable fields.
+				m2 := ms.NewValue().Message()
+				populatedField = testPopulateMessage(t, m2, depth-1)
+				ms.Set(
+					scalarValue(fd.MapKey().Kind()).MapKey(),
+					protoreflect.ValueOfMessage(m2),
+				)
+			}
+		case fd.Message() != nil:
+			populatedField = testPopulateMessage(t, m.Mutable(fd).Message(), depth-1)
+		default:
+			m.Set(fd, scalarValue(fd.Kind()))
+		}
+		if populatedField && !m.Has(fd) {
+			t.Errorf("%v.Has(%v) = false, want true", md.FullName(), fd.Name())
+		}
+		populatedMessage = populatedMessage || populatedField
+	}
+	m.SetUnknown(m.GetUnknown()) // should not panic
+	return populatedMessage
+}
+
+func scalarValue(k protoreflect.Kind) protoreflect.Value {
+	switch k {
+	case protoreflect.BoolKind:
+		return protoreflect.ValueOfBool(true)
+	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
+		return protoreflect.ValueOfInt32(-32)
+	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
+		return protoreflect.ValueOfInt64(-64)
+	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
+		return protoreflect.ValueOfUint32(32)
+	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
+		return protoreflect.ValueOfUint64(64)
+	case protoreflect.FloatKind:
+		return protoreflect.ValueOfFloat32(32.32)
+	case protoreflect.DoubleKind:
+		return protoreflect.ValueOfFloat64(64.64)
+	case protoreflect.StringKind:
+		return protoreflect.ValueOfString(string("string"))
+	case protoreflect.BytesKind:
+		return protoreflect.ValueOfBytes([]byte("bytes"))
+	case protoreflect.EnumKind:
+		return protoreflect.ValueOfEnum(1)
+	default:
+		panic("unknown kind: " + k.String())
+	}
+}