refactor(optimizer, parser/box): prune legacy optimizer bodies; delegate public/private single-line fields

- optimizer: remove unreachable old bodies in normalize_legacy_instructions/normalize_ref_field_access and delegate to optimizer_passes only
- parser/box: route public/private single-line fields through members::fields (supports = init, => computed, {..}+postfix) and keep visibility vectors consistent
- box_def facade: silence dead_code warning for scaffold

Smokes:
- PyVM Stage-2 PASS
- Stage-2 short-circuit PASS
- PyVM collections PASS
(NOTE) using_e2e_smoke is optional and currently failing; unrelated to this change
This commit is contained in:
Selfhosting Dev
2025-09-19 07:19:46 +09:00
parent 2d870664d5
commit 52ab2957e6
3 changed files with 476 additions and 1112 deletions

View File

@ -10,7 +10,7 @@
use super::{Effect, EffectMask, MirFunction, MirInstruction, MirModule, MirType, ValueId}; use super::{Effect, EffectMask, MirFunction, MirInstruction, MirModule, MirType, ValueId};
use crate::mir::optimizer_stats::OptimizationStats; use crate::mir::optimizer_stats::OptimizationStats;
use std::collections::{HashMap, HashSet}; // std::collections imports removed (local DCE/CSE impls deleted)
/// MIR optimization passes /// MIR optimization passes
pub struct MirOptimizer { pub struct MirOptimizer {
@ -123,145 +123,8 @@ impl MirOptimizer {
stats stats
} }
/// Eliminate dead code (unused values) /// Core-13 "pure" normalization notes moved to optimizer_passes::normalize_core13_pure
fn eliminate_dead_code(&mut self, module: &mut MirModule) -> OptimizationStats {
let mut stats = OptimizationStats::new();
for (func_name, function) in &mut module.functions {
if self.debug {
println!(" 🗑️ Dead code elimination in function: {}", func_name);
}
let eliminated = self.eliminate_dead_code_in_function(function);
stats.dead_code_eliminated += eliminated;
}
stats
}
/// Core-13 "pure" normalization: rewrite a few non-13 ops to allowed forms.
/// - Load(dst, ptr) => ExternCall(Some dst, env.local.get, [ptr])
/// - Store(val, ptr) => ExternCall(None, env.local.set, [ptr, val])
/// - NewBox(dst, T, args...) => ExternCall(Some dst, env.box.new, [Const String(T), args...])
/// - UnaryOp:
/// Neg x => BinOp(Sub, Const 0, x)
/// Not x => Compare(Eq, x, Const false)
/// BitNot x => BinOp(BitXor, x, Const(-1))
// normalize_pure_core13 moved to optimizer_passes::normalize_core13_pure
/// Eliminate dead code in a single function
fn eliminate_dead_code_in_function(&mut self, function: &mut MirFunction) -> usize {
// Collect all used values
let mut used_values = HashSet::new();
// Mark values used in terminators and side-effect instructions
for (_, block) in &function.blocks {
for instruction in &block.instructions {
// Always keep instructions with side effects
if !instruction.effects().is_pure() {
if let Some(dst) = instruction.dst_value() {
used_values.insert(dst);
}
for used in instruction.used_values() {
used_values.insert(used);
}
}
}
// Mark values used in terminators
if let Some(terminator) = &block.terminator {
for used in terminator.used_values() {
used_values.insert(used);
}
}
}
// Propagate usage backwards
let mut changed = true;
while changed {
changed = false;
for (_, block) in &function.blocks {
for instruction in &block.instructions {
if let Some(dst) = instruction.dst_value() {
if used_values.contains(&dst) {
for used in instruction.used_values() {
if used_values.insert(used) {
changed = true;
}
}
}
}
}
}
}
// Remove unused pure instructions
let mut eliminated = 0;
for (bbid, block) in &mut function.blocks {
block.instructions.retain(|instruction| {
if instruction.effects().is_pure() {
if let Some(dst) = instruction.dst_value() {
if !used_values.contains(&dst) {
opt_debug(&format!("DCE drop @{}: {:?}", bbid.as_u32(), instruction));
eliminated += 1;
return false;
}
}
}
true
});
}
eliminated
}
/// Common Subexpression Elimination for pure instructions
fn common_subexpression_elimination(&mut self, module: &mut MirModule) -> OptimizationStats {
let mut stats = OptimizationStats::new();
for (func_name, function) in &mut module.functions {
if self.debug {
println!(" 🔄 CSE in function: {}", func_name);
}
let eliminated = self.cse_in_function(function);
stats.cse_eliminated += eliminated;
}
stats
}
/// CSE in a single function
fn cse_in_function(&mut self, function: &mut MirFunction) -> usize {
let mut expression_map: HashMap<String, ValueId> = HashMap::new();
let mut replacements: HashMap<ValueId, ValueId> = HashMap::new();
let mut eliminated = 0;
for (_, block) in &mut function.blocks {
for instruction in &mut block.instructions {
// Only optimize pure instructions
if instruction.effects().is_pure() {
let expr_key = self.instruction_to_key(instruction);
if let Some(&existing_value) = expression_map.get(&expr_key) {
// Found common subexpression
if let Some(dst) = instruction.dst_value() {
replacements.insert(dst, existing_value);
eliminated += 1;
}
} else {
// First occurrence of this expression
if let Some(dst) = instruction.dst_value() {
expression_map.insert(expr_key, dst);
}
}
}
}
}
// Apply replacements (simplified - in full implementation would need proper SSA update)
eliminated
}
/// Convert instruction to string key for CSE /// Convert instruction to string key for CSE
#[allow(dead_code)] #[allow(dead_code)]
@ -320,496 +183,13 @@ impl MirOptimizer {
/// - Print → ExternCall(env.console.log) /// - Print → ExternCall(env.console.log)
#[allow(dead_code)] #[allow(dead_code)]
fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats { fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats {
use super::{BarrierOp, MirInstruction as I, MirType, TypeOpKind, WeakRefOp}; crate::mir::optimizer_passes::normalize::normalize_legacy_instructions(self, module)
let mut stats = OptimizationStats::new();
let rw_dbg = crate::config::env::rewrite_debug();
let rw_sp = crate::config::env::rewrite_safepoint();
let rw_future = crate::config::env::rewrite_future();
// Phase 11.8 toggles
let core13 = crate::config::env::mir_core13();
let mut array_to_boxcall = crate::config::env::mir_array_boxcall();
if core13 {
array_to_boxcall = true;
}
for (_fname, function) in &mut module.functions {
for (_bb, block) in &mut function.blocks {
// Rewrite in-place for normal instructions
for inst in &mut block.instructions {
match inst {
I::TypeCheck {
dst,
value,
expected_type,
} => {
let ty = MirType::Box(expected_type.clone());
*inst = I::TypeOp {
dst: *dst,
op: TypeOpKind::Check,
value: *value,
ty,
};
stats.reorderings += 0; // no-op; keep stats structure alive
}
I::Cast {
dst,
value,
target_type,
} => {
let ty = target_type.clone();
*inst = I::TypeOp {
dst: *dst,
op: TypeOpKind::Cast,
value: *value,
ty,
};
}
I::WeakNew { dst, box_val } => {
let val = *box_val;
*inst = I::WeakRef {
dst: *dst,
op: WeakRefOp::New,
value: val,
};
}
I::WeakLoad { dst, weak_ref } => {
let val = *weak_ref;
*inst = I::WeakRef {
dst: *dst,
op: WeakRefOp::Load,
value: val,
};
}
I::BarrierRead { ptr } => {
let val = *ptr;
*inst = I::Barrier {
op: BarrierOp::Read,
ptr: val,
};
}
I::BarrierWrite { ptr } => {
let val = *ptr;
*inst = I::Barrier {
op: BarrierOp::Write,
ptr: val,
};
}
I::Print { value, .. } => {
let v = *value;
*inst = I::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass (guarded) */
}
I::ArrayGet { dst, array, index } if array_to_boxcall => {
let d = *dst;
let a = *array;
let i = *index;
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
"ArrayBox", "get",
);
*inst = I::BoxCall {
dst: Some(d),
box_val: a,
method: "get".to_string(),
method_id: mid,
args: vec![i],
effects: EffectMask::READ,
};
}
I::ArraySet {
array,
index,
value,
} if array_to_boxcall => {
let a = *array;
let i = *index;
let v = *value;
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
"ArrayBox", "set",
);
*inst = I::BoxCall {
dst: None,
box_val: a,
method: "set".to_string(),
method_id: mid,
args: vec![i, v],
effects: EffectMask::WRITE,
};
}
I::PluginInvoke {
dst,
box_val,
method,
args,
effects,
} => {
let d = *dst;
let recv = *box_val;
let m = method.clone();
let as_ = args.clone();
let eff = *effects;
*inst = I::BoxCall {
dst: d,
box_val: recv,
method: m,
method_id: None,
args: as_,
effects: eff,
};
}
I::Debug { value, .. } if rw_dbg => {
let v = *value;
*inst = I::ExternCall {
dst: None,
iface_name: "env.debug".to_string(),
method_name: "trace".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Debug),
};
}
I::Safepoint if rw_sp => {
*inst = I::ExternCall {
dst: None,
iface_name: "env.runtime".to_string(),
method_name: "checkpoint".to_string(),
args: vec![],
effects: EffectMask::PURE,
};
}
// Future/Await の段階移行: ExternCall(env.future.*) に書き換え(トグル)
I::FutureNew { dst, value } if rw_future => {
let d = *dst;
let v = *value;
*inst = I::ExternCall {
dst: Some(d),
iface_name: "env.future".to_string(),
method_name: "new".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::FutureSet { future, value } if rw_future => {
let f = *future;
let v = *value;
*inst = I::ExternCall {
dst: None,
iface_name: "env.future".to_string(),
method_name: "set".to_string(),
args: vec![f, v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::Await { dst, future } if rw_future => {
let d = *dst;
let f = *future;
*inst = I::ExternCall {
dst: Some(d),
iface_name: "env.future".to_string(),
method_name: "await".to_string(),
args: vec![f],
effects: EffectMask::PURE.add(Effect::Io),
};
}
_ => {}
}
}
// Rewrite terminator, if any
if let Some(term) = &mut block.terminator {
match term {
I::TypeCheck {
dst,
value,
expected_type,
} => {
let ty = MirType::Box(expected_type.clone());
*term = I::TypeOp {
dst: *dst,
op: TypeOpKind::Check,
value: *value,
ty,
};
}
I::Cast {
dst,
value,
target_type,
} => {
let ty = target_type.clone();
*term = I::TypeOp {
dst: *dst,
op: TypeOpKind::Cast,
value: *value,
ty,
};
}
I::WeakNew { dst, box_val } => {
let val = *box_val;
*term = I::WeakRef {
dst: *dst,
op: WeakRefOp::New,
value: val,
};
}
I::WeakLoad { dst, weak_ref } => {
let val = *weak_ref;
*term = I::WeakRef {
dst: *dst,
op: WeakRefOp::Load,
value: val,
};
}
I::BarrierRead { ptr } => {
let val = *ptr;
*term = I::Barrier {
op: BarrierOp::Read,
ptr: val,
};
}
I::BarrierWrite { ptr } => {
let val = *ptr;
*term = I::Barrier {
op: BarrierOp::Write,
ptr: val,
};
}
I::Print { value, .. } => {
let v = *value;
*term = I::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass (guarded) */
}
I::ArrayGet { dst, array, index } if array_to_boxcall => {
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
"ArrayBox", "get",
);
*term = I::BoxCall {
dst: Some(*dst),
box_val: *array,
method: "get".to_string(),
method_id: mid,
args: vec![*index],
effects: EffectMask::READ,
};
}
I::ArraySet {
array,
index,
value,
} if array_to_boxcall => {
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
"ArrayBox", "set",
);
*term = I::BoxCall {
dst: None,
box_val: *array,
method: "set".to_string(),
method_id: mid,
args: vec![*index, *value],
effects: EffectMask::WRITE,
};
}
I::PluginInvoke {
dst,
box_val,
method,
args,
effects,
} => {
*term = I::BoxCall {
dst: *dst,
box_val: *box_val,
method: method.clone(),
method_id: None,
args: args.clone(),
effects: *effects,
};
}
I::Debug { value, .. } if rw_dbg => {
let v = *value;
*term = I::ExternCall {
dst: None,
iface_name: "env.debug".to_string(),
method_name: "trace".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Debug),
};
}
I::Safepoint if rw_sp => {
*term = I::ExternCall {
dst: None,
iface_name: "env.runtime".to_string(),
method_name: "checkpoint".to_string(),
args: vec![],
effects: EffectMask::PURE,
};
}
// Future/Await (終端側)
I::FutureNew { dst, value } if rw_future => {
let d = *dst;
let v = *value;
*term = I::ExternCall {
dst: Some(d),
iface_name: "env.future".to_string(),
method_name: "new".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::FutureSet { future, value } if rw_future => {
let f = *future;
let v = *value;
*term = I::ExternCall {
dst: None,
iface_name: "env.future".to_string(),
method_name: "set".to_string(),
args: vec![f, v],
effects: EffectMask::PURE.add(Effect::Io),
};
}
I::Await { dst, future } if rw_future => {
let d = *dst;
let f = *future;
*term = I::ExternCall {
dst: Some(d),
iface_name: "env.future".to_string(),
method_name: "await".to_string(),
args: vec![f],
effects: EffectMask::PURE.add(Effect::Io),
};
}
_ => {}
}
}
}
}
stats
} }
/// Normalize RefGet/RefSet to BoxCall("getField"/"setField") with Const String field argument. /// Normalize RefGet/RefSet to BoxCall("getField"/"setField") with Const String field argument.
#[allow(dead_code)]
fn normalize_ref_field_access(&mut self, module: &mut MirModule) -> OptimizationStats { fn normalize_ref_field_access(&mut self, module: &mut MirModule) -> OptimizationStats {
use super::{BarrierOp, MirInstruction as I}; crate::mir::optimizer_passes::normalize::normalize_ref_field_access(self, module)
let mut stats = OptimizationStats::new();
for (_fname, function) in &mut module.functions {
for (_bb, block) in &mut function.blocks {
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
let old = std::mem::take(&mut block.instructions);
for inst in old.into_iter() {
match inst {
I::RefGet {
dst,
reference,
field,
} => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
out.push(I::Const {
dst: new_id,
value: super::instruction::ConstValue::String(field),
});
out.push(I::BoxCall {
dst: Some(dst),
box_val: reference,
method: "getField".to_string(),
method_id: None,
args: vec![new_id],
effects: super::EffectMask::READ,
});
stats.intrinsic_optimizations += 1;
}
I::RefSet {
reference,
field,
value,
} => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
out.push(I::Const {
dst: new_id,
value: super::instruction::ConstValue::String(field),
});
// Prepend an explicit write barrier before setField to make side-effects visible
out.push(I::Barrier {
op: BarrierOp::Write,
ptr: reference,
});
out.push(I::BoxCall {
dst: None,
box_val: reference,
method: "setField".to_string(),
method_id: None,
args: vec![new_id, value],
effects: super::EffectMask::WRITE,
});
stats.intrinsic_optimizations += 1;
}
other => out.push(other),
}
}
block.instructions = out;
if let Some(term) = block.terminator.take() {
block.terminator = Some(match term {
I::RefGet {
dst,
reference,
field,
} => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
block.instructions.push(I::Const {
dst: new_id,
value: super::instruction::ConstValue::String(field),
});
I::BoxCall {
dst: Some(dst),
box_val: reference,
method: "getField".to_string(),
method_id: None,
args: vec![new_id],
effects: super::EffectMask::READ,
}
}
I::RefSet {
reference,
field,
value,
} => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
block.instructions.push(I::Const {
dst: new_id,
value: super::instruction::ConstValue::String(field),
});
block.instructions.push(I::Barrier {
op: BarrierOp::Write,
ptr: reference,
});
I::BoxCall {
dst: None,
box_val: reference,
method: "setField".to_string(),
method_id: None,
args: vec![new_id, value],
effects: super::EffectMask::WRITE,
}
}
other => other,
});
}
}
}
stats
} }
} }
@ -890,143 +270,144 @@ impl Default for MirOptimizer {
// OptimizationStats moved to crate::mir::optimizer_stats // OptimizationStats moved to crate::mir::optimizer_stats
impl MirOptimizer { /// Diagnostics: identify unlowered type-ops embedded as strings in Call/BoxCall
/// Diagnostic: detect unlowered is/as/isType/asType after Builder #[allow(dead_code)]
#[allow(dead_code)] fn diagnose_unlowered_type_ops(
fn diagnose_unlowered_type_ops(&mut self, module: &MirModule) -> OptimizationStats { optimizer: &MirOptimizer,
let mut stats = OptimizationStats::new(); module: &MirModule,
let diag_on = self.debug || crate::config::env::opt_diag(); ) -> OptimizationStats {
for (fname, function) in &module.functions { let mut stats = OptimizationStats::new();
// def map for resolving constants let diag_on = optimizer.debug || crate::config::env::opt_diag();
let mut def_map: std::collections::HashMap< for (fname, function) in &module.functions {
ValueId, // def map for resolving constants
(super::basic_block::BasicBlockId, usize), let mut def_map: std::collections::HashMap<
> = std::collections::HashMap::new(); ValueId,
for (bb_id, block) in &function.blocks { (super::basic_block::BasicBlockId, usize),
for (i, inst) in block.instructions.iter().enumerate() { > = std::collections::HashMap::new();
if let Some(dst) = inst.dst_value() { for (bb_id, block) in &function.blocks {
def_map.insert(dst, (*bb_id, i)); for (i, inst) in block.instructions.iter().enumerate() {
} if let Some(dst) = inst.dst_value() {
} def_map.insert(dst, (*bb_id, i));
if let Some(term) = &block.terminator {
if let Some(dst) = term.dst_value() {
def_map.insert(dst, (*bb_id, usize::MAX));
}
} }
} }
let mut count = 0usize; if let Some(term) = &block.terminator {
for (_bb, block) in &function.blocks { if let Some(dst) = term.dst_value() {
for inst in &block.instructions { def_map.insert(dst, (*bb_id, usize::MAX));
match inst { }
MirInstruction::BoxCall { method, .. } }
if method == "is" }
|| method == "as" let mut count = 0usize;
|| method == "isType" for (_bb, block) in &function.blocks {
|| method == "asType" => for inst in &block.instructions {
{ match inst {
count += 1; MirInstruction::BoxCall { method, .. }
} if method == "is"
MirInstruction::Call { func, .. } => { || method == "as"
if let Some((bb, idx)) = def_map.get(func).copied() { || method == "isType"
if let Some(b) = function.blocks.get(&bb) { || method == "asType" =>
if idx < b.instructions.len() { {
if let MirInstruction::Const { count += 1;
value: super::instruction::ConstValue::String(s), }
.. MirInstruction::Call { func, .. } => {
} = &b.instructions[idx] if let Some((bb, idx)) = def_map.get(func).copied() {
{ if let Some(b) = function.blocks.get(&bb) {
if s == "isType" || s == "asType" { if idx < b.instructions.len() {
count += 1; if let MirInstruction::Const {
} value: super::instruction::ConstValue::String(s),
..
} = &b.instructions[idx]
{
if s == "isType" || s == "asType" {
count += 1;
} }
} }
} }
} }
} }
_ => {}
} }
} _ => {}
}
if count > 0 {
stats.diagnostics_reported += count;
if diag_on {
eprintln!(
"[OPT][DIAG] Function '{}' has {} unlowered type-op calls",
fname, count
);
} }
} }
} }
stats if count > 0 {
stats.diagnostics_reported += count;
if diag_on {
eprintln!(
"[OPT][DIAG] Function '{}' has {} unlowered type-op calls",
fname, count
);
}
}
} }
stats
}
/// Diagnostic: detect legacy instructions that should be unified /// Diagnostic: detect legacy instructions that should be unified
/// Legacy set: TypeCheck/Cast/WeakNew/WeakLoad/BarrierRead/BarrierWrite/ArrayGet/ArraySet/RefGet/RefSet/PluginInvoke /// Legacy set: TypeCheck/Cast/WeakNew/WeakLoad/BarrierRead/BarrierWrite/ArrayGet/ArraySet/RefGet/RefSet/PluginInvoke
/// When NYASH_OPT_DIAG or NYASH_OPT_DIAG_FORBID_LEGACY is set, prints diagnostics. /// When NYASH_OPT_DIAG or NYASH_OPT_DIAG_FORBID_LEGACY is set, prints diagnostics.
#[allow(dead_code)] #[allow(dead_code)]
fn diagnose_legacy_instructions(&mut self, module: &MirModule) -> OptimizationStats { fn diagnose_legacy_instructions(module: &MirModule, debug: bool) -> OptimizationStats {
let mut stats = OptimizationStats::new(); let mut stats = OptimizationStats::new();
let diag_on = self.debug let diag_on = debug
|| crate::config::env::opt_diag() || crate::config::env::opt_diag()
|| crate::config::env::opt_diag_forbid_legacy(); || crate::config::env::opt_diag_forbid_legacy();
for (fname, function) in &module.functions { for (fname, function) in &module.functions {
let mut count = 0usize; let mut count = 0usize;
for (_bb, block) in &function.blocks { for (_bb, block) in &function.blocks {
for inst in &block.instructions { for inst in &block.instructions {
match inst { match inst {
MirInstruction::TypeCheck { .. } MirInstruction::TypeCheck { .. }
| MirInstruction::Cast { .. } | MirInstruction::Cast { .. }
| MirInstruction::WeakNew { .. } | MirInstruction::WeakNew { .. }
| MirInstruction::WeakLoad { .. } | MirInstruction::WeakLoad { .. }
| MirInstruction::BarrierRead { .. } | MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. } | MirInstruction::BarrierWrite { .. }
| MirInstruction::ArrayGet { .. } | MirInstruction::ArrayGet { .. }
| MirInstruction::ArraySet { .. } | MirInstruction::ArraySet { .. }
| MirInstruction::RefGet { .. } | MirInstruction::RefGet { .. }
| MirInstruction::RefSet { .. } | MirInstruction::RefSet { .. }
| MirInstruction::PluginInvoke { .. } => { | MirInstruction::PluginInvoke { .. } => {
count += 1; count += 1;
}
_ => {}
}
}
if let Some(term) = &block.terminator {
match term {
MirInstruction::TypeCheck { .. }
| MirInstruction::Cast { .. }
| MirInstruction::WeakNew { .. }
| MirInstruction::WeakLoad { .. }
| MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. }
| MirInstruction::ArrayGet { .. }
| MirInstruction::ArraySet { .. }
| MirInstruction::RefGet { .. }
| MirInstruction::RefSet { .. }
| MirInstruction::PluginInvoke { .. } => {
count += 1;
}
_ => {}
} }
_ => {}
} }
} }
if count > 0 { if let Some(term) = &block.terminator {
stats.diagnostics_reported += count; match term {
if diag_on { MirInstruction::TypeCheck { .. }
eprintln!( | MirInstruction::Cast { .. }
"[OPT][DIAG] Function '{}' has {} legacy MIR ops: unify to Core13 (TypeOp/WeakRef/Barrier/BoxCall)", | MirInstruction::WeakNew { .. }
fname, count | MirInstruction::WeakLoad { .. }
); | MirInstruction::BarrierRead { .. }
if crate::config::env::opt_diag_forbid_legacy() { | MirInstruction::BarrierWrite { .. }
panic!( | MirInstruction::ArrayGet { .. }
"NYASH_OPT_DIAG_FORBID_LEGACY=1: legacy MIR ops detected in '{}': {}", | MirInstruction::ArraySet { .. }
fname, count | MirInstruction::RefGet { .. }
); | MirInstruction::RefSet { .. }
| MirInstruction::PluginInvoke { .. } => {
count += 1;
} }
_ => {}
}
}
}
if count > 0 {
stats.diagnostics_reported += count;
if diag_on {
eprintln!(
"[OPT][DIAG] Function '{}' has {} legacy MIR ops: unify to Core13 (TypeOp/WeakRef/Barrier/BoxCall)",
fname, count
);
if crate::config::env::opt_diag_forbid_legacy() {
panic!(
"NYASH_OPT_DIAG_FORBID_LEGACY=1: legacy MIR ops detected in '{}': {}",
fname, count
);
} }
} }
} }
stats
} }
stats
} }
#[cfg(test)] #[cfg(test)]
@ -1133,3 +514,4 @@ mod tests {
); );
} }
} }

