From e62fb36b1f80ccedbc071d429b5a466d45dafe9d Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 5 Dec 2025 21:05:03 +0900 Subject: [PATCH] refactor: Extract exception handling from control_flow.rs (Phase 5) - Created exception/ directory with try_catch.rs, throw.rs, mod.rs - Extracted ~180 lines of exception handling logic - control_flow/mod.rs now delegates to exception module - All builds pass, no behavior changes --- src/mir/builder/control_flow/exception/mod.rs | 36 ++++ .../builder/control_flow/exception/throw.rs | 41 +++++ .../control_flow/exception/try_catch.rs | 146 ++++++++++++++++ src/mir/builder/control_flow/mod.rs | 160 ++---------------- 4 files changed, 236 insertions(+), 147 deletions(-) create mode 100644 src/mir/builder/control_flow/exception/mod.rs create mode 100644 src/mir/builder/control_flow/exception/throw.rs create mode 100644 src/mir/builder/control_flow/exception/try_catch.rs diff --git a/src/mir/builder/control_flow/exception/mod.rs b/src/mir/builder/control_flow/exception/mod.rs new file mode 100644 index 00000000..52bfe29c --- /dev/null +++ b/src/mir/builder/control_flow/exception/mod.rs @@ -0,0 +1,36 @@ +//! Exception handling implementation. +//! +//! This module provides exception handling control flow primitives: +//! - Try/catch/finally blocks +//! - Throw statements +//! +//! # Architecture +//! +//! Exception handling integrates with the MIR builder's cleanup and +//! deferred return mechanisms to ensure proper resource management +//! and control flow even in the presence of exceptions. +//! +//! ## Try/Catch/Finally +//! +//! The try/catch/finally implementation creates multiple basic blocks: +//! - Try block: Normal execution path +//! - Catch block: Exception handler +//! - Finally block: Cleanup code (optional) +//! - Exit block: Continuation after exception handling +//! +//! ## Cleanup Blocks +//! +//! Cleanup blocks (finally blocks) have special restrictions: +//! - Return statements require `NYASH_CLEANUP_ALLOW_RETURN=1` +//! - Throw statements require `NYASH_CLEANUP_ALLOW_THROW=1` +//! +//! # Modules +//! +//! - `try_catch`: Try/catch/finally block implementation +//! - `throw`: Throw statement implementation + +mod throw; +mod try_catch; + +pub(in crate::mir::builder) use throw::cf_throw; +pub(in crate::mir::builder) use try_catch::cf_try_catch; diff --git a/src/mir/builder/control_flow/exception/throw.rs b/src/mir/builder/control_flow/exception/throw.rs new file mode 100644 index 00000000..14277d6a --- /dev/null +++ b/src/mir/builder/control_flow/exception/throw.rs @@ -0,0 +1,41 @@ +//! Throw statement implementation. +//! +//! This module implements the throw statement for exception raising, +//! with proper cleanup block validation. + +use crate::ast::ASTNode; +use crate::mir::builder::{Effect, EffectMask, MirInstruction, ValueId}; + +/// Control-flow: throw +/// +/// Raises an exception with the given expression value. +/// +/// # Cleanup Block Validation +/// +/// Throwing inside cleanup blocks is controlled by the +/// `NYASH_CLEANUP_ALLOW_THROW=1` environment variable. +pub(in crate::mir::builder) fn cf_throw( + builder: &mut super::super::super::MirBuilder, + expression: ASTNode, +) -> Result { + if builder.in_cleanup_block && !builder.cleanup_allow_throw { + return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string()); + } + if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") { + let v = builder.build_expression(expression)?; + builder.emit_instruction(MirInstruction::ExternCall { + dst: None, + iface_name: "env.debug".to_string(), + method_name: "trace".to_string(), + args: vec![v], + effects: EffectMask::PURE.add(Effect::Debug), + })?; + return Ok(v); + } + let exception_value = builder.build_expression(expression)?; + builder.emit_instruction(MirInstruction::Throw { + exception: exception_value, + effects: EffectMask::PANIC, + })?; + Ok(exception_value) +} diff --git a/src/mir/builder/control_flow/exception/try_catch.rs b/src/mir/builder/control_flow/exception/try_catch.rs new file mode 100644 index 00000000..568239a2 --- /dev/null +++ b/src/mir/builder/control_flow/exception/try_catch.rs @@ -0,0 +1,146 @@ +//! Try/catch/finally exception handling implementation. +//! +//! This module implements the control flow for try/catch/finally blocks, +//! including proper handling of deferred returns and cleanup blocks. + +use crate::ast::ASTNode; +use crate::mir::builder::{Effect, EffectMask, MirInstruction, ValueId}; + +/// Control-flow: try/catch/finally +/// +/// Implements exception handling with: +/// - Try block execution +/// - Catch clause handling +/// - Finally cleanup block +/// - Deferred return state management +pub(in crate::mir::builder) fn cf_try_catch( + builder: &mut super::super::super::MirBuilder, + try_body: Vec, + catch_clauses: Vec, + finally_body: Option>, +) -> Result { + if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH") + .ok() + .as_deref() + == Some("1") + { + let try_ast = ASTNode::Program { + statements: try_body, + span: crate::ast::Span::unknown(), + }; + let result = builder.build_expression(try_ast)?; + return Ok(result); + } + + let try_block = builder.block_gen.next(); + let catch_block = builder.block_gen.next(); + let finally_block = if finally_body.is_some() { + Some(builder.block_gen.next()) + } else { + None + }; + let exit_block = builder.block_gen.next(); + + // Snapshot deferred-return state + let saved_defer_active = builder.return_defer_active; + let saved_defer_slot = builder.return_defer_slot; + let saved_defer_target = builder.return_defer_target; + let saved_deferred_flag = builder.return_deferred_emitted; + let saved_in_cleanup = builder.in_cleanup_block; + let saved_allow_ret = builder.cleanup_allow_return; + let saved_allow_throw = builder.cleanup_allow_throw; + + let ret_slot = builder.next_value_id(); + builder.return_defer_active = true; + builder.return_defer_slot = Some(ret_slot); + builder.return_deferred_emitted = false; + builder.return_defer_target = Some(finally_block.unwrap_or(exit_block)); + + if let Some(catch_clause) = catch_clauses.first() { + if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { + eprintln!( + "[BUILDER] Emitting catch handler for {:?}", + catch_clause.exception_type + ); + } + let exception_value = builder.next_value_id(); + builder.emit_instruction(MirInstruction::Catch { + exception_type: catch_clause.exception_type.clone(), + exception_value, + handler_bb: catch_block, + })?; + } + + // Enter try block + crate::mir::builder::emission::branch::emit_jump(builder, try_block)?; + builder.start_new_block(try_block)?; + let try_ast = ASTNode::Program { + statements: try_body, + span: crate::ast::Span::unknown(), + }; + let _try_result = builder.build_expression(try_ast)?; + if !builder.is_current_block_terminated() { + let next_target = finally_block.unwrap_or(exit_block); + crate::mir::builder::emission::branch::emit_jump(builder, next_target)?; + } + + // Catch block + builder.start_new_block(catch_block)?; + if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { + eprintln!("[BUILDER] Enter catch block {:?}", catch_block); + } + if let Some(catch_clause) = catch_clauses.first() { + let catch_ast = ASTNode::Program { + statements: catch_clause.body.clone(), + span: crate::ast::Span::unknown(), + }; + builder.build_expression(catch_ast)?; + } + if !builder.is_current_block_terminated() { + let next_target = finally_block.unwrap_or(exit_block); + crate::mir::builder::emission::branch::emit_jump(builder, next_target)?; + } + + // Finally + let mut cleanup_terminated = false; + if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) { + builder.start_new_block(finally_block_id)?; + builder.in_cleanup_block = true; + builder.cleanup_allow_return = crate::config::env::cleanup_allow_return(); + builder.cleanup_allow_throw = crate::config::env::cleanup_allow_throw(); + builder.return_defer_active = false; // do not defer inside cleanup + + let finally_ast = ASTNode::Program { + statements: finally_statements, + span: crate::ast::Span::unknown(), + }; + builder.build_expression(finally_ast)?; + cleanup_terminated = builder.is_current_block_terminated(); + if !cleanup_terminated { + crate::mir::builder::emission::branch::emit_jump(builder, exit_block)?; + } + builder.in_cleanup_block = false; + } + + // Exit block + builder.start_new_block(exit_block)?; + let result = if builder.return_deferred_emitted && !cleanup_terminated { + builder.emit_instruction(MirInstruction::Return { + value: Some(ret_slot), + })?; + crate::mir::builder::emission::constant::emit_void(builder) + } else { + crate::mir::builder::emission::constant::emit_void(builder) + }; + + // Restore context + builder.return_defer_active = saved_defer_active; + builder.return_defer_slot = saved_defer_slot; + builder.return_defer_target = saved_defer_target; + builder.return_deferred_emitted = saved_deferred_flag; + builder.in_cleanup_block = saved_in_cleanup; + builder.cleanup_allow_return = saved_allow_ret; + builder.cleanup_allow_throw = saved_allow_throw; + + Ok(result) +} diff --git a/src/mir/builder/control_flow/mod.rs b/src/mir/builder/control_flow/mod.rs index edec0a4e..35a5f992 100644 --- a/src/mir/builder/control_flow/mod.rs +++ b/src/mir/builder/control_flow/mod.rs @@ -4,17 +4,21 @@ //! - Phase 1: Debug utilities (debug.rs) ✅ //! - Phase 2: Pattern lowerers (joinir/patterns/) ✅ //! - Phase 3: JoinIR routing (joinir/routing.rs) ✅ -//! - Phase 4-19: Additional modularization (future) +//! - Phase 4: Merge implementation (joinir/merge/) ✅ +//! - Phase 5: Exception handling (exception/) ✅ -use super::{Effect, EffectMask, MirInstruction, ValueId}; +use super::ValueId; use crate::ast::ASTNode; // Phase 1: Debug utilities pub(in crate::mir::builder) mod debug; -// Phase 2: JoinIR integration (patterns) +// Phase 2-4: JoinIR integration (patterns, routing, merge) pub(in crate::mir::builder) mod joinir; +// Phase 5: Exception handling +pub(in crate::mir::builder) mod exception; + impl super::MirBuilder { /// Control-flow: block pub(super) fn cf_block(&mut self, statements: Vec) -> Result { @@ -118,136 +122,15 @@ impl super::MirBuilder { } /// Control-flow: try/catch/finally + /// + /// Delegates to exception::cf_try_catch for implementation. pub(super) fn cf_try_catch( &mut self, try_body: Vec, catch_clauses: Vec, finally_body: Option>, ) -> Result { - if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH") - .ok() - .as_deref() - == Some("1") - { - let try_ast = ASTNode::Program { - statements: try_body, - span: crate::ast::Span::unknown(), - }; - let result = self.build_expression(try_ast)?; - return Ok(result); - } - - let try_block = self.block_gen.next(); - let catch_block = self.block_gen.next(); - let finally_block = if finally_body.is_some() { - Some(self.block_gen.next()) - } else { - None - }; - let exit_block = self.block_gen.next(); - - // Snapshot deferred-return state - let saved_defer_active = self.return_defer_active; - let saved_defer_slot = self.return_defer_slot; - let saved_defer_target = self.return_defer_target; - let saved_deferred_flag = self.return_deferred_emitted; - let saved_in_cleanup = self.in_cleanup_block; - let saved_allow_ret = self.cleanup_allow_return; - let saved_allow_throw = self.cleanup_allow_throw; - - let ret_slot = self.next_value_id(); - self.return_defer_active = true; - self.return_defer_slot = Some(ret_slot); - self.return_deferred_emitted = false; - self.return_defer_target = Some(finally_block.unwrap_or(exit_block)); - - if let Some(catch_clause) = catch_clauses.first() { - if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { - eprintln!( - "[BUILDER] Emitting catch handler for {:?}", - catch_clause.exception_type - ); - } - let exception_value = self.next_value_id(); - self.emit_instruction(MirInstruction::Catch { - exception_type: catch_clause.exception_type.clone(), - exception_value, - handler_bb: catch_block, - })?; - } - - // Enter try block - crate::mir::builder::emission::branch::emit_jump(self, try_block)?; - self.start_new_block(try_block)?; - let try_ast = ASTNode::Program { - statements: try_body, - span: crate::ast::Span::unknown(), - }; - let _try_result = self.build_expression(try_ast)?; - if !self.is_current_block_terminated() { - let next_target = finally_block.unwrap_or(exit_block); - crate::mir::builder::emission::branch::emit_jump(self, next_target)?; - } - - // Catch block - self.start_new_block(catch_block)?; - if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { - eprintln!("[BUILDER] Enter catch block {:?}", catch_block); - } - if let Some(catch_clause) = catch_clauses.first() { - let catch_ast = ASTNode::Program { - statements: catch_clause.body.clone(), - span: crate::ast::Span::unknown(), - }; - self.build_expression(catch_ast)?; - } - if !self.is_current_block_terminated() { - let next_target = finally_block.unwrap_or(exit_block); - crate::mir::builder::emission::branch::emit_jump(self, next_target)?; - } - - // Finally - let mut cleanup_terminated = false; - if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) { - self.start_new_block(finally_block_id)?; - self.in_cleanup_block = true; - self.cleanup_allow_return = crate::config::env::cleanup_allow_return(); - self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw(); - self.return_defer_active = false; // do not defer inside cleanup - - let finally_ast = ASTNode::Program { - statements: finally_statements, - span: crate::ast::Span::unknown(), - }; - self.build_expression(finally_ast)?; - cleanup_terminated = self.is_current_block_terminated(); - if !cleanup_terminated { - crate::mir::builder::emission::branch::emit_jump(self, exit_block)?; - } - self.in_cleanup_block = false; - } - - // Exit block - self.start_new_block(exit_block)?; - let result = if self.return_deferred_emitted && !cleanup_terminated { - self.emit_instruction(MirInstruction::Return { - value: Some(ret_slot), - })?; - crate::mir::builder::emission::constant::emit_void(self) - } else { - crate::mir::builder::emission::constant::emit_void(self) - }; - - // Restore context - self.return_defer_active = saved_defer_active; - self.return_defer_slot = saved_defer_slot; - self.return_defer_target = saved_defer_target; - self.return_deferred_emitted = saved_deferred_flag; - self.in_cleanup_block = saved_in_cleanup; - self.cleanup_allow_return = saved_allow_ret; - self.cleanup_allow_throw = saved_allow_throw; - - Ok(result) + exception::cf_try_catch(self, try_body, catch_clauses, finally_body) } /// Phase 188-Impl-2: Extract loop variable name from condition @@ -287,26 +170,9 @@ impl super::MirBuilder { } /// Control-flow: throw + /// + /// Delegates to exception::cf_throw for implementation. pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result { - if self.in_cleanup_block && !self.cleanup_allow_throw { - return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string()); - } - if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") { - let v = self.build_expression(expression)?; - self.emit_instruction(MirInstruction::ExternCall { - dst: None, - iface_name: "env.debug".to_string(), - method_name: "trace".to_string(), - args: vec![v], - effects: EffectMask::PURE.add(Effect::Debug), - })?; - return Ok(v); - } - let exception_value = self.build_expression(expression)?; - self.emit_instruction(MirInstruction::Throw { - exception: exception_value, - effects: EffectMask::PANIC, - })?; - Ok(exception_value) + exception::cf_throw(self, expression) } }