-
Notifications
You must be signed in to change notification settings - Fork 45
Python: skip Any_to_bool for bool-typed assert conditions (#1102) #1119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| def main(): | ||
| x: int = 5 | ||
| if isinstance(x, int): | ||
| assert x > 0 | ||
|
|
||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| def main(): | ||
| assert isinstance(5, int) | ||
|
|
||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| def main(): | ||
| assert isinstance([1], list) | ||
|
|
||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /- | ||
| Copyright Strata Contributors | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| -/ | ||
|
|
||
| import StrataTest.Languages.Python.TestExamples | ||
| import StrataTest.Util.TestDiagnostics | ||
|
|
||
| open StrataTest.Util | ||
| open Strata.Python (processPythonFile withPython) | ||
| open Strata.Parser (stringInputContext) | ||
| open Strata | ||
|
|
||
| namespace Strata.Python.Issue1102 | ||
|
|
||
| /-! Regression test for #1102: `isinstance(...)` inside `assert` must not | ||
| crash the Python-to-Laurel-to-Core translation pipeline. Before the fix | ||
| these programs failed in Laurel type checking with | ||
| "Impossible to unify (arrow Any bool) with (arrow bool $__ty…)". | ||
|
|
||
| Each fixture snapshots both the diagnostic `type` and the full rendered | ||
| message, so a silent-drop regression (translating the assert to a no-op) | ||
| or a message reword would also fail. Fixtures live in `Issue1102/`. | ||
|
|
||
| Note: compound assert conditions such as `isinstance(x, T) and x > 0` or | ||
| `not isinstance(x, T)` currently trip a *separate* StrataBug ("block | ||
| expression should have been lowered in a separate pass") that is | ||
| independent of #1102 and should be tracked in its own issue. -/ | ||
|
|
||
| private meta def fixtureDir : System.FilePath := | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This header comment is exactly the kind of documentation reviewers-of-the-future will thank you for — it explicitly names the adjacent bug ( |
||
| "StrataTestExtra/Languages/Python/Issue1102" | ||
|
|
||
| /-- Render a diagnostic as a single deterministic line: | ||
| "type @ line:colStart-colEnd :: message". -/ | ||
| private def renderDiag (d : Diagnostic) : String := | ||
| s!"{repr d.type} @ {d.start.line}:{d.start.column}-{d.ending.column} :: {d.message}" | ||
|
|
||
| /-- Load a fixture and print each diagnostic on its own line, prefixed | ||
| with the fixture name. Prefer this over ad-hoc prose filters: it asserts | ||
| structurally on `type` and keeps the full message in the snapshot. -/ | ||
| private def snapshotDiags (pythonCmd : System.FilePath) (pyFile : String) : IO Unit := do | ||
| let path := fixtureDir / pyFile | ||
| let source ← IO.FS.readFile path | ||
| let diags ← processPythonFile pythonCmd (stringInputContext pyFile source) | ||
| for d in diags do | ||
| IO.println s!"{pyFile}: {renderDiag d}" | ||
| -- Belt-and-braces structural check: any StrataBug is a regression. | ||
| if diags.any (fun d => d.type == Strata.DiagnosticType.StrataBug) then | ||
| throw <| .userError s!"{pyFile}: unexpected StrataBug diagnostic" | ||
|
Comment on lines
+39
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Proof / property-coverage suggestion. The correctness of this fix depends on one invariant of
This is straightforwardly checkable in Lean via pattern recognition on the returned /-- Reject any `Any_to_bool` applied to a reference to a bool-typed local.
Traverses the Laurel AST; conservatively over-approximates "bool-typed"
by consulting the passed-in `variableTypes`. -/
def containsAnyToBoolOfBoolVar (vt : List (String × String)) : StmtExprMd → Bool := …
#guard
let src := "def main():\n assert isinstance(5, int)\n\nmain()\n"
let ast ← … (run translator up to translateStmt on the Assert) …
!containsAnyToBoolOfBoolVar ast.variableTypes ast.stmtAlternatively — and I realise this is more involved — a small inductive lemma on the translator would nail it formally, roughly: theorem translateAssert_no_Any_to_bool_of_bool
(ctx : TranslationContext) (test msg : …)
(h : someWellFormednessHypothesis ctx) :
let (_, stmts) := translateStmt ctx (.Assert _ test msg)
∀ s ∈ stmts, ¬ containsAnyToBoolOfBoolVar ctx.variableTypes sI don't expect either proof in this PR (the translator is |
||
|
|
||
| -- `assert isinstance(5, int)` — the minimal fuzz reproduction. Before | ||
| -- the fix this raised a Laurel type error; after the fix the pipeline | ||
| -- completes and the verifier reports the assertion as unprovable | ||
| -- (expected, since `isinstance` is unmodelled). | ||
| /-- | ||
| info: isinstance_int.py: Strata.DiagnosticType.UserError @ 2:4-29 :: assertion could not be proved | ||
| -/ | ||
| #guard_msgs in | ||
| #eval withPython fun pythonCmd => snapshotDiags pythonCmd "isinstance_int.py" | ||
|
|
||
| -- `assert isinstance([1], list)` — the original fuzz_semantic_0004 shape. | ||
| /-- | ||
| info: isinstance_list.py: Strata.DiagnosticType.UserError @ 2:4-32 :: assertion could not be proved | ||
| -/ | ||
| #guard_msgs in | ||
| #eval withPython fun pythonCmd => snapshotDiags pythonCmd "isinstance_list.py" | ||
|
|
||
| -- `if isinstance(x, int): assert x > 0` — the non-hoisting .If path, | ||
| -- included to show the fix does not affect the path that already worked. | ||
| /-- | ||
| info: isinstance_if.py: Strata.DiagnosticType.UserError @ 4:8-20 :: assertion could not be proved | ||
| -/ | ||
| #guard_msgs in | ||
| #eval withPython fun pythonCmd => snapshotDiags pythonCmd "isinstance_if.py" | ||
|
Comment on lines
+52
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The three fixtures cover the main Hole-hoisting paths well. A few more that would make this suite robust to adjacent refactors:
Concrete proposal for (3): snapshot the current behaviour explicitly: -- `assert isinstance(x, int) and x > 0` — tracks the separate
-- "block expression should have been lowered" StrataBug mentioned in
-- the header. When this fixture starts producing a UserError instead,
-- that separate bug has been fixed and this snapshot needs updating.
/--
info: isinstance_and.py: Strata.DiagnosticType.StrataBug @ … :: …
-/
#guard_msgs in
#eval withPython fun pythonCmd => snapshotDiags pythonCmd "isinstance_and.py"(For this one, you'd also want to drop the "any StrataBug is a regression" guard in |
||
|
|
||
| end Strata.Python.Issue1102 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stylistic simplification:
alreadyBoolis used in exactly oneif. You can push theAny_to_booldecision directly into the match arms and drop both the flag and the separate conditional. This is shorter, and the "bool-typed reference needs no coercion" invariant then lives at the site that establishes it rather than being re-asserted three lines later:Keeps the same behaviour — happy to leave as-is if you prefer the explicit flag for future extensibility (e.g. a second hoisting path that also produces a bool-typed ref), but in that case the comment should say so.