Repository: strawberry-graphql/strawberry

Issue:
Regression using mutable default values in arguments.
<!-- Provide a general summary of the bug in the title above. -->

<!--- This template is entirely optional and can be removed, but is here to help both you and us. -->
<!--- Anything on lines wrapped in comments like these will not show up in the final text. -->

## Describe the Bug
Prior to v0.115
```python
@strawberry.input
class Options:
    flag: str = ''


@strawberry.type
class Query:
    @strawberry.field
    def field(self, x: list[str] = [], y: Options = {}) -> str:
        return f'{x} {y}'
```
would correctly resolve to
```graphql
type Query {
  field(x: [String!]! = [], y: Options! = {}): String!
}
```
As of v0.115 it raises
```python
  File "lib/python3.10/site-packages/strawberry/types/fields/resolver.py", line 87, in find
    resolver._resolved_annotations[parameter] = resolved_annotation
  File "/lib/python3.10/inspect.py", line 2740, in __hash__
    return hash((self.name, self.kind, self.annotation, self.default))
TypeError: unhashable type: 'list'

```

For lists, there is a workaround to use a tuple instead, but it's not ideal because GraphQL type coercion will correctly supply a list. For objects, there's no clean workaround; one would have to  use the equivalent of a `frozendict`.

## System Information
 - Strawberry version 0.115



Discussion:
Thanks for the bug report @coady, I'm looking for a fix now 😊

Resolving patch:
diff --git a/strawberry/types/fields/resolver.py b/strawberry/types/fields/resolver.py
--- a/strawberry/types/fields/resolver.py
+++ b/strawberry/types/fields/resolver.py
@@ -32,6 +32,29 @@
 from strawberry.types.info import Info
 
 
+class Parameter(inspect.Parameter):
+    def __hash__(self):
+        """Override to exclude default value from hash.
+
+        This adds compatibility for using unhashable default values in resolvers such as
+        list and dict. The present use-case is limited to analyzing parameters from one
+        resolver. Therefore, the name, kind, and annotation combination are guaranteed
+        to be unique since two arguments cannot have the same name in a callable.
+
+        Furthermore, even though it is not currently a use-case to collect parameters
+        from different resolvers, the likelihood of collision from having the same hash
+        value but different defaults is mitigated by Python invoking the
+        :py:meth:`__eq__` method if two items have the same hash. See the verification
+        of this behavior in the `test_parameter_hash_collision` test.
+        """
+        return hash((self.name, self.kind, self.annotation))
+
+
+class Signature(inspect.Signature):
+
+    _parameter_cls = Parameter
+
+
 class ReservedParameterSpecification(Protocol):
     def find(
         self, parameters: Tuple[inspect.Parameter, ...], resolver: StrawberryResolver
@@ -159,7 +182,7 @@ def __call__(self, *args, **kwargs) -> T:
 
     @cached_property
     def signature(self) -> inspect.Signature:
-        return inspect.signature(self._unbound_wrapped_func)
+        return Signature.from_callable(self._unbound_wrapped_func, follow_wrapped=True)
 
     @cached_property
     def reserved_parameters(


Test patch:
diff --git a/tests/fields/test_resolvers.py b/tests/fields/test_resolvers.py
--- a/tests/fields/test_resolvers.py
+++ b/tests/fields/test_resolvers.py
@@ -1,6 +1,6 @@
 import dataclasses
 import re
-from typing import ClassVar
+from typing import ClassVar, List, no_type_check
 
 import pytest
 
@@ -10,7 +10,12 @@
     MissingFieldAnnotationError,
     MissingReturnAnnotationError,
 )
-from strawberry.types.fields.resolver import StrawberryResolver, UncallableResolverError
+from strawberry.scalars import JSON
+from strawberry.types.fields.resolver import (
+    Signature,
+    StrawberryResolver,
+    UncallableResolverError,
+)
 from strawberry.types.info import Info
 
 
@@ -284,3 +289,50 @@ def resolver_annotated_info(
         },
         "return": str,
     }
+
+
+@no_type_check
+def test_resolver_with_unhashable_default():
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def field(
+            self, x: List[str] = ["foo"], y: JSON = {"foo": 42}  # noqa: B006
+        ) -> str:
+            return f"{x} {y}"
+
+    schema = strawberry.Schema(Query)
+    result = schema.execute_sync("query { field }")
+    assert result.data == {"field": "['foo'] {'foo': 42}"}
+    assert not result.errors
+
+
+@no_type_check
+def test_parameter_hash_collision():
+    """Ensure support for hashable defaults does not introduce collision."""
+
+    def foo(x: str = "foo"):
+        pass
+
+    def bar(x: str = "bar"):
+        pass
+
+    foo_signature = Signature.from_callable(foo, follow_wrapped=True)
+    bar_signature = Signature.from_callable(bar, follow_wrapped=True)
+
+    foo_param = foo_signature.parameters["x"]
+    bar_param = bar_signature.parameters["x"]
+
+    # Ensure __eq__ still functions properly
+    assert foo_param != bar_param
+
+    # Ensure collision does not occur in hash-map and hash-tables. Colisions are
+    # prevented by Python invoking __eq__ when two items have the same hash.
+    parameters_map = {
+        foo_param: "foo",
+        bar_param: "bar",
+    }
+    parameters_set = {foo_param, bar_param}
+
+    assert len(parameters_map) == 2
+    assert len(parameters_set) == 2