Enhance debugging support by adding type information to evaluation results and updating visualization backend to handle additional type parameters
Some checks failed
/ build (macos-latest) (push) Has been cancelled
/ build (ubuntu-latest) (push) Has been cancelled
/ build (windows-latest) (push) Has been cancelled

This commit is contained in:
mynameisdeleted
2026-07-12 11:28:06 -04:00
parent f254c3a31c
commit 13a28ae1bb
7 changed files with 127 additions and 26 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Logs # Logs
logs logs
*.log *.log
target
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
@@ -87,4 +88,4 @@ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
*.vsix *.vsix

16
demos/lists/python/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}

21
demos/lists/rust/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "cppdbg",
"request": "launch",
"name": "Debug list_example",
"program": "${workspaceFolder}/target/debug/list_example",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cargo build",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/rust-gdb",
"stopAtEntry": false,
"externalConsole": false
}
]
}

17
demos/lists/rust/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "cargo build"
}
]
}

7
demos/lists/rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "list_example"
version = "0.1.0"

View File

@@ -206,7 +206,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
primary.result, primary.result,
nodes, nodes,
edges, edges,
visited visited,
undefined,
primary.type
); );
} else { } else {
nodes.set("root", { nodes.set("root", {
@@ -321,7 +323,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
v.value, v.value,
nodes, nodes,
edges, edges,
visited visited,
undefined,
v.type
); );
roots.push({ name: v.name, value: id, kind: "local", type: v.type }); roots.push({ name: v.name, value: id, kind: "local", type: v.type });
} else { } else {
@@ -430,10 +434,15 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
} }
// Recursively walks a DAP variablesReference into `nodes`/`edges`, // Recursively walks a DAP variablesReference into `nodes`/`edges`,
// returning the id (a real address when one is present in the variable's // returning the id of the node it produced -- a real address when one
// own printed value, else a synthesized id) of the node it produced -- // is present in the variable's own printed value, else the DAP
// mirrors the old scripts' hex(ptr_int)/id(obj)-derived node ids, giving // variablesReference itself (verified against a real debugpy session,
// the same cross-reference cycle dedup via `visited`. Routes to either a // including through a genuine self-referential cycle, to be stably
// reused for the same underlying object -- not just an arbitrary
// per-call counter, which would neither dedup re-visits of the same
// addressless object nor terminate a true cycle). Mirrors the old
// scripts' hex(ptr_int)/id(obj)-derived node ids, giving the same
// cross-reference cycle dedup via `visited`. Routes to either a
// regular struct node or an array-table node depending on shape, so an // regular struct node or an array-table node depending on shape, so an
// array of structs gets the compact table rendering everywhere it // array of structs gets the compact table rendering everywhere it
// appears -- as the primary root, a struct's own field, a local, or a // appears -- as the primary root, a struct's own field, a local, or a
@@ -444,9 +453,10 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
nodes: Map<string, GraphNodeData>, nodes: Map<string, GraphNodeData>,
edges: GraphEdge[], edges: GraphEdge[],
visited: Set<string>, visited: Set<string>,
prefetchedChildren?: DapVariable[] prefetchedChildren?: DapVariable[],
ownType?: string
): Promise<string> { ): Promise<string> {
const id = this.extractAddress(ownValue) || `synthetic:${nodes.size}`; const id = this.extractAddress(ownValue) || `vref:${variablesReference}`;
if (visited.has(id)) { if (visited.has(id)) {
return id; return id;
} }
@@ -458,7 +468,7 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
if (this.looksLikeArrayOfStructs(children)) { if (this.looksLikeArrayOfStructs(children)) {
await this.buildArrayTableNode(id, children, nodes, edges, visited); await this.buildArrayTableNode(id, children, nodes, edges, visited);
} else { } else {
await this.buildStructFields(id, children, nodes, edges, visited); await this.buildStructFields(id, children, nodes, edges, visited, ownType);
} }
return id; return id;
} }
@@ -468,13 +478,14 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
children: DapVariable[], children: DapVariable[],
nodes: Map<string, GraphNodeData>, nodes: Map<string, GraphNodeData>,
edges: GraphEdge[], edges: GraphEdge[],
visited: Set<string> visited: Set<string>,
ownType?: string
) { ) {
const fields: NodeField[] = []; const fields: NodeField[] = [];
for (const child of children) { for (const child of children) {
await this.addField(child, fields, edges, nodes, visited, id, 0); await this.addField(child, fields, edges, nodes, visited, id, 0, ownType);
} }
nodes.set(id, { id, fields }); nodes.set(id, { id, fields, type: ownType });
} }
// Builds a single node holding every element's members as columns // Builds a single node holding every element's members as columns
@@ -519,7 +530,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
m.value, m.value,
nodes, nodes,
edges, edges,
visited visited,
undefined,
m.type
); );
edges.push({ edges.push({
from: id, from: id,
@@ -566,26 +579,50 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
nodes: Map<string, GraphNodeData>, nodes: Map<string, GraphNodeData>,
visited: Set<string>, visited: Set<string>,
ownerId: string, ownerId: string,
depth: number depth: number,
ownerType?: string
) { ) {
const addr = this.extractAddress(child.value); const addr = this.extractAddress(child.value);
if (addr && this.looksLikePointer(child)) { // A field whose *static type* matches the type of the struct that
fields.push({ // directly contains it (not necessarily the original top-level
name: child.name, // owner -- see the recursive call below, which passes child.type
value: addr, // as the new ownerType) can only be sound if it's some kind of
isPointer: true, // reference/handle rather than a true by-value member: a value type
typeHint: child.type, // genuinely containing itself would have infinite size. This is
}); // what catches Python's (and similarly Java/C#'s) recursive
// dataclasses/objects, which never print an address in their repr
// (looksLikePointer's <object at 0x...> check only fires for
// classes that haven't overridden __repr__ -- dataclasses always
// do) and so would otherwise fall through to the inline-flatten
// path below and get capped/mangled at INLINE_FLATTEN_MAX_DEPTH
// instead of forming a proper linked node graph.
const isSelfReferential = !!ownerType && !!child.type && child.type === ownerType;
if ((addr && this.looksLikePointer(child)) || isSelfReferential) {
let targetId: string | undefined;
if (child.variablesReference > 0) { if (child.variablesReference > 0) {
const targetId = await this.walkNodeOrArrayTable( targetId = await this.walkNodeOrArrayTable(
child.variablesReference, child.variablesReference,
child.value, child.value,
nodes, nodes,
edges, edges,
visited visited,
undefined,
child.type
); );
edges.push({ from: ownerId, to: targetId, label: child.name }); edges.push({ from: ownerId, to: targetId, label: child.name });
} }
fields.push({
name: child.name,
// Real pointers show their address; a self-referential
// field with no address of its own shows the id of the
// node it resolves to instead (still a stable, clickable
// cross-reference) -- child.value as a last-resort fallback
// covers a null/childless self-typed field (unusual, but
// keeps this from rendering an empty value).
value: addr || targetId || child.value,
isPointer: true,
typeHint: child.type,
});
return; return;
} }
@@ -626,7 +663,8 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase
nodes, nodes,
visited, visited,
ownerId, ownerId,
depth + 1 depth + 1,
child.type
); );
} }
return; return;

View File

@@ -85,7 +85,7 @@ export class DebugSessionProxy {
expression: string; expression: string;
frameId: number | undefined; frameId: number | undefined;
context: "watch" | "repl" | "copy"; context: "watch" | "repl" | "copy";
}): Promise<{ result: string; variablesReference: number }> { }): Promise<{ result: string; variablesReference: number; type?: string }> {
const reply = await this.session.customRequest("evaluate", { const reply = await this.session.customRequest("evaluate", {
expression: args.expression, expression: args.expression,
frameId: args.frameId, frameId: args.frameId,
@@ -94,6 +94,7 @@ export class DebugSessionProxy {
return { return {
result: reply.result, result: reply.result,
variablesReference: reply.variablesReference, variablesReference: reply.variablesReference,
type: reply.type,
}; };
} }
} }