reflect/protoreflect: add Descriptor specific methods

Added methods:
	Enum.Descriptor
	Message.Descriptor
	EnumType.Descriptor
	MessageType.Descriptor
	ExtensionType.Descriptor
	Message.New

All functionality is switched over to use those methods instead of
implicitly relying on the fact that {Enum,Message}Type implicitly
implement the associated descriptor interface.

This CL does not yet remove {Enum,Message}.Type or prevent
{Enum,Message,Extension}Type from implementating a descriptor.
That is a subsequent CL.

The Message.New method is also added to replace functionality
that will be lost when the Type methods are removed.

Change-Id: I7fefde1673bbd40bfdac489aca05cec9a6c98eb1
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/174918
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/internal/fileinit/desc.go b/internal/fileinit/desc.go
index 1665b30..0e9ef53 100644
--- a/internal/fileinit/desc.go
+++ b/internal/fileinit/desc.go
@@ -302,6 +302,7 @@
 	}
 )
 
+func (ed *enumDesc) Descriptor() pref.EnumDescriptor { return ed }
 func (ed *enumDesc) GoType() reflect.Type            { return ed.lazyInit().typ }
 func (ed *enumDesc) New(n pref.EnumNumber) pref.Enum { return ed.lazyInit().new(n) }
 func (ed *enumDesc) Options() pref.ProtoMessage {
@@ -420,10 +421,11 @@
 	}
 	return messageDescriptor{mb}
 }
-func (mt messageType) GoType() reflect.Type             { return mt.lazyInit().typ }
-func (mt messageType) New() pref.Message                { return mt.lazyInit().new() }
-func (mt messageType) Options() pref.ProtoMessage       { return mt.options() }
-func (md messageDescriptor) Options() pref.ProtoMessage { return md.options() }
+func (mt messageType) Descriptor() pref.MessageDescriptor { return messageDescriptor{mt.messageDesc} }
+func (mt messageType) GoType() reflect.Type               { return mt.lazyInit().typ }
+func (mt messageType) New() pref.Message                  { return mt.lazyInit().new() }
+func (mt messageType) Options() pref.ProtoMessage         { return mt.options() }
+func (md messageDescriptor) Options() pref.ProtoMessage   { return md.options() }
 
 func (fd *fieldDesc) Options() pref.ProtoMessage {
 	return unmarshalOptions(descopts.Field, fd.options)
@@ -482,12 +484,13 @@
 		jsonName    string
 		isPacked    bool
 		defVal      defaultValue
-		enumType    pref.EnumType
-		messageType pref.MessageType
+		enumType    pref.EnumDescriptor
+		messageType pref.MessageDescriptor
 		options     []byte
 	}
 )
 
+func (xd *extensionDesc) Descriptor() pref.ExtensionDescriptor { return xd }
 func (xd *extensionDesc) GoType() reflect.Type                 { return xd.lazyInit().typ }
 func (xd *extensionDesc) New() pref.Value                      { return xd.lazyInit().new() }
 func (xd *extensionDesc) ValueOf(v interface{}) pref.Value     { return xd.lazyInit().valueOf(v) }
diff --git a/internal/fileinit/desc_lazy.go b/internal/fileinit/desc_lazy.go
index be3ebeb..7382d80 100644
--- a/internal/fileinit/desc_lazy.go
+++ b/internal/fileinit/desc_lazy.go
@@ -116,6 +116,7 @@
 		case pref.Optional:
 			switch xd.lazy.kind {
 			case pref.EnumKind:
+				et := pimpl.Export{}.EnumTypeOf(reflect.Zero(typ).Interface())
 				xd.lazy.typ = typ
 				xd.lazy.new = func() pref.Value {
 					return xd.lazy.defVal.get()
@@ -125,12 +126,13 @@
 					return pref.ValueOf(ev.Number())
 				}
 				xd.lazy.interfaceOf = func(pv pref.Value) interface{} {
-					return xd.lazy.enumType.New(pv.Enum())
+					return et.New(pv.Enum())
 				}
 			case pref.MessageKind, pref.GroupKind:
+				mt := pimpl.Export{}.MessageTypeOf(reflect.Zero(typ).Interface())
 				xd.lazy.typ = typ
 				xd.lazy.new = func() pref.Value {
-					return pref.ValueOf(xd.lazy.messageType.New())
+					return pref.ValueOf(mt.New())
 				}
 				xd.lazy.valueOf = func(v interface{}) pref.Value {
 					mv := v.(pref.ProtoMessage).ProtoReflect()
@@ -171,9 +173,9 @@
 		// Resolve extension field dependency.
 		switch xd.lazy.kind {
 		case pref.EnumKind:
-			xd.lazy.enumType = file.popEnumDependency().(pref.EnumType)
+			xd.lazy.enumType = file.popEnumDependency()
 		case pref.MessageKind, pref.GroupKind:
-			xd.lazy.messageType = file.popMessageDependency().(pref.MessageType)
+			xd.lazy.messageType = file.popMessageDependency()
 		}
 		xd.lazy.defVal.lazyInit(xd.lazy.kind, file.enumValuesOf(xd.lazy.enumType))
 	}
@@ -241,7 +243,7 @@
 	if depIdx < len(fd.allEnums)+len(fd.allMessages) {
 		return &fd.allEnums[depIdx]
 	} else {
-		return pimpl.Export{}.EnumTypeOf(fd.GoTypes[depIdx])
+		return pimpl.Export{}.EnumDescriptorOf(fd.GoTypes[depIdx])
 	}
 }
 
@@ -250,7 +252,7 @@
 	if depIdx < len(fd.allEnums)+len(fd.allMessages) {
 		return fd.allMessages[depIdx-len(fd.allEnums)].asDesc()
 	} else {
-		return pimpl.Export{}.MessageTypeOf(fd.GoTypes[depIdx])
+		return pimpl.Export{}.MessageDescriptorOf(fd.GoTypes[depIdx])
 	}
 }
 
