[red-knot] Fix Leaking Narrowing Constraint in ast::ExprIf (#14590)

## Summary

Closes #14588


```py
x: Literal[42, "hello"] = 42 if bool_instance() else "hello"
reveal_type(x)  # revealed: Literal[42] | Literal["hello"]

_ = ... if isinstance(x, str) else ...

# The `isinstance` test incorrectly narrows the type of `x`.
# As a result, `x` is revealed as Literal["hello"], but it should remain Literal[42, "hello"].
reveal_type(x)  # revealed: Literal["hello"]
```

## Test Plan
mdtest included!

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
cake-monotone 2024-11-26 03:36:37 +09:00 committed by GitHub
parent c606bf014e
commit f98eebdbab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 20 additions and 1 deletions

View file

@ -22,3 +22,22 @@ reveal_type(1 if None else 2) # revealed: Literal[2]
reveal_type(1 if "" else 2) # revealed: Literal[2] reveal_type(1 if "" else 2) # revealed: Literal[2]
reveal_type(1 if 0 else 2) # revealed: Literal[2] reveal_type(1 if 0 else 2) # revealed: Literal[2]
``` ```
## Leaked Narrowing Constraint
(issue #14588)
The test inside an if expression should not affect code outside of the block.
```py
def bool_instance() -> bool:
return True
x: Literal[42, "hello"] = 42 if bool_instance() else "hello"
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
_ = ... if isinstance(x, str) else ...
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
```

View file

@ -1189,8 +1189,8 @@ where
// AST inspection, so we can't simplify here, need to record test expression for // AST inspection, so we can't simplify here, need to record test expression for
// later checking) // later checking)
self.visit_expr(test); self.visit_expr(test);
let constraint = self.record_expression_constraint(test);
let pre_if = self.flow_snapshot(); let pre_if = self.flow_snapshot();
let constraint = self.record_expression_constraint(test);
self.visit_expr(body); self.visit_expr(body);
let post_body = self.flow_snapshot(); let post_body = self.flow_snapshot();
self.flow_restore(pre_if); self.flow_restore(pre_if);