-
Notifications
You must be signed in to change notification settings - Fork 795
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
Renew #[pyproto] implementation for abi3 branch #1254
Conversation
e3af6ca
to
6627658
Compare
I only skimmed it, but the design makes sense to me. |
👍 looks like a good idea! I've started reviewing but need more time to finish reading. I have a very busy day today so tomorrow is more likely I'm afraid. |
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.
Sorry for the delay in reviewing. Looks good to me. I have a couple of suggestions.
I believe the new implementation is simpler but has one disadvantage: weak typing.
PyType_Slot casts function pointers to *mut c_void so the function type is eliminated, which can lead to bugs. We should carefully check the slot and pfunc of each PyType_Slot are correct.
How about adding an enum so that when we put the proto methods in the inventory we still have strong types? See my other comment.
Also, I think we should be able to remove the special case for the buffer methods and just use slots for them too.
pyo3-derive-backend/src/pyproto.rs
Outdated
tokens.push(quote! { let mut proto_methods = vec![]; }); | ||
for getter in proto.slot_getters(method_names) { | ||
let get = syn::Ident::new(getter, Span::call_site()); | ||
tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); | ||
} |
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.
How about this form, which is easier to read imo:
tokens.push(quote! { let mut proto_methods = vec![]; }); | |
for getter in proto.slot_getters(method_names) { | |
let get = syn::Ident::new(getter, Span::call_site()); | |
tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); | |
} | |
let getters = proto.slot_getters(method_names).map(|getter| { | |
let get = syn::Ident::new(getter, Span::call_site()); | |
quote!(<#ty as #ext_trait>::#get()) | |
}); | |
tokens.push(quote! { let proto_methods = vec![ $(#getters),* ]; }); |
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.
(and similar for the buffer method tokens just above, but I think that we can probably remove those completely.)
} | ||
fn basic_methods() -> Option<NonNull<PyObjectMethods>> { | ||
None | ||
fn get_type_slots() -> Vec<ffi::PyType_Slot> { |
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.
Instead of storing ffi::PyType_Slot
in this inventory, what about storing an enum:
pub enum PyProtoMethodDef {
#[cfg(not(Py_LIMITED_API))],
GetBuffer(ffi::getbufferproc),
#[cfg(not(Py_LIMITED_API))],
SetBuffer(ffi::setbufferproc),
SetItem(ffi::ssizeobjargproc),
DelItem(ffi::ssizeobjargproc),
}
This would allow us to keep typing inside the inventory, and avoid the special case for the buffer methods in the macros.
We could do the conversion to ffi::PyType_Slot
inside get_type_slots()
.
Also this would work very nicely with the buffer, where slot definitions do exist as long as it is not limited API. https://github.com/python/cpython/blob/cb115e36e1aba04b90b0ecac6f043e60064ac65b/Include/typeslots.h#L7-L8
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.
Also this would work very nicely with the buffer, where slot definitions do exist as long as it is not limited API. https://github.com/python/cpython/blob/cb115e36e1aba04b90b0ecac6f043e60064ac65b/Include/typeslots.h#L7-L8
Nice catch, thanks!
if let Some(buffer) = T::get_buffer() { | ||
unsafe { | ||
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.as_ref().bf_getbuffer; | ||
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.as_ref().bf_releasebuffer; | ||
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer; | ||
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer; | ||
} | ||
} |
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.
I think we can remove this special case now, because buffer methods do have slot definitions (when not limited abi). See my other comment.
Thanks for the review!
This sounds the same as the current approach, and if so, it does not address the code complexity issue. |
Oh, I just recalled that type slots does not work for buffer... (#1132 (comment)) |
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.
Oh, I just recalled that type slots does not work for buffer... (#1132 (comment))
👍 let's leave it as-is for this PR. I think it might work for 3.9 - I'll experiment in a follow-up PR sometime.
This sounds the same as the current approach, and if so, it does not address the code complexity issue.
I think what we need is adding some assertion before we cast a function pointer to *mut c_void. I'd rather try to implement this using a macro and ask your review again.
Fair point. See my new suggestion in comments below, which I think can reduce LOC quite a bit.
pyo3-derive-backend/src/pyproto.rs
Outdated
tokens.push(quote! { let mut proto_methods = vec![]; }); | ||
for getter in proto.slot_getters(method_names) { | ||
let get = syn::Ident::new(getter, Span::call_site()); | ||
tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); |
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.
Maybe we can do the creation of PyType_Slot
objects and conversion to *mut void
here?
Then the slot getters can return well-typed function pointers.
tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); | |
tokens.push(quote! { proto_methods.push(pyo3::ffi::PyType_Slot { | |
slot: #slot, | |
pfunc: <#ty as #ext_trait>::#get() as _ | |
}); }); |
We would need to move the #slot
definitions into the proc macro. I think that could be a good idea anyway to help keep the definitions in one place.
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.
Sounds nice 👍 !
src/class/basic.rs
Outdated
// Set functions used by `#[pyproto]`. | ||
pub fn set_str<T>(&mut self) | ||
pub trait PyBasicSlots { | ||
fn get_str() -> ffi::PyType_Slot |
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.
If you take my suggestion above then this should just return the typed function pointer:
fn get_str() -> ffi::PyType_Slot | |
fn get_str() -> ffi::reprfunc { | |
py_unary_func!(PyObjectStrProtocol, Self::__str__) | |
} |
And same for all other methods. I think this'll reduce the LOC quite a lot!
Chaned to use I also tried to |
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.
Nice, LGTM!
So what's the problem?
Currently, on the master,
#[pyproto]
is implemented via 'table initialization'.I.e.,
#[pyproto]
generates basically such a function:I designed this since many protocols required such kinds of tables before.
However, when we are to use ABI3, most protocols are initialized via
PyType_Slot
and tables are unnecessary.So I decided to renew the whole design, using
Vec<PyType_Slot>
instead of tables.I believe the new implementation is simpler but has one disadvantage: weak typing.
PyType_Slot
casts function pointers to*mut c_void
so the function type is eliminated, which can lead to bugs. We should carefully check theslot
andpfunc
of eachPyType_Slot
are correct.Now
#[pyproto]
generates (roughly):