Fix several bugs in the build-time AST rewrite#57
Conversation
|
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
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
c32c9aa to
41eb67d
Compare
|
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. |
- `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`).
41eb67d to
7094325
Compare
antalszava
left a comment
There was a problem hiding this comment.
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?
| if "line_map" not in cache: | ||
| cache["line_map"] = _ast.construct_line_map( | ||
| cache["transformed_gast"], cache["transformed_source_code"] | ||
| ) |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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 :)
|
|
||
| def __eq__(self, other): | ||
| if not isinstance(self, type(other)): | ||
| if type(self) is not type(other): |
There was a problem hiding this comment.
(Minor as unsure how much testing we'd like here): is there a test case for this?
There was a problem hiding this comment.
good idea! i added a subclass test that isn't equal to a base Op of the same name
| # 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
agreed i reworded to "Evaluate the iterable once."
| # loop. The original `if not test or is_readable` re-ran `test`, doubling side | ||
| # effects for non-trivial conditions. |
There was a problem hiding this comment.
The original
if not test or is_readablere-rantest, 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.
There was a problem hiding this comment.
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 :)
|
|
||
| target_names = self._target_names(node.targets) | ||
| if target_names is None: | ||
| # Non-Name targets (e.g. `obj.x = ...`); leave alone for native semantics. |
There was a problem hiding this comment.
(Minor:) What does leave alone for native semantics refer to? maybe good to clarify
There was a problem hiding this comment.
i clarified it to "skip the rewrite, run natively" (same for del) and added attribute assign + del tests
| ) | ||
|
|
||
| # Natively `del` each non-deletable value. The original emitted | ||
| # `del standard_targets`, deleting the helper tuple rather than the user's |
There was a problem hiding this comment.
| # `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 |
There was a problem hiding this comment.
reworded along those lines, kept it past tense since it's describing the old behavior :)
| # 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. |
There was a problem hiding this comment.
(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.
There was a problem hiding this comment.
definitely i split it into 2, 1 per line! thank you :)
- 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
|
Thanks for the review @antalszava ! i added regression tests for each fix and tightened the comments per the inline notes |
Summary
Cluster of fixes to the
@blqs.buildAST rewrite. Most impactful:delof native values didn't unbind them. The rewrite emitteddel standard_targets(a helper tuple) instead of the user's variables, sodel afor a plainintleftabound. New regression test confirms it now raisesNameErrorafterdel.blqs_cirq.buildmutated the caller'sBuildConfig. Each call prepended two more decorator specs to the user's config; now usesdataclasses.replace.sys.modulesleak and per-call rewrite cost.while/forrewrites double-evaluated the test/iter expressions. Side-effecting conditions and one-shot iterables now match native Python._target_namescrashed onobj.x = .../del obj.x. Now returnsNoneand the rewriter falls back to native semantics.SupportsIsWritable._is_writeable_spelled with extra 'e' — matched neitheris_writable()norRegister._is_writable_.Op.__eq__had reversedisinstancearguments; switched to strict type check.While.__str__newline placement aligned withFor/If.Test plan
pytest blqs blqs_cirq— 279 passed (added one regression test fordelactually unbinding)black --check --line-length=100 blqs blqs_cirqmypy --config-file=ci/mypy.ini blqs/blqs blqs_cirq/blqs_cirqpylint --rcfile=ci/.pylintrc blqs_cirq/blqs_cirq/ blqs/blqs