Implement template strings (#17851)

This PR implements template strings (t-strings) in the parser and
formatter for Ruff.

Minimal changes necessary to compile were made in other parts of the code (e.g. ty, the linter, etc.). These will be covered properly in follow-up PRs.
This commit is contained in:
Dylan 2025-05-30 15:00:56 -05:00 committed by GitHub
parent ad024f9a09
commit 9bbf4987e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
261 changed files with 18023 additions and 1802 deletions

View file

@ -3,7 +3,7 @@ use std::fmt::{self, Display};
use ruff_python_ast::PythonVersion;
use ruff_text_size::{Ranged, TextRange};
use crate::TokenKind;
use crate::{TokenKind, string::InterpolatedStringKind};
/// Represents represent errors that occur during parsing and are
/// returned by the `parse_*` functions.
@ -48,9 +48,9 @@ impl ParseError {
}
}
/// Represents the different types of errors that can occur during parsing of an f-string.
/// Represents the different types of errors that can occur during parsing of an f-string or t-string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FStringErrorType {
pub enum InterpolatedStringErrorType {
/// Expected a right brace after an opened left brace.
UnclosedLbrace,
/// An invalid conversion flag was encountered.
@ -65,9 +65,9 @@ pub enum FStringErrorType {
LambdaWithoutParentheses,
}
impl std::fmt::Display for FStringErrorType {
impl std::fmt::Display for InterpolatedStringErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use FStringErrorType::{
use InterpolatedStringErrorType::{
InvalidConversionFlag, LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace,
UnterminatedString, UnterminatedTripleQuotedString,
};
@ -177,12 +177,26 @@ pub enum ParseErrorType {
/// An unexpected token was found at the end of an expression parsing
UnexpectedExpressionToken,
/// An f-string error containing the [`FStringErrorType`].
FStringError(FStringErrorType),
/// An f-string error containing the [`InterpolatedStringErrorType`].
FStringError(InterpolatedStringErrorType),
/// A t-string error containing the [`InterpolatedStringErrorType`].
TStringError(InterpolatedStringErrorType),
/// Parser encountered an error during lexing.
Lexical(LexicalErrorType),
}
impl ParseErrorType {
pub(crate) fn from_interpolated_string_error(
error: InterpolatedStringErrorType,
string_kind: InterpolatedStringKind,
) -> Self {
match string_kind {
InterpolatedStringKind::FString => Self::FStringError(error),
InterpolatedStringKind::TString => Self::TStringError(error),
}
}
}
impl std::error::Error for ParseErrorType {}
impl std::fmt::Display for ParseErrorType {
@ -292,6 +306,9 @@ impl std::fmt::Display for ParseErrorType {
ParseErrorType::FStringError(fstring_error) => {
write!(f, "f-string: {fstring_error}")
}
ParseErrorType::TStringError(tstring_error) => {
write!(f, "t-string: {tstring_error}")
}
ParseErrorType::UnexpectedExpressionToken => {
write!(f, "Unexpected token at the end of an expression")
}
@ -375,8 +392,10 @@ pub enum LexicalErrorType {
IndentationError,
/// An unrecognized token was encountered.
UnrecognizedToken { tok: char },
/// An f-string error containing the [`FStringErrorType`].
FStringError(FStringErrorType),
/// An f-string error containing the [`InterpolatedStringErrorType`].
FStringError(InterpolatedStringErrorType),
/// A t-string error containing the [`InterpolatedStringErrorType`].
TStringError(InterpolatedStringErrorType),
/// Invalid character encountered in a byte literal.
InvalidByteLiteral,
/// An unexpected character was encountered after a line continuation.
@ -389,11 +408,24 @@ pub enum LexicalErrorType {
impl std::error::Error for LexicalErrorType {}
impl LexicalErrorType {
pub(crate) fn from_interpolated_string_error(
error: InterpolatedStringErrorType,
string_kind: InterpolatedStringKind,
) -> Self {
match string_kind {
InterpolatedStringKind::FString => Self::FStringError(error),
InterpolatedStringKind::TString => Self::TStringError(error),
}
}
}
impl std::fmt::Display for LexicalErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
LexicalErrorType::StringError => write!(f, "Got unexpected string"),
LexicalErrorType::FStringError(error) => write!(f, "f-string: {error}"),
LexicalErrorType::TStringError(error) => write!(f, "t-string: {error}"),
LexicalErrorType::InvalidByteLiteral => {
write!(f, "bytes can only contain ASCII literal characters")
}
@ -848,6 +880,12 @@ pub enum UnsupportedSyntaxErrorKind {
///
/// [PEP 758]: https://peps.python.org/pep-0758/
UnparenthesizedExceptionTypes,
/// Represents the use of a template string (t-string)
/// literal prior to the implementation of [PEP 750]
/// in Python 3.14.
///
/// [PEP 750]: https://peps.python.org/pep-0750/
TemplateStrings,
}
impl Display for UnsupportedSyntaxError {
@ -928,6 +966,7 @@ impl Display for UnsupportedSyntaxError {
UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
"Multiple exception types must be parenthesized"
}
UnsupportedSyntaxErrorKind::TemplateStrings => "Cannot use t-strings",
};
write!(
@ -998,6 +1037,7 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
Change::Added(PythonVersion::PY314)
}
UnsupportedSyntaxErrorKind::TemplateStrings => Change::Added(PythonVersion::PY314),
}
}

View file

@ -18,15 +18,17 @@ use ruff_python_trivia::is_python_whitespace;
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::Mode;
use crate::error::{FStringErrorType, LexicalError, LexicalErrorType};
use crate::error::{InterpolatedStringErrorType, LexicalError, LexicalErrorType};
use crate::lexer::cursor::{Cursor, EOF_CHAR};
use crate::lexer::fstring::{FStringContext, FStrings, FStringsCheckpoint};
use crate::lexer::indentation::{Indentation, Indentations, IndentationsCheckpoint};
use crate::lexer::interpolated_string::{
InterpolatedStringContext, InterpolatedStrings, InterpolatedStringsCheckpoint,
};
use crate::token::{TokenFlags, TokenKind, TokenValue};
mod cursor;
mod fstring;
mod indentation;
mod interpolated_string;
const BOM: char = '\u{feff}';
@ -65,8 +67,8 @@ pub struct Lexer<'src> {
/// Lexer mode.
mode: Mode,
/// F-string contexts.
fstrings: FStrings,
/// F-string and t-string contexts.
interpolated_strings: InterpolatedStrings,
/// Errors encountered while lexing.
errors: Vec<LexicalError>,
@ -102,7 +104,7 @@ impl<'src> Lexer<'src> {
indentations: Indentations::default(),
pending_indentation: None,
mode,
fstrings: FStrings::default(),
interpolated_strings: InterpolatedStrings::default(),
errors: Vec::new(),
};
@ -162,11 +164,11 @@ impl<'src> Lexer<'src> {
}
fn lex_token(&mut self) -> TokenKind {
if let Some(fstring) = self.fstrings.current() {
if !fstring.is_in_expression(self.nesting) {
if let Some(token) = self.lex_fstring_middle_or_end() {
if matches!(token, TokenKind::FStringEnd) {
self.fstrings.pop();
if let Some(interpolated_string) = self.interpolated_strings.current() {
if !interpolated_string.is_in_interpolation(self.nesting) {
if let Some(token) = self.lex_interpolated_string_middle_or_end() {
if token.is_interpolated_string_end() {
self.interpolated_strings.pop();
}
return token;
}
@ -506,23 +508,26 @@ impl<'src> Lexer<'src> {
TokenKind::Lbrace
}
'}' => {
if let Some(fstring) = self.fstrings.current_mut() {
if fstring.nesting() == self.nesting {
return self.push_error(LexicalError::new(
LexicalErrorType::FStringError(FStringErrorType::SingleRbrace),
self.token_range(),
));
if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
if interpolated_string.nesting() == self.nesting {
let error_type = LexicalErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::SingleRbrace,
interpolated_string.kind(),
);
return self.push_error(LexicalError::new(error_type, self.token_range()));
}
fstring.try_end_format_spec(self.nesting);
interpolated_string.try_end_format_spec(self.nesting);
}
self.nesting = self.nesting.saturating_sub(1);
TokenKind::Rbrace
}
':' => {
if self
.fstrings
.interpolated_strings
.current_mut()
.is_some_and(|fstring| fstring.try_start_format_spec(self.nesting))
.is_some_and(|interpolated_string| {
interpolated_string.try_start_format_spec(self.nesting)
})
{
TokenKind::Colon
} else if self.cursor.eat_char('=') {
@ -573,8 +578,8 @@ impl<'src> Lexer<'src> {
self.state = State::AfterNewline;
TokenKind::Newline
} else {
if let Some(fstring) = self.fstrings.current_mut() {
fstring.try_end_format_spec(self.nesting);
if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
interpolated_string.try_end_format_spec(self.nesting);
}
TokenKind::NonLogicalNewline
};
@ -586,8 +591,8 @@ impl<'src> Lexer<'src> {
self.state = State::AfterNewline;
TokenKind::Newline
} else {
if let Some(fstring) = self.fstrings.current_mut() {
fstring.try_end_format_spec(self.nesting);
if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
interpolated_string.try_end_format_spec(self.nesting);
}
TokenKind::NonLogicalNewline
};
@ -610,7 +615,7 @@ impl<'src> Lexer<'src> {
/// Lex an identifier. Also used for keywords and string/bytes literals with a prefix.
fn lex_identifier(&mut self, first: char) -> TokenKind {
// Detect potential string like rb'' b'' f'' u'' r''
// Detect potential string like rb'' b'' f'' t'' u'' r''
let quote = match (first, self.cursor.first()) {
(_, quote @ ('\'' | '"')) => self.try_single_char_prefix(first).then(|| {
self.cursor.bump();
@ -627,8 +632,10 @@ impl<'src> Lexer<'src> {
};
if let Some(quote) = quote {
if self.current_flags.is_f_string() {
return self.lex_fstring_start(quote);
if self.current_flags.is_interpolated_string() {
if let Some(kind) = self.lex_interpolated_string_start(quote) {
return kind;
}
}
return self.lex_string(quote);
@ -711,6 +718,7 @@ impl<'src> Lexer<'src> {
fn try_single_char_prefix(&mut self, first: char) -> bool {
match first {
'f' | 'F' => self.current_flags |= TokenFlags::F_STRING,
't' | 'T' => self.current_flags |= TokenFlags::T_STRING,
'u' | 'U' => self.current_flags |= TokenFlags::UNICODE_STRING,
'b' | 'B' => self.current_flags |= TokenFlags::BYTE_STRING,
'r' => self.current_flags |= TokenFlags::RAW_STRING_LOWERCASE,
@ -730,6 +738,12 @@ impl<'src> Lexer<'src> {
['R', 'f' | 'F'] | ['f' | 'F', 'R'] => {
self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_UPPERCASE;
}
['r', 't' | 'T'] | ['t' | 'T', 'r'] => {
self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_LOWERCASE;
}
['R', 't' | 'T'] | ['t' | 'T', 'R'] => {
self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_UPPERCASE;
}
['r', 'b' | 'B'] | ['b' | 'B', 'r'] => {
self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_LOWERCASE;
}
@ -741,8 +755,8 @@ impl<'src> Lexer<'src> {
true
}
/// Lex a f-string start token.
fn lex_fstring_start(&mut self, quote: char) -> TokenKind {
/// Lex a f-string or t-string start token if positioned at the start of an f-string or t-string.
fn lex_interpolated_string_start(&mut self, quote: char) -> Option<TokenKind> {
#[cfg(debug_assertions)]
debug_assert_eq!(self.cursor.previous(), quote);
@ -754,27 +768,31 @@ impl<'src> Lexer<'src> {
self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING;
}
self.fstrings
.push(FStringContext::new(self.current_flags, self.nesting));
let ftcontext = InterpolatedStringContext::new(self.current_flags, self.nesting)?;
TokenKind::FStringStart
let kind = ftcontext.kind();
self.interpolated_strings.push(ftcontext);
Some(kind.start_token())
}
/// Lex a f-string middle or end token.
fn lex_fstring_middle_or_end(&mut self) -> Option<TokenKind> {
/// Lex an f-string or t-string middle or end token.
fn lex_interpolated_string_middle_or_end(&mut self) -> Option<TokenKind> {
// SAFETY: Safe because the function is only called when `self.fstrings` is not empty.
let fstring = self.fstrings.current().unwrap();
let interpolated_string = self.interpolated_strings.current().unwrap();
let string_kind = interpolated_string.kind();
// Check if we're at the end of the f-string.
if fstring.is_triple_quoted() {
let quote_char = fstring.quote_char();
if interpolated_string.is_triple_quoted() {
let quote_char = interpolated_string.quote_char();
if self.cursor.eat_char3(quote_char, quote_char, quote_char) {
self.current_flags = fstring.flags();
return Some(TokenKind::FStringEnd);
self.current_flags = interpolated_string.flags();
return Some(string_kind.end_token());
}
} else if self.cursor.eat_char(fstring.quote_char()) {
self.current_flags = fstring.flags();
return Some(TokenKind::FStringEnd);
} else if self.cursor.eat_char(interpolated_string.quote_char()) {
self.current_flags = interpolated_string.flags();
return Some(string_kind.end_token());
}
// We have to decode `{{` and `}}` into `{` and `}` respectively. As an
@ -786,7 +804,7 @@ impl<'src> Lexer<'src> {
let mut last_offset = self.offset();
// This isn't going to change for the duration of the loop.
let in_format_spec = fstring.is_in_format_spec(self.nesting);
let in_format_spec = interpolated_string.is_in_format_spec(self.nesting);
let mut in_named_unicode = false;
@ -796,18 +814,18 @@ impl<'src> Lexer<'src> {
// in the source code and the one returned by `self.cursor.first()` when
// we reach the end of the source code.
EOF_CHAR if self.cursor.is_eof() => {
let error = if fstring.is_triple_quoted() {
FStringErrorType::UnterminatedTripleQuotedString
let error = if interpolated_string.is_triple_quoted() {
InterpolatedStringErrorType::UnterminatedTripleQuotedString
} else {
FStringErrorType::UnterminatedString
InterpolatedStringErrorType::UnterminatedString
};
self.fstrings.pop();
self.interpolated_strings.pop();
return Some(self.push_error(LexicalError::new(
LexicalErrorType::FStringError(error),
LexicalErrorType::from_interpolated_string_error(error, string_kind),
self.token_range(),
)));
}
'\n' | '\r' if !fstring.is_triple_quoted() => {
'\n' | '\r' if !interpolated_string.is_triple_quoted() => {
// If we encounter a newline while we're in a format spec, then
// we stop here and let the lexer emit the newline token.
//
@ -815,9 +833,12 @@ impl<'src> Lexer<'src> {
if in_format_spec {
break;
}
self.fstrings.pop();
self.interpolated_strings.pop();
return Some(self.push_error(LexicalError::new(
LexicalErrorType::FStringError(FStringErrorType::UnterminatedString),
LexicalErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::UnterminatedString,
string_kind,
),
self.token_range(),
)));
}
@ -827,7 +848,7 @@ impl<'src> Lexer<'src> {
// Don't consume `{` or `}` as we want them to be emitted as tokens.
// They will be handled in the next iteration.
continue;
} else if !fstring.is_raw_string() {
} else if !interpolated_string.is_raw_string() {
if self.cursor.eat_char2('N', '{') {
in_named_unicode = true;
continue;
@ -840,8 +861,8 @@ impl<'src> Lexer<'src> {
self.cursor.bump();
}
}
quote @ ('\'' | '"') if quote == fstring.quote_char() => {
if let Some(triple_quotes) = fstring.triple_quotes() {
quote @ ('\'' | '"') if quote == interpolated_string.quote_char() => {
if let Some(triple_quotes) = interpolated_string.triple_quotes() {
if self.cursor.rest().starts_with(triple_quotes) {
break;
}
@ -892,10 +913,10 @@ impl<'src> Lexer<'src> {
normalized
};
self.current_value = TokenValue::FStringMiddle(value.into_boxed_str());
self.current_value = TokenValue::InterpolatedStringMiddle(value.into_boxed_str());
self.current_flags = fstring.flags();
Some(TokenKind::FStringMiddle)
self.current_flags = interpolated_string.flags();
Some(string_kind.middle_token())
}
/// Lex a string literal.
@ -1403,9 +1424,9 @@ impl<'src> Lexer<'src> {
// i.e., it recovered from an unclosed parenthesis (`(`, `[`, or `{`).
self.nesting -= 1;
// The lexer can't be moved back for a triple-quoted f-string because the newlines are
// part of the f-string itself, so there is no newline token to be emitted.
if self.current_flags.is_triple_quoted_fstring() {
// The lexer can't be moved back for a triple-quoted f/t-string because the newlines are
// part of the f/t-string itself, so there is no newline token to be emitted.
if self.current_flags.is_triple_quoted_interpolated_string() {
return false;
}
@ -1478,7 +1499,7 @@ impl<'src> Lexer<'src> {
nesting: self.nesting,
indentations_checkpoint: self.indentations.checkpoint(),
pending_indentation: self.pending_indentation,
fstrings_checkpoint: self.fstrings.checkpoint(),
interpolated_strings_checkpoint: self.interpolated_strings.checkpoint(),
errors_position: self.errors.len(),
}
}
@ -1495,7 +1516,7 @@ impl<'src> Lexer<'src> {
nesting,
indentations_checkpoint,
pending_indentation,
fstrings_checkpoint,
interpolated_strings_checkpoint,
errors_position,
} = checkpoint;
@ -1512,7 +1533,8 @@ impl<'src> Lexer<'src> {
self.nesting = nesting;
self.indentations.rewind(indentations_checkpoint);
self.pending_indentation = pending_indentation;
self.fstrings.rewind(fstrings_checkpoint);
self.interpolated_strings
.rewind(interpolated_strings_checkpoint);
self.errors.truncate(errors_position);
}
@ -1531,7 +1553,7 @@ pub(crate) struct LexerCheckpoint {
nesting: u32,
indentations_checkpoint: IndentationsCheckpoint,
pending_indentation: Option<Indentation>,
fstrings_checkpoint: FStringsCheckpoint,
interpolated_strings_checkpoint: InterpolatedStringsCheckpoint,
errors_position: usize,
}
@ -2450,6 +2472,190 @@ f"{(lambda x:{x})}"
assert_snapshot!(lex_source(source));
}
#[test]
fn test_empty_tstrings() {
let source = r#"t"" "" t"" t'' '' t"""""" t''''''"#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_prefix() {
let source = r#"t"" t"" rt"" rt"" Rt"" Rt"" tr"" Tr"" tR"" TR"""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring() {
let source = r#"t"normal {foo} {{another}} {bar} {{{three}}}""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_parentheses() {
let source = r#"t"{}" t"{{}}" t" {}" t"{{{}}}" t"{{{{}}}}" t" {} {{}} {{{}}} {{{{}}}} ""#;
assert_snapshot!(lex_source(source));
}
fn tstring_single_quote_escape_eol(eol: &str) -> LexerOutput {
let source = format!(r"t'text \{eol} more text'");
lex_source(&source)
}
#[test]
fn test_tstring_single_quote_escape_unix_eol() {
assert_snapshot!(tstring_single_quote_escape_eol(UNIX_EOL));
}
#[test]
fn test_tstring_single_quote_escape_mac_eol() {
assert_snapshot!(tstring_single_quote_escape_eol(MAC_EOL));
}
#[test]
fn test_tstring_single_quote_escape_windows_eol() {
assert_snapshot!(tstring_single_quote_escape_eol(WINDOWS_EOL));
}
#[test]
fn test_tstring_escape() {
let source = r#"t"\{x:\"\{x}} \"\"\
end""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_escape_braces() {
let source = r"t'\{foo}' t'\\{foo}' t'\{{foo}}' t'\\{{foo}}'";
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_escape_raw() {
let source = r#"rt"\{x:\"\{x}} \"\"\
end""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_named_unicode() {
let source = r#"t"\N{BULLET} normal \Nope \N""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_named_unicode_raw() {
let source = r#"rt"\N{BULLET} normal""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_named_expression() {
let source = r#"t"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_format_spec() {
let source = r#"t"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_multiline_format_spec() {
// The last t-string is invalid syntactically but we should still lex it.
// Note that the `b` is a `Name` token and not a `TStringMiddle` token.
let source = r"t'''__{
x:d
}__'''
t'''__{
x:a
b
c
}__'''
t'__{
x:d
}__'
t'__{
x:a
b
}__'
";
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_conversion() {
let source = r#"t"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_nested() {
let source = r#"t"foo {t"bar {x + t"{wow}"}"} baz" t'foo {t'bar'} some {t"another"}'"#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_expression_multiline() {
let source = r#"t"first {
x
*
y
} second""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_multiline() {
let source = r#"t"""
hello
world
""" t'''
world
hello
''' t"some {t"""multiline
allowed {x}"""} string""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_comments() {
let source = r#"t"""
# not a comment { # comment {
x
} # not a comment
""""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_ipy_escape_command() {
let source = r#"t"foo {!pwd} bar""#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_lambda_expression() {
let source = r#"
t"{lambda x:{x}}"
t"{(lambda x:{x})}"
"#
.trim();
assert_snapshot!(lex_source(source));
}
#[test]
fn test_tstring_with_nul_char() {
let source = r"t'\0'";
assert_snapshot!(lex_source(source));
}
#[test]
fn test_nested_t_and_fstring() {
let source = r#"t"foo {f"bar {x + t"{wow}"}"} baz" f'foo {t'bar'!r} some {f"another"}'"#;
assert_snapshot!(lex_source(source));
}
#[test]
fn test_match_softkeyword_in_notebook() {
let source = r"match foo:
@ -2458,7 +2664,7 @@ f"{(lambda x:{x})}"
assert_snapshot!(lex_jupyter_source(source));
}
fn lex_fstring_error(source: &str) -> FStringErrorType {
fn lex_fstring_error(source: &str) -> InterpolatedStringErrorType {
let output = lex(source, Mode::Module, TextSize::default());
match output
.errors
@ -2474,7 +2680,9 @@ f"{(lambda x:{x})}"
#[test]
fn test_fstring_error() {
use FStringErrorType::{SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString};
use InterpolatedStringErrorType::{
SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
};
assert_eq!(lex_fstring_error("f'}'"), SingleRbrace);
assert_eq!(lex_fstring_error("f'{{}'"), SingleRbrace);
@ -2499,4 +2707,48 @@ f"{(lambda x:{x})}"
UnterminatedTripleQuotedString
);
}
fn lex_tstring_error(source: &str) -> InterpolatedStringErrorType {
let output = lex(source, Mode::Module, TextSize::default());
match output
.errors
.into_iter()
.next()
.expect("lexer should give at least one error")
.into_error()
{
LexicalErrorType::TStringError(error) => error,
err => panic!("Expected TStringError: {err:?}"),
}
}
#[test]
fn test_tstring_error() {
use InterpolatedStringErrorType::{
SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
};
assert_eq!(lex_tstring_error("t'}'"), SingleRbrace);
assert_eq!(lex_tstring_error("t'{{}'"), SingleRbrace);
assert_eq!(lex_tstring_error("t'{{}}}'"), SingleRbrace);
assert_eq!(lex_tstring_error("t'foo}'"), SingleRbrace);
assert_eq!(lex_tstring_error(r"t'\u007b}'"), SingleRbrace);
assert_eq!(lex_tstring_error("t'{a:b}}'"), SingleRbrace);
assert_eq!(lex_tstring_error("t'{3:}}>10}'"), SingleRbrace);
assert_eq!(lex_tstring_error(r"t'\{foo}\}'"), SingleRbrace);
assert_eq!(lex_tstring_error(r#"t""#), UnterminatedString);
assert_eq!(lex_tstring_error(r"t'"), UnterminatedString);
assert_eq!(lex_tstring_error(r#"t""""#), UnterminatedTripleQuotedString);
assert_eq!(lex_tstring_error(r"t'''"), UnterminatedTripleQuotedString);
assert_eq!(
lex_tstring_error(r#"t"""""#),
UnterminatedTripleQuotedString
);
assert_eq!(
lex_tstring_error(r#"t""""""#),
UnterminatedTripleQuotedString
);
}
}

View file

@ -1,31 +1,45 @@
use ruff_python_ast::StringFlags;
use crate::string::InterpolatedStringKind;
use super::TokenFlags;
/// The context representing the current f-string that the lexer is in.
/// The context representing the current f-string or t-string that the lexer is in.
#[derive(Clone, Debug)]
pub(crate) struct FStringContext {
pub(crate) struct InterpolatedStringContext {
flags: TokenFlags,
/// The level of nesting for the lexer when it entered the current f-string.
/// The level of nesting for the lexer when it entered the current f/t-string.
/// The nesting level includes all kinds of parentheses i.e., round, square,
/// and curly.
nesting: u32,
/// The current depth of format spec for the current f-string. This is because
/// The current depth of format spec for the current f/t-string. This is because
/// there can be multiple format specs nested for the same f-string.
/// For example, `{a:{b:{c}}}` has 3 format specs.
format_spec_depth: u32,
}
impl FStringContext {
pub(crate) const fn new(flags: TokenFlags, nesting: u32) -> Self {
assert!(flags.is_f_string());
impl InterpolatedStringContext {
pub(crate) const fn new(flags: TokenFlags, nesting: u32) -> Option<Self> {
if flags.is_interpolated_string() {
Some(Self {
flags,
nesting,
format_spec_depth: 0,
})
} else {
None
}
}
Self {
flags,
nesting,
format_spec_depth: 0,
pub(crate) fn kind(&self) -> InterpolatedStringKind {
if self.flags.is_f_string() {
InterpolatedStringKind::FString
} else if self.flags.is_t_string() {
InterpolatedStringKind::TString
} else {
unreachable!("Can only be constructed when f-string or t-string flag is present")
}
}
@ -68,15 +82,15 @@ impl FStringContext {
current_nesting.saturating_sub(self.nesting)
}
/// Returns `true` if the lexer is in a f-string expression i.e., between
/// Returns `true` if the lexer is in an f-string expression or t-string interpolation i.e., between
/// two curly braces.
pub(crate) const fn is_in_expression(&self, current_nesting: u32) -> bool {
pub(crate) const fn is_in_interpolation(&self, current_nesting: u32) -> bool {
self.open_parentheses_count(current_nesting) > self.format_spec_depth
}
/// Returns `true` if the lexer is in a f-string format spec i.e., after a colon.
pub(crate) const fn is_in_format_spec(&self, current_nesting: u32) -> bool {
self.format_spec_depth > 0 && !self.is_in_expression(current_nesting)
self.format_spec_depth > 0 && !self.is_in_interpolation(current_nesting)
}
/// Returns `true` if the context is in a valid position to start format spec
@ -106,38 +120,38 @@ impl FStringContext {
}
}
/// The f-strings stack is used to keep track of all the f-strings that the
/// lexer encounters. This is necessary because f-strings can be nested.
/// The interpolated strings stack is used to keep track of all the f-strings and t-strings that the
/// lexer encounters. This is necessary because f-strings and t-strings can be nested.
#[derive(Debug, Default)]
pub(crate) struct FStrings {
stack: Vec<FStringContext>,
pub(crate) struct InterpolatedStrings {
stack: Vec<InterpolatedStringContext>,
}
impl FStrings {
pub(crate) fn push(&mut self, context: FStringContext) {
impl InterpolatedStrings {
pub(crate) fn push(&mut self, context: InterpolatedStringContext) {
self.stack.push(context);
}
pub(crate) fn pop(&mut self) -> Option<FStringContext> {
pub(crate) fn pop(&mut self) -> Option<InterpolatedStringContext> {
self.stack.pop()
}
pub(crate) fn current(&self) -> Option<&FStringContext> {
pub(crate) fn current(&self) -> Option<&InterpolatedStringContext> {
self.stack.last()
}
pub(crate) fn current_mut(&mut self) -> Option<&mut FStringContext> {
pub(crate) fn current_mut(&mut self) -> Option<&mut InterpolatedStringContext> {
self.stack.last_mut()
}
pub(crate) fn checkpoint(&self) -> FStringsCheckpoint {
FStringsCheckpoint(self.stack.clone())
pub(crate) fn checkpoint(&self) -> InterpolatedStringsCheckpoint {
InterpolatedStringsCheckpoint(self.stack.clone())
}
pub(crate) fn rewind(&mut self, checkpoint: FStringsCheckpoint) {
pub(crate) fn rewind(&mut self, checkpoint: InterpolatedStringsCheckpoint) {
self.stack = checkpoint.0;
}
}
#[derive(Debug, Clone)]
pub(crate) struct FStringsCheckpoint(Vec<FStringContext>);
pub(crate) struct InterpolatedStringsCheckpoint(Vec<InterpolatedStringContext>);

View file

@ -67,8 +67,8 @@ use std::iter::FusedIterator;
use std::ops::Deref;
pub use crate::error::{
FStringErrorType, LexicalErrorType, ParseError, ParseErrorType, UnsupportedSyntaxError,
UnsupportedSyntaxErrorKind,
InterpolatedStringErrorType, LexicalErrorType, ParseError, ParseErrorType,
UnsupportedSyntaxError, UnsupportedSyntaxErrorKind,
};
pub use crate::parser::ParseOptions;
pub use crate::token::{Token, TokenKind};

View file

@ -6,22 +6,27 @@ 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, OperatorPrecedence, StringFlags, UnaryOp,
self as ast, AnyStringFlags, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FString,
InterpolatedStringElement, InterpolatedStringElements, IpyEscapeKind, Number, Operator,
OperatorPrecedence, StringFlags, TString, UnaryOp,
};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::error::{FStringKind, StarTupleKind, UnparenthesizedNamedExprKind};
use crate::parser::progress::ParserProgress;
use crate::parser::{FunctionKind, Parser, helpers};
use crate::string::{StringType, parse_fstring_literal_element, parse_string_literal};
use crate::string::{
InterpolatedStringKind, StringType, parse_interpolated_string_literal_element,
parse_string_literal,
};
use crate::token::{TokenKind, TokenValue};
use crate::token_set::TokenSet;
use crate::{
FStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxError, UnsupportedSyntaxErrorKind,
InterpolatedStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxError,
UnsupportedSyntaxErrorKind,
};
use super::{FStringElementsKind, Parenthesized, RecoveryContextKind};
use super::{InterpolatedStringElementsKind, Parenthesized, RecoveryContextKind};
/// A token set consisting of a newline or end of file.
const NEWLINE_EOF_SET: TokenSet = TokenSet::new([TokenKind::Newline, TokenKind::EndOfFile]);
@ -54,6 +59,7 @@ pub(super) const EXPR_SET: TokenSet = TokenSet::new([
TokenKind::Not,
TokenKind::Yield,
TokenKind::FStringStart,
TokenKind::TStringStart,
TokenKind::IpyEscapeCommand,
])
.union(LITERAL_SET);
@ -581,7 +587,9 @@ impl<'src> Parser<'src> {
TokenKind::IpyEscapeCommand => {
Expr::IpyEscapeCommand(self.parse_ipython_escape_command_expression())
}
TokenKind::String | TokenKind::FStringStart => self.parse_strings(),
TokenKind::String | TokenKind::FStringStart | TokenKind::TStringStart => {
self.parse_strings()
}
TokenKind::Lpar => {
return self.parse_parenthesized_expression();
}
@ -1177,12 +1185,15 @@ impl<'src> Parser<'src> {
///
/// # Panics
///
/// If the parser isn't positioned at a `String` or `FStringStart` token.
/// If the parser isn't positioned at a `String`, `FStringStart`, or `TStringStart` token.
///
/// See: <https://docs.python.org/3/reference/grammar.html> (Search "strings:")
pub(super) fn parse_strings(&mut self) -> Expr {
const STRING_START_SET: TokenSet =
TokenSet::new([TokenKind::String, TokenKind::FStringStart]);
const STRING_START_SET: TokenSet = TokenSet::new([
TokenKind::String,
TokenKind::FStringStart,
TokenKind::TStringStart,
]);
let start = self.node_start();
let mut strings = vec![];
@ -1194,8 +1205,16 @@ impl<'src> Parser<'src> {
if self.at(TokenKind::String) {
strings.push(self.parse_string_or_byte_literal());
} else {
strings.push(StringType::FString(self.parse_fstring()));
} else if self.at(TokenKind::FStringStart) {
strings.push(StringType::FString(
self.parse_interpolated_string(InterpolatedStringKind::FString)
.into(),
));
} else if self.at(TokenKind::TStringStart) {
strings.push(StringType::TString(
self.parse_interpolated_string(InterpolatedStringKind::TString)
.into(),
));
}
}
@ -1219,6 +1238,10 @@ impl<'src> Parser<'src> {
value: ast::FStringValue::single(fstring),
range,
}),
StringType::TString(tstring) => Expr::TString(ast::ExprTString {
value: ast::TStringValue::single(tstring),
range,
}),
},
_ => self.handle_implicitly_concatenated_strings(strings, range),
}
@ -1236,11 +1259,13 @@ impl<'src> Parser<'src> {
) -> Expr {
assert!(strings.len() > 1);
let mut has_tstring = false;
let mut has_fstring = false;
let mut byte_literal_count = 0;
for string in &strings {
match string {
StringType::FString(_) => has_fstring = true,
StringType::TString(_) => has_tstring = true,
StringType::Bytes(_) => byte_literal_count += 1,
StringType::Str(_) => {}
}
@ -1269,7 +1294,7 @@ impl<'src> Parser<'src> {
);
}
// Only construct a byte expression if all the literals are bytes
// otherwise, we'll try either string or f-string. This is to retain
// otherwise, we'll try either string, t-string, or f-string. This is to retain
// as much information as possible.
Ordering::Equal => {
let mut values = Vec::with_capacity(strings.len());
@ -1310,7 +1335,7 @@ impl<'src> Parser<'src> {
// )
// 2 + 2
if !has_fstring {
if !has_fstring && !has_tstring {
let mut values = Vec::with_capacity(strings.len());
for string in strings {
values.push(match string {
@ -1324,10 +1349,34 @@ impl<'src> Parser<'src> {
});
}
if has_tstring {
let mut parts = Vec::with_capacity(strings.len());
for string in strings {
match string {
StringType::TString(tstring) => parts.push(ast::TStringPart::TString(tstring)),
StringType::FString(fstring) => {
parts.push(ruff_python_ast::TStringPart::FString(fstring));
}
StringType::Str(string) => parts.push(ast::TStringPart::Literal(string)),
StringType::Bytes(bytes) => parts.push(ast::TStringPart::Literal(
ast::StringLiteral::invalid(bytes.range()),
)),
}
}
return Expr::from(ast::ExprTString {
value: ast::TStringValue::concatenated(parts),
range,
});
}
let mut parts = Vec::with_capacity(strings.len());
for string in strings {
match string {
StringType::FString(fstring) => parts.push(ast::FStringPart::FString(fstring)),
StringType::TString(_) => {
unreachable!("expected no tstring parts by this point")
}
StringType::Str(string) => parts.push(ast::FStringPart::Literal(string)),
StringType::Bytes(bytes) => parts.push(ast::FStringPart::Literal(
ast::StringLiteral::invalid(bytes.range()),
@ -1388,24 +1437,32 @@ impl<'src> Parser<'src> {
}
}
/// Parses a f-string.
/// Parses an f/t-string.
///
/// This does not handle implicitly concatenated strings.
///
/// # Panics
///
/// If the parser isn't positioned at a `FStringStart` token.
/// If the parser isn't positioned at an `FStringStart` or
/// `TStringStart` token.
///
/// See: <https://docs.python.org/3/reference/grammar.html> (Search "fstring:")
/// See: <https://docs.python.org/3/reference/grammar.html> (Search "fstring:" or "tstring:")
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals>
fn parse_fstring(&mut self) -> ast::FString {
fn parse_interpolated_string(
&mut self,
kind: InterpolatedStringKind,
) -> InterpolatedStringData {
let start = self.node_start();
let flags = self.tokens.current_flags().as_any_string_flags();
self.bump(TokenKind::FStringStart);
let elements = self.parse_fstring_elements(flags, FStringElementsKind::Regular);
self.bump(kind.start_token());
let elements = self.parse_interpolated_string_elements(
flags,
InterpolatedStringElementsKind::Regular,
kind,
);
self.expect(TokenKind::FStringEnd);
self.expect(kind.end_token());
// test_ok pep701_f_string_py312
// # parse_options: {"target-version": "3.12"}
@ -1419,6 +1476,18 @@ impl<'src> Parser<'src> {
// f"test {a \
// } more" # line continuation
// test_ok pep750_t_string_py314
// # parse_options: {"target-version": "3.14"}
// t'Magic wand: { bag['wand'] }' # nested quotes
// t"{'\n'.join(a)}" # escape sequence
// t'''A complex trick: {
// bag['bag'] # comment
// }'''
// t"{t"{t"{t"{t"{t"{1+1}"}"}"}"}"}" # arbitrary nesting
// t"{t'''{"nested"} inner'''} outer" # nested (triple) quotes
// t"test {a \
// } more" # line continuation
// test_ok pep701_f_string_py311
// # parse_options: {"target-version": "3.11"}
// f"outer {'# not a comment'}"
@ -1444,10 +1513,12 @@ impl<'src> Parser<'src> {
let range = self.node_range(start);
if !self.options.target_version.supports_pep_701() {
if !self.options.target_version.supports_pep_701()
&& matches!(kind, InterpolatedStringKind::FString)
{
let quote_bytes = flags.quote_str().as_bytes();
let quote_len = flags.quote_len();
for expr in elements.expressions() {
for expr in elements.interpolations() {
for slash_position in memchr::memchr_iter(b'\\', self.source[expr.range].as_bytes())
{
let slash_position = TextSize::try_from(slash_position).unwrap();
@ -1471,10 +1542,10 @@ impl<'src> Parser<'src> {
self.check_fstring_comments(range);
}
ast::FString {
InterpolatedStringData {
elements,
range,
flags: ast::FStringFlags::from(flags),
flags,
}
}
@ -1490,80 +1561,87 @@ impl<'src> Parser<'src> {
}));
}
/// Parses a list of f-string elements.
/// Parses a list of f/t-string elements.
///
/// # Panics
///
/// If the parser isn't positioned at a `{` or `FStringMiddle` token.
fn parse_fstring_elements(
/// If the parser isn't positioned at a `{`, `FStringMiddle`,
/// or `TStringMiddle` token.
fn parse_interpolated_string_elements(
&mut self,
flags: ast::AnyStringFlags,
kind: FStringElementsKind,
) -> FStringElements {
elements_kind: InterpolatedStringElementsKind,
string_kind: InterpolatedStringKind,
) -> ast::InterpolatedStringElements {
let mut elements = vec![];
let middle_token_kind = string_kind.middle_token();
self.parse_list(RecoveryContextKind::FStringElements(kind), |parser| {
let element = match parser.current_token_kind() {
TokenKind::Lbrace => {
FStringElement::Expression(parser.parse_fstring_expression_element(flags))
}
TokenKind::FStringMiddle => {
let range = parser.current_token_range();
let TokenValue::FStringMiddle(value) =
parser.bump_value(TokenKind::FStringMiddle)
else {
unreachable!()
};
FStringElement::Literal(
parse_fstring_literal_element(value, flags, range).unwrap_or_else(
|lex_error| {
// test_err invalid_fstring_literal_element
// f'hello \N{INVALID} world'
// f"""hello \N{INVALID} world"""
let location = lex_error.location();
parser.add_error(
ParseErrorType::Lexical(lex_error.into_error()),
location,
);
ast::FStringLiteralElement {
value: "".into(),
range,
}
},
),
)
}
// `Invalid` tokens are created when there's a lexical error, so
// we ignore it here to avoid creating unexpected token errors
TokenKind::Unknown => {
parser.bump_any();
return;
}
tok => {
// This should never happen because the list parsing will only
// call this closure for the above token kinds which are the same
// as in the FIRST set.
unreachable!(
"f-string: unexpected token `{tok:?}` at {:?}",
parser.current_token_range()
);
}
};
elements.push(element);
});
self.parse_list(
RecoveryContextKind::InterpolatedStringElements(elements_kind),
|parser| {
let element = match parser.current_token_kind() {
TokenKind::Lbrace => ast::InterpolatedStringElement::from(
parser.parse_interpolated_element(flags, string_kind),
),
tok if tok == middle_token_kind => {
let range = parser.current_token_range();
let TokenValue::InterpolatedStringMiddle(value) =
parser.bump_value(middle_token_kind)
else {
unreachable!()
};
InterpolatedStringElement::Literal(
parse_interpolated_string_literal_element(value, flags, range)
.unwrap_or_else(|lex_error| {
// test_err invalid_fstring_literal_element
// f'hello \N{INVALID} world'
// f"""hello \N{INVALID} world"""
let location = lex_error.location();
parser.add_error(
ParseErrorType::Lexical(lex_error.into_error()),
location,
);
ast::InterpolatedStringLiteralElement {
value: "".into(),
range,
}
}),
)
}
// `Invalid` tokens are created when there's a lexical error, so
// we ignore it here to avoid creating unexpected token errors
TokenKind::Unknown => {
parser.bump_any();
return;
}
tok => {
// This should never happen because the list parsing will only
// call this closure for the above token kinds which are the same
// as in the FIRST set.
unreachable!(
"{}: unexpected token `{tok:?}` at {:?}",
string_kind,
parser.current_token_range()
);
}
};
elements.push(element);
},
);
FStringElements::from(elements)
ast::InterpolatedStringElements::from(elements)
}
/// Parses a f-string expression element.
/// Parses an f/t-string expression element.
///
/// # Panics
///
/// If the parser isn't positioned at a `{` token.
fn parse_fstring_expression_element(
fn parse_interpolated_element(
&mut self,
flags: ast::AnyStringFlags,
) -> ast::FStringExpressionElement {
string_kind: InterpolatedStringKind,
) -> ast::InterpolatedElement {
let start = self.node_start();
self.bump(TokenKind::Lbrace);
@ -1571,11 +1649,23 @@ impl<'src> Parser<'src> {
// f"{}"
// f"{ }"
// test_err t_string_empty_expression
// # parse_options: {"target-version": "3.14"}
// t"{}"
// t"{ }"
// test_err f_string_invalid_starred_expr
// # Starred expression inside f-string has a minimum precedence of bitwise or.
// f"{*}"
// f"{*x and y}"
// f"{*yield x}"
// test_err t_string_invalid_starred_expr
// # parse_options: {"target-version": "3.14"}
// # Starred expression inside t-string has a minimum precedence of bitwise or.
// t"{*}"
// t"{*x and y}"
// t"{*yield x}"
let value = self.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or());
if !value.is_parenthesized && value.expr.is_lambda_expr() {
@ -1585,8 +1675,15 @@ impl<'src> Parser<'src> {
// test_err f_string_lambda_without_parentheses
// f"{lambda x: x}"
// test_err t_string_lambda_without_parentheses
// # parse_options: {"target-version": "3.14"}
// t"{lambda x: x}"
self.add_error(
ParseErrorType::FStringError(FStringErrorType::LambdaWithoutParentheses),
ParseErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::LambdaWithoutParentheses,
string_kind,
),
value.range(),
);
}
@ -1614,8 +1711,15 @@ impl<'src> Parser<'src> {
_ => {
// test_err f_string_invalid_conversion_flag_name_tok
// f"{x!z}"
// test_err t_string_invalid_conversion_flag_name_tok
// # parse_options: {"target-version": "3.14"}
// t"{x!z}"
self.add_error(
ParseErrorType::FStringError(FStringErrorType::InvalidConversionFlag),
ParseErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::InvalidConversionFlag,
string_kind,
),
conversion_flag_range,
);
ConversionFlag::None
@ -1625,8 +1729,16 @@ impl<'src> Parser<'src> {
// test_err f_string_invalid_conversion_flag_other_tok
// f"{x!123}"
// f"{x!'a'}"
// test_err t_string_invalid_conversion_flag_other_tok
// # parse_options: {"target-version": "3.14"}
// t"{x!123}"
// t"{x!'a'}"
self.add_error(
ParseErrorType::FStringError(FStringErrorType::InvalidConversionFlag),
ParseErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::InvalidConversionFlag,
string_kind,
),
conversion_flag_range,
);
// TODO(dhruvmanila): Avoid dropping this token
@ -1639,8 +1751,12 @@ impl<'src> Parser<'src> {
let format_spec = if self.eat(TokenKind::Colon) {
let spec_start = self.node_start();
let elements = self.parse_fstring_elements(flags, FStringElementsKind::FormatSpec);
Some(Box::new(ast::FStringFormatSpec {
let elements = self.parse_interpolated_string_elements(
flags,
InterpolatedStringElementsKind::FormatSpec,
string_kind,
);
Some(Box::new(ast::InterpolatedStringFormatSpec {
range: self.node_range(spec_start),
elements,
}))
@ -1661,18 +1777,34 @@ impl<'src> Parser<'src> {
// f"{"
// f"""{"""
// test_err t_string_unclosed_lbrace
// # parse_options: {"target-version": "3.14"}
// t"{"
// t"{foo!r"
// t"{foo="
// t"{"
// t"""{"""
// The lexer does emit `FStringEnd` for the following test cases:
// test_err f_string_unclosed_lbrace_in_format_spec
// f"hello {x:"
// f"hello {x:.3f"
// test_err t_string_unclosed_lbrace_in_format_spec
// # parse_options: {"target-version": "3.14"}
// t"hello {x:"
// t"hello {x:.3f"
self.add_error(
ParseErrorType::FStringError(FStringErrorType::UnclosedLbrace),
ParseErrorType::from_interpolated_string_error(
InterpolatedStringErrorType::UnclosedLbrace,
string_kind,
),
self.current_token_range(),
);
}
ast::FStringExpressionElement {
ast::InterpolatedElement {
expression: Box::new(value.expr),
debug_text,
conversion,
@ -2755,3 +2887,30 @@ impl ExpressionContext {
}
}
}
#[derive(Debug)]
struct InterpolatedStringData {
elements: InterpolatedStringElements,
range: TextRange,
flags: AnyStringFlags,
}
impl From<InterpolatedStringData> for FString {
fn from(value: InterpolatedStringData) -> Self {
Self {
elements: value.elements,
range: value.range,
flags: value.flags.into(),
}
}
}
impl From<InterpolatedStringData> for TString {
fn from(value: InterpolatedStringData) -> Self {
Self {
elements: value.elements,
range: value.range,
flags: value.flags.into(),
}
}
}

View file

@ -94,6 +94,7 @@ pub(super) fn detect_invalid_pre_py39_decorator_node(
Expr::YieldFrom(_) => "`yield from` expression",
Expr::Compare(_) => "comparison expression",
Expr::FString(_) => "f-string",
Expr::TString(_) => "t-string",
Expr::Named(_) => "assignment expression",
Expr::Subscript(_) => "subscript expression",
Expr::IpyEscapeCommand(_) => "IPython escape command",

View file

@ -798,7 +798,7 @@ impl WithItemKind {
}
#[derive(Debug, PartialEq, Copy, Clone)]
enum FStringElementsKind {
enum InterpolatedStringElementsKind {
/// The regular f-string elements.
///
/// For example, the `"hello "`, `x`, and `" world"` elements in:
@ -816,14 +816,16 @@ enum FStringElementsKind {
FormatSpec,
}
impl FStringElementsKind {
const fn list_terminator(self) -> TokenKind {
impl InterpolatedStringElementsKind {
const fn list_terminators(self) -> TokenSet {
match self {
FStringElementsKind::Regular => TokenKind::FStringEnd,
InterpolatedStringElementsKind::Regular => {
TokenSet::new([TokenKind::FStringEnd, TokenKind::TStringEnd])
}
// test_ok fstring_format_spec_terminator
// f"hello {x:} world"
// f"hello {x:.3f} world"
FStringElementsKind::FormatSpec => TokenKind::Rbrace,
InterpolatedStringElementsKind::FormatSpec => TokenSet::new([TokenKind::Rbrace]),
}
}
}
@ -931,9 +933,8 @@ enum RecoveryContextKind {
/// When parsing a list of items in a `with` statement
WithItems(WithItemKind),
/// When parsing a list of f-string elements which are either literal elements
/// or expressions.
FStringElements(FStringElementsKind),
/// When parsing a list of f-string or t-string elements which are either literal elements, expressions, or interpolations.
InterpolatedStringElements(InterpolatedStringElementsKind),
}
impl RecoveryContextKind {
@ -1117,8 +1118,8 @@ impl RecoveryContextKind {
.at(TokenKind::Colon)
.then_some(ListTerminatorKind::Regular),
},
RecoveryContextKind::FStringElements(kind) => {
if p.at(kind.list_terminator()) {
RecoveryContextKind::InterpolatedStringElements(kind) => {
if p.at_ts(kind.list_terminators()) {
Some(ListTerminatorKind::Regular)
} else {
// test_err unterminated_fstring_newline_recovery
@ -1174,10 +1175,10 @@ impl RecoveryContextKind {
) || p.at_name_or_soft_keyword()
}
RecoveryContextKind::WithItems(_) => p.at_expr(),
RecoveryContextKind::FStringElements(_) => matches!(
RecoveryContextKind::InterpolatedStringElements(_) => matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
),
@ -1268,13 +1269,13 @@ impl RecoveryContextKind {
"Expected an expression or the end of the with item list".to_string(),
),
},
RecoveryContextKind::FStringElements(kind) => match kind {
FStringElementsKind::Regular => ParseErrorType::OtherError(
"Expected an f-string element or the end of the f-string".to_string(),
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::FormatSpec => ParseErrorType::OtherError(
"Expected an f-string or t-string element or a '}'".to_string(),
),
FStringElementsKind::FormatSpec => {
ParseErrorType::OtherError("Expected an f-string element or a '}'".to_string())
}
},
}
}
@ -1313,8 +1314,8 @@ bitflags! {
const WITH_ITEMS_PARENTHESIZED = 1 << 25;
const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26;
const WITH_ITEMS_UNPARENTHESIZED = 1 << 28;
const F_STRING_ELEMENTS = 1 << 29;
const F_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
const FT_STRING_ELEMENTS = 1 << 29;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
}
}
@ -1367,10 +1368,10 @@ impl RecoveryContext {
}
WithItemKind::Unparenthesized => RecoveryContext::WITH_ITEMS_UNPARENTHESIZED,
},
RecoveryContextKind::FStringElements(kind) => match kind {
FStringElementsKind::Regular => RecoveryContext::F_STRING_ELEMENTS,
FStringElementsKind::FormatSpec => {
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC
RecoveryContextKind::InterpolatedStringElements(kind) => match kind {
InterpolatedStringElementsKind::Regular => RecoveryContext::FT_STRING_ELEMENTS,
InterpolatedStringElementsKind::FormatSpec => {
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC
}
},
}
@ -1439,11 +1440,13 @@ impl RecoveryContext {
RecoveryContext::WITH_ITEMS_UNPARENTHESIZED => {
RecoveryContextKind::WithItems(WithItemKind::Unparenthesized)
}
RecoveryContext::F_STRING_ELEMENTS => {
RecoveryContextKind::FStringElements(FStringElementsKind::Regular)
}
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::FStringElements(FStringElementsKind::FormatSpec)
RecoveryContext::FT_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular,
),
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::FormatSpec,
)
}
_ => return None,
})

View file

@ -390,7 +390,7 @@ impl Parser<'_> {
range: self.node_range(start),
})
}
TokenKind::String | TokenKind::FStringStart => {
TokenKind::String | TokenKind::FStringStart | TokenKind::TStringStart => {
let str = self.parse_strings();
Pattern::MatchValue(ast::PatternMatchValue {

View file

@ -3012,7 +3012,6 @@ impl<'src> Parser<'src> {
// test_ok param_with_annotation
// def foo(arg: int): ...
// def foo(arg: lambda x: x): ...
// def foo(arg: (x := int)): ...
// test_err param_with_invalid_annotation
// def foo(arg: *int): ...
@ -3703,6 +3702,7 @@ impl<'src> Parser<'src> {
| TokenKind::Complex
| TokenKind::String
| TokenKind::FStringStart
| TokenKind::TStringStart
| TokenKind::Lbrace
| TokenKind::Tilde
| TokenKind::Ellipsis

View file

@ -0,0 +1,98 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
2..3,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
String(
"",
),
4..6,
TokenFlags(
DOUBLE_QUOTES,
),
),
(
TStringStart,
7..9,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
9..10,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
11..13,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
13..14,
TokenFlags(
T_STRING,
),
),
(
String(
"",
),
15..17,
),
(
TStringStart,
18..22,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
22..25,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringStart,
26..30,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
30..33,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Newline,
33..33,
),
]
```

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"normal ",
),
2..9,
@ -37,7 +36,7 @@ snapshot_kind: text
13..14,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" {another} ",
),
14..27,
@ -60,7 +59,7 @@ snapshot_kind: text
31..32,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" {",
),
32..35,
@ -83,7 +82,7 @@ snapshot_kind: text
41..42,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"}",
),
42..44,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\n# not a comment ",
),
4..21,
@ -49,7 +48,7 @@ snapshot_kind: text
41..42,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" # not a comment\n",
),
42..59,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -38,7 +37,7 @@ snapshot_kind: text
6..7,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
7..8,
@ -75,7 +74,7 @@ snapshot_kind: text
13..14,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
14..15,
@ -98,7 +97,7 @@ snapshot_kind: text
17..18,
),
(
FStringMiddle(
InterpolatedStringMiddle(
".3f!r",
),
18..23,
@ -111,7 +110,7 @@ snapshot_kind: text
23..24,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" {x!r}",
),
24..32,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\",
),
2..3,
@ -37,7 +36,7 @@ snapshot_kind: text
5..6,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\\"\\",
),
6..9,
@ -64,7 +63,7 @@ snapshot_kind: text
12..13,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" \\\"\\\"\\\n end",
),
13..24,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\",
),
2..3,
@ -51,7 +50,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\\\",
),
12..14,
@ -88,7 +87,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\{foo}",
),
23..31,
@ -111,7 +110,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\\\{foo}",
),
35..44,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\",
),
3..4,
@ -37,7 +36,7 @@ snapshot_kind: text
6..7,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\\"\\",
),
7..10,
@ -64,7 +63,7 @@ snapshot_kind: text
13..14,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" \\\"\\\"\\\n end",
),
14..25,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"first ",
),
2..8,
@ -63,7 +62,7 @@ snapshot_kind: text
40..41,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" second",
),
41..48,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\nhello\n world\n",
),
4..21,
@ -37,7 +36,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\n world\nhello\n",
),
29..46,
@ -60,7 +59,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"some ",
),
52..57,
@ -80,7 +79,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"multiline\nallowed ",
),
62..80,
@ -114,7 +113,7 @@ snapshot_kind: text
86..87,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" string",
),
87..94,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\N{BULLET} normal \\Nope \\N",
),
2..28,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\N",
),
3..5,
@ -37,7 +36,7 @@ snapshot_kind: text
12..13,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" normal",
),
13..20,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"foo ",
),
2..6,
@ -34,7 +33,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"bar ",
),
9..13,
@ -100,7 +99,7 @@ snapshot_kind: text
28..29,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" baz",
),
29..33,
@ -123,7 +122,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"foo ",
),
37..41,
@ -143,7 +142,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"bar",
),
44..47,
@ -163,7 +162,7 @@ snapshot_kind: text
48..49,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" some ",
),
49..55,
@ -183,7 +182,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"another",
),
58..65,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -36,7 +35,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"{}",
),
8..12,
@ -59,7 +58,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
16..17,
@ -90,7 +89,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"{",
),
23..25,
@ -107,7 +106,7 @@ snapshot_kind: text
26..27,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"}",
),
27..29,
@ -130,7 +129,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"{{}}",
),
33..41,
@ -153,7 +152,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
45..46,
@ -170,7 +169,7 @@ snapshot_kind: text
47..48,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" {} {",
),
48..56,
@ -187,7 +186,7 @@ snapshot_kind: text
57..58,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"} {{}} ",
),
58..71,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: fstring_single_quote_escape_eol(MAC_EOL)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"text \\\r more text",
),
2..19,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: fstring_single_quote_escape_eol(UNIX_EOL)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"text \\\n more text",
),
2..19,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: fstring_single_quote_escape_eol(WINDOWS_EOL)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"text \\\r\n more text",
),
2..20,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -32,7 +31,7 @@ snapshot_kind: text
7..8,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
8..9,
@ -69,7 +68,7 @@ snapshot_kind: text
14..15,
),
(
FStringMiddle(
InterpolatedStringMiddle(
".3f",
),
15..18,
@ -82,7 +81,7 @@ snapshot_kind: text
18..19,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
19..20,
@ -105,7 +104,7 @@ snapshot_kind: text
22..23,
),
(
FStringMiddle(
InterpolatedStringMiddle(
".",
),
23..24,
@ -128,7 +127,7 @@ snapshot_kind: text
26..27,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"f",
),
27..28,
@ -141,7 +140,7 @@ snapshot_kind: text
28..29,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
29..30,
@ -164,7 +163,7 @@ snapshot_kind: text
33..34,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"*^",
),
34..36,
@ -209,7 +208,7 @@ snapshot_kind: text
43..44,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
44..45,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"foo ",
),
2..6,
@ -41,7 +40,7 @@ snapshot_kind: text
11..12,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" bar",
),
12..16,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
4..6,
@ -41,7 +40,7 @@ snapshot_kind: text
13..14,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"d\n",
),
14..16,
@ -54,7 +53,7 @@ snapshot_kind: text
16..17,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
17..19,
@ -81,7 +80,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
27..29,
@ -108,7 +107,7 @@ snapshot_kind: text
36..37,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"a\n b\n c\n",
),
37..61,
@ -121,7 +120,7 @@ snapshot_kind: text
61..62,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
62..64,
@ -148,7 +147,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
70..72,
@ -175,7 +174,7 @@ snapshot_kind: text
79..80,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"d",
),
80..81,
@ -192,7 +191,7 @@ snapshot_kind: text
82..83,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
83..85,
@ -219,7 +218,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
89..91,
@ -246,7 +245,7 @@ snapshot_kind: text
98..99,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"a",
),
99..100,
@ -273,7 +272,7 @@ snapshot_kind: text
111..112,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"__",
),
112..114,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -28,7 +27,7 @@ snapshot_kind: text
4..5,
),
(
FStringMiddle(
InterpolatedStringMiddle(
"=10",
),
5..8,
@ -41,7 +40,7 @@ snapshot_kind: text
8..9,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
9..10,
@ -82,7 +81,7 @@ snapshot_kind: text
18..19,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
19..20,
@ -133,7 +132,7 @@ snapshot_kind: text
30..31,
),
(
FStringMiddle(
InterpolatedStringMiddle(
" ",
),
31..32,

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
snapshot_kind: text
---
## Tokens
```
@ -14,7 +13,7 @@ snapshot_kind: text
),
),
(
FStringMiddle(
InterpolatedStringMiddle(
"\\0",
),
2..4,

View file

@ -0,0 +1,226 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"foo ",
),
2..6,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
6..7,
),
(
FStringStart,
7..9,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
InterpolatedStringMiddle(
"bar ",
),
9..13,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
Lbrace,
13..14,
),
(
Name(
Name("x"),
),
14..15,
),
(
Plus,
16..17,
),
(
TStringStart,
18..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
20..21,
),
(
Name(
Name("wow"),
),
21..24,
),
(
Rbrace,
24..25,
),
(
TStringEnd,
25..26,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
26..27,
),
(
FStringEnd,
27..28,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
Rbrace,
28..29,
),
(
InterpolatedStringMiddle(
" baz",
),
29..33,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
33..34,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
FStringStart,
35..37,
TokenFlags(
F_STRING,
),
),
(
InterpolatedStringMiddle(
"foo ",
),
37..41,
TokenFlags(
F_STRING,
),
),
(
Lbrace,
41..42,
),
(
TStringStart,
42..44,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"bar",
),
44..47,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
47..48,
TokenFlags(
T_STRING,
),
),
(
Exclamation,
48..49,
),
(
Name(
Name("r"),
),
49..50,
),
(
Rbrace,
50..51,
),
(
InterpolatedStringMiddle(
" some ",
),
51..57,
TokenFlags(
F_STRING,
),
),
(
Lbrace,
57..58,
),
(
FStringStart,
58..60,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
InterpolatedStringMiddle(
"another",
),
60..67,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
FStringEnd,
67..68,
TokenFlags(
DOUBLE_QUOTES | F_STRING,
),
),
(
Rbrace,
68..69,
),
(
FStringEnd,
69..70,
TokenFlags(
F_STRING,
),
),
(
Newline,
70..70,
),
]
```

View file

@ -0,0 +1,105 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"normal ",
),
2..9,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
9..10,
),
(
Name(
Name("foo"),
),
10..13,
),
(
Rbrace,
13..14,
),
(
InterpolatedStringMiddle(
" {another} ",
),
14..27,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
27..28,
),
(
Name(
Name("bar"),
),
28..31,
),
(
Rbrace,
31..32,
),
(
InterpolatedStringMiddle(
" {",
),
32..35,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
35..36,
),
(
Name(
Name("three"),
),
36..41,
),
(
Rbrace,
41..42,
),
(
InterpolatedStringMiddle(
"}",
),
42..44,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
44..45,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
45..45,
),
]
```

View file

@ -0,0 +1,71 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..4,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"\n# not a comment ",
),
4..21,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Lbrace,
21..22,
),
(
Comment,
23..34,
),
(
NonLogicalNewline,
34..35,
),
(
Name(
Name("x"),
),
39..40,
),
(
NonLogicalNewline,
40..41,
),
(
Rbrace,
41..42,
),
(
InterpolatedStringMiddle(
" # not a comment\n",
),
42..59,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
59..62,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Newline,
62..62,
),
]
```

View file

@ -0,0 +1,133 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
2..3,
),
(
Name(
Name("x"),
),
3..4,
),
(
Exclamation,
4..5,
),
(
Name(
Name("s"),
),
5..6,
),
(
Rbrace,
6..7,
),
(
InterpolatedStringMiddle(
" ",
),
7..8,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
8..9,
),
(
Name(
Name("x"),
),
9..10,
),
(
Equal,
10..11,
),
(
Exclamation,
11..12,
),
(
Name(
Name("r"),
),
12..13,
),
(
Rbrace,
13..14,
),
(
InterpolatedStringMiddle(
" ",
),
14..15,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
15..16,
),
(
Name(
Name("x"),
),
16..17,
),
(
Colon,
17..18,
),
(
InterpolatedStringMiddle(
".3f!r",
),
18..23,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
23..24,
),
(
InterpolatedStringMiddle(
" {x!r}",
),
24..32,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
32..33,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
33..33,
),
]
```

View file

@ -0,0 +1,86 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\",
),
2..3,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
3..4,
),
(
Name(
Name("x"),
),
4..5,
),
(
Colon,
5..6,
),
(
InterpolatedStringMiddle(
"\\\"\\",
),
6..9,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
9..10,
),
(
Name(
Name("x"),
),
10..11,
),
(
Rbrace,
11..12,
),
(
Rbrace,
12..13,
),
(
InterpolatedStringMiddle(
" \\\"\\\"\\\n end",
),
13..24,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
24..25,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
25..25,
),
]
```

View file

@ -0,0 +1,133 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\",
),
2..3,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
3..4,
),
(
Name(
Name("foo"),
),
4..7,
),
(
Rbrace,
7..8,
),
(
TStringEnd,
8..9,
TokenFlags(
T_STRING,
),
),
(
TStringStart,
10..12,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\\\",
),
12..14,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
14..15,
),
(
Name(
Name("foo"),
),
15..18,
),
(
Rbrace,
18..19,
),
(
TStringEnd,
19..20,
TokenFlags(
T_STRING,
),
),
(
TStringStart,
21..23,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\{foo}",
),
23..31,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
31..32,
TokenFlags(
T_STRING,
),
),
(
TStringStart,
33..35,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\\\{foo}",
),
35..44,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
44..45,
TokenFlags(
T_STRING,
),
),
(
Newline,
45..45,
),
]
```

View file

@ -0,0 +1,86 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..3,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
InterpolatedStringMiddle(
"\\",
),
3..4,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
Lbrace,
4..5,
),
(
Name(
Name("x"),
),
5..6,
),
(
Colon,
6..7,
),
(
InterpolatedStringMiddle(
"\\\"\\",
),
7..10,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
Lbrace,
10..11,
),
(
Name(
Name("x"),
),
11..12,
),
(
Rbrace,
12..13,
),
(
Rbrace,
13..14,
),
(
InterpolatedStringMiddle(
" \\\"\\\"\\\n end",
),
14..25,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
25..26,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
Newline,
26..26,
),
]
```

View file

@ -0,0 +1,85 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"first ",
),
2..8,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
8..9,
),
(
NonLogicalNewline,
9..10,
),
(
Name(
Name("x"),
),
14..15,
),
(
NonLogicalNewline,
15..16,
),
(
Star,
24..25,
),
(
NonLogicalNewline,
25..26,
),
(
Name(
Name("y"),
),
38..39,
),
(
NonLogicalNewline,
39..40,
),
(
Rbrace,
40..41,
),
(
InterpolatedStringMiddle(
" second",
),
41..48,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
48..49,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
49..49,
),
]
```

View file

@ -0,0 +1,136 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..4,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"\nhello\n world\n",
),
4..21,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
21..24,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringStart,
25..29,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"\n world\nhello\n",
),
29..46,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
46..49,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringStart,
50..52,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"some ",
),
52..57,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
57..58,
),
(
TStringStart,
58..62,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"multiline\nallowed ",
),
62..80,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Lbrace,
80..81,
),
(
Name(
Name("x"),
),
81..82,
),
(
Rbrace,
82..83,
),
(
TStringEnd,
83..86,
TokenFlags(
DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Rbrace,
86..87,
),
(
InterpolatedStringMiddle(
" string",
),
87..94,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
94..95,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
95..95,
),
]
```

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\N{BULLET} normal \\Nope \\N",
),
2..28,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
28..29,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
29..29,
),
]
```

View file

@ -0,0 +1,59 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..3,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
InterpolatedStringMiddle(
"\\N",
),
3..5,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
Lbrace,
5..6,
),
(
Name(
Name("BULLET"),
),
6..12,
),
(
Rbrace,
12..13,
),
(
InterpolatedStringMiddle(
" normal",
),
13..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
20..21,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
Newline,
21..21,
),
]
```

View file

@ -0,0 +1,216 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"foo ",
),
2..6,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
6..7,
),
(
TStringStart,
7..9,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"bar ",
),
9..13,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
13..14,
),
(
Name(
Name("x"),
),
14..15,
),
(
Plus,
16..17,
),
(
TStringStart,
18..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
20..21,
),
(
Name(
Name("wow"),
),
21..24,
),
(
Rbrace,
24..25,
),
(
TStringEnd,
25..26,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
26..27,
),
(
TStringEnd,
27..28,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
28..29,
),
(
InterpolatedStringMiddle(
" baz",
),
29..33,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
33..34,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
35..37,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"foo ",
),
37..41,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
41..42,
),
(
TStringStart,
42..44,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"bar",
),
44..47,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
47..48,
TokenFlags(
T_STRING,
),
),
(
Rbrace,
48..49,
),
(
InterpolatedStringMiddle(
" some ",
),
49..55,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
55..56,
),
(
TStringStart,
56..58,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"another",
),
58..65,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
65..66,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
66..67,
),
(
TStringEnd,
67..68,
TokenFlags(
T_STRING,
),
),
(
Newline,
68..68,
),
]
```

View file

@ -0,0 +1,209 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
2..3,
),
(
Rbrace,
3..4,
),
(
TStringEnd,
4..5,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
6..8,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"{}",
),
8..12,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
12..13,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
14..16,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
" ",
),
16..17,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
17..18,
),
(
Rbrace,
18..19,
),
(
TStringEnd,
19..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
21..23,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"{",
),
23..25,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
25..26,
),
(
Rbrace,
26..27,
),
(
InterpolatedStringMiddle(
"}",
),
27..29,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
29..30,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
31..33,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"{{}}",
),
33..41,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
41..42,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
43..45,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
" ",
),
45..46,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
46..47,
),
(
Rbrace,
47..48,
),
(
InterpolatedStringMiddle(
" {} {",
),
48..56,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
56..57,
),
(
Rbrace,
57..58,
),
(
InterpolatedStringMiddle(
"} {{}} ",
),
58..71,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
71..72,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
72..72,
),
]
```

View file

@ -0,0 +1,153 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
2..3,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
4..6,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
6..7,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringStart,
8..11,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
11..12,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringStart,
13..16,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
16..17,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringStart,
18..21,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringEnd,
21..22,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringStart,
23..26,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringEnd,
26..27,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringStart,
28..31,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
31..32,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringStart,
33..36,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringEnd,
36..37,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE,
),
),
(
TStringStart,
38..41,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringEnd,
41..42,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringStart,
43..46,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
TStringEnd,
46..47,
TokenFlags(
DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE,
),
),
(
Newline,
47..47,
),
]
```

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: tstring_single_quote_escape_eol(MAC_EOL)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"text \\\r more text",
),
2..19,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
19..20,
TokenFlags(
T_STRING,
),
),
(
Newline,
20..20,
),
]
```

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: tstring_single_quote_escape_eol(UNIX_EOL)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"text \\\n more text",
),
2..19,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
19..20,
TokenFlags(
T_STRING,
),
),
(
Newline,
20..20,
),
]
```

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: tstring_single_quote_escape_eol(WINDOWS_EOL)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"text \\\r\n more text",
),
2..20,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
20..21,
TokenFlags(
T_STRING,
),
),
(
Newline,
21..21,
),
]
```

View file

@ -0,0 +1,289 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
2..3,
),
(
Name(
Name("foo"),
),
3..6,
),
(
Colon,
6..7,
),
(
Rbrace,
7..8,
),
(
InterpolatedStringMiddle(
" ",
),
8..9,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
9..10,
),
(
Name(
Name("x"),
),
10..11,
),
(
Equal,
11..12,
),
(
Exclamation,
12..13,
),
(
Name(
Name("s"),
),
13..14,
),
(
Colon,
14..15,
),
(
InterpolatedStringMiddle(
".3f",
),
15..18,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
18..19,
),
(
InterpolatedStringMiddle(
" ",
),
19..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
20..21,
),
(
Name(
Name("x"),
),
21..22,
),
(
Colon,
22..23,
),
(
InterpolatedStringMiddle(
".",
),
23..24,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
24..25,
),
(
Name(
Name("y"),
),
25..26,
),
(
Rbrace,
26..27,
),
(
InterpolatedStringMiddle(
"f",
),
27..28,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
28..29,
),
(
InterpolatedStringMiddle(
" ",
),
29..30,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
30..31,
),
(
String(
"",
),
31..33,
),
(
Colon,
33..34,
),
(
InterpolatedStringMiddle(
"*^",
),
34..36,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
36..37,
),
(
Int(
1,
),
37..38,
),
(
Colon,
38..39,
),
(
Lbrace,
39..40,
),
(
Int(
1,
),
40..41,
),
(
Rbrace,
41..42,
),
(
Rbrace,
42..43,
),
(
Rbrace,
43..44,
),
(
InterpolatedStringMiddle(
" ",
),
44..45,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
45..46,
),
(
Name(
Name("x"),
),
46..47,
),
(
Colon,
47..48,
),
(
Lbrace,
48..49,
),
(
Lbrace,
49..50,
),
(
Int(
1,
),
50..51,
),
(
Rbrace,
51..52,
),
(
Dot,
52..53,
),
(
Name(
Name("pop"),
),
53..56,
),
(
Lpar,
56..57,
),
(
Rpar,
57..58,
),
(
Rbrace,
58..59,
),
(
Rbrace,
59..60,
),
(
TStringEnd,
60..61,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
61..61,
),
]
```

View file

@ -0,0 +1,63 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
InterpolatedStringMiddle(
"foo ",
),
2..6,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
6..7,
),
(
Exclamation,
7..8,
),
(
Name(
Name("pwd"),
),
8..11,
),
(
Rbrace,
11..12,
),
(
InterpolatedStringMiddle(
" bar",
),
12..16,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
TStringEnd,
16..17,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
17..17,
),
]
```

View file

@ -0,0 +1,125 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
2..3,
),
(
Lambda,
3..9,
),
(
Name(
Name("x"),
),
10..11,
),
(
Colon,
11..12,
),
(
Lbrace,
12..13,
),
(
Name(
Name("x"),
),
13..14,
),
(
Rbrace,
14..15,
),
(
Rbrace,
15..16,
),
(
TStringEnd,
16..17,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
17..18,
),
(
TStringStart,
18..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
20..21,
),
(
Lpar,
21..22,
),
(
Lambda,
22..28,
),
(
Name(
Name("x"),
),
29..30,
),
(
Colon,
30..31,
),
(
Lbrace,
31..32,
),
(
Name(
Name("x"),
),
32..33,
),
(
Rbrace,
33..34,
),
(
Rpar,
34..35,
),
(
Rbrace,
35..36,
),
(
TStringEnd,
36..37,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
37..37,
),
]
```

View file

@ -0,0 +1,295 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..4,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"__",
),
4..6,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Lbrace,
6..7,
),
(
NonLogicalNewline,
7..8,
),
(
Name(
Name("x"),
),
12..13,
),
(
Colon,
13..14,
),
(
InterpolatedStringMiddle(
"d\n",
),
14..16,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Rbrace,
16..17,
),
(
InterpolatedStringMiddle(
"__",
),
17..19,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
19..22,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Newline,
22..23,
),
(
TStringStart,
23..27,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
InterpolatedStringMiddle(
"__",
),
27..29,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Lbrace,
29..30,
),
(
NonLogicalNewline,
30..31,
),
(
Name(
Name("x"),
),
35..36,
),
(
Colon,
36..37,
),
(
InterpolatedStringMiddle(
"a\n b\n c\n",
),
37..61,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Rbrace,
61..62,
),
(
InterpolatedStringMiddle(
"__",
),
62..64,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
TStringEnd,
64..67,
TokenFlags(
TRIPLE_QUOTED_STRING | T_STRING,
),
),
(
Newline,
67..68,
),
(
TStringStart,
68..70,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"__",
),
70..72,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
72..73,
),
(
NonLogicalNewline,
73..74,
),
(
Name(
Name("x"),
),
78..79,
),
(
Colon,
79..80,
),
(
InterpolatedStringMiddle(
"d",
),
80..81,
TokenFlags(
T_STRING,
),
),
(
NonLogicalNewline,
81..82,
),
(
Rbrace,
82..83,
),
(
InterpolatedStringMiddle(
"__",
),
83..85,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
85..86,
TokenFlags(
T_STRING,
),
),
(
Newline,
86..87,
),
(
TStringStart,
87..89,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"__",
),
89..91,
TokenFlags(
T_STRING,
),
),
(
Lbrace,
91..92,
),
(
NonLogicalNewline,
92..93,
),
(
Name(
Name("x"),
),
97..98,
),
(
Colon,
98..99,
),
(
InterpolatedStringMiddle(
"a",
),
99..100,
TokenFlags(
T_STRING,
),
),
(
NonLogicalNewline,
100..101,
),
(
Name(
Name("b"),
),
109..110,
),
(
NonLogicalNewline,
110..111,
),
(
Rbrace,
111..112,
),
(
InterpolatedStringMiddle(
"__",
),
112..114,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
114..115,
TokenFlags(
T_STRING,
),
),
(
Newline,
115..116,
),
]
```

View file

@ -0,0 +1,187 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
2..3,
),
(
Name(
Name("x"),
),
3..4,
),
(
Colon,
4..5,
),
(
InterpolatedStringMiddle(
"=10",
),
5..8,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Rbrace,
8..9,
),
(
InterpolatedStringMiddle(
" ",
),
9..10,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
10..11,
),
(
Lpar,
11..12,
),
(
Name(
Name("x"),
),
12..13,
),
(
ColonEqual,
13..15,
),
(
Int(
10,
),
15..17,
),
(
Rpar,
17..18,
),
(
Rbrace,
18..19,
),
(
InterpolatedStringMiddle(
" ",
),
19..20,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
20..21,
),
(
Name(
Name("x"),
),
21..22,
),
(
Comma,
22..23,
),
(
Lbrace,
23..24,
),
(
Name(
Name("y"),
),
24..25,
),
(
ColonEqual,
25..27,
),
(
Int(
10,
),
27..29,
),
(
Rbrace,
29..30,
),
(
Rbrace,
30..31,
),
(
InterpolatedStringMiddle(
" ",
),
31..32,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Lbrace,
32..33,
),
(
Lsqb,
33..34,
),
(
Name(
Name("x"),
),
34..35,
),
(
ColonEqual,
35..37,
),
(
Int(
10,
),
37..39,
),
(
Rsqb,
39..40,
),
(
Rbrace,
40..41,
),
(
TStringEnd,
41..42,
TokenFlags(
DOUBLE_QUOTES | T_STRING,
),
),
(
Newline,
42..42,
),
]
```

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(source)
---
## Tokens
```
[
(
TStringStart,
0..2,
TokenFlags(
T_STRING,
),
),
(
InterpolatedStringMiddle(
"\\0",
),
2..4,
TokenFlags(
T_STRING,
),
),
(
TStringEnd,
4..5,
TokenFlags(
T_STRING,
),
),
(
Newline,
5..5,
),
]
```

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..22,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 2..5,
value: "aaa",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 5..10,
expression: Name(
ExprName {
@ -38,13 +37,13 @@ snapshot_kind: text
},
),
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 10..13,
value: "ccc",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 13..18,
expression: Name(
ExprName {
@ -59,7 +58,7 @@ snapshot_kind: text
},
),
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 18..21,
value: "eee",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..8,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 2..4,
value: "\\",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 4..7,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..8,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 2..4,
value: "\n",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 4..7,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..9,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 3..5,
value: "\\\n",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 5..8,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..38,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 2..6,
value: "mix ",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 6..13,
expression: Name(
ExprName {
@ -43,13 +42,13 @@ snapshot_kind: text
},
),
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 13..28,
value: " with text and ",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 28..37,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..14,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..13,
expression: Name(
ExprName {
@ -34,11 +33,11 @@ snapshot_kind: text
),
conversion: None,
format_spec: Some(
FStringFormatSpec {
InterpolatedStringFormatSpec {
range: 9..12,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 9..12,
value: ">10",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -17,13 +16,13 @@ snapshot_kind: text
range: 0..11,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 4..5,
value: "\n",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 5..8,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(

View file

@ -0,0 +1,31 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..3,
value: TString(
ExprTString {
range: 0..3,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..3,
elements: [],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,7 +28,7 @@ snapshot_kind: text
range: 9..17,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,7 +28,7 @@ snapshot_kind: text
range: 9..17,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,13 +28,13 @@ snapshot_kind: text
range: 9..22,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 16..21,
expression: StringLiteral(
ExprStringLiteral {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,13 +28,13 @@ snapshot_kind: text
range: 9..22,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 16..21,
expression: StringLiteral(
ExprStringLiteral {

View file

@ -0,0 +1,58 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..18,
value: TString(
ExprTString {
range: 0..18,
value: TStringValue {
inner: Concatenated(
[
FString(
FString {
range: 0..9,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..8,
value: "Hello ",
},
),
],
flags: FStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,69 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..22,
value: TString(
ExprTString {
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
FString(
FString {
range: 0..9,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..8,
value: "Hello ",
},
),
],
flags: FStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 19..22,
value: "!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..18,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..5,
expression: Name(
ExprName {
@ -31,8 +30,8 @@ snapshot_kind: text
format_spec: None,
},
),
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 5..10,
expression: Name(
ExprName {
@ -47,7 +46,7 @@ snapshot_kind: text
},
),
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 10..17,
value: "{foo}",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..13,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Compare(
ExprCompare {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..16,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..15,
expression: Name(
ExprName {
@ -29,11 +28,11 @@ snapshot_kind: text
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
InterpolatedStringFormatSpec {
range: 7..14,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 7..14,
expression: StringLiteral(
ExprStringLiteral {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..15,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..14,
expression: Name(
ExprName {
@ -29,11 +28,11 @@ snapshot_kind: text
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
InterpolatedStringFormatSpec {
range: 7..13,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 7..13,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..13,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Name(
ExprName {
@ -29,11 +28,11 @@ snapshot_kind: text
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
InterpolatedStringFormatSpec {
range: 7..11,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 7..11,
expression: StringLiteral(
ExprStringLiteral {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..11,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..10,
expression: Compare(
ExprCompare {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..13,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Name(
ExprName {
@ -29,11 +28,11 @@ snapshot_kind: text
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
InterpolatedStringFormatSpec {
range: 7..11,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 7..11,
value: "spec",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Name(
ExprName {

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Yield(
ExprYield {

View file

@ -0,0 +1,51 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..17,
value: TString(
ExprTString {
range: 0..17,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..17,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,51 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..17,
value: TString(
ExprTString {
range: 0..17,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..17,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,77 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..22,
value: TString(
ExprTString {
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..22,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
expression: StringLiteral(
ExprStringLiteral {
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,88 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..31,
value: TString(
ExprTString {
range: 0..31,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..22,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
expression: StringLiteral(
ExprStringLiteral {
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 23..31,
value: "again!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,68 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..18,
value: TString(
ExprTString {
range: 0..18,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..18,
elements: [
Interpolation(
InterpolatedElement {
range: 2..5,
expression: Name(
ExprName {
range: 3..4,
id: Name("a"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
expression: Name(
ExprName {
range: 7..8,
id: Name("b"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..17,
value: "{foo}",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,66 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..13,
value: TString(
ExprTString {
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Compare(
ExprCompare {
range: 3..11,
left: NumberLiteral(
ExprNumberLiteral {
range: 3..5,
value: Int(
42,
),
},
),
ops: [
Eq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
range: 9..11,
value: Int(
42,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,93 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..16,
value: TString(
ExprTString {
range: 0..16,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..16,
elements: [
Interpolation(
InterpolatedElement {
range: 2..15,
expression: Name(
ExprName {
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..14,
elements: [
Interpolation(
InterpolatedElement {
range: 7..14,
expression: StringLiteral(
ExprStringLiteral {
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
StringLiteral {
range: 11..13,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
],
value: "",
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,68 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..15,
value: TString(
ExprTString {
range: 0..15,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..15,
elements: [
Interpolation(
InterpolatedElement {
range: 2..14,
expression: Name(
ExprName {
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..13,
elements: [
Interpolation(
InterpolatedElement {
range: 7..13,
expression: Name(
ExprName {
range: 8..12,
id: Name("spec"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,79 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..13,
value: TString(
ExprTString {
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Name(
ExprName {
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
elements: [
Interpolation(
InterpolatedElement {
range: 7..11,
expression: StringLiteral(
ExprStringLiteral {
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,66 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..11,
value: TString(
ExprTString {
range: 0..11,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..11,
elements: [
Interpolation(
InterpolatedElement {
range: 2..10,
expression: Compare(
ExprCompare {
range: 3..9,
left: NumberLiteral(
ExprNumberLiteral {
range: 3..4,
value: Int(
1,
),
},
),
ops: [
NotEq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
range: 8..9,
value: Int(
2,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,59 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..13,
value: TString(
ExprTString {
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
expression: Name(
ExprName {
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 7..11,
value: "spec",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,52 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: TString(
ExprTString {
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Name(
ExprName {
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,52 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: TString(
ExprTString {
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Name(
ExprName {
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -0,0 +1,46 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: TString(
ExprTString {
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
expression: Yield(
ExprYield {
range: 3..8,
value: None,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,7 +28,7 @@ snapshot_kind: text
range: 10..18,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -29,7 +28,7 @@ snapshot_kind: text
range: 10..18,
elements: [
Literal(
FStringLiteralElement {
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},

View file

@ -0,0 +1,51 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..18,
value: TString(
ExprTString {
range: 0..18,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..9,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -0,0 +1,62 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..22,
value: TString(
ExprTString {
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..9,
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 19..22,
value: "!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..7,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 3..6,
expression: Name(
ExprName {

View file

@ -0,0 +1,49 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
range: 0..7,
value: TString(
ExprTString {
range: 0..7,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..7,
elements: [
Interpolation(
InterpolatedElement {
range: 3..6,
expression: Name(
ExprName {
range: 4..5,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
snapshot_kind: text
---
[
Expr(
@ -16,8 +15,8 @@ snapshot_kind: text
FString {
range: 0..11,
elements: [
Expression(
FStringExpressionElement {
Interpolation(
InterpolatedElement {
range: 5..8,
expression: Name(
ExprName {

Some files were not shown because too many files have changed in this diff Show more