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/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 6453f17..312e1ac 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -276,7 +276,7 @@
 	}
 	// String method.
 	g.P("func (x ", enum.GoIdent, ") String() string {")
-	g.P("return ", protoimplPackage.Ident("X"), ".EnumStringOf(x.Type(), ", protoreflectPackage.Ident("EnumNumber"), "(x))")
+	g.P("return ", protoimplPackage.Ident("X"), ".EnumStringOf(x.Descriptor(), ", protoreflectPackage.Ident("EnumNumber"), "(x))")
 	g.P("}")
 	g.P()
 
@@ -286,7 +286,7 @@
 	if enum.Desc.Syntax() == protoreflect.Proto2 {
 		g.P("// Deprecated: Do not use.")
 		g.P("func (x *", enum.GoIdent, ") UnmarshalJSON(b []byte) error {")
-		g.P("num, err := ", protoimplPackage.Ident("X"), ".UnmarshalJSONEnum(x.Type(), b)")
+		g.P("num, err := ", protoimplPackage.Ident("X"), ".UnmarshalJSONEnum(x.Descriptor(), b)")
 		g.P("if err != nil {")
 		g.P("return err")
 		g.P("}")
diff --git a/cmd/protoc-gen-go/internal_gengo/reflect.go b/cmd/protoc-gen-go/internal_gengo/reflect.go
index ec45e16..8d13d34 100644
--- a/cmd/protoc-gen-go/internal_gengo/reflect.go
+++ b/cmd/protoc-gen-go/internal_gengo/reflect.go
@@ -233,7 +233,15 @@
 	idx := f.allEnumsByPtr[enum]
 	typesVar := enumTypesVarName(f)
 
+	// Descriptor method.
+	g.P("func (", enum.GoIdent, ") Descriptor() ", protoreflectPackage.Ident("EnumDescriptor"), " {")
+	g.P("return ", typesVar, "[", idx, "].Descriptor()")
+	g.P("}")
+	g.P()
+
 	// Type method.
+	// TODO: Remove this.
+	g.P("// Deprecated: Use Descriptor instead.")
 	g.P("func (", enum.GoIdent, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
 	g.P("return ", typesVar, "[", idx, "]")
 	g.P("}")
diff --git a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
index a2cd56f..d7333a8 100644
--- a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
+++ b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
@@ -36,9 +36,14 @@
 }
 
 func (x AnnotationsTestEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (AnnotationsTestEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_annotations_annotations_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (AnnotationsTestEnum) Type() protoreflect.EnumType {
 	return file_annotations_annotations_proto_enumTypes[0]
 }
@@ -49,7 +54,7 @@
 
 // Deprecated: Do not use.
 func (x *AnnotationsTestEnum) 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/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
index 318cefc..d120c9d 100644
--- a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
+++ b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
@@ -1 +1 @@
-annotation:{path:5 path:0 source_file:"annotations/annotations.proto" begin:461 end:480} annotation:{path:5 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:497 end:544} annotation:{path:4 path:0 source_file:"annotations/annotations.proto" begin:1732 end:1754} annotation:{path:4 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:1765 end:1785} annotation:{path:4 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:2806 end:2829}
\ No newline at end of file
+annotation:{path:5 path:0 source_file:"annotations/annotations.proto" begin:461 end:480} annotation:{path:5 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:497 end:544} annotation:{path:4 path:0 source_file:"annotations/annotations.proto" begin:1925 end:1947} annotation:{path:4 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:1958 end:1978} annotation:{path:4 path:0 path:2 path:0 source_file:"annotations/annotations.proto" begin:2999 end:3022}
\ No newline at end of file
diff --git a/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go b/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go
index 3f37f26..499aeb6 100644
--- a/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go
+++ b/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go
@@ -29,9 +29,14 @@
 }
 
 func (x DeprecatedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (DeprecatedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_comments_deprecated_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (DeprecatedEnum) Type() protoreflect.EnumType {
 	return file_comments_deprecated_proto_enumTypes[0]
 }
diff --git a/cmd/protoc-gen-go/testdata/extensions/ext/ext.pb.go b/cmd/protoc-gen-go/testdata/extensions/ext/ext.pb.go
index d7588a3..82d374c 100644
--- a/cmd/protoc-gen-go/testdata/extensions/ext/ext.pb.go
+++ b/cmd/protoc-gen-go/testdata/extensions/ext/ext.pb.go
@@ -38,9 +38,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_extensions_ext_ext_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_extensions_ext_ext_proto_enumTypes[0]
 }
@@ -51,7 +56,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum) 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/cmd/protoc-gen-go/testdata/extensions/proto3/ext3.pb.go b/cmd/protoc-gen-go/testdata/extensions/proto3/ext3.pb.go
index b5460d0..1bedf3f 100644
--- a/cmd/protoc-gen-go/testdata/extensions/proto3/ext3.pb.go
+++ b/cmd/protoc-gen-go/testdata/extensions/proto3/ext3.pb.go
@@ -31,9 +31,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_extensions_proto3_ext3_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_extensions_proto3_ext3_proto_enumTypes[0]
 }
diff --git a/cmd/protoc-gen-go/testdata/import_public/sub/a.pb.go b/cmd/protoc-gen-go/testdata/import_public/sub/a.pb.go
index 6c6c93a..ec7e87e 100644
--- a/cmd/protoc-gen-go/testdata/import_public/sub/a.pb.go
+++ b/cmd/protoc-gen-go/testdata/import_public/sub/a.pb.go
@@ -42,9 +42,14 @@
 }
 
 func (x E) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (E) Descriptor() protoreflect.EnumDescriptor {
+	return file_import_public_sub_a_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (E) Type() protoreflect.EnumType {
 	return file_import_public_sub_a_proto_enumTypes[0]
 }
@@ -55,7 +60,7 @@
 
 // Deprecated: Do not use.
 func (x *E) 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
 	}
@@ -91,9 +96,14 @@
 }
 
 func (x M_Subenum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (M_Subenum) Descriptor() protoreflect.EnumDescriptor {
+	return file_import_public_sub_a_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (M_Subenum) Type() protoreflect.EnumType {
 	return file_import_public_sub_a_proto_enumTypes[1]
 }
@@ -104,7 +114,7 @@
 
 // Deprecated: Do not use.
 func (x *M_Subenum) 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
 	}
@@ -140,9 +150,14 @@
 }
 
 func (x M_Submessage_Submessage_Subenum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (M_Submessage_Submessage_Subenum) Descriptor() protoreflect.EnumDescriptor {
+	return file_import_public_sub_a_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (M_Submessage_Submessage_Subenum) Type() protoreflect.EnumType {
 	return file_import_public_sub_a_proto_enumTypes[2]
 }
@@ -153,7 +168,7 @@
 
 // Deprecated: Do not use.
 func (x *M_Submessage_Submessage_Subenum) 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/cmd/protoc-gen-go/testdata/imports/test_a_1/m1.pb.go b/cmd/protoc-gen-go/testdata/imports/test_a_1/m1.pb.go
index d4023d7..18c004d 100644
--- a/cmd/protoc-gen-go/testdata/imports/test_a_1/m1.pb.go
+++ b/cmd/protoc-gen-go/testdata/imports/test_a_1/m1.pb.go
@@ -30,9 +30,14 @@
 }
 
 func (x E1) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (E1) Descriptor() protoreflect.EnumDescriptor {
+	return file_imports_test_a_1_m1_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (E1) Type() protoreflect.EnumType {
 	return file_imports_test_a_1_m1_proto_enumTypes[0]
 }
diff --git a/cmd/protoc-gen-go/testdata/nopackage/nopackage.pb.go b/cmd/protoc-gen-go/testdata/nopackage/nopackage.pb.go
index b56eec1..2b40692 100644
--- a/cmd/protoc-gen-go/testdata/nopackage/nopackage.pb.go
+++ b/cmd/protoc-gen-go/testdata/nopackage/nopackage.pb.go
@@ -36,9 +36,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_nopackage_nopackage_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_nopackage_nopackage_proto_enumTypes[0]
 }
@@ -49,7 +54,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum) 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/cmd/protoc-gen-go/testdata/proto2/enum.pb.go b/cmd/protoc-gen-go/testdata/proto2/enum.pb.go
index d73fa9e..c9b935f 100644
--- a/cmd/protoc-gen-go/testdata/proto2/enum.pb.go
+++ b/cmd/protoc-gen-go/testdata/proto2/enum.pb.go
@@ -42,9 +42,14 @@
 }
 
 func (x EnumType1) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumType1) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumType1) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[0]
 }
@@ -55,7 +60,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumType1) 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
 	}
@@ -94,9 +99,14 @@
 }
 
 func (x EnumType2) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumType2) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumType2) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[1]
 }
@@ -107,7 +117,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumType2) 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
 	}
@@ -145,9 +155,14 @@
 }
 
 func (x EnumContainerMessage1_NestedEnumType1A) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumContainerMessage1_NestedEnumType1A) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumContainerMessage1_NestedEnumType1A) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[2]
 }
@@ -158,7 +173,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumContainerMessage1_NestedEnumType1A) 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
 	}
@@ -194,9 +209,14 @@
 }
 
 func (x EnumContainerMessage1_NestedEnumType1B) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumContainerMessage1_NestedEnumType1B) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[3].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumContainerMessage1_NestedEnumType1B) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[3]
 }
@@ -207,7 +227,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumContainerMessage1_NestedEnumType1B) 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
 	}
@@ -245,9 +265,14 @@
 }
 
 func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[4].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[4]
 }
@@ -258,7 +283,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) 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
 	}
@@ -294,9 +319,14 @@
 }
 
 func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_enum_proto_enumTypes[5].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) Type() protoreflect.EnumType {
 	return file_proto2_enum_proto_enumTypes[5]
 }
@@ -307,7 +337,7 @@
 
 // Deprecated: Do not use.
 func (x *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) 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/cmd/protoc-gen-go/testdata/proto2/fields.pb.go b/cmd/protoc-gen-go/testdata/proto2/fields.pb.go
index 139fa80..fcd6452 100644
--- a/cmd/protoc-gen-go/testdata/proto2/fields.pb.go
+++ b/cmd/protoc-gen-go/testdata/proto2/fields.pb.go
@@ -40,9 +40,14 @@
 }
 
 func (x FieldTestMessage_Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldTestMessage_Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto2_fields_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldTestMessage_Enum) Type() protoreflect.EnumType {
 	return file_proto2_fields_proto_enumTypes[0]
 }
@@ -53,7 +58,7 @@
 
 // Deprecated: Do not use.
 func (x *FieldTestMessage_Enum) 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/cmd/protoc-gen-go/testdata/proto3/enum.pb.go b/cmd/protoc-gen-go/testdata/proto3/enum.pb.go
index 629ecd2..ca06b43 100644
--- a/cmd/protoc-gen-go/testdata/proto3/enum.pb.go
+++ b/cmd/protoc-gen-go/testdata/proto3/enum.pb.go
@@ -35,9 +35,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto3_enum_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_proto3_enum_proto_enumTypes[0]
 }
diff --git a/cmd/protoc-gen-go/testdata/proto3/fields.pb.go b/cmd/protoc-gen-go/testdata/proto3/fields.pb.go
index 321bca3..de46ea6 100644
--- a/cmd/protoc-gen-go/testdata/proto3/fields.pb.go
+++ b/cmd/protoc-gen-go/testdata/proto3/fields.pb.go
@@ -30,9 +30,14 @@
 }
 
 func (x FieldTestMessage_Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldTestMessage_Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto3_fields_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldTestMessage_Enum) Type() protoreflect.EnumType {
 	return file_proto3_fields_proto_enumTypes[0]
 }
diff --git a/encoding/bench_test.go b/encoding/bench_test.go
index 32c81d3..88f62b7 100644
--- a/encoding/bench_test.go
+++ b/encoding/bench_test.go
@@ -45,7 +45,7 @@
 	}
 
 	knownFields := m.KnownFields()
-	fieldDescs := m.Type().Fields()
+	fieldDescs := m.Descriptor().Fields()
 	for i := 0; i < fieldDescs.Len(); i++ {
 		fd := fieldDescs.Get(i)
 		num := fd.Number()
diff --git a/encoding/jsonpb/decode.go b/encoding/jsonpb/decode.go
index 0a08e4f..7e142c5 100644
--- a/encoding/jsonpb/decode.go
+++ b/encoding/jsonpb/decode.go
@@ -135,7 +135,7 @@
 func (o UnmarshalOptions) unmarshalMessage(m pref.Message, skipTypeURL bool) error {
 	var nerr errors.NonFatal
 
-	if isCustomType(m.Type().FullName()) {
+	if isCustomType(m.Descriptor().FullName()) {
 		return o.unmarshalCustomType(m)
 	}
 
@@ -160,9 +160,9 @@
 	var seenNums set.Ints
 	var seenOneofs set.Ints
 
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
-	fieldDescs := msgType.Fields()
+	fieldDescs := messageDesc.Fields()
 	xtTypes := knownFields.ExtensionTypes()
 
 Loop:
@@ -208,7 +208,9 @@
 					xtTypes.Register(xt)
 				}
 			}
-			fd = xt
+			if xt != nil {
+				fd = xt.Descriptor()
+			}
 		} else {
 			// The name can either be the JSON name or the proto field name.
 			fd = fieldDescs.ByJSONName(name)
@@ -225,13 +227,13 @@
 				}
 				continue
 			}
-			return newError("%v contains unknown field %s", msgType.FullName(), jval)
+			return newError("%v contains unknown field %s", messageDesc.FullName(), jval)
 		}
 
 		// Do not allow duplicate fields.
 		num := uint64(fd.Number())
 		if seenNums.Has(num) {
-			return newError("%v contains repeated field %s", msgType.FullName(), jval)
+			return newError("%v contains repeated field %s", messageDesc.FullName(), jval)
 		}
 		seenNums.Set(num)
 
