blob: b2434712bfb07ea6b1dabee526488efc4b26b7c7 [file] [log] [blame]
Cole Faustc9508aa2023-02-07 11:38:27 -08001// Copyright 2023 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package starlark_import
16
17import (
18 "fmt"
19 "math"
20 "reflect"
21 "unsafe"
22
23 "go.starlark.net/starlark"
24 "go.starlark.net/starlarkstruct"
25)
26
27func Unmarshal[T any](value starlark.Value) (T, error) {
Cole Faustf8231dd2023-04-21 17:37:11 -070028 x, err := UnmarshalReflect(value, reflect.TypeOf((*T)(nil)).Elem())
Cole Faustc9508aa2023-02-07 11:38:27 -080029 return x.Interface().(T), err
30}
31
32func UnmarshalReflect(value starlark.Value, ty reflect.Type) (reflect.Value, error) {
Cole Faustf8231dd2023-04-21 17:37:11 -070033 if ty == reflect.TypeOf((*starlark.Value)(nil)).Elem() {
34 return reflect.ValueOf(value), nil
35 }
Cole Faustc9508aa2023-02-07 11:38:27 -080036 zero := reflect.Zero(ty)
Cole Faustf055db62023-07-24 15:17:03 -070037 if value == nil {
38 panic("nil value")
39 }
Cole Faustc9508aa2023-02-07 11:38:27 -080040 var result reflect.Value
41 if ty.Kind() == reflect.Interface {
42 var err error
43 ty, err = typeOfStarlarkValue(value)
44 if err != nil {
45 return zero, err
46 }
47 }
48 if ty.Kind() == reflect.Map {
49 result = reflect.MakeMap(ty)
50 } else {
51 result = reflect.Indirect(reflect.New(ty))
52 }
53
54 switch v := value.(type) {
55 case starlark.String:
56 if result.Type().Kind() != reflect.String {
57 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
58 }
59 result.SetString(v.GoString())
60 case starlark.Int:
61 signedValue, signedOk := v.Int64()
62 unsignedValue, unsignedOk := v.Uint64()
63 switch result.Type().Kind() {
64 case reflect.Int64:
65 if !signedOk {
66 return zero, fmt.Errorf("starlark int didn't fit in go int64")
67 }
68 result.SetInt(signedValue)
69 case reflect.Int32:
70 if !signedOk || signedValue > math.MaxInt32 || signedValue < math.MinInt32 {
71 return zero, fmt.Errorf("starlark int didn't fit in go int32")
72 }
73 result.SetInt(signedValue)
74 case reflect.Int16:
75 if !signedOk || signedValue > math.MaxInt16 || signedValue < math.MinInt16 {
76 return zero, fmt.Errorf("starlark int didn't fit in go int16")
77 }
78 result.SetInt(signedValue)
79 case reflect.Int8:
80 if !signedOk || signedValue > math.MaxInt8 || signedValue < math.MinInt8 {
81 return zero, fmt.Errorf("starlark int didn't fit in go int8")
82 }
83 result.SetInt(signedValue)
84 case reflect.Int:
85 if !signedOk || signedValue > math.MaxInt || signedValue < math.MinInt {
86 return zero, fmt.Errorf("starlark int didn't fit in go int")
87 }
88 result.SetInt(signedValue)
89 case reflect.Uint64:
90 if !unsignedOk {
91 return zero, fmt.Errorf("starlark int didn't fit in go uint64")
92 }
93 result.SetUint(unsignedValue)
94 case reflect.Uint32:
95 if !unsignedOk || unsignedValue > math.MaxUint32 {
96 return zero, fmt.Errorf("starlark int didn't fit in go uint32")
97 }
98 result.SetUint(unsignedValue)
99 case reflect.Uint16:
100 if !unsignedOk || unsignedValue > math.MaxUint16 {
101 return zero, fmt.Errorf("starlark int didn't fit in go uint16")
102 }
103 result.SetUint(unsignedValue)
104 case reflect.Uint8:
105 if !unsignedOk || unsignedValue > math.MaxUint8 {
106 return zero, fmt.Errorf("starlark int didn't fit in go uint8")
107 }
108 result.SetUint(unsignedValue)
109 case reflect.Uint:
110 if !unsignedOk || unsignedValue > math.MaxUint {
111 return zero, fmt.Errorf("starlark int didn't fit in go uint")
112 }
113 result.SetUint(unsignedValue)
114 default:
115 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
116 }
117 case starlark.Float:
118 f := float64(v)
119 switch result.Type().Kind() {
120 case reflect.Float64:
121 result.SetFloat(f)
122 case reflect.Float32:
123 if f > math.MaxFloat32 || f < -math.MaxFloat32 {
124 return zero, fmt.Errorf("starlark float didn't fit in go float32")
125 }
126 result.SetFloat(f)
127 default:
128 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
129 }
130 case starlark.Bool:
131 if result.Type().Kind() != reflect.Bool {
132 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
133 }
134 result.SetBool(bool(v))
135 case starlark.Tuple:
136 if result.Type().Kind() != reflect.Slice {
137 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
138 }
139 elemType := result.Type().Elem()
140 // TODO: Add this grow call when we're on go 1.20
141 //result.Grow(v.Len())
142 for i := 0; i < v.Len(); i++ {
143 elem, err := UnmarshalReflect(v.Index(i), elemType)
144 if err != nil {
145 return zero, err
146 }
147 result = reflect.Append(result, elem)
148 }
149 case *starlark.List:
150 if result.Type().Kind() != reflect.Slice {
151 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
152 }
153 elemType := result.Type().Elem()
154 // TODO: Add this grow call when we're on go 1.20
155 //result.Grow(v.Len())
156 for i := 0; i < v.Len(); i++ {
157 elem, err := UnmarshalReflect(v.Index(i), elemType)
158 if err != nil {
159 return zero, err
160 }
161 result = reflect.Append(result, elem)
162 }
163 case *starlark.Dict:
164 if result.Type().Kind() != reflect.Map {
165 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
166 }
167 keyType := result.Type().Key()
168 valueType := result.Type().Elem()
169 for _, pair := range v.Items() {
170 key := pair.Index(0)
171 value := pair.Index(1)
172
173 unmarshalledKey, err := UnmarshalReflect(key, keyType)
174 if err != nil {
175 return zero, err
176 }
177 unmarshalledValue, err := UnmarshalReflect(value, valueType)
178 if err != nil {
179 return zero, err
180 }
181
182 result.SetMapIndex(unmarshalledKey, unmarshalledValue)
183 }
184 case *starlarkstruct.Struct:
185 if result.Type().Kind() != reflect.Struct {
186 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
187 }
188 if result.NumField() != len(v.AttrNames()) {
189 return zero, fmt.Errorf("starlark struct and go struct have different number of fields (%d and %d)", len(v.AttrNames()), result.NumField())
190 }
191 for _, attrName := range v.AttrNames() {
192 attr, err := v.Attr(attrName)
193 if err != nil {
194 return zero, err
195 }
196
197 // TODO(b/279787235): this should probably support tags to rename the field
198 resultField := result.FieldByName(attrName)
199 if resultField == (reflect.Value{}) {
200 return zero, fmt.Errorf("starlark struct had field %s, but requested struct type did not", attrName)
201 }
202 // This hack allows us to change unexported fields
203 resultField = reflect.NewAt(resultField.Type(), unsafe.Pointer(resultField.UnsafeAddr())).Elem()
204 x, err := UnmarshalReflect(attr, resultField.Type())
205 if err != nil {
206 return zero, err
207 }
208 resultField.Set(x)
209 }
210 default:
211 return zero, fmt.Errorf("unimplemented starlark type: %s", value.Type())
212 }
213
214 return result, nil
215}
216
217func typeOfStarlarkValue(value starlark.Value) (reflect.Type, error) {
218 var err error
219 switch v := value.(type) {
220 case starlark.String:
221 return reflect.TypeOf(""), nil
222 case *starlark.List:
223 innerType := reflect.TypeOf("")
224 if v.Len() > 0 {
225 innerType, err = typeOfStarlarkValue(v.Index(0))
226 if err != nil {
227 return nil, err
228 }
229 }
230 for i := 1; i < v.Len(); i++ {
231 innerTypeI, err := typeOfStarlarkValue(v.Index(i))
232 if err != nil {
233 return nil, err
234 }
235 if innerType != innerTypeI {
236 return nil, fmt.Errorf("List must contain elements of entirely the same type, found %v and %v", innerType, innerTypeI)
237 }
238 }
239 return reflect.SliceOf(innerType), nil
240 case *starlark.Dict:
241 keyType := reflect.TypeOf("")
242 valueType := reflect.TypeOf("")
243 keys := v.Keys()
244 if v.Len() > 0 {
245 firstKey := keys[0]
246 keyType, err = typeOfStarlarkValue(firstKey)
247 if err != nil {
248 return nil, err
249 }
250 firstValue, found, err := v.Get(firstKey)
251 if !found {
252 err = fmt.Errorf("value not found")
253 }
254 if err != nil {
255 return nil, err
256 }
257 valueType, err = typeOfStarlarkValue(firstValue)
258 if err != nil {
259 return nil, err
260 }
261 }
262 for _, key := range keys {
263 keyTypeI, err := typeOfStarlarkValue(key)
264 if err != nil {
265 return nil, err
266 }
267 if keyType != keyTypeI {
268 return nil, fmt.Errorf("dict must contain elements of entirely the same type, found %v and %v", keyType, keyTypeI)
269 }
270 value, found, err := v.Get(key)
271 if !found {
272 err = fmt.Errorf("value not found")
273 }
274 if err != nil {
275 return nil, err
276 }
277 valueTypeI, err := typeOfStarlarkValue(value)
278 if valueType.Kind() != reflect.Interface && valueTypeI != valueType {
279 // If we see conflicting value types, change the result value type to an empty interface
280 valueType = reflect.TypeOf([]interface{}{}).Elem()
281 }
282 }
283 return reflect.MapOf(keyType, valueType), nil
284 case starlark.Int:
285 return reflect.TypeOf(0), nil
286 case starlark.Float:
287 return reflect.TypeOf(0.0), nil
288 case starlark.Bool:
289 return reflect.TypeOf(true), nil
290 default:
291 return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
292 }
293}
Cole Faustf8231dd2023-04-21 17:37:11 -0700294
Cole Faust88c8efb2023-07-18 11:05:16 -0700295// UnmarshalNoneable is like Unmarshal, but it will accept None as the top level (but not nested)
296// starlark value. If the value is None, a nil pointer will be returned, otherwise a pointer
297// to the result of Unmarshal will be returned.
298func UnmarshalNoneable[T any](value starlark.Value) (*T, error) {
299 if _, ok := value.(starlark.NoneType); ok {
Cole Faustf8231dd2023-04-21 17:37:11 -0700300 return nil, nil
Cole Faustf8231dd2023-04-21 17:37:11 -0700301 }
Cole Faust88c8efb2023-07-18 11:05:16 -0700302 ret, err := Unmarshal[T](value)
303 return &ret, err
Cole Faustf8231dd2023-04-21 17:37:11 -0700304}