Skip to content

Commit ff16446

Browse files
authored
Fix duplicate embedded callback hooks (#616)
1 parent 59826d1 commit ff16446

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

callbacks.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kong
33
import (
44
"fmt"
55
"reflect"
6+
"runtime"
67
"strings"
78
)
89

@@ -127,13 +128,38 @@ func getMethod(value reflect.Value, name string) reflect.Value {
127128
return method
128129
}
129130

131+
func getExplicitMethod(value reflect.Value, name string) reflect.Value {
132+
if isExplicitMethod(value.Type(), name) {
133+
return value.MethodByName(name)
134+
}
135+
if value.CanAddr() && isExplicitMethod(value.Addr().Type(), name) {
136+
return value.Addr().MethodByName(name)
137+
}
138+
return reflect.Value{}
139+
}
140+
141+
func isExplicitMethod(t reflect.Type, name string) bool {
142+
method, ok := t.MethodByName(name)
143+
if !ok {
144+
return false
145+
}
146+
// Promoted embedded methods are compiler-generated wrappers. The embedded
147+
// value itself is visited separately, so skip those wrappers here.
148+
fn := runtime.FuncForPC(method.Func.Pointer())
149+
if fn == nil {
150+
return true
151+
}
152+
file, _ := fn.FileLine(method.Func.Pointer())
153+
return file != "<autogenerated>"
154+
}
155+
130156
// getMethods gets all methods with the given name from the given value
131157
// and any embedded fields.
132158
//
133159
// Returns a slice of bound methods that can be called directly.
134160
func getMethods(value reflect.Value, name string) (methods []reflect.Value) {
135161
walkEmbedded(value, func(v reflect.Value) {
136-
if method := getMethod(v, name); method.IsValid() {
162+
if method := getExplicitMethod(v, name); method.IsValid() {
137163
methods = append(methods, method)
138164
}
139165
})

kong_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,37 @@ func TestApplyCalledOnce(t *testing.T) {
26972697
assert.NoError(t, err)
26982698
}
26992699

2700+
type EmbeddedAfterApplyLeaf struct {
2701+
calls *int
2702+
}
2703+
2704+
func (l EmbeddedAfterApplyLeaf) AfterApply() error {
2705+
(*l.calls)++
2706+
return nil
2707+
}
2708+
2709+
type EmbeddedAfterApplyMiddle struct {
2710+
EmbeddedAfterApplyLeaf
2711+
}
2712+
2713+
type EmbeddedAfterApplyRoot struct {
2714+
EmbeddedAfterApplyMiddle
2715+
}
2716+
2717+
func TestPromotedEmbeddedAfterApplyCalledOnce(t *testing.T) {
2718+
calls := 0
2719+
cli := &EmbeddedAfterApplyRoot{
2720+
EmbeddedAfterApplyMiddle: EmbeddedAfterApplyMiddle{
2721+
EmbeddedAfterApplyLeaf: EmbeddedAfterApplyLeaf{
2722+
calls: &calls,
2723+
},
2724+
},
2725+
}
2726+
_, err := mustNew(t, cli).Parse(nil)
2727+
assert.NoError(t, err)
2728+
assert.Equal(t, 1, calls)
2729+
}
2730+
27002731
type envOnlyAfterApply struct {
27012732
called bool
27022733
}

0 commit comments

Comments
 (0)