Skip to content

Commit 5b5f2f8

Browse files
Extend conformance tests for dataclass hashability (#2201)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 40b929c commit 5b5f2f8

File tree

6 files changed

+58
-20
lines changed

6 files changed

+58
-20
lines changed
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
conformant = "Partial"
1+
conformant = "Unsupported"
22
notes = """
3+
Does not synthesize `__hash__ = None` as a class attribute for unhashable dataclasses.
4+
Does not report when an unhashable dataclass has `__hash__` called directly on an instance.
35
Does not report when dataclass is not compatible with Hashable protocol.
46
"""
57
output = """
8+
dataclasses_hash.py:14: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
9+
dataclasses_hash.py:36: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
610
"""
711
conformance_automated = "Fail"
812
errors_diff = """
9-
Line 15: Expected 1 errors
10-
Line 32: Expected 1 errors
13+
Line 17: Expected 1 errors
14+
Line 18: Expected 1 errors
15+
Line 39: Expected 1 errors
16+
Line 40: Expected 1 errors
17+
Line 14: Unexpected errors ['dataclasses_hash.py:14: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]']
18+
Line 36: Unexpected errors ['dataclasses_hash.py:36: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]']
1119
"""

conformance/results/pyrefly/dataclasses_hash.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ conformance_automated = "Pass"
33
errors_diff = """
44
"""
55
output = """
6-
ERROR dataclasses_hash.py:15:16-22: `DC1` is not assignable to `Hashable` [bad-assignment]
7-
ERROR dataclasses_hash.py:32:16-22: `DC3` is not assignable to `Hashable` [bad-assignment]
6+
ERROR dataclasses_hash.py:17:1-16: Expected a callable, got `None` [not-callable]
7+
ERROR dataclasses_hash.py:18:16-22: `DC1` is not assignable to `Hashable` [bad-assignment]
8+
ERROR dataclasses_hash.py:39:1-16: Expected a callable, got `None` [not-callable]
9+
ERROR dataclasses_hash.py:40:16-22: `DC3` is not assignable to `Hashable` [bad-assignment]
810
"""

conformance/results/pyright/dataclasses_hash.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
conformant = "Pass"
22
output = """
3-
dataclasses_hash.py:15:16 - error: Type "DC1" is not assignable to declared type "Hashable"
3+
dataclasses_hash.py:17:1 - error: Object of type "None" cannot be called (reportOptionalCall)
4+
dataclasses_hash.py:18:16 - error: Type "DC1" is not assignable to declared type "Hashable"
45
  "DC1" is incompatible with protocol "Hashable"
56
    "__hash__" is an incompatible type
67
      Type "None" is not assignable to type "() -> int" (reportAssignmentType)
7-
dataclasses_hash.py:32:16 - error: Type "DC3" is not assignable to declared type "Hashable"
8+
dataclasses_hash.py:39:1 - error: Object of type "None" cannot be called (reportOptionalCall)
9+
dataclasses_hash.py:40:16 - error: Type "DC3" is not assignable to declared type "Hashable"
810
  "DC3" is incompatible with protocol "Hashable"
911
    "__hash__" is an incompatible type
1012
      Type "None" is not assignable to type "() -> int" (reportAssignmentType)

conformance/results/results.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -737,9 +737,9 @@ <h3>Python Type System Conformance Test Results</h3>
737737
<th class="column col2 conformant">Pass</th>
738738
</tr>
739739
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataclasses_hash</th>
740-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not report when dataclass is not compatible with Hashable protocol.</p></span></div></th>
741-
<th class="column col2 conformant">Pass</th>
740+
<th class="column col2 not-conformant"><div class="hover-text">Unsupported<span class="tooltip-text" id="bottom"><p>Does not synthesize `__hash__ = None` as a class attribute for unhashable dataclasses.</p><p>Does not report when an unhashable dataclass has `__hash__` called directly on an instance.</p><p>Does not report when dataclass is not compatible with Hashable protocol.</p></span></div></th>
742741
<th class="column col2 conformant">Pass</th>
742+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not synthesize a `__hash__ = None` class attribute for unhashable dataclasses.</p></span></div></th>
743743
<th class="column col2 conformant">Pass</th>
744744
</tr>
745745
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataclasses_inheritance</th>
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
conformance_automated = "Pass"
1+
conformance_automated = "Fail"
2+
conformant = "Partial"
3+
notes = """
4+
Does not synthesize a `__hash__ = None` class attribute for unhashable dataclasses.
5+
"""
26
errors_diff = """
7+
Line 14: Unexpected errors ['dataclasses_hash.py:14: error: Expression is of type "Callable[[object], int]", not "None" [misc]']
8+
Line 36: Unexpected errors ['dataclasses_hash.py:36: error: Expression is of type "Callable[[object], int]", not "None" [misc]']
39
"""
410
output = """
5-
dataclasses_hash.py:15: error: Incompatible types in assignment (expression has type "DC1", variable has type "Hashable") [assignment]
6-
dataclasses_hash.py:32: error: Incompatible types in assignment (expression has type "DC3", variable has type "Hashable") [assignment]
11+
dataclasses_hash.py:14: error: Expression is of type "Callable[[object], int]", not "None" [misc]
12+
dataclasses_hash.py:17: error: "DC1" has no attribute "__hash__" [attr-defined]
13+
dataclasses_hash.py:18: error: Incompatible types in assignment (expression has type "DC1", variable has type "Hashable") [assignment]
14+
dataclasses_hash.py:36: error: Expression is of type "Callable[[object], int]", not "None" [misc]
15+
dataclasses_hash.py:39: error: "DC3" has no attribute "__hash__" [attr-defined]
16+
dataclasses_hash.py:40: error: Incompatible types in assignment (expression has type "DC3", variable has type "Hashable") [assignment]
717
"""

conformance/tests/dataclasses_hash.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
"""
44

55
from dataclasses import dataclass
6-
from typing import Hashable
6+
from typing import Callable, Hashable, assert_type
77

88

99
@dataclass
1010
class DC1:
1111
a: int
1212

1313

14-
# This should generate an error because DC1 isn't hashable.
14+
assert_type(DC1.__hash__, None)
15+
16+
# These should generate errors because DC1 isn't hashable.
17+
DC1(0).__hash__() # E
1518
v1: Hashable = DC1(0) # E
1619

1720

@@ -20,15 +23,20 @@ class DC2:
2023
a: int
2124

2225

23-
v2: Hashable = DC2(0)
26+
dc2_hash: Callable[..., int] = DC2.__hash__ # OK
27+
DC2(0).__hash__() # OK
28+
v2: Hashable = DC2(0) # OK
2429

2530

2631
@dataclass(eq=True)
2732
class DC3:
2833
a: int
2934

3035

31-
# This should generate an error because DC3 isn't hashable.
36+
assert_type(DC3.__hash__, None)
37+
38+
# These should generate errors because DC3 isn't hashable.
39+
DC3(0).__hash__() # E
3240
v3: Hashable = DC3(0) # E
3341

3442

@@ -37,15 +45,19 @@ class DC4:
3745
a: int
3846

3947

40-
v4: Hashable = DC4(0)
48+
dc4_hash: Callable[..., int] = DC4.__hash__ # OK
49+
DC4(0).__hash__() # OK
50+
v4: Hashable = DC4(0) # OK
4151

4252

4353
@dataclass(eq=True, unsafe_hash=True)
4454
class DC5:
4555
a: int
4656

4757

48-
v5: Hashable = DC5(0)
58+
dc5_hash: Callable[..., int] = DC5.__hash__ # OK
59+
DC5(0).__hash__() # OK
60+
v5: Hashable = DC5(0) # OK
4961

5062

5163
@dataclass(eq=True)
@@ -56,7 +68,9 @@ def __hash__(self) -> int:
5668
return 0
5769

5870

59-
v6: Hashable = DC6(0)
71+
dc6_hash: Callable[..., int] = DC6.__hash__ # OK
72+
DC6(0).__hash__() # OK
73+
v6: Hashable = DC6(0) # OK
6074

6175

6276
@dataclass(frozen=True)
@@ -67,4 +81,6 @@ def __eq__(self, other) -> bool:
6781
return self.a == other.a
6882

6983

70-
v7: Hashable = DC7(0)
84+
dc7_hash: Callable[..., int] = DC7.__hash__ # OK
85+
DC7(0).__hash__() # OK
86+
v7: Hashable = DC7(0) # OK

0 commit comments

Comments
 (0)