Skip to content

Commit 23a5c82

Browse files
authored
Add separator validation (RustPython#5904)
* Add separator validation * Remove @unittest.expectedFailure
1 parent 9336507 commit 23a5c82

File tree

5 files changed

+34
-26
lines changed

5 files changed

+34
-26
lines changed

Lib/test/test_format.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -502,29 +502,21 @@ def test_g_format_has_no_trailing_zeros(self):
502502
self.assertEqual(format(12300050.0, ".6g"), "1.23e+07")
503503
self.assertEqual(format(12300050.0, "#.6g"), "1.23000e+07")
504504

505-
# TODO: RUSTPYTHON
506-
@unittest.expectedFailure
507505
def test_with_two_commas_in_format_specifier(self):
508506
error_msg = re.escape("Cannot specify ',' with ','.")
509507
with self.assertRaisesRegex(ValueError, error_msg):
510508
'{:,,}'.format(1)
511509

512-
# TODO: RUSTPYTHON
513-
@unittest.expectedFailure
514510
def test_with_two_underscore_in_format_specifier(self):
515511
error_msg = re.escape("Cannot specify '_' with '_'.")
516512
with self.assertRaisesRegex(ValueError, error_msg):
517513
'{:__}'.format(1)
518514

519-
# TODO: RUSTPYTHON
520-
@unittest.expectedFailure
521515
def test_with_a_commas_and_an_underscore_in_format_specifier(self):
522516
error_msg = re.escape("Cannot specify both ',' and '_'.")
523517
with self.assertRaisesRegex(ValueError, error_msg):
524518
'{:,_}'.format(1)
525519

526-
# TODO: RUSTPYTHON
527-
@unittest.expectedFailure
528520
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
529521
error_msg = re.escape("Cannot specify both ',' and '_'.")
530522
with self.assertRaisesRegex(ValueError, error_msg):

Lib/test/test_fstring.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,29 +1838,21 @@ def test_invalid_syntax_error_message(self):
18381838
):
18391839
compile("f'{a $ b}'", "?", "exec")
18401840

1841-
# TODO: RUSTPYTHON
1842-
@unittest.expectedFailure
18431841
def test_with_two_commas_in_format_specifier(self):
18441842
error_msg = re.escape("Cannot specify ',' with ','.")
18451843
with self.assertRaisesRegex(ValueError, error_msg):
18461844
f"{1:,,}"
18471845

1848-
# TODO: RUSTPYTHON
1849-
@unittest.expectedFailure
18501846
def test_with_two_underscore_in_format_specifier(self):
18511847
error_msg = re.escape("Cannot specify '_' with '_'.")
18521848
with self.assertRaisesRegex(ValueError, error_msg):
18531849
f"{1:__}"
18541850

1855-
# TODO: RUSTPYTHON
1856-
@unittest.expectedFailure
18571851
def test_with_a_commas_and_an_underscore_in_format_specifier(self):
18581852
error_msg = re.escape("Cannot specify both ',' and '_'.")
18591853
with self.assertRaisesRegex(ValueError, error_msg):
18601854
f"{1:,_}"
18611855

1862-
# TODO: RUSTPYTHON
1863-
@unittest.expectedFailure
18641856
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
18651857
error_msg = re.escape("Cannot specify both ',' and '_'.")
18661858
with self.assertRaisesRegex(ValueError, error_msg):

Lib/test/test_long.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,6 @@ def __lt__(self, other):
625625
eq(x > y, Rcmp > 0)
626626
eq(x >= y, Rcmp >= 0)
627627

628-
# TODO: RUSTPYTHON
629-
@unittest.expectedFailure
630628
def test__format__(self):
631629
self.assertEqual(format(123456789, 'd'), '123456789')
632630
self.assertEqual(format(123456789, 'd'), '123456789')

common/src/format.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ impl FormatParse for FormatGrouping {
127127
}
128128
}
129129

130+
impl From<&FormatGrouping> for char {
131+
fn from(fg: &FormatGrouping) -> Self {
132+
match fg {
133+
FormatGrouping::Comma => ',',
134+
FormatGrouping::Underscore => '_',
135+
}
136+
}
137+
}
138+
130139
#[derive(Debug, PartialEq)]
131140
pub enum FormatType {
132141
String,
@@ -281,6 +290,7 @@ impl FormatSpec {
281290
pub fn parse(text: impl AsRef<Wtf8>) -> Result<Self, FormatSpecError> {
282291
Self::_parse(text.as_ref())
283292
}
293+
284294
fn _parse(text: &Wtf8) -> Result<Self, FormatSpecError> {
285295
// get_integer in CPython
286296
let (conversion, text) = FormatConversion::parse(text);
@@ -290,6 +300,9 @@ impl FormatSpec {
290300
let (zero, text) = parse_zero(text);
291301
let (width, text) = parse_number(text)?;
292302
let (grouping_option, text) = FormatGrouping::parse(text);
303+
if let Some(grouping) = &grouping_option {
304+
Self::validate_separator(grouping, text)?;
305+
}
293306
let (precision, text) = parse_precision(text)?;
294307
let (format_type, text) = FormatType::parse(text);
295308
if !text.is_empty() {
@@ -314,6 +327,20 @@ impl FormatSpec {
314327
})
315328
}
316329

330+
fn validate_separator(grouping: &FormatGrouping, text: &Wtf8) -> Result<(), FormatSpecError> {
331+
let mut chars = text.code_points().peekable();
332+
match chars.peek().and_then(|cp| CodePoint::to_char(*cp)) {
333+
Some(c) if c == ',' || c == '_' => {
334+
if c == char::from(grouping) {
335+
Err(FormatSpecError::UnspecifiedFormat(c, c))
336+
} else {
337+
Err(FormatSpecError::ExclusiveFormat(',', '_'))
338+
}
339+
}
340+
_ => Ok(()),
341+
}
342+
}
343+
317344
fn compute_fill_string(fill_char: CodePoint, fill_chars_needed: i32) -> Wtf8Buf {
318345
(0..fill_chars_needed).map(|_| fill_char).collect()
319346
}
@@ -406,10 +433,7 @@ impl FormatSpec {
406433
fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
407434
match &self.grouping_option {
408435
Some(fg) => {
409-
let sep = match fg {
410-
FormatGrouping::Comma => ',',
411-
FormatGrouping::Underscore => '_',
412-
};
436+
let sep = char::from(fg);
413437
let inter = self.get_separator_interval().try_into().unwrap();
414438
let magnitude_len = magnitude_str.len();
415439
let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
@@ -720,10 +744,7 @@ impl FormatSpec {
720744
}?;
721745
match &self.grouping_option {
722746
Some(fg) => {
723-
let sep = match fg {
724-
FormatGrouping::Comma => ',',
725-
FormatGrouping::Underscore => '_',
726-
};
747+
let sep = char::from(fg);
727748
let inter = self.get_separator_interval().try_into().unwrap();
728749
let len = magnitude_str.len() as i32;
729750
let separated_magnitude =
@@ -818,6 +839,7 @@ pub enum FormatSpecError {
818839
PrecisionTooBig,
819840
InvalidFormatSpecifier,
820841
UnspecifiedFormat(char, char),
842+
ExclusiveFormat(char, char),
821843
UnknownFormatCode(char, &'static str),
822844
PrecisionNotAllowed,
823845
NotAllowed(&'static str),

vm/src/format.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ impl IntoPyException for FormatSpecError {
2121
let msg = format!("Cannot specify '{c1}' with '{c2}'.");
2222
vm.new_value_error(msg)
2323
}
24+
Self::ExclusiveFormat(c1, c2) => {
25+
let msg = format!("Cannot specify both '{c1}' and '{c2}'.");
26+
vm.new_value_error(msg)
27+
}
2428
Self::UnknownFormatCode(c, s) => {
2529
let msg = format!("Unknown format code '{c}' for object of type '{s}'");
2630
vm.new_value_error(msg)

0 commit comments

Comments
 (0)