Title: PEP-681 descriptor-typed fields misinterpreted: init expects descriptor type instead of set value type
Minimal reproducible example (no third-party dependencies)
from __future__ import annotations
from typing import Any, Generic, Optional, TypeVar, Union, TYPE_CHECKING, overload
from typing_extensions import dataclass_transform
_T = TypeVar("_T")
def model_field(
*,
default: Optional[Any] = None,
init: bool = True,
) -> Any:
raise NotImplementedError()
@dataclass_transform(
eq_default=True, order_default=True, field_specifiers=(model_field,)
)
class ModelBase:
def __init_subclass__(
cls,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
class Mapped(Generic[_T]):
if TYPE_CHECKING:
@overload
def __get__(self, instance: None, owner: Any) -> Mapped[_T]:
...
@overload
def __get__(self, instance: object, owner: Any) -> _T:
...
def __get__(
self, instance: Optional[object], owner: Any
) -> Union[Mapped[_T], _T]:
...
def __set__(self, instance: Any, value: _T) -> None:
...
def __delete__(self, instance: Any) -> None:
...
class AlsoCustomer(ModelBase):
a: Mapped[int]
# This is correct at runtime and under pyright
c2 = AlsoCustomer(a=5)
# This should be an error (and is under pyright)
c3 = AlsoCustomer(a="some string")
Actual behavior (mypy)
error: Argument "a" to "AlsoCustomer" has incompatible type "int"; expected "Mapped[int]"
Expected behavior
The synthesized init parameter for a descriptor-typed field (like a: Mapped[int]) should accept the value type of the descriptor's set method (here int), not the descriptor type itself (Mapped[int]). So AlsoCustomer(a=5) should type-check, and AlsoCustomer(a="some string") should be rejected.
This is the interpretation implemented by pyright and matches the runtime behavior of Python dataclasses when descriptors are used as field types.
Relevant specification
PEP-681, Descriptor-typed field support (non-normative note): "When enabled, the type of each parameter on the synthesized init method corresponding to a descriptor-typed field would be the type of the value parameter to the descriptor's set method rather than the descriptor type itself."
Background
Extensive discussion with PEP-681 authors and pyright maintainers reached consensus on this behavior (see microsoft/pyright#2958 and microsoft/pyright#3245). SQLAlchemy's Mapped descriptor relies on this correct interpretation.
Title: PEP-681 descriptor-typed fields misinterpreted: init expects descriptor type instead of set value type
Minimal reproducible example (no third-party dependencies)
Actual behavior (mypy)
Expected behavior
The synthesized init parameter for a descriptor-typed field (like a: Mapped[int]) should accept the value type of the descriptor's set method (here int), not the descriptor type itself (Mapped[int]). So AlsoCustomer(a=5) should type-check, and AlsoCustomer(a="some string") should be rejected.
This is the interpretation implemented by pyright and matches the runtime behavior of Python dataclasses when descriptors are used as field types.
Relevant specification
PEP-681, Descriptor-typed field support (non-normative note): "When enabled, the type of each parameter on the synthesized init method corresponding to a descriptor-typed field would be the type of the value parameter to the descriptor's set method rather than the descriptor type itself."
Background
Extensive discussion with PEP-681 authors and pyright maintainers reached consensus on this behavior (see microsoft/pyright#2958 and microsoft/pyright#3245). SQLAlchemy's Mapped descriptor relies on this correct interpretation.