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())