|
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 |
@@ -1505,6 +1504,10 @@ def visit_decorator(self, dec: Decorator) -> None: |
1505 | 1504 | removed.append(i) |
1506 | 1505 | else: |
1507 | 1506 | self.fail("@final cannot be used with non-method functions", d) |
| 1507 | + elif isinstance(d, CallExpr) and refers_to_fullname( |
| 1508 | + d.callee, DATACLASS_TRANSFORM_NAMES |
| 1509 | + ): |
| 1510 | + dec.func.is_dataclass_transform = True |
1508 | 1511 | elif not dec.var.is_property: |
1509 | 1512 | # We have seen a "non-trivial" decorator before seeing @property, if |
1510 | 1513 | # we will see a @property later, give an error, as we don't support this. |
@@ -1706,6 +1709,11 @@ def apply_class_plugin_hooks(self, defn: ClassDef) -> None: |
1706 | 1709 | decorator_name = self.get_fullname_for_hook(decorator) |
1707 | 1710 | if decorator_name: |
1708 | 1711 | hook = self.plugin.get_class_decorator_hook(decorator_name) |
| 1712 | + # Special case: if the decorator is itself decorated with |
| 1713 | + # typing.dataclass_transform, apply the hook for the dataclasses plugin |
| 1714 | + # TODO: remove special casing here |
| 1715 | + if hook is None and is_dataclass_transform_decorator(decorator): |
| 1716 | + hook = dataclasses_plugin.dataclass_tag_callback |
1709 | 1717 | if hook: |
1710 | 1718 | hook(ClassDefContext(defn, decorator, self)) |
1711 | 1719 |
|
@@ -6596,3 +6604,10 @@ def halt(self, reason: str = ...) -> NoReturn: |
6596 | 6604 | return isinstance(stmt, PassStmt) or ( |
6597 | 6605 | isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr) |
6598 | 6606 | ) |
| 6607 | + |
| 6608 | + |
| 6609 | +def is_dataclass_transform_decorator(node: Node | None) -> bool: |
| 6610 | + if isinstance(node, RefExpr): |
| 6611 | + return is_dataclass_transform_decorator(node.node) |
| 6612 | + |
| 6613 | + return isinstance(node, Decorator) and node.func.is_dataclass_transform |
0 commit comments