Repository: Parquery/icontract (language: python)

Issue:
TypeError when using dataclasses.field
```python
>>> @dataclass
... @invariant(lambda self: self.x > 0)
... class Foo:
...     x: int = field(default=42)
... 
>>> Foo(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/8wg14i81qgjf0baaw6a55qidjspayaxc-python3-3.11.6-env/lib/python3.11/site-packages/icontract/_checkers.py", line 966, in wrapper
    instance = new_func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
```


Discussion / hints:
@remexre thanks for reporting the issue!

Just so that I can reproduce the failure, could you please tell me where the `field` comes from?
It's part of dataclasses: https://docs.python.org/3/library/dataclasses.html#dataclasses.field

Fix patch:
diff --git a/docs/source/known_issues.rst b/docs/source/known_issues.rst
index 512b910..bad8e0f 100644
--- a/docs/source/known_issues.rst
+++ b/docs/source/known_issues.rst
@@ -1,11 +1,16 @@
 Known Issues
 ============
-**Integration with ``help()``**. We wanted to include the contracts in the output of ``help()``. Unfortunately,
+Integration with ``help()``
+---------------------------
+
+We wanted to include the contracts in the output of ``help()``. Unfortunately,
 ``help()`` renders the ``__doc__`` of the class and not of the instance. For functions, this is the class
 "function" which you can not inherit from. See this
 `discussion on python-ideas <https://groups.google.com/forum/#!topic/python-ideas/c9ntrVuh6WE>`_ for more details.
 
-**Defining contracts outside of decorators**. We need to inspect the source code of the condition and error lambdas to
+Defining contracts outside of decorators
+----------------------------------------
+We need to inspect the source code of the condition and error lambdas to
 generate the violation message and infer the error type in the documentation, respectively. ``inspect.getsource(.)``
 is broken on lambdas defined in decorators in Python 3.5.2+ (see
 `this bug report <https://bugs.python.org/issue21217>`_). We circumvented this bug by using ``inspect.findsource(.)``,
@@ -35,7 +40,9 @@ However, we haven't faced a situation in the code base where we would do somethi
 whether this is a big issue. As long as decorators are directly applied to functions and classes, everything
 worked fine on our code base.
 
-**`*args` and `**kwargs`**. Since handling variable number of positional and/or keyword arguments requires complex
+``*args`` and ``**kwargs``
+--------------------------
+Since handling variable number of positional and/or keyword arguments requires complex
 logic and entails many edge cases (in particular in relation to how the arguments from the actual call are resolved and
 passed to the contract), we did not implement it. These special cases also impose changes that need to propagate to
 rendering the violation messages and related tools such as pyicontract-lint and sphinx-icontract. This is a substantial
@@ -44,3 +51,20 @@ effort and needs to be prioritized accordingly.
 Before we spend a large amount of time on this feature, please give us a signal through
 `the issue 147 <https://github.com/Parquery/icontract/issues/147>`_ and describe your concrete use case and its
 relevance. If there is enough feedback from the users, we will of course consider implementing it.
+
+``dataclasses``
+---------------
+When you define contracts for `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_, make sure you define the contracts *after* decorating the class with ``@dataclass`` decorator:
+
+.. code-block:: python
+
+    >>> import icontract
+    >>> import dataclasses
+
+    >>> @icontract.invariant(lambda self: self.x > 0)
+    ... @dataclasses.dataclass
+    ... class Foo:
+    ...     x: int = dataclasses.field(default=42)
+
+
+This is necessary as we can not re-decorate the methods that ``dataclass`` decorator inserts.


Test patch:
diff --git a/tests_3_7/test_invariant.py b/tests_3_7/test_invariant.py
index 7b281ed..60a9443 100644
--- a/tests_3_7/test_invariant.py
+++ b/tests_3_7/test_invariant.py
@@ -26,6 +26,14 @@ class RightHalfPlanePoint:
             RightHalfPlanePoint.__new__.__doc__,
         )
 
+    def test_on_dataclass_with_field(self) -> None:
+        @icontract.invariant(lambda self: self.x > 0)
+        @dataclasses.dataclass
+        class Foo:
+            x: int = dataclasses.field(default=42)
+
+        _ = Foo()
+
 
 class TestViolation(unittest.TestCase):
     def test_on_dataclass(self) -> None:
@@ -53,6 +61,52 @@ class RightHalfPlanePoint:
             tests.error.wo_mandatory_location(str(violation_error)),
         )
 
+    def test_on_dataclass_with_field(self) -> None:
+        @icontract.invariant(lambda self: self.x < 0)
+        @dataclasses.dataclass
+        class Foo:
+            x: int = dataclasses.field(default=-1)
+
+        violation_error = None  # type: Optional[icontract.ViolationError]
+        try:
+            _ = Foo(3)
+        except icontract.ViolationError as err:
+            violation_error = err
+
+        self.assertIsNotNone(violation_error)
+        self.assertEqual(
+            textwrap.dedent(
+                """\
+            self.x < 0:
+            self was TestViolation.test_on_dataclass_with_field.<locals>.Foo(x=3)
+            self.x was 3"""
+            ),
+            tests.error.wo_mandatory_location(str(violation_error)),
+        )
+
+    def test_on_dataclass_with_field_default_violating(self) -> None:
+        @icontract.invariant(lambda self: self.x < 0)
+        @dataclasses.dataclass
+        class Foo:
+            x: int = dataclasses.field(default=42)
+
+        violation_error = None  # type: Optional[icontract.ViolationError]
+        try:
+            _ = Foo()
+        except icontract.ViolationError as err:
+            violation_error = err
+
+        self.assertIsNotNone(violation_error)
+        self.assertEqual(
+            textwrap.dedent(
+                """\
+            self.x < 0:
+            self was TestViolation.test_on_dataclass_with_field_default_violating.<locals>.Foo(x=42)
+            self.x was 42"""
+            ),
+            tests.error.wo_mandatory_location(str(violation_error)),
+        )
+
 
 if __name__ == "__main__":
     unittest.main()