reflect/protoreflect: add Descriptor.ParentFile

Querying for the parent file that contains a descriptor declaration
is a common enough operation to warrant its own first-class method.

Change-Id: I2f41e5126a5b465df23897904a6513dd3ed8dd92
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/176777
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 312e1ac..9e11fb0 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -318,16 +318,7 @@
 // been the full name of the proto enum type instead, but changing it at this
 // point would require thought.
 func enumLegacyName(enum *protogen.Enum) string {
-	// Find the FileDescriptor for this enum.
-	var desc protoreflect.Descriptor = enum.Desc
-	for {
-		p, ok := desc.Parent()
-		if !ok {
-			break
-		}
-		desc = p
-	}
-	fdesc := desc.(protoreflect.FileDescriptor)
+	fdesc := enum.Desc.ParentFile()
 	if fdesc.Package() == "" {
 		return enum.GoIdent.GoName
 	}
diff --git a/internal/fileinit/desc.go b/internal/fileinit/desc.go
index 0e9ef53..2729da6 100644
--- a/internal/fileinit/desc.go
+++ b/internal/fileinit/desc.go
@@ -250,6 +250,7 @@
 	}
 )
 
+func (fd *fileDesc) ParentFile() pref.FileDescriptor { return fd }
 func (fd *fileDesc) Parent() (pref.Descriptor, bool) { return nil, false }
 func (fd *fileDesc) Index() int                      { return 0 }
 func (fd *fileDesc) Syntax() pref.Syntax             { return fd.lazyInit().syntax }
@@ -581,6 +582,7 @@
 	fullName
 }
 
+func (d *baseDesc) ParentFile() pref.FileDescriptor     { return d.parentFile }
 func (d *baseDesc) Parent() (pref.Descriptor, bool)     { return d.parent, true }
 func (d *baseDesc) Index() int                          { return d.index }
 func (d *baseDesc) Syntax() pref.Syntax                 { return d.parentFile.Syntax() }
diff --git a/internal/legacy/extension.go b/internal/legacy/extension.go
index e9c8bb7..9174555 100644
--- a/internal/legacy/extension.go
+++ b/internal/legacy/extension.go
@@ -106,7 +106,7 @@
 		// Derive the proto package name.
 		// For legacy enums, obtain the proto package from the raw descriptor.
 		var protoPkg string
-		if fd := parentFileDescriptor(xt.Descriptor().Enum()); fd != nil {
+		if fd := xt.Descriptor().Enum().ParentFile(); fd != nil {
 			protoPkg = string(fd.Package())
 		}
 		if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" {
@@ -121,7 +121,7 @@
 
 	// Derive the proto file that the extension was declared within.
 	var filename string
-	if fd := parentFileDescriptor(xt.Descriptor()); fd != nil {
+	if fd := xt.Descriptor().ParentFile(); fd != nil {
 		filename = fd.Path()
 	}
 
diff --git a/internal/legacy/file.go b/internal/legacy/file.go
index 0f871a0..e98508b 100644
--- a/internal/legacy/file.go
+++ b/internal/legacy/file.go
@@ -9,8 +9,6 @@
 	"compress/gzip"
 	"io/ioutil"
 	"sync"
-
-	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 )
 
 // Every enum and message type generated by protoc-gen-go since commit 2fc053c5
@@ -59,14 +57,3 @@
 	}
 	return fd
 }
-
-// parentFileDescriptor returns the parent protoreflect.FileDescriptor for the
-// provide descriptor. It returns nil if there is no parent.
-func parentFileDescriptor(d pref.Descriptor) pref.FileDescriptor {
-	for ok := true; ok; d, ok = d.Parent() {
-		if fd, _ := d.(pref.FileDescriptor); fd != nil {
-			return fd
-		}
-	}
-	return nil
-}
diff --git a/internal/legacy/file_test.go b/internal/legacy/file_test.go
index 395a2ef..9a9a3fc 100644
--- a/internal/legacy/file_test.go
+++ b/internal/legacy/file_test.go
@@ -421,6 +421,8 @@
 				name := v.Type().Method(i).Name
 				if m := v.Method(i); m.Type().NumIn() == 0 && m.Type().NumOut() == 1 {
 					switch name {
+					case "ParentFile":
+						// Ignore parent file to avoid recursive cycle.
 					case "Index":
 						// Ignore index since legacy descriptors have no parent.
 					case "Options":
diff --git a/internal/prototype/placeholder_type.go b/internal/prototype/placeholder_type.go
index be28bd3..d4c0107 100644
--- a/internal/prototype/placeholder_type.go
+++ b/internal/prototype/placeholder_type.go
@@ -30,6 +30,7 @@
 
 type placeholderName pref.FullName
 
+func (t placeholderName) ParentFile() pref.FileDescriptor     { return nil }
 func (t placeholderName) Parent() (pref.Descriptor, bool)     { return nil, false }
 func (t placeholderName) Index() int                          { return 0 }
 func (t placeholderName) Syntax() pref.Syntax                 { return 0 }
@@ -43,6 +44,7 @@
 	placeholderName
 }
 
+func (t placeholderFile) ParentFile() pref.FileDescriptor       { return t }
 func (t placeholderFile) Options() pref.ProtoMessage            { return descopts.File }
 func (t placeholderFile) Path() string                          { return t.path }
 func (t placeholderFile) Package() pref.FullName                { return t.FullName() }
diff --git a/internal/prototype/protofile_type.go b/internal/prototype/protofile_type.go
index 49d0db1..4e3416f 100644
--- a/internal/prototype/protofile_type.go
+++ b/internal/prototype/protofile_type.go
@@ -13,6 +13,7 @@
 	descopts "github.com/golang/protobuf/v2/internal/descopts"
 	pragma "github.com/golang/protobuf/v2/internal/pragma"
 	pfmt "github.com/golang/protobuf/v2/internal/typefmt"
+	"github.com/golang/protobuf/v2/reflect/protoreflect"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 )
 
