blob: f7d7ffb51039e8e466defa40cf4eefce7a352c8a [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
Joe Tsai1c8015f2020-01-12 01:16:16 -08003// license that can be found in the LICENSE file.
Joe Tsai90fe9962018-10-18 11:06:29 -07004
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}
Damien Neil16163b42019-08-06 15:43:25 -070079func (t *legacyEnumType) Descriptor() pref.EnumDescriptor {
80 return t.desc
81}
82
Joe Tsai21ade492019-05-22 13:42:54 -040083type legacyEnumWrapper struct {
Joe Tsai6f9095c2018-11-10 14:12:21 -080084 num pref.EnumNumber
85 pbTyp pref.EnumType
86 goTyp reflect.Type
87}
88
Joe Tsai21ade492019-05-22 13:42:54 -040089func (e *legacyEnumWrapper) Descriptor() pref.EnumDescriptor {
Joe Tsai0fc49f82019-05-01 12:29:25 -070090 return e.pbTyp.Descriptor()
91}
Joe Tsaid4211502019-07-02 14:58:02 -070092func (e *legacyEnumWrapper) Type() pref.EnumType {
93 return e.pbTyp
94}
Joe Tsai21ade492019-05-22 13:42:54 -040095func (e *legacyEnumWrapper) Number() pref.EnumNumber {
Joe Tsai0fc49f82019-05-01 12:29:25 -070096 return e.num
97}
Joe Tsai21ade492019-05-22 13:42:54 -040098func (e *legacyEnumWrapper) ProtoReflect() pref.Enum {
Joe Tsai6f9095c2018-11-10 14:12:21 -080099 return e
100}
Joe Tsaifd528ff2019-09-03 16:30:39 -0700101func (e *legacyEnumWrapper) protoUnwrap() interface{} {
Joe Tsai6f9095c2018-11-10 14:12:21 -0800102 v := reflect.New(e.goTyp).Elem()
103 v.SetInt(int64(e.num))
104 return v.Interface()
105}
106
107var (
Damien Neil954bd922019-07-17 16:52:10 -0700108 _ pref.Enum = (*legacyEnumWrapper)(nil)
Joe Tsaifd528ff2019-09-03 16:30:39 -0700109 _ unwrapper = (*legacyEnumWrapper)(nil)
Joe Tsai6f9095c2018-11-10 14:12:21 -0800110)
111
Joe Tsai21ade492019-05-22 13:42:54 -0400112var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
Joe Tsai90fe9962018-10-18 11:06:29 -0700113
Joe Tsai21ade492019-05-22 13:42:54 -0400114// LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type,
Joe Tsai90fe9962018-10-18 11:06:29 -0700115// which must be an int32 kind and not implement the v2 API already.
Joe Tsai35ec98f2019-03-25 14:41:32 -0700116//
117// This is exported for testing purposes.
Joe Tsai21ade492019-05-22 13:42:54 -0400118func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
Joe Tsai90fe9962018-10-18 11:06:29 -0700119 // Fast-path: check if an EnumDescriptor is cached for this concrete type.
Joe Tsai21ade492019-05-22 13:42:54 -0400120 if ed, ok := legacyEnumDescCache.Load(t); ok {
Joe Tsaib9365042019-03-19 14:14:29 -0700121 return ed.(pref.EnumDescriptor)
Joe Tsai90fe9962018-10-18 11:06:29 -0700122 }
123
Joe Tsaid8881392019-06-06 13:01:53 -0700124 // Slow-path: initialize EnumDescriptor from the raw descriptor.
Joe Tsai90fe9962018-10-18 11:06:29 -0700125 ev := reflect.Zero(t).Interface()
Damien Neila8593ba2019-01-08 16:18:07 -0800126 if _, ok := ev.(pref.Enum); ok {
Joe Tsai90fe9962018-10-18 11:06:29 -0700127 panic(fmt.Sprintf("%v already implements proto.Enum", t))
128 }
Joe Tsaid8881392019-06-06 13:01:53 -0700129 edV1, ok := ev.(enumV1)
130 if !ok {
Joe Tsai851185d2019-07-01 13:45:52 -0700131 return aberrantLoadEnumDesc(t)
Joe Tsai90fe9962018-10-18 11:06:29 -0700132 }
Joe Tsaid8881392019-06-06 13:01:53 -0700133 b, idxs := edV1.EnumDescriptor()
Joe Tsai90fe9962018-10-18 11:06:29 -0700134
Joe Tsaid8881392019-06-06 13:01:53 -0700135 var ed pref.EnumDescriptor
136 if len(idxs) == 1 {
137 ed = legacyLoadFileDesc(b).Enums().Get(idxs[0])
138 } else {
139 md := legacyLoadFileDesc(b).Messages().Get(idxs[0])
140 for _, i := range idxs[1 : len(idxs)-1] {
141 md = md.Messages().Get(i)
142 }
143 ed = md.Enums().Get(idxs[len(idxs)-1])
Joe Tsai90fe9962018-10-18 11:06:29 -0700144 }
Joe Tsai21ade492019-05-22 13:42:54 -0400145 if ed, ok := legacyEnumDescCache.LoadOrStore(t, ed); ok {
Joe Tsaid8881392019-06-06 13:01:53 -0700146 return ed.(protoreflect.EnumDescriptor)
Joe Tsaib9365042019-03-19 14:14:29 -0700147 }
Joe Tsai90fe9962018-10-18 11:06:29 -0700148 return ed
149}
Joe Tsai851185d2019-07-01 13:45:52 -0700150
151var aberrantEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
152
153// aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
154// which must not implement protoreflect.Enum or enumV1.
155//
156// If the type does not implement enumV1, then there is no reliable
157// way to derive the original protobuf type information.
158// We are unable to use the global enum registry since it is
159// unfortunately keyed by the protobuf full name, which we also do not know.
160// Thus, this produces some bogus enum descriptor based on the Go type name.
161func aberrantLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
162 // Fast-path: check if an EnumDescriptor is cached for this concrete type.
163 if ed, ok := aberrantEnumDescCache.Load(t); ok {
164 return ed.(pref.EnumDescriptor)
165 }
166
167 // Slow-path: construct a bogus, but unique EnumDescriptor.
168 ed := &filedesc.Enum{L2: new(filedesc.EnumL2)}
Joe Tsai55f18252020-01-11 00:25:01 -0800169 ed.L0.FullName = AberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum
Joe Tsai851185d2019-07-01 13:45:52 -0700170 ed.L0.ParentFile = filedesc.SurrogateProto3
171 ed.L2.Values.List = append(ed.L2.Values.List, filedesc.EnumValue{})
172
173 // TODO: Use the presence of a UnmarshalJSON method to determine proto2?
174
175 vd := &ed.L2.Values.List[0]
176 vd.L0.FullName = ed.L0.FullName + "_UNKNOWN" // e.g., github_com.user.repo.MyEnum_UNKNOWN
177 vd.L0.ParentFile = ed.L0.ParentFile
178 vd.L0.Parent = ed
179
180 // TODO: We could use the String method to obtain some enum value names by
181 // starting at 0 and print the enum until it produces invalid identifiers.
182 // An exhaustive query is clearly impractical, but can be best-effort.
183
184 if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok {
185 return ed.(pref.EnumDescriptor)
186 }
187 return ed
188}
189
Joe Tsai55f18252020-01-11 00:25:01 -0800190// AberrantDeriveFullName derives a fully qualified protobuf name for the given Go type
Joe Tsai851185d2019-07-01 13:45:52 -0700191// The provided name is not guaranteed to be stable nor universally unique.
192// It should be sufficiently unique within a program.
Joe Tsai55f18252020-01-11 00:25:01 -0800193//
194// This is exported for testing purposes.
195func AberrantDeriveFullName(t reflect.Type) pref.FullName {
Joe Tsai851185d2019-07-01 13:45:52 -0700196 sanitize := func(r rune) rune {
197 switch {
198 case r == '/':
199 return '.'
200 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
201 return r
202 default:
203 return '_'
204 }
205 }
206 prefix := strings.Map(sanitize, t.PkgPath())
207 suffix := strings.Map(sanitize, t.Name())
208 if suffix == "" {
209 suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
210 }
211
212 ss := append(strings.Split(prefix, "."), suffix)
213 for i, s := range ss {
214 if s == "" || ('0' <= s[0] && s[0] <= '9') {
215 ss[i] = "x" + s
216 }
217 }
218 return pref.FullName(strings.Join(ss, "."))
219}