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
*.log
target
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -87,4 +88,4 @@ __pycache__/
*.py[cod]
*$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,
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<string, GraphNodeData>,
edges: GraphEdge[],
visited: Set<string>,
prefetchedChildren?: DapVariable[]
prefetchedChildren?: DapVariable[],
ownType?: string
): Promise<string> {
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<string, GraphNodeData>,
edges: GraphEdge[],
visited: Set<string>
visited: Set<string>,
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<string, GraphNodeData>,
visited: Set<string>,
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 <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) {
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;

View File

@@ -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,
};
}
}