Skip to content

Commit f6520c8

Browse files
Narrow falsey str/bytes/int to literal type (#17818)
Closes #16891 ### Before ```python from typing import Literal def f1(a: str) -> Literal[""]: return a and exit() # E: Incompatible return value type (got "str", expected "Literal['']") def f2(a: int) -> Literal[0]: return a and exit() # E: Incompatible return value type (got "int", expected "Literal[0]") def f3(a: bytes) -> Literal[b""]: return a and exit() # E: Incompatible return value type (got "bytes", expected "Literal[b'']") ``` ### After ```none Success: no issues found in 1 source file ```
1 parent 7f3d7f8 commit f6520c8

File tree

5 files changed

+31
-11
lines changed

5 files changed

+31
-11
lines changed

mypy/typeops.py

+4
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,10 @@ def false_only(t: Type) -> ProperType:
657657
new_items = [false_only(item) for item in t.items]
658658
can_be_false_items = [item for item in new_items if item.can_be_false]
659659
return make_simplified_union(can_be_false_items, line=t.line, column=t.column)
660+
elif isinstance(t, Instance) and t.type.fullname in ("builtins.str", "builtins.bytes"):
661+
return LiteralType("", fallback=t)
662+
elif isinstance(t, Instance) and t.type.fullname == "builtins.int":
663+
return LiteralType(0, fallback=t)
660664
else:
661665
ret_type = _get_type_method_ret_type(t, name="__bool__") or _get_type_method_ret_type(
662666
t, name="__len__"

test-data/unit/check-expressions.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ b: bool
342342
i: str
343343
j = b or i
344344
if not j:
345-
reveal_type(j) # N: Revealed type is "builtins.str"
345+
reveal_type(j) # N: Revealed type is "Literal['']"
346346
[builtins fixtures/bool.pyi]
347347

348348
[case testAndOr]

test-data/unit/check-narrowing.test

+17-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ str_or_false: Union[Literal[False], str]
10071007
if str_or_false:
10081008
reveal_type(str_or_false) # N: Revealed type is "builtins.str"
10091009
else:
1010-
reveal_type(str_or_false) # N: Revealed type is "Union[Literal[False], builtins.str]"
1010+
reveal_type(str_or_false) # N: Revealed type is "Union[Literal[False], Literal['']]"
10111011

10121012
true_or_false: Literal[True, False]
10131013

@@ -1017,6 +1017,22 @@ else:
10171017
reveal_type(true_or_false) # N: Revealed type is "Literal[False]"
10181018
[builtins fixtures/primitives.pyi]
10191019

1020+
[case testNarrowingFalseyToLiteral]
1021+
from typing import Union
1022+
1023+
a: str
1024+
b: bytes
1025+
c: int
1026+
d: Union[str, bytes, int]
1027+
1028+
if not a:
1029+
reveal_type(a) # N: Revealed type is "Literal['']"
1030+
if not b:
1031+
reveal_type(b) # N: Revealed type is "Literal[b'']"
1032+
if not c:
1033+
reveal_type(c) # N: Revealed type is "Literal[0]"
1034+
if not d:
1035+
reveal_type(d) # N: Revealed type is "Union[Literal[''], Literal[b''], Literal[0]]"
10201036

10211037
[case testNarrowingIsInstanceFinalSubclass]
10221038
# flags: --warn-unreachable

test-data/unit/check-optional.test

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ x = None # type: Optional[int]
5353
if x:
5454
reveal_type(x) # N: Revealed type is "builtins.int"
5555
else:
56-
reveal_type(x) # N: Revealed type is "Union[builtins.int, None]"
56+
reveal_type(x) # N: Revealed type is "Union[Literal[0], None]"
5757
[builtins fixtures/bool.pyi]
5858

5959
[case testIfNotCases]
6060
from typing import Optional
6161
x = None # type: Optional[int]
6262
if not x:
63-
reveal_type(x) # N: Revealed type is "Union[builtins.int, None]"
63+
reveal_type(x) # N: Revealed type is "Union[Literal[0], None]"
6464
else:
6565
reveal_type(x) # N: Revealed type is "builtins.int"
6666
[builtins fixtures/bool.pyi]
@@ -109,13 +109,13 @@ reveal_type(z2) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
109109
from typing import Optional
110110
x = None # type: Optional[str]
111111
y1 = x and 'b'
112-
reveal_type(y1) # N: Revealed type is "Union[builtins.str, None]"
112+
reveal_type(y1) # N: Revealed type is "Union[Literal[''], None, builtins.str]"
113113
y2 = x and 1 # x could be '', so...
114-
reveal_type(y2) # N: Revealed type is "Union[builtins.str, None, builtins.int]"
114+
reveal_type(y2) # N: Revealed type is "Union[Literal[''], None, builtins.int]"
115115
z1 = 'b' and x
116116
reveal_type(z1) # N: Revealed type is "Union[builtins.str, None]"
117117
z2 = int() and x
118-
reveal_type(z2) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
118+
reveal_type(z2) # N: Revealed type is "Union[Literal[0], builtins.str, None]"
119119

120120
[case testLambdaReturningNone]
121121
f = lambda: None

test-data/unit/check-python38.test

+4-4
Original file line numberDiff line numberDiff line change
@@ -463,9 +463,9 @@ def check_partial_list() -> None:
463463
if (x := 0):
464464
reveal_type(x) # E: Statement is unreachable
465465
else:
466-
reveal_type(x) # N: Revealed type is "builtins.int"
466+
reveal_type(x) # N: Revealed type is "Literal[0]"
467467

468-
reveal_type(x) # N: Revealed type is "builtins.int"
468+
reveal_type(x) # N: Revealed type is "Literal[0]"
469469

470470
[case testWalrusAssignmentAndConditionScopeForProperty]
471471
# flags: --warn-unreachable
@@ -483,7 +483,7 @@ wrapper = PropertyWrapper()
483483
if x := wrapper.f:
484484
reveal_type(x) # N: Revealed type is "builtins.str"
485485
else:
486-
reveal_type(x) # N: Revealed type is "builtins.str"
486+
reveal_type(x) # N: Revealed type is "Literal['']"
487487

488488
reveal_type(x) # N: Revealed type is "builtins.str"
489489

@@ -505,7 +505,7 @@ def f() -> str: ...
505505
if x := f():
506506
reveal_type(x) # N: Revealed type is "builtins.str"
507507
else:
508-
reveal_type(x) # N: Revealed type is "builtins.str"
508+
reveal_type(x) # N: Revealed type is "Literal['']"
509509

510510
reveal_type(x) # N: Revealed type is "builtins.str"
511511

0 commit comments

Comments
 (0)