From 97e8b0a2517d8e3e1ef097b64de02295a445e6ad Mon Sep 17 00:00:00 2001 From: leftibot Date: Tue, 14 Apr 2026 15:42:29 -0600 Subject: [PATCH] Address review: add rethrow_typed and boxed_value accessors to eval_error eval_error now stores the original Boxed_Value from script-thrown exceptions, accessible via has_boxed_value() and boxed_value(). The new rethrow_typed(engine) method provides auto-unboxing without requiring exception_specification to be passed to eval(). This delivers the API simplification envisioned in issue #63: users can catch eval_error, inspect the call stack, and rethrow as typed exceptions in a single catch block. Requested by @lefticus in PR #682 review. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chaiscript/language/chaiscript_common.hpp | 26 ++++++++ .../chaiscript/language/chaiscript_engine.hpp | 2 +- unittests/compiled_tests.cpp | 64 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 6c99a5d7..387b12d3 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -340,8 +340,24 @@ namespace chaiscript { , reason(t_why) { } + eval_error(const std::string &t_why, Boxed_Value t_bv) noexcept + : std::runtime_error("Error: \"" + t_why + "\" ") + , reason(t_why) + , m_boxed_value(std::move(t_bv)) { + } + eval_error(const eval_error &) = default; + bool has_boxed_value() const noexcept { return !m_boxed_value.is_undef(); } + const Boxed_Value &boxed_value() const noexcept { return m_boxed_value; } + + template + void rethrow_typed(const Engine &t_engine) const { + if (has_boxed_value()) { + (try_rethrow(t_engine), ...); + } + } + std::string pretty_print() const { std::ostringstream ss; @@ -365,6 +381,16 @@ namespace chaiscript { ~eval_error() noexcept override = default; private: + Boxed_Value m_boxed_value; + + template + void try_rethrow(const Engine &t_engine) const { + try { + throw t_engine.template boxed_cast(m_boxed_value); + } catch (const chaiscript::exception::bad_boxed_cast &) { + } + } + template static AST_Node_Type id(const T &t) noexcept { return t.identifier; diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 0e2c38b2..6d82f206 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -695,7 +695,7 @@ namespace chaiscript { if (t_handler) { t_handler->handle(bv, m_engine); } - throw exception::eval_error("Exception thrown during evaluation"); + throw exception::eval_error("Exception thrown during evaluation", bv); } } diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 037f6b44..2feca391 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -256,6 +256,70 @@ TEST_CASE("eval_error includes nested exception for script-thrown exceptions") { } } +TEST_CASE("eval_error stores boxed_value for script-thrown exceptions") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(42);"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.has_boxed_value()); + CHECK(chai.boxed_cast(ee.boxed_value()) == 42); + } +} + +TEST_CASE("eval_error rethrow_typed auto-unboxes runtime_error") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(runtime_error(\"typed error\"));"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.has_boxed_value()); + try { + ee.rethrow_typed(chai); + REQUIRE(false); + } catch (const std::runtime_error &e) { + CHECK(e.what() == std::string("typed error")); + } + } +} + +TEST_CASE("eval_error rethrow_typed auto-unboxes int") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(42);"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.has_boxed_value()); + try { + ee.rethrow_typed(chai); + REQUIRE(false); + } catch (const int e) { + CHECK(e == 42); + } + } +} + +TEST_CASE("eval_error rethrow_typed with no match does not throw") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(\"a string\");"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.has_boxed_value()); + ee.rethrow_typed(chai); + CHECK(true); + } +} + +TEST_CASE("eval_error without boxed_value has_boxed_value returns false") { + const chaiscript::exception::eval_error ee("plain error"); + CHECK_FALSE(ee.has_boxed_value()); +} + TEST_CASE("Deduction of pointer return types") { int val = 5; int *val_ptr = &val;