forked from Rust-related/RustPython
Make PyMethodDef construction const (#5117)
* Make PyMethodDef construction const * Remove iter_chain![] Obsolete since arrays now impl IntoIterator
This commit is contained in:
@@ -4,7 +4,7 @@ use crate::util::{
|
||||
ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta, ItemMeta,
|
||||
ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str::FromStr;
|
||||
@@ -172,14 +172,9 @@ pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<Token
|
||||
let slots_impl = context.extend_slots_items.validate()?;
|
||||
let class_extensions = &context.class_extensions;
|
||||
|
||||
let extra_methods = iter_chain![
|
||||
let extra_methods = [
|
||||
parse_quote! {
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn __extend_method_def(
|
||||
method_defs: &mut Vec<::rustpython_vm::function::PyMethodDef>,
|
||||
) {
|
||||
#method_def
|
||||
}
|
||||
const __OWN_METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_def;
|
||||
},
|
||||
parse_quote! {
|
||||
fn __extend_py_class(
|
||||
@@ -201,6 +196,15 @@ pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<Token
|
||||
imp.items.extend(extra_methods);
|
||||
let is_main_impl = impl_ty == payload_ty;
|
||||
if is_main_impl {
|
||||
let method_defs = if with_method_defs.is_empty() {
|
||||
quote!(#impl_ty::__OWN_METHOD_DEFS)
|
||||
} else {
|
||||
quote!(
|
||||
rustpython_vm::function::PyMethodDef::__const_concat_arrays::<
|
||||
{ #impl_ty::__OWN_METHOD_DEFS.len() #(+ #with_method_defs.len())* },
|
||||
>(&[#impl_ty::__OWN_METHOD_DEFS, #(#with_method_defs,)*])
|
||||
)
|
||||
};
|
||||
quote! {
|
||||
#imp
|
||||
impl ::rustpython_vm::class::PyClassImpl for #payload_ty {
|
||||
@@ -214,13 +218,7 @@ pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<Token
|
||||
#with_impl
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn impl_extend_method_def(
|
||||
method_defs: &mut Vec<::rustpython_vm::function::PyMethodDef>,
|
||||
) {
|
||||
#impl_ty::__extend_method_def(method_defs);
|
||||
#with_method_defs
|
||||
}
|
||||
const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs;
|
||||
|
||||
fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
|
||||
#impl_ty::__extend_slots(slots);
|
||||
@@ -268,14 +266,9 @@ pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<Token
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let extra_methods = iter_chain![
|
||||
let extra_methods = [
|
||||
parse_quote! {
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn __extend_method_def(
|
||||
method_defs: &mut Vec<::rustpython_vm::function::PyMethodDef>,
|
||||
) {
|
||||
#method_def
|
||||
}
|
||||
const __OWN_METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_def;
|
||||
},
|
||||
parse_quote! {
|
||||
fn __extend_py_class(
|
||||
@@ -983,6 +976,7 @@ impl MethodNursery {
|
||||
|
||||
impl ToTokens for MethodNursery {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut inner_tokens = TokenStream::new();
|
||||
for item in &self.items {
|
||||
let py_name = &item.py_name;
|
||||
let ident = &item.ident;
|
||||
@@ -1011,16 +1005,18 @@ impl ToTokens for MethodNursery {
|
||||
// } else {
|
||||
// quote_spanned! { ident.span() => #py_name }
|
||||
// };
|
||||
tokens.extend(quote! {
|
||||
inner_tokens.extend(quote! [
|
||||
#(#cfgs)*
|
||||
method_defs.push(rustpython_vm::function::PyMethodDef {
|
||||
name: #py_name,
|
||||
func: rustpython_vm::function::IntoPyNativeFn::into_func(Self::#ident),
|
||||
flags: #flags,
|
||||
doc: #doc,
|
||||
});
|
||||
});
|
||||
rustpython_vm::function::PyMethodDef::new_const(
|
||||
#py_name,
|
||||
Self::#ident,
|
||||
#flags,
|
||||
#doc,
|
||||
),
|
||||
]);
|
||||
}
|
||||
let array: TokenTree = Group::new(Delimiter::Bracket, inner_tokens).into();
|
||||
tokens.extend([array]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1436,7 +1432,7 @@ struct ExtractedImplAttrs {
|
||||
payload: Option<Ident>,
|
||||
flags: TokenStream,
|
||||
with_impl: TokenStream,
|
||||
with_method_defs: TokenStream,
|
||||
with_method_defs: Vec<TokenStream>,
|
||||
with_slots: TokenStream,
|
||||
}
|
||||
|
||||
@@ -1465,18 +1461,18 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
|
||||
let NestedMeta::Meta(Meta::Path(path)) = meta else {
|
||||
bail_span!(meta, "#[pyclass(with(...))] arguments should be paths")
|
||||
};
|
||||
let (extend_class, extend_method_def, extend_slots) =
|
||||
let (extend_class, method_defs, extend_slots) =
|
||||
if path.is_ident("PyRef") || path.is_ident("Py") {
|
||||
// special handling for PyRef
|
||||
(
|
||||
quote!(#path::<Self>::__extend_py_class),
|
||||
quote!(#path::<Self>::__extend_method_def),
|
||||
quote!(#path::<Self>::__OWN_METHOD_DEFS),
|
||||
quote!(#path::<Self>::__extend_slots),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote!(<Self as #path>::__extend_py_class),
|
||||
quote!(<Self as #path>::__extend_method_def),
|
||||
quote!(<Self as #path>::__OWN_METHOD_DEFS),
|
||||
quote!(<Self as #path>::__extend_slots),
|
||||
)
|
||||
};
|
||||
@@ -1484,9 +1480,7 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
|
||||
withs.push(quote_spanned! { path.span() =>
|
||||
#extend_class(ctx, class);
|
||||
});
|
||||
with_method_defs.push(quote_spanned! { path.span() =>
|
||||
#extend_method_def(method_defs);
|
||||
});
|
||||
with_method_defs.push(method_defs);
|
||||
with_slots.push(quote_spanned! { item_span =>
|
||||
#extend_slots(slots);
|
||||
});
|
||||
@@ -1530,9 +1524,7 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
|
||||
with_impl: quote! {
|
||||
#(#withs)*
|
||||
},
|
||||
with_method_defs: quote! {
|
||||
#(#with_method_defs)*
|
||||
},
|
||||
with_method_defs,
|
||||
with_slots: quote! {
|
||||
#(#with_slots)*
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::util::{
|
||||
AttributeExt, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery,
|
||||
ModuleItemMeta, SimpleItemMeta, ALL_ALLOWED_NAMES,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use syn::{parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Result};
|
||||
@@ -110,7 +110,7 @@ pub fn impl_pymodule(attr: AttributeArgs, module_item: Item) -> Result<TokenStre
|
||||
let is_submodule = module_meta.sub()?;
|
||||
let withs = module_meta.with()?;
|
||||
if !is_submodule {
|
||||
items.extend(iter_chain![
|
||||
items.extend([
|
||||
parse_quote! {
|
||||
pub(crate) const MODULE_NAME: &'static str = #module_name;
|
||||
},
|
||||
@@ -122,16 +122,10 @@ pub fn impl_pymodule(attr: AttributeArgs, module_item: Item) -> Result<TokenStre
|
||||
ctx: &::rustpython_vm::Context,
|
||||
) -> &'static ::rustpython_vm::builtins::PyModuleDef {
|
||||
DEF.get_or_init(|| {
|
||||
#[allow(clippy::ptr_arg)]
|
||||
let method_defs = {
|
||||
let mut method_defs = Vec::new();
|
||||
extend_method_def(ctx, &mut method_defs);
|
||||
method_defs
|
||||
};
|
||||
let mut def = ::rustpython_vm::builtins::PyModuleDef {
|
||||
name: ctx.intern_str(MODULE_NAME),
|
||||
doc: DOC.map(|doc| ctx.intern_str(doc)),
|
||||
methods: Box::leak(method_defs.into_boxed_slice()),
|
||||
methods: METHOD_DEFS,
|
||||
slots: Default::default(),
|
||||
};
|
||||
def.slots.exec = Some(extend_module);
|
||||
@@ -161,23 +155,24 @@ pub fn impl_pymodule(attr: AttributeArgs, module_item: Item) -> Result<TokenStre
|
||||
}
|
||||
});
|
||||
}
|
||||
items.extend(iter_chain![
|
||||
let method_defs = if withs.is_empty() {
|
||||
quote!(#function_items)
|
||||
} else {
|
||||
quote!({
|
||||
const OWN_METHODS: &'static [::rustpython_vm::function::PyMethodDef] = &#function_items;
|
||||
rustpython_vm::function::PyMethodDef::__const_concat_arrays::<
|
||||
{ OWN_METHODS.len() #(+ super::#withs::METHOD_DEFS.len())* },
|
||||
>(&[#(super::#withs::METHOD_DEFS,)* OWN_METHODS])
|
||||
})
|
||||
};
|
||||
items.extend([
|
||||
parse_quote! {
|
||||
::rustpython_vm::common::static_cell! {
|
||||
pub(crate) static DEF: ::rustpython_vm::builtins::PyModuleDef;
|
||||
}
|
||||
},
|
||||
parse_quote! {
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub(crate) fn extend_method_def(
|
||||
ctx: &::rustpython_vm::Context,
|
||||
method_defs: &mut Vec<::rustpython_vm::function::PyMethodDef>,
|
||||
) {
|
||||
#(
|
||||
super::#withs::extend_method_def(ctx, method_defs);
|
||||
)*
|
||||
#function_items
|
||||
}
|
||||
pub(crate) const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs;
|
||||
},
|
||||
parse_quote! {
|
||||
pub(crate) fn __init_attributes(
|
||||
@@ -361,26 +356,30 @@ struct ValidatedFunctionNursery(FunctionNursery);
|
||||
|
||||
impl ToTokens for ValidatedFunctionNursery {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut inner_tokens = TokenStream::new();
|
||||
let flags = quote! { rustpython_vm::function::PyMethodFlags::empty() };
|
||||
for item in &self.0.items {
|
||||
let ident = &item.ident;
|
||||
let cfgs = &item.cfgs;
|
||||
let cfgs = quote!(#(#cfgs)*);
|
||||
let py_names = &item.py_names;
|
||||
let doc = &item.doc;
|
||||
let flags = quote! { rustpython_vm::function::PyMethodFlags::empty() };
|
||||
let doc = quote!(Some(#doc));
|
||||
|
||||
tokens.extend(quote! {
|
||||
#(#cfgs)*
|
||||
{
|
||||
let doc = Some(#doc);
|
||||
#(method_defs.push(rustpython_vm::function::PyMethodDef::new(
|
||||
(#py_names),
|
||||
inner_tokens.extend(quote![
|
||||
#(
|
||||
#cfgs
|
||||
rustpython_vm::function::PyMethodDef::new_const(
|
||||
#py_names,
|
||||
#ident,
|
||||
#flags,
|
||||
doc,
|
||||
));)*
|
||||
}
|
||||
});
|
||||
#doc,
|
||||
),
|
||||
)*
|
||||
]);
|
||||
}
|
||||
let array: TokenTree = Group::new(Delimiter::Bracket, inner_tokens).into();
|
||||
tokens.extend([array]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -639,13 +639,6 @@ impl ErrorVec for Vec<syn::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! iter_chain {
|
||||
($($it:expr),*$(,)?) => {
|
||||
::std::iter::empty()
|
||||
$(.chain(::std::iter::once($it)))*
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn iter_use_idents<'a, F, R: 'a>(item_use: &'a syn::ItemUse, mut f: F) -> Result<Vec<R>>
|
||||
where
|
||||
F: FnMut(&'a syn::Ident, bool) -> Result<R>,
|
||||
|
||||
@@ -135,19 +135,16 @@ pub trait PyClassImpl: PyClassDef {
|
||||
}
|
||||
|
||||
fn impl_extend_class(ctx: &Context, class: &'static Py<PyType>);
|
||||
fn impl_extend_method_def(method_defs: &mut Vec<PyMethodDef>);
|
||||
const METHOD_DEFS: &'static [PyMethodDef];
|
||||
fn extend_slots(slots: &mut PyTypeSlots);
|
||||
|
||||
fn make_slots() -> PyTypeSlots {
|
||||
let mut method_defs = Vec::new();
|
||||
Self::impl_extend_method_def(&mut method_defs);
|
||||
|
||||
let mut slots = PyTypeSlots {
|
||||
flags: Self::TP_FLAGS,
|
||||
name: Self::TP_NAME,
|
||||
basicsize: Self::BASICSIZE,
|
||||
doc: Self::DOC,
|
||||
methods: Box::leak(method_defs.into_boxed_slice()),
|
||||
methods: Self::METHOD_DEFS,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ pub type PyNativeFn = py_dyn_fn!(dyn Fn(&VirtualMachine, FuncArgs) -> PyResult);
|
||||
/// `fn foo<F, FKind>(f: F) where F: IntoPyNativeFn<FKind>`
|
||||
pub trait IntoPyNativeFn<Kind>: Sized + PyThreadingConstraint + 'static {
|
||||
fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
|
||||
|
||||
/// `IntoPyNativeFn::into_func()` generates a PyNativeFn that performs the
|
||||
/// appropriate type and arity checking, any requested conversions, and then if
|
||||
/// successful calls the function with the extracted parameters.
|
||||
@@ -37,6 +38,36 @@ pub trait IntoPyNativeFn<Kind>: Sized + PyThreadingConstraint + 'static {
|
||||
let boxed = Box::new(move |vm: &VirtualMachine, args| self.call(vm, args));
|
||||
Box::leak(boxed)
|
||||
}
|
||||
|
||||
/// Equivalent to `into_func()`, but accessible as a constant. This is only
|
||||
/// valid if this function is zero-sized, i.e. that
|
||||
/// `std::mem::size_of::<F>() == 0`. If it isn't, use of this constant will
|
||||
/// raise a compile error.
|
||||
const STATIC_FUNC: &'static PyNativeFn = {
|
||||
if std::mem::size_of::<Self>() == 0 {
|
||||
&|vm, args| {
|
||||
// SAFETY: we just confirmed that Self is zero-sized, so there
|
||||
// aren't any bytes in it that could be uninit.
|
||||
#[allow(clippy::uninit_assumed_init)]
|
||||
let f = unsafe { std::mem::MaybeUninit::<Self>::uninit().assume_init() };
|
||||
f.call(vm, args)
|
||||
}
|
||||
} else {
|
||||
panic!("function must be zero-sized to access STATIC_FUNC")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the [`STATIC_FUNC`](IntoPyNativeFn::STATIC_FUNC) of the passed function. The same
|
||||
/// requirements of zero-sizedness apply, see that documentation for details.
|
||||
#[inline(always)]
|
||||
pub const fn static_func<Kind, F: IntoPyNativeFn<Kind>>(f: F) -> &'static PyNativeFn {
|
||||
// if f is zero-sized, there's no issue forgetting it - even if a capture of f does have a Drop
|
||||
// impl, it would never get called anyway. If you passed it to into_func, it would just get
|
||||
// Box::leak'd, and as a 'static reference it'll never be dropped. and if f isn't zero-sized,
|
||||
// we'll never reach this point anyway because we'll fail to compile.
|
||||
std::mem::forget(f);
|
||||
F::STATIC_FUNC
|
||||
}
|
||||
|
||||
// TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type
|
||||
@@ -186,5 +217,6 @@ mod tests {
|
||||
check_zst(py_func.into_func());
|
||||
let empty_closure = || "foo".to_owned();
|
||||
check_zst(empty_closure.into_func());
|
||||
check_zst(static_func(empty_closure));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,22 @@ impl PyMethodDef {
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn new_const<Kind>(
|
||||
name: &'static str,
|
||||
func: impl IntoPyNativeFn<Kind>,
|
||||
flags: PyMethodFlags,
|
||||
doc: Option<&'static str>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
func: super::static_func(func),
|
||||
flags,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proper_method(
|
||||
&'static self,
|
||||
class: &'static Py<PyType>,
|
||||
@@ -192,6 +208,41 @@ impl PyMethodDef {
|
||||
let func = self.to_function();
|
||||
PyNativeMethod { func, class }.into_ref(ctx)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const fn __const_concat_arrays<const SUM_LEN: usize>(
|
||||
method_groups: &[&[Self]],
|
||||
) -> [Self; SUM_LEN] {
|
||||
const NULL_METHOD: PyMethodDef = PyMethodDef {
|
||||
name: "",
|
||||
func: &|_, _| unreachable!(),
|
||||
flags: PyMethodFlags::empty(),
|
||||
doc: None,
|
||||
};
|
||||
let mut all_methods = [NULL_METHOD; SUM_LEN];
|
||||
let mut all_idx = 0;
|
||||
let mut group_idx = 0;
|
||||
while group_idx < method_groups.len() {
|
||||
let group = method_groups[group_idx];
|
||||
let mut method_idx = 0;
|
||||
while method_idx < group.len() {
|
||||
all_methods[all_idx] = group[method_idx].const_copy();
|
||||
method_idx += 1;
|
||||
all_idx += 1;
|
||||
}
|
||||
group_idx += 1;
|
||||
}
|
||||
all_methods
|
||||
}
|
||||
|
||||
const fn const_copy(&self) -> Self {
|
||||
Self {
|
||||
name: self.name,
|
||||
func: self.func,
|
||||
flags: self.flags,
|
||||
doc: self.doc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PyMethodDef {
|
||||
|
||||
@@ -15,7 +15,7 @@ pub use argument::{
|
||||
};
|
||||
pub use arithmetic::{PyArithmeticValue, PyComparisonValue};
|
||||
pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike};
|
||||
pub use builtin::{IntoPyNativeFn, PyNativeFn};
|
||||
pub use builtin::{static_func, IntoPyNativeFn, PyNativeFn};
|
||||
pub use either::Either;
|
||||
pub use fspath::FsPath;
|
||||
pub use getset::PySetterValue;
|
||||
|
||||
Reference in New Issue
Block a user