Skip to content

Fix several bugs in the build-time AST rewrite#57

Merged
splch merged 8 commits into
mainfrom
fix-build-rewrite-bugs
Jun 5, 2026
Merged

Fix several bugs in the build-time AST rewrite#57
splch merged 8 commits into
mainfrom
fix-build-rewrite-bugs

Conversation

@splch
Copy link
Copy Markdown
Contributor

@splch splch commented May 1, 2026

Summary

Cluster of fixes to the @blqs.build AST rewrite. Most impactful:

  • del of native values didn't unbind them. The rewrite emitted del standard_targets (a helper tuple) instead of the user's variables, so del a for a plain int left a bound. New regression test confirms it now raises NameError after del.
  • blqs_cirq.build mutated the caller's BuildConfig. Each call prepended two more decorator specs to the user's config; now uses dataclasses.replace.
  • AST rewrite ran on every call. Now cached on first call; eliminates per-call tempfile / sys.modules leak and per-call rewrite cost.
  • while/for rewrites double-evaluated the test/iter expressions. Side-effecting conditions and one-shot iterables now match native Python.
  • _target_names crashed on obj.x = ... / del obj.x. Now returns None and the rewriter falls back to native semantics.
  • SupportsIsWritable._is_writeable_ spelled with extra 'e' — matched neither is_writable() nor Register._is_writable_.
  • Op.__eq__ had reversed isinstance arguments; switched to strict type check.
  • While.__str__ newline placement aligned with For/If.

Test plan

  • pytest blqs blqs_cirq — 279 passed (added one regression test for del actually unbinding)
  • black --check --line-length=100 blqs blqs_cirq
  • mypy --config-file=ci/mypy.ini blqs/blqs blqs_cirq/blqs_cirq
  • pylint --rcfile=ci/.pylintrc blqs_cirq/blqs_cirq/ blqs/blqs

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

5 similar comments
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

@splch splch force-pushed the fix-build-rewrite-bugs branch from c32c9aa to 41eb67d Compare May 1, 2026 20:40
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

We require contributors to sign our Contributor License Agreement, and we don't have one on file for your GitHub username. In order for us to review and merge your code, please contact @mjk or opensource@ionq.com to sign the CLA.

