Skip to content

Commit 36e39c9

Browse files
connernilsenmeta-codesync[bot]
authored andcommitted
Add test for except handler narrowing and unreachability regression
Summary: This diff adds a few more tests to make sure we don't regress on an unreachability case that was reported, in addition to a bug in the except handler narrowing that caused the invalid unreachability error to fire ___ Differential Revision: D122859658 fbshipit-source-id: 267141ec1f97ad1d20a46a41f4ed56e1c3ae5fe9
1 parent 21aa240 commit 36e39c9

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

‎pyrefly/lib/test/flow_branching.rs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3475,6 +3475,15 @@ def f() -> None:
34753475
// A test's value settles its branch only when declarations fix it. Each test below is typed
34763476
// `Literal[False]`, `None`, or `Never` only by inference from an unannotated assignment or by
34773477
// flow narrowing, and each is live at runtime.
3478+
//
3479+
// `flag_raised_in_try` is the case that reached us from the field, and it is the sharpest of
3480+
// them: its narrowed value is not merely imprecise but wrong. An `except` handler is given
3481+
// the state from before the `try` body, so `created` reads as `Literal[False]` even though
3482+
// the exception can only have come from after it was set — see
3483+
// `test_except_handler_narrows_to_state_before_try` in `narrow.rs`. Correcting that
3484+
// narrowing will not make this case redundant. It stands for a branch whose test the flow
3485+
// believes is settled while it is live at runtime, which this check must respect however
3486+
// the value was arrived at.
34783487
testcase!(
34793488
test_no_report_for_test_values_only_inference_settles,
34803489
r#"
@@ -3542,6 +3551,21 @@ class Dtype:
35423551
def chained_equality(d: Dtype) -> None:
35433552
if d == "int" and d != "int64":
35443553
print(6)
3554+
3555+
def may_raise() -> None:
3556+
raise RuntimeError
3557+
3558+
def cleanup() -> None:
3559+
pass
3560+
3561+
def flag_raised_in_try() -> None:
3562+
created = False
3563+
try:
3564+
created = True
3565+
may_raise()
3566+
except RuntimeError:
3567+
if created:
3568+
cleanup()
35453569
"#,
35463570
);
35473571

‎pyrefly/lib/test/narrow.rs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4560,3 +4560,34 @@ class Env:
45604560
self.domains = Container() # E: Assigning to attribute `domains` on an object with type `Never`
45614561
"#,
45624562
);
4563+
4564+
// An exception can be raised anywhere in a `try` body, including after an assignment, so a
4565+
// handler has to account for every state the body passes through. We give it only the state
4566+
// the body started from, which reports a variable as whatever it held on entry even where
4567+
// that value is impossible. The `assert_type` below records what we say today, not what is
4568+
// correct: `created` is `True` whenever `may_raise` is what raised, so the answer is `bool`.
4569+
//
4570+
// This reached us as a false `unreachable` on cleanup code guarded by such a flag. The
4571+
// `flag_raised_in_try` case of `test_no_report_for_test_values_only_inference_settles` in
4572+
// `flow_branching.rs` holds that symptom, which no longer reports, because a branch is
4573+
// settled only by declarations. This is the cause, and it is still here: the annotated
4574+
// assignment below is impossible at runtime and we accept it.
4575+
testcase!(
4576+
bug = "An `except` handler narrows to the state before the `try`, not across it",
4577+
test_except_handler_narrows_to_state_before_try,
4578+
r#"
4579+
from typing import Literal, assert_type
4580+
4581+
def may_raise() -> None:
4582+
raise RuntimeError
4583+
4584+
def f() -> None:
4585+
created = False
4586+
try:
4587+
created = True
4588+
may_raise()
4589+
except RuntimeError:
4590+
assert_type(created, Literal[False])
4591+
narrowed: Literal[False] = created
4592+
"#,
4593+
);

0 commit comments

Comments
 (0)