diff --git a/internal/fileinit/fileinit_test.go b/internal/fileinit/fileinit_test.go
index 213289b..ac1c5e2 100644
--- a/internal/fileinit/fileinit_test.go
+++ b/internal/fileinit/fileinit_test.go
@@ -81,10 +81,10 @@
 
 // visitFields calls f for every field set in m and its children.
 func visitFields(m protoreflect.Message, f func(protoreflect.FieldDescriptor)) {
-	typ := m.Type()
+	fieldDescs := m.Descriptor().Fields()
 	k := m.KnownFields()
 	k.Range(func(num protoreflect.FieldNumber, value protoreflect.Value) bool {
-		field := typ.Fields().ByNumber(num)
+		field := fieldDescs.ByNumber(num)
 		f(field)
 		switch field.Kind() {
 		case protoreflect.MessageKind, protoreflect.GroupKind:
diff --git a/internal/impl/export.go b/internal/impl/export.go
index 5dae010..0b53d78 100644
--- a/internal/impl/export.go
+++ b/internal/impl/export.go
@@ -5,10 +5,10 @@
 package impl
 
 import (
+	"reflect"
 	"strconv"
 
 	"github.com/golang/protobuf/v2/encoding/textpb"
-	ptype "github.com/golang/protobuf/v2/internal/prototype"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 )
 
@@ -16,11 +16,12 @@
 // functions that we do not want to appear in godoc.
 type Export struct{}
 
+// enum is any enum type generated by protoc-gen-go
+// and must be a named int32 type.
+type enum = interface{}
+
 // EnumOf returns the protoreflect.Enum interface over e.
-// If e already implements proto.Enum, then it directly calls the
-// ProtoReflect method, otherwise it wraps the v1 enum to implement
-// the v2 reflective interface.
-func (Export) EnumOf(e interface{}) pref.Enum {
+func (Export) EnumOf(e enum) pref.Enum {
 	if ev, ok := e.(pref.Enum); ok {
 		return ev
 	}
@@ -28,16 +29,34 @@
 }
 
 // EnumTypeOf returns the protoreflect.EnumType for e.
-// If e already implements proto.Enum, then it obtains the type by directly
-// calling the ProtoReflect.Type method, otherwise it derives an enum type
-// from the v1 named int32 type.
-func (Export) EnumTypeOf(e interface{}) pref.EnumType {
+func (Export) EnumTypeOf(e enum) pref.EnumType {
 	if ev, ok := e.(pref.Enum); ok {
-		return ev.Type()
+		return &enumType{ev.Descriptor(), reflect.TypeOf(e)}
 	}
 	return legacyWrapper.EnumTypeOf(e)
 }
 
+// TODO: This needs to be centralized in a package.
+type enumType struct {
+	// TODO: Remove me as an embedded field.
+	pref.EnumDescriptor
+	typ reflect.Type // must implement protoreflect.Enum
+}
+
+func (t *enumType) Descriptor() pref.EnumDescriptor { return t.EnumDescriptor }
+func (t *enumType) GoType() reflect.Type            { return t.typ }
+func (t *enumType) New(n pref.EnumNumber) pref.Enum {
+	return reflect.ValueOf(n).Convert(t.typ).Interface().(pref.Enum)
+}
+
+// EnumDescriptorOf returns the protoreflect.EnumDescriptor for e.
+func (Export) EnumDescriptorOf(e enum) pref.EnumDescriptor {
+	if ev, ok := e.(pref.Enum); ok {
+		return ev.Descriptor()
+	}
+	return legacyWrapper.EnumDescriptorOf(e)
+}
+
 // EnumStringOf returns the enum value as a string, either as the name if
 // the number is resolvable, or the number formatted as a string.
 func (Export) EnumStringOf(ed pref.EnumDescriptor, n pref.EnumNumber) string {
@@ -48,11 +67,12 @@
 	return strconv.Itoa(int(n))
 }
 
+// message is any message type generated by protoc-gen-go
+// and must be a pointer to a named struct type.
+type message = interface{}
+
 // MessageOf returns the protoreflect.Message interface over m.
-// If m already implements proto.Message, then it directly calls the
-// ProtoReflect method, otherwise it wraps the v1 message to implement
-// the v2 reflective interface.
-func (Export) MessageOf(m interface{}) pref.Message {
+func (Export) MessageOf(m message) pref.Message {
 	if mv, ok := m.(pref.ProtoMessage); ok {
 		return mv.ProtoReflect()
 	}
@@ -60,32 +80,32 @@
 }
 
 // MessageTypeOf returns the protoreflect.MessageType for m.
-// If m already implements proto.Message, then it obtains the type by directly
-// calling the ProtoReflect.Type method, otherwise it derives a message type
-// from the v1 message struct.
-func (Export) MessageTypeOf(m interface{}) pref.MessageType {
+func (Export) MessageTypeOf(m message) pref.MessageType {
 	if mv, ok := m.(pref.ProtoMessage); ok {
-		return mv.ProtoReflect().Type()
+		return &messageType{mv.ProtoReflect().Descriptor(), reflect.TypeOf(m)}
 	}
 	return legacyWrapper.MessageTypeOf(m)
 }
 
-// ExtensionTypeOf returns a protoreflect.ExtensionType where the type of the
-// field is t. The type t must be provided if the field is an enum or message.
-// If t already implements proto.Enum or proto.Message, then this returns
-// an extension type by directly calling prototype.GoExtension.
-// Otherwise, it derives an extension type by wrapping the enum or message
-// using EnumOf or MessageOf.
-func (Export) ExtensionTypeOf(d pref.ExtensionDescriptor, t interface{}) pref.ExtensionType {
-	switch t := t.(type) {
-	case nil:
-		return ptype.GoExtension(d, nil, nil)
-	case pref.Enum:
-		return ptype.GoExtension(d, t.Type(), nil)
-	case pref.ProtoMessage:
-		return ptype.GoExtension(d, nil, t.ProtoReflect().Type())
+// TODO: This needs to be centralized in a package.
+type messageType struct {
+	// TODO: Remove me as an embedded field.
+	pref.MessageDescriptor
+	typ reflect.Type // must implement protoreflect.ProtoMessage
+}
+
+func (t *messageType) Descriptor() pref.MessageDescriptor { return t.MessageDescriptor }
+func (t *messageType) GoType() reflect.Type               { return t.typ }
+func (t *messageType) New() pref.Message {
+	return reflect.New(t.typ.Elem()).Interface().(pref.ProtoMessage).ProtoReflect()
+}
+
+// MessageDescriptorOf returns the protoreflect.MessageDescriptor for m.
+func (Export) MessageDescriptorOf(m message) pref.MessageDescriptor {
+	if mv, ok := m.(pref.ProtoMessage); ok {
+		return mv.ProtoReflect().Descriptor()
 	}
-	return legacyWrapper.ExtensionTypeOf(d, t)
+	return legacyWrapper.MessageDescriptorOf(m)
 }
 
 // MessageStringOf returns the message value as a string,
diff --git a/internal/impl/legacy_extension.go b/internal/impl/legacy_extension.go
index b82379e..a0ae3f0 100644
--- a/internal/impl/legacy_extension.go
+++ b/internal/impl/legacy_extension.go
@@ -63,7 +63,8 @@
 		return false
 	}
 	t := extensionTypeFromDesc(x.Desc)
-	if t.Cardinality() == pref.Repeated {
+	d := t.Descriptor()
+	if d.Cardinality() == pref.Repeated {
 		return t.ValueOf(x.Value).List().Len() > 0
 	}
 	return true
@@ -75,13 +76,14 @@
 		return pref.Value{}
 	}
 	t := extensionTypeFromDesc(x.Desc)
+	d := t.Descriptor()
 	if x.Value == nil {
 		// NOTE: x.Value is never nil for Lists since they are always populated
 		// during ExtensionFieldTypes.Register.
-		if t.Kind() == pref.MessageKind || t.Kind() == pref.GroupKind {
+		if d.Kind() == pref.MessageKind || d.Kind() == pref.GroupKind {
 			return pref.Value{}
 		}
-		return t.Default()
+		return d.Default()
 	}
 	return t.ValueOf(x.Value)
 }
@@ -102,7 +104,8 @@
 		return
 	}
 	t := extensionTypeFromDesc(x.Desc)
-	if t.Cardinality() == pref.Repeated {
+	d := t.Descriptor()
+	if d.Cardinality() == pref.Repeated {
 		t.ValueOf(x.Value).List().Truncate(0)
 		return
 	}
@@ -149,31 +152,33 @@
 }
 
 func (p legacyExtensionTypes) Register(t pref.ExtensionType) {
-	if p.mi.PBType.FullName() != t.Extendee().FullName() {
+	d := t.Descriptor()
+	if p.mi.PBType.Descriptor().FullName() != d.Extendee().FullName() {
 		panic("extended type mismatch")
 	}
-	if !p.mi.PBType.ExtensionRanges().Has(t.Number()) {
+	if !p.mi.PBType.Descriptor().ExtensionRanges().Has(d.Number()) {
 		panic("invalid extension field number")
 	}
-	x := p.x.Get(t.Number())
+	x := p.x.Get(d.Number())
 	if x.Desc != nil {
 		panic("extension descriptor already registered")
 	}
 	x.Desc = extensionDescFromType(t)
-	if t.Cardinality() == pref.Repeated {
+	if d.Cardinality() == pref.Repeated {
 		// If the field is repeated, initialize the entry with an empty list
 		// so that future Get operations can return a mutable and concrete list.
 		x.Value = t.InterfaceOf(t.New())
 	}
-	p.x.Set(t.Number(), x)
+	p.x.Set(d.Number(), x)
 }
 
 func (p legacyExtensionTypes) Remove(t pref.ExtensionType) {
-	if !p.mi.PBType.ExtensionRanges().Has(t.Number()) {
+	d := t.Descriptor()
+	if !p.mi.PBType.Descriptor().ExtensionRanges().Has(d.Number()) {
 		return
 	}
-	x := p.x.Get(t.Number())
-	if t.Cardinality() == pref.Repeated {
+	x := p.x.Get(d.Number())
+	if d.Cardinality() == pref.Repeated {
 		// Treat an empty repeated field as unpopulated.
 		v := reflect.ValueOf(x.Value)
 		if x.Value == nil || v.IsNil() || v.Elem().Len() == 0 {
@@ -183,7 +188,7 @@
 	if x.Value != nil {
 		panic("value for extension descriptor still populated")
 	}
-	p.x.Clear(t.Number())
+	p.x.Clear(d.Number())
 }
 
 func (p legacyExtensionTypes) ByNumber(n pref.FieldNumber) pref.ExtensionType {
diff --git a/internal/impl/legacy_test.go b/internal/impl/legacy_test.go
index afe28d2..28ab672 100644
--- a/internal/impl/legacy_test.go
+++ b/internal/impl/legacy_test.go
@@ -12,18 +12,16 @@
 
 	pack "github.com/golang/protobuf/v2/internal/encoding/pack"
 	pimpl "github.com/golang/protobuf/v2/internal/impl"
+	plegacy "github.com/golang/protobuf/v2/internal/legacy"
 	pragma "github.com/golang/protobuf/v2/internal/pragma"
 	ptype "github.com/golang/protobuf/v2/internal/prototype"
 	scalar "github.com/golang/protobuf/v2/internal/scalar"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
 	piface "github.com/golang/protobuf/v2/runtime/protoiface"
 	cmp "github.com/google/go-cmp/cmp"
 	cmpopts "github.com/google/go-cmp/cmp/cmpopts"
 
-	// The legacy package must be imported prior to use of any legacy messages.
-	// TODO: Remove this when protoV1 registers these hooks for you.
-	plegacy "github.com/golang/protobuf/v2/internal/legacy"
-
 	proto2_20180125 "github.com/golang/protobuf/v2/internal/testprotos/legacy/proto2.v1.0.0-20180125-92554152"
 )
 
@@ -39,6 +37,11 @@
 	return []piface.ExtensionRangeV1{{Start: 10, End: 20}, {Start: 40, End: 80}, {Start: 10000, End: 20000}}
 }
 
+func init() {
+	mt := pimpl.Export{}.MessageTypeOf(&legacyTestMessage{})
+	preg.GlobalTypes.Register(mt)
+}
+
 func TestLegacyUnknown(t *testing.T) {
 	rawOf := func(toks ...pack.Token) pref.RawFields {
 		return pref.RawFields(pack.Message(toks).Marshal())
@@ -174,15 +177,15 @@
 	if err != nil {
 		panic(xd)
 	}
-	return pimpl.Export{}.ExtensionTypeOf(xd, v)
+	return plegacy.ExtensionTypeOf(xd, reflect.TypeOf(v))
 }
 
 var (
-	parentType    = pimpl.Export{}.MessageTypeOf((*legacyTestMessage)(nil))
-	enumV1Type    = pimpl.Export{}.EnumTypeOf(proto2_20180125.Message_ChildEnum(0))
-	messageV1Type = pimpl.Export{}.MessageTypeOf((*proto2_20180125.Message_ChildMessage)(nil))
-	enumV2Type    = enumProto2Type
-	messageV2Type = enumMessagesType.PBType
+	parentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyTestMessage)(nil))
+	enumV1Desc    = pimpl.Export{}.EnumDescriptorOf(proto2_20180125.Message_ChildEnum(0))
+	messageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
+	enumV2Desc    = enumProto2Type.Descriptor()
+	messageV2Desc = enumMessagesType.PBType.Descriptor()
 
 	extensionTypes = []pref.ExtensionType{
 		mustMakeExtensionType(&ptype.StandaloneExtension{
@@ -191,7 +194,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.BoolKind,
 			Default:      pref.ValueOf(true),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_int32",
@@ -199,7 +202,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.Int32Kind,
 			Default:      pref.ValueOf(int32(-12345)),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_uint32",
@@ -207,7 +210,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.Uint32Kind,
 			Default:      pref.ValueOf(uint32(3200)),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_float",
@@ -215,7 +218,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.FloatKind,
 			Default:      pref.ValueOf(float32(3.14159)),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_string",
@@ -223,7 +226,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.StringKind,
 			Default:      pref.ValueOf(string("hello, \"world!\"\n")),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_bytes",
@@ -231,7 +234,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.BytesKind,
 			Default:      pref.ValueOf([]byte("dead\xde\xad\xbe\xefbeef")),
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_enum_v1",
@@ -239,16 +242,16 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.EnumKind,
 			Default:      pref.ValueOf(pref.EnumNumber(0)),
-			EnumType:     enumV1Type,
-			ExtendedType: parentType,
+			EnumType:     enumV1Desc,
+			ExtendedType: parentDesc,
 		}, proto2_20180125.Message_ChildEnum(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_message_v1",
 			Number:       10007,
 			Cardinality:  pref.Optional,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV1Type,
-			ExtendedType: parentType,
+			MessageType:  messageV1Desc,
+			ExtendedType: parentDesc,
 		}, (*proto2_20180125.Message_ChildMessage)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_enum_v2",
@@ -256,90 +259,90 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.EnumKind,
 			Default:      pref.ValueOf(pref.EnumNumber(57005)),
-			EnumType:     enumV2Type,
-			ExtendedType: parentType,
+			EnumType:     enumV2Desc,
+			ExtendedType: parentDesc,
 		}, EnumProto2(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_message_v2",
 			Number:       10009,
 			Cardinality:  pref.Optional,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV2Type,
-			ExtendedType: parentType,
+			MessageType:  messageV2Desc,
+			ExtendedType: parentDesc,
 		}, (*EnumMessages)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_bool",
 			Number:       10010,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.BoolKind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_int32",
 			Number:       10011,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.Int32Kind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_uint32",
 			Number:       10012,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.Uint32Kind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_float",
 			Number:       10013,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.FloatKind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_string",
 			Number:       10014,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.StringKind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_bytes",
 			Number:       10015,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.BytesKind,
-			ExtendedType: parentType,
+			ExtendedType: parentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_enum_v1",
 			Number:       10016,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.EnumKind,
-			EnumType:     enumV1Type,
-			ExtendedType: parentType,
+			EnumType:     enumV1Desc,
+			ExtendedType: parentDesc,
 		}, proto2_20180125.Message_ChildEnum(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_message_v1",
 			Number:       10017,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV1Type,
-			ExtendedType: parentType,
+			MessageType:  messageV1Desc,
+			ExtendedType: parentDesc,
 		}, (*proto2_20180125.Message_ChildMessage)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_enum_v2",
 			Number:       10018,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.EnumKind,
-			EnumType:     enumV2Type,
-			ExtendedType: parentType,
+			EnumType:     enumV2Desc,
+			ExtendedType: parentDesc,
 		}, EnumProto2(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_message_v2",
 			Number:       10019,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV2Type,
-			ExtendedType: parentType,
+			MessageType:  messageV2Desc,
+			ExtendedType: parentDesc,
 		}, (*EnumMessages)(nil)),
 	}
 
@@ -513,19 +516,21 @@
 	}
 	for i, xt := range extensionTypes {
 		var got interface{}
-		if v := fs.Get(xt.Number()); v.IsValid() {
+		num := xt.Descriptor().Number()
+		if v := fs.Get(num); v.IsValid() {
 			got = xt.InterfaceOf(v)
 		}
 		want := defaultValues[i]
 		if diff := cmp.Diff(want, got, opts); diff != "" {
-			t.Errorf("KnownFields.Get(%d) mismatch (-want +got):\n%v", xt.Number(), diff)
+			t.Errorf("KnownFields.Get(%d) mismatch (-want +got):\n%v", num, diff)
 		}
 	}
 
 	// All fields should be unpopulated.
 	for _, xt := range extensionTypes {
-		if fs.Has(xt.Number()) {
-			t.Errorf("KnownFields.Has(%d) = true, want false", xt.Number())
+		num := xt.Descriptor().Number()
+		if fs.Has(num) {
+			t.Errorf("KnownFields.Has(%d) = true, want false", num)
 		}
 	}
 
@@ -557,11 +562,11 @@
 		&[]*EnumMessages{m2b},
 	}
 	for i, xt := range extensionTypes {
-		fs.Set(xt.Number(), xt.ValueOf(setValues[i]))
+		fs.Set(xt.Descriptor().Number(), xt.ValueOf(setValues[i]))
 	}
 	for i, xt := range extensionTypes[len(extensionTypes)/2:] {
 		v := extensionTypes[i].ValueOf(setValues[i])
-		fs.Get(xt.Number()).List().Append(v)
+		fs.Get(xt.Descriptor().Number()).List().Append(v)
 	}
 
 	// Get the values and check for equality.
@@ -588,10 +593,11 @@
 		&[]*EnumMessages{m2b, m2a},
 	}
 	for i, xt := range extensionTypes {
-		got := xt.InterfaceOf(fs.Get(xt.Number()))
+		num := xt.Descriptor().Number()
+		got := xt.InterfaceOf(fs.Get(num))
 		want := getValues[i]
 		if diff := cmp.Diff(want, got, opts); diff != "" {
-			t.Errorf("KnownFields.Get(%d) mismatch (-want +got):\n%v", xt.Number(), diff)
+			t.Errorf("KnownFields.Get(%d) mismatch (-want +got):\n%v", num, diff)
 		}
 	}
 
@@ -604,13 +610,13 @@
 
 	// Clear the field for all extension types.
 	for _, xt := range extensionTypes[:len(extensionTypes)/2] {
-		fs.Clear(xt.Number())
+		fs.Clear(xt.Descriptor().Number())
 	}
 	for i, xt := range extensionTypes[len(extensionTypes)/2:] {
 		if i%2 == 0 {
-			fs.Clear(xt.Number())
+			fs.Clear(xt.Descriptor().Number())
 		} else {
-			fs.Get(xt.Number()).List().Truncate(0)
+			fs.Get(xt.Descriptor().Number()).List().Truncate(0)
 		}
 	}
 	if n := fs.Len(); n != 0 {
@@ -661,6 +667,13 @@
 					}
 					return out
 				}),
+				// TODO: Add this when ExtensionType no longer implements
+				// ExtensionDescriptor.
+				/*
+					cmp.Transformer("", func(x pref.ExtensionType) pref.ExtensionDescriptor {
+						return x.Descriptor()
+					}),
+				*/
 				cmp.Transformer("", func(x pref.Descriptor) map[string]interface{} {
 					out := make(map[string]interface{})
 					v := reflect.ValueOf(x)
diff --git a/internal/impl/message.go b/internal/impl/message.go
index 8879015..5c2ae28 100644
--- a/internal/impl/message.go
+++ b/internal/impl/message.go
@@ -113,8 +113,8 @@
 // any discrepancies.
 func (mi *MessageType) makeKnownFieldsFunc(si structInfo) {
 	mi.fields = map[pref.FieldNumber]*fieldInfo{}
-	for i := 0; i < mi.PBType.Fields().Len(); i++ {
-		fd := mi.PBType.Fields().Get(i)
+	for i := 0; i < mi.PBType.Descriptor().Fields().Len(); i++ {
+		fd := mi.PBType.Descriptor().Fields().Get(i)
 		fs := si.fieldsByNumber[fd.Number()]
 		var fi fieldInfo
 		switch {
@@ -133,8 +133,8 @@
 	}
 
 	mi.oneofs = map[pref.Name]*oneofInfo{}
-	for i := 0; i < mi.PBType.Oneofs().Len(); i++ {
-		od := mi.PBType.Oneofs().Get(i)
+	for i := 0; i < mi.PBType.Descriptor().Oneofs().Len(); i++ {
+		od := mi.PBType.Descriptor().Oneofs().Get(i)
 		mi.oneofs[od.Name()] = makeOneofInfo(od, si.oneofsByName[od.Name()], si.oneofWrappersByType)
 	}
 }
@@ -203,9 +203,13 @@
 
 type messageReflectWrapper messageDataType
 
+// TODO: Remove this.
 func (m *messageReflectWrapper) Type() pref.MessageType {
 	return m.mi.PBType
 }
+func (m *messageReflectWrapper) Descriptor() pref.MessageDescriptor {
+	return m.mi.PBType.Descriptor()
+}
 func (m *messageReflectWrapper) KnownFields() pref.KnownFields {
 	m.mi.init()
 	return (*knownFields)(m)
@@ -214,6 +218,9 @@
 	m.mi.init()
 	return m.mi.unknownFields((*messageDataType)(m))
 }
+func (m *messageReflectWrapper) New() pref.Message {
+	return m.mi.PBType.New()
+}
 func (m *messageReflectWrapper) Interface() pref.ProtoMessage {
 	if m, ok := m.ProtoUnwrap().(pref.ProtoMessage); ok {
 		return m
@@ -266,7 +273,7 @@
 		fi.set(fs.p, v)
 		return
 	}
-	if fs.mi.PBType.ExtensionRanges().Has(n) {
+	if fs.mi.PBType.Descriptor().ExtensionRanges().Has(n) {
 		fs.extensionFields().Set(n, v)
 		return
 	}
@@ -277,7 +284,7 @@
 		fi.clear(fs.p)
 		return
 	}
-	if fs.mi.PBType.ExtensionRanges().Has(n) {
+	if fs.mi.PBType.Descriptor().ExtensionRanges().Has(n) {
 		fs.extensionFields().Clear(n)
 		return
 	}
@@ -302,7 +309,7 @@
 	if fi := fs.mi.fields[n]; fi != nil {
 		return fi.newMessage()
 	}
-	if fs.mi.PBType.ExtensionRanges().Has(n) {
+	if fs.mi.PBType.Descriptor().ExtensionRanges().Has(n) {
 		return fs.extensionFields().NewMessage(n)
 	}
 	panic(fmt.Sprintf("invalid field: %d", n))
diff --git a/internal/impl/message_test.go b/internal/impl/message_test.go
index b5a9af2..49e3a0a 100644
--- a/internal/impl/message_test.go
+++ b/internal/impl/message_test.go
@@ -228,13 +228,18 @@
 	},
 )}
 
+// TODO: Remove this.
 func (m *ScalarProto2) Type() pref.MessageType { return scalarProto2Type.PBType }
+func (m *ScalarProto2) Descriptor() pref.MessageDescriptor {
+	return scalarProto2Type.PBType.Descriptor()
+}
 func (m *ScalarProto2) KnownFields() pref.KnownFields {
 	return scalarProto2Type.MessageOf(m).KnownFields()
 }
 func (m *ScalarProto2) UnknownFields() pref.UnknownFields {
 	return scalarProto2Type.MessageOf(m).UnknownFields()
 }
+func (m *ScalarProto2) New() pref.Message            { return new(ScalarProto2) }
 func (m *ScalarProto2) Interface() pref.ProtoMessage { return m }
 func (m *ScalarProto2) ProtoReflect() pref.Message   { return m }
 
@@ -338,13 +343,18 @@
 	},
 )}
 
+// TODO: Remove this.
 func (m *ScalarProto3) Type() pref.MessageType { return scalarProto3Type.PBType }
+func (m *ScalarProto3) Descriptor() pref.MessageDescriptor {
+	return scalarProto3Type.PBType.Descriptor()
+}
 func (m *ScalarProto3) KnownFields() pref.KnownFields {
 	return scalarProto3Type.MessageOf(m).KnownFields()
 }
 func (m *ScalarProto3) UnknownFields() pref.UnknownFields {
 	return scalarProto3Type.MessageOf(m).UnknownFields()
 }
+func (m *ScalarProto3) New() pref.Message            { return new(ScalarProto3) }
 func (m *ScalarProto3) Interface() pref.ProtoMessage { return m }
 func (m *ScalarProto3) ProtoReflect() pref.Message   { return m }
 
@@ -464,13 +474,16 @@
 	},
 )}
 
-func (m *ListScalars) Type() pref.MessageType { return listScalarsType.PBType }
+// TODO: Remove this.
+func (m *ListScalars) Type() pref.MessageType             { return listScalarsType.PBType }
+func (m *ListScalars) Descriptor() pref.MessageDescriptor { return listScalarsType.PBType.Descriptor() }
 func (m *ListScalars) KnownFields() pref.KnownFields {
 	return listScalarsType.MessageOf(m).KnownFields()
 }
 func (m *ListScalars) UnknownFields() pref.UnknownFields {
 	return listScalarsType.MessageOf(m).UnknownFields()
 }
+func (m *ListScalars) New() pref.Message            { return new(ListScalars) }
 func (m *ListScalars) Interface() pref.ProtoMessage { return m }
 func (m *ListScalars) ProtoReflect() pref.Message   { return m }
 
@@ -659,13 +672,16 @@
 	},
 )}
 
-func (m *MapScalars) Type() pref.MessageType { return mapScalarsType.PBType }
+// TODO: Remove this.
+func (m *MapScalars) Type() pref.MessageType             { return mapScalarsType.PBType }
+func (m *MapScalars) Descriptor() pref.MessageDescriptor { return mapScalarsType.PBType.Descriptor() }
 func (m *MapScalars) KnownFields() pref.KnownFields {
 	return mapScalarsType.MessageOf(m).KnownFields()
 }
 func (m *MapScalars) UnknownFields() pref.UnknownFields {
 	return mapScalarsType.MessageOf(m).UnknownFields()
 }
+func (m *MapScalars) New() pref.Message            { return new(MapScalars) }
 func (m *MapScalars) Interface() pref.ProtoMessage { return m }
 func (m *MapScalars) ProtoReflect() pref.Message   { return m }
 
@@ -821,13 +837,18 @@
 	},
 )}
 
+// TODO: Remove this.
 func (m *OneofScalars) Type() pref.MessageType { return oneofScalarsType.PBType }
+func (m *OneofScalars) Descriptor() pref.MessageDescriptor {
+	return oneofScalarsType.PBType.Descriptor()
+}
 func (m *OneofScalars) KnownFields() pref.KnownFields {
 	return oneofScalarsType.MessageOf(m).KnownFields()
 }
 func (m *OneofScalars) UnknownFields() pref.UnknownFields {
 	return oneofScalarsType.MessageOf(m).UnknownFields()
 }
+func (m *OneofScalars) New() pref.Message            { return new(OneofScalars) }
 func (m *OneofScalars) Interface() pref.ProtoMessage { return m }
 func (m *OneofScalars) ProtoReflect() pref.Message   { return m }
 
@@ -977,9 +998,11 @@
 	},
 )
 