splch added 3 commits May 1, 2026 19:56
- `del` of non-blqs values now actually unbinds them (the rewrite was
  emitting `del standard_targets`, which deleted the helper tuple
  instead of the user's variables).

- `_target_names` now returns None for attribute/subscript targets
  instead of raising "this should not happen"; callers fall back to
  native Python for those statements.

- `while`-loop test expression is no longer re-evaluated after the
  loop. Tracking exit-reason with a flag avoids doubling side effects
  for non-trivial conditions.

- `for`-loop iter expression is bound to a local once instead of being
  evaluated up to four times. One-shot iterables (file handles,
  generators) now work correctly.

- AST rewrite, tempfile write, and module import are done once on the
  first call and cached, instead of per call. Eliminates the per-call
  tempfile / sys.modules leak and the per-call rewrite cost.

- `blqs_cirq.build` no longer mutates the caller's `BuildConfig`.
  Each call previously prepended two more decorator specs to
  `blqs_build_config.additional_decorator_specs`.

- `SupportsIsWritable` protocol method renamed from `_is_writeable_`
  to `_is_writable_` to match the `is_writable()` consumer (and the
  `Register` implementation).

- `Op.__eq__` uses strict `type(self) is type(other)` rather than the
  reversed-isinstance pattern, which leaked subclass equality when
  the subclass didn't override `__eq__`.

- `While.__str__` newline placement now matches `For.__str__` and
  `If.__str__`.

Adds a regression test that confirms native bindings are unbound
after `del` inside `@blqs.build`.
CI installs unpinned black, which has tightened rules since these files
were last formatted (trailing-comma in long signatures, no blank line
between an import block and the immediately-following block, spacing
around `# type: ignore`).
@mjk mjk force-pushed the fix-build-rewrite-bugs branch from 41eb67d to 7094325 Compare May 2, 2026 00:56
@cla-bot cla-bot Bot added the cla-signed label May 2, 2026
Copy link
Copy Markdown

@antalszava antalszava left a comment

Choose a reason for hiding this comment

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

Looks good overall 🎉 Left a few minor comments (without thorough background knowledge), mostly nitpicking.

Most of the fixed cases don't seem to have newly added test cases - would we like such to be added?

Comment thread blqs/blqs/build.py
Comment on lines +159 to +162
if "line_map" not in cache:
cache["line_map"] = _ast.construct_line_map(
cache["transformed_gast"], cache["transformed_source_code"]
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

we're caching the line map too - could it happen that we have two distinct exceptions for the same inputs (and so using the same line map through caching outputs invalid line nums for the 2nd exception)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no the line map only maps generated line numbers to original ones, so it's the same for every exception. i added a comment noting that :)

Comment thread blqs/blqs/op.py

def __eq__(self, other):
if not isinstance(self, type(other)):
if type(self) is not type(other):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Minor as unsure how much testing we'd like here): is there a test case for this?

Copy link
Copy Markdown
Contributor Author

@splch splch Jun 4, 2026

Choose a reason for hiding this comment

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

good idea! i added a subclass test that isn't equal to a base Op of the same name

Comment thread blqs/blqs/build.py Outdated
Comment on lines +280 to +282
# Bind the iterable to a local once. The original re-evaluated `iter` in each
# slot (is_iterable / For() / loop_vars() / the loop), double-running side
# effects on generators and the like.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if I understand correctly, we're binding here to copy the object, perhaps good to mention that. That'd clarify a bit further the mention of double-running side effects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agreed i reworded to "Evaluate the iterable once."

Comment thread blqs/blqs/build.py Outdated
Comment on lines +315 to +316
# loop. The original `if not test or is_readable` re-ran `test`, doubling side
# effects for non-trivial conditions.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The original if not test or is_readable re-ran test, doubling side # effects for non-trivial conditions.

This part of the comment won't make sense to readers of the proposed code because if not test or is_readable is not longer an existing condition. Best to rephrase.

Copy link
Copy Markdown
Contributor Author

@splch splch Jun 4, 2026

Choose a reason for hiding this comment

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

i rephrased it so it no longer quotes the removed condition, and added a test that the while test isn't re-evaluated after the loop :)

Comment thread blqs/blqs/build.py Outdated

target_names = self._target_names(node.targets)
if target_names is None:
# Non-Name targets (e.g. `obj.x = ...`); leave alone for native semantics.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Minor:) What does leave alone for native semantics refer to? maybe good to clarify

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i clarified it to "skip the rewrite, run natively" (same for del) and added attribute assign + del tests

Comment thread blqs/blqs/build.py Outdated
)

# Natively `del` each non-deletable value. The original emitted
# `del standard_targets`, deleting the helper tuple rather than the user's
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
# `del standard_targets`, deleting the helper tuple rather than the user's
# `del standard_targets` is deleting the helper tuple rather than the user's

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

reworded along those lines, kept it past tense since it's describing the old behavior :)

Comment thread blqs_cirq/blqs_cirq/build.py Outdated
Comment on lines +98 to +100
# Build the inner blqs config once. `dataclasses.replace` returns a fresh object
# rather than mutating the user's BuildConfig, and running `build_with_config`
# here (at decoration time) keeps the AST rewrite off the per-call path.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(minor:) 3 parts to this comment, perhaps good to split it and place each part right before each line of code it belongs to (e.g., build_with_config related on line 112). This should help future maintainability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

definitely i split it into 2, 1 per line! thank you :)

splch added 2 commits June 3, 2026 12:13
- Add tests: Op.__eq__ strict-type, for/while single-evaluation,
  obj.x = / del obj.x native fallback, blqs_cirq config not mutated
- Clarify the For/While/Assign/Delete comments and drop the stale
  reference to the removed while condition
- Note that the cached line map is shared across exceptions by design
- Split the blqs_cirq config comment onto the lines it describes
@splch
Copy link
Copy Markdown
Contributor Author

splch commented Jun 4, 2026

Thanks for the review @antalszava ! i added regression tests for each fix and tightened the comments per the inline notes

@splch splch merged commit 2b29a1c into main Jun 5, 2026
10 checks passed
@splch splch deleted the fix-build-rewrite-bugs branch June 5, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants