|
194 | 194 | Plugin, |
195 | 195 | SemanticAnalyzerPluginInterface, |
196 | 196 | ) |
| 197 | +from mypy.plugins import dataclasses as dataclasses_plugin |
197 | 198 | from mypy.reachability import ( |
198 | 199 | ALWAYS_FALSE, |
199 | 200 | ALWAYS_TRUE, |
|
208 | 209 | from mypy.semanal_namedtuple import NamedTupleAnalyzer |
209 | 210 | from mypy.semanal_newtype import NewTypeAnalyzer |
210 | 211 | from mypy.semanal_shared import ( |
| 212 | + ALLOW_INCOMPATIBLE_OVERRIDE, |
211 | 213 | PRIORITY_FALLBACKS, |
212 | 214 | SemanticAnalyzerInterface, |
213 | 215 | calculate_tuple_fallback, |
|
234 | 236 | from mypy.typeops import function_type, get_type_vars |
235 | 237 | from mypy.types import ( |
236 | 238 | ASSERT_TYPE_NAMES, |
| 239 | + DATACLASS_TRANSFORM_NAMES, |
237 | 240 | FINAL_DECORATOR_NAMES, |
238 | 241 | FINAL_TYPE_NAMES, |
239 | 242 | NEVER_NAMES, |
|
304 | 307 | # available very early on. |
305 | 308 | CORE_BUILTIN_CLASSES: Final = ["object", "bool", "function"] |
306 | 309 |
|
307 | | -# Subclasses can override these Var attributes with incompatible types. This can also be |
308 | | -# set for individual attributes using 'allow_incompatible_override' of Var. |
309 | | -ALLOW_INCOMPATIBLE_OVERRIDE: Final = ("__slots__", "__deletable__", "__match_args__") |
310 | | - |
311 | 310 |
|
312 | 311 | # Used for tracking incomplete references |
313 | 312 | Tag: _TypeAlias = int |
@@ -1508,6 +1507,10 @@ def visit_decorator(self, dec: Decorator) -> None: |
1508 | 1507 | removed.append(i) |
1509 | 1508 | else: |
1510 | 1509 | self.fail("@final cannot be used with non-method functions", d) |
| 1510 | + elif isinstance(d, CallExpr) and refers_to_fullname( |
| 1511 | + d.callee, DATACLASS_TRANSFORM_NAMES |
| 1512 | + ): |
| 1513 | + dec.func.is_dataclass_transform = True |
1511 | 1514 | elif not dec.var.is_property: |
1512 | 1515 | # We have seen a "non-trivial" decorator before seeing @property, if |
1513 | 1516 | # we will see a @property later, give an error, as we don't support this. |
@@ -1709,6 +1712,11 @@ def apply_class_plugin_hooks(self, defn: ClassDef) -> None: |
1709 | 1712 | decorator_name = self.get_fullname_for_hook(decorator) |
1710 | 1713 | if decorator_name: |
1711 | 1714 | hook = self.plugin.get_class_decorator_hook(decorator_name) |
| 1715 | + # Special case: if the decorator is itself decorated with |
| 1716 | + # typing.dataclass_transform, apply the hook for the dataclasses plugin |
| 1717 | + # TODO: remove special casing here |
| 1718 | + if hook is None and is_dataclass_transform_decorator(decorator): |
| 1719 | + hook = dataclasses_plugin.dataclass_tag_callback |
1712 | 1720 | if hook: |
1713 | 1721 | hook(ClassDefContext(defn, decorator, self)) |
1714 | 1722 |
|
@@ -6599,3 +6607,10 @@ def halt(self, reason: str = ...) -> NoReturn: |
6599 | 6607 | return isinstance(stmt, PassStmt) or ( |
6600 | 6608 | isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr) |
6601 | 6609 | ) |
| 6610 | + |
| 6611 | + |
| 6612 | +def is_dataclass_transform_decorator(node: Node | None) -> bool: |
| 6613 | + if isinstance(node, RefExpr): |
| 6614 | + return is_dataclass_transform_decorator(node.node) |
| 6615 | + |
| 6616 | + return isinstance(node, Decorator) and node.func.is_dataclass_transform |
0 commit comments