185cb44bef
Some soong code sets arch-variant properties in order to control a module's default behavior. I'll make this continue to work, but long term the arch-variant properties should be replaced with selects, so expose an API for creating select statements in soong code. Bug: 323382414 Test: m nothing --no-skip-soong-tests Change-Id: I6c65d6e112b6f826f1027777b6fdf36915d34b1d
695 lines
22 KiB
Go
695 lines
22 KiB
Go
// Copyright 2014 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 proptools
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/scanner"
|
|
|
|
"github.com/google/blueprint/parser"
|
|
)
|
|
|
|
const maxUnpackErrors = 10
|
|
|
|
type UnpackError struct {
|
|
Err error
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (e *UnpackError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
|
|
}
|
|
|
|
// packedProperty helps to track properties usage (`used` will be true)
|
|
type packedProperty struct {
|
|
property *parser.Property
|
|
used bool
|
|
}
|
|
|
|
// unpackContext keeps compound names and their values in a map. It is initialized from
|
|
// parsed properties.
|
|
type unpackContext struct {
|
|
propertyMap map[string]*packedProperty
|
|
errs []error
|
|
}
|
|
|
|
// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties.
|
|
// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized
|
|
// from it. See PropertyNameForField for field and property name matching.
|
|
// For instance, if the input contains
|
|
//
|
|
// { foo: "abc", bar: {x: 1},}
|
|
//
|
|
// and a runtime value being has been declared as
|
|
//
|
|
// var v struct { Foo string; Bar int }
|
|
//
|
|
// then v.Foo will be set to "abc" and v.Bar will be set to 1
|
|
// (cf. unpack_test.go for further examples)
|
|
//
|
|
// The type of a receiving field has to match the property type, i.e., a bool/int/string field
|
|
// can be set from a property with bool/int/string value, a struct can be set from a map (only the
|
|
// matching fields are set), and an slice can be set from a list.
|
|
// If a field of a runtime value has been already set prior to the UnpackProperties, the new value
|
|
// is appended to it (see somewhat inappropriately named ExtendBasicType).
|
|
// The same property can initialize fields in multiple runtime values. It is an error if any property
|
|
// value was not used to initialize at least one field.
|
|
func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) {
|
|
var unpackContext unpackContext
|
|
unpackContext.propertyMap = make(map[string]*packedProperty)
|
|
if !unpackContext.buildPropertyMap("", properties) {
|
|
return nil, unpackContext.errs
|
|
}
|
|
|
|
for _, obj := range objects {
|
|
valueObject := reflect.ValueOf(obj)
|
|
if !isStructPtr(valueObject.Type()) {
|
|
panic(fmt.Errorf("properties must be *struct, got %s",
|
|
valueObject.Type()))
|
|
}
|
|
unpackContext.unpackToStruct("", valueObject.Elem())
|
|
if len(unpackContext.errs) >= maxUnpackErrors {
|
|
return nil, unpackContext.errs
|
|
}
|
|
}
|
|
|
|
// Gather property map, and collect any unused properties.
|
|
// Avoid reporting subproperties of unused properties.
|
|
result := make(map[string]*parser.Property)
|
|
var unusedNames []string
|
|
for name, v := range unpackContext.propertyMap {
|
|
if v.used {
|
|
result[name] = v.property
|
|
} else {
|
|
unusedNames = append(unusedNames, name)
|
|
}
|
|
}
|
|
if len(unusedNames) == 0 && len(unpackContext.errs) == 0 {
|
|
return result, nil
|
|
}
|
|
return nil, unpackContext.reportUnusedNames(unusedNames)
|
|
}
|
|
|
|
func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error {
|
|
sort.Strings(unusedNames)
|
|
unusedNames = removeUnnecessaryUnusedNames(unusedNames)
|
|
var lastReported string
|
|
for _, name := range unusedNames {
|
|
// if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*'
|
|
if lastReported != "" {
|
|
trimmed := strings.TrimPrefix(name, lastReported)
|
|
if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') {
|
|
continue
|
|
}
|
|
}
|
|
ctx.errs = append(ctx.errs, &UnpackError{
|
|
fmt.Errorf("unrecognized property %q", name),
|
|
ctx.propertyMap[name].property.ColonPos})
|
|
lastReported = name
|
|
}
|
|
return ctx.errs
|
|
}
|
|
|
|
// When property a.b.c is not used, (also there is no a.* or a.b.* used)
|
|
// "a", "a.b" and "a.b.c" are all in unusedNames.
|
|
// removeUnnecessaryUnusedNames only keeps the last "a.b.c" as the real unused
|
|
// name.
|
|
func removeUnnecessaryUnusedNames(names []string) []string {
|
|
if len(names) == 0 {
|
|
return names
|
|
}
|
|
var simplifiedNames []string
|
|
for index, name := range names {
|
|
if index == len(names)-1 || !strings.HasPrefix(names[index+1], name) {
|
|
simplifiedNames = append(simplifiedNames, name)
|
|
}
|
|
}
|
|
return simplifiedNames
|
|
}
|
|
|
|
func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool {
|
|
nOldErrors := len(ctx.errs)
|
|
for _, property := range properties {
|
|
name := fieldPath(prefix, property.Name)
|
|
if first, present := ctx.propertyMap[name]; present {
|
|
ctx.addError(
|
|
&UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos})
|
|
if ctx.addError(
|
|
&UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
|
|
ctx.propertyMap[name] = &packedProperty{property, false}
|
|
switch propValue := property.Value.Eval().(type) {
|
|
case *parser.Map:
|
|
ctx.buildPropertyMap(name, propValue.Properties)
|
|
case *parser.List:
|
|
// If it is a list, unroll it unless its elements are of primitive type
|
|
// (no further mapping will be needed in that case, so we avoid cluttering
|
|
// the map).
|
|
if len(propValue.Values) == 0 {
|
|
continue
|
|
}
|
|
if t := propValue.Values[0].Type(); t == parser.StringType || t == parser.Int64Type || t == parser.BoolType {
|
|
continue
|
|
}
|
|
|
|
itemProperties := make([]*parser.Property, len(propValue.Values))
|
|
for i, expr := range propValue.Values {
|
|
itemProperties[i] = &parser.Property{
|
|
Name: property.Name + "[" + strconv.Itoa(i) + "]",
|
|
NamePos: property.NamePos,
|
|
ColonPos: property.ColonPos,
|
|
Value: expr,
|
|
}
|
|
}
|
|
if !ctx.buildPropertyMap(prefix, itemProperties) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return len(ctx.errs) == nOldErrors
|
|
}
|
|
|
|
func fieldPath(prefix, fieldName string) string {
|
|
if prefix == "" {
|
|
return fieldName
|
|
}
|
|
return prefix + "." + fieldName
|
|
}
|
|
|
|
func (ctx *unpackContext) addError(e error) bool {
|
|
ctx.errs = append(ctx.errs, e)
|
|
return len(ctx.errs) < maxUnpackErrors
|
|
}
|
|
|
|
func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) {
|
|
structType := structValue.Type()
|
|
|
|
for i := 0; i < structValue.NumField(); i++ {
|
|
fieldValue := structValue.Field(i)
|
|
field := structType.Field(i)
|
|
|
|
// In Go 1.7, runtime-created structs are unexported, so it's not
|
|
// possible to create an exported anonymous field with a generated
|
|
// type. So workaround this by special-casing "BlueprintEmbed" to
|
|
// behave like an anonymous field for structure unpacking.
|
|
if field.Name == "BlueprintEmbed" {
|
|
field.Name = ""
|
|
field.Anonymous = true
|
|
}
|
|
|
|
if field.PkgPath != "" {
|
|
// This is an unexported field, so just skip it.
|
|
continue
|
|
}
|
|
|
|
propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name))
|
|
|
|
if !fieldValue.CanSet() {
|
|
panic(fmt.Errorf("field %s is not settable", propertyName))
|
|
}
|
|
|
|
// Get the property value if it was specified.
|
|
packedProperty, propertyIsSet := ctx.propertyMap[propertyName]
|
|
|
|
origFieldValue := fieldValue
|
|
|
|
// To make testing easier we validate the struct field's type regardless
|
|
// of whether or not the property was specified in the parsed string.
|
|
// TODO(ccross): we don't validate types inside nil struct pointers
|
|
// Move type validation to a function that runs on each factory once
|
|
switch kind := fieldValue.Kind(); kind {
|
|
case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice:
|
|
// Do nothing
|
|
case reflect.Interface:
|
|
if fieldValue.IsNil() {
|
|
panic(fmt.Errorf("field %s contains a nil interface", propertyName))
|
|
}
|
|
fieldValue = fieldValue.Elem()
|
|
elemType := fieldValue.Type()
|
|
if elemType.Kind() != reflect.Ptr {
|
|
panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
|
|
}
|
|
fallthrough
|
|
case reflect.Ptr:
|
|
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
|
|
case reflect.Struct:
|
|
if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
|
|
// Instantiate nil struct pointers
|
|
// Set into origFieldValue in case it was an interface, in which case
|
|
// fieldValue points to the unsettable pointer inside the interface
|
|
fieldValue = reflect.New(fieldValue.Type().Elem())
|
|
origFieldValue.Set(fieldValue)
|
|
}
|
|
fieldValue = fieldValue.Elem()
|
|
case reflect.Bool, reflect.Int64, reflect.String:
|
|
// Nothing
|
|
default:
|
|
panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
|
|
}
|
|
|
|
case reflect.Int, reflect.Uint:
|
|
if !HasTag(field, "blueprint", "mutated") {
|
|
panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
|
|
}
|
|
|
|
default:
|
|
panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
|
|
}
|
|
|
|
if field.Anonymous && isStruct(fieldValue.Type()) {
|
|
ctx.unpackToStruct(namePrefix, fieldValue)
|
|
continue
|
|
}
|
|
|
|
if !propertyIsSet {
|
|
// This property wasn't specified.
|
|
continue
|
|
}
|
|
|
|
packedProperty.used = true
|
|
property := packedProperty.property
|
|
|
|
if HasTag(field, "blueprint", "mutated") {
|
|
if !ctx.addError(
|
|
&UnpackError{
|
|
fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
|
|
property.ColonPos,
|
|
}) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
|
|
if isConfigurable(fieldValue.Type()) {
|
|
// configurableType is the reflect.Type representation of a Configurable[whatever],
|
|
// while configuredType is the reflect.Type of the "whatever".
|
|
configurableType := fieldValue.Type()
|
|
configuredType := fieldValue.Interface().(configurableReflection).configuredType()
|
|
if unpackedValue, ok := ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType); ok {
|
|
ExtendBasicType(fieldValue, unpackedValue.Elem(), Append)
|
|
}
|
|
if len(ctx.errs) >= maxUnpackErrors {
|
|
return
|
|
}
|
|
} else if isStruct(fieldValue.Type()) {
|
|
if property.Value.Eval().Type() != parser.MapType {
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign %s value to map property %q",
|
|
property.Value.Type(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
continue
|
|
}
|
|
ctx.unpackToStruct(propertyName, fieldValue)
|
|
if len(ctx.errs) >= maxUnpackErrors {
|
|
return
|
|
}
|
|
} else if isSlice(fieldValue.Type()) {
|
|
if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok {
|
|
ExtendBasicType(fieldValue, unpackedValue, Append)
|
|
}
|
|
if len(ctx.errs) >= maxUnpackErrors {
|
|
return
|
|
}
|
|
} else {
|
|
unpackedValue, err := propertyToValue(fieldValue.Type(), property)
|
|
if err != nil && !ctx.addError(err) {
|
|
return
|
|
}
|
|
ExtendBasicType(fieldValue, unpackedValue, Append)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Converts the given property to a pointer to a configurable struct
|
|
func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *parser.Property, configurableType, configuredType reflect.Type) (reflect.Value, bool) {
|
|
switch v := property.Value.(type) {
|
|
case *parser.String:
|
|
if configuredType.Kind() != reflect.String {
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign string value to configurable %s property %q",
|
|
configuredType.String(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
return reflect.New(configurableType), false
|
|
}
|
|
result := Configurable[string]{
|
|
propertyName: property.Name,
|
|
cases: []ConfigurableCase[string]{{
|
|
value: &v.Value,
|
|
}},
|
|
appendWrapper: &appendWrapper[string]{},
|
|
}
|
|
return reflect.ValueOf(&result), true
|
|
case *parser.Bool:
|
|
if configuredType.Kind() != reflect.Bool {
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign bool value to configurable %s property %q",
|
|
configuredType.String(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
return reflect.New(configurableType), false
|
|
}
|
|
result := Configurable[bool]{
|
|
propertyName: property.Name,
|
|
cases: []ConfigurableCase[bool]{{
|
|
value: &v.Value,
|
|
}},
|
|
appendWrapper: &appendWrapper[bool]{},
|
|
}
|
|
return reflect.ValueOf(&result), true
|
|
case *parser.List:
|
|
if configuredType.Kind() != reflect.Slice {
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign list value to configurable %s property %q",
|
|
configuredType.String(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
return reflect.New(configurableType), false
|
|
}
|
|
switch configuredType.Elem().Kind() {
|
|
case reflect.String:
|
|
var value []string
|
|
if v.Values != nil {
|
|
value = make([]string, len(v.Values))
|
|
itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
|
|
for i, expr := range v.Values {
|
|
itemProperty.Name = propertyName + "[" + strconv.Itoa(i) + "]"
|
|
itemProperty.Value = expr
|
|
exprUnpacked, err := propertyToValue(configuredType.Elem(), itemProperty)
|
|
if err != nil {
|
|
ctx.addError(err)
|
|
return reflect.ValueOf(Configurable[[]string]{}), false
|
|
}
|
|
value[i] = exprUnpacked.Interface().(string)
|
|
}
|
|
}
|
|
result := Configurable[[]string]{
|
|
propertyName: property.Name,
|
|
cases: []ConfigurableCase[[]string]{{
|
|
value: &value,
|
|
}},
|
|
appendWrapper: &appendWrapper[[]string]{},
|
|
}
|
|
return reflect.ValueOf(&result), true
|
|
default:
|
|
panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
|
|
}
|
|
case *parser.Operator:
|
|
property.Value = v.Value.Eval()
|
|
return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
|
|
case *parser.Variable:
|
|
property.Value = v.Value.Eval()
|
|
return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
|
|
case *parser.Select:
|
|
resultPtr := reflect.New(configurableType)
|
|
result := resultPtr.Elem()
|
|
conditions := make([]ConfigurableCondition, len(v.Conditions))
|
|
for i, cond := range v.Conditions {
|
|
args := make([]string, len(cond.Args))
|
|
for j, arg := range cond.Args {
|
|
args[j] = arg.Value
|
|
}
|
|
conditions[i] = ConfigurableCondition{
|
|
FunctionName: cond.FunctionName,
|
|
Args: args,
|
|
}
|
|
}
|
|
|
|
configurableCaseType := configurableCaseType(configuredType)
|
|
cases := reflect.MakeSlice(reflect.SliceOf(configurableCaseType), 0, len(v.Cases))
|
|
for i, c := range v.Cases {
|
|
p := &parser.Property{
|
|
Name: property.Name + "[" + strconv.Itoa(i) + "]",
|
|
NamePos: c.ColonPos,
|
|
Value: c.Value,
|
|
}
|
|
|
|
patterns := make([]ConfigurablePattern, len(c.Patterns))
|
|
for i, pat := range c.Patterns {
|
|
switch pat := pat.(type) {
|
|
case *parser.String:
|
|
if pat.Value == "__soong_conditions_default__" {
|
|
patterns[i].typ = configurablePatternTypeDefault
|
|
} else {
|
|
patterns[i].typ = configurablePatternTypeString
|
|
patterns[i].stringValue = pat.Value
|
|
}
|
|
case *parser.Bool:
|
|
patterns[i].typ = configurablePatternTypeBool
|
|
patterns[i].boolValue = pat.Value
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
var value reflect.Value
|
|
// Map the "unset" keyword to a nil pointer in the cases map
|
|
if _, ok := c.Value.(parser.UnsetProperty); ok {
|
|
value = reflect.Zero(reflect.PointerTo(configuredType))
|
|
} else {
|
|
var err error
|
|
switch configuredType.Kind() {
|
|
case reflect.String, reflect.Bool:
|
|
value, err = propertyToValue(reflect.PointerTo(configuredType), p)
|
|
if err != nil {
|
|
ctx.addError(&UnpackError{
|
|
err,
|
|
c.Value.Pos(),
|
|
})
|
|
return reflect.New(configurableType), false
|
|
}
|
|
case reflect.Slice:
|
|
if configuredType.Elem().Kind() != reflect.String {
|
|
panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
|
|
}
|
|
value, ok = ctx.unpackToSlice(p.Name, p, reflect.PointerTo(configuredType))
|
|
if !ok {
|
|
return reflect.New(configurableType), false
|
|
}
|
|
default:
|
|
panic("This should be unreachable because ConfigurableElements only accepts strings, boools, or slices of strings")
|
|
}
|
|
}
|
|
|
|
case_ := reflect.New(configurableCaseType)
|
|
case_.Interface().(configurableCaseReflection).initialize(patterns, value.Interface())
|
|
cases = reflect.Append(cases, case_.Elem())
|
|
}
|
|
resultPtr.Interface().(configurablePtrReflection).initialize(
|
|
property.Name,
|
|
conditions,
|
|
cases.Interface(),
|
|
)
|
|
if v.Append != nil {
|
|
p := &parser.Property{
|
|
Name: property.Name,
|
|
NamePos: property.NamePos,
|
|
Value: v.Append,
|
|
}
|
|
val, ok := ctx.unpackToConfigurable(propertyName, p, configurableType, configuredType)
|
|
if !ok {
|
|
return reflect.New(configurableType), false
|
|
}
|
|
result.Interface().(configurableReflection).setAppend(val.Elem().Interface(), false)
|
|
}
|
|
return resultPtr, true
|
|
default:
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign %s value to configurable %s property %q",
|
|
property.Value.Type(), configuredType.String(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
return reflect.New(configurableType), false
|
|
}
|
|
}
|
|
|
|
func (ctx *unpackContext) reportSelectOnNonConfigurablePropertyError(
|
|
property *parser.Property,
|
|
) bool {
|
|
if _, ok := property.Value.Eval().(*parser.Select); !ok {
|
|
return false
|
|
}
|
|
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign select statement to non-configurable property %q. This requires a small soong change to enable in most cases, please file a go/soong-bug if you'd like to use a select statement here",
|
|
property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
|
|
return true
|
|
}
|
|
|
|
// unpackSlice creates a value of a given slice or pointer to slice type from the property,
|
|
// which should be a list
|
|
func (ctx *unpackContext) unpackToSlice(
|
|
sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
|
|
if sliceType.Kind() == reflect.Pointer {
|
|
sliceType = sliceType.Elem()
|
|
result := reflect.New(sliceType)
|
|
slice, ok := ctx.unpackToSliceInner(sliceName, property, sliceType)
|
|
if !ok {
|
|
return result, ok
|
|
}
|
|
result.Elem().Set(slice)
|
|
return result, true
|
|
}
|
|
return ctx.unpackToSliceInner(sliceName, property, sliceType)
|
|
}
|
|
|
|
// unpackToSliceInner creates a value of a given slice type from the property,
|
|
// which should be a list. It doesn't support pointers to slice types like unpackToSlice
|
|
// does.
|
|
func (ctx *unpackContext) unpackToSliceInner(
|
|
sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
|
|
propValueAsList, ok := property.Value.Eval().(*parser.List)
|
|
if !ok {
|
|
if !ctx.reportSelectOnNonConfigurablePropertyError(property) {
|
|
ctx.addError(&UnpackError{
|
|
fmt.Errorf("can't assign %s value to list property %q",
|
|
property.Value.Type(), property.Name),
|
|
property.Value.Pos(),
|
|
})
|
|
}
|
|
return reflect.MakeSlice(sliceType, 0, 0), false
|
|
}
|
|
exprs := propValueAsList.Values
|
|
value := reflect.MakeSlice(sliceType, 0, len(exprs))
|
|
if len(exprs) == 0 {
|
|
return value, true
|
|
}
|
|
|
|
// The function to construct an item value depends on the type of list elements.
|
|
var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool)
|
|
switch exprs[0].Type() {
|
|
case parser.BoolType, parser.StringType, parser.Int64Type:
|
|
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
|
value, err := propertyToValue(t, property)
|
|
if err != nil {
|
|
ctx.addError(err)
|
|
return value, false
|
|
}
|
|
return value, true
|
|
}
|
|
case parser.ListType:
|
|
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
|
return ctx.unpackToSlice(property.Name, property, t)
|
|
}
|
|
case parser.MapType:
|
|
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
|
itemValue := reflect.New(t).Elem()
|
|
ctx.unpackToStruct(property.Name, itemValue)
|
|
return itemValue, true
|
|
}
|
|
case parser.NotEvaluatedType:
|
|
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
|
return reflect.New(t), false
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("bizarre property expression type: %v", exprs[0].Type()))
|
|
}
|
|
|
|
itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
|
|
elemType := sliceType.Elem()
|
|
isPtr := elemType.Kind() == reflect.Ptr
|
|
|
|
for i, expr := range exprs {
|
|
itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
|
|
itemProperty.Value = expr
|
|
if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
|
|
packedProperty.used = true
|
|
}
|
|
if isPtr {
|
|
if itemValue, ok := getItemFunc(itemProperty, elemType.Elem()); ok {
|
|
ptrValue := reflect.New(itemValue.Type())
|
|
ptrValue.Elem().Set(itemValue)
|
|
value = reflect.Append(value, ptrValue)
|
|
}
|
|
} else {
|
|
if itemValue, ok := getItemFunc(itemProperty, elemType); ok {
|
|
value = reflect.Append(value, itemValue)
|
|
}
|
|
}
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
// propertyToValue creates a value of a given value type from the property.
|
|
func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
|
|
var value reflect.Value
|
|
var baseType reflect.Type
|
|
isPtr := typ.Kind() == reflect.Ptr
|
|
if isPtr {
|
|
baseType = typ.Elem()
|
|
} else {
|
|
baseType = typ
|
|
}
|
|
|
|
switch kind := baseType.Kind(); kind {
|
|
case reflect.Bool:
|
|
b, ok := property.Value.Eval().(*parser.Bool)
|
|
if !ok {
|
|
return value, &UnpackError{
|
|
fmt.Errorf("can't assign %s value to bool property %q",
|
|
property.Value.Type(), property.Name),
|
|
property.Value.Pos(),
|
|
}
|
|
}
|
|
value = reflect.ValueOf(b.Value)
|
|
|
|
case reflect.Int64:
|
|
b, ok := property.Value.Eval().(*parser.Int64)
|
|
if !ok {
|
|
return value, &UnpackError{
|
|
fmt.Errorf("can't assign %s value to int64 property %q",
|
|
property.Value.Type(), property.Name),
|
|
property.Value.Pos(),
|
|
}
|
|
}
|
|
value = reflect.ValueOf(b.Value)
|
|
|
|
case reflect.String:
|
|
s, ok := property.Value.Eval().(*parser.String)
|
|
if !ok {
|
|
return value, &UnpackError{
|
|
fmt.Errorf("can't assign %s value to string property %q",
|
|
property.Value.Type(), property.Name),
|
|
property.Value.Pos(),
|
|
}
|
|
}
|
|
value = reflect.ValueOf(s.Value)
|
|
|
|
default:
|
|
return value, &UnpackError{
|
|
fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ),
|
|
property.NamePos}
|
|
}
|
|
|
|
if isPtr {
|
|
ptrValue := reflect.New(value.Type())
|
|
ptrValue.Elem().Set(value)
|
|
return ptrValue, nil
|
|
}
|
|
return value, nil
|
|
}
|