1506: fixes to macro hygiene

This commit is contained in:
David Hewitt 2021-03-28 09:33:56 +01:00
parent 24b00004c6
commit ce851ad7d9
3 changed files with 137 additions and 56 deletions

View File

@ -225,17 +225,18 @@ fn function_c_wrapper(
}; };
slf_module = None; slf_module = None;
}; };
let body = impl_arg_params(spec, None, cb)?; let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, None, cb, &py)?;
Ok(quote! { Ok(quote! {
unsafe extern "C" fn #wrapper_ident( unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject, _slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{ {
pyo3::callback::handle_panic(|_py| { pyo3::callback::handle_panic(|#py| {
#slf_module #slf_module
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body #body
}) })

View File

@ -94,17 +94,18 @@ pub fn impl_wrap_cfunction_with_keywords(
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let body = impl_call(cls, &spec); let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls); let slf = self_ty.receiver(cls);
let body = impl_arg_params(&spec, Some(cls), body)?; let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(&spec, Some(cls), body, &py)?;
Ok(quote! {{ Ok(quote! {{
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{ {
pyo3::callback::handle_panic(|_py| { pyo3::callback::handle_panic(|#py| {
#slf #slf
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body #body
}) })
@ -138,7 +139,8 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream>
let name = &spec.name; let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(#(#names),*) }; let cb = quote! { #cls::#name(#(#names),*) };
let body = impl_arg_params(spec, Some(cls), cb)?; let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -149,12 +151,12 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream>
{ {
use pyo3::callback::IntoPyCallbackOutput; use pyo3::callback::IntoPyCallbackOutput;
pyo3::callback::handle_panic(|_py| { pyo3::callback::handle_panic(|#py| {
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
let initializer: pyo3::PyClassInitializer::<#cls> = #body.convert(_py)?; let initializer: pyo3::PyClassInitializer::<#cls> = #body.convert(#py)?;
let cell = initializer.create_cell_from_subtype(_py, subtype)?; let cell = initializer.create_cell_from_subtype(#py, subtype)?;
Ok(cell as *mut pyo3::ffi::PyObject) Ok(cell as *mut pyo3::ffi::PyObject)
}) })
} }
@ -167,8 +169,8 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream
let name = &spec.name; let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) }; let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb)?; let body = impl_arg_params(spec, Some(cls), cb, &py)?;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -177,10 +179,10 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream
_args: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{ {
pyo3::callback::handle_panic(|_py| { pyo3::callback::handle_panic(|#py| {
let _cls = pyo3::types::PyType::from_type_ptr(_py, _cls as *mut pyo3::ffi::PyTypeObject); let _cls = pyo3::types::PyType::from_type_ptr(#py, _cls as *mut pyo3::ffi::PyTypeObject);
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body #body
}) })
@ -194,8 +196,8 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStrea
let name = &spec.name; let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) }; let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb)?; let body = impl_arg_params(spec, Some(cls), cb, &py)?;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -204,9 +206,9 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStrea
_args: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{ {
pyo3::callback::handle_panic(|_py| { pyo3::callback::handle_panic(|#py| {
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body #body
}) })
@ -349,6 +351,7 @@ pub fn impl_arg_params(
spec: &FnSpec<'_>, spec: &FnSpec<'_>,
self_: Option<&syn::Type>, self_: Option<&syn::Type>,
body: TokenStream, body: TokenStream,
py: &syn::Ident,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
if spec.args.is_empty() { if spec.args.is_empty() {
return Ok(body); return Ok(body);
@ -382,11 +385,20 @@ pub fn impl_arg_params(
} }
let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
let args_array = syn::Ident::new("output", Span::call_site());
let mut param_conversion = Vec::new(); let mut param_conversion = Vec::new();
let mut option_pos = 0; let mut option_pos = 0;
for (idx, arg) in spec.args.iter().enumerate() { for (idx, arg) in spec.args.iter().enumerate() {
param_conversion.push(impl_arg_param(&arg, &spec, idx, self_, &mut option_pos)?); param_conversion.push(impl_arg_param(
&arg,
&spec,
idx,
self_,
&mut option_pos,
py,
&args_array,
)?);
} }
let (mut accept_args, mut accept_kwargs) = (false, false); let (mut accept_args, mut accept_kwargs) = (false, false);
@ -422,8 +434,8 @@ pub fn impl_arg_params(
accept_varkeywords: #accept_kwargs, accept_varkeywords: #accept_kwargs,
}; };
let mut output = [None; #num_params]; let mut #args_array = [None; #num_params];
let (_args, _kwargs) = DESCRIPTION.extract_arguments(_args, _kwargs, &mut output)?; let (_args, _kwargs) = DESCRIPTION.extract_arguments(_args, _kwargs, &mut #args_array)?;
#(#param_conversion)* #(#param_conversion)*
@ -440,20 +452,25 @@ fn impl_arg_param(
idx: usize, idx: usize,
self_: Option<&syn::Type>, self_: Option<&syn::Type>,
option_pos: &mut usize, option_pos: &mut usize,
py: &syn::Ident,
args_array: &syn::Ident,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
// Use this macro inside this function, to ensure that all code generated here is associated
// with the function argument
macro_rules! quote_arg_span {
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
}
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site()); let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
if arg.py { if arg.py {
return Ok(quote_spanned! { return Ok(quote_arg_span! { let #arg_name = #py; });
arg.ty.span() =>
let #arg_name = _py;
});
} }
let ty = arg.ty; let ty = arg.ty;
let name = arg.name; let name = arg.name;
let transform_error = quote! { let transform_error = quote_arg_span! {
|e| pyo3::derive_utils::argument_extraction_error(_py, stringify!(#name), e) |e| pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
}; };
if spec.is_args(&name) { if spec.is_args(&name) {
@ -461,38 +478,37 @@ fn impl_arg_param(
arg.optional.is_none(), arg.optional.is_none(),
arg.name.span() => "args cannot be optional" arg.name.span() => "args cannot be optional"
); );
return Ok(quote_spanned! { return Ok(quote_arg_span! {
arg.ty.span() => let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
let #arg_name = _args.unwrap().extract()
.map_err(#transform_error)?;
}); });
} else if spec.is_kwargs(&name) { } else if spec.is_kwargs(&name) {
ensure_spanned!( ensure_spanned!(
arg.optional.is_some(), arg.optional.is_some(),
arg.name.span() => "kwargs must be Option<_>" arg.name.span() => "kwargs must be Option<_>"
); );
return Ok(quote_spanned! { return Ok(quote_arg_span! {
arg.ty.span() =>
let #arg_name = _kwargs.map(|kwargs| kwargs.extract()) let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
.transpose() .transpose()
.map_err(#transform_error)?; .map_err(#transform_error)?;
}); });
} }
let arg_value = quote!(output[#option_pos]); let arg_value = quote_arg_span!(#args_array[#option_pos]);
*option_pos += 1; *option_pos += 1;
let default = match (spec.default_value(name), arg.optional.is_some()) { let default = match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => quote! { Some(#default) }, (Some(default), true) if default.to_string() != "None" => {
(Some(default), _) => quote! { #default }, quote_arg_span! { Some(#default) }
(None, true) => quote! { None }, }
(None, false) => quote! { panic!("Failed to extract required method argument") }, (Some(default), _) => quote_arg_span! { #default },
(None, true) => quote_arg_span! { None },
(None, false) => quote_arg_span! { panic!("Failed to extract required method argument") },
}; };
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with { let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
quote! {#expr_path(_obj).map_err(#transform_error)?} quote_arg_span! { #expr_path(_obj).map_err(#transform_error)?}
} else { } else {
quote! {_obj.extract().map_err(#transform_error)?} quote_arg_span! { _obj.extract().map_err(#transform_error)?}
}; };
return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) { return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) {
@ -500,23 +516,22 @@ fn impl_arg_param(
let (target_ty, borrow_tmp) = if arg.optional.is_some() { let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>> // Get Option<&T> from Option<PyRef<T>>
( (
quote! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> }, quote_arg_span! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> },
if mut_.is_some() { if mut_.is_some() {
quote! { _tmp.as_deref_mut() } quote_arg_span! { _tmp.as_deref_mut() }
} else { } else {
quote! { _tmp.as_deref() } quote_arg_span! { _tmp.as_deref() }
}, },
) )
} else { } else {
// Get &T from PyRef<T> // Get &T from PyRef<T>
( (
quote! { <#tref as pyo3::derive_utils::ExtractExt>::Target }, quote_arg_span! { <#tref as pyo3::derive_utils::ExtractExt>::Target },
quote! { &#mut_ *_tmp }, quote_arg_span! { &#mut_ *_tmp },
) )
}; };
Ok(quote_spanned! { Ok(quote_arg_span! {
arg.ty.span() =>
let #mut_ _tmp: #target_ty = match #arg_value { let #mut_ _tmp: #target_ty = match #arg_value {
Some(_obj) => #extract, Some(_obj) => #extract,
None => #default, None => #default,
@ -524,8 +539,7 @@ fn impl_arg_param(
let #arg_name = #borrow_tmp; let #arg_name = #borrow_tmp;
}) })
} else { } else {
Ok(quote_spanned! { Ok(quote_arg_span! {
arg.ty.span() =>
let #arg_name = match #arg_value { let #arg_name = match #arg_value {
Some(_obj) => #extract, Some(_obj) => #extract,
None => #default, None => #default,

View File

@ -753,3 +753,69 @@ issue_1505!(
fn issue_1505(&self, _py: Python<'_>) {} fn issue_1505(&self, _py: Python<'_>) {}
} }
); );
// Regression test for issue 1506 - incorrect macro hygiene.
// By applying the `#[pymethods]` attribute inside a macro_rules! macro, this separates the macro
// call scope from the scope of the impl block. For this to work our macros must be careful to not
// cheat hygeine!
#[pyclass]
struct Issue1506 {}
macro_rules! issue_1506 {
(#[pymethods] $($body:tt)*) => {
#[pymethods]
$($body)*
};
}
issue_1506!(
#[pymethods]
impl Issue1506 {
fn issue_1506(
&self,
_py: Python<'_>,
_arg: &PyAny,
_args: &PyTuple,
_kwargs: Option<&PyDict>,
) {
}
#[new]
fn issue_1506_new(
_py: Python<'_>,
_arg: &PyAny,
_args: &PyTuple,
_kwargs: Option<&PyDict>,
) -> Self {
Issue1506 {}
}
#[getter("foo")]
fn issue_1506_getter(&self, _py: Python<'_>) -> i32 {
5
}
#[setter("foo")]
fn issue_1506_setter(&self, _py: Python<'_>, _value: i32) {}
#[staticmethod]
fn issue_1506_static(
_py: Python<'_>,
_arg: &PyAny,
_args: &PyTuple,
_kwargs: Option<&PyDict>,
) {
}
#[classmethod]
fn issue_1506_class(
_cls: &PyType,
_py: Python<'_>,
_arg: &PyAny,
_args: &PyTuple,
_kwargs: Option<&PyDict>,
) {
}
}
);