Skip to content

Commit 6d5f381

Browse files
authored
Merge pull request RustPython#1056 from mkurnikov/args-in-exceptions
Add args attribute to exceptions, make __str__ and __repr__ compatible with CPython
2 parents 3085ead + 8852abc commit 6d5f381

File tree

5 files changed

+166
-64
lines changed

5 files changed

+166
-64
lines changed

tests/Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ pytest = "*"
1010
[dev-packages]
1111

1212
[requires]
13-
python_version = "3"
13+
python_version = "3.6"

tests/Pipfile.lock

Lines changed: 62 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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: 77 additions & 25 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

@@ -113,9 +110,42 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) {
113110
println!("No traceback set on exception");
114111
}
115112

116-
match vm.to_str(exc) {
117-
Ok(txt) => println!("{}", txt.value),
118-
Err(err) => println!("Error during error {:?}", err),
113+
let varargs = vm
114+
.get_attribute(exc.clone(), "args")
115+
.unwrap()
116+
.downcast::<PyTuple>()
117+
.expect("'args' must be a tuple");
118+
let args_repr = exception_args_as_string(vm, varargs);
119+
120+
let exc_name = exc.class().name.clone();
121+
match args_repr.len() {
122+
0 => println!("{}", exc_name),
123+
1 => println!("{}: {}", exc_name, args_repr[0]),
124+
_ => println!("{}: ({})", exc_name, args_repr.join(", ")),
125+
}
126+
}
127+
128+
fn exception_args_as_string(vm: &VirtualMachine, varargs: PyTupleRef) -> Vec<String> {
129+
match varargs.elements.len() {
130+
0 => vec![],
131+
1 => {
132+
let args0_repr = match vm.to_repr(&varargs.elements[0]) {
133+
Ok(args0_repr) => args0_repr.value.clone(),
134+
Err(_) => "<element repr() failed>".to_string(),
135+
};
136+
vec![args0_repr]
137+
}
138+
_ => {
139+
let mut args_vec = Vec::with_capacity(varargs.elements.len());
140+
for vararg in &varargs.elements {
141+
let arg_repr = match vm.to_repr(vararg) {
142+
Ok(arg_repr) => arg_repr.value.clone(),
143+
Err(_) => "<element repr() failed>".to_string(),
144+
};
145+
args_vec.push(arg_repr);
146+
}
147+
args_vec
148+
}
119149
}
120150
}
121151

@@ -125,19 +155,40 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
125155
args,
126156
required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))]
127157
);
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");
158+
let args = vm
159+
.get_attribute(exc.clone(), "args")
160+
.unwrap()
161+
.downcast::<PyTuple>()
162+
.expect("'args' must be a tuple");
163+
let args_str = exception_args_as_string(vm, args);
164+
let joined_str = match args_str.len() {
165+
0 => "".to_string(),
166+
1 => args_str[0].to_string(),
167+
_ => format!("({})", args_str.join(", ")),
135168
};
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))
169+
Ok(vm.new_str(joined_str))
170+
}
171+
172+
fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
173+
arg_check!(
174+
vm,
175+
args,
176+
required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))]
177+
);
178+
let args = vm
179+
.get_attribute(exc.clone(), "args")
180+
.unwrap()
181+
.downcast::<PyTuple>()
182+
.expect("'args' must be a tuple");
183+
let args_repr = exception_args_as_string(vm, args);
184+
185+
let exc_name = exc.class().name.clone();
186+
let joined_str = match args_repr.len() {
187+
0 => format!("{}()", exc_name),
188+
1 => format!("{}({},)", exc_name, args_repr[0]),
189+
_ => format!("{}({})", exc_name, args_repr.join(", ")),
190+
};
191+
Ok(vm.new_str(joined_str))
141192
}
142193

143194
#[derive(Debug)]
@@ -266,6 +317,7 @@ pub fn init(context: &PyContext) {
266317

267318
let exception_type = &context.exceptions.exception_type;
268319
extend_class!(context, exception_type, {
269-
"__str__" => context.new_rustfunc(exception_str)
320+
"__str__" => context.new_rustfunc(exception_str),
321+
"__repr__" => context.new_rustfunc(exception_repr),
270322
});
271323
}

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)