blob: 33c0cd92b1d23753211bdaf4262133043087b9ed [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)
37 var result reflect.Value
38 if ty.Kind() == reflect.Interface {
39 var err error
40 ty, err = typeOfStarlarkValue(value)
41 if err != nil {
42 return zero, err
43 }
44 }
45 if ty.Kind() == reflect.Map {
46 result = reflect.MakeMap(ty)
47 } else {
48 result = reflect.Indirect(reflect.New(ty))
49 }
50
51 switch v := value.(type) {
52 case starlark.String:
53 if result.Type().Kind() != reflect.String {
54 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
55 }
56 result.SetString(v.GoString())
57 case starlark.Int:
58 signedValue, signedOk := v.Int64()
59 unsignedValue, unsignedOk := v.Uint64()
60 switch result.Type().Kind() {
61 case reflect.Int64:
62 if !signedOk {
63 return zero, fmt.Errorf("starlark int didn't fit in go int64")
64 }
65 result.SetInt(signedValue)
66 case reflect.Int32:
67 if !signedOk || signedValue > math.MaxInt32 || signedValue < math.MinInt32 {
68 return zero, fmt.Errorf("starlark int didn't fit in go int32")
69 }
70 result.SetInt(signedValue)
71 case reflect.Int16:
72 if !signedOk || signedValue > math.MaxInt16 || signedValue < math.MinInt16 {
73 return zero, fmt.Errorf("starlark int didn't fit in go int16")
74 }
75 result.SetInt(signedValue)
76 case reflect.Int8:
77 if !signedOk || signedValue > math.MaxInt8 || signedValue < math.MinInt8 {
78 return zero, fmt.Errorf("starlark int didn't fit in go int8")
79 }
80 result.SetInt(signedValue)
81 case reflect.Int:
82 if !signedOk || signedValue > math.MaxInt || signedValue < math.MinInt {
83 return zero, fmt.Errorf("starlark int didn't fit in go int")
84 }
85 result.SetInt(signedValue)
86 case reflect.Uint64:
87 if !unsignedOk {
88 return zero, fmt.Errorf("starlark int didn't fit in go uint64")
89 }
90 result.SetUint(unsignedValue)
91 case reflect.Uint32:
92 if !unsignedOk || unsignedValue > math.MaxUint32 {
93 return zero, fmt.Errorf("starlark int didn't fit in go uint32")
94 }
95 result.SetUint(unsignedValue)
96 case reflect.Uint16:
97 if !unsignedOk || unsignedValue > math.MaxUint16 {
98 return zero, fmt.Errorf("starlark int didn't fit in go uint16")
99 }
100 result.SetUint(unsignedValue)
101 case reflect.Uint8:
102 if !unsignedOk || unsignedValue > math.MaxUint8 {
103 return zero, fmt.Errorf("starlark int didn't fit in go uint8")
104 }
105 result.SetUint(unsignedValue)
106 case reflect.Uint:
107 if !unsignedOk || unsignedValue > math.MaxUint {
108 return zero, fmt.Errorf("starlark int didn't fit in go uint")
109 }
110 result.SetUint(unsignedValue)
111 default:
112 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
113 }
114 case starlark.Float:
115 f := float64(v)
116 switch result.Type().Kind() {
117 case reflect.Float64:
118 result.SetFloat(f)
119 case reflect.Float32:
120 if f > math.MaxFloat32 || f < -math.MaxFloat32 {
121 return zero, fmt.Errorf("starlark float didn't fit in go float32")
122 }
123 result.SetFloat(f)
124 default:
125 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
126 }
127 case starlark.Bool:
128 if result.Type().Kind() != reflect.Bool {
129 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
130 }
131 result.SetBool(bool(v))
132 case starlark.Tuple:
133 if result.Type().Kind() != reflect.Slice {
134 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
135 }
136 elemType := result.Type().Elem()
137 // TODO: Add this grow call when we're on go 1.20
138 //result.Grow(v.Len())
139 for i := 0; i < v.Len(); i++ {
140 elem, err := UnmarshalReflect(v.Index(i), elemType)
141 if err != nil {
142 return zero, err
143 }
144 result = reflect.Append(result, elem)
145 }
146 case *starlark.List:
147 if result.Type().Kind() != reflect.Slice {
148 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
149 }
150 elemType := result.Type().Elem()
151 // TODO: Add this grow call when we're on go 1.20
152 //result.Grow(v.Len())
153 for i := 0; i < v.Len(); i++ {
154 elem, err := UnmarshalReflect(v.Index(i), elemType)
155 if err != nil {
156 return zero, err
157 }
158 result = reflect.Append(result, elem)
159 }
160 case *starlark.Dict:
161 if result.Type().Kind() != reflect.Map {
162 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
163 }
164 keyType := result.Type().Key()
165 valueType := result.Type().Elem()
166 for _, pair := range v.Items() {
167 key := pair.Index(0)
168 value := pair.Index(1)
169
170 unmarshalledKey, err := UnmarshalReflect(key, keyType)
171 if err != nil {
172 return zero, err
173 }
174 unmarshalledValue, err := UnmarshalReflect(value, valueType)
175 if err != nil {
176 return zero, err
177 }
178
179 result.SetMapIndex(unmarshalledKey, unmarshalledValue)
180 }
181 case *starlarkstruct.Struct:
182 if result.Type().Kind() != reflect.Struct {
183 return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
184 }
185 if result.NumField() != len(v.AttrNames()) {
186 return zero, fmt.Errorf("starlark struct and go struct have different number of fields (%d and %d)", len(v.AttrNames()), result.NumField())
187 }
188 for _, attrName := range v.AttrNames() {
189 attr, err := v.Attr(attrName)
190 if err != nil {
191 return zero, err
192 }
193
194 // TODO(b/279787235): this should probably support tags to rename the field
195 resultField := result.FieldByName(attrName)
196 if resultField == (reflect.Value{}) {
197 return zero, fmt.Errorf("starlark struct had field %s, but requested struct type did not", attrName)
198 }
199 // This hack allows us to change unexported fields
200 resultField = reflect.NewAt(resultField.Type(), unsafe.Pointer(resultField.UnsafeAddr())).Elem()
201 x, err := UnmarshalReflect(attr, resultField.Type())
202 if err != nil {
203 return zero, err
204 }
205 resultField.Set(x)
206 }
207 default:
208 return zero, fmt.Errorf("unimplemented starlark type: %s", value.Type())
209 }
210
211 return result, nil
212}
213
214func typeOfStarlarkValue(value starlark.Value) (reflect.Type, error) {
215 var err error
216 switch v := value.(type) {
217 case starlark.String:
218 return reflect.TypeOf(""), nil
219 case *starlark.List:
220 innerType := reflect.TypeOf("")
221 if v.Len() > 0 {
222 innerType, err = typeOfStarlarkValue(v.Index(0))
223 if err != nil {
224 return nil, err
225 }
226 }
227 for i := 1; i < v.Len(); i++ {
228 innerTypeI, err := typeOfStarlarkValue(v.Index(i))
229 if err != nil {
230 return nil, err
231 }
232 if innerType != innerTypeI {
233 return nil, fmt.Errorf("List must contain elements of entirely the same type, found %v and %v", innerType, innerTypeI)
234 }
235 }
236 return reflect.SliceOf(innerType), nil
237 case *starlark.Dict:
238 keyType := reflect.TypeOf("")
239 valueType := reflect.TypeOf("")
240 keys := v.Keys()
241 if v.Len() > 0 {
242 firstKey := keys[0]
243 keyType, err = typeOfStarlarkValue(firstKey)
244 if err != nil {
245 return nil, err
246 }
247 firstValue, found, err := v.Get(firstKey)
248 if !found {
249 err = fmt.Errorf("value not found")
250 }
251 if err != nil {
252 return nil, err
253 }
254 valueType, err = typeOfStarlarkValue(firstValue)
255 if err != nil {
256 return nil, err
257 }
258 }
259 for _, key := range keys {
260 keyTypeI, err := typeOfStarlarkValue(key)
261 if err != nil {
262 return nil, err
263 }
264 if keyType != keyTypeI {
265 return nil, fmt.Errorf("dict must contain elements of entirely the same type, found %v and %v", keyType, keyTypeI)
266 }
267 value, found, err := v.Get(key)
268 if !found {
269 err = fmt.Errorf("value not found")
270 }
271 if err != nil {
272 return nil, err
273 }
274 valueTypeI, err := typeOfStarlarkValue(value)
275 if valueType.Kind() != reflect.Interface && valueTypeI != valueType {
276 // If we see conflicting value types, change the result value type to an empty interface
277 valueType = reflect.TypeOf([]interface{}{}).Elem()
278 }
279 }
280 return reflect.MapOf(keyType, valueType), nil
281 case starlark.Int:
282 return reflect.TypeOf(0), nil
283 case starlark.Float:
284 return reflect.TypeOf(0.0), nil
285 case starlark.Bool:
286 return reflect.TypeOf(true), nil
287 default:
288 return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
289 }
290}
Cole Faustf8231dd2023-04-21 17:37:11 -0700291
Cole Faust88c8efb2023-07-18 11:05:16 -0700292// UnmarshalNoneable is like Unmarshal, but it will accept None as the top level (but not nested)
293// starlark value. If the value is None, a nil pointer will be returned, otherwise a pointer
294// to the result of Unmarshal will be returned.
295func UnmarshalNoneable[T any](value starlark.Value) (*T, error) {
296 if _, ok := value.(starlark.NoneType); ok {
Cole Faustf8231dd2023-04-21 17:37:11 -0700297 return nil, nil
Cole Faustf8231dd2023-04-21 17:37:11 -0700298 }
Cole Faust88c8efb2023-07-18 11:05:16 -0700299 ret, err := Unmarshal[T](value)
300 return &ret, err
Cole Faustf8231dd2023-04-21 17:37:11 -0700301}