Skip to content

Commit

Permalink
Merge pull request #826 from kngwyu/pyclass-err-improve
Browse files Browse the repository at this point in the history
More ergonomic error messages for invalid #[pyclass] args
  • Loading branch information
kngwyu authored Mar 23, 2020
2 parents 00a277c + 3b17ab0 commit 3b8af93
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 38 deletions.
69 changes: 31 additions & 38 deletions pyo3-derive-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,72 +56,65 @@ impl PyClassArgs {
match expr {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => self.add_path(exp),
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
fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> {
let key = match *assign.left {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
let syn::ExprAssign { left, right, .. } = assign;
let key = match &**left {
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, "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() {
"freelist" => {
// 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 {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
"name" => match &**right {
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
self.name = Some(exp.clone().into());
}
_ => {
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong 'name' format",
));
}
_ => expected!("type name (e.g., Name)"),
},
"extends" => match *assign.right {
syn::Expr::Path(ref exp) => {
"extends" => match &**right {
syn::Expr::Path(exp) => {
self.base = syn::TypePath {
path: exp.path.clone(),
qself: None,
};
self.has_extends = true;
}
_ => {
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong format for extends",
));
}
_ => expected!("type path (e.g., my_mod::BaseClass)"),
},
"module" => match *assign.right {
"module" => match &**right {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(ref lit),
lit: syn::Lit::Str(lit),
..
}) => {
self.module = Some(lit.clone());
}
_ => {
return Err(syn::Error::new_spanned(
*assign.right.clone(),
"Wrong format for module",
));
}
_ => expected!(r#"string literal (e.g., "my_mod")"#),
},
_ => {
return Err(syn::Error::new_spanned(
*assign.left.clone(),
"Unsupported parameter",
));
}
_ => expected!("one of freelist/name/extends/module", left),
};

Ok(())
Expand All @@ -145,9 +138,9 @@ impl PyClassArgs {
}
_ => {
return Err(syn::Error::new_spanned(
exp.path.clone(),
"Unsupported parameter",
));
&exp.path,
"Expected one of gc/weakref/subclass/dict",
))
}
};

Expand Down
1 change: 1 addition & 0 deletions tests/test_compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ fn test_compile_errors() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/invalid_macro_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/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs");
Expand Down
18 changes: 18 additions & 0 deletions tests/ui/invalid_pyclass_args.rs
Original file line number Diff line number Diff line change
@@ -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() {}
29 changes: 29 additions & 0 deletions tests/ui/invalid_pyclass_args.stderr
Original file line number Diff line number Diff line change
@@ -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)]
| ^^^^^^^

0 comments on commit 3b8af93

Please sign in to comment.