use rustpython_parser::ast;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{eval_body, eval_expr},
state::InterpreterState,
tools::Tools,
value::{ExceptionValue, Value},
};
pub async fn eval_try_star(
state: &mut InterpreterState,
node: &ast::StmtTryStar,
tools: &Tools,
) -> EvalResult {
let as_try = ast::StmtTry {
range: node.range,
body: node.body.clone(),
handlers: node.handlers.clone(),
orelse: node.orelse.clone(),
finalbody: node.finalbody.clone(),
};
eval_try_star_inner(state, &as_try, tools).await
}
async fn eval_try_star_inner(
state: &mut InterpreterState,
node: &ast::StmtTry,
tools: &Tools,
) -> EvalResult {
let mut result = Value::None;
let mut pending_error: Option<EvalError> = None;
match eval_body(state, &node.body, tools).await {
Ok(val) => {
result = val;
match eval_body(state, &node.orelse, tools).await {
Ok(val) => result = val,
Err(e) => pending_error = Some(e),
}
}
Err(EvalError::Signal(sig)) => {
pending_error = Some(EvalError::Signal(sig));
}
Err(EvalError::Exception(exc)) => {
match try_match_star_handlers(state, &exc, &node.handlers, tools).await? {
Some((value, new_error)) => {
result = value;
pending_error = new_error;
}
None => pending_error = Some(EvalError::Exception(exc)),
}
}
Err(EvalError::Interpreter(ie)) => {
let exc = interpreter_error_to_exception(&ie);
match try_match_star_handlers(state, &exc, &node.handlers, tools).await? {
Some((value, new_error)) => {
result = value;
pending_error = new_error;
}
None => pending_error = Some(EvalError::Interpreter(ie)),
}
}
}
if !node.finalbody.is_empty() {
if let Err(finally_err) = eval_body(state, &node.finalbody, tools).await {
pending_error = Some(finally_err);
}
}
if let Some(err) = pending_error {
return Err(err);
}
Ok(result)
}
async fn try_match_star_handlers(
state: &mut InterpreterState,
exc: &ExceptionValue,
handlers: &[ast::ExceptHandler],
tools: &Tools,
) -> Result<Option<(Value, Option<EvalError>)>, EvalError> {
let mut remaining: Vec<ExceptionValue> = if let Some(nested) = &exc.exceptions {
nested.clone()
} else {
vec![exc.clone()]
};
let mut any_matched = false;
let mut last_value = Value::None;
let mut handler_error: Option<EvalError> = None;
for handler in handlers {
let ast::ExceptHandler::ExceptHandler(h) = handler;
if remaining.is_empty() {
break;
}
let mut matched = Vec::new();
let mut unmatched = Vec::new();
for leaf in remaining.drain(..) {
if matches_handler(state, &leaf, h, tools).await? {
matched.push(leaf);
} else {
unmatched.push(leaf);
}
}
remaining = unmatched;
if matched.is_empty() {
continue;
}
any_matched = true;
let group = ExceptionValue::group("ExceptionGroup", exc.message.clone(), matched);
if let Some(ref name) = h.name {
state
.set_variable(name.as_str(), Value::Exception(group.clone()))
.map_err(EvalError::Interpreter)?;
}
state.active_exception_stack.push(group);
let body_result = eval_body(state, &h.body, tools).await;
state.active_exception_stack.pop();
match body_result {
Ok(val) => last_value = val,
Err(err) => {
handler_error = Some(err);
break;
}
}
if let Some(ref name) = h.name {
let _ = state.delete_variable(name.as_str());
}
}
if let Some(err) = handler_error {
return Ok(Some((last_value, Some(err))));
}
if !remaining.is_empty() {
let re_raise = if remaining.len() == 1 && remaining[0].exceptions.is_none() {
remaining.remove(0)
} else {
ExceptionValue::group("ExceptionGroup", exc.message.clone(), remaining)
};
return Ok(Some((last_value, Some(EvalError::Exception(re_raise)))));
}
if any_matched { Ok(Some((last_value, None))) } else { Ok(None) }
}
pub async fn eval_try(
state: &mut InterpreterState,
node: &ast::StmtTry,
tools: &Tools,
) -> EvalResult {
let mut result = Value::None;
let mut pending_error: Option<EvalError> = None;
match eval_body(state, &node.body, tools).await {
Ok(val) => {
result = val;
match eval_body(state, &node.orelse, tools).await {
Ok(val) => result = val,
Err(e) => pending_error = Some(e),
}
}
Err(EvalError::Signal(sig)) => {
pending_error = Some(EvalError::Signal(sig));
}
Err(EvalError::Exception(exc)) => {
if let Some((value, new_error)) =
try_match_handlers(state, &exc, &node.handlers, tools).await?
{
result = value;
pending_error = new_error;
} else {
pending_error = Some(EvalError::Exception(exc));
}
}
Err(EvalError::Interpreter(ie)) => {
let exc = interpreter_error_to_exception(&ie);
if let Some((value, new_error)) =
try_match_handlers(state, &exc, &node.handlers, tools).await?
{
result = value;
pending_error = new_error;
} else {
pending_error = Some(EvalError::Interpreter(ie));
}
}
}
if !node.finalbody.is_empty() {
match eval_body(state, &node.finalbody, tools).await {
Ok(_) => {
}
Err(finally_err) => {
pending_error = Some(finally_err);
}
}
}
if let Some(err) = pending_error {
return Err(err);
}
Ok(result)
}
async fn try_match_handlers(
state: &mut InterpreterState,
exc: &ExceptionValue,
handlers: &[ast::ExceptHandler],
tools: &Tools,
) -> Result<Option<(Value, Option<EvalError>)>, EvalError> {
for handler in handlers {
let ast::ExceptHandler::ExceptHandler(h) = handler;
if !matches_handler(state, exc, h, tools).await? {
continue;
}
if let Some(ref name) = h.name {
state
.set_variable(name.as_str(), Value::Exception(exc.clone()))
.map_err(EvalError::Interpreter)?;
}
state.active_exception_stack.push(exc.clone());
let body_result = eval_body(state, &h.body, tools).await;
state.active_exception_stack.pop();
let (value, new_error) = match body_result {
Ok(val) => (val, None),
Err(err) => (Value::None, Some(err)),
};
if let Some(ref name) = h.name {
let _ = state.delete_variable(name.as_str());
}
return Ok(Some((value, new_error)));
}
Ok(None)
}
async fn matches_handler(
state: &mut InterpreterState,
exc: &ExceptionValue,
handler: &ast::ExceptHandlerExceptHandler,
tools: &Tools,
) -> Result<bool, EvalError> {
let Some(type_expr) = &handler.type_ else {
return Ok(true);
};
let type_val = eval_expr(state, type_expr, tools).await?;
if let Value::Tuple(types) = &type_val {
for type_item in types {
if matches_exception_type(state, exc, type_item) {
return Ok(true);
}
}
return Ok(false);
}
Ok(matches_exception_type(state, exc, &type_val))
}
fn matches_exception_type(
state: &InterpreterState,
exc: &ExceptionValue,
type_val: &Value,
) -> bool {
let type_name = match type_val {
Value::ExceptionType(n) | Value::Class(n) => n.as_str(),
Value::String(s) => s.as_str(),
_ => return false,
};
if type_name == "Exception" || type_name == "BaseException" {
return true;
}
if exc.type_name == type_name {
return true;
}
if builtin_exception_issubclass(&exc.type_name, type_name) {
return true;
}
matches_user_exception(state, exc, type_name)
}
fn builtin_exception_issubclass(exc_name: &str, parent: &str) -> bool {
let mut cur = exc_name;
for _ in 0..16 {
if cur == parent {
return true;
}
cur = match cur {
"ZeroDivisionError" | "OverflowError" | "FloatingPointError" => "ArithmeticError",
"KeyError" | "IndexError" => "LookupError",
"FileNotFoundError" | "PermissionError" | "TimeoutError" | "IOError" => "OSError",
"UnicodeDecodeError" | "UnicodeEncodeError" | "UnicodeTranslateError" => "UnicodeError",
"UnicodeError" => "ValueError",
"NotImplementedError" | "RecursionError" => "RuntimeError",
"AssertionError" | "AttributeError" | "NameError" | "TypeError" | "ValueError"
| "RuntimeError" | "OSError" | "LookupError" | "ArithmeticError" | "StopIteration"
| "ExceptionGroup" => "Exception",
"BaseExceptionGroup" => "BaseException",
"Exception" => "BaseException",
_ => return false,
};
}
false
}
pub(crate) fn matches_user_exception(
state: &InterpreterState,
exc: &ExceptionValue,
handler_name: &str,
) -> bool {
let Some(raised_class) = state.classes.get(&exc.type_name) else {
return false;
};
for base in &raised_class.mro {
if base == handler_name {
return true;
}
if builtin_exception_issubclass(base, handler_name) {
return true;
}
}
false
}
pub(crate) fn interpreter_error_to_exception_pub(err: &InterpreterError) -> ExceptionValue {
interpreter_error_to_exception(err)
}
fn interpreter_error_to_exception(err: &InterpreterError) -> ExceptionValue {
match err {
InterpreterError::TypeError(msg) => {
ExceptionValue::new("TypeError", strip_line_marker(msg))
}
InterpreterError::ValueError(msg) => {
ExceptionValue::new("ValueError", strip_line_marker(msg))
}
InterpreterError::NameError(msg) => {
ExceptionValue::new("NameError", strip_line_marker(msg))
}
InterpreterError::AttributeError(msg) => {
ExceptionValue::new("AttributeError", strip_line_marker(msg))
}
InterpreterError::AssertionError(msg) => {
ExceptionValue::new("AssertionError", strip_line_marker(msg))
}
InterpreterError::PythonException { type_name, message, .. } => {
ExceptionValue::new(type_name.clone(), strip_line_marker(message))
}
InterpreterError::Runtime(msg) => {
ExceptionValue::new("RuntimeError", strip_line_marker(msg))
}
InterpreterError::Tool { tool_name, message } => ExceptionValue::new(
"Exception",
format!("ToolError in '{tool_name}': {}", strip_line_marker(message)),
),
_ => ExceptionValue::new("Exception", strip_line_marker(&format!("{err}"))),
}
}
fn strip_line_marker(msg: &str) -> String {
if let Some(idx) = msg.rfind(" (at line ") {
if msg[idx..].ends_with(')') {
return msg[..idx].to_string();
}
}
msg.to_string()
}
pub async fn eval_raise(
state: &mut InterpreterState,
node: &ast::StmtRaise,
tools: &Tools,
) -> EvalResult {
let Some(exc_expr) = &node.exc else {
return state.active_exception_stack.last().cloned().map_or_else(
|| Err(InterpreterError::Runtime("No active exception to re-raise".into()).into()),
|exc| Err(EvalError::Exception(exc)),
);
};
let exc_val = eval_expr(state, exc_expr, tools).await?;
let cause = if let Some(ref cause_expr) = node.cause {
let cause_val = eval_expr(state, cause_expr, tools).await?;
match cause_val {
Value::Exception(e) => Some(Box::new(e)),
_ => None,
}
} else {
None
};
let implicit_context = if cause.is_none() {
state.active_exception_stack.last().cloned().map(Box::new)
} else {
None
};
let attached_cause = cause.or(implicit_context);
match exc_val {
Value::Exception(mut exc) => {
exc.cause = attached_cause;
Err(EvalError::Exception(exc))
}
Value::ExceptionType(type_name) => {
let mut exc = ExceptionValue::new(type_name, String::new());
if let Some(c) = attached_cause {
exc = exc.with_cause(*c);
}
Err(EvalError::Exception(exc))
}
_ => {
let type_name = format!("{exc_val}");
if is_exception_type_name(&type_name) {
let mut exc = ExceptionValue::new(type_name, String::new());
if let Some(c) = attached_cause {
exc = exc.with_cause(*c);
}
Err(EvalError::Exception(exc))
} else {
Err(InterpreterError::TypeError(format!(
"exceptions must derive from BaseException, not '{}'",
exc_val.type_name()
))
.into())
}
}
}
}
fn is_exception_type_name(name: &str) -> bool {
crate::eval::functions::is_exception_type_name(name)
}
pub async fn eval_assert(
state: &mut InterpreterState,
node: &ast::StmtAssert,
tools: &Tools,
) -> EvalResult {
let test = eval_expr(state, &node.test, tools).await?;
if !crate::eval::op::truthy(state, &test, tools).await? {
let message = if let Some(ref msg_expr) = node.msg {
let msg = eval_expr(state, msg_expr, tools).await?;
format!("{msg}")
} else {
String::new()
};
return Err(EvalError::Exception(ExceptionValue::new("AssertionError", message)));
}
Ok(Value::None)
}
pub(crate) fn construct_exception_type(
type_name: &str,
args: &[crate::value::Value],
) -> crate::error::EvalResult {
use crate::error::InterpreterError;
use crate::value::{ExceptionValue, Value};
if type_name == "ExceptionGroup" || type_name == "BaseExceptionGroup" {
if args.len() != 2 {
return Err(InterpreterError::TypeError(format!(
"{type_name}() takes exactly 2 arguments ({})",
args.len()
))
.into());
}
let message = format!("{}", args[0]);
let nested = match &args[1] {
Value::List(items) => items
.lock()
.iter()
.map(|v| match v {
Value::Exception(e) => Ok(e.clone()),
other => Err(InterpreterError::TypeError(format!(
"Item in {type_name} must be an exception, not '{}'",
other.type_name()
))),
})
.collect::<Result<Vec<_>, _>>()?,
Value::Tuple(items) => items
.iter()
.map(|v| match v {
Value::Exception(e) => Ok(e.clone()),
other => Err(InterpreterError::TypeError(format!(
"Item in {type_name} must be an exception, not '{}'",
other.type_name()
))),
})
.collect::<Result<Vec<_>, _>>()?,
other => {
return Err(InterpreterError::TypeError(format!(
"second argument (exceptions) must be a sequence (got '{}')",
other.type_name()
))
.into());
}
};
if nested.is_empty() {
return Err(InterpreterError::ValueError(
"second argument (exceptions) must be a non-empty sequence".into(),
)
.into());
}
return Ok(Value::Exception(ExceptionValue::group(type_name.to_string(), message, nested)));
}
let message = match args.len() {
0 => String::new(),
1 => format!("{}", args[0]),
_ => args.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(", "),
};
Ok(Value::Exception(ExceptionValue::new(type_name, message).with_args(args.to_vec())))
}
pub(crate) fn call_exception_method(
method: &str,
exception: &crate::value::ExceptionValue,
args: &[crate::value::Value],
) -> crate::error::EvalResult {
use crate::error::InterpreterError;
use crate::value::{ExceptionValue, Value};
let matcher = args.first().ok_or_else(|| {
InterpreterError::TypeError(format!("{method}() takes exactly 1 argument (0 given)"))
})?;
let leaves = exception.exceptions.clone().unwrap_or_else(|| vec![exception.clone()]);
let matches_type = |leaf: &ExceptionValue| -> bool {
let name_matches =
|n: &str| leaf.type_name == n || builtin_exception_issubclass(&leaf.type_name, n);
match matcher {
Value::ExceptionType(n) | Value::Class(n) => name_matches(n),
Value::String(n) => name_matches(n.as_str()),
Value::Tuple(items) => items.iter().any(|item| match item {
Value::ExceptionType(n) | Value::Class(n) => name_matches(n),
Value::String(n) => name_matches(n.as_str()),
_ => false,
}),
_ => false,
}
};
fn flatten(exc: &ExceptionValue, out: &mut Vec<ExceptionValue>) {
if let Some(nested) = &exc.exceptions {
for child in nested {
flatten(child, out);
}
} else {
out.push(exc.clone());
}
}
let mut flat = Vec::new();
for leaf in &leaves {
flatten(leaf, &mut flat);
}
let mut matched = Vec::new();
let mut rest = Vec::new();
for leaf in flat {
if matches_type(&leaf) {
matched.push(leaf);
} else {
rest.push(leaf);
}
}
match method {
"subgroup" => {
if matched.is_empty() {
Ok(Value::None)
} else {
Ok(Value::Exception(ExceptionValue::group(
exception.type_name.clone(),
exception.message.clone(),
matched,
)))
}
}
"split" => {
let m = if matched.is_empty() {
Value::None
} else {
Value::Exception(ExceptionValue::group(
exception.type_name.clone(),
exception.message.clone(),
matched,
))
};
let r = if rest.is_empty() {
Value::None
} else {
Value::Exception(ExceptionValue::group(
exception.type_name.clone(),
exception.message.clone(),
rest,
))
};
Ok(Value::Tuple(vec![m, r]))
}
_ => Err(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{method}'",
exception.type_name
))
.into()),
}
}