Skip to content

Commit bf2cd10

Browse files
authored
Merge pull request #2041 from StarryInternet/parsons20/optional-parameters-must-be-last
Disallow positional arguments after optional arguments
2 parents 7dbbf71 + 47cf132 commit bf2cd10

File tree

8 files changed

+43
-2
lines changed

8 files changed

+43
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- `from_instance` -> `from_value`
3333
- `into_instance` -> `into_value`
3434
- Deprecate `PyType::is_instance`; it is inconsistent with other `is_instance` methods in PyO3. Instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#2031](https://github.com/PyO3/pyo3/pull/2031)
35+
- Optional parameters of `#[pymethods]` and `#[pyfunction]`s cannot be followed by required parameters, i.e. `fn opt_first(a: Option<i32>, b: i32) {}` is not allowed, while `fn opt_last(a:i32, b: Option<i32>) {}` is. [#2041](https://github.com/PyO3/pyo3/pull/2041)
3536

3637
### Removed
3738

examples/pyo3-pytests/src/datetime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ fn time_with_fold<'p>(
4949
minute: u8,
5050
second: u8,
5151
microsecond: u32,
52-
tzinfo: Option<&PyTzInfo>,
5352
fold: bool,
53+
tzinfo: Option<&PyTzInfo>,
5454
) -> PyResult<&'p PyTime> {
5555
PyTime::new_with_fold(
5656
py,

examples/pyo3-pytests/tests/test_datetime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_time_fold(t):
139139
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
140140
@pytest.mark.parametrize("fold", [False, True])
141141
def test_time_fold(fold):
142-
t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
142+
t = rdt.time_with_fold(0, 0, 0, 0, fold, None)
143143
assert t.fold == fold
144144

145145

pyo3-macros-backend/src/params.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub fn impl_arg_params(
8282
let mut required_positional_parameters = 0usize;
8383
let mut keyword_only_parameters = Vec::new();
8484

85+
let mut all_positional_required = true;
86+
8587
for arg in spec.args.iter() {
8688
if arg.py || is_args(&spec.attrs, arg.name) || is_kwargs(&spec.attrs, arg.name) {
8789
continue;
@@ -100,7 +102,13 @@ pub fn impl_arg_params(
100102
});
101103
} else {
102104
if required {
105+
ensure_spanned!(
106+
all_positional_required,
107+
arg.name.span() => "Required positional parameters cannot come after optional parameters"
108+
);
103109
required_positional_parameters += 1;
110+
} else {
111+
all_positional_required = false;
104112
}
105113
if posonly {
106114
positional_only_parameters += 1;

tests/ui/invalid_pyfunctions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ fn impl_trait_function(impl_trait: impl AsRef<PyAny>) {}
99
#[pyfunction]
1010
async fn async_function() {}
1111

12+
#[pyfunction]
13+
fn required_arg_after_optional(optional: Option<isize>, required: isize) {}
14+
1215
fn main() {}

tests/ui/invalid_pyfunctions.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and
1717
|
1818
10 | async fn async_function() {}
1919
| ^^^^^
20+
21+
error: Required positional parameters cannot come after optional parameters
22+
--> tests/ui/invalid_pyfunctions.rs:13:57
23+
|
24+
13 | fn required_arg_after_optional(optional: Option<isize>, required: isize) {}
25+
| ^^^^^^^^

tests/ui/invalid_pymethods.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,15 @@ impl MyClass {
113113
fn method_cannot_pass_module(&self, m: &PyModule) {}
114114
}
115115

116+
#[pymethods]
117+
impl MyClass {
118+
fn required_arg_after_optional(&self, optional: Option<isize>, required: isize) {}
119+
}
120+
121+
#[pymethods]
122+
impl MyClass {
123+
#[args(has_default = "1")]
124+
fn default_arg_before_required(&self, has_default: isize, required: isize) {}
125+
}
126+
116127
fn main() {}

tests/ui/invalid_pymethods.stderr

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,15 @@ error: `pass_module` cannot be used on Python methods
101101
|
102102
112 | #[pyo3(pass_module)]
103103
| ^^^^^^^^^^^
104+
105+
error: Required positional parameters cannot come after optional parameters
106+
--> tests/ui/invalid_pymethods.rs:118:68
107+
|
108+
118 | fn required_arg_after_optional(&self, optional: Option<isize>, required: isize) {}
109+
| ^^^^^^^^
110+
111+
error: Required positional parameters cannot come after optional parameters
112+
--> tests/ui/invalid_pymethods.rs:124:63
113+
|
114+
124 | fn default_arg_before_required(&self, has_default: isize, required: isize) {}
115+
| ^^^^^^^^

0 commit comments

Comments
 (0)