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