From 4a40f83f99f81f64124c5ad1ae9b18826986627a Mon Sep 17 00:00:00 2001 From: Adrian Acala Date: Tue, 1 Apr 2025 18:25:01 -0700 Subject: [PATCH] Update mypy configuration and add tests for `partial` function - Introduced a new test suite for the `partial` function in `typesafety/test_curry/test_partial/test_partial.py` to ensure its correct behavior with various argument types and signatures. - Updated `mypy.ini` to specify Python version and include the necessary plugins for type checking. - Fixed compatibility issues with `curry.partial` and mypy 1.6.1+. - Added a new entry in `CHANGELOG.md` for unreleased bugfixes. --- CHANGELOG.md | 7 ++ pyproject.toml | 1 + returns/contrib/mypy/_typeops/inference.py | 25 ++++--- typesafety/test_curry/test_partial/mypy.ini | 3 + .../test_curry/test_partial/test_partial.py | 74 +++++++++++++++++++ 5 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 typesafety/test_curry/test_partial/mypy.ini create mode 100644 typesafety/test_curry/test_partial/test_partial.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c474b29..b561ae397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ incremental in minor, bugfixes only are patches. See [0Ver](https://0ver.org/). +## Unreleased + +### Bugfixes + +- Fixes the `curry.partial` compatibility with mypy 1.6.1+ + + ## 0.25.0 ### Features diff --git a/pyproject.toml b/pyproject.toml index 5082bbda2..46896bb06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -185,6 +185,7 @@ lint.per-file-ignores."tests/test_examples/test_maybe/test_maybe_pattern_matchin ] 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.per-file-ignores."typesafety/test_curry/test_partial/test_partial.py" = [ "S101" ] lint.external = [ "WPS" ] lint.flake8-quotes.inline-quotes = "single" lint.mccabe.max-complexity = 6 diff --git a/returns/contrib/mypy/_typeops/inference.py b/returns/contrib/mypy/_typeops/inference.py index bc713ebe2..645e0fad1 100644 --- a/returns/contrib/mypy/_typeops/inference.py +++ b/returns/contrib/mypy/_typeops/inference.py @@ -73,25 +73,26 @@ def _infer_constraints( """Creates mapping of ``typevar`` to real type that we already know.""" checker = self._ctx.api.expr_checker # type: ignore kinds = [arg.kind for arg in applied_args] - exprs = [arg.expression(self._ctx.context) for arg in applied_args] - formal_to_actual = map_actuals_to_formals( kinds, [arg.name for arg in applied_args], self._fallback.arg_kinds, self._fallback.arg_names, - lambda index: checker.accept(exprs[index]), - ) - constraints = infer_constraints_for_callable( - self._fallback, - arg_types=[arg.type for arg in applied_args], - arg_kinds=kinds, - arg_names=[arg.name for arg in applied_args], - formal_to_actual=formal_to_actual, - context=checker.argument_infer_context(), + lambda index: checker.accept( + applied_args[index].expression(self._ctx.context), + ), ) + return { - constraint.type_var: constraint.target for constraint in constraints + constraint.type_var: constraint.target + for constraint in infer_constraints_for_callable( + self._fallback, + arg_types=[arg.type for arg in applied_args], + arg_kinds=kinds, + arg_names=[arg.name for arg in applied_args], + formal_to_actual=formal_to_actual, + context=checker.argument_infer_context(), + ) } diff --git a/typesafety/test_curry/test_partial/mypy.ini b/typesafety/test_curry/test_partial/mypy.ini new file mode 100644 index 000000000..b73e9ef96 --- /dev/null +++ b/typesafety/test_curry/test_partial/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +python_version = 3.11 +plugins = returns.contrib.mypy.returns_plugin diff --git a/typesafety/test_curry/test_partial/test_partial.py b/typesafety/test_curry/test_partial/test_partial.py new file mode 100644 index 000000000..48f6122ba --- /dev/null +++ b/typesafety/test_curry/test_partial/test_partial.py @@ -0,0 +1,74 @@ +import pytest +from typing_extensions import reveal_type + +from returns.curry import partial + +TEST_STR = 'a' + + +def test_partial_fn( # noqa: WPS221 + first_arg: int, + optional_arg: str | None, +) -> tuple[int, str | None]: + """Return arguments as a tuple.""" + return first_arg, optional_arg + + +def test_partial(): + """Ensures that ``partial`` works correctly.""" + bound = partial(test_partial_fn, 1) + reveal_type(bound) # noqa: WPS421 # R: Revealed type is 'def (*, optional_arg: Union[builtins.str, None] =) -> Tuple[builtins.int, Union[builtins.str, None]]' + + assert bound() == (1, None) + assert bound(optional_arg=TEST_STR) == (1, TEST_STR) + + +def test_partial_with_decorator(): + """Ensures that ``partial`` works correctly with decorators.""" + + @partial(first=1) + def _decorated(first: int, second: str) -> float: # noqa: WPS430 + return first / float(second) + + reveal_type(_decorated) # noqa: WPS421 # R: Revealed type is 'def (second: builtins.str) -> builtins.float' + + assert _decorated(second='2') == pytest.approx(0.5) + + +def test_partial_keyword(): + """Ensures that ``partial`` works correctly with keyword args.""" + bound = partial(test_partial_fn, optional_arg=TEST_STR) + reveal_type(bound) # noqa: WPS421 # R: Revealed type is 'def (first_arg: builtins.int) -> Tuple[builtins.int, builtins.str]' + + assert bound(1) == (1, TEST_STR) + + +def test_partial_keyword_only(): + """Ensures that ``partial`` works with keyword only args.""" + + def _target(*, arg: int) -> int: # noqa: WPS430 + return arg + + bound = partial(_target, arg=1) + reveal_type(bound) # noqa: WPS421 # R: Revealed type is 'def () -> builtins.int' + + assert bound() == 1 + + +def test_partial_keyword_mixed(): + """Ensures that ``partial`` works with keyword only args.""" + + def _target(arg1: int, *, arg2: int) -> int: # noqa: WPS430 + return arg1 + arg2 + + bound = partial(_target, arg2=1) + reveal_type(bound) # noqa: WPS421 # R: Revealed type is 'def (arg1: builtins.int) -> builtins.int' + + assert bound(1) == 2 + + +def test_partial_wrong_signature(): + """Ensures that ``partial`` returns ``Any`` for wrong signature.""" + reveal_type(partial(len, 1)) # noqa: WPS421 # R: Revealed type is 'Any' + with pytest.raises(TypeError): + partial(len, 1)()