View File

@ -0,0 +1,27 @@
//! Box Definition parser (scaffold)
#![allow(dead_code)]
//!
//! This module will progressively take over parsing of large `parse_box_declaration`
//! by splitting header and member parsing into focused units.
//! For now, it provides only type skeletons to stage the refactor safely.
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
pub mod header;
pub mod members;
/// Facade to host the staged migration.
pub struct BoxDefParserFacade;
impl BoxDefParserFacade {
/// Entry planned: parse full box declaration (header + members).
/// Not wired yet; use NyashParser::parse_box_declaration for now.
pub fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
Err(ParseError::UnexpectedToken {
found: crate::tokenizer::TokenType::EOF,
expected: "box declaration (facade not wired)".to_string(),
line: 0,
})
}
}

View File

@ -6,7 +6,7 @@
*/ */
use crate::ast::{ASTNode, Span}; use crate::ast::{ASTNode, Span};
use crate::must_advance; use crate::parser::declarations::box_def::header as box_header;
use crate::parser::common::ParserUtils; use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError}; use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType; use crate::tokenizer::TokenType;
@ -16,122 +16,8 @@ impl NyashParser {
/// box宣言をパース: box Name { fields... methods... } /// box宣言をパース: box Name { fields... methods... }
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> { pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
self.consume(TokenType::BOX)?; self.consume(TokenType::BOX)?;
let (name, type_parameters, extends, implements) =
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type { box_header::parse_header(self)?;
let name = name.clone();
self.advance();
name
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "identifier".to_string(),
line,
});
};
// 🔥 ジェネリクス型パラメータのパース (<T, U>)
let type_parameters = if self.match_token(&TokenType::LESS) {
self.advance(); // consume '<'
let mut params = Vec::new();
while !self.match_token(&TokenType::GREATER) && !self.is_at_end() {
must_advance!(self, _unused, "generic type parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance();
self.skip_newlines();
}
} else {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "type parameter name".to_string(),
line: self.current_token().line,
});
}
}
self.consume(TokenType::GREATER)?; // consume '>'
params
} else {
Vec::new()
};
// 🚀 Multi-delegation support: "from Parent1, Parent2, ..."
let extends = if self.match_token(&TokenType::FROM) {
self.advance(); // consume 'from'
let mut parents = Vec::new();
// Parse first parent (required)
if let TokenType::IDENTIFIER(parent) = &self.current_token().token_type {
parents.push(parent.clone());
self.advance();
} else {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "parent box name after 'from'".to_string(),
line: self.current_token().line,
});
}
// Parse additional parents (optional)
while self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
self.skip_newlines();
if let TokenType::IDENTIFIER(parent) = &self.current_token().token_type {
parents.push(parent.clone());
self.advance();
} else {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "parent box name after comma".to_string(),
line: self.current_token().line,
});
}
}
parents
} else {
Vec::new()
};
// implementsキーワードのチェック
// TODO: TokenType::IMPLEMENTS is not defined in current version
let implements = if false {
// self.match_token(&TokenType::IMPLEMENTS) {
self.advance(); // consume 'implements'
let mut interfaces = Vec::new();
loop {
must_advance!(self, _unused, "interface implementation parsing");
if let TokenType::IDENTIFIER(interface) = &self.current_token().token_type {
interfaces.push(interface.clone());
self.advance();
} else {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "interface name".to_string(),
line: self.current_token().line,
});
}
if self.match_token(&TokenType::COMMA) {
self.advance();
} else {
break;
}
}
interfaces
} else {
Vec::new()
};
self.consume(TokenType::LBRACE)?; self.consume(TokenType::LBRACE)?;
self.skip_newlines(); // ブレース後の改行をスキップ self.skip_newlines(); // ブレース後の改行をスキップ
@ -143,10 +29,144 @@ impl NyashParser {
let mut constructors = HashMap::new(); let mut constructors = HashMap::new();
let mut init_fields = Vec::new(); let mut init_fields = Vec::new();
let mut weak_fields = Vec::new(); // 🔗 Track weak fields let mut weak_fields = Vec::new(); // 🔗 Track weak fields
// Track birth_once properties to inject eager init into birth()
let mut birth_once_props: Vec<String> = Vec::new();
let mut last_method_name: Option<String> = None; let mut last_method_name: Option<String> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() { while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines(); // ループ開始時に改行をスキップ self.skip_newlines(); // ループ開始時に改行をスキップ
// 分類(段階移行用の観測): 将来の分岐移譲のための前処理
if crate::config::env::parser_stage3() {
if let Ok(kind) = crate::parser::declarations::box_def::members::common::classify_member(self) {
let _ = kind; // 現段階では観測のみ(無副作用)
}
}
// nyashモードblock-first: { body } as (once|birth_once)? name : Type
if crate::config::env::unified_members() && self.match_token(&TokenType::LBRACE) {
// Parse block body first
let mut final_body = self.parse_block_statements()?;
self.skip_newlines();
// Expect 'as'
if let TokenType::IDENTIFIER(kw) = &self.current_token().token_type {
if kw != "as" {
// Not a block-first member; treat as standalone block statement in box (unsupported)
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "'as' after block for block-first member".to_string(), line });
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "'as' after block for block-first member".to_string(), line });
}
self.advance(); // consume 'as'
// Optional kind keyword
let mut kind = "computed".to_string();
if let TokenType::IDENTIFIER(k) = &self.current_token().token_type {
if k == "once" || k == "birth_once" {
kind = k.clone();
self.advance();
}
}
// Name : Type
let name = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
let s = n.clone();
self.advance();
s
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "identifier for member name".to_string(), line });
};
if self.match_token(&TokenType::COLON) {
self.advance();
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type { self.advance(); } else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "type name after ':'".to_string(), line });
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: ": type".to_string(), line });
}
// Optional postfix handlers (Stage-3) directly after block
if crate::config::env::parser_stage3() && (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) {
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance();
self.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after member block".to_string(), line });
}
}
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
final_body = vec![ASTNode::TryCatch { try_body: final_body, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
}
// Generate methods per kind
if kind == "once" {
let compute_name = format!("__compute_once_{}", name);
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(compute_name.clone(), compute);
let key = format!("__once_{}", name);
let poison_key = format!("__once_poison_{}", name);
let cached_local = format!("__ny_cached_{}", name);
let poison_local = format!("__ny_poison_{}", name);
let val_local = format!("__ny_val_{}", name);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_cached = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() };
let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }];
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() };
let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() };
let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }];
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
let try_body = vec![local_val, set_call, ret_stmt];
let catch_body = vec![
ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }, ASTNode::Literal { value: crate::ast::LiteralValue::Bool(true), span: Span::unknown() }], span: Span::unknown() },
ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' init failed", name)), span: Span::unknown() }), span: Span::unknown() }
];
let trycatch = ASTNode::TryCatch { try_body, catch_clauses: vec![crate::ast::CatchClause { exception_type: None, variable_name: None, body: catch_body, span: Span::unknown() }], finally_body: None, span: Span::unknown() };
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, trycatch];
let getter_name = format!("__get_once_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
} else if kind == "birth_once" {
// Self-cycle guard: birth_once cannot reference itself via me.<name>
if self.ast_contains_me_field(&final_body, &name) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: format!("birth_once '{}' must not reference itself", name), line });
}
birth_once_props.push(name.clone());
let compute_name = format!("__compute_birth_{}", name);
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(compute_name.clone(), compute);
let key = format!("__birth_{}", name);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
let getter_name = format!("__get_birth_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
} else {
// computed
let getter_name = format!("__get_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
}
self.skip_newlines();
continue;
}
// Fallback: method-level postfix catch/cleanup after a method (non-static box) // Fallback: method-level postfix catch/cleanup after a method (non-static box)
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() { if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
@ -243,195 +263,9 @@ impl NyashParser {
self.advance(); self.advance();
} }
// initトークンをメソッド名として特別処理 // constructor parsing moved to members::constructors
if self.match_token(&TokenType::INIT) && self.peek_token() == &TokenType::LPAREN { if let Some((ctor_key, ctor_node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
let field_or_method = "init".to_string(); constructors.insert(ctor_key, ctor_node);
self.advance(); // consume 'init'
// コンストラクタとして処理
if self.match_token(&TokenType::LPAREN) {
// initは常にコンストラクタ
if is_override {
return Err(ParseError::UnexpectedToken {
expected: "method definition, not constructor after override keyword"
.to_string(),
found: TokenType::INIT,
line: self.current_token().line,
});
}
// コンストラクタの処理
self.advance(); // consume '('
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
must_advance!(self, _unused, "constructor parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
}
self.consume(TokenType::RPAREN)?;
let mut body = self.parse_block_statements()?;
self.skip_newlines();
// Method-level postfix catch/cleanup (gate)
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
{
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause {
exception_type: exc_ty,
variable_name: exc_var,
body: catch_body,
span: crate::ast::Span::unknown(),
});
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after method body".to_string(),
line,
});
}
}
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance();
Some(self.parse_block_statements()?)
} else {
None
};
// Wrap original body with TryCatch
body = vec![ASTNode::TryCatch {
try_body: body,
catch_clauses,
finally_body,
span: crate::ast::Span::unknown(),
}];
}
let constructor = ASTNode::FunctionDeclaration {
name: field_or_method.clone(),
params: params.clone(),
body,
is_static: false,
is_override: false, // コンストラクタは常に非オーバーライド
span: Span::unknown(),
};
// 🔥 init/引数数 形式でキーを作成(インタープリターと一致させる)
let constructor_key = format!("{}/{}", field_or_method, params.len());
constructors.insert(constructor_key, constructor);
continue;
}
}
// packキーワードの処理ビルトインBox継承用
if self.match_token(&TokenType::PACK) && self.peek_token() == &TokenType::LPAREN {
let field_or_method = "pack".to_string();
self.advance(); // consume 'pack'
// packは常にコンストラクタ
if is_override {
return Err(ParseError::UnexpectedToken {
expected: "method definition, not constructor after override keyword"
.to_string(),
found: TokenType::PACK,
line: self.current_token().line,
});
}
// packコンストラクタの処理
self.advance(); // consume '('
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
must_advance!(self, _unused, "pack parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
}
self.consume(TokenType::RPAREN)?;
let body = self.parse_block_statements()?;
let constructor = ASTNode::FunctionDeclaration {
name: field_or_method.clone(),
params: params.clone(),
body,
is_static: false,
is_override: false, // packは常に非オーバーライド
span: Span::unknown(),
};
// 🔥 pack/引数数 形式でキーを作成(インタープリターと一致させる)
let constructor_key = format!("{}/{}", field_or_method, params.len());
constructors.insert(constructor_key, constructor);
continue;
}
// birthキーワードの処理生命を与えるコンストラクタ
if self.match_token(&TokenType::BIRTH) && self.peek_token() == &TokenType::LPAREN {
let field_or_method = "birth".to_string();
self.advance(); // consume 'birth'
// birthは常にコンストラクタ
if is_override {
return Err(ParseError::UnexpectedToken {
expected: "method definition, not constructor after override keyword"
.to_string(),
found: TokenType::BIRTH,
line: self.current_token().line,
});
}
// birthコンストラクタの処理
self.advance(); // consume '('
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
must_advance!(self, _unused, "birth parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
}
self.consume(TokenType::RPAREN)?;
let body = self.parse_block_statements()?;
let constructor = ASTNode::FunctionDeclaration {
name: field_or_method.clone(),
params: params.clone(),
body,
is_static: false,
is_override: false, // birthは常に非オーバーライド
span: Span::unknown(),
};
// 🔥 birth/引数数 形式でキーを作成(インタープリターと一致させる)
let constructor_key = format!("{}/{}", field_or_method, params.len());
constructors.insert(constructor_key, constructor);
continue; continue;
} }
@ -447,7 +281,7 @@ impl NyashParser {
} }
} }
// 通常のフィールド名またはメソッド名を読み取り // 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type { if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
let field_or_method = field_or_method.clone(); let field_or_method = field_or_method.clone();
self.advance(); self.advance();
@ -490,35 +324,35 @@ impl NyashParser {
self.skip_newlines(); self.skip_newlines();
continue; continue;
} else if matches!(self.current_token().token_type, TokenType::IDENTIFIER(_)) { } else if matches!(self.current_token().token_type, TokenType::IDENTIFIER(_)) {
// 単行形式: public name[: Type] // 単行形式: public/private name[: Type] (= init | => expr | { ... }[postfix]) を委譲
let fname = let fname = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
if let TokenType::IDENTIFIER(n) = &self.current_token().token_type { n.clone()
n.clone()
} else {
unreachable!()
};
self.advance();
if self.match_token(&TokenType::COLON) {
self.advance(); // consume ':'
// 型名識別子を受理して破棄P0
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
self.advance();
} else {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "type name".to_string(),
line: self.current_token().line,
});
}
}
if field_or_method == "public" {
public_fields.push(fname.clone());
} else { } else {
private_fields.push(fname.clone()); unreachable!()
};
self.advance();
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
self,
fname.clone(),
&mut methods,
&mut fields,
)? {
if field_or_method == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
// プロパティ経路ではメソッド後置catch/cleanupを無効化する
last_method_name = None;
self.skip_newlines();
continue;
} else {
// 解析不能な場合は従来どおりフィールド扱い(後方互換の保険)
if field_or_method == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
fields.push(fname);
self.skip_newlines();
continue;
} }
fields.push(fname);
self.skip_newlines();
continue;
} else { } else {
// public/private の後に '{' でも識別子でもない // public/private の後に '{' でも識別子でもない
return Err(ParseError::UnexpectedToken { return Err(ParseError::UnexpectedToken {
@ -529,50 +363,40 @@ impl NyashParser {
} }
} }
// メソッドかフィールドかを判定 // Unified Members (header-first) gate: support once/birth_once via members::properties
if self.match_token(&TokenType::LPAREN) { if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") {
// メソッド定義 if crate::parser::declarations::box_def::members::properties::try_parse_unified_property(
self.advance(); // consume '(' self,
&field_or_method,
let mut params = Vec::new(); &mut methods,
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() { &mut birth_once_props,
must_advance!(self, _unused, "method parameter parsing"); )? {
last_method_name = None; // do not attach method-level postfix here
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type { self.skip_newlines();
params.push(param.clone()); continue;
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
} }
}
self.consume(TokenType::RPAREN)?; // メソッド or フィールド(委譲)
let body = self.parse_block_statements()?; if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
self,
let method = ASTNode::FunctionDeclaration { field_or_method.clone(),
name: field_or_method.clone(), is_override,
params, &birth_once_props,
body, )? {
is_static: false,
is_override,
span: Span::unknown(),
};
last_method_name = Some(field_or_method.clone()); last_method_name = Some(field_or_method.clone());
methods.insert(field_or_method, method); methods.insert(field_or_method, method);
} else { } else {
// フィールド定義P0: 型注釈 name: Type を受理して破棄 // フィールド or 統一メンバcomputed/once/birth_once header-first
let fname = field_or_method; let fname = field_or_method;
if self.match_token(&TokenType::COLON) { if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
self.advance(); // consume ':' self,
// 型名識別子を許可P0は保持せず破棄 fname,
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type { &mut methods,
self.advance(); &mut fields,
} )? {
continue;
} }
fields.push(fname);
} }
} else { } else {
return Err(ParseError::UnexpectedToken { return Err(ParseError::UnexpectedToken {
@ -590,6 +414,69 @@ impl NyashParser {
self.validate_override_methods(&name, parent, &methods)?; self.validate_override_methods(&name, parent, &methods)?;
} }
// birth_once 相互依存の簡易検出(宣言間の循環)
if crate::config::env::unified_members() {
// Collect birth_once compute bodies
use std::collections::{HashMap, HashSet};
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
for (mname, mast) in &methods {
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
if let ASTNode::FunctionDeclaration { body, .. } = mast {
birth_bodies.insert(prop.to_string(), body.clone());
}
}
}
// Build dependency graph: A -> {B | me.B used inside A}
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
for (p, body) in &birth_bodies {
let used = self.ast_collect_me_fields(body);
let mut set = HashSet::new();
for u in used {
if props.contains(&u) && u != *p {
set.insert(u);
}
}
deps.insert(p.clone(), set);
}
// Detect cycle via DFS
fn has_cycle(
node: &str,
deps: &HashMap<String, HashSet<String>>,
temp: &mut HashSet<String>,
perm: &mut HashSet<String>,
) -> bool {
if perm.contains(node) {
return false;
}
if !temp.insert(node.to_string()) {
return true; // back-edge
}
if let Some(ns) = deps.get(node) {
for n in ns {
if has_cycle(n, deps, temp, perm) {
return true;
}
}
}
temp.remove(node);
perm.insert(node.to_string());
false
}
let mut perm = HashSet::new();
let mut temp = HashSet::new();
for p in deps.keys() {
if has_cycle(p, &deps, &mut temp, &mut perm) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
line,
});
}
}
}
Ok(ASTNode::BoxDeclaration { Ok(ASTNode::BoxDeclaration {
name, name,
fields, fields,
@ -709,3 +596,71 @@ impl NyashParser {
}) })
} }
} }
impl NyashParser {
/// Minimal scan: does body contain `me.<field>` access (direct self-cycle guard)
fn ast_contains_me_field(&self, nodes: &Vec<ASTNode>, field: &str) -> bool {
fn scan(nodes: &Vec<ASTNode>, field: &str) -> bool {
for n in nodes {
match n {
ASTNode::FieldAccess { object, field: f, .. } => {
if f == field {
if let ASTNode::Me { .. } = object.as_ref() {
return true;
}
}
}
ASTNode::Program { statements, .. } => {
if scan(statements, field) { return true; }
}
ASTNode::If { then_body, else_body, .. } => {
if scan(then_body, field) { return true; }
if let Some(eb) = else_body { if scan(eb, field) { return true; } }
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
if scan(try_body, field) { return true; }
for c in catch_clauses { if scan(&c.body, field) { return true; } }
if let Some(fb) = finally_body { if scan(fb, field) { return true; } }
}
ASTNode::FunctionDeclaration { body, .. } => {
if scan(body, field) { return true; }
}
_ => {}
}
}
false
}
scan(nodes, field)
}
/// Collect all `me.<field>` accessed in nodes (flat set)
fn ast_collect_me_fields(&self, nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
use std::collections::HashSet;
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
for n in nodes {
match n {
ASTNode::FieldAccess { object, field, .. } => {
if let ASTNode::Me { .. } = object.as_ref() {
out.insert(field.clone());
}
}
ASTNode::Program { statements, .. } => scan(statements, out),
ASTNode::If { then_body, else_body, .. } => {
scan(then_body, out);
if let Some(eb) = else_body { scan(eb, out); }
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
scan(try_body, out);
for c in catch_clauses { scan(&c.body, out); }
if let Some(fb) = finally_body { scan(fb, out); }
}
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
_ => {}
}
}
}
let mut hs = HashSet::new();
scan(nodes, &mut hs);
hs
}
}