-func (e EnumProto2) Enum() *EnumProto2       { return &e }
-func (e EnumProto2) Type() pref.EnumType     { return enumProto2Type }
-func (e EnumProto2) Number() pref.EnumNumber { return pref.EnumNumber(e) }
+// TODO: Remove this.
+func (e EnumProto2) Type() pref.EnumType             { return enumProto2Type }
+func (e EnumProto2) Descriptor() pref.EnumDescriptor { return enumProto2Type.Descriptor() }
+func (e EnumProto2) Enum() *EnumProto2               { return &e }
+func (e EnumProto2) Number() pref.EnumNumber         { return pref.EnumNumber(e) }
 
 type EnumProto3 int32
 
@@ -994,9 +1017,11 @@
 	},
 )
 
-func (e EnumProto3) Enum() *EnumProto3       { return &e }
-func (e EnumProto3) Type() pref.EnumType     { return enumProto3Type }
-func (e EnumProto3) Number() pref.EnumNumber { return pref.EnumNumber(e) }
+// TODO: Remove this.
+func (e EnumProto3) Type() pref.EnumType             { return enumProto3Type }
+func (e EnumProto3) Descriptor() pref.EnumDescriptor { return enumProto3Type.Descriptor() }
+func (e EnumProto3) Enum() *EnumProto3               { return &e }
+func (e EnumProto3) Number() pref.EnumNumber         { return pref.EnumNumber(e) }
 
 type EnumMessages struct {
 	EnumP2        *EnumProto2              `protobuf:"1"`
@@ -1015,18 +1040,18 @@
 		Syntax:   pref.Proto2,
 		FullName: "EnumMessages",
 		Fields: []ptype.Field{
-			{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BEEF"), EnumType: enumProto2Type},
-			{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BRAVO"), EnumType: enumProto3Type},
-			{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: pimpl.Export{}.MessageOf(new(proto2_20180125.Message)).Type()},
+			{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BEEF"), EnumType: enumProto2Type.Descriptor()},
+			{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BRAVO"), EnumType: enumProto3Type.Descriptor()},
+			{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: pimpl.Export{}.MessageDescriptorOf(new(proto2_20180125.Message))},
 			{Name: "f4", Number: 4, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: ptype.PlaceholderMessage("EnumMessages")},
-			{Name: "f5", Number: 5, Cardinality: pref.Repeated, Kind: pref.EnumKind, EnumType: enumProto2Type},
-			{Name: "f6", Number: 6, Cardinality: pref.Repeated, Kind: pref.MessageKind, MessageType: scalarProto2Type.PBType},
+			{Name: "f5", Number: 5, Cardinality: pref.Repeated, Kind: pref.EnumKind, EnumType: enumProto2Type.Descriptor()},
+			{Name: "f6", Number: 6, Cardinality: pref.Repeated, Kind: pref.MessageKind, MessageType: scalarProto2Type.PBType.Descriptor()},
 			{Name: "f7", Number: 7, Cardinality: pref.Repeated, Kind: pref.MessageKind, MessageType: enumMapDesc},
 			{Name: "f8", Number: 8, Cardinality: pref.Repeated, Kind: pref.MessageKind, MessageType: messageMapDesc},
-			{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BEEF"), OneofName: "union", EnumType: enumProto2Type},
-			{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BRAVO"), OneofName: "union", EnumType: enumProto3Type},
-			{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.MessageKind, OneofName: "union", MessageType: scalarProto2Type.PBType},
-			{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.MessageKind, OneofName: "union", MessageType: scalarProto3Type.PBType},
+			{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BEEF"), OneofName: "union", EnumType: enumProto2Type.Descriptor()},
+			{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.EnumKind, Default: V("BRAVO"), OneofName: "union", EnumType: enumProto3Type.Descriptor()},
+			{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.MessageKind, OneofName: "union", MessageType: scalarProto2Type.PBType.Descriptor()},
+			{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.MessageKind, OneofName: "union", MessageType: scalarProto3Type.PBType.Descriptor()},
 		},
 		Oneofs: []ptype.Oneof{{Name: "union"}},
 	}),
@@ -1040,7 +1065,7 @@
 	FullName: "EnumMessages.F7Entry",
 	Fields: []ptype.Field{
 		{Name: "key", Number: 1, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "value", Number: 2, Cardinality: pref.Optional, Kind: pref.EnumKind, EnumType: enumProto3Type},
+		{Name: "value", Number: 2, Cardinality: pref.Optional, Kind: pref.EnumKind, EnumType: enumProto3Type.Descriptor()},
 	},
 	Options:    &descriptorpb.MessageOptions{MapEntry: scalar.Bool(true)},
 	IsMapEntry: true,
@@ -1051,19 +1076,24 @@
 	FullName: "EnumMessages.F8Entry",
 	Fields: []ptype.Field{
 		{Name: "key", Number: 1, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "value", Number: 2, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: scalarProto3Type.PBType},
+		{Name: "value", Number: 2, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: scalarProto3Type.PBType.Descriptor()},
 	},
 	Options:    &descriptorpb.MessageOptions{MapEntry: scalar.Bool(true)},
 	IsMapEntry: true,
 })
 
+// TODO: Remove this.
 func (m *EnumMessages) Type() pref.MessageType { return enumMessagesType.PBType }
+func (m *EnumMessages) Descriptor() pref.MessageDescriptor {
+	return enumMessagesType.PBType.Descriptor()
+}
 func (m *EnumMessages) KnownFields() pref.KnownFields {
 	return enumMessagesType.MessageOf(m).KnownFields()
 }
 func (m *EnumMessages) UnknownFields() pref.UnknownFields {
 	return enumMessagesType.MessageOf(m).UnknownFields()
 }
+func (m *EnumMessages) New() pref.Message            { return new(EnumMessages) }
 func (m *EnumMessages) Interface() pref.ProtoMessage { return m }
 func (m *EnumMessages) ProtoReflect() pref.Message   { return m }
 
diff --git a/internal/legacy/enum.go b/internal/legacy/enum.go
index 0ee397f..2a57b0b 100644
--- a/internal/legacy/enum.go
+++ b/internal/legacy/enum.go
@@ -55,12 +55,16 @@
 	goTyp reflect.Type
 }
 
-func (e *enumWrapper) Number() pref.EnumNumber {
-	return e.num
-}
+// TODO: Remove this.
 func (e *enumWrapper) Type() pref.EnumType {
 	return e.pbTyp
 }
+func (e *enumWrapper) Descriptor() pref.EnumDescriptor {
+	return e.pbTyp.Descriptor()
+}
+func (e *enumWrapper) Number() pref.EnumNumber {
+	return e.num
+}
 func (e *enumWrapper) ProtoReflect() pref.Enum {
 	return e
 }
diff --git a/internal/legacy/export.go b/internal/legacy/export.go
index 054108a..fa4375a 100644
--- a/internal/legacy/export.go
+++ b/internal/legacy/export.go
@@ -25,6 +25,10 @@
 	return loadEnumType(reflect.TypeOf(e))
 }
 
+func (Export) EnumDescriptorOf(e interface{}) pref.EnumDescriptor {
+	return LoadEnumDesc(reflect.TypeOf(e))
+}
+
 func (Export) MessageOf(m interface{}) pvalue.LegacyMessage {
 	return wrapMessage(reflect.ValueOf(m)).ProtoReflect().(pvalue.LegacyMessage)
 }
@@ -33,8 +37,8 @@
 	return loadMessageType(reflect.TypeOf(m)).PBType
 }
 
-func (Export) ExtensionTypeOf(d pref.ExtensionDescriptor, t interface{}) pref.ExtensionType {
-	return extensionTypeOf(d, reflect.TypeOf(t))
+func (Export) MessageDescriptorOf(m interface{}) pref.MessageDescriptor {
+	return LoadMessageDesc(reflect.TypeOf(m))
 }
 
 func (Export) ExtensionDescFromType(t pref.ExtensionType) *piface.ExtensionDescV1 {
diff --git a/internal/legacy/extension.go b/internal/legacy/extension.go
index 94ad7b7..e9c8bb7 100644
--- a/internal/legacy/extension.go
+++ b/internal/legacy/extension.go
@@ -15,6 +15,7 @@
 	pfmt "github.com/golang/protobuf/v2/internal/typefmt"
 	pvalue "github.com/golang/protobuf/v2/internal/value"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
 	piface "github.com/golang/protobuf/v2/runtime/protoiface"
 )
 
@@ -46,25 +47,25 @@
 
 // extensionDescFromType converts a v2 protoreflect.ExtensionType to a
 // protoiface.ExtensionDescV1. The returned ExtensionDesc must not be mutated.
-func extensionDescFromType(t pref.ExtensionType) *piface.ExtensionDescV1 {
+func extensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 {
 	// Fast-path: check whether an extension desc is already nested within.
-	if t, ok := t.(interface {
+	if xt, ok := xt.(interface {
 		ProtoLegacyExtensionDesc() *piface.ExtensionDescV1
 	}); ok {
-		if d := t.ProtoLegacyExtensionDesc(); d != nil {
+		if d := xt.ProtoLegacyExtensionDesc(); d != nil {
 			return d
 		}
 	}
 
 	// Fast-path: check the cache for whether this ExtensionType has already
 	// been converted to a legacy descriptor.
-	if d, ok := extensionDescCache.Load(t); ok {
+	if d, ok := extensionDescCache.Load(xt); ok {
 		return d.(*piface.ExtensionDescV1)
 	}
 
 	// Determine the parent type if possible.
 	var parent piface.MessageV1
-	if mt, ok := t.Extendee().(pref.MessageType); ok {
+	if mt, _ := preg.GlobalTypes.FindMessageByName(xt.Descriptor().Extendee().FullName()); mt != nil {
 		// Create a new parent message and unwrap it if possible.
 		mv := mt.New().Interface()
 		t := reflect.TypeOf(mv)
@@ -81,7 +82,7 @@
 
 	// Determine the v1 extension type, which is unfortunately not the same as
 	// the v2 ExtensionType.GoType.
-	extType := t.GoType()
+	extType := xt.GoType()
 	switch extType.Kind() {
 	case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
 		extType = reflect.PtrTo(extType) // T -> *T for singular scalar fields
@@ -94,23 +95,21 @@
 	// Reconstruct the legacy enum full name, which is an odd mixture of the
 	// proto package name with the Go type name.
 	var enumName string
-	if t.Kind() == pref.EnumKind {
+	if xt.Descriptor().Kind() == pref.EnumKind {
 		// Derive Go type name.
-		// For legacy enums, unwrap the wrapper to get the underlying Go type.
-		et := t.Enum().(pref.EnumType)
-		var ev interface{} = et.New(0)
-		if u, ok := ev.(pvalue.Unwrapper); ok {
-			ev = u.ProtoUnwrap()
+		t := extType
+		if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
+			t = t.Elem()
 		}
-		enumName = reflect.TypeOf(ev).Name()
+		enumName = t.Name()
 
 		// Derive the proto package name.
 		// For legacy enums, obtain the proto package from the raw descriptor.
 		var protoPkg string
-		if fd := parentFileDescriptor(et); fd != nil {
+		if fd := parentFileDescriptor(xt.Descriptor().Enum()); fd != nil {
 			protoPkg = string(fd.Package())
 		}
-		if ed, ok := ev.(enumV1); ok && protoPkg == "" {
+		if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" {
 			b, _ := ed.EnumDescriptor()
 			protoPkg = loadFileDesc(b).GetPackage()
 		}
@@ -122,21 +121,21 @@
 
 	// Derive the proto file that the extension was declared within.
 	var filename string
-	if fd := parentFileDescriptor(t); fd != nil {
+	if fd := parentFileDescriptor(xt.Descriptor()); fd != nil {
 		filename = fd.Path()
 	}
 
 	// Construct and return a ExtensionDescV1.
 	d := &piface.ExtensionDescV1{
-		Type:          t,
+		Type:          xt,
 		ExtendedType:  parent,
 		ExtensionType: reflect.Zero(extType).Interface(),
-		Field:         int32(t.Number()),
-		Name:          string(t.FullName()),
-		Tag:           ptag.Marshal(t, enumName),
+		Field:         int32(xt.Descriptor().Number()),
+		Name:          string(xt.Descriptor().FullName()),
+		Tag:           ptag.Marshal(xt.Descriptor(), enumName),
 		Filename:      filename,
 	}
-	if d, ok := extensionDescCache.LoadOrStore(t, d); ok {
+	if d, ok := extensionDescCache.LoadOrStore(xt, d); ok {
 		return d.(*piface.ExtensionDescV1)
 	}
 	return d
@@ -169,7 +168,15 @@
 	f := ptag.Unmarshal(d.Tag, t)
 
 	// Construct a v2 ExtensionType.
+	var ed pref.EnumDescriptor
+	var md pref.MessageDescriptor
 	conv := pvalue.NewLegacyConverter(t, f.Kind, Export{})
+	if conv.EnumType != nil {
+		ed = conv.EnumType.Descriptor()
+	}
+	if conv.MessageType != nil {
+		md = conv.MessageType.Descriptor()
+	}
 	xd, err := ptype.NewExtension(&ptype.StandaloneExtension{
 		FullName:     pref.FullName(d.Name),
 		Number:       pref.FieldNumber(d.Field),
@@ -177,19 +184,14 @@
 		Kind:         f.Kind,
 		Default:      f.Default,
 		Options:      f.Options,
-		EnumType:     conv.EnumType,
-		MessageType:  conv.MessageType,
-		ExtendedType: pimpl.Export{}.MessageTypeOf(d.ExtendedType),
+		EnumType:     ed,
+		MessageType:  md,
+		ExtendedType: pimpl.Export{}.MessageDescriptorOf(d.ExtendedType),
 	})
 	if err != nil {
 		panic(err)
 	}
-	var zv interface{}
-	switch xd.Kind() {
-	case pref.EnumKind, pref.MessageKind, pref.GroupKind:
-		zv = reflect.Zero(t).Interface()
-	}
-	xt := pimpl.Export{}.ExtensionTypeOf(xd, zv)
+	xt := ExtensionTypeOf(xd, t)
 
 	// Cache the conversion for both directions.
 	extensionDescCache.LoadOrStore(xt, d)
@@ -199,17 +201,26 @@
 	return xt
 }
 
-// extensionTypeOf returns a protoreflect.ExtensionType where the GoType
-// is the underlying v1 Go type instead of the wrapper types used to present
-// v1 Go types as if they satisfied the v2 API.
+// ExtensionTypeOf returns a protoreflect.ExtensionType where the type of the
+// field is t. The type t must be provided if the field is an enum or message.
 //
-// This function is only valid if xd.Kind is an enum or message.
-func extensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
-	// Step 1: Create an ExtensionType where GoType is the wrapper type.
+// This is exported for testing purposes.
+func ExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
+	// Extension types for non-enums and non-messages are simple.
+	switch xd.Kind() {
+	case pref.EnumKind, pref.MessageKind, pref.GroupKind:
+	default:
+		return ptype.GoExtension(xd, nil, nil)
+	}
+
+	// Create an ExtensionType where GoType is the wrapper type.
 	conv := pvalue.NewLegacyConverter(t, xd.Kind(), Export{})
 	xt := ptype.GoExtension(xd, conv.EnumType, conv.MessageType)
+	if !conv.IsLegacy {
+		return xt
+	}
 
-	// Step 2: Wrap ExtensionType such that GoType presents the legacy Go type.
+	// Wrap ExtensionType such that GoType presents the legacy Go type.
 	xt2 := &extensionType{ExtensionType: xt}
 	if xd.Cardinality() != pref.Repeated {
 		xt2.typ = t
@@ -264,4 +275,4 @@
 func (x *extensionType) New() pref.Value                      { return x.new() }
 func (x *extensionType) ValueOf(v interface{}) pref.Value     { return x.valueOf(v) }
 func (x *extensionType) InterfaceOf(v pref.Value) interface{} { return x.interfaceOf(v) }
-func (x *extensionType) Format(s fmt.State, r rune)           { pfmt.FormatDesc(s, r, x) }
+func (x *extensionType) Format(s fmt.State, r rune)           { pfmt.FormatDesc(s, r, x.Descriptor()) }
diff --git a/internal/legacy/extension_test.go b/internal/legacy/extension_test.go
index 630c158..cdba627 100644
--- a/internal/legacy/extension_test.go
+++ b/internal/legacy/extension_test.go
@@ -5,17 +5,15 @@
 package legacy_test
 
 import (
+	"reflect"
 	"testing"
 
 	pimpl "github.com/golang/protobuf/v2/internal/impl"
+	plegacy "github.com/golang/protobuf/v2/internal/legacy"
 	ptype "github.com/golang/protobuf/v2/internal/prototype"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 	piface "github.com/golang/protobuf/v2/runtime/protoiface"
 
-	// The legacy package must be imported prior to use of any legacy messages.
-	// TODO: Remove this when protoV1 registers these hooks for you.
-	plegacy "github.com/golang/protobuf/v2/internal/legacy"
-
 	proto2_20180125 "github.com/golang/protobuf/v2/internal/testprotos/legacy/proto2.v1.0.0-20180125-92554152"
 )
 
@@ -34,22 +32,22 @@
 func mustMakeExtensionType(x *ptype.StandaloneExtension, v interface{}) pref.ExtensionType {
 	xd, err := ptype.NewExtension(x)
 	if err != nil {
-		panic(xd)
+		panic(err)
 	}
-	return pimpl.Export{}.ExtensionTypeOf(xd, v)
+	return plegacy.ExtensionTypeOf(xd, reflect.TypeOf(v))
 }
 
 var (
-	parentType    = pimpl.Export{}.MessageTypeOf((*legacyTestMessage)(nil))
-	messageV1Type = pimpl.Export{}.MessageTypeOf((*proto2_20180125.Message_ChildMessage)(nil))
+	parentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyTestMessage)(nil))
+	messageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
 
 	wantType = mustMakeExtensionType(&ptype.StandaloneExtension{
 		FullName:     "fizz.buzz.optional_message_v1",
 		Number:       10007,
 		Cardinality:  pref.Optional,
 		Kind:         pref.MessageKind,
-		MessageType:  messageV1Type,
-		ExtendedType: parentType,
+		MessageType:  messageV1Desc,
+		ExtendedType: parentDesc,
 	}, (*proto2_20180125.Message_ChildMessage)(nil))
 	wantDesc = &piface.ExtensionDescV1{
 		ExtendedType:  (*legacyTestMessage)(nil),
diff --git a/internal/legacy/legacy_test.go b/internal/legacy/legacy_test.go
index 3bac3b3..9bb484c 100644
--- a/internal/legacy/legacy_test.go
+++ b/internal/legacy/legacy_test.go
@@ -31,7 +31,7 @@
 	const numParallel = 5
 	var messageATypes [numParallel]protoreflect.MessageType
 	var messageBTypes [numParallel]protoreflect.MessageType
-	var enumTypes [numParallel]protoreflect.EnumType
+	var enumDescs [numParallel]protoreflect.EnumDescriptor
 
 	// Concurrently load message and enum types.
 	var wg sync.WaitGroup
@@ -48,31 +48,30 @@
 		}()
 		go func() {
 			defer wg.Done()
-			enumTypes[i] = Export{}.EnumTypeOf(Enum(0))
+			enumDescs[i] = Export{}.EnumDescriptorOf(Enum(0))
 		}()
 	}
 	wg.Wait()
 
 	var (
 		wantMTA = messageATypes[0]
-		wantMDA = messageATypes[0].Fields().ByNumber(1).Message()
+		wantMDA = messageATypes[0].Descriptor().Fields().ByNumber(1).Message()
 		wantMTB = messageBTypes[0]
-		wantMDB = messageBTypes[0].Fields().ByNumber(2).Message()
-		wantET  = enumTypes[0]
-		wantED  = messageATypes[0].Fields().ByNumber(3).Enum()
+		wantMDB = messageBTypes[0].Descriptor().Fields().ByNumber(2).Message()
+		wantED  = messageATypes[0].Descriptor().Fields().ByNumber(3).Enum()
 	)
 
 	for _, gotMT := range messageATypes[1:] {
 		if gotMT != wantMTA {
 			t.Error("MessageType(MessageA) mismatch")
 		}
-		if gotMDA := gotMT.Fields().ByNumber(1).Message(); gotMDA != wantMDA {
+		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
 			t.Error("MessageDescriptor(MessageA) mismatch")
 		}
-		if gotMDB := gotMT.Fields().ByNumber(2).Message(); gotMDB != wantMDB {
+		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
 			t.Error("MessageDescriptor(MessageB) mismatch")
 		}
-		if gotED := gotMT.Fields().ByNumber(3).Enum(); gotED != wantED {
+		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
 			t.Error("EnumDescriptor(Enum) mismatch")
 		}
 	}
@@ -80,18 +79,18 @@
 		if gotMT != wantMTB {
 			t.Error("MessageType(MessageB) mismatch")
 		}
-		if gotMDA := gotMT.Fields().ByNumber(1).Message(); gotMDA != wantMDA {
+		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
 			t.Error("MessageDescriptor(MessageA) mismatch")
 		}
-		if gotMDB := gotMT.Fields().ByNumber(2).Message(); gotMDB != wantMDB {
+		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
 			t.Error("MessageDescriptor(MessageB) mismatch")
 		}
-		if gotED := gotMT.Fields().ByNumber(3).Enum(); gotED != wantED {
+		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
 			t.Error("EnumDescriptor(Enum) mismatch")
 		}
 	}
-	for _, gotET := range enumTypes[1:] {
-		if gotET != wantET {
+	for _, gotED := range enumDescs[1:] {
+		if gotED != wantED {
 			t.Error("EnumType(Enum) mismatch")
 		}
 	}
diff --git a/internal/legacy/message.go b/internal/legacy/message.go
index 01bc053..ee242ca 100644
--- a/internal/legacy/message.go
+++ b/internal/legacy/message.go
@@ -232,14 +232,14 @@
 	// Populate EnumType and MessageType.
 	if f.EnumType == nil && f.Kind == pref.EnumKind {
 		if ev, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
-			f.EnumType = ev.Type()
+			f.EnumType = ev.Descriptor()
 		} else {
 			f.EnumType = LoadEnumDesc(t)
 		}
 	}
 	if f.MessageType == nil && (f.Kind == pref.MessageKind || f.Kind == pref.GroupKind) {
 		if mv, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok {
-			f.MessageType = mv.ProtoReflect().Type()
+			f.MessageType = mv.ProtoReflect().Descriptor()
 		} else if t.Kind() == reflect.Map {
 			m := &ptype.StandaloneMessage{
 				Syntax:     parent.Syntax,
diff --git a/internal/prototype/go_type.go b/internal/prototype/go_type.go
index 8dba537..6c88a7c 100644
--- a/internal/prototype/go_type.go
+++ b/internal/prototype/go_type.go
@@ -31,6 +31,9 @@
 	typ  reflect.Type
 }
 
+func (t *goEnum) Descriptor() protoreflect.EnumDescriptor {
+	return t.EnumDescriptor
+}
 func (t *goEnum) GoType() reflect.Type {
 	t.New(0) // initialize t.typ
 	return t.typ
@@ -66,6 +69,9 @@
 	typ  reflect.Type
 }
 
+func (t *goMessage) Descriptor() protoreflect.MessageDescriptor {
+	return t.MessageDescriptor
+}
 func (t *goMessage) GoType() reflect.Type {
 	t.New() // initialize t.typ
 	return t.typ
@@ -162,11 +168,20 @@
 	interfaceOf func(v protoreflect.Value) interface{}
 }
 
+func (t *goExtension) Descriptor() protoreflect.ExtensionDescriptor {
+	return t.ExtensionDescriptor
+}
 func (t *goExtension) EnumType() protoreflect.EnumDescriptor {
-	return t.enumType
+	if t.enumType == nil {
+		return nil
+	}
+	return t.enumType.Descriptor()
 }
 func (t *goExtension) MessageType() protoreflect.MessageDescriptor {
-	return t.messageType
+	if t.messageType == nil {
+		return nil
+	}
+	return t.messageType.Descriptor()
 }
 func (t *goExtension) GoType() reflect.Type {
 	t.lazyInit()
diff --git a/internal/testprotos/conformance/conformance.pb.go b/internal/testprotos/conformance/conformance.pb.go
index 6a718fc..90a1076 100644
--- a/internal/testprotos/conformance/conformance.pb.go
+++ b/internal/testprotos/conformance/conformance.pb.go
@@ -42,9 +42,14 @@
 }
 
 func (x WireFormat) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (WireFormat) Descriptor() protoreflect.EnumDescriptor {
+	return file_conformance_conformance_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (WireFormat) Type() protoreflect.EnumType {
 	return file_conformance_conformance_proto_enumTypes[0]
 }
@@ -98,9 +103,14 @@
 }
 
 func (x TestCategory) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestCategory) Descriptor() protoreflect.EnumDescriptor {
+	return file_conformance_conformance_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestCategory) Type() protoreflect.EnumType {
 	return file_conformance_conformance_proto_enumTypes[1]
 }
diff --git a/internal/testprotos/conformance/test_messages_proto2.pb.go b/internal/testprotos/conformance/test_messages_proto2.pb.go
index 04e0621..cde3e92 100644
--- a/internal/testprotos/conformance/test_messages_proto2.pb.go
+++ b/internal/testprotos/conformance/test_messages_proto2.pb.go
@@ -42,9 +42,14 @@
 }
 
 func (x ForeignEnumProto2) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ForeignEnumProto2) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_test_messages_proto2_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ForeignEnumProto2) Type() protoreflect.EnumType {
 	return file_google_protobuf_test_messages_proto2_proto_enumTypes[0]
 }
@@ -55,7 +60,7 @@
 
 // Deprecated: Do not use.
 func (x *ForeignEnumProto2) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
@@ -100,9 +105,14 @@
 }
 
 func (x TestAllTypesProto2_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestAllTypesProto2_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_test_messages_proto2_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestAllTypesProto2_NestedEnum) Type() protoreflect.EnumType {
 	return file_google_protobuf_test_messages_proto2_proto_enumTypes[1]
 }
@@ -113,7 +123,7 @@
 
 // Deprecated: Do not use.
 func (x *TestAllTypesProto2_NestedEnum) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
diff --git a/internal/testprotos/conformance/test_messages_proto3.pb.go b/internal/testprotos/conformance/test_messages_proto3.pb.go
index 28233e5..c49c66d 100644
--- a/internal/testprotos/conformance/test_messages_proto3.pb.go
+++ b/internal/testprotos/conformance/test_messages_proto3.pb.go
@@ -37,9 +37,14 @@
 }
 
 func (x ForeignEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ForeignEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_test_messages_proto3_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ForeignEnum) Type() protoreflect.EnumType {
 	return file_google_protobuf_test_messages_proto3_proto_enumTypes[0]
 }
@@ -79,9 +84,14 @@
 }
 
 func (x TestAllTypesProto3_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestAllTypesProto3_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_test_messages_proto3_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestAllTypesProto3_NestedEnum) Type() protoreflect.EnumType {
 	return file_google_protobuf_test_messages_proto3_proto_enumTypes[1]
 }
@@ -127,9 +137,14 @@
 }
 
 func (x TestAllTypesProto3_AliasedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestAllTypesProto3_AliasedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_test_messages_proto3_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestAllTypesProto3_AliasedEnum) Type() protoreflect.EnumType {
 	return file_google_protobuf_test_messages_proto3_proto_enumTypes[2]
 }
diff --git a/internal/testprotos/test/test.pb.go b/internal/testprotos/test/test.pb.go
index 8217a9a..7d512da 100644
--- a/internal/testprotos/test/test.pb.go
+++ b/internal/testprotos/test/test.pb.go
@@ -43,9 +43,14 @@
 }
 
 func (x ForeignEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ForeignEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_test_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ForeignEnum) Type() protoreflect.EnumType {
 	return file_test_test_proto_enumTypes[0]
 }
@@ -56,7 +61,7 @@
 
 // Deprecated: Do not use.
 func (x *ForeignEnum) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
@@ -92,9 +97,14 @@
 }
 
 func (x TestReservedEnumFields) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestReservedEnumFields) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_test_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestReservedEnumFields) Type() protoreflect.EnumType {
 	return file_test_test_proto_enumTypes[1]
 }
@@ -105,7 +115,7 @@
 
 // Deprecated: Do not use.
 func (x *TestReservedEnumFields) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
@@ -150,9 +160,14 @@
 }
 
 func (x TestAllTypes_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestAllTypes_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_test_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestAllTypes_NestedEnum) Type() protoreflect.EnumType {
 	return file_test_test_proto_enumTypes[2]
 }
@@ -163,7 +178,7 @@
 
 // Deprecated: Do not use.
 func (x *TestAllTypes_NestedEnum) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
@@ -198,9 +213,14 @@
 }
 
 func (x TestDeprecatedMessage_DeprecatedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestDeprecatedMessage_DeprecatedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_test_proto_enumTypes[3].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestDeprecatedMessage_DeprecatedEnum) Type() protoreflect.EnumType {
 	return file_test_test_proto_enumTypes[3]
 }
@@ -211,7 +231,7 @@
 
 // Deprecated: Do not use.
 func (x *TestDeprecatedMessage_DeprecatedEnum) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
diff --git a/internal/testprotos/test/test_import.pb.go b/internal/testprotos/test/test_import.pb.go
index 3b9766f..1682ef5 100644
--- a/internal/testprotos/test/test_import.pb.go
+++ b/internal/testprotos/test/test_import.pb.go
@@ -36,9 +36,14 @@
 }
 
 func (x ImportEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ImportEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_test_import_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ImportEnum) Type() protoreflect.EnumType {
 	return file_test_test_import_proto_enumTypes[0]
 }
@@ -49,7 +54,7 @@
 
 // Deprecated: Do not use.
 func (x *ImportEnum) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Type(), b)
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
diff --git a/internal/testprotos/test3/test.pb.go b/internal/testprotos/test3/test.pb.go
index d871a2e..262f7c9 100644
--- a/internal/testprotos/test3/test.pb.go
+++ b/internal/testprotos/test3/test.pb.go
@@ -39,9 +39,14 @@
 }
 
 func (x ForeignEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ForeignEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test3_test_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ForeignEnum) Type() protoreflect.EnumType {
 	return file_test3_test_proto_enumTypes[0]
 }
