Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ See [0Ver](https://0ver.org/).
### Bugfixes

- Fix `{Success, Failure}.swap` returning `Any`
- Fix type parameter variance of `Law{1,2,3}`, `Unwrappable`, `Contextable`,
and `Trampoline`

## 0.30.0

Expand Down
8 changes: 5 additions & 3 deletions returns/_internal/pipeline/pipe.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Generic, TypeVar

_InstanceType = TypeVar('_InstanceType')
_InstanceType_contra = TypeVar('_InstanceType_contra', contravariant=True)
_ReturnType = TypeVar('_ReturnType')
_ReturnType_co = TypeVar('_ReturnType_co', covariant=True)

_PipelineStepType1 = TypeVar('_PipelineStepType1')
_PipelineStepType2 = TypeVar('_PipelineStepType2')
Expand All @@ -26,8 +28,8 @@ _PipelineStepType20 = TypeVar('_PipelineStepType20')

class _Pipe(
Generic[
_InstanceType,
_ReturnType,
_InstanceType_contra,
_ReturnType_co,
_PipelineStepType1,
_PipelineStepType2,
_PipelineStepType3,
Expand Down Expand Up @@ -76,7 +78,7 @@ class _Pipe(
],
/,
) -> None: ...
def __call__(self, instance: _InstanceType) -> _ReturnType: ...
def __call__(self, instance: _InstanceType_contra) -> _ReturnType_co: ...

def pipe( # noqa: WPS451
function1: _PipelineStepType1,
Expand Down
10 changes: 6 additions & 4 deletions returns/interfaces/specific/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@
_UpdatedType = TypeVar('_UpdatedType')

_ValueType = TypeVar('_ValueType')
_ValueType_co = TypeVar('_ValueType_co', covariant=True)
_EnvType = TypeVar('_EnvType')
_EnvType_contra = TypeVar('_EnvType_contra', contravariant=True)

_ReaderLike2Type = TypeVar('_ReaderLike2Type', bound='ReaderLike2')
_ReaderLike3Type = TypeVar('_ReaderLike3Type', bound='ReaderLike3')


class Contextable(Generic[_ValueType, _EnvType]):
class Contextable(Generic[_ValueType_co, _EnvType_contra]):
"""
Special type we use as a base one for all callble ``Reader`` instances.

Expand All @@ -75,7 +77,7 @@ class Contextable(Generic[_ValueType, _EnvType]):
__slots__ = ()

@abstractmethod
def __call__(self, deps: _EnvType) -> _ValueType:
def __call__(self, deps: _EnvType_contra) -> _ValueType_co:
"""Receives one parameter, returns a value. As simple as that."""


Expand Down Expand Up @@ -128,7 +130,7 @@ def from_context(

class CallableReader2(
ReaderLike2[_FirstType, _SecondType],
Contextable[_ValueType, _EnvType],
Contextable[_ValueType_co, _EnvType_contra],
):
"""
Intermediate interface for ``ReaderLike2`` + ``__call__`` method.
Expand Down Expand Up @@ -193,7 +195,7 @@ def from_context(

class CallableReader3(
ReaderLike3[_FirstType, _SecondType, _ThirdType],
Contextable[_ValueType, _EnvType],
Contextable[_ValueType_co, _EnvType_contra],
):
"""
Intermediate interface for ``ReaderLike3`` + ``__call__`` method.
Expand Down
10 changes: 5 additions & 5 deletions returns/interfaces/unwrappable.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from abc import abstractmethod
from typing import Generic, TypeVar

_FirstType = TypeVar('_FirstType')
_SecondType = TypeVar('_SecondType')
_FirstType_co = TypeVar('_FirstType_co', covariant=True)
_SecondType_co = TypeVar('_SecondType_co', covariant=True)

_UnwrappableType = TypeVar('_UnwrappableType', bound='Unwrappable')


class Unwrappable(Generic[_FirstType, _SecondType]):
class Unwrappable(Generic[_FirstType_co, _SecondType_co]):
"""
Represents containers that can unwrap and return its wrapped value.

Expand All @@ -21,7 +21,7 @@ class Unwrappable(Generic[_FirstType, _SecondType]):
__slots__ = ()

@abstractmethod
def unwrap(self: _UnwrappableType) -> _FirstType:
def unwrap(self: _UnwrappableType) -> _FirstType_co:
"""
Custom magic method to unwrap inner value from container.

Expand All @@ -37,7 +37,7 @@ def unwrap(self: _UnwrappableType) -> _FirstType:
"""

@abstractmethod
def failure(self: _UnwrappableType) -> _SecondType:
def failure(self: _UnwrappableType) -> _SecondType_co:
"""
Custom magic method to unwrap inner value from the failed container.

Expand Down
46 changes: 31 additions & 15 deletions returns/primitives/laws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from returns.primitives.types import Immutable

_Caps = TypeVar('_Caps')
_ReturnType = TypeVar('_ReturnType')
_TypeArgType1 = TypeVar('_TypeArgType1')
_TypeArgType2 = TypeVar('_TypeArgType2')
_TypeArgType3 = TypeVar('_TypeArgType3')
_ReturnType_co = TypeVar('_ReturnType_co', covariant=True)
_TypeArgType1_contra = TypeVar('_TypeArgType1_contra', contravariant=True)
_TypeArgType2_contra = TypeVar('_TypeArgType2_contra', contravariant=True)
_TypeArgType3_contra = TypeVar('_TypeArgType3_contra', contravariant=True)

law_definition = staticmethod
"""Special alias to define laws as functions even inside a class."""
Expand Down Expand Up @@ -42,17 +42,17 @@ def name(self) -> str:
@final
class Law1(
Law,
Generic[_TypeArgType1, _ReturnType],
Generic[_TypeArgType1_contra, _ReturnType_co],
):
"""Law definition for functions with a single argument."""

__slots__ = ()

definition: Callable[['Law1', _TypeArgType1], _ReturnType]
definition: Callable[['Law1', _TypeArgType1_contra], _ReturnType_co]

def __init__(
self,
function: Callable[[_TypeArgType1], _ReturnType],
function: Callable[[_TypeArgType1_contra], _ReturnType_co],
) -> None:
"""Saves function of one argument to the inner state."""
super().__init__(function)
Expand All @@ -61,17 +61,23 @@ def __init__(
@final
class Law2(
Law,
Generic[_TypeArgType1, _TypeArgType2, _ReturnType],
Generic[_TypeArgType1_contra, _TypeArgType2_contra, _ReturnType_co],
):
"""Law definition for functions with two arguments."""

__slots__ = ()

definition: Callable[['Law2', _TypeArgType1, _TypeArgType2], _ReturnType]
definition: Callable[
['Law2', _TypeArgType1_contra, _TypeArgType2_contra],
_ReturnType_co,
]

def __init__(
self,
function: Callable[[_TypeArgType1, _TypeArgType2], _ReturnType],
function: Callable[
[_TypeArgType1_contra, _TypeArgType2_contra],
_ReturnType_co,
],
) -> None:
"""Saves function of two arguments to the inner state."""
super().__init__(function)
Expand All @@ -80,22 +86,32 @@ def __init__(
@final
class Law3(
Law,
Generic[_TypeArgType1, _TypeArgType2, _TypeArgType3, _ReturnType],
Generic[
_TypeArgType1_contra,
_TypeArgType2_contra,
_TypeArgType3_contra,
_ReturnType_co,
],
):
"""Law definition for functions with three argument."""

__slots__ = ()

definition: Callable[
['Law3', _TypeArgType1, _TypeArgType2, _TypeArgType3],
_ReturnType,
[
'Law3',
_TypeArgType1_contra,
_TypeArgType2_contra,
_TypeArgType3_contra,
],
_ReturnType_co,
]

def __init__(
self,
function: Callable[
[_TypeArgType1, _TypeArgType2, _TypeArgType3],
_ReturnType,
[_TypeArgType1_contra, _TypeArgType2_contra, _TypeArgType3_contra],
_ReturnType_co,
],
) -> None:
"""Saves function of three arguments to the inner state."""
Expand Down
9 changes: 5 additions & 4 deletions returns/trampolines.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from typing_extensions import ParamSpec

_ReturnType = TypeVar('_ReturnType')
_ReturnType_co = TypeVar('_ReturnType_co', covariant=True)
_FuncParams = ParamSpec('_FuncParams')


@final
class Trampoline(Generic[_ReturnType]):
class Trampoline(Generic[_ReturnType_co]):
"""
Represents a wrapped function call.

Expand All @@ -18,13 +19,13 @@ class Trampoline(Generic[_ReturnType]):

__slots__ = ('args', 'func', 'kwargs')

func: Callable[..., _ReturnType]
func: Callable[..., _ReturnType_co]
args: tuple[Any, ...]
kwargs: dict[str, Any]

def __init__( # noqa: WPS451
self,
func: Callable[_FuncParams, _ReturnType],
func: Callable[_FuncParams, _ReturnType_co],
/, # We use pos-only here to be able to store `kwargs` correctly.
*args: _FuncParams.args,
**kwargs: _FuncParams.kwargs,
Expand All @@ -34,7 +35,7 @@ def __init__( # noqa: WPS451
self.args = args
self.kwargs = kwargs

def __call__(self) -> _ReturnType:
def __call__(self) -> _ReturnType_co:
"""Call wrapped function with given arguments."""
return self.func(*self.args, **self.kwargs)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- case: contextable_correct_cast
disable_cache: false
main: |
from returns.interfaces.specific.reader import Contextable

first: Contextable[bool, object]
second: Contextable[int, str] = first
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- case: unwrappable_correct_cast
disable_cache: false
main: |
from returns.interfaces.unwrappable import Unwrappable

first: Unwrappable[bool, ValueError]
second: Unwrappable[int, Exception] = first
58 changes: 58 additions & 0 deletions typesafety/test_primitives/test_laws/test_law_variance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
- case: law1_correct_cast
disable_cache: false
main: |
from returns.primitives.laws import Law1

first: Law1[int, bool]
second: Law1[bool, int] = first


- case: law1_wrong_cast
disable_cache: false
main: |
from returns.primitives.laws import Law1

first: Law1[bool, int]
second: Law1[int, bool] = first
out: |
main:4: error: Incompatible types in assignment (expression has type "Law1[bool, int]", variable has type "Law1[int, bool]") [assignment]


- case: law2_correct_cast
disable_cache: false
main: |
from returns.primitives.laws import Law2

first: Law2[int, int, bool]
second: Law2[bool, bool, int] = first


- case: law2_wrong_cast
disable_cache: false
main: |
from returns.primitives.laws import Law2

first: Law2[bool, bool, int]
second: Law2[int, int, bool] = first
out: |
main:4: error: Incompatible types in assignment (expression has type "Law2[bool, bool, int]", variable has type "Law2[int, int, bool]") [assignment]


- case: law3_correct_cast
disable_cache: false
main: |
from returns.primitives.laws import Law3

first: Law3[int, int, int, bool]
second: Law3[bool, bool, bool, int] = first


- case: law3_wrong_cast
disable_cache: false
main: |
from returns.primitives.laws import Law3

first: Law3[bool, bool, bool, int]
second: Law3[int, int, int, bool] = first
out: |
main:4: error: Incompatible types in assignment (expression has type "Law3[bool, bool, bool, int]", variable has type "Law3[int, int, int, bool]") [assignment]
9 changes: 9 additions & 0 deletions typesafety/test_trampolines/test_trampoline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@
reveal_type(t.func) # N: Revealed type is "def (*Any, **Any) -> int"
reveal_type(t.args) # N: Revealed type is "tuple[Any, ...]"
reveal_type(t.kwargs) # N: Revealed type is "dict[str, Any]"


- case: trampoline_correct_cast
disable_cache: false
main: |
from returns.trampolines import Trampoline

first: Trampoline[bool]
second: Trampoline[int] = first
Loading