mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 13:33:50 +00:00
3.6 KiB
3.6 KiB
__slots__
Not specified and empty
class A: ...
class B:
__slots__ = ()
class C:
__slots__ = ("lorem", "ipsum")
class AB(A, B): ... # fine
class AC(A, C): ... # fine
class BC(B, C): ... # fine
class ABC(A, B, C): ... # fine
Incompatible tuples
class A:
__slots__ = ("a", "b")
class B:
__slots__ = ("c", "d")
class C(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
Same value
class A:
__slots__ = ("a", "b")
class B:
__slots__ = ("a", "b")
class C(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
Strings
class A:
__slots__ = "abc"
class B:
__slots__ = ("abc",)
class AB(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
Invalid
TODO: Emit diagnostics
class NonString1:
__slots__ = 42
class NonString2:
__slots__ = b"ar"
class NonIdentifier1:
__slots__ = "42"
class NonIdentifier2:
__slots__ = ("lorem", "42")
class NonIdentifier3:
__slots__ = (e for e in ("lorem", "42"))
Inheritance
class A:
__slots__ = ("a", "b")
class B(A): ...
class C:
__slots__ = ("c", "d")
class D(C): ...
class E(
B, # error: [incompatible-slots]
D, # error: [incompatible-slots]
): ...
Single solid base
class A:
__slots__ = ("a", "b")
class B(A): ...
class C(A): ...
class D(B, A): ... # fine
class E(B, C, A): ... # fine
Post-hoc modifications
class A:
__slots__ = ()
__slots__ += ("a", "b")
reveal_type(A.__slots__) # revealed: tuple[Literal["a"], Literal["b"]]
class B:
__slots__ = ("c", "d")
class C(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
False negatives
Possibly unbound
def _(flag: bool):
class A:
if flag:
__slots__ = ("a", "b")
class B:
__slots__ = ("c", "d")
# Might or might not be fine at runtime
class C(A, B): ...
Bound but with different types
def _(flag: bool):
class A:
if flag:
__slots__ = ("a", "b")
else:
__slots__ = ()
class B:
__slots__ = ("c", "d")
# Might or might not be fine at runtime
class C(A, B): ...
Non-tuples
class A:
__slots__ = ["a", "b"] # This is treated as "dynamic"
class B:
__slots__ = ("c", "d")
# False negative: [incompatible-slots]
class C(A, B): ...
Built-ins with implicit layouts
# False negative: [incompatible-slots]
class A(int, str): ...
Diagnostic if __slots__
is externally modified
We special-case type inference for __slots__
and return the pure inferred type, even if the symbol
is not declared — a case in which we union with Unknown
for other public symbols. The reason for
this is that __slots__
has a special handling in the runtime. Modifying it externally is actually
allowed, but those changes do not take effect. If you have a class C
with __slots__ = ("foo",)
and externally set C.__slots__ = ("bar",)
, you still can't access C.bar
. And you can still
access C.foo
. We therefore issue a diagnostic for such assignments:
class A:
__slots__ = ("a",)
# Modifying `__slots__` from within the class body is fine:
__slots__ = ("a", "b")
# No `Unknown` here:
reveal_type(A.__slots__) # revealed: tuple[Literal["a"], Literal["b"]]
# But modifying it externally is not:
# error: [invalid-assignment]
A.__slots__ = ("a",)
# error: [invalid-assignment]
A.__slots__ = ("a", "b_new")
# error: [invalid-assignment]
A.__slots__ = ("a", "b", "c")