Skip to content

Commit 5bf1f5d

Browse files
committed
Fix 6 test_fstring expectedFailure tests
- Add Unknown(char) variant to FormatType for proper error messages on unrecognized format codes (test_errors) - Strip comments from f-string debug text in compile.rs (test_debug_conversion) - Map ruff SyntaxError messages to match CPython in vm_new.rs: InvalidDeleteTarget, LineContinuationError, UnclosedStringError, OtherError(bytes mixing), OtherError(keyword identifier), FStringError(UnterminatedString/UnterminatedTripleQuotedString), and backtick-to-quote replacement for FStringError messages
1 parent 56b1167 commit 5bf1f5d

File tree

5 files changed

+448
-17
lines changed

5 files changed

+448
-17
lines changed

Lib/test/test_fstring.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,6 @@ def test_ast_compile_time_concat(self):
587587
exec(c)
588588
self.assertEqual(x[0], 'foo3')
589589

590-
@unittest.expectedFailure # TODO: RUSTPYTHON
591590
def test_compile_time_concat_errors(self):
592591
self.assertAllRaise(SyntaxError,
593592
'cannot mix bytes and nonbytes literals',
@@ -600,7 +599,6 @@ def test_literal(self):
600599
self.assertEqual(f'a', 'a')
601600
self.assertEqual(f' ', ' ')
602601

603-
@unittest.expectedFailure # TODO: RUSTPYTHON
604602
def test_unterminated_string(self):
605603
self.assertAllRaise(SyntaxError, 'unterminated string',
606604
[r"""f'{"x'""",
@@ -852,7 +850,6 @@ def test_format_specifier_expressions(self):
852850
"""f'{"s"!{"r"}}'""",
853851
])
854852

855-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxWarning not triggered
856853
def test_custom_format_specifier(self):
857854
class CustomFormat:
858855
def __format__(self, format_spec):
@@ -956,7 +953,6 @@ def test_newlines_before_syntax_error(self):
956953
"f-string: expecting a valid expression after '{'",
957954
["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"])
958955

959-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxWarning not triggered
960956
def test_backslashes_in_string_part(self):
961957
self.assertEqual(f'\t', '\t')
962958
self.assertEqual(r'\t', '\\t')
@@ -1051,7 +1047,6 @@ def test_backslashes_in_expression_part(self):
10511047
["f'{\n}'",
10521048
])
10531049

1054-
@unittest.expectedFailure # TODO: RUSTPYTHON
10551050
def test_invalid_backslashes_inside_fstring_context(self):
10561051
# All of these variations are invalid python syntax,
10571052
# so they are also invalid in f-strings as well.
@@ -1129,7 +1124,6 @@ def test_roundtrip_raw_quotes(self):
11291124
self.assertEqual(fr'\'\"\'', '\\\'\\"\\\'')
11301125
self.assertEqual(fr'\"\'\"\'', '\\"\\\'\\"\\\'')
11311126

1132-
@unittest.expectedFailure # TODO: RUSTPYTHON
11331127
def test_fstring_backslash_before_double_bracket(self):
11341128
deprecated_cases = [
11351129
(r"f'\{{\}}'", '\\{\\}'),
@@ -1149,7 +1143,6 @@ def test_fstring_backslash_before_double_bracket(self):
11491143
self.assertEqual(fr'\}}{1+1}', '\\}2')
11501144
self.assertEqual(fr'{1+1}\}}', '2\\}')
11511145

1152-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxWarning not triggered
11531146
def test_fstring_backslash_before_double_bracket_warns_once(self):
11541147
with self.assertWarns(SyntaxWarning) as w:
11551148
eval(r"f'\{{'")
@@ -1419,7 +1412,6 @@ def test_assignment(self):
14191412
"f'{x}' = x",
14201413
])
14211414

