Skip to content

Update plugin to support mypy 1.17.0 #2149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 23, 2025
Merged
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
786 changes: 424 additions & 362 deletions poetry.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ version = "0.25.0"
description = "Make your functions return something meaningful, typed, and safe!"
license = "BSD-3-Clause"

authors = [
"sobolevn <[email protected]>",
]
authors = [ "sobolevn <[email protected]>" ]

readme = "README.md"

Expand Down Expand Up @@ -53,16 +51,16 @@ python = "^3.10"

typing-extensions = ">=4.0,<5.0"
pytest = { version = "^8.0", optional = true }
hypothesis = { version = "^6.122", optional = true }
mypy = { version = ">=1.12,<1.16", optional = true }
hypothesis = { version = "^6.136", optional = true }
mypy = { version = ">=1.12,<1.18", optional = true }

[tool.poetry.group.dev.dependencies]
anyio = "^4.3"
trio = ">=0.28,<0.31"
attrs = ">=24.2,<26.0"
httpx = "^0.28"

wemake-python-styleguide = "^1.0"
wemake-python-styleguide = "1.0"
codespell = "^2.2"
slotscheck = "^0.19"
ruff = ">=0.11,<0.13"
Expand Down Expand Up @@ -183,7 +181,9 @@ lint.per-file-ignores."tests/test_examples/test_maybe/test_maybe_pattern_matchin
"D103",
"F811",
]
lint.per-file-ignores."tests/test_examples/test_result/test_result_pattern_matching.py" = [ "D103" ]
lint.per-file-ignores."tests/test_examples/test_result/test_result_pattern_matching.py" = [
"D103",
]
lint.per-file-ignores."tests/test_pattern_matching.py" = [ "S101" ]
lint.external = [ "WPS" ]
lint.flake8-quotes.inline-quotes = "single"
Expand Down
14 changes: 11 additions & 3 deletions returns/contrib/mypy/_features/kind.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from collections.abc import Sequence
from enum import Enum, unique
from importlib.metadata import version
from typing import Any

