reflect/protodesc: add validation for NewFile

This covers most of the TODO around validation. I left open the ones
that we didn't have clear consensus on yet.

Change-Id: I336c53173ee8d7447558b1e3a0c1ef945e986cd5
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/175140
Reviewed-by: Joe Tsai <joetsai@google.com>
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/testdata/import_public/a.pb.go b/cmd/protoc-gen-go/testdata/import_public/a.pb.go
index b33e9f0..c881ea1 100644
--- a/cmd/protoc-gen-go/testdata/import_public/a.pb.go
+++ b/cmd/protoc-gen-go/testdata/import_public/a.pb.go
@@ -5,7 +5,6 @@
 
 import (
 	sub "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub"
-	sub2 "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub2"
 	protoreflect "github.com/golang/protobuf/v2/reflect/protoreflect"
 	protoregistry "github.com/golang/protobuf/v2/reflect/protoregistry"
 	protoiface "github.com/golang/protobuf/v2/runtime/protoiface"
@@ -53,10 +52,6 @@
 
 var E_ExtensionField = sub.E_ExtensionField
 
-// Symbols defined in public import of import_public/sub2/a.proto
-
-type Sub2Message = sub2.Sub2Message
-
 type Public struct {
 	M                    *sub.M                  `protobuf:"bytes,1,opt,name=m" json:"m,omitempty"`
 	E                    *sub.E                  `protobuf:"varint,2,opt,name=e,enum=goproto.protoc.import_public.sub.E" json:"e,omitempty"`
@@ -118,25 +113,24 @@
 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
 	0x75, 0x62, 0x6c, 0x69, 0x63, 0x1a, 0x19, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75,
 	0x62, 0x6c, 0x69, 0x63, 0x2f, 0x73, 0x75, 0x62, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x1a, 0x1a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
-	0x73, 0x75, 0x62, 0x32, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x69, 0x6d,
-	0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x62, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x06, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x31,
-	0x0a, 0x01, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72,
-	0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x2e, 0x4d, 0x52, 0x01,
-	0x6d, 0x12, 0x31, 0x0a, 0x01, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67,
-	0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d,
-	0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x2e,
-	0x45, 0x52, 0x01, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c,
-	0x69, 0x63, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42,
-	0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f,
-	0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x32,
-	0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d,
-	0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6d, 0x70, 0x6f,
-	0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x50, 0x00, 0x50, 0x01, 0x50, 0x02,
+	0x1a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
+	0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x06, 0x50, 0x75, 0x62, 0x6c,
+	0x69, 0x63, 0x12, 0x31, 0x0a, 0x01, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e,
+	0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69,
+	0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62,
+	0x2e, 0x4d, 0x52, 0x01, 0x6d, 0x12, 0x31, 0x0a, 0x01, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e,
+	0x73, 0x75, 0x62, 0x2e, 0x45, 0x52, 0x01, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61,
+	0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f,
+	0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f,
+	0x63, 0x61, 0x6c, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d,
+	0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f,
+	0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x50, 0x00, 0x50,
+	0x01,
 }
 
 var (
diff --git a/cmd/protoc-gen-go/testdata/import_public/a.proto b/cmd/protoc-gen-go/testdata/import_public/a.proto
index 091dcac..6807950 100644
--- a/cmd/protoc-gen-go/testdata/import_public/a.proto
+++ b/cmd/protoc-gen-go/testdata/import_public/a.proto
@@ -9,7 +9,6 @@
 option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public";
 
 import public "import_public/sub/a.proto";  // Different Go package.
-import public "import_public/sub2/a.proto"; // Different Go package.
 import public "import_public/b.proto";      // Same Go package.
 
 message Public {
diff --git a/cmd/protoc-gen-go/testdata/import_public/c.pb.go b/cmd/protoc-gen-go/testdata/import_public/c.pb.go
new file mode 100644
index 0000000..49a8fb5
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/import_public/c.pb.go
@@ -0,0 +1,129 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: import_public/c.proto
+
+package import_public
+
+import (
+	sub2 "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub2"
+	protoreflect "github.com/golang/protobuf/v2/reflect/protoreflect"
+	protoregistry "github.com/golang/protobuf/v2/reflect/protoregistry"
+	protoiface "github.com/golang/protobuf/v2/runtime/protoiface"
+	protoimpl "github.com/golang/protobuf/v2/runtime/protoimpl"
+	sync "sync"
+)
+
+const _ = protoimpl.EnforceVersion(protoimpl.Version - 0)
+
+type UsingPublicImport struct {
+	// Local is declared in b.proto, which is a public import of a.proto.
+	Local *Local `protobuf:"bytes,1,opt,name=local" json:"local,omitempty"`
+	// Sub2Message is declared in sub2/a.proto, which is a public import of
+	// sub/a.proto, which is a public import of a.proto.
+	Sub2                 *sub2.Sub2Message       `protobuf:"bytes,2,opt,name=sub2" json:"sub2,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
+	XXX_unrecognized     protoimpl.UnknownFields `json:"-"`
+	XXX_sizecache        protoimpl.SizeCache     `json:"-"`
+}
+
+func (x *UsingPublicImport) Reset() {
+	*x = UsingPublicImport{}
+}
+
+func (x *UsingPublicImport) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UsingPublicImport) ProtoMessage() {}
+
+func (x *UsingPublicImport) ProtoReflect() protoreflect.Message {
+	return file_import_public_c_proto_msgTypes[0].MessageOf(x)
+}
+
+func (m *UsingPublicImport) XXX_Methods() *protoiface.Methods {
+	return file_import_public_c_proto_msgTypes[0].Methods()
+}
+
+// Deprecated: Use UsingPublicImport.ProtoReflect.Type instead.
+func (*UsingPublicImport) Descriptor() ([]byte, []int) {
+	return file_import_public_c_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *UsingPublicImport) GetLocal() *Local {
+	if x != nil {
+		return x.Local
+	}
+	return nil
+}
+
+func (x *UsingPublicImport) GetSub2() *sub2.Sub2Message {
+	if x != nil {
+		return x.Sub2
+	}
+	return nil
+}
+
+var File_import_public_c_proto protoreflect.FileDescriptor
+
+var file_import_public_c_proto_rawDesc = []byte{
+	0x0a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
+	0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
+	0x75, 0x62, 0x6c, 0x69, 0x63, 0x1a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75,
+	0x62, 0x6c, 0x69, 0x63, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a,
+	0x11, 0x55, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x6d, 0x70, 0x6f,
+	0x72, 0x74, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+	0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x42, 0x0a,
+	0x04, 0x73, 0x75, 0x62, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, 0x6f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70,
+	0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x32, 0x2e,
+	0x53, 0x75, 0x62, 0x32, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x04, 0x73, 0x75, 0x62,
+	0x32, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+	0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
+	0x76, 0x32, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65,
+	0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6d,
+	0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+}
+
+var (
+	file_import_public_c_proto_rawDescOnce sync.Once
+	file_import_public_c_proto_rawDescData = file_import_public_c_proto_rawDesc
+)
+
+func file_import_public_c_proto_rawDescGZIP() []byte {
+	file_import_public_c_proto_rawDescOnce.Do(func() {
+		file_import_public_c_proto_rawDescData = protoimpl.X.CompressGZIP(file_import_public_c_proto_rawDescData)
+	})
+	return file_import_public_c_proto_rawDescData
+}
+
+var file_import_public_c_proto_msgTypes = make([]protoimpl.MessageType, 1)
+var file_import_public_c_proto_goTypes = []interface{}{
+	(*UsingPublicImport)(nil), // 0: goproto.protoc.import_public.UsingPublicImport
+	(*Local)(nil),             // 1: goproto.protoc.import_public.Local
+	(*sub2.Sub2Message)(nil),  // 2: goproto.protoc.import_public.sub2.Sub2Message
+}
+var file_import_public_c_proto_depIdxs = []int32{
+	1, // goproto.protoc.import_public.UsingPublicImport.local:type_name -> goproto.protoc.import_public.Local
+	2, // goproto.protoc.import_public.UsingPublicImport.sub2:type_name -> goproto.protoc.import_public.sub2.Sub2Message
+}
+
+func init() { file_import_public_c_proto_init() }
+func file_import_public_c_proto_init() {
+	if File_import_public_c_proto != nil {
+		return
+	}
+	file_import_public_a_proto_init()
+	File_import_public_c_proto = protoimpl.FileBuilder{
+		RawDescriptor:      file_import_public_c_proto_rawDesc,
+		GoTypes:            file_import_public_c_proto_goTypes,
+		DependencyIndexes:  file_import_public_c_proto_depIdxs,
+		MessageOutputTypes: file_import_public_c_proto_msgTypes,
+		FilesRegistry:      protoregistry.GlobalFiles,
+		TypesRegistry:      protoregistry.GlobalTypes,
+	}.Init()
+	file_import_public_c_proto_rawDesc = nil
+	file_import_public_c_proto_goTypes = nil
+	file_import_public_c_proto_depIdxs = nil
+}
diff --git a/cmd/protoc-gen-go/testdata/import_public/c.proto b/cmd/protoc-gen-go/testdata/import_public/c.proto
new file mode 100644
index 0000000..6a3de06
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/import_public/c.proto
@@ -0,0 +1,19 @@
+// 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.
+
+syntax = "proto2";
+
+package goproto.protoc.import_public;
+
+option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public";
+
+import "import_public/a.proto";
+
+message UsingPublicImport {
+  // Local is declared in b.proto, which is a public import of a.proto.
+  optional Local local = 1;
+  // Sub2Message is declared in sub2/a.proto, which is a public import of
+  // sub/a.proto, which is a public import of a.proto.
+  optional sub2.Sub2Message sub2 = 2; // declared in sub2/a.proto
+}
diff --git a/internal/prototype/protofile_list.go b/internal/prototype/protofile_list.go
index f136b5f..426e695 100644
--- a/internal/prototype/protofile_list.go
+++ b/internal/prototype/protofile_list.go
@@ -29,6 +29,9 @@
 func (p *names) Format(s fmt.State, r rune)          { pfmt.FormatList(s, r, p) }
 func (p *names) ProtoInternal(pragma.DoNotImplement) {}
 
+// Names returns a Names list from a slice of names.
+func Names(s []pref.Name) pref.Names { return (*names)(&s) }
+
 type numbersMeta struct {
 	once sync.Once
 	ns   []pref.FieldNumber
@@ -68,6 +71,9 @@
 func (p *fieldRanges) Format(s fmt.State, r rune)          { pfmt.FormatList(s, r, p) }
 func (p *fieldRanges) ProtoInternal(pragma.DoNotImplement) {}
 
+// FieldRanges returns a FieldRanges list from a slice of ranges.
+func FieldRanges(s [][2]pref.FieldNumber) pref.FieldRanges { return (*fieldRanges)(&s) }
+
 type enumRanges [][2]pref.EnumNumber
 
 func (p *enumRanges) Len() int                     { return len(*p) }
@@ -83,6 +89,9 @@
 func (p *enumRanges) Format(s fmt.State, r rune)          { pfmt.FormatList(s, r, p) }
 func (p *enumRanges) ProtoInternal(pragma.DoNotImplement) {}
 
+// EnumRanges returns an EnumRanges list from a slice of ranges.
+func EnumRanges(s [][2]pref.EnumNumber) pref.EnumRanges { return (*enumRanges)(&s) }
+
 type fileImports []pref.FileImport
 
 func (p *fileImports) Len() int                            { return len(*p) }
diff --git a/reflect/protodesc/protodesc.go b/reflect/protodesc/protodesc.go
index 406e37f..c1ed334 100644
--- a/reflect/protodesc/protodesc.go
+++ b/reflect/protodesc/protodesc.go
@@ -23,14 +23,7 @@
 // that we don't directly use?
 //
 // For example:
-//	* That field numbers don't overlap with reserved numbers.
-//	* That field names don't overlap with reserved names.
-//	* That enum numbers don't overlap with reserved numbers.
-//	* That enum names don't overlap with reserved names.
-//	* That "extendee" is not set for a message field.
-//	* That "oneof_index" is not set for an extension field.
 //	* That "json_name" is not set for an extension field. Maybe, maybe not.
-//	* That "type_name" is not set on a field for non-enums and non-messages.
 //	* That "weak" is not set for an extension field (double check this).
 
 // TODO: Store the input file descriptor to implement:
@@ -96,8 +89,10 @@
 		}
 	}
 
+	imps := importedFiles(f.Imports)
+
 	var err error
-	f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), f.Syntax, r)
+	f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), imps, r)
 	if err != nil {
 		return nil, err
 	}
@@ -105,11 +100,11 @@
 	if err != nil {
 		return nil, err
 	}
-	f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), r)
+	f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), imps, r)
 	if err != nil {
 		return nil, err
 	}
-	f.Services, err = servicesFromDescriptorProto(fd.GetService(), r)
+	f.Services, err = servicesFromDescriptorProto(fd.GetService(), imps, r)
 	if err != nil {
 		return nil, err
 	}
@@ -117,16 +112,71 @@
 	return prototype.NewFile(&f)
 }
 
-func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, syntax protoreflect.Syntax, r *protoregistry.Files) (ms []prototype.Message, err error) {
+type importSet map[protoreflect.FileDescriptor]bool
+
+func importedFiles(imps []protoreflect.FileImport) importSet {
+	ret := make(importSet)
+	for _, imp := range imps {
+		ret[imp.FileDescriptor] = true
+		addPublicImports(imp.FileDescriptor, ret)
+	}
+	return ret
+}
+
+func addPublicImports(fd protoreflect.FileDescriptor, out importSet) {
+	imps := fd.Imports()
+	for i := 0; i < imps.Len(); i++ {
+		imp := imps.Get(i)
+		if imp.IsPublic {
+			out[imp.FileDescriptor] = true
+			addPublicImports(imp.FileDescriptor, out)
+		}
+	}
+}
+
+func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, imps importSet, r *protoregistry.Files) (ms []prototype.Message, err error) {
 	for _, md := range mds {
 		var m prototype.Message
 		m.Name = protoreflect.Name(md.GetName())
 		m.Options = md.GetOptions()
 		m.IsMapEntry = md.GetOptions().GetMapEntry()
+
+		for _, s := range md.GetReservedName() {
+			m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
+		}
+		for _, rr := range md.GetReservedRange() {
+			m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
+				protoreflect.FieldNumber(rr.GetStart()),
+				protoreflect.FieldNumber(rr.GetEnd()),
+			})
+		}
+		for _, xr := range md.GetExtensionRange() {
+			m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
+				protoreflect.FieldNumber(xr.GetStart()),
+				protoreflect.FieldNumber(xr.GetEnd()),
+			})
+			m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
+		}
+		resNames := prototype.Names(m.ReservedNames)
+		resRanges := prototype.FieldRanges(m.ReservedRanges)
+		extRanges := prototype.FieldRanges(m.ExtensionRanges)
+
 		for _, fd := range md.GetField() {
+			if fd.GetExtendee() != "" {
+				return nil, errors.New("message field may not have extendee")
+			}
 			var f prototype.Field
 			f.Name = protoreflect.Name(fd.GetName())
+			if resNames.Has(f.Name) {
+				return nil, errors.New("%v contains field with reserved name %q", m.Name, f.Name)
+			}
 			f.Number = protoreflect.FieldNumber(fd.GetNumber())
+			if resRanges.Has(f.Number) {
+				return nil, errors.New("%v contains field with reserved number %d", m.Name, f.Number)
+			}
+			if extRanges.Has(f.Number) {
+				return nil, errors.New("%v contains field with number %d in extension range", m.Name, f.Number)
+			}
 			f.Cardinality = protoreflect.Cardinality(fd.GetLabel())
 			f.Kind = protoreflect.Kind(fd.GetType())
 			opts := fd.GetOptions()
@@ -155,7 +205,7 @@
 			}
 			switch f.Kind {
 			case protoreflect.EnumKind:
-				f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), r)
+				f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), imps, r)
 				if err != nil {
 					return nil, err
 				}
@@ -163,13 +213,17 @@
 					f.EnumType = prototype.PlaceholderEnum(f.EnumType.FullName())
 				}
 			case protoreflect.MessageKind, protoreflect.GroupKind:
-				f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), r)
+				f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), imps, r)
 				if err != nil {
 					return nil, err
 				}
 				if opts.GetWeak() && !f.MessageType.IsPlaceholder() {
 					f.MessageType = prototype.PlaceholderMessage(f.MessageType.FullName())
 				}
+			default:
+				if fd.GetTypeName() != "" {
+					return nil, errors.New("field of kind %v has type_name set", f.Kind)
+				}
 			}
 			m.Fields = append(m.Fields, f)
 		}
@@ -179,24 +233,8 @@
 				Options: od.Options,
 			})
 		}
-		for _, s := range md.GetReservedName() {
-			m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
-		}
-		for _, rr := range md.GetReservedRange() {
-			m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
-				protoreflect.FieldNumber(rr.GetStart()),
-				protoreflect.FieldNumber(rr.GetEnd()),
-			})
-		}
-		for _, xr := range md.GetExtensionRange() {
-			m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
-				protoreflect.FieldNumber(xr.GetStart()),
-				protoreflect.FieldNumber(xr.GetEnd()),
-			})
-			m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
-		}
 
-		m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), syntax, r)
+		m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), imps, r)
 		if err != nil {
 			return nil, err
 		}
@@ -204,7 +242,7 @@
 		if err != nil {
 			return nil, err
 		}
-		m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), r)
+		m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), imps, r)
 		if err != nil {
 			return nil, err
 		}
@@ -219,13 +257,6 @@
 		var e prototype.Enum
 		e.Name = protoreflect.Name(ed.GetName())
 		e.Options = ed.GetOptions()
-		for _, vd := range ed.GetValue() {
-			e.Values = append(e.Values, prototype.EnumValue{
-				Name:    protoreflect.Name(vd.GetName()),
-				Number:  protoreflect.EnumNumber(vd.GetNumber()),
-				Options: vd.Options,
-			})
-		}
 		for _, s := range ed.GetReservedName() {
 			e.ReservedNames = append(e.ReservedNames, protoreflect.Name(s))
 		}
@@ -235,13 +266,33 @@
 				protoreflect.EnumNumber(rr.GetEnd()),
 			})
 		}
+		resNames := prototype.Names(e.ReservedNames)
+		resRanges := prototype.EnumRanges(e.ReservedRanges)
+
+		for _, vd := range ed.GetValue() {
+			v := prototype.EnumValue{
+				Name:    protoreflect.Name(vd.GetName()),
+				Number:  protoreflect.EnumNumber(vd.GetNumber()),
+				Options: vd.Options,
+			}
+			if resNames.Has(v.Name) {
+				return nil, errors.New("enum %v contains value with reserved name %q", e.Name, v.Name)
+			}
+			if resRanges.Has(v.Number) {
+				return nil, errors.New("enum %v contains value with reserved number %d", e.Name, v.Number)
+			}
+			e.Values = append(e.Values, v)
+		}
 		es = append(es, e)
 	}
 	return es, nil
 }
 
-func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, r *protoregistry.Files) (xs []prototype.Extension, err error) {
+func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, imps importSet, r *protoregistry.Files) (xs []prototype.Extension, err error) {
 	for _, xd := range xds {
+		if xd.OneofIndex != nil {
+			return nil, errors.New("extension may not have oneof_index")
+		}
 		var x prototype.Extension
 		x.Name = protoreflect.Name(xd.GetName())
 		x.Number = protoreflect.FieldNumber(xd.GetNumber())
@@ -256,17 +307,21 @@
 		}
 		switch x.Kind {
 		case protoreflect.EnumKind:
-			x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), r)
+			x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), imps, r)
 			if err != nil {
 				return nil, err
 			}
 		case protoreflect.MessageKind, protoreflect.GroupKind:
-			x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), r)
+			x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), imps, r)
 			if err != nil {
 				return nil, err
 			}
+		default:
+			if xd.GetTypeName() != "" {
+				return nil, errors.New("extension of kind %v has type_name set", x.Kind)
+			}
 		}
-		x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), r)
+		x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), imps, r)
 		if err != nil {
 			return nil, err
 		}
@@ -275,7 +330,7 @@
 	return xs, nil
 }
 
-func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, r *protoregistry.Files) (ss []prototype.Service, err error) {
+func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, imps importSet, r *protoregistry.Files) (ss []prototype.Service, err error) {
 	for _, sd := range sds {
 		var s prototype.Service
 		s.Name = protoreflect.Name(sd.GetName())
@@ -284,11 +339,11 @@
 			var m prototype.Method
 			m.Name = protoreflect.Name(md.GetName())
 			m.Options = md.GetOptions()
-			m.InputType, err = findMessageDescriptor(md.GetInputType(), r)
+			m.InputType, err = findMessageDescriptor(md.GetInputType(), imps, r)
 			if err != nil {
 				return nil, err
 			}
-			m.OutputType, err = findMessageDescriptor(md.GetOutputType(), r)
+			m.OutputType, err = findMessageDescriptor(md.GetOutputType(), imps, r)
 			if err != nil {
 				return nil, err
 			}
@@ -306,7 +361,7 @@
 // simplifies our implementation as we won't need to implement C++'s namespace
 // scoping rules.
 
-func findMessageDescriptor(s string, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
+func findMessageDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
 	if !strings.HasPrefix(s, ".") {
 		return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
 	}
@@ -315,10 +370,33 @@
 	if err != nil {
 		return prototype.PlaceholderMessage(name), nil
 	}
+	if err := validateFileInImports(md, imps); err != nil {
+		return nil, err
+	}
 	return md, nil
 }
 
-func findEnumDescriptor(s string, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
+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] {
+		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)
 	}
@@ -327,6 +405,9 @@
 	if err != nil {
 		return prototype.PlaceholderEnum(name), nil
 	}
+	if err := validateFileInImports(ed, imps); err != nil {
+		return nil, err
+	}
 	return ed, nil
 }
 
diff --git a/reflect/protodesc/protodesc_test.go b/reflect/protodesc/protodesc_test.go
new file mode 100644
index 0000000..b82078e
--- /dev/null
+++ b/reflect/protodesc/protodesc_test.go
@@ -0,0 +1,575 @@
+package protodesc
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/v2/internal/scalar"
+	"github.com/golang/protobuf/v2/reflect/protoregistry"
+
+	descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
+)
+
+// Tests validation logic for malformed descriptors.
+func TestNewFile_ValidationErrors(t *testing.T) {
+	testCases := []struct {
+		name    string
+		deps    []*descriptorpb.FileDescriptorProto
+		fd      *descriptorpb.FileDescriptorProto
+		wantErr string
+	}{{
+		name: "field number reserved",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("field-number-reserved.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("BadMessage"),
+				ReservedRange: []*descriptorpb.DescriptorProto_ReservedRange{{
+					Start: scalar.Int32(3),
+					End:   scalar.Int32(4),
+				}},
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("good_field"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}, {
+					Name:   scalar.String("bad_field"),
+					Number: scalar.Int32(3),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}},
+			}},
+		},
+		wantErr: "reserved number 3",
+	}, {
+		name: "field name reserved",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("field-name-reserved.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name:         scalar.String("BadMessage"),
+				ReservedName: []string{"bad_field", "baz"},
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("good_field"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}, {
+					Name:   scalar.String("bad_field"),
+					Number: scalar.Int32(3),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}},
+			}},
+		},
+		wantErr: `reserved name "bad_field"`,
+	}, {
+		name: "normal field with extendee",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("extensible.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("ExtensibleMessage"),
+				ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+					Start: scalar.Int32(1000),
+					End:   scalar.Int32(2000),
+				}},
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("field-with-extendee.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("foo"),
+			Dependency: []string{"extensible.proto"},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("BadMessage"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("good_field"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}, {
+					Name:     scalar.String("bad_field"),
+					Number:   scalar.Int32(3),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+					Extendee: scalar.String(".foo.ExtensibleMessage"),
+				}},
+			}},
+		},
+		wantErr: "may not have extendee",
+	}, {
+		name: "type_name on int32 field",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("int32-with-type-name.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("AnEnum"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("BadMessage"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("good_field"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				}, {
+					Name:     scalar.String("bad_field"),
+					Number:   scalar.Int32(3),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+					TypeName: scalar.String("AnEnum"),
+				}},
+			}},
+		},
+		wantErr: "type_name",
+	}, {
+		name: "type_name on string extension",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("extensible.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("ExtensibleMessage"),
+				ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+					Start: scalar.Int32(1000),
+					End:   scalar.Int32(2000),
+				}},
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("string-ext-with-type-name.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("bar"),
+			Dependency: []string{"extensible.proto"},
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("AnEnum"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+			Extension: []*descriptorpb.FieldDescriptorProto{{
+				Name:     scalar.String("my_ext"),
+				Number:   scalar.Int32(1000),
+				Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Extendee: scalar.String(".foo.ExtensibleMessage"),
+				TypeName: scalar.String("AnEnum"),
+			}},
+		},
+		wantErr: "type_name",
+	}, {
+		name: "oneof_index on extension",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("extensible.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("ExtensibleMessage"),
+				ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+					Start: scalar.Int32(1000),
+					End:   scalar.Int32(2000),
+				}},
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("ext-with-oneof-index.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("bar"),
+			Dependency: []string{"extensible.proto"},
+			Extension: []*descriptorpb.FieldDescriptorProto{{
+				Name:       scalar.String("my_ext"),
+				Number:     scalar.Int32(1000),
+				Label:      descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:       descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Extendee:   scalar.String(".foo.ExtensibleMessage"),
+				OneofIndex: scalar.Int32(0),
+			}},
+		},
+		wantErr: "oneof_index",
+	}, {
+		name: "enum with reserved number",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("enum-with-reserved-number.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("AnEnum"),
+				ReservedRange: []*descriptorpb.EnumDescriptorProto_EnumReservedRange{{
+					Start: scalar.Int32(5),
+					End:   scalar.Int32(6),
+				}, {
+					Start: scalar.Int32(10),
+					End:   scalar.Int32(12),
+				}},
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}, {
+					Name:   scalar.String("FOO"),
+					Number: scalar.Int32(1),
+				}, {
+					Name:   scalar.String("BAR"),
+					Number: scalar.Int32(2),
+				}, {
+					Name:   scalar.String("BAD"),
+					Number: scalar.Int32(11),
+				}},
+			}},
+		},
+		wantErr: "reserved number 11",
+	}, {
+		name: "enum with reserved number",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("enum-with-reserved-name.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("ParentMessage"),
+				EnumType: []*descriptorpb.EnumDescriptorProto{{
+					Name:         scalar.String("AnEnum"),
+					ReservedName: []string{"ABC", "XYZ"},
+					Value: []*descriptorpb.EnumValueDescriptorProto{{
+						Name:   scalar.String("UNKNOWN"),
+						Number: scalar.Int32(0),
+					}, {
+						Name:   scalar.String("FOO"),
+						Number: scalar.Int32(1),
+					}, {
+						Name:   scalar.String("BAR"),
+						Number: scalar.Int32(2),
+					}, {
+						Name:   scalar.String("XYZ"),
+						Number: scalar.Int32(3),
+					}},
+				}},
+			}},
+		},
+		wantErr: `reserved name "XYZ"`,
+	}, {
+		name: "message dependency without import",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("foo.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Foo"),
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("message-dependency-without-import.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("bar"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Bar"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("foo"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: scalar.String(".foo.Foo"),
+				}},
+			}},
+		},
+		wantErr: "foo.Foo without import of foo.proto",
+	}, {
+		name: "enum dependency without import",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("foo.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("Foo"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("enum-dependency-without-import.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("bar"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Bar"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("foo"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.Foo"),
+				}},
+			}},
+		},
+		wantErr: "foo.Foo without import of foo.proto",
+	}, {
+		name: "message dependency on without import on file imported by a public import",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("foo.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Foo"),
+			}},
+		}, {
+			Name:       scalar.String("baz.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("foo"),
+			Dependency: []string{"foo.proto"},
+		}, {
+			Name:             scalar.String("old-baz.proto"),
+			Syntax:           scalar.String("proto2"),
+			Package:          scalar.String("foo"),
+			Dependency:       []string{"baz.proto"},
+			PublicDependency: []int32{0},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("message-dependency-without-import.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("bar"),
+			Dependency: []string{"old-baz.proto"},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Bar"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("foo"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: scalar.String(".foo.Foo"),
+				}},
+			}},
+		},
+		wantErr: "foo.Foo without import of foo.proto",
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := new(protoregistry.Files)
+			for _, dep := range tc.deps {
+				f, err := NewFile(dep, r)
+				if err != nil {
+					t.Fatalf("Error creating dependency: %v", err)
+				}
+				if err := r.Register(f); err != nil {
+					t.Fatalf("Error adding dependency: %v", err)
+				}
+			}
+			if _, err := NewFile(tc.fd, r); err == nil || !strings.Contains(err.Error(), tc.wantErr) {
+				t.Errorf("NewFile: got err = %v; want error containing %q", err, tc.wantErr)
+			}
+		})
+	}
+}
+
+// Sanity checks for well-formed descriptors. Most behavior with well-formed descriptors is covered
+// by other tests that rely on generated descriptors.
+func TestNewFile_ValidationOK(t *testing.T) {
+	testCases := []struct {
+		name string
+		deps []*descriptorpb.FileDescriptorProto
+		fd   *descriptorpb.FileDescriptorProto
+	}{{
+		name: "self contained file",
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:    scalar.String("self-contained.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("TopLevelEnum"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("TopLevelMessage"),
+				EnumType: []*descriptorpb.EnumDescriptorProto{{
+					Name: scalar.String("NestedEnum"),
+					Value: []*descriptorpb.EnumValueDescriptorProto{{
+						Name:   scalar.String("UNKNOWN"),
+						Number: scalar.Int32(0),
+					}},
+				}},
+				NestedType: []*descriptorpb.DescriptorProto{{
+					Name: scalar.String("NestedMessage"),
+				}},
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("top_level_enum"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.TopLevelEnum"),
+				}, {
+					Name:     scalar.String("nested_enum"),
+					Number:   scalar.Int32(3),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.TopLevelMessage.NestedEnum"),
+				}, {
+					Name:     scalar.String("nested_message"),
+					Number:   scalar.Int32(4),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.TopLevelMessage.NestedMessage"),
+				}},
+			}},
+		},
+	}, {
+		name: "external types with explicit import",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("foo.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("FooMessage"),
+			}},
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("BarEnum"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("external-types-with-explicit-import.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("bar"),
+			Dependency: []string{"foo.proto"},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Bar"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("foo"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: scalar.String(".foo.FooMessage"),
+				}, {
+					Name:     scalar.String("bar"),
+					Number:   scalar.Int32(3),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.BarEnum"),
+				}},
+			}},
+		},
+	}, {
+		name: "external types with transitive public imports",
+		deps: []*descriptorpb.FileDescriptorProto{{
+			Name:    scalar.String("quux.proto"),
+			Syntax:  scalar.String("proto2"),
+			Package: scalar.String("foo"),
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("QuuxMessage"),
+			}},
+		}, {
+			Name:             scalar.String("foo.proto"),
+			Syntax:           scalar.String("proto2"),
+			Package:          scalar.String("foo"),
+			Dependency:       []string{"quux.proto"},
+			PublicDependency: []int32{0},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("FooMessage"),
+			}},
+			EnumType: []*descriptorpb.EnumDescriptorProto{{
+				Name: scalar.String("BarEnum"),
+				Value: []*descriptorpb.EnumValueDescriptorProto{{
+					Name:   scalar.String("UNKNOWN"),
+					Number: scalar.Int32(0),
+				}},
+			}},
+		}, {
+			Name:             scalar.String("old-name.proto"),
+			Syntax:           scalar.String("proto2"),
+			Package:          scalar.String("foo"),
+			Dependency:       []string{"foo.proto"},
+			PublicDependency: []int32{0},
+		}},
+		fd: &descriptorpb.FileDescriptorProto{
+			Name:       scalar.String("external-types-with-public-import.proto"),
+			Syntax:     scalar.String("proto2"),
+			Package:    scalar.String("bar"),
+			Dependency: []string{"old-name.proto"},
+			MessageType: []*descriptorpb.DescriptorProto{{
+				Name: scalar.String("Bar"),
+				Field: []*descriptorpb.FieldDescriptorProto{{
+					Name:   scalar.String("id"),
+					Number: scalar.Int32(1),
+					Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				}, {
+					Name:     scalar.String("foo"),
+					Number:   scalar.Int32(2),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: scalar.String(".foo.FooMessage"),
+				}, {
+					Name:     scalar.String("bar"),
+					Number:   scalar.Int32(3),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+					TypeName: scalar.String(".foo.BarEnum"),
+				}, {
+					Name:     scalar.String("quux"),
+					Number:   scalar.Int32(4),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: scalar.String(".foo.QuuxMessage"),
+				}},
+			}},
+		},
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := new(protoregistry.Files)
+			for _, dep := range tc.deps {
+				f, err := NewFile(dep, r)
+				if err != nil {
+					t.Fatalf("Error creating dependency: %v", err)
+				}
+				if err := r.Register(f); err != nil {
+					t.Fatalf("Error adding dependency: %v", err)
+				}
+			}
+			if _, err := NewFile(tc.fd, r); err != nil {
+				t.Errorf("NewFile: got err = %v", err)
+			}
+		})
+	}
+}