blob: 2da4d71b51040c5330a3ecc6b6ac62b62d66d496 [file] [log] [blame]
// 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.
package impl
import (
"reflect"
"sync"
ptag "google.golang.org/protobuf/internal/encoding/tag"
"google.golang.org/protobuf/internal/filedesc"
pref "google.golang.org/protobuf/reflect/protoreflect"
preg "google.golang.org/protobuf/reflect/protoregistry"
piface "google.golang.org/protobuf/runtime/protoiface"
)
// legacyExtensionDescKey is a comparable version of protoiface.ExtensionDescV1
// suitable for use as a key in a map.
type legacyExtensionDescKey struct {
typeV2 pref.ExtensionType
extendedType reflect.Type
extensionType reflect.Type
field int32
name string
tag string
filename string
}
func legacyExtensionDescKeyOf(d *piface.ExtensionDescV1) legacyExtensionDescKey {
return legacyExtensionDescKey{
d.Type,
reflect.TypeOf(d.ExtendedType),
reflect.TypeOf(d.ExtensionType),
d.Field, d.Name, d.Tag, d.Filename,
}
}
var (
legacyExtensionTypeCache sync.Map // map[legacyExtensionDescKey]protoreflect.ExtensionType
legacyExtensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoiface.ExtensionDescV1
)
// legacyExtensionDescFromType converts a v2 protoreflect.ExtensionType to a
// protoiface.ExtensionDescV1. The returned ExtensionDesc must not be mutated.
func legacyExtensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 {
// Fast-path: check whether an extension desc is already nested within.
if xt, ok := xt.(interface {
ProtoLegacyExtensionDesc() *piface.ExtensionDescV1
}); ok {
if d := xt.ProtoLegacyExtensionDesc(); d != nil {
return d
}
}
// Fast-path: check the cache for whether this ExtensionType has already
// been converted to a legacy descriptor.
if d, ok := legacyExtensionDescCache.Load(xt); ok {
return d.(*piface.ExtensionDescV1)
}
// Determine the parent type if possible.
xd := xt.Descriptor()
var parent piface.MessageV1
messageName := xd.ContainingMessage().FullName()
if mt, _ := preg.GlobalTypes.FindMessageByName(messageName); mt != nil {
// Create a new parent message and unwrap it if possible.
mv := mt.New().Interface()
t := reflect.TypeOf(mv)
if mv, ok := mv.(Unwrapper); ok {
t = reflect.TypeOf(mv.ProtoUnwrap())
}
// Check whether the message implements the legacy v1 Message interface.
mz := reflect.Zero(t).Interface()
if mz, ok := mz.(piface.MessageV1); ok {
parent = mz
}
}
// Determine the v1 extension type, which is unfortunately not the same as
// the v2 ExtensionType.GoType.
extType := xt.GoType()
switch extType.Kind() {
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
extType = reflect.PtrTo(extType) // T -> *T for singular scalar fields
case reflect.Ptr:
if extType.Elem().Kind() == reflect.Slice {
extType = extType.Elem() // *[]T -> []T for repeated fields
}
}
// Reconstruct the legacy enum full name, which is an odd mixture of the
// proto package name with the Go type name.
var enumName string
if xd.Kind() == pref.EnumKind {
// Derive Go type name.
t := extType
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
t = t.Elem()
}
enumName = t.Name()
// Derive the proto package name.
// For legacy enums, obtain the proto package from the raw descriptor.
var protoPkg string
if fd := xd.Enum().ParentFile(); fd != nil {
protoPkg = string(fd.Package())
}
if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" {
b, _ := ed.EnumDescriptor()
protoPkg = string(legacyLoadFileDesc(b).Package())
}
if protoPkg != "" {
enumName = protoPkg + "." + enumName
}
}
// Derive the proto file that the extension was declared within.
var filename string
if fd := xd.ParentFile(); fd != nil {
filename = fd.Path()
}
// Construct and return a ExtensionDescV1.
d := &piface.ExtensionDescV1{
Type: xt,
ExtendedType: parent,
ExtensionType: reflect.Zero(extType).Interface(),
Field: int32(xd.Number()),
Name: string(xd.FullName()),
Tag: ptag.Marshal(xd, enumName),
Filename: filename,
}
if d, ok := legacyExtensionDescCache.LoadOrStore(xt, d); ok {
return d.(*piface.ExtensionDescV1)
}
return d
}
// legacyExtensionTypeFromDesc converts a protoiface.ExtensionDescV1 to a
// v2 protoreflect.ExtensionType. The returned descriptor type takes ownership
// of the input extension desc. The input must not be mutated so long as the
// returned type is still in use.
func legacyExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
// Fast-path: check whether an extension type is already nested within.
if d.Type != nil {
return d.Type
}
// Fast-path: check the cache for whether this ExtensionType has already
// been converted from a legacy descriptor.
dk := legacyExtensionDescKeyOf(d)
if t, ok := legacyExtensionTypeCache.Load(dk); ok {
return t.(pref.ExtensionType)
}
// Resolve enum or message dependencies.
var ed pref.EnumDescriptor
var md pref.MessageDescriptor
t := reflect.TypeOf(d.ExtensionType)
isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
if isOptional || isRepeated {
t = t.Elem()
}
switch v := reflect.Zero(t).Interface().(type) {
case pref.Enum:
ed = v.Descriptor()
case enumV1:
ed = LegacyLoadEnumDesc(t)
case pref.ProtoMessage:
md = v.ProtoReflect().Descriptor()
case messageV1:
md = LegacyLoadMessageDesc(t)
}
// Derive basic field information from the struct tag.
var evs pref.EnumValueDescriptors
if ed != nil {
evs = ed.Values()
}
fd := ptag.Unmarshal(d.Tag, t, evs).(*filedesc.Field)
// Construct a v2 ExtensionType.
xd := &filedesc.Extension{L2: new(filedesc.ExtensionL2)}
xd.L0.ParentFile = filedesc.SurrogateProto2
xd.L0.FullName = pref.FullName(d.Name)
xd.L1.Number = pref.FieldNumber(d.Field)
xd.L2.Cardinality = fd.L1.Cardinality
xd.L1.Kind = fd.L1.Kind
xd.L2.IsPacked = fd.L1.IsPacked
xd.L2.Default = fd.L1.Default
xd.L1.Extendee = Export{}.MessageDescriptorOf(d.ExtendedType)
xd.L2.Enum = ed
xd.L2.Message = md
tt := reflect.TypeOf(d.ExtensionType)
if isOptional {
tt = tt.Elem()
} else if isRepeated {
tt = reflect.PtrTo(tt)
}
xt := LegacyExtensionTypeOf(xd, tt)
// Cache the conversion for both directions.
legacyExtensionDescCache.LoadOrStore(xt, d)
if xt, ok := legacyExtensionTypeCache.LoadOrStore(dk, xt); ok {
return xt.(pref.ExtensionType)
}
return xt
}
// LegacyExtensionTypeOf returns a protoreflect.ExtensionType where the
// element type of the field is t.
//
// This is exported for testing purposes.
func LegacyExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
xt := &legacyExtensionType{
typ: t,
conv: NewConverter(t, xd),
}
xt.desc = &extDesc{xd, xt}
return xt
}
type legacyExtensionType struct {
desc pref.ExtensionTypeDescriptor
typ reflect.Type
conv Converter
}
func (x *legacyExtensionType) GoType() reflect.Type { return x.typ }
func (x *legacyExtensionType) New() pref.Value { return x.conv.New() }
func (x *legacyExtensionType) Zero() pref.Value { return x.conv.Zero() }
func (x *legacyExtensionType) ValueOf(v interface{}) pref.Value {
return x.conv.PBValueOf(reflect.ValueOf(v))
}
func (x *legacyExtensionType) InterfaceOf(v pref.Value) interface{} {
return x.conv.GoValueOf(v).Interface()
}
func (x *legacyExtensionType) Descriptor() pref.ExtensionTypeDescriptor { return x.desc }
type extDesc struct {
pref.ExtensionDescriptor
xt *legacyExtensionType
}
func (t *extDesc) Type() pref.ExtensionType { return t.xt }
func (t *extDesc) Descriptor() pref.ExtensionDescriptor { return t.ExtensionDescriptor }