from mypy.checkmember import analyze_member_access
from mypy.plugin import (
Expand Down Expand Up @@ -61,18 +63,24 @@ def attribute_access(ctx: AttributeContext) -> MypyType:
else:
return ctx.default_attr_type

exprchecker = ctx.api.expr_checker # type: ignore
mypy_version_tuple = tuple(
map(int, version('mypy').partition('+')[0].split('.'))
)

extra_kwargs: dict[str, Any] = {}
if mypy_version_tuple < (1, 16):
extra_kwargs['msg'] = ctx.api.msg
return analyze_member_access(
ctx.context.name, # type: ignore
accessed,
ctx.context,
is_lvalue=False,
is_super=False,
is_operator=False,
msg=ctx.api.msg,
original_type=instance,
chk=ctx.api, # type: ignore
in_literal_context=exprchecker.is_literal_context(),
in_literal_context=ctx.api.expr_checker.is_literal_context(), # type: ignore
**extra_kwargs,
)


Expand Down
33 changes: 25 additions & 8 deletions returns/contrib/mypy/_typeops/analtype.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from collections.abc import Sequence
from importlib.metadata import version
from types import MappingProxyType
from typing import Final, Literal, overload
from typing import Any, Final, Literal, overload

from mypy.checkmember import analyze_member_access
from mypy.nodes import ARG_NAMED, ARG_OPT
Expand All @@ -21,7 +23,7 @@
@overload
def analyze_call(
function: FunctionLike,
args: list[FuncArg],
args: Sequence[FuncArg],
ctx: CallableContext,
*,
show_errors: Literal[True],
Expand All @@ -31,14 +33,20 @@ def analyze_call(
@overload
def analyze_call(
function: FunctionLike,
args: list[FuncArg],
args: Sequence[FuncArg],
ctx: CallableContext,
*,
show_errors: bool,
) -> CallableType | None: ...


def analyze_call(function, args, ctx, *, show_errors):
def analyze_call(
function: FunctionLike,
args: Sequence[FuncArg],
ctx: CallableContext,
*,
show_errors: bool,
) -> CallableType | None:
"""
Analyzes function call based on passed arguments.

Expand All @@ -48,7 +56,7 @@ def analyze_call(function, args, ctx, *, show_errors):
We also allow to return ``None`` instead of showing errors.
This might be helpful for cases when we run intermediate analysis.
"""
checker = ctx.api.expr_checker
checker = ctx.api.expr_checker # type: ignore[attr-defined]
with checker.msg.filter_errors(save_filtered_errors=True) as local_errors:
_return_type, checked_function = checker.check_call(
function,
Expand All @@ -63,7 +71,7 @@ def analyze_call(function, args, ctx, *, show_errors):

checker.msg.add_errors(local_errors.filtered_errors()) # noqa: WPS441

return checked_function
return checked_function # type: ignore[no-any-return]


def safe_translate_to_function(
Expand Down Expand Up @@ -110,6 +118,16 @@ def translate_to_function(
This also preserves all type arguments as-is.
"""
checker = ctx.api.expr_checker # type: ignore

mypy_version = version('mypy')
mypy_version_tuple = tuple(
map(int, mypy_version.partition('+')[0].split('.'))
)

extra_kwargs: dict[str, Any] = {}
if mypy_version_tuple < (1, 16):
extra_kwargs['msg'] = checker.msg

return get_proper_type(
analyze_member_access(
'__call__',
Expand All @@ -118,9 +136,8 @@ def translate_to_function(
is_lvalue=False,
is_super=False,
is_operator=True,
msg=checker.msg,
original_type=function_def,
chk=checker.chk,
in_literal_context=checker.is_literal_context(),
**extra_kwargs,
)
)
8 changes: 7 additions & 1 deletion returns/contrib/mypy/returns_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,14 @@ def get_attribute_hook(
self,
fullname: str,
) -> _AttributeCallback | None:
"""Called for any exiting or ``__getattr__`` aatribute access."""
"""Called for any exiting or ``__getattr__`` attribute access."""
if fullname.startswith(_consts.TYPED_KINDN_ACCESS):
name_parts = fullname.split('.')
attribute_name = name_parts[-1]
if attribute_name.startswith('__') and attribute_name.endswith(
'__'
):
return None
return kind.attribute_access
return None

Expand Down
4 changes: 0 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ strict = true
strict_bytes = true
warn_unreachable = true

# TODO: update our output assertions to match a new syntax
force_uppercase_builtins = true
force_union_syntax = true

# TODO: Enable this later, it's disabled temporarily while we don't discover why
# the explicit restriction on `typeshed.stdlib.unittest.mock`,
# which is the next section, is not working properly when running
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def do_nothing(
def _callable_strategy(
arg1: type[object], arg2: type[object]
) -> StrategyFactory[Callable]:
type_arg1 = int if arg1 == Any else arg1 # type: ignore[comparison-overlap]
type_arg2 = int if arg2 == Any else arg2 # type: ignore[comparison-overlap]
type_arg1 = int if arg1 == Any else arg1
type_arg2 = int if arg2 == Any else arg2
return_results = st.functions(
pure=True,
returns=strategy_from_container(Result)(Result[type_arg1, type_arg2]), # type: ignore[valid-type]
Expand Down
10 changes: 5 additions & 5 deletions tests/test_contrib/test_hypothesis/test_type_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ def test_types_to_strategies_default() -> None: # noqa: WPS210
)

wrapper_strategy = (
"builds(from_value, shared(sampled_from([<class 'NoneType'>,"
" <class 'bool'>, <class 'int'>, <class 'float'>, <class 'str'>,"
" <class 'bytes'>]), key='typevar=~_FirstType').flatmap(from_type))"
'builds(from_value, shared(sampled_from([NoneType,'
' bool, int, float, str,'
" bytes]), key='typevar=~_FirstType').flatmap(from_type))"
)
assert (
_strategy_string(result[container_type], container_type)
Expand All @@ -214,8 +214,8 @@ def test_types_to_strategies_default() -> None: # noqa: WPS210
)
assert (
_strategy_string(result[TypeVar], _ValueType)
== "shared(sampled_from([<class 'NoneType'>, <class 'bool'>,"
" <class 'int'>, <class 'float'>, <class 'str'>, <class 'bytes'>]),"
== 'shared(sampled_from([NoneType, bool,'
' int, float, str, bytes]),'
" key='typevar=~_ValueType').flatmap(from_type).filter(lambda"
' inner: inner == inner)'
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
- case: check_all_laws
disable_cache: false
parametrized:
- container: Result
- container: Maybe
- container: IO
- container: IOResult
- container: Reader
- container: ReaderResult
- container: ReaderIOResult
- container: ReaderFutureResult
- container: Future
- container: FutureResult
- container: Result
- container: Maybe
- container: IO
- container: IOResult
- container: Reader
- container: ReaderResult
- container: ReaderIOResult
- container: ReaderFutureResult
- container: Future
- container: FutureResult
main: |
from returns.context import (
Reader, ReaderResult, ReaderIOResult, ReaderFutureResult,
Expand All @@ -25,14 +25,10 @@

x: Type[Lawful] = {{ container }}


- case: test_all_laws_accepts_only_one_approach
disable_cache: false
# TODO: remove this config after
# mypy/typeshed/stdlib/unittest/mock.pyi:120:
# error: Class cannot subclass "Any" (has type "Any")
# is fixed.
mypy_config:
disallow_subclassing_any = False
mypy_config: disallow_subclassing_any = False
main: |
from hypothesis import strategies as st
from returns.contrib.hypothesis.laws import check_all_laws
Expand All @@ -44,31 +40,25 @@
check_all_laws(
Result, use_init=True, container_strategy=st.builds(Success, st.integers())
)

out: |
main:8: error: No overload variant of "check_all_laws" matches argument types "Type[Result[_ValueType_co, _ErrorType_co]]", "bool", "SearchStrategy[Success[Any]]" [call-overload]
main:8: error: No overload variant of "check_all_laws" matches argument types "type[Result[_ValueType_co, _ErrorType_co]]", "bool", "SearchStrategy[Success[Any]]" [call-overload]
main:8: note: Possible overload variants:
main:8: note: def [Example_co] check_all_laws(container_type: Type[Lawful[Example_co]], *, container_strategy: Union[SearchStrategy[Example_co], Callable[[Type[Example_co]], SearchStrategy[Example_co]]], settings_kwargs: Optional[Dict[str, Any]] = ..., type_strategies: Optional[Dict[Type[object], Union[SearchStrategy[Any], Callable[[Type[Any]], SearchStrategy[Any]]]]] = ...) -> None
main:8: note: def [Example_co] check_all_laws(container_type: Type[Lawful[Example_co]], *, settings_kwargs: Optional[Dict[str, Any]] = ..., use_init: bool = ...) -> None
main:8: note: def [Example_co] check_all_laws(container_type: type[Lawful[Example_co]], *, container_strategy: SearchStrategy[Example_co] | Callable[[type[Example_co]], SearchStrategy[Example_co]], settings_kwargs: dict[str, Any] | None = ..., type_strategies: dict[type[object], SearchStrategy[Any] | Callable[[type[Any]], SearchStrategy[Any]]] | None = ...) -> None
main:8: note: def [Example_co] check_all_laws(container_type: type[Lawful[Example_co]], *, settings_kwargs: dict[str, Any] | None = ..., use_init: bool = ...) -> None


- case: test_all_laws_requires_container_strategy
disable_cache: false
# TODO: remove this config after
# mypy/typeshed/stdlib/unittest/mock.pyi:120:
# error: Class cannot subclass "Any" (has type "Any")
# is fixed.
mypy_config:
disallow_subclassing_any = False
mypy_config: disallow_subclassing_any = False
main: |
from hypothesis import strategies as st
from returns.contrib.hypothesis.laws import check_all_laws
from returns.result import Result, Success

check_all_laws(Result, container_strategy=st.builds(Success, st.integers()), type_strategies={int: st.integers()})
check_all_laws(Result, type_strategies={int: st.integers()})

out: |
main:6: error: No overload variant of "check_all_laws" matches argument types "Type[Result[_ValueType_co, _ErrorType_co]]", "Dict[Type[int], SearchStrategy[int]]" [call-overload]
main:6: error: No overload variant of "check_all_laws" matches argument types "type[Result[_ValueType_co, _ErrorType_co]]", "dict[type[int], SearchStrategy[int]]" [call-overload]
main:6: note: Possible overload variants:
main:6: note: def [Example_co] check_all_laws(container_type: Type[Lawful[Example_co]], *, container_strategy: Union[SearchStrategy[Example_co], Callable[[Type[Example_co]], SearchStrategy[Example_co]]], settings_kwargs: Optional[Dict[str, Any]] = ..., type_strategies: Optional[Dict[Type[object], Union[SearchStrategy[Any], Callable[[Type[Any]], SearchStrategy[Any]]]]] = ...) -> None
main:6: note: def [Example_co] check_all_laws(container_type: Type[Lawful[Example_co]], *, settings_kwargs: Optional[Dict[str, Any]] = ..., use_init: bool = ...) -> None
main:6: note: def [Example_co] check_all_laws(container_type: type[Lawful[Example_co]], *, container_strategy: SearchStrategy[Example_co] | Callable[[type[Example_co]], SearchStrategy[Example_co]], settings_kwargs: dict[str, Any] | None = ..., type_strategies: dict[type[object], SearchStrategy[Any] | Callable[[type[Any]], SearchStrategy[Any]]] | None = ...) -> None
main:6: note: def [Example_co] check_all_laws(container_type: type[Lawful[Example_co]], *, settings_kwargs: dict[str, Any] | None = ..., use_init: bool = ...) -> None
4 changes: 1 addition & 3 deletions typesafety/test_curry/test_curry/test_curry_arguments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@
reveal_type(MyClass(test)) # N: Revealed type is "main.MyClass[Overload(def (a: builtins.int) -> Overload(def (b: builtins.int, c: builtins.str) -> builtins.int, def (b: builtins.int) -> def (c: builtins.str) -> builtins.int), def (a: builtins.int, b: builtins.int) -> def (c: builtins.str) -> builtins.int, def (a: builtins.int, b: builtins.int, c: builtins.str) -> builtins.int)]"


# TODO: remove skip after this bug in `mypy` is fixed:
# https://github.com/python/mypy/issues/8801
- case: curry_init_magic_method
disable_cache: false
skip: true
Expand All @@ -71,7 +69,7 @@
def __init__(self, arg: int, other: str) -> None:
...

reveal_type(Test) # N: Revealed type is "Overload(def (arg: builtins.int) -> def (other: builtins.str) -> ex.Test, def (arg: builtins.int, other: builtins.str) -> ex.Test)"
reveal_type(Test) # N: Revealed type is "Overload(def () -> main.Test, def (arg: builtins.int) -> main.Test, def (arg: builtins.int, other: builtins.str) -> main.Test)"


- case: curry_call_magic_method
Expand Down
23 changes: 11 additions & 12 deletions typesafety/test_curry/test_curry/test_curry_generics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
T = TypeVar('T')

@curry
def zero(arg: List[T]) -> T:
def zero(arg: list[T]) -> T:
...

x: List[int]
x: list[int]

reveal_type(zero) # N: Revealed type is "def [T] (arg: builtins.list[T`-1]) -> T`-1"
reveal_type(zero(x)) # N: Revealed type is "builtins.int"
Expand All @@ -25,10 +25,10 @@
T = TypeVar('T')

@curry
def zero(arg: List[T], other: int) -> T:
def zero(arg: list[T], other: int) -> T:
...

x: List[int]
x: list[int]

reveal_type(zero) # N: Revealed type is "Overload(def [T] (arg: builtins.list[T`-1]) -> def (other: builtins.int) -> T`-1, def [T] (arg: builtins.list[T`-1], other: builtins.int) -> T`-1)"
reveal_type(zero(x)) # N: Revealed type is "def (other: builtins.int) -> builtins.int"
Expand All @@ -45,35 +45,34 @@
T = TypeVar('T')

@curry
def zero(arg: int, other: List[T]) -> T:
def zero(arg: int, other: list[T]) -> T:
...

x: List[int]
y: List[str]
x: list[int]
y: list[str]

reveal_type(zero(1)(x)) # N: Revealed type is "builtins.int"
reveal_type(zero(1, x)) # N: Revealed type is "builtins.int"
reveal_type(zero(1)(y)) # N: Revealed type is "builtins.str"
reveal_type(zero(1, y)) # N: Revealed type is "builtins.str"


# TODO: enable and fix our plugin
- case: curry_two_generic_args3
disable_cache: false
skip: True
skip: true
main: |
from returns.curry import curry
from typing import List, TypeVar

T = TypeVar('T')

@curry
def zero(arg: T, other: List[T]) -> T:
def zero(arg: T, other: list[T]) -> T:
...

x: List[int]
x: list[int]

reveal_type(zero) # N: Revealed type is "Overload(def [T] (arg: T`-1) -> def [T] (other: builtins.list[T`-1]) -> T`-1, def [T] (arg: T`-1, other: builtins.list[T`-1]) -> T`-1)"
reveal_type(zero(1)) # N: Revealed type is "def [T] (other: builtins.list[builtins.int]) -> builtins.int"
reveal_type(zero(1)) # N: Revealed type is "def [T] (other: builtins.list[T`2]) -> T`2"
reveal_type(zero(1)(x)) # N: Revealed type is "builtins.int"
reveal_type(zero(1, x)) # N: Revealed type is "builtins.int"
Loading