Repository: HypothesisWorks/hypothesis

Issue:
Ghostwriter error on attrs class
While playing around with the ghostwriter today, I got an exception like this one running it against some internal code:

```
Traceback (most recent call last):
  File "/tmp/repro/venv/bin/hypothesis", line 8, in <module>
    sys.exit(main())
  File "/tmp/repro/venv/lib/python3.9/site-packages/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/tmp/repro/venv/lib/python3.9/site-packages/click/core.py", line 1062, in main
    rv = self.invoke(ctx)
  File "/tmp/repro/venv/lib/python3.9/site-packages/click/core.py", line 1668, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/tmp/repro/venv/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/tmp/repro/venv/lib/python3.9/site-packages/click/core.py", line 763, in invoke
    return __callback(*args, **kwargs)
  File "/tmp/repro/venv/lib/python3.9/site-packages/hypothesis/extra/cli.py", line 242, in write
    code = getattr(ghostwriter, writer)(*func, except_=except_ or (), style=style)
  File "/tmp/repro/venv/lib/python3.9/site-packages/hypothesis/extra/ghostwriter.py", line 882, in magic
    make_(
  File "/tmp/repro/venv/lib/python3.9/site-packages/hypothesis/extra/ghostwriter.py", line 802, in make_
    imp, body = how(*args, **kwargs, except_=except_, style=style)
  File "/tmp/repro/venv/lib/python3.9/site-packages/hypothesis/extra/ghostwriter.py", line 626, in _make_test_body
    given_strategies = given_strategies or _get_strategies(
  File "/tmp/repro/venv/lib/python3.9/site-packages/hypothesis/extra/ghostwriter.py", line 409, in _get_strategies
    if strat.args:
AttributeError: 'MappedSearchStrategy' object has no attribute 'args'
```

I reduced the input to trigger this exception down to

```python
import attr

@attr.s()
class Foo:
    foo: str = attr.ib()
```

I saw this with cPython 3.9.6, attrs 21.2.0, and hypothesis master branch at hypothesis-python-6.14.5-5-g9d4da6c2f. 


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/attrs.py b/hypothesis-python/src/hypothesis/strategies/_internal/attrs.py
--- a/hypothesis-python/src/hypothesis/strategies/_internal/attrs.py
+++ b/hypothesis-python/src/hypothesis/strategies/_internal/attrs.py
@@ -21,6 +21,7 @@
 from hypothesis import strategies as st
 from hypothesis.errors import ResolutionFailed
 from hypothesis.internal.compat import get_type_hints
+from hypothesis.strategies._internal.core import BuildsStrategy
 from hypothesis.strategies._internal.types import is_a_type, type_sorting_key
 from hypothesis.utils.conventions import infer
 
@@ -34,9 +35,7 @@ def from_attrs(target, args, kwargs, to_infer):
     # We might make this strategy more efficient if we added a layer here that
     # retries drawing if validation fails, for improved composition.
     # The treatment of timezones in datetimes() provides a precedent.
-    return st.tuples(st.tuples(*args), st.fixed_dictionaries(kwargs)).map(
-        lambda value: target(*value[0], **value[1])
-    )
+    return BuildsStrategy(target, args, kwargs)
 
 
 def from_attrs_attribute(attrib, target):


Test patch:
diff --git a/hypothesis-python/tests/ghostwriter/test_expected_output.py b/hypothesis-python/tests/ghostwriter/test_expected_output.py
--- a/hypothesis-python/tests/ghostwriter/test_expected_output.py
+++ b/hypothesis-python/tests/ghostwriter/test_expected_output.py
@@ -101,7 +101,10 @@ def divide(a: int, b: int) -> float:
             .replace("import sre_constants\n", "").replace("sre_constants.", "re."),
         ),
         ("re_compile_unittest", ghostwriter.fuzz(re.compile, style="unittest")),
-        ("base64_magic", ghostwriter.magic(base64)),
+        pytest.param(
+            ("base64_magic", ghostwriter.magic(base64)),
+            marks=pytest.mark.skipif("sys.version_info[:2] >= (3, 10)"),
+        ),
         ("sorted_idempotent", ghostwriter.idempotent(sorted)),
         ("timsort_idempotent", ghostwriter.idempotent(timsort)),
         (
diff --git a/hypothesis-python/tests/ghostwriter/test_ghostwriter.py b/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
--- a/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
+++ b/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
@@ -36,6 +36,7 @@
     ValuesView,
 )
 
+import attr
 import pytest
 
 from hypothesis.errors import InvalidArgument, MultipleFailures, Unsatisfiable
@@ -164,6 +165,15 @@ def takes_frozensets(a: FrozenSet[int], b: FrozenSet[int]) -> None:
     pass
 
 
+@attr.s()
+class Foo:
+    foo: str = attr.ib()
+
+
+def takes_attrs_class(x: Foo) -> None:
+    pass
+
+
 @varied_excepts
 @pytest.mark.parametrize(
     "func",
@@ -183,6 +193,7 @@ def takes_frozensets(a: FrozenSet[int], b: FrozenSet[int]) -> None:
         takes_pattern,
         takes_sized,
         takes_frozensets,
+        takes_attrs_class,
     ],
 )
 def test_ghostwriter_fuzz(func, ex):