-
Notifications
You must be signed in to change notification settings - Fork 760
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
adding new getter for type obj #4197
Conversation
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.
Thanks, this looks great! Just one pair of changes WRT lifetimes and some docs suggestions...
src/types/typeobject.rs
Outdated
.assume_borrowed_or_err(self.py()) | ||
}? | ||
.to_owned() | ||
.downcast_into()?; |
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.
Is it necessary to do a safe downcast? As I understand, a tuple is guaranteed here from Python itself. That makes me think we can save the implicit type check and use downcast_into_unchecked
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.
We went back and forth at the sprints about this. It looks like it might be possible to guarantee it's ok:
$ python
Python 3.12.0 (main, Oct 8 2023, 21:31:51) [GCC 12.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> int.__mro__ = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot set '__mro__' attribute of immutable type 'int'
>>> class Foo:
... pass
...
>>> Foo.__mro__ = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute '__mro__' of 'type' objects is not writable
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.
Just for completeness, I checked the CPython source directly, and yeah, can confirm this must be a tuple. Both have PyTuple_CheckExact
guards on them:
static inline void
set_tp_bases(PyTypeObject *self, PyObject *bases)
{
assert(PyTuple_CheckExact(bases));
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
// XXX tp_bases can probably be statically allocated for each
// static builtin type.
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
assert(self->tp_bases == NULL);
if (PyTuple_GET_SIZE(bases) == 0) {
assert(self->tp_base == NULL);
}
else {
assert(PyTuple_GET_SIZE(bases) == 1);
assert(PyTuple_GET_ITEM(bases, 0) == (PyObject *)self->tp_base);
assert(self->tp_base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
assert(_Py_IsImmortal(self->tp_base));
}
_Py_SetImmortal(bases);
}
self->tp_bases = bases;
}
static inline void
set_tp_mro(PyTypeObject *self, PyObject *mro)
{
assert(PyTuple_CheckExact(mro));
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
// XXX tp_mro can probably be statically allocated for each
// static builtin type.
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
assert(self->tp_mro == NULL);
/* Other checks are done via set_tp_bases. */
_Py_SetImmortal(mro);
}
self->tp_mro = mro;
}
Edit: Correction, is indeed enforced as a tuple, but the real enforcement for the Python side is actually downstream from tp_setget
:
static int
type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, void *context)
{
// Check arguments
if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
return -1;
}
assert(new_bases != NULL);
if (!PyTuple_Check(new_bases)) {
PyErr_Format(PyExc_TypeError,
"can only assign tuple to %s.__bases__, not %s",
type->tp_name, Py_TYPE(new_bases)->tp_name);
return -1;
}
// ...
and __mro__
has no setter defined at all
{"__mro__", (getter)type_get_mro, NULL, NULL},
src/types/typeobject.rs
Outdated
.assume_borrowed_or_err(self.py()) | ||
}? | ||
.to_owned() | ||
.downcast_into()?; |
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.
Same as below
Got it, seems we will roll back to the unsafe way to do it, right? @davidhewitt I will update this PR soon. |
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
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.
This looks great to me, thanks!
Failure looks genuine:
... seems like some of the |
Thanks for implementing! |
@davidhewitt thanks! Will have a look at the unused imports |
src/types/typeobject.rs
Outdated
.getattr(intern!(self.py(), "__mro__")) | ||
.expect("Cannot get `__mro__` from object.") | ||
.extract() | ||
.expect("Cannot convert to Rust object."); |
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.
very minor: The expect message here is a little awkward IMO. There is no broad "Rust object" that we're trying to convert to, instead we're converting from the PyObject down into PyTuple.
My suggestion would be something like this instead
Unexpected type in
__mro__
attribute.
Adding some really small feedback on the |
Co-authored-by: Matt Hooks <me@matthooks.com>
Co-authored-by: Matt Hooks <me@matthooks.com>
Thank you @Hooksie |
Adding new getter that works like
__mro__
and__bases__
in Python.closes #4192