Skip to content

Commit ce4ec15

Browse files
committed
Conformance: align dataclass descriptor tests with runtime
1 parent 7c607df commit ce4ec15

8 files changed

Lines changed: 89 additions & 52 deletions

File tree

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
conformant = "Partial"
22
notes = """
3-
Assumes descriptor behavior only when field is assigned in class body.
4-
Does not correctly evaluate type of descriptor access.
3+
Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.
54
"""
65
output = """
7-
dataclasses_descriptors.py:61: error: Cannot access instance-only attribute "x" on class object [misc]
8-
dataclasses_descriptors.py:62: error: Cannot access instance-only attribute "y" on class object [misc]
9-
dataclasses_descriptors.py:66: error: Expression is of type "Desc2[int]", not "int" [assert-type]
10-
dataclasses_descriptors.py:67: error: Expression is of type "Desc2[str]", not "str" [assert-type]
6+
dataclasses_descriptors.py:74: error: Expression is of type "list[int]", not "Desc2[int]" [assert-type]
7+
dataclasses_descriptors.py:74: error: Cannot access instance-only attribute "x" on class object [misc]
8+
dataclasses_descriptors.py:75: error: Expression is of type "list[str]", not "Desc2[str]" [assert-type]
9+
dataclasses_descriptors.py:75: error: Cannot access instance-only attribute "y" on class object [misc]
10+
dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [assert-type]
1111
"""
1212
conformance_automated = "Fail"
1313
errors_diff = """
14-
Line 61: Unexpected errors ['dataclasses_descriptors.py:61: error: Cannot access instance-only attribute "x" on class object [misc]']
15-
Line 62: Unexpected errors ['dataclasses_descriptors.py:62: error: Cannot access instance-only attribute "y" on class object [misc]']
16-
Line 66: Unexpected errors ['dataclasses_descriptors.py:66: error: Expression is of type "Desc2[int]", not "int" [assert-type]']
17-
Line 67: Unexpected errors ['dataclasses_descriptors.py:67: error: Expression is of type "Desc2[str]", not "str" [assert-type]']
14+
Line 85: Unexpected errors ['dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [assert-type]']
1815
"""
Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
conformant = "Partial"
22
notes = """
3-
Conformance suite is questionable; see https://github.com/python/typing/issues/2259
3+
Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.
4+
Does not preserve type parameters when accessing a non-data descriptor through the dataclass.
45
"""
56
conformance_automated = "Fail"
67
errors_diff = """
7-
Line 61: Unexpected errors ['./dataclasses_descriptors.py:61:12: Any[error] is not equivalent to list[int]', "./dataclasses_descriptors.py:61:12: <class 'DC2'> has no attribute 'x' [undefined_attribute]"]
8-
Line 62: Unexpected errors ['./dataclasses_descriptors.py:62:12: Any[error] is not equivalent to list[str]', "./dataclasses_descriptors.py:62:12: <class 'DC2'> has no attribute 'y' [undefined_attribute]"]
9-
Line 63: Unexpected errors ['./dataclasses_descriptors.py:63:12: list[Any[generic_argument]] is not equivalent to list[str]']
10-
Line 66: Unexpected errors ['./dataclasses_descriptors.py:66:12: ./dataclasses_descriptors.py.Desc2[int] is not equivalent to int']
11-
Line 67: Unexpected errors ['./dataclasses_descriptors.py:67:12: ./dataclasses_descriptors.py.Desc2[str] is not equivalent to str']
12-
Line 68: Unexpected errors ['./dataclasses_descriptors.py:68:12: Any[generic_argument] is not equivalent to str']
8+
Line 76: Unexpected errors ['.\\\\dataclasses_descriptors.py:76:12: list[Any[generic_argument]] is not equivalent to list[str]']
9+
Line 85: Unexpected errors ['.\\\\dataclasses_descriptors.py:85:12: Any[generic_argument] is not equivalent to .\\\\dataclasses_descriptors.py.Desc2[str]']
1310
"""
1411
output = """
15-
./dataclasses_descriptors.py:61:12: Any[error] is not equivalent to list[int]
16-
./dataclasses_descriptors.py:61:12: <class 'DC2'> has no attribute 'x' [undefined_attribute]
17-
./dataclasses_descriptors.py:62:12: Any[error] is not equivalent to list[str]
18-
./dataclasses_descriptors.py:62:12: <class 'DC2'> has no attribute 'y' [undefined_attribute]
19-
./dataclasses_descriptors.py:63:12: list[Any[generic_argument]] is not equivalent to list[str]
20-
./dataclasses_descriptors.py:66:12: ./dataclasses_descriptors.py.Desc2[int] is not equivalent to int
21-
./dataclasses_descriptors.py:67:12: ./dataclasses_descriptors.py.Desc2[str] is not equivalent to str
22-
./dataclasses_descriptors.py:68:12: Any[generic_argument] is not equivalent to str
12+
.\\dataclasses_descriptors.py:74:12: Any[error] is not equivalent to .\\dataclasses_descriptors.py.Desc2[int]
13+
.\\dataclasses_descriptors.py:74:12: <class 'DC2'> has no attribute 'x' [undefined_attribute]
14+
.\\dataclasses_descriptors.py:75:12: Any[error] is not equivalent to .\\dataclasses_descriptors.py.Desc2[str]
15+
.\\dataclasses_descriptors.py:75:12: <class 'DC2'> has no attribute 'y' [undefined_attribute]
16+
.\\dataclasses_descriptors.py:76:12: list[Any[generic_argument]] is not equivalent to list[str]
17+
.\\dataclasses_descriptors.py:85:12: Any[generic_argument] is not equivalent to .\\dataclasses_descriptors.py.Desc2[str]
2318
"""
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
conformant = "Partial"
22
notes = """
3-
* Assumes descriptor behavior only when field is assigned in class body
4-
* Doesn't allow non-data descriptors or data descriptors with differing `__get__` and `__set__` types
3+
Rejects a data descriptor field whose `__get__` and `__set__` have differing types.
4+
Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.
55
"""
66
conformance_automated = "Fail"
77
errors_diff = """
8-
Line 32: Unexpected errors ['Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]']
9-
Line 58: Unexpected errors ['Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition]']
8+
Line 33: Unexpected errors ['Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]']
9+
Line 85: Unexpected errors ['assert_type(str, Desc2[str]) failed [assert-type]']
1010
"""
1111
output = """
12-
ERROR dataclasses_descriptors.py:32:5-6: Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]
13-
ERROR dataclasses_descriptors.py:58:5-6: Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition]
12+
ERROR dataclasses_descriptors.py:33:5-6: Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]
13+
ERROR dataclasses_descriptors.py:67:5-6: Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition]
14+
ERROR dataclasses_descriptors.py:85:12-31: assert_type(str, Desc2[str]) failed [assert-type]
1415
"""
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
conformant = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not preserve non-data descriptor objects stored in dataclass instances.
4+
"""
25
output = """
6+
dataclasses_descriptors.py:74:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "list[int]" (reportAssertTypeFailure)
7+
dataclasses_descriptors.py:75:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "list[str]" (reportAssertTypeFailure)
8+
dataclasses_descriptors.py:83:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "int" (reportAssertTypeFailure)
9+
dataclasses_descriptors.py:84:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)
10+
dataclasses_descriptors.py:85:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)
311
"""
4-
conformance_automated = "Pass"
12+
conformance_automated = "Fail"
513
errors_diff = """
14+
Line 83: Unexpected errors ['dataclasses_descriptors.py:83:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "int" (reportAssertTypeFailure)']
15+
Line 84: Unexpected errors ['dataclasses_descriptors.py:84:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)']
16+
Line 85: Unexpected errors ['dataclasses_descriptors.py:85:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)']
617
"""

conformance/results/results.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,12 @@ <h3>Python Type System Conformance Test Results</h3>
900900
<a class="test_group" href="https://typing.readthedocs.io/en/latest/spec/dataclasses.html">Dataclasses</a>
901901
</th></tr>
902902
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataclasses_descriptors</th>
903-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Assumes descriptor behavior only when field is assigned in class body.</p><p>Does not correctly evaluate type of descriptor access.</p></span></div></th>
904-
<th class="column col2 conformant">Pass</th>
905-
<th class="column col2 conformant">Pass</th>
906-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>* Assumes descriptor behavior only when field is assigned in class body</p><p>* Doesn't allow non-data descriptors or data descriptors with differing `__get__` and `__set__` types</p></span></div></th>
907-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Conformance suite is questionable; see https://github.com/python/typing/issues/2259</p></span></div></th>
908-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Only infers a descriptor `__get__` method as being called when a descriptor attribute is accessed on an instance if the descriptor attribute is present in the class namespace.</p></span></div></th>
903+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.</p></span></div></th>
904+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not preserve non-data descriptor objects stored in dataclass instances.</p></span></div></th>
905+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not preserve non-data descriptor objects stored in dataclass instances.</p></span></div></th>
906+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Rejects a data descriptor field whose `__get__` and `__set__` have differing types.</p><p>Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.</p></span></div></th>
907+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.</p><p>Does not preserve type parameters when accessing a non-data descriptor through the dataclass.</p></span></div></th>
908+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not preserve non-data descriptor objects stored in dataclass instances.</p></span></div></th>
909909
</tr>
910910
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataclasses_final</th>
911911
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Wrongly requires a Final dataclass field to be initialized at class level.</p><p>Doesn't support Final nested inside ClassVar.</p></span></div></th>
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
conformance_automated = "Fail"
22
conformant = "Partial"
33
notes = """
4-
Only infers a descriptor `__get__` method as being called when a descriptor attribute is accessed on an instance if the descriptor attribute is present in the class namespace.
4+
Does not preserve non-data descriptor objects stored in dataclass instances.
55
"""
66
errors_diff = """
7-
Line 66: Unexpected errors ['dataclasses_descriptors.py:66:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `int`']
8-
Line 67: Unexpected errors ['dataclasses_descriptors.py:67:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `str`']
7+
Line 83: Unexpected errors ['dataclasses_descriptors.py:83:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `Desc2[int]`']
8+
Line 84: Unexpected errors ['dataclasses_descriptors.py:84:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `Desc2[str]`']
9+
Line 85: Unexpected errors ['dataclasses_descriptors.py:85:1: error[type-assertion-failure] Type `str` does not match asserted type `Desc2[str]`']
910
"""
1011
output = """
11-
dataclasses_descriptors.py:66:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `int`
12-
dataclasses_descriptors.py:67:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `str`
12+
dataclasses_descriptors.py:74:1: error[type-assertion-failure] Type `list[int]` does not match asserted type `Desc2[int]`
13+
dataclasses_descriptors.py:75:1: error[type-assertion-failure] Type `list[str]` does not match asserted type `Desc2[str]`
14+
dataclasses_descriptors.py:83:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `Desc2[int]`
15+
dataclasses_descriptors.py:84:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `Desc2[str]`
16+
dataclasses_descriptors.py:85:1: error[type-assertion-failure] Type `str` does not match asserted type `Desc2[str]`
1317
"""
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
conformance_automated = "Pass"
1+
conformance_automated = "Fail"
2+
conformant = "Partial"
3+
notes = """
4+
Does not preserve non-data descriptor objects stored in dataclass instances.
5+
"""
26
errors_diff = """
7+
Line 83: Unexpected errors ['dataclasses_descriptors.py:83: error: Expression is of type "int", not "Desc2[int]" [misc]']
8+
Line 84: Unexpected errors ['dataclasses_descriptors.py:84: error: Expression is of type "str", not "Desc2[str]" [misc]']
9+
Line 85: Unexpected errors ['dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [misc]']
310
"""
411
output = """
12+
dataclasses_descriptors.py:74: error: Expression is of type "list[int]", not "Desc2[int]" [misc]
13+
dataclasses_descriptors.py:75: error: Expression is of type "list[str]", not "Desc2[str]" [misc]
14+
dataclasses_descriptors.py:83: error: Expression is of type "int", not "Desc2[int]" [misc]
15+
dataclasses_descriptors.py:84: error: Expression is of type "str", not "Desc2[str]" [misc]
16+
dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [misc]
517
"""

conformance/tests/dataclasses_descriptors.py

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

55
# This portion of the dataclass spec is under-specified in the documentation,
6-
# but its behavior can be determined from the runtime implementation.
6+
# but the expected behavior follows the runtime implementation.
7+
# See https://github.com/python/typing/issues/2259.
78

89
from dataclasses import dataclass
910
from typing import Any, Generic, TypeVar, assert_type, overload
@@ -38,6 +39,14 @@ class DC1:
3839
assert_type(DC1.y, Desc1)
3940

4041

42+
# ``Desc2`` is a non-data descriptor (it implements only ``__get__``). A
43+
# non-data descriptor is shadowed by an instance's ``__dict__``, so its
44+
# ``__get__`` is not invoked for attributes that the dataclass ``__init__``
45+
# stores on the instance. Such attributes keep the value that was assigned to
46+
# them. The descriptor protocol only runs for attributes that are present in the
47+
# class namespace.
48+
49+
4150
class Desc2(Generic[T]):
4251
@overload
4352
def __get__(self, instance: None, owner: Any) -> list[T]:
@@ -55,14 +64,22 @@ def __get__(self, instance: object | None, owner: Any) -> list[T] | T:
5564
class DC2:
5665
x: Desc2[int]
5766
y: Desc2[str]
58-
z: Desc2[str] = Desc2()
67+
z: Desc2[str] = Desc2() # E?: a non-data descriptor default may be rejected
5968

6069

61-
assert_type(DC2.x, list[int])
62-
assert_type(DC2.y, list[str])
70+
# ``x`` and ``y`` are not present in the class namespace, so accessing them on
71+
# the class is an ``AttributeError`` at runtime; a type checker may report an
72+
# error here. ``z`` is present in the class namespace, so class access runs
73+
# ``Desc2.__get__(None)`` and yields ``list[str]``.
74+
assert_type(DC2.x, Desc2[int]) # E?
75+
assert_type(DC2.y, Desc2[str]) # E?
6376
assert_type(DC2.z, list[str])
6477

78+
# All three attributes are stored on the instance by ``__init__``. Because
79+
# ``Desc2`` is a non-data descriptor, the instance ``__dict__`` shadows it and
80+
# ``__get__`` never runs, so each attribute keeps the assigned ``Desc2`` object
81+
# (even ``z``, which is also present in the class namespace).
6582
dc2 = DC2(Desc2(), Desc2(), Desc2())
66-
assert_type(dc2.x, int)
67-
assert_type(dc2.y, str)
68-
assert_type(dc2.z, str)
83+
assert_type(dc2.x, Desc2[int])
84+
assert_type(dc2.y, Desc2[str])
85+
assert_type(dc2.z, Desc2[str])

0 commit comments

Comments
 (0)