Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve triple quotes and prefixes for strings #15818

Merged
merged 29 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4559f26
preserve triple quoting
ntBre Jan 29, 2025
de72a61
unset triple quotes for SIM905 fix, add without_triple_quotes method
ntBre Jan 29, 2025
59067bd
pass flags instead of separate quote and triple_quote values
ntBre Jan 29, 2025
5354c0c
move prefix logic into p_str_repr
ntBre Jan 29, 2025
7d93868
handle prefixes for bytestrings
ntBre Jan 29, 2025
4c85aad
test all prefix and quote combinations
ntBre Jan 29, 2025
13e6e7a
add *Flags::without_triple_quotes
ntBre Jan 29, 2025
6b0efc8
use format_string_contents
ntBre Jan 29, 2025
0483893
unwrap -> expect
ntBre Jan 29, 2025
7dcc98e
use a matrix for prefix-quote tests
ntBre Jan 29, 2025
759cb64
use TripleQuoted enum instead of bool
ntBre Jan 29, 2025
5ae157c
remove redundant quote tests
ntBre Jan 30, 2025
55379b7
add nested quote test for bytestring
ntBre Jan 30, 2025
0550ac1
mark is_yes const
ntBre Jan 30, 2025
6183c74
prototype making the `TripleQuoted` enum more central to the API
AlexWaygood Jan 30, 2025
82f341c
Merge branch 'alex/prototype' of github.com:AlexWaygood/ruff
ntBre Jan 30, 2025
718fb82
update doc links
ntBre Jan 30, 2025
073f1e9
rename field to triple_quotes
ntBre Jan 30, 2025
f95a28d
add StringFlags::write_string_contents
ntBre Jan 30, 2025
0c55361
expand ascii bytes comment
ntBre Jan 30, 2025
dab84a3
remove AnyStringFlags::default
ntBre Jan 30, 2025
7f62864
use write_string_contents in format_string_contents
ntBre Jan 30, 2025
7eadc7c
tidy imports
ntBre Jan 30, 2025
845fe6a
move buf_size optimization into write_string_contents
ntBre Jan 30, 2025
b799946
rename with_triple_quotes_set_to
ntBre Jan 31, 2025
eef013d
inline p_raw_bytes
ntBre Feb 3, 2025
970565f
switch to display impl for StringFlags
ntBre Feb 3, 2025
0f83712
add nested quote test case
ntBre Feb 3, 2025
41f5f5e
move test case to the bottom
ntBre Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/red_knot_python_semantic/src/types/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::fmt::{self, Display, Formatter, Write};

use ruff_db::display::FormatterJoinExtension;
use ruff_python_ast::str::Quote;
use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;

use crate::types::class_base::ClassBase;
Expand Down Expand Up @@ -98,7 +98,7 @@ impl Display for DisplayRepresentation<'_> {
let escape =
AsciiEscape::with_preferred_quote(bytes.value(self.db).as_ref(), Quote::Double);

escape.bytes_repr().write(f)
escape.bytes_repr(TripleQuotes::No).write(f)
}
Type::SliceLiteral(slice) => {
f.write_str("slice[")?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::cmp::Ordering;
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{
Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral, StringLiteralFlags,
StringLiteralValue, UnaryOp,
str::TripleQuotes, Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral,
StringLiteralFlags, StringLiteralValue, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange};

Expand Down Expand Up @@ -123,7 +123,17 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::from(StringLiteral {
value: Box::from(*elt),
range: TextRange::default(),
flags,
// intentionally omit the triple quote flag, if set, to avoid strange
// replacements like
//
// ```python
// """
// itemA
// itemB
// itemC
// """.split() # -> ["""itemA""", """itemB""", """itemC"""]
// ```
flags: flags.with_triple_quotes_set_to(TripleQuotes::No),
})
})
.collect(),
Expand Down
163 changes: 91 additions & 72 deletions crates/ruff_python_ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use itertools::Itertools;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};

