diff --git a/service/dap/server.go b/service/dap/server.go index 31331d6e..761d4dbd 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -1907,10 +1907,15 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab func getNamedVariableCount(v *proc.Variable) int { namedVars := 0 + if v.Kind == reflect.Map && v.Len > 0 { + // len + namedVars += 1 + } if isListOfBytesOrRunes(v) { // string value of array/slice of bytes and runes. namedVars += 1 } + return namedVars } @@ -1919,6 +1924,15 @@ func getNamedVariableCount(v *proc.Variable) int { func (s *Server) metadataToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) { children := []dap.Variable{} // must return empty array, not null, if no children + if v.Kind == reflect.Map && v.Len > 0 { + children = append(children, dap.Variable{ + Name: "len()", + Value: fmt.Sprintf("%d", v.Len), + Type: "int", + EvaluateName: fmt.Sprintf("len(%s)", v.fullyQualifiedNameOrExpr), + }) + } + if isListOfBytesOrRunes(v.Variable) { // Return the string value of []byte or []rune. typeName := api.PrettyTypeName(v.DwarfType) diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 8d57a3c8..08872dcb 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -1288,8 +1288,9 @@ func TestScopesAndVariablesRequests2(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) m2 := client.ExpectVariablesResponse(t) - checkChildren(t, m2, "m2", 1) // each key-value represented by a single child - ref = checkVarExact(t, m2, 0, "1", "m2[1]", "*main.astruct {A: 10, B: 11}", "int: *main.astruct", hasChildren) + checkChildren(t, m2, "m2", 2) // each key-value represented by a single child + checkVarExact(t, m2, 0, "len()", "len(m2)", "1", "int", noChildren) + ref = checkVarExact(t, m2, 1, "1", "m2[1]", "*main.astruct {A: 10, B: 11}", "int: *main.astruct", hasChildren) if ref > 0 { client.VariablesRequest(ref) m2kv1 := client.ExpectVariablesResponse(t) @@ -1311,8 +1312,9 @@ func TestScopesAndVariablesRequests2(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) m3 := client.ExpectVariablesResponse(t) - checkChildren(t, m3, "m3", 2) // each key-value represented by a single child - ref = checkVarRegex(t, m3, 0, `main\.astruct {A: 1, B: 1}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", "int", hasChildren) + checkChildren(t, m3, "m3", 3) // each key-value represented by a single child + checkVarExact(t, m3, 0, "len()", "len(m3)", "2", "int", noChildren) + ref = checkVarRegex(t, m3, 1, `main\.astruct {A: 1, B: 1}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", "int", hasChildren) if ref > 0 { client.VariablesRequest(ref) m3kv0 := client.ExpectVariablesResponse(t) @@ -1320,7 +1322,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) { checkVarRegex(t, m3kv0, 0, "A", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "1", "int", noChildren) validateEvaluateName(t, client, m3kv0, 0) } - ref = checkVarRegex(t, m3, 1, `main\.astruct {A: 2, B: 2}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", "", hasChildren) + ref = checkVarRegex(t, m3, 2, `main\.astruct {A: 2, B: 2}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", "", hasChildren) if ref > 0 { // inspect another key from another key-value child client.VariablesRequest(ref) m3kv1 := client.ExpectVariablesResponse(t) @@ -1334,10 +1336,11 @@ func TestScopesAndVariablesRequests2(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) m4 := client.ExpectVariablesResponse(t) - checkChildren(t, m4, "m4", 4) // each key and value represented by a child, so double the key-value count - checkVarRegex(t, m4, 0, `\[key 0\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 1, B: 1}`, `main\.astruct`, hasChildren) - checkVarRegex(t, m4, 1, `\[val 0\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 11, B: 11}`, `main\.astruct`, hasChildren) - ref = checkVarRegex(t, m4, 2, `\[key 1\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 2, B: 2}`, `main\.astruct`, hasChildren) + checkChildren(t, m4, "m4", 5) // each key and value represented by a child, so double the key-value count + checkVarExact(t, m4, 0, "len()", "len(m4)", "2", "int", noChildren) + checkVarRegex(t, m4, 1, `\[key 0\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 1, B: 1}`, `main\.astruct`, hasChildren) + checkVarRegex(t, m4, 2, `\[val 0\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 11, B: 11}`, `main\.astruct`, hasChildren) + ref = checkVarRegex(t, m4, 3, `\[key 1\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 2, B: 2}`, `main\.astruct`, hasChildren) if ref > 0 { client.VariablesRequest(ref) m4Key1 := client.ExpectVariablesResponse(t) @@ -1347,7 +1350,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) { validateEvaluateName(t, client, m4Key1, 0) validateEvaluateName(t, client, m4Key1, 1) } - ref = checkVarRegex(t, m4, 3, `\[val 1\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 22, B: 22}`, "main.astruct", hasChildren) + ref = checkVarRegex(t, m4, 4, `\[val 1\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 22, B: 22}`, "main.astruct", hasChildren) if ref > 0 { client.VariablesRequest(ref) m4Val1 := client.ExpectVariablesResponse(t) @@ -1555,11 +1558,11 @@ func TestVariablesLoading(t *testing.T) { // Map not fully loaded based on LoadConfig.MaxArrayValues // Expect to be able to load map by paging. - ref = checkVarRegexIndexed(t, locals, -1, "m1", "m1", `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren, 66, 0) + ref = checkVarRegexIndexed(t, locals, -1, "m1", "m1", `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren, 66, 1) if ref > 0 { client.VariablesRequest(ref) m1 := client.ExpectVariablesResponse(t) - checkChildren(t, m1, "m1", 64) + checkChildren(t, m1, "m1", 65) client.IndexedVariablesRequest(ref, 0, 66) m1 = client.ExpectVariablesResponse(t) @@ -1587,6 +1590,10 @@ func TestVariablesLoading(t *testing.T) { } } } + client.NamedVariablesRequest(ref) + named := client.ExpectVariablesResponse(t) + checkChildren(t, named, "m1", 1) + checkVarExact(t, named, 0, "len()", "len(m1)", "66", "int", noChildren) } // Struct partially missing based on LoadConfig.MaxStructFields @@ -1665,7 +1672,7 @@ func TestVariablesLoading(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) tmV0 := client.ExpectVariablesResponse(t) - checkChildren(t, tmV0, "tm.v[0]", 64) + checkChildren(t, tmV0, "tm.v[0]", 65) } } } @@ -1695,7 +1702,7 @@ func TestVariablesLoading(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) tmV0 := client.ExpectVariablesResponse(t) - checkChildren(t, tmV0, "tm.v[0]", 64) + checkChildren(t, tmV0, "tm.v[0]", 65) } } } @@ -3052,20 +3059,20 @@ func TestVariableValueTruncation(t *testing.T) { if ref > 0 { client.VariablesRequest(ref) // Key format: ... @
- checkVarRegex(t, client.ExpectVariablesResponse(t), 0, `main\.C {s: "very long string 0123456789.+\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren) + checkVarRegex(t, client.ExpectVariablesResponse(t), 1, `main\.C {s: "very long string 0123456789.+\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren) } // key - scalar, value - scalar (inlined key:value display) => key not truncated ref = checkVarExact(t, locals, -1, "m6", "m6", "map[string]int ["+longstr+": 123, ]", "map[string]int", hasChildren) if ref > 0 { client.VariablesRequest(ref) - checkVarExact(t, client.ExpectVariablesResponse(t), 0, longstr, `m6[`+longstr+`]`, "123", "string: int", noChildren) + checkVarExact(t, client.ExpectVariablesResponse(t), 1, longstr, `m6[`+longstr+`]`, "123", "string: int", noChildren) } // key - compound, value - compound (array-like display) => key not truncated ref = checkVarExact(t, locals, -1, "m7", "m7", "map[main.C]main.C [{s: "+longstr+"}: {s: \"hello\"}, ]", "map[main.C]main.C", hasChildren) if ref > 0 { client.VariablesRequest(ref) m7 := client.ExpectVariablesResponse(t) - checkVarRegex(t, m7, 0, "[key 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstr+`}`, `main\.C`, hasChildren) + checkVarRegex(t, m7, 1, "[key 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstr+`}`, `main\.C`, hasChildren) } }, disconnect: true, @@ -4400,7 +4407,7 @@ func TestSetVariable(t *testing.T) { tester.evaluate(`m1["Malone"]`, "main.astruct {A: 2, B: 3}", hasChildren) m1Ref := checkVarRegex(t, locals, -1, "m1", "m1", `.*map\[string\]main\.astruct.*`, `map\[string\]main\.astruct`, hasChildren) m1 := tester.variables(m1Ref) - elem1 := m1.Body.Variables[0] + elem1 := m1.Body.Variables[1] tester.expectSetVariable(elem1.VariablesReference, "A", "-9999") tester.expectSetVariable(elem1.VariablesReference, "B", "10000") tester.evaluate(elem1.EvaluateName, "main.astruct {A: -9999, B: 10000}", hasChildren)