Repository: HypothesisWorks/hypothesis

Issue:
Using typing in a target function signature breaks `strategies.builds`
In our code, we rely a lot on typing feature that was first introduced in Python 3.5 (see [PEP-484](https://www.python.org/dev/peps/pep-0484/)) so the breakage of typed signatures is a major issue for us.

To reproduce run the following script with latest `hypothesis==3.33.0` and any of `python{3.5,3.6,3.7dev}`:
```py
from typing import Tuple
from hypothesis import strategies as st

def target_untyped(num, flag):
    return (num, flag, )

def target_typed(num: int, flag: bool) -> Tuple[int, bool]:
    return (num, flag, )

StrategyUntypedPositionalArgs = st.builds(
    target_untyped,
    st.integers(),
    st.booleans(),
)

StrategyTypedPositionalArgs = st.builds(
    target_typed,
    st.integers(),
    st.booleans(),
)

# target with untyped args works
print(StrategyUntypedPositionalArgs.example())

# target with typed args raises TypeError
print(StrategyTypedPositionalArgs.example())
```

Bisecting shows the first failing commit is:
```sh
7f9fe4ae8aa156f54a4a66a0044763129a8d1d37 is the first bad commit
commit 7f9fe4ae8aa156f54a4a66a0044763129a8d1d37
Author: Zac-HD <zac.hatfield.dodds@gmail.com>
Date:   Sun Jul 23 20:55:59 2017 +1000

    Use from_type for missing required args to builds()

:040000 040000 89f12436a970300b9f9500ace096ff4c0bbe29bd 1369e4cef83506b9a5bf17b3b2329a1670a846d9 M	src
```

Workaround suggestion: do not update to `3.14.0`, last working version is `3.13.0`.


Discussion:
Thanks for reporting this - providing a reproducing case and the bisection is really useful.  I'll post a fix as soon as possible 😄 
Glad if this helps; if you need any additional info, don't hesitate to ask.
I've already tracked it down - there's a one-line fix to the introspection where I called it with the tuple of received arguments but handled a tuple of argument names.  Just writing a regression test now, then changelog and it'll be released as soon as someone can review it.
Nice, thanks!

Resolving patch:
diff --git a/src/hypothesis/internal/reflection.py b/src/hypothesis/internal/reflection.py
--- a/src/hypothesis/internal/reflection.py
+++ b/src/hypothesis/internal/reflection.py
@@ -91,17 +91,29 @@ def function_digest(function):
 
 
 def required_args(target, args=(), kwargs=()):
-    """Return a set of required args to target."""
+    """Return a set of names of required args to target that were not supplied
+    in args or kwargs.
+
+    This is used in builds() to determine which arguments to attempt to
+    fill from type hints.  target may be any callable (including classes
+    and bound methods).  args and kwargs should be as they are passed to
+    builds() - that is, a tuple of values and a dict of names: values.
+
+    """
     try:
         spec = getfullargspec(
             target.__init__ if inspect.isclass(target) else target)
     except TypeError:  # pragma: no cover
         return None
-    # For classes, self is present in the argspec but not really required
-    posargs = spec.args[1:] if inspect.isclass(target) else spec.args
-    return set(posargs + spec.kwonlyargs) \
+    # self appears in the argspec of __init__ and bound methods, but it's an
+    # error to explicitly supply it - so we might skip the first argument.
+    skip_self = int(inspect.isclass(target) or inspect.ismethod(target))
+    # Start with the args that were not supplied and all kwonly arguments,
+    # then remove all positional arguments with default values, and finally
+    # remove kwonly defaults and any supplied keyword arguments
+    return set(spec.args[skip_self + len(args):] + spec.kwonlyargs) \
         - set(spec.args[len(spec.args) - len(spec.defaults or ()):]) \
-        - set(spec.kwonlydefaults or ()) - set(args) - set(kwargs)
+        - set(spec.kwonlydefaults or ()) - set(kwargs)
 
 
 def convert_keyword_arguments(function, args, kwargs):


Test patch:
diff --git a/tests/cover/test_reflection.py b/tests/cover/test_reflection.py
--- a/tests/cover/test_reflection.py
+++ b/tests/cover/test_reflection.py
@@ -24,13 +24,13 @@
 import pytest
 
 from mock import Mock, MagicMock, NonCallableMock, NonCallableMagicMock
-from tests.common.utils import raises, validate_deprecation
+from tests.common.utils import raises
 from hypothesis.internal.compat import PY3, FullArgSpec, getfullargspec
 from hypothesis.internal.reflection import is_mock, proxies, arg_string, \
-    unbind_method, eval_directory, function_digest, fully_qualified_name, \
-    source_exec_as_module, convert_keyword_arguments, \
-    define_function_signature, convert_positional_arguments, \
-    get_pretty_function_description
+    required_args, unbind_method, eval_directory, function_digest, \
+    fully_qualified_name, source_exec_as_module, \
+    convert_keyword_arguments, define_function_signature, \
+    convert_positional_arguments, get_pretty_function_description
 
 
 def do_conversion_test(f, args, kwargs):
@@ -576,3 +576,26 @@ def test_is_mock_with_positive_cases():
     assert is_mock(MagicMock())
     assert is_mock(NonCallableMock())
     assert is_mock(NonCallableMagicMock())
+
+
+class Target(object):
+
+    def __init__(self, a, b):
+        pass
+
+    def method(self, a, b):
+        pass
+
+
+@pytest.mark.parametrize('target', [Target, Target(1, 2).method])
+@pytest.mark.parametrize('args,kwargs,expected', [
+    ((), {}, set('ab')),
+    ((1,), {}, set('b')),
+    ((1, 2), {}, set()),
+    ((), dict(a=1), set('b')),
+    ((), dict(b=2), set('a')),
+    ((), dict(a=1, b=2), set()),
+])
+def test_required_args(target, args, kwargs, expected):
+    # Mostly checking that `self` (and only self) is correctly excluded
+    assert required_args(target, args, kwargs) == expected
diff --git a/tests/py3/test_lookup.py b/tests/py3/test_lookup.py
--- a/tests/py3/test_lookup.py
+++ b/tests/py3/test_lookup.py
@@ -231,6 +231,11 @@ def annotated_func(a: int, b: int=2, *, c: int, d: int=4):
     return a + b + c + d
 
 
+def test_issue_946_regression():
+    # Turned type hints into kwargs even if the required posarg was passed
+    st.builds(annotated_func, st.integers()).example()
+
+
 @pytest.mark.parametrize('thing', [
     annotated_func,  # Works via typing.get_type_hints
     typing.NamedTuple('N', [('a', int)]),  # Falls back to inspection
@@ -334,3 +339,29 @@ def inner(ex):
         assert isinstance(ex, F)
 
     inner()
+
+
+class AnnotatedTarget(object):
+
+    def __init__(self, a: int, b: int):
+        pass
+
+    def method(self, a: int, b: int):
+        pass
+
+
+@pytest.mark.parametrize('target', [
+    AnnotatedTarget, AnnotatedTarget(1, 2).method
+])
+@pytest.mark.parametrize('args,kwargs', [
+    ((), {}),
+    ((1,), {}),
+    ((1, 2), {}),
+    ((), dict(a=1)),
+    ((), dict(b=2)),
+    ((), dict(a=1, b=2)),
+])
+def test_required_args(target, args, kwargs):
+    # Mostly checking that `self` (and only self) is correctly excluded
+    st.builds(target, *map(st.just, args),
+              **{k: st.just(v) for k, v in kwargs.items()}).example()