Skip to content

Commit

Permalink
Sync typeshed, fix strict equality dict view checks (#11200)
Browse files Browse the repository at this point in the history
Source commit:
python/typeshed@a9227ed

Fixes for python/typeshed#6039

Co-authored-by: hauntsaninja <>
  • Loading branch information
hauntsaninja committed Sep 26, 2021
1 parent 3ec0284 commit d469295
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 49 deletions.
8 changes: 5 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@

# Types considered safe for comparisons with --strict-equality due to known behaviour of __eq__.
# NOTE: All these types are subtypes of AbstractSet.
OVERLAPPING_TYPES_WHITELIST: Final = [
OVERLAPPING_TYPES_ALLOWLIST: Final = [
"builtins.set",
"builtins.frozenset",
"typing.KeysView",
"typing.ItemsView",
"builtins._dict_keys",
"builtins._dict_items",
]


Expand Down Expand Up @@ -2357,8 +2359,8 @@ def dangerous_comparison(self, left: Type, right: Type,
return False
if isinstance(left, Instance) and isinstance(right, Instance):
# Special case some builtin implementations of AbstractSet.
if (left.type.fullname in OVERLAPPING_TYPES_WHITELIST and
right.type.fullname in OVERLAPPING_TYPES_WHITELIST):
if (left.type.fullname in OVERLAPPING_TYPES_ALLOWLIST and
right.type.fullname in OVERLAPPING_TYPES_ALLOWLIST):
abstract_set = self.chk.lookup_typeinfo('typing.AbstractSet')
left = map_instance_to_supertype(left, abstract_set)
right = map_instance_to_supertype(right, abstract_set)
Expand Down
28 changes: 24 additions & 4 deletions mypy/typeshed/stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ from _typeshed import (
)
from ast import AST, mod
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from types import CodeType, TracebackType
from types import CodeType, MappingProxyType, TracebackType
from typing import (
IO,
AbstractSet,
Expand Down Expand Up @@ -73,6 +73,8 @@ _T_co = TypeVar("_T_co", covariant=True)
_T_contra = TypeVar("_T_contra", contravariant=True)
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_KT_co = TypeVar("_KT_co", covariant=True) # Key type covariant containers.
_VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers.
_S = TypeVar("_S")
_T1 = TypeVar("_T1")
_T2 = TypeVar("_T2")
Expand Down Expand Up @@ -787,6 +789,20 @@ class list(MutableSequence[_T], Generic[_T]):
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any) -> GenericAlias: ...

class _dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]):
if sys.version_info >= (3, 10):
mapping: MappingProxyType[_KT_co, _VT_co]

# The generics are the wrong way around because of a mypy limitation
# https://github.com/python/mypy/issues/11138
class _dict_values(ValuesView[_VT_co], Generic[_VT_co, _KT_co]):
if sys.version_info >= (3, 10):
mapping: MappingProxyType[_KT_co, _VT_co]

class _dict_items(ItemsView[_KT_co, _VT_co], Generic[_KT_co, _VT_co]):
if sys.version_info >= (3, 10):
mapping: MappingProxyType[_KT_co, _VT_co]

class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
@overload
def __init__(self: dict[_KT, _VT]) -> None: ...
Expand All @@ -796,6 +812,10 @@ class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
def __init__(self, map: SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...
@overload
def __init__(self, iterable: Iterable[Tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
# Next overload is for dict(string.split(sep) for string in iterable)
# Cannot be Iterable[Sequence[_T]] or otherwise dict(["foo", "bar", "baz"]) is not an error
@overload
def __init__(self: dict[str, str], iterable: Iterable[list[str]]) -> None: ...
def __new__(cls: Type[_T1], *args: Any, **kwargs: Any) -> _T1: ...
def clear(self) -> None: ...
def copy(self) -> dict[_KT, _VT]: ...
Expand All @@ -807,9 +827,9 @@ class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
def update(self, __m: Iterable[Tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
@overload
def update(self, **kwargs: _VT) -> None: ...
def keys(self) -> KeysView[_KT]: ...
def values(self) -> ValuesView[_VT]: ...
def items(self) -> ItemsView[_KT, _VT]: ...
def keys(self) -> _dict_keys[_KT, _VT]: ...
def values(self) -> _dict_values[_VT, _KT]: ...
def items(self) -> _dict_items[_KT, _VT]: ...
@classmethod
@overload
def fromkeys(cls, __iterable: Iterable[_T], __value: None = ...) -> dict[_T, Any | None]: ...
Expand Down
35 changes: 14 additions & 21 deletions mypy/typeshed/stdlib/collections/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import sys
from _typeshed import Self
from builtins import _dict_items, _dict_keys, _dict_values
from typing import Any, Dict, Generic, NoReturn, Tuple, Type, TypeVar, overload

if sys.version_info >= (3, 10):
from typing import (
Callable,
ItemsView,
Iterable,
Iterator,
KeysView,
Mapping,
MutableMapping,
MutableSequence,
Reversible,
Sequence,
ValuesView,
)
from typing import Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Reversible, Sequence
else:
from _collections_abc import *

_S = TypeVar("_S")
_T = TypeVar("_T")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)

# namedtuple is special-cased in the type checker; the initializer is ignored.
if sys.version_info >= (3, 7):
Expand Down Expand Up @@ -247,23 +238,25 @@ class Counter(Dict[_T, int], Generic[_T]):
def __iand__(self, other: Counter[_T]) -> Counter[_T]: ...
def __ior__(self, other: Counter[_T]) -> Counter[_T]: ... # type: ignore

class _OrderedDictKeysView(KeysView[_KT], Reversible[_KT]):
def __reversed__(self) -> Iterator[_KT]: ...
class _OrderedDictKeysView(_dict_keys[_KT_co, _VT_co], Reversible[_KT_co]):
def __reversed__(self) -> Iterator[_KT_co]: ...

class _OrderedDictItemsView(ItemsView[_KT, _VT], Reversible[Tuple[_KT, _VT]]):
def __reversed__(self) -> Iterator[Tuple[_KT, _VT]]: ...
class _OrderedDictItemsView(_dict_items[_KT_co, _VT_co], Reversible[Tuple[_KT_co, _VT_co]]):
def __reversed__(self) -> Iterator[Tuple[_KT_co, _VT_co]]: ...

class _OrderedDictValuesView(ValuesView[_VT], Reversible[_VT]):
def __reversed__(self) -> Iterator[_VT]: ...
# The generics are the wrong way around because of a mypy limitation
# https://github.com/python/mypy/issues/11138
class _OrderedDictValuesView(_dict_values[_VT_co, _KT_co], Reversible[_VT_co], Generic[_VT_co, _KT_co]):
def __reversed__(self) -> Iterator[_VT_co]: ...

class OrderedDict(Dict[_KT, _VT], Reversible[_KT], Generic[_KT, _VT]):
def popitem(self, last: bool = ...) -> Tuple[_KT, _VT]: ...
def move_to_end(self, key: _KT, last: bool = ...) -> None: ...
def copy(self: _S) -> _S: ...
def __reversed__(self) -> Iterator[_KT]: ...
def keys(self) -> _OrderedDictKeysView[_KT]: ...
def keys(self) -> _OrderedDictKeysView[_KT, _VT]: ...
def items(self) -> _OrderedDictItemsView[_KT, _VT]: ...
def values(self) -> _OrderedDictValuesView[_VT]: ...
def values(self) -> _OrderedDictValuesView[_VT, _KT]: ...

class defaultdict(Dict[_KT, _VT], Generic[_KT, _VT]):
default_factory: Callable[[], _VT] | None
Expand Down
86 changes: 76 additions & 10 deletions mypy/typeshed/stdlib/importlib/abc.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import sys
import types
from _typeshed import StrOrBytesPath
from _typeshed import (
OpenBinaryMode,
OpenBinaryModeReading,
OpenBinaryModeUpdating,
OpenBinaryModeWriting,
OpenTextMode,
StrOrBytesPath,
StrPath,
)
from abc import ABCMeta, abstractmethod
from importlib.machinery import ModuleSpec
from typing import IO, Any, Iterator, Mapping, Protocol, Sequence, Tuple, Union
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from typing import IO, Any, BinaryIO, Iterator, Mapping, Protocol, Sequence, Tuple, Union, overload
from typing_extensions import Literal, runtime_checkable

_Path = Union[bytes, str]
Expand Down Expand Up @@ -84,22 +93,79 @@ if sys.version_info >= (3, 7):
if sys.version_info >= (3, 9):
@runtime_checkable
class Traversable(Protocol):
@abstractmethod
def is_dir(self) -> bool: ...
@abstractmethod
def is_file(self) -> bool: ...
@abstractmethod
def iterdir(self) -> Iterator[Traversable]: ...
@abstractmethod
def read_bytes(self) -> bytes: ...
def joinpath(self, child: StrPath) -> Traversable: ...
# The .open method comes from pathlib.pyi and should be kept in sync.
@overload
@abstractmethod
def read_text(self, encoding: str | None = ...) -> str: ...
def open(
self,
mode: OpenTextMode = ...,
buffering: int = ...,
encoding: str | None = ...,
errors: str | None = ...,
newline: str | None = ...,
) -> TextIOWrapper: ...
# Unbuffered binary mode: returns a FileIO
@overload
@abstractmethod
def is_dir(self) -> bool: ...
def open(
self, mode: OpenBinaryMode, buffering: Literal[0], encoding: None = ..., errors: None = ..., newline: None = ...
) -> FileIO: ...
# Buffering is on: return BufferedRandom, BufferedReader, or BufferedWriter
@overload
@abstractmethod
def is_file(self) -> bool: ...
def open(
self,
mode: OpenBinaryModeUpdating,
buffering: Literal[-1, 1] = ...,
encoding: None = ...,
errors: None = ...,
newline: None = ...,
) -> BufferedRandom: ...
@overload
@abstractmethod
def joinpath(self, child: _Path) -> Traversable: ...
def open(
self,
mode: OpenBinaryModeWriting,
buffering: Literal[-1, 1] = ...,
encoding: None = ...,
errors: None = ...,
newline: None = ...,
) -> BufferedWriter: ...
@overload
@abstractmethod
def __truediv__(self, child: _Path) -> Traversable: ...
def open(
self,
mode: OpenBinaryModeReading,
buffering: Literal[-1, 1] = ...,
encoding: None = ...,
errors: None = ...,
newline: None = ...,
) -> BufferedReader: ...
# Buffering cannot be determined: fall back to BinaryIO
@overload
@abstractmethod
def open(self, mode: Literal["r", "rb"] = ..., *args: Any, **kwargs: Any) -> IO[Any]: ...
@property
def open(
self, mode: OpenBinaryMode, buffering: int, encoding: None = ..., errors: None = ..., newline: None = ...
) -> BinaryIO: ...
# Fallback if mode is not specified
@overload
@abstractmethod
def open(
self, mode: str, buffering: int = ..., encoding: str | None = ..., errors: str | None = ..., newline: str | None = ...
) -> IO[Any]: ...
@property
def name(self) -> str: ...
@abstractmethod
def __truediv__(self, key: StrPath) -> Traversable: ...
@abstractmethod
def read_bytes(self) -> bytes: ...
@abstractmethod
def read_text(self, encoding: str | None = ...) -> str: ...
23 changes: 17 additions & 6 deletions mypy/typeshed/stdlib/inspect.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,24 @@ if sys.version_info >= (3, 10):
else:
def signature(obj: Callable[..., Any], *, follow_wrapped: bool = ...) -> Signature: ...

class _void: ...
class _empty: ...

class Signature:
def __init__(self, parameters: Sequence[Parameter] | None = ..., *, return_annotation: Any = ...) -> None: ...
# TODO: can we be more specific here?
empty: object
def __init__(
self, parameters: Sequence[Parameter] | None = ..., *, return_annotation: Any = ..., __validate_parameters__: bool = ...
) -> None: ...
empty: _empty
@property
def parameters(self) -> types.MappingProxyType[str, Parameter]: ...
# TODO: can we be more specific here?
@property
def return_annotation(self) -> Any: ...
def bind(self, *args: Any, **kwargs: Any) -> BoundArguments: ...
def bind_partial(self, *args: Any, **kwargs: Any) -> BoundArguments: ...
def replace(self: Self, *, parameters: Sequence[Parameter] | None = ..., return_annotation: Any = ...) -> Self: ...
def replace(
self: Self, *, parameters: Sequence[Parameter] | Type[_void] | None = ..., return_annotation: Any = ...
) -> Self: ...
if sys.version_info >= (3, 10):
@classmethod
def from_callable(
Expand Down Expand Up @@ -166,7 +172,7 @@ class _ParameterKind(enum.IntEnum):

class Parameter:
def __init__(self, name: str, kind: _ParameterKind, *, default: Any = ..., annotation: Any = ...) -> None: ...
empty: Any
empty: _empty
name: str
default: Any
annotation: Any
Expand All @@ -178,7 +184,12 @@ class Parameter:
KEYWORD_ONLY: ClassVar[Literal[_ParameterKind.KEYWORD_ONLY]]
VAR_KEYWORD: ClassVar[Literal[_ParameterKind.VAR_KEYWORD]]
def replace(
self: Self, *, name: str | None = ..., kind: _ParameterKind | None = ..., default: Any = ..., annotation: Any = ...
self: Self,
*,
name: str | Type[_void] = ...,
kind: _ParameterKind | Type[_void] = ...,
default: Any = ...,
annotation: Any = ...,
) -> Self: ...

class BoundArguments:
Expand Down
2 changes: 2 additions & 0 deletions mypy/typeshed/stdlib/locale.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ D_T_FMT: int
D_FMT: int
T_FMT: int
T_FMT_AMPM: int
AM_STR: int
PM_STR: int

DAY_1: int
DAY_2: int
Expand Down
1 change: 1 addition & 0 deletions mypy/typeshed/stdlib/multiprocessing/process.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class BaseProcess:
name: str
daemon: bool
authkey: bytes
_identity: Tuple[int, ...] # undocumented
def __init__(
self,
group: None = ...,
Expand Down
1 change: 1 addition & 0 deletions mypy/typeshed/stdlib/pathlib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Path(PurePath):
def mkdir(self, mode: int = ..., parents: bool = ..., exist_ok: bool = ...) -> None: ...
# Adapted from builtins.open
# Text mode: always returns a TextIOWrapper
# The Traversable .open in stdlib/importlib/abc.pyi should be kept in sync with this.
@overload
def open(
self,
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,7 @@ def thing(stuff: StuffDict) -> int: ...
[out]
_testNewAnalyzerTypedDictInStub_newsemanal.py:2: note: Revealed type is "def (stuff: TypedDict('stub.StuffDict', {'foo': builtins.str, 'bar': builtins.int})) -> builtins.int"

[case testStrictEqualityWhitelist]
[case testStrictEqualityAllowlist]
# mypy: strict-equality
{1} == frozenset({1})
frozenset({1}) == {1}
Expand All @@ -1429,10 +1429,10 @@ frozenset({1}) == [1] # Error
{1: 2}.values() == {2} # Error
{1: 2}.keys() == [1] # Error
[out]
_testStrictEqualityWhitelist.py:5: error: Non-overlapping equality check (left operand type: "FrozenSet[int]", right operand type: "List[int]")
_testStrictEqualityWhitelist.py:11: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "Set[str]")
_testStrictEqualityWhitelist.py:12: error: Non-overlapping equality check (left operand type: "ValuesView[int]", right operand type: "Set[int]")
_testStrictEqualityWhitelist.py:13: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "List[int]")
_testStrictEqualityAllowlist.py:5: error: Non-overlapping equality check (left operand type: "FrozenSet[int]", right operand type: "List[int]")
_testStrictEqualityAllowlist.py:11: error: Non-overlapping equality check (left operand type: "_dict_keys[int, int]", right operand type: "Set[str]")
_testStrictEqualityAllowlist.py:12: error: Non-overlapping equality check (left operand type: "_dict_values[int, int]", right operand type: "Set[int]")
_testStrictEqualityAllowlist.py:13: error: Non-overlapping equality check (left operand type: "_dict_keys[int, int]", right operand type: "List[int]")

[case testUnreachableWithStdlibContextManagers]
# mypy: warn-unreachable, strict-optional
Expand Down

0 comments on commit d469295

Please sign in to comment.