Skip to content

Fix slots class variable conflict (issue #5641) - #5642

Closed
KalininDL wants to merge 4 commits into
pyrefly-org:mainfrom
KalininDL:fix-slots-class-variable-conflict
Closed

KalininDL wants to merge 4 commits into
pyrefly-org:mainfrom
KalininDL:fix-slots-class-variable-conflict

Conversation

@KalininDL

Copy link
Copy Markdown
Contributor

A class that lists a name in __slots__ and also binds it in the class body fails at class creation with ValueError - see (data model).
Pyrefly didn't report this.

What is done:
When the names in a class's own __slots__ are known statically, Pyrefly now checks each one against the class body and reports bad-class-definition on the first definition that binds a value: an assignment, a method or property, a nested class, or an import.

Not reported (matching CPython):

  • a bare annotation (x: int), which doesn't create a class attribute. it also doesn't hide a value bound before or after it, so x = 1 followed by x: int is still reported.
  • __dict__, __weakref__ and __qualname__ are allowed - CPython doesn't create slot descriptors for them
  • a subclass without its own __slots__ that assigns an inherited slot name.
  • dynamic __slots__.
  • stubs, which are never executed; typeshed's gevent stubs, for example, declare Cython slots as properties.

Fixes #5641

  • I am a human, and I fully understand the code I am submitting.

Test Plan

  • New tests in pyrefly/lib/test/slots.rs - all pass.
  • Checked every case manually against CPython 3.13.
  • cargo test -p pyrefly --lib -- --skip deep_phi_chain --skip commands::coverage - all pass. test::cycles::deep_phi_chain_term_expr overflows the stack in a local debug build on main.
  • python3 test.py --no-test: formatting and lint clean, no changes to the conformance output.

KalininDL and others added 2 commits November 13, 2026 15:14
CPython raises `ValueError` when a class lists a name in `__slots__` and
also binds that name in its body, for example with an assignment, a method,
or a nested class, even when a bare annotation of the same name comes before
or after it. Pyrefly does not report this yet. The tests record the current
behavior and the cases that must stay accepted: annotations without a value,
a subclass that shadows an inherited slot, dynamic `__slots__`, the special
names `__dict__`, `__weakref__` and `__qualname__`, and stubs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class that lists a name in `__slots__` and also binds it in the class body
fails at definition time with `ValueError: 'foo' in __slots__ conflicts with
class variable`, because the slot descriptor and the class attribute would
need the same entry in the class namespace. Pyright and ty report this;
Pyrefly did not.

When Pyrefly reads a class's own `__slots__` and the names are known, it now
looks up each name among the class-body definitions. A name bound by an
assignment, a method, a nested class, or an import is reported as
`bad-class-definition` on that binding. A bare annotation does not create a
class attribute, so it is not reported, and it does not hide a value bound
elsewhere in the body. `__dict__`, `__weakref__` and `__qualname__` are
skipped, because CPython does not create slot descriptors for them. Stubs
are skipped, because they are never executed and stubs for extension
modules commonly declare a slot as a property.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

KalininDL and others added 2 commits November 13, 2026 17:31
A bare annotation does not create a class attribute, so it cannot conflict
with a name in `__slots__`. The check already skipped bare annotations among
several definitions of a name, but a single bare annotation in a branch,
such as `if flag(): x: int`, is merged into a definition that also covers
imports and other bindings, and it was reported.

The check now asks the same question of that definition as of each of
several definitions: whether its binding is a bare annotation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A metaclass's `__new__` receives the class namespace before the class is
created and may remove names from it. Pydantic's `BaseModel` assigns
placeholder values to names it lists in `__slots__`, and its metaclass pops
them before calling `type.__new__`, so the class is valid; mypy_primer
showed these as false positives.

The check now runs only when the metaclass is `type` or `ABCMeta`, which
create the class from the namespace as written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/s and removed size/s labels Nov 13, 2026
@meta-codesync

meta-codesync Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D131793863. (Because this pull request was imported automatically, there will not be any future comments.)

@github-actions

Copy link
Copy Markdown

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@stroxler stroxler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

meta-codesync Bot pushed a commit that referenced this pull request Nov 13, 2026
Summary:
Reviewing #5642 surfaced slot/class-variable declarations inside `TYPE_CHECKING` and a value deleted later in the class body. The review presented these as blockers because CPython accepts the resulting classes, without first establishing the intended static semantics. Typing rules still apply inside `TYPE_CHECKING`; runtime success alone does not establish that a diagnostic is incorrect.

Nudge reviews toward consistent static rules and justified support boundaries, rather than requiring a special case for every runtime-admitted corner. This benefits user predictability and avoids accumulating checker complexity. Give the blind reviewers the same principle, ground counterexample expectations in a specification or support contract, and justify blocking severity separately from scores and reviewer agreement. Architectural blockers remain possible when their maintenance impact warrants fixing them now.

The example motivates the guidelines; it does not settle those edge cases' semantics. No checker behavior or scoring weights change.

___

landed-with-radar-review

Differential Revision: D131832048

fbshipit-source-id: 970adc2f05f68de9ab63bebf09154c32f547fab6
@meta-codesync meta-codesync Bot closed this in 3e2fae9 Nov 13, 2026
@meta-codesync meta-codesync Bot added the Merged label Nov 13, 2026
@meta-codesync

meta-codesync Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

This pull request has been merged in 3e2fae9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Name in __slots__ that conflicts with a class variable is not reported

2 participants