@@ -703,47 +703,47 @@ class FlipFlopStr:
703
703
def mutate(self) -> None:
704
704
self.state = "state-2" if self.state == "state-1" else "state-1"
705
705
706
- def test1(switch: FlipFlopEnum) -> None:
706
+
707
+ def test1(switch: FlipFlopStr) -> None:
707
708
# Naively, we might assume the 'assert' here would narrow the type to
708
- # Literal[State.A ]. However, doing this ends up breaking a fair number of real-world
709
+ # Literal["state-1" ]. However, doing this ends up breaking a fair number of real-world
709
710
# code (usually test cases) that looks similar to this function: e.g. checks
710
711
# to make sure a field was mutated to some particular value.
711
712
#
712
713
# And since mypy can't really reason about state mutation, we take a conservative
713
714
# approach and avoid narrowing anything here.
714
715
715
- assert switch.state == State.A
716
- reveal_type(switch.state) # N: Revealed type is "__main__.State "
716
+ assert switch.state == "state-1"
717
+ reveal_type(switch.state) # N: Revealed type is "builtins.str "
717
718
718
719
switch.mutate()
719
720
720
- assert switch.state == State.B
721
- reveal_type(switch.state) # N: Revealed type is "__main__.State "
721
+ assert switch.state == "state-2"
722
+ reveal_type(switch.state) # N: Revealed type is "builtins.str "
722
723
723
724
def test2(switch: FlipFlopEnum) -> None:
724
- # So strictly speaking, we ought to do the same thing with 'is' comparisons
725
- # for the same reasons as above. But in practice, not too many people seem to
726
- # know that doing 'some_enum is MyEnum.Value' is idiomatic. So in practice,
727
- # this is probably good enough for now.
725
+ # This is the same thing as 'test1', except we use enums, which we allow to be narrowed
726
+ # to literals.
728
727
729
- assert switch.state is State.A
728
+ assert switch.state == State.A
730
729
reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A]"
731
730
732
731
switch.mutate()
733
732
734
- assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]")
733
+ assert switch.state == State.B # E: Non-overlapping equality check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]")
735
734
reveal_type(switch.state) # E: Statement is unreachable
736
735
737
- def test3(switch: FlipFlopStr) -> None:
738
- # This is the same thing as 'test1', except we try using str literals.
736
+ def test3(switch: FlipFlopEnum) -> None:
737
+ # Same thing, but using 'is' comparisons. Previously mypy's behaviour differed
738
+ # here, narrowing when using 'is', but not when using '=='.
739
739
740
- assert switch.state == "state-1"
741
- reveal_type(switch.state) # N: Revealed type is "builtins.str "
740
+ assert switch.state is State.A
741
+ reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A] "
742
742
743
743
switch.mutate()
744
744
745
- assert switch.state == "state-2"
746
- reveal_type(switch.state) # N: Revealed type is "builtins.str"
745
+ assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]")
746
+ reveal_type(switch.state) # E: Statement is unreachable
747
747
[builtins fixtures/primitives.pyi]
748
748
749
749
[case testNarrowingEqualityRequiresExplicitStrLiteral]
@@ -795,6 +795,7 @@ reveal_type(x_union) # N: Revealed type is "Union[Literal['A'], Literal['B'
795
795
796
796
[case testNarrowingEqualityRequiresExplicitEnumLiteral]
797
797
# flags: --strict-optional
798
+ from typing import Union
798
799
from typing_extensions import Literal, Final
799
800
from enum import Enum
800
801
@@ -805,26 +806,34 @@ class Foo(Enum):
805
806
A_final: Final = Foo.A
806
807
A_literal: Literal[Foo.A]
807
808
808
- # See comments in testNarrowingEqualityRequiresExplicitStrLiteral and
809
- # testNarrowingEqualityFlipFlop for more on why we can't narrow here.
809
+ # Note this is unlike testNarrowingEqualityRequiresExplicitStrLiteral
810
+ # See also testNarrowingEqualityFlipFlop
810
811
x1: Foo
811
812
if x1 == Foo.A:
812
- reveal_type(x1) # N: Revealed type is "__main__.Foo"
813
+ reveal_type(x1) # N: Revealed type is "Literal[ __main__.Foo.A] "
813
814
else:
814
- reveal_type(x1) # N: Revealed type is "__main__.Foo"
815
+ reveal_type(x1) # N: Revealed type is "Literal[ __main__.Foo.B] "
815
816
816
817
x2: Foo
817
818
if x2 == A_final:
818
- reveal_type(x2) # N: Revealed type is "__main__.Foo"
819
+ reveal_type(x2) # N: Revealed type is "Literal[ __main__.Foo.A] "
819
820
else:
820
- reveal_type(x2) # N: Revealed type is "__main__.Foo"
821
+ reveal_type(x2) # N: Revealed type is "Literal[ __main__.Foo.B] "
821
822
822
823
# But we let this narrow since there's an explicit literal in the RHS.
823
824
x3: Foo
824
825
if x3 == A_literal:
825
826
reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.A]"
826
827
else:
827
828
reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.B]"
829
+
830
+
831
+ class SingletonFoo(Enum):
832
+ A = "A"
833
+
834
+ def bar(x: Union[SingletonFoo, Foo], y: SingletonFoo) -> None:
835
+ if x == y:
836
+ reveal_type(x) # N: Revealed type is "Literal[__main__.SingletonFoo.A]"
828
837
[builtins fixtures/primitives.pyi]
829
838
830
839
[case testNarrowingEqualityDisabledForCustomEquality]
0 commit comments