From 1eb8880667fdb21ad9387fa4388182dfefe55ddd Mon Sep 17 00:00:00 2001 From: bahtya Date: Fri, 10 Apr 2026 02:51:44 +0800 Subject: [PATCH] fix(match): enum literal narrowing order-sensitive in match statements When matching a union containing Literal[SomeEnum.VALUE] and other types, the order of match cases affected type narrowing results. Matching the enum literal first (before other types) failed to narrow the subject type, causing false positives with assert_never. Root cause: visit_value_pattern used narrow_type_by_identity_equality with '==' semantics, which has an overly-conservative enum ambiguity check for StrEnum/IntEnum (since they can compare equal to str/int). In match statements, this check is unnecessary when the other union members are unrelated types. Fix: For enum literal values without custom __eq__, bypass the identity equality narrowing and use conditional_types_with_intersection directly, which correctly narrows based on the literal value regardless of enum base type ambiguity. Fixes #21187 Signed-off-by: bahtya --- mypy/checkpattern.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index be6a78a021ba0..4a2df2b53d672 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -31,6 +31,7 @@ from mypy.subtypes import is_subtype from mypy.typeops import ( coerce_to_literal, + custom_special_method, make_simplified_union, try_getting_str_literals_from_type, tuple_fallback, @@ -39,6 +40,7 @@ AnyType, FunctionLike, Instance, + LiteralType, NoneType, ProperType, TupleType, @@ -205,6 +207,24 @@ def visit_value_pattern(self, o: ValuePattern) -> PatternType: current_type = self.type_context[-1] typ = self.chk.expr_checker.accept(o.expr) typ = coerce_to_literal(typ) + + # For enum literal types without custom __eq__, use conditional_types_with_intersection + # directly to avoid the overly-conservative enum ambiguity check in + # narrow_type_by_identity_equality. In match statements, value patterns narrow + # based on the exact value, so StrEnum/IntEnum ambiguity with str/int is not a + # concern when the other union members are unrelated types. + # Enums with custom __eq__ are excluded to preserve existing narrowing behavior. + proper_typ = get_proper_type(typ) + if ( + isinstance(proper_typ, LiteralType) + and proper_typ.is_enum_literal() + and not custom_special_method(proper_typ.fallback, "__eq__", check_all=False) + ): + narrowed_type, rest_type = self.chk.conditional_types_with_intersection( + current_type, [get_type_range(typ)], o, default=current_type + ) + return PatternType(narrowed_type, rest_type, {}) + node = TempNode(current_type) # Value patterns are essentially a syntactic sugar on top of `if x == Value`. # They should be treated equivalently.