Files
hakorune/src/mir/builder/stmts.rs
nyash-codex e404746612 refactor(mir): Phase 139-P3-B - RoutingDecision を enum 対応 + レガシー削除
- RoutingDecision の missing_caps を Vec<CapabilityTag> に変更(型安全化)
- error_tags は to_tag() メソッドで自動生成
- 全 callsite を enum variant に修正
- capability_tags モジュール(文字列定数群)を完全削除
- 全テスト PASS(型安全性向上を確認)
- フォーマット適用
2025-12-16 07:02:14 +09:00

509 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ValueId, String> {
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<ASTNode>) -> Result<ValueId, String> {
// 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<ValueId, String> {
// 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<ASTNode>,
else_body: Option<Vec<ASTNode>>,
) -> 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<String>,
initial_values: Vec<Option<Box<ASTNode>>>,
) -> Result<ValueId, String> {
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<Box<ASTNode>>,
) -> Result<ValueId, String> {
// 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<ValueId, String> {
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<ValueId> = 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<T>; the inner type is not statically known here.
// Register at least Future<Unknown> 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<ValueId, String> {
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<ValueId, String> {
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)
}
}