Repository: HypothesisWorks/hypothesis

Issue:
Fails on `typing.Type` and `ForwardRef`
When we use `Type[MyType]` - everything is good. But, `Type['MyType']` fails.

Code:

```python
>>> from hypothesis import strategies as st
>>> from typing import Type
>>> st.from_type(Type[int])
just(<class 'int'>)
>>> st.from_type(Type['int'])
from_type(typing.Type[ForwardRef('int')])
>>> st.from_type(Type['int']).example()
```

Output:
```python-traceback
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hypothesis/strategies/_internal/strategies.py", line 314, in example
    example_generating_inner_function()
  File "hypothesis/strategies/_internal/strategies.py", line 302, in example_generating_inner_function
    @settings(
  File "hypothesis/core.py", line 1001, in wrapped_test
    processed_args = process_arguments_to_given(
  File "hypothesis/core.py", line 420, in process_arguments_to_given
    search_strategy.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/collections.py", line 39, in do_validate
    s.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/strategies.py", line 638, in do_validate
    self.mapped_strategy.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/lazy.py", line 118, in do_validate
    w.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/strategies.py", line 638, in do_validate
    self.mapped_strategy.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/collections.py", line 39, in do_validate
    s.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 377, in validate
    self.do_validate()
  File "hypothesis/strategies/_internal/lazy.py", line 118, in do_validate
    w.validate()
  File "hypothesis/strategies/_internal/strategies.py", line 378, in validate
    self.is_empty
  File "hypothesis/strategies/_internal/strategies.py", line 125, in accept
    recur(self)
  File "hypothesis/strategies/_internal/strategies.py", line 121, in recur
    mapping[strat] = getattr(strat, calculation)(recur)
  File "hypothesis/strategies/_internal/deferred.py", line 80, in calc_is_empty
    return recur(self.wrapped_strategy)
  File "hypothesis/strategies/_internal/deferred.py", line 43, in wrapped_strategy
    result = self.__definition()
  File "hypothesis/strategies/_internal/core.py", line 1389, in <lambda>
    lambda thing: deferred(lambda: _from_type(thing)),
  File "hypothesis/strategies/_internal/core.py", line 1477, in _from_type
    return types.from_typing_type(thing)
  File "hypothesis/strategies/_internal/types.py", line 282, in from_typing_type
    strategies = [
  File "hypothesis/strategies/_internal/types.py", line 283, in <listcomp>
    v if isinstance(v, st.SearchStrategy) else v(thing)
  File "hypothesis/strategies/_internal/types.py", line 507, in resolve_Type
    raise ResolutionFailed(
hypothesis.errors.ResolutionFailed: 
	thing=typing.Type[ForwardRef('int')] cannot be resolved.
	Upgrading to python>=3.6 may fix this problem via improvements to the typing module.
```


Discussion:
Looks like `_try_import_forward_ref()` from #2550 could be useful here too!  Are there other places ForwardRef can occur that we should be looking at?
Pretty much any generic. Like `List`, `Callable`, etc
I agree that they should all use the same logic. 👍 
Hmm.  Sounds like we should be calling `_try_import_forward_ref()` pretty early in `st.from_type()`, then it would be shared across all users and we could `raise ResolutionFailed` instead of returning a ForwardRef if we can't resolve it.
We can make it "safe" for this general use-case. Like "Let's try to resolve this, failed? Ok, no worries."

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
@@ -596,31 +596,12 @@ def run(data):
                                     for v in args:
                                         printer.pretty(v)
                                         # We add a comma unconditionally because
-                                        # generated arguments will always be
-                                        # kwargs, so there will always be more
-                                        # to come.
+                                        # generated arguments will always be kwargs,
+                                        # so there will always be more to come.
                                         printer.text(",")
                                         printer.breakable()
 
-                                    # We need to make sure to print these in the
-                                    # argument order for Python 2 and older versions
-                                    # of Python 3.5. In modern versions this isn't
-                                    # an issue because kwargs is ordered.
-                                    arg_order = {
-                                        v: i
-                                        for i, v in enumerate(
-                                            getfullargspec(self.test).args
-                                        )
-                                    }
-                                    for i, (k, v) in enumerate(
-                                        sorted(
-                                            kwargs.items(),
-                                            key=lambda t: (
-                                                arg_order.get(t[0], float("inf")),
-                                                t[0],
-                                            ),
-                                        )
-                                    ):
+                                    for i, (k, v) in enumerate(kwargs.items()):
                                         printer.text(k)
                                         printer.text("=")
                                         printer.pretty(v)
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
@@ -127,7 +127,7 @@ def has_type_arguments(type_):
 def is_generic_type(type_):
     """Decides whethere a given type is generic or not."""
     # The ugly truth is that `MyClass`, `MyClass[T]`, and `MyClass[int]` are very different.
-    # In different python versions the might have the same type (3.6)
+    # In different python versions they might have the same type (3.6)
     # or it can be regular type vs `_GenericAlias` (3.7+)
     # We check for `MyClass[T]` and `MyClass[int]` with the first condition,
     # while the second condition is for `MyClass` in `python3.7+`.
@@ -480,12 +480,11 @@ def resolve_Type(thing):
     if getattr(args[0], "__origin__", None) is typing.Union:
         args = args[0].__args__
     # Duplicate check from from_type here - only paying when needed.
-    for a in args:  # pragma: no cover  # only on Python 3.6
+    for a in args:
         if type(a) == ForwardRef:
             raise ResolutionFailed(
-                "thing=%s cannot be resolved.  Upgrading to "
-                "python>=3.6 may fix this problem via improvements "
-                "to the typing module." % (thing,)
+                f"Cannot find the type referenced by {thing} - try using "
+                f"st.register_type_strategy({thing}, st.from_type(...))"
             )
     return st.sampled_from(sorted(args, key=type_sorting_key))
 


Test patch:
diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py
--- a/hypothesis-python/tests/cover/test_lookup.py
+++ b/hypothesis-python/tests/cover/test_lookup.py
@@ -603,6 +603,12 @@ def test_cannot_resolve_abstract_class_with_no_concrete_subclass(instance):
     assert False, "test body unreachable as strategy cannot resolve"
 
 
+@fails_with(ResolutionFailed)
+@given(st.from_type(typing.Type["ConcreteFoo"]))
+def test_cannot_resolve_type_with_forwardref(instance):
+    assert False, "test body unreachable as strategy cannot resolve"
+
+
 @pytest.mark.parametrize("typ", [typing.Hashable, typing.Sized])
 @given(data=st.data())
 def test_inference_on_generic_collections_abc_aliases(typ, data):
diff --git a/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py b/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py
--- a/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py
+++ b/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py
@@ -19,7 +19,7 @@
 There was a problem previously that ``bound='MyType'`` was resolved as ``ForwardRef('MyType')``
 which is not a real type. And ``hypothesis`` was not able to generate any meaningful values out of it.
 
-Right here we test different possible outcomes for different Python versions (excluding ``3.5``):
+Right here we test different possible outcomes for different Python versions:
 - Regular case, when ``'MyType'`` can be imported
 - Alias case, when we use type aliases for ``'MyType'``
 - ``if TYPE_CHECKING:`` case, when ``'MyType'`` only exists during type checking and is not importable at all