Skip to content

Commit 1f92212

Browse files
authored
Merge pull request RustPython#4612 from youknowone/PyObject_Format
Add vm.format to call __format__ like PyObject_Format
2 parents d7f65cb + 1199177 commit 1f92212

File tree

10 files changed

+87
-63
lines changed

10 files changed

+87
-63
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/builtins/bool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl PyBool {
116116

117117
#[pymethod(magic)]
118118
fn format(obj: PyObjectRef, format_spec: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
119-
if format_spec.as_str().is_empty() {
119+
if format_spec.is_empty() {
120120
obj.str(vm)
121121
} else {
122122
Err(vm.new_type_error("unsupported format string passed to bool.__format__".to_owned()))

vm/src/builtins/int.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use super::{float, PyByteArray, PyBytes, PyStr, PyStrRef, PyType, PyTypeRef};
1+
use super::{float, PyByteArray, PyBytes, PyStr, PyType, PyTypeRef};
22
use crate::{
33
atomic_func,
4+
builtins::PyStrRef,
45
bytesinner::PyBytesInner,
56
class::PyClassImpl,
6-
common::format::FormatSpec,
7-
common::hash,
7+
common::{format::FormatSpec, hash},
88
convert::{IntoPyException, ToPyObject, ToPyResult},
99
function::{
1010
ArgByteOrder, ArgIntoBool, OptionalArg, OptionalOption, PyArithmeticValue,

vm/src/builtins/object.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ impl PyBaseObject {
183183
/// Return str(self).
184184
#[pymethod(magic)]
185185
fn str(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
186+
// FIXME: try tp_repr first and fallback to object.__repr__
186187
zelf.repr(vm)
187188
}
188189

@@ -243,14 +244,13 @@ impl PyBaseObject {
243244

244245
#[pymethod(magic)]
245246
fn format(obj: PyObjectRef, format_spec: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
246-
if format_spec.as_str().is_empty() {
247-
obj.str(vm)
248-
} else {
249-
Err(vm.new_type_error(format!(
247+
if !format_spec.is_empty() {
248+
return Err(vm.new_type_error(format!(
250249
"unsupported format string passed to {}.__format__",
251250
obj.class().name()
252-
)))
251+
)));
253252
}
253+
obj.str(vm)
254254
}
255255

256256
#[pyslot]

vm/src/builtins/str.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,20 @@ impl PyStr {
753753
}
754754

755755
#[pymethod(name = "__format__")]
756-
fn format_str(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
757-
FormatSpec::parse(spec.as_str())
758-
.and_then(|format_spec| format_spec.format_string(self.borrow()))
759-
.map_err(|err| err.into_pyexception(vm))
756+
fn __format__(zelf: PyRef<Self>, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
757+
let spec = spec.as_str();
758+
if spec.is_empty() {
759+
return if zelf.class().is(vm.ctx.types.str_type) {
760+
Ok(zelf)
761+
} else {
762+
zelf.as_object().str(vm)
763+
};
764+
}
765+
766+
let s = FormatSpec::parse(spec)
767+
.and_then(|format_spec| format_spec.format_string(zelf.borrow()))
768+
.map_err(|err| err.into_pyexception(vm))?;
769+
Ok(vm.ctx.new_str(s))
760770
}
761771

762772
/// Return a titlecased version of the string where words start with an

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, vm.ctx.new_str(format_spec))?;
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, spec.downcast::<PyStr>().unwrap())?;
17751769
self.push_value(formatted.into());
17761770
Ok(None)
17771771
}

vm/src/protocol/object.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use crate::{
55
builtins::{
6-
pystr::IntoPyStrRef, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyStrRef,
6+
pystr::IntoPyStrRef, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyStr, PyStrRef,
77
PyTupleRef, PyTypeRef,
88
},
99
bytesinner::ByteInnerNewOptions,
@@ -330,12 +330,22 @@ impl PyObject {
330330

331331
// Container of the virtual machine state:
332332
pub fn str(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
333-
if self.class().is(vm.ctx.types.str_type) {
334-
Ok(self.to_owned().downcast().unwrap())
335-
} else {
336-
let s = vm.call_special_method(self.to_owned(), identifier!(vm, __str__), ())?;
337-
s.try_into_value(vm)
338-
}
333+
let obj = match self.to_owned().downcast_exact::<PyStr>(vm) {
334+
Ok(s) => return Ok(s.into_pyref()),
335+
Err(obj) => obj,
336+
};
337+
// TODO: replace to obj.class().slots.str
338+
let str_method = match vm.get_special_method(obj, identifier!(vm, __str__))? {
339+
Ok(str_method) => str_method,
340+
Err(obj) => return obj.repr(vm),
341+
};
342+
let s = str_method.invoke((), vm)?;
343+
s.downcast::<PyStr>().map_err(|obj| {
344+
vm.new_type_error(format!(
345+
"__str__ returned non-string (type {})",
346+
obj.class().name()
347+
))
348+
})
339349
}
340350

341351
// Equivalent to check_class. Masks Attribute errors (into TypeErrors) and lets everything

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.unwrap_or(vm.ctx.new_str("")))
325320
}
326321

327322
#[pyfunction]

vm/src/vm/vm_ops.rs

Lines changed: 29 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,34 @@ impl VirtualMachine {
494494
.invoke((), self)
495495
}
496496

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

0 commit comments

Comments
 (0)