Skip to content

Commit ab9573f

Browse files
committed
object.__format__ the right way
1 parent 195673b commit ab9573f

File tree

9 files changed

+44
-28
lines changed

9 files changed

+44
-28
lines changed

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: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,13 @@ impl PyBaseObject {
243243

244244
#[pymethod(magic)]
245245
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!(
246+
if !format_spec.is_empty() {
247+
return Err(vm.new_type_error(format!(
250248
"unsupported format string passed to {}.__format__",
251249
obj.class().name()
252-
)))
250+
)));
253251
}
252+
obj.str(vm)
254253
}
255254

256255
#[pyslot]

vm/src/builtins/str.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -745,10 +745,20 @@ impl PyStr {
745745
}
746746

747747
#[pymethod(name = "__format__")]
748-
fn format_str(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
749-
FormatSpec::parse(spec.as_str())
750-
.and_then(|format_spec| format_spec.format_string(self.borrow()))
751-
.map_err(|err| err.into_pyexception(vm))
748+
fn __format__(zelf: PyRef<Self>, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
749+
let spec = spec.as_str();
750+
if spec.is_empty() {
751+
return if zelf.class().is(vm.ctx.types.str_type) {
752+
Ok(zelf)
753+
} else {
754+
zelf.as_object().str(vm)
755+
};
756+
}
757+
758+
let s = FormatSpec::parse(spec)
759+
.and_then(|format_spec| format_spec.format_string(zelf.borrow()))
760+
.map_err(|err| err.into_pyexception(vm))?;
761+
Ok(vm.ctx.new_str(s))
752762
}
753763

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

vm/src/format.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ fn format_internal(
107107
};
108108

109109
// 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()))?;
110+
pystr = vm.format(&argument, vm.ctx.new_str(format_spec))?;
111111
pystr.as_ref()
112112
}
113113
FormatPart::Literal(literal) => literal,

vm/src/frame.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1765,7 +1765,7 @@ impl ExecutingFrame<'_> {
17651765
};
17661766

17671767
let spec = self.pop_value();
1768-
let formatted = vm.format(&value, Some(spec.downcast::<PyStr>().unwrap()))?;
1768+
let formatted = vm.format(&value, spec.downcast::<PyStr>().unwrap())?;
17691769
self.push_value(formatted.into());
17701770
Ok(None)
17711771
}

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ mod builtins {
316316
format_spec: OptionalArg<PyStrRef>,
317317
vm: &VirtualMachine,
318318
) -> PyResult<PyStrRef> {
319-
vm.format(&value, format_spec.into())
319+
vm.format(&value, format_spec.unwrap_or(vm.ctx.new_str("")))
320320
}
321321

322322
#[pyfunction]

vm/src/vm/vm_ops.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,8 @@ impl VirtualMachine {
495495
}
496496

497497
// 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 {
498+
pub fn format(&self, obj: &PyObject, format_spec: PyStrRef) -> PyResult<PyStrRef> {
499+
if format_spec.is_empty() {
503500
let obj = match obj.to_owned().downcast_exact::<PyStr>(self) {
504501
Ok(s) => return Ok(s.into_pyref()),
505502
Err(obj) => obj,

0 commit comments

Comments
 (0)