8000 Support descriptors in dataclass transform by JukkaL · Pull Request #15006 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Support descriptors in dataclass transform #15006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add more error checking
  • Loading branch information
JukkaL committed Apr 4, 2023
commit 9ff3c6ce2a4d6a471d11ce6ab0729af2ce02a36f
49 changes: 31 additions & 18 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None:
)

current_attr_names.add(lhs.name)
init_type = _infer_dataclass_attr_init_type(sym)
init_type = self._infer_dataclass_attr_init_type(sym, lhs.name)
found_attrs[lhs.name] = DataclassAttribute(
name=lhs.name,
alias=alias,
Expand Down Expand Up @@ -760,6 +760,36 @@ def _get_bool_arg(self, name: str, default: bool) -> bool:
return require_bool_literal_argument(self._api, expression, name, default)
return default

def _infer_dataclass_attr_init_type(self, sym: SymbolTableNode, name: str) -> Type | None:
"""Infer __init__ argument type for an attribute.

In particular, possibly use the signature of __set__.
"""
default = sym.type
if sym.implicit:
return default
t = get_proper_type(sym.type)
if not isinstance(t, Instance):
return default
if "__set__" in t.type.names:
setter = t.type.names["__set__"]
if isinstance(setter.node, FuncDef):
setter_type = get_proper_type(setter.type)
if isinstance(setter_type, CallableType) and setter_type.arg_kinds == [
ARG_POS,
ARG_POS,
ARG_POS,
]:
return expand_type_by_instance(setter_type.arg_types[2], t)
else:
self._api.fail(
f'Unsupported signature for "__set__" in "{t.type.name}"', sym.node
)
else:
self._api.fail(f'Unsupported "__set__" in "{t.type.name}"', sym.node)

return default


def add_dataclass_tag(info: TypeInfo) -> None:
# The value is ignored, only the existence matters.
Expand Down Expand Up @@ -816,20 +846,3 @@ def _has_direct_dataclass_transform_metaclass(info: TypeInfo) -> bool:
info.declared_metaclass is not None
and info.declared_metaclass.type.dataclass_transform_spec is not None
)


def _infer_dataclass_attr_init_type(sym: SymbolTableNode) -> Type | None:
if sym.implicit:
return sym.type
t = get_proper_type(sym.type)
if not isinstance(t, Instance):
return sym.type
if "__set__" in t.type.names:
n = t.type.names["__set__"]
if isinstance(n.node, FuncDef):
setter_type = get_proper_type(n.type)
if not isinstance(setter_type, CallableType):
assert False, "unknown type"
if setter_type.arg_kinds == [ARG_POS, ARG_POS, ARG_POS]:
return expand_type_by_instance(setter_type.arg_types[2], t)
return sym.type
37 changes: 37 additions & 0 deletions test-data/unit/check-dataclass-transform.test
Original file line number Diff line number Diff line change
Expand Up @@ -919,3 +919,40 @@ c.x = 1 # E: Incompatible types in assignment (expression has type "int", varia

[typing fixtures/typing-full.pyi]
[builtins fixtures/dataclasses.pyi]

[case testDataclassTransformUnsupportedDescriptors]
# flags: --python-version 3.11

from typing import dataclass_transform, overload, Any

@dataclass_transform()
def my_dataclass(cls): ...

class Desc:
@overload
def __get__(self, instance: None, owner: Any) -> int: ...
@overload
def __get__(self, instance: object, owner: Any) -> str: ...
def __get__(self, instance, owner): ...

def __set__(*args, **kwargs): ...

class Desc2:
@overload
def __get__(self, instance: None, owner: Any) -> int: ...
@overload
def __get__(self, instance: object, owner: Any) -> str: ...
def __get__(self, instance, owner): ...

@overload
def __set__(self, instance: Any, value: bytes) -> None: ...
@overload
def __set__(self) -> None: ...
def __set__(self, *args, **kawrga) -> None: ...

@my_dataclass
class C:
x: Desc # E: Unsupported signature for "__set__" in "Desc"
y: Desc2 # E: Unsupported "__set__" in "Desc2"
[typing fixtures/typing-full.pyi]
[builtins fixtures/dataclasses.pyi]
0