|
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