diff --git a/conformance/results/mypy/dataclasses_descriptors.toml b/conformance/results/mypy/dataclasses_descriptors.toml index e4731029..1b75be2b 100644 --- a/conformance/results/mypy/dataclasses_descriptors.toml +++ b/conformance/results/mypy/dataclasses_descriptors.toml @@ -1,18 +1,15 @@ conformant = "Partial" notes = """ -Assumes descriptor behavior only when field is assigned in class body. -Does not correctly evaluate type of descriptor access. +Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present. """ output = """ -dataclasses_descriptors.py:61: error: Cannot access instance-only attribute "x" on class object [misc] -dataclasses_descriptors.py:62: error: Cannot access instance-only attribute "y" on class object [misc] -dataclasses_descriptors.py:66: error: Expression is of type "Desc2[int]", not "int" [assert-type] -dataclasses_descriptors.py:67: error: Expression is of type "Desc2[str]", not "str" [assert-type] +dataclasses_descriptors.py:74: error: Expression is of type "list[int]", not "Desc2[int]" [assert-type] +dataclasses_descriptors.py:74: error: Cannot access instance-only attribute "x" on class object [misc] +dataclasses_descriptors.py:75: error: Expression is of type "list[str]", not "Desc2[str]" [assert-type] +dataclasses_descriptors.py:75: error: Cannot access instance-only attribute "y" on class object [misc] +dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [assert-type] """ conformance_automated = "Fail" errors_diff = """ -Line 61: Unexpected errors ['dataclasses_descriptors.py:61: error: Cannot access instance-only attribute "x" on class object [misc]'] -Line 62: Unexpected errors ['dataclasses_descriptors.py:62: error: Cannot access instance-only attribute "y" on class object [misc]'] -Line 66: Unexpected errors ['dataclasses_descriptors.py:66: error: Expression is of type "Desc2[int]", not "int" [assert-type]'] -Line 67: Unexpected errors ['dataclasses_descriptors.py:67: error: Expression is of type "Desc2[str]", not "str" [assert-type]'] +Line 85: Unexpected errors ['dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [assert-type]'] """ diff --git a/conformance/results/pycroscope/dataclasses_descriptors.toml b/conformance/results/pycroscope/dataclasses_descriptors.toml index 5b89c7ef..3bd80ff2 100644 --- a/conformance/results/pycroscope/dataclasses_descriptors.toml +++ b/conformance/results/pycroscope/dataclasses_descriptors.toml @@ -1,23 +1,19 @@ conformant = "Partial" notes = """ -Conformance suite is questionable; see +Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present. +Does not preserve type parameters when accessing a non-data descriptor through the dataclass. +See also: """ conformance_automated = "Fail" errors_diff = """ -Line 61: Unexpected errors ['./dataclasses_descriptors.py:61:12: Any[error] is not equivalent to list[int]', "./dataclasses_descriptors.py:61:12: has no attribute 'x' [undefined_attribute]"] -Line 62: Unexpected errors ['./dataclasses_descriptors.py:62:12: Any[error] is not equivalent to list[str]', "./dataclasses_descriptors.py:62:12: has no attribute 'y' [undefined_attribute]"] -Line 63: Unexpected errors ['./dataclasses_descriptors.py:63:12: list[Any[generic_argument]] is not equivalent to list[str]'] -Line 66: Unexpected errors ['./dataclasses_descriptors.py:66:12: ./dataclasses_descriptors.py.Desc2[int] is not equivalent to int'] -Line 67: Unexpected errors ['./dataclasses_descriptors.py:67:12: ./dataclasses_descriptors.py.Desc2[str] is not equivalent to str'] -Line 68: Unexpected errors ['./dataclasses_descriptors.py:68:12: Any[generic_argument] is not equivalent to str'] +Line 76: Unexpected errors ['./dataclasses_descriptors.py:76:12: list[Any[generic_argument]] is not equivalent to list[str]'] +Line 85: Unexpected errors ['./dataclasses_descriptors.py:85:12: Any[generic_argument] is not equivalent to ./dataclasses_descriptors.py.Desc2[str]'] """ output = """ -./dataclasses_descriptors.py:61:12: Any[error] is not equivalent to list[int] -./dataclasses_descriptors.py:61:12: has no attribute 'x' [undefined_attribute] -./dataclasses_descriptors.py:62:12: Any[error] is not equivalent to list[str] -./dataclasses_descriptors.py:62:12: has no attribute 'y' [undefined_attribute] -./dataclasses_descriptors.py:63:12: list[Any[generic_argument]] is not equivalent to list[str] -./dataclasses_descriptors.py:66:12: ./dataclasses_descriptors.py.Desc2[int] is not equivalent to int -./dataclasses_descriptors.py:67:12: ./dataclasses_descriptors.py.Desc2[str] is not equivalent to str -./dataclasses_descriptors.py:68:12: Any[generic_argument] is not equivalent to str +./dataclasses_descriptors.py:74:12: Any[error] is not equivalent to ./dataclasses_descriptors.py.Desc2[int] +./dataclasses_descriptors.py:74:12: has no attribute 'x' [undefined_attribute] +./dataclasses_descriptors.py:75:12: Any[error] is not equivalent to ./dataclasses_descriptors.py.Desc2[str] +./dataclasses_descriptors.py:75:12: has no attribute 'y' [undefined_attribute] +./dataclasses_descriptors.py:76:12: list[Any[generic_argument]] is not equivalent to list[str] +./dataclasses_descriptors.py:85:12: Any[generic_argument] is not equivalent to ./dataclasses_descriptors.py.Desc2[str] """ diff --git a/conformance/results/pyrefly/dataclasses_descriptors.toml b/conformance/results/pyrefly/dataclasses_descriptors.toml index 5f36bae9..b0dcda1b 100644 --- a/conformance/results/pyrefly/dataclasses_descriptors.toml +++ b/conformance/results/pyrefly/dataclasses_descriptors.toml @@ -1,14 +1,21 @@ conformant = "Partial" notes = """ -Assumes descriptor behavior only when field is assigned in class body -Doesn't allow non-data descriptors or data descriptors with differing `__get__` and `__set__` types +Rejects a data descriptor field whose `__get__` and `__set__` have differing types. +Does not allow non-data descriptors as dataclass fields. """ conformance_automated = "Fail" errors_diff = """ -Line 32: Unexpected errors ['Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]'] -Line 58: Unexpected errors ['Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition]'] +Line 33: Unexpected errors ['Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition]'] +Line 83: Unexpected errors ['assert_type(int, Desc2[int]) failed [assert-type]'] +Line 84: Unexpected errors ['assert_type(str, Desc2[str]) failed [assert-type]'] +Line 85: Unexpected errors ['assert_type(str, Desc2[str]) failed [assert-type]'] """ output = """ -ERROR dataclasses_descriptors.py:32:5-6: Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition] -ERROR dataclasses_descriptors.py:58:5-6: Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition] +ERROR dataclasses_descriptors.py:33:5-6: Cannot set field `y` to data descriptor `Desc1` with inconsistent types [bad-class-definition] +ERROR dataclasses_descriptors.py:67:5-6: Cannot set field `z` to non-data descriptor `Desc2` [bad-class-definition] +ERROR dataclasses_descriptors.py:74:12-31: assert_type(list[int], Desc2[int]) failed [assert-type] +ERROR dataclasses_descriptors.py:75:12-31: assert_type(list[str], Desc2[str]) failed [assert-type] +ERROR dataclasses_descriptors.py:83:12-31: assert_type(int, Desc2[int]) failed [assert-type] +ERROR dataclasses_descriptors.py:84:12-31: assert_type(str, Desc2[str]) failed [assert-type] +ERROR dataclasses_descriptors.py:85:12-31: assert_type(str, Desc2[str]) failed [assert-type] """ diff --git a/conformance/results/pyright/dataclasses_descriptors.toml b/conformance/results/pyright/dataclasses_descriptors.toml index 5e485e85..59eba6ef 100644 --- a/conformance/results/pyright/dataclasses_descriptors.toml +++ b/conformance/results/pyright/dataclasses_descriptors.toml @@ -1,6 +1,17 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Does not preserve non-data descriptor objects stored in dataclass instances. +""" output = """ +dataclasses_descriptors.py:74:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "list[int]" (reportAssertTypeFailure) +dataclasses_descriptors.py:75:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "list[str]" (reportAssertTypeFailure) +dataclasses_descriptors.py:83:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "int" (reportAssertTypeFailure) +dataclasses_descriptors.py:84:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure) +dataclasses_descriptors.py:85:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure) """ -conformance_automated = "Pass" +conformance_automated = "Fail" errors_diff = """ +Line 83: Unexpected errors ['dataclasses_descriptors.py:83:13 - error: "assert_type" mismatch: expected "Desc2[int]" but received "int" (reportAssertTypeFailure)'] +Line 84: Unexpected errors ['dataclasses_descriptors.py:84:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)'] +Line 85: Unexpected errors ['dataclasses_descriptors.py:85:13 - error: "assert_type" mismatch: expected "Desc2[str]" but received "str" (reportAssertTypeFailure)'] """ diff --git a/conformance/results/results.html b/conformance/results/results.html index e220e045..fd88ec48 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -1787,31 +1787,42 @@

Python Type System Conformance Test Results

Partial
    -
  • Assumes descriptor behavior only when field is assigned in class body.
  • -
  • Does not correctly evaluate type of descriptor access.
  • +
  • Does not preserve a non-data descriptor object stored in a dataclass instance when a class attribute of the same name is also present.
Partial Partial
    -
  • Assumes descriptor behavior only when field is assigned in class body
  • -
  • Doesn't allow non-data descriptors or data descriptors with differing __get__ and __set__ types
  • +
  • Rejects a data descriptor field whose __get__ and __set__ have differing types.
  • +
  • Does not allow non-data descriptors as dataclass fields.
- Pass Partial
    -
  • 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.
  • +
  • Does not preserve non-data descriptor objects stored in dataclass instances.
  • +
+ + + Partial +
    +
  • Does not preserve non-data descriptor objects stored in dataclass instances.
  • +
+ + + Partial +
    +
  • Does not preserve non-data descriptor objects stored in dataclass instances.
- Pass dataclasses_final @@ -2002,9 +2013,9 @@

Python Type System Conformance Test Results

11.5 / 16 • 71.9% 15.5 / 16 • 96.9% 15.5 / 16 • 96.9% - 16 / 16 • 100.0% + 15.5 / 16 • 96.9% 14.5 / 16 • 90.6% - 16 / 16 • 100.0% + 15.5 / 16 • 96.9% @@ -2646,9 +2657,9 @@

Python Type System Conformance Test Results

109 / 141 • 77.3% 130 / 141 • 92.2% 138 / 141 • 97.9% - 136.5 / 141 • 96.8% + 136 / 141 • 96.5% 116 / 141 • 82.3% - 140.5 / 141 • 99.6% + 140 / 141 • 99.3% diff --git a/conformance/results/ty/dataclasses_descriptors.toml b/conformance/results/ty/dataclasses_descriptors.toml index 91ad20b9..3e28120d 100644 --- a/conformance/results/ty/dataclasses_descriptors.toml +++ b/conformance/results/ty/dataclasses_descriptors.toml @@ -1,13 +1,17 @@ conformance_automated = "Fail" conformant = "Partial" notes = """ -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. +Does not preserve non-data descriptor objects stored in dataclass instances. """ errors_diff = """ -Line 66: Unexpected errors ['dataclasses_descriptors.py:66:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `int`'] -Line 67: Unexpected errors ['dataclasses_descriptors.py:67:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `str`'] +Line 83: Unexpected errors ['dataclasses_descriptors.py:83:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `Desc2[int]`'] +Line 84: Unexpected errors ['dataclasses_descriptors.py:84:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `Desc2[str]`'] +Line 85: Unexpected errors ['dataclasses_descriptors.py:85:1: error[type-assertion-failure] Type `str` does not match asserted type `Desc2[str]`'] """ output = """ -dataclasses_descriptors.py:66:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `int` -dataclasses_descriptors.py:67:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `str` +dataclasses_descriptors.py:74:1: error[type-assertion-failure] Type `list[int]` does not match asserted type `Desc2[int]` +dataclasses_descriptors.py:75:1: error[type-assertion-failure] Type `list[str]` does not match asserted type `Desc2[str]` +dataclasses_descriptors.py:83:1: error[type-assertion-failure] Type `int | Desc2[int]` does not match asserted type `Desc2[int]` +dataclasses_descriptors.py:84:1: error[type-assertion-failure] Type `str | Desc2[str]` does not match asserted type `Desc2[str]` +dataclasses_descriptors.py:85:1: error[type-assertion-failure] Type `str` does not match asserted type `Desc2[str]` """ diff --git a/conformance/results/zuban/dataclasses_descriptors.toml b/conformance/results/zuban/dataclasses_descriptors.toml index cdd4d0cd..03d0482a 100644 --- a/conformance/results/zuban/dataclasses_descriptors.toml +++ b/conformance/results/zuban/dataclasses_descriptors.toml @@ -1,5 +1,17 @@ -conformance_automated = "Pass" +conformance_automated = "Fail" +conformant = "Partial" +notes = """ +Does not preserve non-data descriptor objects stored in dataclass instances. +""" errors_diff = """ +Line 83: Unexpected errors ['dataclasses_descriptors.py:83: error: Expression is of type "int", not "Desc2[int]" [misc]'] +Line 84: Unexpected errors ['dataclasses_descriptors.py:84: error: Expression is of type "str", not "Desc2[str]" [misc]'] +Line 85: Unexpected errors ['dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [misc]'] """ output = """ +dataclasses_descriptors.py:74: error: Expression is of type "list[int]", not "Desc2[int]" [misc] +dataclasses_descriptors.py:75: error: Expression is of type "list[str]", not "Desc2[str]" [misc] +dataclasses_descriptors.py:83: error: Expression is of type "int", not "Desc2[int]" [misc] +dataclasses_descriptors.py:84: error: Expression is of type "str", not "Desc2[str]" [misc] +dataclasses_descriptors.py:85: error: Expression is of type "str", not "Desc2[str]" [misc] """ diff --git a/conformance/tests/dataclasses_descriptors.py b/conformance/tests/dataclasses_descriptors.py index f2f30a62..176ea8f4 100644 --- a/conformance/tests/dataclasses_descriptors.py +++ b/conformance/tests/dataclasses_descriptors.py @@ -3,7 +3,8 @@ """ # This portion of the dataclass spec is under-specified in the documentation, -# but its behavior can be determined from the runtime implementation. +# but the expected behavior follows the runtime implementation. +# See https://github.com/python/typing/issues/2259. from dataclasses import dataclass from typing import Any, Generic, TypeVar, assert_type, overload @@ -38,6 +39,14 @@ class DC1: assert_type(DC1.y, Desc1) +# ``Desc2`` is a non-data descriptor (it implements only ``__get__``). A +# non-data descriptor is shadowed by an instance's ``__dict__``, so its +# ``__get__`` is not invoked for attributes that the dataclass ``__init__`` +# stores on the instance. Such attributes keep the value that was assigned to +# them. The descriptor protocol only runs for attributes that are present in the +# class namespace. + + class Desc2(Generic[T]): @overload def __get__(self, instance: None, owner: Any) -> list[T]: @@ -55,14 +64,22 @@ def __get__(self, instance: object | None, owner: Any) -> list[T] | T: class DC2: x: Desc2[int] y: Desc2[str] - z: Desc2[str] = Desc2() + z: Desc2[str] = Desc2() # E?: a non-data descriptor default may be rejected -assert_type(DC2.x, list[int]) -assert_type(DC2.y, list[str]) +# ``x`` and ``y`` are not present in the class namespace, so accessing them on +# the class is an ``AttributeError`` at runtime; a type checker may report an +# error here. ``z`` is present in the class namespace, so class access runs +# ``Desc2.__get__(None)`` and yields ``list[str]``. +assert_type(DC2.x, Desc2[int]) # E? +assert_type(DC2.y, Desc2[str]) # E? assert_type(DC2.z, list[str]) +# All three attributes are stored on the instance by ``__init__``. Because +# ``Desc2`` is a non-data descriptor, the instance ``__dict__`` shadows it and +# ``__get__`` never runs, so each attribute keeps the assigned ``Desc2`` object +# (even ``z``, which is also present in the class namespace). dc2 = DC2(Desc2(), Desc2(), Desc2()) -assert_type(dc2.x, int) -assert_type(dc2.y, str) -assert_type(dc2.z, str) +assert_type(dc2.x, Desc2[int]) +assert_type(dc2.y, Desc2[str]) +assert_type(dc2.z, Desc2[str])