PyHamcrest icon indicating copy to clipboard operation
PyHamcrest copied to clipboard

Matchers should be contravariant

Open mezuzza opened this issue 2 years ago • 7 comments

Currently, the Matcher class has a type arg T as it should, but Matchers are currently invariant with respect to T. However, I think it's quite clear that the following should be legal code:

matcher: Matcher[str] = has_length(greater_than(0))

I've recreated that definition here:

from typing import Any, Generic, Sequence, Sized, TypeVar

T = TypeVar("T")


class Matcher(Generic[T]):
  def matches(self, _: T) -> bool:
    ...


def greater_than(_: Any) -> Matcher[Any]:
  ...


def has_length(_: int | Matcher[int]) -> Matcher[Sized]:
  ...


matcher: Matcher[str] = has_length(greater_than(0))

Pyright provides the following error:

» pyright test.py
[...]
test.py
  test.py:23:25 - error: Expression of type "Matcher[Sized]" cannot be assigned to declared type "Matcher[str]"
    "Matcher[Sized]" is incompatible with "Matcher[str]"
      TypeVar "T@Matcher" is invariant
        "Sized" is incompatible with "str" (reportGeneralTypeIssues)
1 error, 0 warnings, 0 informations
Completed in 0.582sec

Changing line 3 to T = TypeVar('T', contravariant=True) fixes the issue.

python version: 3.10.8 pyhamcrest version: 2.0.4 pyright version: 1.1.280

mezuzza avatar Nov 27 '22 20:11 mezuzza