Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 138 additions & 152 deletions mypy/checker.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion mypy/test/testargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import argparse
import sys
from typing import Any, cast

from mypy.main import infer_python_executable, process_options
from mypy.options import Options
Expand Down Expand Up @@ -63,7 +64,7 @@ def test_executable_inference(self) -> None:

# first test inferring executable from version
options = Options()
options.python_executable = None
options.python_executable = cast(Any, None)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little unfortunate, because infer_python_executable has side effects we infer more code as unreachable

options.python_version = sys.version_info[:2]
infer_python_executable(options, special_opts)
assert options.python_version == sys.version_info[:2]
Expand Down
4 changes: 2 additions & 2 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ def test_generic_function_type(self) -> None:

def test_type_alias_expand_once(self) -> None:
A, target = self.fx.def_alias_1(self.fx.a)
assert get_proper_type(A) == target
assert get_proper_type(target) == target
assert get_proper_type(A) == target
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fun, from this assert mypy now knows that target is a proper type, making get_proper_type(target) redundant :-)


A, target = self.fx.def_alias_2(self.fx.a)
assert get_proper_type(A) == target
assert get_proper_type(target) == target
assert get_proper_type(A) == target

def test_recursive_nested_in_non_recursive(self) -> None:
A, _ = self.fx.def_alias_1(self.fx.a)
Expand Down
6 changes: 4 additions & 2 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ def is_singleton_type(typ: Type) -> bool:
return typ.is_singleton_type()