@@ -62,6 +63,7 @@
 	f.fileMeta = new(fileMeta)
 	return fileDesc{f}
 }
+func (t fileDesc) ParentFile() pref.FileDescriptor       { return t }
 func (t fileDesc) Parent() (pref.Descriptor, bool)       { return nil, false }
 func (t fileDesc) Index() int                            { return 0 }
 func (t fileDesc) Syntax() pref.Syntax                   { return t.f.Syntax }
@@ -80,6 +82,15 @@
 func (t fileDesc) ProtoType(pref.FileDescriptor)         {}
 func (t fileDesc) ProtoInternal(pragma.DoNotImplement)   {}
 
+func parentFile(d protoreflect.Descriptor) protoreflect.FileDescriptor {
+	for ; d != nil; d, _ = d.Parent() {
+		if fd, ok := d.(protoreflect.FileDescriptor); ok {
+			return fd
+		}
+	}
+	return nil
+}
+
 type messageMeta struct {
 	inheritedMeta
 
@@ -92,6 +103,7 @@
 }
 type messageDesc struct{ m *Message }
 
+func (t messageDesc) ParentFile() pref.FileDescriptor { return parentFile(t) }
 func (t messageDesc) Parent() (pref.Descriptor, bool) { return t.m.parent, true }
 func (t messageDesc) Index() int                      { return t.m.index }
 func (t messageDesc) Syntax() pref.Syntax             { return t.m.syntax }
@@ -143,6 +155,7 @@
 }
 type fieldDesc struct{ f *Field }
 
+func (t fieldDesc) ParentFile() pref.FileDescriptor { return parentFile(t) }
 func (t fieldDesc) Parent() (pref.Descriptor, bool) { return t.f.parent, true }
 func (t fieldDesc) Index() int                      { return t.f.index }
 func (t fieldDesc) Syntax() pref.Syntax             { return t.f.syntax }
@@ -244,6 +257,7 @@
 }
 type oneofDesc struct{ o *Oneof }
 
+func (t oneofDesc) ParentFile() pref.FileDescriptor     { return parentFile(t) }
 func (t oneofDesc) Parent() (pref.Descriptor, bool)     { return t.o.parent, true }
 func (t oneofDesc) Index() int                          { return t.o.index }
 func (t oneofDesc) Syntax() pref.Syntax                 { return t.o.syntax }
@@ -266,6 +280,7 @@
 }
 type extensionDesc struct{ x *Extension }
 
+func (t extensionDesc) ParentFile() pref.FileDescriptor { return parentFile(t) }
 func (t extensionDesc) Parent() (pref.Descriptor, bool) { return t.x.parent, true }
 func (t extensionDesc) Syntax() pref.Syntax             { return t.x.syntax }
 func (t extensionDesc) Index() int                      { return t.x.index }
@@ -304,6 +319,7 @@
 }
 type enumDesc struct{ e *Enum }
 
+func (t enumDesc) ParentFile() pref.FileDescriptor     { return parentFile(t) }
 func (t enumDesc) Parent() (pref.Descriptor, bool)     { return t.e.parent, true }
 func (t enumDesc) Index() int                          { return t.e.index }
 func (t enumDesc) Syntax() pref.Syntax                 { return t.e.syntax }
@@ -323,6 +339,7 @@
 }
 type enumValueDesc struct{ v *EnumValue }
 
+func (t enumValueDesc) ParentFile() pref.FileDescriptor { return parentFile(t) }
 func (t enumValueDesc) Parent() (pref.Descriptor, bool) { return t.v.parent, true }
 func (t enumValueDesc) Index() int                      { return t.v.index }
 func (t enumValueDesc) Syntax() pref.Syntax             { return t.v.syntax }
@@ -344,6 +361,7 @@
 }
 type serviceDesc struct{ s *Service }
 
