blob: 87b4921bdfe4f35ffbafed62a2ceb4321ebbf0da [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
5package impl
6
7import (
8 "fmt"
9 "math"
10 "reflect"
11 "sync"
12
13 descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor"
Joe Tsai6f9095c2018-11-10 14:12:21 -080014 pvalue "github.com/golang/protobuf/v2/internal/value"
Joe Tsai90fe9962018-10-18 11:06:29 -070015 pref "github.com/golang/protobuf/v2/reflect/protoreflect"
16 ptype "github.com/golang/protobuf/v2/reflect/prototype"
17)
18
Joe Tsai6f9095c2018-11-10 14:12:21 -080019var enumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
20
21// wrapLegacyEnum wraps v as a protoreflect.ProtoEnum,
22// where v must be an int32 kind and not implement the v2 API already.
23func wrapLegacyEnum(v reflect.Value) pref.ProtoEnum {
24 // Fast-path: check if a EnumType is cached for this concrete type.
25 if et, ok := enumTypeCache.Load(v.Type()); ok {
26 return et.(pref.EnumType).New(pref.EnumNumber(v.Int()))
27 }
28
29 // Slow-path: derive enum descriptor and initialize EnumType.
30 var m sync.Map // map[protoreflect.EnumNumber]proto.Enum
31 ed := loadEnumDesc(v.Type())
32 et := ptype.GoEnum(ed, func(et pref.EnumType, n pref.EnumNumber) pref.ProtoEnum {
33 if e, ok := m.Load(n); ok {
34 return e.(pref.ProtoEnum)
35 }
36 e := &legacyEnumWrapper{num: n, pbTyp: et, goTyp: v.Type()}
37 m.Store(n, e)
38 return e
39 })
40 enumTypeCache.Store(v.Type(), et)
41 return et.(pref.EnumType).New(pref.EnumNumber(v.Int()))
42}
43
44type legacyEnumWrapper struct {
45 num pref.EnumNumber
46 pbTyp pref.EnumType
47 goTyp reflect.Type
48}
49
50func (e *legacyEnumWrapper) Number() pref.EnumNumber {
51 return e.num
52}
53func (e *legacyEnumWrapper) Type() pref.EnumType {
54 return e.pbTyp
55}
56func (e *legacyEnumWrapper) ProtoReflect() pref.Enum {
57 return e
58}
59func (e *legacyEnumWrapper) Unwrap() interface{} {
60 v := reflect.New(e.goTyp).Elem()
61 v.SetInt(int64(e.num))
62 return v.Interface()
63}
64
65var (
66 _ pref.Enum = (*legacyEnumWrapper)(nil)
67 _ pref.ProtoEnum = (*legacyEnumWrapper)(nil)
68 _ pvalue.Unwrapper = (*legacyEnumWrapper)(nil)
69)
70
Joe Tsai90fe9962018-10-18 11:06:29 -070071var enumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
72
73// loadEnumDesc returns an EnumDescriptor derived from the Go type,
74// which must be an int32 kind and not implement the v2 API already.
75func loadEnumDesc(t reflect.Type) pref.EnumDescriptor {
76 // Fast-path: check if an EnumDescriptor is cached for this concrete type.
77 if v, ok := enumDescCache.Load(t); ok {
78 return v.(pref.EnumDescriptor)
79 }
80
81 // Slow-path: initialize EnumDescriptor from the proto descriptor.
Joe Tsai6f9095c2018-11-10 14:12:21 -080082 if t.Kind() != reflect.Int32 || t.PkgPath() == "" {
83 panic(fmt.Sprintf("got %v, want named int32 kind", t))
Joe Tsai90fe9962018-10-18 11:06:29 -070084 }
85
86 // Derive the enum descriptor from the raw descriptor proto.
87 e := new(ptype.StandaloneEnum)
88 ev := reflect.Zero(t).Interface()
89 if _, ok := ev.(pref.ProtoEnum); ok {
90 panic(fmt.Sprintf("%v already implements proto.Enum", t))
91 }
92 if ed, ok := ev.(legacyEnum); ok {
93 b, idxs := ed.EnumDescriptor()
94 fd := loadFileDesc(b)
95
96 // Derive syntax.
97 switch fd.GetSyntax() {
98 case "proto2", "":
99 e.Syntax = pref.Proto2
100 case "proto3":
101 e.Syntax = pref.Proto3
102 }
103
104 // Derive the full name and correct enum descriptor.
105 var ed *descriptorV1.EnumDescriptorProto
106 e.FullName = pref.FullName(fd.GetPackage())
107 if len(idxs) == 1 {
108 ed = fd.EnumType[idxs[0]]
109 e.FullName = e.FullName.Append(pref.Name(ed.GetName()))
110 } else {
111 md := fd.MessageType[idxs[0]]
112 e.FullName = e.FullName.Append(pref.Name(md.GetName()))
113 for _, i := range idxs[1 : len(idxs)-1] {
114 md = md.NestedType[i]
115 e.FullName = e.FullName.Append(pref.Name(md.GetName()))
116 }
117 ed = md.EnumType[idxs[len(idxs)-1]]
118 e.FullName = e.FullName.Append(pref.Name(ed.GetName()))
119 }
120
121 // Derive the enum values.
122 for _, vd := range ed.GetValue() {
123 e.Values = append(e.Values, ptype.EnumValue{
124 Name: pref.Name(vd.GetName()),
125 Number: pref.EnumNumber(vd.GetNumber()),
126 })
127 }
128 } else {
129 // If the type does not implement legacyEnum, then there is no reliable
130 // way to derive the original protobuf type information.
131 // We are unable to use the global enum registry since it is
132 // unfortunately keyed by the full name, which we do not know.
133 // Furthermore, some generated enums register with a fork of
134 // golang/protobuf so the enum may not even be found in the registry.
135 //
136 // Instead, create a bogus enum descriptor to ensure that
137 // most operations continue to work. For example, textpb and jsonpb
138 // will be unable to parse a message with an enum value by name.
139 e.Syntax = pref.Proto2
140 e.FullName = deriveFullName(t)
141 e.Values = []ptype.EnumValue{{Name: "INVALID", Number: math.MinInt32}}
142 }
143
144 ed, err := ptype.NewEnum(e)
145 if err != nil {
146 panic(err)
147 }
148 enumDescCache.Store(t, ed)
149 return ed
150}