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
5 changes: 5 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF027_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ def in_type_def():
from typing import cast
a = 'int'
cast('f"{a}"','11')

# Regression test for parser bug
# https://github.com/astral-sh/ruff/issues/18860
def fuzz_bug():
c('{\t"i}')
2 changes: 1 addition & 1 deletion crates/ruff_python_parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,7 +1527,7 @@ impl<'src> Parser<'src> {
self.bump(kind.start_token());
let elements = self.parse_interpolated_string_elements(
flags,
InterpolatedStringElementsKind::Regular,
InterpolatedStringElementsKind::Regular(kind),
kind,
);

Expand Down
63 changes: 41 additions & 22 deletions crates/ruff_python_parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::error::UnsupportedSyntaxError;
use crate::parser::expression::ExpressionContext;
use crate::parser::progress::{ParserProgress, TokenId};
use crate::string::InterpolatedStringKind;
use crate::token::TokenValue;
use crate::token_set::TokenSet;
use crate::token_source::{TokenSource, TokenSourceCheckpoint};
Expand Down Expand Up @@ -799,15 +800,15 @@ impl WithItemKind {
}
}

#[derive(Debug, PartialEq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum InterpolatedStringElementsKind {
/// The regular f-string elements.
///
/// For example, the `"hello "`, `x`, and `" world"` elements in:
/// ```py
/// f"hello {x:.2f} world"
/// ```
Regular,
Regular(InterpolatedStringKind),

/// The f-string elements are part of the format specifier.
///
Expand All @@ -819,15 +820,13 @@ enum InterpolatedStringElementsKind {
}

impl InterpolatedStringElementsKind {
const fn list_terminators(self) -> TokenSet {
const fn list_terminator(self) -> TokenKind {
match self {
InterpolatedStringElementsKind::Regular => {
TokenSet::new([TokenKind::FStringEnd, TokenKind::TStringEnd])
}
InterpolatedStringElementsKind::Regular(string_kind) => string_kind.end_token(),
// test_ok fstring_format_spec_terminator
// f"hello {x:} world"
// f"hello {x:.3f} world"
InterpolatedStringElementsKind::FormatSpec => TokenSet::new([TokenKind::Rbrace]),
InterpolatedStringElementsKind::FormatSpec => TokenKind::Rbrace,
}
}
}
Expand Down Expand Up @@ -1121,7 +1120,7 @@ impl RecoveryContextKind {
.then_some(ListTerminatorKind::Regular),
},
RecoveryContextKind::InterpolatedStringElements(kind) => {
if p.at_ts(kind.list_terminators()) {
if p.at(kind.list_terminator()) {
Some(ListTerminatorKind::Regular)
} else {
// test_err unterminated_fstring_newline_recovery
Expand Down Expand Up @@ -1177,13 +1176,23 @@ impl RecoveryContextKind {
) || p.at_name_or_soft_keyword()
}
RecoveryContextKind::WithItems(_) => p.at_expr(),
RecoveryContextKind::InterpolatedStringElements(_) => matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
),
RecoveryContextKind::InterpolatedStringElements(elements_kind) => {
match elements_kind {
InterpolatedStringElementsKind::Regular(interpolated_string_kind) => {
p.current_token_kind() == interpolated_string_kind.middle_token()
|| p.current_token_kind() == TokenKind::Lbrace
}
InterpolatedStringElementsKind::FormatSpec => {
matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
)
}
}
}
}
}

Expand Down Expand Up @@ -1272,8 +1281,8 @@ impl RecoveryContextKind {
),
},
RecoveryContextKind::InterpolatedStringElements(kind) => match kind {
InterpolatedStringElementsKind::Regular => ParseErrorType::OtherError(
"Expected an f-string or t-string element or the end of the f-string or t-string".to_string(),
InterpolatedStringElementsKind::Regular(string_kind) => ParseErrorType::OtherError(
format!("Expected an element of or the end of the {string_kind}"),
),
InterpolatedStringElementsKind::FormatSpec => ParseErrorType::OtherError(
"Expected an f-string or t-string element or a '}'".to_string(),
Expand Down Expand Up @@ -1316,8 +1325,9 @@ bitflags! {
const WITH_ITEMS_PARENTHESIZED = 1 << 25;
const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26;
const WITH_ITEMS_UNPARENTHESIZED = 1 << 28;
const FT_STRING_ELEMENTS = 1 << 29;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
const F_STRING_ELEMENTS = 1 << 29;
const T_STRING_ELEMENTS = 1 << 30;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
}
}

Expand Down Expand Up @@ -1371,7 +1381,13 @@ impl RecoveryContext {
WithItemKind::Unparenthesized => RecoveryContext::WITH_ITEMS_UNPARENTHESIZED,
},
RecoveryContextKind::InterpolatedStringElements(kind) => match kind {
InterpolatedStringElementsKind::Regular => RecoveryContext::FT_STRING_ELEMENTS,
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::FString) => {
RecoveryContext::F_STRING_ELEMENTS
}
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString) => {
RecoveryContext::T_STRING_ELEMENTS
}

InterpolatedStringElementsKind::FormatSpec => {
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC
}
Expand Down Expand Up @@ -1442,8 +1458,11 @@ impl RecoveryContext {
RecoveryContext::WITH_ITEMS_UNPARENTHESIZED => {
RecoveryContextKind::WithItems(WithItemKind::Unparenthesized)
}
RecoveryContext::FT_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular,
RecoveryContext::F_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::FString),
),
RecoveryContext::T_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString),
),
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::InterpolatedStringElements(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: Lexical(
LineContinuationError,
),
location: 3..4,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: Lexical(
TStringError(
SingleRbrace,
),
),
location: 8..9,
}
23 changes: 23 additions & 0 deletions crates/ruff_python_parser/src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,26 @@ foo.bar[0].baz[2].egg??
.unwrap();
insta::assert_debug_snapshot!(parsed.syntax());
}

#[test]
fn test_fstring_expr_inner_line_continuation_and_t_string() {
let source = r#"f'{\t"i}'"#;

let parsed = parse_expression(source);

let error = parsed.unwrap_err();

insta::assert_debug_snapshot!(error);
}

#[test]
fn test_fstring_expr_inner_line_continuation_newline_t_string() {
let source = r#"f'{\
t"i}'"#;

let parsed = parse_expression(source);

let error = parsed.unwrap_err();

insta::assert_debug_snapshot!(error);
}
2 changes: 1 addition & 1 deletion crates/ruff_python_parser/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl From<StringType> for Expr {
}
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InterpolatedStringKind {
FString,
TString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,5 @@ Module(

|
1 | f"{lambda x: x}"
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the f-string
|
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Module(
2 | 'hello'
3 | f'world {x}
4 | )
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the f-string
5 | 1 + 1
6 | (
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,5 @@ Module(
|
1 | # parse_options: {"target-version": "3.14"}
2 | t"{lambda x: x}"
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the t-string
|
Loading