mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:12:22 +00:00
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:
parent
a842899862
commit
c5b58187da
8 changed files with 219 additions and 38 deletions
|
@ -267,17 +267,13 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = f"aaaaaaaaa { x ! r }"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
x = f"aaaaaaaaa { x = ! r }"
|
x = f"aaaaaaaaa { x = !r }"
|
||||||
|
|
||||||
# Combine conversion flags with format specifiers
|
# Combine conversion flags with format specifiers
|
||||||
x = f"{x = ! s
|
x = f"{x = !s
|
||||||
:>0
|
:>0
|
||||||
|
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -265,17 +265,13 @@ aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = t"aaaaaaaaa { x ! r }"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
x = t"aaaaaaaaa { x = ! r }"
|
x = t"aaaaaaaaa { x = !r }"
|
||||||
|
|
||||||
# Combine conversion flags with format specifiers
|
# Combine conversion flags with format specifiers
|
||||||
x = t"{x = ! s
|
x = t"{x = !s
|
||||||
:>0
|
:>0
|
||||||
|
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -273,17 +273,13 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = f"aaaaaaaaa { x ! r }"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
x = f"aaaaaaaaa { x = ! r }"
|
x = f"aaaaaaaaa { x = !r }"
|
||||||
|
|
||||||
# Combine conversion flags with format specifiers
|
# Combine conversion flags with format specifiers
|
||||||
x = f"{x = ! s
|
x = f"{x = !s
|
||||||
:>0
|
:>0
|
||||||
|
|
||||||
}"
|
}"
|
||||||
|
@ -1036,10 +1032,6 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = f"aaaaaaaaa {x!r}"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
|
@ -1836,10 +1828,6 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = f"aaaaaaaaa {x!r}"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
|
|
|
@ -271,17 +271,13 @@ aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = t"aaaaaaaaa { x ! r }"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
x = t"aaaaaaaaa { x = ! r }"
|
x = t"aaaaaaaaa { x = !r }"
|
||||||
|
|
||||||
# Combine conversion flags with format specifiers
|
# Combine conversion flags with format specifiers
|
||||||
x = t"{x = ! s
|
x = t"{x = !s
|
||||||
:>0
|
:>0
|
||||||
|
|
||||||
}"
|
}"
|
||||||
|
@ -1032,10 +1028,6 @@ aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc
|
||||||
|
|
||||||
# Conversion flags
|
# Conversion flags
|
||||||
#
|
#
|
||||||
# This is not a valid Python code because of the additional whitespace between the `!`
|
|
||||||
# and conversion type. But, our parser isn't strict about this. This should probably be
|
|
||||||
# removed once we have a strict parser.
|
|
||||||
x = t"aaaaaaaaa {x!r}"
|
|
||||||
|
|
||||||
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
# Even in the case of debug expressions, we only need to preserve the whitespace within
|
||||||
# the expression part of the replacement field.
|
# the expression part of the replacement field.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
f"{x! s}"
|
||||||
|
t"{x! s}"
|
||||||
|
f"{x! z}"
|
|
@ -63,13 +63,16 @@ pub enum InterpolatedStringErrorType {
|
||||||
UnterminatedTripleQuotedString,
|
UnterminatedTripleQuotedString,
|
||||||
/// A lambda expression without parentheses was encountered.
|
/// A lambda expression without parentheses was encountered.
|
||||||
LambdaWithoutParentheses,
|
LambdaWithoutParentheses,
|
||||||
|
/// Conversion flag does not immediately follow exclamation.
|
||||||
|
ConversionFlagNotImmediatelyAfterExclamation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for InterpolatedStringErrorType {
|
impl std::fmt::Display for InterpolatedStringErrorType {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
use InterpolatedStringErrorType::{
|
use InterpolatedStringErrorType::{
|
||||||
InvalidConversionFlag, LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace,
|
ConversionFlagNotImmediatelyAfterExclamation, InvalidConversionFlag,
|
||||||
UnterminatedString, UnterminatedTripleQuotedString,
|
LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace, UnterminatedString,
|
||||||
|
UnterminatedTripleQuotedString,
|
||||||
};
|
};
|
||||||
match self {
|
match self {
|
||||||
UnclosedLbrace => write!(f, "expecting '}}'"),
|
UnclosedLbrace => write!(f, "expecting '}}'"),
|
||||||
|
@ -80,6 +83,10 @@ impl std::fmt::Display for InterpolatedStringErrorType {
|
||||||
LambdaWithoutParentheses => {
|
LambdaWithoutParentheses => {
|
||||||
write!(f, "lambda expressions are not allowed without parentheses")
|
write!(f, "lambda expressions are not allowed without parentheses")
|
||||||
}
|
}
|
||||||
|
ConversionFlagNotImmediatelyAfterExclamation => write!(
|
||||||
|
f,
|
||||||
|
"conversion type must come right after the exclamation mark"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1771,6 +1771,19 @@ impl<'src> Parser<'src> {
|
||||||
let conversion = if self.eat(TokenKind::Exclamation) {
|
let conversion = if self.eat(TokenKind::Exclamation) {
|
||||||
let conversion_flag_range = self.current_token_range();
|
let conversion_flag_range = self.current_token_range();
|
||||||
if self.at(TokenKind::Name) {
|
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 {
|
let TokenValue::Name(name) = self.bump_value(TokenKind::Name) else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py
|
||||||
|
---
|
||||||
|
## AST
|
||||||
|
|
||||||
|
```
|
||||||
|
Module(
|
||||||
|
ModModule {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 0..30,
|
||||||
|
body: [
|
||||||
|
Expr(
|
||||||
|
StmtExpr {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 0..9,
|
||||||
|
value: FString(
|
||||||
|
ExprFString {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 0..9,
|
||||||
|
value: FStringValue {
|
||||||
|
inner: Single(
|
||||||
|
FString(
|
||||||
|
FString {
|
||||||
|
range: 0..9,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
elements: [
|
||||||
|
Interpolation(
|
||||||
|
InterpolatedElement {
|
||||||
|
range: 2..8,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
expression: Name(
|
||||||
|
ExprName {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 3..4,
|
||||||
|
id: Name("x"),
|
||||||
|
ctx: Load,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
debug_text: None,
|
||||||
|
conversion: Str,
|
||||||
|
format_spec: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
flags: FStringFlags {
|
||||||
|
quote_style: Double,
|
||||||
|
prefix: Regular,
|
||||||
|
triple_quoted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expr(
|
||||||
|
StmtExpr {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 10..19,
|
||||||
|
value: TString(
|
||||||
|
ExprTString {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 10..19,
|
||||||
|
value: TStringValue {
|
||||||
|
inner: Single(
|
||||||
|
TString(
|
||||||
|
TString {
|
||||||
|
range: 10..19,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
elements: [
|
||||||
|
Interpolation(
|
||||||
|
InterpolatedElement {
|
||||||
|
range: 12..18,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
expression: Name(
|
||||||
|
ExprName {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 13..14,
|
||||||
|
id: Name("x"),
|
||||||
|
ctx: Load,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
debug_text: None,
|
||||||
|
conversion: Str,
|
||||||
|
format_spec: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
flags: TStringFlags {
|
||||||
|
quote_style: Double,
|
||||||
|
prefix: Regular,
|
||||||
|
triple_quoted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expr(
|
||||||
|
StmtExpr {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 20..29,
|
||||||
|
value: FString(
|
||||||
|
ExprFString {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 20..29,
|
||||||
|
value: FStringValue {
|
||||||
|
inner: Single(
|
||||||
|
FString(
|
||||||
|
FString {
|
||||||
|
range: 20..29,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
elements: [
|
||||||
|
Interpolation(
|
||||||
|
InterpolatedElement {
|
||||||
|
range: 22..28,
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
expression: Name(
|
||||||
|
ExprName {
|
||||||
|
node_index: AtomicNodeIndex(..),
|
||||||
|
range: 23..24,
|
||||||
|
id: Name("x"),
|
||||||
|
ctx: Load,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
debug_text: None,
|
||||||
|
conversion: None,
|
||||||
|
format_spec: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
flags: FStringFlags {
|
||||||
|
quote_style: Double,
|
||||||
|
prefix: Regular,
|
||||||
|
triple_quoted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
|
|
||||||
|
1 | f"{x! s}"
|
||||||
|
| ^ Syntax Error: f-string: conversion type must come right after the exclamation mark
|
||||||
|
2 | t"{x! s}"
|
||||||
|
3 | f"{x! z}"
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
1 | f"{x! s}"
|
||||||
|
2 | t"{x! s}"
|
||||||
|
| ^ Syntax Error: t-string: conversion type must come right after the exclamation mark
|
||||||
|
3 | f"{x! z}"
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
1 | f"{x! s}"
|
||||||
|
2 | t"{x! s}"
|
||||||
|
3 | f"{x! z}"
|
||||||
|
| ^ Syntax Error: f-string: conversion type must come right after the exclamation mark
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
1 | f"{x! s}"
|
||||||
|
2 | t"{x! s}"
|
||||||
|
3 | f"{x! z}"
|
||||||
|
| ^ Syntax Error: f-string: invalid conversion character
|
||||||
|
|
|
Loading…
Add table
Add a link
Reference in a new issue