macros-backend: improve error handling ergonomics
This commit is contained in:
parent
364b7c2214
commit
88872eba4f
|
@ -1,5 +1,5 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, MetaList, Result};
|
||||
|
@ -17,13 +17,11 @@ impl<'a> Enum<'a> {
|
|||
/// `data_enum` is the `syn` representation of the input enum, `ident` is the
|
||||
/// `Identifier` of the enum.
|
||||
fn new(data_enum: &'a DataEnum, ident: &'a Ident) -> Result<Self> {
|
||||
if data_enum.variants.is_empty() {
|
||||
return Err(spanned_err(
|
||||
&ident,
|
||||
"Cannot derive FromPyObject for empty enum.",
|
||||
));
|
||||
}
|
||||
let vars = data_enum
|
||||
ensure_spanned!(
|
||||
!data_enum.variants.is_empty(),
|
||||
ident.span() => "cannot derive FromPyObject for empty enum"
|
||||
);
|
||||
let variants = data_enum
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
|
@ -40,7 +38,7 @@ impl<'a> Enum<'a> {
|
|||
|
||||
Ok(Enum {
|
||||
enum_ident: ident,
|
||||
variants: vars,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -123,12 +121,10 @@ impl<'a> Container<'a> {
|
|||
attrs: Vec<ContainerAttribute>,
|
||||
is_enum_variant: bool,
|
||||
) -> Result<Self> {
|
||||
if fields.is_empty() {
|
||||
return Err(spanned_err(
|
||||
fields,
|
||||
"Cannot derive FromPyObject for empty structs and variants.",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
!fields.is_empty(),
|
||||
fields.span() => "cannot derive FromPyObject for empty structs and variants"
|
||||
);
|
||||
let transparent = attrs
|
||||
.iter()
|
||||
.any(|attr| *attr == ContainerAttribute::Transparent);
|
||||
|
@ -166,17 +162,11 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
ContainerType::Struct(fields)
|
||||
}
|
||||
(Fields::Unit, _) => {
|
||||
// covered by length check above
|
||||
return Err(spanned_err(
|
||||
&fields,
|
||||
"Cannot derive FromPyObject for Unit structs and variants",
|
||||
));
|
||||
}
|
||||
(Fields::Unit, _) => unreachable!(), // covered by length check above
|
||||
};
|
||||
let err_name = attrs
|
||||
.iter()
|
||||
.find_map(|a| a.annotation())
|
||||
.find_map(|a| a.annotation().map(syn::LitStr::value))
|
||||
.unwrap_or_else(|| path.segments.last().unwrap().ident.to_string());
|
||||
|
||||
let v = Container {
|
||||
|
@ -188,31 +178,13 @@ impl<'a> Container<'a> {
|
|||
Ok(v)
|
||||
}
|
||||
|
||||
fn verify_struct_container_attrs(
|
||||
attrs: &'a [ContainerAttribute],
|
||||
original: &[Attribute],
|
||||
) -> Result<()> {
|
||||
fn verify_struct_container_attrs(attrs: &'a [ContainerAttribute]) -> Result<()> {
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
ContainerAttribute::Transparent => continue,
|
||||
ContainerAttribute::ErrorAnnotation(_) => {
|
||||
let span = original
|
||||
.iter()
|
||||
.map(|a| a.span())
|
||||
.fold(None, |mut acc: Option<Span>, span| {
|
||||
if let Some(all) = acc.as_mut() {
|
||||
all.join(span)
|
||||
} else {
|
||||
Some(span)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(Span::call_site);
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"Annotating error messages for structs is \
|
||||
not supported. Remove the annotation attribute.",
|
||||
));
|
||||
}
|
||||
ContainerAttribute::Transparent => {}
|
||||
ContainerAttribute::ErrorAnnotation(annotation) => bail_spanned!(
|
||||
annotation.span() => "annotation is not supported for structs"
|
||||
),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -247,7 +219,7 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
let msg = if self.is_enum_variant {
|
||||
quote!(format!(
|
||||
"Expected tuple of length {}, but got length {}.",
|
||||
"expected tuple of length {}, but got length {}",
|
||||
#len,
|
||||
s.len()
|
||||
))
|
||||
|
@ -279,12 +251,10 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
|
||||
fn check_transparent_len(fields: &Fields) -> Result<()> {
|
||||
if fields.len() != 1 {
|
||||
return Err(spanned_err(
|
||||
fields,
|
||||
"Transparent structs and variants can only have 1 field",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
fields.len() == 1,
|
||||
fields.span() => "transparent structs and variants can only have 1 field"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -295,14 +265,14 @@ enum ContainerAttribute {
|
|||
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
|
||||
Transparent,
|
||||
/// Change the name of an enum variant in the generated error message.
|
||||
ErrorAnnotation(String),
|
||||
ErrorAnnotation(syn::LitStr),
|
||||
}
|
||||
|
||||
impl ContainerAttribute {
|
||||
/// Convenience method to access `ErrorAnnotation`.
|
||||
fn annotation(&self) -> Option<String> {
|
||||
fn annotation(&self) -> Option<&syn::LitStr> {
|
||||
match self {
|
||||
ContainerAttribute::ErrorAnnotation(s) => Some(s.to_string()),
|
||||
ContainerAttribute::ErrorAnnotation(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -311,30 +281,29 @@ impl ContainerAttribute {
|
|||
///
|
||||
/// Fails if any are invalid.
|
||||
fn parse_attrs(value: &[Attribute]) -> Result<Vec<Self>> {
|
||||
let mut attrs = Vec::new();
|
||||
let list = get_pyo3_meta_list(value)?;
|
||||
for meta in list.nested {
|
||||
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
||||
match metaitem {
|
||||
Meta::Path(p) if p.is_ident("transparent") => {
|
||||
attrs.push(ContainerAttribute::Transparent);
|
||||
continue;
|
||||
}
|
||||
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
|
||||
if let syn::Lit::Str(s) = &nv.lit {
|
||||
attrs.push(ContainerAttribute::ErrorAnnotation(s.value()))
|
||||
} else {
|
||||
return Err(spanned_err(&nv.lit, "Expected string literal."));
|
||||
get_pyo3_meta_list(value)?
|
||||
.nested
|
||||
.into_iter()
|
||||
.map(|meta| {
|
||||
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
||||
match metaitem {
|
||||
Meta::Path(p) if p.is_ident("transparent") => {
|
||||
return Ok(ContainerAttribute::Transparent);
|
||||
}
|
||||
continue;
|
||||
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
|
||||
if let syn::Lit::Str(s) = &nv.lit {
|
||||
return Ok(ContainerAttribute::ErrorAnnotation(s.clone()));
|
||||
} else {
|
||||
bail_spanned!(nv.lit.span() => "expected string literal for annotation");
|
||||
}
|
||||
}
|
||||
_ => {} // return Err below
|
||||
}
|
||||
_ => {} // return Err below
|
||||
}
|
||||
}
|
||||
|
||||
return Err(spanned_err(meta, "Unrecognized `pyo3` container attribute"));
|
||||
}
|
||||
Ok(attrs)
|
||||
bail_spanned!(meta.span() => "unknown `pyo3` container attribute");
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,22 +323,17 @@ impl FieldAttribute {
|
|||
let metaitem = match list.nested.len() {
|
||||
0 => return Ok(None),
|
||||
1 => list.nested.into_iter().next().unwrap(),
|
||||
_ => {
|
||||
return Err(spanned_err(
|
||||
list.nested,
|
||||
"Only one of `item`, `attribute` can be provided, possibly with an \
|
||||
additional argument: `item(\"key\")` or `attribute(\"name\").",
|
||||
))
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
list.nested.span() =>
|
||||
"only one of `attribute` or `item` can be provided"
|
||||
),
|
||||
};
|
||||
let meta = match metaitem {
|
||||
syn::NestedMeta::Meta(meta) => meta,
|
||||
syn::NestedMeta::Lit(lit) => {
|
||||
return Err(spanned_err(
|
||||
lit,
|
||||
"Expected `attribute` or `item`, not a literal.",
|
||||
))
|
||||
}
|
||||
syn::NestedMeta::Lit(lit) => bail_spanned!(
|
||||
lit.span() =>
|
||||
"expected `attribute` or `item`, got a literal"
|
||||
),
|
||||
};
|
||||
let path = meta.path();
|
||||
if path.is_ident("attribute") {
|
||||
|
@ -377,60 +341,54 @@ impl FieldAttribute {
|
|||
} else if path.is_ident("item") {
|
||||
Ok(Some(FieldAttribute::GetItem(Self::item_arg(meta)?)))
|
||||
} else {
|
||||
Err(spanned_err(meta, "Expected `attribute` or `item`."))
|
||||
bail_spanned!(meta.span() => "expected `attribute` or `item`");
|
||||
}
|
||||
}
|
||||
|
||||
fn attribute_arg(meta: Meta) -> syn::Result<Option<syn::LitStr>> {
|
||||
let arg_list = match meta {
|
||||
let mut arg_list = match meta {
|
||||
Meta::List(list) => list,
|
||||
Meta::Path(_) => return Ok(None),
|
||||
Meta::NameValue(nv) => {
|
||||
let err_msg = "Expected a string literal or no argument: `pyo3(attribute(\"name\") or `pyo3(attribute)`";
|
||||
return Err(spanned_err(nv, err_msg));
|
||||
}
|
||||
Meta::NameValue(nv) => bail_spanned!(
|
||||
nv.span() =>
|
||||
"expected a string literal or no argument: `pyo3(attribute(\"name\")` or \
|
||||
`pyo3(attribute)`"
|
||||
),
|
||||
};
|
||||
let arg_msg = "Expected a single string literal argument.";
|
||||
let first = match arg_list.nested.len() {
|
||||
1 => arg_list.nested.first().unwrap(),
|
||||
_ => return Err(spanned_err(arg_list, arg_msg)),
|
||||
};
|
||||
if let syn::NestedMeta::Lit(syn::Lit::Str(litstr)) = first {
|
||||
if litstr.value().is_empty() {
|
||||
return Err(spanned_err(litstr, "Attribute name cannot be empty."));
|
||||
|
||||
if arg_list.nested.len() == 1 {
|
||||
let arg = arg_list.nested.pop().unwrap().into_value();
|
||||
|
||||
if let syn::NestedMeta::Lit(syn::Lit::Str(litstr)) = arg {
|
||||
ensure_spanned!(
|
||||
!litstr.value().is_empty(),
|
||||
litstr.span() => "attribute name cannot be empty"
|
||||
);
|
||||
return Ok(Some(litstr));
|
||||
}
|
||||
return Ok(Some(parse_quote!(#litstr)));
|
||||
}
|
||||
Err(spanned_err(first, arg_msg))
|
||||
|
||||
bail_spanned!(arg_list.span() => "expected a single string literal argument");
|
||||
}
|
||||
|
||||
fn item_arg(meta: Meta) -> syn::Result<Option<syn::Lit>> {
|
||||
let arg_list = match meta {
|
||||
let mut arg_list = match meta {
|
||||
Meta::List(list) => list,
|
||||
Meta::Path(_) => return Ok(None),
|
||||
Meta::NameValue(nv) => {
|
||||
return Err(spanned_err(
|
||||
nv,
|
||||
"Expected a literal or no argument: `pyo3(item(\"key\") or `pyo3(item)`",
|
||||
))
|
||||
}
|
||||
Meta::NameValue(nv) => bail_spanned!(
|
||||
nv.span() => "expected a literal or no argument: `pyo3(item(key)` or `pyo3(item)`"
|
||||
),
|
||||
};
|
||||
let arg_msg = "Expected a single literal argument.";
|
||||
if arg_list.nested.is_empty() {
|
||||
return Err(spanned_err(arg_list, arg_msg));
|
||||
} else if arg_list.nested.len() > 1 {
|
||||
return Err(spanned_err(arg_list.nested, arg_msg));
|
||||
}
|
||||
let first = arg_list.nested.first().unwrap();
|
||||
if let syn::NestedMeta::Lit(lit) = first {
|
||||
return Ok(Some(parse_quote!(#lit)));
|
||||
}
|
||||
Err(spanned_err(first, arg_msg))
|
||||
}
|
||||
}
|
||||
|
||||
fn spanned_err<T: ToTokens>(tokens: T, msg: &str) -> syn::Error {
|
||||
syn::Error::new_spanned(tokens, msg)
|
||||
if arg_list.nested.len() == 1 {
|
||||
let arg = arg_list.nested.pop().unwrap().into_value();
|
||||
if let syn::NestedMeta::Lit(lit) = arg {
|
||||
return Ok(Some(lit));
|
||||
}
|
||||
}
|
||||
|
||||
bail_spanned!(arg_list.span() => "expected a single literal argument");
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract pyo3 metalist, flattens multiple lists into a single one.
|
||||
|
@ -456,12 +414,10 @@ fn get_pyo3_meta_list(attrs: &[Attribute]) -> Result<MetaList> {
|
|||
fn verify_and_get_lifetime(generics: &syn::Generics) -> Result<Option<&syn::LifetimeDef>> {
|
||||
let mut lifetimes = generics.lifetimes();
|
||||
let lifetime = lifetimes.next();
|
||||
if lifetimes.next().is_some() {
|
||||
return Err(spanned_err(
|
||||
&generics,
|
||||
"FromPyObject can be derived with at most one lifetime parameter.",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
lifetimes.next().is_none(),
|
||||
generics.span() => "FromPyObject can be derived with at most one lifetime parameter"
|
||||
);
|
||||
Ok(lifetime)
|
||||
}
|
||||
|
||||
|
@ -496,17 +452,14 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
}
|
||||
syn::Data::Struct(st) => {
|
||||
let attrs = ContainerAttribute::parse_attrs(&tokens.attrs)?;
|
||||
Container::verify_struct_container_attrs(&attrs, &tokens.attrs)?;
|
||||
Container::verify_struct_container_attrs(&attrs)?;
|
||||
let ident = &tokens.ident;
|
||||
let st = Container::new(&st.fields, parse_quote!(#ident), attrs, false)?;
|
||||
st.build()
|
||||
}
|
||||
syn::Data::Union(_) => {
|
||||
return Err(spanned_err(
|
||||
tokens,
|
||||
"#[derive(FromPyObject)] is not supported for unions.",
|
||||
))
|
||||
}
|
||||
syn::Data::Union(_) => bail_spanned!(
|
||||
tokens.span() => "#[derive(FromPyObject)] is not supported for unions"
|
||||
),
|
||||
};
|
||||
|
||||
let ident = &tokens.ident;
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
// Listed first so that macros in this module are available in the rest of the crate.
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod defs;
|
||||
mod from_pyobject;
|
||||
mod konst;
|
||||
|
@ -14,7 +18,6 @@ mod pyfunction;
|
|||
mod pyimpl;
|
||||
mod pymethod;
|
||||
mod pyproto;
|
||||
mod utils;
|
||||
|
||||
pub use from_pyobject::build_derive_from_pyobject;
|
||||
pub use module::{add_fn_to_module, process_functions_in_module, py_init};
|
||||
|
|
|
@ -133,7 +133,7 @@ impl<'a> FnSpec<'a> {
|
|||
let mut parse_receiver = |msg: &'static str| {
|
||||
inputs_iter
|
||||
.next()
|
||||
.ok_or_else(|| syn::Error::new_spanned(sig, msg))
|
||||
.ok_or_else(|| err_spanned!(sig.span() => msg))
|
||||
.and_then(parse_method_receiver)
|
||||
};
|
||||
|
||||
|
@ -149,12 +149,10 @@ impl<'a> FnSpec<'a> {
|
|||
let fn_type = match fn_type_attr {
|
||||
Some(MethodTypeAttribute::StaticMethod) => FnType::FnStatic,
|
||||
Some(MethodTypeAttribute::ClassAttribute) => {
|
||||
if !sig.inputs.is_empty() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
"Class attribute methods cannot take arguments",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
sig.inputs.is_empty(),
|
||||
sig.inputs.span() => "class attribute methods cannot take arguments"
|
||||
);
|
||||
FnType::ClassAttribute
|
||||
}
|
||||
Some(MethodTypeAttribute::New) => FnType::FnNew,
|
||||
|
@ -183,7 +181,7 @@ impl<'a> FnSpec<'a> {
|
|||
FnType::Setter(parse_receiver("expected receiver for #[setter]")?)
|
||||
}
|
||||
None => FnType::Fn(parse_receiver(
|
||||
"Static method needs #[staticmethod] attribute",
|
||||
"static method needs #[staticmethod] attribute",
|
||||
)?),
|
||||
};
|
||||
|
||||
|
@ -191,10 +189,7 @@ impl<'a> FnSpec<'a> {
|
|||
for input in inputs_iter {
|
||||
match input {
|
||||
syn::FnArg::Receiver(recv) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
recv,
|
||||
"Unexpected receiver for method",
|
||||
));
|
||||
bail_spanned!(recv.span() => "unexpected receiver for method")
|
||||
}
|
||||
syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => {
|
||||
let (ident, by_ref, mutability) = match &**pat {
|
||||
|
@ -204,9 +199,7 @@ impl<'a> FnSpec<'a> {
|
|||
mutability,
|
||||
..
|
||||
}) => (ident, by_ref, mutability),
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(pat, "unsupported argument"));
|
||||
}
|
||||
_ => bail_spanned!(pat.span() => "unsupported argument"),
|
||||
};
|
||||
|
||||
arguments.push(FnArg {
|
||||
|
@ -229,7 +222,7 @@ impl<'a> FnSpec<'a> {
|
|||
if let Some(text_signature) =
|
||||
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
|
||||
{
|
||||
Err(syn::Error::new_spanned(text_signature, error_msg))
|
||||
bail_spanned!(text_signature.span() => error_msg)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -244,7 +237,7 @@ impl<'a> FnSpec<'a> {
|
|||
__new__, put it on the struct definition instead",
|
||||
)?,
|
||||
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
|
||||
parse_erroneous_text_signature("text_signature not allowed with this attribute")?
|
||||
parse_erroneous_text_signature("text_signature not allowed with this method type")?
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -326,25 +319,20 @@ fn parse_method_attributes(
|
|||
|
||||
macro_rules! set_ty {
|
||||
($new_ty:expr, $ident:expr) => {
|
||||
if ty.replace($new_ty).is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
$ident,
|
||||
"Cannot specify a second method type",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
ty.replace($new_ty).is_none(),
|
||||
$ident.span() => "cannot specify a second method type"
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
for attr in attrs.iter() {
|
||||
for attr in attrs.drain(..) {
|
||||
match attr.parse_meta()? {
|
||||
syn::Meta::Path(name) => {
|
||||
if name.is_ident("new") || name.is_ident("__new__") {
|
||||
set_ty!(MethodTypeAttribute::New, name);
|
||||
} else if name.is_ident("init") || name.is_ident("__init__") {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
"#[init] is disabled since PyO3 0.9.0",
|
||||
));
|
||||
bail_spanned!(name.span() => "#[init] is disabled since PyO3 0.9.0");
|
||||
} else if name.is_ident("call") || name.is_ident("__call__") {
|
||||
set_ty!(MethodTypeAttribute::Call, name);
|
||||
} else if name.is_ident("classmethod") {
|
||||
|
@ -355,10 +343,9 @@ fn parse_method_attributes(
|
|||
set_ty!(MethodTypeAttribute::ClassAttribute, name);
|
||||
} else if name.is_ident("setter") || name.is_ident("getter") {
|
||||
if let syn::AttrStyle::Inner(_) = attr.style {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"Inner style attribute is not supported for setter and getter",
|
||||
));
|
||||
bail_spanned!(
|
||||
attr.span() => "inner attribute is not supported for setter and getter"
|
||||
);
|
||||
}
|
||||
if name.is_ident("setter") {
|
||||
set_ty!(MethodTypeAttribute::Setter, name);
|
||||
|
@ -366,32 +353,28 @@ fn parse_method_attributes(
|
|||
set_ty!(MethodTypeAttribute::Getter, name);
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr.clone())
|
||||
new_attrs.push(attr)
|
||||
}
|
||||
}
|
||||
syn::Meta::List(syn::MetaList { path, nested, .. }) => {
|
||||
syn::Meta::List(syn::MetaList {
|
||||
path, mut nested, ..
|
||||
}) => {
|
||||
if path.is_ident("new") {
|
||||
set_ty!(MethodTypeAttribute::New, path);
|
||||
} else if path.is_ident("init") {
|
||||
return Err(syn::Error::new_spanned(
|
||||
path,
|
||||
"#[init] is disabled since PyO3 0.9.0",
|
||||
));
|
||||
bail_spanned!(path.span() => "#[init] is disabled since PyO3 0.9.0");
|
||||
} else if path.is_ident("call") {
|
||||
set_ty!(MethodTypeAttribute::Call, path);
|
||||
} else if path.is_ident("setter") || path.is_ident("getter") {
|
||||
if let syn::AttrStyle::Inner(_) = attr.style {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"Inner style attribute is not supported for setter and getter",
|
||||
));
|
||||
}
|
||||
if nested.len() != 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"setter/getter requires one value",
|
||||
));
|
||||
bail_spanned!(
|
||||
attr.span() => "inner attribute is not supported for setter and getter"
|
||||
);
|
||||
}
|
||||
ensure_spanned!(
|
||||
nested.len() == 1,
|
||||
attr.span() => "setter/getter requires one value"
|
||||
);
|
||||
|
||||
if path.is_ident("setter") {
|
||||
set_ty!(MethodTypeAttribute::Setter, path);
|
||||
|
@ -399,7 +382,7 @@ fn parse_method_attributes(
|
|||
set_ty!(MethodTypeAttribute::Getter, path);
|
||||
};
|
||||
|
||||
property_name = match nested.first().unwrap() {
|
||||
property_name = match nested.pop().unwrap().into_value() {
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(w)) if w.segments.len() == 1 => {
|
||||
Some(w.segments[0].ident.clone())
|
||||
}
|
||||
|
@ -423,15 +406,14 @@ fn parse_method_attributes(
|
|||
let attrs = PyFunctionAttr::from_meta(&nested)?;
|
||||
args.extend(attrs.arguments)
|
||||
} else {
|
||||
new_attrs.push(attr.clone())
|
||||
new_attrs.push(attr)
|
||||
}
|
||||
}
|
||||
syn::Meta::NameValue(_) => new_attrs.push(attr.clone()),
|
||||
syn::Meta::NameValue(_) => new_attrs.push(attr),
|
||||
}
|
||||
}
|
||||
|
||||
attrs.clear();
|
||||
attrs.extend(new_attrs);
|
||||
*attrs = new_attrs;
|
||||
|
||||
let python_name = if allow_custom_name {
|
||||
parse_method_name_attribute(ty.as_ref(), attrs, property_name)?
|
||||
|
@ -456,14 +438,8 @@ fn parse_method_name_attribute(
|
|||
|
||||
// Reject some invalid combinations
|
||||
if let (Some(name), Some(ty)) = (&name, ty) {
|
||||
match ty {
|
||||
New | Call | Getter | Setter => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
"name not allowed with this method type",
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
if let New | Call | Getter | Setter = ty {
|
||||
bail_spanned!(name.span() => "name not allowed with this method type");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::pymethod::get_arg_names;
|
|||
use crate::utils;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::Ident;
|
||||
use syn::{spanned::Spanned, Ident};
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
|
@ -61,7 +61,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
|||
fn wrap_fn_argument(cap: &syn::PatType) -> syn::Result<method::FnArg> {
|
||||
let (mutability, by_ref, ident) = match &*cap.pat {
|
||||
syn::Pat::Ident(patid) => (&patid.mutability, &patid.by_ref, &patid.ident),
|
||||
_ => return Err(syn::Error::new_spanned(&cap.pat, "Unsupported argument")),
|
||||
_ => bail_spanned!(cap.pat.span() => "unsupported argument"),
|
||||
};
|
||||
|
||||
Ok(method::FnArg {
|
||||
|
@ -83,7 +83,7 @@ fn extract_pyfn_attrs(
|
|||
let mut modname = None;
|
||||
let mut fn_attrs = PyFunctionAttr::default();
|
||||
|
||||
for attr in attrs.iter() {
|
||||
for attr in attrs.drain(..) {
|
||||
match attr.parse_meta() {
|
||||
Ok(syn::Meta::List(list)) if list.path.is_ident("pyfn") => {
|
||||
let meta: Vec<_> = list.nested.iter().cloned().collect();
|
||||
|
@ -93,37 +93,30 @@ fn extract_pyfn_attrs(
|
|||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
modname = Some(path.clone())
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&meta[0],
|
||||
"The first parameter of pyfn must be a MetaItem",
|
||||
))
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
meta[0].span() => "the first parameter of pyfn must be a MetaItem"
|
||||
),
|
||||
}
|
||||
// read Python function name
|
||||
match &meta[1] {
|
||||
syn::NestedMeta::Lit(syn::Lit::Str(lits)) => {
|
||||
fnname = Some(syn::Ident::new(&lits.value(), lits.span()));
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&meta[1],
|
||||
"The second parameter of pyfn must be a Literal",
|
||||
))
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
meta[1].span() => "the second parameter of pyfn must be a Literal"
|
||||
),
|
||||
}
|
||||
// Read additional arguments
|
||||
if list.nested.len() >= 3 {
|
||||
fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])?;
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
format!("can not parse 'pyfn' params {:?}", attr),
|
||||
));
|
||||
bail_spanned!(
|
||||
attr.span() => format!("can not parse 'pyfn' params {:?}", attr)
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => new_attrs.push(attr.clone()),
|
||||
_ => new_attrs.push(attr),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,10 +145,7 @@ pub fn add_fn_to_module(
|
|||
for (i, input) in func.sig.inputs.iter().enumerate() {
|
||||
match input {
|
||||
syn::FnArg::Receiver(_) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
input,
|
||||
"Unexpected receiver for #[pyfn]",
|
||||
))
|
||||
bail_spanned!(input.span() => "unexpected receiver for #[pyfn]");
|
||||
}
|
||||
syn::FnArg::Typed(cap) => {
|
||||
if pyfn_attrs.pass_module && i == 0 {
|
||||
|
@ -172,10 +162,9 @@ pub fn add_fn_to_module(
|
|||
}
|
||||
}
|
||||
}
|
||||
return Err(syn::Error::new_spanned(
|
||||
cap,
|
||||
"Expected &PyModule as first argument with `pass_module`.",
|
||||
));
|
||||
bail_spanned!(
|
||||
cap.span() => "expected &PyModule as first argument with `pass_module`"
|
||||
);
|
||||
} else {
|
||||
arguments.push(wrap_fn_argument(cap)?);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::Token;
|
||||
use syn::{spanned::Spanned, Token};
|
||||
|
||||
// TODO:
|
||||
// Add lifetime support for args with Rptr
|
||||
|
@ -109,12 +109,7 @@ fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Result<syn::Type> {
|
|||
syn::Type::Path(ref ty) => get_option_ty(&ty.path).unwrap_or_else(|| *cap.ty.clone()),
|
||||
_ => *cap.ty.clone(),
|
||||
},
|
||||
ty => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
ty,
|
||||
format!("Unsupported argument type: {:?}", ty),
|
||||
))
|
||||
}
|
||||
ty => bail_spanned!(ty.span() => format!("unsupported argument type: {:?}", ty)),
|
||||
};
|
||||
insert_lifetime(&mut ty);
|
||||
Ok(ty)
|
||||
|
@ -166,7 +161,7 @@ fn modify_arg_ty(
|
|||
syn::FnArg::Typed(ref cap) => {
|
||||
sig.inputs[idx] = fix_name(&cap.pat, &decl1)?;
|
||||
}
|
||||
_ => return Err(syn::Error::new_spanned(arg, "not supported")),
|
||||
_ => bail_spanned!(arg.span() => "not supported"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -190,6 +185,6 @@ fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::Result<syn::FnArg> {
|
|||
ty: cap.ty.clone(),
|
||||
}))
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(arg, "Expected a typed argument"))
|
||||
Err(err_spanned!(arg.span() => "expected a typed argument"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use quote::quote;
|
|||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, Expr, Token};
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, Token};
|
||||
|
||||
/// The parsed arguments of the pyclass macro
|
||||
pub struct PyClassArgs {
|
||||
|
@ -58,7 +58,7 @@ impl PyClassArgs {
|
|||
match expr {
|
||||
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => self.add_path(exp),
|
||||
syn::Expr::Assign(assign) => self.add_assign(assign),
|
||||
_ => Err(syn::Error::new_spanned(expr, "Failed to parse arguments")),
|
||||
_ => bail_spanned!(expr.span() => "failed to parse arguments"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,20 +69,15 @@ impl PyClassArgs {
|
|||
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
|
||||
exp.path.segments.first().unwrap().ident.to_string()
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(assign, "Failed to parse arguments"));
|
||||
}
|
||||
_ => bail_spanned!(assign.span() => "failed to parse arguments"),
|
||||
};
|
||||
|
||||
macro_rules! expected {
|
||||
($expected: literal) => {
|
||||
expected!($expected, right)
|
||||
expected!($expected, right.span())
|
||||
};
|
||||
($expected: literal, $span: ident) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
$span,
|
||||
concat!("Expected ", $expected),
|
||||
));
|
||||
($expected: literal, $span: expr) => {
|
||||
bail_spanned!($span => concat!("expected ", $expected));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -97,23 +92,18 @@ impl PyClassArgs {
|
|||
..
|
||||
}) => {
|
||||
self.name = Some(lit.parse().map_err(|_| {
|
||||
syn::Error::new_spanned(
|
||||
lit,
|
||||
"expected a single identifier in double-quotes",
|
||||
)
|
||||
err_spanned!(
|
||||
lit.span() => "expected a single identifier in double-quotes")
|
||||
})?);
|
||||
}
|
||||
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
exp,
|
||||
format!(
|
||||
concat!(
|
||||
"since PyO3 0.13 a pyclass name should be in double-quotes, ",
|
||||
"e.g. \"{}\""
|
||||
),
|
||||
bail_spanned!(
|
||||
exp.span() => format!(
|
||||
"since PyO3 0.13 a pyclass name should be in double-quotes, \
|
||||
e.g. \"{}\"",
|
||||
exp.path.get_ident().expect("path has 1 segment")
|
||||
),
|
||||
));
|
||||
)
|
||||
);
|
||||
}
|
||||
_ => expected!("type name (e.g. \"Name\")"),
|
||||
},
|
||||
|
@ -136,7 +126,7 @@ impl PyClassArgs {
|
|||
}
|
||||
_ => expected!(r#"string literal (e.g., "my_mod")"#),
|
||||
},
|
||||
_ => expected!("one of freelist/name/extends/module", left),
|
||||
_ => expected!("one of freelist/name/extends/module", left.span()),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
@ -156,12 +146,9 @@ impl PyClassArgs {
|
|||
"unsendable" => {
|
||||
self.has_unsendable = true;
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&exp.path,
|
||||
"Expected one of gc/weakref/subclass/dict/unsendable",
|
||||
))
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
exp.path.span() => "expected one of gc/weakref/subclass/dict/unsendable"
|
||||
),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
@ -175,7 +162,11 @@ pub fn build_py_class(class: &mut syn::ItemStruct, attr: &PyClassArgs) -> syn::R
|
|||
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
|
||||
let mut descriptors = Vec::new();
|
||||
|
||||
check_generics(class)?;
|
||||
ensure_spanned!(
|
||||
class.generics.params.is_empty(),
|
||||
class.generics.span() => "#[pyclass] cannot have generic parameters"
|
||||
);
|
||||
|
||||
if let syn::Fields::Named(fields) = &mut class.fields {
|
||||
for field in fields.named.iter_mut() {
|
||||
let field_descs = parse_descriptors(field)?;
|
||||
|
@ -184,10 +175,7 @@ pub fn build_py_class(class: &mut syn::ItemStruct, attr: &PyClassArgs) -> syn::R
|
|||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&class.fields,
|
||||
"#[pyclass] can only be used with C-style structs",
|
||||
));
|
||||
bail_spanned!(class.fields.span() => "#[pyclass] can only be used with C-style structs");
|
||||
}
|
||||
|
||||
impl_class(&class.ident, &attr, doc, descriptors)
|
||||
|
@ -197,7 +185,7 @@ pub fn build_py_class(class: &mut syn::ItemStruct, attr: &PyClassArgs) -> syn::R
|
|||
fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
|
||||
let mut descs = Vec::new();
|
||||
let mut new_attrs = Vec::new();
|
||||
for attr in item.attrs.iter() {
|
||||
for attr in item.attrs.drain(..) {
|
||||
if let Ok(syn::Meta::List(list)) = attr.parse_meta() {
|
||||
if list.path.is_ident("pyo3") {
|
||||
for meta in list.nested.iter() {
|
||||
|
@ -207,22 +195,18 @@ fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
|
|||
} else if metaitem.path().is_ident("set") {
|
||||
descs.push(FnType::Setter(SelfType::Receiver { mutable: true }));
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
metaitem,
|
||||
"Only get and set are supported",
|
||||
));
|
||||
bail_spanned!(metaitem.span() => "only get and set are supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr.clone())
|
||||
new_attrs.push(attr)
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr.clone());
|
||||
new_attrs.push(attr);
|
||||
}
|
||||
}
|
||||
item.attrs.clear();
|
||||
item.attrs.extend(new_attrs);
|
||||
item.attrs = new_attrs;
|
||||
Ok(descs)
|
||||
}
|
||||
|
||||
|
@ -516,14 +500,3 @@ fn impl_descriptors(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn check_generics(class: &mut syn::ItemStruct) -> syn::Result<()> {
|
||||
if class.generics.params.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
&class.generics,
|
||||
"#[pyclass] cannot have generic parameters",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,12 +56,9 @@ impl PyFunctionAttr {
|
|||
NestedMeta::Lit(lit) => {
|
||||
self.add_literal(item, lit)?;
|
||||
}
|
||||
NestedMeta::Meta(syn::Meta::List(list)) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
list,
|
||||
"List is not supported as argument",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(syn::Meta::List(list)) => bail_spanned!(
|
||||
list.span() => "list is not supported as argument"
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -75,20 +72,15 @@ impl PyFunctionAttr {
|
|||
self.arguments.push(Argument::VarArgsSeparator);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(syn::Error::new_spanned(
|
||||
item,
|
||||
format!("Only \"*\" is supported here, got: {:?}", lit),
|
||||
)),
|
||||
_ => bail_spanned!(item.span() => "expected \"*\""),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_work(&mut self, item: &NestedMeta, path: &Path) -> syn::Result<()> {
|
||||
if self.has_kw || self.has_kwargs {
|
||||
return Err(syn::Error::new_spanned(
|
||||
item,
|
||||
"Positional argument or varargs(*) is not allowed after keyword arguments",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
!(self.has_kw || self.has_kwargs),
|
||||
item.span() => "positional argument or varargs(*) not allowed after keyword arguments"
|
||||
);
|
||||
if self.has_varargs {
|
||||
self.arguments.push(Argument::Kwarg(path.clone(), None));
|
||||
} else {
|
||||
|
@ -98,22 +90,18 @@ impl PyFunctionAttr {
|
|||
}
|
||||
|
||||
fn vararg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> {
|
||||
if self.has_kwargs || self.has_varargs {
|
||||
return Err(syn::Error::new_spanned(
|
||||
item,
|
||||
"* is not allowed after varargs(*) or kwargs(**)",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
!(self.has_kwargs || self.has_varargs),
|
||||
item.span() => "* is not allowed after varargs(*) or kwargs(**)"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn kw_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> {
|
||||
if self.has_kwargs {
|
||||
return Err(syn::Error::new_spanned(
|
||||
item,
|
||||
"Keyword argument or kwargs(**) is not allowed after kwargs(**)",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
!self.has_kwargs,
|
||||
item.span() => "keyword argument or kwargs(**) is not allowed after kwargs(**)"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -159,12 +147,7 @@ impl PyFunctionAttr {
|
|||
syn::Lit::Bool(litb) => {
|
||||
self.add_nv_common(item, &nv.path, format!("{}", litb.value))?;
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.lit.clone(),
|
||||
"Only string literal is supported",
|
||||
));
|
||||
}
|
||||
_ => bail_spanned!(nv.lit.span() => "expected a string literal"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
@ -190,14 +173,10 @@ pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Opti
|
|||
ident.set_span(*span);
|
||||
Ok(Some(ident))
|
||||
}
|
||||
[(_, span)] => Err(syn::Error::new(
|
||||
*span,
|
||||
"Expected string literal for #[name] argument",
|
||||
)),
|
||||
[_first_attr, second_attr, ..] => Err(syn::Error::new(
|
||||
second_attr.1,
|
||||
"#[name] can not be specified multiple times",
|
||||
)),
|
||||
[(_, span)] => bail_spanned!(*span => "expected string literal for #[name] argument"),
|
||||
[_first_attr, second_attr, ..] => bail_spanned!(
|
||||
second_attr.1 => "#[name] can not be specified multiple times"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,18 +3,16 @@
|
|||
use crate::pymethod;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
pub fn build_py_methods(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
|
||||
if let Some((_, path, _)) = &ast.trait_ {
|
||||
Err(syn::Error::new_spanned(
|
||||
path,
|
||||
"#[pymethods] cannot be used on trait impl blocks",
|
||||
))
|
||||
bail_spanned!(path.span() => "#[pymethods] cannot be used on trait impl blocks");
|
||||
} else if ast.generics != Default::default() {
|
||||
Err(syn::Error::new_spanned(
|
||||
ast.generics.clone(),
|
||||
"#[pymethods] cannot be used with lifetime parameters or generics",
|
||||
))
|
||||
bail_spanned!(
|
||||
ast.generics.span() =>
|
||||
"#[pymethods] cannot be used with lifetime parameters or generics"
|
||||
);
|
||||
} else {
|
||||
impl_methods(&ast.self_ty, &mut ast.items)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::method::{FnArg, FnSpec, FnType, SelfType};
|
|||
use crate::utils;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::{ext::IdentExt, spanned::Spanned};
|
||||
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor(&'a syn::Field),
|
||||
|
@ -44,16 +44,12 @@ pub fn gen_py_method(
|
|||
}
|
||||
|
||||
fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
||||
let err_msg = |typ| format!("A Python method can't have a generic {} parameter", typ);
|
||||
let err_msg = |typ| format!("a Python method can't have a generic {} parameter", typ);
|
||||
for param in &sig.generics.params {
|
||||
match param {
|
||||
syn::GenericParam::Lifetime(_) => {}
|
||||
syn::GenericParam::Type(_) => {
|
||||
return Err(syn::Error::new_spanned(param, err_msg("type")));
|
||||
}
|
||||
syn::GenericParam::Const(_) => {
|
||||
return Err(syn::Error::new_spanned(param, err_msg("const")));
|
||||
}
|
||||
syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")),
|
||||
syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -263,12 +259,10 @@ pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStr
|
|||
|
||||
fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
|
||||
let (py_arg, args) = split_off_python_arg(&spec.args);
|
||||
if !args.is_empty() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
args[0].ty,
|
||||
"Getter function can only have one argument of type pyo3::Python",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
args.is_empty(),
|
||||
args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)"
|
||||
);
|
||||
|
||||
let name = &spec.name;
|
||||
let fncall = if py_arg.is_some() {
|
||||
|
@ -317,15 +311,12 @@ fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
|
|||
let (py_arg, args) = split_off_python_arg(&spec.args);
|
||||
|
||||
if args.is_empty() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&spec.name,
|
||||
"Setter function expected to have one argument",
|
||||
));
|
||||
bail_spanned!(spec.name.span() => "setter function expected to have one argument");
|
||||
} else if args.len() > 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&args[1].ty,
|
||||
"Setter function can have at most two arguments: one of pyo3::Python, and one other",
|
||||
));
|
||||
bail_spanned!(
|
||||
args[1].ty.span() =>
|
||||
"setter function can have at most two arguments ([pyo3::Python,] and value)"
|
||||
);
|
||||
}
|
||||
|
||||
let name = &spec.name;
|
||||
|
|
|
@ -8,50 +8,38 @@ use proc_macro2::{Span, TokenStream};
|
|||
use quote::quote;
|
||||
use quote::ToTokens;
|
||||
use std::collections::HashSet;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
|
||||
if let Some((_, path, _)) = &mut ast.trait_ {
|
||||
let proto = if let Some(segment) = path.segments.last() {
|
||||
match segment.ident.to_string().as_str() {
|
||||
"PyObjectProtocol" => &defs::OBJECT,
|
||||
"PyAsyncProtocol" => &defs::ASYNC,
|
||||
"PyMappingProtocol" => &defs::MAPPING,
|
||||
"PyIterProtocol" => &defs::ITER,
|
||||
"PyContextProtocol" => &defs::CONTEXT,
|
||||
"PySequenceProtocol" => &defs::SEQ,
|
||||
"PyNumberProtocol" => &defs::NUM,
|
||||
"PyDescrProtocol" => &defs::DESCR,
|
||||
"PyBufferProtocol" => &defs::BUFFER,
|
||||
"PyGCProtocol" => &defs::GC,
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
path,
|
||||
"#[pyproto] can not be used with this block",
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
path,
|
||||
"#[pyproto] can only be used with protocol trait implementations",
|
||||
));
|
||||
let (path, proto) = if let Some((_, path, _)) = &mut ast.trait_ {
|
||||
let proto = match path.segments.last() {
|
||||
Some(segment) if segment.ident == "PyObjectProtocol" => &defs::OBJECT,
|
||||
Some(segment) if segment.ident == "PyAsyncProtocol" => &defs::ASYNC,
|
||||
Some(segment) if segment.ident == "PyMappingProtocol" => &defs::MAPPING,
|
||||
Some(segment) if segment.ident == "PyIterProtocol" => &defs::ITER,
|
||||
Some(segment) if segment.ident == "PyContextProtocol" => &defs::CONTEXT,
|
||||
Some(segment) if segment.ident == "PySequenceProtocol" => &defs::SEQ,
|
||||
Some(segment) if segment.ident == "PyNumberProtocol" => &defs::NUM,
|
||||
Some(segment) if segment.ident == "PyDescrProtocol" => &defs::DESCR,
|
||||
Some(segment) if segment.ident == "PyBufferProtocol" => &defs::BUFFER,
|
||||
Some(segment) if segment.ident == "PyGCProtocol" => &defs::GC,
|
||||
_ => bail_spanned!(path.span() => "unrecognised trait for #[pyproto]"),
|
||||
};
|
||||
|
||||
let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto)?;
|
||||
|
||||
// attach lifetime
|
||||
let mut seg = path.segments.pop().unwrap().into_value();
|
||||
seg.arguments = syn::PathArguments::AngleBracketed(syn::parse_quote! {<'p>});
|
||||
path.segments.push(seg);
|
||||
ast.generics.params = syn::parse_quote! {'p};
|
||||
|
||||
Ok(tokens)
|
||||
(path, proto)
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
ast,
|
||||
"#[pyproto] can only be used with protocol trait implementations",
|
||||
))
|
||||
}
|
||||
bail_spanned!(
|
||||
ast.span() => "#[pyproto] can only be used with protocol trait implementations"
|
||||
);
|
||||
};
|
||||
|
||||
let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto)?;
|
||||
|
||||
// attach lifetime
|
||||
let mut seg = path.segments.pop().unwrap().into_value();
|
||||
seg.arguments = syn::PathArguments::AngleBracketed(syn::parse_quote! {<'p>});
|
||||
path.segments.push(seg);
|
||||
ast.generics.params = syn::parse_quote! {'p};
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn impl_proto_impl(
|
||||
|
@ -80,10 +68,9 @@ fn impl_proto_impl(
|
|||
let method = if let FnType::Fn(self_ty) = &fn_spec.tp {
|
||||
pymethod::impl_proto_wrap(ty, &fn_spec, &self_ty)
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&met.sig,
|
||||
"Expected method with receiver for #[pyproto] method",
|
||||
));
|
||||
bail_spanned!(
|
||||
met.sig.span() => "expected method with receiver for #[pyproto] method"
|
||||
);
|
||||
};
|
||||
|
||||
let coexist = if m.can_coexist {
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use proc_macro2::Span;
|
||||
use std::fmt::Display;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
||||
macro_rules! err_spanned {
|
||||
($span:expr => $msg:expr) => {
|
||||
syn::Error::new($span, $msg)
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro inspired by `anyhow::bail!` to return a compiler error with the given span.
|
||||
macro_rules! bail_spanned {
|
||||
($span:expr => $msg:expr) => {
|
||||
return Err(err_spanned!($span => $msg));
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro inspired by `anyhow::ensure!` to return a compiler error with the given span if the
|
||||
/// specified condition is not met.
|
||||
macro_rules! ensure_spanned {
|
||||
($condition:expr, $span:expr => $msg:expr) => {
|
||||
if !($condition) {
|
||||
bail_spanned!($span => $msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given type `ty` is `pyo3::Python`.
|
||||
pub fn is_python(ty: &syn::Type) -> bool {
|
||||
|
@ -32,9 +56,9 @@ pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
|
|||
attr.path.is_ident("text_signature")
|
||||
}
|
||||
|
||||
fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
|
||||
fn parse_text_signature_attr(
|
||||
attr: &syn::Attribute,
|
||||
python_name: &T,
|
||||
python_name: &syn::Ident,
|
||||
) -> syn::Result<Option<syn::LitStr>> {
|
||||
if !is_text_signature_attr(attr) {
|
||||
return Ok(None);
|
||||
|
@ -45,34 +69,25 @@ fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
|
|||
.next()
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.ok_or_else(|| {
|
||||
syn::Error::new_spanned(
|
||||
&python_name,
|
||||
format!("failed to parse python name: {}", python_name),
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| err_spanned!(python_name.span() => "failed to parse python name"))?;
|
||||
match attr.parse_meta()? {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}) => {
|
||||
let value = lit.value();
|
||||
if value.starts_with('(') && value.ends_with(')') {
|
||||
Ok(Some(syn::LitStr::new(
|
||||
&(python_name_str.to_owned() + &value),
|
||||
lit.span(),
|
||||
)))
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
lit,
|
||||
"text_signature must start with \"(\" and end with \")\"",
|
||||
))
|
||||
}
|
||||
ensure_spanned!(
|
||||
value.starts_with('(') && value.ends_with(')'),
|
||||
lit.span() => "text_signature must start with \"(\" and end with \")\""
|
||||
);
|
||||
Ok(Some(syn::LitStr::new(
|
||||
&(python_name_str.to_owned() + &value),
|
||||
lit.span(),
|
||||
)))
|
||||
}
|
||||
meta => Err(syn::Error::new_spanned(
|
||||
meta,
|
||||
"text_signature must be of the form #[text_signature = \"\"]",
|
||||
)),
|
||||
meta => bail_spanned!(
|
||||
meta.span() => "text_signature must be of the form #[text_signature = \"\"]"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,14 +99,11 @@ pub fn parse_text_signature_attrs(
|
|||
let mut attrs_out = Vec::with_capacity(attrs.len());
|
||||
for attr in attrs.drain(..) {
|
||||
if let Some(value) = parse_text_signature_attr(&attr, python_name)? {
|
||||
if text_signature.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"text_signature attribute already specified previously",
|
||||
));
|
||||
} else {
|
||||
text_signature = Some(value);
|
||||
}
|
||||
ensure_spanned!(
|
||||
text_signature.is_none(),
|
||||
attr.span() => "text_signature attribute already specified previously"
|
||||
);
|
||||
text_signature = Some(value);
|
||||
} else {
|
||||
attrs_out.push(attr);
|
||||
}
|
||||
|
@ -136,7 +148,7 @@ pub fn get_doc(
|
|||
};
|
||||
separator = "\n";
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(metanv, "Invalid doc comment"));
|
||||
bail_spanned!(metanv.span() => "invalid doc comment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromPyObject, attributes(pyo3, extract))]
|
||||
#[proc_macro_derive(FromPyObject, attributes(pyo3))]
|
||||
pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(item as syn::DeriveInput);
|
||||
let expanded = build_derive_from_pyobject(&ast).unwrap_or_else(|e| e.to_compile_error());
|
||||
|
|
|
@ -6,6 +6,7 @@ fn test_compile_errors() {
|
|||
t.compile_fail("tests/ui/invalid_need_module_arg_position.rs");
|
||||
t.compile_fail("tests/ui/invalid_property_args.rs");
|
||||
t.compile_fail("tests/ui/invalid_pyclass_args.rs");
|
||||
t.compile_fail("tests/ui/invalid_pymethods.rs");
|
||||
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
|
||||
t.compile_fail("tests/ui/reject_generics.rs");
|
||||
t.compile_fail("tests/ui/static_ref.rs");
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:4:11
|
||||
|
|
||||
4 | struct Foo();
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:7:13
|
||||
|
|
||||
7 | struct Foo2 {}
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty enum.
|
||||
error: cannot derive FromPyObject for empty enum
|
||||
--> $DIR/invalid_frompy_derive.rs:10:6
|
||||
|
|
||||
10 | enum EmptyEnum {}
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:14:15
|
||||
|
|
||||
14 | EmptyTuple(),
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:20:17
|
||||
|
|
||||
20 | EmptyStruct {},
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:26:27
|
||||
|
|
||||
26 | struct EmptyTransparentTup();
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:30:31
|
||||
|
|
||||
30 | struct EmptyTransparentStruct {}
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:35:15
|
||||
|
|
||||
35 | EmptyTuple(),
|
||||
| ^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:42:17
|
||||
|
|
||||
42 | EmptyStruct {},
|
||||
| ^^
|
||||
|
||||
error: Transparent structs and variants can only have 1 field
|
||||
error: transparent structs and variants can only have 1 field
|
||||
--> $DIR/invalid_frompy_derive.rs:48:35
|
||||
|
|
||||
48 | struct TransparentTupTooManyFields(String, String);
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Transparent structs and variants can only have 1 field
|
||||
error: transparent structs and variants can only have 1 field
|
||||
--> $DIR/invalid_frompy_derive.rs:52:39
|
||||
|
|
||||
52 | struct TransparentStructTooManyFields {
|
||||
|
@ -68,13 +68,13 @@ error: Transparent structs and variants can only have 1 field
|
|||
55 | | }
|
||||
| |_^
|
||||
|
||||
error: Transparent structs and variants can only have 1 field
|
||||
error: transparent structs and variants can only have 1 field
|
||||
--> $DIR/invalid_frompy_derive.rs:60:15
|
||||
|
|
||||
60 | EmptyTuple(String, String),
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Transparent structs and variants can only have 1 field
|
||||
error: transparent structs and variants can only have 1 field
|
||||
--> $DIR/invalid_frompy_derive.rs:67:17
|
||||
|
|
||||
67 | EmptyStruct {
|
||||
|
@ -84,87 +84,85 @@ error: Transparent structs and variants can only have 1 field
|
|||
70 | | },
|
||||
| |_____^
|
||||
|
||||
error: Expected `attribute` or `item`.
|
||||
error: expected `attribute` or `item`
|
||||
--> $DIR/invalid_frompy_derive.rs:76:12
|
||||
|
|
||||
76 | #[pyo3(attr)]
|
||||
| ^^^^
|
||||
|
||||
error: Expected a single string literal argument.
|
||||
--> $DIR/invalid_frompy_derive.rs:82:22
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:82:12
|
||||
|
|
||||
82 | #[pyo3(attribute(1))]
|
||||
| ^
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Expected a single string literal argument.
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:88:12
|
||||
|
|
||||
88 | #[pyo3(attribute("a", "b"))]
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Attribute name cannot be empty.
|
||||
error: attribute name cannot be empty
|
||||
--> $DIR/invalid_frompy_derive.rs:94:22
|
||||
|
|
||||
94 | #[pyo3(attribute(""))]
|
||||
| ^^
|
||||
|
||||
error: Expected a single string literal argument.
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:100:12
|
||||
|
|
||||
100 | #[pyo3(attribute())]
|
||||
| ^^^^^^^^^^^
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Expected a single literal argument.
|
||||
--> $DIR/invalid_frompy_derive.rs:106:17
|
||||
error: expected a single literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:106:12
|
||||
|
|
||||
106 | #[pyo3(item("a", "b"))]
|
||||
| ^^^^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: Expected a single literal argument.
|
||||
error: expected a single literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:112:12
|
||||
|
|
||||
112 | #[pyo3(item())]
|
||||
| ^^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: Only one of `item`, `attribute` can be provided, possibly with an additional argument: `item("key")` or `attribute("name").
|
||||
error: only one of `attribute` or `item` can be provided
|
||||
--> $DIR/invalid_frompy_derive.rs:118:12
|
||||
|
|
||||
118 | #[pyo3(item, attribute)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: Unrecognized `pyo3` container attribute
|
||||
error: unknown `pyo3` container attribute
|
||||
--> $DIR/invalid_frompy_derive.rs:123:8
|
||||
|
|
||||
123 | #[pyo3(unknown = "should not work")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| ^^^^^^^
|
||||
|
||||
error: Annotating error messages for structs is not supported. Remove the annotation attribute.
|
||||
--> $DIR/invalid_frompy_derive.rs:129:1
|
||||
error: annotation is not supported for structs
|
||||
--> $DIR/invalid_frompy_derive.rs:129:21
|
||||
|
|
||||
129 | #[pyo3(annotation = "should not work")]
|
||||
| ^
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Expected string literal.
|
||||
error: expected string literal for annotation
|
||||
--> $DIR/invalid_frompy_derive.rs:136:25
|
||||
|
|
||||
136 | #[pyo3(annotation = 1)]
|
||||
| ^
|
||||
|
||||
error: FromPyObject can be derived with at most one lifetime parameter.
|
||||
error: FromPyObject can be derived with at most one lifetime parameter
|
||||
--> $DIR/invalid_frompy_derive.rs:141:22
|
||||
|
|
||||
141 | enum TooManyLifetimes<'a, 'b> {
|
||||
| ^^^^^^^^
|
||||
| ^
|
||||
|
||||
error: #[derive(FromPyObject)] is not supported for unions.
|
||||
error: #[derive(FromPyObject)] is not supported for unions
|
||||
--> $DIR/invalid_frompy_derive.rs:147:1
|
||||
|
|
||||
147 | / union Union {
|
||||
148 | | a: usize,
|
||||
149 | | }
|
||||
| |_^
|
||||
147 | union Union {
|
||||
| ^^^^^
|
||||
|
||||
error: Cannot derive FromPyObject for empty structs and variants.
|
||||
error: cannot derive FromPyObject for empty structs and variants
|
||||
--> $DIR/invalid_frompy_derive.rs:151:10
|
||||
|
|
||||
151 | #[derive(FromPyObject)]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
error: Positional argument or varargs(*) is not allowed after keyword arguments
|
||||
error: positional argument or varargs(*) not allowed after keyword arguments
|
||||
--> $DIR/invalid_macro_args.rs:3:21
|
||||
|
|
||||
3 | #[pyfunction(a = 5, b)]
|
||||
| ^
|
||||
|
||||
error: Keyword argument or kwargs(**) is not allowed after kwargs(**)
|
||||
error: keyword argument or kwargs(**) is not allowed after kwargs(**)
|
||||
--> $DIR/invalid_macro_args.rs:8:29
|
||||
|
|
||||
8 | #[pyfunction(kwargs = "**", a = 5)]
|
||||
| ^^^^^
|
||||
| ^
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: Expected &PyModule as first argument with `pass_module`.
|
||||
error: expected &PyModule as first argument with `pass_module`
|
||||
--> $DIR/invalid_need_module_arg_position.rs:6:13
|
||||
|
|
||||
6 | fn fail(string: &str, module: &PyModule) -> PyResult<&str> {
|
||||
| ^^^^^^^^^^^^
|
||||
| ^^^^^^
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
error: Getter function can only have one argument of type pyo3::Python
|
||||
error: getter function can only have one argument (of type pyo3::Python)
|
||||
--> $DIR/invalid_property_args.rs:9:50
|
||||
|
|
||||
9 | fn getter_with_arg(&self, py: Python, index: u32) {}
|
||||
| ^^^
|
||||
|
||||
error: Setter function expected to have one argument
|
||||
error: setter function expected to have one argument
|
||||
--> $DIR/invalid_property_args.rs:18:8
|
||||
|
|
||||
18 | fn setter_with_no_arg(&mut self, py: Python) {}
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Setter function can have at most two arguments: one of pyo3::Python, and one other
|
||||
error: setter function can have at most two arguments ([pyo3::Python,] and value)
|
||||
--> $DIR/invalid_property_args.rs:24:72
|
||||
|
|
||||
24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
error: Expected one of freelist/name/extends/module
|
||||
error: expected one of freelist/name/extends/module
|
||||
--> $DIR/invalid_pyclass_args.rs:3:11
|
||||
|
|
||||
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
||||
| ^^^^^^
|
||||
|
||||
error: Expected type path (e.g., my_mod::BaseClass)
|
||||
error: expected type path (e.g., my_mod::BaseClass)
|
||||
--> $DIR/invalid_pyclass_args.rs:6:21
|
||||
|
|
||||
6 | #[pyclass(extends = "PyDict")]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: Expected type name (e.g. "Name")
|
||||
error: expected type name (e.g. "Name")
|
||||
--> $DIR/invalid_pyclass_args.rs:9:18
|
||||
|
|
||||
9 | #[pyclass(name = m::MyClass)]
|
||||
| ^^^^^^^^^^
|
||||
| ^
|
||||
|
||||
error: expected a single identifier in double-quotes
|
||||
--> $DIR/invalid_pyclass_args.rs:12:18
|
||||
|
@ -28,13 +28,13 @@ error: since PyO3 0.13 a pyclass name should be in double-quotes, e.g. "CustomNa
|
|||
15 | #[pyclass(name = CustomName)]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: Expected string literal (e.g., "my_mod")
|
||||
error: expected string literal (e.g., "my_mod")
|
||||
--> $DIR/invalid_pyclass_args.rs:18:20
|
||||
|
|
||||
18 | #[pyclass(module = my_module)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Expected one of gc/weakref/subclass/dict/unsendable
|
||||
error: expected one of gc/weakref/subclass/dict/unsendable
|
||||
--> $DIR/invalid_pyclass_args.rs:21:11
|
||||
|
|
||||
21 | #[pyclass(weakrev)]
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classattr]
|
||||
fn class_attr_with_args(foo: i32) {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
fn staticmethod_without_attribute() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[staticmethod]
|
||||
fn staticmethod_with_receiver(&self) {}
|
||||
}
|
||||
|
||||
// FIXME: This currently doesn't fail
|
||||
// #[pymethods]
|
||||
// impl MyClass {
|
||||
// #[classmethod]
|
||||
// fn classmethod_with_receiver(&self) {}
|
||||
// }
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[getter(x)]
|
||||
fn getter_without_receiver() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[setter(x)]
|
||||
fn setter_without_receiver() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[text_signature = "()"]
|
||||
fn text_signature_on_new() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[call]
|
||||
#[text_signature = "()"]
|
||||
fn text_signature_on_call(&self) {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[getter(x)]
|
||||
#[text_signature = "()"]
|
||||
fn text_signature_on_getter(&self) {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[setter(x)]
|
||||
#[text_signature = "()"]
|
||||
fn text_signature_on_setter(&self) {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classattr]
|
||||
#[text_signature = "()"]
|
||||
fn text_signature_on_classattr() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classattr]
|
||||
#[staticmethod]
|
||||
fn multiple_method_types() {}
|
||||
}
|
||||
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,65 @@
|
|||
error: class attribute methods cannot take arguments
|
||||
--> $DIR/invalid_pymethods.rs:9:29
|
||||
|
|
||||
9 | fn class_attr_with_args(foo: i32) {}
|
||||
| ^^^
|
||||
|
||||
error: static method needs #[staticmethod] attribute
|
||||
--> $DIR/invalid_pymethods.rs:14:5
|
||||
|
|
||||
14 | fn staticmethod_without_attribute() {}
|
||||
| ^^
|
||||
|
||||
error: unexpected receiver for method
|
||||
--> $DIR/invalid_pymethods.rs:20:35
|
||||
|
|
||||
20 | fn staticmethod_with_receiver(&self) {}
|
||||
| ^
|
||||
|
||||
error: expected receiver for #[getter]
|
||||
--> $DIR/invalid_pymethods.rs:33:5
|
||||
|
|
||||
33 | fn getter_without_receiver() {}
|
||||
| ^^
|
||||
|
||||
error: expected receiver for #[setter]
|
||||
--> $DIR/invalid_pymethods.rs:39:5
|
||||
|
|
||||
39 | fn setter_without_receiver() {}
|
||||
| ^^
|
||||
|
||||
error: text_signature not allowed on __new__; if you want to add a signature on __new__, put it on the struct definition instead
|
||||
--> $DIR/invalid_pymethods.rs:45:24
|
||||
|
|
||||
45 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:52:24
|
||||
|
|
||||
52 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:59:24
|
||||
|
|
||||
59 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:66:24
|
||||
|
|
||||
66 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:73:24
|
||||
|
|
||||
73 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: cannot specify a second method type
|
||||
--> $DIR/invalid_pymethods.rs:80:7
|
||||
|
|
||||
80 | #[staticmethod]
|
||||
| ^^^^^^^^^^^^
|
|
@ -2,4 +2,4 @@ error: #[pyclass] cannot have generic parameters
|
|||
--> $DIR/reject_generics.rs:4:25
|
||||
|
|
||||
4 | struct ClassWithGenerics<A> {
|
||||
| ^^^
|
||||
| ^
|
||||
|
|
Loading…
Reference in New Issue