Skip to content

Conversation

@Icxolu
Copy link
Contributor

@Icxolu Icxolu commented Jan 10, 2026

This a split-off of the opaque object layout mechanism from #4678 by @mbway

Since we bumped MSRV several times after the initial implementation and GATs are now possible, I decided to base this of that initial implementation. I rebased it on current main, resolved the conflict and focused on the mechanism only. Basic __init__ support landed in #4951, so that is removed from here. Metaclass support (extending PyType) is also removed here and left as a followup.

I hope this makes this easier to review and we can base of further improvements to the inheritance machinery on this.

@Icxolu Icxolu added CI-no-fail-fast If one job fails, allow the rest to keep testing CI-build-full labels Jan 10, 2026
@mbway
Copy link
Contributor

mbway commented Jan 10, 2026

Sorry for not getting around to rebasing my PR 😞. I had it on my TODO list but I've had other things that got in the way. It's been a while since I implemented the original but I think I can remember the solution I ended up with had some benefits compared to the GAT approach.

If you want to go ahead with this version then that's OK. Otherwise I can prioritise bringing my PR up to date.

@Icxolu
Copy link
Contributor Author

Icxolu commented Jan 10, 2026

Nothing to be sorry about. Thanks a lot for all the work you already put in! I can take a look again and compare the approaches but at least initially this one felt easier to follow for me and looked like it needed fewer changes to adapt to the current state. If there advantages for one or the other approach we can of course evaluate that before we decide to go with one.

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Very nice, thank you for moving this forward!

Overall this seems very mergeable, with a bunch of comments / suggestions. If I read correctly, as implemented there's not yet a way for users to usefully inherit from unknown sized types, that'll come in a follow up?

Comment on lines 36 to 46
/// Gets the offset of the dictionary from the start of the object in bytes.
#[inline]
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyClassObject::<T>::dict_offset()
pub fn dict_offset<T: PyClass>() -> PyObjectOffset {
<T as PyClassImpl>::Layout::dict_offset()
}

/// Gets the offset of the weakref list from the start of the object in bytes.
#[inline]
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyClassObject::<T>::weaklist_offset()
pub fn weaklist_offset<T: PyClass>() -> PyObjectOffset {
<T as PyClassImpl>::Layout::weaklist_offset()
}
Copy link
Member

Choose a reason for hiding this comment

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

Slight aside, and doesn't block this PR, it would be well worth solving #4253 so that we can eventually phase this bit of complexity out.

Comment on lines 577 to 556
#[cfg(Py_3_12)]
impl<T: PyClassImpl> PyClassObjectBaseLayout<T> for PyVariableClassObject<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
Copy link
Member

Choose a reason for hiding this comment

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

This implementation seems to be perfectly duplicated with the one for PyStaticClassObject, which seems to imply a refactoring opportunity exists IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you have something particular in mind?

Copy link
Member

Choose a reason for hiding this comment

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

I took a quick look at this and I couldn't see a way to deduplicate, I guess let's not block this PR on it, we can refactor later as we learn more.

Comment on lines +1479 to +1485
#[repr(C)]
struct ExpectedLayout {
ob_base: ffi::PyObject,
contents: PyClassObjectContents<FrozenClass>,
}
Copy link
Member

Choose a reason for hiding this comment

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

Should this be using PyStaticClassObject?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is redefined here, because the fields of PyStaticClassObject are intentionally private, so we can't compute the offset here without loosening that.

@Icxolu
Copy link
Contributor Author

Icxolu commented Jan 11, 2026

Thanks for the review!

If I read correctly, as implemented there's not yet a way for users to usefully inherit from unknown sized types, that'll come in a follow up?

Yeah, I did not include anything that uses it in here to keep the diff as small as possible (which is also why the coverage is a bit low). In follow ups we can than start making use of the mechanism. I would probably look into exposing native type inheritance on abi3 next. Likely starting with exceptions, so maybe we can finally sunset create_exception and start recommending
#[pyclass(extends=PyException)]

@Icxolu Icxolu added the CI-skip-changelog Skip checking changelog entry label Jan 13, 2026
Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Looks great, thanks both!

Comment on lines 577 to 556
#[cfg(Py_3_12)]
impl<T: PyClassImpl> PyClassObjectBaseLayout<T> for PyVariableClassObject<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
Copy link
Member

Choose a reason for hiding this comment

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

I took a quick look at this and I couldn't see a way to deduplicate, I guess let's not block this PR on it, we can refactor later as we learn more.

@davidhewitt davidhewitt added this pull request to the merge queue Jan 13, 2026
Merged via the queue into PyO3:main with commit cc0e690 Jan 13, 2026
84 of 86 checks passed
@Icxolu Icxolu deleted the opaque-layout branch January 13, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI-build-full CI-no-fail-fast If one job fails, allow the rest to keep testing CI-skip-changelog Skip checking changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants