visitor.py LOC cleanup - #348
Conversation
Argus reviewAuto-review is off for this repo. Tick the box below to run a review on this PR.
Estimated cost
Tip: you can also comment |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
|
👋 Hey there! It looks like the changelog might need an update. Please take a moment to edit the
|
TheGupta2012
left a comment
There was a problem hiding this comment.
Thanks @micpap25 for working on this! Can you please resolve the comments and continue the work for refactoring the visitor? This seems like a good start!
Function is simple but has unintuitive logic; the "return None" approach used here should be removed if we decide not to use this refactoring.
ryanhill1
left a comment
There was a problem hiding this comment.
Thanks for picking up #188 — consolidating the repeated if self._check_only: return [] tail into a decorator is the right instinct, and 26 net lines out of visitor.py is real progress. The _handle_function_init_expression inlining is behaviour-preserving as far as I can tell, and mypy, black and isort are all clean.
The decorator itself has two bugs that need fixing before this can go further, and once they're fixed there's a design question about early returns that I think decides whether this approach works at all.
Test suite on the branch as pushed: 539 failed, 109 passed (pytest tests --deselect tests/cli).
What I ran
Patching B1 alone → 6 failures left. Patching B1 + B2 → 648 passed, 3 skipped, matching main. So B1 and B2 account for the entire breakage, and the rest of the refactor doesn't regress anything the suite covers.
Reordering the two displaced # pylint: disable comments (B3) drops pylint from 11 errors to 1. main scores 10.00/10 on visitor.py, so all 11 are new here.
The design question — R1
The decorator wraps the whole function, so it now also swallows early returns that previously bypassed the _check_only gate. Three are reachable, all on openpulse paths. Demonstrated with a pulse program:
m = loads(pulse_program_with_defcal_measure)
m.validate()
len(m._unrolled_ast.statements)
# main: 4 (QubitDeclaration, CalibrationGrammarDeclaration, CalibrationStatement, QuantumMeasurementStatement)
# PR: 3 (QuantumMeasurementStatement dropped)No test covers this, which is why the suite is green once B1/B2 are fixed. It may well be that returning [] there is more correct — but #188 says explicitly that behavioural changes should be proposed separately, so this needs to be either a deliberate, tested decision or excluded from the refactor. Details inline at R1.
Not blocking, worth knowing
- Merge conflict with #346. That PR modifies
_handle_function_init_expression; this one deletes it. Whichever lands second will need a rebase. - No CHANGELOG entry, and you've flagged tests as outstanding yourself — #188 asks for coverage on refactors specifically to catch things like R1.
Labels: B blocking, R semantics, D dead code, N nits.
| def wrapper(self, *args, **kwargs): | ||
| """Wrapper that intercepts the return value and replaces it with an empty list.""" | ||
| result = func(self, *args, **kwargs) | ||
| if self.check_only: |
There was a problem hiding this comment.
B1 — blocking: wrong attribute name, breaks 539 of 648 tests.
The attribute is self._check_only (set at L144); there is no check_only property on QasmVisitor.
src/pyqasm/visitor.py:3260: in visit_statement
E AttributeError: 'QasmVisitor' object has no attribute 'check_only'
src/pyqasm/visitor.py:95: AttributeError
Every decorated method raises on first call. s/self.check_only/self._check_only/ alone takes the suite from 539 failed to 6 failed.
| # each element in the list of the values | ||
| # should be of const int type and no duplicates should be present | ||
|
|
||
| @semantic_check_gate |
There was a problem hiding this comment.
B2 — blocking: decorator applied to a nested function, breaks all 6 switch tests.
_evaluate_case(statements) is a closure, not a method. The wrapper's first positional parameter is named self, so it binds to the statements list:
tests/qasm3/test_switch.py::test_switch
E AttributeError: 'list' object has no attribute '_check_only'
src/pyqasm/visitor.py:95: AttributeError
(That traceback is after B1 is fixed — this is an independent bug.) The closure already reads self._check_only from the enclosing scope, so the decorator buys nothing here. Removing it is the fix; with B1 and B2 both fixed the suite is 648 passed / 3 skipped.
| qubit_node = self._module._qubit_depths[(qubit_name, qubit_id)] | ||
| qubit_node.depth = max_involved_depth | ||
|
|
||
| @semantic_check_gate |
There was a problem hiding this comment.
B3 — blocking: decorator displaces a block-scoped # pylint: disable, adding 10 pylint errors.
A standalone # pylint: disable= comment is block-scoped. Inserting the decorator above it detaches it from the class body scope, so the suppression stops applying:
visitor.py:1203:4: R0914: Too many local variables (17/15)
visitor.py:1437:4: R0914: Too many local variables (20/15)
visitor.py:1950:4: R0912: Too many branches (25/12)
... 10 total
main scores 10.00/10 on this file. Moving the decorator below the comment at both sites (here and L1714) restores it — verified, drops pylint to a single remaining error (B4).
|
|
||
| return statements | ||
|
|
||
| @semantic_check_gate |
There was a problem hiding this comment.
B3 (cont.) — same displacement here, for # pylint: disable=too-many-branches, too-many-statements, too-many-locals.
| result = func(self, *args, **kwargs) | ||
| if self.check_only: | ||
| return [] | ||
| else: |
There was a problem hiding this comment.
B4 — blocking: pylint failure.
visitor.py:95:8: R1705: Unnecessary "else" after "return" (no-else-return)
if self._check_only:
return []
return result| qasm3_ast.CalibrationGrammarDeclaration: self._visit_calibration_grammar_declaration, | ||
| } | ||
|
|
||
| @semantic_check_gate |
There was a problem hiding this comment.
N1 — semantic_check_gate reads as "quantum gate" in this file. Something like skip_if_check_only or _check_only_returns_empty says what it does without the collision.
N2 — worth type-hinting func: Callable[..., Any] while you're here; #346 is adding hints across these modules and this would land unhinted.
| ) | ||
| self._handle_extern_function_cleanup(statements, statement) | ||
| function_name = statement.init_expression.name.name | ||
| if function_name in FUNCTION_MAP and isinstance(init_value, (float, int)): |
There was a problem hiding this comment.
N3 — the inlining looks behaviour-preserving. The old helper(...) or statement.init_expression fallback was equivalent because FloatLiteral is a truthy dataclass, so nothing was relying on the or.
Only note: this is now duplicated three times (here, L1943, L2113). If it grows a fourth case, a small _maybe_fold_function_call(expr, value) helper would be worth it — but three identical two-liners is arguably clearer than the indirection you removed, so no objection.
|
|
||
| Returns: | ||
| None | ||
| list[qasm3_ast.QuantumReset] - A list of unrolled resets. |
There was a problem hiding this comment.
N4 — the rest of the file uses Google style with a colon: list[qasm3_ast.QuantumReset]: A list of unrolled resets. This one uses a dash.
| base_type: The declared type (DurationType or StretchType) | ||
| rvalue: The initializer or assigned value | ||
| statement: The AST statement node. | ||
| statement_type: The expected AST node type. |
There was a problem hiding this comment.
N5 — pre-existing, not yours, but you're editing this block: statement_type isn't a parameter. It's derived inside the function at L128 (statement_type = type(statement)). Worth deleting the line while you're here.
| rvalue: The initializer or assigned value | ||
| statement: The AST statement node. | ||
| statement_type: The expected AST node type. | ||
| base_type: The declared type, function does nothing if not DurationType or StretchType |
There was a problem hiding this comment.
N6 — this is the one line in the hunk that didn't get a trailing period, and the other three did.
Summary of changes
Start on #188
First commit fixes some typos and starts a validation function; long-term goal is to aggregate more of the validation process into helper functions.
Still needs tests!