Skip to content

Conversation

@minestarks
Copy link
Member

@minestarks minestarks commented Oct 29, 2025

This PR adds a feature to circuit diagrams to display Q#/QASM source code locations for each qubit and gate as clickable links in VS Code. They can also be displayed as hover text in the Python circuit widget.

2025-10-31_14-28-26.mp4

Notes

Operation call sites

Operation boxes are clickable and show hover text with the location of the call site.

image

We always show the nearest user code location to the actual call site.

For example, if the user code shows ForEach(MResetZ, qs), even though the actual MResetZ gate is applied in the Std library, the location in the circuit diagram will point to the ForEach expression.

In Python, "user code" includes both cell contents and any real files pulled via qsharp.init(project_root=...)

Qubit declaration sites

Qubit labels are clickable and show hover text with a location.

image

This location corresponds to the use statement that allocated the qubit. Example:

use q = Qubit();

There is an exception to this when the circuit diagram is generated for an operation which takes qubit input arguments.
In this case, the declaration locations are the input arguments to the operation. Example:

operation Test(q1: Qubit, q2: Qubit) : Result[] {}

Here, q0 is declared at 1:16 and q1 is declared at 1:27.

Qubits can have multiple declaration sites. Example:

operation Main() : Unit {
    {
        use q1 = Qubit();
        X(q1);
        Reset(q1);
    }
    {
        use q2 = Qubit();
        Y(q2);
        Reset(q2);
    }
}

The circuit diagram for this code only shows one qubit wire, but it maps to both q1 and q2. So clicking on that qubit label will show both locations:

image

Circuit JSON schema

Components now have a source field.

{
    "kind": "unitary",
    "gate": "H",
    "targets": [{ "qubit": 0 }],
    "source": { "file": "line_0", "line": 0, "column": 43 }
}

Qubits have a declarations field.

{
    "id": 0,
    "numResults": 0,
    "declarations": [{ "file": "foo.ts", "line": 0, "column": 26 }]
}

These fields don't exist in the .qsc file schema. Even though we don't have a user scenario that writes circuits out to editable .qsc files yet, if we ever do, these fields should be excluded as they lose their meaning when the generated circuit is divorced from its originating source code.

VS Code

In VS code, clicking on an element with a source code location will navigate to the source code.

image

Python

Source code locations can be enabled in Python by passing in source_locations=True into qsharp.circuit().

image

While the hover text shows the location, clicking on the gates does nothing.

Source code locations are not enabled by default in Python. This is because today, the locations for Q#/QASM code evaluated via qsharp.eval don't look very meaningful (line_1:4:2 etc). If a qsharp.json is used, then the files will have proper disk locations, but this is a relatively uncommon scenario.

ASCII art update to show source code location labels, e.g.

ASCII art update to show source code location labels, e.g.

[email protected]:4:20 ──────── ● ────────
[email protected]:4:20 ──────── ● ────────
[email protected]:4:20 ─ [email protected]:5:20 ──

These labels are disabled in the Python string rendering (the only place this ASCII art would be shown to the user) . It's useful in test snapshots (see circuit_tests.rs ), but at the moment they look too ugly for public consumption.

Configuration and enablement

Since there is a perf cost to capturing call stacks during circuit tracing, we have to be conservative about when to enable circuit tracing.

At this point circuit tracing is only enabled when:

  • Explicitly generating a circuit, via the "show circuit" command in VS code, or the circuit() API in Python
  • Debugging in VS Code PR forthcoming to decouple the "Run" command from the debugger to avoid the perf hit)
  • When the Python library is initialized with trace_circuit=True

Additionally source location capturing is configurable via a user setting. When it's disabled, the Q#/QASM evaluator skips call stack captures.

In VS Code, new settings.json configurations have been added to tweak circuit generation options.

"Q#.circuits.config": {
    "maxOperations": 10001,
    "generationMethod": "classicalEval",
    "sourceLocations": true
}  

While maxOperations and generationMethod are not new features, they weren't configurable by the user.

In Python, these configuration options are exposed in the circuit() function:

generation_method: Optional[CircuitGenerationMethod] = None,
max_operations: Optional[int] = None,
source_locations: bool = False,

Internals

Evaluator and circuit tracer changes

Introduced a Tracer trait, now implemented by the circuit tracer (a.k.a. the circuit builder). This trait diverges from Backend mainly in that it takes a call stack parameter (stack: &[Frame]), which allows the circuit tracer implementation to capture source code metadata in the circuit. The TracingBackend composes a regular Backend, a Tracer, or both, so backends that do not care about tracing remain unchanged.

Every time the evaluator makes an intrinsic call, it captures the call stack and passes it to tracer (unless source location capturing is disabled).

Also removed Result associated type from Backend as it made passing this type around needlessly complicated. Implementors can simply return val::Result, as the impl Into<Result> was enforced everywhere anyway.

The mechanics of the circuit tracer is largely unchanged, but it is refactored to make way for future changes. WireMapBuilder and OperationBuilder are separated out for manageability; these will continue evolving as we implement further circuit diagram features.

Entry expressions for operation circuits

Note that when we generate an circuit for an operation that takes qubits (as opposed to an Entrypoint, which has no input parameters), we have to do some additional processing to figure out qubit declaration locations.

Since in this scenario we use a generated entry expression, the real qubit declarations would live there. Example:

{
    use qs = Qubit[2];
    (Test.Test)(qs[0], qs[1]);
    let r: Result[] = [];
    r
}

We can't use these as the qubit declaration locations since they don't map to user code, and don't provide any meaningful insight to the user.

VS Code command

A new generic qsharp-vscode.gotoLocations command was added to jump to a specific location in source. The built-in VS Code commands didn't play nicely with our UX.

Circuit rendering

If operations or gate labels contain source code locations, they are rendered as clickable SVG elements. The hover text should show the source code location as well.

Minor fixes in rendering and CSS (dashed lines were invisible, SWAP gates had an error in the bounding box calculation.)

Changed the signature of draw to better organize the increasing number of options.

@minestarks minestarks force-pushed the minestarks/circuit-source-links branch from d8ca28c to 991ff11 Compare October 30, 2025 17:19
@minestarks minestarks marked this pull request as ready for review October 31, 2025 22:18
@ScottCarda-MS
Copy link
Contributor

Found a bug where going to source from a circuit element causes unexpected/inconsistent behavior when the source of that element is found in a circuit file (.qsc). This can be seen with the circuit integration sample.

Copy link
Collaborator

@swernli swernli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave some feedback on dyn vs impl in person, otherwise just this nitpick on naming.

@minestarks minestarks enabled auto-merge November 18, 2025 00:11
@minestarks minestarks added this pull request to the merge queue Nov 18, 2025
Merged via the queue into main with commit c1ca1a2 Nov 18, 2025
18 checks passed
@minestarks minestarks deleted the minestarks/circuit-source-links branch November 18, 2025 01:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants