Repository: HypothesisWorks/hypothesis

Issue:
strategies.from_type doesn't work for the empty tuple type
*edit: added expected behavior for clarity*
Python==3.5.4, hypothesis==3.71.3

#### Issue
`strategies.from_type(typing.Tuple[()])` should return `()`, but crashes instead.

First of all, I know this is a bit silly, since there is only one possible empty tuple, `()`, and hence not much to generate. It's also a pretty niche concern, but I think it'd be nice to support this for completeness. 

According to the `mypy` [docs](https://mypy.readthedocs.io/en/latest/builtin_types.html), the empty tuple has type `typing.Tuple[()]`. I couldn't find anything on this in the `typing` docs, but since Guido reviewed and merged [the commit](https://github.com/python/mypy/commit/71332d5857b36f4fa7e389bf9bf2c95776cd26f0) to `mypy's` docs ,  `Tuple[()]` seems like the official way to do it.

#### Expected behavior
```python
>>> from typing import Tuple
>>> from hypothesis.strategies import from_type
>>> from_type(Tuple[()]).example()
()
```
#### Actual behavior
```python
>>> from typing import Tuple
>>> from hypothesis.strategies import from_type
>>> from_type(Tuple[()]).example()
Traceback (most recent call last):
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 131, in recur
    result = mapping[strat]
KeyError: tuples(deferred(lambda: func(*args, **kwargs)))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 131, in recur
    result = mapping[strat]
KeyError: tuples(deferred(lambda: func(*args, **kwargs)))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 304, in example
    phases=tuple(set(Phase) - {Phase.shrink}),
  File "<venv path>\lib\site-packages\hypothesis\core.py", line 979, in find
    specifier.validate()
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 380, in validate
    self.is_empty
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 142, in accept
    recur(self)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 139, in recur
    mapping[strat] = getattr(strat, calculation)(recur)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\deferred.py", line 81, in calc_is_empty
    return recur(self.wrapped_strategy)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\strategies.py", line 139, in recur
    mapping[strat] = getattr(strat, calculation)(recur)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\lazy.py", line 90, in calc_is_empty
    return recur(self.wrapped_strategy)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\lazy.py", line 106, in wrapped_strategy
    unwrap_strategies(s) for s in self.__args)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\lazy.py", line 106, in <genexpr>
    unwrap_strategies(s) for s in self.__args)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\lazy.py", line 48, in unwrap_strategies
    result = unwrap_strategies(s.wrapped_strategy)
  File "<venv path>\lib\site-packages\hypothesis\searchstrategy\deferred.py", line 45, in wrapped_strategy
    result = self.__definition()
  File "<venv path>\lib\site-packages\hypothesis\strategies.py", line 1200, in <lambda>
    return deferred(lambda: func(*args, **kwargs))
  File "<venv path>\lib\site-packages\hypothesis\strategies.py", line 1261, in from_type
    raise InvalidArgument('thing=%s must be a type' % (thing,))
hypothesis.errors.InvalidArgument: thing=() must be a type
```


Discussion:
Thanks for reporting this @lubieowoce - I don't think it's silly at all!  If we're going to infer strategies from type hints, we should do it *properly* and not leave out any edge cases.
Thank you for the quick response! If you're busy with other work, let me know – I'm not familiar with the codebase, but this looks like a small change, so I can try and submit a fix.

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/searchstrategy/types.py b/hypothesis-python/src/hypothesis/searchstrategy/types.py
--- a/hypothesis-python/src/hypothesis/searchstrategy/types.py
+++ b/hypothesis-python/src/hypothesis/searchstrategy/types.py
@@ -83,6 +83,8 @@ def from_typing_type(thing):
         if getattr(thing, '__tuple_use_ellipsis__', False) or \
                 len(elem_types) == 2 and elem_types[-1] is Ellipsis:
             return st.lists(st.from_type(elem_types[0])).map(tuple)
+        elif len(elem_types) == 1 and elem_types[0] == ():
+            return st.tuples()  # Empty tuple; see issue #1583
         return st.tuples(*map(st.from_type, elem_types))
     if isinstance(thing, typing.TypeVar):
         if getattr(thing, '__bound__', None) is not None:


Test patch:
diff --git a/hypothesis-python/tests/numpy/test_gen_data.py b/hypothesis-python/tests/numpy/test_gen_data.py
--- a/hypothesis-python/tests/numpy/test_gen_data.py
+++ b/hypothesis-python/tests/numpy/test_gen_data.py
@@ -329,3 +329,11 @@ def test_overflowing_floats_are_deprecated(fill, data):
 @given(nps.arrays(dtype='float16', shape=(1,)))
 def test_inferred_floats_do_not_overflow(arr):
     pass
+
+
+@given(nps.arrays(
+    dtype='float16', shape=10, unique=True,
+    elements=st.integers(1, 9), fill=st.just(np.nan)
+))
+def test_unique_array_with_fill_can_use_all_elements(arr):
+    assume(len(set(arr)) == arr.size)
diff --git a/hypothesis-python/tests/py3/test_lookup.py b/hypothesis-python/tests/py3/test_lookup.py
--- a/hypothesis-python/tests/py3/test_lookup.py
+++ b/hypothesis-python/tests/py3/test_lookup.py
@@ -441,3 +441,9 @@ def test_resolving_recursive_type():
         # https://github.com/HypothesisWorks/hypothesis-python/issues/1074
         assert sys.version_info[:2] == (3, 5)
         pytest.skip('Could not find type hints to resolve')
+
+
+@given(from_type(typing.Tuple[()]))
+def test_resolves_empty_Tuple_issue_1583_regression(ex):
+    # See e.g. https://github.com/python/mypy/commit/71332d58
+    assert ex == ()