reflect/protoreflect: add ExtensionType IsValid{Interface,Value} methods

Add a way to typecheck a Value or interface{} without converting it to
the other form.  This permits implementations which store field values as
a Value (such as dynamicpb, or (soon) extensions in generated messages)
to validate inputs without an unnecessary conversion.

Fixes golang/protobuf#905

Change-Id: I1b78612b22ae832efbb55f81ae420871729e3a02
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/192457
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/internal/impl/extension_test.go b/internal/impl/extension_test.go
new file mode 100644
index 0000000..d6353ed
--- /dev/null
+++ b/internal/impl/extension_test.go
@@ -0,0 +1,130 @@
+// Copyright 2019 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.
+
+package impl_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	cmp "github.com/google/go-cmp/cmp"
+	testpb "google.golang.org/protobuf/internal/testprotos/test"
+	pref "google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func TestExtensionType(t *testing.T) {
+	cmpOpts := cmp.Options{
+		cmp.Comparer(func(x, y proto.Message) bool {
+			return proto.Equal(x, y)
+		}),
+	}
+	for _, test := range []struct {
+		xt    pref.ExtensionType
+		value interface{}
+	}{
+		{
+			xt:    testpb.E_OptionalInt32Extension,
+			value: int32(0),
+		},
+		{
+			xt:    testpb.E_OptionalInt64Extension,
+			value: int64(0),
+		},
+		{
+			xt:    testpb.E_OptionalUint32Extension,
+			value: uint32(0),
+		},
+		{
+			xt:    testpb.E_OptionalUint64Extension,
+			value: uint64(0),
+		},
+		{
+			xt:    testpb.E_OptionalFloatExtension,
+			value: float32(0),
+		},
+		{
+			xt:    testpb.E_OptionalDoubleExtension,
+			value: float64(0),
+		},
+		{
+			xt:    testpb.E_OptionalBoolExtension,
+			value: true,
+		},
+		{
+			xt:    testpb.E_OptionalStringExtension,
+			value: "",
+		},
+		{
+			xt:    testpb.E_OptionalBytesExtension,
+			value: []byte{},
+		},
+		{
+			xt:    testpb.E_OptionalNestedMessageExtension,
+			value: &testpb.TestAllTypes_NestedMessage{},
+		},
+		{
+			xt:    testpb.E_OptionalNestedEnumExtension,
+			value: testpb.TestAllTypes_FOO,
+		},
+		{
+			xt:    testpb.E_RepeatedInt32Extension,
+			value: []int32{0},
+		},
+		{
+			xt:    testpb.E_RepeatedInt64Extension,
+			value: []int64{0},
+		},
+		{
+			xt:    testpb.E_RepeatedUint32Extension,
+			value: []uint32{0},
+		},
+		{
+			xt:    testpb.E_RepeatedUint64Extension,
+			value: []uint64{0},
+		},
+		{
+			xt:    testpb.E_RepeatedFloatExtension,
+			value: []float32{0},
+		},
+		{
+			xt:    testpb.E_RepeatedDoubleExtension,
+			value: []float64{0},
+		},
+		{
+			xt:    testpb.E_RepeatedBoolExtension,
+			value: []bool{true},
+		},
+		{
+			xt:    testpb.E_RepeatedStringExtension,
+			value: []string{""},
+		},
+		{
+			xt:    testpb.E_RepeatedBytesExtension,
+			value: [][]byte{nil},
+		},
+		{
+			xt:    testpb.E_RepeatedNestedMessageExtension,
+			value: []*testpb.TestAllTypes_NestedMessage{{}},
+		},
+		{
+			xt:    testpb.E_RepeatedNestedEnumExtension,
+			value: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO},
+		},
+	} {
+		name := test.xt.TypeDescriptor().FullName()
+		t.Run(fmt.Sprint(name), func(t *testing.T) {
+			if !test.xt.IsValidInterface(test.value) {
+				t.Fatalf("IsValidInterface(%[1]T(%[1]v)) = false, want true", test.value)
+			}
+			v := test.xt.ValueOf(test.value)
+			if !test.xt.IsValidValue(v) {
+				t.Fatalf("IsValidValue(%[1]T(%[1]v)) = false, want true", v)
+			}
+			if got, want := test.xt.InterfaceOf(v), test.value; !cmp.Equal(got, want, cmpOpts) {
+				t.Fatalf("round trip InterfaceOf(ValueOf(x)) = %v, want %v", got, want)
+			}
+		})
+	}
+}