mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:13:08 +00:00
[syntax-errors] Assignment expressions before Python 3.8 (#16383)
## Summary This PR is the first in a series derived from https://github.com/astral-sh/ruff/pull/16308, each of which add support for detecting one version-related syntax error from https://github.com/astral-sh/ruff/issues/6591. This one should be the largest because it also includes the addition of the `Parser::add_unsupported_syntax_error` method Otherwise I think the general structure will be the same for each syntax error: * Detecting the error in the parser * Inline parser tests for the new error * New ruff CLI tests for the new error ## Test Plan As noted above, there are new inline parser tests, as well as new ruff CLI tests. Once https://github.com/astral-sh/ruff/pull/16379 is resolved, there should also be new mdtests for red-knot, but this PR does not currently include those.
This commit is contained in:
parent
ba44e9de13
commit
4431978262
9 changed files with 182 additions and 28 deletions
|
@ -2628,6 +2628,45 @@ class A(Generic[T]):
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walrus_before_py38() {
|
||||
// ok
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--stdin-filename", "test.py"])
|
||||
.arg("--target-version=py38")
|
||||
.arg("-")
|
||||
.pass_stdin(r#"(x := 1)"#),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
|
||||
// not ok on 3.7 with preview
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--stdin-filename", "test.py"])
|
||||
.arg("--target-version=py37")
|
||||
.arg("--preview")
|
||||
.arg("-")
|
||||
.pass_stdin(r#"(x := 1)"#),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:1:2: SyntaxError: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_before_py310() {
|
||||
// ok on 3.10
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# parse_options: { "target-version": "3.7" }
|
||||
(x := 1)
|
|
@ -0,0 +1,2 @@
|
|||
# parse_options: { "target-version": "3.8" }
|
||||
(x := 1)
|
|
@ -444,31 +444,35 @@ pub struct UnsupportedSyntaxError {
|
|||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
||||
impl UnsupportedSyntaxError {
|
||||
/// The earliest allowed version for the syntax associated with this error.
|
||||
pub const fn minimum_version(&self) -> PythonVersion {
|
||||
match self.kind {
|
||||
UnsupportedSyntaxErrorKind::MatchBeforePy310 => PythonVersion::PY310,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum UnsupportedSyntaxErrorKind {
|
||||
Match,
|
||||
Walrus,
|
||||
}
|
||||
|
||||
impl Display for UnsupportedSyntaxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
UnsupportedSyntaxErrorKind::MatchBeforePy310 => write!(
|
||||
let kind = match self.kind {
|
||||
UnsupportedSyntaxErrorKind::Match => "`match` statement",
|
||||
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)",
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"Cannot use `match` statement on Python {} (syntax was added in Python {})",
|
||||
"Cannot use {kind} on Python {} (syntax was added in Python {})",
|
||||
self.target_version,
|
||||
self.minimum_version(),
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum UnsupportedSyntaxErrorKind {
|
||||
MatchBeforePy310,
|
||||
impl UnsupportedSyntaxError {
|
||||
/// The earliest allowed version for the syntax associated with this error.
|
||||
pub const fn minimum_version(&self) -> PythonVersion {
|
||||
match self.kind {
|
||||
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
|
||||
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
|
|
@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
|
|||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{
|
||||
self as ast, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FStringElement, FStringElements,
|
||||
IpyEscapeKind, Number, Operator, StringFlags, UnaryOp,
|
||||
IpyEscapeKind, Number, Operator, PythonVersion, StringFlags, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
|
@ -16,7 +16,7 @@ use crate::parser::{helpers, FunctionKind, Parser};
|
|||
use crate::string::{parse_fstring_literal_element, parse_string_literal, StringType};
|
||||
use crate::token::{TokenKind, TokenValue};
|
||||
use crate::token_set::TokenSet;
|
||||
use crate::{FStringErrorType, Mode, ParseErrorType};
|
||||
use crate::{FStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxErrorKind};
|
||||
|
||||
use super::{FStringElementsKind, Parenthesized, RecoveryContextKind};
|
||||
|
||||
|
@ -2161,10 +2161,24 @@ impl<'src> Parser<'src> {
|
|||
|
||||
let value = self.parse_conditional_expression_or_higher();
|
||||
|
||||
let range = self.node_range(start);
|
||||
|
||||
// test_err walrus_py37
|
||||
// # parse_options: { "target-version": "3.7" }
|
||||
// (x := 1)
|
||||
|
||||
// test_ok walrus_py38
|
||||
// # parse_options: { "target-version": "3.8" }
|
||||
// (x := 1)
|
||||
|
||||
if self.options.target_version < PythonVersion::PY38 {
|
||||
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Walrus, range);
|
||||
}
|
||||
|
||||
ast::ExprNamed {
|
||||
target: Box::new(target),
|
||||
value: Box::new(value.expr),
|
||||
range: self.node_range(start),
|
||||
range,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::parser::progress::{ParserProgress, TokenId};
|
|||
use crate::token::TokenValue;
|
||||
use crate::token_set::TokenSet;
|
||||
use crate::token_source::{TokenSource, TokenSourceCheckpoint};
|
||||
use crate::{Mode, ParseError, ParseErrorType, TokenKind};
|
||||
use crate::{Mode, ParseError, ParseErrorType, TokenKind, UnsupportedSyntaxErrorKind};
|
||||
use crate::{Parsed, Tokens};
|
||||
|
||||
pub use crate::parser::options::ParseOptions;
|
||||
|
@ -438,6 +438,16 @@ impl<'src> Parser<'src> {
|
|||
inner(&mut self.errors, error, ranged.range());
|
||||
}
|
||||
|
||||
/// Add an [`UnsupportedSyntaxError`] with the given [`UnsupportedSyntaxErrorKind`] and
|
||||
/// [`TextRange`].
|
||||
fn add_unsupported_syntax_error(&mut self, kind: UnsupportedSyntaxErrorKind, range: TextRange) {
|
||||
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
|
||||
kind,
|
||||
range,
|
||||
target_version: self.options.target_version,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns `true` if the current token is of the given kind.
|
||||
fn at(&self, kind: TokenKind) -> bool {
|
||||
self.current_token_kind() == kind
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::parser::{
|
|||
};
|
||||
use crate::token::{TokenKind, TokenValue};
|
||||
use crate::token_set::TokenSet;
|
||||
use crate::{Mode, ParseErrorType, UnsupportedSyntaxError, UnsupportedSyntaxErrorKind};
|
||||
use crate::{Mode, ParseErrorType, UnsupportedSyntaxErrorKind};
|
||||
|
||||
use super::expression::ExpressionContext;
|
||||
use super::Parenthesized;
|
||||
|
@ -2278,11 +2278,7 @@ impl<'src> Parser<'src> {
|
|||
// pass
|
||||
|
||||
if self.options.target_version < PythonVersion::PY310 {
|
||||
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
|
||||
kind: UnsupportedSyntaxErrorKind::MatchBeforePy310,
|
||||
range: match_range,
|
||||
target_version: self.options.target_version,
|
||||
});
|
||||
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Match, match_range);
|
||||
}
|
||||
|
||||
ast::StmtMatch {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/walrus_py37.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..54,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 45..53,
|
||||
value: Named(
|
||||
ExprNamed {
|
||||
range: 46..52,
|
||||
target: Name(
|
||||
ExprName {
|
||||
range: 46..47,
|
||||
id: Name("x"),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 51..52,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Unsupported Syntax Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: { "target-version": "3.7" }
|
||||
2 | (x := 1)
|
||||
| ^^^^^^ Syntax Error: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/walrus_py38.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..54,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 45..53,
|
||||
value: Named(
|
||||
ExprNamed {
|
||||
range: 46..52,
|
||||
target: Name(
|
||||
ExprName {
|
||||
range: 46..47,
|
||||
id: Name("x"),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 51..52,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue