Skip to content

Commit

Permalink
fix functools.cache and operator.attrgetter
Browse files Browse the repository at this point in the history
  • Loading branch information
KotlinIsland committed Jan 8, 2025
1 parent 4a9567b commit adb1fcc
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Basedmypy Changelog

## [2.9.1]
### Fixed
- definition of `functools.cache` and `operator.attrgetter`

## [2.9.0]
### Added
- `collections.User*` should have `__repr__`
Expand Down
14 changes: 14 additions & 0 deletions docs/source/based_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,21 @@ The defined type of a variable will be shown in the message for `reveal_type`:
Typed ``functools.Cache``
-------------------------

In mypy, ``functools.cache`` is unsafe:

.. code-block:: python
@cache
def f(): ...
f(1, 2, 3) # no error
This is resolved:

.. code-block:: python
@cache
def f(): ...
f(1, 2, 3) # error: expected no args
Checked f-strings
Expand Down
24 changes: 13 additions & 11 deletions mypy/test/typetest/functools.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

from functools import _lru_cache_wrapper, lru_cache
from functools import lru_cache

# use `basedtyping` when we drop python 3.8
from types import FunctionType, MethodType
from typing import TYPE_CHECKING, Callable, Protocol
from types import MethodType
from typing import TYPE_CHECKING, Callable
from typing_extensions import assert_type

from mypy_extensions import Arg
Expand All @@ -15,31 +15,33 @@

class A:
@cache
def m(self, a: int): ...
def m(self, a: list[int]): ...

@classmethod
@cache
def c(cls, a: int): ...
def c(cls, a: list[int]): ...

@staticmethod
@cache
def s(a: int): ...
def s(a: list[int]): ...


@cache
def f(a: int): ...
def f(a: list[int]): ...


if TYPE_CHECKING:
from functools import _HashCallable, _LruCacheWrapperBase, _LruCacheWrapperMethod

a = A()
ExpectedFunction = _LruCacheWrapperBase[Callable[[Arg(int, "a")], None]]
ExpectedMethod = _LruCacheWrapperMethod[Callable[[Arg(int, "a")], None]]
ExpectedFunction = _LruCacheWrapperBase[Callable[[Arg(list[int], "a")], None]]
ExpectedMethod = _LruCacheWrapperMethod[Callable[[Arg(list[int], "a")], None]]
ExpectedMethodNone = _LruCacheWrapperMethod["() -> None"]
a = A()
a.m([1]) # type: ignore[arg-type]
assert_type(a.m, ExpectedMethod)
assert_type(a.c, ExpectedMethod)
# this is wrong, it shouldn't eat the `a` argument, but this is because of mypy `staticmethod` special casing
assert_type(a.s, ExpectedMethodNone)
assert_type(a.s, MethodType & (_LruCacheWrapperBase[Callable[[Arg(int, "a")], None]] | _HashCallable)) # type: ignore[assert-type]
assert_type(a.s, MethodType & (_LruCacheWrapperBase[Callable[[Arg(list[int], "a")], None]] | _HashCallable)) # type: ignore[assert-type]
assert_type(f.__get__(1), ExpectedMethodNone)
f([1]) # type: ignore[arg-type]
8 changes: 2 additions & 6 deletions mypy/test/typetest/operator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
from operator import attrgetter, itemgetter
from typing import assert_type
from operator import attrgetter
from typing_extensions import assert_type


def check_attrgetter():
assert_type(attrgetter("name"), attrgetter[object])


def check_itemgetter():
assert_type(itemgetter("name"), itemgetter[object])
12 changes: 7 additions & 5 deletions mypy/typeshed/stdlib/functools.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class _HashCallable(Protocol):
def __call__(self, /, *args: Hashable, **kwargs: Hashable) -> Never: ...

@type_check_only
class _LruCacheWrapperBase(Protocol[_out_TCallable]):
__wrapped__: Final[_out_TCallable] = ... # type: ignore[misc]
__call__: Final[_out_TCallable | _HashCallable] = ... # type: ignore[misc]
class _LruCacheWrapperBase(Generic[_out_TCallable]):
__wrapped__: Final[_out_TCallable] # type: ignore[misc]
__call__: Final[_out_TCallable | _HashCallable] # type: ignore[misc]

def cache_info(self) -> _CacheInfo: ...
def cache_clear(self) -> None: ...
Expand All @@ -70,10 +70,12 @@ class _LruCacheWrapperBase(Protocol[_out_TCallable]):
def __copy__(self) -> Self: ...
def __deepcopy__(self, memo: Any, /) -> Self: ...


# replace with `Method & X` once #856 is resolved
@type_check_only
class _LruCacheWrapperMethod(MethodType, _LruCacheWrapperBase[_out_TCallable]):
pass
class _LruCacheWrapperMethod(MethodType, _LruCacheWrapperBase[_out_TCallable]): # type: ignore[misc]
__call__: Final[_out_TCallable | _HashCallable] # type: ignore[misc, assignment]


# actually defined in `_functools`
@final
Expand Down
10 changes: 5 additions & 5 deletions mypy/typeshed/stdlib/operator.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,15 @@ if sys.version_info >= (3, 11):
@final
class attrgetter(Generic[_T_co]):
@overload
def __new__(cls, attr: str, /) -> attrgetter[Any]: ...
def __new__(cls, attr: str, /) -> attrgetter[object]: ...
@overload
def __new__(cls, attr: str, attr2: str, /) -> attrgetter[tuple[Any, Any]]: ...
def __new__(cls, attr: str, attr2: str, /) -> attrgetter[tuple[object, object]]: ...
@overload
def __new__(cls, attr: str, attr2: str, attr3: str, /) -> attrgetter[tuple[Any, Any, Any]]: ...
def __new__(cls, attr: str, attr2: str, attr3: str, /) -> attrgetter[tuple[object, object, object]]: ...
@overload
def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[Any, Any, Any, Any]]: ...
def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[object, object, object, object]]: ...
@overload
def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[Any, ...]]: ...
def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[object, ...]]: ...
def __call__(self, obj: Any, /) -> _T_co: ...

@final
Expand Down

0 comments on commit adb1fcc

Please sign in to comment.