hypothesis icon indicating copy to clipboard operation
hypothesis copied to clipboard

register_type_strategy is not annotated that it accepts TypeAlias

Open CharString opened this issue 11 months ago • 1 comments

This doesn't type check:

def is_hashable(v) -> bool:
    try:
        hash(v)
        return True
    except Exception:
        return False


def hashables() -> st.SearchStrategy[Hashable]:
    # not all values of all Hashable types are actually hashable
    # e.g. Signalling NaNs
    return st.from_type(Hashable).filter(is_hashable)


type HashableValue = Hashable

# this doesn't type check
st.register_type_strategy(HashableValue, hashables())

Diagnostics:

  1. Argument of type "TypeAliasType" cannot be assigned to parameter "custom_type" of type "type[Ex@register_type_strategy]" in function "register_type_strategy"   Type "TypeAliasType" is not assignable to type "type[Hashable]" [reportArgumentType]

But it looks like it works as I intended:

@given(...)
def test_hashables(h: Hashable):
    # still fails on Decimal("sNaN")
    hash(h)


@settings(max_examples=1000)
@given(...)
def test_hashable_values(h: HashableValue):
    # hasn't generated Decimal("sNaN")
    hash(h)

type Annie = Any

@given(...)
def test_annie(x: Annie):
    # still raises InvalidArgument, so TypeAliasses don't seem to circumvent checks
    pass

Is this intended and should I not rely on this current behaviour? Or shall I write a PR?

CharString avatar May 25 '25 12:05 CharString

This might be worth reporting upstream to mypy (and pyright). Type checker behavior differs for TypeAliasType when compared to TypeAlias and NewType:

from typing import TypeAliasType, TypeAlias, NewType, cast
from hypothesis import strategies as st

A: TypeAlias = int
B = NewType("B", int)
C = TypeAliasType("C", int)  # or type C = int

# reveal_type(A) # type[int]
# reveal_type(B) # type[B]
# reveal_type(C) # TypeAliasType

st.register_type_strategy(A, st.just(1))           # works
st.register_type_strategy(B, st.just(cast(B, 1)))  # works
st.register_type_strategy(C, st.just(1))           # type error (mypy and pyright)

This behavior is supported by Hypothesis. register_type_strategy accepts any of the following:

def is_a_type(thing: object) -> bool:
    """Return True if thing is a type or a generic type like thing."""
    return (
        isinstance(thing, type)
        or is_generic_type(thing)
        or is_a_new_type(thing)
        or is_a_type_alias_type(thing)
    )

Liam-DeVoe avatar May 25 '25 17:05 Liam-DeVoe