Repository: HypothesisWorks/hypothesis

Issue:
Python 3.9: get_type cannot resolve some typing types to a strategy
Under Python 3.9 with the [test & tools libraries](https://github.com/HypothesisWorks/hypothesis/blob/master/CONTRIBUTING.rst#installing-from-source-and-testing) installed, `test_lookup.test_resolves_weird_types` fails to resolve `typing.Sequence` and `typing.Mapping` into a strategy. It tries to resolve the following types:

* `collections.abc.ByteString` (a `typing.Sequence`)
* `collections.UserString` (a `typing.Sequence`)
* `sortedcontainers.sorteddict.SortedKeysView` (a `typing.Sequence`)
* `more_itertools.more.SequenceView` (a `typing.Sequence`)
* `os._Environ` (a `typing.Mapping`)

It seems like any type registered as a `collections.abc.Mapping` is tried when resolving `typing.Mapping`, and the same for `Sequence`. But I am not sure what the code is doing nor what it should be doing. 


<details><summary>Example traceback</summary><pre><code>
______________________________________________________ test_resolves_weird_types[typ0] _______________________________________________________
Traceback (most recent call last):
  File "/home/pviktori/dev/hypothesis/hypothesis-python/tests/cover/test_lookup.py", line 249, in test_resolves_weird_types
    from_type(typ).example()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 315, in example
    example_generating_inner_function()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 303, in example_generating_inner_function
    @settings(
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 1102, in wrapped_test
    raise the_error_hypothesis_found
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 1071, in wrapped_test
    state.run_engine()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 732, in run_engine
    runner.run()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 458, in run
    self._run()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 857, in _run
    self.generate_new_examples()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 661, in generate_new_examples
    minimal_example = self.cached_test_function(
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 1034, in cached_test_function
    self.test_function(data)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 180, in test_function
    self.__stoppable_test_function(data)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/engine.py", line 162, in __stoppable_test_function
    self._test_function(data)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 685, in _execute_once_for_engine
    escalate_hypothesis_internal_error()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 656, in _execute_once_for_engine
    result = self.execute_once(data)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 611, in execute_once
    result = self.test_runner(data, run)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/executors.py", line 52, in default_new_style_executor
    return function(data)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/core.py", line 552, in run
    args, kwargs = data.draw(self.search_strategy)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 889, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/collections.py", line 57, in do_draw
    return tuple(data.draw(e) for e in self.element_strategies)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/collections.py", line 57, in &lt;genexpr>
    return tuple(data.draw(e) for e in self.element_strategies)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 884, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 658, in do_draw
    result = self.pack(data.draw(self.mapped_strategy))
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 884, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/lazy.py", line 150, in do_draw
    return data.draw(self.wrapped_strategy)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 884, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 658, in do_draw
    result = self.pack(data.draw(self.mapped_strategy))
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 884, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/collections.py", line 57, in do_draw
    return tuple(data.draw(e) for e in self.element_strategies)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/collections.py", line 57, in &lt;genexpr>
    return tuple(data.draw(e) for e in self.element_strategies)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 884, in draw
    return strategy.do_draw(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/flatmapped.py", line 42, in do_draw
    return data.draw(expanded_source)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/internal/conjecture/data.py", line 871, in draw
    strategy.validate()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 378, in validate
    self.do_validate()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/lazy.py", line 118, in do_validate
    w.validate()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 379, in validate
    self.is_empty
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 126, in accept
    recur(self)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py", line 122, in recur
    mapping[strat] = getattr(strat, calculation)(recur)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/deferred.py", line 80, in calc_is_empty
    return recur(self.wrapped_strategy)
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/deferred.py", line 43, in wrapped_strategy
    result = self.__definition()
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/core.py", line 1338, in &lt;lambda>
    lambda thing: deferred(lambda: _from_type(thing)),
  File "/home/pviktori/dev/hypothesis/hypothesis-python/src/hypothesis/strategies/_internal/core.py", line 1452, in _from_type
    raise ResolutionFailed(
hypothesis.errors.ResolutionFailed: Could not resolve &lt;class 'sortedcontainers.sorteddict.SortedItemsView'> to a strategy; consider using register_type_strategy
</code></pre></details>


Discussion:

Resolving patch:
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
@@ -22,6 +22,8 @@
 import io
 import ipaddress
 import numbers
+import os
+import sys
 import typing
 import uuid
 from types import FunctionType
@@ -310,6 +312,12 @@ def _networks(bits):
     [type(None)] + sorted(_global_type_lookup, key=str)
 )
 
+if sys.version_info[:2] >= (3, 9):  # pragma: no cover
+    # subclass of MutableMapping, and in Python 3.9 we resolve to a union
+    # which includes this... but we don't actually ever want to build one.
+    _global_type_lookup[os._Environ] = st.just(os.environ)
+
+
 try:  # pragma: no cover
     import numpy as np
     from hypothesis.extra.numpy import arrays, array_shapes, scalar_dtypes, array_dtypes
@@ -412,7 +420,7 @@ def really_inner(thing):
 
 @register("Type")
 def resolve_Type(thing):
-    if thing.__args__ is None:
+    if getattr(thing, "__args__", None) is None:
         return st.just(type)
     args = (thing.__args__[0],)
     if getattr(args[0], "__origin__", None) is typing.Union:
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -376,6 +376,7 @@ def run_tox(task, version):
 PY36 = "3.6.9"
 PY37 = "3.7.4"
 PY38 = "3.8.0"
+PY39 = "3.9-dev"
 PYPY35 = "pypy3.5-7.0.0"
 PYPY36 = "pypy3.6-7.1.1"
 
@@ -388,8 +389,8 @@ def install_core():
 # ALIASES are the executable names for each Python version
 ALIASES = {PYPY35: "pypy3", PYPY36: "pypy3"}
 
-for n in [PY35, PY36, PY37, PY38]:
-    major, minor, patch = n.split(".")
+for n in [PY35, PY36, PY37, PY38, PY39]:
+    major, minor, patch = n.replace("-dev", ".").split(".")
     ALIASES[n] = "python%s.%s" % (major, minor)
 
 
@@ -422,6 +423,11 @@ def check_py38():
     run_tox("py38-full", PY38)
 
 
+@python_tests
+def check_py39():
+    run_tox("py39-full", PY39)
+
+
 @python_tests
 def check_pypy35():
     run_tox("pypy3-full", PYPY35)


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
@@ -38,6 +38,7 @@
 generics = sorted(
     (t for t in types._global_type_lookup if isinstance(t, typing_root_type)), key=str
 )
+xfail_on_39 = () if sys.version_info[:2] < (3, 9) else pytest.mark.xfail
 
 
 @pytest.mark.parametrize("typ", generics)
@@ -56,7 +57,7 @@ def inner(ex):
         elif isinstance(typ, typing._ProtocolMeta):
             pass
         elif typ is typing.Type and not isinstance(typing.Type, type):
-            assert isinstance(ex, typing.TypeVar)
+            assert ex is type or isinstance(ex, typing.TypeVar)
         else:
             try:
                 assert isinstance(ex, typ)
@@ -232,7 +233,7 @@ def if_available(name):
 @pytest.mark.parametrize(
     "typ",
     [
-        typing.Sequence,
+        pytest.param(typing.Sequence, marks=xfail_on_39),
         typing.Container,
         typing.Mapping,
         typing.Reversible,