Skip to content

Commit 0260762

Browse files
Merge branch 'master' into literals_as_anyof_types
2 parents 76eb7ce + 606973f commit 0260762

File tree

8 files changed

+245
-188
lines changed

8 files changed

+245
-188
lines changed

mypy/checker.py

Lines changed: 104 additions & 144 deletions
Large diffs are not rendered by default.

mypy/checkexpr.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,8 +2526,6 @@ def check_argument_types(
25262526
25272527
The check_call docstring describes some of the arguments.
25282528
"""
2529-
if self.chk.can_skip_diagnostics:
2530-
return
25312529
self.check_var_args_kwargs(arg_types, arg_kinds, context)
25322530

25332531
check_arg = check_arg or self.check_arg
@@ -4288,29 +4286,32 @@ def check_boolean_op(self, e: OpExpr) -> Type:
42884286

42894287
assert e.op in ("and", "or") # Checked by visit_op_expr
42904288

4289+
left_map: mypy.checker.TypeMap
4290+
right_map: mypy.checker.TypeMap
42914291
if e.right_always:
4292-
left_map: mypy.checker.TypeMap = None
4293-
right_map: mypy.checker.TypeMap = {}
4292+
left_map, right_map = {e.left: UninhabitedType()}, {}
42944293
elif e.right_unreachable:
4295-
left_map, right_map = {}, None
4294+
left_map, right_map = {}, {e.right: UninhabitedType()}
42964295
elif e.op == "and":
42974296
right_map, left_map = self.chk.find_isinstance_check(e.left)
42984297
elif e.op == "or":
42994298
left_map, right_map = self.chk.find_isinstance_check(e.left)
43004299

4301-
# If left_map is None then we know mypy considers the left expression
4300+
# If left_map is unreachable then we know mypy considers the left expression
43024301
# to be redundant.
4302+
left_unreachable = mypy.checker.is_unreachable_map(left_map)
43034303
if (
43044304
codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes
4305-
and left_map is None
4305+
and left_unreachable
43064306
# don't report an error if it's intentional
43074307
and not e.right_always
43084308
):
43094309
self.msg.redundant_left_operand(e.op, e.left)
43104310

4311+
right_unreachable = mypy.checker.is_unreachable_map(right_map)
43114312
if (
43124313
self.chk.should_report_unreachable_issues()
4313-
and right_map is None
4314+
and right_unreachable
43144315
# don't report an error if it's intentional
43154316
and not e.right_unreachable
43164317
):
@@ -4320,16 +4321,14 @@ def check_boolean_op(self, e: OpExpr) -> Type:
43204321
right_map, e.right, self._combined_context(expanded_left_type)
43214322
)
43224323

4323-
if left_map is None and right_map is None:
4324+
if left_unreachable and right_unreachable:
43244325
return UninhabitedType()
43254326

4326-
if right_map is None:
4327+
if right_unreachable:
43274328
# The boolean expression is statically known to be the left value
4328-
assert left_map is not None
43294329
return left_type
4330-
if left_map is None:
4330+
if left_unreachable:
43314331
# The boolean expression is statically known to be the right value
4332-
assert right_map is not None
43334332
return right_type
43344333

43354334
if e.op == "and":
@@ -5913,14 +5912,12 @@ def check_for_comp(self, e: GeneratorExpr | DictionaryComprehension) -> None:
59135912

59145913
# values are only part of the comprehension when all conditions are true
59155914
true_map, false_map = self.chk.find_isinstance_check(condition)
5916-
5917-
if true_map:
5918-
self.chk.push_type_map(true_map)
5915+
self.chk.push_type_map(true_map)
59195916

59205917
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
5921-
if true_map is None:
5918+
if mypy.checker.is_unreachable_map(true_map):
59225919
self.msg.redundant_condition_in_comprehension(False, condition)
5923-
elif false_map is None:
5920+
elif mypy.checker.is_unreachable_map(false_map):
59245921
self.msg.redundant_condition_in_comprehension(True, condition)
59255922

59265923
def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = False) -> Type:
@@ -5931,9 +5928,9 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
59315928
# but only for the current expression
59325929
if_map, else_map = self.chk.find_isinstance_check(e.cond)
59335930
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
5934-
if if_map is None:
5931+
if mypy.checker.is_unreachable_map(if_map):
59355932
self.msg.redundant_condition_in_if(False, e.cond)
5936-
elif else_map is None:
5933+
elif mypy.checker.is_unreachable_map(else_map):
59375934
self.msg.redundant_condition_in_if(True, e.cond)
59385935

59395936
if ctx is None:
@@ -5974,14 +5971,14 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
59745971

59755972
def analyze_cond_branch(
59765973
self,
5977-
map: dict[Expression, Type] | None,
5974+
map: dict[Expression, Type],
59785975
node: Expression,
59795976
context: Type | None,
59805977
allow_none_return: bool = False,
59815978
suppress_unreachable_errors: bool = True,
59825979
) -> Type:
59835980
with self.chk.binder.frame_context(can_skip=True, fall_through=0):
5984-
if map is None:
5981+
if mypy.checker.is_unreachable_map(map):
59855982
# We still need to type check node, in case we want to
59865983
# process it for isinstance checks later. Since the branch was
59875984
# determined to be unreachable, any errors should be suppressed.

mypy/stubutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
)
3232

3333
# Modules that may fail when imported, or that may have side effects (fully qualified).
34-
NOT_IMPORTABLE_MODULES = ()
34+
NOT_IMPORTABLE_MODULES: tuple[str, ...] = ()
3535

3636
# Typing constructs to be replaced by their builtin equivalents.
3737
TYPING_BUILTIN_REPLACEMENTS: Final = {

test-data/unit/check-isinstance.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,6 @@ if y in x:
21412141
else:
21422142
reveal_type(y) # N: Revealed type is "builtins.int | None"
21432143
[builtins fixtures/list.pyi]
2144-
[out]
21452144

21462145
[case testNarrowTypeAfterInListNested]
21472146
# flags: --warn-unreachable

test-data/unit/check-narrowing.test

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,14 +3246,15 @@ class B: ...
32463246

32473247
T = TypeVar("T")
32483248

3249-
def forgets_about_subclasses1(self, obj: T) -> T:
3249+
def forgets_about_subclasses(self, obj: T) -> T:
32503250
if isinstance(obj, A):
32513251
return A() # E: Incompatible return value type (got "A", expected "T")
32523252
elif isinstance(obj, B):
32533253
return B() # E: Incompatible return value type (got "B", expected "T")
32543254
raise
32553255

32563256
def correct1(self, obj: T) -> T:
3257+
# TODO: mypy should not error in this case
32573258
if type(obj) == A:
32583259
return A() # E: Incompatible return value type (got "A", expected "T")
32593260
elif type(obj) == B:
@@ -3262,7 +3263,10 @@ def correct1(self, obj: T) -> T:
32623263

32633264
T_value = TypeVar("T_value", A, B)
32643265

3265-
def forgets_about_subclasses2(self, obj: T_value) -> T_value:
3266+
def forgets_about_multiple_inheritance(self, obj: T_value) -> T_value:
3267+
# Note that it is a little confusing that mypy only errors in the first branch here, but this
3268+
# is a branch that would be taken by a subclass of both A and B.
3269+
# See also https://github.com/python/mypy/issues/10302#issuecomment-3832182574
32663270
if isinstance(obj, A):
32673271
return A() # E: Incompatible return value type (got "A", expected "B")
32683272
elif isinstance(obj, B):
@@ -3479,6 +3483,71 @@ def bar(X: type[T]) -> T:
34793483
raise
34803484
[builtins fixtures/type.pyi]
34813485

3486+
[case testNarrowingConstrainedTypeVarType]
3487+
# flags: --strict-equality --warn-unreachable
3488+
from typing import TypeVar, Any, Type
3489+
3490+
TargetType = TypeVar("TargetType", int, float, str)
3491+
3492+
# TODO: this behaviour is incorrect, it will be fixed by improving reachability
3493+
def convert_type(target_type: Type[TargetType]) -> TargetType:
3494+
if target_type == str:
3495+
return str() # E: Incompatible return value type (got "str", expected "int") \
3496+
# E: Incompatible return value type (got "str", expected "float")
3497+
if target_type == int:
3498+
return int() # E: Incompatible return value type (got "int", expected "str")
3499+
if target_type == float:
3500+
return float() # E: Incompatible return value type (got "float", expected "int") \
3501+
# E: Incompatible return value type (got "float", expected "str")
3502+
raise
3503+
[builtins fixtures/primitives.pyi]
3504+
3505+
3506+
[case testNarrowingEqualityWithPromotions]
3507+
# flags: --strict-equality --warn-unreachable
3508+
from __future__ import annotations
3509+
from typing import Literal
3510+
3511+
# TODO: the behaviour on some of these test cases is incorrect
3512+
def f1(number: float, i: int):
3513+
if number == i:
3514+
reveal_type(number) # N: Revealed type is "builtins.float"
3515+
reveal_type(i) # N: Revealed type is "builtins.int"
3516+
3517+
def f2(number: float, five: Literal[5]):
3518+
if number == five:
3519+
reveal_type(number) # E: Statement is unreachable
3520+
reveal_type(five)
3521+
3522+
def f3(number: float | int, five: Literal[5]):
3523+
if number == five:
3524+
reveal_type(number) # N: Revealed type is "Literal[5]"
3525+
reveal_type(five) # N: Revealed type is "Literal[5]"
3526+
3527+
def f4(number: float | None, i: int):
3528+
if number == i:
3529+
reveal_type(number) # N: Revealed type is "builtins.float | None"
3530+
reveal_type(i) # N: Revealed type is "builtins.int"
3531+
3532+
def f5(number: float | int, i: int):
3533+
if number == i:
3534+
reveal_type(number) # N: Revealed type is "builtins.int"
3535+
reveal_type(i) # N: Revealed type is "builtins.int"
3536+
3537+
def f6(number: float | complex, i: int):
3538+
if number == i:
3539+
reveal_type(number) # N: Revealed type is "builtins.float | builtins.complex"
3540+
reveal_type(i) # N: Revealed type is "builtins.int"
3541+
3542+
class Custom:
3543+
def __eq__(self, other: object) -> bool: return True
3544+
3545+
def f7(number: float, x: Custom | int):
3546+
if number == x:
3547+
reveal_type(number) # N: Revealed type is "builtins.float"
3548+
reveal_type(x) # N: Revealed type is "__main__.Custom"
3549+
[builtins fixtures/primitives.pyi]
3550+
34823551
[case testNarrowingAnyNegativeIntersection-xfail]
34833552
# flags: --strict-equality --warn-unreachable
34843553
# https://github.com/python/mypy/issues/20597

test-data/unit/check-protocols.test

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2940,7 +2940,25 @@ class Gleemer:
29402940

29412941

29422942
[case testPartialTypeProtocolHashable]
2943-
# flags: --no-strict-optional
2943+
# flags: --no-strict-optional --no-local-partial-types
2944+
from typing import Protocol
2945+
2946+
class Hashable(Protocol):
2947+
def __hash__(self) -> int: ...
2948+
2949+
class ObjectHashable:
2950+
def __hash__(self) -> int: ...
2951+
2952+
class DataArray(ObjectHashable):
2953+
__hash__ = None
2954+
2955+
def f(self, x: Hashable) -> None:
2956+
reveal_type([self, x]) # N: Revealed type is "builtins.list[builtins.object]"
2957+
[builtins fixtures/tuple.pyi]
2958+
2959+
2960+
[case testPartialTypeProtocolHashableLocalPartialTypes]
2961+
# flags: --no-strict-optional --local-partial-types
29442962
from typing import Protocol
29452963

29462964
class Hashable(Protocol):

test-data/unit/check-python310.test

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ match m:
3636
case other:
3737
reveal_type(other) # N: Revealed type is "Any"
3838

39-
[case testMatchLiteralPatternAlreadyNarrower-skip]
40-
m: bool
39+
[case testMatchLiteralPatternBoolIntAlreadyNarrower]
40+
# flags: --strict-equality --warn-unreachable
4141

42-
match m:
43-
case 1:
44-
reveal_type(m) # This should probably be unreachable, but isn't detected as such.
42+
def foo(m: bool, x: int):
43+
match m:
44+
case 1:
45+
reveal_type(m) # N: Revealed type is "Literal[1]"
46+
47+
match m:
48+
case x:
49+
reveal_type(m) # N: Revealed type is "builtins.bool"
4550
[builtins fixtures/primitives.pyi]
4651

4752
[case testMatchLiteralPatternUnreachable]
@@ -402,7 +407,7 @@ match d.tag:
402407
reveal_type(d) # N: Revealed type is "__main__.B"
403408
reveal_type(d.num) # N: Revealed type is "builtins.int"
404409

405-
[case testMatchSequenceUnion-skip]
410+
[case testMatchSequenceUnion]
406411
from typing import List, Union
407412
m: Union[List[List[str]], str]
408413

@@ -1878,7 +1883,8 @@ match m:
18781883
assert_never(unreachable)
18791884
[builtins fixtures/enum.pyi]
18801885

1881-
[case testMatchLiteralPatternEnumCustomEquals-skip]
1886+
[case testMatchLiteralPatternEnumCustomEquals]
1887+
# flags: --strict-equality --warn-unreachable
18821888
from enum import Enum
18831889
class Medal(Enum):
18841890
gold = 1
@@ -1891,9 +1897,9 @@ m: Medal
18911897

18921898
match m:
18931899
case Medal.gold:
1894-
reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]"
1895-
case _:
18961900
reveal_type(m) # N: Revealed type is "__main__.Medal"
1901+
case _:
1902+
reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.silver] | Literal[__main__.Medal.bronze]"
18971903
[builtins fixtures/enum.pyi]
18981904

18991905
[case testMatchNarrowUsingPatternGuardSpecialCase]
@@ -2035,7 +2041,8 @@ match c:
20352041
assert_never(c) # E: Argument 1 to "assert_never" has incompatible type "Literal['red']"; expected "Never"
20362042
[typing fixtures/typing-typeddict.pyi]
20372043

2038-
[case testMatchAsPatternIntersection-skip]
2044+
[case testMatchAsPatternIntersection]
2045+
# flags: --strict-equality --warn-unreachable
20392046
class A: pass
20402047
class B: pass
20412048
class C: pass
@@ -2046,7 +2053,7 @@ def f(x: A) -> None:
20462053
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.B">"
20472054
case C() as y:
20482055
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.C">"
2049-
reveal_type(y) # N: Revealed type is "Union[__main__.<subclass of "__main__.A" and "__main__.B">, __main__.<subclass of "__main__.A" and "__main__.C">]"
2056+
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.B"> | __main__.<subclass of "__main__.A" and "__main__.C">"
20502057

20512058
[case testMatchWithBreakAndContinue]
20522059
def f(x: int | str | None) -> None:

test-data/unit/check-tuples.test

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,17 +1533,24 @@ reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]"
15331533
[builtins fixtures/tuple.pyi]
15341534

15351535
[case testTupleOverlapDifferentTuples]
1536+
# flags: --warn-unreachable
15361537
from typing import Optional, Tuple
15371538
class A: pass
15381539
class B: pass
15391540

1540-
possibles: Tuple[int, Tuple[A]]
1541-
x: Optional[Tuple[B]]
1541+
def f1(possibles: Tuple[int, Tuple[A]], x: Optional[Tuple[B]]):
1542+
if x in possibles:
1543+
reveal_type(x) # N: Revealed type is "tuple[__main__.B]"
1544+
else:
1545+
reveal_type(x) # N: Revealed type is "tuple[__main__.B] | None"
1546+
1547+
class AA(A): pass
15421548

1543-
if x in possibles:
1544-
reveal_type(x) # N: Revealed type is "tuple[__main__.B]"
1545-
else:
1546-
reveal_type(x) # N: Revealed type is "tuple[__main__.B] | None"
1549+
def f2(possibles: Tuple[int, Tuple[A]], x: Optional[Tuple[AA]]):
1550+
if x in possibles:
1551+
reveal_type(x) # N: Revealed type is "tuple[__main__.AA]"
1552+
else:
1553+
reveal_type(x) # N: Revealed type is "tuple[__main__.AA] | None"
15471554

15481555
[builtins fixtures/tuple.pyi]
15491556

0 commit comments

Comments
 (0)