fix Regression when using type alias on Enum #3042#3044
fix Regression when using type alias on Enum #3042#3044asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a regression in narrowing for is/== comparisons where a name bound to an enum member literal (e.g. a = E.X) stopped behaving like the literal enum member during narrowing.
Changes:
- Added a narrowing-only RHS inference path (
narrow_expr_infer) that avoids using the promoted view forExpr::Name. - Updated multiple narrowing operations (
is,is not,==,!=,isinstance,issubclass, etc.) to use the new RHS inference. - Added a regression test covering
is notnarrowing against a module-level enum-member alias.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
pyrefly/lib/alt/narrow.rs |
Introduces narrow_expr_infer and routes RHS inference for many narrowing ops through it to improve enum-literal alias precision. |
pyrefly/lib/test/narrow.rs |
Adds a regression testcase ensuring is not a correctly narrows when a aliases an enum member literal. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Expr::Name(name) if !Ast::is_synthesized_empty_name(name) => self | ||
| .get(&Key::BoundName(ShortIdentifier::expr_name(name))) | ||
| .arc_clone_ty(), |
There was a problem hiding this comment.
narrow_expr_infer duplicates the Expr::Name inference logic from expr_infer_type_info_with_hint, but it skips other name-specific handling (e.g., the SelfTypeLiteral interception used for typing.Self, plus any tracing/deprecation hooks that run in the normal expression path). To avoid behavioral drift for name expressions that are special-cased elsewhere, consider reusing the existing expression inference path with an explicit “no implicit-literal promotion” option, or at least mirroring the SelfTypeLiteral interception before falling back to Key::BoundName.
| Expr::Name(name) if !Ast::is_synthesized_empty_name(name) => self | |
| .get(&Key::BoundName(ShortIdentifier::expr_name(name))) | |
| .arc_clone_ty(), | |
| Expr::Name(name) if !Ast::is_synthesized_empty_name(name) => { | |
| let bound_name_ty = self | |
| .get(&Key::BoundName(ShortIdentifier::expr_name(name))) | |
| .arc_clone_ty(); | |
| match self.expr_infer(expr, errors) { | |
| Type::SelfTypeLiteral(_) as inferred_name_ty => inferred_name_ty, | |
| _ => bound_name_ty, | |
| } | |
| } |
|
|
||
| def f1(x: Literal[E.X, E.Y]): | ||
| if x is not a: | ||
| assert_type(x, Literal[E.Y]) |
There was a problem hiding this comment.
This adds coverage for is not narrowing against a module-level alias to an enum literal, but the implementation change also affects ==/!= narrowing (switching those RHS inferences to narrow_expr_infer). Consider extending this testcase (or adding another) to cover x == a and x != a to prevent regressions in the equality paths.
| assert_type(x, Literal[E.Y]) | |
| assert_type(x, Literal[E.Y]) | |
| if x == a: | |
| assert_type(x, Literal[E.X]) | |
| if x != a: | |
| assert_type(x, Literal[E.Y]) |
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
|
do we understand which commit caused the regression? and why? I think that's an important step in understanding if this fix is the correct one or not |
|
I commented on the original issue - the change was caused by us treating (lower-cased) globals as mutable That was intentional, and I think we should consider our options for solutions carefully |
Summary
Fixes #3042
narrowing now infers comparison RHS names from their underlying binding instead of the function-scope promoted view, so a module alias like a = E.X still behaves as the literal enum member during is/== narrowing.
keeps the existing promotion behavior out of the general expression path and only makes narrowing more precise where it matters.
Test Plan
add test