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

@ -7,7 +7,7 @@ use ruff_python_parser::Tokens;
use crate::PyFormatOptions;
use crate::comments::Comments;
use crate::other::f_string_element::FStringExpressionElementContext;
use crate::other::interpolated_string_element::InterpolatedElementContext;
pub struct PyFormatContext<'a> {
options: PyFormatOptions,
@ -25,8 +25,8 @@ pub struct PyFormatContext<'a> {
/// quote style that is inverted from the one here in order to ensure that
/// the formatted Python code will be valid.
docstring: Option<Quote>,
/// The state of the formatter with respect to f-strings.
f_string_state: FStringState,
/// The state of the formatter with respect to f-strings and t-strings.
interpolated_string_state: InterpolatedStringState,
}
impl<'a> PyFormatContext<'a> {
@ -44,7 +44,7 @@ impl<'a> PyFormatContext<'a> {
node_level: NodeLevel::TopLevel(TopLevelStatementPosition::Other),
indent_level: IndentLevel::new(0),
docstring: None,
f_string_state: FStringState::Outside,
interpolated_string_state: InterpolatedStringState::Outside,
}
}
@ -97,12 +97,15 @@ impl<'a> PyFormatContext<'a> {
}
}
pub(crate) fn f_string_state(&self) -> FStringState {
self.f_string_state
pub(crate) fn interpolated_string_state(&self) -> InterpolatedStringState {
self.interpolated_string_state
}
pub(crate) fn set_f_string_state(&mut self, f_string_state: FStringState) {
self.f_string_state = f_string_state;
pub(crate) fn set_interpolated_string_state(
&mut self,
interpolated_string_state: InterpolatedStringState,
) {
self.interpolated_string_state = interpolated_string_state;
}
/// Returns `true` if preview mode is enabled.
@ -135,24 +138,24 @@ impl Debug for PyFormatContext<'_> {
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) enum FStringState {
pub(crate) enum InterpolatedStringState {
/// The formatter is inside an f-string expression element i.e., between the
/// curly brace in `f"foo {x}"`.
///
/// The containing `FStringContext` is the surrounding f-string context.
InsideExpressionElement(FStringExpressionElementContext),
InsideInterpolatedElement(InterpolatedElementContext),
/// The formatter is outside an f-string.
#[default]
Outside,
}
impl FStringState {
impl InterpolatedStringState {
pub(crate) fn can_contain_line_breaks(self) -> Option<bool> {
match self {
FStringState::InsideExpressionElement(context) => {
InterpolatedStringState::InsideInterpolatedElement(context) => {
Some(context.can_contain_line_breaks())
}
FStringState::Outside => None,
InterpolatedStringState::Outside => None,
}
}
}
@ -375,25 +378,25 @@ where
}
}
pub(crate) struct WithFStringState<'a, B, D>
pub(crate) struct WithInterpolatedStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
buffer: D,
saved_location: FStringState,
saved_location: InterpolatedStringState,
}
impl<'a, B, D> WithFStringState<'a, B, D>
impl<'a, B, D> WithInterpolatedStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
pub(crate) fn new(expr_location: FStringState, mut buffer: D) -> Self {
pub(crate) fn new(expr_location: InterpolatedStringState, mut buffer: D) -> Self {
let context = buffer.state_mut().context_mut();
let saved_location = context.f_string_state();
let saved_location = context.interpolated_string_state();
context.set_f_string_state(expr_location);
context.set_interpolated_string_state(expr_location);
Self {
buffer,
@ -402,7 +405,7 @@ where
}
}
impl<'a, B, D> Deref for WithFStringState<'a, B, D>
impl<'a, B, D> Deref for WithInterpolatedStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
@ -414,7 +417,7 @@ where
}
}
impl<'a, B, D> DerefMut for WithFStringState<'a, B, D>
impl<'a, B, D> DerefMut for WithInterpolatedStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
@ -424,7 +427,7 @@ where
}
}
impl<'a, B, D> Drop for WithFStringState<'a, B, D>
impl<'a, B, D> Drop for WithInterpolatedStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
@ -433,6 +436,6 @@ where
self.buffer
.state_mut()
.context_mut()
.set_f_string_state(self.saved_location);
.set_interpolated_string_state(self.saved_location);
}
}