reflect/protoregistry: add Types registry

The first commit of protoregistry only added a registry for files.
However, a separate type of registry is needed to provide a mapping between
protobuf names and actual Go types representing those names.

Additional high-level API:
    var GlobalTypes = new(Types)
    type Type interface{ ... }
    type Types struct{ ... }
        func NewTypes(...Type) *Types
        func (*Types) Register(...Type) error
        func (*Types) FindEnumByName(pref.FullName) (pref.EnumType, error)
        func (*Types) FindMessageByName(pref.FullName) (pref.MessageType, error)
        func (*Types) FindMessageByURL(string) (pref.MessageType, error)
        func (*Types) FindExtensionByName(pref.FullName) (pref.ExtensionType, error)
        func (*Types) FindExtensionByNumber(pref.FullName, pref.FieldNumber) (pref.ExtensionType, error)
        func (*Types) RangeEnums(func(pref.EnumType) bool)
        func (*Types) RangeMessages(func(pref.MessageType) bool)
        func (*Types) RangeExtensions(func(pref.ExtensionType) bool)
        func (*Types) RangeExtensionsByMessage(pref.FullName, func(pref.ExtensionType) bool)

Change-Id: I0d07705801684a1eb5853bcd05fcce12598a0047
Reviewed-on: https://go-review.googlesource.com/c/131345
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/reflect/protoregistry/registry.go b/reflect/protoregistry/registry.go
index e190bda..5524452 100644
--- a/reflect/protoregistry/registry.go
+++ b/reflect/protoregistry/registry.go
@@ -4,9 +4,20 @@
 
 // Package protoregistry provides data structures to register and lookup
 // protobuf descriptor types.
+//
+// The Files registry contains file descriptors and provides the ability
+// to iterate over the files or lookup a specific descriptor within the files.
+// Files only contains protobuf descriptors and has no understanding of Go
+// type information that may be associated with each descriptor.
+//
+// The Types registry contains descriptor types for which there is a known
+// Go type associated with that descriptor. It provides the ability to iterate
+// over the registered types or lookup a type by name.
 package protoregistry
 
 import (
+	"fmt"
+	"reflect"
 	"sort"
 	"strings"
 
@@ -19,33 +30,12 @@
 // registration issues. This presumes that we provide a way to disable automatic
 // registration in generated code.
 
-// TODO: Add a type registry:
-/*
-var GlobalTypes = new(Types)
-
-type Type interface {
-	protoreflect.Descriptor
-	GoType() reflect.Type
-}
-type Types struct {
-	Parent   *Types
-	Resolver func(url string) (Type, error)
-}
-func NewTypes(typs ...Type) *Types
-func (*Types) Register(typs ...Type) error
-func (*Types) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error)
-func (*Types) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error)
-func (*Types) FindMessageByURL(url string) (protoreflect.MessageType, error)
-func (*Types) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
-func (*Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
-func (*Types) RangeEnums(f func(protoreflect.EnumType) bool)
-func (*Types) RangeMessages(f func(protoreflect.MessageType) bool)
-func (*Types) RangeExtensions(f func(protoreflect.ExtensionType) bool)
-func (*Types) RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
-*/
-
 // GlobalFiles is a global registry of file descriptors.
-var GlobalFiles = new(Files)
+var GlobalFiles *Files = new(Files)
+
+// GlobalTypes is the registry used by default for type lookups
+// unless a local registry is provided by the user.
+var GlobalTypes *Types = new(Types)
 
 // NotFound is a sentinel error value to indicate that the type was not found.
 var NotFound = errors.New("not found")
@@ -298,3 +288,337 @@
 		f(fd.Services().Get(i).Name())
 	}
 }
