blob: 1c76674e4a6001c8d5c646cadc98d9e18dabf6f1 [file] [log] [blame]
Joe Tsai08e00302018-11-26 22:32:06 -08001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package legacy
6
7import (
8 "fmt"
9 "reflect"
Joe Tsai62517cc2018-12-04 14:00:01 -080010 "sync"
Joe Tsai08e00302018-11-26 22:32:06 -080011
12 papi "github.com/golang/protobuf/protoapi"
13 ptag "github.com/golang/protobuf/v2/internal/encoding/tag"
14 pimpl "github.com/golang/protobuf/v2/internal/impl"
15 pvalue "github.com/golang/protobuf/v2/internal/value"
16 pref "github.com/golang/protobuf/v2/reflect/protoreflect"
17 ptype "github.com/golang/protobuf/v2/reflect/prototype"
18)
19
Joe Tsai62517cc2018-12-04 14:00:01 -080020// extensionDescKey is a comparable version of protoapi.ExtensionDesc
21// suitable for use as a key in a map.
22type extensionDescKey struct {
23 typeV2 pref.ExtensionType
24 extendedType reflect.Type
25 extensionType reflect.Type
26 field int32
27 name string
28 tag string
29 filename string
30}
31
32func extensionDescKeyOf(d *papi.ExtensionDesc) extensionDescKey {
33 return extensionDescKey{
34 d.Type,
35 reflect.TypeOf(d.ExtendedType),
36 reflect.TypeOf(d.ExtensionType),
37 d.Field, d.Name, d.Tag, d.Filename,
38 }
39}
40
41var (
42 extensionTypeCache sync.Map // map[extensionDescKey]protoreflect.ExtensionType
43 extensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoapi.ExtensionDesc
44)
45
Joe Tsai6dbffb72018-12-04 14:06:19 -080046// extensionDescFromType converts a v2 protoreflect.ExtensionType to a
Joe Tsai62517cc2018-12-04 14:00:01 -080047// v1 protoapi.ExtensionDesc. The returned ExtensionDesc must not be mutated.
Joe Tsai6dbffb72018-12-04 14:06:19 -080048func extensionDescFromType(t pref.ExtensionType) *papi.ExtensionDesc {
Joe Tsai62517cc2018-12-04 14:00:01 -080049 // Fast-path: check the cache for whether this ExtensionType has already
50 // been converted to a legacy descriptor.
51 if d, ok := extensionDescCache.Load(t); ok {
52 return d.(*papi.ExtensionDesc)
Joe Tsai08e00302018-11-26 22:32:06 -080053 }
54
55 // Determine the parent type if possible.
56 var parent papi.Message
57 if mt, ok := t.ExtendedType().(pref.MessageType); ok {
58 // Create a new parent message and unwrap it if possible.
59 mv := mt.New()
60 t := reflect.TypeOf(mv)
61 if mv, ok := mv.(pvalue.Unwrapper); ok {
62 t = reflect.TypeOf(mv.ProtoUnwrap())
63 }
64
65 // Check whether the message implements the legacy v1 Message interface.
66 mz := reflect.Zero(t).Interface()
67 if mz, ok := mz.(papi.Message); ok {
68 parent = mz
69 }
70 }
71
72 // Determine the v1 extension type, which is unfortunately not the same as
73 // the v2 ExtensionType.GoType.
74 extType := t.GoType()
75 switch extType.Kind() {
76 case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
77 extType = reflect.PtrTo(extType) // T -> *T for singular scalar fields
78 case reflect.Ptr:
79 if extType.Elem().Kind() == reflect.Slice {
80 extType = extType.Elem() // *[]T -> []T for repeated fields
81 }
82 }
83
84 // Reconstruct the legacy enum full name, which is an odd mixture of the
85 // proto package name with the Go type name.
86 var enumName string
87 if t.Kind() == pref.EnumKind {
88 // Derive Go type name.
89 // For legacy enums, unwrap the wrapper to get the underlying Go type.
90 et := t.EnumType().(pref.EnumType)
91 var ev interface{} = et.New(0)
92 if u, ok := ev.(pvalue.Unwrapper); ok {
93 ev = u.ProtoUnwrap()
94 }
95 enumName = reflect.TypeOf(ev).Name()
96
97 // Derive the proto package name.
98 // For legacy enums, obtain the proto package from the raw descriptor.
99 var protoPkg string
100 if fd := parentFileDescriptor(et); fd != nil {
101 protoPkg = string(fd.Package())
102 }
Joe Tsai6dbffb72018-12-04 14:06:19 -0800103 if ed, ok := ev.(enumV1); ok && protoPkg == "" {
Joe Tsai08e00302018-11-26 22:32:06 -0800104 b, _ := ed.EnumDescriptor()
Joe Tsai6dbffb72018-12-04 14:06:19 -0800105 protoPkg = loadFileDesc(b).GetPackage()
Joe Tsai08e00302018-11-26 22:32:06 -0800106 }
107
108 if protoPkg != "" {
109 enumName = protoPkg + "." + enumName
110 }
111 }
112
113 // Derive the proto file that the extension was declared within.
114 var filename string
115 if fd := parentFileDescriptor(t); fd != nil {
116 filename = fd.Path()
117 }
118
119 // Construct and return a v1 ExtensionDesc.
Joe Tsai62517cc2018-12-04 14:00:01 -0800120 d := &papi.ExtensionDesc{
Joe Tsai08e00302018-11-26 22:32:06 -0800121 Type: t,
122 ExtendedType: parent,
123 ExtensionType: reflect.Zero(extType).Interface(),
124 Field: int32(t.Number()),
125 Name: string(t.FullName()),
126 Tag: ptag.Marshal(t, enumName),
127 Filename: filename,
128 }
Joe Tsai62517cc2018-12-04 14:00:01 -0800129 extensionDescCache.Store(t, d)
130 return d
Joe Tsai08e00302018-11-26 22:32:06 -0800131}
132
Joe Tsai6dbffb72018-12-04 14:06:19 -0800133// extensionTypeFromDesc converts a v1 protoapi.ExtensionDesc to a
Joe Tsai62517cc2018-12-04 14:00:01 -0800134// v2 protoreflect.ExtensionType. The returned descriptor type takes ownership
135// of the input extension desc. The input must not be mutated so long as the
136// returned type is still in use.
Joe Tsai6dbffb72018-12-04 14:06:19 -0800137func extensionTypeFromDesc(d *papi.ExtensionDesc) pref.ExtensionType {
Joe Tsai62517cc2018-12-04 14:00:01 -0800138 // Fast-path: check whether an extension type is already nested within.
Joe Tsai08e00302018-11-26 22:32:06 -0800139 if d.Type != nil {
Joe Tsai6dbffb72018-12-04 14:06:19 -0800140 // Cache descriptor for future extensionDescFromType operation.
Joe Tsai62517cc2018-12-04 14:00:01 -0800141 // This assumes that there is only one legacy protoapi.ExtensionDesc
142 // that wraps any given specific protoreflect.ExtensionType.
143 extensionDescCache.LoadOrStore(d.Type, d)
144 return d.Type
145 }
146
147 // Fast-path: check the cache for whether this ExtensionType has already
148 // been converted from a legacy descriptor.
149 dk := extensionDescKeyOf(d)
150 if t, ok := extensionTypeCache.Load(dk); ok {
151 return t.(pref.ExtensionType)
Joe Tsai08e00302018-11-26 22:32:06 -0800152 }
153
154 // Derive basic field information from the struct tag.
155 t := reflect.TypeOf(d.ExtensionType)
156 isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
157 isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
158 if isOptional || isRepeated {
159 t = t.Elem()
160 }
161 f := ptag.Unmarshal(d.Tag, t)
162
163 // Construct a v2 ExtensionType.
164 conv := pvalue.NewLegacyConverter(t, f.Kind, Export{})
165 xd, err := ptype.NewExtension(&ptype.StandaloneExtension{
166 FullName: pref.FullName(d.Name),
167 Number: pref.FieldNumber(d.Field),
168 Cardinality: f.Cardinality,
169 Kind: f.Kind,
170 Default: f.Default,
171 Options: f.Options,
172 EnumType: conv.EnumType,
173 MessageType: conv.MessageType,
174 ExtendedType: Export{}.MessageTypeOf(d.ExtendedType),
175 })
176 if err != nil {
177 panic(err)
178 }
179 xt := pimpl.Export{}.ExtensionTypeOf(xd, reflect.Zero(t).Interface())
Joe Tsai08e00302018-11-26 22:32:06 -0800180
Joe Tsai62517cc2018-12-04 14:00:01 -0800181 // Cache the conversion for both directions.
182 extensionDescCache.Store(xt, d)
183 extensionTypeCache.Store(dk, xt)
184 return xt
Joe Tsai08e00302018-11-26 22:32:06 -0800185}
186
Joe Tsai6dbffb72018-12-04 14:06:19 -0800187// extensionTypeOf returns a protoreflect.ExtensionType where the GoType
Joe Tsai08e00302018-11-26 22:32:06 -0800188// is the underlying v1 Go type instead of the wrapper types used to present
189// v1 Go types as if they satisfied the v2 API.
190//
191// This function is only valid if xd.Kind is an enum or message.
Joe Tsai6dbffb72018-12-04 14:06:19 -0800192func extensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
Joe Tsai08e00302018-11-26 22:32:06 -0800193 // Step 1: Create an ExtensionType where GoType is the wrapper type.
194 conv := pvalue.NewLegacyConverter(t, xd.Kind(), Export{})
195 xt := ptype.GoExtension(xd, conv.EnumType, conv.MessageType)
196
197 // Step 2: Wrap ExtensionType such that GoType presents the legacy Go type.
Joe Tsai6dbffb72018-12-04 14:06:19 -0800198 xt2 := &extensionType{ExtensionType: xt}
Joe Tsai08e00302018-11-26 22:32:06 -0800199 if xd.Cardinality() != pref.Repeated {
200 xt2.typ = t
201 xt2.new = func() interface{} {
202 return xt.New().(pvalue.Unwrapper).ProtoUnwrap()
203 }
204 xt2.valueOf = func(v interface{}) pref.Value {
205 if reflect.TypeOf(v) != xt2.typ {
206 panic(fmt.Sprintf("invalid type: got %T, want %v", v, xt2.typ))
207 }
208 if xd.Kind() == pref.EnumKind {
209 return xt.ValueOf(Export{}.EnumOf(v))
210 } else {
211 return xt.ValueOf(Export{}.MessageOf(v))
212 }
213 }
214 xt2.interfaceOf = func(v pref.Value) interface{} {
215 return xt.InterfaceOf(v).(pvalue.Unwrapper).ProtoUnwrap()
216 }
217 } else {
218 xt2.typ = reflect.PtrTo(reflect.SliceOf(t))
219 xt2.new = func() interface{} {
220 return reflect.New(xt2.typ.Elem()).Interface()
221 }
222 xt2.valueOf = func(v interface{}) pref.Value {
223 if reflect.TypeOf(v) != xt2.typ {
224 panic(fmt.Sprintf("invalid type: got %T, want %v", v, xt2.typ))
225 }
226 return pref.ValueOf(pvalue.ListOf(v, conv))
227 }
228 xt2.interfaceOf = func(pv pref.Value) interface{} {
229 v := pv.List().(pvalue.Unwrapper).ProtoUnwrap()
230 if reflect.TypeOf(v) != xt2.typ {
231 panic(fmt.Sprintf("invalid type: got %T, want %v", v, xt2.typ))
232 }
233 return v
234 }
235 }
236 return xt2
237}
238
Joe Tsai6dbffb72018-12-04 14:06:19 -0800239type extensionType struct {
Joe Tsai08e00302018-11-26 22:32:06 -0800240 pref.ExtensionType
241 typ reflect.Type
242 new func() interface{}
243 valueOf func(interface{}) pref.Value
244 interfaceOf func(pref.Value) interface{}
245}
246
Joe Tsai6dbffb72018-12-04 14:06:19 -0800247func (x *extensionType) GoType() reflect.Type { return x.typ }
248func (x *extensionType) New() interface{} { return x.new() }
249func (x *extensionType) ValueOf(v interface{}) pref.Value { return x.valueOf(v) }
250func (x *extensionType) InterfaceOf(v pref.Value) interface{} { return x.interfaceOf(v) }
Joe Tsai08e00302018-11-26 22:32:06 -0800251
252// TODO: Provide custom stringer with the new GoType.