From 13a28ae1bbe05bcada4474046720a9add6160aff Mon Sep 17 00:00:00 2001 From: mynameisdeleted Date: Sun, 12 Jul 2026 11:28:06 -0400 Subject: [PATCH] Enhance debugging support by adding type information to evaluation results and updating visualization backend to handle additional type parameters --- .gitignore | 3 +- demos/lists/python/.vscode/launch.json | 16 ++++ demos/lists/rust/.vscode/launch.json | 21 +++++ demos/lists/rust/.vscode/tasks.json | 17 ++++ demos/lists/rust/Cargo.lock | 7 ++ .../NodeTableGraphVisualizationSupport.ts | 86 +++++++++++++------ extension/src/proxies/DebugSessionProxy.ts | 3 +- 7 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 demos/lists/python/.vscode/launch.json create mode 100644 demos/lists/rust/.vscode/launch.json create mode 100644 demos/lists/rust/.vscode/tasks.json create mode 100644 demos/lists/rust/Cargo.lock diff --git a/.gitignore b/.gitignore index 9a38826..8e69848 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Logs logs *.log +target npm-debug.log* yarn-debug.log* yarn-error.log* @@ -87,4 +88,4 @@ __pycache__/ *.py[cod] *$py.class -*.vsix \ No newline at end of file +*.vsix diff --git a/demos/lists/python/.vscode/launch.json b/demos/lists/python/.vscode/launch.json new file mode 100644 index 0000000..dfc4ae8 --- /dev/null +++ b/demos/lists/python/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/demos/lists/rust/.vscode/launch.json b/demos/lists/rust/.vscode/launch.json new file mode 100644 index 0000000..03366c6 --- /dev/null +++ b/demos/lists/rust/.vscode/launch.json @@ -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 + } + ] +} \ No newline at end of file diff --git a/demos/lists/rust/.vscode/tasks.json b/demos/lists/rust/.vscode/tasks.json new file mode 100644 index 0000000..3842afe --- /dev/null +++ b/demos/lists/rust/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "build", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "label": "cargo build" + } + ] +} diff --git a/demos/lists/rust/Cargo.lock b/demos/lists/rust/Cargo.lock new file mode 100644 index 0000000..fa88d3f --- /dev/null +++ b/demos/lists/rust/Cargo.lock @@ -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" diff --git a/extension/src/VisualizationBackend/NodeTableGraphVisualizationSupport.ts b/extension/src/VisualizationBackend/NodeTableGraphVisualizationSupport.ts index 04f1e80..224fb33 100644 --- a/extension/src/VisualizationBackend/NodeTableGraphVisualizationSupport.ts +++ b/extension/src/VisualizationBackend/NodeTableGraphVisualizationSupport.ts @@ -206,7 +206,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase primary.result, nodes, edges, - visited + visited, + undefined, + primary.type ); } else { nodes.set("root", { @@ -321,7 +323,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase v.value, nodes, edges, - visited + visited, + undefined, + v.type ); roots.push({ name: v.name, value: id, kind: "local", type: v.type }); } else { @@ -430,10 +434,15 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase } // Recursively walks a DAP variablesReference into `nodes`/`edges`, - // returning the id (a real address when one is present in the variable's - // own printed value, else a synthesized id) of the node it produced -- - // 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 + // returning the id of the node it produced -- a real address when one + // is present in the variable's own printed value, else the DAP + // variablesReference itself (verified against a real debugpy session, + // 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 // array of structs gets the compact table rendering everywhere it // 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, edges: GraphEdge[], visited: Set, - prefetchedChildren?: DapVariable[] + prefetchedChildren?: DapVariable[], + ownType?: string ): Promise { - const id = this.extractAddress(ownValue) || `synthetic:${nodes.size}`; + const id = this.extractAddress(ownValue) || `vref:${variablesReference}`; if (visited.has(id)) { return id; } @@ -458,7 +468,7 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase if (this.looksLikeArrayOfStructs(children)) { await this.buildArrayTableNode(id, children, nodes, edges, visited); } else { - await this.buildStructFields(id, children, nodes, edges, visited); + await this.buildStructFields(id, children, nodes, edges, visited, ownType); } return id; } @@ -468,13 +478,14 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase children: DapVariable[], nodes: Map, edges: GraphEdge[], - visited: Set + visited: Set, + ownType?: string ) { const fields: NodeField[] = []; 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 @@ -519,7 +530,9 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase m.value, nodes, edges, - visited + visited, + undefined, + m.type ); edges.push({ from: id, @@ -566,26 +579,50 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase nodes: Map, visited: Set, ownerId: string, - depth: number + depth: number, + ownerType?: string ) { const addr = this.extractAddress(child.value); - if (addr && this.looksLikePointer(child)) { - fields.push({ - name: child.name, - value: addr, - isPointer: true, - typeHint: child.type, - }); + // A field whose *static type* matches the type of the struct that + // directly contains it (not necessarily the original top-level + // owner -- see the recursive call below, which passes child.type + // as the new ownerType) can only be sound if it's some kind of + // reference/handle rather than a true by-value member: a value 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 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) { - const targetId = await this.walkNodeOrArrayTable( + targetId = await this.walkNodeOrArrayTable( child.variablesReference, child.value, nodes, edges, - visited + visited, + undefined, + child.type ); 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; } @@ -626,7 +663,8 @@ export class NodeTableGraphVisualizationBackend extends VisualizationBackendBase nodes, visited, ownerId, - depth + 1 + depth + 1, + child.type ); } return; diff --git a/extension/src/proxies/DebugSessionProxy.ts b/extension/src/proxies/DebugSessionProxy.ts index ce29972..68a9d2f 100644 --- a/extension/src/proxies/DebugSessionProxy.ts +++ b/extension/src/proxies/DebugSessionProxy.ts @@ -85,7 +85,7 @@ export class DebugSessionProxy { expression: string; frameId: number | undefined; context: "watch" | "repl" | "copy"; - }): Promise<{ result: string; variablesReference: number }> { + }): Promise<{ result: string; variablesReference: number; type?: string }> { const reply = await this.session.customRequest("evaluate", { expression: args.expression, frameId: args.frameId, @@ -94,6 +94,7 @@ export class DebugSessionProxy { return { result: reply.result, variablesReference: reply.variablesReference, + type: reply.type, }; } }