Skip to content

Commit

Permalink
Rename ExprStringLiteral::as_unconcatenated_string() to `ExprString…
Browse files Browse the repository at this point in the history
…Literal::as_single_part_string()` (#16253)
  • Loading branch information
AlexWaygood authored Feb 19, 2025
1 parent 97d0659 commit 25920fe
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/checkers/ast/analyze/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions crates/ruff_linter/src/rules/refurb/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,9 @@ fn match_open_keywords(

/// Match open mode to see if it is supported.
fn match_open_mode(mode: &Expr) -> Option<OpenMode> {
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),
Expand Down
6 changes: 4 additions & 2 deletions crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 23 additions & 10 deletions crates/ruff_python_ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ pub struct FormatExprBytesLiteral;

impl FormatNodeRule<ExprBytesLiteral> 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.
Expand Down
28 changes: 12 additions & 16 deletions crates/ruff_python_formatter/src/expression/expr_f_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@ pub struct FormatExprFString;

impl FormatNodeRule<ExprFString> 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.
Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl FormatRuleWithOptions<ExprStringLiteral, PyFormatContext<'_>> for FormatExp

impl FormatNodeRule<ExprStringLiteral> 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.
Expand Down
8 changes: 3 additions & 5 deletions crates/ruff_python_formatter/src/statement/stmt_assign.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_parser/src/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 25920fe

Please sign in to comment.