1422-
@unittest.expectedFailure # TODO: RUSTPYTHON
14231415
def test_del(self):
14241416
self.assertAllRaise(SyntaxError, 'invalid syntax',
14251417
["del f''",
@@ -1525,7 +1517,6 @@ def test_str_format_differences(self):
15251517
self.assertEqual('{d[a]}'.format(d=d), 'string')
15261518
self.assertEqual('{d[0]}'.format(d=d), 'integer')
15271519

1528-
@unittest.expectedFailure # TODO: RUSTPYTHON
15291520
def test_errors(self):
15301521
# see issue 26287
15311522
self.assertAllRaise(TypeError, 'unsupported',
@@ -1568,7 +1559,6 @@ def test_backslash_char(self):
15681559
self.assertEqual(eval('f"\\\n"'), '')
15691560
self.assertEqual(eval('f"\\\r"'), '')
15701561

1571-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '1+2 = # my comment\n 3' != '1+2 = \n 3'
15721562
def test_debug_conversion(self):
15731563
x = 'A string'
15741564
self.assertEqual(f'{x=}', 'x=' + repr(x))
@@ -1799,7 +1789,6 @@ def test_debug_in_file(self):
17991789
self.assertEqual(stdout.decode('utf-8').strip().replace('\r\n', '\n').replace('\r', '\n'),
18001790
"3\n=3")
18011791

1802-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2
18031792
def test_syntax_warning_infinite_recursion_in_file(self):
18041793
with temp_cwd():
18051794
script = 'script.py'

crates/codegen/src/compile.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8450,7 +8450,12 @@ impl Compiler {
84508450
if let Some(ast::DebugText { leading, trailing }) = &fstring_expr.debug_text {
84518451
let range = fstring_expr.expression.range();
84528452
let source = self.source_file.slice(range);
8453-
let text = [leading, source, trailing].concat();
8453+
let text = [
8454+
strip_fstring_debug_comments(leading).as_str(),
8455+
source,
8456+
strip_fstring_debug_comments(trailing).as_str(),
8457+
]
8458+
.concat();
84548459

84558460
self.emit_load_const(ConstantData::Str { value: text.into() });
84568461
element_count += 1;
@@ -8786,6 +8791,27 @@ impl ToU32 for usize {
87868791
}
87878792
}
87888793

8794+
/// Strip Python comments from f-string debug text (leading/trailing around `=`).
8795+
/// A comment starts with `#` and extends to the end of the line.
8796+
/// The newline character itself is preserved.
8797+
fn strip_fstring_debug_comments(text: &str) -> String {
8798+
let mut result = String::with_capacity(text.len());
8799+
let mut in_comment = false;
8800+
for ch in text.chars() {
8801+
if in_comment {
8802+
if ch == '\n' {
8803+
in_comment = false;
8804+
result.push(ch);
8805+
}
8806+
} else if ch == '#' {
8807+
in_comment = true;
8808+
} else {
8809+
result.push(ch);
8810+
}
8811+
}
8812+
result
8813+
}
8814+
87898815
#[cfg(test)]
87908816
mod ruff_tests {
87918817
use super::*;

crates/common/src/format.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ pub enum FormatType {
149149
GeneralFormat(Case),
150150
FixedPoint(Case),
151151
Percentage,
152+
Unknown(char),
152153
}
153154

154155
impl From<&FormatType> for char {
@@ -170,6 +171,7 @@ impl From<&FormatType> for char {
170171
FormatType::FixedPoint(Case::Lower) => 'f',
171172
FormatType::FixedPoint(Case::Upper) => 'F',
172173
FormatType::Percentage => '%',
174+
FormatType::Unknown(c) => *c,
173175
}
174176
}
175177
}
@@ -194,6 +196,7 @@ impl FormatParse for FormatType {
194196
Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_wtf8()),
195197
Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_wtf8()),
196198
Some('%') => (Some(Self::Percentage), chars.as_wtf8()),
199+
Some(c) => (Some(Self::Unknown(c)), chars.as_wtf8()),
197200
_ => (None, text),
198201
}
199202
}
@@ -429,7 +432,8 @@ impl FormatSpec {
429432
| FormatType::FixedPoint(_)
430433
| FormatType::GeneralFormat(_)
431434
| FormatType::Exponent(_)
432-
| FormatType::Percentage,
435+
| FormatType::Percentage
436+
| FormatType::Number(_),
433437
) => 3,
434438
None => 3,
435439
_ => panic!("Separators only valid for numbers!"),
@@ -475,6 +479,7 @@ impl FormatSpec {
475479
let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase();
476480
Ok(first_letter.collect::<String>() + &input.to_string()[1..])
477481
}
482+
Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(*c, "int")),
478483
_ => Err(FormatSpecError::InvalidFormatSpecifier),
479484
}
480485
}
@@ -496,7 +501,8 @@ impl FormatSpec {
496501
| Some(FormatType::Hex(_))
497502
| Some(FormatType::String)
498503
| Some(FormatType::Character)
499-
| Some(FormatType::Number(Case::Upper)) => {
504+
| Some(FormatType::Number(Case::Upper))
505+
| Some(FormatType::Unknown(_)) => {
500506
let ch = char::from(self.format_type.as_ref().unwrap());
501507
Err(FormatSpecError::UnknownFormatCode(ch, "float"))
502508
}
@@ -609,6 +615,7 @@ impl FormatSpec {
609615
Some(float) => return self.format_float(float),
610616
_ => Err(FormatSpecError::UnableToConvert),
611617
},
618+
Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(c, "int")),
612619
None => self.format_int_radix(magnitude, 10),
613620
}?;
614621
let format_sign = self.sign.unwrap_or(FormatSign::Minus);
@@ -707,7 +714,8 @@ impl FormatSpec {
707714
| Some(FormatType::String)
708715
| Some(FormatType::Character)
709716
| Some(FormatType::Number(Case::Upper))
710-
| Some(FormatType::Percentage) => {
717+
| Some(FormatType::Percentage)
718+
| Some(FormatType::Unknown(_)) => {
711719
let ch = char::from(self.format_type.as_ref().unwrap());
712720
Err(FormatSpecError::UnknownFormatCode(ch, "complex"))
713721
}

0 commit comments

Comments
 (0)