def try_expanding_sum_type_to_union(typ: Type, target_fullname: str) -> Type:
def try_expanding_sum_type_to_union(typ: Type, target_fullname: str | None) -> Type:
"""Attempts to recursively expand any enum Instances with the given target_fullname
into a Union of all of its component LiteralTypes.

Expand Down Expand Up @@ -1034,7 +1034,9 @@ class Status(Enum):
]
return UnionType.make_union(items)

if isinstance(typ, Instance) and typ.type.fullname == target_fullname:
if isinstance(typ, Instance) and (
target_fullname is None or typ.type.fullname == target_fullname
):
if typ.type.fullname == "builtins.bool":
return UnionType([LiteralType(True, typ), LiteralType(False, typ)])

Expand Down
6 changes: 6 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -4920,6 +4920,7 @@ def test_setattr() -> None:
assert i.one == 1
assert i.two == None
assert i.const == 42
i = i
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests have a "check initial values" then call __setattr__ then check subsequent values. We do a self assign to reset attribute narrowing


i.__setattr__("two", "2")
assert i.two == "2"
Expand Down Expand Up @@ -4957,6 +4958,7 @@ def test_setattr_inherited() -> None:
assert i.one == 1
assert i.two == None
assert i.const == 42
i = i

i.__setattr__("two", "2")
assert i.two == "2"
Expand Down Expand Up @@ -4996,6 +4998,7 @@ def test_setattr_overridden() -> None:
assert i.one == 1
assert i.two == None
assert i.const == 42
i = i

i.__setattr__("two", "2")
assert i.two == "2"
Expand Down Expand Up @@ -5064,6 +5067,7 @@ def test_setattr_nonnative() -> None:
assert i.one == 1
assert i.two == None
assert i.const == 42
i = i

i.__setattr__("two", "2")
assert i.two == "2"
Expand Down Expand Up @@ -5134,6 +5138,8 @@ def test_no_setattr_nonnative() -> None:
object.__setattr__(i, "three", 102)
assert i.three == 102

i = i

del i.three
assert i.three == None

Expand Down
9 changes: 4 additions & 5 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ else:
reveal_type(z) # No output: this branch is unreachable
[builtins fixtures/bool.pyi]

[case testEnumReachabilityNoNarrowingForUnionMessiness]
[case testEnumReachabilityNarrowingForUnionMessiness]
from enum import Enum
from typing import Literal

Expand All @@ -1045,17 +1045,16 @@ x: Foo
y: Literal[Foo.A, Foo.B]
z: Literal[Foo.B, Foo.C]

# For the sake of simplicity, no narrowing is done when the narrower type is a Union.
if x is y:
reveal_type(x) # N: Revealed type is "__main__.Foo"
reveal_type(x) # N: Revealed type is "Literal[__main__.Foo.A] | Literal[__main__.Foo.B]"
reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A] | Literal[__main__.Foo.B]"
else:
reveal_type(x) # N: Revealed type is "__main__.Foo"
reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A] | Literal[__main__.Foo.B]"

if y is z:
reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A] | Literal[__main__.Foo.B]"
reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.B] | Literal[__main__.Foo.C]"
reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.B]"
reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.B]"
else:
reveal_type(y) # N: Revealed type is "Literal[__main__.Foo.A] | Literal[__main__.Foo.B]"
reveal_type(z) # N: Revealed type is "Literal[__main__.Foo.B] | Literal[__main__.Foo.C]"
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -2377,7 +2377,7 @@ x: int = "" # E: Incompatible types in assignment (expression has type "str", v
x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int")

[case testDisableBytearrayPromotion]
# flags: --disable-bytearray-promotion --strict-equality
# flags: --disable-bytearray-promotion --strict-equality --warn-unreachable
def f(x: bytes) -> None: ...
f(bytearray(b"asdf")) # E: Argument 1 to "f" has incompatible type "bytearray"; expected "bytes"
f(memoryview(b"asdf"))
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -2729,7 +2729,7 @@ from typing import Union

y: str
if type(y) is int: # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures
y # E: Statement is unreachable
y # E: Statement is unreachable
else:
reveal_type(y) # N: Revealed type is "builtins.str"
[builtins fixtures/isinstance.pyi]
Expand Down Expand Up @@ -2760,6 +2760,7 @@ else:
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str"

[case testTypeEqualsMultipleTypesShouldntNarrow]
# flags: --warn-unreachable
# make sure we don't do any narrowing if there are multiple types being compared

from typing import Union
Expand Down
115 changes: 111 additions & 4 deletions test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -1364,13 +1364,13 @@ class A: ...
val: Optional[A]

if val == None:
reveal_type(val) # N: Revealed type is "__main__.A | None"
reveal_type(val) # N: Revealed type is "None"
else:
reveal_type(val) # N: Revealed type is "__main__.A"
if val != None:
reveal_type(val) # N: Revealed type is "__main__.A"
else:
reveal_type(val) # N: Revealed type is "__main__.A | None"
reveal_type(val) # N: Revealed type is "None"

if val in (None,):
reveal_type(val) # N: Revealed type is "__main__.A | None"
Expand All @@ -1380,6 +1380,19 @@ if val not in (None,):
reveal_type(val) # N: Revealed type is "__main__.A | None"
else:
reveal_type(val) # N: Revealed type is "__main__.A | None"

class Hmm:
def __eq__(self, other) -> bool: ...

hmm: Optional[Hmm]
if hmm == None:
reveal_type(hmm) # N: Revealed type is "__main__.Hmm | None"
else:
reveal_type(hmm) # N: Revealed type is "__main__.Hmm"
if hmm != None:
reveal_type(hmm) # N: Revealed type is "__main__.Hmm"
else:
reveal_type(hmm) # N: Revealed type is "__main__.Hmm | None"
[builtins fixtures/primitives.pyi]

[case testNarrowingWithTupleOfTypes]
Expand Down Expand Up @@ -2277,13 +2290,13 @@ def f4(x: SE) -> None:
# https://github.com/python/mypy/issues/17864
def f(x: str | int) -> None:
if x == "x":
reveal_type(x) # N: Revealed type is "builtins.str | builtins.int"
reveal_type(x) # N: Revealed type is "builtins.str"
y = x

if x in ["x"]:
# TODO: we should fix this reveal https://github.com/python/mypy/issues/3229
reveal_type(x) # N: Revealed type is "builtins.str | builtins.int"
y = x
y = x # E: Incompatible types in assignment (expression has type "str | int", variable has type "str")
z = x
z = y
[builtins fixtures/primitives.pyi]
Expand Down Expand Up @@ -2699,3 +2712,97 @@ reveal_type(t.foo) # N: Revealed type is "__main__.D"
t.foo = C1()
reveal_type(t.foo) # N: Revealed type is "__main__.C"
[builtins fixtures/property.pyi]

[case testNarrowingNotImplemented]
from __future__ import annotations
from typing_extensions import Self

class X:
def __divmod__(self, other: Self | int) -> tuple[Self, Self]: ...

def __floordiv__(self, other: Self | int) -> Self:
qr = self.__divmod__(other)
if qr is NotImplemented:
return NotImplemented
return qr[0]
[builtins fixtures/notimplemented.pyi]


[case testNarrowingBooleans]
# flags: --warn-return-any
from typing import Any

def foo(x: dict[str, Any]) -> bool:
if x.get("event") is False:
return False
if x.get("event") is True:
return True
raise
[builtins fixtures/dict.pyi]


[case testNarrowingTypeObjects]
from __future__ import annotations
from typing import Callable, Any, TypeVar, Generic, Protocol
_T_co = TypeVar('_T_co', covariant=True)

class Boxxy(Protocol[_T_co]):
def get_box(self) -> _T_co: ...

class TupleLike(Generic[_T_co]):
def __init__(self, iterable: Boxxy[_T_co], /) -> None:
raise

class Box1(Generic[_T_co]):
def __init__(self, content: _T_co, /) -> None: ...
def get_box(self) -> _T_co: raise

class Box2(Generic[_T_co]):
def __init__(self, content: _T_co, /) -> None: ...
def get_box(self) -> _T_co: raise

def get_type(setting_name: str) -> Callable[[Box1], Any] | type[Any]:
raise

def main(key: str):
existing_value_type = get_type(key)
if existing_value_type is TupleLike:
reveal_type(TupleLike) # N: Revealed type is "def [_T_co] (__main__.Boxxy[_T_co`1]) -> __main__.TupleLike[_T_co`1]"
TupleLike(Box2("str"))
[builtins fixtures/tuple.pyi]

[case testNarrowingCollections]
# flags: --warn-unreachable
from typing import cast

class X:
def __init__(self) -> None:
self.x: dict[str, str] = {}
self.y: list[str] = []

def xxx(self) -> None:
if self.x == {}:
reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
self.x["asdf"]

if self.x == dict():
reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
self.x["asdf"]

if self.x == cast(dict[int, int], {}):
reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
self.x["asdf"]

def yyy(self) -> None:
if self.y == []:
reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]"
self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist"

if self.y == list():
reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]"
self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist"

if self.y == cast(list[int], []):
reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]"
self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist"
[builtins fixtures/dict.pyi]
Loading