11 "sync" |
11 "sync" |
12 |
12 |
13 "google.golang.org/protobuf/internal/filedesc" |
13 "google.golang.org/protobuf/internal/filedesc" |
14 "google.golang.org/protobuf/internal/strs" |
14 "google.golang.org/protobuf/internal/strs" |
15 "google.golang.org/protobuf/reflect/protoreflect" |
15 "google.golang.org/protobuf/reflect/protoreflect" |
16 pref "google.golang.org/protobuf/reflect/protoreflect" |
|
17 ) |
16 ) |
18 |
17 |
19 // legacyEnumName returns the name of enums used in legacy code. |
18 // legacyEnumName returns the name of enums used in legacy code. |
20 // It is neither the protobuf full name nor the qualified Go name, |
19 // It is neither the protobuf full name nor the qualified Go name, |
21 // but rather an odd hybrid of both. |
20 // but rather an odd hybrid of both. |
22 func legacyEnumName(ed pref.EnumDescriptor) string { |
21 func legacyEnumName(ed protoreflect.EnumDescriptor) string { |
23 var protoPkg string |
22 var protoPkg string |
24 enumName := string(ed.FullName()) |
23 enumName := string(ed.FullName()) |
25 if fd := ed.ParentFile(); fd != nil { |
24 if fd := ed.ParentFile(); fd != nil { |
26 protoPkg = string(fd.Package()) |
25 protoPkg = string(fd.Package()) |
27 enumName = strings.TrimPrefix(enumName, protoPkg+".") |
26 enumName = strings.TrimPrefix(enumName, protoPkg+".") |
32 return protoPkg + "." + strs.GoCamelCase(enumName) |
31 return protoPkg + "." + strs.GoCamelCase(enumName) |
33 } |
32 } |
34 |
33 |
35 // legacyWrapEnum wraps v as a protoreflect.Enum, |
34 // legacyWrapEnum wraps v as a protoreflect.Enum, |
36 // where v must be a int32 kind and not implement the v2 API already. |
35 // where v must be a int32 kind and not implement the v2 API already. |
37 func legacyWrapEnum(v reflect.Value) pref.Enum { |
36 func legacyWrapEnum(v reflect.Value) protoreflect.Enum { |
38 et := legacyLoadEnumType(v.Type()) |
37 et := legacyLoadEnumType(v.Type()) |
39 return et.New(pref.EnumNumber(v.Int())) |
38 return et.New(protoreflect.EnumNumber(v.Int())) |
40 } |
39 } |
41 |
40 |
42 var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType |
41 var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType |
43 |
42 |
44 // legacyLoadEnumType dynamically loads a protoreflect.EnumType for t, |
43 // legacyLoadEnumType dynamically loads a protoreflect.EnumType for t, |
45 // where t must be an int32 kind and not implement the v2 API already. |
44 // where t must be an int32 kind and not implement the v2 API already. |
46 func legacyLoadEnumType(t reflect.Type) pref.EnumType { |
45 func legacyLoadEnumType(t reflect.Type) protoreflect.EnumType { |
47 // Fast-path: check if a EnumType is cached for this concrete type. |
46 // Fast-path: check if a EnumType is cached for this concrete type. |
48 if et, ok := legacyEnumTypeCache.Load(t); ok { |
47 if et, ok := legacyEnumTypeCache.Load(t); ok { |
49 return et.(pref.EnumType) |
48 return et.(protoreflect.EnumType) |
50 } |
49 } |
51 |
50 |
52 // Slow-path: derive enum descriptor and initialize EnumType. |
51 // Slow-path: derive enum descriptor and initialize EnumType. |
53 var et pref.EnumType |
52 var et protoreflect.EnumType |
54 ed := LegacyLoadEnumDesc(t) |
53 ed := LegacyLoadEnumDesc(t) |
55 et = &legacyEnumType{ |
54 et = &legacyEnumType{ |
56 desc: ed, |
55 desc: ed, |
57 goType: t, |
56 goType: t, |
58 } |
57 } |
59 if et, ok := legacyEnumTypeCache.LoadOrStore(t, et); ok { |
58 if et, ok := legacyEnumTypeCache.LoadOrStore(t, et); ok { |
60 return et.(pref.EnumType) |
59 return et.(protoreflect.EnumType) |
61 } |
60 } |
62 return et |
61 return et |
63 } |
62 } |
64 |
63 |
65 type legacyEnumType struct { |
64 type legacyEnumType struct { |
66 desc pref.EnumDescriptor |
65 desc protoreflect.EnumDescriptor |
67 goType reflect.Type |
66 goType reflect.Type |
68 m sync.Map // map[protoreflect.EnumNumber]proto.Enum |
67 m sync.Map // map[protoreflect.EnumNumber]proto.Enum |
69 } |
68 } |
70 |
69 |
71 func (t *legacyEnumType) New(n pref.EnumNumber) pref.Enum { |
70 func (t *legacyEnumType) New(n protoreflect.EnumNumber) protoreflect.Enum { |
72 if e, ok := t.m.Load(n); ok { |
71 if e, ok := t.m.Load(n); ok { |
73 return e.(pref.Enum) |
72 return e.(protoreflect.Enum) |
74 } |
73 } |
75 e := &legacyEnumWrapper{num: n, pbTyp: t, goTyp: t.goType} |
74 e := &legacyEnumWrapper{num: n, pbTyp: t, goTyp: t.goType} |
76 t.m.Store(n, e) |
75 t.m.Store(n, e) |
77 return e |
76 return e |
78 } |
77 } |
79 func (t *legacyEnumType) Descriptor() pref.EnumDescriptor { |
78 func (t *legacyEnumType) Descriptor() protoreflect.EnumDescriptor { |
80 return t.desc |
79 return t.desc |
81 } |
80 } |
82 |
81 |
83 type legacyEnumWrapper struct { |
82 type legacyEnumWrapper struct { |
84 num pref.EnumNumber |
83 num protoreflect.EnumNumber |
85 pbTyp pref.EnumType |
84 pbTyp protoreflect.EnumType |
86 goTyp reflect.Type |
85 goTyp reflect.Type |
87 } |
86 } |
88 |
87 |
89 func (e *legacyEnumWrapper) Descriptor() pref.EnumDescriptor { |
88 func (e *legacyEnumWrapper) Descriptor() protoreflect.EnumDescriptor { |
90 return e.pbTyp.Descriptor() |
89 return e.pbTyp.Descriptor() |
91 } |
90 } |
92 func (e *legacyEnumWrapper) Type() pref.EnumType { |
91 func (e *legacyEnumWrapper) Type() protoreflect.EnumType { |
93 return e.pbTyp |
92 return e.pbTyp |
94 } |
93 } |
95 func (e *legacyEnumWrapper) Number() pref.EnumNumber { |
94 func (e *legacyEnumWrapper) Number() protoreflect.EnumNumber { |
96 return e.num |
95 return e.num |
97 } |
96 } |
98 func (e *legacyEnumWrapper) ProtoReflect() pref.Enum { |
97 func (e *legacyEnumWrapper) ProtoReflect() protoreflect.Enum { |
99 return e |
98 return e |
100 } |
99 } |
101 func (e *legacyEnumWrapper) protoUnwrap() interface{} { |
100 func (e *legacyEnumWrapper) protoUnwrap() interface{} { |
102 v := reflect.New(e.goTyp).Elem() |
101 v := reflect.New(e.goTyp).Elem() |
103 v.SetInt(int64(e.num)) |
102 v.SetInt(int64(e.num)) |
104 return v.Interface() |
103 return v.Interface() |
105 } |
104 } |
106 |
105 |
107 var ( |
106 var ( |
108 _ pref.Enum = (*legacyEnumWrapper)(nil) |
107 _ protoreflect.Enum = (*legacyEnumWrapper)(nil) |
109 _ unwrapper = (*legacyEnumWrapper)(nil) |
108 _ unwrapper = (*legacyEnumWrapper)(nil) |
110 ) |
109 ) |
111 |
110 |
112 var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor |
111 var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor |
113 |
112 |
114 // LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type, |
113 // LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type, |
115 // which must be an int32 kind and not implement the v2 API already. |
114 // which must be an int32 kind and not implement the v2 API already. |
116 // |
115 // |
117 // This is exported for testing purposes. |
116 // This is exported for testing purposes. |
118 func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor { |
117 func LegacyLoadEnumDesc(t reflect.Type) protoreflect.EnumDescriptor { |
119 // Fast-path: check if an EnumDescriptor is cached for this concrete type. |
118 // Fast-path: check if an EnumDescriptor is cached for this concrete type. |
120 if ed, ok := legacyEnumDescCache.Load(t); ok { |
119 if ed, ok := legacyEnumDescCache.Load(t); ok { |
121 return ed.(pref.EnumDescriptor) |
120 return ed.(protoreflect.EnumDescriptor) |
122 } |
121 } |
123 |
122 |
124 // Slow-path: initialize EnumDescriptor from the raw descriptor. |
123 // Slow-path: initialize EnumDescriptor from the raw descriptor. |
125 ev := reflect.Zero(t).Interface() |
124 ev := reflect.Zero(t).Interface() |
126 if _, ok := ev.(pref.Enum); ok { |
125 if _, ok := ev.(protoreflect.Enum); ok { |
127 panic(fmt.Sprintf("%v already implements proto.Enum", t)) |
126 panic(fmt.Sprintf("%v already implements proto.Enum", t)) |
128 } |
127 } |
129 edV1, ok := ev.(enumV1) |
128 edV1, ok := ev.(enumV1) |
130 if !ok { |
129 if !ok { |
131 return aberrantLoadEnumDesc(t) |
130 return aberrantLoadEnumDesc(t) |
132 } |
131 } |
133 b, idxs := edV1.EnumDescriptor() |
132 b, idxs := edV1.EnumDescriptor() |
134 |
133 |
135 var ed pref.EnumDescriptor |
134 var ed protoreflect.EnumDescriptor |
136 if len(idxs) == 1 { |
135 if len(idxs) == 1 { |
137 ed = legacyLoadFileDesc(b).Enums().Get(idxs[0]) |
136 ed = legacyLoadFileDesc(b).Enums().Get(idxs[0]) |
138 } else { |
137 } else { |
139 md := legacyLoadFileDesc(b).Messages().Get(idxs[0]) |
138 md := legacyLoadFileDesc(b).Messages().Get(idxs[0]) |
140 for _, i := range idxs[1 : len(idxs)-1] { |
139 for _, i := range idxs[1 : len(idxs)-1] { |
156 // If the type does not implement enumV1, then there is no reliable |
155 // If the type does not implement enumV1, then there is no reliable |
157 // way to derive the original protobuf type information. |
156 // way to derive the original protobuf type information. |
158 // We are unable to use the global enum registry since it is |
157 // 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. |
158 // 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. |
159 // Thus, this produces some bogus enum descriptor based on the Go type name. |
161 func aberrantLoadEnumDesc(t reflect.Type) pref.EnumDescriptor { |
160 func aberrantLoadEnumDesc(t reflect.Type) protoreflect.EnumDescriptor { |
162 // Fast-path: check if an EnumDescriptor is cached for this concrete type. |
161 // Fast-path: check if an EnumDescriptor is cached for this concrete type. |
163 if ed, ok := aberrantEnumDescCache.Load(t); ok { |
162 if ed, ok := aberrantEnumDescCache.Load(t); ok { |
164 return ed.(pref.EnumDescriptor) |
163 return ed.(protoreflect.EnumDescriptor) |
165 } |
164 } |
166 |
165 |
167 // Slow-path: construct a bogus, but unique EnumDescriptor. |
166 // Slow-path: construct a bogus, but unique EnumDescriptor. |
168 ed := &filedesc.Enum{L2: new(filedesc.EnumL2)} |
167 ed := &filedesc.Enum{L2: new(filedesc.EnumL2)} |
169 ed.L0.FullName = AberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum |
168 ed.L0.FullName = AberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum |
180 // TODO: We could use the String method to obtain some enum value names by |
179 // 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. |
180 // starting at 0 and print the enum until it produces invalid identifiers. |
182 // An exhaustive query is clearly impractical, but can be best-effort. |
181 // An exhaustive query is clearly impractical, but can be best-effort. |
183 |
182 |
184 if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok { |
183 if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok { |
185 return ed.(pref.EnumDescriptor) |
184 return ed.(protoreflect.EnumDescriptor) |
186 } |
185 } |
187 return ed |
186 return ed |
188 } |
187 } |
189 |
188 |
190 // AberrantDeriveFullName derives a fully qualified protobuf name for the given Go type |
189 // AberrantDeriveFullName derives a fully qualified protobuf name for the given Go type |
191 // The provided name is not guaranteed to be stable nor universally unique. |
190 // The provided name is not guaranteed to be stable nor universally unique. |
192 // It should be sufficiently unique within a program. |
191 // It should be sufficiently unique within a program. |
193 // |
192 // |
194 // This is exported for testing purposes. |
193 // This is exported for testing purposes. |
195 func AberrantDeriveFullName(t reflect.Type) pref.FullName { |
194 func AberrantDeriveFullName(t reflect.Type) protoreflect.FullName { |
196 sanitize := func(r rune) rune { |
195 sanitize := func(r rune) rune { |
197 switch { |
196 switch { |
198 case r == '/': |
197 case r == '/': |
199 return '.' |
198 return '.' |
200 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9': |
199 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9': |