|  | 
| 2 | 2 | 
 | 
| 3 | 3 | from __future__ import annotations | 
| 4 | 4 | 
 | 
| 5 |  | -from typing import Iterator, Optional | 
|  | 5 | +from typing import TYPE_CHECKING, Iterator, Optional | 
| 6 | 6 | from typing_extensions import Final | 
| 7 | 7 | 
 | 
| 8 | 8 | from mypy import errorcodes, message_registry | 
|  | 
| 26 | 26 |     DataclassTransformSpec, | 
| 27 | 27 |     Expression, | 
| 28 | 28 |     FuncDef, | 
|  | 29 | +    FuncItem, | 
| 29 | 30 |     IfStmt, | 
| 30 | 31 |     JsonDict, | 
| 31 | 32 |     NameExpr, | 
|  | 
| 55 | 56 | from mypy.types import ( | 
| 56 | 57 |     AnyType, | 
| 57 | 58 |     CallableType, | 
|  | 59 | +    FunctionLike, | 
| 58 | 60 |     Instance, | 
| 59 | 61 |     LiteralType, | 
| 60 | 62 |     NoneType, | 
|  | 
| 69 | 71 | ) | 
| 70 | 72 | from mypy.typevars import fill_typevars | 
| 71 | 73 | 
 | 
|  | 74 | +if TYPE_CHECKING: | 
|  | 75 | +    from mypy.checker import TypeChecker | 
|  | 76 | + | 
| 72 | 77 | # The set of decorators that generate dataclasses. | 
| 73 | 78 | dataclass_makers: Final = {"dataclass", "dataclasses.dataclass"} | 
| 74 | 79 | 
 | 
| 75 | 80 | 
 | 
| 76 | 81 | SELF_TVAR_NAME: Final = "_DT" | 
| 77 |  | -_TRANSFORM_SPEC_FOR_DATACLASSES = DataclassTransformSpec( | 
|  | 82 | +_TRANSFORM_SPEC_FOR_DATACLASSES: Final = DataclassTransformSpec( | 
| 78 | 83 |     eq_default=True, | 
| 79 | 84 |     order_default=False, | 
| 80 | 85 |     kw_only_default=False, | 
| 81 | 86 |     frozen_default=False, | 
| 82 | 87 |     field_specifiers=("dataclasses.Field", "dataclasses.field"), | 
| 83 | 88 | ) | 
| 84 |  | -_INTERNAL_REPLACE_SYM_NAME = "__mypy-replace" | 
|  | 89 | +_INTERNAL_REPLACE_SYM_NAME: Final = "__mypy-replace" | 
|  | 90 | +_INTERNAL_POST_INIT_SYM_NAME: Final = "__mypy-__post_init__" | 
| 85 | 91 | 
 | 
| 86 | 92 | 
 | 
| 87 | 93 | class DataclassAttribute: | 
| @@ -350,6 +356,8 @@ def transform(self) -> bool: | 
| 350 | 356 | 
 | 
| 351 | 357 |         if self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: | 
| 352 | 358 |             self._add_internal_replace_method(attributes) | 
|  | 359 | +        if "__post_init__" in info.names: | 
|  | 360 | +            self._add_internal_post_init_method(attributes) | 
| 353 | 361 | 
 | 
| 354 | 362 |         info.metadata["dataclass"] = { | 
| 355 | 363 |             "attributes": [attr.serialize() for attr in attributes], | 
| @@ -385,7 +393,47 @@ def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) -> | 
| 385 | 393 |             fallback=self._api.named_type("builtins.function"), | 
| 386 | 394 |         ) | 
| 387 | 395 | 
 | 
