feat(parser): Phase 285A1.4 & A1.5 - Weak field sugar + Parser hang fix
A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax
Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md
Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -38,6 +38,8 @@ pub fn to_bool_vm(v: &VMValue) -> Result<bool, String> {
|
||||
Err(format!("cannot coerce BoxRef({}) to bool", b.type_name()))
|
||||
}
|
||||
VMValue::Future(_) => Err("cannot coerce Future to bool".to_string()),
|
||||
// Phase 285A0: WeakRef in boolean context is TypeError
|
||||
VMValue::WeakBox(_) => Err("WeakRef in boolean context - use upgrade() first".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +82,16 @@ pub fn eq_vm(a: &VMValue, b: &VMValue) -> bool {
|
||||
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
|
||||
.is_some()
|
||||
}
|
||||
// Phase 285A0: WeakBox equality
|
||||
(WeakBox(wa), WeakBox(wb)) => {
|
||||
match (wa.upgrade(), wb.upgrade()) {
|
||||
(Some(arc_a), Some(arc_b)) => Arc::ptr_eq(&arc_a, &arc_b),
|
||||
(None, None) => true, // Both dropped
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// WeakBox == Void when dropped
|
||||
(WeakBox(w), Void) | (Void, WeakBox(w)) => w.upgrade().is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -94,6 +106,7 @@ pub fn tag_of_vm(v: &VMValue) -> &'static str {
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::Void => "Void",
|
||||
VMValue::BoxRef(_) => "BoxRef",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -173,6 +173,7 @@ impl MirInterpreter {
|
||||
VMValue::Bool(_) => "BoolBox".to_string(),
|
||||
VMValue::Void => "<Void>".to_string(),
|
||||
VMValue::Future(_) => "<Future>".to_string(),
|
||||
VMValue::WeakBox(_) => "<WeakRef>".to_string(), // Phase 285A0
|
||||
};
|
||||
self.box_trace_emit_call(&cls, method, args.len());
|
||||
}
|
||||
@ -190,6 +191,7 @@ impl MirInterpreter {
|
||||
VMValue::String(_) => "String".to_string(),
|
||||
VMValue::Void => "Void".to_string(),
|
||||
VMValue::Future(_) => "Future".to_string(),
|
||||
VMValue::WeakBox(_) => "WeakRef".to_string(), // Phase 285A0
|
||||
};
|
||||
eprintln!("[vm-trace] length dispatch recv_type={}", type_name);
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ pub(super) fn try_handle_object_fields(
|
||||
VV::Void => NV::Void,
|
||||
VV::Future(_) => NV::Void, // not expected in fields
|
||||
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
|
||||
VV::WeakBox(_) => NV::Void, // Phase 285A0: WeakBox not expected in this context
|
||||
}
|
||||
}
|
||||
fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue {
|
||||
@ -94,6 +95,7 @@ pub(super) fn try_handle_object_fields(
|
||||
Ok(VMValue::String(_)) => "String".to_string(),
|
||||
Ok(VMValue::Void) => "Void".to_string(),
|
||||
Ok(VMValue::Future(_)) => "Future".to_string(),
|
||||
Ok(VMValue::WeakBox(_)) => "WeakRef".to_string(), // Phase 285A0
|
||||
Err(_) => "<err>".to_string(),
|
||||
};
|
||||
eprintln!("[vm-trace] getField recv_kind={}", rk);
|
||||
@ -229,6 +231,7 @@ pub(super) fn try_handle_object_fields(
|
||||
VMValue::BoxRef(_) => "BoxRef",
|
||||
VMValue::Void => "Void",
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
this.box_trace_emit_get(&inst.class_name, &fname, kind);
|
||||
}
|
||||
@ -339,6 +342,7 @@ pub(super) fn try_handle_object_fields(
|
||||
VMValue::BoxRef(b) => b.type_name(),
|
||||
VMValue::Void => "Void",
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
// class name unknown here; use receiver type name if possible
|
||||
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
|
||||
@ -438,6 +442,7 @@ pub(super) fn try_handle_object_fields(
|
||||
VMValue::BoxRef(b) => b.type_name(),
|
||||
VMValue::Void => "Void",
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
|
||||
VMValue::BoxRef(b) => {
|
||||
|
||||
@ -458,6 +458,7 @@ impl MirInterpreter {
|
||||
VMValue::Void => "Void",
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::BoxRef(bx) => bx.type_name(),
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
|
||||
// 2. Lookup type in TypeRegistry and get slot
|
||||
|
||||
@ -24,6 +24,7 @@ mod externals;
|
||||
mod memory;
|
||||
mod misc;
|
||||
mod type_ops;
|
||||
mod weak; // Phase 285A0: WeakRef handlers
|
||||
|
||||
impl MirInterpreter {
|
||||
pub(super) fn execute_instruction(&mut self, inst: &MirInstruction) -> Result<(), VMError> {
|
||||
@ -118,6 +119,17 @@ impl MirInterpreter {
|
||||
};
|
||||
self.regs.insert(*dst, selected_val);
|
||||
}
|
||||
// Phase 285A0: WeakRef handlers (delegated to weak.rs)
|
||||
MirInstruction::WeakNew { dst, box_val } => {
|
||||
self.handle_weak_new(*dst, *box_val)?;
|
||||
}
|
||||
MirInstruction::WeakLoad { dst, weak_ref } => {
|
||||
self.handle_weak_load(*dst, *weak_ref)?;
|
||||
}
|
||||
MirInstruction::WeakRef { dst, op, value } => match op {
|
||||
WeakRefOp::New => self.handle_weak_new(*dst, *value)?,
|
||||
WeakRefOp::Load => self.handle_weak_load(*dst, *value)?,
|
||||
}
|
||||
MirInstruction::BarrierRead { .. }
|
||||
| MirInstruction::BarrierWrite { .. }
|
||||
| MirInstruction::Barrier { .. }
|
||||
|
||||
59
src/backend/mir_interpreter/handlers/weak.rs
Normal file
59
src/backend/mir_interpreter/handlers/weak.rs
Normal file
@ -0,0 +1,59 @@
|
||||
//! Phase 285A0: WeakRef handlers - 弱参照の作成とアップグレード
|
||||
//!
|
||||
//! SSOT: docs/reference/language/lifecycle.md:179
|
||||
//!
|
||||
//! WeakRef は強参照サイクルを避けるための非所有参照です。
|
||||
//! - `weak(x)` → WeakNew: BoxRef から WeakRef を作成
|
||||
//! - `w.weak_to_strong()` → WeakLoad: WeakRef から BoxRef へアップグレード(失敗時は null/Void)
|
||||
|
||||
use super::*;
|
||||
|
||||
impl MirInterpreter {
|
||||
/// WeakNew: BoxRef → WeakRef 変換
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dst` - 結果を格納する ValueId
|
||||
/// * `box_val` - 変換元の Box ValueId
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<(), VMError>` - 成功時は Ok、失敗時は Err
|
||||
///
|
||||
/// # Errors
|
||||
/// * `box_val` が BoxRef でない場合はエラー
|
||||
pub(super) fn handle_weak_new(
|
||||
&mut self,
|
||||
dst: ValueId,
|
||||
box_val: ValueId,
|
||||
) -> Result<(), VMError> {
|
||||
let box_value = self.reg_load(box_val)?;
|
||||
let weak_value = box_value
|
||||
.downgrade_to_weak()
|
||||
.ok_or_else(|| self.err_invalid("WeakNew: target is not a Box"))?;
|
||||
self.regs.insert(dst, weak_value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// WeakLoad: WeakRef → BoxRef | null (= Void) アップグレード
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dst` - 結果を格納する ValueId
|
||||
/// * `weak_ref` - WeakRef ValueId
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<(), VMError>` - 成功時は Ok、失敗時は Err
|
||||
///
|
||||
/// # Note
|
||||
/// - SSOT: upgrade failure returns null (= Void in VM) - lifecycle.md:179
|
||||
/// - ターゲットが既に drop された場合や Dead 状態の場合は Void を返す
|
||||
pub(super) fn handle_weak_load(
|
||||
&mut self,
|
||||
dst: ValueId,
|
||||
weak_ref: ValueId,
|
||||
) -> Result<(), VMError> {
|
||||
let weak_value = self.reg_load(weak_ref)?;
|
||||
// upgrade_weak() が None を返した場合は Void(null)
|
||||
let result = weak_value.upgrade_weak().unwrap_or(VMValue::Void);
|
||||
self.regs.insert(dst, result);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -436,6 +436,7 @@ impl MirInterpreter {
|
||||
("BoxRef", b.type_name().to_string(), tag)
|
||||
}
|
||||
}
|
||||
VMValue::WeakBox(_) => ("WeakRef", "".to_string(), None), // Phase 285A0
|
||||
};
|
||||
if let Some(tag) = nullish {
|
||||
eprintln!(
|
||||
|
||||
@ -14,7 +14,7 @@ pub(super) use crate::backend::abi_util::{eq_vm, to_bool_vm};
|
||||
pub(super) use crate::backend::vm::{VMError, VMValue};
|
||||
pub(super) use crate::mir::{
|
||||
BasicBlockId, BinaryOp, Callee, CompareOp, ConstValue, MirFunction, MirInstruction, MirModule,
|
||||
MirType, TypeOpKind, ValueId,
|
||||
MirType, TypeOpKind, ValueId, WeakRefOp,
|
||||
};
|
||||
|
||||
mod exec;
|
||||
|
||||
@ -61,6 +61,7 @@ impl MirInterpreter {
|
||||
))
|
||||
}
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
Err(self.err_type_mismatch("load_as_int", "Integer", type_name))
|
||||
}
|
||||
@ -93,6 +94,7 @@ impl MirInterpreter {
|
||||
return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name()))
|
||||
}
|
||||
VMValue::Future(_) => "Future",
|
||||
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
|
||||
};
|
||||
Err(self.err_type_mismatch("load_as_bool", "Bool", type_name))
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
use crate::ast::Span;
|
||||
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
use crate::mir::{BasicBlockId, ConstValue};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
/// VM execution error
|
||||
#[derive(Debug)]
|
||||
@ -90,7 +90,7 @@ impl std::fmt::Display for VMError {
|
||||
impl std::error::Error for VMError {}
|
||||
|
||||
/// VM value representation
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub enum VMValue {
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
@ -99,6 +99,30 @@ pub enum VMValue {
|
||||
Future(crate::boxes::future::FutureBox),
|
||||
Void,
|
||||
BoxRef(Arc<dyn NyashBox>),
|
||||
/// Phase 285A0: Weak reference to a Box (non-owning)
|
||||
WeakBox(Weak<dyn NyashBox>),
|
||||
}
|
||||
|
||||
// Manual Debug implementation for WeakBox
|
||||
impl std::fmt::Debug for VMValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VMValue::Integer(i) => write!(f, "Integer({})", i),
|
||||
VMValue::Float(v) => write!(f, "Float({})", v),
|
||||
VMValue::Bool(b) => write!(f, "Bool({})", b),
|
||||
VMValue::String(s) => write!(f, "String({:?})", s),
|
||||
VMValue::Future(_) => write!(f, "Future(...)"),
|
||||
VMValue::Void => write!(f, "Void"),
|
||||
VMValue::BoxRef(arc) => write!(f, "BoxRef({})", arc.type_name()),
|
||||
VMValue::WeakBox(weak) => {
|
||||
if weak.upgrade().is_some() {
|
||||
write!(f, "WeakBox(alive)")
|
||||
} else {
|
||||
write!(f, "WeakBox(dropped)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manual PartialEq implementation to avoid requiring PartialEq on FutureBox
|
||||
@ -112,6 +136,14 @@ impl PartialEq for VMValue {
|
||||
(VMValue::Void, VMValue::Void) => true,
|
||||
(VMValue::Future(_), VMValue::Future(_)) => false,
|
||||
(VMValue::BoxRef(_), VMValue::BoxRef(_)) => false,
|
||||
// Phase 285A0: WeakBox equality (compare by pointer if both alive)
|
||||
(VMValue::WeakBox(a), VMValue::WeakBox(b)) => {
|
||||
match (a.upgrade(), b.upgrade()) {
|
||||
(Some(arc_a), Some(arc_b)) => Arc::ptr_eq(&arc_a, &arc_b),
|
||||
(None, None) => true, // Both dropped
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -128,6 +160,14 @@ impl VMValue {
|
||||
VMValue::Future(f) => Box::new(f.clone()),
|
||||
VMValue::Void => Box::new(VoidBox::new()),
|
||||
VMValue::BoxRef(arc_box) => arc_box.share_box(),
|
||||
VMValue::WeakBox(weak) => {
|
||||
// Upgrade or return void if dropped
|
||||
if let Some(arc) = weak.upgrade() {
|
||||
arc.share_box()
|
||||
} else {
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +181,32 @@ impl VMValue {
|
||||
VMValue::Future(f) => f.to_string_box().value,
|
||||
VMValue::Void => "void".to_string(),
|
||||
VMValue::BoxRef(arc_box) => arc_box.to_string_box().value,
|
||||
VMValue::WeakBox(weak) => {
|
||||
if weak.upgrade().is_some() {
|
||||
"WeakRef(alive)".to_string()
|
||||
} else {
|
||||
"WeakRef(dropped)".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 285A0: Downgrade a strong BoxRef to a weak reference
|
||||
/// Returns None if not a BoxRef
|
||||
pub fn downgrade_to_weak(&self) -> Option<VMValue> {
|
||||
match self {
|
||||
VMValue::BoxRef(arc) => Some(VMValue::WeakBox(Arc::downgrade(arc))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 285A0: Upgrade a weak reference to a strong BoxRef
|
||||
/// Returns Some(BoxRef) if target is alive, None if dropped
|
||||
pub fn upgrade_weak(&self) -> Option<VMValue> {
|
||||
match self {
|
||||
VMValue::WeakBox(weak) => weak.upgrade().map(VMValue::BoxRef),
|
||||
// Non-weak values: return self (already strong)
|
||||
_ => Some(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,6 +252,10 @@ impl VMValue {
|
||||
VMValue::Float(f) => Ok(*f != 0.0),
|
||||
VMValue::String(s) => Ok(!s.is_empty()),
|
||||
VMValue::Future(_) => Ok(true),
|
||||
// Phase 285A0: WeakBox truthiness is TypeError (SSOT: types.md:26)
|
||||
VMValue::WeakBox(_) => Err(VMError::TypeError(
|
||||
"WeakRef cannot be used in boolean context; use upgrade() first".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,7 +53,13 @@ impl MirBuilder {
|
||||
return self.build_str_normalization(arg_values[0]);
|
||||
}
|
||||
|
||||
// 5. Determine call route (unified vs legacy)
|
||||
// 5. Phase 285A0: weak(x) → WeakNew lowering
|
||||
// SSOT: docs/reference/language/lifecycle.md:171 - weak(x) returns WeakRef
|
||||
if name == "weak" && arg_values.len() == 1 {
|
||||
return self.emit_weak_new(arg_values[0]);
|
||||
}
|
||||
|
||||
// 6. Determine call route (unified vs legacy)
|
||||
let use_unified = super::call_unified::is_unified_call_enabled()
|
||||
&& (super::super::call_resolution::is_builtin_function(&name)
|
||||
|| super::super::call_resolution::is_extern_function(&name));
|
||||
|
||||
@ -208,7 +208,7 @@ fn has_control_flow_recursive_p3(node: &ASTNode) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{LiteralValue, Span};
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn test_extract_if_phi_success() {
|
||||
|
||||
@ -200,6 +200,17 @@ impl MirBuilder {
|
||||
method: String,
|
||||
arguments: &[ASTNode],
|
||||
) -> Result<ValueId, String> {
|
||||
// Phase 285A0.1: WeakRef.weak_to_strong() → WeakLoad
|
||||
// SSOT: docs/reference/language/lifecycle.md:179 - weak_to_strong() returns Box | null
|
||||
if method == "weak_to_strong" && arguments.is_empty() {
|
||||
return self.emit_weak_load(object_value);
|
||||
}
|
||||
|
||||
// Phase 285A0.1: upgrade() is deprecated - Fail-Fast
|
||||
if method == "upgrade" && arguments.is_empty() {
|
||||
return Err("WeakRef uses weak_to_strong(), not upgrade()".to_string());
|
||||
}
|
||||
|
||||
// Build argument values
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in arguments {
|
||||
|
||||
@ -340,6 +340,14 @@ impl JoinValue {
|
||||
crate::backend::VMValue::Future(_) => {
|
||||
Err(JoinIrOpError::new("Future not supported in JoinValue"))
|
||||
}
|
||||
// Phase 285A0: Upgrade WeakBox before conversion
|
||||
crate::backend::VMValue::WeakBox(w) => {
|
||||
if let Some(arc) = w.upgrade() {
|
||||
Ok(JoinValue::BoxRef(arc))
|
||||
} else {
|
||||
Ok(JoinValue::Unit) // Dropped weak → Unit (null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
* Extracted from parser/mod.rs as part of modularization
|
||||
*/
|
||||
|
||||
pub(crate) mod params;
|
||||
|
||||
use super::ParseError;
|
||||
use crate::ast::Span;
|
||||
use crate::tokenizer::{Token, TokenType};
|
||||
92
src/parser/common/params.rs
Normal file
92
src/parser/common/params.rs
Normal file
@ -0,0 +1,92 @@
|
||||
/*!
|
||||
* Parameter Parsing Utilities
|
||||
*
|
||||
* Phase 285A1.5: Common helper for parameter name list parsing
|
||||
* Prevents infinite parser hangs on unsupported parameter type annotations
|
||||
*/
|
||||
|
||||
use super::ParserUtils;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
/// Parse parameter name list with Fail-Fast on unexpected tokens
|
||||
///
|
||||
/// Parses: IDENT (',' IDENT)*
|
||||
/// Rejects: Type annotations, unexpected tokens, malformed comma sequences
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `p` - Mutable reference to NyashParser
|
||||
/// * `context` - Context string for error messages ("method", "constructor", "function", etc.)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(Vec<String>)` - List of parameter names
|
||||
/// * `Err(ParseError)` - Parse error with context-aware message
|
||||
///
|
||||
/// # Features
|
||||
/// * Progress-zero detection: Tracks token position, errors if stuck
|
||||
/// * Explicit token handling: All token types explicitly matched
|
||||
/// * Fail-Fast: Either advances or errors (no infinite loop possible)
|
||||
/// * Unified error messages: Single source of truth for error text
|
||||
pub(crate) fn parse_param_name_list(
|
||||
p: &mut NyashParser,
|
||||
context: &str,
|
||||
) -> Result<Vec<String>, ParseError> {
|
||||
let mut params = Vec::new();
|
||||
let mut last_token_position: Option<(usize, usize)> = None;
|
||||
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
// Progress-zero detection: same position twice → infinite loop risk
|
||||
let current_position = p.current_position();
|
||||
if let Some(last_pos) = last_token_position {
|
||||
if current_position == last_pos {
|
||||
return Err(ParseError::InfiniteLoop {
|
||||
location: format!("{} parameter list", context),
|
||||
token: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
last_token_position = Some(current_position);
|
||||
|
||||
match &p.current_token().token_type {
|
||||
TokenType::IDENTIFIER(param) => {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
} else if !p.match_token(&TokenType::RPAREN) {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"',' or ')' in {} parameter list. \
|
||||
Note: Parameter type annotations are not supported.",
|
||||
context
|
||||
),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
TokenType::COMMA => {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: TokenType::COMMA,
|
||||
expected: format!("parameter name in {} parameter list", context),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"parameter name or ')' in {} parameter list. \
|
||||
Note: Parameter type annotations are not supported.",
|
||||
context
|
||||
),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
@ -23,17 +23,10 @@ pub(crate) fn try_parse_constructor(
|
||||
let name = "init".to_string();
|
||||
p.advance(); // consume 'init'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "constructor parameter parsing");
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (init)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let mut body = p.parse_block_statements()?;
|
||||
// Optional postfix catch/cleanup (method-level gate)
|
||||
@ -97,17 +90,10 @@ pub(crate) fn try_parse_constructor(
|
||||
let name = "pack".to_string();
|
||||
p.advance(); // consume 'pack'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "pack parameter parsing");
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (pack)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let body = p.parse_block_statements()?;
|
||||
let node = ASTNode::FunctionDeclaration {
|
||||
@ -134,17 +120,10 @@ pub(crate) fn try_parse_constructor(
|
||||
let name = "birth".to_string();
|
||||
p.advance(); // consume 'birth'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "birth parameter parsing");
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (birth)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let body = p.parse_block_statements()?;
|
||||
let node = ASTNode::FunctionDeclaration {
|
||||
|
||||
@ -151,6 +151,35 @@ pub(crate) fn try_parse_visibility_block_or_single(
|
||||
p.consume(TokenType::RBRACE)?;
|
||||
return Ok(true);
|
||||
}
|
||||
// Phase 285A1.4: Sugar syntax - public weak parent, private weak parent
|
||||
if p.match_token(&TokenType::WEAK) {
|
||||
p.advance(); // consume WEAK only
|
||||
|
||||
// Read field name (reuse existing pattern)
|
||||
if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type {
|
||||
let fname = fname.clone();
|
||||
p.advance(); // consume IDENTIFIER
|
||||
|
||||
// Delegate to existing weak field parser (handles type annotation, etc.)
|
||||
parse_weak_field(p, fname.clone(), methods, fields, weak_fields)?;
|
||||
|
||||
// Register with visibility tracking
|
||||
if visibility == "public" {
|
||||
public_fields.push(fname);
|
||||
} else {
|
||||
private_fields.push(fname);
|
||||
}
|
||||
|
||||
*last_method_name = None; // Reset method context (Phase 285A1.4)
|
||||
return Ok(true);
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "field name after 'weak' in visibility context".to_string(),
|
||||
found: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
|
||||
let fname = n.clone();
|
||||
p.advance();
|
||||
|
||||
@ -17,17 +17,8 @@ pub(crate) fn try_parse_method(
|
||||
}
|
||||
p.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "method parameter parsing");
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "method")?;
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let mut body = p.parse_block_statements()?;
|
||||
|
||||
|
||||
@ -313,6 +313,19 @@ impl NyashParser {
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
// Phase 285A0: weak(expr) → WeakRef creation
|
||||
// SSOT: docs/reference/language/lifecycle.md:171
|
||||
TokenType::WEAK => {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let argument = self.parse_expression()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
Ok(ASTNode::FunctionCall {
|
||||
name: "weak".to_string(),
|
||||
arguments: vec![argument],
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::InvalidExpression { line })
|
||||
|
||||
@ -29,27 +29,9 @@ impl NyashParser {
|
||||
|
||||
// パラメータリストをパース
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "function declaration 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();
|
||||
}
|
||||
} else if !self.match_token(&TokenType::RPAREN) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(self, "function")?;
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
|
||||
@ -67,27 +67,9 @@ impl NyashParser {
|
||||
|
||||
// パラメータリストをパース
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "static function 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();
|
||||
}
|
||||
} else if !self.match_token(&TokenType::RPAREN) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(self, "static method")?;
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
|
||||
@ -642,6 +642,9 @@ impl NyashRunner {
|
||||
.debug(&format!("[runner/vm] exit_code={}", exit_code));
|
||||
}
|
||||
|
||||
// Phase 285: Emit leak report before exit (if enabled)
|
||||
crate::runtime::leak_tracker::emit_leak_report();
|
||||
|
||||
// Exit with the return value as exit code
|
||||
process::exit(exit_code);
|
||||
}
|
||||
|
||||
@ -511,6 +511,14 @@ pub extern "C" fn nyrt_host_call_slot(
|
||||
crate::backend::vm::VMValue::Void => {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
// Phase 285A0: WeakBox upgrade or void
|
||||
crate::backend::vm::VMValue::WeakBox(w) => {
|
||||
if let Some(arc) = w.upgrade() {
|
||||
arc.share_box()
|
||||
} else {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
let out = map.has(key_box);
|
||||
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
|
||||
@ -539,6 +547,14 @@ pub extern "C" fn nyrt_host_call_slot(
|
||||
crate::backend::vm::VMValue::Void => {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
// Phase 285A0: WeakBox upgrade or void
|
||||
crate::backend::vm::VMValue::WeakBox(w) => {
|
||||
if let Some(arc) = w.upgrade() {
|
||||
arc.share_box()
|
||||
} else {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
let out = map.get(key_box);
|
||||
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
|
||||
@ -567,6 +583,14 @@ pub extern "C" fn nyrt_host_call_slot(
|
||||
crate::backend::vm::VMValue::Void => {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
// Phase 285A0: WeakBox upgrade or void
|
||||
crate::backend::vm::VMValue::WeakBox(w) => {
|
||||
if let Some(arc) = w.upgrade() {
|
||||
arc.share_box()
|
||||
} else {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
let val_box: Box<dyn NyashBox> = match argv[1].clone() {
|
||||
crate::backend::vm::VMValue::Integer(i) => {
|
||||
@ -586,6 +610,14 @@ pub extern "C" fn nyrt_host_call_slot(
|
||||
crate::backend::vm::VMValue::Void => {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
// Phase 285A0: WeakBox upgrade or void
|
||||
crate::backend::vm::VMValue::WeakBox(w) => {
|
||||
if let Some(arc) = w.upgrade() {
|
||||
arc.share_box()
|
||||
} else {
|
||||
Box::new(crate::box_trait::VoidBox::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
let out = map.set(key_box, val_box);
|
||||
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
|
||||
|
||||
@ -1,10 +1,41 @@
|
||||
//! Leak Tracker - Exit-time diagnostics for strong references still held
|
||||
//!
|
||||
//! Phase 285: Extended to report all global roots (modules, host_handles, plugin boxes).
|
||||
//!
|
||||
//! ## Environment Variable
|
||||
//!
|
||||
//! - `NYASH_LEAK_LOG=1` - Summary counts only
|
||||
//! - `NYASH_LEAK_LOG=2` - Verbose (include names/entries, truncated to first 10)
|
||||
//!
|
||||
//! ## Output Format
|
||||
//!
|
||||
//! ```text
|
||||
//! [lifecycle/leak] Roots still held at exit:
|
||||
//! [lifecycle/leak] modules: 3
|
||||
//! [lifecycle/leak] host_handles: 5
|
||||
//! [lifecycle/leak] plugin_boxes: 2
|
||||
//! ```
|
||||
|
||||
use crate::runtime::get_global_ring0;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static ENABLED: Lazy<bool> =
|
||||
Lazy::new(|| std::env::var("NYASH_LEAK_LOG").unwrap_or_default() == "1");
|
||||
/// Leak log level: 0 = off, 1 = summary, 2 = verbose
|
||||
static LEVEL: Lazy<u8> = Lazy::new(|| {
|
||||
match std::env::var("NYASH_LEAK_LOG")
|
||||
.unwrap_or_default()
|
||||
.as_str()
|
||||
{
|
||||
"1" => 1,
|
||||
"2" => 2,
|
||||
_ => 0,
|
||||
}
|
||||
});
|
||||
|
||||
/// Backward compatibility: enabled if level >= 1
|
||||
static ENABLED: Lazy<bool> = Lazy::new(|| *LEVEL >= 1);
|
||||
|
||||
static LEAKS: Lazy<Mutex<HashMap<(String, u32), &'static str>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
@ -38,21 +69,97 @@ impl Drop for Reporter {
|
||||
if !*ENABLED {
|
||||
return;
|
||||
}
|
||||
let m = LEAKS.lock().unwrap();
|
||||
if m.is_empty() {
|
||||
return;
|
||||
}
|
||||
get_global_ring0().log.warn(&format!(
|
||||
"[leak] Detected {} non-finalized plugin boxes:",
|
||||
m.len()
|
||||
));
|
||||
for ((ty, id), _) in m.iter() {
|
||||
get_global_ring0().log.warn(&format!(
|
||||
" - {}(id={}) not finalized (missing fini or scope)",
|
||||
ty, id
|
||||
));
|
||||
}
|
||||
emit_leak_report();
|
||||
}
|
||||
}
|
||||
|
||||
static REPORTER: Lazy<Reporter> = Lazy::new(|| Reporter);
|
||||
|
||||
/// Emit exit-time leak report (Phase 285)
|
||||
///
|
||||
/// Called automatically on program exit via Reporter::drop.
|
||||
/// Can also be called manually for testing.
|
||||
pub fn emit_leak_report() {
|
||||
let level = *LEVEL;
|
||||
if level == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let ring0 = get_global_ring0();
|
||||
|
||||
// Collect root counts
|
||||
let modules = crate::runtime::modules_registry::snapshot_names_and_strings();
|
||||
let host_handles = crate::runtime::host_handles::snapshot();
|
||||
let plugin_boxes = LEAKS.lock().map(|m| m.len()).unwrap_or(0);
|
||||
|
||||
let modules_count = modules.len();
|
||||
let host_handles_count = host_handles.len();
|
||||
|
||||
// Only print if there's something to report
|
||||
if modules_count == 0 && host_handles_count == 0 && plugin_boxes == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Summary header
|
||||
ring0
|
||||
.log
|
||||
.warn("[lifecycle/leak] Roots still held at exit:");
|
||||
|
||||
// Summary counts
|
||||
if modules_count > 0 {
|
||||
ring0
|
||||
.log
|
||||
.warn(&format!("[lifecycle/leak] modules: {}", modules_count));
|
||||
}
|
||||
if host_handles_count > 0 {
|
||||
ring0.log.warn(&format!(
|
||||
"[lifecycle/leak] host_handles: {}",
|
||||
host_handles_count
|
||||
));
|
||||
}
|
||||
if plugin_boxes > 0 {
|
||||
ring0
|
||||
.log
|
||||
.warn(&format!("[lifecycle/leak] plugin_boxes: {}", plugin_boxes));
|
||||
}
|
||||
|
||||
// Verbose details (level 2)
|
||||
if level >= 2 {
|
||||
const MAX_ENTRIES: usize = 10;
|
||||
|
||||
// Module names
|
||||
if !modules.is_empty() {
|
||||
ring0.log.warn("[lifecycle/leak] module names:");
|
||||
for (i, (name, _value)) in modules.iter().take(MAX_ENTRIES).enumerate() {
|
||||
ring0
|
||||
.log
|
||||
.warn(&format!("[lifecycle/leak] [{}] {}", i, name));
|
||||
}
|
||||
if modules.len() > MAX_ENTRIES {
|
||||
ring0.log.warn(&format!(
|
||||
"[lifecycle/leak] ... and {} more",
|
||||
modules.len() - MAX_ENTRIES
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin box details
|
||||
if plugin_boxes > 0 {
|
||||
ring0.log.warn("[lifecycle/leak] plugin box details:");
|
||||
if let Ok(m) = LEAKS.lock() {
|
||||
for (i, ((ty, id), _)) in m.iter().take(MAX_ENTRIES).enumerate() {
|
||||
ring0.log.warn(&format!(
|
||||
"[lifecycle/leak] [{}] {}(id={}) not finalized",
|
||||
i, ty, id
|
||||
));
|
||||
}
|
||||
if m.len() > MAX_ENTRIES {
|
||||
ring0.log.warn(&format!(
|
||||
"[lifecycle/leak] ... and {} more",
|
||||
m.len() - MAX_ENTRIES
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user