reflect/prototype: initial commit

Add the prototype package which provides constructors for
protoreflect.{Enum,Message,Extension}Type.

Switch all usages of the internal/prototype equivalent to the new package.
Switch all custom implementions of {Enum,Message}Type to the new package.

Change-Id: Ia9dae6fed4f2b90e55c123627044a7faf098c4b1
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/178438
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/legacy/enum.go b/internal/legacy/enum.go
index d750f1e..d38aa8c 100644
--- a/internal/legacy/enum.go
+++ b/internal/legacy/enum.go
@@ -13,6 +13,7 @@
 	ptype "google.golang.org/protobuf/internal/prototype"
 	pvalue "google.golang.org/protobuf/internal/value"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/prototype"
 )
 
 // wrapEnum wraps v as a protoreflect.Enum,
@@ -33,16 +34,20 @@
 	}
 
 	// Slow-path: derive enum descriptor and initialize EnumType.
+	var et pref.EnumType
 	var m sync.Map // map[protoreflect.EnumNumber]proto.Enum
 	ed := LoadEnumDesc(t)
-	et := ptype.GoEnum(ed, func(et pref.EnumType, n pref.EnumNumber) pref.Enum {
-		if e, ok := m.Load(n); ok {
-			return e.(pref.Enum)
-		}
-		e := &enumWrapper{num: n, pbTyp: et, goTyp: t}
-		m.Store(n, e)
-		return e
-	})
+	et = &prototype.Enum{
+		EnumDescriptor: ed,
+		NewEnum: func(n pref.EnumNumber) pref.Enum {
+			if e, ok := m.Load(n); ok {
+				return e.(pref.Enum)
+			}
+			e := &enumWrapper{num: n, pbTyp: et, goTyp: t}
+			m.Store(n, e)
+			return e
+		},
+	}
 	if et, ok := enumTypeCache.LoadOrStore(t, et); ok {
 		return et.(pref.EnumType)
 	}
diff --git a/internal/legacy/export.go b/internal/legacy/export.go
index ec87a68..701b578 100644
--- a/internal/legacy/export.go
+++ b/internal/legacy/export.go
@@ -5,6 +5,7 @@
 package legacy
 
 import (
+	"fmt"
 	"reflect"
 
 	pimpl "google.golang.org/protobuf/internal/impl"
@@ -17,8 +18,8 @@
 // functions that we do not want to appear in godoc.
 type Export struct{}
 
-func (Export) EnumOf(e interface{}) pvalue.LegacyEnum {
-	return wrapEnum(reflect.ValueOf(e)).(pvalue.LegacyEnum)
+func (Export) EnumOf(e interface{}) pref.Enum {
+	return wrapEnum(reflect.ValueOf(e))
 }
 
 func (Export) EnumTypeOf(e interface{}) pref.EnumType {
@@ -29,8 +30,8 @@
 	return LoadEnumDesc(reflect.TypeOf(e))
 }
 
-func (Export) MessageOf(m interface{}) pvalue.LegacyMessage {
-	return wrapMessage(reflect.ValueOf(m)).ProtoReflect().(pvalue.LegacyMessage)
+func (Export) MessageOf(m interface{}) pref.Message {
+	return wrapMessage(reflect.ValueOf(m)).ProtoReflect()
 }
 
 func (Export) MessageTypeOf(m interface{}) pref.MessageType {
@@ -49,6 +50,61 @@
 	return extensionTypeFromDesc(d)
 }
 
+var (
+	enumIfaceV2    = reflect.TypeOf((*pref.Enum)(nil)).Elem()
+	messageIfaceV1 = reflect.TypeOf((*piface.MessageV1)(nil)).Elem()
+	messageIfaceV2 = reflect.TypeOf((*pref.ProtoMessage)(nil)).Elem()
+)
+
+func (Export) NewConverter(t reflect.Type, k pref.Kind) pvalue.Converter {
+	c, _ := newConverter(t, k)
+	return c
+}
+
+func newConverter(t reflect.Type, k pref.Kind) (pvalue.Converter, bool) {
+	switch k {
+	case pref.EnumKind:
+		if t.Kind() == reflect.Int32 && !t.Implements(enumIfaceV2) {
+			return pvalue.Converter{
+				PBValueOf: func(v reflect.Value) pref.Value {
+					if v.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
+					}
+					return pref.ValueOf(pref.EnumNumber(v.Int()))
+				},
+				GoValueOf: func(v pref.Value) reflect.Value {
+					return reflect.ValueOf(v.Enum()).Convert(t)
+				},
+				NewEnum: func(n pref.EnumNumber) pref.Enum {
+					return wrapEnum(reflect.ValueOf(n).Convert(t))
+				},
+			}, true
+		}
+	case pref.MessageKind, pref.GroupKind:
+		if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV1) && !t.Implements(messageIfaceV2) {
+			return pvalue.Converter{
+				PBValueOf: func(v reflect.Value) pref.Value {
+					if v.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
+					}
+					return pref.ValueOf(Export{}.MessageOf(v.Interface()))
+				},
+				GoValueOf: func(v pref.Value) reflect.Value {
+					rv := reflect.ValueOf(v.Message().(pvalue.Unwrapper).ProtoUnwrap())
+					if rv.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t))
+					}
+					return rv
+				},
+				NewMessage: func() pref.Message {
+					return wrapMessage(reflect.New(t.Elem())).ProtoReflect()
+				},
+			}, true
+		}
+	}
+	return pvalue.NewConverter(t, k), false
+}
+
 func init() {
 	pimpl.RegisterLegacyWrapper(Export{})
 }
