reflect/protoregistry: add conflict override

The ignoreConflict function provides the ability to ignore certain conflicts.
By default, all conflicts are ignored with a log message produced instead.

Change-Id: I67fe56eef492e12421e5c8cb8d618dc2a46c82ed
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/186658
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/filedesc/build.go b/internal/filedesc/build.go
index 52cbdf1..427e26e 100644
--- a/internal/filedesc/build.go
+++ b/internal/filedesc/build.go
@@ -6,8 +6,6 @@
 package filedesc
 
 import (
-	"log"
-
 	"google.golang.org/protobuf/internal/encoding/wire"
 	"google.golang.org/protobuf/internal/fieldnum"
 	"google.golang.org/protobuf/reflect/protoreflect"
@@ -107,7 +105,7 @@
 	out.Services = fd.allServices
 
 	if err := db.FileRegistry.Register(fd); err != nil {
-		CheckRegistryError(err)
+		panic(err)
 	}
 	return out
 }
@@ -152,13 +150,3 @@
 		}
 	}
 }
-
-// CheckRegistryError handles registration errors.
-// It is a variable so that its behavior can be replaced in another source file.
-var CheckRegistryError = func(err error) {
-	log.Printf(""+
-		"WARNING: %v\n"+
-		"A future release will panic on registration conflicts.\n"+
-		// TODO: Add a URL pointing to documentation on how to resolve conflicts.
-		"\n", err)
-}
diff --git a/internal/filetype/build.go b/internal/filetype/build.go
index 3428040..fa1bb9a 100644
--- a/internal/filetype/build.go
+++ b/internal/filetype/build.go
@@ -155,7 +155,7 @@
 
 			// Register enum types.
 			if err := tb.TypeRegistry.Register(&out.Enums[i]); err != nil {
-				fdesc.CheckRegistryError(err)
+				panic(err)
 			}
 		}
 	}
@@ -183,7 +183,7 @@
 
 			// Register message types.
 			if err := tb.TypeRegistry.Register(&out.Messages[i]); err != nil {
-				fdesc.CheckRegistryError(err)
+				panic(err)
 			}
 		}
 
@@ -251,7 +251,7 @@
 
 			// Register extension types.
 			if err := tb.TypeRegistry.Register(&out.Extensions[i]); err != nil {
-				fdesc.CheckRegistryError(err)
+				panic(err)
 			}
 		}
 	}
diff --git a/reflect/protoregistry/registry.go b/reflect/protoregistry/registry.go
index 33552f1..4abfc10 100644
--- a/reflect/protoregistry/registry.go
+++ b/reflect/protoregistry/registry.go
@@ -17,6 +17,7 @@
 
 import (
 	"fmt"
+	"log"
 	"reflect"
 	"strings"
 
@@ -24,6 +25,18 @@
 	"google.golang.org/protobuf/reflect/protoreflect"
 )
 
+// ignoreConflict reports whether to ignore a registration conflict
+// given the descriptor being registered and the error.
+// It is a variable so that the behavior is easily overridden in another file.
+var ignoreConflict = func(d protoreflect.Descriptor, err error) bool {
+	log.Printf(""+
+		"WARNING: %v\n"+
+		"A future release will panic on registration conflicts.\n"+
+		// TODO: Add a URL pointing to documentation on how to resolve conflicts.
+		"\n", err)
+	return true
+}
+
 // GlobalFiles is a global registry of file descriptors.
 var GlobalFiles *Files = new(Files)
 
@@ -92,25 +105,38 @@
 	path := fd.Path()
 	if prev := r.filesByPath[path]; prev != nil {
 		err := errors.New("file %q is already registered", fd.Path())
-		return amendErrorWithCaller(err, prev, fd)
+		err = amendErrorWithCaller(err, prev, fd)
+		if r == GlobalFiles && ignoreConflict(fd, err) {
+			err = nil
+		}
+		return err
 	}
 
 	for name := fd.Package(); name != ""; name = name.Parent() {
 		switch prev := r.descsByName[name]; prev.(type) {
 		case nil, *packageDescriptor:
 		default:
-			err := errors.New("file %q has a name conflict over %v", fd.Path(), name)
-			return amendErrorWithCaller(err, prev, fd)
+			err := errors.New("file %q has a package name conflict over %v", fd.Path(), name)
+			err = amendErrorWithCaller(err, prev, fd)
+			if r == GlobalFiles && ignoreConflict(fd, err) {
+				err = nil
+			}
+			return err
 		}
 	}
 	var err error
+	var hasConflict bool
 	rangeTopLevelDescriptors(fd, func(d protoreflect.Descriptor) {
 		if prev := r.descsByName[d.FullName()]; prev != nil {
+			hasConflict = true
 			err = errors.New("file %q has a name conflict over %v", fd.Path(), d.FullName())
 			err = amendErrorWithCaller(err, prev, fd)
+			if r == GlobalFiles && ignoreConflict(d, err) {
+				err = nil
+			}
 		}
 	})
-	if err != nil {
+	if hasConflict {
 		return err
 	}
 
@@ -291,6 +317,7 @@
 // Type is an interface satisfied by protoreflect.EnumType,
 // protoreflect.MessageType, or protoreflect.ExtensionType.
 type Type interface {
+	protoreflect.Descriptor
 	GoType() reflect.Type
 }
 
@@ -413,9 +440,13 @@
 				panic(fmt.Sprintf("invalid type: %T", t))
 			}
 			if prev := r.typesByName[name]; prev != nil {
+				err := errors.New("%v %v is already registered", typeName(typ), name)
+				err = amendErrorWithCaller(err, prev, typ)
+				if r == GlobalTypes && ignoreConflict(typ, err) {
+					err = nil
+				}
 				if firstErr == nil {
-					err := errors.New("%v %v is already registered", typeName(typ), name)
-					firstErr = amendErrorWithCaller(err, prev, typ)
+					firstErr = err
 				}
 				continue typeLoop
 			}
@@ -425,9 +456,13 @@
 				field := xt.Number()
 				message := xt.ContainingMessage().FullName()
 				if prev := r.extensionsByMessage[message][field]; prev != nil {
+					err := errors.New("extension number %d is already registered on message %v", field, message)
+					err = amendErrorWithCaller(err, prev, typ)
+					if r == GlobalTypes && ignoreConflict(typ, err) {
+						err = nil
+					}
 					if firstErr == nil {
-						err := errors.New("extension number %d is already registered on message %v", field, message)
-						firstErr = amendErrorWithCaller(err, prev, typ)
+						firstErr = err
 					}
 					continue typeLoop
 				}
diff --git a/reflect/protoregistry/registry_test.go b/reflect/protoregistry/registry_test.go
index 3d87651..63dd1ba 100644
--- a/reflect/protoregistry/registry_test.go
+++ b/reflect/protoregistry/registry_test.go
@@ -129,7 +129,7 @@
 			inFile: mustMakeFile(`syntax:"proto2" name:"test2a.proto" enum_type:[{name:"foo" value:[{name:"VALUE" number:0}]}]`),
 		}, {
 			inFile:  mustMakeFile(`syntax:"proto2" name:"test2b.proto" package:"foo.bar.baz"`),
-			wantErr: `file "test2b.proto" has a name conflict over foo`,
+			wantErr: `file "test2b.proto" has a package name conflict over foo`,
 		}},
 	}, {
 		// Test when new enum conflicts with existing enum in same package.