blob: e6e86fb5ce3816557b18c0d518b89bb129e63df3 [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
Joe Tsai21ade492019-05-22 13:42:54 -04005package impl
Joe Tsai08e00302018-11-26 22:32:06 -08006
7import (
8 "fmt"
9 "reflect"
Joe Tsai62517cc2018-12-04 14:00:01 -080010 "sync"
Joe Tsai08e00302018-11-26 22:32:06 -080011
Joe Tsai63bdd1f2019-05-22 05:22:04 -040012 "google.golang.org/protobuf/internal/descfmt"
Damien Neile89e6242019-05-13 23:55:40 -070013 ptag "google.golang.org/protobuf/internal/encoding/tag"
Joe Tsaid8881392019-06-06 13:01:53 -070014 "google.golang.org/protobuf/internal/filedesc"
Damien Neile89e6242019-05-13 23:55:40 -070015 pref "google.golang.org/protobuf/reflect/protoreflect"
16 preg "google.golang.org/protobuf/reflect/protoregistry"
17 piface "google.golang.org/protobuf/runtime/protoiface"
Joe Tsai08e00302018-11-26 22:32:06 -080018)
19
Joe Tsai21ade492019-05-22 13:42:54 -040020// legacyExtensionDescKey is a comparable version of protoiface.ExtensionDescV1
Joe Tsai62517cc2018-12-04 14:00:01 -080021// suitable for use as a key in a map.
Joe Tsai21ade492019-05-22 13:42:54 -040022type legacyExtensionDescKey struct {
Joe Tsai62517cc2018-12-04 14:00:01 -080023 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
Joe Tsai21ade492019-05-22 13:42:54 -040032func legacyExtensionDescKeyOf(d *piface.ExtensionDescV1) legacyExtensionDescKey {
33 return legacyExtensionDescKey{
Joe Tsai62517cc2018-12-04 14:00:01 -080034 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 (
Joe Tsai21ade492019-05-22 13:42:54 -040042 legacyExtensionTypeCache sync.Map // map[legacyExtensionDescKey]protoreflect.ExtensionType
43 legacyExtensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoiface.ExtensionDescV1
Joe Tsai62517cc2018-12-04 14:00:01 -080044)
45
Joe Tsai21ade492019-05-22 13:42:54 -040046// legacyExtensionDescFromType converts a v2 protoreflect.ExtensionType to a
Joe Tsai4fddeba2019-03-20 18:29:32 -070047// protoiface.ExtensionDescV1. The returned ExtensionDesc must not be mutated.
Joe Tsai21ade492019-05-22 13:42:54 -040048func legacyExtensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 {
Joe Tsaiafb455e2019-03-14 16:08:22 -070049 // Fast-path: check whether an extension desc is already nested within.
Joe Tsai0fc49f82019-05-01 12:29:25 -070050 if xt, ok := xt.(interface {
Joe Tsai4fddeba2019-03-20 18:29:32 -070051 ProtoLegacyExtensionDesc() *piface.ExtensionDescV1
52 }); ok {
Joe Tsai0fc49f82019-05-01 12:29:25 -070053 if d := xt.ProtoLegacyExtensionDesc(); d != nil {
Joe Tsaiafb455e2019-03-14 16:08:22 -070054 return d
55 }
56 }
57
Joe Tsai62517cc2018-12-04 14:00:01 -080058 // Fast-path: check the cache for whether this ExtensionType has already
59 // been converted to a legacy descriptor.
Joe Tsai21ade492019-05-22 13:42:54 -040060 if d, ok := legacyExtensionDescCache.Load(xt); ok {
Joe Tsai4fddeba2019-03-20 18:29:32 -070061 return d.(*piface.ExtensionDescV1)
Joe Tsai08e00302018-11-26 22:32:06 -080062 }
63
64 // Determine the parent type if possible.
Joe Tsai4fddeba2019-03-20 18:29:32 -070065 var parent piface.MessageV1
Joe Tsaid4211502019-07-02 14:58:02 -070066 messageName := xt.ContainingMessage().FullName()
Joe Tsaiac31a352019-05-13 14:32:56 -070067 if mt, _ := preg.GlobalTypes.FindMessageByName(messageName); mt != nil {
Joe Tsai08e00302018-11-26 22:32:06 -080068 // Create a new parent message and unwrap it if possible.
Joe Tsai3bc7d6f2019-01-09 02:57:13 -080069 mv := mt.New().Interface()
Joe Tsai08e00302018-11-26 22:32:06 -080070 t := reflect.TypeOf(mv)
Damien Neil954bd922019-07-17 16:52:10 -070071 if mv, ok := mv.(Unwrapper); ok {
Joe Tsai08e00302018-11-26 22:32:06 -080072 t = reflect.TypeOf(mv.ProtoUnwrap())
73 }
74
75 // Check whether the message implements the legacy v1 Message interface.
76 mz := reflect.Zero(t).Interface()
Joe Tsai4fddeba2019-03-20 18:29:32 -070077 if mz, ok := mz.(piface.MessageV1); ok {
Joe Tsai08e00302018-11-26 22:32:06 -080078 parent = mz
79 }
80 }
81
82 // Determine the v1 extension type, which is unfortunately not the same as
83 // the v2 ExtensionType.GoType.
Joe Tsai0fc49f82019-05-01 12:29:25 -070084 extType := xt.GoType()
Joe Tsai08e00302018-11-26 22:32:06 -080085 switch extType.Kind() {
86 case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
87 extType = reflect.PtrTo(extType) // T -> *T for singular scalar fields
88 case reflect.Ptr:
89 if extType.Elem().Kind() == reflect.Slice {
90 extType = extType.Elem() // *[]T -> []T for repeated fields
91 }
92 }
93
94 // Reconstruct the legacy enum full name, which is an odd mixture of the
95 // proto package name with the Go type name.
96 var enumName string
Joe Tsaid4211502019-07-02 14:58:02 -070097 if xt.Kind() == pref.EnumKind {
Joe Tsai08e00302018-11-26 22:32:06 -080098 // Derive Go type name.
Joe Tsai0fc49f82019-05-01 12:29:25 -070099 t := extType
100 if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
101 t = t.Elem()
Joe Tsai08e00302018-11-26 22:32:06 -0800102 }
Joe Tsai0fc49f82019-05-01 12:29:25 -0700103 enumName = t.Name()
Joe Tsai08e00302018-11-26 22:32:06 -0800104
105 // Derive the proto package name.
106 // For legacy enums, obtain the proto package from the raw descriptor.
107 var protoPkg string
Joe Tsaid4211502019-07-02 14:58:02 -0700108 if fd := xt.Enum().ParentFile(); fd != nil {
Joe Tsai08e00302018-11-26 22:32:06 -0800109 protoPkg = string(fd.Package())
110 }
Joe Tsai0fc49f82019-05-01 12:29:25 -0700111 if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" {
Joe Tsai08e00302018-11-26 22:32:06 -0800112 b, _ := ed.EnumDescriptor()
Joe Tsaid8881392019-06-06 13:01:53 -0700113 protoPkg = string(legacyLoadFileDesc(b).Package())
Joe Tsai08e00302018-11-26 22:32:06 -0800114 }
115
116 if protoPkg != "" {
117 enumName = protoPkg + "." + enumName
118 }
119 }
120
121 // Derive the proto file that the extension was declared within.
122 var filename string
Joe Tsaid4211502019-07-02 14:58:02 -0700123 if fd := xt.ParentFile(); fd != nil {
Joe Tsai08e00302018-11-26 22:32:06 -0800124 filename = fd.Path()
125 }
126
Joe Tsai4fddeba2019-03-20 18:29:32 -0700127 // Construct and return a ExtensionDescV1.
128 d := &piface.ExtensionDescV1{
Joe Tsai0fc49f82019-05-01 12:29:25 -0700129 Type: xt,
Joe Tsai08e00302018-11-26 22:32:06 -0800130 ExtendedType: parent,
131 ExtensionType: reflect.Zero(extType).Interface(),
Joe Tsaid4211502019-07-02 14:58:02 -0700132 Field: int32(xt.Number()),
133 Name: string(xt.FullName()),
134 Tag: ptag.Marshal(xt, enumName),
Joe Tsai08e00302018-11-26 22:32:06 -0800135 Filename: filename,
136 }
Joe Tsai21ade492019-05-22 13:42:54 -0400137 if d, ok := legacyExtensionDescCache.LoadOrStore(xt, d); ok {
Joe Tsai4fddeba2019-03-20 18:29:32 -0700138 return d.(*piface.ExtensionDescV1)
Joe Tsaib9365042019-03-19 14:14:29 -0700139 }
Joe Tsai62517cc2018-12-04 14:00:01 -0800140 return d
Joe Tsai08e00302018-11-26 22:32:06 -0800141}
142
Joe Tsai21ade492019-05-22 13:42:54 -0400143// legacyExtensionTypeFromDesc converts a protoiface.ExtensionDescV1 to a
Joe Tsai62517cc2018-12-04 14:00:01 -0800144// v2 protoreflect.ExtensionType. The returned descriptor type takes ownership
145// of the input extension desc. The input must not be mutated so long as the
146// returned type is still in use.
Joe Tsai21ade492019-05-22 13:42:54 -0400147func legacyExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
Joe Tsai62517cc2018-12-04 14:00:01 -0800148 // Fast-path: check whether an extension type is already nested within.
Joe Tsai08e00302018-11-26 22:32:06 -0800149 if d.Type != nil {
Joe Tsai62517cc2018-12-04 14:00:01 -0800150 return d.Type
151 }
152
153 // Fast-path: check the cache for whether this ExtensionType has already
154 // been converted from a legacy descriptor.
Joe Tsai21ade492019-05-22 13:42:54 -0400155 dk := legacyExtensionDescKeyOf(d)
156 if t, ok := legacyExtensionTypeCache.Load(dk); ok {
Joe Tsai62517cc2018-12-04 14:00:01 -0800157 return t.(pref.ExtensionType)
Joe Tsai08e00302018-11-26 22:32:06 -0800158 }
159
Joe Tsaid8881392019-06-06 13:01:53 -0700160 // Resolve enum or message dependencies.
161 var ed pref.EnumDescriptor
162 var md pref.MessageDescriptor
Joe Tsai08e00302018-11-26 22:32:06 -0800163 t := reflect.TypeOf(d.ExtensionType)
164 isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
165 isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
166 if isOptional || isRepeated {
167 t = t.Elem()
168 }
Joe Tsaid8881392019-06-06 13:01:53 -0700169 switch v := reflect.Zero(t).Interface().(type) {
170 case pref.Enum:
171 ed = v.Descriptor()
172 case enumV1:
173 ed = LegacyLoadEnumDesc(t)
174 case pref.ProtoMessage:
175 md = v.ProtoReflect().Descriptor()
176 case messageV1:
177 md = LegacyLoadMessageDesc(t)
178 }
179
180 // Derive basic field information from the struct tag.
181 var evs pref.EnumValueDescriptors
182 if ed != nil {
183 evs = ed.Values()
184 }
185 fd := ptag.Unmarshal(d.Tag, t, evs).(*filedesc.Field)
Joe Tsai08e00302018-11-26 22:32:06 -0800186
187 // Construct a v2 ExtensionType.
Joe Tsaid8881392019-06-06 13:01:53 -0700188 xd := &filedesc.Extension{L2: new(filedesc.ExtensionL2)}
189 xd.L0.ParentFile = filedesc.SurrogateProto2
190 xd.L0.FullName = pref.FullName(d.Name)
191 xd.L1.Number = pref.FieldNumber(d.Field)
192 xd.L2.Cardinality = fd.L1.Cardinality
193 xd.L1.Kind = fd.L1.Kind
194 xd.L2.IsPacked = fd.L1.IsPacked
195 xd.L2.Default = fd.L1.Default
196 xd.L1.Extendee = Export{}.MessageDescriptorOf(d.ExtendedType)
197 xd.L2.Enum = ed
198 xd.L2.Message = md
Damien Neil954bd922019-07-17 16:52:10 -0700199 tt := reflect.TypeOf(d.ExtensionType)
200 if isOptional {
201 tt = tt.Elem()
202 } else if isRepeated {
203 tt = reflect.PtrTo(tt)
204 }
205 xt := LegacyExtensionTypeOf(xd, tt)
Joe Tsai08e00302018-11-26 22:32:06 -0800206
Joe Tsai62517cc2018-12-04 14:00:01 -0800207 // Cache the conversion for both directions.
Joe Tsai21ade492019-05-22 13:42:54 -0400208 legacyExtensionDescCache.LoadOrStore(xt, d)
209 if xt, ok := legacyExtensionTypeCache.LoadOrStore(dk, xt); ok {
Joe Tsaib9365042019-03-19 14:14:29 -0700210 return xt.(pref.ExtensionType)
211 }
Joe Tsai62517cc2018-12-04 14:00:01 -0800212 return xt
Joe Tsai08e00302018-11-26 22:32:06 -0800213}
214
Joe Tsai21ade492019-05-22 13:42:54 -0400215// LegacyExtensionTypeOf returns a protoreflect.ExtensionType where the
Damien Neil954bd922019-07-17 16:52:10 -0700216// element type of the field is t.
Joe Tsai08e00302018-11-26 22:32:06 -0800217//
Joe Tsai0fc49f82019-05-01 12:29:25 -0700218// This is exported for testing purposes.
Joe Tsai21ade492019-05-22 13:42:54 -0400219func LegacyExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
Damien Neil954bd922019-07-17 16:52:10 -0700220 return &legacyExtensionType{
221 ExtensionDescriptor: xd,
222 typ: t,
223 conv: NewConverter(t, xd),
Joe Tsai0fc49f82019-05-01 12:29:25 -0700224 }
Joe Tsai08e00302018-11-26 22:32:06 -0800225}
226
Joe Tsai21ade492019-05-22 13:42:54 -0400227type legacyExtensionType struct {
Damien Neil954bd922019-07-17 16:52:10 -0700228 pref.ExtensionDescriptor
229 typ reflect.Type
230 conv Converter
Joe Tsai08e00302018-11-26 22:32:06 -0800231}
232
Damien Neil954bd922019-07-17 16:52:10 -0700233func (x *legacyExtensionType) GoType() reflect.Type { return x.typ }
234func (x *legacyExtensionType) New() pref.Value { return x.conv.New() }
235func (x *legacyExtensionType) ValueOf(v interface{}) pref.Value {
236 return x.conv.PBValueOf(reflect.ValueOf(v))
237}
238func (x *legacyExtensionType) InterfaceOf(v pref.Value) interface{} {
239 return x.conv.GoValueOf(v).Interface()
240}
241func (x *legacyExtensionType) Descriptor() pref.ExtensionDescriptor { return x.ExtensionDescriptor }
Joe Tsaid4211502019-07-02 14:58:02 -0700242func (x *legacyExtensionType) Format(s fmt.State, r rune) { descfmt.FormatDesc(s, r, x) }