From cc003b2990f01885b33d239d2c6025a04a8c1507 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Mon, 28 Aug 2023 00:20:17 -0400 Subject: [PATCH] attrs, dataclasses: don't enforce slots when base doesn't --- mypy/plugins/attrs.py | 5 +++++ mypy/plugins/dataclasses.py | 6 ++++++ test-data/unit/check-dataclasses.test | 16 ++++++++++++++++ test-data/unit/check-plugin-attrs.test | 15 +++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index d444c18852dd..3d326a5f4e80 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -893,6 +893,11 @@ def _add_attrs_magic_attribute( def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: + if any(p.slots is None for p in ctx.cls.info.mro[1:-1]): + # At least one type in mro (excluding `self` and `object`) + # does not have concrete `__slots__` defined. Ignoring. + return + # Unlike `@dataclasses.dataclass`, `__slots__` is rewritten here. ctx.cls.info.slots = {attr.name for attr in attributes} diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index d782acf50af5..39b597491e9e 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -443,6 +443,12 @@ def add_slots( self._cls, ) return + + if any(p.slots is None for p in info.mro[1:-1]): + # At least one type in mro (excluding `self` and `object`) + # does not have concrete `__slots__` defined. Ignoring. + return + info.slots = generated_slots # Now, insert `.__slots__` attribute to class namespace: diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 7881dfbcf1bb..91c409807497 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1519,6 +1519,22 @@ class Some: self.y = 1 # E: Trying to assign name "y" that is not in "__slots__" of type "__main__.Some" [builtins fixtures/dataclasses.pyi] +[case testDataclassWithSlotsDerivedFromNonSlot] +# flags: --python-version 3.10 +from dataclasses import dataclass + +class A: + pass + +@dataclass(slots=True) +class B(A): + x: int + + def __post_init__(self) -> None: + self.y = 42 + +[builtins fixtures/dataclasses.pyi] + [case testDataclassWithSlotsConflict] # flags: --python-version 3.10 from dataclasses import dataclass diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 7580531bebc9..e8598132c50e 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -1677,6 +1677,21 @@ class C: self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.C" [builtins fixtures/plugin_attrs.pyi] +[case testAttrsClassWithSlotsDerivedFromNonSlots] +import attrs + +class A: + pass + +@attrs.define(slots=True) +class B(A): + x: int + + def __attrs_post_init__(self) -> None: + self.y = 42 + +[builtins fixtures/plugin_attrs.pyi] + [case testRuntimeSlotsAttr] from attr import dataclass