Skip to content

Commit

Permalink
Narrow falsey str/bytes/int to literal type (#17818)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
brianschubert authored Sep 26, 2024
1 parent 7f3d7f8 commit f6520c8
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 11 deletions.
4 changes: 4 additions & 0 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ def false_only(t: Type) -> ProperType:
new_items = [false_only(item) for item in t.items]
can_be_false_items = [item for item in new_items if item.can_be_false]
return make_simplified_union(can_be_false_items, line=t.line, column=t.column)
elif isinstance(t, Instance) and t.type.fullname in ("builtins.str", "builtins.bytes"):
return LiteralType("", fallback=t)
elif isinstance(t, Instance) and t.type.fullname == "builtins.int":
return LiteralType(0, fallback=t)
else:
ret_type = _get_type_method_ret_type(t, name="__bool__") or _get_type_method_ret_type(
t, name="__len__"
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ b: bool
i: str
j = b or i
if not j:
reveal_type(j) # N: Revealed type is "builtins.str"
reveal_type(j) # N: Revealed type is "Literal['']"
[builtins fixtures/bool.pyi]

[case testAndOr]
Expand Down
18 changes: 17 additions & 1 deletion test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ str_or_false: Union[Literal[False], str]
if str_or_false:
reveal_type(str_or_false) # N: Revealed type is "builtins.str"
else:
reveal_type(str_or_false) # N: Revealed type is "Union[Literal[False], builtins.str]"
reveal_type(str_or_false) # N: Revealed type is "Union[Literal[False], Literal['']]"

true_or_false: Literal[True, False]

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

[case testNarrowingFalseyToLiteral]
from typing import Union

a: str
b: bytes
c: int
d: Union[str, bytes, int]

if not a:
reveal_type(a) # N: Revealed type is "Literal['']"
if not b:
reveal_type(b) # N: Revealed type is "Literal[b'']"
if not c:
reveal_type(c) # N: Revealed type is "Literal[0]"
if not d:
reveal_type(d) # N: Revealed type is "Union[Literal[''], Literal[b''], Literal[0]]"

[case testNarrowingIsInstanceFinalSubclass]
# flags: --warn-unreachable
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ x = None # type: Optional[int]
if x:
reveal_type(x) # N: Revealed type is "builtins.int"
else:
reveal_type(x) # N: Revealed type is "Union[builtins.int, None]"
reveal_type(x) # N: Revealed type is "Union[Literal[0], None]"
[builtins fixtures/bool.pyi]

[case testIfNotCases]
from typing import Optional
x = None # type: Optional[int]
if not x:
reveal_type(x) # N: Revealed type is "Union[builtins.int, None]"
reveal_type(x) # N: Revealed type is "Union[Literal[0], None]"
else:
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/bool.pyi]
Expand Down Expand Up @@ -109,13 +109,13 @@ reveal_type(z2) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
from typing import Optional
x = None # type: Optional[str]
y1 = x and 'b'
reveal_type(y1) # N: Revealed type is "Union[builtins.str, None]"
reveal_type(y1) # N: Revealed type is "Union[Literal[''], None, builtins.str]"
y2 = x and 1 # x could be '', so...
reveal_type(y2) # N: Revealed type is "Union[builtins.str, None, builtins.int]"
reveal_type(y2) # N: Revealed type is "Union[Literal[''], None, builtins.int]"
z1 = 'b' and x
reveal_type(z1) # N: Revealed type is "Union[builtins.str, None]"
z2 = int() and x
reveal_type(z2) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
reveal_type(z2) # N: Revealed type is "Union[Literal[0], builtins.str, None]"

[case testLambdaReturningNone]
f = lambda: None
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-python38.test
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,9 @@ def check_partial_list() -> None:
if (x := 0):
reveal_type(x) # E: Statement is unreachable
else:
reveal_type(x) # N: Revealed type is "builtins.int"
reveal_type(x) # N: Revealed type is "Literal[0]"

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

[case testWalrusAssignmentAndConditionScopeForProperty]
# flags: --warn-unreachable
Expand All @@ -483,7 +483,7 @@ wrapper = PropertyWrapper()
if x := wrapper.f:
reveal_type(x) # N: Revealed type is "builtins.str"
else:
reveal_type(x) # N: Revealed type is "builtins.str"
reveal_type(x) # N: Revealed type is "Literal['']"

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

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

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

Expand Down

0 comments on commit f6520c8

Please sign in to comment.