Skip to content

Commit bd2c051

Browse files
committed
fix: classify __slots__ as class variables
Mark ordinary and plugin-generated __slots__ symbols as class variables so inheritance does not produce spurious instance/class variable override errors. Add regression coverage for ordinary classes, dataclasses, and attrs classes.
1 parent 8b3e7d8 commit bd2c051

6 files changed

Lines changed: 41 additions & 1 deletion

File tree

mypy/plugins/attrs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,9 @@ def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) ->
960960
[ctx.api.named_type("builtins.str") for _ in attributes],
961961
fallback=ctx.api.named_type("builtins.tuple"),
962962
)
963-
add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__slots__", typ=slots_type)
963+
add_attribute_to_class(
964+
api=ctx.api, cls=ctx.cls, name="__slots__", typ=slots_type, is_classvar=True
965+
)
964966

965967

966968
def _add_match_args(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None:

mypy/plugins/dataclasses.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ def add_slots(self, info: TypeInfo, attributes: list[DataclassAttribute]) -> Non
475475
self._cls,
476476
"__slots__",
477477
slots_type,
478+
is_classvar=True,
478479
overwrite_existing=slots_defined_by_plugin,
479480
)
480481

mypy/semanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4648,6 +4648,7 @@ def make_name_lvalue_var(
46484648
assert self.type is not None
46494649
v.info = self.type
46504650
v.is_initialized_in_class = True
4651+
v.is_classvar = name == "__slots__"
46514652
v.allow_incompatible_override = name in ALLOW_INCOMPATIBLE_OVERRIDE
46524653
if kind != LDEF:
46534654
v._fullname = self.qualified_name(name)

test-data/unit/check-dataclasses.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,18 @@ class Some:
15141514
self.y = 1 # E: Trying to assign name "y" that is not in "__slots__" of type "__main__.Some"
15151515
[builtins fixtures/dataclasses.pyi]
15161516

1517+
[case testDataclassGeneratedSlotsClassVariableOverride]
1518+
from dataclasses import dataclass
1519+
from typing import ClassVar
1520+
1521+
@dataclass(slots=True)
1522+
class Base:
1523+
value: int
1524+
1525+
class Child(Base):
1526+
__slots__: ClassVar[tuple[str, ...]] = ()
1527+
[builtins fixtures/tuple.pyi]
1528+
15171529
[case testDataclassWithSlotsDef]
15181530
from dataclasses import dataclass
15191531

test-data/unit/check-plugin-attrs.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,19 @@ class C:
17451745
self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.C"
17461746
[builtins fixtures/plugin_attrs.pyi]
17471747

1748+
[case testAttrsGeneratedSlotsClassVariableOverride]
1749+
from typing import ClassVar
1750+
1751+
import attrs
1752+
1753+
@attrs.define(slots=True)
1754+
class Base:
1755+
value: int
1756+
1757+
class Child(Base):
1758+
__slots__: ClassVar[tuple[str, ...]] = ()
1759+
[builtins fixtures/plugin_attrs.pyi]
1760+
17481761
[case testAttrsClassWithSlotsDerivedFromNonSlots]
17491762
import attrs
17501763

test-data/unit/check-slots.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ A.b = 1
8585
[builtins fixtures/tuple.pyi]
8686

8787

88+
[case testSlotsClassVariableOverride]
89+
from typing import ClassVar
90+
91+
class Base:
92+
__slots__ = ()
93+
94+
class Child(Base):
95+
__slots__: ClassVar[tuple[str, ...]] = ()
96+
[builtins fixtures/tuple.pyi]
97+
98+
8899
[case testSlotsDefinitionMultipleVars1]
89100
class A:
90101
__slots__ = __fields__ = ("a", "b")

0 commit comments

Comments
 (0)