use crate::name::Name;
use crate::str::TripleQuotes;
use crate::{
int,
str::Quote,
ntBre marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -981,25 +982,24 @@ pub trait StringFlags: Copy {
/// Does the string use single or double quotes in its opener and closer?
fn quote_style(self) -> Quote;

/// Is the string triple-quoted, i.e.,
/// does it begin and end with three consecutive quote characters?
fn is_triple_quoted(self) -> bool;
fn triple_quotes(self) -> TripleQuotes;

fn prefix(self) -> AnyStringPrefix;

/// Is the string triple-quoted, i.e.,
/// does it begin and end with three consecutive quote characters?
fn is_triple_quoted(self) -> bool {
self.triple_quotes().is_yes()
}

/// A `str` representation of the quotes used to start and close.
/// This does not include any prefixes the string has in its opener.
fn quote_str(self) -> &'static str {
if self.is_triple_quoted() {
match self.quote_style() {
Quote::Single => "'''",
Quote::Double => r#"""""#,
}
} else {
match self.quote_style() {
Quote::Single => "'",
Quote::Double => "\"",
}
match (self.triple_quotes(), self.quote_style()) {
(TripleQuotes::Yes, Quote::Single) => "'''",
(TripleQuotes::Yes, Quote::Double) => r#"""""#,
(TripleQuotes::No, Quote::Single) => "'",
(TripleQuotes::No, Quote::Double) => "\"",
}
}

Expand Down Expand Up @@ -1029,9 +1029,18 @@ pub trait StringFlags: Copy {
}

fn format_string_contents(self, contents: &str) -> String {
let prefix = self.prefix();
let buf_size = self.opener_len().to_usize() + contents.len() + self.closer_len().to_usize();
let mut buffer = String::with_capacity(buf_size);
ntBre marked this conversation as resolved.
Show resolved Hide resolved
self.write_string_contents(&mut buffer, contents);
buffer
}

fn write_string_contents(self, buffer: &mut String, contents: &str) {
let quote_str = self.quote_str();
format!("{prefix}{quote_str}{contents}{quote_str}")
buffer.push_str(self.prefix().as_str());
buffer.push_str(quote_str);
buffer.push_str(contents);
buffer.push_str(quote_str);
}
}

Expand Down Expand Up @@ -1078,7 +1087,7 @@ pub struct FStringFlags(FStringFlagsInner);
impl FStringFlags {
/// Construct a new [`FStringFlags`] with **no flags set**.
///
/// See [`FStringFlags::with_quote_style`], [`FStringFlags::with_triple_quotes`], and
/// See [`FStringFlags::with_quote_style`], [`FStringFlags::with_triple_quotes_set_to`], and
/// [`FStringFlags::with_prefix`] for ways of setting the quote style (single or double),
/// enabling triple quotes, and adding prefixes (such as `r`), respectively.
///
Expand All @@ -1097,8 +1106,9 @@ impl FStringFlags {
}

#[must_use]
pub fn with_triple_quotes(mut self) -> Self {
self.0 |= FStringFlagsInner::TRIPLE_QUOTED;
pub fn with_triple_quotes_set_to(mut self, triple_quotes: TripleQuotes) -> Self {
self.0
.set(FStringFlagsInner::TRIPLE_QUOTED, triple_quotes.is_yes());
self
}

Expand Down Expand Up @@ -1132,8 +1142,12 @@ 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 is_triple_quoted(self) -> bool {
self.0.contains(FStringFlagsInner::TRIPLE_QUOTED)
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(FStringFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
}
}

/// Return the quoting style (single or double quotes)
Expand Down Expand Up @@ -1458,9 +1472,10 @@ pub struct StringLiteralFlags(StringLiteralFlagsInner);
impl StringLiteralFlags {
/// Construct a new [`StringLiteralFlags`] with **no flags set**.
///
/// See [`StringLiteralFlags::with_quote_style`], [`StringLiteralFlags::with_triple_quotes`],
/// and [`StringLiteralFlags::with_prefix`] for ways of setting the quote style (single or
/// double), enabling triple quotes, and adding prefixes (such as `r` or `u`), respectively.
/// See [`StringLiteralFlags::with_quote_style`],
/// [`StringLiteralFlags::with_triple_quotes_set_to`], and [`StringLiteralFlags::with_prefix`]
/// for ways of setting the quote style (single or double), enabling triple quotes, and adding
/// prefixes (such as `r` or `u`), respectively.
///
/// See the documentation for [`StringLiteralFlags`] for additional caveats on this constructor,
/// and situations in which alternative ways to construct this struct should be used, especially
Expand All @@ -1477,8 +1492,11 @@ impl StringLiteralFlags {
}

#[must_use]
pub fn with_triple_quotes(mut self) -> Self {
self.0 |= StringLiteralFlagsInner::TRIPLE_QUOTED;
pub fn with_triple_quotes_set_to(mut self, triple_quotes: TripleQuotes) -> Self {
self.0.set(
StringLiteralFlagsInner::TRIPLE_QUOTED,
triple_quotes.is_yes(),
);
self
}

Expand Down Expand Up @@ -1550,8 +1568,12 @@ impl StringFlags for StringLiteralFlags {
/// Return `true` if the string is triple-quoted, i.e.,
/// it begins and ends with three consecutive quote characters.
/// For example: `"""bar"""`
fn is_triple_quoted(self) -> bool {
self.0.contains(StringLiteralFlagsInner::TRIPLE_QUOTED)
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(StringLiteralFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
}
}

fn prefix(self) -> AnyStringPrefix {
Expand Down Expand Up @@ -1847,9 +1869,10 @@ pub struct BytesLiteralFlags(BytesLiteralFlagsInner);
impl BytesLiteralFlags {
/// Construct a new [`BytesLiteralFlags`] with **no flags set**.
///
/// See [`BytesLiteralFlags::with_quote_style`], [`BytesLiteralFlags::with_triple_quotes`], and
/// [`BytesLiteralFlags::with_prefix`] for ways of setting the quote style (single or double),
/// enabling triple quotes, and adding prefixes (such as `r`), respectively.
/// See [`BytesLiteralFlags::with_quote_style`],
/// [`BytesLiteralFlags::with_triple_quotes_set_to`], and [`BytesLiteralFlags::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 [`BytesLiteralFlags`] for additional caveats on this constructor,
/// and situations in which alternative ways to construct this struct should be used, especially
Expand All @@ -1866,8 +1889,11 @@ impl BytesLiteralFlags {
}

#[must_use]
pub fn with_triple_quotes(mut self) -> Self {
self.0 |= BytesLiteralFlagsInner::TRIPLE_QUOTED;
pub fn with_triple_quotes_set_to(mut self, triple_quotes: TripleQuotes) -> Self {
self.0.set(
BytesLiteralFlagsInner::TRIPLE_QUOTED,
triple_quotes.is_yes(),
);
self
}

Expand Down Expand Up @@ -1910,8 +1936,12 @@ impl StringFlags for BytesLiteralFlags {
/// Return `true` if the bytestring is triple-quoted, i.e.,
/// it begins and ends with three consecutive quote characters.
/// For example: `b"""{bar}"""`
fn is_triple_quoted(self) -> bool {
self.0.contains(BytesLiteralFlagsInner::TRIPLE_QUOTED)
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(BytesLiteralFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
}
}

/// Return the quoting style (single or double quotes)
Expand Down Expand Up @@ -2035,7 +2065,7 @@ bitflags! {
}
}

#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct AnyStringFlags(AnyStringFlagsInner);

impl AnyStringFlags {
Expand Down Expand Up @@ -2073,13 +2103,11 @@ impl AnyStringFlags {
self
}

pub fn new(prefix: AnyStringPrefix, quotes: Quote, triple_quoted: bool) -> Self {
let new = Self::default().with_prefix(prefix).with_quote_style(quotes);
if triple_quoted {
new.with_triple_quotes()
} else {
new
}
pub fn new(prefix: AnyStringPrefix, quotes: Quote, triple_quotes: TripleQuotes) -> Self {
Self(AnyStringFlagsInner::empty())
.with_prefix(prefix)
.with_quote_style(quotes)
.with_triple_quotes_set_to(triple_quotes)
}

/// Does the string have a `u` or `U` prefix?
Expand Down Expand Up @@ -2114,8 +2142,9 @@ impl AnyStringFlags {
}

#[must_use]
pub fn with_triple_quotes(mut self) -> Self {
self.0 |= AnyStringFlagsInner::TRIPLE_QUOTED;
pub fn with_triple_quotes_set_to(mut self, triple_quotes: TripleQuotes) -> Self {
self.0
.set(AnyStringFlagsInner::TRIPLE_QUOTED, triple_quotes.is_yes());
self
ntBre marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand All @@ -2130,10 +2159,12 @@ impl StringFlags for AnyStringFlags {
}
}

/// Is the string triple-quoted, i.e.,
/// does it begin and end with three consecutive quote characters?
fn is_triple_quoted(self) -> bool {
self.0.contains(AnyStringFlagsInner::TRIPLE_QUOTED)
fn triple_quotes(self) -> TripleQuotes {
if self.0.contains(AnyStringFlagsInner::TRIPLE_QUOTED) {
TripleQuotes::Yes
} else {
TripleQuotes::No
}
}

fn prefix(self) -> AnyStringPrefix {
Expand Down Expand Up @@ -2193,14 +2224,10 @@ impl From<AnyStringFlags> for StringLiteralFlags {
value.prefix()
)
};
let new = StringLiteralFlags::empty()
StringLiteralFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(prefix);
if value.is_triple_quoted() {
new.with_triple_quotes()
} else {
new
}
.with_prefix(prefix)
.with_triple_quotes_set_to(value.triple_quotes())
}
}

Expand All @@ -2209,7 +2236,7 @@ impl From<StringLiteralFlags> for AnyStringFlags {
Self::new(
AnyStringPrefix::Regular(value.prefix()),
value.quote_style(),
value.is_triple_quoted(),
value.triple_quotes(),
)
}
}
Expand All @@ -2222,14 +2249,10 @@ impl From<AnyStringFlags> for BytesLiteralFlags {
value.prefix()
)
};
let new = BytesLiteralFlags::empty()
BytesLiteralFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(bytestring_prefix);
if value.is_triple_quoted() {
new.with_triple_quotes()
} else {
new
}
.with_prefix(bytestring_prefix)
.with_triple_quotes_set_to(value.triple_quotes())
}
}

Expand All @@ -2238,7 +2261,7 @@ impl From<BytesLiteralFlags> for AnyStringFlags {
Self::new(
AnyStringPrefix::Bytes(value.prefix()),
value.quote_style(),
value.is_triple_quoted(),
value.triple_quotes(),
)
}
}
Expand All @@ -2251,14 +2274,10 @@ impl From<AnyStringFlags> for FStringFlags {
value.prefix()
)
};
let new = FStringFlags::empty()
FStringFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(fstring_prefix);
if value.is_triple_quoted() {
new.with_triple_quotes()
} else {
new
}
.with_prefix(fstring_prefix)
.with_triple_quotes_set_to(value.triple_quotes())
}
}

Expand All @@ -2267,7 +2286,7 @@ impl From<FStringFlags> for AnyStringFlags {
Self::new(
AnyStringPrefix::Format(value.prefix()),
value.quote_style(),
value.is_triple_quoted(),
value.triple_quotes(),
)
}
}
Expand Down
Loading
Loading