diff --git a/encoding/jsonpb/decode_test.go b/encoding/jsonpb/decode_test.go
index 8f92209..05e619d 100644
--- a/encoding/jsonpb/decode_test.go
+++ b/encoding/jsonpb/decode_test.go
@@ -13,6 +13,7 @@
 	"github.com/golang/protobuf/v2/encoding/jsonpb"
 	"github.com/golang/protobuf/v2/encoding/testprotos/pb2"
 	"github.com/golang/protobuf/v2/encoding/testprotos/pb3"
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	"github.com/golang/protobuf/v2/internal/scalar"
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -1976,7 +1977,7 @@
 	}, {
 		desc: "Any with non-custom message",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2005,7 +2006,7 @@
 	}, {
 		desc: "Any with empty embedded message",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText:    `{"@type": "foo/pb2.Nested"}`,
@@ -2019,7 +2020,7 @@
 	}, {
 		desc: "Any with missing required error",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2038,7 +2039,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
@@ -2047,7 +2048,7 @@
 		desc: "Any with partial required and AllowPartial",
 		umo: jsonpb.UnmarshalOptions{
 			AllowPartial: true,
-			Resolver:     preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver:     preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2066,14 +2067,14 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
 	}, {
 		desc: "Any with invalid UTF8",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2097,7 +2098,7 @@
 	}, {
 		desc: "Any with BoolValue",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.BoolValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.BoolValue{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2118,7 +2119,7 @@
 	}, {
 		desc: "Any with Empty",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2131,7 +2132,7 @@
 	}, {
 		desc: "Any with missing Empty",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2141,7 +2142,7 @@
 	}, {
 		desc: "Any with StringValue containing invalid UTF8",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.StringValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2163,7 +2164,7 @@
 	}, {
 		desc: "Any with Int64Value",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Int64Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Int64Value{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2184,7 +2185,7 @@
 	}, {
 		desc: "Any with invalid Int64Value",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Int64Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Int64Value{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2195,7 +2196,7 @@
 	}, {
 		desc: "Any with invalid UInt64Value",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.UInt64Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.UInt64Value{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2206,7 +2207,7 @@
 	}, {
 		desc: "Any with Duration",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Duration{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Duration{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2227,7 +2228,7 @@
 	}, {
 		desc: "Any with Value of StringValue",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Value{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2249,7 +2250,7 @@
 	}, {
 		desc: "Any with Value of NullValue",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Value{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2271,11 +2272,11 @@
 		desc: "Any with Struct",
 		umo: jsonpb.UnmarshalOptions{
 			Resolver: preg.NewTypes(
-				(&knownpb.Struct{}).ProtoReflect().Type(),
-				(&knownpb.Value{}).ProtoReflect().Type(),
-				(&knownpb.BoolValue{}).ProtoReflect().Type(),
-				knownpb.NullValue_NULL_VALUE.Type(),
-				(&knownpb.StringValue{}).ProtoReflect().Type(),
+				pimpl.Export{}.MessageTypeOf(&knownpb.Struct{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.Value{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.BoolValue{}),
+				pimpl.Export{}.EnumTypeOf(knownpb.NullValue_NULL_VALUE),
+				pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{}),
 			),
 		},
 		inputMessage: &knownpb.Any{},
@@ -2335,8 +2336,8 @@
 		desc: "Any with duplicate @type",
 		umo: jsonpb.UnmarshalOptions{
 			Resolver: preg.NewTypes(
-				(&pb2.Nested{}).ProtoReflect().Type(),
-				(&knownpb.StringValue{}).ProtoReflect().Type(),
+				pimpl.Export{}.MessageTypeOf(&pb2.Nested{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{}),
 			),
 		},
 		inputMessage: &knownpb.Any{},
@@ -2349,7 +2350,7 @@
 	}, {
 		desc: "Any with duplicate value",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.StringValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2361,7 +2362,7 @@
 	}, {
 		desc: "Any with unknown field",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2374,9 +2375,9 @@
 		desc: "Any with embedded type containing Any",
 		umo: jsonpb.UnmarshalOptions{
 			Resolver: preg.NewTypes(
-				(&pb2.KnownTypes{}).ProtoReflect().Type(),
-				(&knownpb.Any{}).ProtoReflect().Type(),
-				(&knownpb.StringValue{}).ProtoReflect().Type(),
+				pimpl.Export{}.MessageTypeOf(&pb2.KnownTypes{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.Any{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{}),
 			),
 		},
 		inputMessage: &knownpb.Any{},
@@ -2411,7 +2412,7 @@
 	}, {
 		desc: "well known types as field values",
 		umo: jsonpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		inputMessage: &pb2.KnownTypes{},
 		inputText: `{
@@ -2566,7 +2567,7 @@
 		desc: "DiscardUnknown: Any",
 		umo: jsonpb.UnmarshalOptions{
 			DiscardUnknown: true,
-			Resolver:       preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver:       preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
@@ -2580,7 +2581,7 @@
 		desc: "DiscardUnknown: Any with Empty",
 		umo: jsonpb.UnmarshalOptions{
 			DiscardUnknown: true,
-			Resolver:       preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver:       preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `{
diff --git a/encoding/jsonpb/encode.go b/encoding/jsonpb/encode.go
index 9375091..90767ce 100644
--- a/encoding/jsonpb/encode.go
+++ b/encoding/jsonpb/encode.go
@@ -71,7 +71,7 @@
 func (o MarshalOptions) marshalMessage(m pref.Message) error {
 	var nerr errors.NonFatal
 
-	if isCustomType(m.Type().FullName()) {
+	if isCustomType(m.Descriptor().FullName()) {
 		return o.marshalCustomType(m)
 	}
 
@@ -87,7 +87,7 @@
 // marshalFields marshals the fields in the given protoreflect.Message.
 func (o MarshalOptions) marshalFields(m pref.Message) error {
 	var nerr errors.NonFatal
-	fieldDescs := m.Type().Fields()
+	fieldDescs := m.Descriptor().Fields()
 	knownFields := m.KnownFields()
 
 	// Marshal out known fields.
@@ -280,13 +280,13 @@
 	// Get a sorted list based on field key first.
 	entries := make([]xtEntry, 0, xtTypes.Len())
 	xtTypes.Range(func(xt pref.ExtensionType) bool {
-		name := xt.FullName()
+		name := xt.Descriptor().FullName()
 		// If extended type is a MessageSet, set field name to be the message type name.
 		if isMessageSetExtension(xt) {
-			name = xt.Message().FullName()
+			name = xt.Descriptor().Message().FullName()
 		}
 
-		num := xt.Number()
+		num := xt.Descriptor().Number()
 		if knownFields.Has(num) {
 			// Use [name] format for JSON field name.
 			pval := knownFields.Get(num)
@@ -313,7 +313,7 @@
 		if err := o.encoder.WriteName("[" + entry.key + "]"); !nerr.Merge(err) {
 			return err
 		}
-		if err := o.marshalValue(entry.value, entry.xtType); !nerr.Merge(err) {
+		if err := o.marshalValue(entry.value, entry.xtType.Descriptor()); !nerr.Merge(err) {
 			return err
 		}
 	}
@@ -322,16 +322,17 @@
 
 // isMessageSetExtension reports whether extension extends a message set.
 func isMessageSetExtension(xt pref.ExtensionType) bool {
-	if xt.Name() != "message_set_extension" {
+	xd := xt.Descriptor()
+	if xd.Name() != "message_set_extension" {
 		return false
 	}
-	md := xt.Message()
+	md := xd.Message()
 	if md == nil {
 		return false
 	}
-	if xt.FullName().Parent() != md.FullName() {
+	if xd.FullName().Parent() != md.FullName() {
 		return false
 	}
-	xmd, ok := xt.Extendee().(interface{ IsMessageSet() bool })
+	xmd, ok := xd.Extendee().(interface{ IsMessageSet() bool })
 	return ok && xmd.IsMessageSet()
 }
diff --git a/encoding/jsonpb/encode_test.go b/encoding/jsonpb/encode_test.go
index b0e3973..c2dd2d2 100644
--- a/encoding/jsonpb/encode_test.go
+++ b/encoding/jsonpb/encode_test.go
@@ -14,6 +14,7 @@
 	"github.com/golang/protobuf/v2/encoding/jsonpb"
 	"github.com/golang/protobuf/v2/internal/encoding/pack"
 	"github.com/golang/protobuf/v2/internal/encoding/wire"
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	"github.com/golang/protobuf/v2/internal/scalar"
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -1531,7 +1532,7 @@
 	}, {
 		desc: "Any with non-custom message",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.Nested{
@@ -1559,7 +1560,7 @@
 	}, {
 		desc: "Any with empty embedded message",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: &knownpb.Any{TypeUrl: "foo/pb2.Nested"},
 		want: `{
@@ -1573,7 +1574,7 @@
 	}, {
 		desc: "Any with missing required error",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.PartialRequired{
@@ -1587,7 +1588,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
@@ -1600,7 +1601,7 @@
 		desc: "Any with partial required and AllowPartial",
 		mo: jsonpb.MarshalOptions{
 			AllowPartial: true,
-			Resolver:     preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver:     preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.PartialRequired{
@@ -1614,7 +1615,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
@@ -1625,7 +1626,7 @@
 	}, {
 		desc: "Any with invalid UTF8",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.Nested{
@@ -1648,7 +1649,7 @@
 	}, {
 		desc: "Any with invalid value",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: &knownpb.Any{
 			TypeUrl: "foo/pb2.Nested",
@@ -1658,7 +1659,7 @@
 	}, {
 		desc: "Any with BoolValue",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.BoolValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.BoolValue{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.BoolValue{Value: true}
@@ -1678,7 +1679,7 @@
 	}, {
 		desc: "Any with Empty",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Empty{}
@@ -1698,7 +1699,7 @@
 	}, {
 		desc: "Any with StringValue containing invalid UTF8",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.StringValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.StringValue{Value: "abcd"}
@@ -1719,7 +1720,7 @@
 	}, {
 		desc: "Any with Int64Value",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Int64Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Int64Value{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Int64Value{Value: 42}
@@ -1739,7 +1740,7 @@
 	}, {
 		desc: "Any with Duration",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Duration{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Duration{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Duration{}
@@ -1759,7 +1760,7 @@
 	}, {
 		desc: "Any with empty Value",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Value{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Value{}
@@ -1776,7 +1777,7 @@
 	}, {
 		desc: "Any with Value of StringValue",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Value{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Value{Kind: &knownpb.Value_StringValue{"abcd"}}
@@ -1797,7 +1798,7 @@
 	}, {
 		desc: "Any with Value of NullValue",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Value{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Value{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.Value{Kind: &knownpb.Value_NullValue{}}
@@ -1818,11 +1819,11 @@
 		desc: "Any with Struct",
 		mo: jsonpb.MarshalOptions{
 			Resolver: preg.NewTypes(
-				(&knownpb.Struct{}).ProtoReflect().Type(),
-				(&knownpb.Value{}).ProtoReflect().Type(),
-				(&knownpb.BoolValue{}).ProtoReflect().Type(),
-				knownpb.NullValue_NULL_VALUE.Type(),
-				(&knownpb.StringValue{}).ProtoReflect().Type(),
+				pimpl.Export{}.MessageTypeOf(&knownpb.Struct{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.Value{}),
+				pimpl.Export{}.MessageTypeOf(&knownpb.BoolValue{}),
+				pimpl.Export{}.EnumTypeOf(knownpb.NullValue_NULL_VALUE),
+				pimpl.Export{}.MessageTypeOf(&knownpb.StringValue{}),
 			),
 		},
 		input: func() proto.Message {
@@ -1865,7 +1866,7 @@
 	}, {
 		desc: "Any with missing type_url",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.BoolValue{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.BoolValue{})),
 		},
 		input: func() proto.Message {
 			m := &knownpb.BoolValue{Value: true}
@@ -1881,7 +1882,7 @@
 	}, {
 		desc: "well known types as field values",
 		mo: jsonpb.MarshalOptions{
-			Resolver: preg.NewTypes((&knownpb.Empty{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&knownpb.Empty{})),
 		},
 		input: &pb2.KnownTypes{
 			OptBool:      &knownpb.BoolValue{Value: false},
diff --git a/encoding/jsonpb/well_known_types.go b/encoding/jsonpb/well_known_types.go
index a7a30c1..ddd7e97 100644
--- a/encoding/jsonpb/well_known_types.go
+++ b/encoding/jsonpb/well_known_types.go
@@ -49,7 +49,7 @@
 // JSON conversion rules. It needs to be a message type where isCustomType
 // returns true, else it will panic.
 func (o MarshalOptions) marshalCustomType(m pref.Message) error {
-	name := m.Type().FullName()
+	name := m.Descriptor().FullName()
 	switch name {
 	case "google.protobuf.Any":
 		return o.marshalAny(m)
@@ -94,7 +94,7 @@
 // special JSON conversion rules. It needs to be a message type where
 // isCustomType returns true, else it will panic.
 func (o UnmarshalOptions) unmarshalCustomType(m pref.Message) error {
-	name := m.Type().FullName()
+	name := m.Descriptor().FullName()
 	switch name {
 	case "google.protobuf.Any":
 		return o.unmarshalAny(m)
@@ -142,7 +142,7 @@
 // field `value` which holds the custom JSON in addition to the `@type` field.
 
 func (o MarshalOptions) marshalAny(m pref.Message) error {
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
 
 	// Start writing the JSON object.
@@ -155,7 +155,7 @@
 			return nil
 		} else {
 			// Return error if type_url field is not set, but value is set.
-			return errors.New("%s: type_url is not set", msgType.FullName())
+			return errors.New("%s: type_url is not set", messageDesc.FullName())
 		}
 	}
 
@@ -173,7 +173,7 @@
 	// Resolve the type in order to unmarshal value field.
 	emt, err := o.Resolver.FindMessageByURL(typeURL)
 	if !nerr.Merge(err) {
-		return errors.New("%s: unable to resolve %q: %v", msgType.FullName(), typeURL, err)
+		return errors.New("%s: unable to resolve %q: %v", messageDesc.FullName(), typeURL, err)
 	}
 
 	em := emt.New()
@@ -185,13 +185,13 @@
 		AllowPartial: o.AllowPartial,
 	}.Unmarshal(valueVal.Bytes(), em.Interface())
 	if !nerr.Merge(err) {
-		return errors.New("%s: unable to unmarshal %q: %v", msgType.FullName(), typeURL, err)
+		return errors.New("%s: unable to unmarshal %q: %v", messageDesc.FullName(), typeURL, err)
 	}
 
 	// If type of value has custom JSON encoding, marshal out a field "value"
 	// with corresponding custom JSON encoding of the embedded message as a
 	// field.
-	if isCustomType(emt.FullName()) {
+	if isCustomType(emt.Descriptor().FullName()) {
 		o.encoder.WriteName("value")
 		return o.marshalCustomType(em)
 	}
@@ -238,7 +238,7 @@
 
 	// Create new message for the embedded message type and unmarshal into it.
 	em := emt.New()
-	if isCustomType(emt.FullName()) {
+	if isCustomType(emt.Descriptor().FullName()) {
 		// If embedded message is a custom type, unmarshal the JSON "value" field
 		// into it.
 		if err := o.unmarshalAnyValue(em); !nerr.Merge(err) {
@@ -441,32 +441,23 @@
 
 // Wrapper types are encoded as JSON primitives like string, number or boolean.
 
-func (o MarshalOptions) marshalWrapperType(m pref.Message) error {
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
+// The "value" field has the same field number for all wrapper types.
+const wrapperFieldNumber = fieldnum.BoolValue_Value
 
-	// The "value" field has the same field number for all wrapper types.
-	const num = fieldnum.BoolValue_Value
-	fd := fieldDescs.ByNumber(num)
-	val := knownFields.Get(num)
+func (o MarshalOptions) marshalWrapperType(m pref.Message) error {
+	fd := m.Descriptor().Fields().ByNumber(wrapperFieldNumber)
+	val := m.KnownFields().Get(wrapperFieldNumber)
 	return o.marshalSingular(val, fd)
 }
 
 func (o UnmarshalOptions) unmarshalWrapperType(m pref.Message) error {
-	var nerr errors.NonFatal
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
-
-	// The "value" field has the same field number for all wrapper types.
-	const num = fieldnum.BoolValue_Value
-	fd := fieldDescs.ByNumber(num)
+	fd := m.Descriptor().Fields().ByNumber(wrapperFieldNumber)
 	val, err := o.unmarshalScalar(fd)
+	var nerr errors.NonFatal
 	if !nerr.Merge(err) {
 		return err
 	}
-	knownFields.Set(num, val)
+	m.KnownFields().Set(wrapperFieldNumber, val)
 	return nerr.E
 }
 
@@ -517,22 +508,14 @@
 // Struct.fields map and follows the serialization rules for a map.
 
 func (o MarshalOptions) marshalStruct(m pref.Message) error {
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
-
-	fd := fieldDescs.ByNumber(fieldnum.Struct_Fields)
-	val := knownFields.Get(fieldnum.Struct_Fields)
+	fd := m.Descriptor().Fields().ByNumber(fieldnum.Struct_Fields)
+	val := m.KnownFields().Get(fieldnum.Struct_Fields)
 	return o.marshalMap(val.Map(), fd)
 }
 
 func (o UnmarshalOptions) unmarshalStruct(m pref.Message) error {
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
-
-	fd := fieldDescs.ByNumber(fieldnum.Struct_Fields)
-	val := knownFields.Get(fieldnum.Struct_Fields)
+	fd := m.Descriptor().Fields().ByNumber(fieldnum.Struct_Fields)
+	val := m.KnownFields().Get(fieldnum.Struct_Fields)
 	return o.unmarshalMap(val.Map(), fd)
 }
 
@@ -541,22 +524,14 @@
 // repeated field.
 
 func (o MarshalOptions) marshalListValue(m pref.Message) error {
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
-
-	fd := fieldDescs.ByNumber(fieldnum.ListValue_Values)
-	val := knownFields.Get(fieldnum.ListValue_Values)
+	fd := m.Descriptor().Fields().ByNumber(fieldnum.ListValue_Values)
+	val := m.KnownFields().Get(fieldnum.ListValue_Values)
 	return o.marshalList(val.List(), fd)
 }
 
 func (o UnmarshalOptions) unmarshalListValue(m pref.Message) error {
-	msgType := m.Type()
-	fieldDescs := msgType.Fields()
-	knownFields := m.KnownFields()
-
-	fd := fieldDescs.ByNumber(fieldnum.ListValue_Values)
-	val := knownFields.Get(fieldnum.ListValue_Values)
+	fd := m.Descriptor().Fields().ByNumber(fieldnum.ListValue_Values)
+	val := m.KnownFields().Get(fieldnum.ListValue_Values)
 	return o.unmarshalList(val.List(), fd)
 }
 
@@ -565,15 +540,15 @@
 // Value message needs to be a oneof field set, else it is an error.
 
 func (o MarshalOptions) marshalKnownValue(m pref.Message) error {
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
 	num := knownFields.WhichOneof("kind")
 	if num == 0 {
 		// Return error if none of the fields is set.
-		return errors.New("%s: none of the oneof fields is set", msgType.FullName())
+		return errors.New("%s: none of the oneof fields is set", messageDesc.FullName())
 	}
 
-	fd := msgType.Fields().ByNumber(num)
+	fd := messageDesc.Fields().ByNumber(num)
 	val := knownFields.Get(num)
 	return o.marshalSingular(val, fd)
 }
@@ -669,7 +644,7 @@
 )
 
 func (o MarshalOptions) marshalDuration(m pref.Message) error {
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
 
 	secsVal := knownFields.Get(fieldnum.Duration_Seconds)
@@ -677,13 +652,13 @@
 	secs := secsVal.Int()
 	nanos := nanosVal.Int()
 	if secs < -maxSecondsInDuration || secs > maxSecondsInDuration {
-		return errors.New("%s: seconds out of range %v", msgType.FullName(), secs)
+		return errors.New("%s: seconds out of range %v", messageDesc.FullName(), secs)
 	}
 	if nanos < -secondsInNanos || nanos > secondsInNanos {
-		return errors.New("%s: nanos out of range %v", msgType.FullName(), nanos)
+		return errors.New("%s: nanos out of range %v", messageDesc.FullName(), nanos)
 	}
 	if (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) {
-		return errors.New("%s: signs of seconds and nanos do not match", msgType.FullName())
+		return errors.New("%s: signs of seconds and nanos do not match", messageDesc.FullName())
 	}
 	// Generated output always contains 0, 3, 6, or 9 fractional digits,
 	// depending on required precision, followed by the suffix "s".
@@ -712,16 +687,16 @@
 		return unexpectedJSONError{jval}
 	}
 
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	input := jval.String()
 	secs, nanos, ok := parseDuration(input)
 	if !ok {
-		return errors.New("%s: invalid duration value %q", msgType.FullName(), input)
+		return errors.New("%s: invalid duration value %q", messageDesc.FullName(), input)
 	}
 	// Validate seconds. No need to validate nanos because parseDuration would
 	// have covered that already.
 	if secs < -maxSecondsInDuration || secs > maxSecondsInDuration {
-		return errors.New("%s: out of range %q", msgType.FullName(), input)
+		return errors.New("%s: out of range %q", messageDesc.FullName(), input)
 	}
 
 	knownFields := m.KnownFields()
@@ -859,7 +834,7 @@
 )
 
 func (o MarshalOptions) marshalTimestamp(m pref.Message) error {
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
 
 	secsVal := knownFields.Get(fieldnum.Timestamp_Seconds)
@@ -867,10 +842,10 @@
 	secs := secsVal.Int()
 	nanos := nanosVal.Int()
 	if secs < minTimestampSeconds || secs > maxTimestampSeconds {
-		return errors.New("%s: seconds out of range %v", msgType.FullName(), secs)
+		return errors.New("%s: seconds out of range %v", messageDesc.FullName(), secs)
 	}
 	if nanos < 0 || nanos > secondsInNanos {
-		return errors.New("%s: nanos out of range %v", msgType.FullName(), nanos)
+		return errors.New("%s: nanos out of range %v", messageDesc.FullName(), nanos)
 	}
 	// Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3,
 	// 6 or 9 fractional digits.
@@ -893,17 +868,17 @@
 		return unexpectedJSONError{jval}
 	}
 
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	input := jval.String()
 	t, err := time.Parse(time.RFC3339Nano, input)
 	if err != nil {
-		return errors.New("%s: invalid timestamp value %q", msgType.FullName(), input)
+		return errors.New("%s: invalid timestamp value %q", messageDesc.FullName(), input)
 	}
 	// Validate seconds. No need to validate nanos because time.Parse would have
 	// covered that already.
 	secs := t.Unix()
 	if secs < minTimestampSeconds || secs > maxTimestampSeconds {
-		return errors.New("%s: out of range %q", msgType.FullName(), input)
+		return errors.New("%s: out of range %q", messageDesc.FullName(), input)
 	}
 
 	knownFields := m.KnownFields()
@@ -918,11 +893,7 @@
 // end up differently after a round-trip.
 
 func (o MarshalOptions) marshalFieldMask(m pref.Message) error {
-	msgType := m.Type()
-	knownFields := m.KnownFields()
-	name := msgType.FullName()
-
-	val := knownFields.Get(fieldnum.FieldMask_Paths)
+	val := m.KnownFields().Get(fieldnum.FieldMask_Paths)
 	list := val.List()
 	paths := make([]string, 0, list.Len())
 
@@ -931,7 +902,7 @@
 		// Return error if conversion to camelCase is not reversible.
 		cc := camelCase(s)
 		if s != snakeCase(cc) {
-			return errors.New("%s.paths contains irreversible value %q", name, s)
+			return errors.New("%s.paths contains irreversible value %q", m.Descriptor().FullName(), s)
 		}
 		paths = append(paths, cc)
 	}
@@ -955,8 +926,7 @@
 	}
 	paths := strings.Split(str, ",")
 
-	knownFields := m.KnownFields()
-	val := knownFields.Get(fieldnum.FieldMask_Paths)
+	val := m.KnownFields().Get(fieldnum.FieldMask_Paths)
 	list := val.List()
 
 	for _, s := range paths {
diff --git a/encoding/testprotos/pb2/test.pb.go b/encoding/testprotos/pb2/test.pb.go
index f5b9f40..abc47c8 100644
--- a/encoding/testprotos/pb2/test.pb.go
+++ b/encoding/testprotos/pb2/test.pb.go
@@ -43,9 +43,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_pb2_test_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_pb2_test_proto_enumTypes[0]
 }
@@ -56,7 +61,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum) 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
 	}
@@ -98,9 +103,14 @@
 }
 
 func (x Enums_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enums_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_pb2_test_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enums_NestedEnum) Type() protoreflect.EnumType {
 	return file_pb2_test_proto_enumTypes[1]
 }
@@ -111,7 +121,7 @@
 
 // Deprecated: Do not use.
 func (x *Enums_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/encoding/testprotos/pb3/test.pb.go b/encoding/testprotos/pb3/test.pb.go
index 0e0b2b3..c5892af 100644
--- a/encoding/testprotos/pb3/test.pb.go
+++ b/encoding/testprotos/pb3/test.pb.go
@@ -39,9 +39,14 @@
 }
 
 func (x Enum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum) Descriptor() protoreflect.EnumDescriptor {
+	return file_pb3_test_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum) Type() protoreflect.EnumType {
 	return file_pb3_test_proto_enumTypes[0]
 }
@@ -81,9 +86,14 @@
 }
 
 func (x Enums_NestedEnum) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enums_NestedEnum) Descriptor() protoreflect.EnumDescriptor {
+	return file_pb3_test_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enums_NestedEnum) Type() protoreflect.EnumType {
 	return file_pb3_test_proto_enumTypes[1]
 }
diff --git a/encoding/textpb/decode.go b/encoding/textpb/decode.go
index 040a0e8..38092ac 100644
--- a/encoding/textpb/decode.go
+++ b/encoding/textpb/decode.go
@@ -96,16 +96,16 @@
 func (o UnmarshalOptions) unmarshalMessage(tmsg [][2]text.Value, m pref.Message) error {
 	var nerr errors.NonFatal
 
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 	knownFields := m.KnownFields()
 
 	// Handle expanded Any message.
-	if msgType.FullName() == "google.protobuf.Any" && isExpandedAny(tmsg) {
+	if messageDesc.FullName() == "google.protobuf.Any" && isExpandedAny(tmsg) {
 		return o.unmarshalAny(tmsg[0], knownFields)
 	}
 
-	fieldDescs := msgType.Fields()
-	reservedNames := msgType.ReservedNames()
+	fieldDescs := messageDesc.Fields()
+	reservedNames := messageDesc.ReservedNames()
 	xtTypes := knownFields.ExtensionTypes()
 	var seenNums set.Ints
 	var seenOneofs set.Ints
@@ -126,7 +126,7 @@
 			}
 		case text.String:
 			// Handle extensions only. This code path is not for Any.
-			if msgType.FullName() == "google.protobuf.Any" {
+			if messageDesc.FullName() == "google.protobuf.Any" {
 				break
 			}
 			// Extensions have to be registered first in the message's
@@ -145,7 +145,9 @@
 					xtTypes.Register(xt)
 				}
 			}
-			fd = xt
+			if xt != nil {
+				fd = xt.Descriptor()
+			}
 		}
 
 		if fd == nil {
@@ -154,7 +156,7 @@
 				continue
 			}
 			// TODO: Can provide option to ignore unknown message fields.
-			return errors.New("%v contains unknown field: %v", msgType.FullName(), tkey)
+			return errors.New("%v contains unknown field: %v", messageDesc.FullName(), tkey)
 		}
 
 		if cardinality := fd.Cardinality(); cardinality == pref.Repeated {
diff --git a/encoding/textpb/decode_test.go b/encoding/textpb/decode_test.go
index ddebffa..dd166cd 100644
--- a/encoding/textpb/decode_test.go
+++ b/encoding/textpb/decode_test.go
@@ -11,6 +11,7 @@
 	protoV1 "github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/v2/encoding/textpb"
 	"github.com/golang/protobuf/v2/internal/errors"
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	"github.com/golang/protobuf/v2/internal/scalar"
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -1390,7 +1391,7 @@
 	}, {
 		desc: "Any expanded",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `
@@ -1420,7 +1421,7 @@
 	}, {
 		desc: "Any expanded with empty value",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText:    `[foo.com/pb2.Nested]: {}`,
@@ -1430,7 +1431,7 @@
 	}, {
 		desc: "Any expanded with missing required error",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `
@@ -1458,7 +1459,7 @@
 	}, {
 		desc: "Any with invalid UTF-8",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb3.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb3.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `
@@ -1476,7 +1477,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
@@ -1490,7 +1491,7 @@
 	}, {
 		desc: "Any expanded with invalid value",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText:    `[pb2.Nested]: 123`,
@@ -1498,7 +1499,7 @@
 	}, {
 		desc: "Any expanded with unknown fields",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `
@@ -1509,7 +1510,7 @@
 	}, {
 		desc: "Any contains expanded and unexpanded fields",
 		umo: textpb.UnmarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		inputMessage: &knownpb.Any{},
 		inputText: `
diff --git a/encoding/textpb/encode.go b/encoding/textpb/encode.go
index 78ed3a2..b8b2e71 100644
--- a/encoding/textpb/encode.go
+++ b/encoding/textpb/encode.go
@@ -74,10 +74,10 @@
 func (o MarshalOptions) marshalMessage(m pref.Message) (text.Value, error) {
 	var nerr errors.NonFatal
 	var msgFields [][2]text.Value
-	msgType := m.Type()
+	messageDesc := m.Descriptor()
 
 	// Handle Any expansion.
-	if msgType.FullName() == "google.protobuf.Any" {
+	if messageDesc.FullName() == "google.protobuf.Any" {
 		msg, err := o.marshalAny(m)
 		if err == nil || nerr.Merge(err) {
 			// Return as is for nil or non-fatal error.
@@ -87,7 +87,7 @@
 	}
 
 	// Handle known fields.
-	fieldDescs := msgType.Fields()
+	fieldDescs := messageDesc.Fields()
 	knownFields := m.KnownFields()
 	size := fieldDescs.Len()
 	for i := 0; i < size; i++ {
@@ -271,18 +271,18 @@
 	var nerr errors.NonFatal
 	var err error
 	xtTypes.Range(func(xt pref.ExtensionType) bool {
-		name := xt.FullName()
+		name := xt.Descriptor().FullName()
 		// If extended type is a MessageSet, set field name to be the message type name.
 		if isMessageSetExtension(xt) {
-			name = xt.Message().FullName()
+			name = xt.Descriptor().Message().FullName()
 		}
 
-		num := xt.Number()
+		num := xt.Descriptor().Number()
 		if knownFields.Has(num) {
 			// Use string type to produce [name] format.
 			tname := text.ValueOf(string(name))
 			pval := knownFields.Get(num)
-			xtFields, err = o.appendField(xtFields, tname, pval, xt)
+			xtFields, err = o.appendField(xtFields, tname, pval, xt.Descriptor())
 			if !nerr.Merge(err) {
 				return false
 			}
@@ -303,17 +303,18 @@
 
 // isMessageSetExtension reports whether extension extends a message set.
 func isMessageSetExtension(xt pref.ExtensionType) bool {
-	if xt.Name() != "message_set_extension" {
+	xd := xt.Descriptor()
+	if xd.Name() != "message_set_extension" {
 		return false
 	}
-	md := xt.Message()
+	md := xd.Message()
 	if md == nil {
 		return false
 	}
-	if xt.FullName().Parent() != md.FullName() {
+	if xd.FullName().Parent() != md.FullName() {
 		return false
 	}
-	xmd, ok := xt.Extendee().(interface{ IsMessageSet() bool })
+	xmd, ok := xd.Extendee().(interface{ IsMessageSet() bool })
 	return ok && xmd.IsMessageSet()
 }
 
diff --git a/encoding/textpb/encode_test.go b/encoding/textpb/encode_test.go
index 41cae94..e4ca966 100644
--- a/encoding/textpb/encode_test.go
+++ b/encoding/textpb/encode_test.go
@@ -15,6 +15,7 @@
 	"github.com/golang/protobuf/v2/internal/detrand"
 	"github.com/golang/protobuf/v2/internal/encoding/pack"
 	"github.com/golang/protobuf/v2/internal/encoding/wire"
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	"github.com/golang/protobuf/v2/internal/scalar"
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -1191,7 +1192,7 @@
 	}, {
 		desc: "Any expanded",
 		mo: textpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.Nested{
@@ -1219,7 +1220,7 @@
 	}, {
 		desc: "Any expanded with missing required error",
 		mo: textpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
 		},
 		input: func() proto.Message {
 			m := &pb2.PartialRequired{
@@ -1233,7 +1234,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   b,
 			}
 		}(),
@@ -1245,7 +1246,7 @@
 	}, {
 		desc: "Any with invalid UTF-8",
 		mo: textpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb3.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb3.Nested{})),
 		},
 		input: func() proto.Message {
 			m := &pb3.Nested{
@@ -1256,7 +1257,7 @@
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
 			return &knownpb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 				Value:   bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
 			}
 		}(),
@@ -1268,7 +1269,7 @@
 	}, {
 		desc: "Any with invalid value",
 		mo: textpb.MarshalOptions{
-			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+			Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
 		},
 		input: &knownpb.Any{
 			TypeUrl: "foo/pb2.Nested",
diff --git a/encoding/textpb/other_test.go b/encoding/textpb/other_test.go
index d9118a7..5b1552d 100644
--- a/encoding/textpb/other_test.go
+++ b/encoding/textpb/other_test.go
@@ -6,6 +6,7 @@
 	protoV1 "github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/v2/encoding/textpb"
 	"github.com/golang/protobuf/v2/internal/impl"
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	"github.com/golang/protobuf/v2/internal/scalar"
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -157,15 +158,15 @@
 			}
 			return &pb2.KnownTypes{
 				OptAny: &knownpb.Any{
-					TypeUrl: string(m.ProtoReflect().Type().FullName()),
+					TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 					Value:   b,
 				},
 			}
 		}(),
 	}, {
 		desc:     "Any field with registered type",
-		resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
-		message: func() proto.Message {
+		resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
+		message: func() *pb2.KnownTypes {
 			m := &pb2.Nested{
 				OptString: scalar.String("embedded inside Any"),
 				OptNested: &pb2.Nested{
@@ -178,7 +179,7 @@
 			}
 			return &pb2.KnownTypes{
 				OptAny: &knownpb.Any{
-					TypeUrl: string(m.ProtoReflect().Type().FullName()),
+					TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
 					Value:   b,
 				},
 			}
@@ -186,11 +187,11 @@
 	}, {
 		desc: "Any field containing Any message",
 		resolver: func() *preg.Types {
-			mt1 := (&pb2.Nested{}).ProtoReflect().Type()
+			mt1 := impl.Export{}.MessageTypeOf(&pb2.Nested{})
 			mt2 := impl.Export{}.MessageTypeOf(&knownpb.Any{})
 			return preg.NewTypes(mt1, mt2)
 		}(),
-		message: func() proto.Message {
+		message: func() *pb2.KnownTypes {
 			m1 := &pb2.Nested{
 				OptString: scalar.String("message inside Any of another Any field"),
 			}
@@ -219,20 +220,18 @@
 		tt := tt
 		t.Run(tt.desc, func(t *testing.T) {
 			t.Parallel()
-			mo := textpb.MarshalOptions{Resolver: tt.resolver}
-			umo := textpb.UnmarshalOptions{Resolver: tt.resolver}
-
-			b, err := mo.Marshal(tt.message)
+			b, err := textpb.MarshalOptions{Resolver: tt.resolver}.Marshal(tt.message)
 			if err != nil {
 				t.Errorf("Marshal() returned error: %v\n\n", err)
 			}
-			gotMessage := tt.message.ProtoReflect().Type().New().Interface()
-			err = umo.Unmarshal(gotMessage, b)
+
+			gotMessage := new(pb2.KnownTypes)
+			err = textpb.UnmarshalOptions{Resolver: tt.resolver}.Unmarshal(gotMessage, b)
 			if err != nil {
 				t.Errorf("Unmarshal() returned error: %v\n\n", err)
 			}
 
-			if !protoV1.Equal(gotMessage.(protoV1.Message), tt.message.(protoV1.Message)) {
+			if !protoV1.Equal(gotMessage, tt.message.(protoV1.Message)) {
 				t.Errorf("Unmarshal()\n<got>\n%v\n<want>\n%v\n", gotMessage, tt.message)
 			}
 		})
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()
+}
diff --git a/proto/decode.go b/proto/decode.go
index fa6d443..51b2068 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -70,8 +70,8 @@
 }
 
 func (o UnmarshalOptions) unmarshalMessage(b []byte, m protoreflect.Message) error {
-	messageType := m.Type()
-	fieldTypes := messageType.Fields()
+	messageDesc := m.Descriptor()
+	fieldDescs := messageDesc.Fields()
 	knownFields := m.KnownFields()
 	unknownFields := m.UnknownFields()
 	var nerr errors.NonFatal
@@ -83,31 +83,34 @@
 		}
 
 		// Parse the field value.
-		fieldType := fieldTypes.ByNumber(num)
-		if fieldType == nil {
-			fieldType = knownFields.ExtensionTypes().ByNumber(num)
-			if fieldType == nil && messageType.ExtensionRanges().Has(num) {
-				extType, err := o.Resolver.FindExtensionByNumber(messageType.FullName(), num)
+		fieldDesc := fieldDescs.ByNumber(num)
+		if fieldDesc == nil {
+			extType := knownFields.ExtensionTypes().ByNumber(num)
+			if extType == nil && messageDesc.ExtensionRanges().Has(num) {
+				var err error
+				extType, err = o.Resolver.FindExtensionByNumber(messageDesc.FullName(), num)
 				if err != nil && err != protoregistry.NotFound {
 					return err
 				}
 				if extType != nil {
 					knownFields.ExtensionTypes().Register(extType)
-					fieldType = extType
 				}
 			}
+			if extType != nil {
+				fieldDesc = extType.Descriptor()
+			}
 		}
 		var err error
 		var valLen int
 		switch {
-		case fieldType == nil:
+		case fieldDesc == nil:
 			err = errUnknown
-		case fieldType.Cardinality() != protoreflect.Repeated:
-			valLen, err = o.unmarshalScalarField(b[tagLen:], wtyp, num, knownFields, fieldType)
-		case !fieldType.IsMap():
-			valLen, err = o.unmarshalList(b[tagLen:], wtyp, num, knownFields.Get(num).List(), fieldType)
+		case fieldDesc.Cardinality() != protoreflect.Repeated:
+			valLen, err = o.unmarshalScalarField(b[tagLen:], wtyp, num, knownFields, fieldDesc)
+		case !fieldDesc.IsMap():
+			valLen, err = o.unmarshalList(b[tagLen:], wtyp, num, knownFields.Get(num).List(), fieldDesc)
 		default:
-			valLen, err = o.unmarshalMap(b[tagLen:], wtyp, num, knownFields.Get(num).Map(), fieldType)
+			valLen, err = o.unmarshalMap(b[tagLen:], wtyp, num, knownFields.Get(num).Map(), fieldDesc)
 		}
 		if err == errUnknown {
 			valLen = wire.ConsumeFieldValue(num, wtyp, b[tagLen:])
diff --git a/proto/encode.go b/proto/encode.go
index 1b48b41..255d0a3 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -125,16 +125,16 @@
 	// defined order.
 	//
 	// When using deterministic serialization, we sort the known fields by field number.
-	fields := m.Type().Fields()
+	fieldDescs := m.Descriptor().Fields()
 	knownFields := m.KnownFields()
 	var err error
 	var nerr errors.NonFatal
 	o.rangeKnown(knownFields, func(num protoreflect.FieldNumber, value protoreflect.Value) bool {
-		field := fields.ByNumber(num)
+		field := fieldDescs.ByNumber(num)
 		if field == nil {
-			field = knownFields.ExtensionTypes().ByNumber(num)
+			field = knownFields.ExtensionTypes().ByNumber(num).Descriptor()
 			if field == nil {
-				panic(fmt.Errorf("no descriptor for field %d in %q", num, m.Type().FullName()))
+				panic(fmt.Errorf("no descriptor for field %d in %q", num, m.Descriptor().FullName()))
 			}
 		}
 		b, err = o.marshalField(b, field, value)
diff --git a/proto/equal.go b/proto/equal.go
index cd31056..1b3c868 100644
--- a/proto/equal.go
+++ b/proto/equal.go
@@ -21,7 +21,7 @@
 
 // equalMessage compares two messages.
 func equalMessage(a, b pref.Message) bool {
-	mda, mdb := a.Type(), b.Type()
+	mda, mdb := a.Descriptor(), b.Descriptor()
 	if mda != mdb && mda.FullName() != mdb.FullName() {
 		return false
 	}
@@ -77,7 +77,7 @@
 		return true
 	}
 	xtypesa.Range(func(xt pref.ExtensionType) bool {
-		num := xt.Number()
+		num := xt.Descriptor().Number()
 		if xtypesb.ByNumber(num) != xt {
 			equal = false
 			return false
@@ -86,7 +86,7 @@
 		if !hasa && !hasb {
 			return true
 		}
-		if hasa != hasb || !equalFields(xt, knowna.Get(num), knownb.Get(num)) {
+		if hasa != hasb || !equalFields(xt.Descriptor(), knowna.Get(num), knownb.Get(num)) {
 			equal = false
 			return false
 		}
diff --git a/proto/isinit.go b/proto/isinit.go
index 08edbd8..199f181 100644
--- a/proto/isinit.go
+++ b/proto/isinit.go
@@ -27,7 +27,7 @@
 
 // IsInitialized returns an error if any required fields in m are not set.
 func isInitialized(m pref.Message, stack []interface{}) error {
-	md := m.Type()
+	md := m.Descriptor()
 	known := m.KnownFields()
 	fields := md.Fields()
 	for i, nums := 0, md.RequiredNumbers(); i < nums.Len(); i++ {
@@ -41,7 +41,7 @@
 	known.Range(func(num pref.FieldNumber, v pref.Value) bool {
 		field := fields.ByNumber(num)
 		if field == nil {
-			field = known.ExtensionTypes().ByNumber(num)
+			field = known.ExtensionTypes().ByNumber(num).Descriptor()
 		}
 		if field == nil {
 			panic(fmt.Errorf("no descriptor for field %d in %q", num, md.FullName()))
diff --git a/proto/size.go b/proto/size.go
index 3bc5a3c..b881653 100644
--- a/proto/size.go
+++ b/proto/size.go
@@ -34,14 +34,14 @@
 }
 
 func sizeMessage(m protoreflect.Message) (size int) {
-	fields := m.Type().Fields()
+	fieldDescs := m.Descriptor().Fields()
 	knownFields := m.KnownFields()
 	m.KnownFields().Range(func(num protoreflect.FieldNumber, value protoreflect.Value) bool {
-		field := fields.ByNumber(num)
+		field := fieldDescs.ByNumber(num)
 		if field == nil {
-			field = knownFields.ExtensionTypes().ByNumber(num)
+			field = knownFields.ExtensionTypes().ByNumber(num).Descriptor()
 			if field == nil {
-				panic(fmt.Errorf("no descriptor for field %d in %q", num, m.Type().FullName()))
+				panic(fmt.Errorf("no descriptor for field %d in %q", num, m.Descriptor().FullName()))
 			}
 		}
 		size += sizeField(field, value)
diff --git a/reflect/protodesc/protodesc.go b/reflect/protodesc/protodesc.go
index 2bf65eb..4fcd648 100644
--- a/reflect/protodesc/protodesc.go
+++ b/reflect/protodesc/protodesc.go
@@ -7,7 +7,6 @@
 package protodesc
 
 import (
-	"fmt"
 	"strings"
 
 	"github.com/golang/protobuf/v2/internal/encoding/defval"
@@ -410,14 +409,3 @@
 	}
 	return ed, nil
 }
-
-func typeName(t protoreflect.Descriptor) string {
-	switch t.(type) {
-	case protoreflect.EnumType:
-		return "enum"
-	case protoreflect.MessageType:
-		return "message"
-	default:
-		return fmt.Sprintf("%T", t)
-	}
-}
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index f01b291..490bfff 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -221,8 +221,10 @@
 }
 type isMessageDescriptor interface{ ProtoType(MessageDescriptor) }
 
-// MessageType extends a MessageDescriptor with Go specific type information.
+// MessageType encapsulates a MessageDescriptor with a concrete Go implementation.
 type MessageType interface {
+	// TODO: Remove this.
+	// Deprecated: Do not rely on these methods.
 	MessageDescriptor
 
 	// New returns a newly allocated empty message.
@@ -232,6 +234,11 @@
 	//
 	// Invariant: t.GoType() == reflect.TypeOf(t.New().Interface())
 	GoType() reflect.Type
+
+	// Descriptor returns the message descriptor.
+	//
+	// Invariant: t.Descriptor() == t.New().Descriptor()
+	Descriptor() MessageDescriptor
 }
 
 // MessageDescriptors is a list of message declarations.
@@ -382,8 +389,8 @@
 	doNotImplement
 }
 
-// ExtensionType extends a ExtensionDescriptor with Go type information.
-// The embedded field descriptor must be for a extension field.
+// ExtensionType encapsulates an ExtensionDescriptor with a concrete
+// Go implementation. The nested field descriptor must be for a extension field.
 //
 // While a normal field is a member of the parent message that it is declared
 // within (see Descriptor.Parent), an extension field is a member of some other
@@ -404,6 +411,8 @@
 // Field "bar_field" is an extension of FooMessage, but its full name is
 // "example.BarMessage.bar_field" instead of "example.FooMessage.bar_field".
 type ExtensionType interface {
+	// TODO: Remove this.
+	// Deprecated: Do not rely on these methods.
 	ExtensionDescriptor
 
 	// New returns a new value for the field.
@@ -416,6 +425,9 @@
 	//	t.GoType() == reflect.TypeOf(t.InterfaceOf(t.New()))
 	GoType() reflect.Type
 
+	// Descriptor returns the extension descriptor.
+	Descriptor() ExtensionDescriptor
+
 	// TODO: What to do with nil?
 	//	Should ValueOf(nil) return Value{}?
 	//	Should InterfaceOf(Value{}) return nil?
@@ -457,8 +469,10 @@
 }
 type isEnumDescriptor interface{ ProtoType(EnumDescriptor) }
 
-// EnumType extends a EnumDescriptor with Go specific type information.
+// EnumType encapsulates an EnumDescriptor with a concrete Go implementation.
 type EnumType interface {
+	// TODO: Remove this.
+	// Deprecated: Do not rely on these methods.
 	EnumDescriptor
 
 	// New returns an instance of this enum type with its value set to n.
@@ -468,6 +482,11 @@
 	//
 	// Invariants: t.GoType() == reflect.TypeOf(t.New(0))
 	GoType() reflect.Type
+
+	// Descriptor returns the enum descriptor.
+	//
+	// Invariant: t.Descriptor() == t.New(0).Descriptor()
+	Descriptor() EnumDescriptor
 }
 
 // EnumDescriptors is a list of enum declarations.
diff --git a/reflect/protoreflect/value.go b/reflect/protoreflect/value.go
index 6d21627..36baf85 100644
--- a/reflect/protoreflect/value.go
+++ b/reflect/protoreflect/value.go
@@ -11,6 +11,10 @@
 // Enum does not provide a mutable API since enums are commonly backed by
 // Go constants, which are not addressable.
 type Enum interface {
+	Descriptor() EnumDescriptor
+
+	// TODO: Remove this.
+	// Deprecated: Use Descriptor instead.
 	Type() EnumType
 
 	// Number returns the enum value as an integer.
@@ -24,6 +28,10 @@
 // which provide specialized, performant implementations of high-level
 // operations such as Marshal and Unmarshal.
 type Message interface {
+	Descriptor() MessageDescriptor
+
+	// TODO: Remove this.
+	// Deprecated: Use Descriptor instead.
 	Type() MessageType
 
 	// KnownFields returns an interface to access/mutate known fields.
@@ -32,6 +40,9 @@
 	// UnknownFields returns an interface to access/mutate unknown fields.
 	UnknownFields() UnknownFields
 
+	// New returns a newly allocated empty message.
+	New() Message
+
 	// Interface unwraps the message reflection interface and
 	// returns the underlying proto.Message interface.
 	Interface() ProtoMessage
diff --git a/reflect/protoregistry/registry.go b/reflect/protoregistry/registry.go
index 2f1aa62..84e276a 100644
--- a/reflect/protoregistry/registry.go
+++ b/reflect/protoregistry/registry.go
@@ -267,7 +267,6 @@
 // Type is an interface satisfied by protoreflect.EnumType,
 // protoreflect.MessageType, or protoreflect.ExtensionType.
 type Type interface {
-	protoreflect.Descriptor
 	GoType() reflect.Type
 }
 
@@ -361,7 +360,17 @@
 		switch typ.(type) {
 		case protoreflect.EnumType, protoreflect.MessageType, protoreflect.ExtensionType:
 			// Check for conflicts in typesByName.
-			name := typ.FullName()
+			var name protoreflect.FullName
+			switch t := typ.(type) {
+			case protoreflect.EnumType:
+				name = t.Descriptor().FullName()
+			case protoreflect.MessageType:
+				name = t.Descriptor().FullName()
+			case protoreflect.ExtensionType:
+				name = t.Descriptor().FullName()
+			default:
+				panic(fmt.Sprintf("invalid type: %T", t))
+			}
 			if r.typesByName[name] != nil {
 				if firstErr == nil {
 					firstErr = errors.New("%v %v is already registered", typeName(typ), name)
@@ -371,8 +380,8 @@
 
 			// Check for conflicts in extensionsByMessage.
 			if xt, _ := typ.(protoreflect.ExtensionType); xt != nil {
-				field := xt.Number()
-				message := xt.Extendee().FullName()
+				field := xt.Descriptor().Number()
+				message := xt.Descriptor().Extendee().FullName()
 				if r.extensionsByMessage[message][field] != nil {
 					if firstErr == nil {
 						firstErr = errors.New("extension %v is already registered on message %v", name, message)
diff --git a/reflect/protoregistry/registry_test.go b/reflect/protoregistry/registry_test.go
index 8fbd0e6..c20f132 100644
--- a/reflect/protoregistry/registry_test.go
+++ b/reflect/protoregistry/registry_test.go
@@ -12,6 +12,7 @@
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 
+	pimpl "github.com/golang/protobuf/v2/internal/impl"
 	ptype "github.com/golang/protobuf/v2/internal/prototype"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -302,12 +303,12 @@
 
 func TestTypes(t *testing.T) {
 	// Suffix 1 in registry, 2 in parent, 3 in resolver.
-	mt1 := (&testpb.Message1{}).ProtoReflect().Type()
-	mt2 := (&testpb.Message2{}).ProtoReflect().Type()
-	mt3 := (&testpb.Message3{}).ProtoReflect().Type()
-	et1 := testpb.Enum1_ONE.Type()
-	et2 := testpb.Enum2_UNO.Type()
-	et3 := testpb.Enum3_YI.Type()
+	mt1 := pimpl.Export{}.MessageTypeOf(&testpb.Message1{})
+	mt2 := pimpl.Export{}.MessageTypeOf(&testpb.Message2{})
+	mt3 := pimpl.Export{}.MessageTypeOf(&testpb.Message3{})
+	et1 := pimpl.Export{}.EnumTypeOf(testpb.Enum1_ONE)
+	et2 := pimpl.Export{}.EnumTypeOf(testpb.Enum2_UNO)
+	et3 := pimpl.Export{}.EnumTypeOf(testpb.Enum3_YI)
 	// Suffix indicates field number.
 	xt11 := testpb.E_StringField.Type
 	xt12 := testpb.E_EnumField.Type
@@ -582,8 +583,20 @@
 		}
 	})
 
+	fullName := func(t preg.Type) pref.FullName {
+		switch t := t.(type) {
+		case pref.EnumType:
+			return t.Descriptor().FullName()
+		case pref.MessageType:
+			return t.Descriptor().FullName()
+		case pref.ExtensionType:
+			return t.Descriptor().FullName()
+		default:
+			panic("invalid type")
+		}
+	}
 	sortTypes := cmpopts.SortSlices(func(x, y preg.Type) bool {
-		return x.FullName() < y.FullName()
+		return fullName(x) < fullName(y)
 	})
 	compare := cmp.Comparer(func(x, y preg.Type) bool {
 		return x == y
diff --git a/reflect/protoregistry/testprotos/test.pb.go b/reflect/protoregistry/testprotos/test.pb.go
index 435b8b0..c34bcc1 100644
--- a/reflect/protoregistry/testprotos/test.pb.go
+++ b/reflect/protoregistry/testprotos/test.pb.go
@@ -36,9 +36,14 @@
 }
 
 func (x Enum1) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum1) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum1) Type() protoreflect.EnumType {
 	return file_test_proto_enumTypes[0]
 }
@@ -49,7 +54,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum1) 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
 	}
@@ -85,9 +90,14 @@
 }
 
 func (x Enum2) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum2) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum2) Type() protoreflect.EnumType {
 	return file_test_proto_enumTypes[1]
 }
@@ -98,7 +108,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum2) 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
 	}
@@ -134,9 +144,14 @@
 }
 
 func (x Enum3) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Enum3) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Enum3) Type() protoreflect.EnumType {
 	return file_test_proto_enumTypes[2]
 }
@@ -147,7 +162,7 @@
 
 // Deprecated: Do not use.
 func (x *Enum3) 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/runtime/protoiface/legacy.go b/runtime/protoiface/legacy.go
index f97bf03..90edaf9 100644
--- a/runtime/protoiface/legacy.go
+++ b/runtime/protoiface/legacy.go
@@ -27,6 +27,8 @@
 	// Type is the descriptor type for the extension field using the v2 API.
 	// If populated, the information in this field takes precedence over
 	// all other fields in ExtensionDescV1.
+	//
+	// TODO: Delete this and make this whole struct implement ExtensionDescV1.
 	Type protoreflect.ExtensionType
 
 	// ExtendedType is a typed nil-pointer to the parent message type that
diff --git a/testing/prototest/prototest.go b/testing/prototest/prototest.go
index f222e2e..f3a1a9f 100644
--- a/testing/prototest/prototest.go
+++ b/testing/prototest/prototest.go
@@ -20,9 +20,9 @@
 // TestMessage runs the provided message through a series of tests
 // exercising the protobuf reflection API.
 func TestMessage(t testing.TB, message proto.Message) {
-	md := message.ProtoReflect().Type()
+	md := message.ProtoReflect().Descriptor()
 
-	m := md.New()
+	m := message.ProtoReflect().New()
 	for i := 0; i < md.Fields().Len(); i++ {
 		fd := md.Fields().Get(i)
 		switch {
@@ -67,13 +67,13 @@
 	// TODO: Extensions, unknown fields.
 
 	// Test round-trip marshal/unmarshal.
-	m1 := md.New().Interface()
+	m1 := message.ProtoReflect().New().Interface()
 	populateMessage(m1.ProtoReflect(), 1, nil)
 	b, err := proto.Marshal(m1)
 	if err != nil {
 		t.Errorf("Marshal() = %v, want nil\n%v", err, marshalText(m1))
 	}
-	m2 := md.New().Interface()
+	m2 := message.ProtoReflect().New().Interface()
 	if err := proto.Unmarshal(b, m2); err != nil {
 		t.Errorf("Unmarshal() = %v, want nil\n%v", err, marshalText(m1))
 	}
@@ -350,7 +350,7 @@
 		if err != nil {
 			return fmt.Sprintf("<%v>", err)
 		}
-		return fmt.Sprintf("%v{%v}", v.Type().FullName(), string(b))
+		return fmt.Sprintf("%v{%v}", v.Descriptor().FullName(), string(b))
 	case string:
 		return fmt.Sprintf("%q", v)
 	default:
@@ -418,11 +418,11 @@
 //
 // The stack parameter is used to avoid infinite recursion when populating circular
 // data structures.
-func newValue(m pref.Message, fd pref.FieldDescriptor, n seed, stack []pref.MessageType) pref.Value {
+func newValue(m pref.Message, fd pref.FieldDescriptor, n seed, stack []pref.MessageDescriptor) pref.Value {
 	num := fd.Number()
 	switch {
 	case fd.IsMap():
-		mapv := m.Type().New().KnownFields().Get(num).Map()
+		mapv := m.New().KnownFields().Get(num).Map()
 		if n == 0 {
 			return pref.ValueOf(mapv)
 		}
@@ -432,7 +432,7 @@
 		mapv.Set(newMapKey(fd, n), newMapValue(fd, mapv, 10*n, stack))
 		return pref.ValueOf(mapv)
 	case fd.Cardinality() == pref.Repeated:
-		list := m.Type().New().KnownFields().Get(num).List()
+		list := m.New().KnownFields().Get(num).List()
 		if n == 0 {
 			return pref.ValueOf(list)
 		}
@@ -448,7 +448,7 @@
 	}
 }
 
-func newListElement(fd pref.FieldDescriptor, list pref.List, n seed, stack []pref.MessageType) pref.Value {
+func newListElement(fd pref.FieldDescriptor, list pref.List, n seed, stack []pref.MessageDescriptor) pref.Value {
 	if fd.Message() == nil {
 		return newScalarValue(fd, n)
 	}
@@ -460,7 +460,7 @@
 	return newScalarValue(kd, n).MapKey()
 }
 
-func newMapValue(fd pref.FieldDescriptor, mapv pref.Map, n seed, stack []pref.MessageType) pref.Value {
+func newMapValue(fd pref.FieldDescriptor, mapv pref.Map, n seed, stack []pref.MessageDescriptor) pref.Value {
 	vd := fd.Message().Fields().ByNumber(2)
 	if vd.Message() == nil {
 		return newScalarValue(vd, n)
@@ -545,11 +545,11 @@
 	panic("unhandled kind")
 }
 
-func populateMessage(m pref.Message, n seed, stack []pref.MessageType) pref.Value {
+func populateMessage(m pref.Message, n seed, stack []pref.MessageDescriptor) pref.Value {
 	if n == 0 {
 		return pref.ValueOf(m)
 	}
-	md := m.Type()
+	md := m.Descriptor()
 	for _, x := range stack {
 		if md == x {
 			return pref.ValueOf(m)
diff --git a/types/descriptor/descriptor.pb.go b/types/descriptor/descriptor.pb.go
index bcddc8e..38d10fd 100644
--- a/types/descriptor/descriptor.pb.go
+++ b/types/descriptor/descriptor.pb.go
@@ -98,9 +98,14 @@
 }
 
 func (x FieldDescriptorProto_Type) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldDescriptorProto_Type) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldDescriptorProto_Type) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[0]
 }
@@ -111,7 +116,7 @@
 
 // Deprecated: Do not use.
 func (x *FieldDescriptorProto_Type) 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
 	}
@@ -154,9 +159,14 @@
 }
 
 func (x FieldDescriptorProto_Label) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldDescriptorProto_Label) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldDescriptorProto_Label) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[1]
 }
@@ -167,7 +177,7 @@
 
 // Deprecated: Do not use.
 func (x *FieldDescriptorProto_Label) 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
 	}
@@ -211,9 +221,14 @@
 }
 
 func (x FileOptions_OptimizeMode) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FileOptions_OptimizeMode) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FileOptions_OptimizeMode) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[2]
 }
@@ -224,7 +239,7 @@
 
 // Deprecated: Do not use.
 func (x *FileOptions_OptimizeMode) 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
 	}
@@ -267,9 +282,14 @@
 }
 
 func (x FieldOptions_CType) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldOptions_CType) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[3].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldOptions_CType) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[3]
 }
@@ -280,7 +300,7 @@
 
 // Deprecated: Do not use.
 func (x *FieldOptions_CType) 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
 	}
@@ -325,9 +345,14 @@
 }
 
 func (x FieldOptions_JSType) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (FieldOptions_JSType) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[4].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (FieldOptions_JSType) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[4]
 }
@@ -338,7 +363,7 @@
 
 // Deprecated: Do not use.
 func (x *FieldOptions_JSType) 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
 	}
@@ -383,9 +408,14 @@
 }
 
 func (x MethodOptions_IdempotencyLevel) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (MethodOptions_IdempotencyLevel) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_descriptor_proto_enumTypes[5].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (MethodOptions_IdempotencyLevel) Type() protoreflect.EnumType {
 	return file_google_protobuf_descriptor_proto_enumTypes[5]
 }
@@ -396,7 +426,7 @@
 
 // Deprecated: Do not use.
 func (x *MethodOptions_IdempotencyLevel) 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/types/known/struct.pb.go b/types/known/struct.pb.go
index f5c876e..a2148c6 100644
--- a/types/known/struct.pb.go
+++ b/types/known/struct.pb.go
@@ -35,9 +35,14 @@
 }
 
 func (x NullValue) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (NullValue) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_struct_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (NullValue) Type() protoreflect.EnumType {
 	return file_google_protobuf_struct_proto_enumTypes[0]
 }
diff --git a/types/known/type.pb.go b/types/known/type.pb.go
index b3b1ba2..98bba0c 100644
--- a/types/known/type.pb.go
+++ b/types/known/type.pb.go
@@ -36,9 +36,14 @@
 }
 
 func (x Syntax) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Syntax) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_type_proto_enumTypes[0].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Syntax) Type() protoreflect.EnumType {
 	return file_google_protobuf_type_proto_enumTypes[0]
 }
@@ -143,9 +148,14 @@
 }
 
 func (x Field_Kind) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Field_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_type_proto_enumTypes[1].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Field_Kind) Type() protoreflect.EnumType {
 	return file_google_protobuf_type_proto_enumTypes[1]
 }
@@ -190,9 +200,14 @@
 }
 
 func (x Field_Cardinality) String() string {
-	return protoimpl.X.EnumStringOf(x.Type(), protoreflect.EnumNumber(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Field_Cardinality) Descriptor() protoreflect.EnumDescriptor {
+	return file_google_protobuf_type_proto_enumTypes[2].Descriptor()
+}
+
+// Deprecated: Use Descriptor instead.
 func (Field_Cardinality) Type() protoreflect.EnumType {
 	return file_google_protobuf_type_proto_enumTypes[2]
 }