From 79a0e216ab2650c3460afbd87668fa38be9fffb2 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Fri, 14 Sep 2018 13:52:41 +0200 Subject: [PATCH] proc: add iface.(data) syntax to access concrete value of an interface With this syntax users do not need to type the concrete type of an interface variable to access its contents. This also sidesteps the problem where the serialization of a type by go/printer is different from the one used for debug_info type names. Updates #1328 --- Documentation/cli/expr.md | 16 +++++++++++++++- _fixtures/testvariables2.go | 11 ++++++++++- pkg/proc/eval.go | 17 +++++++++++------ service/test/variables_test.go | 2 ++ 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Documentation/cli/expr.md b/Documentation/cli/expr.md index 8233d756..b65655f2 100644 --- a/Documentation/cli/expr.md +++ b/Documentation/cli/expr.md @@ -75,13 +75,27 @@ interface {}(*struct string) *"test" error(*struct main.astruct) *{A: 1, B: 2} ``` -To use a field of a struct contained inside an interface variable use a type assertion: +To use the contents of an interface variable use a type assertion: ``` (dlv) p iface1.(*main.astruct).B 2 ``` +Or just use the special `.(data)` type assertion: + +``` +(dlv) p iface1.(data).B +2 +``` + +If the contents of the interface variable are a struct or a pointer to struct the fields can also be accessed directly: + +``` +(dlv) p iface1.B +2 +``` + # Specifying package paths Packages with the same name can be disambiguated by using the full package path. For example, if the application imports two packages, `some/package` and `some/other/package`, both defining a variable `A`, the two variables can be accessed using this syntax: diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index e46f7c6e..e144f4c8 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -254,6 +254,15 @@ func main() { s4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} + var iface2map interface{} = map[string]interface{}{ + "a": map[string]interface{}{ + "1": map[string]interface{}{ + "x": 1, + "y": 2, + }, + }, + } + var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { @@ -261,5 +270,5 @@ func main() { } runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4) + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map) } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index e1f3a2ed..8f7bfbaa 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -640,12 +640,17 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err if xv.Children[0].Addr == 0 { return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) } - typ, err := scope.BinInfo.findTypeExpr(node.Type) - if err != nil { - return nil, err - } - if xv.Children[0].DwarfType.Common().Name != typ.Common().Name { - return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) + // Accept .(data) as a type assertion that always succeeds, so that users + // can access the data field of an interface without actually having to + // type the concrete type. + if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" { + typ, err := scope.BinInfo.findTypeExpr(node.Type) + if err != nil { + return nil, err + } + if xv.Children[0].DwarfType.Common().Name != typ.Common().Name { + return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) + } } // loadInterface will set OnlyAddr for the data member since here we are // passing false to loadData, however returning the variable with OnlyAddr diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 2849390c..8f773784 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -798,6 +798,8 @@ func TestEvalExpression(t *testing.T) { {"s2[0].NonPointerRecieverMethod", false, "main.astruct.NonPointerRecieverMethod", "main.astruct.NonPointerRecieverMethod", "func()", nil}, {"as2.Error", false, "main.(*astruct).Error", "main.(*astruct).Error", "func() string", nil}, {"as2.NonPointerRecieverMethod", false, "main.astruct.NonPointerRecieverMethod", "main.astruct.NonPointerRecieverMethod", "func()", nil}, + + {`iface2map.(data)`, false, "…", "…", "map[string]interface {}", nil}, } ver, _ := goversion.Parse(runtime.Version())