Repository: HypothesisWorks/hypothesis

Issue:
`from_type` raises TypeError for `Union` of builtin generic types
On the current version of hypothesis (6.17.4) on python 3.9.2 

```python
st.from_type(Union[list[str], str]).example()
```

raises `TypeError("issubclass() arg 1 must be a class")` because `list[int]` is an instance of `types.GenericAlias` and not an instance of `type`.


Discussion:
I think [this guard against types that are not really types](https://github.com/HypothesisWorks/hypothesis/blob/c4fb7c28ba7af68c7f78cb01c4642cdb51d826cc/hypothesis-python/src/hypothesis/strategies/_internal/types.py#L84) 

should be 

```python
if isinstance(t, types.GenericAlias):
    ....
```

Bonus fact: if this needs to guard against `Union` too the check should also include `typing._UnionGenericAlias` as that is not a subclass of `GenericAlias` for some reason.


Huh, yeah - thanks for the report!  Looks like we missed/under-tested the equivalent of #2979 for Python 3.9 ([PEP 585](https://www.python.org/dev/peps/pep-0585/)).
OK, that *specific* check can just use a `try`/`except TypeError`.

The underlying problem is that we have never really tested Hypothesis with [the new-in-3.9 `types.GenericAlias` type](https://docs.python.org/3/library/stdtypes.html#types-genericalias), which I expect to see a lot more of over the next few years (e.g. `list[int]` is a lot more attractive than constantly importing stuff from the `typing` module).  I expect that we'll need to update a few things to check "is this type generic" in a more, um, *generic* way; at least accessing `__origin__` and `__args__` seems to work the same way.

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py
--- a/hypothesis-python/src/hypothesis/core.py
+++ b/hypothesis-python/src/hypothesis/core.py
@@ -941,8 +941,6 @@ def fuzz_one_input(
 
         Returns None if buffer invalid for the strategy, canonical pruned
         bytes if the buffer was valid, and leaves raised exceptions alone.
-
-        Note: this feature is experimental and may change or be removed.
         """
         # Note: most users, if they care about fuzzer performance, will access the
         # property and assign it to a local variable to move the attribute lookup
diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py
--- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py
+++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py
@@ -81,6 +81,7 @@ def type_sorting_key(t):
         raise InvalidArgument(f"thing={t} must be a type")  # pragma: no cover
     if t is None or t is type(None):  # noqa: E721
         return (-1, repr(t))
+    t = getattr(t, "__origin__", t)
     if not isinstance(t, type):  # pragma: no cover
         # Some generics in the typing module are not actually types in 3.7
         return (2, repr(t))


Test patch:
diff --git a/hypothesis-python/tests/cover/test_lookup_py39.py b/hypothesis-python/tests/cover/test_lookup_py39.py
--- a/hypothesis-python/tests/cover/test_lookup_py39.py
+++ b/hypothesis-python/tests/cover/test_lookup_py39.py
@@ -21,6 +21,8 @@
 from hypothesis import given, strategies as st
 from hypothesis.errors import InvalidArgument
 
+from tests.common.debug import find_any
+
 
 @pytest.mark.parametrize(
     "annotated_type,expected_strategy_repr",
@@ -85,3 +87,10 @@ def test_string_forward_ref_message():
     s = st.builds(User)
     with pytest.raises(InvalidArgument, match="`from __future__ import annotations`"):
         s.example()
+
+
+def test_issue_3080():
+    # Check for https://github.com/HypothesisWorks/hypothesis/issues/3080
+    s = st.from_type(typing.Union[list[int], int])
+    find_any(s, lambda x: isinstance(x, int))
+    find_any(s, lambda x: isinstance(x, list))