Skip to content

Commit 8f87dbc

Browse files
authored
Merge pull request RustPython#4412 from yt2b/implement_format_validation
Implement integer format validation
2 parents 096a604 + 980abb0 commit 8f87dbc

File tree

4 files changed

+74
-19
lines changed

4 files changed

+74
-19
lines changed

Lib/test/test_types.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,6 @@ def test_type_function(self):
223223
self.assertRaises(TypeError, type, 1, 2)
224224
self.assertRaises(TypeError, type, 1, 2, 3, 4)
225225

226-
# TODO: RUSTPYTHON
227-
@unittest.expectedFailure
228226
def test_int__format__(self):
229227
def test(i, format_spec, result):
230228
# just make sure we have the unified type for integers
@@ -576,8 +574,6 @@ def test(f, format_spec, result):
576574
test(12345.6, "1=20", '111111111111112345.6')
577575
test(12345.6, "*=20", '*************12345.6')
578576

579-
# TODO: RUSTPYTHON
580-
@unittest.expectedFailure
581577
def test_format_spec_errors(self):
582578
# int, float, and string all share the same format spec
583579
# mini-language parser.

common/src/format.rs

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,28 @@ pub enum FormatType {
137137
Percentage,
138138
}
139139

140+
impl From<&FormatType> for char {
141+
fn from(from: &FormatType) -> char {
142+
match from {
143+
FormatType::String => 's',
144+
FormatType::Binary => 'b',
145+
FormatType::Character => 'c',
146+
FormatType::Decimal => 'd',
147+
FormatType::Octal => 'o',
148+
FormatType::HexLower => 'x',
149+
FormatType::HexUpper => 'X',
150+
FormatType::Number => 'n',
151+
FormatType::ExponentLower => 'e',
152+
FormatType::ExponentUpper => 'E',
153+
FormatType::GeneralFormatLower => 'g',
154+
FormatType::GeneralFormatUpper => 'G',
155+
FormatType::FixedPointLower => 'f',
156+
FormatType::FixedPointUpper => 'F',
157+
FormatType::Percentage => '%',
158+
}
159+
}
160+
}
161+
140162
impl FormatParse for FormatType {
141163
fn parse(text: &str) -> (Option<Self>, &str) {
142164
let mut chars = text.chars();
@@ -337,6 +359,33 @@ impl FormatSpec {
337359
magnitude_str
338360
}
339361

362+
fn validate_format(&self, default_format_type: FormatType) -> Result<(), String> {
363+
let format_type = self.format_type.as_ref().unwrap_or(&default_format_type);
364+
match (&self.grouping_option, format_type) {
365+
(
366+
Some(FormatGrouping::Comma),
367+
FormatType::String
368+
| FormatType::Character
369+
| FormatType::Binary
370+
| FormatType::Octal
371+
| FormatType::HexLower
372+
| FormatType::HexUpper
373+
| FormatType::Number,
374+
) => {
375+
let ch = char::from(format_type);
376+
Err(format!("Cannot specify ',' with '{}'.", ch))
377+
}
378+
(
379+
Some(FormatGrouping::Underscore),
380+
FormatType::String | FormatType::Character | FormatType::Number,
381+
) => {
382+
let ch = char::from(format_type);
383+
Err(format!("Cannot specify '_' with '{}'.", ch))
384+
}
385+
_ => Ok(()),
386+
}
387+
}
388+
340389
fn get_separator_interval(&self) -> usize {
341390
match self.format_type {
342391
Some(FormatType::Binary) => 4,
@@ -467,14 +516,15 @@ impl FormatSpec {
467516
}
468517

469518
#[inline]
470-
fn format_int_radix(&self, magnitude: BigInt, radix: u32) -> Result<String, &'static str> {
519+
fn format_int_radix(&self, magnitude: BigInt, radix: u32) -> Result<String, String> {
471520
match self.precision {
472-
Some(_) => Err("Precision not allowed in integer format specifier"),
521+
Some(_) => Err("Precision not allowed in integer format specifier".to_owned()),
473522
None => Ok(magnitude.to_str_radix(radix)),
474523
}
475524
}
476525

477-
pub fn format_int(&self, num: &BigInt) -> Result<String, &'static str> {
526+
pub fn format_int(&self, num: &BigInt) -> Result<String, String> {
527+
self.validate_format(FormatType::Decimal)?;
478528
let magnitude = num.abs();
479529
let prefix = if self.alternate_form {
480530
match self.format_type {
@@ -487,30 +537,34 @@ impl FormatSpec {
487537
} else {
488538
""
489539
};
490-
let raw_magnitude_str: Result<String, &'static str> = match self.format_type {
540+
let raw_magnitude_str: Result<String, String> = match self.format_type {
491541
Some(FormatType::Binary) => self.format_int_radix(magnitude, 2),
492542
Some(FormatType::Decimal) => self.format_int_radix(magnitude, 10),
493543
Some(FormatType::Octal) => self.format_int_radix(magnitude, 8),
494544
Some(FormatType::HexLower) => self.format_int_radix(magnitude, 16),
495545
Some(FormatType::HexUpper) => match self.precision {
496-
Some(_) => Err("Precision not allowed in integer format specifier"),
546+
Some(_) => Err("Precision not allowed in integer format specifier".to_owned()),
497547
None => {
498548
let mut result = magnitude.to_str_radix(16);
499549
result.make_ascii_uppercase();
500550
Ok(result)
501551
}
502552
},
503553
Some(FormatType::Number) => self.format_int_radix(magnitude, 10),
504-
Some(FormatType::String) => Err("Unknown format code 's' for object of type 'int'"),
554+
Some(FormatType::String) => {
555+
Err("Unknown format code 's' for object of type 'int'".to_owned())
556+
}
505557
Some(FormatType::Character) => match (self.sign, self.alternate_form) {
506-
(Some(_), _) => Err("Sign not allowed with integer format specifier 'c'"),
507-
(_, true) => {
508-
Err("Alternate form (#) not allowed with integer format specifier 'c'")
558+
(Some(_), _) => {
559+
Err("Sign not allowed with integer format specifier 'c'".to_owned())
509560
}
561+
(_, true) => Err(
562+
"Alternate form (#) not allowed with integer format specifier 'c'".to_owned(),
563+
),
510564
(_, _) => match num.to_u32() {
511565
Some(n) if n <= 0x10ffff => Ok(std::char::from_u32(n).unwrap().to_string()),
512566
// TODO: raise OverflowError
513-
Some(_) | None => Err("%c arg not in range(0x110000)"),
567+
Some(_) | None => Err("%c arg not in range(0x110000)".to_owned()),
514568
},
515569
},
516570
Some(FormatType::GeneralFormatUpper)
@@ -520,8 +574,8 @@ impl FormatSpec {
520574
| Some(FormatType::ExponentUpper)
521575
| Some(FormatType::ExponentLower)
522576
| Some(FormatType::Percentage) => match num.to_f64() {
523-
Some(float) => return self.format_float(float),
524-
_ => Err("Unable to convert int to float"),
577+
Some(float) => return self.format_float(float).map_err(|msg| msg.to_owned()),
578+
_ => Err("Unable to convert int to float".to_owned()),
525579
},
526580
None => self.format_int_radix(magnitude, 10),
527581
};
@@ -537,6 +591,7 @@ impl FormatSpec {
537591
let sign_prefix = format!("{}{}", sign_str, prefix);
538592
let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, &sign_prefix);
539593
self.format_sign_and_align(&magnitude_str, &sign_prefix, FormatAlign::Right)
594+
.map_err(|msg| msg.to_owned())
540595
}
541596

542597
pub fn format_string(&self, s: &str) -> Result<String, &'static str> {

extra_tests/snippets/builtin_format.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ def test_zero_padding():
6666
assert f"{123.456:+011,}" == "+00,123.456"
6767
assert f"{1234:.3g}" == "1.23e+03"
6868
assert f"{1234567:.6G}" == "1.23457E+06"
69+
assert_raises(ValueError, "{:,o}".format, 1, _msg="ValueError: Cannot specify ',' with 'o'.")
70+
assert_raises(ValueError, "{:_n}".format, 1, _msg="ValueError: Cannot specify '_' with 'n'.")

vm/src/builtins/int.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,11 @@ impl PyInt {
568568

569569
#[pymethod(magic)]
570570
fn format(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
571-
FormatSpec::parse(spec.as_str())
572-
.and_then(|format_spec| format_spec.format_int(&self.value))
573-
.map_err(|msg| vm.new_value_error(msg.to_owned()))
571+
let format_spec =
572+
FormatSpec::parse(spec.as_str()).map_err(|msg| vm.new_value_error(msg.to_owned()))?;
573+
format_spec
574+
.format_int(&self.value)
575+
.map_err(|msg| vm.new_value_error(msg))
574576
}
575577

576578
#[pymethod(magic)]

0 commit comments

Comments
 (0)