Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions crates/ruff/tests/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# parse_options: { "target-version": "3.7" }
(x := 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# parse_options: { "target-version": "3.8" }
(x := 1)
40 changes: 22 additions & 18 deletions crates/ruff_python_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
f,
"Cannot use `match` statement on Python {} (syntax was added in Python {})",
self.target_version,
self.minimum_version(),
),
}
let kind = match self.kind {
UnsupportedSyntaxErrorKind::Match => "`match` statement",
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)",
};
write!(
f,
"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")]
Expand Down
20 changes: 17 additions & 3 deletions crates/ruff_python_parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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};

Expand Down Expand Up @@ -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,
}
}

Expand Down
12 changes: 11 additions & 1 deletion crates/ruff_python_parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
8 changes: 2 additions & 6 deletions crates/ruff_python_parser/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
|
Original file line number Diff line number Diff line change
@@ -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