Skip to content

Commit be3433c

Browse files
committed
ui: named arguments, click-to-source, and call stack params
Show named argument parameters throughout the UI: - BytecodeView labels show "invoke add(a, b)" instead of "invoke add" when argument names are available in the invoke context's pointer group entries - Call stack breadcrumbs show "add(a, b)" instead of "add()" in both programs-react CallStackDisplay and web TraceDrawer - CallInfoPanel banner shows "Calling add(a, b)" Add click-to-source for context badges: - BytecodeView accepts onDeclarationClick callback - Clicking an invoke/return/revert badge with a declaration field fires the callback with sourceId, offset, and length - Falls back to pinning the tooltip if no declaration Extract declaration and argument names from contexts: - summarizeContext now returns argumentNames and declaration - New formatCallSignature utility for consistent formatting - New DeclarationRange type for click-to-source data - CallFrame and CallInfo types carry argumentNames
1 parent 3efa891 commit be3433c

9 files changed

Lines changed: 276 additions & 8 deletions

File tree

‎packages/bugc-react/src/components/BytecodeView.tsx‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
classifyContext,
1111
summarizeContext,
1212
type ContextKind,
13+
type DeclarationRange,
1314
} from "#utils/debugUtils";
1415
import { useEthdebugTooltip } from "#hooks/useEthdebugTooltip";
1516
import { EthdebugTooltip } from "./EthdebugTooltip.js";
@@ -35,16 +36,20 @@ export interface BytecodeViewProps {
3536
bytecode: BytecodeOutput;
3637
/** Callback when hovering over an opcode with source ranges */
3738
onOpcodeHover?: (ranges: SourceRange[]) => void;
39+
/** Callback when clicking a context badge with a declaration */
40+
onDeclarationClick?: (decl: DeclarationRange) => void;
3841
}
3942

4043
interface InstructionsViewProps {
4144
instructions: Evm.Instruction[];
4245
onOpcodeHover?: (ranges: SourceRange[]) => void;
46+
onDeclarationClick?: (decl: DeclarationRange) => void;
4347
}
4448

