use super::{Effect, EffectMask, MirInstruction, MirType, ValueId}; use crate::ast::{ASTNode, CallExpr}; use crate::mir::utils::is_current_block_terminated; use crate::mir::TypeOpKind; impl super::MirBuilder { // Print statement: env.console.log(value) with early TypeOp handling pub(super) fn build_print_statement(&mut self, expression: ASTNode) -> Result { super::utils::builder_debug_log("enter build_print_statement"); // Prefer wrapper for simple function-call pattern (non-breaking refactor) if let Ok(call) = CallExpr::try_from(expression.clone()) { if (call.name == "isType" || call.name == "asType") && call.arguments.len() == 2 { super::utils::builder_debug_log( "pattern: print(FunctionCall isType|asType) [via wrapper]", ); if let Some(type_name) = super::MirBuilder::extract_string_literal(&call.arguments[1]) { super::utils::builder_debug_log(&format!( "extract_string_literal OK: {}", type_name )); let val = self.build_expression(call.arguments[0].clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.next_value_id(); let op = if call.name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!( "emit TypeOp {:?} value={} dst= {}", op, val, dst )); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty, })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL [via wrapper]"); } } } match &expression { // print(isType(val, "Type")) / print(asType(...)) ASTNode::FunctionCall { name, arguments, .. } if (name == "isType" || name == "asType") && arguments.len() == 2 => { super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType)"); if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[1]) { super::utils::builder_debug_log(&format!( "extract_string_literal OK: {}", type_name )); let val = self.build_expression(arguments[0].clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.next_value_id(); let op = if name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!( "emit TypeOp {:?} value={} dst= {}", op, val, dst )); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty, })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL"); } } // print(obj.is("Type")) / print(obj.as("Type")) ASTNode::MethodCall { object, method, arguments, .. } if (method == "is" || method == "as") && arguments.len() == 1 => { super::utils::builder_debug_log("pattern: print(MethodCall is|as)"); if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[0]) { super::utils::builder_debug_log(&format!( "extract_string_literal OK: {}", type_name )); let obj_val = self.build_expression(*object.clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.next_value_id(); let op = if method == "is" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!( "emit TypeOp {:?} obj={} dst= {}", op, obj_val, dst )); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty, })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL"); } } _ => {} } let value = self.build_expression(expression)?; super::utils::builder_debug_log(&format!("fallback print value={}", value)); // Phase 3.2: Use unified call for print statements let use_unified = super::calls::call_unified::is_unified_call_enabled(); if use_unified { // New unified path - treat print as global function self.emit_unified_call( None, // print returns nothing super::builder_calls::CallTarget::Global("print".to_string()), vec![value], )?; } else { // Legacy path - use ExternCall self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![value], effects: EffectMask::PURE.add(Effect::Io), })?; } Ok(value) } // Block: sequentially build statements and return last value or Void pub(super) fn build_block(&mut self, statements: Vec) -> Result { // Scope hint for bare block (Program) let scope_id = self.current_block.map(|bb| bb.as_u32()).unwrap_or(0); self.hint_scope_enter(scope_id); let _lex_scope = super::vars::lexical_scope::LexicalScopeGuard::new(self); let mut last_value = None; let total = statements.len(); eprintln!("[DEBUG/build_block] Processing {} statements", total); for (idx, statement) in statements.into_iter().enumerate() { eprintln!( "[DEBUG/build_block] Statement {}/{} current_block={:?} current_function={}", idx + 1, total, self.current_block, self.scope_ctx.current_function .as_ref() .map(|f| f.signature.name.as_str()) .unwrap_or("none") ); last_value = Some(self.build_statement(statement)?); // If the current block was terminated by this statement (e.g., return/throw), // do not emit any further instructions for this block. if is_current_block_terminated(self)? { eprintln!( "[DEBUG/build_block] Block terminated after statement {}", idx + 1 ); break; } } let out = last_value.unwrap_or_else(|| { // Use ConstantEmissionBox for Void crate::mir::builder::emission::constant::emit_void(self) }); // Scope leave only if block not already terminated if !self.is_current_block_terminated() { self.hint_scope_leave(scope_id); } eprintln!("[DEBUG/build_block] Completed, returning value {:?}", out); Ok(out) } /// Build a single statement node. /// /// Note: /// - While/ForRange は将来 Loop lowering へ委譲する拡張ポイントとして扱い、 /// 現状は他の専用ビルダ/既存パスと同様に build_expression に委譲する。 /// /// Phase 212.5: If statement のサポート追加 /// - Statement としての If(副作用のみが欲しい)を明示的に処理 /// - Expression としての If(値を使う)は build_expression 経由のまま pub(super) fn build_statement(&mut self, node: ASTNode) -> Result { // Align current_span to this statement node before lowering expressions under it. self.metadata_ctx.set_current_span(node.span()); match node { // Phase 212.5: Statement としての If 処理 ASTNode::If { condition, then_body, else_body, .. } => { // Statement としての If - 既存 If lowering を呼ぶ self.build_if_statement(*condition, then_body, else_body)?; // Statement なので値は使わない(Void を返す) Ok(crate::mir::builder::emission::constant::emit_void(self)) } // 将来ここに While / ForRange / Match / Using など statement 専用分岐を追加する。 other => self.build_expression(other), } } /// Phase 212.5: Statement としての If 処理(副作用のみ) /// /// ループ内 if や top-level statement if はここを通る。 /// Expression としての if(値を使う場合)は build_expression 経由。 /// /// # Arguments /// * `condition` - If の条件式 /// * `then_body` - then ブロックの statements /// * `else_body` - else ブロックの statements (optional) /// /// # Example /// ```hako /// if i > 0 { /// sum = sum + 1 // ← Statement としての If /// } /// ``` pub(super) fn build_if_statement( &mut self, condition: ASTNode, then_body: Vec, else_body: Option>, ) -> Result<(), String> { use crate::ast::Span; // then_body と else_body を ASTNode::Program に変換 let then_node = ASTNode::Program { statements: then_body, span: Span::unknown(), }; let else_node = else_body.map(|b| ASTNode::Program { statements: b, span: Span::unknown(), }); // 既存の If lowering を呼ぶ(cf_if は lower_if_form を呼ぶ) // 戻り値は無視(Statement なので値は使わない) let _result = self.cf_if(condition, then_node, else_node)?; Ok(()) } // Local declarations with optional initializers pub(super) fn build_local_statement( &mut self, variables: Vec, initial_values: Vec>>, ) -> Result { if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!( "[build_local_statement] ENTRY: variables={:?}, initial_values.len()={}", variables, initial_values.len() ); } let mut last_value = None; for (i, var_name) in variables.iter().enumerate() { let var_id = if i < initial_values.len() && initial_values[i].is_some() { // Evaluate the initializer expression let init_expr = initial_values[i].as_ref().unwrap(); let init_val = self.build_expression(*init_expr.clone())?; // FIX: Allocate a new ValueId for this local variable // Use next_value_id() which respects function context let var_id = self.next_value_id(); if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!( "[build_local_statement] '{}': init_val={:?}, allocated var_id={:?}", var_name, init_val, var_id ); } self.emit_instruction(crate::mir::MirInstruction::Copy { dst: var_id, src: init_val, })?; // Propagate metadata (type/origin) from initializer to variable crate::mir::builder::metadata::propagate::propagate(self, init_val, var_id); var_id } else { // Create a concrete register for uninitialized locals (Void) let void_id = crate::mir::builder::emission::constant::emit_void(self); if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!( "[build_local_statement] '{}': uninitialized, void_id={:?}", var_name, void_id ); } void_id }; if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!( "[build_local_statement] Inserting '{}' -> {:?} into variable_map", var_name, var_id ); } self.declare_local_in_current_scope(var_name, var_id)?; // SlotRegistry にもローカル変数スロットを登録しておくよ(観測専用) if let Some(reg) = self.comp_ctx.current_slot_registry.as_mut() { let ty = self.type_ctx.value_types.get(&var_id).cloned(); reg.ensure_slot(&var_name, ty); } last_value = Some(var_id); } // Phase 135 P0: Use function-level ValueId (SSOT) - build_local_statement is always in function context Ok(last_value.unwrap_or_else(|| self.next_value_id())) } // Return statement pub(super) fn build_return_statement( &mut self, value: Option>, ) -> Result { // Enforce cleanup policy if self.in_cleanup_block && !self.cleanup_allow_return { return Err("return is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_RETURN=1 to permit)".to_string()); } let return_value = if let Some(expr) = value { self.build_expression(*expr)? } else { crate::mir::builder::emission::constant::emit_void(self) }; if self.return_defer_active { // Defer: copy into slot and jump to target if let (Some(slot), Some(target)) = (self.return_defer_slot, self.return_defer_target) { self.return_deferred_emitted = true; self.emit_instruction(MirInstruction::Copy { dst: slot, src: return_value, })?; crate::mir::builder::metadata::propagate::propagate(self, return_value, slot); if !self.is_current_block_terminated() { crate::mir::builder::emission::branch::emit_jump(self, target)?; } Ok(return_value) } else { // Fallback: no configured slot/target; emit a real return self.emit_instruction(MirInstruction::Return { value: Some(return_value), })?; Ok(return_value) } } else { // Normal return self.emit_instruction(MirInstruction::Return { value: Some(return_value), })?; Ok(return_value) } } // Nowait: prefer env.future.spawn_instance if method call; else FutureNew pub(super) fn build_nowait_statement( &mut self, variable: String, expression: ASTNode, ) -> Result { if let ASTNode::MethodCall { object, method, arguments, .. } = expression.clone() { let recv_val = self.build_expression(*object)?; let mname_id = crate::mir::builder::emission::constant::emit_string(self, method.clone()); let mut arg_vals: Vec = Vec::with_capacity(2 + arguments.len()); arg_vals.push(recv_val); arg_vals.push(mname_id); for a in arguments.into_iter() { arg_vals.push(self.build_expression(a)?); } let future_id = self.next_value_id(); self.emit_instruction(MirInstruction::ExternCall { dst: Some(future_id), iface_name: "env.future".to_string(), method_name: "spawn_instance".to_string(), args: arg_vals, effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io), })?; // Future spawn returns a Future; the inner type is not statically known here. // Register at least Future to avoid later fail-fast type inference panics. self.type_ctx.value_types .insert(future_id, MirType::Future(Box::new(MirType::Unknown))); self.variable_ctx.variable_map.insert(variable.clone(), future_id); if let Some(reg) = self.comp_ctx.current_slot_registry.as_mut() { reg.ensure_slot(&variable, None); } return Ok(future_id); } let expression_value = self.build_expression(expression)?; let future_id = self.next_value_id(); self.emit_instruction(MirInstruction::FutureNew { dst: future_id, value: expression_value, })?; let inner = self .type_ctx.value_types .get(&expression_value) .cloned() .unwrap_or(MirType::Unknown); self.type_ctx.value_types .insert(future_id, MirType::Future(Box::new(inner))); self.variable_ctx.variable_map.insert(variable.clone(), future_id); if let Some(reg) = self.comp_ctx.current_slot_registry.as_mut() { reg.ensure_slot(&variable, None); } Ok(future_id) } // Await: insert Safepoint before/after and emit Await pub(super) fn build_await_expression( &mut self, expression: ASTNode, ) -> Result { let future_value = self.build_expression(expression)?; self.emit_instruction(MirInstruction::Safepoint)?; let result_id = self.next_value_id(); self.emit_instruction(MirInstruction::Await { dst: result_id, future: future_value, })?; let result_type = match self.type_ctx.value_types.get(&future_value) { Some(MirType::Future(inner)) => (**inner).clone(), _ => MirType::Unknown, }; self.type_ctx.value_types.insert(result_id, result_type); self.emit_instruction(MirInstruction::Safepoint)?; Ok(result_id) } // me: resolve to param if present; else symbolic const (stable mapping) pub(super) fn build_me_expression(&mut self) -> Result { if let Some(id) = self.variable_ctx.variable_map.get("me").cloned() { return Ok(id); } let me_tag = if let Some(ref cls) = self.comp_ctx.current_static_box { cls.clone() } else { "__me__".to_string() }; let me_value = crate::mir::builder::emission::constant::emit_string(self, me_tag); self.variable_ctx.variable_map.insert("me".to_string(), me_value); if let Some(reg) = self.comp_ctx.current_slot_registry.as_mut() { reg.ensure_slot("me", None); } // P0: Known 化 — 分かる範囲で me の起源クラスを付与(挙動不変)。 super::origin::infer::annotate_me_origin(self, me_value); Ok(me_value) } }