mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-30 00:14:15 +00:00
[syntax-errors] Parenthesized keyword argument names after Python 3.8 (#16482)
Summary -- Unlike the other syntax errors detected so far, parenthesized keyword arguments are only allowed *before* 3.8. It sounds like they were only accidentally allowed before that [^1]. As an aside, you get a pretty confusing error from Python for this, so it's nice that we can catch it: ```pycon >>> def f(**kwargs): ... ... f((a)=1) ... File "<python-input-0>", line 2 f((a)=1) ^^^ SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> ``` Test Plan -- Inline tests. [^1]: https://github.com/python/cpython/issues/78822
This commit is contained in:
parent
6c14225c66
commit
b3c884f4f3
8 changed files with 319 additions and 22 deletions
|
@ -436,11 +436,6 @@ pub struct UnsupportedSyntaxError {
|
|||
pub kind: UnsupportedSyntaxErrorKind,
|
||||
pub range: TextRange,
|
||||
/// The target [`PythonVersion`] for which this error was detected.
|
||||
///
|
||||
/// This is different from the version reported by the
|
||||
/// [`minimum_version`](UnsupportedSyntaxErrorKind::minimum_version) method, which is the
|
||||
/// earliest allowed version for this piece of syntax. The `target_version` is primarily used
|
||||
/// for user-facing error messages.
|
||||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
||||
|
@ -457,6 +452,26 @@ pub enum UnsupportedSyntaxErrorKind {
|
|||
Walrus,
|
||||
ExceptStar,
|
||||
|
||||
/// Represents the use of a parenthesized keyword argument name after Python 3.8.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// From [BPO 34641] it sounds like this was only accidentally supported and was removed when
|
||||
/// noticed. Code like this used to be valid:
|
||||
///
|
||||
/// ```python
|
||||
/// f((a)=1)
|
||||
/// ```
|
||||
///
|
||||
/// After Python 3.8, you have to omit the parentheses around `a`:
|
||||
///
|
||||
/// ```python
|
||||
/// f(a=1)
|
||||
/// ```
|
||||
///
|
||||
/// [BPO 34641]: https://github.com/python/cpython/issues/78822
|
||||
ParenthesizedKeywordArgumentName,
|
||||
|
||||
/// Represents the use of unparenthesized tuple unpacking in a `return` statement or `yield`
|
||||
/// expression before Python 3.8.
|
||||
///
|
||||
|
@ -603,6 +618,9 @@ impl Display for UnsupportedSyntaxError {
|
|||
UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
|
||||
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
|
||||
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
|
||||
UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
|
||||
"Cannot use parenthesized keyword argument name"
|
||||
}
|
||||
UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Return) => {
|
||||
"Cannot use iterable unpacking in return statements"
|
||||
}
|
||||
|
@ -619,32 +637,67 @@ impl Display for UnsupportedSyntaxError {
|
|||
"Cannot set default type for a type parameter"
|
||||
}
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{kind} on Python {} (syntax was added in Python {})",
|
||||
"{kind} on Python {} (syntax was {changed})",
|
||||
self.target_version,
|
||||
self.kind.minimum_version(),
|
||||
changed = self.kind.changed_version(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnsupportedSyntaxErrorKind {
|
||||
/// The earliest allowed version for the syntax associated with this error.
|
||||
pub const fn minimum_version(&self) -> PythonVersion {
|
||||
/// Represents the kind of change in Python syntax between versions.
|
||||
enum Change {
|
||||
Added(PythonVersion),
|
||||
Removed(PythonVersion),
|
||||
}
|
||||
|
||||
impl Display for Change {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
|
||||
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
|
||||
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
|
||||
UnsupportedSyntaxErrorKind::StarTuple(_) => PythonVersion::PY38,
|
||||
UnsupportedSyntaxErrorKind::RelaxedDecorator => PythonVersion::PY39,
|
||||
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => PythonVersion::PY38,
|
||||
UnsupportedSyntaxErrorKind::TypeParameterList => PythonVersion::PY312,
|
||||
UnsupportedSyntaxErrorKind::TypeAliasStatement => PythonVersion::PY312,
|
||||
UnsupportedSyntaxErrorKind::TypeParamDefault => PythonVersion::PY313,
|
||||
Change::Added(version) => write!(f, "added in Python {version}"),
|
||||
Change::Removed(version) => write!(f, "removed in Python {version}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnsupportedSyntaxErrorKind {
|
||||
/// Returns the Python version when the syntax associated with this error was changed, and the
|
||||
/// type of [`Change`] (added or removed).
|
||||
const fn changed_version(self) -> Change {
|
||||
match self {
|
||||
UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
|
||||
UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
|
||||
UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
|
||||
UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
|
||||
UnsupportedSyntaxErrorKind::RelaxedDecorator => Change::Added(PythonVersion::PY39),
|
||||
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
|
||||
Change::Added(PythonVersion::PY38)
|
||||
}
|
||||
UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
|
||||
Change::Removed(PythonVersion::PY38)
|
||||
}
|
||||
UnsupportedSyntaxErrorKind::TypeParameterList => Change::Added(PythonVersion::PY312),
|
||||
UnsupportedSyntaxErrorKind::TypeAliasStatement => Change::Added(PythonVersion::PY312),
|
||||
UnsupportedSyntaxErrorKind::TypeParamDefault => Change::Added(PythonVersion::PY313),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether or not this kind of syntax is unsupported on `target_version`.
|
||||
pub(crate) fn is_unsupported(self, target_version: PythonVersion) -> bool {
|
||||
match self.changed_version() {
|
||||
Change::Added(version) => target_version < version,
|
||||
Change::Removed(version) => target_version >= version,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this kind of syntax is supported on `target_version`.
|
||||
pub(crate) fn is_supported(self, target_version: PythonVersion) -> bool {
|
||||
!self.is_unsupported(target_version)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
mod sizes {
|
||||
use crate::error::{LexicalError, LexicalErrorType};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue