macros-backend: improve error handling ergonomics

This commit is contained in:
David Hewitt 2021-01-09 17:33:28 +00:00
parent 364b7c2214
commit 88872eba4f
21 changed files with 509 additions and 504 deletions

View File

@ -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;

View File

@ -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};

View File

@ -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");
}
}

View File

@ -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)?);
}

View File

@ -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"))
}
}

View File

@ -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",
))
}
}

View File

@ -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"
),
}
}

View File

@ -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)
}

View File

@ -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;

View File

@ -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 {

View File

@ -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")
}
}
}

View File

@ -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());

View File

@ -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");

View File

@ -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)]

View File

@ -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)]
| ^^^^^
| ^

View File

@ -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> {
| ^^^^^^^^^^^^
| ^^^^^^

View File

@ -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) {}

View File

@ -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)]

View File

@ -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() {}

View File

@ -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]
| ^^^^^^^^^^^^

View File

@ -2,4 +2,4 @@ error: #[pyclass] cannot have generic parameters
--> $DIR/reject_generics.rs:4:25
|
4 | struct ClassWithGenerics<A> {
| ^^^
| ^