diff --git a/td/td_smuggle.go b/td/td_smuggle.go index 15496cb7..93e459a5 100644 --- a/td/td_smuggle.go +++ b/td/td_smuggle.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Maxime Soulé +// Copyright (c) 2018-2024, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -72,6 +72,7 @@ var smuggleValueType = reflect.TypeOf(smuggleValue{}) type smuggleField struct { Name string Indexed bool + Method bool } func joinFieldsPath(path []smuggleField) string { @@ -84,6 +85,9 @@ func joinFieldsPath(path []smuggleField) string { buf.WriteByte('.') } buf.WriteString(part.Name) + if part.Method { + buf.WriteString("()") + } } } return buf.String() @@ -94,6 +98,7 @@ func splitFieldsPath(origPath string) ([]smuggleField, error) { return nil, fmt.Errorf("FIELD_PATH cannot be empty") } + privateField := "" var res []smuggleField for path := origPath; len(path) > 0; { r, _ := utf8.DecodeRuneInString(path) @@ -130,12 +135,33 @@ func splitFieldsPath(origPath string) ([]smuggleField, error) { field, path = path[:end], path[end:] } - for j, r := range field { - if !unicode.IsLetter(r) && (j == 0 || !unicode.IsNumber(r)) { - return nil, fmt.Errorf("unexpected %q in field name %q in FIELDS_PATH %q", r, field, origPath) + if strings.HasSuffix(field, "()") { + if len(field) == 2 { + return nil, fmt.Errorf("missing method name before () in FIELDS_PATH %q", origPath) + } + for j, r := range field[:len(field)-2] { + if j == 0 && !unicode.IsUpper(r) { + return nil, fmt.Errorf("method name %q is not public in FIELDS_PATH %q", field, origPath) + } + if !unicode.IsLetter(r) && !unicode.IsNumber(r) { + return nil, fmt.Errorf("unexpected %q in method name %q in FIELDS_PATH %q", r, field, origPath) + } + } + if privateField != "" { + return nil, fmt.Errorf("cannot call method %s as it is based on an unexported field %q in FIELDS_PATH %q", field, privateField, origPath) + } + res = append(res, smuggleField{Name: field[:len(field)-2], Method: true}) + } else { + for j, r := range field { + if privateField == "" && j == 0 && !unicode.IsUpper(r) { + privateField = field + } + if !unicode.IsLetter(r) && (j == 0 || !unicode.IsNumber(r)) { + return nil, fmt.Errorf("unexpected %q in field name %q in FIELDS_PATH %q", r, field, origPath) + } } + res = append(res, smuggleField{Name: field}) } - res = append(res, smuggleField{Name: field}) } } return res, nil @@ -155,7 +181,63 @@ func buildFieldsPathFn(path string) (func(any) (smuggleValue, error), error) { vgot := reflect.ValueOf(got) for idxPart, field := range parts { + if field.Method { + var method reflect.Value + for { + method = vgot.MethodByName(field.Name) + if !method.IsValid() { + switch vgot.Kind() { + case reflect.Interface, reflect.Ptr: + if !vgot.IsNil() { + vgot = vgot.Elem() + continue + } + return smuggleValue{}, nilFieldErr(parts[:idxPart]) + } + if idxPart > 0 { + return smuggleValue{}, fmt.Errorf( + "field %s (type %s) does not implement %s() method", + joinFieldsPath(parts[:idxPart]), + vgot.Type(), + field.Name) + } + return smuggleValue{}, fmt.Errorf( + "type %s has no method %s()", vgot.Type(), field.Name) + } + break + } + mt := method.Type() + if mt.NumIn() != 0 || + (mt.NumOut() != 1 && (mt.NumOut() != 2 || mt.Out(1) != types.Error)) { + return smuggleValue{}, fmt.Errorf( + "cannot call %s, signature %s not handled, only func() A or func() (A, error) allowed", + joinFieldsPath(parts[:idxPart+1]), + method.Type()) + } + var ret []reflect.Value + var panicked any + func() { + defer func() { panicked = recover() }() + ret = method.Call(nil) + }() + if panicked != nil { + return smuggleValue{}, fmt.Errorf( + "method %s panicked: %v", + joinFieldsPath(parts[:idxPart+1]), + panicked) + } + if len(ret) == 2 && !ret[1].IsNil() { + return smuggleValue{}, fmt.Errorf( + "method %s returned an error: %w", + joinFieldsPath(parts[:idxPart+1]), + ret[1].Interface().(error)) + } + vgot = ret[0] + continue + } + // Resolve all interface and pointer dereferences + origKind := vgot.Kind() for { switch vgot.Kind() { case reflect.Interface, reflect.Ptr: @@ -178,13 +260,22 @@ func buildFieldsPathFn(path string) (func(any) (smuggleValue, error), error) { } continue } + deref := "" + if origKind != vgot.Kind() { + deref = " (after dereferencing)" + } if idxPart == 0 { return smuggleValue{}, - fmt.Errorf("it is a %s and should be a struct", vgot.Kind()) + fmt.Errorf("it is a %s%s and should be a struct", vgot.Kind(), deref) + } + if parts[idxPart-1].Method { + return smuggleValue{}, fmt.Errorf( + "method %s returned a %s%s and should be a struct", + joinFieldsPath(parts[:idxPart]), vgot.Kind(), deref) } return smuggleValue{}, fmt.Errorf( - "field %q is a %s and should be a struct", - joinFieldsPath(parts[:idxPart]), vgot.Kind()) + "field %q is a %s%s and should be a struct", + joinFieldsPath(parts[:idxPart]), vgot.Kind(), deref) } switch vgot.Kind() { @@ -546,6 +637,7 @@ func buildCaster(outType reflect.Type, useString bool) reflect.Value { // several struct layers. // // type A struct{ Num int } +// // func (a *A) String() string { return fmt.Sprintf("Num is %d", a.Num) } // type B struct{ As map[string]*A } // type C struct{ B B } // got := C{B: B{As: map[string]*A{"foo": {Num: 12}}}} @@ -563,15 +655,30 @@ func buildCaster(outType reflect.Type, useString bool) reflect.Value { // // Tests that got.B.As["foo"].Num is 12 // td.Cmp(t, got, td.Smuggle("B.As[foo].Num", 12)) // -// Contrary to [JSONPointer] operator, private fields can be -// followed. Arrays, slices and maps work using the index/key inside -// square brackets (e.g. [12] or [foo]). Maps work only for simple key -// types (string or numbers), without "" when using strings -// (e.g. [foo]). +// In addition, simple public methods can also be called like in: +// +// td.Cmp(t, got, td.Smuggle("B.As[foo].String()", "Num is 12")) +// +// Allowed methods must not take any parameter and must return one +// value or a value and an error. For the latter case, if the method +// returns a non-nil error, the comparison fails. The comparison also +// fails if a panic occurs or if a method cannot be called. No private +// fields should be traversed before calling the method. For fun, +// consider a more complex example involving [reflect] and chaining +// method calls: +// +// got := reflect.Valueof(&C{B: B{As: map[string]*A{"foo": {Num: 12}}}}) +// td.Cmp(t, got, td.Smuggle("Elem().Interface().B.As[foo].String()", "Num is 12")) +// +// Contrary to [JSONPointer] operator, private fields can be followed +// and public methods on public fields can be called. Arrays, slices +// and maps work using the index/key inside square brackets (e.g. [12] +// or [foo]). Maps work only for simple key types (string or numbers), +// without "" when using strings (e.g. [foo]). // // Behind the scenes, a temporary function is automatically created to -// achieve the same goal, but add some checks against nil values and -// auto-dereference interfaces and pointers, even on several levels, +// achieve the same goal, but adds some checks against nil values and +// auto-dereferences interfaces and pointers, even on several levels, // like in: // // type A struct{ N any } diff --git a/td/td_smuggle_private_test.go b/td/td_smuggle_private_test.go index d63ea5ce..2175bb49 100644 --- a/td/td_smuggle_private_test.go +++ b/td/td_smuggle_private_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021, Maxime Soulé +// Copyright (c) 2021-2024, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -7,6 +7,7 @@ package td import ( + "errors" "reflect" "testing" @@ -22,7 +23,11 @@ func TestFieldsPath(t *testing.T) { var gotStr []string for _, s := range got { - gotStr = append(gotStr, s.Name) + if s.Method { + gotStr = append(gotStr, s.Name+"()") + } else { + gotStr = append(gotStr, s.Name) + } } if !reflect.DeepEqual(gotStr, expected) { @@ -35,7 +40,7 @@ func TestFieldsPath(t *testing.T) { } check("test", "test") - check("test.foo.bar", "test", "foo", "bar") + check("Test.Foo().bar", "Test", "Foo()", "bar") check("test.foo.bar", "test", "foo", "bar") check("test[foo.bar]", "test", "foo.bar") check("test[foo][bar]", "test", "foo", "bar") @@ -68,114 +73,258 @@ func TestFieldsPath(t *testing.T) { checkErr("foo[bar", `cannot find final ']' in FIELD_PATH "foo[bar"`) checkErr("test.%foo", `unexpected '%' in field name "%foo" in FIELDS_PATH "test.%foo"`) checkErr("test.f%oo", `unexpected '%' in field name "f%oo" in FIELDS_PATH "test.f%oo"`) + checkErr("Foo().()", `missing method name before () in FIELDS_PATH "Foo().()"`) + checkErr("abc.foo()", `method name "foo()" is not public in FIELDS_PATH "abc.foo()"`) + checkErr("Fo%o().abc", `unexpected '%' in method name "Fo%o()" in FIELDS_PATH "Fo%o().abc"`) + checkErr("Pipo.bingo.zzz.Foo.Zip().abc", `cannot call method Zip() as it is based on an unexported field "bingo" in FIELDS_PATH "Pipo.bingo.zzz.Foo.Zip().abc"`) checkErr("foo[bar", `cannot find final ']' in FIELD_PATH "foo[bar"`) } +type SmuggleBuild struct { + Field struct { + Path string + } + Iface any + Next *SmuggleBuild +} + +func (s SmuggleBuild) FollowIface() any { + return s.Iface +} + +func (s *SmuggleBuild) PtrFollowIface() any { + return s.Iface +} + +func (s SmuggleBuild) MayFollowIface() (any, error) { + if s.Iface == nil { + return nil, errors.New("Iface is nil") + } + return s.Iface, nil +} + +func (s SmuggleBuild) FollowNext() *SmuggleBuild { + return s.Next +} + +func (s *SmuggleBuild) PtrFollowNext() *SmuggleBuild { + return s.Next +} + +func (s SmuggleBuild) MayFollowNext() (*SmuggleBuild, error) { + if s.Next == nil { + return nil, errors.New("Next is nil") + } + return s.Next, nil +} + +func (s SmuggleBuild) Error() (bool, error) { + return false, errors.New("an error occurred") +} + +func (s SmuggleBuild) SetPath(path string) { + s.Field.Path = path +} + +func (s SmuggleBuild) Panic() string { + panic("oops!") +} + +func (s SmuggleBuild) Num() int { + return 42 +} + +func (s *SmuggleBuild) PNum() int { + return 42 +} + func TestBuildFieldsPathFn(t *testing.T) { _, err := buildFieldsPathFn("bad[path") test.Error(t, err) - // - // Struct - type Build struct { - Field struct { - Path string + t.Run("Struct", func(t *testing.T) { + fn, err := buildFieldsPathFn("Field.Path.Bad") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Field.Path" is a string and should be a struct`) + } + + _, err = fn(123) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), "it is a int and should be a struct") + } } - Iface any - } - fn, err := buildFieldsPathFn("Field.Path.Bad") - if test.NoError(t, err) { - _, err = fn(Build{}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Field.Path" is a string and should be a struct`) + fn, err = buildFieldsPathFn("Iface.Bad") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: 42}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface" is a int (after dereferencing) and should be a struct`) + } + + num := 42 + _, err = fn(&num) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `it is a int (after dereferencing) and should be a struct`) + } } - _, err = fn(123) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), "it is a int and should be a struct") + fn, err = buildFieldsPathFn("Field.Unknown") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), `field "Field.Unknown" not found`) + } } - } + }) - fn, err = buildFieldsPathFn("Field.Unknown") - if test.NoError(t, err) { - _, err = fn(Build{}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), `field "Field.Unknown" not found`) + t.Run("Map", func(t *testing.T) { + fn, err := buildFieldsPathFn("Iface[str].Field") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: map[int]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" is not an integer and so cannot match int map key type`) + } + + _, err = fn(SmuggleBuild{Iface: map[uint]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" is not an unsigned integer and so cannot match uint map key type`) + } + + _, err = fn(SmuggleBuild{Iface: map[float32]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" is not a float and so cannot match float32 map key type`) + } + + _, err = fn(SmuggleBuild{Iface: map[complex128]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" is not a complex number and so cannot match complex128 map key type`) + } + + _, err = fn(SmuggleBuild{Iface: map[struct{ A int }]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" cannot match unsupported struct { A int } map key type`) + } + + _, err = fn(SmuggleBuild{Iface: map[string]SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), `field "Iface[str]", "str" map key not found`) + } } - } + }) - // - // Map - fn, err = buildFieldsPathFn("Iface[str].Field") - if test.NoError(t, err) { - _, err = fn(Build{Iface: map[int]Build{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" is not an integer and so cannot match int map key type`) + t.Run("Array-Slice", func(t *testing.T) { + fn, err := buildFieldsPathFn("Iface[str].Field") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: []int{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[str]", "str" is not a slice/array index`) + } } - _, err = fn(Build{Iface: map[uint]Build{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" is not an unsigned integer and so cannot match uint map key type`) + fn, err = buildFieldsPathFn("Iface[18].Field") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: []int{1, 2, 3}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface[18]", 18 is out of slice/array range (len 3)`) + } + + _, err = fn(SmuggleBuild{Iface: 42}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field "Iface" is a int, but a map, array or slice is expected`) + } } - _, err = fn(Build{Iface: map[float32]Build{}}) - if test.Error(t, err) { + fn, err = buildFieldsPathFn("[18].Field") + if test.NoError(t, err) { + _, err = fn(42) test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" is not a float and so cannot match float32 map key type`) + `it is a int, but a map, array or slice is expected`) } + }) - _, err = fn(Build{Iface: map[complex128]Build{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" is not a complex number and so cannot match complex128 map key type`) + t.Run("Function", func(t *testing.T) { + fn, err := buildFieldsPathFn("Iface.Unknown()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `field Iface (type td.SmuggleBuild) does not implement Unknown() method`) + } } - _, err = fn(Build{Iface: map[struct{ A int }]Build{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" cannot match unsupported struct { A int } map key type`) + fn, err = buildFieldsPathFn("Iface.NilUnknown()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), `field "Iface" is nil`) + } } - _, err = fn(Build{Iface: map[string]Build{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), `field "Iface[str]", "str" map key not found`) + fn, err = buildFieldsPathFn("Unknown()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `type td.SmuggleBuild has no method Unknown()`) + } } - } - // - // Array / Slice - fn, err = buildFieldsPathFn("Iface[str].Field") - if test.NoError(t, err) { - _, err = fn(Build{Iface: []int{}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[str]", "str" is not a slice/array index`) + fn, err = buildFieldsPathFn("Iface.SetPath()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: &SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `cannot call Iface.SetPath(), signature func(string) not handled, only func() A or func() (A, error) allowed`) + } } - } - fn, err = buildFieldsPathFn("Iface[18].Field") - if test.NoError(t, err) { - _, err = fn(Build{Iface: []int{1, 2, 3}}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface[18]", 18 is out of slice/array range (len 3)`) + fn, err = buildFieldsPathFn("Iface.Panic()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: &SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `method Iface.Panic() panicked: oops!`) + } } - _, err = fn(Build{Iface: 42}) - if test.Error(t, err) { - test.EqualStr(t, err.Error(), - `field "Iface" is a int, but a map, array or slice is expected`) + fn, err = buildFieldsPathFn("Iface.Error()") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: &SmuggleBuild{}}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `method Iface.Error() returned an error: an error occurred`) + } } - } - fn, err = buildFieldsPathFn("[18].Field") - if test.NoError(t, err) { - _, err = fn(42) - test.EqualStr(t, err.Error(), - `it is a int, but a map, array or slice is expected`) - } + fn, err = buildFieldsPathFn("Num().Bad") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `method Num() returned a int and should be a struct`) + } + } + + fn, err = buildFieldsPathFn("FollowIface().Bad") + if test.NoError(t, err) { + _, err = fn(SmuggleBuild{Iface: 42}) + if test.Error(t, err) { + test.EqualStr(t, err.Error(), + `method FollowIface() returned a int (after dereferencing) and should be a struct`) + } + } + }) } diff --git a/td/td_smuggle_test.go b/td/td_smuggle_test.go index b7d36ebe..c8f00ac8 100644 --- a/td/td_smuggle_test.go +++ b/td/td_smuggle_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Maxime Soulé +// Copyright (c) 2018-2024, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -645,6 +645,7 @@ func TestSmuggleFieldsPath(t *testing.T) { checkOK(t, gotStruct, td.Smuggle("MyStructMid.MyStructBase.ValBool", true)) checkOK(t, gotStruct, td.Smuggle("ValBool", true)) // thanks to composition + checkOK(t, gotStruct, td.Smuggle("Ptr", td.Ptr(42))) // OK across pointers checkOK(t, b, td.Smuggle("PA.Num", 2)) @@ -762,6 +763,79 @@ func TestSmuggleFieldsPath(t *testing.T) { } } +func TestSmuggleFieldsPathMethod(t *testing.T) { + pipo := td.SmuggleBuild{Field: struct{ Path string }{"pipo"}} + + checkOK(t, td.SmuggleBuild{Next: &pipo}, + td.Smuggle(`FollowNext().Field.Path`, "pipo")) + + checkOK(t, &td.SmuggleBuild{Next: &pipo}, + td.Smuggle(`FollowNext().Field.Path`, "pipo")) + + checkOK(t, &td.SmuggleBuild{Next: &pipo}, + td.Smuggle(`PtrFollowNext().Field.Path`, "pipo")) + + checkOK(t, td.SmuggleBuild{Iface: pipo}, + td.Smuggle(`FollowIface().Field.Path`, "pipo")) + + checkOK(t, &td.SmuggleBuild{Iface: pipo}, + td.Smuggle(`FollowIface().Field.Path`, "pipo")) + + checkOK(t, &td.SmuggleBuild{Iface: &pipo}, + td.Smuggle(`PtrFollowIface().Field.Path`, "pipo")) + + checkOK(t, td.SmuggleBuild{Iface: pipo}, + td.Smuggle(`MayFollowIface().Field.Path`, "pipo")) + + // Method call on typed nil is OK as PNum() is a method on *td.SmuggleBuild + checkOK(t, td.SmuggleBuild{}, td.Smuggle(`Next.PNum()`, 42)) + checkOK(t, td.SmuggleBuild{Iface: (*td.SmuggleBuild)(nil)}, + td.Smuggle(`Iface.PNum()`, 42)) + + // Method call on typed nil is KO as Num() is a method on td.SmuggleBuild + checkError(t, td.SmuggleBuild{}, td.Smuggle(`Next.Num()`, 42), + expectedError{ + Message: mustBe("ran smuggle code with %% as argument"), + Path: mustBe("DATA"), + Summary: mustMatch(` +it failed coz: method Next.Num\(\) panicked: value method .+/td.SmuggleBuild.Num called using nil \*SmuggleBuild pointer`), + }) + + checkError(t, td.SmuggleBuild{Next: &pipo}, + td.Smuggle(`Next.Panic()`, "dummy"), + expectedError{ + Message: mustBe("ran smuggle code with %% as argument"), + Path: mustBe("DATA"), + Summary: mustContain(` +it failed coz: method Next.Panic() panicked: oops!`), + }) + + checkError(t, td.SmuggleBuild{Next: &td.SmuggleBuild{}}, + td.Smuggle(`Next.MayFollowNext().Field.Path`, "pipo"), + expectedError{ + Message: mustBe("ran smuggle code with %% as argument"), + Path: mustBe("DATA"), + Summary: mustContain(` +it failed coz: method Next.MayFollowNext() returned an error: Next is nil`), + }) + + checkError(t, td.SmuggleBuild{Next: &td.SmuggleBuild{}}, + td.Smuggle(`Next.MayFollowIface().Field.Path`, "pipo"), + expectedError{ + Message: mustBe("ran smuggle code with %% as argument"), + Path: mustBe("DATA"), + Summary: mustContain(` +it failed coz: method Next.MayFollowIface() returned an error: Iface is nil`), + }) + + // Test with reflect + checkOK(t, reflect.ValueOf(pipo), + td.Smuggle(`Interface().Field.Path`, "pipo")) + + checkOK(t, reflect.ValueOf(reflect.ValueOf(&pipo)), + td.Smuggle(`Interface().Elem().Interface().Field.Path`, "pipo")) +} + func TestSmuggleTypeBehind(t *testing.T) { // Type behind is the smuggle function parameter one