From f32733c063bd906e29aaa192f149c32455da0675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Thu, 30 Apr 2026 12:14:25 +0300 Subject: [PATCH] [flake8-pyi] Fix PYI016 false positive for f-string debug specifier (#24098) Co-authored-by: Micha Reiser --- .../test/fixtures/flake8_pyi/PYI016.py | 12 +++- .../ruff_linter/src/rules/flake8_pyi/mod.rs | 21 +++++- ...__flake8_pyi__tests__PYI016_PYI016.py.snap | 41 ++++++++++++ crates/ruff_python_ast/src/comparable.rs | 35 +++++++++- crates/ruff_python_ast/src/nodes.rs | 66 +++++++++++++++++-- crates/ruff_python_codegen/src/generator.rs | 4 +- .../src/other/interpolated_string_element.rs | 4 +- .../ruff_python_formatter/src/string/mod.rs | 4 +- .../ruff_python_formatter/tests/normalizer.rs | 8 ++- .../src/parser/expression.rs | 9 +-- ...__fstring_parse_self_documenting_base.snap | 1 + ...ring_parse_self_documenting_base_more.snap | 2 + ...fstring_parse_self_documenting_format.snap | 1 + ...ts__parse_fstring_self_doc_prec_space.snap | 1 + ...parse_fstring_self_doc_trailing_space.snap | 1 + ...ts__parse_tstring_self_doc_prec_space.snap | 1 + ...parse_tstring_self_doc_trailing_space.snap | 1 + ...__tstring_parse_self_documenting_base.snap | 1 + ...ring_parse_self_documenting_base_more.snap | 2 + ...tstring_parse_self_documenting_format.snap | 1 + ...id_syntax@f_string_unclosed_lbrace.py.snap | 2 +- .../invalid_syntax@re_lexing__ty_1828.py.snap | 2 +- ...id_syntax@t_string_unclosed_lbrace.py.snap | 2 +- ...valid_syntax@expressions__f_string.py.snap | 11 +++- ...valid_syntax@expressions__t_string.py.snap | 11 +++- 25 files changed, 217 insertions(+), 27 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py index 74eaf356d5..d8e43741bc 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py @@ -145,4 +145,14 @@ field49: typing.Optional[complex | complex] | complex # Regression test for https://github.com/astral-sh/ruff/issues/19403 # Should throw duplicate union member but not fix -isinstance(None, typing.Union[None, None]) \ No newline at end of file +isinstance(None, typing.Union[None, None]) + +# Regression test for https://github.com/astral-sh/ruff/issues/19914 +# f-string debug (=) specifier: different source text means different output +field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0") +field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1") +field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate) +field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs) +field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate) +field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces) +field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 066e39e894..5b4aa5411f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -12,7 +12,8 @@ mod tests { use crate::registry::Rule; use crate::rules::pep8_naming; use crate::settings::types::PreviewMode; - use crate::test::test_path; + use crate::source_kind::SourceKind; + use crate::test::{test_contents, test_path}; use crate::{assert_diagnostics, assert_diagnostics_diff, settings}; #[test_case(Rule::AnyEqNeAnnotation, Path::new("PYI032.py"))] @@ -214,4 +215,22 @@ mod tests { assert_diagnostics!(diagnostics); Ok(()) } + + #[test] + fn pyi016_multiline_debug_fstring_mixed_newlines() { + let path = Path::new(".pyi"); + let diagnostics = test_contents( + &SourceKind::Python { + code: "from typing import Literal\n\ +value: Literal[f\"\"\"{(\r\n1\r\n)=}\"\"\"] | Literal[f\"\"\"{(\n1\n)=}\"\"\"]\n" + .to_string(), + is_stub: true, + }, + path, + &settings::LinterSettings::for_rule(Rule::DuplicateUnionMember), + ) + .0; + + assert_eq!(diagnostics.len(), 1); + } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap index 536adf49a5..fdb0825d59 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap @@ -1175,5 +1175,46 @@ PYI016 Duplicate union member `None` 147 | # Should throw duplicate union member but not fix 148 | isinstance(None, typing.Union[None, None]) | ^^^^ +149 | +150 | # Regression test for https://github.com/astral-sh/ruff/issues/19914 | help: Remove duplicate union member `None` + +PYI016 [*] Duplicate union member `typing.Literal[f"{x=}"]` + --> PYI016.py:154:36 + | +152 | field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0") +153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1") +154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate) + | ^^^^^^^^^^^^^^^^^^^^^^^ +155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs) +156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate) + | +help: Remove duplicate union member `typing.Literal[f"{x=}"]` +151 | # f-string debug (=) specifier: different source text means different output +152 | field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0") +153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1") + - field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate) +154 + field52: typing.Literal[f"{x=}"] # Error (true duplicate) +155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs) +156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate) +157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces) + +PYI016 [*] Duplicate union member `typing.Literal[f"{x}"]` + --> PYI016.py:156:35 + | +154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate) +155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs) +156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate) + | ^^^^^^^^^^^^^^^^^^^^^^ +157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces) +158 | field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0") + | +help: Remove duplicate union member `typing.Literal[f"{x}"]` +153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1") +154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate) +155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs) + - field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate) +156 + field54: typing.Literal[f"{x}"] # Error (true duplicate) +157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces) +158 | field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0") diff --git a/crates/ruff_python_ast/src/comparable.rs b/crates/ruff_python_ast/src/comparable.rs index 8ae6b22d9e..5fe7d74e3f 100644 --- a/crates/ruff_python_ast/src/comparable.rs +++ b/crates/ruff_python_ast/src/comparable.rs @@ -517,10 +517,39 @@ pub enum ComparableInterpolatedStringElement<'a> { InterpolatedElement(InterpolatedElement<'a>), } +/// Comparable wrapper for [`ast::DebugText`]. +/// +/// Compares the full debug text (leading + expression source + trailing) rather than only the +/// expression source, because whitespace is part of the f-string's runtime output: `f"{x =}"` +/// produces `"x ="` while `f"{x=}"` produces `"x="`, making them distinct +/// `Literal` types. +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ComparableDebugText<'a> { + text: Cow<'a, str>, +} + +impl<'a> From<&'a ast::DebugText> for ComparableDebugText<'a> { + fn from(debug_text: &'a ast::DebugText) -> Self { + // Normalizing newlines is safe because Python normalizes `\r\n` and `\r` to `\n` + // at compile time, so they produce identical runtime values. + Self { + text: normalize_newlines(debug_text.as_str()), + } + } +} + +fn normalize_newlines(contents: &str) -> Cow<'_, str> { + if contents.contains('\r') { + Cow::Owned(contents.replace("\r\n", "\n").replace('\r', "\n")) + } else { + Cow::Borrowed(contents) + } +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct InterpolatedElement<'a> { expression: ComparableExpr<'a>, - debug_text: Option<&'a ast::DebugText>, + debug_text: Option>, conversion: ast::ConversionFlag, format_spec: Option>>, } @@ -552,7 +581,7 @@ impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> { Self { expression: (expression).into(), - debug_text: debug_text.as_ref(), + debug_text: debug_text.as_ref().map(Into::into), conversion: *conversion, format_spec: format_spec .as_ref() @@ -926,7 +955,7 @@ pub struct ExprCall<'a> { #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprInterpolatedElement<'a> { value: Box>, - debug_text: Option<&'a ast::DebugText>, + debug_text: Option>, conversion: ast::ConversionFlag, format_spec: Vec>, } diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 1798101104..e18f6deb41 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -387,13 +387,71 @@ impl ConversionFlag { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +/// The debug text of a self-documenting f-string expression (e.g., `f"{x=}"`). +/// +/// Stores the concatenation of leading text, expression source, and trailing text as a single +/// [`CompactString`], with byte offsets to split them. The offsets are needed because the leading +/// and trailing portions can contain non-whitespace characters (grouping parentheses, comments in +/// triple-quoted f-strings) that cannot be distinguished from expression content by scanning. +/// +/// [`CompactString`]: compact_str::CompactString +#[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "get-size", derive(get_size2::GetSize))] pub struct DebugText { + /// The full text between the `{` and the conversion / `format_spec` / `}`. + text: compact_str::CompactString, + /// Byte offset where the expression source begins. + expression_start: u32, + /// Byte offset where the expression source ends. + expression_end: u32, +} + +impl std::fmt::Debug for DebugText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DebugText") + .field("leading", &self.leading()) + .field("expression", &self.expression()) + .field("trailing", &self.trailing()) + .finish() + } +} + +impl DebugText { + pub fn new(leading: &str, expression: &str, trailing: &str) -> Self { + let expression_start = leading.text_len().to_u32(); + let expression_end = expression_start + expression.text_len().to_u32(); + let mut buf = compact_str::CompactString::with_capacity( + leading.len() + expression.len() + trailing.len(), + ); + buf.push_str(leading); + buf.push_str(expression); + buf.push_str(trailing); + Self { + text: buf, + expression_start, + expression_end, + } + } + + /// The full debug text between the `{` and the conversion / `format_spec` / `}`. + pub fn as_str(&self) -> &str { + &self.text + } + /// The text between the `{` and the expression node. - pub leading: String, - /// The text between the expression and the conversion, the `format_spec`, or the `}`, depending on what's present in the source - pub trailing: String, + pub fn leading(&self) -> &str { + &self.text[..self.expression_start as usize] + } + + /// The source text of the expression (e.g., `0x0` in `f"{0x0=}"`). + pub fn expression(&self) -> &str { + &self.text[self.expression_start as usize..self.expression_end as usize] + } + + /// The text between the expression and the conversion, the `format_spec`, or the `}`. + pub fn trailing(&self) -> &str { + &self.text[self.expression_end as usize..] + } } impl ExprFString { diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index c05a27bd22..ee1ffbd414 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -1500,13 +1500,13 @@ impl<'a> Generator<'a> { self.p(brace); if let Some(debug_text) = debug_text { - self.buffer += debug_text.leading.as_str(); + self.buffer += debug_text.leading(); } self.buffer += &generator.buffer; if let Some(debug_text) = debug_text { - self.buffer += debug_text.trailing.as_str(); + self.buffer += debug_text.trailing(); } if !conversion.is_none() { diff --git a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs index 4ee4243ea7..e28083b368 100644 --- a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs @@ -133,9 +133,9 @@ impl Format> for FormatInterpolatedElement<'_> { write!( f, [ - NormalizedDebugText(&debug_text.leading), + NormalizedDebugText(debug_text.leading()), verbatim_text(expression), - NormalizedDebugText(&debug_text.trailing), + NormalizedDebugText(debug_text.trailing()), ] )?; diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index 6f9fc5b33e..cbbe4676d8 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -112,8 +112,8 @@ impl StringLikeExtensions for ast::StringLike<'_> { contains_line_break_or_comments(&spec.elements, context, triple_quotes) }) || expression.debug_text.as_ref().is_some_and(|debug_text| { - memchr2(b'\n', b'\r', debug_text.leading.as_bytes()).is_some() - || memchr2(b'\n', b'\r', debug_text.trailing.as_bytes()).is_some() + memchr2(b'\n', b'\r', debug_text.leading().as_bytes()).is_some() + || memchr2(b'\n', b'\r', debug_text.trailing().as_bytes()).is_some() }) } }) diff --git a/crates/ruff_python_formatter/tests/normalizer.rs b/crates/ruff_python_formatter/tests/normalizer.rs index 24d0bdf7d2..ea563be75a 100644 --- a/crates/ruff_python_formatter/tests/normalizer.rs +++ b/crates/ruff_python_formatter/tests/normalizer.rs @@ -189,9 +189,11 @@ impl Transformer for Normalizer { return; }; - // Changing the newlines to the configured newline is okay because Python normalizes all newlines to `\n` - debug.leading = debug.leading.replace("\r\n", "\n").replace('\r', "\n"); - debug.trailing = debug.trailing.replace("\r\n", "\n").replace('\r', "\n"); + // The formatter normalizes newlines in the text around a debug expression. + let leading = debug.leading().replace("\r\n", "\n").replace('\r', "\n"); + let expression = debug.expression().to_string(); + let trailing = debug.trailing().replace("\r\n", "\n").replace('\r', "\n"); + *debug = ast::DebugText::new(&leading, &expression, &trailing); } fn visit_string_literal(&self, string_literal: &mut ast::StringLiteral) { diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 7c0d169b3a..3e1a7ccab4 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1719,10 +1719,11 @@ impl<'src> Parser<'src> { let debug_text = if self.eat(TokenKind::Equal) { let leading_range = TextRange::new(start + "{".text_len(), value.start()); let trailing_range = TextRange::new(value.end(), self.current_token_range().start()); - Some(ast::DebugText { - leading: self.src_text(leading_range).to_string(), - trailing: self.src_text(trailing_range).to_string(), - }) + Some(ast::DebugText::new( + self.src_text(leading_range), + self.src_text(value.range()), + self.src_text(trailing_range), + )) } else { None }; diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap index e1a4f80cca..09118051d2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap @@ -33,6 +33,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap index a6617dd01c..455bb9c133 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap @@ -40,6 +40,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), @@ -69,6 +70,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "second", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap index 861fc69c1c..e38ddc5944 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap @@ -33,6 +33,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap index 43ce4660a4..e4eed4b579 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap @@ -33,6 +33,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " =", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap index 766de86c5f..8981652d97 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap @@ -33,6 +33,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "x", trailing: "= ", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap index 102f40910a..128940b1be 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap @@ -32,6 +32,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " =", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap index 24b1b36cd8..704d367b6c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap @@ -32,6 +32,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "x", trailing: "= ", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap index 5169493e0a..335cdf29d2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap @@ -32,6 +32,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap index e9b8a959fc..4f8ad69bb6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap @@ -39,6 +39,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), @@ -68,6 +69,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "second", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap index 18ce2cfc63..3c18324acd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap @@ -32,6 +32,7 @@ expression: suite debug_text: Some( DebugText { leading: "", + expression: "user", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap index 004ae87faa..3fa3b46130 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/err/f_string_unclosed_lbrace.py --- ## AST @@ -134,6 +133,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "foo", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__ty_1828.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__ty_1828.py.snap index 49ea0c7d58..f46548da50 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__ty_1828.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__ty_1828.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/invalid/re_lexing/ty_1828.py --- ## AST @@ -75,6 +74,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "d", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap index f39f719d1d..f6537ec7ee 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py --- ## AST @@ -129,6 +128,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "foo", trailing: "=", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap index a7a0f0c925..ac894204d5 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/valid/expressions/f_string.py --- ## AST @@ -608,6 +607,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -660,6 +660,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -726,6 +727,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -798,6 +800,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "1, 2", trailing: " = ", }, ), @@ -866,6 +869,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "3.1415", trailing: "=", }, ), @@ -1391,6 +1395,7 @@ Module( debug_text: Some( DebugText { leading: " ( ", + expression: "foo", trailing: " ) = ", }, ), @@ -1938,6 +1943,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " =", }, ), @@ -1990,6 +1996,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "x", trailing: " = ", }, ), @@ -2042,6 +2049,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: "=", }, ), @@ -2155,6 +2163,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " = ", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap index ad30629e3a..76e9eb3b6c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/valid/expressions/t_string.py --- ## AST @@ -585,6 +584,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -635,6 +635,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -699,6 +700,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "foo", trailing: " = ", }, ), @@ -769,6 +771,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "1, 2", trailing: " = ", }, ), @@ -834,6 +837,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "3.1415", trailing: "=", }, ), @@ -1360,6 +1364,7 @@ Module( debug_text: Some( DebugText { leading: " ( ", + expression: "foo", trailing: " ) = ", }, ), @@ -1895,6 +1900,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " =", }, ), @@ -1945,6 +1951,7 @@ Module( debug_text: Some( DebugText { leading: " ", + expression: "x", trailing: " = ", }, ), @@ -1995,6 +2002,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: "=", }, ), @@ -2104,6 +2112,7 @@ Module( debug_text: Some( DebugText { leading: "", + expression: "x", trailing: " = ", }, ),