diff --git a/internal/legacy/extension.go b/internal/legacy/extension.go
index 1f53f1b..c8cb3e9 100644
--- a/internal/legacy/extension.go
+++ b/internal/legacy/extension.go
@@ -16,6 +16,7 @@
 	pvalue "google.golang.org/protobuf/internal/value"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	preg "google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/reflect/prototype"
 	piface "google.golang.org/protobuf/runtime/protoiface"
 )
 
@@ -171,12 +172,19 @@
 	// Construct a v2 ExtensionType.
 	var ed pref.EnumDescriptor
 	var md pref.MessageDescriptor
-	conv := pvalue.NewLegacyConverter(t, f.Kind, Export{})
-	if conv.EnumType != nil {
-		ed = conv.EnumType.Descriptor()
-	}
-	if conv.MessageType != nil {
-		md = conv.MessageType.Descriptor()
+	switch f.Kind {
+	case pref.EnumKind:
+		if e, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
+			ed = e.Descriptor()
+		} else {
+			ed = LoadEnumDesc(t)
+		}
+	case pref.MessageKind, pref.GroupKind:
+		if m, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok {
+			md = m.ProtoReflect().Descriptor()
+		} else {
+			md = LoadMessageDesc(t)
+		}
 	}
 	xd, err := ptype.NewExtension(&ptype.StandaloneExtension{
 		FullName:     pref.FullName(d.Name),
@@ -207,17 +215,21 @@
 //
 // This is exported for testing purposes.
 func ExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
-	// Extension types for non-enums and non-messages are simple.
+	var conv pvalue.Converter
+	var isLegacy bool
+	xt := &prototype.Extension{ExtensionDescriptor: xd}
 	switch xd.Kind() {
-	case pref.EnumKind, pref.MessageKind, pref.GroupKind:
+	case pref.EnumKind:
+		conv, isLegacy = newConverter(t, xd.Kind())
+		xt.NewEnum = conv.NewEnum
+	case pref.MessageKind, pref.GroupKind:
+		conv, isLegacy = newConverter(t, xd.Kind())
+		xt.NewMessage = conv.NewMessage
 	default:
-		return ptype.GoExtension(xd, nil, nil)
+		// Extension types for non-enums and non-messages are simple.
+		return &prototype.Extension{ExtensionDescriptor: xd}
 	}
-
-	// Create an ExtensionType where GoType is the wrapper type.
-	conv := pvalue.NewLegacyConverter(t, xd.Kind(), Export{})
-	xt := ptype.GoExtension(xd, conv.EnumType, conv.MessageType)
-	if !conv.IsLegacy {
+	if !isLegacy {
 		return xt
 	}
 
diff --git a/internal/legacy/message.go b/internal/legacy/message.go
index 9919cdb..b17d705 100644
--- a/internal/legacy/message.go
+++ b/internal/legacy/message.go
@@ -15,6 +15,7 @@
 	pimpl "google.golang.org/protobuf/internal/impl"
 	ptype "google.golang.org/protobuf/internal/prototype"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/prototype"
 )
 
 // wrapMessage wraps v as a protoreflect.ProtoMessage,
@@ -38,10 +39,12 @@
 	md := LoadMessageDesc(t)
 	mt := new(pimpl.MessageInfo)
 	mt.GoType = t
-	mt.PBType = ptype.GoMessage(md, func(pref.MessageType) pref.Message {
-		p := reflect.New(t.Elem()).Interface()
-		return mt.MessageOf(p)
-	})
+	mt.PBType = &prototype.Message{
+		MessageDescriptor: md,
+		NewMessage: func() pref.Message {
+			return mt.MessageOf(reflect.New(t.Elem()).Interface())
+		},
+	}
 	if mt, ok := messageTypeCache.LoadOrStore(t, mt); ok {
 		return mt.(*pimpl.MessageInfo)
 	}