Skip to content

Conversation

abhinav
Copy link
Contributor

@abhinav abhinav commented Jan 29, 2025

Follow up to #493 and 840220c

Kong currently supports hooks on embedded fields of a parsed node,
but only at the first level of embedding:

type mainCmd struct {
    FooOptions
}

type FooOptions struct {
    BarOptions
}

func (f *FooOptions) BeforeApply() error {
    // this will be called
}

type BarOptions struct {
}

func (b *BarOptions) BeforeApply() error {
    // this will not be called
}

This change adds support for hooks to be defined
on embedded fields of embedded fields so that the above
example would work as expected.

Per #493, the definition of "embedded" field is adjusted to mean:

  • Any anonymous (Go-embedded) field that is exported
  • Any non-anonymous field that is tagged with embed:""

Testing:
Includes a test case for embedding an anonymous field in an embed:""
and an embed:"" field in an anonymous field.

Follow up to alecthomas#493 and 840220c

Kong currently supports hooks on embedded fields of a parsed node,
but only at the first level of embedding:

```
type mainCmd struct {
    FooOptions
}

type FooOptions struct {
    BarOptions
}

func (f *FooOptions) BeforeApply() error {
    // this will be called
}

type BarOptions struct {
}

func (b *BarOptions) BeforeApply() error {
    // this will not be called
}
```

This change adds support for hooks to be defined
on embedded fields of embedded fields so that the above
example would work as expected.

Per alecthomas#493, the definition of "embedded" field is adjusted to mean:

- Any anonymous (Go-embedded) field that is exported
- Any non-anonymous field that is tagged with `embed:""`

*Testing*:
Includes a test case for embedding an anonymous field in an `embed:""`
and an `embed:""` field in an anonymous field.
The 'receivers' parameter helps avoid constant memory allocation
as the backing storage for the slice is reused across recursive calls.
Comment on lines +79 to +80
var traverse func(value reflect.Value, receivers []reflect.Value) []reflect.Value
traverse = func(value reflect.Value, receivers []reflect.Value) []reflect.Value {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing worth noting:
Adding a receivers parameter here allows the recursive calls to reuse the slice's backing storage
so that when one append gives the slice 2x capacity, that is re-used in following appends
before needing another resize further down the stack.

We could change the nil below to something like make([]reflect.Value, 0, 16), and this iteration/collection would be alloc-free for a recursive depth of 16 (which should be more than enough for 90% of the use cases).

@alecthomas
Copy link
Owner

Nice, thanks!

@alecthomas alecthomas merged commit 4be6ae6 into alecthomas:master Jan 30, 2025
5 checks passed
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

Successfully merging this pull request may close these issues.

2 participants