From 7f1a9bacbba826a2c43e2bc933f429fbfe9c80a9 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Sun, 17 Oct 2021 00:52:01 -0400 Subject: [PATCH 1/9] Add support for positional-only args --- pyo3-macros-backend/src/method.rs | 11 ++++++++ pyo3-macros-backend/src/params.rs | 8 ++++-- pyo3-macros-backend/src/pyfunction.rs | 26 +++++++++++++++++- tests/test_methods.rs | 38 +++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index e05d67e41ea..6bfd0692fd3 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -449,6 +449,17 @@ impl<'a> FnSpec<'a> { None } + pub fn is_pos_only(&self, name: &syn::Ident) -> bool { + for s in self.attrs.iter() { + if let Argument::PosOnlyArg(path, _) = s { + if path.is_ident(name) { + return true; + } + } + } + false + } + pub fn is_kw_only(&self, name: &syn::Ident) -> bool { for s in self.attrs.iter() { if let Argument::Kwarg(path, _) = s { diff --git a/pyo3-macros-backend/src/params.rs b/pyo3-macros-backend/src/params.rs index 45259b77990..de39f1bce78 100644 --- a/pyo3-macros-backend/src/params.rs +++ b/pyo3-macros-backend/src/params.rs @@ -78,6 +78,7 @@ pub fn impl_arg_params( }; let mut positional_parameter_names = Vec::new(); + let mut positional_only_parameters = 0usize; let mut required_positional_parameters = 0usize; let mut keyword_only_parameters = Vec::new(); @@ -86,6 +87,7 @@ pub fn impl_arg_params( continue; } let name = arg.name.unraw().to_string(); + let posonly = spec.is_pos_only(arg.name); let kwonly = spec.is_kw_only(arg.name); let required = !(arg.optional.is_some() || spec.default_value(arg.name).is_some()); @@ -100,6 +102,9 @@ pub fn impl_arg_params( if required { required_positional_parameters += 1; } + if posonly { + positional_only_parameters += 1; + } positional_parameter_names.push(name); } } @@ -154,8 +159,7 @@ pub fn impl_arg_params( cls_name: #cls_name, func_name: stringify!(#python_name), positional_parameter_names: &[#(#positional_parameter_names),*], - // TODO: https://github.com/PyO3/pyo3/issues/1439 - support specifying these - positional_only_parameters: 0, + positional_only_parameters: #positional_only_parameters, required_positional_parameters: #required_positional_parameters, keyword_only_parameters: &[#(#keyword_only_parameters),*], accept_varargs: #accept_args, diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index 8e3e872f02d..c5faa3a09d8 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -22,9 +22,11 @@ use syn::{ #[derive(Debug, Clone, PartialEq)] pub enum Argument { + PosOnlyArgsSeparator, VarArgsSeparator, VarArgs(syn::Path), KeywordArgs(syn::Path), + PosOnlyArg(syn::Path, Option), Arg(syn::Path, Option), Kwarg(syn::Path, Option), } @@ -34,6 +36,7 @@ pub enum Argument { pub struct PyFunctionSignature { pub arguments: Vec, has_kw: bool, + has_posonly_args: bool, has_varargs: bool, has_kwargs: bool, } @@ -126,7 +129,20 @@ impl PyFunctionSignature { self.arguments.push(Argument::VarArgsSeparator); Ok(()) } - _ => bail_spanned!(item.span() => "expected \"*\""), + syn::Lit::Str(lits) if lits.value() == "/" => { + // "/" + self.posonly_arg_is_ok(item)?; + self.has_posonly_args = true; + // any arguments _before_ this become positional-only + self.arguments.iter_mut().for_each(|a| + if let Argument::Arg(path, name) = a { + *a = Argument::PosOnlyArg(path.clone(), name.clone()); + } + ); + self.arguments.push(Argument::PosOnlyArgsSeparator); + Ok(()) + } + _ => bail_spanned!(item.span() => "expected \"/\" or \"*\""), } } @@ -143,6 +159,14 @@ impl PyFunctionSignature { Ok(()) } + fn posonly_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> { + ensure_spanned!( + !(self.has_posonly_args || self.has_kwargs || self.has_varargs), + item.span() => "/ is not allowed after /, varargs(*), or kwargs(**)" + ); + Ok(()) + } + fn vararg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> { ensure_spanned!( !(self.has_kwargs || self.has_varargs), diff --git a/tests/test_methods.rs b/tests/test_methods.rs index f238278091b..d6e15312f52 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -215,6 +215,26 @@ impl MethArgs { [a.to_object(py), args.into(), kwargs.to_object(py)].to_object(py) } + #[args(a, b, "/")] + fn get_posargs_only(&self, a: i32, b: i32) -> i32 { + a + b + } + + #[args(a, "/", b)] + fn get_posargs_only_with_posargs(&self, a: i32, b: i32) -> i32 { + a + b + } + + #[args(a, "/", "*", b)] + fn get_posargs_only_and_kwargs_only(&self, a: i32, b: i32) -> i32 { + a + b + } + + #[args(a, "/", "*", b = 3)] + fn get_posargs_only_and_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 { + a + b + } + #[args("*", a = 2, b = 3)] fn get_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 { a + b @@ -308,6 +328,24 @@ fn meth_args() { py_expect_exception!(py, inst, "inst.get_pos_arg_kw(1, a=1)", PyTypeError); py_expect_exception!(py, inst, "inst.get_pos_arg_kw(b=2)", PyTypeError); + py_run!(py, inst, "assert inst.get_posargs_only(3, 5) == 8"); + py_expect_exception!(py, inst, "assert inst.get_posargs_only(a = 3, b = 5)", PyTypeError); + py_expect_exception!(py, inst, "assert inst.get_posargs_only(3, b = 5)", PyTypeError); + + py_run!(py, inst, "assert inst.get_posargs_only_with_posargs(1, 2) == 3"); + py_run!(py, inst, "assert inst.get_posargs_only_with_posargs(1, b = 2) == 3"); + py_expect_exception!(py, inst, "assert inst.get_posargs_only(a = 1, b = 2)", PyTypeError); + + py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(3, b = 5) == 8"); + py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(3, 5)", PyTypeError); + py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(a = 3, b = 5)", PyTypeError); + + py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3) == 6"); + py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, b = 5) == 8"); + py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, 5)", PyTypeError); + py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3, b = 5)", PyTypeError); + py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3)", PyTypeError); + py_run!(py, inst, "assert inst.get_kwargs_only_with_defaults() == 5"); py_run!( py, From d760793ff07c5a9d0fcbc693504ebd2c1d8f6bd6 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Sun, 17 Oct 2021 01:13:40 -0400 Subject: [PATCH 2/9] Update changelog. Add a few more tests. Run rust-fmt. --- CHANGELOG.md | 1 + pyo3-macros-backend/src/pyfunction.rs | 4 +- tests/test_methods.rs | 135 +++++++++++++++++++++++--- 3 files changed, 122 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b98789367e3..eff7c79b9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add commonly-used sequence methods to `PyList` and `PyTuple`. [#1849](https://github.com/PyO3/pyo3/pull/1849) - Add `as_sequence` methods to `PyList` and `PyTuple`. [#1860](https://github.com/PyO3/pyo3/pull/1860) - Add `abi3-py310` feature. [#1889](https://github.com/PyO3/pyo3/pull/1889) +- Add support for positional-only arguments in `#[pyfunction]` [#1439](https://github.com/PyO3/pyo3/issues/1439) ### Changed diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index c5faa3a09d8..6a57f3bacac 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -134,11 +134,11 @@ impl PyFunctionSignature { self.posonly_arg_is_ok(item)?; self.has_posonly_args = true; // any arguments _before_ this become positional-only - self.arguments.iter_mut().for_each(|a| + self.arguments.iter_mut().for_each(|a| { if let Argument::Arg(path, name) = a { *a = Argument::PosOnlyArg(path.clone(), name.clone()); } - ); + }); self.arguments.push(Argument::PosOnlyArgsSeparator); Ok(()) } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index d6e15312f52..610ee17144c 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -225,6 +225,16 @@ impl MethArgs { a + b } + #[args(a, "/", b, c = 5)] + fn get_posargs_only_with_posargs_and_kwargs(&self, a: i32, b: i32, c: i32) -> i32 { + a + b + c + } + + #[args(a, "/", b, "*", c, d = 5)] + fn get_all_arg_types_together(&self, a: i32, b: i32, c: i32, d: i32) -> i32 { + a + b + c + d + } + #[args(a, "/", "*", b)] fn get_posargs_only_and_kwargs_only(&self, a: i32, b: i32) -> i32 { a + b @@ -329,22 +339,115 @@ fn meth_args() { py_expect_exception!(py, inst, "inst.get_pos_arg_kw(b=2)", PyTypeError); py_run!(py, inst, "assert inst.get_posargs_only(3, 5) == 8"); - py_expect_exception!(py, inst, "assert inst.get_posargs_only(a = 3, b = 5)", PyTypeError); - py_expect_exception!(py, inst, "assert inst.get_posargs_only(3, b = 5)", PyTypeError); - - py_run!(py, inst, "assert inst.get_posargs_only_with_posargs(1, 2) == 3"); - py_run!(py, inst, "assert inst.get_posargs_only_with_posargs(1, b = 2) == 3"); - py_expect_exception!(py, inst, "assert inst.get_posargs_only(a = 1, b = 2)", PyTypeError); - - py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(3, b = 5) == 8"); - py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(3, 5)", PyTypeError); - py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only(a = 3, b = 5)", PyTypeError); - - py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3) == 6"); - py_run!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, b = 5) == 8"); - py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, 5)", PyTypeError); - py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3, b = 5)", PyTypeError); - py_expect_exception!(py, inst, "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3)", PyTypeError); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only(a = 3, b = 5)", + PyTypeError + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only(3, b = 5)", + PyTypeError + ); + + py_run!( + py, + inst, + "assert inst.get_posargs_only_with_posargs(1, 2) == 3" + ); + py_run!( + py, + inst, + "assert inst.get_posargs_only_with_posargs(1, b = 2) == 3" + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_with_posargs(a = 1, b = 2)", + PyTypeError + ); + py_run!( + py, + inst, + "assert inst.get_posargs_only_with_posargs_and_kwargs(1, 2) == 8" + ); + py_run!( + py, + inst, + "assert inst.get_posargs_only_with_posargs_and_kwargs(1, 2, c = 3) == 6" + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_with_posargs_and_kwargs(a = 1, b = 2)", + PyTypeError + ); + + py_run!( + py, + inst, + "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3, e = 3) == 12" + ); + py_run!( + py, + inst, + "assert inst.get_all_arg_types_together(1, 2, d = 3) == 16" + ); + py_expect_exception!( + py, + inst, + "assert inst.get_all_arg_types_together(a = 1, b = 2, d = 3)", + PyTypeError + ); + + py_run!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only(3, b = 5) == 8" + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only(3, 5)", + PyTypeError + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only(a = 3, b = 5)", + PyTypeError + ); + + py_run!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3) == 6" + ); + py_run!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, b = 5) == 8" + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, 5)", + PyTypeError + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3, b = 5)", + PyTypeError + ); + py_expect_exception!( + py, + inst, + "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3)", + PyTypeError + ); py_run!(py, inst, "assert inst.get_kwargs_only_with_defaults() == 5"); py_run!( From 8ce5858e84374db6fdaa43a213b8db1f61e1bbb6 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Sun, 17 Oct 2021 01:52:42 -0400 Subject: [PATCH 3/9] Fix test. --- tests/test_methods.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 610ee17144c..ba610044c07 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -388,7 +388,7 @@ fn meth_args() { py_run!( py, inst, - "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3, e = 3) == 12" + "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3) == 12" ); py_run!( py, From 0b43b101ea3a8dd0e50f050118879953ac085b5f Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Sun, 17 Oct 2021 02:03:53 -0400 Subject: [PATCH 4/9] Fix tests again. --- tests/test_methods.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_methods.rs b/tests/test_methods.rs index ba610044c07..d160ec222c6 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -388,12 +388,12 @@ fn meth_args() { py_run!( py, inst, - "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3) == 12" + "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3) == 9" ); py_run!( py, inst, - "assert inst.get_all_arg_types_together(1, 2, d = 3) == 16" + "assert inst.get_all_arg_types_together(1, 2, c = 3) == 11" ); py_expect_exception!( py, From 61398f19d8b06de6ddd96fe91a6c859aab5c1c86 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Sun, 17 Oct 2021 11:19:46 -0400 Subject: [PATCH 5/9] Update CHANGELOG.md to link PR instead of issue Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff7c79b9f6..6202c5b92c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add commonly-used sequence methods to `PyList` and `PyTuple`. [#1849](https://github.com/PyO3/pyo3/pull/1849) - Add `as_sequence` methods to `PyList` and `PyTuple`. [#1860](https://github.com/PyO3/pyo3/pull/1860) - Add `abi3-py310` feature. [#1889](https://github.com/PyO3/pyo3/pull/1889) -- Add support for positional-only arguments in `#[pyfunction]` [#1439](https://github.com/PyO3/pyo3/issues/1439) +- Add support for positional-only arguments in `#[pyfunction]` [#1925](https://github.com/PyO3/pyo3/pull/1925) ### Changed From 22a0eefa83956892442bdbe393099af86d82b0ab Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Mon, 18 Oct 2021 09:32:15 -0400 Subject: [PATCH 6/9] Update guide to mention positional-only params --- guide/src/class.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guide/src/class.md b/guide/src/class.md index 3551ad20a30..f3455c83f4a 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -691,6 +691,9 @@ the form of `attr_name="default value"`. Each parameter has to match the method Each parameter can be one of the following types: + * `"/"`: positional-only arguments separator, each parameter defined before `"/"` is a + positional-only parameter. + Corresponds to python's `def meth(arg1, arg2, ..., /, argN..)`. * `"*"`: var arguments separator, each parameter defined after `"*"` is a keyword-only parameter. Corresponds to python's `def meth(*, arg1.., arg2=..)`. * `args="*"`: "args" is var args, corresponds to Python's `def meth(*args)`. Type of the `args` @@ -745,7 +748,7 @@ impl MyClass { } } ``` -N.B. the position of the `"*"` argument (if included) controls the system of handling positional and keyword arguments. In Python: +N.B. the position of the `"/"` and `"*"` arguments (if included) control the system of handling positional and keyword arguments. In Python: ```python import mymodule From e1a315fd8897304e1f66999ef65b9dab49c8e1d2 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Mon, 18 Oct 2021 09:32:23 -0400 Subject: [PATCH 7/9] Add unreachable!() per code review --- pyo3-macros-backend/src/pyfunction.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index 6a57f3bacac..890b7450509 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -137,6 +137,8 @@ impl PyFunctionSignature { self.arguments.iter_mut().for_each(|a| { if let Argument::Arg(path, name) = a { *a = Argument::PosOnlyArg(path.clone(), name.clone()); + } else { + unreachable!(); } }); self.arguments.push(Argument::PosOnlyArgsSeparator); From 52cce8047bae62c8f339e4b9ad2f8d11dae9f9c3 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Mon, 18 Oct 2021 09:32:42 -0400 Subject: [PATCH 8/9] Update and expand tests for pos args. --- tests/test_methods.rs | 127 +++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/tests/test_methods.rs b/tests/test_methods.rs index d160ec222c6..c3e79833316 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -216,33 +216,48 @@ impl MethArgs { } #[args(a, b, "/")] - fn get_posargs_only(&self, a: i32, b: i32) -> i32 { + fn get_pos_only(&self, a: i32, b: i32) -> i32 { a + b } #[args(a, "/", b)] - fn get_posargs_only_with_posargs(&self, a: i32, b: i32) -> i32 { + fn get_pos_only_and_pos(&self, a: i32, b: i32) -> i32 { a + b } #[args(a, "/", b, c = 5)] - fn get_posargs_only_with_posargs_and_kwargs(&self, a: i32, b: i32, c: i32) -> i32 { + fn get_pos_only_and_pos_and_kw(&self, a: i32, b: i32, c: i32) -> i32 { a + b + c } + #[args(a, "/", "*", b)] + fn get_pos_only_and_kw_only(&self, a: i32, b: i32) -> i32 { + a + b + } + + #[args(a, "/", "*", b = 3)] + fn get_pos_only_and_kw_only_with_default(&self, a: i32, b: i32) -> i32 { + a + b + } + #[args(a, "/", b, "*", c, d = 5)] fn get_all_arg_types_together(&self, a: i32, b: i32, c: i32, d: i32) -> i32 { a + b + c + d } - #[args(a, "/", "*", b)] - fn get_posargs_only_and_kwargs_only(&self, a: i32, b: i32) -> i32 { - a + b + #[args(a, "/", args = "*")] + fn get_pos_only_with_starargs(&self, a: i32, args: Vec) -> i32 { + a + args.iter().sum::() } - #[args(a, "/", "*", b = 3)] - fn get_posargs_only_and_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 { - a + b + #[args(a, "/", kwargs = "**")] + fn get_pos_only_with_starstarkwargs( + &self, + py: Python, + a: i32, + kwargs: Option<&PyDict>, + ) -> PyObject { + [a.to_object(py), kwargs.to_object(py)].to_object(py) } #[args("*", a = 2, b = 3)] @@ -338,114 +353,162 @@ fn meth_args() { py_expect_exception!(py, inst, "inst.get_pos_arg_kw(1, a=1)", PyTypeError); py_expect_exception!(py, inst, "inst.get_pos_arg_kw(b=2)", PyTypeError); - py_run!(py, inst, "assert inst.get_posargs_only(3, 5) == 8"); - py_expect_exception!( + py_run!(py, inst, "assert inst.get_pos_only(10, 11) == 21"); + py_expect_exception!(py, inst, "inst.get_pos_only(10, b = 11)", PyTypeError); + py_expect_exception!(py, inst, "inst.get_pos_only(a = 10, b = 11)", PyTypeError); + + py_run!(py, inst, "assert inst.get_pos_only_and_pos(10, 11) == 21"); + py_run!( py, inst, - "assert inst.get_posargs_only(a = 3, b = 5)", - PyTypeError + "assert inst.get_pos_only_and_pos(10, b = 11) == 21" ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only(3, b = 5)", + "inst.get_pos_only_and_pos(a = 10, b = 11)", PyTypeError ); py_run!( py, inst, - "assert inst.get_posargs_only_with_posargs(1, 2) == 3" + "assert inst.get_pos_only_and_pos_and_kw(10, 11) == 26" + ); + py_run!( + py, + inst, + "assert inst.get_pos_only_and_pos_and_kw(10, b = 11) == 26" + ); + py_run!( + py, + inst, + "assert inst.get_pos_only_and_pos_and_kw(10, b, c = 0) == 21" ); py_run!( py, inst, - "assert inst.get_posargs_only_with_posargs(1, b = 2) == 3" + "assert inst.get_pos_only_and_pos_and_kw(10, b = 11, c = 0) == 21" ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_with_posargs(a = 1, b = 2)", + "inst.get_pos_only_and_pos_and_kw(a = 10, b = 11)", PyTypeError ); + py_run!( py, inst, - "assert inst.get_posargs_only_with_posargs_and_kwargs(1, 2) == 8" + "assert inst.get_pos_only_and_kw_only(10, b = 11) == 21" ); - py_run!( + py_expect_exception!( py, inst, - "assert inst.get_posargs_only_with_posargs_and_kwargs(1, 2, c = 3) == 6" + "inst.get_pos_only_and_kw_only(10, 11)", + PyTypeError ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_with_posargs_and_kwargs(a = 1, b = 2)", + "inst.get_pos_only_and_kw_only(a = 10, b = 11)", PyTypeError ); py_run!( py, inst, - "assert inst.get_all_arg_types_together(1, 2, c = 3, d = 3) == 9" + "assert inst.get_pos_only_and_kw_only_with_default(10) == 13" ); py_run!( py, inst, - "assert inst.get_all_arg_types_together(1, 2, c = 3) == 11" + "assert inst.get_pos_only_and_kw_only_with_default(10, b = 11) == 21" ); py_expect_exception!( py, inst, - "assert inst.get_all_arg_types_together(a = 1, b = 2, d = 3)", + "inst.get_pos_only_and_kw_only_with_default(10, 11)", + PyTypeError + ); + py_expect_exception!( + py, + inst, + "inst.get_pos_only_and_kw_only_with_default(a = 10, b = 11)", PyTypeError ); py_run!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only(3, b = 5) == 8" + "assert inst.get_all_arg_types_together(10, 10, c = 10) == 35" + ); + py_run!( + py, + inst, + "assert inst.get_all_arg_types_together(10, 10, c = 10, d = 10) == 50" + ); + py_run!( + py, + inst, + "assert inst.get_all_arg_types_together(10, b = 10, c = 10, d = 10) == 50" ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only(3, 5)", + "inst.get_all_arg_types_together(10, 10, 10)", PyTypeError ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only(a = 3, b = 5)", + "inst.get_all_arg_types_together(a = 10, b = 10, c = 10)", PyTypeError ); + py_run!(py, inst, "assert inst.get_pos_only_with_starargs(10) == 10"); py_run!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3) == 6" + "assert inst.get_pos_only_with_starargs(10, 10) == 20" ); py_run!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, b = 5) == 8" + "assert inst.get_pos_only_with_starargs(10, 10, 10, 10, 10) == 50" ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only_with_defaults(3, 5)", + "inst.get_pos_only_with_starargs(a = 10)", PyTypeError ); + + py_run!( + py, + inst, + "assert inst.get_pos_only_with_starstarkwargs(10) == [10, {}]" + ); + py_run!( + py, + inst, + "assert inst.get_pos_only_with_starstarkwargs(10, b = 10) == [10, {'b': 10}]" + ); + py_run!( + py, + inst, + "assert inst.get_pos_only_with_starstarkwargs(10, b = 10, c = 10, d = 10, e = 10) == [10, {'b': 10, 'c': 10, 'd': 10, 'e': 10}]" + ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3, b = 5)", + "inst.get_pos_only_with_starstarkwargs(a = 10)", PyTypeError ); py_expect_exception!( py, inst, - "assert inst.get_posargs_only_and_kwargs_only_with_defaults(a = 3)", + "inst.get_pos_only_with_starstarkwargs(a = 10, b = 10)", PyTypeError ); From abd0aea9d235fdb3e786e28754b601b6a9e7508f Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Mon, 18 Oct 2021 21:33:10 -0400 Subject: [PATCH 9/9] Fix tests, lint, add UI tests. --- tests/test_methods.rs | 33 +++++++++++++----------------- tests/ui/invalid_macro_args.rs | 20 ++++++++++++++++++ tests/ui/invalid_macro_args.stderr | 24 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/tests/test_methods.rs b/tests/test_methods.rs index c3e79833316..40afb4f3b53 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -246,17 +246,12 @@ impl MethArgs { } #[args(a, "/", args = "*")] - fn get_pos_only_with_starargs(&self, a: i32, args: Vec) -> i32 { + fn get_pos_only_with_varargs(&self, a: i32, args: Vec) -> i32 { a + args.iter().sum::() } #[args(a, "/", kwargs = "**")] - fn get_pos_only_with_starstarkwargs( - &self, - py: Python, - a: i32, - kwargs: Option<&PyDict>, - ) -> PyObject { + fn get_pos_only_with_kwargs(&self, py: Python, a: i32, kwargs: Option<&PyDict>) -> PyObject { [a.to_object(py), kwargs.to_object(py)].to_object(py) } @@ -383,7 +378,7 @@ fn meth_args() { py_run!( py, inst, - "assert inst.get_pos_only_and_pos_and_kw(10, b, c = 0) == 21" + "assert inst.get_pos_only_and_pos_and_kw(10, 11, c = 0) == 21" ); py_run!( py, @@ -446,12 +441,12 @@ fn meth_args() { py_run!( py, inst, - "assert inst.get_all_arg_types_together(10, 10, c = 10, d = 10) == 50" + "assert inst.get_all_arg_types_together(10, 10, c = 10, d = 10) == 40" ); py_run!( py, inst, - "assert inst.get_all_arg_types_together(10, b = 10, c = 10, d = 10) == 50" + "assert inst.get_all_arg_types_together(10, b = 10, c = 10, d = 10) == 40" ); py_expect_exception!( py, @@ -466,49 +461,49 @@ fn meth_args() { PyTypeError ); - py_run!(py, inst, "assert inst.get_pos_only_with_starargs(10) == 10"); + py_run!(py, inst, "assert inst.get_pos_only_with_varargs(10) == 10"); py_run!( py, inst, - "assert inst.get_pos_only_with_starargs(10, 10) == 20" + "assert inst.get_pos_only_with_varargs(10, 10) == 20" ); py_run!( py, inst, - "assert inst.get_pos_only_with_starargs(10, 10, 10, 10, 10) == 50" + "assert inst.get_pos_only_with_varargs(10, 10, 10, 10, 10) == 50" ); py_expect_exception!( py, inst, - "inst.get_pos_only_with_starargs(a = 10)", + "inst.get_pos_only_with_varargs(a = 10)", PyTypeError ); py_run!( py, inst, - "assert inst.get_pos_only_with_starstarkwargs(10) == [10, {}]" + "assert inst.get_pos_only_with_kwargs(10) == [10, None]" ); py_run!( py, inst, - "assert inst.get_pos_only_with_starstarkwargs(10, b = 10) == [10, {'b': 10}]" + "assert inst.get_pos_only_with_kwargs(10, b = 10) == [10, {'b': 10}]" ); py_run!( py, inst, - "assert inst.get_pos_only_with_starstarkwargs(10, b = 10, c = 10, d = 10, e = 10) == [10, {'b': 10, 'c': 10, 'd': 10, 'e': 10}]" + "assert inst.get_pos_only_with_kwargs(10, b = 10, c = 10, d = 10, e = 10) == [10, {'b': 10, 'c': 10, 'd': 10, 'e': 10}]" ); py_expect_exception!( py, inst, - "inst.get_pos_only_with_starstarkwargs(a = 10)", + "inst.get_pos_only_with_kwargs(a = 10)", PyTypeError ); py_expect_exception!( py, inst, - "inst.get_pos_only_with_starstarkwargs(a = 10, b = 10)", + "inst.get_pos_only_with_kwargs(a = 10, b = 10)", PyTypeError ); diff --git a/tests/ui/invalid_macro_args.rs b/tests/ui/invalid_macro_args.rs index 241d35c6b26..677c0cfbfd5 100644 --- a/tests/ui/invalid_macro_args.rs +++ b/tests/ui/invalid_macro_args.rs @@ -10,4 +10,24 @@ fn kw_after_kwargs(py: Python, kwargs: &PyDict, a: i32) -> PyObject { [a.to_object(py), vararg.into()].to_object(py) } +#[pyfunction(a, "*", b, "/", c)] +fn pos_only_after_kw_only(py: Python, a: i32, b: i32, c: i32) -> i32 { + a + b + c +} + +#[pyfunction(a, args="*", "/", b)] +fn pos_only_after_args(py: Python, a: i32, args: Vec, b: i32) -> i32 { + a + b + c +} + +#[pyfunction(a, kwargs="**", "/", b)] +fn pos_only_after_kwargs(py: Python, a: i32, args: Vec, b: i32) -> i32 { + a + b +} + +#[pyfunction(kwargs = "**", "*", a)] +fn kw_only_after_kwargs(py: Python, kwargs: &PyDict, a: i32) -> PyObject { + [a.to_object(py), vararg.into()].to_object(py) +} + fn main() {} diff --git a/tests/ui/invalid_macro_args.stderr b/tests/ui/invalid_macro_args.stderr index b0965fc930b..a2bc0d945c5 100644 --- a/tests/ui/invalid_macro_args.stderr +++ b/tests/ui/invalid_macro_args.stderr @@ -9,3 +9,27 @@ error: keyword argument or kwargs(**) is not allowed after kwargs(**) | 8 | #[pyfunction(kwargs = "**", a = 5)] | ^ + +error: / is not allowed after /, varargs(*), or kwargs(**) + --> tests/ui/invalid_macro_args.rs:13:25 + | +13 | #[pyfunction(a, "*", b, "/", c)] + | ^^^ + +error: / is not allowed after /, varargs(*), or kwargs(**) + --> tests/ui/invalid_macro_args.rs:18:27 + | +18 | #[pyfunction(a, args="*", "/", b)] + | ^^^ + +error: / is not allowed after /, varargs(*), or kwargs(**) + --> tests/ui/invalid_macro_args.rs:23:30 + | +23 | #[pyfunction(a, kwargs="**", "/", b)] + | ^^^ + +error: * is not allowed after varargs(*) or kwargs(**) + --> tests/ui/invalid_macro_args.rs:28:29 + | +28 | #[pyfunction(kwargs = "**", "*", a)] + | ^^^