blob: 4ec31df00065813e030f1607f14c6dee8bfcdeea [file] [log] [blame]
Joe Tsai90fe9962018-10-18 11:06:29 -07001// 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 Tsai90fe9962018-10-18 11:06:29 -07006
7import (
8 "fmt"
Joe Tsai90fe9962018-10-18 11:06:29 -07009 "reflect"
Joe Tsai851185d2019-07-01 13:45:52 -070010 "strings"
Joe Tsai90fe9962018-10-18 11:06:29 -070011 "sync"
12
Joe Tsai851185d2019-07-01 13:45:52 -070013 "google.golang.org/protobuf/internal/filedesc"
Joe Tsai2e7817f2019-08-23 12:18:57 -070014 "google.golang.org/protobuf/internal/strs"
Joe Tsaid8881392019-06-06 13:01:53 -070015 "google.golang.org/protobuf/reflect/protoreflect"
Damien Neile89e6242019-05-13 23:55:40 -070016 pref "google.golang.org/protobuf/reflect/protoreflect"
Joe Tsai90fe9962018-10-18 11:06:29 -070017)
18
Joe Tsai2e7817f2019-08-23 12:18:57 -070019// legacyEnumName returns the name of enums used in legacy code.
20// It is neither the protobuf full name nor the qualified Go name,
21// but rather an odd hybrid of both.
22func legacyEnumName(ed pref.EnumDescriptor) string {
23 var protoPkg string
24 enumName := string(ed.FullName())
25 if fd := ed.ParentFile(); fd != nil {
26 protoPkg = string(fd.Package())
27 enumName = strings.TrimPrefix(enumName, protoPkg+".")
28 }
29 if protoPkg == "" {
30 return strs.GoCamelCase(enumName)
31 }
32 return protoPkg + "." + strs.GoCamelCase(enumName)
33}
34
Joe Tsai21ade492019-05-22 13:42:54 -040035// legacyWrapEnum wraps v as a protoreflect.Enum,
Joe Tsai08e00302018-11-26 22:32:06 -080036// where v must be a int32 kind and not implement the v2 API already.
Joe Tsai21ade492019-05-22 13:42:54 -040037func legacyWrapEnum(v reflect.Value) pref.Enum {
38 et := legacyLoadEnumType(v.Type())
Joe Tsaif0c01e42018-11-06 13:05:20 -080039 return et.New(pref.EnumNumber(v.Int()))
40}
41
Joe Tsai21ade492019-05-22 13:42:54 -040042var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
Joe Tsai6f9095c2018-11-10 14:12:21 -080043
Joe Tsai21ade492019-05-22 13:42:54 -040044// legacyLoadEnumType dynamically loads a protoreflect.EnumType for t,
Joe Tsaif0c01e42018-11-06 13:05:20 -080045// where t must be an int32 kind and not implement the v2 API already.
Joe Tsai21ade492019-05-22 13:42:54 -040046func legacyLoadEnumType(t reflect.Type) pref.EnumType {
Joe Tsai6f9095c2018-11-10 14:12:21 -080047 // Fast-path: check if a EnumType is cached for this concrete type.
Joe Tsai21ade492019-05-22 13:42:54 -040048 if et, ok := legacyEnumTypeCache.Load(t); ok {
Joe Tsaif0c01e42018-11-06 13:05:20 -080049 return et.(pref.EnumType)
Joe Tsai6f9095c2018-11-10 14:12:21 -080050 }
51
52 // Slow-path: derive enum descriptor and initialize EnumType.
Joe Tsaib2f66be2019-05-22 00:42:45 -040053 var et pref.EnumType
Joe Tsai21ade492019-05-22 13:42:54 -040054 ed := LegacyLoadEnumDesc(t)
Damien Neil16163b42019-08-06 15:43:25 -070055 et = &legacyEnumType{
56 desc: ed,
57 goType: t,
Joe Tsaib2f66be2019-05-22 00:42:45 -040058 }
Joe Tsai21ade492019-05-22 13:42:54 -040059 if et, ok := legacyEnumTypeCache.LoadOrStore(t, et); ok {
Joe Tsaib9365042019-03-19 14:14:29 -070060 return et.(pref.EnumType)
61 }
62 return et
Joe Tsai6f9095c2018-11-10 14:12:21 -080063}
64
Damien Neil16163b42019-08-06 15:43:25 -070065type legacyEnumType struct {
66 desc pref.EnumDescriptor
67 goType reflect.Type
68 m sync.Map // map[protoreflect.EnumNumber]proto.Enum
69}
70
71func (t *legacyEnumType) New(n pref.EnumNumber) pref.Enum {
72 if e, ok := t.m.Load(n); ok {
73 return e.(pref.Enum)
74 }
75 e := &legacyEnumWrapper{num: n, pbTyp: t, goTyp: t.goType}
76 t.m.Store(n, e)
77 return e
78}
79func (t *legacyEnumType) GoType() reflect.Type {
80 return t.goType
81}
82func (t *legacyEnumType) Descriptor() pref.EnumDescriptor {
83 return t.desc
84}
85
Joe Tsai21ade492019-05-22 13:42:54 -040086type legacyEnumWrapper struct {
Joe Tsai6f9095c2018-11-10 14:12:21 -080087 num pref.EnumNumber
88 pbTyp pref.EnumType
89 goTyp reflect.Type
90}
91
Joe Tsai21ade492019-05-22 13:42:54 -040092func (e *legacyEnumWrapper) Descriptor() pref.EnumDescriptor {
Joe Tsai0fc49f82019-05-01 12:29:25 -070093 return e.pbTyp.Descriptor()
94}
Joe Tsaid4211502019-07-02 14:58:02 -070095func (e *legacyEnumWrapper) Type() pref.EnumType {
96 return e.pbTyp
97}
Joe Tsai21ade492019-05-22 13:42:54 -040098func (e *legacyEnumWrapper) Number() pref.EnumNumber {
Joe Tsai0fc49f82019-05-01 12:29:25 -070099 return e.num
100}
Joe Tsai21ade492019-05-22 13:42:54 -0400101func (e *legacyEnumWrapper) ProtoReflect() pref.Enum {
Joe Tsai6f9095c2018-11-10 14:12:21 -0800102 return e
103}
Joe Tsai21ade492019-05-22 13:42:54 -0400104func (e *legacyEnumWrapper) ProtoUnwrap() interface{} {
Joe Tsai6f9095c2018-11-10 14:12:21 -0800105 v := reflect.New(e.goTyp).Elem()
106 v.SetInt(int64(e.num))
107 return v.Interface()
108}
109
110var (
Damien Neil954bd922019-07-17 16:52:10 -0700111 _ pref.Enum = (*legacyEnumWrapper)(nil)
112 _ Unwrapper = (*legacyEnumWrapper)(nil)
Joe Tsai6f9095c2018-11-10 14:12:21 -0800113)
114
Joe Tsai21ade492019-05-22 13:42:54 -0400115var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
Joe Tsai90fe9962018-10-18 11:06:29 -0700116
Joe Tsai21ade492019-05-22 13:42:54 -0400117// LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type,
Joe Tsai90fe9962018-10-18 11:06:29 -0700118// which must be an int32 kind and not implement the v2 API already.
Joe Tsai35ec98f2019-03-25 14:41:32 -0700119//
120// This is exported for testing purposes.
Joe Tsai21ade492019-05-22 13:42:54 -0400121func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
Joe Tsai90fe9962018-10-18 11:06:29 -0700122 // Fast-path: check if an EnumDescriptor is cached for this concrete type.
Joe Tsai21ade492019-05-22 13:42:54 -0400123 if ed, ok := legacyEnumDescCache.Load(t); ok {
Joe Tsaib9365042019-03-19 14:14:29 -0700124 return ed.(pref.EnumDescriptor)
Joe Tsai90fe9962018-10-18 11:06:29 -0700125 }
126
Joe Tsaid8881392019-06-06 13:01:53 -0700127 // Slow-path: initialize EnumDescriptor from the raw descriptor.
Joe Tsai90fe9962018-10-18 11:06:29 -0700128 ev := reflect.Zero(t).Interface()
Damien Neila8593ba2019-01-08 16:18:07 -0800129 if _, ok := ev.(pref.Enum); ok {
Joe Tsai90fe9962018-10-18 11:06:29 -0700130 panic(fmt.Sprintf("%v already implements proto.Enum", t))
131 }
Joe Tsaid8881392019-06-06 13:01:53 -0700132 edV1, ok := ev.(enumV1)
133 if !ok {
Joe Tsai851185d2019-07-01 13:45:52 -0700134 return aberrantLoadEnumDesc(t)
Joe Tsai90fe9962018-10-18 11:06:29 -0700135 }
Joe Tsaid8881392019-06-06 13:01:53 -0700136 b, idxs := edV1.EnumDescriptor()
Joe Tsai90fe9962018-10-18 11:06:29 -0700137
Joe Tsaid8881392019-06-06 13:01:53 -0700138 var ed pref.EnumDescriptor
139 if len(idxs) == 1 {
140 ed = legacyLoadFileDesc(b).Enums().Get(idxs[0])
141 } else {
142 md := legacyLoadFileDesc(b).Messages().Get(idxs[0])
143 for _, i := range idxs[1 : len(idxs)-1] {
144 md = md.Messages().Get(i)
145 }
146 ed = md.Enums().Get(idxs[len(idxs)-1])
Joe Tsai90fe9962018-10-18 11:06:29 -0700147 }
Joe Tsai21ade492019-05-22 13:42:54 -0400148 if ed, ok := legacyEnumDescCache.LoadOrStore(t, ed); ok {
Joe Tsaid8881392019-06-06 13:01:53 -0700149 return ed.(protoreflect.EnumDescriptor)
Joe Tsaib9365042019-03-19 14:14:29 -0700150 }
Joe Tsai90fe9962018-10-18 11:06:29 -0700151 return ed
152}
Joe Tsai851185d2019-07-01 13:45:52 -0700153
154var aberrantEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
155
156// aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
157// which must not implement protoreflect.Enum or enumV1.
158//
159// If the type does not implement enumV1, then there is no reliable
160// way to derive the original protobuf type information.
161// We are unable to use the global enum registry since it is
162// unfortunately keyed by the protobuf full name, which we also do not know.
163// Thus, this produces some bogus enum descriptor based on the Go type name.
164func aberrantLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
165 // Fast-path: check if an EnumDescriptor is cached for this concrete type.
166 if ed, ok := aberrantEnumDescCache.Load(t); ok {
167 return ed.(pref.EnumDescriptor)
168 }
169
170 // Slow-path: construct a bogus, but unique EnumDescriptor.
171 ed := &filedesc.Enum{L2: new(filedesc.EnumL2)}
172 ed.L0.FullName = aberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum
173 ed.L0.ParentFile = filedesc.SurrogateProto3
174 ed.L2.Values.List = append(ed.L2.Values.List, filedesc.EnumValue{})
175
176 // TODO: Use the presence of a UnmarshalJSON method to determine proto2?
177
178 vd := &ed.L2.Values.List[0]
179 vd.L0.FullName = ed.L0.FullName + "_UNKNOWN" // e.g., github_com.user.repo.MyEnum_UNKNOWN
180 vd.L0.ParentFile = ed.L0.ParentFile
181 vd.L0.Parent = ed
182
183 // TODO: We could use the String method to obtain some enum value names by
184 // starting at 0 and print the enum until it produces invalid identifiers.
185 // An exhaustive query is clearly impractical, but can be best-effort.
186
187 if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok {
188 return ed.(pref.EnumDescriptor)
189 }
190 return ed
191}
192
193// aberrantDeriveFullName derives a fully qualified protobuf name for the given Go type
194// The provided name is not guaranteed to be stable nor universally unique.
195// It should be sufficiently unique within a program.
196func aberrantDeriveFullName(t reflect.Type) pref.FullName {
197 sanitize := func(r rune) rune {
198 switch {
199 case r == '/':
200 return '.'
201 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
202 return r
203 default:
204 return '_'
205 }
206 }
207 prefix := strings.Map(sanitize, t.PkgPath())
208 suffix := strings.Map(sanitize, t.Name())
209 if suffix == "" {
210 suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
211 }
212
213 ss := append(strings.Split(prefix, "."), suffix)
214 for i, s := range ss {
215 if s == "" || ('0' <= s[0] && s[0] <= '9') {
216 ss[i] = "x" + s
217 }
218 }
219 return pref.FullName(strings.Join(ss, "."))
220}