| 388 |  | -        self._cls.info.names[_INTERNAL_REPLACE_SYM_NAME] = SymbolTableNode( | 
|  | 396 | +        info.names[_INTERNAL_REPLACE_SYM_NAME] = SymbolTableNode( | 
|  | 397 | +            kind=MDEF, node=FuncDef(typ=signature), plugin_generated=True | 
|  | 398 | +        ) | 
|  | 399 | + | 
|  | 400 | +    def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -> None: | 
|  | 401 | +        arg_types: list[Type] = [fill_typevars(self._cls.info)] | 
|  | 402 | +        arg_kinds = [ARG_POS] | 
|  | 403 | +        arg_names: list[str | None] = ["self"] | 
|  | 404 | + | 
|  | 405 | +        info = self._cls.info | 
|  | 406 | +        for attr in attributes: | 
|  | 407 | +            if not attr.is_init_var: | 
|  | 408 | +                continue | 
|  | 409 | +            attr_type = attr.expand_type(info) | 
|  | 410 | +            assert attr_type is not None | 
|  | 411 | +            arg_types.append(attr_type) | 
|  | 412 | +            # We always use `ARG_POS` without a default value, because it is practical. | 
|  | 413 | +            # Consider this case: | 
|  | 414 | +            # | 
|  | 415 | +            # @dataclass | 
|  | 416 | +            # class My: | 
|  | 417 | +            #     y: dataclasses.InitVar[str] = 'a' | 
|  | 418 | +            #     def __post_init__(self, y: str) -> None: ... | 
|  | 419 | +            # | 
|  | 420 | +            # We would be *required* to specify `y: str = ...` if default is added here. | 
|  | 421 | +            # But, most people won't care about adding default values to `__post_init__`, | 
|  | 422 | +            # because it is not designed to be called directly, and duplicating default values | 
|  | 423 | +            # for the sake of type-checking is unpleasant. | 
|  | 424 | +            arg_kinds.append(ARG_POS) | 
|  | 425 | +            arg_names.append(attr.name) | 
|  | 426 | + | 
|  | 427 | +        signature = CallableType( | 
|  | 428 | +            arg_types=arg_types, | 
|  | 429 | +            arg_kinds=arg_kinds, | 
|  | 430 | +            arg_names=arg_names, | 
|  | 431 | +            ret_type=NoneType(), | 
|  | 432 | +            fallback=self._api.named_type("builtins.function"), | 
|  | 433 | +            name="__post_init__", | 
|  | 434 | +        ) | 
|  | 435 | + | 
|  | 436 | +        info.names[_INTERNAL_POST_INIT_SYM_NAME] = SymbolTableNode( | 
| 389 | 437 |             kind=MDEF, node=FuncDef(typ=signature), plugin_generated=True | 
| 390 | 438 |         ) | 
| 391 | 439 | 
 | 
| @@ -1052,3 +1100,33 @@ def replace_function_sig_callback(ctx: FunctionSigContext) -> CallableType: | 
| 1052 | 1100 |         fallback=ctx.default_signature.fallback, | 
| 1053 | 1101 |         name=f"{ctx.default_signature.name} of {inst_type_str}", | 
| 1054 | 1102 |     ) | 
|  | 1103 | + | 
|  | 1104 | + | 
|  | 1105 | +def is_processed_dataclass(info: TypeInfo | None) -> bool: | 
|  | 1106 | +    return info is not None and "dataclass" in info.metadata | 
|  | 1107 | + | 
|  | 1108 | + | 
|  | 1109 | +def check_post_init(api: TypeChecker, defn: FuncItem, info: TypeInfo) -> None: | 
|  | 1110 | +    if defn.type is None: | 
|  | 1111 | +        return | 
|  | 1112 | + | 
|  | 1113 | +    ideal_sig = info.get_method(_INTERNAL_POST_INIT_SYM_NAME) | 
|  | 1114 | +    if ideal_sig is None or ideal_sig.type is None: | 
|  | 1115 | +        return | 
|  | 1116 | + | 
|  | 1117 | +    # We set it ourself, so it is always fine: | 
|  | 1118 | +    assert isinstance(ideal_sig.type, ProperType) | 
|  | 1119 | +    assert isinstance(ideal_sig.type, FunctionLike) | 
|  | 1120 | +    # Type of `FuncItem` is always `FunctionLike`: | 
|  | 1121 | +    assert isinstance(defn.type, FunctionLike) | 
|  | 1122 | + | 
|  | 1123 | +    api.check_override( | 
|  | 1124 | +        override=defn.type, | 
|  | 1125 | +        original=ideal_sig.type, | 
|  | 1126 | +        name="__post_init__", | 
|  | 1127 | +        name_in_super="__post_init__", | 
|  | 1128 | +        supertype="dataclass", | 
|  | 1129 | +        original_class_or_static=False, | 
|  | 1130 | +        override_class_or_static=False, | 
|  | 1131 | +        node=defn, | 
|  | 1132 | +    ) | 
0 commit comments