+func (t serviceDesc) ParentFile() pref.FileDescriptor { return parentFile(t) }
 func (t serviceDesc) Parent() (pref.Descriptor, bool) { return t.s.parent, true }
 func (t serviceDesc) Index() int                      { return t.s.index }
 func (t serviceDesc) Syntax() pref.Syntax             { return t.s.syntax }
@@ -366,6 +384,7 @@
 }
 type methodDesc struct{ m *Method }
 
+func (t methodDesc) ParentFile() pref.FileDescriptor     { return parentFile(t) }
 func (t methodDesc) Parent() (pref.Descriptor, bool)     { return t.m.parent, true }
 func (t methodDesc) Index() int                          { return t.m.index }
 func (t methodDesc) Syntax() pref.Syntax                 { return t.m.syntax }
diff --git a/internal/prototype/standalone_type.go b/internal/prototype/standalone_type.go
index 91579da..3bf4115 100644
--- a/internal/prototype/standalone_type.go
+++ b/internal/prototype/standalone_type.go
@@ -15,6 +15,7 @@
 
 type standaloneMessage struct{ m *StandaloneMessage }
 
+func (t standaloneMessage) ParentFile() pref.FileDescriptor { return nil }
 func (t standaloneMessage) Parent() (pref.Descriptor, bool) { return nil, false }
 func (t standaloneMessage) Index() int                      { return 0 }
 func (t standaloneMessage) Syntax() pref.Syntax             { return t.m.Syntax }
@@ -47,6 +48,7 @@
 
 type standaloneEnum struct{ e *StandaloneEnum }
 
+func (t standaloneEnum) ParentFile() pref.FileDescriptor { return nil }
 func (t standaloneEnum) Parent() (pref.Descriptor, bool) { return nil, false }
 func (t standaloneEnum) Index() int                      { return 0 }
 func (t standaloneEnum) Syntax() pref.Syntax             { return t.e.Syntax }
@@ -65,6 +67,7 @@
 
 type standaloneExtension struct{ x *StandaloneExtension }
 
+func (t standaloneExtension) ParentFile() pref.FileDescriptor { return nil }
 func (t standaloneExtension) Parent() (pref.Descriptor, bool) { return nil, false }
 func (t standaloneExtension) Index() int                      { return 0 }
 func (t standaloneExtension) Syntax() pref.Syntax             { return pref.Proto2 }
diff --git a/internal/typefmt/desc_test.go b/internal/typefmt/desc_test.go
index 346362b..296131c 100644
--- a/internal/typefmt/desc_test.go
+++ b/internal/typefmt/desc_test.go
@@ -11,6 +11,7 @@
 // TestDescriptorAccessors tests that descriptorAccessors is up-to-date.
 func TestDescriptorAccessors(t *testing.T) {
 	ignore := map[string]bool{
+		"ParentFile":    true,
 		"Parent":        true,
 		"Index":         true,
 		"Syntax":        true,
diff --git a/reflect/protodesc/protodesc.go b/reflect/protodesc/protodesc.go
index 4fcd648..7bc2191 100644
--- a/reflect/protodesc/protodesc.go
+++ b/reflect/protodesc/protodesc.go
@@ -375,26 +375,6 @@
 	return md, nil
 }
 
-func validateFileInImports(d protoreflect.Descriptor, imps importSet) error {
-	fd := fileDescriptor(d)
-	if fd == nil {
-		return errors.New("%v has no parent FileDescriptor", d.FullName())
-	}
-	if !imps[fd.Path()] {
-		return errors.New("reference to type %v without import of %v", d.FullName(), fd.Path())
-	}
-	return nil
-}
-
-func fileDescriptor(d protoreflect.Descriptor) protoreflect.FileDescriptor {
-	for ; d != nil; d, _ = d.Parent() {
-		if fd, ok := d.(protoreflect.FileDescriptor); ok {
-			return fd
-		}
-	}
-	return nil
-}
-
 func findEnumDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
 	if !strings.HasPrefix(s, ".") {
 		return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
@@ -409,3 +389,14 @@
 	}
 	return ed, nil
 }
+
+func validateFileInImports(d protoreflect.Descriptor, imps importSet) error {
+	fd := d.ParentFile()
+	if fd == nil {
+		return errors.New("%v has no parent FileDescriptor", d.FullName())
+	}
+	if !imps[fd.Path()] {
+		return errors.New("reference to type %v without import of %v", d.FullName(), fd.Path())
+	}
+	return nil
+}
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index 490bfff..3971b44 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -24,6 +24,14 @@
 // This can occur if a descriptor type is created dynamically, or multiple
 // versions of the same proto type are linked into the Go binary.
 type Descriptor interface {
+	// ParentFile returns the parent file descriptor that this descriptor
+	// is declared within. The parent file for the file descriptor is itself.
+	//
+	// Support for this functionality is optional and may return nil.
+	ParentFile() FileDescriptor
+
+	// TODO: Switch the signature of Parent to drop the bool.
+
 	// Parent returns the parent containing this descriptor declaration.
 	// The following shows the mapping from child type to possible parent types:
 	//