Skip to content

Comments

[Conformance]fix Feature Request: support __slots__ #630#2425

Open
asukaminato0721 wants to merge 5 commits intofacebook:mainfrom
asukaminato0721:630
Open

[Conformance]fix Feature Request: support __slots__ #630#2425
asukaminato0721 wants to merge 5 commits intofacebook:mainfrom
asukaminato0721:630

Conversation

@asukaminato0721
Copy link
Contributor

Summary

Fixes #630

Enforced __slots__ restrictions for instance attribute writes/deletes by extracting literal slot names across the MRO; if any class lacks __slots__, includes __dict__, or has non-literal slots, we fall back to existing behavior to avoid false positives.

Test Plan

Promoted the dataclass slots conformance test to a normal passing test (removed the bug marker).

@meta-cla meta-cla bot added the cla signed label Feb 14, 2026
@asukaminato0721 asukaminato0721 changed the title fix Feature Request: support __slots__ fix Feature Request: support __slots__ #630 Feb 14, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 14, 2026 19:42
Copilot AI review requested due to automatic review settings February 14, 2026 19:42
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements __slots__-aware restrictions for instance attribute writes/deletes (fixing #630) by extracting literal slot names across the MRO and falling back to prior behavior when slot information is incomplete, and updates the dataclass slots conformance test to assert the new diagnostics.

Changes:

  • Enforce __slots__ restrictions for instance attribute set/delete by computing literal slot-name sets across the MRO.
  • Fall back to existing (more permissive) behavior when slot extraction is not fully reliable (missing __slots__, __dict__ in slots, or non-literal slot definitions).
  • Promote the dataclass slots conformance case from a bug-marked test to an expected-pass test with explicit error annotations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pyrefly/lib/alt/attr.rs Adds slot-name extraction and applies it to attribute writes/deletes during attribute lookup.
pyrefly/lib/test/dataclasses.rs Updates the dataclass slots conformance test expectations to require missing-attribute errors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1116 to +1117
| AttributeBase1::Quantified(_, cls)
| AttributeBase1::SuperInstance(cls, _) => Some(cls.clone()),
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

class_for_slots_restriction treats AttributeBase1::SuperInstance(cls, _) as cls.clone(), but cls is the start-lookup class for the super() proxy, not the underlying instance’s actual class (see how get_super_attribute uses SuperObj::Instance(obj)/SuperObj::Class(obj)). This will enforce __slots__ against the wrong class and can incorrectly reject super().attr = ... / del super().attr when attr is a slot on the real instance type but not on the start-lookup class. Use the SuperObj to derive the instance ClassType (and likely skip slots restriction for SuperObj::Class).

Suggested change
| AttributeBase1::Quantified(_, cls)
| AttributeBase1::SuperInstance(cls, _) => Some(cls.clone()),
| AttributeBase1::Quantified(_, cls) => Some(cls.clone()),
AttributeBase1::SuperInstance(_, super_obj) => match super_obj {
// For `super()` bound to an instance, enforce slots based on the
// underlying instance's actual class, not the start-lookup class.
SuperObj::Instance(instance_cls) => Some(instance_cls.clone()),
// For `super()` bound to a class, do not apply instance slots
// restriction.
SuperObj::Class(_) => None,
},

Copilot uses AI. Check for mistakes.
self.x = 3
# should error: y is not in slots
self.y = 3
self.y = 3 # E: Object of class `DC3` has no attribute `y`
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

This change enforces __slots__ restrictions on attribute writes/deletes, but the updated conformance test only covers direct self.y = .... Consider adding a small regression case for super() attribute writes/deletes on a slots class (e.g., super().y = ... where y is/ isn’t in the instance’s slots) to ensure SuperInstance handling stays correct.

Suggested change
self.y = 3 # E: Object of class `DC3` has no attribute `y`
self.y = 3 # E: Object of class `DC3` has no attribute `y`
class DC2Child(DC2):
def set_attr_via_super(self) -> None:
super().__init__()
# should error: y is not in slots of DC2
super().y = 3 # E: Object of class `DC2` has no attribute `y`
def del_attr_via_super(self) -> None:
# should error: y is not in slots of DC2
del super().y # E: Object of class `DC2` has no attribute `y`
class DC3Child(DC3):
def set_attr_via_super(self) -> None:
super().__init__()
# should error: y is not in slots of DC3
super().y = 3 # E: Object of class `DC3` has no attribute `y`
def del_attr_via_super(self) -> None:
# should error: y is not in slots of DC3
del super().y # E: Object of class `DC3` has no attribute `y`

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

@migeed-z migeed-z self-assigned this Feb 17, 2026
@migeed-z
Copy link
Contributor

Thank you for the PR!
Is there any insight on why there are so many errors on mypy primer?

@migeed-z migeed-z changed the title fix Feature Request: support __slots__ #630 [Conformance]fix Feature Request: support __slots__ #630 Feb 17, 2026
@asukaminato0721
Copy link
Contributor Author

class Node:
    prv: WeakAttr[Node] = WeakAttr(slot='_prv_weak')
    __slots__ = ('__weakref__', '_prv_weak', 'nxt')

    nxt: Node | WeakAttr[Node]  # Allow subclasses to use a WeakAttr for nxt too (see SentinelNode)

    def __init__(self, prv: Node, nxt: Node) -> None:
        self.prv = prv
        self.nxt = nxt

    def unlink(self) -> None:
        """Remove self from in between prv and nxt.
        Self's references to prv and nxt are retained so it can be relinked (see below).
        """
        self.prv.nxt = self.nxt
        self.nxt.prv = self.prv

    def relink(self) -> None:
        """Restore self between prv and nxt after unlinking (see above)."""
        self.prv.nxt = self.nxt.prv = self

get it...

+ ERROR bidict/_orderedbase.py:78:9-17: Object of class `Node` has no attribute `prv` [missing-attribute]
+ ERROR bidict/_orderedbase.py:86:9-21: Object of class `Node` has no attribute `prv` [missing-attribute]
+ ERROR bidict/_orderedbase.py:90:24-36: Object of class `Node` has no attribute `prv` [missing-attribute]

@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

manticore (https://github.com/trailofbits/manticore)
+ ERROR manticore/platforms/evm.py:170:9-18: Object of class `Transaction` has no attribute `sort` [missing-attribute]

core (https://github.com/home-assistant/core)
+ ERROR homeassistant/components/bluetooth/manager.py:93:9-20: Object of class `HomeAssistantBluetoothManager` has no attribute `_debug` [missing-attribute]
+ ERROR homeassistant/components/bluetooth/manager.py:182:9-26: Object of class `HomeAssistantBluetoothManager` has no attribute `_all_history` [missing-attribute]
+ ERROR homeassistant/components/bluetooth/manager.py:182:28-53: Object of class `HomeAssistantBluetoothManager` has no attribute `_connectable_history` [missing-attribute]
+ ERROR homeassistant/helpers/llm.py:384:9-22: Object of class `MergedAPI` has no attribute `llm_apis` [missing-attribute]
+ ERROR homeassistant/helpers/llm.py:457:9-28: Object of class `AssistAPI` has no attribute `cached_slugify` [missing-attribute]

discord.py (https://github.com/Rapptz/discord.py)
+ ERROR discord/abc.py:574:9-25: Object of class `GuildChannel` has no attribute `_overwrites` [missing-attribute]
+ ERROR discord/message.py:1479:9-20: Object of class `PartialMessage` has no attribute `pinned` [missing-attribute]
+ ERROR discord/message.py:1508:9-20: Object of class `PartialMessage` has no attribute `pinned` [missing-attribute]

static-frame (https://github.com/static-frame/static-frame)
+ ERROR static_frame/core/frame.py:6498:13-31: Object of class `IndexBase` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/frame.py:6505:13-33: Object of class `IndexBase` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/index.py:1763:9-17: Object of class `_IndexGOMixin` has no attribute `_map` [missing-attribute]
+ ERROR static_frame/core/index.py:1764:9-20: Object of class `_IndexGOMixin` has no attribute `_labels` [missing-attribute]
+ ERROR static_frame/core/index.py:1765:9-23: Object of class `_IndexGOMixin` has no attribute `_positions` [missing-attribute]
+ ERROR static_frame/core/index.py:1766:9-21: Object of class `_IndexGOMixin` has no attribute `_recache` [missing-attribute]
+ ERROR static_frame/core/index.py:1767:9-18: Object of class `_IndexGOMixin` has no attribute `_name` [missing-attribute]
+ ERROR static_frame/core/index.py:1768:9-28: Object of class `_IndexGOMixin` has no attribute `_labels_mutable` [missing-attribute]
+ ERROR static_frame/core/index.py:1769:9-34: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1770:9-37: Object of class `_IndexGOMixin` has no attribute `_positions_mutable_count` [missing-attribute]
+ ERROR static_frame/core/index.py:1771:9-27: Object of class `_IndexGOMixin` has no attribute `_argsort_cache` [missing-attribute]
+ ERROR static_frame/core/index.py:1772:9-25: Object of class `_IndexGOMixin` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/index.py:1786:9-29: Object of class `_IndexGOMixin` has no attribute `_labels_mutable` [missing-attribute]
+ ERROR static_frame/core/index.py:1788:13-39: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1790:13-39: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1798:9-38: Object of class `_IndexGOMixin` has no attribute `_positions_mutable_count` [missing-attribute]
+ ERROR static_frame/core/index.py:1804:13-39: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1809:9-21: Object of class `_IndexGOMixin` has no attribute `_labels` [missing-attribute]
+ ERROR static_frame/core/index.py:1812:9-24: Object of class `_IndexGOMixin` has no attribute `_positions` [missing-attribute]
+ ERROR static_frame/core/index.py:1813:9-22: Object of class `_IndexGOMixin` has no attribute `_recache` [missing-attribute]
+ ERROR static_frame/core/index.py:1816:9-28: Object of class `_IndexGOMixin` has no attribute `_argsort_cache` [missing-attribute]
+ ERROR static_frame/core/index.py:1878:13-39: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1882:13-39: Object of class `_IndexGOMixin` has no attribute `_labels_mutable_dtype` [missing-attribute]
+ ERROR static_frame/core/index.py:1888:9-26: Object of class `_IndexGOMixin` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/index.py:1899:13-22: Object of class `_IndexGOMixin` has no attribute `_map` [missing-attribute]
+ ERROR static_frame/core/index.py:1901:9-38: Object of class `_IndexGOMixin` has no attribute `_positions_mutable_count` [missing-attribute]
+ ERROR static_frame/core/index.py:1902:9-22: Object of class `_IndexGOMixin` has no attribute `_recache` [missing-attribute]
+ ERROR static_frame/core/index_base.py:267:13-30: Object of class `IndexBase` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/index_datetime.py:242:9-26: Object of class `_IndexDatetimeGOMixin` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/index_datetime.py:250:9-38: Object of class `_IndexDatetimeGOMixin` has no attribute `_positions_mutable_count` [missing-attribute]
+ ERROR static_frame/core/index_datetime.py:251:9-22: Object of class `_IndexDatetimeGOMixin` has no attribute `_recache` [missing-attribute]
+ ERROR static_frame/core/series.py:2450:9-27: Object of class `IndexBase` has no attribute `_sort_status` [missing-attribute]
+ ERROR static_frame/core/yarn.py:1109:9-33: Object of class `IndexBase` has no attribute `_sort_status` [missing-attribute]

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.

Feature Request: support __slots__

2 participants