1// Copyright 2014 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 proptools
16
17import (
18	"fmt"
19	"reflect"
20	"sort"
21	"strconv"
22	"strings"
23	"text/scanner"
24
25	"github.com/google/blueprint/parser"
26)
27
28const maxUnpackErrors = 10
29
30type UnpackError struct {
31	Err error
32	Pos scanner.Position
33}
34
35func (e *UnpackError) Error() string {
36	return fmt.Sprintf("%s: %s", e.Pos, e.Err)
37}
38
39// packedProperty helps to track properties usage (`used` will be true)
40type packedProperty struct {
41	property *parser.Property
42	used     bool
43}
44
45// unpackContext keeps compound names and their values in a map. It is initialized from
46// parsed properties.
47type unpackContext struct {
48	propertyMap map[string]*packedProperty
49	errs        []error
50}
51
52// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties.
53// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized
54// from it. See PropertyNameForField for field and property name matching.
55// For instance, if the input contains
56//
57//	{ foo: "abc", bar: {x: 1},}
58//
59// and a runtime value being has been declared as
60//
61//	var v struct { Foo string; Bar int }
62//
63// then v.Foo will be set to "abc" and v.Bar will be set to 1
64// (cf. unpack_test.go for further examples)
65//
66// The type of a receiving field has to match the property type, i.e., a bool/int/string field
67// can be set from a property with bool/int/string value, a struct can be set from a map (only the
68// matching fields are set), and an slice can be set from a list.
69// If a field of a runtime value has been already set prior to the UnpackProperties, the new value
70// is appended to it (see somewhat inappropriately named ExtendBasicType).
71// The same property can initialize fields in multiple runtime values. It is an error if any property
72// value was not used to initialize at least one field.
73func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) {
74	var unpackContext unpackContext
75	unpackContext.propertyMap = make(map[string]*packedProperty)
76	if !unpackContext.buildPropertyMap("", properties) {
77		return nil, unpackContext.errs
78	}
79
80	for _, obj := range objects {
81		valueObject := reflect.ValueOf(obj)
82		if !isStructPtr(valueObject.Type()) {
83			panic(fmt.Errorf("properties must be *struct, got %s",
84				valueObject.Type()))
85		}
86		unpackContext.unpackToStruct("", valueObject.Elem())
87		if len(unpackContext.errs) >= maxUnpackErrors {
88			return nil, unpackContext.errs
89		}
90	}
91
92	// Gather property map, and collect any unused properties.
93	// Avoid reporting subproperties of unused properties.
94	result := make(map[string]*parser.Property)
95	var unusedNames []string
96	for name, v := range unpackContext.propertyMap {
97		if v.used {
98			result[name] = v.property
99		} else {
100			unusedNames = append(unusedNames, name)
101		}
102	}
103	if len(unusedNames) == 0 && len(unpackContext.errs) == 0 {
104		return result, nil
105	}
106	return nil, unpackContext.reportUnusedNames(unusedNames)
107}
108
109func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error {
110	sort.Strings(unusedNames)
111	unusedNames = removeUnnecessaryUnusedNames(unusedNames)
112	var lastReported string
113	for _, name := range unusedNames {
114		// if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*'
115		if lastReported != "" {
116			trimmed := strings.TrimPrefix(name, lastReported)
117			if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') {
118				continue
119			}
120		}
121		ctx.errs = append(ctx.errs, &UnpackError{
122			fmt.Errorf("unrecognized property %q", name),
123			ctx.propertyMap[name].property.ColonPos})
124		lastReported = name
125	}
126	return ctx.errs
127}
128
129// When property a.b.c is not used, (also there is no a.* or a.b.* used)
130// "a", "a.b" and "a.b.c" are all in unusedNames.
131// removeUnnecessaryUnusedNames only keeps the last "a.b.c" as the real unused
132// name.
133func removeUnnecessaryUnusedNames(names []string) []string {
134	if len(names) == 0 {
135		return names
136	}
137	var simplifiedNames []string
138	for index, name := range names {
139		if index == len(names)-1 || !strings.HasPrefix(names[index+1], name) {
140			simplifiedNames = append(simplifiedNames, name)
141		}
142	}
143	return simplifiedNames
144}
145
146func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool {
147	nOldErrors := len(ctx.errs)
148	for _, property := range properties {
149		name := fieldPath(prefix, property.Name)
150		if first, present := ctx.propertyMap[name]; present {
151			ctx.addError(
152				&UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos})
153			if ctx.addError(
154				&UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) {
155				return false
156			}
157			continue
158		}
159
160		ctx.propertyMap[name] = &packedProperty{property, false}
161		switch propValue := property.Value.Eval().(type) {
162		case *parser.Map:
163			ctx.buildPropertyMap(name, propValue.Properties)
164		case *parser.List:
165			// If it is a list, unroll it unless its elements are of primitive type
166			// (no further mapping will be needed in that case, so we avoid cluttering
167			// the map).
168			if len(propValue.Values) == 0 {
169				continue
170			}
171			if t := propValue.Values[0].Type(); t == parser.StringType || t == parser.Int64Type || t == parser.BoolType {
172				continue
173			}
174
175			itemProperties := make([]*parser.Property, len(propValue.Values))
176			for i, expr := range propValue.Values {
177				itemProperties[i] = &parser.Property{
178					Name:     property.Name + "[" + strconv.Itoa(i) + "]",
179					NamePos:  property.NamePos,
180					ColonPos: property.ColonPos,
181					Value:    expr,
182				}
183			}
184			if !ctx.buildPropertyMap(prefix, itemProperties) {
185				return false
186			}
187		}
188	}
189
190	return len(ctx.errs) == nOldErrors
191}
192
193func fieldPath(prefix, fieldName string) string {
194	if prefix == "" {
195		return fieldName
196	}
197	return prefix + "." + fieldName
198}
199
200func (ctx *unpackContext) addError(e error) bool {
201	ctx.errs = append(ctx.errs, e)
202	return len(ctx.errs) < maxUnpackErrors
203}
204
205func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) {
206	structType := structValue.Type()
207
208	for i := 0; i < structValue.NumField(); i++ {
209		fieldValue := structValue.Field(i)
210		field := structType.Field(i)
211
212		// In Go 1.7, runtime-created structs are unexported, so it's not
213		// possible to create an exported anonymous field with a generated
214		// type. So workaround this by special-casing "BlueprintEmbed" to
215		// behave like an anonymous field for structure unpacking.
216		if field.Name == "BlueprintEmbed" {
217			field.Name = ""
218			field.Anonymous = true
219		}
220
221		if field.PkgPath != "" {
222			// This is an unexported field, so just skip it.
223			continue
224		}
225
226		propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name))
227
228		if !fieldValue.CanSet() {
229			panic(fmt.Errorf("field %s is not settable", propertyName))
230		}
231
232		// Get the property value if it was specified.
233		packedProperty, propertyIsSet := ctx.propertyMap[propertyName]
234
235		origFieldValue := fieldValue
236
237		// To make testing easier we validate the struct field's type regardless
238		// of whether or not the property was specified in the parsed string.
239		// TODO(ccross): we don't validate types inside nil struct pointers
240		// Move type validation to a function that runs on each factory once
241		switch kind := fieldValue.Kind(); kind {
242		case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice:
243			// Do nothing
244		case reflect.Interface:
245			if fieldValue.IsNil() {
246				panic(fmt.Errorf("field %s contains a nil interface", propertyName))
247			}
248			fieldValue = fieldValue.Elem()
249			elemType := fieldValue.Type()
250			if elemType.Kind() != reflect.Ptr {
251				panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
252			}
253			fallthrough
254		case reflect.Ptr:
255			switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
256			case reflect.Struct:
257				if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
258					// Instantiate nil struct pointers
259					// Set into origFieldValue in case it was an interface, in which case
260					// fieldValue points to the unsettable pointer inside the interface
261					fieldValue = reflect.New(fieldValue.Type().Elem())
262					origFieldValue.Set(fieldValue)
263				}
264				fieldValue = fieldValue.Elem()
265			case reflect.Bool, reflect.Int64, reflect.String:
266				// Nothing
267			default:
268				panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
269			}
270
271		case reflect.Int, reflect.Uint:
272			if !HasTag(field, "blueprint", "mutated") {
273				panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
274			}
275
276		default:
277			panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
278		}
279
280		if field.Anonymous && isStruct(fieldValue.Type()) {
281			ctx.unpackToStruct(namePrefix, fieldValue)
282			continue
283		}
284
285		if !propertyIsSet {
286			// This property wasn't specified.
287			continue
288		}
289
290		packedProperty.used = true
291		property := packedProperty.property
292
293		if HasTag(field, "blueprint", "mutated") {
294			if !ctx.addError(
295				&UnpackError{
296					fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
297					property.ColonPos,
298				}) {
299				return
300			}
301			continue
302		}
303
304		if isConfigurable(fieldValue.Type()) {
305			// configurableType is the reflect.Type representation of a Configurable[whatever],
306			// while configuredType is the reflect.Type of the "whatever".
307			configurableType := fieldValue.Type()
308			configuredType := fieldValue.Interface().(configurableReflection).configuredType()
309			if unpackedValue, ok := ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType); ok {
310				ExtendBasicType(fieldValue, unpackedValue.Elem(), Append)
311			}
312			if len(ctx.errs) >= maxUnpackErrors {
313				return
314			}
315		} else if isStruct(fieldValue.Type()) {
316			if property.Value.Eval().Type() != parser.MapType {
317				ctx.addError(&UnpackError{
318					fmt.Errorf("can't assign %s value to map property %q",
319						property.Value.Type(), property.Name),
320					property.Value.Pos(),
321				})
322				continue
323			}
324			ctx.unpackToStruct(propertyName, fieldValue)
325			if len(ctx.errs) >= maxUnpackErrors {
326				return
327			}
328		} else if isSlice(fieldValue.Type()) {
329			if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok {
330				ExtendBasicType(fieldValue, unpackedValue, Append)
331			}
332			if len(ctx.errs) >= maxUnpackErrors {
333				return
334			}
335		} else {
336			unpackedValue, err := propertyToValue(fieldValue.Type(), property)
337			if err != nil && !ctx.addError(err) {
338				return
339			}
340			ExtendBasicType(fieldValue, unpackedValue, Append)
341		}
342	}
343}
344
345// Converts the given property to a pointer to a configurable struct
346func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *parser.Property, configurableType, configuredType reflect.Type) (reflect.Value, bool) {
347	switch v := property.Value.(type) {
348	case *parser.String:
349		if configuredType.Kind() != reflect.String {
350			ctx.addError(&UnpackError{
351				fmt.Errorf("can't assign string value to configurable %s property %q",
352					configuredType.String(), property.Name),
353				property.Value.Pos(),
354			})
355			return reflect.New(configurableType), false
356		}
357		result := Configurable[string]{
358			propertyName: property.Name,
359			inner: &configurableInner[string]{
360				single: singleConfigurable[string]{
361					cases: []ConfigurableCase[string]{{
362						value: &v.Value,
363					}},
364				},
365			},
366		}
367		return reflect.ValueOf(&result), true
368	case *parser.Bool:
369		if configuredType.Kind() != reflect.Bool {
370			ctx.addError(&UnpackError{
371				fmt.Errorf("can't assign bool value to configurable %s property %q",
372					configuredType.String(), property.Name),
373				property.Value.Pos(),
374			})
375			return reflect.New(configurableType), false
376		}
377		result := Configurable[bool]{
378			propertyName: property.Name,
379			inner: &configurableInner[bool]{
380				single: singleConfigurable[bool]{
381					cases: []ConfigurableCase[bool]{{
382						value: &v.Value,
383					}},
384				},
385			},
386		}
387		return reflect.ValueOf(&result), true
388	case *parser.List:
389		if configuredType.Kind() != reflect.Slice {
390			ctx.addError(&UnpackError{
391				fmt.Errorf("can't assign list value to configurable %s property %q",
392					configuredType.String(), property.Name),
393				property.Value.Pos(),
394			})
395			return reflect.New(configurableType), false
396		}
397		switch configuredType.Elem().Kind() {
398		case reflect.String:
399			var value []string
400			if v.Values != nil {
401				value = make([]string, len(v.Values))
402				itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
403				for i, expr := range v.Values {
404					itemProperty.Name = propertyName + "[" + strconv.Itoa(i) + "]"
405					itemProperty.Value = expr
406					exprUnpacked, err := propertyToValue(configuredType.Elem(), itemProperty)
407					if err != nil {
408						ctx.addError(err)
409						return reflect.ValueOf(Configurable[[]string]{}), false
410					}
411					value[i] = exprUnpacked.Interface().(string)
412				}
413			}
414			result := Configurable[[]string]{
415				propertyName: property.Name,
416				inner: &configurableInner[[]string]{
417					single: singleConfigurable[[]string]{
418						cases: []ConfigurableCase[[]string]{{
419							value: &value,
420						}},
421					},
422				},
423			}
424			return reflect.ValueOf(&result), true
425		default:
426			panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
427		}
428	case *parser.Operator:
429		property.Value = v.Value.Eval()
430		return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
431	case *parser.Variable:
432		property.Value = v.Value.Eval()
433		return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
434	case *parser.Select:
435		resultPtr := reflect.New(configurableType)
436		result := resultPtr.Elem()
437		conditions := make([]ConfigurableCondition, len(v.Conditions))
438		for i, cond := range v.Conditions {
439			args := make([]string, len(cond.Args))
440			for j, arg := range cond.Args {
441				args[j] = arg.Value
442			}
443			conditions[i] = ConfigurableCondition{
444				functionName: cond.FunctionName,
445				args:         args,
446			}
447		}
448
449		configurableCaseType := configurableCaseType(configuredType)
450		cases := reflect.MakeSlice(reflect.SliceOf(configurableCaseType), 0, len(v.Cases))
451		for i, c := range v.Cases {
452			p := &parser.Property{
453				Name:    property.Name + "[" + strconv.Itoa(i) + "]",
454				NamePos: c.ColonPos,
455				Value:   c.Value,
456			}
457
458			patterns := make([]ConfigurablePattern, len(c.Patterns))
459			for i, pat := range c.Patterns {
460				switch pat := pat.(type) {
461				case *parser.String:
462					if pat.Value == "__soong_conditions_default__" {
463						patterns[i].typ = configurablePatternTypeDefault
464					} else {
465						patterns[i].typ = configurablePatternTypeString
466						patterns[i].stringValue = pat.Value
467					}
468				case *parser.Bool:
469					patterns[i].typ = configurablePatternTypeBool
470					patterns[i].boolValue = pat.Value
471				default:
472					panic("unimplemented")
473				}
474			}
475
476			var value reflect.Value
477			// Map the "unset" keyword to a nil pointer in the cases map
478			if _, ok := c.Value.(parser.UnsetProperty); ok {
479				value = reflect.Zero(reflect.PointerTo(configuredType))
480			} else {
481				var err error
482				switch configuredType.Kind() {
483				case reflect.String, reflect.Bool:
484					value, err = propertyToValue(reflect.PointerTo(configuredType), p)
485					if err != nil {
486						ctx.addError(&UnpackError{
487							err,
488							c.Value.Pos(),
489						})
490						return reflect.New(configurableType), false
491					}
492				case reflect.Slice:
493					if configuredType.Elem().Kind() != reflect.String {
494						panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
495					}
496					value, ok = ctx.unpackToSlice(p.Name, p, reflect.PointerTo(configuredType))
497					if !ok {
498						return reflect.New(configurableType), false
499					}
500				default:
501					panic("This should be unreachable because ConfigurableElements only accepts strings, boools, or slices of strings")
502				}
503			}
504
505			case_ := reflect.New(configurableCaseType)
506			case_.Interface().(configurableCaseReflection).initialize(patterns, value.Interface())
507			cases = reflect.Append(cases, case_.Elem())
508		}
509		resultPtr.Interface().(configurablePtrReflection).initialize(
510			property.Name,
511			conditions,
512			cases.Interface(),
513		)
514		if v.Append != nil {
515			p := &parser.Property{
516				Name:    property.Name,
517				NamePos: property.NamePos,
518				Value:   v.Append,
519			}
520			val, ok := ctx.unpackToConfigurable(propertyName, p, configurableType, configuredType)
521			if !ok {
522				return reflect.New(configurableType), false
523			}
524			result.Interface().(configurableReflection).setAppend(val.Elem().Interface(), false, false)
525		}
526		return resultPtr, true
527	default:
528		ctx.addError(&UnpackError{
529			fmt.Errorf("can't assign %s value to configurable %s property %q",
530				property.Value.Type(), configuredType.String(), property.Name),
531			property.Value.Pos(),
532		})
533		return reflect.New(configurableType), false
534	}
535}
536
537// If the given property is a select, returns an error saying that you can't assign a select to
538// a non-configurable property. Otherwise returns nil.
539func selectOnNonConfigurablePropertyError(property *parser.Property) error {
540	if _, ok := property.Value.Eval().(*parser.Select); !ok {
541		return nil
542	}
543
544	return &UnpackError{
545		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",
546			property.Name),
547		property.Value.Pos(),
548	}
549}
550
551// unpackSlice creates a value of a given slice or pointer to slice type from the property,
552// which should be a list
553func (ctx *unpackContext) unpackToSlice(
554	sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
555	if sliceType.Kind() == reflect.Pointer {
556		sliceType = sliceType.Elem()
557		result := reflect.New(sliceType)
558		slice, ok := ctx.unpackToSliceInner(sliceName, property, sliceType)
559		if !ok {
560			return result, ok
561		}
562		result.Elem().Set(slice)
563		return result, true
564	}
565	return ctx.unpackToSliceInner(sliceName, property, sliceType)
566}
567
568// unpackToSliceInner creates a value of a given slice type from the property,
569// which should be a list. It doesn't support pointers to slice types like unpackToSlice
570// does.
571func (ctx *unpackContext) unpackToSliceInner(
572	sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
573	propValueAsList, ok := property.Value.Eval().(*parser.List)
574	if !ok {
575		if err := selectOnNonConfigurablePropertyError(property); err != nil {
576			ctx.addError(err)
577		} else {
578			ctx.addError(&UnpackError{
579				fmt.Errorf("can't assign %s value to list property %q",
580					property.Value.Type(), property.Name),
581				property.Value.Pos(),
582			})
583		}
584		return reflect.MakeSlice(sliceType, 0, 0), false
585	}
586	exprs := propValueAsList.Values
587	value := reflect.MakeSlice(sliceType, 0, len(exprs))
588	if len(exprs) == 0 {
589		return value, true
590	}
591
592	// The function to construct an item value depends on the type of list elements.
593	var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool)
594	switch exprs[0].Type() {
595	case parser.BoolType, parser.StringType, parser.Int64Type:
596		getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
597			value, err := propertyToValue(t, property)
598			if err != nil {
599				ctx.addError(err)
600				return value, false
601			}
602			return value, true
603		}
604	case parser.ListType:
605		getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
606			return ctx.unpackToSlice(property.Name, property, t)
607		}
608	case parser.MapType:
609		getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
610			itemValue := reflect.New(t).Elem()
611			ctx.unpackToStruct(property.Name, itemValue)
612			return itemValue, true
613		}
614	case parser.NotEvaluatedType:
615		getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
616			return reflect.New(t), false
617		}
618	default:
619		panic(fmt.Errorf("bizarre property expression type: %v", exprs[0].Type()))
620	}
621
622	itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
623	elemType := sliceType.Elem()
624	isPtr := elemType.Kind() == reflect.Ptr
625
626	for i, expr := range exprs {
627		itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
628		itemProperty.Value = expr
629		if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
630			packedProperty.used = true
631		}
632		if isPtr {
633			if itemValue, ok := getItemFunc(itemProperty, elemType.Elem()); ok {
634				ptrValue := reflect.New(itemValue.Type())
635				ptrValue.Elem().Set(itemValue)
636				value = reflect.Append(value, ptrValue)
637			}
638		} else {
639			if itemValue, ok := getItemFunc(itemProperty, elemType); ok {
640				value = reflect.Append(value, itemValue)
641			}
642		}
643	}
644	return value, true
645}
646
647// propertyToValue creates a value of a given value type from the property.
648func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
649	var value reflect.Value
650	var baseType reflect.Type
651	isPtr := typ.Kind() == reflect.Ptr
652	if isPtr {
653		baseType = typ.Elem()
654	} else {
655		baseType = typ
656	}
657
658	switch kind := baseType.Kind(); kind {
659	case reflect.Bool:
660		b, ok := property.Value.Eval().(*parser.Bool)
661		if !ok {
662			if err := selectOnNonConfigurablePropertyError(property); err != nil {
663				return value, err
664			} else {
665				return value, &UnpackError{
666					fmt.Errorf("can't assign %s value to bool property %q",
667						property.Value.Type(), property.Name),
668					property.Value.Pos(),
669				}
670			}
671		}
672		value = reflect.ValueOf(b.Value)
673
674	case reflect.Int64:
675		b, ok := property.Value.Eval().(*parser.Int64)
676		if !ok {
677			return value, &UnpackError{
678				fmt.Errorf("can't assign %s value to int64 property %q",
679					property.Value.Type(), property.Name),
680				property.Value.Pos(),
681			}
682		}
683		value = reflect.ValueOf(b.Value)
684
685	case reflect.String:
686		s, ok := property.Value.Eval().(*parser.String)
687		if !ok {
688			if err := selectOnNonConfigurablePropertyError(property); err != nil {
689				return value, err
690			} else {
691				return value, &UnpackError{
692					fmt.Errorf("can't assign %s value to string property %q",
693						property.Value.Type(), property.Name),
694					property.Value.Pos(),
695				}
696			}
697		}
698		value = reflect.ValueOf(s.Value)
699
700	default:
701		return value, &UnpackError{
702			fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ),
703			property.NamePos}
704	}
705
706	if isPtr {
707		ptrValue := reflect.New(value.Type())
708		ptrValue.Elem().Set(value)
709		return ptrValue, nil
710	}
711	return value, nil
712}
713