Skip to content

Commit cd1d7b1

Browse files
committed
add args attribute to exceptions, make __str__ and __repr__ compatible with CPython
1 parent 3085ead commit cd1d7b1

3 files changed

Lines changed: 91 additions & 23 deletions

File tree

tests/snippets/exceptions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
empty_exc = KeyError()
2+
assert str(empty_exc) == ''
3+
assert repr(empty_exc) == 'KeyError()'
4+
assert len(empty_exc.args) == 0
5+
assert type(empty_exc.args) == tuple
6+
7+
exc = KeyError('message')
8+
assert str(exc) == "'message'"
9+
assert repr(exc) == "KeyError('message')"
10+
11+
exc = KeyError('message', 'another message')
12+
assert str(exc) == "('message', 'another message')"
13+
assert repr(exc) == "KeyError('message', 'another message')"
14+
assert exc.args[0] == 'message'
15+
assert exc.args[1] == 'another message'
16+
17+
class A:
18+
def __repr__(self):
19+
return 'repr'
20+
def __str__(self):
21+
return 'str'
22+
23+
exc = KeyError(A())
24+
assert str(exc) == 'repr'
25+
assert repr(exc) == 'KeyError(repr)'

vm/src/exceptions.rs

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::function::PyFuncArgs;
22
use crate::obj::objsequence;
3+
use crate::obj::objtuple::{PyTuple, PyTupleRef};
34
use crate::obj::objtype;
45
use crate::obj::objtype::PyClassRef;
56
use crate::pyobject::{create_type, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol};
@@ -8,16 +9,12 @@ use std::fs::File;
89
use std::io::{BufRead, BufReader};
910

1011
fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
11-
let zelf = args.args[0].clone();
12-
let msg = if args.args.len() > 1 {
13-
args.args[1].clone()
14-
} else {
15-
let empty_string = String::default();
16-
vm.new_str(empty_string)
17-
};
12+
let exc_self = args.args[0].clone();
13+
let exc_args = vm.ctx.new_tuple(args.args[1..].to_vec());
14+
vm.set_attr(&exc_self, "args", exc_args)?;
15+
1816
let traceback = vm.ctx.new_list(Vec::new());
19-
vm.set_attr(&zelf, "msg", msg)?;
20-
vm.set_attr(&zelf, "__traceback__", traceback)?;
17+
vm.set_attr(&exc_self, "__traceback__", traceback)?;
2118
Ok(vm.get_none())
2219
}
2320

@@ -119,25 +116,70 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) {
119116
}
120117
}
121118

119+
fn exception_args_as_string(vm: &VirtualMachine, varargs: PyTupleRef) -> Vec<String> {
120+
match varargs.elements.len() {
121+
0 => vec![],
122+
1 => {
123+
let args0_repr = match vm.to_repr(&varargs.elements[0]) {
124+
Ok(args0_repr) => args0_repr.value.clone(),
125+
Err(_) => "<element repr() failed>".to_string(),
126+
};
127+
vec![args0_repr]
128+
}
129+
_ => {
130+
let mut args_vec = Vec::with_capacity(varargs.elements.len());
131+
for vararg in &varargs.elements {
132+
let arg_repr = match vm.to_repr(vararg) {
133+
Ok(arg_repr) => arg_repr.value.clone(),
134+
Err(_) => "<element repr() failed>".to_string(),
135+
};
136+
args_vec.push(arg_repr);
137+
}
138+
args_vec
139+
}
140+
}
141+
}
142+
122143
fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
123144
arg_check!(
124145
vm,
125146
args,
126147
required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))]
127148
);
128-
let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") {
129-
match vm.to_pystr(&m) {
130-
Ok(msg) => msg,
131-
_ => "<exception str() failed>".to_string(),
132-
}
133-
} else {
134-
panic!("Error message must be set");
149+
let args = vm
150+
.get_attribute(exc.clone(), "args")
151+
.unwrap()
152+
.downcast::<PyTuple>()
153+
.expect("'args' must be a tuple");
154+
let args_str = exception_args_as_string(vm, args);
155+
let joined_str = match args_str.len() {
156+
0 => "".to_string(),
157+
1 => args_str[0].to_string(),
158+
_ => format!("({})", args_str.join(", ")),
135159
};
136-
let mut exc_repr = exc.class().name.clone();
137-
if !msg.is_empty() {
138-
&exc_repr.push_str(&format!(": {}", msg));
139-
}
140-
Ok(vm.new_str(exc_repr))
160+
Ok(vm.new_str(joined_str))
161+
}
162+
163+
fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
164+
arg_check!(
165+
vm,
166+
args,
167+
required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))]
168+
);
169+
let args = vm
170+
.get_attribute(exc.clone(), "args")
171+
.unwrap()
172+
.downcast::<PyTuple>()
173+
.expect("'args' must be a tuple");
174+
let args_repr = exception_args_as_string(vm, args);
175+
176+
let exc_repr = exc.class().name.clone();
177+
let joined_str = match args_repr.len() {
178+
0 => format!("{}()", exc_repr),
179+
1 => format!("{}({})", exc_repr, args_repr[0].to_string()),
180+
_ => format!("{}({})", exc_repr, args_repr.join(", ")),
181+
};
182+
Ok(vm.new_str(joined_str))
141183
}
142184

143185
#[derive(Debug)]
@@ -266,6 +308,7 @@ pub fn init(context: &PyContext) {
266308

267309
let exception_type = &context.exceptions.exception_type;
268310
extend_class!(context, exception_type, {
269-
"__str__" => context.new_rustfunc(exception_str)
311+
"__str__" => context.new_rustfunc(exception_str),
312+
"__repr__" => context.new_rustfunc(exception_repr),
270313
});
271314
}

vm/src/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ impl VirtualMachine {
173173
self.invoke(exc_type.into_object(), args)
174174
}
175175

176-
/// Create Python instance of `exc_type` with message
176+
/// Create Python instance of `exc_type` with message as first element of `args` tuple
177177
pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef {
178178
// TODO: exc_type may be user-defined exception, so we should return PyResult
179179
// TODO: maybe there is a clearer way to create an instance:

0 commit comments

Comments
 (0)