Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pyrefly/lib/alt/class/class_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,35 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
}
let extends_abc = self.extends_abc(&bases_with_metadata, metaclass);

// Check for __slots__ layout conflict: if two or more base classes
// define non-empty __slots__, CPython raises TypeError at runtime
// ("multiple bases have instance lay-out conflict").
{
let bases_with_nonempty_slots: Vec<&Class> = bases_with_metadata
.iter()
.filter(|(_, metadata)| {
metadata.slots_info().is_some_and(|si| !si.names.is_empty())
})
.map(|(base, _)| base)
.collect();
if bases_with_nonempty_slots.len() > 1 {
let names: Vec<String> = bases_with_nonempty_slots
.iter()
.map(|b| format!("`{}`", b.name()))
.collect();
self.error(
errors,
cls.range(),
ErrorInfo::Kind(ErrorKind::InvalidInheritance),
format!(
"Class `{}` has multiple base classes with non-empty `__slots__` ({}), which causes a TypeError at runtime",
cls.name(),
names.join(", "),
),
);
}
}

// Compute final base class list.
let bases = if is_typed_dict && bases_with_metadata.is_empty() {
// This is a "fallback" class that contains attributes that are available on all TypedDict subclasses.
Expand Down
50 changes: 46 additions & 4 deletions pyrefly/lib/test/slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ c.x = 2 # OK: descriptor __set__ handles this, not instance storage

// https://github.com/facebook/pyrefly/issues/2917
testcase!(
bug = "Should detect instance layout conflict when multiple bases have __slots__",
test_slots_multiple_inheritance_layout_conflict,
r#"
class Left:
Expand All @@ -292,13 +291,12 @@ class Right:

# Inheriting from two classes that both define non-empty __slots__
# causes a TypeError at runtime.
class Combined(Left, Right): ...
class Combined(Left, Right): ... # E: multiple base classes with non-empty `__slots__`
"#,
);

// https://github.com/facebook/pyrefly/issues/2916
testcase!(
bug = "Should detect instance layout conflict even with identical slot names",
test_slots_layout_conflict_same_names,
r#"
class First:
Expand All @@ -308,7 +306,51 @@ class Second:
__slots__ = ("x",)

# Even though the slot names match, these are different C-level layouts.
class Both(First, Second): ...
class Both(First, Second): ... # E: multiple base classes with non-empty `__slots__`
"#,
);

testcase!(
test_slots_layout_conflict_empty_slots_ok,
r#"
class A:
__slots__ = ("x",)

class B:
__slots__ = ()

# One base has non-empty slots, the other has empty slots - this is fine.
class C(A, B): ...
"#,
);

testcase!(
test_slots_layout_conflict_both_empty_ok,
r#"
class A:
__slots__ = ()

class B:
__slots__ = ()

# Both bases have empty slots - no conflict.
class C(A, B): ...
"#,
);

testcase!(
test_slots_layout_conflict_three_bases,
r#"
class A:
__slots__ = ("x",)

class B:
__slots__ = ("y",)

class C:
__slots__ = ("z",)

class D(A, B, C): ... # E: multiple base classes with non-empty `__slots__`
"#,
);

Expand Down
Loading