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

@ -433,6 +433,18 @@ See also [JoinedStr](https://docs.python.org/3/library/ast.html#ast.JoinedStr)""
fields = [{ name = "value", type = "FStringValue" }]
custom_source_order = true
[Expr.nodes.ExprTString]
doc = """An AST node that represents either a single-part t-string literal
or an implicitly concatenated t-string literal.
This type differs from the original Python AST `TemplateStr` in that it
doesn't join the implicitly concatenated parts into a single string. Instead,
it keeps them separate and provide various methods to access the parts.
See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateStr)"""
fields = [{ name = "value", type = "TStringValue" }]
custom_source_order = true
[Expr.nodes.ExprStringLiteral]
doc = """An AST node that represents either a single-part string literal
or an implicitly concatenated string literal."""
@ -539,9 +551,10 @@ doc = "See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.e
[ExceptHandler.nodes]
ExceptHandlerExceptHandler = {}
[FStringElement.nodes]
FStringExpressionElement = { variant = "Expression" }
FStringLiteralElement = { variant = "Literal" }
[InterpolatedStringElement.nodes]
InterpolatedElement = { variant = "Interpolation" }
InterpolatedStringLiteralElement = { variant = "Literal" }
[Pattern]
doc = "See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern)"
@ -565,7 +578,7 @@ TypeParamTypeVarTuple = {}
TypeParamParamSpec = {}
[ungrouped.nodes]
FStringFormatSpec = {}
InterpolatedStringFormatSpec = {}
PatternArguments = {}
PatternKeyword = {}
Comprehension = {}
@ -581,6 +594,7 @@ Decorator = {}
ElifElseClause = {}
TypeParams = {}
FString = {}
TString = {}
StringLiteral = {}
BytesLiteral = {}
Identifier = {}

View file

@ -15,7 +15,7 @@ from typing import Any
import tomllib
# Types that require `crate::`. We can slowly remove these types as we move them to generate scripts.
types_requiring_create_prefix = {
types_requiring_crate_prefix = {
"IpyEscapeKind",
"ExprContext",
"Identifier",
@ -23,6 +23,7 @@ types_requiring_create_prefix = {
"BytesLiteralValue",
"StringLiteralValue",
"FStringValue",
"TStringValue",
"Arguments",
"CmpOp",
"Comprehension",
@ -762,7 +763,7 @@ def write_node(out: list[str], ast: Ast) -> None:
ty = field.parsed_ty
rust_ty = f"{field.parsed_ty.name}"
if ty.name in types_requiring_create_prefix:
if ty.name in types_requiring_crate_prefix:
rust_ty = f"crate::{rust_ty}"
if ty.slice_:
rust_ty = f"[{rust_ty}]"

View file

@ -512,48 +512,57 @@ impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> {
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableFStringElement<'a> {
pub enum ComparableInterpolatedStringElement<'a> {
Literal(Cow<'a, str>),
FStringExpressionElement(FStringExpressionElement<'a>),
InterpolatedElement(InterpolatedElement<'a>),
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct FStringExpressionElement<'a> {
pub struct InterpolatedElement<'a> {
expression: ComparableExpr<'a>,
debug_text: Option<&'a ast::DebugText>,
conversion: ast::ConversionFlag,
format_spec: Option<Vec<ComparableFStringElement<'a>>>,
format_spec: Option<Vec<ComparableInterpolatedStringElement<'a>>>,
}
impl<'a> From<&'a ast::FStringElement> for ComparableFStringElement<'a> {
fn from(fstring_element: &'a ast::FStringElement) -> Self {
match fstring_element {
ast::FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => {
Self::Literal(value.as_ref().into())
impl<'a> From<&'a ast::InterpolatedStringElement> for ComparableInterpolatedStringElement<'a> {
fn from(interpolated_string_element: &'a ast::InterpolatedStringElement) -> Self {
match interpolated_string_element {
ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
value,
..
}) => Self::Literal(value.as_ref().into()),
ast::InterpolatedStringElement::Interpolation(formatted_value) => {
formatted_value.into()
}
ast::FStringElement::Expression(formatted_value) => formatted_value.into(),
}
}
}
impl<'a> From<&'a ast::FStringExpressionElement> for ComparableFStringElement<'a> {
fn from(fstring_expression_element: &'a ast::FStringExpressionElement) -> Self {
let ast::FStringExpressionElement {
impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> {
fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
let ast::InterpolatedElement {
expression,
debug_text,
conversion,
format_spec,
range: _,
} = fstring_expression_element;
} = interpolated_element;
Self::FStringExpressionElement(FStringExpressionElement {
Self {
expression: (expression).into(),
debug_text: debug_text.as_ref(),
conversion: *conversion,
format_spec: format_spec
.as_ref()
.map(|spec| spec.elements.iter().map(Into::into).collect()),
})
}
}
}
impl<'a> From<&'a ast::InterpolatedElement> for ComparableInterpolatedStringElement<'a> {
fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
Self::InterpolatedElement(interpolated_element.into())
}
}
@ -610,7 +619,7 @@ impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableFString<'a> {
elements: Box<[ComparableFStringElement<'a>]>,
elements: Box<[ComparableInterpolatedStringElement<'a>]>,
}
impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
@ -637,7 +646,7 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
fn from(value: &'a ast::FStringValue) -> Self {
#[derive(Default)]
struct Collector<'a> {
elements: Vec<ComparableFStringElement<'a>>,
elements: Vec<ComparableInterpolatedStringElement<'a>>,
}
impl<'a> Collector<'a> {
@ -647,17 +656,17 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
// `elements` vector, while subsequent strings
// are concatenated onto this top string.
fn push_literal(&mut self, literal: &'a str) {
if let Some(ComparableFStringElement::Literal(existing_literal)) =
if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
self.elements.last_mut()
{
existing_literal.to_mut().push_str(literal);
} else {
self.elements
.push(ComparableFStringElement::Literal(literal.into()));
.push(ComparableInterpolatedStringElement::Literal(literal.into()));
}
}
fn push_expression(&mut self, expression: &'a ast::FStringExpressionElement) {
fn push_expression(&mut self, expression: &'a ast::InterpolatedElement) {
self.elements.push(expression.into());
}
}
@ -672,10 +681,10 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
ast::FStringPart::FString(fstring) => {
for element in &fstring.elements {
match element {
ast::FStringElement::Literal(literal) => {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::FStringElement::Expression(expression) => {
ast::InterpolatedStringElement::Interpolation(expression) => {
collector.push_expression(expression);
}
}
@ -690,6 +699,133 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableTString<'a> {
strings: Box<[ComparableInterpolatedStringElement<'a>]>,
interpolations: Box<[InterpolatedElement<'a>]>,
}
impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
// The approach taken below necessarily deviates from the
// corresponding implementation for [`ast::FStringValue`].
// The reason is that a t-string value is composed of _three_
// non-comparable parts: literals, f-string expressions, and
// t-string interpolations. Since we have merged the AST nodes
// that capture f-string expressions and t-string interpolations
// into the shared [`ast::InterpolatedElement`], we must
// be careful to distinguish between them here.
//
// Consequently, we model a [`ComparableTString`] on the actual
// [CPython implementation] of a `string.templatelib.Template` object:
// it is composed of `strings` and `interpolations`. In CPython,
// the `strings` field is a tuple of honest strings (since f-strings
// are evaluated). Our `strings` field will house both f-string
// expressions and string literals.
//
// Finally, as in CPython, we must be careful to ensure that the length
// of `strings` is always one more than the length of `interpolations` -
// that way we can recover the original reading order by interleaving
// starting with `strings`. This is how we can tell the
// difference between, e.g. `t"{foo}bar"` and `t"bar{foo}"`.
//
// - [CPython implementation](https://github.com/python/cpython/blob/c91ad5da9d92eac4718e4da8d53689c3cc24535e/Python/codegen.c#L4052-L4103)
fn from(value: &'a ast::TStringValue) -> Self {
struct Collector<'a> {
strings: Vec<ComparableInterpolatedStringElement<'a>>,
interpolations: Vec<InterpolatedElement<'a>>,
}
impl Default for Collector<'_> {
fn default() -> Self {
Self {
strings: vec![ComparableInterpolatedStringElement::Literal("".into())],
interpolations: vec![],
}
}
}
impl<'a> Collector<'a> {
// The logic for concatenating adjacent string literals
// occurs here, implicitly: when we encounter a sequence
// of string literals, the first gets pushed to the
// `strings` vector, while subsequent strings
// are concatenated onto this top string.
fn push_literal(&mut self, literal: &'a str) {
if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
self.strings.last_mut()
{
existing_literal.to_mut().push_str(literal);
} else {
self.strings
.push(ComparableInterpolatedStringElement::Literal(literal.into()));
}
}
fn start_new_literal(&mut self) {
self.strings
.push(ComparableInterpolatedStringElement::Literal("".into()));
}
fn push_fstring_expression(&mut self, expression: &'a ast::InterpolatedElement) {
if let Some(ComparableInterpolatedStringElement::Literal(last_literal)) =
self.strings.last()
{
// Recall that we insert empty strings after
// each interpolation. If we encounter an f-string
// expression, we replace the empty string with it.
if last_literal.is_empty() {
self.strings.pop();
}
}
self.strings.push(expression.into());
}
fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) {
self.interpolations.push(expression.into());
self.start_new_literal();
}
}
let mut collector = Collector::default();
for part in value {
match part {
ast::TStringPart::Literal(string_literal) => {
collector.push_literal(&string_literal.value);
}
ast::TStringPart::TString(fstring) => {
for element in &fstring.elements {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::InterpolatedStringElement::Interpolation(interpolation) => {
collector.push_tstring_interpolation(interpolation);
}
}
}
}
ast::TStringPart::FString(fstring) => {
for element in &fstring.elements {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::InterpolatedStringElement::Interpolation(expression) => {
collector.push_fstring_expression(expression);
}
}
}
}
}
}
Self {
strings: collector.strings.into_boxed_slice(),
interpolations: collector.interpolations.into_boxed_slice(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableStringLiteral<'a> {
value: &'a str,
@ -833,11 +969,11 @@ pub struct ExprCall<'a> {
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprFStringExpressionElement<'a> {
pub struct ExprInterpolatedElement<'a> {
value: Box<ComparableExpr<'a>>,
debug_text: Option<&'a ast::DebugText>,
conversion: ast::ConversionFlag,
format_spec: Vec<ComparableFStringElement<'a>>,
format_spec: Vec<ComparableInterpolatedStringElement<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
@ -845,6 +981,11 @@ pub struct ExprFString<'a> {
value: ComparableFString<'a>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprTString<'a> {
value: ComparableTString<'a>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprStringLiteral<'a> {
value: ComparableStringLiteral<'a>,
@ -929,8 +1070,10 @@ pub enum ComparableExpr<'a> {
YieldFrom(ExprYieldFrom<'a>),
Compare(ExprCompare<'a>),
Call(ExprCall<'a>),
FStringExpressionElement(ExprFStringExpressionElement<'a>),
FStringExpressionElement(ExprInterpolatedElement<'a>),
FString(ExprFString<'a>),
TStringInterpolationElement(ExprInterpolatedElement<'a>),
TString(ExprTString<'a>),
StringLiteral(ExprStringLiteral<'a>),
BytesLiteral(ExprBytesLiteral<'a>),
NumberLiteral(ExprNumberLiteral<'a>),
@ -1089,6 +1232,11 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
value: value.into(),
})
}
ast::Expr::TString(ast::ExprTString { value, range: _ }) => {
Self::TString(ExprTString {
value: value.into(),
})
}
ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) => {
Self::StringLiteral(ExprStringLiteral {
value: ComparableStringLiteral {

View file

@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::{
self as ast, AnyNodeRef, AnyStringFlags, Expr, ExprBytesLiteral, ExprFString, ExprRef,
ExprStringLiteral, StringFlags,
ExprStringLiteral, ExprTString, StringFlags,
};
impl<'a> From<&'a Box<Expr>> for ExprRef<'a> {
@ -80,17 +80,18 @@ impl LiteralExpressionRef<'_> {
}
/// An enum that holds a reference to a string-like expression from the AST. This includes string
/// literals, bytes literals, and f-strings.
/// literals, bytes literals, f-strings, and t-strings.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum StringLike<'a> {
String(&'a ast::ExprStringLiteral),
Bytes(&'a ast::ExprBytesLiteral),
FString(&'a ast::ExprFString),
TString(&'a ast::ExprTString),
}
impl<'a> StringLike<'a> {
pub const fn is_fstring(self) -> bool {
matches!(self, Self::FString(_))
pub const fn is_interpolated_string(self) -> bool {
matches!(self, Self::TString(_) | Self::FString(_))
}
/// Returns an iterator over the [`StringLikePart`] contained in this string-like expression.
@ -99,6 +100,7 @@ impl<'a> StringLike<'a> {
StringLike::String(expr) => StringLikePartIter::String(expr.value.iter()),
StringLike::Bytes(expr) => StringLikePartIter::Bytes(expr.value.iter()),
StringLike::FString(expr) => StringLikePartIter::FString(expr.value.iter()),
StringLike::TString(expr) => StringLikePartIter::TString(expr.value.iter()),
}
}
@ -108,6 +110,7 @@ impl<'a> StringLike<'a> {
Self::String(ExprStringLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::Bytes(ExprBytesLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::FString(ExprFString { value, .. }) => value.is_implicit_concatenated(),
Self::TString(ExprTString { value, .. }) => value.is_implicit_concatenated(),
}
}
@ -116,6 +119,7 @@ impl<'a> StringLike<'a> {
StringLike::String(expr) => ExprRef::StringLiteral(expr),
StringLike::Bytes(expr) => ExprRef::BytesLiteral(expr),
StringLike::FString(expr) => ExprRef::FString(expr),
StringLike::TString(expr) => ExprRef::TString(expr),
}
}
}
@ -138,12 +142,19 @@ impl<'a> From<&'a ast::ExprFString> for StringLike<'a> {
}
}
impl<'a> From<&'a ast::ExprTString> for StringLike<'a> {
fn from(value: &'a ast::ExprTString) -> Self {
StringLike::TString(value)
}
}
impl<'a> From<&StringLike<'a>> for ExprRef<'a> {
fn from(value: &StringLike<'a>) -> Self {
match value {
StringLike::String(expr) => ExprRef::StringLiteral(expr),
StringLike::Bytes(expr) => ExprRef::BytesLiteral(expr),
StringLike::FString(expr) => ExprRef::FString(expr),
StringLike::TString(expr) => ExprRef::TString(expr),
}
}
}
@ -160,6 +171,7 @@ impl<'a> From<&StringLike<'a>> for AnyNodeRef<'a> {
StringLike::String(expr) => AnyNodeRef::ExprStringLiteral(expr),
StringLike::Bytes(expr) => AnyNodeRef::ExprBytesLiteral(expr),
StringLike::FString(expr) => AnyNodeRef::ExprFString(expr),
StringLike::TString(expr) => AnyNodeRef::ExprTString(expr),
}
}
}
@ -172,6 +184,7 @@ impl<'a> TryFrom<&'a Expr> for StringLike<'a> {
Expr::StringLiteral(value) => Ok(Self::String(value)),
Expr::BytesLiteral(value) => Ok(Self::Bytes(value)),
Expr::FString(value) => Ok(Self::FString(value)),
Expr::TString(value) => Ok(Self::TString(value)),
_ => Err(()),
}
}
@ -185,6 +198,7 @@ impl<'a> TryFrom<AnyNodeRef<'a>> for StringLike<'a> {
AnyNodeRef::ExprStringLiteral(value) => Ok(Self::String(value)),
AnyNodeRef::ExprBytesLiteral(value) => Ok(Self::Bytes(value)),
AnyNodeRef::ExprFString(value) => Ok(Self::FString(value)),
AnyNodeRef::ExprTString(value) => Ok(Self::TString(value)),
_ => Err(()),
}
}
@ -196,6 +210,7 @@ impl Ranged for StringLike<'_> {
StringLike::String(literal) => literal.range(),
StringLike::Bytes(literal) => literal.range(),
StringLike::FString(literal) => literal.range(),
StringLike::TString(literal) => literal.range(),
}
}
}
@ -206,6 +221,7 @@ pub enum StringLikePart<'a> {
String(&'a ast::StringLiteral),
Bytes(&'a ast::BytesLiteral),
FString(&'a ast::FString),
TString(&'a ast::TString),
}
impl<'a> StringLikePart<'a> {
@ -215,6 +231,7 @@ impl<'a> StringLikePart<'a> {
StringLikePart::String(string) => AnyStringFlags::from(string.flags),
StringLikePart::Bytes(bytes) => AnyStringFlags::from(bytes.flags),
StringLikePart::FString(f_string) => AnyStringFlags::from(f_string.flags),
StringLikePart::TString(t_string) => AnyStringFlags::from(t_string.flags),
}
}
@ -238,8 +255,8 @@ impl<'a> StringLikePart<'a> {
}
}
pub const fn is_fstring(self) -> bool {
matches!(self, Self::FString(_))
pub const fn is_interpolated_string(self) -> bool {
matches!(self, Self::FString(_) | Self::TString(_))
}
}
@ -261,6 +278,12 @@ impl<'a> From<&'a ast::FString> for StringLikePart<'a> {
}
}
impl<'a> From<&'a ast::TString> for StringLikePart<'a> {
fn from(value: &'a ast::TString) -> Self {
StringLikePart::TString(value)
}
}
impl<'a> From<&StringLikePart<'a>> for AnyNodeRef<'a> {
fn from(value: &StringLikePart<'a>) -> Self {
AnyNodeRef::from(*value)
@ -273,6 +296,7 @@ impl<'a> From<StringLikePart<'a>> for AnyNodeRef<'a> {
StringLikePart::String(part) => AnyNodeRef::StringLiteral(part),
StringLikePart::Bytes(part) => AnyNodeRef::BytesLiteral(part),
StringLikePart::FString(part) => AnyNodeRef::FString(part),
StringLikePart::TString(part) => AnyNodeRef::TString(part),
}
}
}
@ -283,6 +307,7 @@ impl Ranged for StringLikePart<'_> {
StringLikePart::String(part) => part.range(),
StringLikePart::Bytes(part) => part.range(),
StringLikePart::FString(part) => part.range(),
StringLikePart::TString(part) => part.range(),
}
}
}
@ -295,6 +320,7 @@ pub enum StringLikePartIter<'a> {
String(std::slice::Iter<'a, ast::StringLiteral>),
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
FString(std::slice::Iter<'a, ast::FStringPart>),
TString(std::slice::Iter<'a, ast::TStringPart>),
}
impl<'a> Iterator for StringLikePartIter<'a> {
@ -313,6 +339,16 @@ impl<'a> Iterator for StringLikePartIter<'a> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => {
let part = inner.next()?;
match part {
ast::TStringPart::Literal(string_literal) => {
StringLikePart::String(string_literal)
}
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
};
Some(part)
@ -323,6 +359,7 @@ impl<'a> Iterator for StringLikePartIter<'a> {
StringLikePartIter::String(inner) => inner.size_hint(),
StringLikePartIter::Bytes(inner) => inner.size_hint(),
StringLikePartIter::FString(inner) => inner.size_hint(),
StringLikePartIter::TString(inner) => inner.size_hint(),
}
}
}
@ -341,6 +378,16 @@ impl DoubleEndedIterator for StringLikePartIter<'_> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => {
let part = inner.next_back()?;
match part {
ast::TStringPart::Literal(string_literal) => {
StringLikePart::String(string_literal)
}
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
};
Some(part)

View file

@ -1270,6 +1270,7 @@ pub enum Expr {
Compare(crate::ExprCompare),
Call(crate::ExprCall),
FString(crate::ExprFString),
TString(crate::ExprTString),
StringLiteral(crate::ExprStringLiteral),
BytesLiteral(crate::ExprBytesLiteral),
NumberLiteral(crate::ExprNumberLiteral),
@ -1394,6 +1395,12 @@ impl From<crate::ExprFString> for Expr {
}
}
impl From<crate::ExprTString> for Expr {
fn from(node: crate::ExprTString) -> Self {
Self::TString(node)
}
}
impl From<crate::ExprStringLiteral> for Expr {
fn from(node: crate::ExprStringLiteral) -> Self {
Self::StringLiteral(node)
@ -1499,6 +1506,7 @@ impl ruff_text_size::Ranged for Expr {
Self::Compare(node) => node.range(),
Self::Call(node) => node.range(),
Self::FString(node) => node.range(),
Self::TString(node) => node.range(),
Self::StringLiteral(node) => node.range(),
Self::BytesLiteral(node) => node.range(),
Self::NumberLiteral(node) => node.range(),
@ -2185,6 +2193,43 @@ impl Expr {
}
}
#[inline]
pub const fn is_t_string_expr(&self) -> bool {
matches!(self, Self::TString(_))
}
#[inline]
pub fn t_string_expr(self) -> Option<crate::ExprTString> {
match self {
Self::TString(val) => Some(val),
_ => None,
}
}
#[inline]
pub fn expect_t_string_expr(self) -> crate::ExprTString {
match self {
Self::TString(val) => val,
_ => panic!("called expect on {self:?}"),
}
}
#[inline]
pub fn as_t_string_expr_mut(&mut self) -> Option<&mut crate::ExprTString> {
match self {
Self::TString(val) => Some(val),
_ => None,
}
}
#[inline]
pub fn as_t_string_expr(&self) -> Option<&crate::ExprTString> {
match self {
Self::TString(val) => Some(val),
_ => None,
}
}
#[inline]
pub const fn is_string_literal_expr(&self) -> bool {
matches!(self, Self::StringLiteral(_))
@ -2761,67 +2806,67 @@ impl ExceptHandler {
}
#[derive(Clone, Debug, PartialEq)]
pub enum FStringElement {
Expression(crate::FStringExpressionElement),
Literal(crate::FStringLiteralElement),
pub enum InterpolatedStringElement {
Interpolation(crate::InterpolatedElement),
Literal(crate::InterpolatedStringLiteralElement),
}
impl From<crate::FStringExpressionElement> for FStringElement {
fn from(node: crate::FStringExpressionElement) -> Self {
Self::Expression(node)
impl From<crate::InterpolatedElement> for InterpolatedStringElement {
fn from(node: crate::InterpolatedElement) -> Self {
Self::Interpolation(node)
}
}
impl From<crate::FStringLiteralElement> for FStringElement {
fn from(node: crate::FStringLiteralElement) -> Self {
impl From<crate::InterpolatedStringLiteralElement> for InterpolatedStringElement {
fn from(node: crate::InterpolatedStringLiteralElement) -> Self {
Self::Literal(node)
}
}
impl ruff_text_size::Ranged for FStringElement {
impl ruff_text_size::Ranged for InterpolatedStringElement {
fn range(&self) -> ruff_text_size::TextRange {
match self {
Self::Expression(node) => node.range(),
Self::Interpolation(node) => node.range(),
Self::Literal(node) => node.range(),
}
}
}
#[allow(dead_code, clippy::match_wildcard_for_single_variants)]
impl FStringElement {
impl InterpolatedStringElement {
#[inline]
pub const fn is_expression(&self) -> bool {
matches!(self, Self::Expression(_))
pub const fn is_interpolation(&self) -> bool {
matches!(self, Self::Interpolation(_))
}
#[inline]
pub fn expression(self) -> Option<crate::FStringExpressionElement> {
pub fn interpolation(self) -> Option<crate::InterpolatedElement> {
match self {
Self::Expression(val) => Some(val),
Self::Interpolation(val) => Some(val),
_ => None,
}
}
#[inline]
pub fn expect_expression(self) -> crate::FStringExpressionElement {
pub fn expect_interpolation(self) -> crate::InterpolatedElement {
match self {
Self::Expression(val) => val,
Self::Interpolation(val) => val,
_ => panic!("called expect on {self:?}"),
}
}
#[inline]
pub fn as_expression_mut(&mut self) -> Option<&mut crate::FStringExpressionElement> {
pub fn as_interpolation_mut(&mut self) -> Option<&mut crate::InterpolatedElement> {
match self {
Self::Expression(val) => Some(val),
Self::Interpolation(val) => Some(val),
_ => None,
}
}
#[inline]
pub fn as_expression(&self) -> Option<&crate::FStringExpressionElement> {
pub fn as_interpolation(&self) -> Option<&crate::InterpolatedElement> {
match self {
Self::Expression(val) => Some(val),
Self::Interpolation(val) => Some(val),
_ => None,
}
}
@ -2832,7 +2877,7 @@ impl FStringElement {
}
#[inline]
pub fn literal(self) -> Option<crate::FStringLiteralElement> {
pub fn literal(self) -> Option<crate::InterpolatedStringLiteralElement> {
match self {
Self::Literal(val) => Some(val),
_ => None,
@ -2840,7 +2885,7 @@ impl FStringElement {
}
#[inline]
pub fn expect_literal(self) -> crate::FStringLiteralElement {
pub fn expect_literal(self) -> crate::InterpolatedStringLiteralElement {
match self {
Self::Literal(val) => val,
_ => panic!("called expect on {self:?}"),
@ -2848,7 +2893,7 @@ impl FStringElement {
}
#[inline]
pub fn as_literal_mut(&mut self) -> Option<&mut crate::FStringLiteralElement> {
pub fn as_literal_mut(&mut self) -> Option<&mut crate::InterpolatedStringLiteralElement> {
match self {
Self::Literal(val) => Some(val),
_ => None,
@ -2856,7 +2901,7 @@ impl FStringElement {
}
#[inline]
pub fn as_literal(&self) -> Option<&crate::FStringLiteralElement> {
pub fn as_literal(&self) -> Option<&crate::InterpolatedStringLiteralElement> {
match self {
Self::Literal(val) => Some(val),
_ => None,
@ -3659,6 +3704,12 @@ impl ruff_text_size::Ranged for crate::ExprFString {
}
}
impl ruff_text_size::Ranged for crate::ExprTString {
fn range(&self) -> ruff_text_size::TextRange {
self.range
}
}
impl ruff_text_size::Ranged for crate::ExprStringLiteral {
fn range(&self) -> ruff_text_size::TextRange {
self.range
@ -3749,13 +3800,13 @@ impl ruff_text_size::Ranged for crate::ExceptHandlerExceptHandler {
}
}
impl ruff_text_size::Ranged for crate::FStringExpressionElement {
impl ruff_text_size::Ranged for crate::InterpolatedElement {
fn range(&self) -> ruff_text_size::TextRange {
self.range
}
}
impl ruff_text_size::Ranged for crate::FStringLiteralElement {
impl ruff_text_size::Ranged for crate::InterpolatedStringLiteralElement {
fn range(&self) -> ruff_text_size::TextRange {
self.range
}
@ -3827,7 +3878,7 @@ impl ruff_text_size::Ranged for crate::TypeParamParamSpec {
}
}
impl ruff_text_size::Ranged for crate::FStringFormatSpec {
impl ruff_text_size::Ranged for crate::InterpolatedStringFormatSpec {
fn range(&self) -> ruff_text_size::TextRange {
self.range
}
@ -3923,6 +3974,12 @@ impl ruff_text_size::Ranged for crate::FString {
}
}
impl ruff_text_size::Ranged for crate::TString {
fn range(&self) -> ruff_text_size::TextRange {
self.range
}
}
impl ruff_text_size::Ranged for crate::StringLiteral {
fn range(&self) -> ruff_text_size::TextRange {
self.range
@ -4015,6 +4072,7 @@ impl Expr {
Expr::Compare(node) => node.visit_source_order(visitor),
Expr::Call(node) => node.visit_source_order(visitor),
Expr::FString(node) => node.visit_source_order(visitor),
Expr::TString(node) => node.visit_source_order(visitor),
Expr::StringLiteral(node) => node.visit_source_order(visitor),
Expr::BytesLiteral(node) => node.visit_source_order(visitor),
Expr::NumberLiteral(node) => node.visit_source_order(visitor),
@ -4045,15 +4103,15 @@ impl ExceptHandler {
}
}
impl FStringElement {
impl InterpolatedStringElement {
#[allow(unused)]
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized,
{
match self {
FStringElement::Expression(node) => node.visit_source_order(visitor),
FStringElement::Literal(node) => node.visit_source_order(visitor),
InterpolatedStringElement::Interpolation(node) => node.visit_source_order(visitor),
InterpolatedStringElement::Literal(node) => node.visit_source_order(visitor),
}
}
}
@ -4436,6 +4494,8 @@ pub enum ExprRef<'a> {
Call(&'a crate::ExprCall),
#[is(name = "f_string_expr")]
FString(&'a crate::ExprFString),
#[is(name = "t_string_expr")]
TString(&'a crate::ExprTString),
#[is(name = "string_literal_expr")]
StringLiteral(&'a crate::ExprStringLiteral),
#[is(name = "bytes_literal_expr")]
@ -4487,6 +4547,7 @@ impl<'a> From<&'a Expr> for ExprRef<'a> {
Expr::Compare(node) => ExprRef::Compare(node),
Expr::Call(node) => ExprRef::Call(node),
Expr::FString(node) => ExprRef::FString(node),
Expr::TString(node) => ExprRef::TString(node),
Expr::StringLiteral(node) => ExprRef::StringLiteral(node),
Expr::BytesLiteral(node) => ExprRef::BytesLiteral(node),
Expr::NumberLiteral(node) => ExprRef::NumberLiteral(node),
@ -4613,6 +4674,12 @@ impl<'a> From<&'a crate::ExprFString> for ExprRef<'a> {
}
}
impl<'a> From<&'a crate::ExprTString> for ExprRef<'a> {
fn from(node: &'a crate::ExprTString) -> Self {
Self::TString(node)
}
}
impl<'a> From<&'a crate::ExprStringLiteral> for ExprRef<'a> {
fn from(node: &'a crate::ExprStringLiteral) -> Self {
Self::StringLiteral(node)
@ -4718,6 +4785,7 @@ impl ruff_text_size::Ranged for ExprRef<'_> {
Self::Compare(node) => node.range(),
Self::Call(node) => node.range(),
Self::FString(node) => node.range(),
Self::TString(node) => node.range(),
Self::StringLiteral(node) => node.range(),
Self::BytesLiteral(node) => node.range(),
Self::NumberLiteral(node) => node.range(),
@ -4765,36 +4833,38 @@ impl ruff_text_size::Ranged for ExceptHandlerRef<'_> {
}
#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)]
pub enum FStringElementRef<'a> {
Expression(&'a crate::FStringExpressionElement),
Literal(&'a crate::FStringLiteralElement),
pub enum InterpolatedStringElementRef<'a> {
Interpolation(&'a crate::InterpolatedElement),
Literal(&'a crate::InterpolatedStringLiteralElement),
}
impl<'a> From<&'a FStringElement> for FStringElementRef<'a> {
fn from(node: &'a FStringElement) -> Self {
impl<'a> From<&'a InterpolatedStringElement> for InterpolatedStringElementRef<'a> {
fn from(node: &'a InterpolatedStringElement) -> Self {
match node {
FStringElement::Expression(node) => FStringElementRef::Expression(node),
FStringElement::Literal(node) => FStringElementRef::Literal(node),
InterpolatedStringElement::Interpolation(node) => {
InterpolatedStringElementRef::Interpolation(node)
}
InterpolatedStringElement::Literal(node) => InterpolatedStringElementRef::Literal(node),
}
}
}
impl<'a> From<&'a crate::FStringExpressionElement> for FStringElementRef<'a> {
fn from(node: &'a crate::FStringExpressionElement) -> Self {
Self::Expression(node)
impl<'a> From<&'a crate::InterpolatedElement> for InterpolatedStringElementRef<'a> {
fn from(node: &'a crate::InterpolatedElement) -> Self {
Self::Interpolation(node)
}
}
impl<'a> From<&'a crate::FStringLiteralElement> for FStringElementRef<'a> {
fn from(node: &'a crate::FStringLiteralElement) -> Self {
impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for InterpolatedStringElementRef<'a> {
fn from(node: &'a crate::InterpolatedStringLiteralElement) -> Self {
Self::Literal(node)
}
}
impl ruff_text_size::Ranged for FStringElementRef<'_> {
impl ruff_text_size::Ranged for InterpolatedStringElementRef<'_> {
fn range(&self) -> ruff_text_size::TextRange {
match self {
Self::Expression(node) => node.range(),
Self::Interpolation(node) => node.range(),
Self::Literal(node) => node.range(),
}
}
@ -4984,6 +5054,7 @@ pub enum AnyNodeRef<'a> {
ExprCompare(&'a crate::ExprCompare),
ExprCall(&'a crate::ExprCall),
ExprFString(&'a crate::ExprFString),
ExprTString(&'a crate::ExprTString),
ExprStringLiteral(&'a crate::ExprStringLiteral),
ExprBytesLiteral(&'a crate::ExprBytesLiteral),
ExprNumberLiteral(&'a crate::ExprNumberLiteral),
@ -4999,8 +5070,8 @@ pub enum AnyNodeRef<'a> {
ExprSlice(&'a crate::ExprSlice),
ExprIpyEscapeCommand(&'a crate::ExprIpyEscapeCommand),
ExceptHandlerExceptHandler(&'a crate::ExceptHandlerExceptHandler),
FStringExpressionElement(&'a crate::FStringExpressionElement),
FStringLiteralElement(&'a crate::FStringLiteralElement),
InterpolatedElement(&'a crate::InterpolatedElement),
InterpolatedStringLiteralElement(&'a crate::InterpolatedStringLiteralElement),
PatternMatchValue(&'a crate::PatternMatchValue),
PatternMatchSingleton(&'a crate::PatternMatchSingleton),
PatternMatchSequence(&'a crate::PatternMatchSequence),
@ -5012,7 +5083,7 @@ pub enum AnyNodeRef<'a> {
TypeParamTypeVar(&'a crate::TypeParamTypeVar),
TypeParamTypeVarTuple(&'a crate::TypeParamTypeVarTuple),
TypeParamParamSpec(&'a crate::TypeParamParamSpec),
FStringFormatSpec(&'a crate::FStringFormatSpec),
InterpolatedStringFormatSpec(&'a crate::InterpolatedStringFormatSpec),
PatternArguments(&'a crate::PatternArguments),
PatternKeyword(&'a crate::PatternKeyword),
Comprehension(&'a crate::Comprehension),
@ -5028,6 +5099,7 @@ pub enum AnyNodeRef<'a> {
ElifElseClause(&'a crate::ElifElseClause),
TypeParams(&'a crate::TypeParams),
FString(&'a crate::FString),
TString(&'a crate::TString),
StringLiteral(&'a crate::StringLiteral),
BytesLiteral(&'a crate::BytesLiteral),
Identifier(&'a crate::Identifier),
@ -5181,6 +5253,7 @@ impl<'a> From<&'a Expr> for AnyNodeRef<'a> {
Expr::Compare(node) => AnyNodeRef::ExprCompare(node),
Expr::Call(node) => AnyNodeRef::ExprCall(node),
Expr::FString(node) => AnyNodeRef::ExprFString(node),
Expr::TString(node) => AnyNodeRef::ExprTString(node),
Expr::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node),
Expr::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node),
Expr::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node),
@ -5220,6 +5293,7 @@ impl<'a> From<ExprRef<'a>> for AnyNodeRef<'a> {
ExprRef::Compare(node) => AnyNodeRef::ExprCompare(node),
ExprRef::Call(node) => AnyNodeRef::ExprCall(node),
ExprRef::FString(node) => AnyNodeRef::ExprFString(node),
ExprRef::TString(node) => AnyNodeRef::ExprTString(node),
ExprRef::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node),
ExprRef::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node),
ExprRef::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node),
@ -5259,6 +5333,7 @@ impl<'a> AnyNodeRef<'a> {
Self::ExprCompare(node) => Some(ExprRef::Compare(node)),
Self::ExprCall(node) => Some(ExprRef::Call(node)),
Self::ExprFString(node) => Some(ExprRef::FString(node)),
Self::ExprTString(node) => Some(ExprRef::TString(node)),
Self::ExprStringLiteral(node) => Some(ExprRef::StringLiteral(node)),
Self::ExprBytesLiteral(node) => Some(ExprRef::BytesLiteral(node)),
Self::ExprNumberLiteral(node) => Some(ExprRef::NumberLiteral(node)),
@ -5305,29 +5380,39 @@ impl<'a> AnyNodeRef<'a> {
}
}
impl<'a> From<&'a FStringElement> for AnyNodeRef<'a> {
fn from(node: &'a FStringElement) -> AnyNodeRef<'a> {
impl<'a> From<&'a InterpolatedStringElement> for AnyNodeRef<'a> {
fn from(node: &'a InterpolatedStringElement) -> AnyNodeRef<'a> {
match node {
FStringElement::Expression(node) => AnyNodeRef::FStringExpressionElement(node),
FStringElement::Literal(node) => AnyNodeRef::FStringLiteralElement(node),
InterpolatedStringElement::Interpolation(node) => AnyNodeRef::InterpolatedElement(node),
InterpolatedStringElement::Literal(node) => {
AnyNodeRef::InterpolatedStringLiteralElement(node)
}
}
}
}
impl<'a> From<FStringElementRef<'a>> for AnyNodeRef<'a> {
fn from(node: FStringElementRef<'a>) -> AnyNodeRef<'a> {
impl<'a> From<InterpolatedStringElementRef<'a>> for AnyNodeRef<'a> {
fn from(node: InterpolatedStringElementRef<'a>) -> AnyNodeRef<'a> {
match node {
FStringElementRef::Expression(node) => AnyNodeRef::FStringExpressionElement(node),
FStringElementRef::Literal(node) => AnyNodeRef::FStringLiteralElement(node),
InterpolatedStringElementRef::Interpolation(node) => {
AnyNodeRef::InterpolatedElement(node)
}
InterpolatedStringElementRef::Literal(node) => {
AnyNodeRef::InterpolatedStringLiteralElement(node)
}
}
}
}
impl<'a> AnyNodeRef<'a> {
pub fn as_f_string_element_ref(self) -> Option<FStringElementRef<'a>> {
pub fn as_interpolated_string_element_ref(self) -> Option<InterpolatedStringElementRef<'a>> {
match self {
Self::FStringExpressionElement(node) => Some(FStringElementRef::Expression(node)),
Self::FStringLiteralElement(node) => Some(FStringElementRef::Literal(node)),
Self::InterpolatedElement(node) => {
Some(InterpolatedStringElementRef::Interpolation(node))
}
Self::InterpolatedStringLiteralElement(node) => {
Some(InterpolatedStringElementRef::Literal(node))
}
_ => None,
}
@ -5683,6 +5768,12 @@ impl<'a> From<&'a crate::ExprFString> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a crate::ExprTString> for AnyNodeRef<'a> {
fn from(node: &'a crate::ExprTString) -> AnyNodeRef<'a> {
AnyNodeRef::ExprTString(node)
}
}
impl<'a> From<&'a crate::ExprStringLiteral> for AnyNodeRef<'a> {
fn from(node: &'a crate::ExprStringLiteral) -> AnyNodeRef<'a> {
AnyNodeRef::ExprStringLiteral(node)
@ -5773,15 +5864,15 @@ impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a crate::FStringExpressionElement> for AnyNodeRef<'a> {
fn from(node: &'a crate::FStringExpressionElement) -> AnyNodeRef<'a> {
AnyNodeRef::FStringExpressionElement(node)
impl<'a> From<&'a crate::InterpolatedElement> for AnyNodeRef<'a> {
fn from(node: &'a crate::InterpolatedElement) -> AnyNodeRef<'a> {
AnyNodeRef::InterpolatedElement(node)
}
}
impl<'a> From<&'a crate::FStringLiteralElement> for AnyNodeRef<'a> {
fn from(node: &'a crate::FStringLiteralElement) -> AnyNodeRef<'a> {
AnyNodeRef::FStringLiteralElement(node)
impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for AnyNodeRef<'a> {
fn from(node: &'a crate::InterpolatedStringLiteralElement) -> AnyNodeRef<'a> {
AnyNodeRef::InterpolatedStringLiteralElement(node)
}
}
@ -5851,9 +5942,9 @@ impl<'a> From<&'a crate::TypeParamParamSpec> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a crate::FStringFormatSpec> for AnyNodeRef<'a> {
fn from(node: &'a crate::FStringFormatSpec) -> AnyNodeRef<'a> {
AnyNodeRef::FStringFormatSpec(node)
impl<'a> From<&'a crate::InterpolatedStringFormatSpec> for AnyNodeRef<'a> {
fn from(node: &'a crate::InterpolatedStringFormatSpec) -> AnyNodeRef<'a> {
AnyNodeRef::InterpolatedStringFormatSpec(node)
}
}
@ -5947,6 +6038,12 @@ impl<'a> From<&'a crate::FString> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a crate::TString> for AnyNodeRef<'a> {
fn from(node: &'a crate::TString) -> AnyNodeRef<'a> {
AnyNodeRef::TString(node)
}
}
impl<'a> From<&'a crate::StringLiteral> for AnyNodeRef<'a> {
fn from(node: &'a crate::StringLiteral) -> AnyNodeRef<'a> {
AnyNodeRef::StringLiteral(node)
@ -6013,6 +6110,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> {
AnyNodeRef::ExprCompare(node) => node.range(),
AnyNodeRef::ExprCall(node) => node.range(),
AnyNodeRef::ExprFString(node) => node.range(),
AnyNodeRef::ExprTString(node) => node.range(),
AnyNodeRef::ExprStringLiteral(node) => node.range(),
AnyNodeRef::ExprBytesLiteral(node) => node.range(),
AnyNodeRef::ExprNumberLiteral(node) => node.range(),
@ -6028,8 +6126,8 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> {
AnyNodeRef::ExprSlice(node) => node.range(),
AnyNodeRef::ExprIpyEscapeCommand(node) => node.range(),
AnyNodeRef::ExceptHandlerExceptHandler(node) => node.range(),
AnyNodeRef::FStringExpressionElement(node) => node.range(),
AnyNodeRef::FStringLiteralElement(node) => node.range(),
AnyNodeRef::InterpolatedElement(node) => node.range(),
AnyNodeRef::InterpolatedStringLiteralElement(node) => node.range(),
AnyNodeRef::PatternMatchValue(node) => node.range(),
AnyNodeRef::PatternMatchSingleton(node) => node.range(),
AnyNodeRef::PatternMatchSequence(node) => node.range(),
@ -6041,7 +6139,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> {
AnyNodeRef::TypeParamTypeVar(node) => node.range(),
AnyNodeRef::TypeParamTypeVarTuple(node) => node.range(),
AnyNodeRef::TypeParamParamSpec(node) => node.range(),
AnyNodeRef::FStringFormatSpec(node) => node.range(),
AnyNodeRef::InterpolatedStringFormatSpec(node) => node.range(),
AnyNodeRef::PatternArguments(node) => node.range(),
AnyNodeRef::PatternKeyword(node) => node.range(),
AnyNodeRef::Comprehension(node) => node.range(),
@ -6057,6 +6155,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> {
AnyNodeRef::ElifElseClause(node) => node.range(),
AnyNodeRef::TypeParams(node) => node.range(),
AnyNodeRef::FString(node) => node.range(),
AnyNodeRef::TString(node) => node.range(),
AnyNodeRef::StringLiteral(node) => node.range(),
AnyNodeRef::BytesLiteral(node) => node.range(),
AnyNodeRef::Identifier(node) => node.range(),
@ -6112,6 +6211,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ExprCompare(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprCall(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprFString(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprTString(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprStringLiteral(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprBytesLiteral(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprNumberLiteral(node) => std::ptr::NonNull::from(*node).cast(),
@ -6127,8 +6227,10 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ExprSlice(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExprIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::ExceptHandlerExceptHandler(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::FStringExpressionElement(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::FStringLiteralElement(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::InterpolatedElement(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::InterpolatedStringLiteralElement(node) => {
std::ptr::NonNull::from(*node).cast()
}
AnyNodeRef::PatternMatchValue(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::PatternMatchSingleton(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::PatternMatchSequence(node) => std::ptr::NonNull::from(*node).cast(),
@ -6140,7 +6242,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::TypeParamTypeVar(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::TypeParamTypeVarTuple(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::TypeParamParamSpec(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::FStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::InterpolatedStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::PatternArguments(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::PatternKeyword(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::Comprehension(node) => std::ptr::NonNull::from(*node).cast(),
@ -6156,6 +6258,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ElifElseClause(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::TypeParams(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::FString(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::TString(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::StringLiteral(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::BytesLiteral(node) => std::ptr::NonNull::from(*node).cast(),
AnyNodeRef::Identifier(node) => std::ptr::NonNull::from(*node).cast(),
@ -6215,6 +6318,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::ExprCompare(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprCall(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprFString(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprTString(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprStringLiteral(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprBytesLiteral(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprNumberLiteral(node) => node.visit_source_order(visitor),
@ -6230,8 +6334,8 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::ExprSlice(node) => node.visit_source_order(visitor),
AnyNodeRef::ExprIpyEscapeCommand(node) => node.visit_source_order(visitor),
AnyNodeRef::ExceptHandlerExceptHandler(node) => node.visit_source_order(visitor),
AnyNodeRef::FStringExpressionElement(node) => node.visit_source_order(visitor),
AnyNodeRef::FStringLiteralElement(node) => node.visit_source_order(visitor),
AnyNodeRef::InterpolatedElement(node) => node.visit_source_order(visitor),
AnyNodeRef::InterpolatedStringLiteralElement(node) => node.visit_source_order(visitor),
AnyNodeRef::PatternMatchValue(node) => node.visit_source_order(visitor),
AnyNodeRef::PatternMatchSingleton(node) => node.visit_source_order(visitor),
AnyNodeRef::PatternMatchSequence(node) => node.visit_source_order(visitor),
@ -6243,7 +6347,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::TypeParamTypeVar(node) => node.visit_source_order(visitor),
AnyNodeRef::TypeParamTypeVarTuple(node) => node.visit_source_order(visitor),
AnyNodeRef::TypeParamParamSpec(node) => node.visit_source_order(visitor),
AnyNodeRef::FStringFormatSpec(node) => node.visit_source_order(visitor),
AnyNodeRef::InterpolatedStringFormatSpec(node) => node.visit_source_order(visitor),
AnyNodeRef::PatternArguments(node) => node.visit_source_order(visitor),
AnyNodeRef::PatternKeyword(node) => node.visit_source_order(visitor),
AnyNodeRef::Comprehension(node) => node.visit_source_order(visitor),
@ -6259,6 +6363,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::ElifElseClause(node) => node.visit_source_order(visitor),
AnyNodeRef::TypeParams(node) => node.visit_source_order(visitor),
AnyNodeRef::FString(node) => node.visit_source_order(visitor),
AnyNodeRef::TString(node) => node.visit_source_order(visitor),
AnyNodeRef::StringLiteral(node) => node.visit_source_order(visitor),
AnyNodeRef::BytesLiteral(node) => node.visit_source_order(visitor),
AnyNodeRef::Identifier(node) => node.visit_source_order(visitor),
@ -6330,6 +6435,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::ExprCompare(_)
| AnyNodeRef::ExprCall(_)
| AnyNodeRef::ExprFString(_)
| AnyNodeRef::ExprTString(_)
| AnyNodeRef::ExprStringLiteral(_)
| AnyNodeRef::ExprBytesLiteral(_)
| AnyNodeRef::ExprNumberLiteral(_)
@ -6355,10 +6461,10 @@ impl AnyNodeRef<'_> {
}
impl AnyNodeRef<'_> {
pub const fn is_f_string_element(self) -> bool {
pub const fn is_interpolated_string_element(self) -> bool {
matches!(
self,
AnyNodeRef::FStringExpressionElement(_) | AnyNodeRef::FStringLiteralElement(_)
AnyNodeRef::InterpolatedElement(_) | AnyNodeRef::InterpolatedStringLiteralElement(_)
)
}
}
@ -6437,6 +6543,7 @@ pub enum NodeKind {
ExprCompare,
ExprCall,
ExprFString,
ExprTString,
ExprStringLiteral,
ExprBytesLiteral,
ExprNumberLiteral,
@ -6452,8 +6559,8 @@ pub enum NodeKind {
ExprSlice,
ExprIpyEscapeCommand,
ExceptHandlerExceptHandler,
FStringExpressionElement,
FStringLiteralElement,
InterpolatedElement,
InterpolatedStringLiteralElement,
PatternMatchValue,
PatternMatchSingleton,
PatternMatchSequence,
@ -6465,7 +6572,7 @@ pub enum NodeKind {
TypeParamTypeVar,
TypeParamTypeVarTuple,
TypeParamParamSpec,
FStringFormatSpec,
InterpolatedStringFormatSpec,
PatternArguments,
PatternKeyword,
Comprehension,
@ -6481,6 +6588,7 @@ pub enum NodeKind {
ElifElseClause,
TypeParams,
FString,
TString,
StringLiteral,
BytesLiteral,
Identifier,
@ -6534,6 +6642,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ExprCompare(_) => NodeKind::ExprCompare,
AnyNodeRef::ExprCall(_) => NodeKind::ExprCall,
AnyNodeRef::ExprFString(_) => NodeKind::ExprFString,
AnyNodeRef::ExprTString(_) => NodeKind::ExprTString,
AnyNodeRef::ExprStringLiteral(_) => NodeKind::ExprStringLiteral,
AnyNodeRef::ExprBytesLiteral(_) => NodeKind::ExprBytesLiteral,
AnyNodeRef::ExprNumberLiteral(_) => NodeKind::ExprNumberLiteral,
@ -6549,8 +6658,10 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ExprSlice(_) => NodeKind::ExprSlice,
AnyNodeRef::ExprIpyEscapeCommand(_) => NodeKind::ExprIpyEscapeCommand,
AnyNodeRef::ExceptHandlerExceptHandler(_) => NodeKind::ExceptHandlerExceptHandler,
AnyNodeRef::FStringExpressionElement(_) => NodeKind::FStringExpressionElement,
AnyNodeRef::FStringLiteralElement(_) => NodeKind::FStringLiteralElement,
AnyNodeRef::InterpolatedElement(_) => NodeKind::InterpolatedElement,
AnyNodeRef::InterpolatedStringLiteralElement(_) => {
NodeKind::InterpolatedStringLiteralElement
}
AnyNodeRef::PatternMatchValue(_) => NodeKind::PatternMatchValue,
AnyNodeRef::PatternMatchSingleton(_) => NodeKind::PatternMatchSingleton,
AnyNodeRef::PatternMatchSequence(_) => NodeKind::PatternMatchSequence,
@ -6562,7 +6673,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::TypeParamTypeVar(_) => NodeKind::TypeParamTypeVar,
AnyNodeRef::TypeParamTypeVarTuple(_) => NodeKind::TypeParamTypeVarTuple,
AnyNodeRef::TypeParamParamSpec(_) => NodeKind::TypeParamParamSpec,
AnyNodeRef::FStringFormatSpec(_) => NodeKind::FStringFormatSpec,
AnyNodeRef::InterpolatedStringFormatSpec(_) => NodeKind::InterpolatedStringFormatSpec,
AnyNodeRef::PatternArguments(_) => NodeKind::PatternArguments,
AnyNodeRef::PatternKeyword(_) => NodeKind::PatternKeyword,
AnyNodeRef::Comprehension(_) => NodeKind::Comprehension,
@ -6578,6 +6689,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause,
AnyNodeRef::TypeParams(_) => NodeKind::TypeParams,
AnyNodeRef::FString(_) => NodeKind::FString,
AnyNodeRef::TString(_) => NodeKind::TString,
AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral,
AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral,
AnyNodeRef::Identifier(_) => NodeKind::Identifier,
@ -7023,6 +7135,20 @@ pub struct ExprFString {
pub value: crate::FStringValue,
}
/// An AST node that represents either a single-part t-string literal
/// or an implicitly concatenated t-string literal.
///
/// This type differs from the original Python AST `TemplateStr` in that it
/// doesn't join the implicitly concatenated parts into a single string. Instead,
/// it keeps them separate and provide various methods to access the parts.
///
/// See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateStr)
#[derive(Clone, Debug, PartialEq)]
pub struct ExprTString {
pub range: ruff_text_size::TextRange,
pub value: crate::TStringValue,
}
/// An AST node that represents either a single-part string literal
/// or an implicitly concatenated string literal.
#[derive(Clone, Debug, PartialEq)]

View file

@ -12,8 +12,8 @@ use crate::parenthesize::parenthesized_range;
use crate::statement_visitor::StatementVisitor;
use crate::visitor::Visitor;
use crate::{
self as ast, Arguments, CmpOp, DictItem, ExceptHandler, Expr, FStringElement, MatchCase,
Operator, Pattern, Stmt, TypeParam,
self as ast, Arguments, CmpOp, DictItem, ExceptHandler, Expr, InterpolatedStringElement,
MatchCase, Operator, Pattern, Stmt, TypeParam,
};
use crate::{AnyNodeRef, ExprContext};
@ -138,7 +138,10 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool {
}
Expr::FString(ast::ExprFString { value, .. }) => value
.elements()
.any(|expr| any_over_f_string_element(expr, func)),
.any(|expr| any_over_interpolated_string_element(expr, func)),
Expr::TString(ast::ExprTString { value, .. }) => value
.elements()
.any(|expr| any_over_interpolated_string_element(expr, func)),
Expr::Named(ast::ExprNamed {
target,
value,
@ -315,22 +318,22 @@ pub fn any_over_pattern(pattern: &Pattern, func: &dyn Fn(&Expr) -> bool) -> bool
}
}
pub fn any_over_f_string_element(
element: &ast::FStringElement,
pub fn any_over_interpolated_string_element(
element: &ast::InterpolatedStringElement,
func: &dyn Fn(&Expr) -> bool,
) -> bool {
match element {
ast::FStringElement::Literal(_) => false,
ast::FStringElement::Expression(ast::FStringExpressionElement {
ast::InterpolatedStringElement::Literal(_) => false,
ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
expression,
format_spec,
..
}) => {
any_over_expr(expression, func)
|| format_spec.as_ref().is_some_and(|spec| {
spec.elements
.iter()
.any(|spec_element| any_over_f_string_element(spec_element, func))
spec.elements.iter().any(|spec_element| {
any_over_interpolated_string_element(spec_element, func)
})
})
}
}
@ -1304,6 +1307,8 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
// These literals may or may not be empty.
Expr::FString(f_string) => is_non_empty_f_string(f_string),
// These literals may or may not be empty.
Expr::TString(f_string) => is_non_empty_t_string(f_string),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
}
@ -1313,8 +1318,78 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
ast::FStringPart::Literal(string_literal) => !string_literal.is_empty(),
ast::FStringPart::FString(f_string) => {
f_string.elements.iter().all(|element| match element {
FStringElement::Literal(string_literal) => !string_literal.is_empty(),
FStringElement::Expression(f_string) => inner(&f_string.expression),
InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
})
}
})
}
/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
/// f-string expression, or `false` if the expression may resolve to an empty string.
fn is_non_empty_t_string(expr: &ast::ExprTString) -> bool {
fn inner(expr: &Expr) -> bool {
match expr {
// When stringified, these expressions are always non-empty.
Expr::Lambda(_) => true,
Expr::Dict(_) => true,
Expr::Set(_) => true,
Expr::ListComp(_) => true,
Expr::SetComp(_) => true,
Expr::DictComp(_) => true,
Expr::Compare(_) => true,
Expr::NumberLiteral(_) => true,
Expr::BooleanLiteral(_) => true,
Expr::NoneLiteral(_) => true,
Expr::EllipsisLiteral(_) => true,
Expr::List(_) => true,
Expr::Tuple(_) => true,
// These expressions must resolve to the inner expression.
Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
Expr::Named(ast::ExprNamed { value, .. }) => inner(value),
// These expressions are complex. We can't determine whether they're empty or not.
Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
Expr::BinOp(ast::ExprBinOp { .. }) => false,
Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
Expr::Generator(_) => false,
Expr::Await(_) => false,
Expr::Yield(_) => false,
Expr::YieldFrom(_) => false,
Expr::Call(_) => false,
Expr::Attribute(_) => false,
Expr::Subscript(_) => false,
Expr::Starred(_) => false,
Expr::Name(_) => false,
Expr::Slice(_) => false,
Expr::IpyEscapeCommand(_) => false,
// These literals may or may not be empty.
Expr::FString(f_string) => is_non_empty_f_string(f_string),
// These literals may or may not be empty.
Expr::TString(t_string) => is_non_empty_t_string(t_string),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
}
}
expr.value.iter().any(|part| match part {
ast::TStringPart::Literal(string_literal) => !string_literal.is_empty(),
ast::TStringPart::TString(t_string) => {
t_string.elements.iter().all(|element| match element {
ast::InterpolatedStringElement::Literal(string_literal) => {
!string_literal.is_empty()
}
ast::InterpolatedStringElement::Interpolation(t_string) => {
inner(&t_string.expression)
}
})
}
ast::TStringPart::FString(f_string) => {
f_string.elements.iter().all(|element| match element {
InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
})
}
})
@ -1331,10 +1406,10 @@ fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
value
.elements()
.all(|f_string_element| match f_string_element {
FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => {
value.is_empty()
}
FStringElement::Expression(ast::FStringExpressionElement {
InterpolatedStringElement::Literal(
ast::InterpolatedStringLiteralElement { value, .. },
) => value.is_empty(),
InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
expression,
..
}) => inner(expression),
@ -1348,8 +1423,8 @@ fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
ast::FStringPart::Literal(string_literal) => string_literal.is_empty(),
ast::FStringPart::FString(f_string) => {
f_string.elements.iter().all(|element| match element {
FStringElement::Literal(string_literal) => string_literal.is_empty(),
FStringElement::Expression(f_string) => inner(&f_string.expression),
InterpolatedStringElement::Literal(string_literal) => string_literal.is_empty(),
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
})
}
})

View file

@ -85,23 +85,23 @@ impl ast::ExprCompare {
}
}
impl ast::FStringFormatSpec {
impl ast::InterpolatedStringFormatSpec {
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
for element in &self.elements {
visitor.visit_f_string_element(element);
visitor.visit_interpolated_string_element(element);
}
}
}
impl ast::FStringExpressionElement {
impl ast::InterpolatedElement {
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
let ast::FStringExpressionElement {
let ast::InterpolatedElement {
expression,
format_spec,
..
@ -110,18 +110,18 @@ impl ast::FStringExpressionElement {
if let Some(format_spec) = format_spec {
for spec_part in &format_spec.elements {
visitor.visit_f_string_element(spec_part);
visitor.visit_interpolated_string_element(spec_part);
}
}
}
}
impl ast::FStringLiteralElement {
impl ast::InterpolatedStringLiteralElement {
pub(crate) fn visit_source_order<'a, V>(&'a self, _visitor: &mut V)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
let ast::FStringLiteralElement { range: _, value: _ } = self;
let ast::InterpolatedStringLiteralElement { range: _, value: _ } = self;
}
}
@ -145,6 +145,29 @@ impl ast::ExprFString {
}
}
impl ast::ExprTString {
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
let ast::ExprTString { value, range: _ } = self;
for t_string_part in value {
match t_string_part {
ast::TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::TStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
ast::TStringPart::TString(t_string) => {
visitor.visit_t_string(t_string);
}
}
}
}
}
impl ast::ExprStringLiteral {
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
@ -615,7 +638,24 @@ impl ast::FString {
} = self;
for fstring_element in elements {
visitor.visit_f_string_element(fstring_element);
visitor.visit_interpolated_string_element(fstring_element);
}
}
}
impl ast::TString {
pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
let ast::TString {
elements,
range: _,
flags: _,
} = self;
for tstring_element in elements {
visitor.visit_interpolated_string_element(tstring_element);
}
}
}

View file

@ -2,7 +2,7 @@
use crate::generated::{
ExprBytesLiteral, ExprDict, ExprFString, ExprList, ExprName, ExprSet, ExprStringLiteral,
ExprTuple, StmtClassDef,
ExprTString, ExprTuple, StmtClassDef,
};
use std::borrow::Cow;
use std::fmt;
@ -17,10 +17,12 @@ use itertools::Itertools;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::str_prefix::{AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix};
use crate::str_prefix::{
AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, TStringPrefix,
};
use crate::{
Expr, ExprRef, FStringElement, LiteralExpressionRef, OperatorPrecedence, Pattern, Stmt,
TypeParam, int,
Expr, ExprRef, InterpolatedStringElement, LiteralExpressionRef, OperatorPrecedence, Pattern,
Stmt, TypeParam, int,
name::Name,
str::{Quote, TripleQuotes},
};
@ -312,35 +314,35 @@ impl<'a> IntoIterator for &'a ExprSet {
}
#[derive(Clone, Debug, PartialEq)]
pub struct FStringFormatSpec {
pub struct InterpolatedStringFormatSpec {
pub range: TextRange,
pub elements: FStringElements,
pub elements: InterpolatedStringElements,
}
/// See also [FormattedValue](https://docs.python.org/3/library/ast.html#ast.FormattedValue)
#[derive(Clone, Debug, PartialEq)]
pub struct FStringExpressionElement {
pub struct InterpolatedElement {
pub range: TextRange,
pub expression: Box<Expr>,
pub debug_text: Option<DebugText>,
pub conversion: ConversionFlag,
pub format_spec: Option<Box<FStringFormatSpec>>,
pub format_spec: Option<Box<InterpolatedStringFormatSpec>>,
}
/// An `FStringLiteralElement` with an empty `value` is an invalid f-string element.
#[derive(Clone, Debug, PartialEq)]
pub struct FStringLiteralElement {
pub struct InterpolatedStringLiteralElement {
pub range: TextRange,
pub value: Box<str>,
}
impl FStringLiteralElement {
impl InterpolatedStringLiteralElement {
pub fn is_valid(&self) -> bool {
!self.value.is_empty()
}
}
impl Deref for FStringLiteralElement {
impl Deref for InterpolatedStringLiteralElement {
type Target = str;
fn deref(&self) -> &Self::Target {
@ -483,7 +485,7 @@ impl FStringValue {
self.iter().filter_map(|part| part.as_f_string())
}
/// Returns an iterator over all the [`FStringElement`] contained in this value.
/// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value.
///
/// An f-string element is what makes up an [`FString`] i.e., it is either a
/// string literal or an expression. In the following example,
@ -494,7 +496,7 @@ impl FStringValue {
///
/// The f-string elements returned would be string literal (`"bar "`),
/// expression (`x`) and string literal (`"qux"`).
pub fn elements(&self) -> impl Iterator<Item = &FStringElement> {
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
self.f_strings().flat_map(|fstring| fstring.elements.iter())
}
}
@ -554,6 +556,181 @@ impl Ranged for FStringPart {
}
}
impl ExprTString {
/// Returns the single [`TString`] if the t-string isn't implicitly concatenated, [`None`]
/// otherwise.
pub const fn as_single_part_tstring(&self) -> Option<&TString> {
match &self.value.inner {
TStringValueInner::Single(TStringPart::TString(tstring)) => Some(tstring),
_ => None,
}
}
}
/// The value representing an [`ExprTString`].
#[derive(Clone, Debug, PartialEq)]
pub struct TStringValue {
inner: TStringValueInner,
}
impl TStringValue {
/// Creates a new t-string literal with a single [`TString`] part.
pub fn single(value: TString) -> Self {
Self {
inner: TStringValueInner::Single(TStringPart::TString(value)),
}
}
/// Creates a new t-string with the given values that represents an implicitly
/// concatenated t-string.
///
/// # Panics
///
/// Panics if `values` has less than 2 elements.
/// Use [`TStringValue::single`] instead.
pub fn concatenated(values: Vec<TStringPart>) -> Self {
assert!(
values.len() > 1,
"Use `TStringValue::single` to create single-part t-strings"
);
Self {
inner: TStringValueInner::Concatenated(values),
}
}
/// Returns `true` if the t-string is implicitly concatenated, `false` otherwise.
pub fn is_implicit_concatenated(&self) -> bool {
matches!(self.inner, TStringValueInner::Concatenated(_))
}
/// Returns a slice of all the [`TStringPart`]s contained in this value.
pub fn as_slice(&self) -> &[TStringPart] {
match &self.inner {
TStringValueInner::Single(part) => std::slice::from_ref(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns a mutable slice of all the [`TStringPart`]s contained in this value.
fn as_mut_slice(&mut self) -> &mut [TStringPart] {
match &mut self.inner {
TStringValueInner::Single(part) => std::slice::from_mut(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns an iterator over all the [`TStringPart`]s contained in this value.
pub fn iter(&self) -> Iter<TStringPart> {
self.as_slice().iter()
}
/// Returns an iterator over all the [`TStringPart`]s contained in this value
/// that allows modification.
pub fn iter_mut(&mut self) -> IterMut<TStringPart> {
self.as_mut_slice().iter_mut()
}
/// Returns an iterator over the [`StringLiteral`] parts contained in this value.
///
/// Note that this doesn't recurse into the t-string parts. For example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// Here, the string literal parts returned would be `"foo"` and `"baz"`.
pub fn literals(&self) -> impl Iterator<Item = &StringLiteral> {
self.iter().filter_map(|part| part.as_literal())
}
/// Returns an iterator over the [`TString`] parts contained in this value.
///
/// Note that this doesn't recurse into the t-string parts. For example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// Here, the t-string parts returned would be `f"bar {x}"` and `f"qux"`.
pub fn t_strings(&self) -> impl Iterator<Item = &TString> {
self.iter().filter_map(|part| part.as_t_string())
}
/// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value.
///
/// An t-string element is what makes up an [`TString`] i.e., it is either a
/// string literal or an interpolation. In the following example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// The t-string elements returned would be string literal (`"bar "`),
/// interpolation (`x`) and string literal (`"qux"`).
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
self.t_strings().flat_map(|fstring| fstring.elements.iter())
}
}
impl<'a> IntoIterator for &'a TStringValue {
type Item = &'a TStringPart;
type IntoIter = Iter<'a, TStringPart>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut TStringValue {
type Item = &'a mut TStringPart;
type IntoIter = IterMut<'a, TStringPart>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
/// An internal representation of [`TStringValue`].
#[derive(Clone, Debug, PartialEq)]
enum TStringValueInner {
/// A single t-string i.e., `t"foo"`.
///
/// This is always going to be `TStringPart::TString` variant which is
/// maintained by the `TStringValue::single` constructor.
Single(TStringPart),
/// An implicitly concatenated t-string i.e., `"foo" t"bar {x}"`.
Concatenated(Vec<TStringPart>),
}
/// An t-string part which is either a string literal, an f-string,
/// or a t-string.
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
pub enum TStringPart {
Literal(StringLiteral),
FString(FString),
TString(TString),
}
impl TStringPart {
pub fn quote_style(&self) -> Quote {
match self {
Self::Literal(string_literal) => string_literal.flags.quote_style(),
Self::FString(f_string) => f_string.flags.quote_style(),
Self::TString(t_string) => t_string.flags.quote_style(),
}
}
}
impl Ranged for TStringPart {
fn range(&self) -> TextRange {
match self {
TStringPart::Literal(string_literal) => string_literal.range(),
TStringPart::FString(f_string) => f_string.range(),
TStringPart::TString(t_string) => t_string.range(),
}
}
}
pub trait StringFlags: Copy {
/// Does the string use single or double quotes in its opener and closer?
fn quote_style(self) -> Quote;
@ -635,7 +812,7 @@ impl std::fmt::Display for DisplayFlags<'_> {
bitflags! {
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)]
struct FStringFlagsInner: u8 {
struct InterpolatedStringFlagsInner: u8 {
/// The f-string uses double quotes (`"`) for its opener and closer.
/// If this flag is not set, the f-string uses single quotes (`'`)
/// for its opener and closer.
@ -662,6 +839,11 @@ bitflags! {
/// Flags that can be queried to obtain information
/// regarding the prefixes and quotes used for an f-string.
///
/// Note: This is identical to [`TStringFlags`] except that
/// the implementation of the `prefix` method of the
/// [`StringFlags`] trait returns a variant of
/// `AnyStringPrefix::Format`.
///
/// ## Notes on usage
///
/// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix
@ -671,7 +853,7 @@ bitflags! {
/// will properly handle nested f-strings. For usage that doesn't fit into one of these categories,
/// the public constructor [`FStringFlags::empty`] can be used.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct FStringFlags(FStringFlagsInner);
pub struct FStringFlags(InterpolatedStringFlagsInner);
impl FStringFlags {
/// Construct a new [`FStringFlags`] with **no flags set**.
@ -684,42 +866,60 @@ impl FStringFlags {
/// situations in which alternative ways to construct this struct should be used, especially
/// when writing lint rules.
pub fn empty() -> Self {
Self(FStringFlagsInner::empty())
Self(InterpolatedStringFlagsInner::empty())
}
#[must_use]
pub fn with_quote_style(mut self, quote_style: Quote) -> Self {
self.0
.set(FStringFlagsInner::DOUBLE, quote_style.is_double());
self.0.set(
InterpolatedStringFlagsInner::DOUBLE,
quote_style.is_double(),
);
self
}
#[must_use]
pub fn with_triple_quotes(mut self, triple_quotes: TripleQuotes) -> Self {
self.0
.set(FStringFlagsInner::TRIPLE_QUOTED, triple_quotes.is_yes());
self.0.set(
InterpolatedStringFlagsInner::TRIPLE_QUOTED,
triple_quotes.is_yes(),
);
self
}
#[must_use]
pub fn with_prefix(mut self, prefix: FStringPrefix) -> Self {
match prefix {
FStringPrefix::Regular => {
Self(self.0 - FStringFlagsInner::R_PREFIX_LOWER - FStringFlagsInner::R_PREFIX_UPPER)
}
FStringPrefix::Regular => Self(
self.0
- InterpolatedStringFlagsInner::R_PREFIX_LOWER
- InterpolatedStringFlagsInner::R_PREFIX_UPPER,
),
FStringPrefix::Raw { uppercase_r } => {
self.0.set(FStringFlagsInner::R_PREFIX_UPPER, uppercase_r);
self.0.set(FStringFlagsInner::R_PREFIX_LOWER, !uppercase_r);
self.0
.set(InterpolatedStringFlagsInner::R_PREFIX_UPPER, uppercase_r);
self.0
.set(InterpolatedStringFlagsInner::R_PREFIX_LOWER, !uppercase_r);
self
}
}
}
pub const fn prefix(self) -> FStringPrefix {
if self.0.contains(FStringFlagsInner::R_PREFIX_LOWER) {
debug_assert!(!self.0.contains(FStringFlagsInner::R_PREFIX_UPPER));
if self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_LOWER)
{
debug_assert!(
!self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER)
);
FStringPrefix::Raw { uppercase_r: false }
} else if self.0.contains(FStringFlagsInner::R_PREFIX_UPPER) {
} else if self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER)
{
FStringPrefix::Raw { uppercase_r: true }
} else {
FStringPrefix::Regular
@ -727,12 +927,108 @@ impl FStringFlags {
}
}
// TODO(dylan): the documentation about using
// `Checker::default_tstring_flags` is not yet
// correct. This method does not yet exist because
// introducing it would emit a dead code warning
// until we call it in lint rules.
/// Flags that can be queried to obtain information
/// regarding the prefixes and quotes used for an f-string.
///
/// Note: This is identical to [`FStringFlags`] except that
/// the implementation of the `prefix` method of the
/// [`StringFlags`] trait returns a variant of
/// `AnyStringPrefix::Template`.
///
/// ## Notes on usage
///
/// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix
/// from an existing t-string literal, consider passing along the [`FString::flags`] field. If you
/// don't have an existing literal but have a `Checker` from the `ruff_linter` crate available,
/// consider using `Checker::default_tstring_flags` to create instances of this struct; this method
/// will properly handle nested t-strings. For usage that doesn't fit into one of these categories,
/// the public constructor [`TStringFlags::empty`] can be used.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct TStringFlags(InterpolatedStringFlagsInner);
impl TStringFlags {
/// Construct a new [`TStringFlags`] with **no flags set**.
///
/// See [`TStringFlags::with_quote_style`], [`TStringFlags::with_triple_quotes`], and
/// [`TStringFlags::with_prefix`] for ways of setting the quote style (single or double),
/// enabling triple quotes, and adding prefixes (such as `r`), respectively.
///
/// See the documentation for [`TStringFlags`] for additional caveats on this constructor, and
/// situations in which alternative ways to construct this struct should be used, especially
/// when writing lint rules.
pub fn empty() -> Self {
Self(InterpolatedStringFlagsInner::empty())
}
#[must_use]
pub fn with_quote_style(mut self, quote_style: Quote) -> Self {
self.0.set(
InterpolatedStringFlagsInner::DOUBLE,
quote_style.is_double(),
);
self
}
#[must_use]
pub fn with_triple_quotes(mut self, triple_quotes: TripleQuotes) -> Self {
self.0.set(
InterpolatedStringFlagsInner::TRIPLE_QUOTED,
triple_quotes.is_yes(),
);
self
}
#[must_use]
pub fn with_prefix(mut self, prefix: TStringPrefix) -> Self {
match prefix {
TStringPrefix::Regular => Self(
self.0
- InterpolatedStringFlagsInner::R_PREFIX_LOWER
- InterpolatedStringFlagsInner::R_PREFIX_UPPER,
),
TStringPrefix::Raw { uppercase_r } => {
self.0
.set(InterpolatedStringFlagsInner::R_PREFIX_UPPER, uppercase_r);
self.0
.set(InterpolatedStringFlagsInner::R_PREFIX_LOWER, !uppercase_r);
self
}
}
}
pub const fn prefix(self) -> TStringPrefix {
if self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_LOWER)
{
debug_assert!(
!self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER)
);
TStringPrefix::Raw { uppercase_r: false }
} else if self
.0
.contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER)
{
TStringPrefix::Raw { uppercase_r: true }
} else {
TStringPrefix::Regular
}
}
}
impl StringFlags for FStringFlags {
/// Return `true` if the f-string is triple-quoted, i.e.,
/// it begins and ends with three consecutive quote characters.
/// For example: `f"""{bar}"""`
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(FStringFlagsInner::TRIPLE_QUOTED) {
if self.0.contains(InterpolatedStringFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
@ -744,7 +1040,7 @@ impl StringFlags for FStringFlags {
/// - `f"{"a"}"` -> `QuoteStyle::Double`
/// - `f'{"a"}'` -> `QuoteStyle::Single`
fn quote_style(self) -> Quote {
if self.0.contains(FStringFlagsInner::DOUBLE) {
if self.0.contains(InterpolatedStringFlagsInner::DOUBLE) {
Quote::Double
} else {
Quote::Single
@ -766,11 +1062,50 @@ impl fmt::Debug for FStringFlags {
}
}
impl StringFlags for TStringFlags {
/// Return `true` if the t-string is triple-quoted, i.e.,
/// it begins and ends with three consecutive quote characters.
/// For example: `t"""{bar}"""`
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(InterpolatedStringFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
}
}
/// Return the quoting style (single or double quotes)
/// used by the t-string's opener and closer:
/// - `t"{"a"}"` -> `QuoteStyle::Double`
/// - `t'{"a"}'` -> `QuoteStyle::Single`
fn quote_style(self) -> Quote {
if self.0.contains(InterpolatedStringFlagsInner::DOUBLE) {
Quote::Double
} else {
Quote::Single
}
}
fn prefix(self) -> AnyStringPrefix {
AnyStringPrefix::Template(self.prefix())
}
}
impl fmt::Debug for TStringFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TStringFlags")
.field("quote_style", &self.quote_style())
.field("prefix", &self.prefix())
.field("triple_quoted", &self.is_triple_quoted())
.finish()
}
}
/// An AST node that represents a single f-string which is part of an [`ExprFString`].
#[derive(Clone, Debug, PartialEq)]
pub struct FString {
pub range: TextRange,
pub elements: FStringElements,
pub elements: InterpolatedStringElements,
pub flags: FStringFlags,
}
@ -784,66 +1119,84 @@ impl From<FString> for Expr {
}
}
/// A newtype wrapper around a list of [`FStringElement`].
/// A newtype wrapper around a list of [`InterpolatedStringElement`].
#[derive(Clone, Default, PartialEq)]
pub struct FStringElements(Vec<FStringElement>);
pub struct InterpolatedStringElements(Vec<InterpolatedStringElement>);
impl FStringElements {
/// Returns an iterator over all the [`FStringLiteralElement`] nodes contained in this f-string.
pub fn literals(&self) -> impl Iterator<Item = &FStringLiteralElement> {
impl InterpolatedStringElements {
/// Returns an iterator over all the [`InterpolatedStringLiteralElement`] nodes contained in this f-string.
pub fn literals(&self) -> impl Iterator<Item = &InterpolatedStringLiteralElement> {
self.iter().filter_map(|element| element.as_literal())
}
/// Returns an iterator over all the [`FStringExpressionElement`] nodes contained in this f-string.
pub fn expressions(&self) -> impl Iterator<Item = &FStringExpressionElement> {
self.iter().filter_map(|element| element.as_expression())
/// Returns an iterator over all the [`InterpolatedElement`] nodes contained in this f-string.
pub fn interpolations(&self) -> impl Iterator<Item = &InterpolatedElement> {
self.iter().filter_map(|element| element.as_interpolation())
}
}
impl From<Vec<FStringElement>> for FStringElements {
fn from(elements: Vec<FStringElement>) -> Self {
FStringElements(elements)
impl From<Vec<InterpolatedStringElement>> for InterpolatedStringElements {
fn from(elements: Vec<InterpolatedStringElement>) -> Self {
InterpolatedStringElements(elements)
}
}
impl<'a> IntoIterator for &'a FStringElements {
type IntoIter = Iter<'a, FStringElement>;
type Item = &'a FStringElement;
impl<'a> IntoIterator for &'a InterpolatedStringElements {
type IntoIter = Iter<'a, InterpolatedStringElement>;
type Item = &'a InterpolatedStringElement;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut FStringElements {
type IntoIter = IterMut<'a, FStringElement>;
type Item = &'a mut FStringElement;
impl<'a> IntoIterator for &'a mut InterpolatedStringElements {
type IntoIter = IterMut<'a, InterpolatedStringElement>;
type Item = &'a mut InterpolatedStringElement;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl Deref for FStringElements {
type Target = [FStringElement];
impl Deref for InterpolatedStringElements {
type Target = [InterpolatedStringElement];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FStringElements {
impl DerefMut for InterpolatedStringElements {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl fmt::Debug for FStringElements {
impl fmt::Debug for InterpolatedStringElements {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
/// An AST node that represents a single t-string which is part of an [`ExprTString`].
#[derive(Clone, Debug, PartialEq)]
pub struct TString {
pub range: TextRange,
pub elements: InterpolatedStringElements,
pub flags: TStringFlags,
}
impl From<TString> for Expr {
fn from(payload: TString) -> Self {
ExprTString {
range: payload.range,
value: TStringValue::single(payload),
}
.into()
}
}
impl ExprStringLiteral {
/// Return `Some(literal)` if the string only consists of a single `StringLiteral` part
/// (indicating that it is not implicitly concatenated). Otherwise, return `None`.
@ -1662,18 +2015,23 @@ bitflags! {
/// but can have no other prefixes.
const F_PREFIX = 1 << 4;
/// The string has a `t` or `T` prefix, meaning it is a t-string.
/// T-strings can also be raw strings,
/// but can have no other prefixes.
const T_PREFIX = 1 << 5;
/// The string has an `r` prefix, meaning it is a raw string.
/// F-strings and byte-strings can be raw,
/// as can strings with no other prefixes.
/// U-strings cannot be raw.
const R_PREFIX_LOWER = 1 << 5;
const R_PREFIX_LOWER = 1 << 6;
/// The string has an `R` prefix, meaning it is a raw string.
/// The casing of the `r`/`R` has no semantic significance at runtime;
/// see https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#r-strings-and-r-strings
/// for why we track the casing of the `r` prefix,
/// but not for any other prefix
const R_PREFIX_UPPER = 1 << 6;
const R_PREFIX_UPPER = 1 << 7;
}
}
@ -1711,6 +2069,15 @@ impl AnyStringFlags {
AnyStringPrefix::Format(FStringPrefix::Raw { uppercase_r: true }) => {
AnyStringFlagsInner::F_PREFIX.union(AnyStringFlagsInner::R_PREFIX_UPPER)
}
// t-strings
AnyStringPrefix::Template(TStringPrefix::Regular) => AnyStringFlagsInner::T_PREFIX,
AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: false }) => {
AnyStringFlagsInner::T_PREFIX.union(AnyStringFlagsInner::R_PREFIX_LOWER)
}
AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: true }) => {
AnyStringFlagsInner::T_PREFIX.union(AnyStringFlagsInner::R_PREFIX_UPPER)
}
};
self
}
@ -1734,9 +2101,10 @@ impl AnyStringFlags {
)
}
/// Does the string have an `f` or `F` prefix?
pub const fn is_f_string(self) -> bool {
self.0.contains(AnyStringFlagsInner::F_PREFIX)
/// Does the string have an `f`,`F`,`t`, or `T` prefix?
pub const fn is_interpolated_string(self) -> bool {
self.0
.intersects(AnyStringFlagsInner::F_PREFIX.union(AnyStringFlagsInner::T_PREFIX))
}
/// Does the string have a `b` or `B` prefix?
@ -1793,6 +2161,17 @@ impl StringFlags for AnyStringFlags {
return AnyStringPrefix::Format(FStringPrefix::Regular);
}
// t-strings
if flags.contains(AnyStringFlagsInner::T_PREFIX) {
if flags.contains(AnyStringFlagsInner::R_PREFIX_LOWER) {
return AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: false });
}
if flags.contains(AnyStringFlagsInner::R_PREFIX_UPPER) {
return AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: true });
}
return AnyStringPrefix::Template(TStringPrefix::Regular);
}
// bytestrings
if flags.contains(AnyStringFlagsInner::B_PREFIX) {
if flags.contains(AnyStringFlagsInner::R_PREFIX_LOWER) {
@ -1872,7 +2251,7 @@ impl From<BytesLiteralFlags> for AnyStringFlags {
impl From<AnyStringFlags> for FStringFlags {
fn from(value: AnyStringFlags) -> FStringFlags {
let AnyStringPrefix::Format(fstring_prefix) = value.prefix() else {
let AnyStringPrefix::Format(prefix) = value.prefix() else {
unreachable!(
"Should never attempt to convert {} into an f-string",
value.prefix()
@ -1880,7 +2259,7 @@ impl From<AnyStringFlags> for FStringFlags {
};
FStringFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(fstring_prefix)
.with_prefix(prefix)
.with_triple_quotes(value.triple_quotes())
}
}
@ -1891,6 +2270,27 @@ impl From<FStringFlags> for AnyStringFlags {
}
}
impl From<AnyStringFlags> for TStringFlags {
fn from(value: AnyStringFlags) -> TStringFlags {
let AnyStringPrefix::Template(prefix) = value.prefix() else {
unreachable!(
"Should never attempt to convert {} into a t-string",
value.prefix()
)
};
TStringFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(prefix)
.with_triple_quotes(value.triple_quotes())
}
}
impl From<TStringFlags> for AnyStringFlags {
fn from(value: TStringFlags) -> Self {
value.as_any_string_flags()
}
}
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
pub enum Number {
Int(int::Int),

View file

@ -72,7 +72,8 @@ impl OperatorPrecedence {
| ExprRef::BooleanLiteral(_)
| ExprRef::NoneLiteral(_)
| ExprRef::EllipsisLiteral(_)
| ExprRef::FString(_) => Self::Atomic,
| ExprRef::FString(_)
| ExprRef::TString(_) => Self::Atomic,
// Subscription, slicing, call, attribute reference
ExprRef::Attribute(_)
| ExprRef::Subscript(_)

View file

@ -59,6 +59,13 @@ impl PythonVersion {
Self::PY313
}
/// The latest Python version supported in preview
pub fn latest_preview() -> Self {
let latest_preview = Self::PY314;
debug_assert!(latest_preview >= Self::latest());
latest_preview
}
pub const fn latest_ty() -> Self {
// Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version.
Self::PY313

View file

@ -72,6 +72,9 @@ impl Transformer for Relocator {
Expr::FString(ast::ExprFString { range, .. }) => {
*range = self.range;
}
Expr::TString(ast::ExprTString { range, .. }) => {
*range = self.range;
}
Expr::StringLiteral(ast::ExprStringLiteral { range, .. }) => {
*range = self.range;
}

View file

@ -5,7 +5,7 @@ use std::sync::LazyLock;
use ruff_text_size::{TextLen, TextRange};
/// Enumeration of the two kinds of quotes that can be used
/// for Python string/f-string/bytestring literals
/// for Python string/f/t-string/bytestring literals
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, is_macro::Is)]
pub enum Quote {
/// E.g. `'`

View file

@ -91,6 +91,47 @@ impl fmt::Display for FStringPrefix {
}
}
/// Enumeration of the valid prefixes a t-string literal can have.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TStringPrefix {
/// Just a regular t-string with no other prefixes, e.g. t"{bar}"
Regular,
/// A "raw" template string, that has an `r` or `R` prefix,
/// e.g. `rt"{bar}"` or `Rt"{bar}"`
Raw { uppercase_r: bool },
}
impl TStringPrefix {
/// Return a `str` representation of the prefix
pub const fn as_str(self) -> &'static str {
match self {
Self::Regular => "t",
Self::Raw { uppercase_r: true } => "Rt",
Self::Raw { uppercase_r: false } => "rt",
}
}
pub const fn text_len(self) -> TextSize {
match self {
Self::Regular => TextSize::new(1),
Self::Raw { .. } => TextSize::new(2),
}
}
/// Return true if this prefix indicates a "raw t-string",
/// e.g. `rt"{bar}"` or `Rt"{bar}"`
pub const fn is_raw(self) -> bool {
matches!(self, Self::Raw { .. })
}
}
impl fmt::Display for TStringPrefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// Enumeration of the valid prefixes a bytestring literal can have.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ByteStringPrefix {
@ -151,6 +192,9 @@ pub enum AnyStringPrefix {
/// Prefixes that indicate the string is an f-string
Format(FStringPrefix),
/// Prefixes that indicate the string is a t-string
Template(TStringPrefix),
/// All other prefixes
Regular(StringLiteralPrefix),
}
@ -161,6 +205,7 @@ impl AnyStringPrefix {
Self::Regular(regular_prefix) => regular_prefix.as_str(),
Self::Bytes(bytestring_prefix) => bytestring_prefix.as_str(),
Self::Format(fstring_prefix) => fstring_prefix.as_str(),
Self::Template(tstring_prefix) => tstring_prefix.as_str(),
}
}
@ -169,6 +214,7 @@ impl AnyStringPrefix {
Self::Regular(regular_prefix) => regular_prefix.text_len(),
Self::Bytes(bytestring_prefix) => bytestring_prefix.text_len(),
Self::Format(fstring_prefix) => fstring_prefix.text_len(),
Self::Template(tstring_prefix) => tstring_prefix.text_len(),
}
}
@ -177,6 +223,7 @@ impl AnyStringPrefix {
Self::Regular(regular_prefix) => regular_prefix.is_raw(),
Self::Bytes(bytestring_prefix) => bytestring_prefix.is_raw(),
Self::Format(fstring_prefix) => fstring_prefix.is_raw(),
Self::Template(tstring_prefix) => tstring_prefix.is_raw(),
}
}
}

View file

@ -5,10 +5,10 @@ pub mod transformer;
use crate::{
self as ast, Alias, AnyParameterRef, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension,
Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement,
FStringPart, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments,
PatternKeyword, Stmt, StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringPart,
InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TStringPart, TypeParam,
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@ -99,8 +99,14 @@ pub trait Visitor<'a> {
fn visit_f_string(&mut self, f_string: &'a FString) {
walk_f_string(self, f_string);
}
fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
walk_f_string_element(self, f_string_element);
fn visit_interpolated_string_element(
&mut self,
interpolated_string_element: &'a InterpolatedStringElement,
) {
walk_interpolated_string_element(self, interpolated_string_element);
}
fn visit_t_string(&mut self, t_string: &'a TString) {
walk_t_string(self, t_string);
}
fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) {
walk_string_literal(self, string_literal);
@ -484,6 +490,17 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
}
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for part in value {
match part {
TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
TStringPart::FString(f_string) => visitor.visit_f_string(f_string),
TStringPart::TString(t_string) => visitor.visit_t_string(t_string),
}
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
for string_literal in value {
visitor.visit_string_literal(string_literal);
@ -739,30 +756,36 @@ pub fn walk_pattern_keyword<'a, V: Visitor<'a> + ?Sized>(
}
pub fn walk_f_string<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, f_string: &'a FString) {
for f_string_element in &f_string.elements {
visitor.visit_f_string_element(f_string_element);
for interpolated_string_element in &f_string.elements {
visitor.visit_interpolated_string_element(interpolated_string_element);
}
}
pub fn walk_f_string_element<'a, V: Visitor<'a> + ?Sized>(
pub fn walk_interpolated_string_element<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
f_string_element: &'a FStringElement,
interpolated_string_element: &'a InterpolatedStringElement,
) {
if let ast::FStringElement::Expression(ast::FStringExpressionElement {
if let ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
expression,
format_spec,
..
}) = f_string_element
}) = interpolated_string_element
{
visitor.visit_expr(expression);
if let Some(format_spec) = format_spec {
for spec_element in &format_spec.elements {
visitor.visit_f_string_element(spec_element);
visitor.visit_interpolated_string_element(spec_element);
}
}
}
}
pub fn walk_t_string<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, t_string: &'a TString) {
for t_string_element in &t_string.elements {
visitor.visit_interpolated_string_element(t_string_element);
}
}
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
_visitor: &V,
_expr_context: &'a ExprContext,

View file

@ -1,8 +1,8 @@
use crate::{
Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator, ElifElseClause,
ExceptHandler, Expr, FString, FStringElement, Keyword, MatchCase, Mod, Operator, Parameter,
ParameterWithDefault, Parameters, Pattern, PatternArguments, PatternKeyword, Singleton, Stmt,
StringLiteral, TypeParam, TypeParams, UnaryOp, WithItem,
ExceptHandler, Expr, FString, InterpolatedStringElement, Keyword, MatchCase, Mod, Operator,
Parameter, ParameterWithDefault, Parameters, Pattern, PatternArguments, PatternKeyword,
Singleton, Stmt, StringLiteral, TString, TypeParam, TypeParams, UnaryOp, WithItem,
};
use crate::{AnyNodeRef, Identifier};
@ -157,8 +157,16 @@ pub trait SourceOrderVisitor<'a> {
}
#[inline]
fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
walk_f_string_element(self, f_string_element);
fn visit_interpolated_string_element(
&mut self,
interpolated_string_element: &'a InterpolatedStringElement,
) {
walk_interpolated_string_element(self, interpolated_string_element);
}
#[inline]
fn visit_t_string(&mut self, t_string: &'a TString) {
walk_t_string(self, t_string);
}
#[inline]
@ -272,6 +280,7 @@ where
Expr::Compare(expr) => expr.visit_source_order(visitor),
Expr::Call(expr) => expr.visit_source_order(visitor),
Expr::FString(expr) => expr.visit_source_order(visitor),
Expr::TString(expr) => expr.visit_source_order(visitor),
Expr::StringLiteral(expr) => expr.visit_source_order(visitor),
Expr::BytesLiteral(expr) => expr.visit_source_order(visitor),
Expr::NumberLiteral(expr) => expr.visit_source_order(visitor),
@ -497,15 +506,17 @@ where
visitor.leave_node(node);
}
pub fn walk_f_string_element<'a, V: SourceOrderVisitor<'a> + ?Sized>(
pub fn walk_interpolated_string_element<'a, V: SourceOrderVisitor<'a> + ?Sized>(
visitor: &mut V,
f_string_element: &'a FStringElement,
f_string_element: &'a InterpolatedStringElement,
) {
let node = AnyNodeRef::from(f_string_element);
if visitor.enter_node(node).is_traverse() {
match f_string_element {
FStringElement::Expression(element) => element.visit_source_order(visitor),
FStringElement::Literal(element) => element.visit_source_order(visitor),
InterpolatedStringElement::Interpolation(element) => {
element.visit_source_order(visitor);
}
InterpolatedStringElement::Literal(element) => element.visit_source_order(visitor),
}
}
visitor.leave_node(node);
@ -550,6 +561,18 @@ where
visitor.leave_node(node);
}
#[inline]
pub fn walk_t_string<'a, V>(visitor: &mut V, t_string: &'a TString)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
let node = AnyNodeRef::from(t_string);
if visitor.enter_node(node).is_traverse() {
t_string.visit_source_order(visitor);
}
visitor.leave_node(node);
}
#[inline]
pub fn walk_string_literal<'a, V>(visitor: &mut V, string_literal: &'a StringLiteral)
where

View file

@ -1,8 +1,8 @@
use crate::{
self as ast, Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator,
ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement, Keyword, MatchCase,
Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt,
StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
ElifElseClause, ExceptHandler, Expr, ExprContext, FString, InterpolatedStringElement, Keyword,
MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt,
StringLiteral, TString, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
TypeParams, UnaryOp, WithItem,
};
@ -86,8 +86,14 @@ pub trait Transformer {
fn visit_f_string(&self, f_string: &mut FString) {
walk_f_string(self, f_string);
}
fn visit_f_string_element(&self, f_string_element: &mut FStringElement) {
walk_f_string_element(self, f_string_element);
fn visit_interpolated_string_element(
&self,
interpolated_string_element: &mut InterpolatedStringElement,
) {
walk_interpolated_string_element(self, interpolated_string_element);
}
fn visit_t_string(&self, t_string: &mut TString) {
walk_t_string(self, t_string);
}
fn visit_string_literal(&self, string_literal: &mut StringLiteral) {
walk_string_literal(self, string_literal);
@ -470,6 +476,21 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
}
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for t_string_part in value.iter_mut() {
match t_string_part {
ast::TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::TStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
ast::TStringPart::TString(t_string) => {
visitor.visit_t_string(t_string);
}
}
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
for string_literal in value.iter_mut() {
visitor.visit_string_literal(string_literal);
@ -744,29 +765,35 @@ pub fn walk_pattern_keyword<V: Transformer + ?Sized>(
pub fn walk_f_string<V: Transformer + ?Sized>(visitor: &V, f_string: &mut FString) {
for element in &mut f_string.elements {
visitor.visit_f_string_element(element);
visitor.visit_interpolated_string_element(element);
}
}
pub fn walk_f_string_element<V: Transformer + ?Sized>(
pub fn walk_interpolated_string_element<V: Transformer + ?Sized>(
visitor: &V,
f_string_element: &mut FStringElement,
interpolated_string_element: &mut InterpolatedStringElement,
) {
if let ast::FStringElement::Expression(ast::FStringExpressionElement {
if let ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
expression,
format_spec,
..
}) = f_string_element
}) = interpolated_string_element
{
visitor.visit_expr(expression);
if let Some(format_spec) = format_spec {
for spec_element in &mut format_spec.elements {
visitor.visit_f_string_element(spec_element);
visitor.visit_interpolated_string_element(spec_element);
}
}
}
}
pub fn walk_t_string<V: Transformer + ?Sized>(visitor: &V, t_string: &mut TString) {
for element in &mut t_string.elements {
visitor.visit_interpolated_string_element(element);
}
}
pub fn walk_expr_context<V: Transformer + ?Sized>(_visitor: &V, _expr_context: &mut ExprContext) {}
pub fn walk_bool_op<V: Transformer + ?Sized>(_visitor: &V, _bool_op: &mut BoolOp) {}