@@ -81,9 +86,14 @@
 }
 
 func (x TestAllTypes_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TestAllTypes_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test3_test_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (TestAllTypes_NestedEnum) Type() protoreflect.EnumType {
 	return file_test3_test_proto_enumTypes[1]
 }
diff --git a/internal/testprotos/test3/test_import.pb.go b/internal/testprotos/test3/test_import.pb.go
index 385827b..3ea92b6 100644
--- a/internal/testprotos/test3/test_import.pb.go
+++ b/internal/testprotos/test3/test_import.pb.go
@@ -30,9 +30,14 @@
 }
 
 func (x ImportEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ImportEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_test3_test_import_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (ImportEnum) Type() protoreflect.EnumType {
 	return file_test3_test_import_proto_enumTypes[0]
 }
diff --git a/internal/value/convert.go b/internal/value/convert.go
index 3bf5488..4852b40 100644
--- a/internal/value/convert.go
+++ b/internal/value/convert.go
@@ -56,11 +56,11 @@
 	LegacyWrapper interface {
 		EnumOf(interface{}) LegacyEnum
 		EnumTypeOf(interface{}) pref.EnumType
+		EnumDescriptorOf(interface{}) pref.EnumDescriptor
 
 		MessageOf(interface{}) LegacyMessage
 		MessageTypeOf(interface{}) pref.MessageType
-
-		ExtensionTypeOf(pref.ExtensionDescriptor, interface{}) pref.ExtensionType
+		MessageDescriptorOf(interface{}) pref.MessageDescriptor
 
 		// TODO: Remove these eventually.
 		// See the TODOs in internal/impl/legacy_extension.go.
@@ -121,31 +121,14 @@
 			return makeScalarConverter(t, bytesType)
 		}
 	case pref.EnumKind:
-		// Handle v2 enums, which must satisfy the proto.Enum interface.
-		if t.Kind() != reflect.Ptr && t.Implements(enumIfaceV2) {
-			et := reflect.Zero(t).Interface().(pref.Enum).Type()
-			return Converter{
-				PBValueOf: func(v reflect.Value) pref.Value {
-					if v.Type() != t {
-						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
-					}
-					e := v.Interface().(pref.Enum)
-					return pref.ValueOf(e.Number())
-				},
-				GoValueOf: func(v pref.Value) reflect.Value {
-					rv := reflect.ValueOf(et.New(v.Enum()))
-					if rv.Type() != t {
-						panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t))
-					}
-					return rv
-				},
-				EnumType: et,
+		// Handle enums, which must be a named int32 type.
+		if t.PkgPath() != "" && t.Kind() == reflect.Int32 {
+			var et pref.EnumType
+			if t.Implements(enumIfaceV2) {
+				et = &enumType{reflect.Zero(t).Interface().(pref.Enum).Descriptor(), t}
+			} else {
+				et = w.EnumTypeOf(reflect.Zero(t).Interface())
 			}
-		}
-
-		// Handle v1 enums, which we identify as simply a named int32 type.
-		if w != nil && t.PkgPath() != "" && t.Kind() == reflect.Int32 {
-			et := w.EnumTypeOf(reflect.Zero(t).Interface())
 			return Converter{
 				PBValueOf: func(v reflect.Value) pref.Value {
 					if v.Type() != t {
@@ -157,13 +140,14 @@
 					return reflect.ValueOf(v.Enum()).Convert(t)
 				},
 				EnumType: et,
-				IsLegacy: true,
+				IsLegacy: !t.Implements(enumIfaceV2),
 			}
 		}
 	case pref.MessageKind, pref.GroupKind:
 		// Handle v2 messages, which must satisfy the proto.Message interface.
 		if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV2) {
-			mt := reflect.Zero(t).Interface().(pref.ProtoMessage).ProtoReflect().Type()
+			md := reflect.Zero(t).Interface().(pref.ProtoMessage).ProtoReflect().Descriptor()
+			mt := &messageType{md, t}
 			return Converter{
 				PBValueOf: func(v reflect.Value) pref.Value {
 					if v.Type() != t {
@@ -240,3 +224,29 @@
 	MessageType pref.MessageType
 	IsLegacy    bool
 }
+
+// TODO: This needs to be centralized in a package.
+type enumType struct {
+	// TODO: Remove me as an embedded field.
+	pref.EnumDescriptor
+	typ reflect.Type // must implement protoreflect.Enum
+}
+
+func (t *enumType) Descriptor() pref.EnumDescriptor { return t.EnumDescriptor }
+func (t *enumType) GoType() reflect.Type            { return t.typ }
+func (t *enumType) New(n pref.EnumNumber) pref.Enum {
+	return reflect.ValueOf(n).Convert(t.typ).Interface().(pref.Enum)
+}
+
+// TODO: This needs to be centralized in a package.
+type messageType struct {
+	// TODO: Remove me as an embedded field.
+	pref.MessageDescriptor
+	typ reflect.Type // must implement protoreflect.ProtoMessage
+}
+
+func (t *messageType) Descriptor() pref.MessageDescriptor { return t.MessageDescriptor }
+func (t *messageType) GoType() reflect.Type               { return t.typ }
+func (t *messageType) New() pref.Message {
+	return reflect.New(t.typ.Elem()).Interface().(pref.ProtoMessage).ProtoReflect()
+}