+
+// Type is an interface satisfied by protoreflect.EnumType,
+// protoreflect.MessageType, or protoreflect.ExtensionType.
+type Type interface {
+	protoreflect.Descriptor
+	GoType() reflect.Type
+}
+
+var (
+	_ Type = protoreflect.EnumType(nil)
+	_ Type = protoreflect.MessageType(nil)
+	_ Type = protoreflect.ExtensionType(nil)
+)
+
+// Types is a registry for looking up or iterating over descriptor types.
+// The Find and Range methods are safe for concurrent use.
+type Types struct {
+	// Parent sets the parent registry to consult if a find operation
+	// could not locate the appropriate entry.
+	//
+	// Setting a parent results in each Range operation also iterating over the
+	// entries contained within the parent. In such a case, it is possible for
+	// Range to emit duplicates (since they may exist in both child and parent).
+	// Range iteration is guaranteed to iterate over local entries before
+	// iterating over parent entries.
+	Parent *Types
+
+	// Resolver sets the local resolver to consult if the local registry does
+	// not contain an entry. The resolver takes precedence over the parent.
+	//
+	// The url is a URL where the full name of the type is the last segment
+	// of the path (i.e. string following the last '/' character).
+	// When missing a '/' character, the URL is the full name of the type.
+	// See documentation on the google.protobuf.Any.type_url field for details.
+	//
+	// If the resolver returns a result, it is not automatically registered
+	// into the local registry. Thus, a resolver function should cache results
+	// such that it deterministically returns the same result given the
+	// same URL assuming the error returned is nil or NotFound.
+	//
+	// If the resolver returns the NotFound error, the registry will consult the
+	// parent registry if it is set.
+	//
+	// Setting a resolver has no effect on the result of each Range operation.
+	Resolver func(url string) (Type, error)
+
+	// TODO: The syntax of the URL is ill-defined and the protobuf team recently
+	// changed the documented semantics in a way that breaks prior usages.
+	// I do not believe they can do this and need to sync up with the
+	// protobuf team again to hash out what the proper syntax of the URL is.
+
+	// TODO: Should we separate this out as a registry for each type?
+	//
+	// In Java, the extension and message registry are distinct classes.
+	// Their extension registry has knowledge of distinct Java types,
+	// while their message registry only contains descriptor information.
+	//
+	// In Go, we have always registered messages, enums, and extensions.
+	// Messages and extensions are registered with Go information, while enums
+	// are only registered with descriptor information. We cannot drop Go type
+	// information for messages otherwise we would be unable to implement
+	// portions of the v1 API such as ptypes.DynamicAny.
+	//
+	// There is no enum registry in Java. In v1, we used the enum registry
+	// because enum types provided no reflective methods. The addition of
+	// ProtoReflect removes that need.
+
+	typesByName         typesByName
+	extensionsByMessage extensionsByMessage
+}
+
+type (
+	typesByName         map[protoreflect.FullName]Type
+	extensionsByMessage map[protoreflect.FullName]extensionsByNumber
+	extensionsByNumber  map[protoreflect.FieldNumber]protoreflect.ExtensionType
+)
+
+// NewTypes returns a registry initialized with the provided set of types.
+// If there are conflicts, the first one takes precedence.
+func NewTypes(typs ...Type) *Types {
+	// TODO: Allow setting resolver and parent via constructor?
+	r := new(Types)
+	r.Register(typs...) // ignore errors; first takes precedence
+	return r
+}
+
+// Register registers the provided list of descriptor types.
+//
+// If a registration conflict occurs for enum, message, or extension types
+// (e.g., two different types have the same full name),
+// then the first type takes precedence and an error is returned.
+func (r *Types) Register(typs ...Type) error {
+	var firstErr error
+typeLoop:
+	for _, typ := range typs {
+		switch typ.(type) {
+		case protoreflect.EnumType, protoreflect.MessageType, protoreflect.ExtensionType:
+			// Check for conflicts in typesByName.
+			name := typ.FullName()
+			if r.typesByName[name] != nil {
+				if firstErr == nil {
+					firstErr = errors.New("%v %v is already registered", typeName(typ), name)
+				}
+				continue typeLoop
+			}
+
+			// Check for conflicts in extensionsByMessage.
+			if xt, _ := typ.(protoreflect.ExtensionType); xt != nil {
+				field := xt.Number()
+				message := xt.ExtendedType().FullName()
+				if r.extensionsByMessage[message][field] != nil {
+					if firstErr == nil {
+						firstErr = errors.New("extension %v is already registered on message %v", name, message)
+					}
+					continue typeLoop
+				}
+
+				// Update extensionsByMessage.
+				if r.extensionsByMessage == nil {
+					r.extensionsByMessage = make(extensionsByMessage)
+				}
+				if r.extensionsByMessage[message] == nil {
+					r.extensionsByMessage[message] = make(extensionsByNumber)
+				}
+				r.extensionsByMessage[message][field] = xt
+			}
+
+			// Update typesByName.
+			if r.typesByName == nil {
+				r.typesByName = make(typesByName)
+			}
+			r.typesByName[name] = typ
+		default:
+			if firstErr == nil {
+				firstErr = errors.New("invalid type: %v", typeName(typ))
+			}
+		}
+	}
+	return firstErr
+}
+
+// FindEnumByName looks up an enum by its full name.
+// E.g., "google.protobuf.Field.Kind".
+//
+// This returns (nil, NotFound) if not found.
+func (r *Types) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error) {
+	r.globalCheck()
+	if r == nil {
+		return nil, NotFound
+	}
+	v, _ := r.typesByName[enum]
+	if v == nil && r.Resolver != nil {
+		var err error
+		v, err = r.Resolver(string(enum))
+		if err != nil && err != NotFound {
+			return nil, err
+		}
+	}
+	if v != nil {
+		if et, _ := v.(protoreflect.EnumType); et != nil {
+			return et, nil
+		}
+		return nil, errors.New("found wrong type: got %v, want enum", typeName(v))
+	}
+	return r.Parent.FindEnumByName(enum)
+}
+
+// FindMessageByName looks up a message by its full name.
+// E.g., "google.protobuf.Any"
+//
+// This return (nil, NotFound) if not found.
+func (r *Types) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
+	// The full name by itself is a valid URL.
+	return r.FindMessageByURL(string(message))
+}
+
+// FindMessageByURL looks up a message by a URL identifier.
+// See Resolver for the format of the URL.
+//
+// This returns (nil, NotFound) if not found.
+func (r *Types) FindMessageByURL(url string) (protoreflect.MessageType, error) {
+	r.globalCheck()
+	if r == nil {
+		return nil, NotFound
+	}
+	message := protoreflect.FullName(url)
+	if i := strings.LastIndexByte(url, '/'); i >= 0 {
+		message = message[i+len("/"):]
+	}
+
+	v, _ := r.typesByName[message]
+	if v == nil && r.Resolver != nil {
+		var err error
+		v, err = r.Resolver(url)
+		if err != nil && err != NotFound {
+			return nil, err
+		}
+	}
+	if v != nil {
+		if mt, _ := v.(protoreflect.MessageType); mt != nil {
+			return mt, nil
+		}
+		return nil, errors.New("found wrong type: got %v, want message", typeName(v))
+	}
+	return r.Parent.FindMessageByURL(url)
+}
+
+// FindExtensionByName looks up a extension field by the field's full name.
+// Note that this is the full name of the field as determined by
+// where the extension is declared and is unrelated to the full name of the
+// message being extended.
+//
+// This returns (nil, NotFound) if not found.
+func (r *Types) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
+	r.globalCheck()
+	if r == nil {
+		return nil, NotFound
+	}
+	v, _ := r.typesByName[field]
+	if v == nil && r.Resolver != nil {
+		var err error
+		v, err = r.Resolver(string(field))
+		if err != nil && err != NotFound {
+			return nil, err
+		}
+	}
+	if v != nil {
+		if xt, _ := v.(protoreflect.ExtensionType); xt != nil {
+			return xt, nil
+		}
+		return nil, errors.New("found wrong type: got %v, want extension", typeName(v))
+	}
+	return r.Parent.FindExtensionByName(field)
+}
+
+// FindExtensionByNumber looks up a extension field by the field number
+// within some parent message, identified by full name.
+//
+// This returns (nil, NotFound) if not found.
+func (r *Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
+	r.globalCheck()
+	if r == nil {
+		return nil, NotFound
+	}
+	if xt, ok := r.extensionsByMessage[message][field]; ok {
+		return xt, nil
+	}
+	return r.Parent.FindExtensionByNumber(message, field)
+}
+
+// RangeEnums iterates over all registered enums.
+// Iteration order is undefined.
+func (r *Types) RangeEnums(f func(protoreflect.EnumType) bool) {
+	r.globalCheck()
+	if r == nil {
+		return
+	}
+	for _, typ := range r.typesByName {
+		if et, ok := typ.(protoreflect.EnumType); ok {
+			if !f(et) {
+				return
+			}
+		}
+	}
+	r.Parent.RangeEnums(f)
+}
+
+// RangeMessages iterates over all registered messages.
+// Iteration order is undefined.
+func (r *Types) RangeMessages(f func(protoreflect.MessageType) bool) {
+	r.globalCheck()
+	if r == nil {
+		return
+	}
+	for _, typ := range r.typesByName {
+		if mt, ok := typ.(protoreflect.MessageType); ok {
+			if !f(mt) {
+				return
+			}
+		}
+	}
+	r.Parent.RangeMessages(f)
+}
+
+// RangeExtensions iterates over all registered extensions.
+// Iteration order is undefined.
+func (r *Types) RangeExtensions(f func(protoreflect.ExtensionType) bool) {
+	r.globalCheck()
+	if r == nil {
+		return
+	}
+	for _, typ := range r.typesByName {
+		if xt, ok := typ.(protoreflect.ExtensionType); ok {
+			if !f(xt) {
+				return
+			}
+		}
+	}
+	r.Parent.RangeExtensions(f)
+}
+
+// RangeExtensionsByMessage iterates over all registered extensions filtered
+// by a given message type. Iteration order is undefined.
+func (r *Types) RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool) {
+	r.globalCheck()
+	if r == nil {
+		return
+	}
+	for _, xt := range r.extensionsByMessage[message] {
+		if !f(xt) {
+			return
+		}
+	}
+	r.Parent.RangeExtensionsByMessage(message, f)
+}
+
+func (r *Types) globalCheck() {
+	if r == GlobalTypes && (r.Parent != nil || r.Resolver != nil) {
+		panic("GlobalTypes.Parent and GlobalTypes.Resolver cannot be set")
+	}
+}
+
+func typeName(t Type) string {
+	switch t.(type) {
+	case protoreflect.EnumType:
+		return "enum"
+	case protoreflect.MessageType:
+		return "message"
+	case protoreflect.ExtensionType:
+		return "extension"
+	default:
+		return fmt.Sprintf("%T", t)
+	}
+}
diff --git a/reflect/protoregistry/registry_test.go b/reflect/protoregistry/registry_test.go
index 73a73a0..5be19b1 100644
--- a/reflect/protoregistry/registry_test.go
+++ b/reflect/protoregistry/registry_test.go
@@ -12,9 +12,13 @@
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 
+	"github.com/golang/protobuf/protoapi"
+	"github.com/golang/protobuf/v2/internal/legacy"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
 	ptype "github.com/golang/protobuf/v2/reflect/prototype"
