From 25920fe489346f8e0a117ab7781455aa30388c79 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 19 Feb 2025 16:06:57 +0000 Subject: [PATCH] Rename `ExprStringLiteral::as_unconcatenated_string()` to `ExprStringLiteral::as_single_part_string()` (#16253) --- .../src/types/string_annotation.rs | 2 +- .../src/checkers/ast/analyze/definitions.rs | 2 +- .../ruff_linter/src/rules/refurb/helpers.rs | 8 ++--- .../src/rules/ruff/rules/sequence_sorting.rs | 6 ++-- crates/ruff_python_ast/src/nodes.rs | 33 +++++++++++++------ .../src/expression/expr_bytes_literal.rs | 4 +-- .../src/expression/expr_f_string.rs | 28 +++++++--------- .../src/expression/expr_string_literal.rs | 2 +- .../src/statement/stmt_assign.rs | 8 ++--- crates/ruff_python_parser/src/typing.rs | 2 +- 10 files changed, 50 insertions(+), 45 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/string_annotation.rs b/crates/red_knot_python_semantic/src/types/string_annotation.rs index 1d78d5ff32..e5a5b4741f 100644 --- a/crates/red_knot_python_semantic/src/types/string_annotation.rs +++ b/crates/red_knot_python_semantic/src/types/string_annotation.rs @@ -138,7 +138,7 @@ pub(crate) fn parse_string_annotation( let source = source_text(db.upcast(), file); - if let Some(string_literal) = string_expr.as_unconcatenated_literal() { + if let Some(string_literal) = string_expr.as_single_part_string() { let prefix = string_literal.flags.prefix(); if prefix.is_raw() { context.report_lint( diff --git a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs index 54e2350cd5..f0a4cd2f0a 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs @@ -183,7 +183,7 @@ pub(crate) fn definitions(checker: &mut Checker) { }; // We don't recognise implicitly concatenated strings as valid docstrings in our model currently. - let Some(sole_string_part) = string_literal.as_unconcatenated_literal() else { + let Some(sole_string_part) = string_literal.as_single_part_string() else { #[allow(deprecated)] let location = checker .locator diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index d81558e588..d3a96b908f 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -290,11 +290,9 @@ fn match_open_keywords( /// Match open mode to see if it is supported. fn match_open_mode(mode: &Expr) -> Option { - let ast::ExprStringLiteral { value, .. } = mode.as_string_literal_expr()?; - if value.is_implicit_concatenated() { - return None; - } - match value.to_str() { + let mode = mode.as_string_literal_expr()?.as_single_part_string()?; + + match &*mode.value { "r" => Some(OpenMode::ReadText), "rb" => Some(OpenMode::ReadBytes), "w" => Some(OpenMode::WriteText), diff --git a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs index 83b40b1c8e..6926170ee9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs @@ -300,8 +300,10 @@ impl<'a> SortClassification<'a> { let Some(string_node) = expr.as_string_literal_expr() else { return Self::NotAListOfStringLiterals; }; - any_implicit_concatenation |= string_node.value.is_implicit_concatenated(); - items.push(string_node.value.to_str()); + match string_node.as_single_part_string() { + Some(literal) => items.push(&*literal.value), + None => any_implicit_concatenation = true, + } } if any_implicit_concatenation { return Self::UnsortedButUnfixable; diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index e9af873e00..76cd0ae7d3 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -824,6 +824,17 @@ pub struct ExprFString { pub value: FStringValue, } +impl ExprFString { + /// Returns the single [`FString`] if the f-string isn't implicitly concatenated, [`None`] + /// otherwise. + pub const fn as_single_part_fstring(&self) -> Option<&FString> { + match &self.value.inner { + FStringValueInner::Single(FStringPart::FString(fstring)) => Some(fstring), + _ => None, + } + } +} + /// The value representing an [`ExprFString`]. #[derive(Clone, Debug, PartialEq)] pub struct FStringValue { @@ -856,15 +867,6 @@ impl FStringValue { matches!(self.inner, FStringValueInner::Concatenated(_)) } - /// Returns the single [`FString`] if the f-string isn't implicitly concatenated, [`None`] - /// otherwise. - pub fn as_single(&self) -> Option<&FString> { - match &self.inner { - FStringValueInner::Single(FStringPart::FString(fstring)) => Some(fstring), - _ => None, - } - } - /// Returns a slice of all the [`FStringPart`]s contained in this value. pub fn as_slice(&self) -> &[FStringPart] { match &self.inner { @@ -1290,7 +1292,7 @@ pub struct ExprStringLiteral { 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`. - pub fn as_unconcatenated_literal(&self) -> Option<&StringLiteral> { + pub fn as_single_part_string(&self) -> Option<&StringLiteral> { match &self.value.inner { StringLiteralValueInner::Single(value) => Some(value), StringLiteralValueInner::Concatenated(_) => None, @@ -1728,6 +1730,17 @@ pub struct ExprBytesLiteral { pub value: BytesLiteralValue, } +impl ExprBytesLiteral { + /// Return `Some(literal)` if the bytestring only consists of a single `BytesLiteral` part + /// (indicating that it is not implicitly concatenated). Otherwise, return `None`. + pub const fn as_single_part_bytestring(&self) -> Option<&BytesLiteral> { + match &self.value.inner { + BytesLiteralValueInner::Single(value) => Some(value), + BytesLiteralValueInner::Concatenated(_) => None, + } + } +} + /// The value representing a [`ExprBytesLiteral`]. #[derive(Clone, Debug, PartialEq)] pub struct BytesLiteralValue { diff --git a/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs b/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs index 15c0a6e4bf..a60b46f9ae 100644 --- a/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs @@ -13,9 +13,7 @@ pub struct FormatExprBytesLiteral; impl FormatNodeRule for FormatExprBytesLiteral { fn fmt_fields(&self, item: &ExprBytesLiteral, f: &mut PyFormatter) -> FormatResult<()> { - let ExprBytesLiteral { value, .. } = item; - - if let [bytes_literal] = value.as_slice() { + if let Some(bytes_literal) = item.as_single_part_bytestring() { bytes_literal.format().fmt(f) } else { // Always join byte literals that aren't parenthesized and thus, always on a single line. diff --git a/crates/ruff_python_formatter/src/expression/expr_f_string.rs b/crates/ruff_python_formatter/src/expression/expr_f_string.rs index cfbfb0243d..045df2cdd8 100644 --- a/crates/ruff_python_formatter/src/expression/expr_f_string.rs +++ b/crates/ruff_python_formatter/src/expression/expr_f_string.rs @@ -15,13 +15,7 @@ pub struct FormatExprFString; impl FormatNodeRule for FormatExprFString { fn fmt_fields(&self, item: &ExprFString, f: &mut PyFormatter) -> FormatResult<()> { - let ExprFString { value, .. } = item; - - if let [f_string_part] = value.as_slice() { - // SAFETY: A single string literal cannot be an f-string. This is guaranteed by the - // [`ruff_python_ast::FStringValue::single`] constructor. - let f_string = f_string_part.as_f_string().unwrap(); - + if let Some(f_string) = item.as_single_part_fstring() { f_string.format().fmt(f) } else { // Always join fstrings that aren't parenthesized and thus, are always on a single line. @@ -44,16 +38,18 @@ impl NeedsParentheses for ExprFString { _parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { - if self.value.is_implicit_concatenated() { - OptionalParentheses::Multiline - } else if StringLike::FString(self).is_multiline(context) - || self.value.as_single().is_some_and(|f_string| { - FStringLayout::from_f_string(f_string, context.source()).is_multiline() - }) - { - OptionalParentheses::Never + if let Some(fstring_part) = self.as_single_part_fstring() { + // The f-string is not implicitly concatenated + if StringLike::FString(self).is_multiline(context) + || FStringLayout::from_f_string(fstring_part, context.source()).is_multiline() + { + OptionalParentheses::Never + } else { + OptionalParentheses::BestFit + } } else { - OptionalParentheses::BestFit + // The f-string is implicitly concatenated + OptionalParentheses::Multiline } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_string_literal.rs b/crates/ruff_python_formatter/src/expression/expr_string_literal.rs index c000a490f4..c6f0890731 100644 --- a/crates/ruff_python_formatter/src/expression/expr_string_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_string_literal.rs @@ -28,7 +28,7 @@ impl FormatRuleWithOptions> for FormatExp impl FormatNodeRule for FormatExprStringLiteral { fn fmt_fields(&self, item: &ExprStringLiteral, f: &mut PyFormatter) -> FormatResult<()> { - if let Some(string_literal) = item.as_unconcatenated_literal() { + if let Some(string_literal) = item.as_single_part_string() { string_literal.format().with_options(self.kind).fmt(f) } else { // Always join strings that aren't parenthesized and thus, always on a single line. diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index c92f568652..80dfc81884 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -1,7 +1,7 @@ use ruff_formatter::{format_args, write, FormatError, RemoveSoftLinesBuffer}; use ruff_python_ast::{ - AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, FStringPart, Operator, StmtAssign, - StringLike, TypeParams, + AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, Operator, StmtAssign, StringLike, + TypeParams, }; use crate::builders::parenthesize_if_expands; @@ -1107,9 +1107,7 @@ fn format_f_string_assignment<'a>( return None; }; - let [FStringPart::FString(f_string)] = expr.value.as_slice() else { - return None; - }; + let f_string = expr.as_single_part_fstring()?; // If the f-string is flat, there are no breakpoints from which it can be made multiline. // This is the case when the f-string has no expressions or if it does then the expressions diff --git a/crates/ruff_python_parser/src/typing.rs b/crates/ruff_python_parser/src/typing.rs index 5111eac646..613ac59695 100644 --- a/crates/ruff_python_parser/src/typing.rs +++ b/crates/ruff_python_parser/src/typing.rs @@ -56,7 +56,7 @@ pub fn parse_type_annotation( string_expr: &ExprStringLiteral, source: &str, ) -> AnnotationParseResult { - if let Some(string_literal) = string_expr.as_unconcatenated_literal() { + if let Some(string_literal) = string_expr.as_single_part_string() { // Compare the raw contents (without quotes) of the expression with the parsed contents // contained in the string literal. if &source[string_literal.content_range()] == string_literal.as_str() {