Add syntax error when conversion flag does not immediately follow exclamation mark (#18706)

Closes #18671

Note that while this has, I believe, always been invalid syntax, it was
reported as a different syntax error until Python 3.12:

Python 3.11:

```pycon
>>> x = 1
>>> f"{x! s}"
  File "<stdin>", line 1
    f"{x! s}"
             ^
SyntaxError: f-string: invalid conversion character: expected 's', 'r', or 'a'
```

Python 3.12:

```pycon
>>> x = 1
>>> f"{x! s}"
  File "<stdin>", line 1
    f"{x! s}"
        ^^^
SyntaxError: f-string: conversion type must come right after the exclamanation mark
```
This commit is contained in:
Dylan 2025-06-16 11:44:42 -05:00 committed by GitHub
parent a842899862
commit c5b58187da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 219 additions and 38 deletions

View file

@ -63,13 +63,16 @@ pub enum InterpolatedStringErrorType {
UnterminatedTripleQuotedString,
/// A lambda expression without parentheses was encountered.
LambdaWithoutParentheses,
/// Conversion flag does not immediately follow exclamation.
ConversionFlagNotImmediatelyAfterExclamation,
}
impl std::fmt::Display for InterpolatedStringErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use InterpolatedStringErrorType::{
InvalidConversionFlag, LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace,
UnterminatedString, UnterminatedTripleQuotedString,
ConversionFlagNotImmediatelyAfterExclamation, InvalidConversionFlag,
LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace, UnterminatedString,
UnterminatedTripleQuotedString,
};
match self {
UnclosedLbrace => write!(f, "expecting '}}'"),
@ -80,6 +83,10 @@ impl std::fmt::Display for InterpolatedStringErrorType {
LambdaWithoutParentheses => {
write!(f, "lambda expressions are not allowed without parentheses")
}
ConversionFlagNotImmediatelyAfterExclamation => write!(
f,
"conversion type must come right after the exclamation mark"
),
}
}
}

View file

@ -1771,6 +1771,19 @@ impl<'src> Parser<'src> {
let conversion = if self.eat(TokenKind::Exclamation) {
let conversion_flag_range = self.current_token_range();
if self.at(TokenKind::Name) {
// test_err f_string_conversion_follows_exclamation
// f"{x! s}"
// t"{x! s}"
// f"{x! z}"
if self.prev_token_end != conversion_flag_range.start() {
self.add_error(
ParseErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::ConversionFlagNotImmediatelyAfterExclamation,
string_kind,
),
TextRange::new(self.prev_token_end, conversion_flag_range.start()),
);
}
let TokenValue::Name(name) = self.bump_value(TokenKind::Name) else {
unreachable!();
};