Skip to content

Commit 195673b

Browse files
committed
Add vm.format to call __format__ like PyObject_Format
1 parent c656cdd commit 195673b

File tree

5 files changed

+50
-43
lines changed

5 files changed

+50
-43
lines changed

Lib/test/test_unicode.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,8 +2399,6 @@ def test_raiseMemError(self):
23992399
self.assertRaises(MemoryError, alloc)
24002400
self.assertRaises(MemoryError, alloc)
24012401

2402-
# TODO: RUSTPYTHON
2403-
@unittest.expectedFailure
24042402
def test_format_subclass(self):
24052403
class S(str):
24062404
def __str__(self):

vm/src/format.rs

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::{
2-
builtins::{PyBaseExceptionRef, PyStrRef},
2+
builtins::PyBaseExceptionRef,
33
common::format::*,
44
convert::{IntoPyException, ToPyException},
55
function::FuncArgs,
66
stdlib::builtins,
7-
AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine,
7+
PyObject, PyResult, VirtualMachine,
88
};
99

1010
impl IntoPyException for FormatSpecError {
@@ -94,7 +94,20 @@ fn format_internal(
9494
FormatString::from_str(format_spec).map_err(|e| e.to_pyexception(vm))?;
9595
let format_spec = format_internal(vm, &nested_format, field_func)?;
9696

97-
pystr = call_object_format(vm, argument, *conversion_spec, &format_spec)?;
97+
let argument = match conversion_spec.and_then(FormatConversion::from_char) {
98+
Some(FormatConversion::Str) => argument.str(vm)?.into(),
99+
Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
100+
Some(FormatConversion::Ascii) => {
101+
vm.ctx.new_str(builtins::ascii(argument, vm)?).into()
102+
}
103+
Some(FormatConversion::Bytes) => {
104+
vm.call_method(&argument, identifier!(vm, decode).as_str(), ())?
105+
}
106+
None => argument,
107+
};
108+
109+
// FIXME: compiler can intern specs using parser tree. Then this call can be interned_str
110+
pystr = vm.format(&argument, Some(vm.ctx.intern_str(format_spec).to_owned()))?;
98111
pystr.as_ref()
99112
}
100113
FormatPart::Literal(literal) => literal,
@@ -158,27 +171,3 @@ pub(crate) fn format_map(
158171
FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
159172
})
160173
}
161-
162-
pub fn call_object_format(
163-
vm: &VirtualMachine,
164-
argument: PyObjectRef,
165-
conversion_spec: Option<char>,
166-
format_spec: &str,
167-
) -> PyResult<PyStrRef> {
168-
let argument = match conversion_spec.and_then(FormatConversion::from_char) {
169-
Some(FormatConversion::Str) => argument.str(vm)?.into(),
170-
Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
171-
Some(FormatConversion::Ascii) => vm.ctx.new_str(builtins::ascii(argument, vm)?).into(),
172-
Some(FormatConversion::Bytes) => {
173-
vm.call_method(&argument, identifier!(vm, decode).as_str(), ())?
174-
}
175-
None => argument,
176-
};
177-
let result = vm.call_special_method(argument, identifier!(vm, __format__), (format_spec,))?;
178-
result.downcast().map_err(|result| {
179-
vm.new_type_error(format!(
180-
"__format__ must return a str, not {}",
181-
&result.class().name()
182-
))
183-
})
184-
}

vm/src/frame.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::{
1111
convert::{IntoObject, ToPyResult},
1212
coroutine::Coro,
1313
exceptions::ExceptionCtor,
14-
format::call_object_format,
1514
function::{ArgMapping, Either, FuncArgs},
1615
protocol::{PyIter, PyIterReturn},
1716
scope::Scope,
@@ -1766,12 +1765,7 @@ impl ExecutingFrame<'_> {
17661765
};
17671766

17681767
let spec = self.pop_value();
1769-
let formatted = call_object_format(
1770-
vm,
1771-
value,
1772-
None,
1773-
spec.downcast_ref::<PyStr>().unwrap().as_str(),
1774-
)?;
1768+
let formatted = vm.format(&value, Some(spec.downcast::<PyStr>().unwrap()))?;
17751769
self.push_value(formatted.into());
17761770
Ok(None)
17771771
}

vm/src/stdlib/builtins.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ mod builtins {
1717
},
1818
common::{hash::PyHash, str::to_ascii},
1919
convert::ToPyException,
20-
format::call_object_format,
2120
function::{
2221
ArgBytesLike, ArgCallable, ArgIntoBool, ArgIterable, ArgMapping, ArgStrOrBytesLike,
2322
Either, FuncArgs, KwArgs, OptionalArg, OptionalOption, PosArgs, PyArithmeticValue,
@@ -317,11 +316,7 @@ mod builtins {
317316
format_spec: OptionalArg<PyStrRef>,
318317
vm: &VirtualMachine,
319318
) -> PyResult<PyStrRef> {
320-
let format_spec = format_spec
321-
.into_option()
322-
.unwrap_or_else(|| vm.ctx.empty_str.clone());
323-
324-
call_object_format(vm, value, None, format_spec.as_str())
319+
vm.format(&value, format_spec.into())
325320
}
326321

327322
#[pyfunction]

vm/src/vm/vm_ops.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{PyMethod, VirtualMachine};
22
use crate::{
3-
builtins::{PyInt, PyIntRef, PyStrInterned},
3+
builtins::{PyInt, PyIntRef, PyStr, PyStrInterned, PyStrRef},
44
function::PyArithmeticValue,
55
object::{AsObject, PyObject, PyObjectRef, PyResult},
66
protocol::PyIterReturn,
@@ -494,6 +494,37 @@ impl VirtualMachine {
494494
.invoke((), self)
495495
}
496496

497+
// PyObject_Format
498+
pub fn format(&self, obj: &PyObject, format_spec: Option<PyStrRef>) -> PyResult<PyStrRef> {
499+
let format_spec_is_empty = format_spec
500+
.as_ref()
501+
.map_or(true, |spec| spec.as_str().is_empty());
502+
if format_spec_is_empty {
503+
let obj = match obj.to_owned().downcast_exact::<PyStr>(self) {
504+
Ok(s) => return Ok(s.into_pyref()),
505+
Err(obj) => obj,
506+
};
507+
if obj.class().is(self.ctx.types.int_type) {
508+
return obj.str(self);
509+
}
510+
}
511+
let bound_format = self
512+
.get_special_method(obj.to_owned(), identifier!(self, __format__))?
513+
.map_err(|_| {
514+
self.new_type_error(format!(
515+
"Type {} doesn't define __format__",
516+
obj.class().name()
517+
))
518+
})?;
519+
let formatted = bound_format.invoke((format_spec,), self)?;
520+
formatted.downcast().map_err(|result| {
521+
self.new_type_error(format!(
522+
"__format__ must return a str, not {}",
523+
&result.class().name()
524+
))
525+
})
526+
}
527+
497528
// https://docs.python.org/3/reference/expressions.html#membership-test-operations
498529
fn _membership_iter_search(
499530
&self,

0 commit comments

Comments
 (0)