platform_build_soong/starlark_import/unmarshal.go
Cole Faust f8231dd0ea Platform mapping-based product config
This allows us to set product variables as build settings instead
of loading them from a target's provider, which further allows us
to read product config variables in transitions.

Bug: 287539062
Bug: 269577299
Test: Presubmits
Change-Id: I8497703f706162572ceb3486240e1eb02a37f5f6
2023-07-17 16:27:08 -07:00

304 lines
9.5 KiB
Go

// Copyright 2023 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package starlark_import
import (
"fmt"
"math"
"reflect"
"unsafe"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)
func Unmarshal[T any](value starlark.Value) (T, error) {
x, err := UnmarshalReflect(value, reflect.TypeOf((*T)(nil)).Elem())
return x.Interface().(T), err
}
func UnmarshalReflect(value starlark.Value, ty reflect.Type) (reflect.Value, error) {
if ty == reflect.TypeOf((*starlark.Value)(nil)).Elem() {
return reflect.ValueOf(value), nil
}
zero := reflect.Zero(ty)
var result reflect.Value
if ty.Kind() == reflect.Interface {
var err error
ty, err = typeOfStarlarkValue(value)
if err != nil {
return zero, err
}
}
if ty.Kind() == reflect.Map {
result = reflect.MakeMap(ty)
} else {
result = reflect.Indirect(reflect.New(ty))
}
switch v := value.(type) {
case starlark.String:
if result.Type().Kind() != reflect.String {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
result.SetString(v.GoString())
case starlark.Int:
signedValue, signedOk := v.Int64()
unsignedValue, unsignedOk := v.Uint64()
switch result.Type().Kind() {
case reflect.Int64:
if !signedOk {
return zero, fmt.Errorf("starlark int didn't fit in go int64")
}
result.SetInt(signedValue)
case reflect.Int32:
if !signedOk || signedValue > math.MaxInt32 || signedValue < math.MinInt32 {
return zero, fmt.Errorf("starlark int didn't fit in go int32")
}
result.SetInt(signedValue)
case reflect.Int16:
if !signedOk || signedValue > math.MaxInt16 || signedValue < math.MinInt16 {
return zero, fmt.Errorf("starlark int didn't fit in go int16")
}
result.SetInt(signedValue)
case reflect.Int8:
if !signedOk || signedValue > math.MaxInt8 || signedValue < math.MinInt8 {
return zero, fmt.Errorf("starlark int didn't fit in go int8")
}
result.SetInt(signedValue)
case reflect.Int:
if !signedOk || signedValue > math.MaxInt || signedValue < math.MinInt {
return zero, fmt.Errorf("starlark int didn't fit in go int")
}
result.SetInt(signedValue)
case reflect.Uint64:
if !unsignedOk {
return zero, fmt.Errorf("starlark int didn't fit in go uint64")
}
result.SetUint(unsignedValue)
case reflect.Uint32:
if !unsignedOk || unsignedValue > math.MaxUint32 {
return zero, fmt.Errorf("starlark int didn't fit in go uint32")
}
result.SetUint(unsignedValue)
case reflect.Uint16:
if !unsignedOk || unsignedValue > math.MaxUint16 {
return zero, fmt.Errorf("starlark int didn't fit in go uint16")
}
result.SetUint(unsignedValue)
case reflect.Uint8:
if !unsignedOk || unsignedValue > math.MaxUint8 {
return zero, fmt.Errorf("starlark int didn't fit in go uint8")
}
result.SetUint(unsignedValue)
case reflect.Uint:
if !unsignedOk || unsignedValue > math.MaxUint {
return zero, fmt.Errorf("starlark int didn't fit in go uint")
}
result.SetUint(unsignedValue)
default:
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
case starlark.Float:
f := float64(v)
switch result.Type().Kind() {
case reflect.Float64:
result.SetFloat(f)
case reflect.Float32:
if f > math.MaxFloat32 || f < -math.MaxFloat32 {
return zero, fmt.Errorf("starlark float didn't fit in go float32")
}
result.SetFloat(f)
default:
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
case starlark.Bool:
if result.Type().Kind() != reflect.Bool {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
result.SetBool(bool(v))
case starlark.Tuple:
if result.Type().Kind() != reflect.Slice {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
elemType := result.Type().Elem()
// TODO: Add this grow call when we're on go 1.20
//result.Grow(v.Len())
for i := 0; i < v.Len(); i++ {
elem, err := UnmarshalReflect(v.Index(i), elemType)
if err != nil {
return zero, err
}
result = reflect.Append(result, elem)
}
case *starlark.List:
if result.Type().Kind() != reflect.Slice {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
elemType := result.Type().Elem()
// TODO: Add this grow call when we're on go 1.20
//result.Grow(v.Len())
for i := 0; i < v.Len(); i++ {
elem, err := UnmarshalReflect(v.Index(i), elemType)
if err != nil {
return zero, err
}
result = reflect.Append(result, elem)
}
case *starlark.Dict:
if result.Type().Kind() != reflect.Map {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
keyType := result.Type().Key()
valueType := result.Type().Elem()
for _, pair := range v.Items() {
key := pair.Index(0)
value := pair.Index(1)
unmarshalledKey, err := UnmarshalReflect(key, keyType)
if err != nil {
return zero, err
}
unmarshalledValue, err := UnmarshalReflect(value, valueType)
if err != nil {
return zero, err
}
result.SetMapIndex(unmarshalledKey, unmarshalledValue)
}
case *starlarkstruct.Struct:
if result.Type().Kind() != reflect.Struct {
return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
}
if result.NumField() != len(v.AttrNames()) {
return zero, fmt.Errorf("starlark struct and go struct have different number of fields (%d and %d)", len(v.AttrNames()), result.NumField())
}
for _, attrName := range v.AttrNames() {
attr, err := v.Attr(attrName)
if err != nil {
return zero, err
}
// TODO(b/279787235): this should probably support tags to rename the field
resultField := result.FieldByName(attrName)
if resultField == (reflect.Value{}) {
return zero, fmt.Errorf("starlark struct had field %s, but requested struct type did not", attrName)
}
// This hack allows us to change unexported fields
resultField = reflect.NewAt(resultField.Type(), unsafe.Pointer(resultField.UnsafeAddr())).Elem()
x, err := UnmarshalReflect(attr, resultField.Type())
if err != nil {
return zero, err
}
resultField.Set(x)
}
default:
return zero, fmt.Errorf("unimplemented starlark type: %s", value.Type())
}
return result, nil
}
func typeOfStarlarkValue(value starlark.Value) (reflect.Type, error) {
var err error
switch v := value.(type) {
case starlark.String:
return reflect.TypeOf(""), nil
case *starlark.List:
innerType := reflect.TypeOf("")
if v.Len() > 0 {
innerType, err = typeOfStarlarkValue(v.Index(0))
if err != nil {
return nil, err
}
}
for i := 1; i < v.Len(); i++ {
innerTypeI, err := typeOfStarlarkValue(v.Index(i))
if err != nil {
return nil, err
}
if innerType != innerTypeI {
return nil, fmt.Errorf("List must contain elements of entirely the same type, found %v and %v", innerType, innerTypeI)
}
}
return reflect.SliceOf(innerType), nil
case *starlark.Dict:
keyType := reflect.TypeOf("")
valueType := reflect.TypeOf("")
keys := v.Keys()
if v.Len() > 0 {
firstKey := keys[0]
keyType, err = typeOfStarlarkValue(firstKey)
if err != nil {
return nil, err
}
firstValue, found, err := v.Get(firstKey)
if !found {
err = fmt.Errorf("value not found")
}
if err != nil {
return nil, err
}
valueType, err = typeOfStarlarkValue(firstValue)
if err != nil {
return nil, err
}
}
for _, key := range keys {
keyTypeI, err := typeOfStarlarkValue(key)
if err != nil {
return nil, err
}
if keyType != keyTypeI {
return nil, fmt.Errorf("dict must contain elements of entirely the same type, found %v and %v", keyType, keyTypeI)
}
value, found, err := v.Get(key)
if !found {
err = fmt.Errorf("value not found")
}
if err != nil {
return nil, err
}
valueTypeI, err := typeOfStarlarkValue(value)
if valueType.Kind() != reflect.Interface && valueTypeI != valueType {
// If we see conflicting value types, change the result value type to an empty interface
valueType = reflect.TypeOf([]interface{}{}).Elem()
}
}
return reflect.MapOf(keyType, valueType), nil
case starlark.Int:
return reflect.TypeOf(0), nil
case starlark.Float:
return reflect.TypeOf(0.0), nil
case starlark.Bool:
return reflect.TypeOf(true), nil
default:
return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
}
}
// NoneableString converts a starlark.Value to a string pointer. If the starlark.Value is NoneType,
// a nil pointer will be returned instead. All other types of starlark values are errors.
func NoneableString(value starlark.Value) (*string, error) {
switch v := value.(type) {
case starlark.String:
result := v.GoString()
return &result, nil
case starlark.NoneType:
return nil, nil
default:
return nil, fmt.Errorf("expected string or none, got %q", value.Type())
}
}