-
Notifications
You must be signed in to change notification settings - Fork 28
/
bootstrapped_native.go
266 lines (243 loc) · 8.93 KB
/
bootstrapped_native.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright 2020 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Native types are not supported in bootstrapping mode. This
// file contains the functions used for normal operation.
//
//go:build !vdltoolbootstrapping
package golang
import (
"path"
"strings"
"v.io/v23/vdl"
"v.io/v23/vdlroot/vdltool"
"v.io/x/ref/lib/vdl/compile"
)
// The native types feature is hard to use correctly. E.g. the package
// containing the wire type must be imported into your Go binary in order for
// the wire<->native registration to work, which is hard to ensure. E.g.
//
// package base // VDL package
// type Wire int // has native type native.Int
//
// package dep // VDL package
// import "base"
// type Foo struct {
// X base.Wire
// }
//
// The Go code for package "dep" imports "native", rather than "base":
//
// package dep // Go package generated from VDL package
// import "native"
// type Foo struct {
// X native.Int
// }
//
// Note that when you import the "dep" package in your own code, you always use
// native.Int, rather than base.Wire; the base.Wire representation is only used
// as the wire format, but doesn't appear in generated code. But in order for
// this to work correctly, the "base" package must imported. This is tricky.
//
// Restrict the feature to these whitelisted VDL packages for now.
var nativeTypePackageWhitelist = map[string]bool{
"math": true,
"time": true,
"v.io/x/ref/lib/vdl/testdata/nativetest": true,
"v.io/v23/security": true,
"v.io/v23/vdl": true,
"v.io/v23/vdl/vdltest": true,
}
// validateGoConfig ensures a valid vdl.Config.
func validateGoConfig(pkg *compile.Package, env *compile.Env) {
vdlconfig := path.Join(pkg.GenPath, pkg.ConfigName)
// Validate native type configuration. Since native types are hard to use, we
// restrict them to a built-in whitelist of packages for now.
if len(pkg.Config.Go.WireToNativeTypes) > 0 && !nativeTypePackageWhitelist[pkg.Path] {
env.Errors.Errorf("%s: Go.WireToNativeTypes is restricted to whitelisted VDL packages", vdlconfig)
}
// Make sure each wire type is actually defined in the package, and required
// fields are all filled in.
for wire, native := range pkg.Config.Go.WireToNativeTypes {
baseWire := wire
if strings.HasPrefix(wire, "*") {
baseWire = strings.TrimPrefix(wire, "*")
}
if def := pkg.ResolveType(baseWire); def == nil {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes undefined", vdlconfig, wire)
}
if native.Type == "" {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoType.Type)", vdlconfig, wire)
}
for _, imp := range native.Imports {
if imp.Path == "" || imp.Name == "" {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoImport.Path or Name)", vdlconfig, wire)
continue
}
importPrefix := imp.Name + "."
if !strings.Contains(native.Type, importPrefix) &&
!strings.Contains(native.ToNative, importPrefix) &&
!strings.Contains(native.FromNative, importPrefix) {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (native type %q doesn't contain import prefix %q)", vdlconfig, wire, native.Type, importPrefix)
}
}
if native.Zero.Mode != vdltool.GoZeroModeUnique && native.Zero.IsZero == "" {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (native type %q must be either Mode:Unique or have a non-empty IsZero)", vdlconfig, wire, native.Type)
}
}
}
// isNativeType returns true iff t is a native type.
func isNativeType(env *compile.Env, t *vdl.Type) bool {
if def := env.FindTypeDef(t); def != nil {
key := def.Name
if t.Kind() == vdl.Optional {
key = "*" + key
}
_, ok := def.File.Package.Config.Go.WireToNativeTypes[key]
return ok
}
return false
}
// asNativeType retruns the supplied type as a native type if
// it is configured as such.
func asNativeType(env *compile.Env, tt *vdl.Type) (nativeGoType, bool) {
if def := env.FindTypeDef(tt); def != nil {
pkg := def.File.Package
key := def.Name
if tt.Kind() == vdl.Optional {
key = "*" + key
}
native, ok := pkg.Config.Go.WireToNativeTypes[key]
return nativeGoType{native, pkg}, ok
}
return nativeGoType{}, false
}
// nativeGoType abstracts native types so that they can be implemented
// differently when bootstrapping.
type nativeGoType struct {
typ vdltool.GoType
wirePkg *compile.Package
}
func (nt nativeGoType) String() string {
return nt.typ.Type
}
func (nt nativeGoType) pkg() *compile.Package {
return nt.wirePkg
}
func (nt nativeGoType) isBool() bool {
return nt.typ.Kind == vdltool.GoKindBool
}
// nativeType returns the type name for the native type.
func (nt nativeGoType) nativeType(data *goData) string {
native := nt.typ
result := native.Type
for _, imp := range native.Imports {
// Translate the packages specified in the native type into local package
// identifiers. E.g. if the native type is "foo.Type" with import
// "path/to/foo", we need to replace "foo." in the native type with the
// local package identifier for "path/to/foo".
if strings.Contains(result, imp.Name+".") {
// Add the import dependency if there is a match.
pkg := data.Pkg(imp.Path)
result = strings.ReplaceAll(result, imp.Name+".", pkg)
}
}
data.AddForcedPkg(nt.wirePkg.GenPath)
return result
}
// constNative returns a const value for the native type.
func (nt nativeGoType) constNative(data *goData, v *vdl.Value, typed bool) string {
native := nt.typ
nType := nt.nativeType(data)
if native.Zero.Mode != vdltool.GoZeroModeUnknown && v.IsZero() {
// This is the case where the value is zero, and the zero mode is either
// Canonical or Unique, which means that the Go zero value of the native
// type is sufficient to represent the value.
if typed {
return nt.typedConstNativeZero(nType)
}
return nt.untypedConstNativeZero(nType)
}
return constNativeConversion(data, v, nType, toNative(data, native, v.Type()))
}
func toNative(data *goData, native vdltool.GoType, ttWire *vdl.Type) string {
if native.ToNative != "" {
result := native.ToNative
for _, imp := range native.Imports {
// Translate the packages specified in the native type into local package
// identifiers. E.g. if the native type is "foo.Type" with import
// "path/to/foo", we need to replace "foo." in the native type with the
// local package identifier for "path/to/foo".
if strings.Contains(result, imp.Name+".") {
// Add the import dependency if there is a match.
pkg := data.Pkg(imp.Path)
result = strings.ReplaceAll(result, imp.Name+".", pkg)
}
}
return result
}
return typeGoWire(data, ttWire) + "ToNative"
}
// fieldConst will return the typed field const of the supplied
// value if it has a native type configured, or "" otherwise.
func (nt nativeGoType) fieldConst(data *goData, v *vdl.Value) (string, bool) {
switch nt.typ.Kind {
case vdltool.GoKindArray, vdltool.GoKindSlice, vdltool.GoKindMap, vdltool.GoKindStruct:
return typedConst(data, v), true
}
return "", false
}
func (nt nativeGoType) isZeroModeUnique() bool {
return nt.typ.Zero.Mode == vdltool.GoZeroModeUnique
}
func (nt nativeGoType) isZeroModeKnown() bool {
return nt.typ.Zero.Mode != vdltool.GoZeroModeUnknown
}
func (nt nativeGoType) isDotZero() bool {
return strings.HasPrefix(nt.typ.Zero.IsZero, ".")
}
func (nt nativeGoType) isZeroValue() string {
return nt.typ.Zero.IsZero
}
func (nt nativeGoType) zeroUniqueModeValue(data *goData, genIfStatement bool) string {
// We use an untyped const as the zero value, because Go only allows
// comparison of slices with nil. E.g.
// type MySlice []string
// pass := MySlice(nil) == nil // valid
// fail := MySlice(nil) == MySlice(nil) // invalid
nType := nt.nativeType(data)
zeroValue := nt.untypedConstNativeZero(nType)
if genIfStatement {
if k := nt.typ.Kind; k == vdltool.GoKindStruct || k == vdltool.GoKindArray {
// Without a special-case, we'll get a statement like:
// if x == Foo{} {
// But that isn't valid Go code, so we change it to:
// if x == (Foo{}) {
zeroValue = "(" + zeroValue + ")"
}
}
return zeroValue
}
func (nt nativeGoType) typedConstNativeZero(nType string) string {
zero := nt.untypedConstNativeZero(nType)
switch nt.typ.Kind {
case vdltool.GoKindStruct, vdltool.GoKindArray:
return zero // untyped const is already typed, e.g. NativeType{}
default:
return nType + "(" + zero + ")" // e.g. NativeType(0)
}
}
func (nt nativeGoType) untypedConstNativeZero(nType string) string {
switch nt.typ.Kind {
case vdltool.GoKindStruct, vdltool.GoKindArray:
return nType + "{}" // No way to create an untyped zero struct or array.
case vdltool.GoKindBool:
return "false"
case vdltool.GoKindNumber:
return "0"
case vdltool.GoKindString:
return `""`
default:
return "nil"
}
}