Merge pull request #826 from kngwyu/pyclass-err-improve

More ergonomic error messages for invalid #[pyclass] args
This commit is contained in:
Yuji Kanagawa 2020-03-23 18:51:16 +09:00 committed by GitHub
commit 3b8af93aea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 38 deletions

View File

@ -56,72 +56,65 @@ impl PyClassArgs {
match expr { match expr {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => self.add_path(exp), syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => self.add_path(exp),
syn::Expr::Assign(ref assign) => self.add_assign(assign), syn::Expr::Assign(ref assign) => self.add_assign(assign),
_ => Err(syn::Error::new_spanned(expr, "Could not parse arguments")), _ => Err(syn::Error::new_spanned(expr, "Failed to parse arguments")),
} }
} }
/// Match a single flag /// Match a single flag
fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> { fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> {
let key = match *assign.left { let syn::ExprAssign { left, right, .. } = assign;
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { let key = match &**left {
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
exp.path.segments.first().unwrap().ident.to_string() exp.path.segments.first().unwrap().ident.to_string()
} }
_ => { _ => {
return Err(syn::Error::new_spanned(assign, "could not parse argument")); return Err(syn::Error::new_spanned(assign, "Failed to parse arguments"));
} }
}; };
macro_rules! expected {
($expected: literal) => {
expected!($expected, right)
};
($expected: literal, $span: ident) => {
return Err(syn::Error::new_spanned(
$span,
concat!("Expected ", $expected),
));
};
}
match key.as_str() { match key.as_str() {
"freelist" => { "freelist" => {
// We allow arbitrary expressions here so you can e.g. use `8*64` // We allow arbitrary expressions here so you can e.g. use `8*64`
self.freelist = Some(*assign.right.clone()); self.freelist = Some(syn::Expr::clone(right));
} }
"name" => match *assign.right { "name" => match &**right {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
self.name = Some(exp.clone().into()); self.name = Some(exp.clone().into());
} }
_ => { _ => expected!("type name (e.g., Name)"),
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong 'name' format",
));
}
}, },
"extends" => match *assign.right { "extends" => match &**right {
syn::Expr::Path(ref exp) => { syn::Expr::Path(exp) => {
self.base = syn::TypePath { self.base = syn::TypePath {
path: exp.path.clone(), path: exp.path.clone(),
qself: None, qself: None,
}; };
self.has_extends = true; self.has_extends = true;
} }
_ => { _ => expected!("type path (e.g., my_mod::BaseClass)"),
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong format for extends",
));
}
}, },
"module" => match *assign.right { "module" => match &**right {
syn::Expr::Lit(syn::ExprLit { syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(ref lit), lit: syn::Lit::Str(lit),
.. ..
}) => { }) => {
self.module = Some(lit.clone()); self.module = Some(lit.clone());
} }
_ => { _ => expected!(r#"string literal (e.g., "my_mod")"#),
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong format for module",
));
}
}, },
_ => { _ => expected!("one of freelist/name/extends/module", left),
return Err(syn::Error::new_spanned(
*assign.left.clone(),
"Unsupported parameter",
));
}
}; };
Ok(()) Ok(())
@ -145,9 +138,9 @@ impl PyClassArgs {
} }
_ => { _ => {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
exp.path.clone(), &exp.path,
"Unsupported parameter", "Expected one of gc/weakref/subclass/dict",
)); ))
} }
}; };

View File

@ -3,6 +3,7 @@ fn test_compile_errors() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/invalid_macro_args.rs"); t.compile_fail("tests/ui/invalid_macro_args.rs");
t.compile_fail("tests/ui/invalid_property_args.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_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/missing_clone.rs"); t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/reject_generics.rs");

View File

@ -0,0 +1,18 @@
use pyo3::prelude::*;
#[pyclass(extend=pyo3::types::PyDict)]
struct TypoIntheKey {}
#[pyclass(extends = "PyDict")]
struct InvalidExtends {}
#[pyclass(name = m::MyClass)]
struct InvalidName {}
#[pyclass(module = my_module)]
struct InvalidModule {}
#[pyclass(weakrev)]
struct InvalidArg {}
fn main() {}

View File

@ -0,0 +1,29 @@
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)
--> $DIR/invalid_pyclass_args.rs:6:21
|
6 | #[pyclass(extends = "PyDict")]
| ^^^^^^^^
error: Expected type name (e.g., Name)
--> $DIR/invalid_pyclass_args.rs:9:18
|
9 | #[pyclass(name = m::MyClass)]
| ^^^^^^^^^^
error: Expected string literal (e.g., "my_mod")
--> $DIR/invalid_pyclass_args.rs:12:20
|
12 | #[pyclass(module = my_module)]
| ^^^^^^^^^
error: Expected one of gc/weakref/subclass/dict
--> $DIR/invalid_pyclass_args.rs:15:11
|
15 | #[pyclass(weakrev)]
| ^^^^^^^