Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type composition is broken (embedded fields not detected) #583

Open
dargueta opened this issue Jan 25, 2023 · 2 comments
Open

Type composition is broken (embedded fields not detected) #583

dargueta opened this issue Jan 25, 2023 · 2 comments

Comments

@dargueta
Copy link

dargueta commented Jan 25, 2023

It appears that embedded fields aren't being recognized by the parser, even if they have the appropriate hcl tag. This is a problem because that means I can't factor out common fields into a separate struct to avoid a bunch of copying and pasting when I only need a single optional label.

This is similar to #136, however the major difference here is that the embedded fields are tagged. It does work if you add "remain", however that precludes you from mixing two structures into a third.

Expectation

Given the following definition:

type A struct {
    A string `hcl:"a"`
}

type B struct {
    A
    B int `hcl:"b"`
}

type File struct {
    Stuff []B `hcl:"stuff,block"`
}

This should load correctly

stuff {
    a = "asdf"
    b = 123
}

What Happens

Unfortunately, I get an error:

Error: test.hcl:3,3-4: Unsupported argument; An argument named "a" is not expected here.

If I comment out a then it works.

Reproduction Example

package main

import (
	"fmt"

	"github.com/hashicorp/hcl/v2/hclsimple"
)

type A struct {
	A string `hcl:"a"`
}

type B struct {
	A
	B int `hcl:"b"`
}

type File struct {
	Stuff []B `hcl:"stuff,block"`
}

func main() {
	input := `
	stuff {
		a = "asdf"
		b = 123
	}
	`

	output := File{}
	err := hclsimple.Decode("test.hcl", []byte(input), nil, &output)
	fmt.Printf("Error: %v\n", err)
}

Environment

OS: Ubuntu 22.04.1
Go: 1.19.4
hcl: v2.15.0

@johakoch
Copy link

Hi @dargueta

by adding

	Remain         hcl.Body `hcl:",remain"`

to the structs, I don't get the error, true. But if I

fmt.Printf("output.Stuff[0].A: %#v\n", output.Stuff[0].A)
fmt.Printf("output.Stuff[0].B: %#v\n", output.Stuff[0].B)

I get

output.Stuff[0].A: main.A{A:"", Remain:hcl.Body(nil)}
output.Stuff[0].B: 123

which is not wanted, I guess.

@johakoch
Copy link

decodeBodyToStruct() in gohcl/decode.go calls ImpliedBodySchema() (in gohcl/schema.go). Both call getFieldTags() (gohcl/schema.go), which iterates over the struct type's fields and only handles those with an hcl tag.

So embedded structs like A in

type B struct {
	A
	// ...
}

are ignored.

In decodeBodyToStruct() I included the following lines

	if content == nil {
		return diags
	}

	// start included
	t := val.Type()
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldValue := val.Field(i)

		if field.Anonymous && fieldValue.Kind() == reflect.Struct {
			embeddedStructPtr := reflect.New(field.Type).Interface()

			if diag := DecodeBody(body, ctx, embeddedStructPtr); diag.HasErrors() {
				diags = append(diags, diag...)
				continue
			}

			fieldValue.Set(reflect.ValueOf(embeddedStructPtr).Elem())
		}
	}
	// end included

	tags := getFieldTags(val.Type())

which for

type As struct { // renamed to As instead of A to distinguish from the field A
	A      string   `hcl:"a"`
	Remain hcl.Body `hcl:",remain"`
}

type B struct {
	As
	B      int      `hcl:"b"`
	Remain hcl.Body `hcl:",remain"`
}

type File struct {
	Stuff []B `hcl:"stuff,block"`
}

func main() {
	hclContent := `
	stuff {
		a = "asdf"
		b = 123
	}
	`
	parser := hclparse.NewParser()
	file, diag := parser.ParseHCL([]byte(hclContent), "config.hcl")
	if diag.HasErrors() {
		fmt.Println("Error parsing HCL:", diag)
		return
	}
	ctx := &hcl.EvalContext{}
	var output File
	if err := gohcl.DecodeBody(file.Body, ctx, &output); err.HasErrors() {
		fmt.Println("Error decoding HCL:", err)
		return
	}

	fmt.Printf("output.Stuff[0].A: %#v\n", output.Stuff[0].A)
	fmt.Printf("output.Stuff[0].B: %#v\n", output.Stuff[0].B)
}

happened to produce

output.Stuff[0].A: "asdf"
output.Stuff[0].B: 123

This was just an ugly attempt to see how I can modify the code to get the result in this specific situation. But I'm not experienced enough with the hcl repo code to actually produce a PR that really works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants