-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds support for __slots__
assignment, refs #10801
#10864
Changes from 9 commits
a0977b2
070a41c
89f6859
a392d8a
0195f73
7c4aa9f
fce051a
4edc4bf
4c112ae
7ecfa7f
a69dd23
7b4b1e1
af13351
7051dde
e26afe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
[case testSlotsDefinitionWithStrAndListAndTuple] | ||
class A: | ||
__slots__ = "a" | ||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 # E: Trying to assign name "b" that is not in "__slots__" of type "__main__.A" | ||
|
||
class B: | ||
__slots__ = ("a", "b") | ||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.c = 3 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.B" | ||
|
||
class C: | ||
__slots__ = ['c'] | ||
def __init__(self) -> None: | ||
self.a = 1 # E: Trying to assign name "a" that is not in "__slots__" of type "__main__.C" | ||
self.c = 3 | ||
|
||
class WithVariable: | ||
__fields__ = ['a', 'b'] | ||
__slots__ = __fields__ | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.c = 3 | ||
[builtins fixtures/tuple.pyi] | ||
[builtins fixtures/list.pyi] | ||
|
||
|
||
[case testSlotsDefinitionWithDict] | ||
class D: | ||
__slots__ = {'key': 'docs'} | ||
def __init__(self) -> None: | ||
self.key = 1 | ||
self.missing = 2 # E: Trying to assign name "missing" that is not in "__slots__" of type "__main__.D" | ||
[builtins fixtures/dict.pyi] | ||
|
||
|
||
[case testSlotsDefinitionWithDynamicDict] | ||
slot_kwargs = {'b': 'docs'} | ||
class WithDictKwargs: | ||
__slots__ = {'a': 'docs', **slot_kwargs} | ||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.c = 3 | ||
[builtins fixtures/dict.pyi] | ||
|
||
|
||
[case testSlotsDefinitionWithSet] | ||
class E: | ||
__slots__ = {'e'} | ||
def __init__(self) -> None: | ||
self.e = 1 | ||
self.missing = 2 # E: Trying to assign name "missing" that is not in "__slots__" of type "__main__.E" | ||
[builtins fixtures/set.pyi] | ||
|
||
|
||
[case testSlotsDefinitionOutsideOfClass] | ||
__slots__ = ("a", "b") | ||
class A: | ||
def __init__(self) -> None: | ||
self.x = 1 | ||
self.y = 2 | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsDefinitionMutlipleVars1] | ||
sobolevn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class A: | ||
__slots__ = __fields__ = ("a", "b") | ||
def __init__(self) -> None: | ||
self.x = 1 | ||
self.y = 2 | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
|
||
[case testSlotsDefinitionMutlipleVars2] | ||
sobolevn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class A: | ||
__fields__ = __slots__ = ("a", "b") | ||
def __init__(self) -> None: | ||
self.x = 1 | ||
self.y = 2 | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentEmptySlots] | ||
class A: | ||
__slots__ = () | ||
def __init__(self) -> None: | ||
self.a = 1 # E: Trying to assign name "a" that is not in "__slots__" of type "__main__.A" | ||
self.b = 2 # E: Trying to assign name "b" that is not in "__slots__" of type "__main__.A" | ||
sobolevn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
a = A() | ||
a.a = 1 | ||
a.b = 2 | ||
a.missing = 2 # E: "A" has no attribute "missing" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithSuper] | ||
class A: | ||
__slots__ = ("a",) | ||
class B(A): | ||
__slots__ = ("b", "c") | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self._one = 1 # E: Trying to assign name "_one" that is not in "__slots__" of type "__main__.B" | ||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
b.c = 3 # E: "B" has no attribute "c" | ||
b._two = 2 # E: "B" has no attribute "_two" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithSuperDuplicateSlots] | ||
class A: | ||
__slots__ = ("a",) | ||
class B(A): | ||
__slots__ = ("a", "b",) | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self._one = 1 # E: Trying to assign name "_one" that is not in "__slots__" of type "__main__.B" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithMixing] | ||
class A: | ||
__slots__ = ("a",) | ||
class Mixin: | ||
__slots__ = ("m",) | ||
class B(A, Mixin): | ||
__slots__ = ("b",) | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.m = 2 | ||
self._one = 1 # E: Trying to assign name "_one" that is not in "__slots__" of type "__main__.B" | ||
|
||
b = B() | ||
b.a = 1 | ||
b.m = 2 | ||
b.b = 2 # E: "B" has no attribute "b" | ||
b._two = 2 # E: "B" has no attribute "_two" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithSlottedSuperButNoChildSlots] | ||
class A: | ||
__slots__ = ("a",) | ||
class B(A): | ||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 1 | ||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithoutSuperSlots] | ||
class A: | ||
pass # no slots | ||
class B(A): | ||
__slots__ = ("a", "b") | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.missing = 3 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this an error if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how Python works in runtime. If any base class has class A:
pass # no slots
class B(A):
__slots__ = ("a", "b")
def __init__(self) -> None:
self.a = 1
self.b = 2
self.missing = 3
b = B()
b.a = 1
b.b = 2
b.missing = 3
b.extra = 4 # E: "B" has no attribute "extra" But, without class B:
__slots__ = ("a", "b")
def __init__(self) -> None:
self.a = 1
self.b = 2
self.missing = 3 # AttributeError: 'B' object has no attribute 'missing'
b = B() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, that I need to add this to the docs of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha, I see, that makes more sense. I would probably add it to class basics: https://mypy.readthedocs.io/en/stable/class_basics.html#class-basics |
||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
b.missing = 3 | ||
b.extra = 4 # E: "B" has no attribute "extra" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithoutSuperMixingSlots] | ||
class A: | ||
__slots__ = () | ||
class Mixin: | ||
pass # no slots | ||
class B(A, Mixin): | ||
__slots__ = ("a", "b") | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.missing = 3 | ||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
b.missing = 3 | ||
b.extra = 4 # E: "B" has no attribute "extra" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithExplicitDict] | ||
class A: | ||
__slots__ = ("a",) | ||
class B(A): | ||
__slots__ = ("__dict__",) | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
b.c = 3 # E: "B" has no attribute "c" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithExplicitSuperDict] | ||
class A: | ||
__slots__ = ("__dict__",) | ||
class B(A): | ||
__slots__ = ("a",) | ||
|
||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
|
||
b = B() | ||
b.a = 1 | ||
b.b = 2 | ||
b.c = 3 # E: "B" has no attribute "c" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentWithVariable] | ||
slot_name = "b" | ||
class A: | ||
__slots__ = ("a", slot_name) | ||
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.c = 3 | ||
|
||
a = A() | ||
a.a = 1 | ||
a.b = 2 | ||
a.c = 3 | ||
a.d = 4 # E: "A" has no attribute "d" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentMultipleLeftValues] | ||
class A: | ||
__slots__ = ("a", "b") | ||
def __init__(self) -> None: | ||
self.a, self.b, self.c = (1, 2, 3) # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.A" | ||
[builtins fixtures/tuple.pyi] | ||
|
||
|
||
[case testSlotsAssignmentMultipleAssignments] | ||
class A: | ||
__slots__ = ("a",) | ||
def __init__(self) -> None: | ||
self.a = self.b = self.c = 1 | ||
[out] | ||
main:4: error: Trying to assign name "b" that is not in "__slots__" of type "__main__.A" | ||
main:4: error: Trying to assign name "c" that is not in "__slots__" of type "__main__.A" | ||
[builtins fixtures/tuple.pyi] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a class has a mix of slots and non-slots members, wouldn't this report an error incorrectly?
E.g.
Edit: ah based on the tests it looks like this does not report an error incorrectly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a test case for this, thank you. It actually was not consistent with Python's runtime behavior:
I will change the code to make this an error with
mypy
as well.