From be1071e454e5f012f8998e91a868e31c83efdad3 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 24 Jan 2025 14:57:09 +0900 Subject: [PATCH 01/40] Start type checking unreachable code Except things important enough we marked them before, which include: - `if not typing.TYPE_CHECKING` - platform checks that do not match --- mypy/checker.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 04a286beef5e..d165c32a03a5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -459,15 +459,15 @@ def check_first_pass(self) -> None: ) with self.tscope.module_scope(self.tree.fullname): with self.enter_partial_types(), self.binder.top_frame_context(): + reported_unreachable = False for d in self.tree.defs: - if self.binder.is_unreachable(): + if not reported_unreachable and self.binder.is_unreachable(): if not self.should_report_unreachable_issues(): - break - if not self.is_noop_for_reachability(d): + reported_unreachable = True + elif not self.is_noop_for_reachability(d): self.msg.unreachable_statement(d) - break - else: - self.accept(d) + reported_unreachable = True + self.accept(d) assert not self.current_node_deferred @@ -3044,18 +3044,18 @@ def visit_block(self, b: Block) -> None: if b.is_unreachable: # This block was marked as being unreachable during semantic analysis. # It turns out any blocks marked in this way are *intentionally* marked - # as unreachable -- so we don't display an error. + # as unreachable -- so we don't display an error nor run type checking. self.binder.unreachable() return + reported_unreachable = False for s in b.body: - if self.binder.is_unreachable(): + if not reported_unreachable and self.binder.is_unreachable(): if not self.should_report_unreachable_issues(): - break - if not self.is_noop_for_reachability(s): + reported_unreachable = True + elif not self.is_noop_for_reachability(s): self.msg.unreachable_statement(s) - break - else: - self.accept(s) + reported_unreachable = True + self.accept(s) def should_report_unreachable_issues(self) -> bool: return ( From d9969181b7c388b72aade987b82bf60242c620d7 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 15:41:21 +0900 Subject: [PATCH 02/40] Pass self-check --- mypy/checkexpr.py | 4 +++- mypyc/ir/pprint.py | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4078d447dab8..142a5ce3af2f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3548,7 +3548,9 @@ def visit_op_expr(self, e: OpExpr) -> Type: allow_reverse=False, ) else: - assert_never(use_reverse) + # TODO: fix this bug with enum narrowing + # somehow the `use_reverse is UseReverse.ALWAYS` doesn't narrow. + assert_never(use_reverse) # type: ignore[arg-type] e.method_type = method_type return result else: diff --git a/mypyc/ir/pprint.py b/mypyc/ir/pprint.py index ac0e791290ab..9bbc5679d248 100644 --- a/mypyc/ir/pprint.py +++ b/mypyc/ir/pprint.py @@ -222,15 +222,16 @@ def visit_call_c(self, op: CallC) -> str: def visit_primitive_op(self, op: PrimitiveOp) -> str: args = [] arg_index = 0 - type_arg_index = 0 + # type_arg_index = 0 for arg_type in zip(op.desc.arg_types): if arg_type: args.append(self.format("%r", op.args[arg_index])) arg_index += 1 else: - assert op.type_args - args.append(self.format("%r", op.type_args[type_arg_index])) - type_arg_index += 1 + assert False + # assert op.type_args + # args.append(self.format("%r", op.type_args[type_arg_index])) + # type_arg_index += 1 args_str = ", ".join(args) if op.is_void: From 07de5bfca7f26fe82a00f8d362de80556f7481a3 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 15:58:53 +0900 Subject: [PATCH 03/40] Hacky fix to narrow unreachable literals --- mypy/binder.py | 3 +++ mypy/checker.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 384bdca728b2..cf3b626d5e6b 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -22,6 +22,7 @@ TypeOfAny, TypeType, TypeVarType, + UninhabitedType, UnionType, UnpackType, find_unpack_in_list, @@ -151,6 +152,8 @@ def push_frame(self, conditional_frame: bool = False) -> Frame: return f def _put(self, key: Key, type: Type, from_assignment: bool, index: int = -1) -> None: + if isinstance(type, UninhabitedType): + self.frames[index].unreachable = True self.frames[index].types[key] = CurrentType(type, from_assignment) def _get(self, key: Key, index: int = -1) -> CurrentType | None: diff --git a/mypy/checker.py b/mypy/checker.py index d165c32a03a5..7c1ee28091d8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6289,8 +6289,8 @@ def find_isinstance_check_helper( if_type = true_only(vartype) else_type = false_only(vartype) - if_map = {node: if_type} if not isinstance(if_type, UninhabitedType) else None - else_map = {node: else_type} if not isinstance(else_type, UninhabitedType) else None + if_map = {node: if_type} # if not isinstance(if_type, UninhabitedType) else None + else_map = {node: else_type} # if not isinstance(else_type, UninhabitedType) else None return if_map, else_map def comparison_type_narrowing_helper(self, node: ComparisonExpr) -> tuple[TypeMap, TypeMap]: From bac268b531833508e008eff08fb0d90195c1835a Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 16:26:13 +0900 Subject: [PATCH 04/40] 20% of tests --- mypy/binder.py | 2 +- mypy/checkexpr.py | 17 ++++++++++++----- test-data/unit/check-functions.test | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index cf3b626d5e6b..622284416702 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -152,7 +152,7 @@ def push_frame(self, conditional_frame: bool = False) -> Frame: return f def _put(self, key: Key, type: Type, from_assignment: bool, index: int = -1) -> None: - if isinstance(type, UninhabitedType): + if isinstance(get_proper_type(type), UninhabitedType): self.frames[index].unreachable = True self.frames[index].types[key] = CurrentType(type, from_assignment) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 142a5ce3af2f..58175f945e63 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4275,11 +4275,18 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: elif e.op == "or": left_map, right_map = self.chk.find_isinstance_check(e.left) + left_impossible = left_map is None or any( + isinstance(get_proper_type(v), UninhabitedType) for v in left_map.values() + ) + right_impossible = right_map is None or any( + isinstance(get_proper_type(v), UninhabitedType) for v in right_map.values() + ) + # If left_map is None then we know mypy considers the left expression # to be redundant. if ( codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes - and left_map is None + and left_impossible # don't report an error if it's intentional and not e.right_always ): @@ -4287,7 +4294,7 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: if ( self.chk.should_report_unreachable_issues() - and right_map is None + and right_impossible # don't report an error if it's intentional and not e.right_unreachable ): @@ -4295,14 +4302,14 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: right_type = self.analyze_cond_branch(right_map, e.right, expanded_left_type) - if left_map is None and right_map is None: + if left_impossible and right_impossible: return UninhabitedType() - if right_map is None: + if right_impossible: # The boolean expression is statically known to be the left value assert left_map is not None return left_type - if left_map is None: + if left_impossible: # The boolean expression is statically known to be the right value assert right_map is not None return right_type diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index a0a6e9d60920..8494060ff909 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1532,7 +1532,7 @@ def bar() -> None: if False: foo = 1 else: - def foo(obj): ... + def foo(obj): ... # E: Incompatible redefinition (redefinition with type "Callable[[Any], Any]", original type "int") def baz() -> None: if False: From 2da4b7c2eb46dc824ab6eed8fc221f3e021c5dc4 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 16:49:56 +0900 Subject: [PATCH 05/40] 25% of tests --- test-data/unit/check-callable.test | 35 ++++++++++++++++++++---------- test-data/unit/check-literal.test | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 39e6c4fa3ff1..dd2bb82aa1c5 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -1,20 +1,22 @@ [case testCallableDef] +# flags: --warn-unreachable def f() -> None: pass if callable(f): f() else: - f += 5 + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/callable.pyi] [case testCallableLambda] +# flags: --warn-unreachable f = lambda: None if callable(f): f() else: - f += 5 + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/callable.pyi] @@ -194,6 +196,7 @@ else: [builtins fixtures/callable.pyi] [case testCallableCallableClasses] +# flags: --warn-unreachable from typing import Union @@ -214,7 +217,7 @@ if callable(a): 5 + 'test' # E: Unsupported operand types for + ("int" and "str") if not callable(b): - 5 + 'test' + _ = "unreachable" # E: Statement is unreachable if callable(c): reveal_type(c) # N: Revealed type is "__main__.B" @@ -224,6 +227,7 @@ else: [builtins fixtures/callable.pyi] [case testDecoratedCallMethods] +# flags: --warn-unreachable from typing import Any, Callable, Union, TypeVar F = TypeVar('F', bound=Callable) @@ -258,11 +262,11 @@ s4: Some4 if callable(s1): 1 + 'a' # E: Unsupported operand types for + ("int" and "str") else: - 2 + 'b' + _ = "unreachable" # E: Statement is unreachable if callable(s2): 1 + 'a' # E: Unsupported operand types for + ("int" and "str") else: - 2 + 'b' + _ = "unreachable" # E: Statement is unreachable if callable(s3): 1 + 'a' # E: Unsupported operand types for + ("int" and "str") else: @@ -310,11 +314,14 @@ def f(t: T) -> None: # N: Revealed type is "builtins.int" \ # N: Revealed type is "builtins.str" else: - reveal_type(t) # N: Revealed type is "builtins.int" # N: Revealed type is "builtins.str" + reveal_type(t) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "def () -> builtins.int" \ + # N: Revealed type is "builtins.str" [builtins fixtures/callable.pyi] [case testCallableTypeVarBound] +# flags: --warn-unreachable from typing import TypeVar @@ -329,11 +336,12 @@ def f(t: T) -> str: if callable(t): return t() else: - return 5 + return t() # E: Statement is unreachable [builtins fixtures/callable.pyi] [case testCallableTypeType] +# flags: --warn-unreachable from typing import Type @@ -347,11 +355,12 @@ def f(t: T) -> A: if callable(t): return t() else: - return 5 + return t() # E: Statement is unreachable [builtins fixtures/callable.pyi] [case testCallableTypeUnion] +# flags: --warn-unreachable from abc import ABCMeta, abstractmethod from typing import Type, Union @@ -371,11 +380,12 @@ if callable(x): # Abstract classes raise an error when called, but are indeed `callable` pass else: - 'test' + 5 + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/callable.pyi] [case testCallableUnionOfTypes] +# flags: --warn-unreachable from abc import ABCMeta, abstractmethod from typing import Type, Union @@ -395,7 +405,7 @@ if callable(x): # Abstract classes raise an error when called, but are indeed `callable` pass else: - 'test' + 5 + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/callable.pyi] @@ -501,10 +511,11 @@ if callable(): # E: Missing positional argument "x" in call to "callable" [builtins fixtures/callable.pyi] [case testCallableWithNoneArgs] - +# flags: --warn-unreachable fn = None if callable(fn): - fn() + _ = "unreachable" # E: Statement is unreachable + fn() # E: "None" not callable [builtins fixtures/callable.pyi] diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 88c02f70488c..f73270e584de 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2837,7 +2837,7 @@ w: Union[Truth, AlsoTruth] if w: reveal_type(w) # N: Revealed type is "Union[__main__.Truth, __main__.AlsoTruth]" else: - reveal_type(w) # E: Statement is unreachable + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/bool.pyi] From ba9dab2018678d8c1296da3f61badf733e5a5bee Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 17:12:32 +0900 Subject: [PATCH 06/40] 30% of tests --- test-data/unit/check-enum.test | 56 ++++++++++----- test-data/unit/check-isinstance.test | 15 ++-- test-data/unit/check-narrowing.test | 102 +++++++++++++++++---------- 3 files changed, 114 insertions(+), 59 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index a3abf53e29ac..3130ccc27de0 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -871,6 +871,7 @@ main:2: note: Revealed type is "Literal[1]?" main:2: note: Revealed type is "Literal['foo']?" [case testEnumReachabilityChecksBasic] +# flags: --warn-unreachable from enum import Enum from typing import Literal @@ -887,7 +888,9 @@ elif x is Foo.B: elif x is Foo.C: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(x) # No output here: this branch is unreachable + # TODO: this should narrow to Never + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.C]" reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" if Foo.A is x: @@ -897,7 +900,8 @@ elif Foo.B is x: elif Foo.C is x: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(x) # No output here: this branch is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.C]" reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" y: Foo @@ -908,7 +912,8 @@ elif y is Foo.B: elif y is Foo.C: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # No output here: this branch is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.C]" reveal_type(y) # N: Revealed type is "__main__.Foo" if Foo.A is y: @@ -918,11 +923,13 @@ elif Foo.B is y: elif Foo.C is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # No output here: this branch is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.C]" reveal_type(y) # N: Revealed type is "__main__.Foo" [builtins fixtures/bool.pyi] [case testEnumReachabilityChecksWithOrdering] +# flags: --warn-unreachable from enum import Enum from typing import Literal @@ -939,7 +946,8 @@ if x is Foo.A: elif x is Foo.B: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x) # No output here: this branch is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.B]" class Bar(Enum): __order__ = "A B" @@ -954,7 +962,8 @@ if y is Bar.A: elif y is Bar.B: reveal_type(y) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y) # No output here: this branch is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Bar.B]" x2: Foo if x2 is Foo.A: @@ -962,7 +971,8 @@ if x2 is Foo.A: elif x2 is Foo.B: reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x2) # No output here: this branch is unreachable + reveal_type(x2) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.B]" y2: Bar if y2 is Bar.A: @@ -970,10 +980,12 @@ if y2 is Bar.A: elif y2 is Bar.B: reveal_type(y2) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y2) # No output here: this branch is unreachable + reveal_type(y2) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Bar.B]" [builtins fixtures/tuple.pyi] [case testEnumReachabilityChecksIndirect] +# flags: --warn-unreachable from enum import Enum from typing import Final, Literal @@ -1027,15 +1039,17 @@ if y is z: reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" accepts_foo_a(z) else: - reveal_type(y) # No output: this branch is unreachable - reveal_type(z) # No output: this branch is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.A]" + reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" if z is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A]" reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" accepts_foo_a(z) else: - reveal_type(y) # No output: this branch is unreachable - reveal_type(z) # No output: this branch is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.A]" + reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" [builtins fixtures/bool.pyi] [case testEnumReachabilityNoNarrowingForUnionMessiness] @@ -1311,14 +1325,16 @@ reveal_type(y) # N: Revealed type is "__main__.Foo" # The standard output when we end up inferring two disjoint facts about the same expr if x is Foo.A and x is Foo.B: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Foo" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" # ..and we get the same result if we have two disjoint groups within the same comp expr if x is Foo.A < x is Foo.B: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Foo" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1336,7 +1352,8 @@ class Foo(Enum): x: Foo if x is Foo.A is Foo.B: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Foo" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1344,7 +1361,8 @@ reveal_type(x) # N: Revealed type is "__main__.Foo" literal_a: Literal[Foo.A] literal_b: Literal[Foo.B] if x is literal_a is literal_b: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Foo" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1352,7 +1370,8 @@ reveal_type(x) # N: Revealed type is "__main__.Foo" final_a: Final = Foo.A final_b: Final = Foo.B if x is final_a is final_b: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Foo" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -2286,7 +2305,8 @@ if e == MyEnum.A: elif e == MyEnum.B: reveal_type(e) # N: Revealed type is "Literal[__main__.MyEnum.B]" else: - reveal_type(e) # E: Statement is unreachable + reveal_type(e) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.MyEnum.B]" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 49140bf52b8d..1cd63ebd59fd 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -378,6 +378,7 @@ while 2: [builtins fixtures/isinstancelist.pyi] [case testUnionTryFinally5] +# flags: --warn-unreachable class A: pass class B(A): @@ -391,7 +392,7 @@ while 2: finally: x.b # E: "A" has no attribute "b" break - x.b + _ = "unreachable" # E: Statement is unreachable x.b [case testUnionTryFinally6] @@ -722,6 +723,7 @@ x + [1] # E: Unsupported operand types for + ("int" and "List[int] [builtins fixtures/isinstancelist.pyi] [case testIsInstanceThreeUnion3] +# flags: --warn-unreachable from typing import Union, List while bool(): @@ -731,11 +733,14 @@ while bool(): if isinstance(x, int): x + 1 break - elif isinstance(x, str): - x + 'a' + # TODO: only report unreachability once + # TODO: narrow x to Never + elif isinstance(x, str): # E: Statement is unreachable \ + # E: Subclass of "int" and "str" cannot exist: would have incompatible method signatures + _ = "unreachable" # E: Statement is unreachable + x + 'a' # E: Unsupported operand types for + ("int" and "str") break - x + [1] # These lines aren't reached because x was an int - x + 'a' + _ = "unreachable" # E: Statement is unreachable x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ # E: Unsupported operand types for + ("str" and "List[int]") \ # N: Left operand is of type "Union[int, str, List[int]]" diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 1856ca26f736..7848cd07cf94 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -263,7 +263,9 @@ else: reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" if x.key is Key.D: - reveal_type(x) # E: Statement is unreachable + # TODO: this should narrow to Never + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" else: reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" [builtins fixtures/tuple.pyi] @@ -289,7 +291,8 @@ else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" if x['key'] == 'D': - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" [builtins fixtures/primitives.pyi] @@ -316,7 +319,8 @@ else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" if x['key'] == 'D': - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" [builtins fixtures/primitives.pyi] @@ -606,8 +610,9 @@ else: y: Union[Parent1, Parent2] if y["model"]["key"] is Key.C: - reveal_type(y) # E: Statement is unreachable - reveal_type(y["model"]) + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]" + reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]" else: reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]" reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]" @@ -642,8 +647,9 @@ else: y: Union[Parent1, Parent2] if y["model"]["key"] == 'C': - reveal_type(y) # E: Statement is unreachable - reveal_type(y["model"]) + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]" + reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" else: reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]" reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" @@ -721,7 +727,8 @@ def test2(switch: FlipFlopEnum) -> None: switch.mutate() assert switch.state == State.B # E: Non-overlapping equality check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") - reveal_type(switch.state) # E: Statement is unreachable + reveal_type(switch.state) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.State.A]" def test3(switch: FlipFlopEnum) -> None: # Same thing, but using 'is' comparisons. Previously mypy's behaviour differed @@ -733,7 +740,8 @@ def test3(switch: FlipFlopEnum) -> None: switch.mutate() assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") - reveal_type(switch.state) # E: Statement is unreachable + reveal_type(switch.state) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.State.A]" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitStrLiteral] @@ -893,8 +901,9 @@ else: # No contamination here if 1 == x == z: # E: Non-overlapping equality check (left operand type: "Optional[Literal[1, 2]]", right operand type: "Default") - reveal_type(x) # E: Statement is unreachable - reveal_type(z) + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[Literal[1], Literal[2], None]" + reveal_type(z) # N: Revealed type is "__main__.Default" else: reveal_type(x) # N: Revealed type is "Union[Literal[1], Literal[2], None]" reveal_type(z) # N: Revealed type is "__main__.Default" @@ -909,9 +918,10 @@ b: Literal[1, 2] c: Literal[2, 3] if a == b == c: - reveal_type(a) # E: Statement is unreachable - reveal_type(b) - reveal_type(c) + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Literal[1]" + reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" + reveal_type(c) # N: Revealed type is "Union[Literal[2], Literal[3]]" else: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" @@ -920,7 +930,8 @@ else: if a == a == a: reveal_type(a) # N: Revealed type is "Literal[1]" else: - reveal_type(a) # E: Statement is unreachable + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Literal[1]" if a == a == b: reveal_type(a) # N: Revealed type is "Literal[1]" @@ -982,8 +993,9 @@ elif a == a == 4: else: # In contrast, this branch must be unreachable: we assume (maybe naively) # that 'a' won't be mutated in the middle of the expression. - reveal_type(a) # E: Statement is unreachable - reveal_type(b) + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Literal[4]" + reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" [builtins fixtures/primitives.pyi] [case testNarrowingLiteralTruthiness] @@ -1038,21 +1050,25 @@ f1: F1 if isinstance(f1, F1): reveal_type(f1) # N: Revealed type is "__main__.F1" else: - reveal_type(f1) # E: Statement is unreachable + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "__main__.F1" if isinstance(n, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final - reveal_type(n) # E: Statement is unreachable + reveal_type(n) # E: Statement is unreachable \ + # N: Revealed type is "__main__.N" else: reveal_type(n) # N: Revealed type is "__main__.N" if isinstance(f1, N): # E: Subclass of "F1" and "N" cannot exist: "F1" is final - reveal_type(f1) # E: Statement is unreachable + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "__main__.F1" else: reveal_type(f1) # N: Revealed type is "__main__.F1" if isinstance(f1, F2): # E: Subclass of "F1" and "F2" cannot exist: "F1" is final \ # E: Subclass of "F1" and "F2" cannot exist: "F2" is final - reveal_type(f1) # E: Statement is unreachable + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "__main__.F1" else: reveal_type(f1) # N: Revealed type is "__main__.F1" [builtins fixtures/isinstance.pyi] @@ -1081,7 +1097,8 @@ else: if isinstance(n_f2, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F2" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F1" is final - reveal_type(n_f2) # E: Statement is unreachable + reveal_type(n_f2) # E: Statement is unreachable \ + # N: Revealed type is "Union[__main__.N, __main__.F2]" else: reveal_type(n_f2) # N: Revealed type is "Union[__main__.N, __main__.F2]" @@ -1106,10 +1123,11 @@ T = TypeVar("T", A, B) def f(cls: Type[T]) -> T: if issubclass(cls, A): - reveal_type(cls) # N: Revealed type is "Type[__main__.A]" + reveal_type(cls) # N: Revealed type is "Type[__main__.A]" \ + # N: Revealed type is "Type[__main__.B]" x: bool if x: - return A() + return A() # E: Incompatible return value type (got "A", expected "B") else: return B() # E: Incompatible return value type (got "B", expected "A") assert False @@ -1325,7 +1343,7 @@ def unreachable(x: Union[str, List[str]]) -> None: elif isinstance(x, list): reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" else: - reveal_type(x) # No output: this branch is unreachable + reveal_type(x) # No output: this branch is unreachable # N: Revealed type is "builtins.list[builtins.str]" def all_parts_covered(x: Union[str, List[str], List[int], int]) -> None: if isinstance(x, str): @@ -1645,12 +1663,14 @@ from typing import Tuple, Union x: Union[Tuple[int, int], Tuple[int, int, int]] if len(x) >= 4: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" else: reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" if len(x) < 2: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" else: reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" [builtins fixtures/len.pyi] @@ -1728,23 +1748,27 @@ from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def foo(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) == 1: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" if len(x) != 1: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" def bar(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" if len(x) < 2: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" [builtins fixtures/len.pyi] @@ -1790,23 +1814,27 @@ from typing_extensions import Unpack def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) == 1: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" if len(x) != 1: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" def bar(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" if len(x) < 2: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" [builtins fixtures/len.pyi] @@ -2081,7 +2109,9 @@ if isinstance(x, (Y, Z, NoneType)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" is final \ # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.X" + [builtins fixtures/isinstance.pyi] From 7c39efa49687394fc8552dd018b896dc24d2ff77 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 17:43:19 +0900 Subject: [PATCH 07/40] 40% of tests --- test-data/unit/check-isinstance.test | 125 ++++++++++++++++++--------- test-data/unit/check-narrowing.test | 3 +- test-data/unit/check-protocols.test | 15 ++-- test-data/unit/check-python310.test | 107 ++++++++++++++++------- 4 files changed, 171 insertions(+), 79 deletions(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 1cd63ebd59fd..5b17b8fc0f54 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -393,7 +393,7 @@ while 2: x.b # E: "A" has no attribute "b" break _ = "unreachable" # E: Statement is unreachable - x.b + x.b # E: Statement is unreachable [case testUnionTryFinally6] class A: pass @@ -1053,9 +1053,10 @@ def last(x: LinkedList) -> Nil: [builtins fixtures/isinstance.pyi] [case testReturnAndFlow] +# flags: --warn-unreachable def foo() -> int: return 1 and 2 - return 'a' + _ = "unreachable" # E: Statement is unreachable [case testCastIsinstance] from typing import Union @@ -1071,15 +1072,17 @@ x + 'a' # E: Unsupported operand types for + ("int" and "str") [builtins fixtures/isinstancelist.pyi] [case testUnreachableCode] +# flags: --warn-unreachable x = 1 # type: int while bool(): x = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") break - x = 'a' # Note: no error because unreachable code + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstancelist.pyi] [case testUnreachableCode2] +# flags: --warn-unreachable x = 1 while bool(): try: @@ -1088,50 +1091,56 @@ while bool(): continue else: continue - x + 'a' + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [case testUnreachableWhileTrue] +# flags: --warn-unreachable def f(x: int) -> None: while True: if x: return - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/bool.pyi] [case testUnreachableAssertFalse] +# flags: --warn-unreachable def f() -> None: assert False - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/bool.pyi] [case testUnreachableAssertFalse2] +# flags: --warn-unreachable def f() -> None: # The old parser doesn't understand the syntax below assert False, "hi" - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/bool.pyi] [case testUnreachableReturnOrAssertFalse] +# flags: --warn-unreachable def f(x: int) -> int: if x: return x else: assert False - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/bool.pyi] [case testUnreachableTryExcept] +# flags: --warn-unreachable def f() -> None: try: f() return except BaseException: return - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/exception.pyi] [case testUnreachableTryExceptElse] +# flags: --warn-unreachable def f() -> None: try: f() @@ -1139,39 +1148,43 @@ def f() -> None: return else: return - 1() + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/exception.pyi] [case testUnreachableTryReturnFinally1] +# flags: --warn-unreachable def f() -> None: try: return finally: pass - 1() + _ = "unreachable" # E: Statement is unreachable [case testUnreachableTryReturnFinally2] +# flags: --warn-unreachable def f() -> None: try: pass finally: return - 1() + _ = "unreachable" # E: Statement is unreachable [case testUnreachableTryReturnExceptRaise] +# flags: --warn-unreachable def f() -> None: try: return except: raise - 1() + _ = "unreachable" # E: Statement is unreachable [case testUnreachableReturnLambda] +# flags: --warn-unreachable from typing import Callable def g(t: Callable[[int], int]) -> int: pass def f() -> int: return g(lambda x: x) - 1() + _ = "unreachable" # E: Statement is unreachable [case testIsinstanceAnd] class A: pass @@ -1241,6 +1254,7 @@ else: [builtins fixtures/isinstancelist.pyi] [case testIsinstanceMultiAndSpecialCase] +# flags: --warn-unreachable class A: # Ensure A.__add__ and int.__add__ are different to # force 'isinstance(y, int)' checks below to never succeed. @@ -1255,12 +1269,14 @@ class C(A): x = B() # type: A y = C() # type: A -if isinstance(x, B) and isinstance(y, int): - 1() # type checking skipped -if isinstance(y, int) and isinstance(x, B): - 1() # type checking skipped -if isinstance(y, int) and y > 42: - 1() # type checking skipped +if isinstance(x, B) and isinstance(y, int): # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures + _ = "unreachable" # E: Statement is unreachable +if isinstance(y, int) and isinstance(x, B): # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures \ + # E: Right operand of "and" is never evaluated + _ = "unreachable" # E: Statement is unreachable +if isinstance(y, int) and y > 42: # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures \ + # E: Right operand of "and" is never evaluated + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstancelist.pyi] [case testReturnWithCallExprAndIsinstance] @@ -1346,6 +1362,7 @@ def f2(x: Union[FloatLike, IntLike]) -> None: [builtins fixtures/isinstance.pyi] [case testIsinstanceOfSuperclass] +# flags: --warn-unreachable class A: pass class B(A): pass @@ -1353,8 +1370,9 @@ x = B() if isinstance(x, A): reveal_type(x) # N: Revealed type is "__main__.B" if not isinstance(x, A): - reveal_type(x) # unreachable - x = A() + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.B" + x = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") reveal_type(x) # N: Revealed type is "__main__.B" [builtins fixtures/isinstance.pyi] @@ -1406,6 +1424,7 @@ def f(x: Union[List[int], str]) -> None: [builtins fixtures/isinstancelist.pyi] [case testIsinstanceOrIsinstance] +# flags: --warn-unreachable class A: pass class B(A): @@ -1423,12 +1442,12 @@ else: f = 0 reveal_type(x1) # N: Revealed type is "__main__.A" x2 = A() -if isinstance(x2, A) or isinstance(x2, C): +if isinstance(x2, A) or isinstance(x2, C): # E: Right operand of "or" is never evaluated reveal_type(x2) # N: Revealed type is "__main__.A" f = x2.flag # E: "A" has no attribute "flag" else: # unreachable - 1() + _ = "unreachable" # E: Statement is unreachable reveal_type(x2) # N: Revealed type is "__main__.A" [builtins fixtures/isinstance.pyi] @@ -1513,11 +1532,13 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] [case testIssubclassUnreachable] +# flags: --warn-unreachable from typing import Type, Sequence, Union x: Type[str] -if issubclass(x, int): - reveal_type(x) # unreachable block +if issubclass(x, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Type[builtins.str]" class X: pass class Y(X): pass @@ -1527,7 +1548,8 @@ a: Union[Type[Y], Type[Z]] if issubclass(a, X): reveal_type(a) # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" else: - reveal_type(a) # unreachable block + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" [builtins fixtures/isinstancelist.pyi] [case testIssubclasDestructuringUnions1] @@ -1863,12 +1885,14 @@ def f(x: T) -> T: [builtins fixtures/isinstance.pyi] [case testIsinstanceAndTypeType] +# flags: --warn-unreachable from typing import Type def f(x: Type[int]) -> None: if isinstance(x, type): reveal_type(x) # N: Revealed type is "Type[builtins.int]" else: - reveal_type(x) # Unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Type[builtins.int]" reveal_type(x) # N: Revealed type is "Type[builtins.int]" [builtins fixtures/isinstance.pyi] @@ -2368,7 +2392,8 @@ class C: class Example(A, B): pass # E: Definition of "f" in base class "A" is incompatible with definition in base class "B" x: A if isinstance(x, B): # E: Subclass of "A" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.A" else: reveal_type(x) # N: Revealed type is "__main__.A" @@ -2376,7 +2401,8 @@ y: C if isinstance(y, B): reveal_type(y) # N: Revealed type is "__main__." if isinstance(y, A): # E: Subclass of "C", "B", and "A" cannot exist: would have incompatible method signatures - reveal_type(y) # E: Statement is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "__main__." [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionReversed] @@ -2404,7 +2430,8 @@ class B: def t0(self) -> None: if isinstance(self, A0): # E: Subclass of "B" and "A0" cannot exist: would have incompatible method signatures - x0: Literal[0] = self.f() # E: Statement is unreachable + x0: Literal[0] = self.f() # E: Statement is unreachable \ + # E: Incompatible types in assignment (expression has type "Literal[1, 2]", variable has type "Literal[0]") def t1(self) -> None: if isinstance(self, A1): @@ -2441,7 +2468,8 @@ class B: x: A[int] if isinstance(x, B): # E: Subclass of "A[int]" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.A[builtins.int]" else: reveal_type(x) # N: Revealed type is "__main__.A[builtins.int]" @@ -2480,26 +2508,34 @@ def f1(x: T1) -> T1: reveal_type(x) # N: Revealed type is "__main__." \ # N: Revealed type is "__main__." else: - reveal_type(x) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "__main__.A" \ + # N: Revealed type is "__main__." else: - reveal_type(x) # N: Revealed type is "__main__.B" + reveal_type(x) # N: Revealed type is "__main__.A" \ + # N: Revealed type is "__main__.B" return x T2 = TypeVar('T2', B, C) def f2(x: T2) -> T2: if isinstance(x, B): - reveal_type(x) # N: Revealed type is "__main__.B" + reveal_type(x) # N: Revealed type is "__main__.B" \ + # N: Revealed type is "__main__.C" # Note: even though --warn-unreachable is set, we don't report # errors for the below: we don't yet have a way of filtering out # reachability errors that occur for only one variation of the # TypeVar yet. if isinstance(x, C): - reveal_type(x) + reveal_type(x) # N: Revealed type is "__main__.B" \ + # N: Revealed type is "__main__.C" else: - reveal_type(x) # N: Revealed type is "__main__.B" + reveal_type(x) # N: Revealed type is "__main__.B" \ + # N: Revealed type is "__main__.C" else: - reveal_type(x) # N: Revealed type is "__main__.C" + reveal_type(x) # N: Revealed type is "__main__.B" \ + # N: Revealed type is "__main__.C" return x + + [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionGenericsWithValuesDirectReturn] @@ -2513,6 +2549,7 @@ class B: class C: attr: str +# TODO: these kinds of typevars play havoc with checking unreachable code T1 = TypeVar('T1', A, B) def f1(x: T1) -> T1: if isinstance(x, A): @@ -2520,16 +2557,16 @@ def f1(x: T1) -> T1: # 'x' is a subclass of __main__.A and __main__.B return A() # E: Incompatible return value type (got "A", expected "B") else: - return B() + return B() # E: Incompatible return value type (got "B", expected "A") T2 = TypeVar('T2', B, C) def f2(x: T2) -> T2: if isinstance(x, B): # In contrast, it's impossible for a subclass of "B" and "C" to # exist, so this is fine - return B() + return B() # E: Incompatible return value type (got "B", expected "C") else: - return C() + return C() # E: Incompatible return value type (got "C", expected "B") [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionUsage] @@ -2611,7 +2648,8 @@ class B(Y, X): pass foo: A if isinstance(foo, B): # E: Subclass of "A" and "B" cannot exist: would have inconsistent method resolution order - reveal_type(foo) # E: Statement is unreachable + reveal_type(foo) # E: Statement is unreachable \ + # N: Revealed type is "__main__.A" [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionAmbiguousClass] @@ -2645,7 +2683,8 @@ x: Type[A] if issubclass(x, B): reveal_type(x) # N: Revealed type is "Type[__main__.]" if issubclass(x, C): # E: Subclass of "A", "B", and "C" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Type[__main__.]" else: reveal_type(x) # N: Revealed type is "Type[__main__.]" else: diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 7848cd07cf94..c6ff6da81671 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2368,7 +2368,8 @@ def fn_while(arg: T) -> None: x = None for _ in range(2): if x is not None: - reveal_type(x) # N: Revealed type is "builtins.int" + reveal_type(x) # N: Revealed type is "None" \ + # N: Revealed type is "builtins.int" x = 1 reveal_type(x) # N: Revealed type is "Union[builtins.int, None]" diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index a7124b7a83d3..2a1d682fb1c9 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1714,6 +1714,7 @@ if isinstance(x, Iterable): [typing fixtures/typing-full.pyi] [case testConcreteClassesInProtocolsIsInstance] +# flags: --warn-unreachable from typing import Protocol, runtime_checkable, TypeVar, Generic T = TypeVar('T') @@ -1742,25 +1743,29 @@ c = C() if isinstance(c, P1): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # Unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "__main__.C" if isinstance(c, P): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # Unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "__main__.C" c1i: C1[int] if isinstance(c1i, P1): reveal_type(c1i) # N: Revealed type is "__main__.C1[builtins.int]" else: - reveal_type(c1i) # Unreachable + reveal_type(c1i) # E: Statement is unreachable \ + # N: Revealed type is "__main__.C1[builtins.int]" if isinstance(c1i, P): reveal_type(c1i) # N: Revealed type is "__main__." else: reveal_type(c1i) # N: Revealed type is "__main__.C1[builtins.int]" c1s: C1[str] -if isinstance(c1s, P1): - reveal_type(c1s) # Unreachable +if isinstance(c1s, P1): # E: Subclass of "C1[str]" and "P1" cannot exist: would have incompatible method signatures + reveal_type(c1s) # E: Statement is unreachable \ + # N: Revealed type is "__main__.C1[builtins.str]" else: reveal_type(c1s) # N: Revealed type is "__main__.C1[builtins.str]" diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 016f50552a5f..1e3f242bc973 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -26,12 +26,14 @@ match m: [builtins fixtures/primitives.pyi] [case testMatchLiteralPatternUnreachable] +# flags: --warn-unreachable # primitives are needed because otherwise mypy doesn't see that int and str are incompatible m: int match m: case "str": - reveal_type(m) + reveal_type(m) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/primitives.pyi] -- Value Pattern -- @@ -70,14 +72,16 @@ class B: ... b: B [case testMatchValuePatternUnreachable] +# flags: --warn-unreachable # primitives are needed because otherwise mypy doesn't see that int and str are incompatible import b m: int match m: - case b.b: - reveal_type(m) + case b.b: # E: Subclass of "int" and "str" cannot exist: would have incompatible method signatures + reveal_type(m) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [file b.py] b: str [builtins fixtures/primitives.pyi] @@ -169,6 +173,7 @@ match m: [builtins fixtures/list.pyi] [case testMatchSequencePatternMatches] +# flags: --warn-unreachable import array, collections from typing import Sequence, Iterable @@ -219,15 +224,21 @@ match m8: match m9: case [i]: - reveal_type(i) + reveal_type(i) # E: Statement is unreachable \ + # E: Cannot determine type of "i" \ + # N: Revealed type is "Any" match m10: case [j]: - reveal_type(j) + reveal_type(j) # E: Statement is unreachable \ + # E: Cannot determine type of "j" \ + # N: Revealed type is "Any" match m11: case [k]: - reveal_type(k) + reveal_type(k) # E: Statement is unreachable \ + # E: Cannot determine type of "k" \ + # N: Revealed type is "Any" [builtins fixtures/primitives.pyi] [typing fixtures/typing-full.pyi] @@ -244,24 +255,33 @@ match m: [builtins fixtures/list.pyi] [case testMatchSequencePatternTupleTooLong] +# flags: --warn-unreachable from typing import Tuple m: Tuple[int, str] match m: case [a, b, c]: - reveal_type(a) - reveal_type(b) - reveal_type(c) + reveal_type(a) # E: Statement is unreachable \ + # E: Cannot determine type of "a" \ + # N: Revealed type is "Any" + reveal_type(b) # E: Cannot determine type of "b" \ + # N: Revealed type is "Any" + reveal_type(c) # E: Cannot determine type of "c" \ + # N: Revealed type is "Any" [builtins fixtures/list.pyi] [case testMatchSequencePatternTupleTooShort] +# flags: --warn-unreachable from typing import Tuple m: Tuple[int, str, bool] match m: case [a, b]: - reveal_type(a) - reveal_type(b) + reveal_type(a) # E: Statement is unreachable \ + # E: Cannot determine type of "a" \ + # N: Revealed type is "Any" + reveal_type(b) # E: Cannot determine type of "b" \ + # N: Revealed type is "Any" [builtins fixtures/list.pyi] [case testMatchSequencePatternTupleNarrows] @@ -298,15 +318,20 @@ match m: [builtins fixtures/list.pyi] [case testMatchSequencePatternTupleStarredTooShort] +# flags: --warn-unreachable from typing import Tuple m: Tuple[int] reveal_type(m) # N: Revealed type is "Tuple[builtins.int]" match m: case [a, *b, c]: - reveal_type(a) - reveal_type(b) - reveal_type(c) + reveal_type(a) # E: Statement is unreachable \ + # E: Cannot determine type of "a" \ + # N: Revealed type is "Any" + reveal_type(b) # E: Cannot determine type of "b" \ + # N: Revealed type is "Any" + reveal_type(c) # E: Cannot determine type of "c" \ + # N: Revealed type is "Any" [builtins fixtures/list.pyi] [case testMatchNonMatchingSequencePattern] @@ -505,6 +530,7 @@ a: str [typing fixtures/typing-typeddict.pyi] [case testMatchMappingPatternCapturesTypedDictUnreachable] +# flags: --warn-unreachable # TypedDict keys are always str, so this is actually unreachable from typing import TypedDict import b @@ -517,9 +543,11 @@ m: A match m: case {1: v}: - reveal_type(v) + reveal_type(v) # E: Statement is unreachable \ + # N: Revealed type is "builtins.object" case {b.b: v2}: - reveal_type(v2) + reveal_type(v2) # E: Statement is unreachable \ + # N: Revealed type is "builtins.object" [file b.py] b: int [typing fixtures/typing-typeddict.pyi] @@ -831,6 +859,7 @@ match m: reveal_type(i) # N: Revealed type is "builtins.int" [case testMatchClassPatternCaptureFilledGenericTypeAlias] +# flags: --warn-unreachable from typing import Generic, TypeVar T = TypeVar('T') @@ -844,7 +873,9 @@ m: object match m: case B(a=i): # E: Class pattern class must not be a type alias with type parameters - reveal_type(i) + reveal_type(i) # E: Statement is unreachable \ + # E: Cannot determine type of "i" \ + # N: Revealed type is "Any" [case testMatchClassPatternCaptureGenericTypeAlias] from typing import Generic, TypeVar @@ -1003,13 +1034,17 @@ match m: [builtins fixtures/tuple.pyi] [case testMatchClassPatternIsNotType] +# flags: --warn-unreachable a = 1 m: object match m: case a(i, j): # E: Expected type in class pattern; found "builtins.int" - reveal_type(i) - reveal_type(j) + reveal_type(i) # E: Statement is unreachable \ + # E: Cannot determine type of "i" \ + # N: Revealed type is "Any" + reveal_type(j) # E: Cannot determine type of "j" \ + # N: Revealed type is "Any" [case testMatchClassPatternAny] from typing import Any @@ -1288,11 +1323,13 @@ match m: reveal_type(a) # N: Revealed type is "builtins.str" [case testMatchAlwaysFalsePatternGuard] +# flags: --warn-unreachable m: str match m: case a if False: - reveal_type(a) + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "builtins.str" [case testMatchRedefiningPatternGuard] m: str @@ -1328,11 +1365,13 @@ match m: [builtins fixtures/isinstancelist.pyi] [case testMatchUnreachablePatternGuard] +# flags: --warn-unreachable m: str match m: - case a if isinstance(a, int): - reveal_type(a) + case a if isinstance(a, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "builtins.str" [builtins fixtures/isinstancelist.pyi] -- Exhaustiveness -- @@ -1453,7 +1492,7 @@ def f(value: Literal[1] | Literal[2]) -> int: case 2: return 1 case o: - assert_never(o) + assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "Literal[2]"; expected "Never" [typing fixtures/typing-medium.pyi] [case testMatchSequencePatternNegativeNarrowing] @@ -1527,7 +1566,7 @@ def f(m: Medal) -> None: always_assigned = 1 reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" case _: - assert_never(m) + assert_never(m) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" reveal_type(always_assigned) # N: Revealed type is "builtins.int" [builtins fixtures/bool.pyi] @@ -1593,7 +1632,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) + assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Literal[Medal.bronze]"; expected "Never" [builtins fixtures/tuple.pyi] @@ -1613,7 +1652,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) + assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Literal[Medal.bronze]"; expected "Never" [builtins fixtures/enum.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] @@ -2297,6 +2336,7 @@ def f4(e: int | str | bytes) -> int: [builtins fixtures/primitives.pyi] [case testMatchSequencePatternVariadicTupleNotTooShort] +# flags: --warn-unreachable from typing import Tuple from typing_extensions import Unpack @@ -2310,8 +2350,11 @@ match fm1: fm2: Tuple[int, int, Unpack[Tuple[str, ...]], int] match fm2: case [fa2, fb2]: - reveal_type(fa2) - reveal_type(fb2) + reveal_type(fa2) # E: Statement is unreachable \ + # E: Cannot determine type of "fa2" \ + # N: Revealed type is "Any" + reveal_type(fb2) # E: Cannot determine type of "fb2" \ + # N: Revealed type is "Any" fm3: Tuple[int, int, Unpack[Tuple[str, ...]], int] match fm3: @@ -2347,6 +2390,7 @@ match m3: [builtins fixtures/tuple.pyi] [case testMatchSequencePatternTypeVarTupleNotTooShort] +# flags: --warn-unreachable from typing import Tuple from typing_extensions import Unpack, TypeVarTuple @@ -2362,8 +2406,11 @@ def test(xs: Tuple[Unpack[Ts]]) -> None: fm2: Tuple[int, int, Unpack[Ts], int] match fm2: case [fa2, fb2]: - reveal_type(fa2) - reveal_type(fb2) + reveal_type(fa2) # E: Statement is unreachable \ + # E: Cannot determine type of "fa2" \ + # N: Revealed type is "Any" + reveal_type(fb2) # E: Cannot determine type of "fb2" \ + # N: Revealed type is "Any" fm3: Tuple[int, int, Unpack[Ts], int] match fm3: From 18642d0c32e9cbc356ee568ee48683a1b62e1262 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 18:02:33 +0900 Subject: [PATCH 08/40] 50% of tests --- test-data/unit/check-incremental.test | 6 ++-- test-data/unit/check-overloading.test | 37 +++++++++++++------- test-data/unit/check-possibly-undefined.test | 2 +- test-data/unit/check-python38.test | 12 ++++--- test-data/unit/check-reports.test | 2 +- test-data/unit/check-typeddict.test | 6 ++-- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 0c7e67e5444d..e585f3dfb4f4 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5415,8 +5415,7 @@ reveal_type(z) [out] tmp/c.py:2: note: Revealed type is "a." [out2] -tmp/b.py:2: error: Cannot determine type of "y" -tmp/c.py:2: note: Revealed type is "Any" +tmp/c.py:2: note: Revealed type is "a.A" [case testIsInstanceAdHocIntersectionIncrementalUnreachaableToIntersection] import c @@ -5447,8 +5446,7 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -tmp/b.py:2: error: Cannot determine type of "y" -tmp/c.py:2: note: Revealed type is "Any" +tmp/c.py:2: note: Revealed type is "a.A" [out2] tmp/c.py:2: note: Revealed type is "a." diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 243568c54253..b0bc6c8976e6 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -3705,7 +3705,7 @@ reveal_type(mymap(f3, seq)) # N: Revealed type is "Union[typing.Iterable[builti [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeNoStrictOptional1] -# flags: --no-strict-optional +# flags: --no-strict-optional --warn-unreachable from typing import overload, Union, NoReturn @overload @@ -3730,13 +3730,14 @@ def test_narrow_int() -> None: c: str if int(): c = narrow_int(c) - reveal_type(c) # Note: branch is now dead, so no type is revealed - # TODO: maybe we should make mypy report a warning instead? + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional1] +# flags: --warn-unreachable from typing import overload, Union, NoReturn @overload @@ -3761,14 +3762,14 @@ def test_narrow_int() -> None: c: str if int(): c = narrow_int(c) - reveal_type(c) # Note: branch is now dead, so no type is revealed - # TODO: maybe we should make mypy report a warning instead? + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeNoStrictOptional2] -# flags: --no-strict-optional +# flags: --no-strict-optional --warn-unreachable from typing import overload, Union, TypeVar, NoReturn, Optional T = TypeVar('T') @@ -3794,12 +3795,14 @@ def test_narrow_none() -> None: c: None if int(): c = narrow_none(c) - reveal_type(c) # Note: branch is now dead, so no type is revealed + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional2] +# flags: --warn-unreachable from typing import overload, Union, TypeVar, NoReturn, Optional T = TypeVar('T') @@ -3825,14 +3828,15 @@ def test_narrow_none() -> None: c: None if int(): c = narrow_none(c) - reveal_type(c) # Branch is now dead + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeNoStrictOptional3] -# flags: --no-strict-optional +# flags: --no-strict-optional --warn-unreachable from typing import overload, TypeVar, NoReturn, Optional @overload @@ -3857,12 +3861,14 @@ def test_narrow_none_v2() -> None: c: None if int(): c = narrow_none_v2(c) - reveal_type(c) # Note: branch is now dead, so no type is revealed + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional3] +# flags: --warn-unreachable from typing import overload, TypeVar, NoReturn, Optional @overload @@ -3887,12 +3893,14 @@ def test_narrow_none_v2() -> None: c: None if int(): c = narrow_none_v2(c) - reveal_type(c) # Note: branch is now dead, so no type is revealed + reveal_type(c) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowWhenBlacklistingSubtype] +# flags: --warn-unreachable from typing import TypeVar, NoReturn, Union, overload class Parent: ... @@ -3917,12 +3925,14 @@ def test() -> None: val2: A if int(): val2 = narrow_to_not_a(val2) - reveal_type(val2) # Branch now dead + reveal_type(val2) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowWhenBlacklistingSubtype2] +# flags: --warn-unreachable from typing import TypeVar, NoReturn, Union, overload class Parent: ... @@ -3945,7 +3955,8 @@ def test_v2(val: Union[A, B], val2: A) -> None: if int(): val2 = narrow_to_not_a_v2(val2) - reveal_type(val2) # Branch now dead + reveal_type(val2) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index ae277949c049..235d293ab866 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -1040,6 +1040,6 @@ def foo(x: Union[int, str]) -> None: elif isinstance(x, int): f = "bar" else: - assert_never(x) + assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" f # OK [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index f90baed0eb16..5e7f28586e39 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -459,7 +459,8 @@ def check_partial_list() -> None: # flags: --warn-unreachable if (x := 0): - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "Literal[0]" @@ -485,7 +486,8 @@ else: reveal_type(x) # N: Revealed type is "builtins.str" if y := wrapper.always_false: - reveal_type(y) # E: Statement is unreachable + reveal_type(y) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable else: reveal_type(y) # N: Revealed type is "Literal[False]" @@ -508,7 +510,8 @@ reveal_type(x) # N: Revealed type is "builtins.str" def always_false() -> Literal[False]: ... if y := always_false(): - reveal_type(y) # E: Statement is unreachable + reveal_type(y) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable else: reveal_type(y) # N: Revealed type is "Literal[False]" @@ -517,7 +520,8 @@ reveal_type(y) # N: Revealed type is "Literal[False]" def always_false_with_parameter(x: int) -> Literal[False]: ... if z := always_false_with_parameter(5): - reveal_type(z) # E: Statement is unreachable + reveal_type(z) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable else: reveal_type(z) # N: Revealed type is "Literal[False]" diff --git a/test-data/unit/check-reports.test b/test-data/unit/check-reports.test index 423cbcc49289..fc29e61a5cb5 100644 --- a/test-data/unit/check-reports.test +++ b/test-data/unit/check-reports.test @@ -41,7 +41,7 @@ def f(x: int) -> None: [outfile out/lineprecision.txt] Name Lines Precise Imprecise Any Empty Unanalyzed ------------------------------------------------------------- -__main__ 10 5 0 0 2 3 +__main__ 10 7 0 0 2 1 [case testLinePrecisionEmptyLines] # flags: --lineprecision-report out diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c5ebed57bbcd..b0e03b2b1313 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -2127,9 +2127,11 @@ TD = TypeVar('TD', D1, D2) def f(arg: TD) -> None: value: int if 'foo' in arg: - assert_type(arg['foo'], int) + assert_type(arg['foo'], int) # E: Expression is of type "Any", not "int" \ + # E: TypedDict "D2" has no key "foo" else: - assert_type(arg['bar'], int) + assert_type(arg['bar'], int) # E: Expression is of type "Any", not "int" \ + # E: TypedDict "D1" has no key "bar" [builtins fixtures/dict.pyi] From 879b27452e9175bf53d0542c4a2e882a447e8178 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 18:16:15 +0900 Subject: [PATCH 09/40] 60% of tests --- test-data/unit/check-typeis.test | 7 +- test-data/unit/check-typevar-tuple.test | 6 +- test-data/unit/check-typevar-values.test | 43 +++++---- test-data/unit/check-unreachable-code.test | 103 ++++++++++++++------- 4 files changed, 97 insertions(+), 62 deletions(-) diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index e70c71a4b62e..34f00f4b1881 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -819,6 +819,7 @@ accept_typeguard(typeguard) [builtins fixtures/tuple.pyi] [case testTypeIsInOverloads] +# flags: --warn-unreachable from typing import Any, overload, Union from typing_extensions import TypeIs @@ -843,13 +844,15 @@ def func3(val: Union[int, str]): if func1(val): reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" else: - reveal_type(val) + reveal_type(val) # E: Statement is unreachable \ + # N: Revealed type is "Union[builtins.int, builtins.str]" def func4(val: int): if func1(val): reveal_type(val) # N: Revealed type is "builtins.int" else: - reveal_type(val) + reveal_type(val) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] [case testTypeIsInOverloadsSameReturn] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 2cc84c8e6b15..ea085190056c 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1273,7 +1273,8 @@ def test(d: A[int, str]) -> None: if isinstance(d, A): reveal_type(d) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" else: - reveal_type(d) # E: Statement is unreachable + reveal_type(d) # E: Statement is unreachable \ + # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" class B(Generic[Unpack[TP]]): ... @@ -1281,7 +1282,8 @@ def test2(d: B[int, str]) -> None: if isinstance(d, B): reveal_type(d) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" else: - reveal_type(d) # E: Statement is unreachable + reveal_type(d) # E: Statement is unreachable \ + # N: Revealed type is "__main__.B[builtins.int, builtins.str]" [builtins fixtures/isinstancelist.pyi] [case testVariadicTupleSubtyping] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 36ab3af6d3e9..c3ab60d77a7e 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -112,27 +112,26 @@ from typing import TypeVar T = TypeVar('T', int, str) def f(x: T) -> T: if isinstance(x, int): - return 2 + return 2 # E: Incompatible return value type (got "int", expected "str") return x def g(x: T) -> T: if isinstance(x, str): - return '' + return '' # E: Incompatible return value type (got "str", expected "int") return x def h(x: T) -> T: if isinstance(x, int): return '' # E: Incompatible return value type (got "str", expected "int") return x [builtins fixtures/isinstance.pyi] -[out] [case testIsinstanceAndTypeVarValues2] from typing import TypeVar T = TypeVar('T', int, str) def f(x: T) -> T: if isinstance(x, int): - return 2 + return 2 # E: Incompatible return value type (got "int", expected "str") else: - return '' + return '' # E: Incompatible return value type (got "str", expected "int") def g(x: T) -> T: if isinstance(x, int): return '' # E: Incompatible return value type (got "str", expected "int") @@ -140,7 +139,6 @@ def g(x: T) -> T: return 2 # E: Incompatible return value type (got "int", expected "str") return x [builtins fixtures/isinstance.pyi] -[out] [case testIsinstanceAndTypeVarValues3] from typing import TypeVar @@ -149,8 +147,8 @@ def f(x: T) -> T: if isinstance(x, int): y = 1 else: - y = '' - return y + y = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + return y # E: Incompatible return value type (got "int", expected "str") [builtins fixtures/isinstance.pyi] [case testIsinstanceAndTypeVarValues4] @@ -160,10 +158,9 @@ def f(x: T) -> T: if isinstance(x, int): y = 1 else: - y = object() - return y # E: Incompatible return value type (got "object", expected "str") + y = object() # E: Incompatible types in assignment (expression has type "object", variable has type "int") + return y # E: Incompatible return value type (got "int", expected "str") [builtins fixtures/isinstance.pyi] -[out] [case testIsinstanceAndTypeVarValues5] from typing import TypeVar @@ -189,9 +186,10 @@ def f1(x: T1) -> None: x = y x = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") else: - x = B() + x = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") x = y - x.foo() # E: "B" has no attribute "foo" + x.foo() # E: "A" has no attribute "foo" \ + # E: "B" has no attribute "foo" class C: field: int @@ -203,11 +201,12 @@ def f2(x: T2) -> None: if isinstance(x, C): # C and D are non-overlapping, so this branch is never checked x = y - x = C() + x = C() # E: Incompatible types in assignment (expression has type "C", variable has type "D") else: - x = D() + x = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C") x = y - x.foo() # E: "D" has no attribute "foo" + x.foo() # E: "C" has no attribute "foo" \ + # E: "D" has no attribute "foo" S = TypeVar('S', int, str) def g(x: S) -> None: @@ -215,7 +214,6 @@ def g(x: S) -> None: if isinstance(x, int): x = y [builtins fixtures/isinstance.pyi] -[out] [case testIsinstanceWithUserDefinedTypeAndTypeVarValues2] from typing import TypeVar @@ -226,14 +224,13 @@ def f(x: T) -> None: if isinstance(x, S): # This is checked only when type of x is str. x = y - x = S() + x = S() # E: Incompatible types in assignment (expression has type "S", variable has type "int") x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "S") else: x = y - x = 1 + x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "S") x = S() # E: Incompatible types in assignment (expression has type "S", variable has type "int") [builtins fixtures/isinstance.pyi] -[out] [case testTypeVarValuesAndNestedCalls] from typing import TypeVar @@ -740,8 +737,10 @@ W = TypeVar("W", int, str) def fn(w: W) -> W: if type(w) is str: - reveal_type(w) # N: Revealed type is "builtins.str" + reveal_type(w) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" elif type(w) is int: - reveal_type(w) # N: Revealed type is "builtins.int" + reveal_type(w) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" return w [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index a40aa21ff26a..545f4b5e6289 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -615,6 +615,7 @@ reveal_type(x) # N: Revealed type is "__main__.B" [typing fixtures/typing-medium.pyi] [case testUnreachableWhenSuperclassIsAny] +# flags: --warn-unreachable from typing import Any # This can happen if we're importing a class from a missing module @@ -623,8 +624,9 @@ class Child(Parent): def foo(self) -> int: reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) - return None + reveal_type(self) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Child" + return None # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 @@ -633,8 +635,9 @@ class Child(Parent): self = super(Child, self).something() reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) - return None + reveal_type(self) # E: Statement is unreachable \ + # N: Revealed type is "__main__.Child" + return None # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 [builtins fixtures/isinstance.pyi] @@ -716,7 +719,8 @@ a: int if isinstance(a, int): reveal_type(a) # N: Revealed type is "builtins.int" else: - reveal_type(a) # E: Statement is unreachable + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow2] @@ -725,7 +729,8 @@ b: int while isinstance(b, int): reveal_type(b) # N: Revealed type is "builtins.int" else: - reveal_type(b) # E: Statement is unreachable + reveal_type(b) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow3] @@ -733,14 +738,16 @@ else: def foo(c: int) -> None: reveal_type(c) # N: Revealed type is "builtins.int" assert not isinstance(c, int) - reveal_type(c) # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow4] # flags: --warn-unreachable d: int if False: - reveal_type(d) # E: Statement is unreachable + reveal_type(d) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow5] @@ -749,7 +756,8 @@ e: int if True: reveal_type(e) # N: Revealed type is "builtins.int" else: - reveal_type(e) # E: Statement is unreachable + reveal_type(e) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagStatementAfterReturn] @@ -757,7 +765,8 @@ else: def foo(x: int) -> None: reveal_type(x) # N: Revealed type is "builtins.int" return - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [case testUnreachableFlagTryBlocks] # flags: --warn-unreachable @@ -766,24 +775,28 @@ def foo(x: int) -> int: try: reveal_type(x) # N: Revealed type is "builtins.int" return x - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" finally: reveal_type(x) # N: Revealed type is "builtins.int" if True: reveal_type(x) # N: Revealed type is "builtins.int" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" def bar(x: int) -> int: try: if True: raise Exception() - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" except: reveal_type(x) # N: Revealed type is "builtins.int" return x else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" def baz(x: int) -> int: try: @@ -872,24 +885,30 @@ def expect_str(x: str) -> str: pass x: int if False: assert False - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" if False: raise Exception() - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" if False: - assert_never(x) - reveal_type(x) # E: Statement is unreachable + assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" if False: - nonthrowing_assert_never(x) # E: Statement is unreachable - reveal_type(x) + nonthrowing_assert_never(x) # E: Statement is unreachable \ + # E: Argument 1 to "nonthrowing_assert_never" has incompatible type "int"; expected "Never" + reveal_type(x) # N: Revealed type is "builtins.int" if False: # Ignore obvious type errors - assert_never(expect_str(x)) - reveal_type(x) # E: Statement is unreachable + assert_never(expect_str(x)) # E: Argument 1 to "assert_never" has incompatible type "str"; expected "Never" \ + # E: Argument 1 to "expect_str" has incompatible type "int"; expected "str" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" [builtins fixtures/exception.pyi] [case testNeverVariants] @@ -936,7 +955,8 @@ class Case1: def test2(self) -> bool: return not self.property_decorator_missing and self.missing() # E: Function "property_decorator_missing" could always be true in boolean context \ - # E: Right operand of "and" is never evaluated + # E: Right operand of "and" is never evaluated \ + # E: "Case1" has no attribute "missing" def property_decorator_missing(self) -> bool: return True @@ -954,21 +974,25 @@ def test1(x: T1) -> T1: if isinstance(x, int): reveal_type(x) # N: Revealed type is "T1`-1" else: - reveal_type(x) # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "T1`-1" return x def test2(x: T2) -> T2: if isinstance(x, int): - reveal_type(x) # N: Revealed type is "builtins.int" + reveal_type(x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" else: - reveal_type(x) # N: Revealed type is "builtins.str" + reveal_type(x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" if False: # This is unreachable, but we don't report an error, unfortunately. # The presence of the TypeVar with values unfortunately currently shuts # down type-checking for this entire function. # TODO: Find a way of removing this limitation - reveal_type(x) + reveal_type(x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" return x @@ -977,13 +1001,16 @@ class Test3(Generic[T2]): def func(self) -> None: if isinstance(self.x, int): - reveal_type(self.x) # N: Revealed type is "builtins.int" + reveal_type(self.x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" else: - reveal_type(self.x) # N: Revealed type is "builtins.str" + reveal_type(self.x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" if False: # Same issue as above - reveal_type(self.x) + reveal_type(self.x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" class Test4(Generic[T3]): @@ -1333,7 +1360,8 @@ def test_untyped_fn(obj): obj.reload() assert obj.prop is False - reveal_type(obj.prop) + reveal_type(obj.prop) # N: Revealed type is "Any" \ + # N: 'reveal_type' always outputs 'Any' in unchecked functions def test_typed_fn(obj) -> None: assert obj.prop is True @@ -1342,7 +1370,8 @@ def test_typed_fn(obj) -> None: obj.reload() assert obj.prop is False - reveal_type(obj.prop) # E: Statement is unreachable + reveal_type(obj.prop) # E: Statement is unreachable \ + # N: Revealed type is "Literal[True]" [case testUnreachableCheckedUntypedFunction] # flags: --warn-unreachable --check-untyped-defs @@ -1354,7 +1383,8 @@ def test_untyped_fn(obj): obj.reload() assert obj.prop is False - reveal_type(obj.prop) # E: Statement is unreachable + reveal_type(obj.prop) # E: Statement is unreachable \ + # N: Revealed type is "Literal[True]" [case testConditionalTypeVarException] # every part of this test case was necessary to trigger the crash @@ -1379,8 +1409,9 @@ from typing import Literal def nope() -> Literal[False]: ... def f() -> None: + # TODO: this should be unreachable if nope(): - x = 1 # E: Statement is unreachable + x = 1 [builtins fixtures/dict.pyi] [case testUnreachableLiteralFrom__bool__] @@ -1403,7 +1434,7 @@ else: x = 2 # E: Statement is unreachable if Lie(): - x = 3 # E: Statement is unreachable + x = 3 if Maybe(): x = 4 @@ -1454,7 +1485,7 @@ def force_forward_ref() -> int: def f(value: None) -> None: x if value is not None: - assert_never(value) + assert_never(value) # E: Argument 1 to "assert_never" has incompatible type "None"; expected "Never" x = force_forward_ref() [builtins fixtures/exception.pyi] From c62093e07c8fb180cb8e7c2200d1c4269b641eb0 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Feb 2025 18:37:05 +0900 Subject: [PATCH 10/40] Pass all tests --- mypyc/test-data/irbuild-match.test | 14 ++++---------- test-data/unit/check-incremental.test | 2 +- test-data/unit/deps-classes.test | 8 ++++++-- test-data/unit/deps.test | 6 ++++-- test-data/unit/fine-grained.test | 8 +++----- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/mypyc/test-data/irbuild-match.test b/mypyc/test-data/irbuild-match.test index 28aff3dcfc45..4b09afd9cf9d 100644 --- a/mypyc/test-data/irbuild-match.test +++ b/mypyc/test-data/irbuild-match.test @@ -1765,10 +1765,8 @@ def f(x): r10 :: object r11 :: object[1] r12 :: object_ptr - r13, r14 :: object - r15 :: i32 - r16 :: bit - r17, r18 :: bool + r13 :: object + r14 :: bool L0: r0 = __main__.Foo :: type r1 = PyObject_IsInstance(x, r0) @@ -1792,13 +1790,9 @@ L2: goto L8 L3: L4: - r14 = box(bool, 0) - r15 = PyObject_IsTrue(r14) - r16 = r15 >= 0 :: signed - r17 = truncate r15: i32 to builtins.bool - if r17 goto L6 else goto L5 :: bool + if 0 goto L6 else goto L5 :: bool L5: - r18 = raise AssertionError('Unreachable') + r14 = raise AssertionError('Unreachable') unreachable L6: goto L8 diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index e585f3dfb4f4..9b0d71d5d50e 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5417,7 +5417,7 @@ tmp/c.py:2: note: Revealed type is "a." [out2] tmp/c.py:2: note: Revealed type is "a.A" -[case testIsInstanceAdHocIntersectionIncrementalUnreachaableToIntersection] +[case testIsInstanceAdHocIntersectionIncrementalUnreachableToIntersection] import c [file a.py] class A: diff --git a/test-data/unit/deps-classes.test b/test-data/unit/deps-classes.test index a8fc5d629491..4e9eb9f94b88 100644 --- a/test-data/unit/deps-classes.test +++ b/test-data/unit/deps-classes.test @@ -84,19 +84,23 @@ class A: pass -> m [case testIfFalseInClassBody] +from typing import Any + class A: if False: - x = None # type: str + x: Any = None x.foo() [builtins fixtures/bool.pyi] [out] -> m.A [case testAlwaysFalseIsinstanceInClassBody] +from typing import Any + class A: x: int if isinstance(x, str): - y: str = None + y: Any = None y.foo() [builtins fixtures/isinstance.pyi] [out] diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 2c231c9afff6..ac84c918665b 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -414,7 +414,9 @@ class B: def f(x: A) -> None: if isinstance(x, B): - x.y + # TODO: this should narrow to Never and therefore not error + #x.y + pass [builtins fixtures/isinstancelist.pyi] [out] -> , m.A, m.f @@ -582,7 +584,7 @@ def f(a: A) -> None: [case testUnreachableAssignment] from typing import List, Tuple -def f() -> None: pass +def f() -> int: pass class C: def __init__(self, x: int) -> None: diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index d2b1a8a92b80..65eecd9e3ab8 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9692,10 +9692,9 @@ reveal_type(z) [out] c.py:2: note: Revealed type is "a." == -c.py:2: note: Revealed type is "Any" -b.py:2: error: Cannot determine type of "y" +c.py:2: note: Revealed type is "a.A" -[case testIsInstanceAdHocIntersectionFineGrainedIncrementalUnreachaableToIntersection] +[case testIsInstanceAdHocIntersectionFineGrainedIncrementalUnreachableToIntersection] import c [file a.py] class A: @@ -9724,8 +9723,7 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -b.py:2: error: Cannot determine type of "y" -c.py:2: note: Revealed type is "Any" +c.py:2: note: Revealed type is "a.A" == c.py:2: note: Revealed type is "a." From e242b5e59367664b4ec656d863f2861c401af7fe Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 20 Feb 2025 19:14:55 +0900 Subject: [PATCH 11/40] Narrow `isinstance` checks to `Never` --- mypy/checker.py | 4 +- mypy/checkexpr.py | 4 +- test-data/unit/check-enum.test | 41 ++++++--------- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-expressions.test | 2 +- test-data/unit/check-incremental.test | 8 ++- test-data/unit/check-isinstance.test | 54 ++++++++------------ test-data/unit/check-narrowing.test | 42 +++++++-------- test-data/unit/check-optional.test | 2 +- test-data/unit/check-possibly-undefined.test | 2 +- test-data/unit/check-protocols.test | 12 ++--- test-data/unit/check-python310.test | 15 +++--- test-data/unit/check-python38.test | 2 +- test-data/unit/check-typeis.test | 8 ++- test-data/unit/check-typevar-tuple.test | 6 +-- test-data/unit/check-typevar-values.test | 4 +- test-data/unit/check-unreachable-code.test | 34 ++++++------ test-data/unit/fine-grained.test | 8 ++- 18 files changed, 109 insertions(+), 141 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7c1ee28091d8..414c866ddb00 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8000,9 +8000,7 @@ def conditional_types_to_typemaps( maps: list[TypeMap] = [] for typ in (yes_type, no_type): proper_type = get_proper_type(typ) - if isinstance(proper_type, UninhabitedType): - maps.append(None) - elif proper_type is None: + if proper_type is None: maps.append({}) else: assert typ is not None diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 58175f945e63..69db9ac04c6d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3548,9 +3548,7 @@ def visit_op_expr(self, e: OpExpr) -> Type: allow_reverse=False, ) else: - # TODO: fix this bug with enum narrowing - # somehow the `use_reverse is UseReverse.ALWAYS` doesn't narrow. - assert_never(use_reverse) # type: ignore[arg-type] + assert_never(use_reverse) e.method_type = method_type return result else: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 3130ccc27de0..dd649c05e912 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -888,9 +888,8 @@ elif x is Foo.B: elif x is Foo.C: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - # TODO: this should narrow to Never - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.C]" + reveal_type(x) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" if Foo.A is x: @@ -900,8 +899,8 @@ elif Foo.B is x: elif Foo.C is x: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.C]" + reveal_type(x) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" y: Foo @@ -912,8 +911,7 @@ elif y is Foo.B: elif y is Foo.C: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.C]" + reveal_type(y) # N: Revealed type is "Never" reveal_type(y) # N: Revealed type is "__main__.Foo" if Foo.A is y: @@ -923,8 +921,8 @@ elif Foo.B is y: elif Foo.C is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.C]" + reveal_type(y) # N: Revealed type is "Never" + _ = "unreachable" # E: Statement is unreachable reveal_type(y) # N: Revealed type is "__main__.Foo" [builtins fixtures/bool.pyi] @@ -946,8 +944,7 @@ if x is Foo.A: elif x is Foo.B: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.B]" + reveal_type(x) # N: Revealed type is "Never" class Bar(Enum): __order__ = "A B" @@ -962,8 +959,7 @@ if y is Bar.A: elif y is Bar.B: reveal_type(y) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Bar.B]" + reveal_type(y) # N: Revealed type is "Never" x2: Foo if x2 is Foo.A: @@ -971,8 +967,7 @@ if x2 is Foo.A: elif x2 is Foo.B: reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x2) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.B]" + reveal_type(x2) # N: Revealed type is "Never" y2: Bar if y2 is Bar.A: @@ -980,8 +975,7 @@ if y2 is Bar.A: elif y2 is Bar.B: reveal_type(y2) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y2) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Bar.B]" + reveal_type(y2) # N: Revealed type is "Never" [builtins fixtures/tuple.pyi] [case testEnumReachabilityChecksIndirect] @@ -1039,17 +1033,17 @@ if y is z: reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" accepts_foo_a(z) else: - reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.A]" - reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" + reveal_type(y) # N: Revealed type is "Never" + reveal_type(z) # E: Statement is unreachable \ + # N: Revealed type is "Literal[__main__.Foo.A]?" if z is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A]" reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" accepts_foo_a(z) else: - reveal_type(y) # E: Statement is unreachable \ + reveal_type(y) # E: Statement is unreachable \ # N: Revealed type is "Literal[__main__.Foo.A]" - reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" + reveal_type(z) # N: Revealed type is "Never" [builtins fixtures/bool.pyi] [case testEnumReachabilityNoNarrowingForUnionMessiness] @@ -2305,8 +2299,7 @@ if e == MyEnum.A: elif e == MyEnum.B: reveal_type(e) # N: Revealed type is "Literal[__main__.MyEnum.B]" else: - reveal_type(e) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.MyEnum.B]" + reveal_type(e) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 6ec246fb3a13..b2d4436aa054 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -805,7 +805,7 @@ g = 3 if True else 4 # E: If condition is always true [redu h = 3 if False else 4 # E: If condition is always false [redundant-expr] i = [x for x in lst if True] # E: If condition in comprehension is always true [redundant-expr] j = [x for x in lst if False] # E: If condition in comprehension is always false [redundant-expr] -k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true [redundant-expr] +k = [x for x in lst if isinstance(x, int) or foo()] [builtins fixtures/isinstancelist.pyi] [case testRedundantExprTruthiness] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 81eb4c7c0dc8..d5d9b4e76444 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1506,7 +1506,7 @@ z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not retu from typing import TypeVar T = TypeVar("T", int, str) def foo(x: T) -> T: - return x + 1 if isinstance(x, int) else x + "a" + return x + 1 if isinstance(x, int) else x + "a" # E: Unsupported left operand type for + ("Never") [builtins fixtures/isinstancelist.pyi] -- Special cases diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 9b0d71d5d50e..b25a8318649d 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5415,7 +5415,9 @@ reveal_type(z) [out] tmp/c.py:2: note: Revealed type is "a." [out2] -tmp/c.py:2: note: Revealed type is "a.A" +tmp/a.py:7: error: Need type annotation for "y" +tmp/b.py:2: error: Need type annotation for "z" +tmp/c.py:2: note: Revealed type is "Never" [case testIsInstanceAdHocIntersectionIncrementalUnreachableToIntersection] import c @@ -5446,7 +5448,9 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -tmp/c.py:2: note: Revealed type is "a.A" +tmp/a.py:7: error: Need type annotation for "y" +tmp/b.py:2: error: Need type annotation for "z" +tmp/c.py:2: note: Revealed type is "Never" [out2] tmp/c.py:2: note: Revealed type is "a." diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 5b17b8fc0f54..f88cd4c0ed48 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -734,11 +734,9 @@ while bool(): x + 1 break # TODO: only report unreachability once - # TODO: narrow x to Never - elif isinstance(x, str): # E: Statement is unreachable \ - # E: Subclass of "int" and "str" cannot exist: would have incompatible method signatures + elif isinstance(x, str): # E: Statement is unreachable _ = "unreachable" # E: Statement is unreachable - x + 'a' # E: Unsupported operand types for + ("int" and "str") + x + 'a' # E: Unsupported left operand type for + ("Never") break _ = "unreachable" # E: Statement is unreachable x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ @@ -1275,7 +1273,8 @@ if isinstance(y, int) and isinstance(x, B): # E: Subclass of "A" and "int" cann # E: Right operand of "and" is never evaluated _ = "unreachable" # E: Statement is unreachable if isinstance(y, int) and y > 42: # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures \ - # E: Right operand of "and" is never evaluated + # E: Right operand of "and" is never evaluated \ + # E: Unsupported left operand type for > ("Never") _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstancelist.pyi] @@ -1370,9 +1369,9 @@ x = B() if isinstance(x, A): reveal_type(x) # N: Revealed type is "__main__.B" if not isinstance(x, A): - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.B" - x = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") + reveal_type(x) # N: Revealed type is "Never" + x = A() # E: Statement is unreachable \ + # E: Incompatible types in assignment (expression has type "A", variable has type "B") reveal_type(x) # N: Revealed type is "__main__.B" [builtins fixtures/isinstance.pyi] @@ -1537,8 +1536,7 @@ from typing import Type, Sequence, Union x: Type[str] if issubclass(x, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Type[builtins.str]" + reveal_type(x) # N: Revealed type is "Type[builtins.str]" class X: pass class Y(X): pass @@ -1548,8 +1546,7 @@ a: Union[Type[Y], Type[Z]] if issubclass(a, X): reveal_type(a) # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" else: - reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" + reveal_type(a) # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" [builtins fixtures/isinstancelist.pyi] [case testIssubclasDestructuringUnions1] @@ -1891,8 +1888,7 @@ def f(x: Type[int]) -> None: if isinstance(x, type): reveal_type(x) # N: Revealed type is "Type[builtins.int]" else: - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Type[builtins.int]" + reveal_type(x) # N: Revealed type is "Never" reveal_type(x) # N: Revealed type is "Type[builtins.int]" [builtins fixtures/isinstance.pyi] @@ -2392,8 +2388,7 @@ class C: class Example(A, B): pass # E: Definition of "f" in base class "A" is incompatible with definition in base class "B" x: A if isinstance(x, B): # E: Subclass of "A" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.A" @@ -2401,8 +2396,7 @@ y: C if isinstance(y, B): reveal_type(y) # N: Revealed type is "__main__." if isinstance(y, A): # E: Subclass of "C", "B", and "A" cannot exist: would have incompatible method signatures - reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "__main__." + reveal_type(y) # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionReversed] @@ -2431,7 +2425,7 @@ class B: def t0(self) -> None: if isinstance(self, A0): # E: Subclass of "B" and "A0" cannot exist: would have incompatible method signatures x0: Literal[0] = self.f() # E: Statement is unreachable \ - # E: Incompatible types in assignment (expression has type "Literal[1, 2]", variable has type "Literal[0]") + # E: "Never" has no attribute "f" def t1(self) -> None: if isinstance(self, A1): @@ -2468,8 +2462,7 @@ class B: x: A[int] if isinstance(x, B): # E: Subclass of "A[int]" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.A[builtins.int]" + reveal_type(x) # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.A[builtins.int]" @@ -2509,9 +2502,9 @@ def f1(x: T1) -> T1: # N: Revealed type is "__main__." else: reveal_type(x) # N: Revealed type is "__main__.A" \ - # N: Revealed type is "__main__." + # N: Revealed type is "Never" else: - reveal_type(x) # N: Revealed type is "__main__.A" \ + reveal_type(x) # N: Revealed type is "Never" \ # N: Revealed type is "__main__.B" return x @@ -2519,19 +2512,18 @@ T2 = TypeVar('T2', B, C) def f2(x: T2) -> T2: if isinstance(x, B): reveal_type(x) # N: Revealed type is "__main__.B" \ - # N: Revealed type is "__main__.C" + # N: Revealed type is "Never" # Note: even though --warn-unreachable is set, we don't report # errors for the below: we don't yet have a way of filtering out # reachability errors that occur for only one variation of the # TypeVar yet. if isinstance(x, C): - reveal_type(x) # N: Revealed type is "__main__.B" \ - # N: Revealed type is "__main__.C" + reveal_type(x) # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.B" \ - # N: Revealed type is "__main__.C" + # N: Revealed type is "Never" else: - reveal_type(x) # N: Revealed type is "__main__.B" \ + reveal_type(x) # N: Revealed type is "Never" \ # N: Revealed type is "__main__.C" return x @@ -2648,8 +2640,7 @@ class B(Y, X): pass foo: A if isinstance(foo, B): # E: Subclass of "A" and "B" cannot exist: would have inconsistent method resolution order - reveal_type(foo) # E: Statement is unreachable \ - # N: Revealed type is "__main__.A" + reveal_type(foo) # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionAmbiguousClass] @@ -2683,8 +2674,7 @@ x: Type[A] if issubclass(x, B): reveal_type(x) # N: Revealed type is "Type[__main__.]" if issubclass(x, C): # E: Subclass of "A", "B", and "C" cannot exist: would have incompatible method signatures - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Type[__main__.]" + reveal_type(x) # N: Revealed type is "Type[__main__.]" else: reveal_type(x) # N: Revealed type is "Type[__main__.]" else: diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index c6ff6da81671..5234ad9ef993 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -902,8 +902,8 @@ else: # No contamination here if 1 == x == z: # E: Non-overlapping equality check (left operand type: "Optional[Literal[1, 2]]", right operand type: "Default") reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[Literal[1], Literal[2], None]" - reveal_type(z) # N: Revealed type is "__main__.Default" + # N: Revealed type is "Literal[1]" + reveal_type(z) # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[Literal[1], Literal[2], None]" reveal_type(z) # N: Revealed type is "__main__.Default" @@ -920,8 +920,8 @@ c: Literal[2, 3] if a == b == c: reveal_type(a) # E: Statement is unreachable \ # N: Revealed type is "Literal[1]" - reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" - reveal_type(c) # N: Revealed type is "Union[Literal[2], Literal[3]]" + reveal_type(b) # N: Revealed type is "Literal[1]" + reveal_type(c) # N: Revealed type is "Never" else: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" @@ -930,15 +930,14 @@ else: if a == a == a: reveal_type(a) # N: Revealed type is "Literal[1]" else: - reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "Literal[1]" + reveal_type(a) # N: Revealed type is "Never" if a == a == b: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Literal[1]" else: reveal_type(a) # N: Revealed type is "Literal[1]" - reveal_type(b) # N: Revealed type is "Literal[2]" + reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" # In this case, it's ok for 'b' to narrow down to Literal[1] in the else case # since that's the only way 'b == 2' can be false @@ -993,9 +992,10 @@ elif a == a == 4: else: # In contrast, this branch must be unreachable: we assume (maybe naively) # that 'a' won't be mutated in the middle of the expression. - reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "Literal[4]" - reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" + reveal_type(a) # N: Revealed type is "Never" + reveal_type(b) # E: Statement is unreachable \ + # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" + [builtins fixtures/primitives.pyi] [case testNarrowingLiteralTruthiness] @@ -1050,25 +1050,21 @@ f1: F1 if isinstance(f1, F1): reveal_type(f1) # N: Revealed type is "__main__.F1" else: - reveal_type(f1) # E: Statement is unreachable \ - # N: Revealed type is "__main__.F1" + reveal_type(f1) # N: Revealed type is "Never" if isinstance(n, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final - reveal_type(n) # E: Statement is unreachable \ - # N: Revealed type is "__main__.N" + reveal_type(n) # N: Revealed type is "Never" else: reveal_type(n) # N: Revealed type is "__main__.N" if isinstance(f1, N): # E: Subclass of "F1" and "N" cannot exist: "F1" is final - reveal_type(f1) # E: Statement is unreachable \ - # N: Revealed type is "__main__.F1" + reveal_type(f1) # N: Revealed type is "Never" else: reveal_type(f1) # N: Revealed type is "__main__.F1" if isinstance(f1, F2): # E: Subclass of "F1" and "F2" cannot exist: "F1" is final \ # E: Subclass of "F1" and "F2" cannot exist: "F2" is final - reveal_type(f1) # E: Statement is unreachable \ - # N: Revealed type is "__main__.F1" + reveal_type(f1) # N: Revealed type is "Never" else: reveal_type(f1) # N: Revealed type is "__main__.F1" [builtins fixtures/isinstance.pyi] @@ -1097,8 +1093,7 @@ else: if isinstance(n_f2, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F2" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F1" is final - reveal_type(n_f2) # E: Statement is unreachable \ - # N: Revealed type is "Union[__main__.N, __main__.F2]" + reveal_type(n_f2) # N: Revealed type is "Never" else: reveal_type(n_f2) # N: Revealed type is "Union[__main__.N, __main__.F2]" @@ -1343,7 +1338,7 @@ def unreachable(x: Union[str, List[str]]) -> None: elif isinstance(x, list): reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" else: - reveal_type(x) # No output: this branch is unreachable # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(x) # No output: this branch is unreachable # N: Revealed type is "Never" def all_parts_covered(x: Union[str, List[str], List[int], int]) -> None: if isinstance(x, str): @@ -2109,8 +2104,7 @@ if isinstance(x, (Y, Z, NoneType)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" is final \ # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.X" + reveal_type(x) # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] @@ -2368,7 +2362,7 @@ def fn_while(arg: T) -> None: x = None for _ in range(2): if x is not None: - reveal_type(x) # N: Revealed type is "None" \ + reveal_type(x) # N: Revealed type is "Never" \ # N: Revealed type is "builtins.int" x = 1 reveal_type(x) # N: Revealed type is "Union[builtins.int, None]" diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 5ed4c15f470e..b3dfe212d129 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -585,7 +585,7 @@ x is not None and x + '42' # E: Unsupported operand types for + ("int" and "str from typing import Optional x: None = None -x is not None and x + 42 +x is not None and x + 42 # E: Unsupported left operand type for + ("Never") [builtins fixtures/isinstance.pyi] [case testOptionalLambdaInference] diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 235d293ab866..ae277949c049 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -1040,6 +1040,6 @@ def foo(x: Union[int, str]) -> None: elif isinstance(x, int): f = "bar" else: - assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" + assert_never(x) f # OK [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 2a1d682fb1c9..bc748e727ad9 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1743,20 +1743,17 @@ c = C() if isinstance(c, P1): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # E: Statement is unreachable \ - # N: Revealed type is "__main__.C" + reveal_type(c) # N: Revealed type is "Never" if isinstance(c, P): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # E: Statement is unreachable \ - # N: Revealed type is "__main__.C" + reveal_type(c) # N: Revealed type is "Never" c1i: C1[int] if isinstance(c1i, P1): reveal_type(c1i) # N: Revealed type is "__main__.C1[builtins.int]" else: - reveal_type(c1i) # E: Statement is unreachable \ - # N: Revealed type is "__main__.C1[builtins.int]" + reveal_type(c1i) # N: Revealed type is "Never" if isinstance(c1i, P): reveal_type(c1i) # N: Revealed type is "__main__." else: @@ -1764,8 +1761,7 @@ else: c1s: C1[str] if isinstance(c1s, P1): # E: Subclass of "C1[str]" and "P1" cannot exist: would have incompatible method signatures - reveal_type(c1s) # E: Statement is unreachable \ - # N: Revealed type is "__main__.C1[builtins.str]" + reveal_type(c1s) # N: Revealed type is "Never" else: reveal_type(c1s) # N: Revealed type is "__main__.C1[builtins.str]" diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 1e3f242bc973..9d1116a31e77 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1370,8 +1370,7 @@ m: str match m: case a if isinstance(a, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures - reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "builtins.str" + reveal_type(a) # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] -- Exhaustiveness -- @@ -1492,7 +1491,7 @@ def f(value: Literal[1] | Literal[2]) -> int: case 2: return 1 case o: - assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "Literal[2]"; expected "Never" + assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "Literal[1, 2]"; expected "Never" [typing fixtures/typing-medium.pyi] [case testMatchSequencePatternNegativeNarrowing] @@ -1566,7 +1565,7 @@ def f(m: Medal) -> None: always_assigned = 1 reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" case _: - assert_never(m) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" + assert_never(m) reveal_type(always_assigned) # N: Revealed type is "builtins.int" [builtins fixtures/bool.pyi] @@ -1632,7 +1631,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Literal[Medal.bronze]"; expected "Never" + assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" [builtins fixtures/tuple.pyi] @@ -1652,7 +1651,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Literal[Medal.bronze]"; expected "Never" + assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" [builtins fixtures/enum.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] @@ -2319,7 +2318,7 @@ def f3(e: int | str | bytes) -> int: return 0 # E: Statement is unreachable case str(x): return 0 - reveal_type(e) # N: Revealed type is "builtins.bytes" + reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.bytes]" return 0 def f4(e: int | str | bytes) -> int: @@ -2330,7 +2329,7 @@ def f4(e: int | str | bytes) -> int: return 0 # E: Statement is unreachable case x if isinstance(x, str): return 0 - reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.bytes]" + reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.str, builtins.bytes]" return 0 [builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 5e7f28586e39..9986e1520784 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -697,7 +697,7 @@ def f1() -> None: x = 1 if (x := d[x]) is None: # E: Name "x" may be undefined - y = x + y = x # E: Need type annotation for "y" z = x [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index 34f00f4b1881..c6cb1f0a724a 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -454,7 +454,7 @@ def g(x: object) -> None: ... def test(x: List[Any]) -> None: if not(f(x) or isinstance(x, A)): return - g(reveal_type(x)) # N: Revealed type is "Union[builtins.list[builtins.str], __main__.]" + g(reveal_type(x)) # N: Revealed type is "builtins.list[Any]" [builtins fixtures/tuple.pyi] [case testTypeIsMultipleCondition] @@ -844,15 +844,13 @@ def func3(val: Union[int, str]): if func1(val): reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" else: - reveal_type(val) # E: Statement is unreachable \ - # N: Revealed type is "Union[builtins.int, builtins.str]" + reveal_type(val) # N: Revealed type is "Never" def func4(val: int): if func1(val): reveal_type(val) # N: Revealed type is "builtins.int" else: - reveal_type(val) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + reveal_type(val) # N: Revealed type is "Never" [builtins fixtures/tuple.pyi] [case testTypeIsInOverloadsSameReturn] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index ea085190056c..bf2e75ae536f 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1273,8 +1273,7 @@ def test(d: A[int, str]) -> None: if isinstance(d, A): reveal_type(d) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" else: - reveal_type(d) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" + reveal_type(d) # N: Revealed type is "Never" class B(Generic[Unpack[TP]]): ... @@ -1282,8 +1281,7 @@ def test2(d: B[int, str]) -> None: if isinstance(d, B): reveal_type(d) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" else: - reveal_type(d) # E: Statement is unreachable \ - # N: Revealed type is "__main__.B[builtins.int, builtins.str]" + reveal_type(d) # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testVariadicTupleSubtyping] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index c3ab60d77a7e..3fa2356acd46 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -737,10 +737,10 @@ W = TypeVar("W", int, str) def fn(w: W) -> W: if type(w) is str: - reveal_type(w) # N: Revealed type is "builtins.int" \ + reveal_type(w) # N: Revealed type is "Never" \ # N: Revealed type is "builtins.str" elif type(w) is int: reveal_type(w) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + # N: Revealed type is "Never" return w [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 545f4b5e6289..418738b47ed9 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -624,9 +624,9 @@ class Child(Parent): def foo(self) -> int: reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Child" - return None # E: Incompatible return value type (got "None", expected "int") + reveal_type(self) # N: Revealed type is "Never" + return None # E: Statement is unreachable \ + # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 @@ -635,9 +635,9 @@ class Child(Parent): self = super(Child, self).something() reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Child" - return None # E: Incompatible return value type (got "None", expected "int") + reveal_type(self) # N: Revealed type is "Never" + return None # E: Statement is unreachable \ + # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 [builtins fixtures/isinstance.pyi] @@ -719,8 +719,7 @@ a: int if isinstance(a, int): reveal_type(a) # N: Revealed type is "builtins.int" else: - reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + reveal_type(a) # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow2] @@ -729,8 +728,7 @@ b: int while isinstance(b, int): reveal_type(b) # N: Revealed type is "builtins.int" else: - reveal_type(b) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + reveal_type(b) # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow3] @@ -738,8 +736,7 @@ else: def foo(c: int) -> None: reveal_type(c) # N: Revealed type is "builtins.int" assert not isinstance(c, int) - reveal_type(c) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + reveal_type(c) # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow4] @@ -974,16 +971,15 @@ def test1(x: T1) -> T1: if isinstance(x, int): reveal_type(x) # N: Revealed type is "T1`-1" else: - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "T1`-1" + reveal_type(x) # N: Revealed type is "Never" return x def test2(x: T2) -> T2: if isinstance(x, int): reveal_type(x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + # N: Revealed type is "Never" else: - reveal_type(x) # N: Revealed type is "builtins.int" \ + reveal_type(x) # N: Revealed type is "Never" \ # N: Revealed type is "builtins.str" if False: @@ -1002,9 +998,9 @@ class Test3(Generic[T2]): def func(self) -> None: if isinstance(self.x, int): reveal_type(self.x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + # N: Revealed type is "Never" else: - reveal_type(self.x) # N: Revealed type is "builtins.int" \ + reveal_type(self.x) # N: Revealed type is "Never" \ # N: Revealed type is "builtins.str" if False: @@ -1485,7 +1481,7 @@ def force_forward_ref() -> int: def f(value: None) -> None: x if value is not None: - assert_never(value) # E: Argument 1 to "assert_never" has incompatible type "None"; expected "Never" + assert_never(value) x = force_forward_ref() [builtins fixtures/exception.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 65eecd9e3ab8..8fb5da31c656 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9692,7 +9692,9 @@ reveal_type(z) [out] c.py:2: note: Revealed type is "a." == -c.py:2: note: Revealed type is "a.A" +c.py:2: note: Revealed type is "Never" +a.py:7: error: Need type annotation for "y" +b.py:2: error: Need type annotation for "z" [case testIsInstanceAdHocIntersectionFineGrainedIncrementalUnreachableToIntersection] import c @@ -9723,7 +9725,9 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -c.py:2: note: Revealed type is "a.A" +a.py:7: error: Need type annotation for "y" +b.py:2: error: Need type annotation for "z" +c.py:2: note: Revealed type is "Never" == c.py:2: note: Revealed type is "a." From 69efb41ad4b44e7d0c802a801355a1b5876ca641 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 20 Feb 2025 20:11:29 +0900 Subject: [PATCH 12/40] Don't treat `reveal_type()` as a no-op --- mypy/checker.py | 5 ++- test-data/unit/check-enum.test | 36 +++++++++++++--------- test-data/unit/check-isinstance.test | 21 ++++++++----- test-data/unit/check-narrowing.test | 27 ++++++++++------ test-data/unit/check-overloading.test | 32 +++++++++---------- test-data/unit/check-protocols.test | 12 +++++--- test-data/unit/check-python310.test | 3 +- test-data/unit/check-python38.test | 12 ++++---- test-data/unit/check-typeis.test | 6 ++-- test-data/unit/check-typevar-tuple.test | 6 ++-- test-data/unit/check-unreachable-code.test | 24 +++++++++------ 11 files changed, 109 insertions(+), 75 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 414c866ddb00..cbded8ce1bea 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -173,6 +173,7 @@ ANY_STRATEGY, MYPYC_NATIVE_INT_NAMES, OVERLOAD_NAMES, + REVEAL_TYPE_NAMES, AnyType, BoolTypeQuery, CallableType, @@ -3081,7 +3082,9 @@ def is_noop_for_reachability(self, s: Statement) -> bool: elif isinstance(s, ExpressionStmt): if isinstance(s.expr, EllipsisExpr): return True - elif isinstance(s.expr, CallExpr): + elif isinstance(s.expr, CallExpr) and not refers_to_fullname( + s.expr.callee, REVEAL_TYPE_NAMES + ): with self.expr_checker.msg.filter_errors(): typ = get_proper_type( self.expr_checker.accept( diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index dd649c05e912..0f96d2840c3d 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -888,8 +888,8 @@ elif x is Foo.B: elif x is Foo.C: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(x) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" if Foo.A is x: @@ -899,8 +899,8 @@ elif Foo.B is x: elif Foo.C is x: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(x) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.A], Literal[__main__.Foo.B], Literal[__main__.Foo.C]]" y: Foo @@ -911,7 +911,8 @@ elif y is Foo.B: elif y is Foo.C: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # N: Revealed type is "Never" + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(y) # N: Revealed type is "__main__.Foo" if Foo.A is y: @@ -921,8 +922,8 @@ elif Foo.B is y: elif Foo.C is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.C]" else: - reveal_type(y) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(y) # N: Revealed type is "__main__.Foo" [builtins fixtures/bool.pyi] @@ -944,7 +945,8 @@ if x is Foo.A: elif x is Foo.B: reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" class Bar(Enum): __order__ = "A B" @@ -959,7 +961,8 @@ if y is Bar.A: elif y is Bar.B: reveal_type(y) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y) # N: Revealed type is "Never" + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" x2: Foo if x2 is Foo.A: @@ -967,7 +970,8 @@ if x2 is Foo.A: elif x2 is Foo.B: reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.B]" else: - reveal_type(x2) # N: Revealed type is "Never" + reveal_type(x2) # E: Statement is unreachable \ + # N: Revealed type is "Never" y2: Bar if y2 is Bar.A: @@ -975,7 +979,8 @@ if y2 is Bar.A: elif y2 is Bar.B: reveal_type(y2) # N: Revealed type is "Literal[__main__.Bar.B]" else: - reveal_type(y2) # N: Revealed type is "Never" + reveal_type(y2) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/tuple.pyi] [case testEnumReachabilityChecksIndirect] @@ -1033,9 +1038,9 @@ if y is z: reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" accepts_foo_a(z) else: - reveal_type(y) # N: Revealed type is "Never" - reveal_type(z) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.Foo.A]?" + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" + reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" if z is y: reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A]" reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.A]?" @@ -2299,7 +2304,8 @@ if e == MyEnum.A: elif e == MyEnum.B: reveal_type(e) # N: Revealed type is "Literal[__main__.MyEnum.B]" else: - reveal_type(e) # N: Revealed type is "Never" + reveal_type(e) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index f88cd4c0ed48..ce4925cc7e3f 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1369,9 +1369,9 @@ x = B() if isinstance(x, A): reveal_type(x) # N: Revealed type is "__main__.B" if not isinstance(x, A): - reveal_type(x) # N: Revealed type is "Never" - x = A() # E: Statement is unreachable \ - # E: Incompatible types in assignment (expression has type "A", variable has type "B") + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" + x = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") reveal_type(x) # N: Revealed type is "__main__.B" [builtins fixtures/isinstance.pyi] @@ -1888,7 +1888,8 @@ def f(x: Type[int]) -> None: if isinstance(x, type): reveal_type(x) # N: Revealed type is "Type[builtins.int]" else: - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(x) # N: Revealed type is "Type[builtins.int]" [builtins fixtures/isinstance.pyi] @@ -2388,7 +2389,8 @@ class C: class Example(A, B): pass # E: Definition of "f" in base class "A" is incompatible with definition in base class "B" x: A if isinstance(x, B): # E: Subclass of "A" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.A" @@ -2396,7 +2398,8 @@ y: C if isinstance(y, B): reveal_type(y) # N: Revealed type is "__main__." if isinstance(y, A): # E: Subclass of "C", "B", and "A" cannot exist: would have incompatible method signatures - reveal_type(y) # N: Revealed type is "Never" + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionReversed] @@ -2462,7 +2465,8 @@ class B: x: A[int] if isinstance(x, B): # E: Subclass of "A[int]" and "B" cannot exist: would have incompatible method signatures - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.A[builtins.int]" @@ -2640,7 +2644,8 @@ class B(Y, X): pass foo: A if isinstance(foo, B): # E: Subclass of "A" and "B" cannot exist: would have inconsistent method resolution order - reveal_type(foo) # N: Revealed type is "Never" + reveal_type(foo) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionAmbiguousClass] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 5234ad9ef993..f7f38d0cd9d8 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -930,7 +930,8 @@ else: if a == a == a: reveal_type(a) # N: Revealed type is "Literal[1]" else: - reveal_type(a) # N: Revealed type is "Never" + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Never" if a == a == b: reveal_type(a) # N: Revealed type is "Literal[1]" @@ -992,9 +993,9 @@ elif a == a == 4: else: # In contrast, this branch must be unreachable: we assume (maybe naively) # that 'a' won't be mutated in the middle of the expression. - reveal_type(a) # N: Revealed type is "Never" - reveal_type(b) # E: Statement is unreachable \ - # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Never" + reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" [builtins fixtures/primitives.pyi] @@ -1050,21 +1051,25 @@ f1: F1 if isinstance(f1, F1): reveal_type(f1) # N: Revealed type is "__main__.F1" else: - reveal_type(f1) # N: Revealed type is "Never" + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "Never" if isinstance(n, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final - reveal_type(n) # N: Revealed type is "Never" + reveal_type(n) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(n) # N: Revealed type is "__main__.N" if isinstance(f1, N): # E: Subclass of "F1" and "N" cannot exist: "F1" is final - reveal_type(f1) # N: Revealed type is "Never" + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(f1) # N: Revealed type is "__main__.F1" if isinstance(f1, F2): # E: Subclass of "F1" and "F2" cannot exist: "F1" is final \ # E: Subclass of "F1" and "F2" cannot exist: "F2" is final - reveal_type(f1) # N: Revealed type is "Never" + reveal_type(f1) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(f1) # N: Revealed type is "__main__.F1" [builtins fixtures/isinstance.pyi] @@ -1093,7 +1098,8 @@ else: if isinstance(n_f2, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F2" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F1" is final - reveal_type(n_f2) # N: Revealed type is "Never" + reveal_type(n_f2) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(n_f2) # N: Revealed type is "Union[__main__.N, __main__.F2]" @@ -2104,7 +2110,8 @@ if isinstance(x, (Y, Z, NoneType)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" is final \ # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index b0bc6c8976e6..481383671eec 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -3730,8 +3730,8 @@ def test_narrow_int() -> None: c: str if int(): c = narrow_int(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3762,8 +3762,8 @@ def test_narrow_int() -> None: c: str if int(): c = narrow_int(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3795,8 +3795,8 @@ def test_narrow_none() -> None: c: None if int(): c = narrow_none(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3828,8 +3828,8 @@ def test_narrow_none() -> None: c: None if int(): c = narrow_none(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3861,8 +3861,8 @@ def test_narrow_none_v2() -> None: c: None if int(): c = narrow_none_v2(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3893,8 +3893,8 @@ def test_narrow_none_v2() -> None: c: None if int(): c = narrow_none_v2(c) - reveal_type(c) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3925,8 +3925,8 @@ def test() -> None: val2: A if int(): val2 = narrow_to_not_a(val2) - reveal_type(val2) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(val2) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] @@ -3955,8 +3955,8 @@ def test_v2(val: Union[A, B], val2: A) -> None: if int(): val2 = narrow_to_not_a_v2(val2) - reveal_type(val2) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(val2) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [typing fixtures/typing-medium.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index bc748e727ad9..287082e94d23 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1743,17 +1743,20 @@ c = C() if isinstance(c, P1): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # N: Revealed type is "Never" + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" if isinstance(c, P): reveal_type(c) # N: Revealed type is "__main__.C" else: - reveal_type(c) # N: Revealed type is "Never" + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" c1i: C1[int] if isinstance(c1i, P1): reveal_type(c1i) # N: Revealed type is "__main__.C1[builtins.int]" else: - reveal_type(c1i) # N: Revealed type is "Never" + reveal_type(c1i) # E: Statement is unreachable \ + # N: Revealed type is "Never" if isinstance(c1i, P): reveal_type(c1i) # N: Revealed type is "__main__." else: @@ -1761,7 +1764,8 @@ else: c1s: C1[str] if isinstance(c1s, P1): # E: Subclass of "C1[str]" and "P1" cannot exist: would have incompatible method signatures - reveal_type(c1s) # N: Revealed type is "Never" + reveal_type(c1s) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(c1s) # N: Revealed type is "__main__.C1[builtins.str]" diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 9d1116a31e77..c241bb51a40f 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1370,7 +1370,8 @@ m: str match m: case a if isinstance(a, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures - reveal_type(a) # N: Revealed type is "Never" + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] -- Exhaustiveness -- diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 9986e1520784..d5e7d275d468 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -486,8 +486,8 @@ else: reveal_type(x) # N: Revealed type is "builtins.str" if y := wrapper.always_false: - reveal_type(y) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(y) # N: Revealed type is "Literal[False]" @@ -510,8 +510,8 @@ reveal_type(x) # N: Revealed type is "builtins.str" def always_false() -> Literal[False]: ... if y := always_false(): - reveal_type(y) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(y) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(y) # N: Revealed type is "Literal[False]" @@ -520,8 +520,8 @@ reveal_type(y) # N: Revealed type is "Literal[False]" def always_false_with_parameter(x: int) -> Literal[False]: ... if z := always_false_with_parameter(5): - reveal_type(z) # N: Revealed type is "Never" - _ = "unreachable" # E: Statement is unreachable + reveal_type(z) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(z) # N: Revealed type is "Literal[False]" diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index c6cb1f0a724a..1b85f1c712de 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -844,13 +844,15 @@ def func3(val: Union[int, str]): if func1(val): reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" else: - reveal_type(val) # N: Revealed type is "Never" + reveal_type(val) # E: Statement is unreachable \ + # N: Revealed type is "Never" def func4(val: int): if func1(val): reveal_type(val) # N: Revealed type is "builtins.int" else: - reveal_type(val) # N: Revealed type is "Never" + reveal_type(val) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/tuple.pyi] [case testTypeIsInOverloadsSameReturn] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index bf2e75ae536f..b442cdefeff7 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1273,7 +1273,8 @@ def test(d: A[int, str]) -> None: if isinstance(d, A): reveal_type(d) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" else: - reveal_type(d) # N: Revealed type is "Never" + reveal_type(d) # E: Statement is unreachable \ + # N: Revealed type is "Never" class B(Generic[Unpack[TP]]): ... @@ -1281,7 +1282,8 @@ def test2(d: B[int, str]) -> None: if isinstance(d, B): reveal_type(d) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" else: - reveal_type(d) # N: Revealed type is "Never" + reveal_type(d) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testVariadicTupleSubtyping] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 418738b47ed9..bd0d8f64e4c4 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -624,9 +624,9 @@ class Child(Parent): def foo(self) -> int: reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) # N: Revealed type is "Never" - return None # E: Statement is unreachable \ - # E: Incompatible return value type (got "None", expected "int") + reveal_type(self) # E: Statement is unreachable \ + # N: Revealed type is "Never" + return None # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 @@ -635,9 +635,9 @@ class Child(Parent): self = super(Child, self).something() reveal_type(self) # N: Revealed type is "__main__.Child" if self is None: - reveal_type(self) # N: Revealed type is "Never" - return None # E: Statement is unreachable \ - # E: Incompatible return value type (got "None", expected "int") + reveal_type(self) # E: Statement is unreachable \ + # N: Revealed type is "Never" + return None # E: Incompatible return value type (got "None", expected "int") reveal_type(self) # N: Revealed type is "__main__.Child" return 3 [builtins fixtures/isinstance.pyi] @@ -719,7 +719,8 @@ a: int if isinstance(a, int): reveal_type(a) # N: Revealed type is "builtins.int" else: - reveal_type(a) # N: Revealed type is "Never" + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow2] @@ -728,7 +729,8 @@ b: int while isinstance(b, int): reveal_type(b) # N: Revealed type is "builtins.int" else: - reveal_type(b) # N: Revealed type is "Never" + reveal_type(b) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow3] @@ -736,7 +738,8 @@ else: def foo(c: int) -> None: reveal_type(c) # N: Revealed type is "builtins.int" assert not isinstance(c, int) - reveal_type(c) # N: Revealed type is "Never" + reveal_type(c) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testUnreachableFlagWithBadControlFlow4] @@ -971,7 +974,8 @@ def test1(x: T1) -> T1: if isinstance(x, int): reveal_type(x) # N: Revealed type is "T1`-1" else: - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" return x def test2(x: T2) -> T2: From f846af7b57694196ef15df3f24894b93dea3ec00 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Feb 2025 11:43:04 +0900 Subject: [PATCH 13/40] Ensure functions with typevar values don't get false positives --- mypy/checker.py | 7 +++- test-data/unit/check-callable.test | 11 +++---- test-data/unit/check-isinstance.test | 23 +++++-------- test-data/unit/check-typeddict.test | 6 ++-- test-data/unit/check-typevar-values.test | 38 ++++++++++------------ test-data/unit/check-unreachable-code.test | 18 ++++------ 6 files changed, 45 insertions(+), 58 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cbded8ce1bea..37e006b09e71 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3051,7 +3051,12 @@ def visit_block(self, b: Block) -> None: reported_unreachable = False for s in b.body: if not reported_unreachable and self.binder.is_unreachable(): - if not self.should_report_unreachable_issues(): + # TODO: should this guard against self.current_node_deferred too? + if self.binder.is_unreachable_warning_suppressed(): + # turns out in these cases we actually don't want to check code. + # for instance, type var values + break + elif not self.should_report_unreachable_issues(): reported_unreachable = True elif not self.is_noop_for_reachability(s): self.msg.unreachable_statement(s) diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index dd2bb82aa1c5..4ee9cce009d2 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -310,13 +310,12 @@ T = TypeVar('T', int, Callable[[], int], Union[str, Callable[[], str]]) def f(t: T) -> None: if callable(t): - reveal_type(t()) # N: Revealed type is "Any" \ - # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + reveal_type(t()) # N: Revealed type is "Any" \ + # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" else: - reveal_type(t) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "def () -> builtins.int" \ - # N: Revealed type is "builtins.str" + reveal_type(t) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" [builtins fixtures/callable.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index ce4925cc7e3f..414c0b90cc05 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2505,30 +2505,25 @@ def f1(x: T1) -> T1: reveal_type(x) # N: Revealed type is "__main__." \ # N: Revealed type is "__main__." else: - reveal_type(x) # N: Revealed type is "__main__.A" \ - # N: Revealed type is "Never" + reveal_type(x) # N: Revealed type is "__main__.A" else: - reveal_type(x) # N: Revealed type is "Never" \ - # N: Revealed type is "__main__.B" + reveal_type(x) # N: Revealed type is "__main__.B" return x T2 = TypeVar('T2', B, C) def f2(x: T2) -> T2: if isinstance(x, B): - reveal_type(x) # N: Revealed type is "__main__.B" \ - # N: Revealed type is "Never" + reveal_type(x) # N: Revealed type is "__main__.B" # Note: even though --warn-unreachable is set, we don't report # errors for the below: we don't yet have a way of filtering out # reachability errors that occur for only one variation of the # TypeVar yet. if isinstance(x, C): - reveal_type(x) # N: Revealed type is "Never" + reveal_type(x) else: - reveal_type(x) # N: Revealed type is "__main__.B" \ - # N: Revealed type is "Never" + reveal_type(x) # N: Revealed type is "__main__.B" else: - reveal_type(x) # N: Revealed type is "Never" \ - # N: Revealed type is "__main__.C" + reveal_type(x) # N: Revealed type is "__main__.C" return x @@ -2553,16 +2548,16 @@ def f1(x: T1) -> T1: # 'x' is a subclass of __main__.A and __main__.B return A() # E: Incompatible return value type (got "A", expected "B") else: - return B() # E: Incompatible return value type (got "B", expected "A") + return B() T2 = TypeVar('T2', B, C) def f2(x: T2) -> T2: if isinstance(x, B): # In contrast, it's impossible for a subclass of "B" and "C" to # exist, so this is fine - return B() # E: Incompatible return value type (got "B", expected "C") + return B() else: - return C() # E: Incompatible return value type (got "C", expected "B") + return C() [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionUsage] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index b0e03b2b1313..c5ebed57bbcd 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -2127,11 +2127,9 @@ TD = TypeVar('TD', D1, D2) def f(arg: TD) -> None: value: int if 'foo' in arg: - assert_type(arg['foo'], int) # E: Expression is of type "Any", not "int" \ - # E: TypedDict "D2" has no key "foo" + assert_type(arg['foo'], int) else: - assert_type(arg['bar'], int) # E: Expression is of type "Any", not "int" \ - # E: TypedDict "D1" has no key "bar" + assert_type(arg['bar'], int) [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 3fa2356acd46..990654f6589d 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -112,11 +112,11 @@ from typing import TypeVar T = TypeVar('T', int, str) def f(x: T) -> T: if isinstance(x, int): - return 2 # E: Incompatible return value type (got "int", expected "str") + return 2 return x def g(x: T) -> T: if isinstance(x, str): - return '' # E: Incompatible return value type (got "str", expected "int") + return '' return x def h(x: T) -> T: if isinstance(x, int): @@ -129,9 +129,9 @@ from typing import TypeVar T = TypeVar('T', int, str) def f(x: T) -> T: if isinstance(x, int): - return 2 # E: Incompatible return value type (got "int", expected "str") + return 2 else: - return '' # E: Incompatible return value type (got "str", expected "int") + return '' def g(x: T) -> T: if isinstance(x, int): return '' # E: Incompatible return value type (got "str", expected "int") @@ -147,8 +147,8 @@ def f(x: T) -> T: if isinstance(x, int): y = 1 else: - y = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") - return y # E: Incompatible return value type (got "int", expected "str") + y = '' + return y [builtins fixtures/isinstance.pyi] [case testIsinstanceAndTypeVarValues4] @@ -158,8 +158,8 @@ def f(x: T) -> T: if isinstance(x, int): y = 1 else: - y = object() # E: Incompatible types in assignment (expression has type "object", variable has type "int") - return y # E: Incompatible return value type (got "int", expected "str") + y = object() + return y # E: Incompatible return value type (got "object", expected "str") [builtins fixtures/isinstance.pyi] [case testIsinstanceAndTypeVarValues5] @@ -186,10 +186,9 @@ def f1(x: T1) -> None: x = y x = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") else: - x = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + x = B() x = y - x.foo() # E: "A" has no attribute "foo" \ - # E: "B" has no attribute "foo" + x.foo() # E: "B" has no attribute "foo" class C: field: int @@ -201,12 +200,11 @@ def f2(x: T2) -> None: if isinstance(x, C): # C and D are non-overlapping, so this branch is never checked x = y - x = C() # E: Incompatible types in assignment (expression has type "C", variable has type "D") + x = C() else: - x = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C") + x = D() x = y - x.foo() # E: "C" has no attribute "foo" \ - # E: "D" has no attribute "foo" + x.foo() # E: "D" has no attribute "foo" S = TypeVar('S', int, str) def g(x: S) -> None: @@ -224,11 +222,11 @@ def f(x: T) -> None: if isinstance(x, S): # This is checked only when type of x is str. x = y - x = S() # E: Incompatible types in assignment (expression has type "S", variable has type "int") + x = S() x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "S") else: x = y - x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "S") + x = 1 x = S() # E: Incompatible types in assignment (expression has type "S", variable has type "int") [builtins fixtures/isinstance.pyi] @@ -737,10 +735,8 @@ W = TypeVar("W", int, str) def fn(w: W) -> W: if type(w) is str: - reveal_type(w) # N: Revealed type is "Never" \ - # N: Revealed type is "builtins.str" + reveal_type(w) # N: Revealed type is "builtins.str" elif type(w) is int: - reveal_type(w) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "Never" + reveal_type(w) # N: Revealed type is "builtins.int" return w [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index bd0d8f64e4c4..e35b49d0473b 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -980,19 +980,16 @@ def test1(x: T1) -> T1: def test2(x: T2) -> T2: if isinstance(x, int): - reveal_type(x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "Never" + reveal_type(x) # N: Revealed type is "builtins.int" else: - reveal_type(x) # N: Revealed type is "Never" \ - # N: Revealed type is "builtins.str" + reveal_type(x) # N: Revealed type is "builtins.str" if False: # This is unreachable, but we don't report an error, unfortunately. # The presence of the TypeVar with values unfortunately currently shuts # down type-checking for this entire function. # TODO: Find a way of removing this limitation - reveal_type(x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + reveal_type(x) return x @@ -1001,16 +998,13 @@ class Test3(Generic[T2]): def func(self) -> None: if isinstance(self.x, int): - reveal_type(self.x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "Never" + reveal_type(self.x) # N: Revealed type is "builtins.int" else: - reveal_type(self.x) # N: Revealed type is "Never" \ - # N: Revealed type is "builtins.str" + reveal_type(self.x) # N: Revealed type is "builtins.str" if False: # Same issue as above - reveal_type(self.x) # N: Revealed type is "builtins.int" \ - # N: Revealed type is "builtins.str" + reveal_type(self.x) class Test4(Generic[T3]): From 390ade859652d2db685268a9f70d1ea9955b53eb Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Feb 2025 12:08:52 +0900 Subject: [PATCH 14/40] Ensure that `.x` is `Never` --- mypy/checkmember.py | 3 +++ test-data/unit/check-expressions.test | 2 +- test-data/unit/check-isinstance.test | 6 +++--- test-data/unit/check-optional.test | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0994d0df400b..20d512c7c6bb 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -70,6 +70,7 @@ TypeVarLikeType, TypeVarTupleType, TypeVarType, + UninhabitedType, UnionType, get_proper_type, ) @@ -253,6 +254,8 @@ def _analyze_member_access( elif isinstance(typ, DeletedType): mx.msg.deleted_as_rvalue(typ, mx.context) return AnyType(TypeOfAny.from_error) + elif isinstance(typ, UninhabitedType): + return UninhabitedType() return report_missing_attribute(mx.original_type, typ, name, mx) diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index d5d9b4e76444..56030944bd7f 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1506,7 +1506,7 @@ z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not retu from typing import TypeVar T = TypeVar("T", int, str) def foo(x: T) -> T: - return x + 1 if isinstance(x, int) else x + "a" # E: Unsupported left operand type for + ("Never") + return x + 1 if isinstance(x, int) else x + "a" # E: "Never" not callable [builtins fixtures/isinstancelist.pyi] -- Special cases diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 414c0b90cc05..3c780912a317 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -736,7 +736,7 @@ while bool(): # TODO: only report unreachability once elif isinstance(x, str): # E: Statement is unreachable _ = "unreachable" # E: Statement is unreachable - x + 'a' # E: Unsupported left operand type for + ("Never") + x + 'a' # E: "Never" not callable break _ = "unreachable" # E: Statement is unreachable x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ @@ -1274,7 +1274,7 @@ if isinstance(y, int) and isinstance(x, B): # E: Subclass of "A" and "int" cann _ = "unreachable" # E: Statement is unreachable if isinstance(y, int) and y > 42: # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures \ # E: Right operand of "and" is never evaluated \ - # E: Unsupported left operand type for > ("Never") + # E: "Never" not callable _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstancelist.pyi] @@ -2428,7 +2428,7 @@ class B: def t0(self) -> None: if isinstance(self, A0): # E: Subclass of "B" and "A0" cannot exist: would have incompatible method signatures x0: Literal[0] = self.f() # E: Statement is unreachable \ - # E: "Never" has no attribute "f" + # E: "Never" not callable def t1(self) -> None: if isinstance(self, A1): diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index b3dfe212d129..c6642913a822 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -585,7 +585,7 @@ x is not None and x + '42' # E: Unsupported operand types for + ("int" and "str from typing import Optional x: None = None -x is not None and x + 42 # E: Unsupported left operand type for + ("Never") +x is not None and x + 42 # E: "Never" not callable [builtins fixtures/isinstance.pyi] [case testOptionalLambdaInference] From f3ba1494689364d50b8c11c45110bb8102a5149f Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Feb 2025 12:19:40 +0900 Subject: [PATCH 15/40] Ensure that `()` is `Never` --- mypy/checkexpr.py | 3 +++ test-data/unit/check-expressions.test | 2 +- test-data/unit/check-isinstance.test | 8 +++----- test-data/unit/check-optional.test | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 69db9ac04c6d..08a9027ea563 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1638,6 +1638,9 @@ def check_call( object_type, original_type=callee, ) + elif isinstance(callee, UninhabitedType): + self.infer_arg_types_in_empty_context(args) + return (UninhabitedType(), UninhabitedType()) else: return self.msg.not_callable(callee, context), AnyType(TypeOfAny.from_error) diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 56030944bd7f..81eb4c7c0dc8 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1506,7 +1506,7 @@ z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not retu from typing import TypeVar T = TypeVar("T", int, str) def foo(x: T) -> T: - return x + 1 if isinstance(x, int) else x + "a" # E: "Never" not callable + return x + 1 if isinstance(x, int) else x + "a" [builtins fixtures/isinstancelist.pyi] -- Special cases diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 3c780912a317..f724380fd86b 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -736,7 +736,7 @@ while bool(): # TODO: only report unreachability once elif isinstance(x, str): # E: Statement is unreachable _ = "unreachable" # E: Statement is unreachable - x + 'a' # E: "Never" not callable + x + 'a' break _ = "unreachable" # E: Statement is unreachable x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ @@ -1273,8 +1273,7 @@ if isinstance(y, int) and isinstance(x, B): # E: Subclass of "A" and "int" cann # E: Right operand of "and" is never evaluated _ = "unreachable" # E: Statement is unreachable if isinstance(y, int) and y > 42: # E: Subclass of "A" and "int" cannot exist: would have incompatible method signatures \ - # E: Right operand of "and" is never evaluated \ - # E: "Never" not callable + # E: Right operand of "and" is never evaluated _ = "unreachable" # E: Statement is unreachable [builtins fixtures/isinstancelist.pyi] @@ -2427,8 +2426,7 @@ class B: def t0(self) -> None: if isinstance(self, A0): # E: Subclass of "B" and "A0" cannot exist: would have incompatible method signatures - x0: Literal[0] = self.f() # E: Statement is unreachable \ - # E: "Never" not callable + x0: Literal[0] = self.f() # E: Statement is unreachable def t1(self) -> None: if isinstance(self, A1): diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index c6642913a822..5ed4c15f470e 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -585,7 +585,7 @@ x is not None and x + '42' # E: Unsupported operand types for + ("int" and "str from typing import Optional x: None = None -x is not None and x + 42 # E: "Never" not callable +x is not None and x + 42 [builtins fixtures/isinstance.pyi] [case testOptionalLambdaInference] From 92a4428bf4f221810eefb7d197bbf4776aaf3908 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 12:37:11 +0900 Subject: [PATCH 16/40] Only report unreachability once --- mypy/binder.py | 7 +++++++ mypy/checker.py | 3 +++ test-data/unit/check-isinstance.test | 5 ++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 622284416702..763bae8f4046 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -66,6 +66,7 @@ def __init__(self, id: int, conditional_frame: bool = False) -> None: self.unreachable = False self.conditional_frame = conditional_frame self.suppress_unreachable_warnings = False + self.unreachable_warning_emitted = False def __repr__(self) -> str: return f"Frame({self.id}, {self.types}, {self.unreachable}, {self.conditional_frame})" @@ -186,6 +187,9 @@ def unreachable(self) -> None: def suppress_unreachable_warnings(self) -> None: self.frames[-1].suppress_unreachable_warnings = True + def emitted_unreachable_warning(self) -> None: + self.frames[-1].unreachable_warning_emitted = True + def get(self, expr: Expression) -> Type | None: key = literal_hash(expr) assert key is not None, "Internal error: binder tried to get non-literal" @@ -202,6 +206,9 @@ def is_unreachable(self) -> bool: def is_unreachable_warning_suppressed(self) -> bool: return any(f.suppress_unreachable_warnings for f in self.frames) + def is_unreachable_warning_emitted(self) -> bool: + return any(f.unreachable_warning_emitted for f in self.frames) + def cleanse(self, expr: Expression) -> None: """Remove all references to a Node from the binder.""" key = literal_hash(expr) diff --git a/mypy/checker.py b/mypy/checker.py index 37e006b09e71..afdd59bd8a97 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -467,6 +467,7 @@ def check_first_pass(self) -> None: reported_unreachable = True elif not self.is_noop_for_reachability(d): self.msg.unreachable_statement(d) + self.binder.emitted_unreachable_warning() reported_unreachable = True self.accept(d) @@ -3060,6 +3061,7 @@ def visit_block(self, b: Block) -> None: reported_unreachable = True elif not self.is_noop_for_reachability(s): self.msg.unreachable_statement(s) + self.binder.emitted_unreachable_warning() reported_unreachable = True self.accept(s) @@ -3069,6 +3071,7 @@ def should_report_unreachable_issues(self) -> bool: and self.options.warn_unreachable and not self.current_node_deferred and not self.binder.is_unreachable_warning_suppressed() + and not self.binder.is_unreachable_warning_emitted() ) def is_noop_for_reachability(self, s: Statement) -> bool: diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index f724380fd86b..ffc275772b2e 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -393,7 +393,7 @@ while 2: x.b # E: "A" has no attribute "b" break _ = "unreachable" # E: Statement is unreachable - x.b # E: Statement is unreachable + x.b [case testUnionTryFinally6] class A: pass @@ -733,9 +733,8 @@ while bool(): if isinstance(x, int): x + 1 break - # TODO: only report unreachability once elif isinstance(x, str): # E: Statement is unreachable - _ = "unreachable" # E: Statement is unreachable + _ = "unreachable" x + 'a' break _ = "unreachable" # E: Statement is unreachable From c76d422e9a4ac2f29fd2c23ade0f0613f31178c4 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 12:49:18 +0900 Subject: [PATCH 17/40] Narrow `callable` checks to `Never` --- mypy/checker.py | 14 +++++--------- test-data/unit/check-callable.test | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index afdd59bd8a97..e34da808c73e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5881,15 +5881,11 @@ def conditional_callable_type_map( callables, uncallables = self.partition_by_callable(current_type, unsound_partition=False) - if callables and uncallables: - callable_map = {expr: UnionType.make_union(callables)} if callables else None - uncallable_map = {expr: UnionType.make_union(uncallables)} if uncallables else None - return callable_map, uncallable_map - - elif callables: - return {}, None - - return None, {} + callable_map = {expr: UnionType.make_union(callables) if callables else UninhabitedType()} + uncallable_map = { + expr: UnionType.make_union(uncallables) if uncallables else UninhabitedType() + } + return callable_map, uncallable_map def conditional_types_for_iterable( self, item_type: Type, iterable_type: Type diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 4ee9cce009d2..300fd7bf0816 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -514,7 +514,7 @@ if callable(): # E: Missing positional argument "x" in call to "callable" fn = None if callable(fn): _ = "unreachable" # E: Statement is unreachable - fn() # E: "None" not callable + fn() [builtins fixtures/callable.pyi] From be1d524769052bf7f6f872d7a523fcf7754b3bf7 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 13:13:27 +0900 Subject: [PATCH 18/40] Address CI failures --- mypyc/test-data/run-singledispatch.test | 6 ------ test-data/unit/daemon.test | 11 ++++------- test-data/unit/pythoneval.test | 3 +++ test-data/unit/reports.test | 7 ++++--- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/mypyc/test-data/run-singledispatch.test b/mypyc/test-data/run-singledispatch.test index 61e4897c96d6..9a0f20b7db5a 100644 --- a/mypyc/test-data/run-singledispatch.test +++ b/mypyc/test-data/run-singledispatch.test @@ -347,11 +347,6 @@ def verify_typeinfo(stub: TypeInfo, a: MaybeMissing[Type[Any]], b: List[str]) -> yield Error('in TypeInfo') yield Error('hello') -@verify.register(TypeVarExpr) -def verify_typevarexpr(stub: TypeVarExpr, a: MaybeMissing[Any], b: List[str]) -> Iterator[Error]: - if False: - yield None - def verify_list(stub, a, b) -> List[str]: """Helper function that converts iterator of errors to list of messages""" return list(err.msg for err in verify(stub, a, b)) @@ -361,7 +356,6 @@ def test_verify() -> None: assert verify_list(MypyFile(), MISSING, ['a', 'b']) == ["shouldn't be missing"] assert verify_list(MypyFile(), 5, ['a', 'b']) == ['in TypeInfo', 'hello'] assert verify_list(TypeInfo(), str, ['a', 'b']) == ['in TypeInfo', 'hello'] - assert verify_list(TypeVarExpr(), 'a', ['x', 'y']) == [] [case testArgsInRegisteredImplNamedDifferentlyFromMainFunction] diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 7dfddd8f74df..af8124dd7cfc 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -463,8 +463,7 @@ $ dmypy inspect foo.py:1:2:3:4 Can't find expression at span 1:2:3:4 == Return code: 1 $ dmypy inspect foo.py:17:5:17:5 -No known type available for "NameExpr" (maybe unreachable or try --force-reload) -== Return code: 1 +"int" [file foo.py] from typing import Optional @@ -523,9 +522,8 @@ $ dmypy inspect foo.py:1:2 Can't find any expressions at position 1:2 == Return code: 1 $ dmypy inspect foo.py:11:5 --force-reload -No known type available for "NameExpr" (maybe unreachable) -No known type available for "OpExpr" (maybe unreachable) -== Return code: 1 +"int" +"int" [file foo.py] from typing import Optional @@ -547,8 +545,7 @@ $ dmypy check foo.py bar.py --export-types $ dmypy inspect foo.py:9:1 --show attrs --include-span --include-kind -vv NameExpr:9:1:9:1 -> {"foo.C": ["a", "x", "y"], "foo.B": ["a", "b"]} $ dmypy inspect foo.py:11:10 --show attrs -No known type available for "StrExpr" (maybe unreachable or try --force-reload) -== Return code: 1 +{"str": ["__add__", "__contains__", "__eq__", "__ge__", "__getitem__", "__getnewargs__", "__gt__", "__hash__", "__iter__", "__le__", "__len__", "__lt__", "__mod__", "__mul__", "__ne__", "__new__", "__rmul__", "capitalize", "casefold", "center", "count", "encode", "endswith", "expandtabs", "find", "format", "format_map", "index", "isalnum", "isalpha", "isascii", "isdecimal", "isdigit", "isidentifier", "islower", "isnumeric", "isprintable", "isspace", "istitle", "isupper", "join", "ljust", "lower", "lstrip", "maketrans", "partition", "removeprefix", "removesuffix", "replace", "rfind", "rindex", "rjust", "rpartition", "rsplit", "rstrip", "split", "splitlines", "startswith", "strip", "swapcase", "title", "translate", "upper", "zfill"], "Sequence": ["__contains__", "__getitem__", "__iter__", "__reversed__", "count", "index"], "Reversible": ["__reversed__"], "Collection": ["__len__"], "Iterable": ["__iter__"], "Container": ["__contains__"]} $ dmypy inspect foo.py:1:1 --show attrs Can't find any expressions at position 1:1 == Return code: 1 diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 0e0e2b1f344d..ff760ddfdaa4 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1481,8 +1481,10 @@ if isinstance(x, int): [out] _testIsInstanceAdHocIntersectionWithStrAndBytes.py:3: error: Subclass of "str" and "bytes" cannot exist: would have incompatible method signatures _testIsInstanceAdHocIntersectionWithStrAndBytes.py:4: error: Statement is unreachable +_testIsInstanceAdHocIntersectionWithStrAndBytes.py:4: note: Revealed type is "Never" _testIsInstanceAdHocIntersectionWithStrAndBytes.py:6: error: Subclass of "str" and "int" cannot exist: would have incompatible method signatures _testIsInstanceAdHocIntersectionWithStrAndBytes.py:7: error: Statement is unreachable +_testIsInstanceAdHocIntersectionWithStrAndBytes.py:7: note: Revealed type is "Never" [case testAsyncioFutureWait] # mypy: strict-optional @@ -1553,6 +1555,7 @@ if isinstance(obj, Awaitable): [out] _testSpecialTypingProtocols.py:6: note: Revealed type is "Tuple[builtins.int]" _testSpecialTypingProtocols.py:8: error: Statement is unreachable +_testSpecialTypingProtocols.py:8: note: Revealed type is "Never" [case testTypeshedRecursiveTypesExample] from typing import List, Union diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test index 82c3869bb855..8975d6323fe8 100644 --- a/test-data/unit/reports.test +++ b/test-data/unit/reports.test @@ -235,7 +235,8 @@ def bar(x): any_f(x) assert False - any_f(x) + any_f(x) @@ -296,10 +297,10 @@ def bar(x): [outfile report/any-exprs.txt] Name Anys Exprs Coverage --------------------------------- - i 1 6 83.33% + i 0 8 100.00% j 0 5 100.00% --------------------------------- -Total 1 11 90.91% +Total 0 13 100.00% [case testAnyExprReportHigherKindedTypesAreNotAny] # cmd: mypy --any-exprs-report report i.py From 13ad1cb54c01ee320201883ae4fbb653c8ff36d7 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 15:03:36 +0900 Subject: [PATCH 19/40] Narrow `match` captures to `Never` --- mypy/checker.py | 36 ++++++++++++++--------------- mypy/checkpattern.py | 5 ++-- test-data/unit/check-python310.test | 10 ++++---- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e34da808c73e..216031ae36d9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5485,24 +5485,24 @@ def visit_match_stmt(self, s: MatchStmt) -> None: ) pattern_type = self.pattern_checker.accept(p, current_subject_type) with self.binder.frame_context(can_skip=True, fall_through=2): - if b.is_unreachable or isinstance( - get_proper_type(pattern_type.type), UninhabitedType - ): - self.push_type_map(None, from_assignment=False) - else_map: TypeMap = {} - else: - pattern_map, else_map = conditional_types_to_typemaps( - named_subject, pattern_type.type, pattern_type.rest_type - ) - self.remove_capture_conflicts(pattern_type.captures, inferred_types) - self.push_type_map(pattern_map, from_assignment=False) - if pattern_map: - for expr, typ in pattern_map.items(): - self.push_type_map( - self._get_recursive_sub_patterns_map(expr, typ), - from_assignment=False, - ) - self.push_type_map(pattern_type.captures, from_assignment=False) + # TODO: if this following code is necessary, find a failing test case. + # if b.is_unreachable: + # self.push_type_map(None, from_assignment=False) + # else_map: TypeMap = {} + # else: + pattern_map, else_map = conditional_types_to_typemaps( + named_subject, pattern_type.type, pattern_type.rest_type + ) + self.remove_capture_conflicts(pattern_type.captures, inferred_types) + self.push_type_map(pattern_map, from_assignment=False) + if pattern_map: + for expr, typ in pattern_map.items(): + self.push_type_map( + self._get_recursive_sub_patterns_map(expr, typ), + from_assignment=False, + ) + self.push_type_map(pattern_type.captures, from_assignment=False) + if g is not None: with self.binder.frame_context(can_skip=False, fall_through=3): gt = get_proper_type(self.expr_checker.accept(g)) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 2a8620482d87..d426b084d889 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -140,12 +140,11 @@ def visit_as_pattern(self, o: AsPattern) -> PatternType: else: typ, rest_type, type_map = current_type, UninhabitedType(), {} - if not is_uninhabited(typ) and o.name is not None: + if o.name is not None: typ, _ = self.chk.conditional_types_with_intersection( current_type, [get_type_range(typ)], o, default=current_type ) - if not is_uninhabited(typ): - type_map[o.name] = typ + type_map[o.name] = typ return PatternType(typ, rest_type, type_map) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index c241bb51a40f..3ed7f5ab6c1d 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -33,7 +33,7 @@ m: int match m: case "str": reveal_type(m) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + # N: Revealed type is "Never" [builtins fixtures/primitives.pyi] -- Value Pattern -- @@ -81,7 +81,7 @@ m: int match m: case b.b: # E: Subclass of "int" and "str" cannot exist: would have incompatible method signatures reveal_type(m) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + # N: Revealed type is "Never" [file b.py] b: str [builtins fixtures/primitives.pyi] @@ -1492,7 +1492,7 @@ def f(value: Literal[1] | Literal[2]) -> int: case 2: return 1 case o: - assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "Literal[1, 2]"; expected "Never" + assert_never(o) [typing fixtures/typing-medium.pyi] [case testMatchSequencePatternNegativeNarrowing] @@ -1632,7 +1632,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" + assert_never(unreachable) [builtins fixtures/tuple.pyi] @@ -1652,7 +1652,7 @@ match m: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: - assert_never(unreachable) # E: Argument 1 to "assert_never" has incompatible type "Medal"; expected "Never" + assert_never(unreachable) [builtins fixtures/enum.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] From 53553e7af2520a26a4ffc0cf91aab032e9b12196 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 15:25:58 +0900 Subject: [PATCH 20/40] Don't check unreachable code with partial types --- mypy/checker.py | 6 ++---- test-data/unit/check-narrowing.test | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 216031ae36d9..16273cfd3446 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -599,13 +599,12 @@ def accept_loop( partials_old = sum(len(pts.map) for pts in self.partial_types) # Disable error types that we cannot safely identify in intermediate iteration steps: - warn_unreachable = self.options.warn_unreachable warn_redundant = codes.REDUNDANT_EXPR in self.options.enabled_error_codes - self.options.warn_unreachable = False self.options.enabled_error_codes.discard(codes.REDUNDANT_EXPR) while True: with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): + self.binder.suppress_unreachable_warnings() if on_enter_body is not None: on_enter_body() @@ -616,10 +615,9 @@ def accept_loop( partials_old = partials_new # If necessary, reset the modified options and make up for the postponed error checks: - self.options.warn_unreachable = warn_unreachable if warn_redundant: self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR) - if warn_unreachable or warn_redundant: + if self.options.warn_unreachable or warn_redundant: with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): if on_enter_body is not None: on_enter_body() diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index f7f38d0cd9d8..bb842ec4ec2d 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2369,8 +2369,7 @@ def fn_while(arg: T) -> None: x = None for _ in range(2): if x is not None: - reveal_type(x) # N: Revealed type is "Never" \ - # N: Revealed type is "builtins.int" + reveal_type(x) # N: Revealed type is "builtins.int" x = 1 reveal_type(x) # N: Revealed type is "Union[builtins.int, None]" From 6cc9930542b2d25a0258fd81d65976b13d5b680b Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 16:19:19 +0900 Subject: [PATCH 21/40] Avoid strange behavior regarding "narrowing" expressions for unreachability --- mypy/binder.py | 5 +++-- test-data/unit/check-unreachable-code.test | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 763bae8f4046..b31912d9224e 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -153,8 +153,6 @@ def push_frame(self, conditional_frame: bool = False) -> Frame: return f def _put(self, key: Key, type: Type, from_assignment: bool, index: int = -1) -> None: - if isinstance(get_proper_type(type), UninhabitedType): - self.frames[index].unreachable = True self.frames[index].types[key] = CurrentType(type, from_assignment) def _get(self, key: Key, index: int = -1) -> CurrentType | None: @@ -170,6 +168,9 @@ def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> N This is used for isinstance() etc. Assignments should go through assign_type(). """ + if isinstance(get_proper_type(typ), UninhabitedType): + self.frames[-1].unreachable = True + if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)): return if not literal(expr): diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index e35b49d0473b..ef782fac6bda 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1403,9 +1403,8 @@ from typing import Literal def nope() -> Literal[False]: ... def f() -> None: - # TODO: this should be unreachable if nope(): - x = 1 + x = 1 # E: Statement is unreachable [builtins fixtures/dict.pyi] [case testUnreachableLiteralFrom__bool__] @@ -1428,7 +1427,7 @@ else: x = 2 # E: Statement is unreachable if Lie(): - x = 3 + x = 3 # E: Statement is unreachable if Maybe(): x = 4 From 4ddd34ea9061b1036b51596aa0a51d2abac88427 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Mon, 24 Feb 2025 17:13:53 +0900 Subject: [PATCH 22/40] Unpack `Never` iterables --- mypy/checker.py | 6 ++++-- test-data/unit/check-unreachable-code.test | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 16273cfd3446..1bfccde75707 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4251,7 +4251,7 @@ def check_multi_assignment_from_iterable( ) -> None: rvalue_type = get_proper_type(rvalue_type) if self.type_is_iterable(rvalue_type) and isinstance( - rvalue_type, (Instance, CallableType, TypeType, Overloaded) + rvalue_type, (Instance, CallableType, TypeType, Overloaded, UninhabitedType) ): item_type = self.iterable_item_type(rvalue_type, context) for lv in lvalues: @@ -7556,7 +7556,9 @@ def note( self.msg.note(msg, context, offset=offset, code=code) def iterable_item_type( - self, it: Instance | CallableType | TypeType | Overloaded, context: Context + self, + it: Instance | CallableType | TypeType | Overloaded | UninhabitedType, + context: Context, ) -> Type: if isinstance(it, Instance): iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable")) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index ef782fac6bda..f1ab33c64168 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1535,3 +1535,16 @@ x = 0 # not unreachable f2: Callable[[], NoReturn] = lambda: foo() x = 0 # not unreachable + +[case testUnpackNever] +x: str +if isinstance(x, int): + # mypy wants a type annotation for y because it's `Never`. + # This doesn't make much sense in this case, but it's a + # requirement for composable behavior. + # (and `ys` should also get flagged.) + + y, *ys = x # E: Need type annotation for "y" + reveal_type(y) # N: Revealed type is "Never" + reveal_type(ys) # N: Revealed type is "builtins.list[Never]" +[builtins fixtures/isinstancelist.pyi] From 3cda7db8b3b99b09de82d79eec69fbe4ad774ad7 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 00:19:26 +0900 Subject: [PATCH 23/40] Narrow disjoint narrows to `Never` --- mypy/checker.py | 2 +- test-data/unit/check-enum.test | 10 +++++----- test-data/unit/check-narrowing.test | 6 ++++-- test-data/unit/check-unreachable-code.test | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1bfccde75707..0b6f96e16a7a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6704,7 +6704,7 @@ def should_coerce_inner(typ: Type) -> bool: if target and not is_same_type(target, expr_type): # We have multiple disjoint target types. So the 'if' branch # must be unreachable. - return None, {} + return {operands[j]: UninhabitedType() for j in chain_indices}, {} target = expr_type possible_target_indices.append(i) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 0f96d2840c3d..33ba1e39bbcf 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1325,7 +1325,7 @@ reveal_type(y) # N: Revealed type is "__main__.Foo" # The standard output when we end up inferring two disjoint facts about the same expr if x is Foo.A and x is Foo.B: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Foo" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1333,7 +1333,7 @@ reveal_type(x) # N: Revealed type is "__main__.Foo" # ..and we get the same result if we have two disjoint groups within the same comp expr if x is Foo.A < x is Foo.B: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Foo" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1352,7 +1352,7 @@ class Foo(Enum): x: Foo if x is Foo.A is Foo.B: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Foo" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1361,7 +1361,7 @@ literal_a: Literal[Foo.A] literal_b: Literal[Foo.B] if x is literal_a is literal_b: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Foo" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -1370,7 +1370,7 @@ final_a: Final = Foo.A final_b: Final = Foo.B if x is final_a is final_b: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "__main__.Foo" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "__main__.Foo" reveal_type(x) # N: Revealed type is "__main__.Foo" diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index bb842ec4ec2d..853f18c77741 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -728,7 +728,7 @@ def test2(switch: FlipFlopEnum) -> None: assert switch.state == State.B # E: Non-overlapping equality check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.State.A]" + # N: Revealed type is "Never" def test3(switch: FlipFlopEnum) -> None: # Same thing, but using 'is' comparisons. Previously mypy's behaviour differed @@ -741,7 +741,9 @@ def test3(switch: FlipFlopEnum) -> None: assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable \ - # N: Revealed type is "Literal[__main__.State.A]" + # N: Revealed type is "Never" + + [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitStrLiteral] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index f1ab33c64168..feb2a9eb89fc 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1365,7 +1365,7 @@ def test_typed_fn(obj) -> None: assert obj.prop is False reveal_type(obj.prop) # E: Statement is unreachable \ - # N: Revealed type is "Literal[True]" + # N: Revealed type is "Never" [case testUnreachableCheckedUntypedFunction] # flags: --warn-unreachable --check-untyped-defs @@ -1378,7 +1378,7 @@ def test_untyped_fn(obj): assert obj.prop is False reveal_type(obj.prop) # E: Statement is unreachable \ - # N: Revealed type is "Literal[True]" + # N: Revealed type is "Never" [case testConditionalTypeVarException] # every part of this test case was necessary to trigger the crash From 6e97289fa1cc0b662a85a931d7ea68cf7a8d553a Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 00:33:19 +0900 Subject: [PATCH 24/40] Fix `redundant-expr` for comprehensions --- mypy/checkexpr.py | 8 ++++++-- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-isinstance.test | 3 --- test-data/unit/check-literal.test | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 08a9027ea563..84904268ad86 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5808,9 +5808,13 @@ def check_for_comp(self, e: GeneratorExpr | DictionaryComprehension) -> None: self.chk.push_type_map(true_map) if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes: - if true_map is None: + if true_map is None or any( + isinstance(get_proper_type(t), UninhabitedType) for t in true_map.values() + ): self.msg.redundant_condition_in_comprehension(False, condition) - elif false_map is None: + elif false_map is None or any( + isinstance(get_proper_type(t), UninhabitedType) for t in false_map.values() + ): self.msg.redundant_condition_in_comprehension(True, condition) def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = False) -> Type: diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index b2d4436aa054..6ec246fb3a13 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -805,7 +805,7 @@ g = 3 if True else 4 # E: If condition is always true [redu h = 3 if False else 4 # E: If condition is always false [redundant-expr] i = [x for x in lst if True] # E: If condition in comprehension is always true [redundant-expr] j = [x for x in lst if False] # E: If condition in comprehension is always false [redundant-expr] -k = [x for x in lst if isinstance(x, int) or foo()] +k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true [redundant-expr] [builtins fixtures/isinstancelist.pyi] [case testRedundantExprTruthiness] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index ffc275772b2e..dc216a424d12 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2522,8 +2522,6 @@ def f2(x: T2) -> T2: else: reveal_type(x) # N: Revealed type is "__main__.C" return x - - [builtins fixtures/isinstance.pyi] [case testIsInstanceAdHocIntersectionGenericsWithValuesDirectReturn] @@ -2537,7 +2535,6 @@ class B: class C: attr: str -# TODO: these kinds of typevars play havoc with checking unreachable code T1 = TypeVar('T1', A, B) def f1(x: T1) -> T1: if isinstance(x, A): diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index f73270e584de..e5dfe7e17832 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2837,8 +2837,8 @@ w: Union[Truth, AlsoTruth] if w: reveal_type(w) # N: Revealed type is "Union[__main__.Truth, __main__.AlsoTruth]" else: - _ = "unreachable" # E: Statement is unreachable - + reveal_type(w) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/bool.pyi] [case testLiteralAndInstanceSubtyping] From 28894ce58e5fddf22d973e12a1d47231f0b0ba51 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 00:58:57 +0900 Subject: [PATCH 25/40] Narrow `issubclass` to `Never` --- mypy/checker.py | 7 ++++++- test-data/unit/check-isinstance.test | 9 ++++++--- test-data/unit/check-narrowing.test | 5 ++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0b6f96e16a7a..f9abc478f0f5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8221,11 +8221,16 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: t = typ if isinstance(t, TypeVarType): t = t.upper_bound + + if isinstance(get_proper_type(t), UninhabitedType): + converted_type_map[expr] = UninhabitedType() + continue # TODO: should we only allow unions of instances as per PEP 484? - if not isinstance(get_proper_type(t), (UnionType, Instance, NoneType)): + elif not isinstance(get_proper_type(t), (UnionType, Instance, NoneType)): # unknown type; error was likely reported earlier return {} converted_type_map[expr] = TypeType.make_normalized(typ) + return converted_type_map diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index dc216a424d12..ebcea85e142f 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1534,7 +1534,8 @@ from typing import Type, Sequence, Union x: Type[str] if issubclass(x, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures - reveal_type(x) # N: Revealed type is "Type[builtins.str]" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" class X: pass class Y(X): pass @@ -1544,7 +1545,8 @@ a: Union[Type[Y], Type[Z]] if issubclass(a, X): reveal_type(a) # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" else: - reveal_type(a) # N: Revealed type is "Union[Type[__main__.Y], Type[__main__.Z]]" + reveal_type(a) # E: Statement is unreachable \ + # N: Revealed type is "Never" [builtins fixtures/isinstancelist.pyi] [case testIssubclasDestructuringUnions1] @@ -2668,7 +2670,8 @@ x: Type[A] if issubclass(x, B): reveal_type(x) # N: Revealed type is "Type[__main__.]" if issubclass(x, C): # E: Subclass of "A", "B", and "C" cannot exist: would have incompatible method signatures - reveal_type(x) # N: Revealed type is "Type[__main__.]" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Type[__main__.]" else: diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 853f18c77741..5bc95943a420 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1126,11 +1126,10 @@ T = TypeVar("T", A, B) def f(cls: Type[T]) -> T: if issubclass(cls, A): - reveal_type(cls) # N: Revealed type is "Type[__main__.A]" \ - # N: Revealed type is "Type[__main__.B]" + reveal_type(cls) # N: Revealed type is "Type[__main__.A]" x: bool if x: - return A() # E: Incompatible return value type (got "A", expected "B") + return A() else: return B() # E: Incompatible return value type (got "B", expected "A") assert False From be6573ed304dd9e0104074458c658291afd73446 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 01:33:08 +0900 Subject: [PATCH 26/40] Narrow things based on key types even if the type is `Never` --- mypy/checker.py | 17 ++++++++------- test-data/unit/check-narrowing.test | 33 ++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f9abc478f0f5..d359ab003f77 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6093,7 +6093,7 @@ def find_isinstance_check( for example, some errors are suppressed. May return {}, {}. - Can return None, None in situations involving NoReturn. + Can return None, None in situations involving Never. """ if_map, else_map = self.find_isinstance_check_helper( node, in_boolean_context=in_boolean_context @@ -6628,6 +6628,7 @@ def replay_lookup(new_parent_type: ProperType) -> Type | None: # Take each element in the parent union and replay the original lookup procedure # to figure out which parents are compatible. new_parent_types = [] + expr_type_p = get_proper_type(expr_type) for item in flatten_nested_unions(parent_type.items): member_type = replay_lookup(get_proper_type(item)) if member_type is None: @@ -6635,14 +6636,14 @@ def replay_lookup(new_parent_type: ProperType) -> Type | None: # parent type entirely and abort. return output - if is_overlapping_types(member_type, expr_type): - new_parent_types.append(item) + # note: this is not unconditionally setting `UninhabitedType` + # as there might be X.a which is a Never + if isinstance(expr_type_p, UninhabitedType): + if isinstance(get_proper_type(member_type), UninhabitedType): + new_parent_types.append(item) - # If none of the parent types overlap (if we derived an empty union), something - # went wrong. We should never hit this case, but deriving the uninhabited type or - # reporting an error both seem unhelpful. So we abort. - if not new_parent_types: - return output + elif is_overlapping_types(member_type, expr_type): + new_parent_types.append(item) expr = parent_expr expr_type = output[parent_expr] = make_simplified_union(new_parent_types) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 5bc95943a420..7795ab45f91c 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -264,8 +264,8 @@ else: if x.key is Key.D: # TODO: this should narrow to Never - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" [builtins fixtures/tuple.pyi] @@ -291,8 +291,8 @@ else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" if x['key'] == 'D': - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" [builtins fixtures/primitives.pyi] @@ -319,8 +319,8 @@ else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" if x['key'] == 'D': - reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" [builtins fixtures/primitives.pyi] @@ -611,8 +611,8 @@ else: y: Union[Parent1, Parent2] if y["model"]["key"] is Key.C: reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]" - reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]" + # N: Revealed type is "Never" + reveal_type(y["model"]) # N: Revealed type is "Never" else: reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]" reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]" @@ -648,8 +648,8 @@ else: y: Union[Parent1, Parent2] if y["model"]["key"] == 'C': reveal_type(y) # E: Statement is unreachable \ - # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]" - reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" + # N: Revealed type is "Never" + reveal_type(y["model"]) # N: Revealed type is "Never" else: reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]" reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" @@ -2445,3 +2445,16 @@ def foo(x: T) -> T: reveal_type(x) # N: Revealed type is "T`-1" return x [builtins fixtures/isinstance.pyi] + +[case testNarrowingToClassWithNeverProperty] +# flags: --warn-unreachable +from typing import Never + +class X: + a: Never + +x: X +if x.a is 5: + reveal_type(x) # N: Revealed type is "__main__.X" +else: + reveal_type(x) # N: Revealed type is "__main__.X" From 4718cfdc99aa16d1904ee370870c5eaa63800deb Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 18:27:32 +0900 Subject: [PATCH 27/40] Update some tests --- mypy/binder.py | 3 ++- test-data/unit/check-narrowing.test | 17 +++++++++++------ test-data/unit/deps.test | 4 +--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index b31912d9224e..9a6da8b84cba 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -168,7 +168,8 @@ def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> N This is used for isinstance() etc. Assignments should go through assign_type(). """ - if isinstance(get_proper_type(typ), UninhabitedType): + proper_typ = get_proper_type(typ) + if isinstance(proper_typ, UninhabitedType) and not proper_typ.ambiguous: self.frames[-1].unreachable = True if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)): diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 7795ab45f91c..b70c263e308d 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -263,7 +263,6 @@ else: reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" if x.key is Key.D: - # TODO: this should narrow to Never reveal_type(x) # E: Statement is unreachable \ # N: Revealed type is "Never" else: @@ -2448,13 +2447,19 @@ def foo(x: T) -> T: [case testNarrowingToClassWithNeverProperty] # flags: --warn-unreachable -from typing import Never +from typing import Never, Union class X: a: Never -x: X -if x.a is 5: - reveal_type(x) # N: Revealed type is "__main__.X" +class B: + a: str + +x: Union[X, B] +# TODO: this should not be unreachable (`Never & int` is just `Never`) +if isinstance(x.a, int): # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "__main__.X" else: - reveal_type(x) # N: Revealed type is "__main__.X" + reveal_type(x) # N: Revealed type is "Union[__main__.X, __main__.B]" +[builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index ac84c918665b..793624c22ca5 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -414,9 +414,7 @@ class B: def f(x: A) -> None: if isinstance(x, B): - # TODO: this should narrow to Never and therefore not error - #x.y - pass + x.y [builtins fixtures/isinstancelist.pyi] [out] -> , m.A, m.f From 475f8b191dcb1e4577ce5eb5e8e6c48d727651f7 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 19:06:24 +0900 Subject: [PATCH 28/40] `typing.assert_never(5)` is fine in unreachable code This is an awful hack but the documentation says: > Ask a static type checker to confirm that a line of code is > unreachable. as well as something specifically about `Never` (it's confusing) --- mypy/checkexpr.py | 6 ++++++ test-data/unit/check-unreachable-code.test | 11 +++++++++++ test-data/unit/lib-stub/typing_extensions.pyi | 1 + 3 files changed, 18 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 84904268ad86..fd9419b20691 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1669,6 +1669,12 @@ def check_callable_call( if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). return callee.ret_type, callee + if ( + isinstance(callable_node, RefExpr) + and callable_node.fullname in ("typing.assert_never", "typing_extensions.assert_never") + and self.chk.binder.is_unreachable() + ): + return callee.ret_type, callee if ( callee.is_type_obj() diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index feb2a9eb89fc..46686e43044a 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -875,6 +875,8 @@ d = [x for x in lst if FOOBAR] [case testUnreachableFlagOkWithDeadStatements] # flags: --warn-unreachable from typing import NoReturn +from typing_extensions import assert_never as typing_assert_never + def assert_never(x: NoReturn) -> NoReturn: assert False @@ -895,6 +897,7 @@ if False: if False: assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" + typing_assert_never(x) reveal_type(x) # E: Statement is unreachable \ # N: Revealed type is "builtins.int" @@ -907,6 +910,14 @@ if False: # Ignore obvious type errors assert_never(expect_str(x)) # E: Argument 1 to "assert_never" has incompatible type "str"; expected "Never" \ # E: Argument 1 to "expect_str" has incompatible type "int"; expected "str" + typing_assert_never(expect_str(x)) + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "builtins.int" + +if True: + typing_assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" + assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" + typing_assert_never(x) reveal_type(x) # E: Statement is unreachable \ # N: Revealed type is "builtins.int" [builtins fixtures/exception.pyi] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index cb054b0e6b4f..fddc0203eef7 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -81,6 +81,7 @@ def TypedDict(typename: str, fields: Dict[str, Type[_T]], *, total: Any = ...) - def reveal_type(__obj: _T) -> _T: pass def assert_type(__val: _T, __typ: Any) -> _T: pass +def assert_never(__val: Never) -> Never: pass def dataclass_transform( *, From 72349c0b517f9ad0e27de51545621863be98e44e Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 4 Mar 2025 19:29:05 +0900 Subject: [PATCH 29/40] Non-ambiguous `Never` is fine as inference result --- mypy/checker.py | 2 +- test-data/unit/check-incremental.test | 4 ---- test-data/unit/check-python38.test | 2 +- test-data/unit/check-tuples.test | 3 +-- test-data/unit/check-unreachable-code.test | 7 +------ test-data/unit/fine-grained.test | 4 ---- 6 files changed, 4 insertions(+), 18 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d359ab003f77..948a957090aa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8500,7 +8500,7 @@ def is_valid_inferred_type(typ: Type, is_lvalue_final: bool = False) -> bool: # the context. This resolution happens in leave_partial_types when # we pop a partial types scope. return is_lvalue_final - elif isinstance(proper_type, UninhabitedType): + elif isinstance(proper_type, UninhabitedType) and proper_type.ambiguous: return False return not typ.accept(InvalidInferredTypes()) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index b25a8318649d..50de2c6dbc11 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5415,8 +5415,6 @@ reveal_type(z) [out] tmp/c.py:2: note: Revealed type is "a." [out2] -tmp/a.py:7: error: Need type annotation for "y" -tmp/b.py:2: error: Need type annotation for "z" tmp/c.py:2: note: Revealed type is "Never" [case testIsInstanceAdHocIntersectionIncrementalUnreachableToIntersection] @@ -5448,8 +5446,6 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -tmp/a.py:7: error: Need type annotation for "y" -tmp/b.py:2: error: Need type annotation for "z" tmp/c.py:2: note: Revealed type is "Never" [out2] tmp/c.py:2: note: Revealed type is "a." diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index d5e7d275d468..5f131492fdcc 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -697,7 +697,7 @@ def f1() -> None: x = 1 if (x := d[x]) is None: # E: Name "x" may be undefined - y = x # E: Need type annotation for "y" + y = x z = x [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index d675a35c4aae..8197825be6ee 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -940,7 +940,7 @@ for x in t: [case testForLoopOverEmptyTuple] import typing t = () -for x in t: pass # E: Need type annotation for "x" +for x in t: pass [builtins fixtures/for.pyi] [case testForLoopOverNoneValuedTuple] @@ -1604,7 +1604,6 @@ t5: Tuple[int, int] = (1, 2, "s", 4) # E: Incompatible types in assignment (exp # long initializer assignment with mismatched pairs t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str", 1, 1, 1, 1, 1) # E: Incompatible types in assignment (expression has type Tuple[int, int, ... <15 more items>], variable has type Tuple[int, int, ... <10 more items>]) - [builtins fixtures/tuple.pyi] [case testTupleWithStarExpr] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 46686e43044a..e81634827f39 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1550,12 +1550,7 @@ x = 0 # not unreachable [case testUnpackNever] x: str if isinstance(x, int): - # mypy wants a type annotation for y because it's `Never`. - # This doesn't make much sense in this case, but it's a - # requirement for composable behavior. - # (and `ys` should also get flagged.) - - y, *ys = x # E: Need type annotation for "y" + y, *ys = x reveal_type(y) # N: Revealed type is "Never" reveal_type(ys) # N: Revealed type is "builtins.list[Never]" [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 8fb5da31c656..248206e09c24 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9693,8 +9693,6 @@ reveal_type(z) c.py:2: note: Revealed type is "a." == c.py:2: note: Revealed type is "Never" -a.py:7: error: Need type annotation for "y" -b.py:2: error: Need type annotation for "z" [case testIsInstanceAdHocIntersectionFineGrainedIncrementalUnreachableToIntersection] import c @@ -9725,8 +9723,6 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -a.py:7: error: Need type annotation for "y" -b.py:2: error: Need type annotation for "z" c.py:2: note: Revealed type is "Never" == c.py:2: note: Revealed type is "a." From ca111511bdc43e025733f0042a08862e1102d6bb Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 16:55:25 +0900 Subject: [PATCH 30/40] Implement or-ing typemaps for `Never`-based unreachability --- mypy/checker.py | 5 +++-- test-data/unit/check-narrowing.test | 8 ++++---- test-data/unit/check-python310.test | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 948a957090aa..512a40027904 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8136,6 +8136,7 @@ def and_conditional_maps(m1: TypeMap, m2: TypeMap, use_meet: bool = False) -> Ty result = m2.copy() m2_keys = {literal_hash(n2) for n2 in m2} for n1 in m1: + # TODO: should this check for UninhabitedType as well as AnyType? if literal_hash(n1) not in m2_keys or isinstance(get_proper_type(m1[n1]), AnyType): result[n1] = m1[n1] if use_meet: @@ -8156,9 +8157,9 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap, coalesce_any: bool = False) -> joining restrictions. """ - if m1 is None: + if m1 is None or any(isinstance(get_proper_type(t1), UninhabitedType) for t1 in m1.values()): return m2 - if m2 is None: + if m2 is None or any(isinstance(get_proper_type(t2), UninhabitedType) for t2 in m2.values()): return m1 # Both conditions can be true. Combine information about # expressions whose type is refined by both conditions. (We do not diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index b70c263e308d..a0e4e919eadb 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -741,8 +741,6 @@ def test3(switch: FlipFlopEnum) -> None: assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable \ # N: Revealed type is "Never" - - [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitStrLiteral] @@ -939,7 +937,7 @@ if a == a == b: reveal_type(b) # N: Revealed type is "Literal[1]" else: reveal_type(a) # N: Revealed type is "Literal[1]" - reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2]]" + reveal_type(b) # N: Revealed type is "Literal[2]" # In this case, it's ok for 'b' to narrow down to Literal[1] in the else case # since that's the only way 'b == 2' can be false @@ -1336,6 +1334,7 @@ else: [typing fixtures/typing-typeddict.pyi] [case testNarrowingRuntimeCover] +# flags: --warn-unreachable from typing import Dict, List, Union def unreachable(x: Union[str, List[str]]) -> None: @@ -1344,7 +1343,8 @@ def unreachable(x: Union[str, List[str]]) -> None: elif isinstance(x, list): reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" else: - reveal_type(x) # No output: this branch is unreachable # N: Revealed type is "Never" + reveal_type(x) # E: Statement is unreachable \ + # N: Revealed type is "Never" def all_parts_covered(x: Union[str, List[str], List[int], int]) -> None: if isinstance(x, str): diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 3ed7f5ab6c1d..1a06c205145a 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -2319,7 +2319,7 @@ def f3(e: int | str | bytes) -> int: return 0 # E: Statement is unreachable case str(x): return 0 - reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.bytes]" + reveal_type(e) # N: Revealed type is "builtins.bytes" return 0 def f4(e: int | str | bytes) -> int: @@ -2330,7 +2330,7 @@ def f4(e: int | str | bytes) -> int: return 0 # E: Statement is unreachable case x if isinstance(x, str): return 0 - reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.str, builtins.bytes]" + reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.bytes]" return 0 [builtins fixtures/primitives.pyi] From 22e73d5fcf3695918369f266ace220e707af1114 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 17:10:49 +0900 Subject: [PATCH 31/40] Tuple length checks should narrow to `Never` --- mypy/checker.py | 60 +++++++++++------------------ test-data/unit/check-narrowing.test | 20 +++++----- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 512a40027904..0a0ab8e8ac69 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6271,14 +6271,8 @@ def find_isinstance_check_helper( ): # Combine a `len(x) > 0` check with the default logic below. yes_type, no_type = self.narrow_with_len(self.lookup_type(node), ">", 0) - if yes_type is not None: - yes_type = true_only(yes_type) - else: - yes_type = UninhabitedType() - if no_type is not None: - no_type = false_only(no_type) - else: - no_type = UninhabitedType() + yes_type = true_only(yes_type) + no_type = false_only(no_type) if_map = {node: yes_type} if not isinstance(yes_type, UninhabitedType) else None else_map = {node: no_type} if not isinstance(no_type, UninhabitedType) else None return if_map, else_map @@ -6952,8 +6946,8 @@ def find_tuple_len_narrowing(self, node: ComparisonExpr) -> list[tuple[TypeMap, continue for tpl in tuples: yes_type, no_type = self.narrow_with_len(self.lookup_type(tpl), op, size) - yes_map = None if yes_type is None else {tpl: yes_type} - no_map = None if no_type is None else {tpl: no_type} + yes_map: TypeMap = {tpl: yes_type} + no_map: TypeMap = {tpl: no_type} type_maps.append((yes_map, no_map)) else: left, right = items @@ -6970,12 +6964,12 @@ def find_tuple_len_narrowing(self, node: ComparisonExpr) -> list[tuple[TypeMap, yes_type, no_type = self.narrow_with_len( self.lookup_type(left.args[0]), op, r_size ) - yes_map = None if yes_type is None else {left.args[0]: yes_type} - no_map = None if no_type is None else {left.args[0]: no_type} + yes_map = {left.args[0]: yes_type} + no_map = {left.args[0]: no_type} type_maps.append((yes_map, no_map)) return type_maps - def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type | None, Type | None]: + def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type, Type]: """Dispatch tuple type narrowing logic depending on the kind of type we got.""" typ = get_proper_type(typ) if isinstance(typ, TupleType): @@ -6991,27 +6985,19 @@ def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type | None, T other_types.append(t) continue yt, nt = self.narrow_with_len(t, op, size) - if yt is not None: - yes_types.append(yt) - if nt is not None: - no_types.append(nt) + yes_types.append(yt) + no_types.append(nt) + yes_types += other_types no_types += other_types - if yes_types: - yes_type = make_simplified_union(yes_types) - else: - yes_type = None - if no_types: - no_type = make_simplified_union(no_types) - else: - no_type = None + + yes_type = make_simplified_union(yes_types) + no_type = make_simplified_union(no_types) return yes_type, no_type else: assert False, "Unsupported type for len narrowing" - def refine_tuple_type_with_len( - self, typ: TupleType, op: str, size: int - ) -> tuple[Type | None, Type | None]: + def refine_tuple_type_with_len(self, typ: TupleType, op: str, size: int) -> tuple[Type, Type]: """Narrow a TupleType using length restrictions.""" unpack_index = find_unpack_in_list(typ.items) if unpack_index is None: @@ -7019,8 +7005,8 @@ def refine_tuple_type_with_len( # depending on the current length, expected length, and the comparison op. method = int_op_to_method[op] if method(typ.length(), size): - return typ, None - return None, typ + return typ, UninhabitedType() + return UninhabitedType(), typ unpack = typ.items[unpack_index] assert isinstance(unpack, UnpackType) unpacked = get_proper_type(unpack.type) @@ -7032,7 +7018,7 @@ def refine_tuple_type_with_len( if op in ("==", "is"): if min_len <= size: return typ, typ - return None, typ + return UninhabitedType(), typ elif op in ("<", "<="): if op == "<=": size += 1 @@ -7042,7 +7028,7 @@ def refine_tuple_type_with_len( # TODO: also record max_len to avoid false negatives? unpack = UnpackType(unpacked.copy_modified(min_len=size - typ.length() + 1)) return typ, typ.copy_modified(items=prefix + [unpack] + suffix) - return None, typ + return UninhabitedType(), typ else: yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size) return no_type, yes_type @@ -7057,7 +7043,7 @@ def refine_tuple_type_with_len( if min_len <= size: # TODO: return fixed union + prefixed variadic tuple for no_type? return typ.copy_modified(items=prefix + [arg] * (size - min_len) + suffix), typ - return None, typ + return UninhabitedType(), typ elif op in ("<", "<="): if op == "<=": size += 1 @@ -7072,14 +7058,14 @@ def refine_tuple_type_with_len( for n in range(size - min_len): yes_items.append(typ.copy_modified(items=prefix + [arg] * n + suffix)) return UnionType.make_union(yes_items, typ.line, typ.column), no_type - return None, typ + return UninhabitedType(), typ else: yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size) return no_type, yes_type def refine_instance_type_with_len( self, typ: Instance, op: str, size: int - ) -> tuple[Type | None, Type | None]: + ) -> tuple[Type, Type]: """Narrow a homogeneous tuple using length restrictions.""" base = map_instance_to_supertype(typ, self.lookup_typeinfo("builtins.tuple")) arg = base.args[0] @@ -7095,14 +7081,14 @@ def refine_instance_type_with_len( size += 1 if allow_precise: unpack = UnpackType(self.named_generic_type("builtins.tuple", [arg])) - no_type: Type | None = TupleType(items=[arg] * size + [unpack], fallback=typ) + no_type: Type = TupleType(items=[arg] * size + [unpack], fallback=typ) else: no_type = typ if allow_precise: items = [] for n in range(size): items.append(TupleType([arg] * n, fallback=typ)) - yes_type: Type | None = UnionType.make_union(items, typ.line, typ.column) + yes_type: Type = UnionType.make_union(items, typ.line, typ.column) else: yes_type = typ return yes_type, no_type diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index a0e4e919eadb..06c97d037fe2 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1665,13 +1665,13 @@ from typing import Tuple, Union x: Union[Tuple[int, int], Tuple[int, int, int]] if len(x) >= 4: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" if len(x) < 2: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" [builtins fixtures/len.pyi] @@ -1750,7 +1750,7 @@ Ts = TypeVarTuple("Ts") def foo(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) == 1: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" @@ -1758,18 +1758,18 @@ def foo(x: Tuple[int, Unpack[Ts], str]) -> None: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + # N: Revealed type is "Never" def bar(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + # N: Revealed type is "Never" if len(x) < 2: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" [builtins fixtures/len.pyi] @@ -1816,7 +1816,7 @@ from typing_extensions import Unpack def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) == 1: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" @@ -1824,18 +1824,18 @@ def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + # N: Revealed type is "Never" def bar(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + # N: Revealed type is "Never" if len(x) < 2: reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" [builtins fixtures/len.pyi] From 2bd080230a1156d82889c676102699de03313631 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 17:24:04 +0900 Subject: [PATCH 32/40] Narrow walrus to `Never` if unreachable --- mypy/checker.py | 9 +++++++-- test-data/unit/check-python38.test | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0a0ab8e8ac69..bcf361976e07 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6228,12 +6228,17 @@ def find_isinstance_check_helper( if if_condition_map is not None: if_map.update(if_condition_map) + else: + if_map[node.target] = UninhabitedType() + if else_condition_map is not None: else_map.update(else_condition_map) + else: + else_map[node.target] = UninhabitedType() return ( - (None if if_assignment_map is None or if_condition_map is None else if_map), - (None if else_assignment_map is None or else_condition_map is None else else_map), + (None if if_assignment_map is None else if_map), + (None if else_assignment_map is None else else_map), ) elif isinstance(node, OpExpr) and node.op == "and": left_if_vars, left_else_vars = self.find_isinstance_check(node.left) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 5f131492fdcc..1643cec1d749 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -460,7 +460,7 @@ def check_partial_list() -> None: if (x := 0): reveal_type(x) # E: Statement is unreachable \ - # N: Revealed type is "builtins.int" + # N: Revealed type is "Never" else: reveal_type(x) # N: Revealed type is "Literal[0]" From 8fb6fab6622e72ab25c5f4159f20ad544b0ce7a2 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 18:09:09 +0900 Subject: [PATCH 33/40] Narrow unreachable captures due to guards in `match` to `Never` --- mypy/checker.py | 3 +++ test-data/unit/check-narrowing.test | 3 --- test-data/unit/check-python310.test | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bcf361976e07..ba94082467fc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5515,6 +5515,9 @@ def visit_match_stmt(self, s: MatchStmt) -> None: if isinstance(p, AsPattern): case_target = p.pattern or p.name if isinstance(case_target, NameExpr): + if guard_map is None: + guard_map = {case_target: UninhabitedType()} + for type_map in (guard_map, else_map): if not type_map: continue diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 06c97d037fe2..c1f1ae069c7b 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -995,7 +995,6 @@ else: reveal_type(a) # E: Statement is unreachable \ # N: Revealed type is "Never" reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" - [builtins fixtures/primitives.pyi] [case testNarrowingLiteralTruthiness] @@ -2112,8 +2111,6 @@ if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final reveal_type(x) # E: Statement is unreachable \ # N: Revealed type is "Never" - - [builtins fixtures/isinstance.pyi] [case testTypeNarrowingReachableNegative] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 1a06c205145a..e9199031cf49 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1329,7 +1329,7 @@ m: str match m: case a if False: reveal_type(a) # E: Statement is unreachable \ - # N: Revealed type is "builtins.str" + # N: Revealed type is "Never" [case testMatchRedefiningPatternGuard] m: str From 0f41d33b1587771abd844a2c84e5e5f7d0ad9155 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 18:20:08 +0900 Subject: [PATCH 34/40] Narrow impossible values in mapping pattern to `Never` --- mypy/checkpattern.py | 18 +++++++++--------- test-data/unit/check-python310.test | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index d426b084d889..3f646e699524 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -469,14 +469,14 @@ def visit_mapping_pattern(self, o: MappingPattern) -> PatternType: captures: dict[Expression, Type] = {} for key, value in zip(o.keys, o.values): inner_type = self.get_mapping_item_type(o, current_type, key) - if inner_type is None: + if is_uninhabited(inner_type): can_match = False - inner_type = self.chk.named_type("builtins.object") + pattern_type = self.accept(value, inner_type) if is_uninhabited(pattern_type.type): can_match = False - else: - self.update_type_map(captures, pattern_type.captures) + + self.update_type_map(captures, pattern_type.captures) if o.rest is not None: mapping = self.chk.named_type("typing.Mapping") @@ -501,13 +501,13 @@ def visit_mapping_pattern(self, o: MappingPattern) -> PatternType: def get_mapping_item_type( self, pattern: MappingPattern, mapping_type: Type, key: Expression - ) -> Type | None: + ) -> Type: mapping_type = get_proper_type(mapping_type) if isinstance(mapping_type, TypedDictType): with self.msg.filter_errors() as local_errors: - result: Type | None = self.chk.expr_checker.visit_typeddict_index_expr( - mapping_type, key - )[0] + result: Type = self.chk.expr_checker.visit_typeddict_index_expr(mapping_type, key)[ + 0 + ] has_local_errors = local_errors.has_new_errors() # If we can't determine the type statically fall back to treating it as a normal # mapping @@ -516,7 +516,7 @@ def get_mapping_item_type( result = self.get_simple_mapping_item_type(pattern, mapping_type, key) if local_errors.has_new_errors(): - result = None + result = UninhabitedType() else: with self.msg.filter_errors(): result = self.get_simple_mapping_item_type(pattern, mapping_type, key) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index e9199031cf49..7ececb6c823b 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -544,10 +544,10 @@ m: A match m: case {1: v}: reveal_type(v) # E: Statement is unreachable \ - # N: Revealed type is "builtins.object" + # N: Revealed type is "Never" case {b.b: v2}: reveal_type(v2) # E: Statement is unreachable \ - # N: Revealed type is "builtins.object" + # N: Revealed type is "Never" [file b.py] b: int [typing fixtures/typing-typeddict.pyi] From b641ed4837a12e194ad72e0e570fcae99a22e4f0 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 6 Mar 2025 18:42:25 +0900 Subject: [PATCH 35/40] Smooth out edges --- mypy/checkexpr.py | 23 +++++++++++++--------- test-data/unit/check-typeis.test | 3 +++ test-data/unit/check-unreachable-code.test | 5 ++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fd9419b20691..405302e340fe 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1669,12 +1669,6 @@ def check_callable_call( if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). return callee.ret_type, callee - if ( - isinstance(callable_node, RefExpr) - and callable_node.fullname in ("typing.assert_never", "typing_extensions.assert_never") - and self.chk.binder.is_unreachable() - ): - return callee.ret_type, callee if ( callee.is_type_obj() @@ -1804,9 +1798,20 @@ def check_callable_call( callable_name, ) - self.check_argument_types( - arg_types, arg_kinds, args, callee, formal_to_actual, context, object_type=object_type - ) + if not ( + isinstance(callable_node, RefExpr) + and callable_node.fullname in ("typing.assert_never", "typing_extensions.assert_never") + and self.chk.binder.is_unreachable() + ): + self.check_argument_types( + arg_types, + arg_kinds, + args, + callee, + formal_to_actual, + context, + object_type=object_type, + ) if ( callee.is_type_obj() diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index 1b85f1c712de..24613af16e93 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -454,7 +454,10 @@ def g(x: object) -> None: ... def test(x: List[Any]) -> None: if not(f(x) or isinstance(x, A)): return + g(reveal_type(x)) # N: Revealed type is "builtins.list[Any]" + # explanation: `if f(x): ...` is assumed always true! + # so there's no narrowing here. [builtins fixtures/tuple.pyi] [case testTypeIsMultipleCondition] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index e81634827f39..30080a3c8e1f 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -910,7 +910,7 @@ if False: # Ignore obvious type errors assert_never(expect_str(x)) # E: Argument 1 to "assert_never" has incompatible type "str"; expected "Never" \ # E: Argument 1 to "expect_str" has incompatible type "int"; expected "str" - typing_assert_never(expect_str(x)) + typing_assert_never(expect_str(x)) # E: Argument 1 to "expect_str" has incompatible type "int"; expected "str" reveal_type(x) # E: Statement is unreachable \ # N: Revealed type is "builtins.int" @@ -1364,6 +1364,9 @@ def test_untyped_fn(obj): obj.update(prop=False) obj.reload() + reveal_type(obj.prop) # N: Revealed type is "Any" \ + # N: 'reveal_type' always outputs 'Any' in unchecked functions + assert obj.prop is False reveal_type(obj.prop) # N: Revealed type is "Any" \ # N: 'reveal_type' always outputs 'Any' in unchecked functions From dcc4f299549ea5432c72c230165e6bf7030c0ecf Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 7 Mar 2025 19:19:07 +0900 Subject: [PATCH 36/40] Propagate suppressing unreachability warnings --- mypy/checker.py | 4 ++++ test-data/unit/check-typevar-values.test | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index ba94082467fc..59ac83ec0348 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1193,6 +1193,9 @@ def check_func_def( for item, typ in expanded: old_binder = self.binder self.binder = ConditionalTypeBinder() + if old_binder.is_unreachable_warning_suppressed(): + self.binder.suppress_unreachable_warnings() + with self.binder.top_frame_context(): defn.expanded.append(item) @@ -1380,6 +1383,7 @@ def check_func_def( new_frame = self.binder.push_frame() new_frame.types[key] = narrowed_type self.binder.declarations[key] = old_binder.declarations[key] + with self.scope.push_function(defn): # We suppress reachability warnings for empty generator functions # (return; yield) which have a "yield" that's unreachable by definition diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 990654f6589d..d69b44f52947 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -740,3 +740,24 @@ def fn(w: W) -> W: reveal_type(w) # N: Revealed type is "builtins.int" return w [builtins fixtures/isinstance.pyi] + +[case testTypeVarValuesPropagateUnreachabilitySilence] +# flags: --warn-unreachable +from typing import Any, Callable, TypeVar + +from typing_extensions import Concatenate, ParamSpec + +T = TypeVar("T", int, str) +P = ParamSpec("P") + +def accepts_mapping( + func: Callable[P, Any] +) -> Callable[Concatenate[T, P], T]: + def wrapper(data: T, /, *args: P.args, **kwargs: P.kwargs) -> T: + if isinstance(data, str): + return "" + else: + return 1 + + return wrapper +[builtins fixtures/isinstance.pyi] From 98abde4497897212f0d4de87d12506d2e3636e65 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 9 Mar 2025 12:40:17 +0900 Subject: [PATCH 37/40] Fix typemap handling for ternaries --- mypy/checker.py | 5 ++-- mypy/checkexpr.py | 27 +++++++++++++++++----- test-data/unit/check-isinstance.test | 3 ++- test-data/unit/check-unreachable-code.test | 9 +++++++- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 59ac83ec0348..a40b35c9755b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8134,8 +8134,9 @@ def and_conditional_maps(m1: TypeMap, m2: TypeMap, use_meet: bool = False) -> Ty result = m2.copy() m2_keys = {literal_hash(n2) for n2 in m2} for n1 in m1: - # TODO: should this check for UninhabitedType as well as AnyType? - if literal_hash(n1) not in m2_keys or isinstance(get_proper_type(m1[n1]), AnyType): + if literal_hash(n1) not in m2_keys or isinstance( + get_proper_type(m1[n1]), (UninhabitedType, AnyType) + ): result[n1] = m1[n1] if use_meet: # For now, meet common keys only if specifically requested. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 405302e340fe..49989506d390 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5836,9 +5836,13 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F # but only for the current expression if_map, else_map = self.chk.find_isinstance_check(e.cond) if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes: - if if_map is None: + if if_map is None or any( + isinstance(get_proper_type(t), UninhabitedType) for t in if_map.values() + ): self.msg.redundant_condition_in_if(False, e.cond) - elif else_map is None: + elif else_map is None or any( + isinstance(get_proper_type(t), UninhabitedType) for t in else_map.values() + ): self.msg.redundant_condition_in_if(True, e.cond) if_type = self.analyze_cond_branch( @@ -5915,17 +5919,28 @@ def analyze_cond_branch( node: Expression, context: Type | None, allow_none_return: bool = False, - suppress_unreachable_errors: bool = True, + suppress_unreachable_errors: bool | None = None, ) -> Type: + # TODO: default based on flag (default to `True` if flag is not passed) + unreachable_errors_suppressed = ( + suppress_unreachable_errors + if suppress_unreachable_errors is not None + else self.chk.binder.is_unreachable_warning_suppressed() + ) with self.chk.binder.frame_context(can_skip=True, fall_through=0): - if map is None: + self.chk.push_type_map(map) + + if map is None or any( + isinstance(get_proper_type(t), UninhabitedType) for t in map.values() + ): # We still need to type check node, in case we want to # process it for isinstance checks later. Since the branch was # determined to be unreachable, any errors should be suppressed. - with self.msg.filter_errors(filter_errors=suppress_unreachable_errors): + + with self.msg.filter_errors(filter_errors=unreachable_errors_suppressed): self.accept(node, type_context=context, allow_none_return=allow_none_return) return UninhabitedType() - self.chk.push_type_map(map) + return self.accept(node, type_context=context, allow_none_return=allow_none_return) # diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index ebcea85e142f..a44419efa56f 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1444,7 +1444,8 @@ if isinstance(x2, A) or isinstance(x2, C): # E: Right operand of "or" is never f = x2.flag # E: "A" has no attribute "flag" else: # unreachable - _ = "unreachable" # E: Statement is unreachable + reveal_type(x2) # E: Statement is unreachable \ + # N: Revealed type is "Never" reveal_type(x2) # N: Revealed type is "__main__.A" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 30080a3c8e1f..3fa3224dd9b9 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -962,7 +962,8 @@ k = [x for x in lst if isinstance(x, int) or foo()] # E: Right operand of "or" class Case1: def test1(self) -> bool: - return False and self.missing() # E: Right operand of "and" is never evaluated + return False and self.missing() # E: Right operand of "and" is never evaluated \ + # E: "Case1" has no attribute "missing" def test2(self) -> bool: return not self.property_decorator_missing and self.missing() # E: Function "property_decorator_missing" could always be true in boolean context \ @@ -1557,3 +1558,9 @@ if isinstance(x, int): reveal_type(y) # N: Revealed type is "Never" reveal_type(ys) # N: Revealed type is "builtins.list[Never]" [builtins fixtures/isinstancelist.pyi] + +[case testUnusedConditionalBranchesDoNotAffectType] +# flags: --enable-error-code redundant-expr +def foo(var: str) -> str: + return "aa" if isinstance(var, str) else 0 # E: If condition is always true +[builtins fixtures/isinstancelist.pyi] From 390388a8d35446f927c7334c57b442530c8aeac6 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 9 Mar 2025 13:07:49 +0900 Subject: [PATCH 38/40] Ensure `--strict-equality` works correctly with partial types --- mypy/checker.py | 2 +- test-data/unit/check-errorcodes.test | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index a40b35c9755b..fb9f08f04c43 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -617,7 +617,7 @@ def accept_loop( # If necessary, reset the modified options and make up for the postponed error checks: if warn_redundant: self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR) - if self.options.warn_unreachable or warn_redundant: + if self.options.warn_unreachable or warn_redundant or self.options.strict_equality: with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): if on_enter_body is not None: on_enter_body() diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 6ec246fb3a13..f25e2e8f0b06 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -519,6 +519,11 @@ if int() != str(): # E: Non-overlapping equality check (left operand type: "int pass if int() is str(): # E: Non-overlapping identity check (left operand type: "int", right operand type: "str") [comparison-overlap] pass + +x = int() +while True: + if x is str(): # E: Non-overlapping identity check (left operand type: "int", right operand type: "str") [comparison-overlap] + pass [builtins fixtures/primitives.pyi] [case testErrorCodeMissingModule] From dc8ed710790802ded7fe9ca5f079747fd77da220 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 13 Mar 2025 18:08:15 +0900 Subject: [PATCH 39/40] Support `Never` callables in plugins I don't know how to test this. --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index fb9f08f04c43..c50006f23e5d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -711,7 +711,7 @@ def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> Callab """Get type as seen by an overload item caller.""" inner_type = get_proper_type(inner_type) outer_type: FunctionLike | None = None - if inner_type is None or isinstance(inner_type, AnyType): + if inner_type is None or isinstance(inner_type, (AnyType, UninhabitedType)): return None if isinstance(inner_type, TypeVarLikeType): inner_type = get_proper_type(inner_type.upper_bound) From 102ac11a7a12af415d0fbb14ce5628dcc6841427 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 13 Mar 2025 19:17:47 +0900 Subject: [PATCH 40/40] Add a test about narrowing behavior --- test-data/unit/check-unreachable-code.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 3fa3224dd9b9..994f2e28d22a 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1564,3 +1564,12 @@ if isinstance(x, int): def foo(var: str) -> str: return "aa" if isinstance(var, str) else 0 # E: If condition is always true [builtins fixtures/isinstancelist.pyi] + +[case testMypyDoesntNarrowBasedOnLies] +x: int +if isinstance(x, int): + assert False + +reveal_type(x) # N: Revealed type is "builtins.int" +# mypy should not narrow this ^ +[builtins fixtures/isinstancelist.pyi]