Skip to content

Commit a912053

Browse files
committed
Extend tests for dataclass hashability
1 parent 6b1a64c commit a912053

File tree

7 files changed

+98
-21
lines changed

7 files changed

+98
-21
lines changed
Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
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:29: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
10+
dataclasses_hash.py:40: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
11+
dataclasses_hash.py:55: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
12+
dataclasses_hash.py:69: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
13+
dataclasses_hash.py:85: error: Expression is of type "Callable[[DC6], int]", not "None" [assert-type]
14+
dataclasses_hash.py:102: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]
615
"""
716
conformance_automated = "Fail"
817
errors_diff = """
9-
Line 15: Expected 1 errors
10-
Line 32: Expected 1 errors
18+
Line 17: Expected 1 errors
19+
Line 18: Expected 1 errors
20+
Line 43: Expected 1 errors
21+
Line 44: Expected 1 errors
22+
Line 14: Unexpected errors ['dataclasses_hash.py:14: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]']
23+
Line 40: Unexpected errors ['dataclasses_hash.py:40: error: Expression is of type "Callable[[object], int]", not "None" [assert-type]']
1124
"""

conformance/results/pyrefly/dataclasses_hash.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ 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:29:12-32: assert_type((self: DC2) -> int, None) failed [assert-type]
9+
ERROR dataclasses_hash.py:43:1-16: Expected a callable, got `None` [not-callable]
10+
ERROR dataclasses_hash.py:44:16-22: `DC3` is not assignable to `Hashable` [bad-assignment]
11+
ERROR dataclasses_hash.py:55:12-32: assert_type((self: DC4) -> int, None) failed [assert-type]
12+
ERROR dataclasses_hash.py:69:12-32: assert_type((self: DC5) -> int, None) failed [assert-type]
13+
ERROR dataclasses_hash.py:85:12-32: assert_type((self: DC6) -> int, None) failed [assert-type]
14+
ERROR dataclasses_hash.py:102:12-32: assert_type((self: DC7) -> int, None) failed [assert-type]
815
"""

conformance/results/pyright/dataclasses_hash.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
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:29:13 - error: "assert_type" mismatch: expected "None" but received "(self: DC2) -> int" (reportAssertTypeFailure)
9+
dataclasses_hash.py:43:1 - error: Object of type "None" cannot be called (reportOptionalCall)
10+
dataclasses_hash.py:44:16 - error: Type "DC3" is not assignable to declared type "Hashable"
811
  "DC3" is incompatible with protocol "Hashable"
912
    "__hash__" is an incompatible type
1013
      Type "None" is not assignable to type "() -> int" (reportAssignmentType)
14+
dataclasses_hash.py:55:13 - error: "assert_type" mismatch: expected "None" but received "(self: DC4) -> int" (reportAssertTypeFailure)
15+
dataclasses_hash.py:69:13 - error: "assert_type" mismatch: expected "None" but received "(self: DC5) -> int" (reportAssertTypeFailure)
16+
dataclasses_hash.py:85:13 - error: "assert_type" mismatch: expected "None" but received "(self: DC6) -> int" (reportAssertTypeFailure)
17+
dataclasses_hash.py:102:13 - error: "assert_type" mismatch: expected "None" but received "(self: DC7) -> int" (reportAssertTypeFailure)
1118
"""
1219
conformance_automated = "Pass"
1320
errors_diff = """

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: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
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 40: Unexpected errors ['dataclasses_hash.py:40: 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:29: error: Expression is of type "Callable[[object], int]", not "None" [misc]
15+
dataclasses_hash.py:40: error: Expression is of type "Callable[[object], int]", not "None" [misc]
16+
dataclasses_hash.py:43: error: "DC3" has no attribute "__hash__" [attr-defined]
17+
dataclasses_hash.py:44: error: Incompatible types in assignment (expression has type "DC3", variable has type "Hashable") [assignment]
18+
dataclasses_hash.py:55: error: Expression is of type "Callable[[object], int]", not "None" [misc]
19+
dataclasses_hash.py:69: error: Expression is of type "Callable[[object], int]", not "None" [misc]
20+
dataclasses_hash.py:85: error: Expression is of type "Callable[[DC6], int]", not "None" [misc]
21+
dataclasses_hash.py:102: error: Expression is of type "Callable[[object], int]", not "None" [misc]
722
"""

conformance/tests/dataclasses_hash.py

Lines changed: 43 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 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,24 @@ class DC2:
2023
a: int
2124

2225

23-
v2: Hashable = DC2(0)
26+
# Because `DC2` is frozen, type checkers should synthesize
27+
# a callable `__hash__` method for it, and therefore should
28+
# emit a diagnostic here:
29+
assert_type(DC2.__hash__, None) # E
30+
31+
DC2(0).__hash__() # OK
32+
v2: Hashable = DC2(0) # OK
2433

2534

2635
@dataclass(eq=True)
2736
class DC3:
2837
a: int
2938

3039

31-
# This should generate an error because DC3 isn't hashable.
40+
assert_type(DC3.__hash__, None)
41+
42+
# These should generate errors because DC3 isn't hashable.
43+
DC3(0).__hash__() # E
3244
v3: Hashable = DC3(0) # E
3345

3446

@@ -37,15 +49,27 @@ class DC4:
3749
a: int
3850

3951

40-
v4: Hashable = DC4(0)
52+
# Because `DC4` is frozen, type checkers should synthesize
53+
# a callable `__hash__` method for it, and therefore should
54+
# emit a diagnostic here:
55+
assert_type(DC4.__hash__, None) # E
56+
57+
DC4(0).__hash__() # OK
58+
v4: Hashable = DC4(0) # OK
4159

4260

4361
@dataclass(eq=True, unsafe_hash=True)
4462
class DC5:
4563
a: int
4664

4765

48-
v5: Hashable = DC5(0)
66+
# Type checkers should synthesize a callable `__hash__`
67+
# method for `DC5` due to `unsafe_hash=True`, and therefore
68+
# should emit a diagnostic here:
69+
assert_type(DC5.__hash__, None) # E
70+
71+
DC5(0).__hash__() # OK
72+
v5: Hashable = DC5(0) # OK
4973

5074

5175
@dataclass(eq=True)
@@ -56,7 +80,12 @@ def __hash__(self) -> int:
5680
return 0
5781

5882

59-
v6: Hashable = DC6(0)
83+
# Type checkers should respect the manually defined `__hash__`
84+
# method for `DC6`, and therefore should emit a diagnostic here:
85+
assert_type(DC6.__hash__, None) # E
86+
87+
DC6(0).__hash__() # OK
88+
v6: Hashable = DC6(0) # OK
6089

6190

6291
@dataclass(frozen=True)
@@ -67,4 +96,10 @@ def __eq__(self, other) -> bool:
6796
return self.a == other.a
6897

6998

70-
v7: Hashable = DC7(0)
99+
# Because `DC7` is frozen, type checkers should synthesize
100+
# a callable `__hash__` method for it, and therefore should
101+
# emit a diagnostic here:
102+
assert_type(DC7.__hash__, None) # E
103+
104+
DC7(0).__hash__() # OK
105+
v7: Hashable = DC7(0) # OK

conformance/tests/generics_typevartuple_callable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ def func3(*args: * tuple[int, *Ts, T]) -> tuple[T, *Ts]:
4747

4848

4949
def has_int_and_str(a: int, b: str, c: float, d: complex):
50-
assert_type(func3(a, b, c, d), tuple[float, str, complex])
50+
assert_type(func3(a, b, c, d), tuple[complex, str, float])

0 commit comments

Comments
 (0)