+
+	testpb "github.com/golang/protobuf/v2/reflect/protoregistry/testprotos"
 )
 
 func TestFiles(t *testing.T) {
@@ -312,3 +316,357 @@
 		})
 	}
 }
+
+func extensionType(xd *protoapi.ExtensionDesc) pref.ExtensionType {
+	return legacy.Export{}.ExtensionTypeFromDesc(xd)
+}
+
+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.ProtoReflect().Type()
+	et2 := testpb.Enum2_UNO.ProtoReflect().Type()
+	et3 := testpb.Enum3_YI.ProtoReflect().Type()
+	// Suffix indicates field number.
+	xt11 := extensionType(testpb.E_StringField)
+	xt12 := extensionType(testpb.E_EnumField)
+	xt13 := extensionType(testpb.E_MessageField)
+	xt21 := extensionType(testpb.E_Message4_MessageField)
+	xt22 := extensionType(testpb.E_Message4_EnumField)
+	xt23 := extensionType(testpb.E_Message4_StringField)
+	parent := &preg.Types{}
+	if err := parent.Register(mt2, et2, xt12, xt22); err != nil {
+		t.Fatalf("parent.Register() returns unexpected error: %v", err)
+	}
+	registry := &preg.Types{
+		Parent: parent,
+		Resolver: func(url string) (preg.Type, error) {
+			switch {
+			case strings.HasSuffix(url, "testprotos.Message3"):
+				return mt3, nil
+			case strings.HasSuffix(url, "testprotos.Enum3"):
+				return et3, nil
+			case strings.HasSuffix(url, "testprotos.message_field"):
+				return xt13, nil
+			case strings.HasSuffix(url, "testprotos.Message4.string_field"):
+				return xt23, nil
+			}
+			return nil, preg.NotFound
+		},
+	}
+	if err := registry.Register(mt1, et1, xt11, xt21); err != nil {
+		t.Fatalf("registry.Register() returns unexpected error: %v", err)
+	}
+
+	t.Run("FindMessageByName", func(t *testing.T) {
+		tests := []struct {
+			name         string
+			messageType  pref.MessageType
+			wantErr      bool
+			wantNotFound bool
+		}{{
+			name:        "testprotos.Message1",
+			messageType: mt1,
+		}, {
+			name:        "testprotos.Message2",
+			messageType: mt2,
+		}, {
+			name:        "testprotos.Message3",
+			messageType: mt3,
+		}, {
+			name:         "testprotos.NoSuchMessage",
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			name:    "testprotos.Enum1",
+			wantErr: true,
+		}, {
+			name:    "testprotos.Enum2",
+			wantErr: true,
+		}, {
+			name:    "testprotos.Enum3",
+			wantErr: true,
+		}}
+		for _, tc := range tests {
+			got, err := registry.FindMessageByName(pref.FullName(tc.name))
+			gotErr := err != nil
+			if gotErr != tc.wantErr {
+				t.Errorf("FindMessageByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr)
+				continue
+			}
+			if tc.wantNotFound && err != preg.NotFound {
+				t.Errorf("FindMessageByName(%v) got error: %v, want NotFound error", tc.name, err)
+				continue
+			}
+			if got != tc.messageType {
+				t.Errorf("FindMessageByName(%v) got wrong value: %v", tc.name, got)
+			}
+		}
+	})
+
+	t.Run("FindMessageByURL", func(t *testing.T) {
+		tests := []struct {
+			name         string
+			messageType  pref.MessageType
+			wantErr      bool
+			wantNotFound bool
+		}{{
+			name:        "testprotos.Message1",
+			messageType: mt1,
+		}, {
+			name:        "foo.com/testprotos.Message2",
+			messageType: mt2,
+		}, {
+			name:        "/testprotos.Message3",
+			messageType: mt3,
+		}, {
+			name:         "type.googleapis.com/testprotos.Nada",
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			name:    "testprotos.Enum1",
+			wantErr: true,
+		}}
+		for _, tc := range tests {
+			got, err := registry.FindMessageByURL(tc.name)
+			gotErr := err != nil
+			if gotErr != tc.wantErr {
+				t.Errorf("FindMessageByURL(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr)
+				continue
+			}
+			if tc.wantNotFound && err != preg.NotFound {
+				t.Errorf("FindMessageByURL(%v) got error: %v, want NotFound error", tc.name, err)
+				continue
+			}
+			if got != tc.messageType {
+				t.Errorf("FindMessageByURL(%v) got wrong value: %v", tc.name, got)
+			}
+		}
+	})
+
+	t.Run("FindEnumByName", func(t *testing.T) {
+		tests := []struct {
+			name         string
+			enumType     pref.EnumType
+			wantErr      bool
+			wantNotFound bool
+		}{{
+			name:     "testprotos.Enum1",
+			enumType: et1,
+		}, {
+			name:     "testprotos.Enum2",
+			enumType: et2,
+		}, {
+			name:     "testprotos.Enum3",
+			enumType: et3,
+		}, {
+			name:         "testprotos.None",
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			name:    "testprotos.Message1",
+			wantErr: true,
+		}}
+		for _, tc := range tests {
+			got, err := registry.FindEnumByName(pref.FullName(tc.name))
+			gotErr := err != nil
+			if gotErr != tc.wantErr {
+				t.Errorf("FindEnumByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr)
+				continue
+			}
+			if tc.wantNotFound && err != preg.NotFound {
+				t.Errorf("FindEnumByName(%v) got error: %v, want NotFound error", tc.name, err)
+				continue
+			}
+			if got != tc.enumType {
+				t.Errorf("FindEnumByName(%v) got wrong value: %v", tc.name, got)
+			}
+		}
+	})
+
+	t.Run("FindExtensionByName", func(t *testing.T) {
+		tests := []struct {
+			name          string
+			extensionType pref.ExtensionType
+			wantErr       bool
+			wantNotFound  bool
+		}{{
+			name:          "testprotos.string_field",
+			extensionType: xt11,
+		}, {
+			name:          "testprotos.enum_field",
+			extensionType: xt12,
+		}, {
+			name:          "testprotos.message_field",
+			extensionType: xt13,
+		}, {
+			name:          "testprotos.Message4.message_field",
+			extensionType: xt21,
+		}, {
+			name:          "testprotos.Message4.enum_field",
+			extensionType: xt22,
+		}, {
+			name:          "testprotos.Message4.string_field",
+			extensionType: xt23,
+		}, {
+			name:         "testprotos.None",
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			name:    "testprotos.Message1",
+			wantErr: true,
+		}}
+		for _, tc := range tests {
+			got, err := registry.FindExtensionByName(pref.FullName(tc.name))
+			gotErr := err != nil
+			if gotErr != tc.wantErr {
+				t.Errorf("FindExtensionByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr)
+				continue
+			}
+			if tc.wantNotFound && err != preg.NotFound {
+				t.Errorf("FindExtensionByName(%v) got error: %v, want NotFound error", tc.name, err)
+				continue
+			}
+			if got != tc.extensionType {
+				t.Errorf("FindExtensionByName(%v) got wrong value: %v", tc.name, got)
+			}
+		}
+	})
+
+	t.Run("FindExtensionByNumber", func(t *testing.T) {
+		tests := []struct {
+			parent        string
+			number        int32
+			extensionType pref.ExtensionType
+			wantErr       bool
+			wantNotFound  bool
+		}{{
+			parent:        "testprotos.Message1",
+			number:        11,
+			extensionType: xt11,
+		}, {
+			parent:        "testprotos.Message1",
+			number:        12,
+			extensionType: xt12,
+		}, {
+			// FindExtensionByNumber does not use Resolver.
+			parent:       "testprotos.Message1",
+			number:       13,
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			parent:        "testprotos.Message1",
+			number:        21,
+			extensionType: xt21,
+		}, {
+			parent:        "testprotos.Message1",
+			number:        22,
+			extensionType: xt22,
+		}, {
+			// FindExtensionByNumber does not use Resolver.
+			parent:       "testprotos.Message1",
+			number:       23,
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			parent:       "testprotos.NoSuchMessage",
+			number:       11,
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			parent:       "testprotos.Message1",
+			number:       30,
+			wantErr:      true,
+			wantNotFound: true,
+		}, {
+			parent:       "testprotos.Message1",
+			number:       99,
+			wantErr:      true,
+			wantNotFound: true,
+		}}
+		for _, tc := range tests {
+			got, err := registry.FindExtensionByNumber(pref.FullName(tc.parent), pref.FieldNumber(tc.number))
+			gotErr := err != nil
+			if gotErr != tc.wantErr {
+				t.Errorf("FindExtensionByNumber(%v, %d) = (_, %v), want error? %t", tc.parent, tc.number, err, tc.wantErr)
+				continue
+			}
+			if tc.wantNotFound && err != preg.NotFound {
+				t.Errorf("FindExtensionByNumber(%v, %d) got error %v, want NotFound error", tc.parent, tc.number, err)
+				continue
+			}
+			if got != tc.extensionType {
+				t.Errorf("FindExtensionByNumber(%v, %d) got wrong value: %v", tc.parent, tc.number, got)
+			}
+		}
+	})
+
+	sortTypes := cmpopts.SortSlices(func(x, y preg.Type) bool {
+		return x.FullName() < y.FullName()
+	})
+	compare := cmp.Comparer(func(x, y preg.Type) bool {
+		return x == y
+	})
+
+	t.Run("RangeMessages", func(t *testing.T) {
+		// RangeMessages do not include messages from Resolver.
+		want := []preg.Type{mt1, mt2}
+		var got []preg.Type
+		registry.RangeMessages(func(mt pref.MessageType) bool {
+			got = append(got, mt)
+			return true
+		})
+
+		diff := cmp.Diff(want, got, sortTypes, compare)
+		if diff != "" {
+			t.Errorf("RangeMessages() mismatch (-want +got):\n%v", diff)
+		}
+	})
+
+	t.Run("RangeEnums", func(t *testing.T) {
+		// RangeEnums do not include enums from Resolver.
+		want := []preg.Type{et1, et2}
+		var got []preg.Type
+		registry.RangeEnums(func(et pref.EnumType) bool {
+			got = append(got, et)
+			return true
+		})
+
+		diff := cmp.Diff(want, got, sortTypes, compare)
+		if diff != "" {
+			t.Errorf("RangeEnums() mismatch (-want +got):\n%v", diff)
+		}
+	})
+
+	t.Run("RangeExtensions", func(t *testing.T) {
+		// RangeExtensions do not include messages from Resolver.
+		want := []preg.Type{xt11, xt12, xt21, xt22}
+		var got []preg.Type
+		registry.RangeExtensions(func(xt pref.ExtensionType) bool {
+			got = append(got, xt)
+			return true
+		})
+
+		diff := cmp.Diff(want, got, sortTypes, compare)
+		if diff != "" {
+			t.Errorf("RangeExtensions() mismatch (-want +got):\n%v", diff)
+		}
+	})
+
+	t.Run("RangeExtensionsByMessage", func(t *testing.T) {
+		// RangeExtensions do not include messages from Resolver.
+		want := []preg.Type{xt11, xt12, xt21, xt22}
+		var got []preg.Type
+		registry.RangeExtensionsByMessage(pref.FullName("testprotos.Message1"), func(xt pref.ExtensionType) bool {
+			got = append(got, xt)
+			return true
+		})
+
+		diff := cmp.Diff(want, got, sortTypes, compare)
+		if diff != "" {
+			t.Errorf("RangeExtensionsByMessage() mismatch (-want +got):\n%v", diff)
+		}
+	})
+}
diff --git a/reflect/protoregistry/testprotos/test.pb.go b/reflect/protoregistry/testprotos/test.pb.go
new file mode 100644
index 0000000..94362b3
--- /dev/null
+++ b/reflect/protoregistry/testprotos/test.pb.go
@@ -0,0 +1,582 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: reflect/protoregistry/testprotos/test.proto
+
+package testprotos
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	protoreflect "github.com/golang/protobuf/v2/reflect/protoreflect"
+	prototype "github.com/golang/protobuf/v2/reflect/prototype"
+	protoimpl "github.com/golang/protobuf/v2/runtime/protoimpl"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type Enum1 int32
+
+const (
+	Enum1_ONE Enum1 = 1
+)
+
+type xxx_Enum1 Enum1
+
+func (e Enum1) ProtoReflect() protoreflect.Enum {
+	return (xxx_Enum1)(e)
+}
+func (e xxx_Enum1) Type() protoreflect.EnumType {
+	return xxx_Test_ProtoFile_EnumTypes[0]
+}
+func (e xxx_Enum1) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(e)
+}
+
+var Enum1_name = map[int32]string{
+	1: "ONE",
+}
+
+var Enum1_value = map[string]int32{
+	"ONE": 1,
+}
+
+func (x Enum1) Enum() *Enum1 {
+	p := new(Enum1)
+	*p = x
+	return p
+}
+
+func (x Enum1) String() string {
+	return proto.EnumName(Enum1_name, int32(x))
+}
+
+func (x *Enum1) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(Enum1_value, data, "Enum1")
+	if err != nil {
+		return err
+	}
+	*x = Enum1(value)
+	return nil
+}
+
+func (Enum1) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{0}
+}
+
+type Enum2 int32
+
+const (
+	Enum2_UNO Enum2 = 1
+)
+
+type xxx_Enum2 Enum2
+
+func (e Enum2) ProtoReflect() protoreflect.Enum {
+	return (xxx_Enum2)(e)
+}
+func (e xxx_Enum2) Type() protoreflect.EnumType {
+	return xxx_Test_ProtoFile_EnumTypes[1]
+}
+func (e xxx_Enum2) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(e)
+}
+
+var Enum2_name = map[int32]string{
+	1: "UNO",
+}
+
+var Enum2_value = map[string]int32{
+	"UNO": 1,
+}
+
+func (x Enum2) Enum() *Enum2 {
+	p := new(Enum2)
+	*p = x
+	return p
+}
+
+func (x Enum2) String() string {
+	return proto.EnumName(Enum2_name, int32(x))
+}
+
+func (x *Enum2) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(Enum2_value, data, "Enum2")
+	if err != nil {
+		return err
+	}
+	*x = Enum2(value)
+	return nil
+}
+
+func (Enum2) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{1}
+}
+
+type Enum3 int32
+
+const (
+	Enum3_YI Enum3 = 1
+)
+
+type xxx_Enum3 Enum3
+
+func (e Enum3) ProtoReflect() protoreflect.Enum {
+	return (xxx_Enum3)(e)
+}
+func (e xxx_Enum3) Type() protoreflect.EnumType {
+	return xxx_Test_ProtoFile_EnumTypes[2]
+}
+func (e xxx_Enum3) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(e)
+}
+
+var Enum3_name = map[int32]string{
+	1: "YI",
+}
+
+var Enum3_value = map[string]int32{
+	"YI": 1,
+}
+
+func (x Enum3) Enum() *Enum3 {
+	p := new(Enum3)
+	*p = x
+	return p
+}
+
+func (x Enum3) String() string {
+	return proto.EnumName(Enum3_name, int32(x))
+}
+
+func (x *Enum3) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(Enum3_value, data, "Enum3")
+	if err != nil {
+		return err
+	}
+	*x = Enum3(value)
+	return nil
+}
+
+func (Enum3) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{2}
+}
+
+type Message1 struct {
+	XXX_NoUnkeyedLiteral         struct{} `json:"-"`
+	proto.XXX_InternalExtensions `json:"-"`
+	XXX_unrecognized             []byte `json:"-"`
+	XXX_sizecache                int32  `json:"-"`
+}
+
+type xxx_Message1 struct{ m *Message1 }
+
+func (m *Message1) ProtoReflect() protoreflect.Message {
+	return xxx_Message1{m}
+}
+func (m xxx_Message1) Type() protoreflect.MessageType {
+	return xxx_Test_ProtoFile_MessageTypes[0].Type
+}
+func (m xxx_Message1) KnownFields() protoreflect.KnownFields {
+	return xxx_Test_ProtoFile_MessageTypes[0].KnownFieldsOf(m.m)
+}
+func (m xxx_Message1) UnknownFields() protoreflect.UnknownFields {
+	return xxx_Test_ProtoFile_MessageTypes[0].UnknownFieldsOf(m.m)
+}
+func (m xxx_Message1) Interface() protoreflect.ProtoMessage {
+	return m.m
+}
+
+func (m *Message1) Reset()         { *m = Message1{} }
+func (m *Message1) String() string { return proto.CompactTextString(m) }
+func (*Message1) ProtoMessage()    {}
+func (*Message1) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{0}
+}
+
+var extRange_Message1 = []proto.ExtensionRange{
+	{Start: 10, End: 536870911},
+}
+
+func (*Message1) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_Message1
+}
+
+func (m *Message1) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Message1.Unmarshal(m, b)
+}
+func (m *Message1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Message1.Marshal(b, m, deterministic)
+}
+func (m *Message1) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Message1.Merge(m, src)
+}
+func (m *Message1) XXX_Size() int {
+	return xxx_messageInfo_Message1.Size(m)
+}
+func (m *Message1) XXX_DiscardUnknown() {
+	xxx_messageInfo_Message1.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Message1 proto.InternalMessageInfo
+
+type Message2 struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+type xxx_Message2 struct{ m *Message2 }
+
+func (m *Message2) ProtoReflect() protoreflect.Message {
+	return xxx_Message2{m}
+}
+func (m xxx_Message2) Type() protoreflect.MessageType {
+	return xxx_Test_ProtoFile_MessageTypes[1].Type
+}
+func (m xxx_Message2) KnownFields() protoreflect.KnownFields {
+	return xxx_Test_ProtoFile_MessageTypes[1].KnownFieldsOf(m.m)
+}
+func (m xxx_Message2) UnknownFields() protoreflect.UnknownFields {
+	return xxx_Test_ProtoFile_MessageTypes[1].UnknownFieldsOf(m.m)
+}
+func (m xxx_Message2) Interface() protoreflect.ProtoMessage {
+	return m.m
+}
+
+func (m *Message2) Reset()         { *m = Message2{} }
+func (m *Message2) String() string { return proto.CompactTextString(m) }
+func (*Message2) ProtoMessage()    {}
+func (*Message2) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{1}
+}
+
+func (m *Message2) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Message2.Unmarshal(m, b)
+}
+func (m *Message2) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Message2.Marshal(b, m, deterministic)
+}
+func (m *Message2) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Message2.Merge(m, src)
+}
+func (m *Message2) XXX_Size() int {
+	return xxx_messageInfo_Message2.Size(m)
+}
+func (m *Message2) XXX_DiscardUnknown() {
+	xxx_messageInfo_Message2.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Message2 proto.InternalMessageInfo
+
+type Message3 struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+type xxx_Message3 struct{ m *Message3 }
+
+func (m *Message3) ProtoReflect() protoreflect.Message {
+	return xxx_Message3{m}
+}
+func (m xxx_Message3) Type() protoreflect.MessageType {
+	return xxx_Test_ProtoFile_MessageTypes[2].Type
+}
+func (m xxx_Message3) KnownFields() protoreflect.KnownFields {
+	return xxx_Test_ProtoFile_MessageTypes[2].KnownFieldsOf(m.m)
+}
+func (m xxx_Message3) UnknownFields() protoreflect.UnknownFields {
+	return xxx_Test_ProtoFile_MessageTypes[2].UnknownFieldsOf(m.m)
+}
+func (m xxx_Message3) Interface() protoreflect.ProtoMessage {
+	return m.m
+}
+
+func (m *Message3) Reset()         { *m = Message3{} }
+func (m *Message3) String() string { return proto.CompactTextString(m) }
+func (*Message3) ProtoMessage()    {}
+func (*Message3) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{2}
+}
+
+func (m *Message3) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Message3.Unmarshal(m, b)
+}
+func (m *Message3) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Message3.Marshal(b, m, deterministic)
+}
+func (m *Message3) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Message3.Merge(m, src)
+}
+func (m *Message3) XXX_Size() int {
+	return xxx_messageInfo_Message3.Size(m)
+}
+func (m *Message3) XXX_DiscardUnknown() {
+	xxx_messageInfo_Message3.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Message3 proto.InternalMessageInfo
+
+type Message4 struct {
+	BoolField            *bool    `protobuf:"varint,30,opt,name=bool_field,json=boolField" json:"bool_field,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+type xxx_Message4 struct{ m *Message4 }
+
+func (m *Message4) ProtoReflect() protoreflect.Message {
+	return xxx_Message4{m}
+}
+func (m xxx_Message4) Type() protoreflect.MessageType {
+	return xxx_Test_ProtoFile_MessageTypes[3].Type
+}
+func (m xxx_Message4) KnownFields() protoreflect.KnownFields {
+	return xxx_Test_ProtoFile_MessageTypes[3].KnownFieldsOf(m.m)
+}
+func (m xxx_Message4) UnknownFields() protoreflect.UnknownFields {
+	return xxx_Test_ProtoFile_MessageTypes[3].UnknownFieldsOf(m.m)
+}
+func (m xxx_Message4) Interface() protoreflect.ProtoMessage {
+	return m.m
+}
+
+func (m *Message4) Reset()         { *m = Message4{} }
+func (m *Message4) String() string { return proto.CompactTextString(m) }
+func (*Message4) ProtoMessage()    {}
+func (*Message4) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3628d63611f7063d, []int{3}
+}
+
+func (m *Message4) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Message4.Unmarshal(m, b)
+}
+func (m *Message4) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Message4.Marshal(b, m, deterministic)
+}
+func (m *Message4) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Message4.Merge(m, src)
+}
+func (m *Message4) XXX_Size() int {
+	return xxx_messageInfo_Message4.Size(m)
+}
+func (m *Message4) XXX_DiscardUnknown() {
+	xxx_messageInfo_Message4.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Message4 proto.InternalMessageInfo
+
+func (m *Message4) GetBoolField() bool {
+	if m != nil && m.BoolField != nil {
+		return *m.BoolField
+	}
+	return false
+}
+
+var E_StringField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*string)(nil),
+	Field:         11,
+	Name:          "testprotos.string_field",
+	Tag:           "bytes,11,opt,name=string_field",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+var E_EnumField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*Enum1)(nil),
+	Field:         12,
+	Name:          "testprotos.enum_field",
+	Tag:           "varint,12,opt,name=enum_field,enum=testprotos.Enum1",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+var E_MessageField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*Message2)(nil),
+	Field:         13,
+	Name:          "testprotos.message_field",
+	Tag:           "bytes,13,opt,name=message_field",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+var E_Message4_MessageField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*Message2)(nil),
+	Field:         21,
+	Name:          "testprotos.Message4.message_field",
+	Tag:           "bytes,21,opt,name=message_field",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+var E_Message4_EnumField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*Enum1)(nil),
+	Field:         22,
+	Name:          "testprotos.Message4.enum_field",
+	Tag:           "varint,22,opt,name=enum_field,enum=testprotos.Enum1",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+var E_Message4_StringField = &proto.ExtensionDesc{
+	ExtendedType:  (*Message1)(nil),
+	ExtensionType: (*string)(nil),
+	Field:         23,
+	Name:          "testprotos.Message4.string_field",
+	Tag:           "bytes,23,opt,name=string_field",
+	Filename:      "reflect/protoregistry/testprotos/test.proto",
+}
+
+func init() {
+	proto.RegisterFile("reflect/protoregistry/testprotos/test.proto", fileDescriptor_3628d63611f7063d)
+	proto.RegisterEnum("testprotos.Enum1", Enum1_name, Enum1_value)
+	proto.RegisterEnum("testprotos.Enum2", Enum2_name, Enum2_value)
+	proto.RegisterEnum("testprotos.Enum3", Enum3_name, Enum3_value)
+	proto.RegisterType((*Message1)(nil), "testprotos.Message1")
+	proto.RegisterType((*Message2)(nil), "testprotos.Message2")
+	proto.RegisterType((*Message3)(nil), "testprotos.Message3")
+	proto.RegisterType((*Message4)(nil), "testprotos.Message4")
+	proto.RegisterExtension(E_StringField)
+	proto.RegisterExtension(E_EnumField)
+	proto.RegisterExtension(E_MessageField)
+	proto.RegisterExtension(E_Message4_MessageField)
+	proto.RegisterExtension(E_Message4_EnumField)
+	proto.RegisterExtension(E_Message4_StringField)
+}
+
+var fileDescriptor_3628d63611f7063d = []byte{
+	// 304 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2e, 0x4a, 0x4d, 0xcb,
+	0x49, 0x4d, 0x2e, 0xd1, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29,
+	0xaa, 0xd4, 0x2f, 0x49, 0x2d, 0x2e, 0x01, 0x8b, 0x14, 0x83, 0x99, 0x7a, 0x60, 0xb6, 0x10, 0x17,
+	0x42, 0x58, 0x49, 0x84, 0x8b, 0xc3, 0x37, 0xb5, 0xb8, 0x38, 0x31, 0x3d, 0xd5, 0x50, 0x8b, 0x83,
+	0x83, 0x4b, 0xa0, 0xa1, 0xa1, 0xa1, 0x81, 0x49, 0x89, 0x0b, 0x2e, 0x6a, 0x84, 0xc4, 0x36, 0x56,
+	0xfa, 0xcd, 0x08, 0xe7, 0x98, 0x08, 0xc9, 0x72, 0x71, 0x25, 0xe5, 0xe7, 0xe7, 0xc4, 0xa7, 0x65,
+	0xa6, 0xe6, 0xa4, 0x48, 0xc8, 0x29, 0x30, 0x6a, 0x70, 0x04, 0x71, 0x82, 0x44, 0xdc, 0x40, 0x02,
+	0x46, 0xfe, 0x5c, 0xbc, 0xb9, 0x10, 0xa5, 0x10, 0x15, 0x42, 0x22, 0x7a, 0x08, 0x7b, 0xf5, 0x60,
+	0x96, 0x4a, 0x88, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x61, 0x93, 0x33, 0x0a, 0xe2, 0x81, 0x1a, 0x00,
+	0x31, 0xd0, 0x8d, 0x8b, 0x2b, 0x35, 0xaf, 0x34, 0x17, 0xaf, 0x69, 0x62, 0x0a, 0x8c, 0x1a, 0x7c,
+	0x46, 0x82, 0xc8, 0x72, 0xae, 0x79, 0xa5, 0xb9, 0x86, 0x41, 0x9c, 0x20, 0xad, 0x10, 0x73, 0xcc,
+	0xb9, 0x78, 0x8a, 0x4b, 0x8a, 0x32, 0xf3, 0xd2, 0xf1, 0x9a, 0x24, 0xae, 0xc0, 0xa8, 0xc1, 0x19,
+	0xc4, 0x0d, 0x51, 0x09, 0xd6, 0xa8, 0x25, 0xc0, 0xc5, 0x0a, 0x36, 0x4c, 0x88, 0x9d, 0x8b, 0xd9,
+	0xdf, 0xcf, 0x55, 0x80, 0x11, 0x26, 0x62, 0x04, 0x12, 0x09, 0xf5, 0xf3, 0x17, 0x60, 0xd4, 0xe2,
+	0x87, 0x88, 0x18, 0x0b, 0xb1, 0x71, 0x31, 0x45, 0x7a, 0x0a, 0x30, 0x5a, 0x11, 0x67, 0x1b, 0x37,
+	0x86, 0x6d, 0x56, 0xc4, 0x78, 0x97, 0x87, 0xb0, 0x77, 0xad, 0x88, 0x8c, 0x07, 0x5e, 0x62, 0xe3,
+	0xc1, 0xc9, 0x21, 0xca, 0x2e, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f,
+	0x3d, 0x3f, 0x27, 0x31, 0x2f, 0x1d, 0x92, 0xee, 0x92, 0x4a, 0xd3, 0xf4, 0xcb, 0x8c, 0xf4, 0x09,
+	0xa5, 0x45, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x76, 0x64, 0x81, 0x59, 0xae, 0x02, 0x00, 0x00,
+}
+
+func init() {
+	xxx_Test_ProtoFile_FileDesc.Enums = xxx_Test_ProtoFile_EnumDescs[0:3]
+	xxx_Test_ProtoFile_FileDesc.Messages = xxx_Test_ProtoFile_MessageDescs[0:4]
+	var err error
+	Test_ProtoFile, err = prototype.NewFile(&xxx_Test_ProtoFile_FileDesc)
+	if err != nil {
+		panic(err)
+	}
+}
+
+const _ = protoimpl.EnforceVersion(protoimpl.Version - 0)
+
+var Test_ProtoFile protoreflect.FileDescriptor
+
+var xxx_Test_ProtoFile_FileDesc = prototype.File{
+	Syntax:  protoreflect.Proto2,
+	Path:    "reflect/protoregistry/testprotos/test.proto",
+	Package: "testprotos",
+}
+var xxx_Test_ProtoFile_EnumTypes = [3]protoreflect.EnumType{
+	prototype.GoEnum(
+		xxx_Test_ProtoFile_EnumDescs[0].Reference(),
+		func(_ protoreflect.EnumType, n protoreflect.EnumNumber) protoreflect.ProtoEnum {
+			return Enum1(n)
+		},
+	),
+	prototype.GoEnum(
+		xxx_Test_ProtoFile_EnumDescs[1].Reference(),
+		func(_ protoreflect.EnumType, n protoreflect.EnumNumber) protoreflect.ProtoEnum {
+			return Enum2(n)
+		},
+	),
+	prototype.GoEnum(
+		xxx_Test_ProtoFile_EnumDescs[2].Reference(),
+		func(_ protoreflect.EnumType, n protoreflect.EnumNumber) protoreflect.ProtoEnum {
+			return Enum3(n)
+		},
+	),
+}
+var xxx_Test_ProtoFile_EnumDescs = [3]prototype.Enum{
+	{
+		Name: "Enum1",
+		Values: []prototype.EnumValue{
+			{Name: "ONE", Number: 1},
+		},
+	},
+	{
+		Name: "Enum2",
+		Values: []prototype.EnumValue{
+			{Name: "UNO", Number: 1},
+		},
+	},
+	{
+		Name: "Enum3",
+		Values: []prototype.EnumValue{
+			{Name: "YI", Number: 1},
+		},
+	},
+}
+var xxx_Test_ProtoFile_MessageTypes = [4]protoimpl.MessageType{
+	{Type: prototype.GoMessage(
+		xxx_Test_ProtoFile_MessageDescs[0].Reference(),
+		func(protoreflect.MessageType) protoreflect.ProtoMessage {
+			return new(Message1)
+		},
+	)},
+	{Type: prototype.GoMessage(
+		xxx_Test_ProtoFile_MessageDescs[1].Reference(),
+		func(protoreflect.MessageType) protoreflect.ProtoMessage {
+			return new(Message2)
+		},
+	)},
+	{Type: prototype.GoMessage(
+		xxx_Test_ProtoFile_MessageDescs[2].Reference(),
+		func(protoreflect.MessageType) protoreflect.ProtoMessage {
+			return new(Message3)
+		},
+	)},
+	{Type: prototype.GoMessage(
+		xxx_Test_ProtoFile_MessageDescs[3].Reference(),
+		func(protoreflect.MessageType) protoreflect.ProtoMessage {
+			return new(Message4)
+		},
+	)},
+}
+var xxx_Test_ProtoFile_MessageDescs = [4]prototype.Message{
+	{
+		Name:            "Message1",
+		ExtensionRanges: [][2]protoreflect.FieldNumber{{10, 536870912}},
+	},
+	{
+		Name: "Message2",
+	},
+	{
+		Name: "Message3",
+	},
+	{
+		Name: "Message4",
+		Fields: []prototype.Field{
+			{
+				Name:        "bool_field",
+				Number:      30,
+				Cardinality: protoreflect.Optional,
+				Kind:        protoreflect.BoolKind,
+				JSONName:    "boolField",
+				IsPacked:    prototype.False,
+			},
+		},
+	},
+}
diff --git a/reflect/protoregistry/testprotos/test.proto b/reflect/protoregistry/testprotos/test.proto
new file mode 100644
index 0000000..5a4c77b
--- /dev/null
+++ b/reflect/protoregistry/testprotos/test.proto
@@ -0,0 +1,45 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Different proto type definitions for testing the Types registry.
+syntax = "proto2";
+
+package testprotos;
+option go_package = "github.com/golang/protobuf/v2/reflect/protoregistry/testprotos";
+
+message Message1 {
+  extensions 10 to max;
+}
+
+message Message2 {}
+
+message Message3 {}
+
+enum Enum1 {
+  ONE = 1;
+}
+
+enum Enum2 {
+  UNO = 1;
+}
+
+enum Enum3 {
+  YI = 1;
+}
+
+extend Message1 {
+  optional string string_field = 11;
+  optional Enum1 enum_field = 12;
+  optional Message2 message_field = 13;
+}
+
+message Message4 {
+  optional bool bool_field = 30;
+
+  extend Message1 {
+    optional Message2 message_field = 21;
+    optional Enum1 enum_field = 22;
+    optional string string_field = 23;
+  }
+}
diff --git a/regenerate.bash b/regenerate.bash
index d5ae4d8..534def4 100755
--- a/regenerate.bash
+++ b/regenerate.bash
@@ -53,3 +53,7 @@
 echo "# encoding/textpb/testprotos/pb?/test.proto"
 PROTOC_GEN_GO_ENABLE_REFLECT=1 protoc --go_out=paths=source_relative:. \
   encoding/textpb/testprotos/pb?/test.proto
+
+echo "# reflect/protoregistry/testprotos/test.proto"
+PROTOC_GEN_GO_ENABLE_REFLECT=1 protoc --go_out=paths=source_relative:. \
+  reflect/protoregistry/testprotos/test.proto