4549
function InstructionsView({
4650
instructions,
4751
onOpcodeHover,
52+
onDeclarationClick,
4853
}: InstructionsViewProps): JSX.Element {
4954
const {
5055
tooltip,
@@ -106,6 +111,21 @@ function InstructionsView({
106111
}
107112
};
108113

114+
const handleBadgeClick = (
115+
e: React.MouseEvent<HTMLSpanElement>,
116+
instruction: Evm.Instruction,
117+
) => {
118+
const ctx = instruction.debug?.context;
119+
if (!ctx) return;
120+
121+
const summary = summarizeContext(ctx);
122+
if (summary.declaration && onDeclarationClick) {
123+
onDeclarationClick(summary.declaration);
124+
} else {
125+
pinTooltip(e, formatTooltipContent(instruction));
126+
}
127+
};
128+
109129
return (
110130
<div className="bytecode-disassembly-interactive">
111131
{instructions.map((instruction, idx) => {
@@ -138,7 +158,7 @@ function InstructionsView({
138158
className={`context-badge context-badge-${kind}`}
139159
onMouseEnter={(e) => handleDebugIconMouseEnter(e, instruction)}
140160
onMouseLeave={hideTooltip}
141-
onClick={(e) => handleDebugIconClick(e, instruction)}
161+
onClick={(e) => handleBadgeClick(e, instruction)}
142162
title={summarizeContext(instruction.debug?.context).label}
143163
>
144164
{contextBadgeLabel(kind)}
@@ -202,6 +222,7 @@ function InstructionsView({
202222
export function BytecodeView({
203223
bytecode,
204224
onOpcodeHover,
225+
onDeclarationClick,
205226
}: BytecodeViewProps): JSX.Element {
206227
const runtimeHex = Array.from(bytecode.runtime)
207228
.map((b) => b.toString(16).padStart(2, "0"))
@@ -236,6 +257,7 @@ export function BytecodeView({
236257
<InstructionsView
237258
instructions={bytecode.createInstructions}
238259
onOpcodeHover={onOpcodeHover}
260+
onDeclarationClick={onDeclarationClick}
239261
/>
240262
)}
241263
</div>
@@ -263,6 +285,7 @@ export function BytecodeView({
263285
<InstructionsView
264286
instructions={bytecode.runtimeInstructions}
265287
onOpcodeHover={onOpcodeHover}
288+
onDeclarationClick={onDeclarationClick}
266289
/>
267290
</div>
268291
</div>

‎packages/bugc-react/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ export {
5252
hasSourceRange,
5353
classifyContext,
5454
summarizeContext,
55+
formatCallSignature,
5556
type ContextKind,
5657
type ContextSummary,
58+
type DeclarationRange,
5759
// IR debug utilities
5860
extractInstructionDebug,
5961
extractTerminatorDebug,

‎packages/bugc-react/src/utils/debugUtils.ts‎

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,25 @@ export function classifyContext(context: unknown): ContextKind {
135135
return "other";
136136
}
137137

138+
/**
139+
* A declaration source range for click-to-source.
140+
*/
141+
export interface DeclarationRange {
142+
sourceId: string;
143+
offset: number;
144+
length: number;
145+
}
146+
138147
/**
139148
* Summary of a function call context for display.
140149
*/
141150
export interface ContextSummary {
142151
kind: ContextKind;
143152
label: string;
144153
functionName?: string;
154+
argumentNames?: string[];
145155
details?: string;
156+
declaration?: DeclarationRange;
146157
}
147158

148159
/**
@@ -165,11 +176,16 @@ export function summarizeContext(context: unknown): ContextSummary {
165176
: invoke?.create
166177
? "create"
167178
: "";
179+
const argNames = extractArgumentNames(invoke);
180+
const declaration = extractDeclaration(invoke);
181+
const paramList = argNames.length > 0 ? `(${argNames.join(", ")})` : "()";
168182
return {
169183
kind,
170-
label: `invoke ${name}`,
184+
label: `invoke ${name}${paramList}`,
171185
functionName: name,
186+
argumentNames: argNames.length > 0 ? argNames : undefined,
172187
details: callType ? `${callType} call` : undefined,
188+
declaration,
173189
};
174190
}
175191

@@ -178,10 +194,12 @@ export function summarizeContext(context: unknown): ContextSummary {
178194
| Record<string, unknown>
179195
| undefined;
180196
const name = (ret?.identifier as string) ?? "unknown";
197+
const declaration = extractDeclaration(ret);
181198
return {
182199
kind,
183-
label: `return ${name}`,
200+
label: `return ${name}()`,
184201
functionName: name,
202+
declaration,
185203
};
186204
}
187205

@@ -191,11 +209,13 @@ export function summarizeContext(context: unknown): ContextSummary {
191209
| undefined;
192210
const name = (rev?.identifier as string) ?? "unknown";
193211
const panic = rev?.panic as number | undefined;
212+
const declaration = extractDeclaration(rev);
194213
return {
195214
kind,
196-
label: `revert ${name}`,
215+
label: `revert ${name}()`,
197216
functionName: name,
198217
details: panic !== undefined ? `panic(${panic})` : undefined,
218+
declaration,
199219
};
200220
}
201221

@@ -213,6 +233,88 @@ export function summarizeContext(context: unknown): ContextSummary {
213233
}
214234
}
215235

236+
/**
237+
* Format a function call signature with param names.
238+
*
239+
* Returns "name(a, b)" if names are available,
240+
* "name()" otherwise.
241+
*/
242+
export function formatCallSignature(
243+
identifier: string | undefined,
244+
argNames?: string[],
245+
): string {
246+
const name = identifier || "(anonymous)";
247+
if (argNames && argNames.length > 0) {
248+
return `${name}(${argNames.join(", ")})`;
249+
}
250+
return `${name}()`;
251+
}
252+
253+
/**
254+
* Extract argument names from an invoke context's
255+
* arguments pointer group.
256+
*/
257+
function extractArgumentNames(
258+
invoke: Record<string, unknown> | undefined,
259+
): string[] {
260+
if (!invoke) return [];
261+
262+
const args = invoke.arguments as Record<string, unknown> | undefined;
263+
if (!args) return [];
264+
265+
const pointer = args.pointer as Record<string, unknown> | undefined;
266+
if (!pointer) return [];
267+
268+
const group = pointer.group as Array<Record<string, unknown>> | undefined;
269+
if (!Array.isArray(group)) return [];
270+
271+
const names: string[] = [];
272+
let hasAnyName = false;
273+
for (const entry of group) {
274+
const name = entry.name as string | undefined;
275+
if (name) {
276+
names.push(name);
277+
hasAnyName = true;
278+
} else {
279+
names.push("_");
280+
}
281+
}
282+
283+
return hasAnyName ? names : [];
284+
}
285+
286+
/**
287+
* Extract a declaration source range from a function
288+
* identity object (invoke.invoke, return.return, etc.).
289+
*/
290+
function extractDeclaration(
291+
identity: Record<string, unknown> | undefined,
292+
): DeclarationRange | undefined {
293+
if (!identity) return undefined;
294+
295+
const decl = identity.declaration as Record<string, unknown> | undefined;
296+
if (!decl) return undefined;
297+
298+
const source = decl.source as Record<string, unknown> | undefined;
299+
const range = decl.range as Record<string, number> | undefined;
300+
301+
if (
302+
!source ||
303+
!range ||
304+
typeof source.id !== "string" ||
305+
typeof range.offset !== "number" ||
306+
typeof range.length !== "number"
307+
) {
308+
return undefined;
309+
}
310+
311+
return {
312+
sourceId: source.id,
313+
offset: range.offset,
314+
length: range.length,
315+
};
316+
}
317+
216318
/**
217319
* Find a field by name, searching inside gather arrays.
218320
*/

‎packages/bugc-react/src/utils/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ export {
88
hasSourceRange,
99
classifyContext,
1010
summarizeContext,
11+
formatCallSignature,
1112
type ContextKind,
1213
type ContextSummary,
14+
type DeclarationRange,
1315
} from "./debugUtils.js";
1416

1517
export {

‎packages/programs-react/src/components/CallInfoPanel.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export interface CallInfoPanelProps {
2525

2626
function formatBanner(info: ResolvedCallInfo): string {
2727
const name = info.identifier || "(anonymous)";
28+
const params = info.argumentNames
29+
? `(${info.argumentNames.join(", ")})`
30+
: "()";
2831

2932
if (info.kind === "invoke") {
3033
const prefix =
@@ -33,7 +36,7 @@ function formatBanner(info: ResolvedCallInfo): string {
3336
: info.callType === "create"
3437
? "Creating"
3538
: "Calling";
36-
return `${prefix} ${name}()`;
39+
return `${prefix} ${name}${params}`;
3740
}
3841

3942
if (info.kind === "return") {

‎packages/programs-react/src/components/CallStackDisplay.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export function CallStackDisplay({
5252
<span className="call-stack-name">
5353
{frame.identifier || "(anonymous)"}
5454
</span>
55-
<span className="call-stack-parens">()</span>
55+
<span className="call-stack-parens">
56+
({frame.argumentNames ? frame.argumentNames.join(", ") : ""})
57+
</span>
5658
</button>
5759
</React.Fragment>
5860
))}

‎packages/programs-react/src/components/TraceContext.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export interface ResolvedCallInfo {
6363
identifier?: string;
6464
/** Call variant for invoke contexts */
6565
callType?: "internal" | "external" | "create";
66+
/** Named arguments (from invoke context) */
67+
argumentNames?: string[];
6668
/** Panic code for revert contexts */
6769
panic?: number;
6870
/** Resolved pointer refs */
@@ -309,6 +311,7 @@ export function TraceProvider({
309311
kind: extractedCallInfo.kind,
310312
identifier: extractedCallInfo.identifier,
311313
callType: extractedCallInfo.callType,
314+
argumentNames: extractedCallInfo.argumentNames,
312315
panic: extractedCallInfo.panic,
313316
pointerRefs: extractedCallInfo.pointerRefs.map((ref) => ({
314317
label: ref.label,

0 commit comments

Comments
 (0)