Skip to content

feat: resolve custom includes for a program given as a string - #377

Open
TheGupta2012 wants to merge 1 commit into
mainfrom
support-include-sources-in-loads
Open

feat: resolve custom includes for a program given as a string#377
TheGupta2012 wants to merge 1 commit into
mainfrom
support-include-sources-in-loads

Conversation

@TheGupta2012

@TheGupta2012 TheGupta2012 commented Aug 18, 2026

Copy link
Copy Markdown
Member

Fixes #368.

The problem

load() resolves custom include statements; loads() silently does not. The two entrypoints are otherwise documented as equivalent, so the asymmetry is easy to hit, and the failure is misleading when you do:

pyqasm.load("prog.qasm")                     # include inlined
pyqasm.loads(open("prog.qasm").read())       # include ignored, then:
# ValidationError: Unsupported / undeclared QASM operation: mygate

The error names the gate, not the unresolved include, which sends you to the gate table rather than to include handling.

Resolution is keyed on a path — _resolve_include_path joins dirname(base_file) with the include name — and a string has no path. Programs frequently arrive as strings, fetched from object storage or returned by a vendor API, so for those callers load() is not an option and includes could never resolve.

The change

One kwarg, include_dir, naming the directory includes resolve against:

pyqasm.loads(src, include_dir="/usr/share/qasm")

preprocess.py keeps its existing walk; _process_file is split so the body after the read is reusable for text that came from a string, and include_dir is threaded through to _resolve_include_path as one extra candidate, tried first. process_include_statements(filename, include_dir=None) stays signature-compatible.

Why not just resolve against the cwd

That is the smaller change — no kwarg at all — and it was considered. Two things stopped it:

  • It makes program text drive filesystem reads. _resolve_include_path does not constrain the name, so include "../secret.inc"; and include "/etc/hosts"; both resolve. Today loads(str) never opens a file; defaulting to cwd resolution would change that for every existing caller. For load() this is fine — the caller handed us a path — but a string carries no such intent. qBraid accepts user-submitted QASM, so this is concrete rather than theoretical.
  • The cwd is rarely what the caller thinks. In a web worker, notebook kernel or CI job it is incidental, and a program from storage could silently pick up an unrelated local file with a matching name.

An explicit directory keeps the same mechanism without either property. Resolution stays opt-in: omit the kwarg and loads() reads no files at all, passing an unresolved include through exactly as before — which test_no_include_added and test_includes_preserved pin, and which existing programs rely on.

Errors

Pass the kwarg and an include the directory does not hold is named, along with the directory — so a typo in the path is self-diagnosing and needs no separate guard:

ValidationError: Include file 'mygates.inc' not found in include_dir '/typo/path'
at line 3, column 10: 'include "mygates.inc";'

include_dir with an already-parsed Program raises ValueError — it has no include statements left to resolve, so the kwarg cannot do anything, and failing beats silently ignoring it (the principle #356 applied to the other kwargs). A non-string value raises TypeError at the call site.

Verification

  • Full suite: 790 passed, 4 skipped. Every pre-existing include test passes unchanged.
  • tox -e format-check: pylint 10.00/10, isort, black, mypy and headers clean.
  • Exercised against Quantinuum's real hqslib1.inc (5747 bytes, from CQCL/tket): loads(bell_state, include_dir=vendor) produces output byte-identical to load() on the same program with the include beside it.

One regression was caught by the existing suite during development and fixed: binding "\n".join(ctx.base_file_header) into the return expression evaluated the header before the walk that appends to it, dropping a qelib1.inc discovered inside an included file. test_valid_include_processing[include_qasm2_backward.qasm] failed on it.

Tests added (tests/test_include.py)

Test Pins
test_loads_resolves_include_from_include_dir the reported case now works
test_loads_and_load_agree_on_the_same_program the two entrypoints give byte-identical output
test_nested_include_resolves_beside_the_file_that_named_it a resolved include's own includes resolve beside it
test_loads_reports_the_unresolved_include_by_name the error names the include and the directory
test_loads_without_include_dir_still_passes_includes_through the opt-in boundary — no filesystem reads by default
test_include_dir_wins_over_the_directory_of_the_file precedence under load()
test_include_dir_rejected_for_a_parsed_program the Program combination raises
test_include_dir_rejects_a_non_path 3 parametrizations of malformed input

Follow-ups not taken

include_path=[...] (several directories) and per-file overrides are both additive on top of this and can follow if there is demand.

Related

#370 / #378 is the other half of loading a Quantinuum compiled program from a string: this PR locates hqslib1.inc, and #378 lets its opaque lines parse. #378 is stacked on this branch.

@argus-eye

argus-eye Bot commented Aug 18, 2026

Copy link
Copy Markdown

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

  • Trigger Argus review

Estimated cost

  • Files changed: 4
  • Diff lines (±): 363
  • Historical avg: ~243.6k tokens · ~$0.95 · across last 10 review(s)

Tip: you can also comment @argus-eye review at any time.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cf973ba2-4ed7-445d-b9ee-3e7afd161f3e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TheGupta2012
TheGupta2012 force-pushed the support-include-sources-in-loads branch 6 times, most recently from b58063e to ea15c60 Compare August 19, 2026 08:26
@TheGupta2012
TheGupta2012 requested a review from ryanhill1 August 19, 2026 09:08
load() resolved include statements relative to the file's own path; loads()
made no attempt at all, though the two entrypoints are documented as
equivalent. A program arriving as a string -- from object storage, or a
vendor API -- has no filesystem location to resolve relative includes
against, so the failure surfaced downstream as 'Unsupported / undeclared
QASM operation', naming the gate rather than the unresolved include.

Add an include_dir kwarg naming the directory includes resolve against, and
share one include walk between the file and string paths. For load() it is
tried before the directory of the including file.

Resolution stays opt-in. Without the kwarg loads() reads no files at all
and an unresolved include is passed through unchanged, which existing
programs rely on; resolving against the working directory by default would
instead let program text drive filesystem reads. With the kwarg, an include
the directory does not hold raises a ValidationError naming both.

Fixes #368

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TheGupta2012
TheGupta2012 force-pushed the support-include-sources-in-loads branch from ea15c60 to ecfa38e Compare August 19, 2026 14:35
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.

loads() does not resolve custom include statements, unlike load()

3 participants