@@ -26,6 +26,35 @@ use crate::{
2626 stdlib:: builtins,
2727} ;
2828
29+ fn format_decimal_object (
30+ vm : & VirtualMachine ,
31+ spec : & CFormatSpec ,
32+ obj : & PyObject ,
33+ ) -> PyResult < String > {
34+ let type_error = || {
35+ vm. new_type_error ( format ! (
36+ "%{} format: a real number is required, not {}" ,
37+ spec. format_type. to_char( ) ,
38+ obj. class( ) . slot_name( )
39+ ) )
40+ } ;
41+ // Decimal conversions prefer __int__; __index__ is only a fallback when absent.
42+ let i = obj
43+ . number ( )
44+ . int ( vm)
45+ . or_else ( || obj. try_index_opt ( vm) )
46+ . ok_or_else ( type_error) ?
47+ . map_err ( |error| {
48+ if error. fast_isinstance ( vm. ctx . exceptions . type_error ) {
49+ type_error ( )
50+ } else {
51+ error
52+ }
53+ } ) ?;
54+ check_int_to_str_digits ( i. as_bigint ( ) , vm) ?;
55+ Ok ( spec. format_number ( i. as_bigint ( ) ) )
56+ }
57+
2958fn spec_format_bytes (
3059 vm : & VirtualMachine ,
3160 spec : & CFormatSpec ,
@@ -80,27 +109,7 @@ fn spec_format_bytes(
80109 Ok ( spec. format_number( & bigint) . into_bytes( ) )
81110 }
82111 obj => {
83- // CPython parity: `%d` / `%i` / `%u` accept any object
84- // with `__index__` (preferred) or `__int__`.
85- if let Some ( int_result) = obj. try_index_opt( vm) {
86- let i = int_result?;
87- check_int_to_str_digits( i. as_bigint( ) , vm) ?;
88- return Ok ( spec. format_number( i. as_bigint( ) ) . into_bytes( ) ) ;
89- }
90-
91- if let Some ( method) = vm. get_method( obj. clone( ) , identifier!( vm, __int__) ) {
92- let result = method?. call( ( ) , vm) ?;
93- if let Some ( i) = result. downcast_ref:: <PyInt >( ) {
94- check_int_to_str_digits( i. as_bigint( ) , vm) ?;
95- return Ok ( spec. format_number( i. as_bigint( ) ) . into_bytes( ) ) ;
96- }
97- }
98-
99- Err ( vm. new_type_error( format!(
100- "%{} format: a real number is required, not {}" ,
101- spec. format_type. to_char( ) ,
102- obj. class( ) . slot_name( )
103- ) ) )
112+ format_decimal_object( vm, spec, obj) . map( String :: into_bytes)
104113 }
105114 } )
106115 }
@@ -209,25 +218,7 @@ fn spec_format_string(
209218 Ok ( spec. format_number( & bigint) . into( ) )
210219 }
211220 obj => {
212- // CPython parity: `%d` / `%i` / `%u` accept any object
213- // with `__index__` (preferred) or `__int__`.
214- if let Some ( int_result) = obj. try_index_opt( vm) {
215- let i = int_result?;
216- check_int_to_str_digits( i. as_bigint( ) , vm) ?;
217- return Ok ( spec. format_number( i. as_bigint( ) ) . into( ) ) ;
218- }
219- if let Some ( method) = vm. get_method( obj. clone( ) , identifier!( vm, __int__) ) {
220- let result = method?. call( ( ) , vm) ?;
221- if let Some ( i) = result. downcast_ref:: <PyInt >( ) {
222- check_int_to_str_digits( i. as_bigint( ) , vm) ?;
223- return Ok ( spec. format_number( i. as_bigint( ) ) . into( ) ) ;
224- }
225- }
226- Err ( vm. new_type_error( format!(
227- "%{} format: a real number is required, not {}" ,
228- spec. format_type. to_char( ) ,
229- obj. class( ) . slot_name( )
230- ) ) )
221+ format_decimal_object( vm, spec, obj) . map( Into :: into)
231222 }
232223 } )
233224 }
@@ -285,37 +276,11 @@ fn spec_format_string(
285276 }
286277}
287278
288- fn try_update_quantity_from_element (
289- vm : & VirtualMachine ,
290- element : Option < & PyObject > ,
291- ) -> PyResult < CFormatQuantity > {
279+ fn get_star_arg ( vm : & VirtualMachine , element : Option < & PyObject > ) -> PyResult < i32 > {
292280 match element {
293- Some ( width_obj) => {
294- if let Some ( i) = width_obj. downcast_ref :: < PyInt > ( ) {
295- let i = i. try_to_primitive :: < i32 > ( vm) ?. unsigned_abs ( ) ;
296- Ok ( CFormatQuantity :: Amount ( i as usize ) )
297- } else {
298- Err ( vm. new_type_error ( "* wants int" ) )
299- }
300- }
301- None => Err ( vm. new_type_error ( "not enough arguments for format string" ) ) ,
302- }
303- }
304-
305- fn try_conversion_flag_from_tuple (
306- vm : & VirtualMachine ,
307- element : Option < & PyObject > ,
308- ) -> PyResult < CConversionFlags > {
309- match element {
310- Some ( width_obj) => {
311- if let Some ( i) = width_obj. downcast_ref :: < PyInt > ( ) {
312- let i = i. try_to_primitive :: < i32 > ( vm) ?;
313- let flags = if i < 0 {
314- CConversionFlags :: LEFT_ADJUST
315- } else {
316- CConversionFlags :: from_bits ( 0 ) . unwrap ( )
317- } ;
318- Ok ( flags)
281+ Some ( obj) => {
282+ if let Some ( i) = obj. downcast_ref :: < PyInt > ( ) {
283+ i. try_to_primitive :: < i32 > ( vm)
319284 } else {
320285 Err ( vm. new_type_error ( "* wants int" ) )
321286 }
@@ -334,10 +299,11 @@ fn try_update_quantity_from_tuple<'a, I: Iterator<Item = &'a PyObject>>(
334299 return Ok ( ( ) ) ;
335300 } ;
336301
337- let element = elements. next ( ) ;
338- f. insert ( try_conversion_flag_from_tuple ( vm, element) ?) ;
339- let quantity = try_update_quantity_from_element ( vm, element) ?;
340- * q = Some ( quantity) ;
302+ let width = get_star_arg ( vm, elements. next ( ) ) ?;
303+ if width < 0 {
304+ f. insert ( CConversionFlags :: LEFT_ADJUST ) ;
305+ }
306+ * q = Some ( CFormatQuantity :: Amount ( width. unsigned_abs ( ) as usize ) ) ;
341307 Ok ( ( ) )
342308}
343309
@@ -350,8 +316,10 @@ fn try_update_precision_from_tuple<'a, I: Iterator<Item = &'a PyObject>>(
350316 return Ok ( ( ) ) ;
351317 } ;
352318
353- let quantity = try_update_quantity_from_element ( vm, elements. next ( ) ) ?;
354- * p = Some ( CFormatPrecision :: Quantity ( quantity) ) ;
319+ let precision = get_star_arg ( vm, elements. next ( ) ) ?. max ( 0 ) as usize ;
320+ * p = Some ( CFormatPrecision :: Quantity ( CFormatQuantity :: Amount (
321+ precision,
322+ ) ) ) ;
355323 Ok ( ( ) )
356324}
357325
0 commit comments