diff --git a/pkl/decode_struct.go b/pkl/decode_struct.go index e9c4ae9..45fc77b 100644 --- a/pkl/decode_struct.go +++ b/pkl/decode_struct.go @@ -305,7 +305,14 @@ func getStructFields(typ reflect.Type) map[string]structField { field := typ.Field(i) // embedded if field.Anonymous { - for k, v := range getStructFields(field.Type.Elem()) { + var fields map[string]structField + switch field.Type.Kind() { + case reflect.Ptr: + fields = getStructFields(field.Type.Elem()) + case reflect.Struct: + fields = getStructFields(field.Type) + } + for k, v := range fields { ret[k] = v } } else { @@ -326,7 +333,7 @@ func (d *decoder) getOutputValue(typ reflect.Type) (*reflect.Value, error) { numFields := typ.NumField() for i := 0; i < numFields; i++ { field := typ.Field(i) - if field.Anonymous { + if field.Anonymous && field.Type.Kind() == reflect.Ptr { fieldValue := reflect.New(field.Type.Elem()) // Assertion: all embedded fields are pointers to structs. structValue, err := d.getOutputValue(field.Type.Elem()) diff --git a/pkl/test_fixtures/custom/house.go b/pkl/test_fixtures/custom/house.go new file mode 100644 index 0000000..e4e3a81 --- /dev/null +++ b/pkl/test_fixtures/custom/house.go @@ -0,0 +1,17 @@ +package custom + +type Shape struct { + Area int `pkl:"area"` +} + +type House struct { + Shape + + Bedrooms int `pkl:"bedrooms"` + + Bathrooms int `pkl:"bathrooms"` +} + +type CustomClasses struct { + House *House `pkl:"house"` +} diff --git a/pkl/test_fixtures/custom/house.pkl b/pkl/test_fixtures/custom/house.pkl new file mode 100644 index 0000000..5a37456 --- /dev/null +++ b/pkl/test_fixtures/custom/house.pkl @@ -0,0 +1,31 @@ +// ===----------------------------------------------------------------------===// +// Copyright © 2024 Apple Inc. and the Pkl project authors. 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 +// +// https://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. +// ===----------------------------------------------------------------------===// +module custom + +house: House = new { + area = 2000 + bedrooms = 3 + bathrooms = 2 +} + +abstract class Shape { + area: Int +} + +class House extends Shape { + bedrooms: Int + bathrooms: Int +} diff --git a/pkl/unmarshal_test.go b/pkl/unmarshal_test.go index da32958..569447c 100644 --- a/pkl/unmarshal_test.go +++ b/pkl/unmarshal_test.go @@ -17,7 +17,10 @@ package pkl_test import ( "bytes" + "context" _ "embed" + "github.com/apple/pkl-go/pkl/test_fixtures/custom" + "github.com/stretchr/testify/require" "testing" unknowntype "github.com/apple/pkl-go/pkl/test_fixtures/gen/unknown_type" @@ -83,6 +86,9 @@ var anies []byte //go:embed test_fixtures/msgpack/unknown_type.pkl.msgpack var unknownType []byte +//go:embed test_fixtures/custom/house.pkl +var customHousePkl []byte + func TestUnmarshall_Primitives(t *testing.T) { var res primitives.Primitives var expected = primitives.Primitives{ @@ -428,3 +434,23 @@ func TestUnmarshal_UnknownType(t *testing.T) { assert.Error(t, err) assert.Equal(t, "cannot decode Pkl value of type `PcfRenderer` into Go type `interface {}`. Define a custom mapping for this using `pkl.RegisterMapping`", err.Error()) } + +func TestUnmarshal_Custom(t *testing.T) { + evaluator, err := pkl.NewEvaluator(context.Background(), pkl.PreconfiguredOptions) + require.NoError(t, err, "failed to create pkl evaluator") + defer evaluator.Close() + + var res custom.CustomClasses + err = evaluator.EvaluateModule(context.Background(), pkl.TextSource(string(customHousePkl)), &res) + require.NoError(t, err, "failed to evaluate pkl module") + + assert.Equal(t, custom.CustomClasses{ + House: &custom.House{ + Shape: custom.Shape{ + Area: 2000, + }, + Bedrooms: 3, + Bathrooms: 2, + }, + }, res) +}