[syntax-errors] Positional-only parameters before Python 3.8 (#16481)

Summary
--

Detect positional-only parameters before Python 3.8, as marked by the
`/` separator in a parameter list.

Test Plan
--
Inline tests.
This commit is contained in:
Brent Westbrook 2025-03-05 08:46:43 -05:00 committed by GitHub
parent 23fd4927ae
commit d0623888b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 404 additions and 0 deletions

View file

@ -449,6 +449,32 @@ pub enum UnsupportedSyntaxErrorKind {
Match,
Walrus,
ExceptStar,
/// Represents the use of a [PEP 570] positional-only parameter before Python 3.8.
///
/// ## Examples
///
/// Python 3.8 added the `/` syntax for marking preceding parameters as positional-only:
///
/// ```python
/// def foo(a, b, /, c): ...
/// ```
///
/// This means `a` and `b` in this case can only be provided by position, not by name. In other
/// words, this code results in a `TypeError` at runtime:
///
/// ```pycon
/// >>> def foo(a, b, /, c): ...
/// ...
/// >>> foo(a=1, b=2, c=3)
/// Traceback (most recent call last):
/// File "<python-input-3>", line 1, in <module>
/// foo(a=1, b=2, c=3)
/// ~~~^^^^^^^^^^^^^^^
/// TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a, b'
/// ```
///
/// [PEP 570]: https://peps.python.org/pep-0570/
PositionalOnlyParameter,
/// Represents the use of a [type parameter list] before Python 3.12.
///
/// ## Examples
@ -487,6 +513,9 @@ impl Display for UnsupportedSyntaxError {
UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
"Cannot use positional-only parameter separator"
}
UnsupportedSyntaxErrorKind::TypeParameterList => "Cannot use type parameter lists",
UnsupportedSyntaxErrorKind::TypeAliasStatement => "Cannot use `type` alias statement",
UnsupportedSyntaxErrorKind::TypeParamDefault => {
@ -509,6 +538,7 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => PythonVersion::PY38,
UnsupportedSyntaxErrorKind::TypeParameterList => PythonVersion::PY312,
UnsupportedSyntaxErrorKind::TypeAliasStatement => PythonVersion::PY312,
UnsupportedSyntaxErrorKind::TypeParamDefault => PythonVersion::PY313,

View file

@ -3050,6 +3050,21 @@ impl<'src> Parser<'src> {
// first time, otherwise it's a user error.
std::mem::swap(&mut parameters.args, &mut parameters.posonlyargs);
seen_positional_only_separator = true;
// test_ok pos_only_py38
// # parse_options: {"target-version": "3.8"}
// def foo(a, /): ...
// test_err pos_only_py37
// # parse_options: {"target-version": "3.7"}
// def foo(a, /): ...
// def foo(a, /, b, /): ...
// def foo(a, *args, /, b): ...
// def foo(a, //): ...
parser.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::PositionalOnlyParameter,
slash_range,
);
}
last_keyword_only_separator_range = None;