feat(phase285w): Implement weak x unary operator syntax

Phase 285W-Syntax-0: Migrate weak reference syntax from function call
to unary operator for consistency and clarity.

**Changes**:
- Parser: Add UnaryOperator::Weak variant and parse_unary() handling
- MIR: Lower UnaryOp::Weak to emit_weak_new() (reuses existing path)
- AST: Add Weak to UnaryOperator enum + Display/JSON support
- Tests: Migrate 8 files from `weak(x)` to `weak x` syntax
  - 7 .hako test files updated
  - 1 smoke test shell script updated
- Cleanup: Remove obsolete weak(x) parser/MIR special cases
- Docs: Update Phase 285 README

**Syntax Change**:
- Old: `local w = weak(x)`  (function call)
- New: `local w = weak x`   (unary operator)

**Validation**: All migrated phase285* smoke tests pass (4/4 relevant)

**SSOT**: docs/reference/language/lifecycle.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-24 17:21:21 +09:00
parent 5b2b5528a5
commit 9227673ef7
17 changed files with 73 additions and 44 deletions

View File

@ -0,0 +1,15 @@
// Phase 285LLVM-1.3: InstanceBox field access parity
// Goal: VM/LLVM で同じ出力になる
box SomeBox {
x
}
static box Main {
main() {
local sb = new SomeBox()
sb.x = 42
print(sb.x) // Expected: 42
return 0
}
}

View File

@ -3,7 +3,7 @@ box Node {
name
setParent(p) {
me.parent = weak(p) // ✅ Explicit weak() required
me.parent = weak p // ✅ Explicit weak operator (Phase 285W-Syntax-0)
}
}

View File

@ -1,7 +1,7 @@
// Phase 285A0.1: WeakRef basic test
// SSOT: docs/reference/language/lifecycle.md:179 - weak(x)/weak_to_strong()
// Phase 285W-Syntax-0: WeakRef basic test with weak x syntax
// SSOT: docs/reference/language/lifecycle.md:179 - weak <expr>/weak_to_strong()
//
// Test: weak(x) creates WeakRef, weak_to_strong() returns Box when alive
// Test: weak x creates WeakRef, weak_to_strong() returns Box when alive
// Note: Full drop semantics test deferred (needs GC/scope analysis)
// VM: PASS expected
// LLVM: SKIP (Phase 285A1)
@ -14,7 +14,7 @@ static box Main {
main() {
local x = new SomeBox()
x.x = 42
local w = weak(x)
local w = weak x
// Test 1: weak_to_strong should succeed while x is alive
local y = w.weak_to_strong()
@ -23,9 +23,9 @@ static box Main {
return 1
}
// Test 2: verify weak_to_strong returns same box
// Test 2: verify weak_to_strong returns same object (identity)
if y.x != 42 {
print("ng: weak_to_strong value differs from original")
print("ng: weak_to_strong returned different object (expected x=42)")
return 1
}

View File

@ -8,7 +8,7 @@ static box Main {
main() {
local n1 = new Node()
local n2 = new Node()
n1.parent = weak(n2) // ✅ Explicit weak()
n1.parent = weak n2 // ✅ Explicit weak operator (Phase 285W-Syntax-0)
print("OK: legacy init { weak parent } syntax still works")
return 0
}

View File

@ -6,8 +6,8 @@ static box Main {
main() {
local n1 = new Node()
local n2 = new Node()
n1.parent = weak(n2) // ✅ Explicit weak()
print("OK: explicit weak()")
n1.parent = weak n2 // ✅ Explicit weak operator (Phase 285W-Syntax-0)
print("OK: explicit weak operator")
return 0
}
}

View File

@ -7,7 +7,7 @@ static box Main {
main() {
local n1 = new Node()
local n2 = new Node()
n1.parent = weak(n2)
n1.parent = weak n2
n1.sibling = n1.parent // ✅ WeakRef → WeakRef
print("OK: weak field transfer")
return 0

View File

@ -7,7 +7,7 @@ box Node {
}
setParent(p) {
me.parent = weak(p)
me.parent = weak p
}
getName() {

View File

@ -7,8 +7,8 @@ static box Main {
main() {
local n1 = new Node()
local n2 = new Node()
n1.parent = weak(n2) // ✅ public weak field
n1.delegate = weak(n2) // ✅ public weak field
n1.parent = weak n2 // ✅ public weak field
n1.delegate = weak n2 // ✅ public weak field
print("OK: visibility block with weak")
return 0
}

View File

@ -10,8 +10,10 @@ Status: In progress (A1 series implemented; LLVM sub-phases ongoing)
| 285LLVM-1.2 | ✅ COMPLETE | WeakRef基本動作identity保留 (2025-12-24) |
| 285LLVM-1.3 | ✅ COMPLETE | InstanceBox Field Access (getField/setField) (2025-12-24) |
| **285LLVM-1.4** | ✅ **COMPLETE** | **print Handle Resolution (型タグ伝播)** (2025-12-24) |
| **285W-Syntax-0** | ✅ **COMPLETE** | **weak文法SSOT確定 (weak x unary operator)** (2025-12-24) |
**LLVM Details**: See [phase-285llvm-1.3-verification-report.md](phase-285llvm-1.3-verification-report.md)
**Syntax Change**: Phase 285W-Syntax-0 migrates from `weak(x)` function call to `weak x` unary operator
---

View File

@ -261,6 +261,7 @@ pub enum UnaryOperator {
Minus, // -x
Not, // not x / !x
BitNot, // ~x
Weak, // weak x (Phase 285W-Syntax-0)
}
/// 二項演算子の種類
@ -292,6 +293,7 @@ impl fmt::Display for UnaryOperator {
UnaryOperator::Minus => "-",
UnaryOperator::Not => "not",
UnaryOperator::BitNot => "~",
UnaryOperator::Weak => "weak",
};
write!(f, "{}", symbol)
}

View File

@ -560,6 +560,7 @@ fn un_to_str(op: &UnaryOperator) -> &'static str {
UnaryOperator::Minus => "-",
UnaryOperator::Not => "not",
UnaryOperator::BitNot => "~",
UnaryOperator::Weak => "weak", // Phase 285W-Syntax-0
}
}
@ -568,6 +569,7 @@ fn str_to_un(s: &str) -> Option<UnaryOperator> {
"-" => UnaryOperator::Minus,
"not" => UnaryOperator::Not,
"~" => UnaryOperator::BitNot,
"weak" => UnaryOperator::Weak, // Phase 285W-Syntax-0
_ => return None,
})
}

View File

@ -53,13 +53,7 @@ impl MirBuilder {
return self.build_str_normalization(arg_values[0]);
}
// 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)
// 5. 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));

View File

@ -74,12 +74,23 @@ impl super::MirBuilder {
ASTNode::UnaryOp {
operator, operand, ..
} => {
let op_string = match operator {
crate::ast::UnaryOperator::Minus => "-".to_string(),
crate::ast::UnaryOperator::Not => "not".to_string(),
crate::ast::UnaryOperator::BitNot => "~".to_string(),
};
self.build_unary_op(op_string, *operand)
match operator {
// Phase 285W-Syntax-0: weak <expr> → WeakNew
crate::ast::UnaryOperator::Weak => {
let box_val = self.build_expression_impl(*operand)?;
self.emit_weak_new(box_val)
}
// Traditional unary operators
_ => {
let op_string = match operator {
crate::ast::UnaryOperator::Minus => "-".to_string(),
crate::ast::UnaryOperator::Not => "not".to_string(),
crate::ast::UnaryOperator::BitNot => "~".to_string(),
crate::ast::UnaryOperator::Weak => unreachable!("handled above"),
};
self.build_unary_op(op_string, *operand)
}
}
}
ASTNode::Variable { name, .. } => self.build_variable_access(name.clone()),
@ -207,7 +218,8 @@ impl super::MirBuilder {
// Generic static box: lower all static methods into standalone MIR functions (BoxName.method/N)
// Note: Metadata clearing is now handled by BoxCompilationContext (箱理論)
// See lifecycle.rs for context creation and builder_calls.rs for context swap
self.comp_ctx.user_defined_boxes.insert(name.clone());
// Phase 285LLVM-1.1: Register static box (no fields)
self.comp_ctx.register_user_box(name.clone());
for (method_name, method_ast) in methods.clone() {
if let ASTNode::FunctionDeclaration { params, body, .. } = method_ast {
let func_name = format!(
@ -235,7 +247,8 @@ impl super::MirBuilder {
}
} else {
// Instance box: register type and lower instance methods/ctors as functions
self.comp_ctx.user_defined_boxes.insert(name.clone());
// Phase 285LLVM-1.1: Register with field information for LLVM harness
self.comp_ctx.register_user_box_with_fields(name.clone(), fields.clone());
self.build_box_declaration(
name.clone(),
methods.clone(),

View File

@ -159,6 +159,7 @@ impl NormalizedExprLowererBox {
}
}
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator), // Phase 285W-Syntax-0
},
ASTNode::BinaryOp { operator, left, right, .. } => {
@ -232,6 +233,7 @@ impl NormalizedExprLowererBox {
}
}
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator), // Phase 285W-Syntax-0
},
ASTNode::BinaryOp { operator, left, right, .. } => {
@ -387,6 +389,7 @@ impl NormalizedExprLowererBox {
Ok(Some(dst))
}
UnaryOperator::BitNot => Ok(None),
UnaryOperator::Weak => Ok(None), // Phase 285W-Syntax-0: Not supported in normalized lowering
}
}

View File

@ -313,19 +313,6 @@ 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 })

View File

@ -242,6 +242,17 @@ impl NyashParser {
});
}
// Phase 285W-Syntax-0: weak <expr> unary operator
if self.match_token(&TokenType::WEAK) {
self.advance(); // consume 'weak'
let operand = self.parse_unary()?; // 再帰的に単項演算をパース
return Ok(ASTNode::UnaryOp {
operator: UnaryOperator::Weak,
operand: Box::new(operand),
span: Span::unknown(),
});
}
self.parse_call()
}

View File

@ -1,7 +1,7 @@
#!/bin/bash
# phase285_weak_basic_vm.sh - Phase 285A0.1: WeakRef basic smoke test (VM)
# phase285_weak_basic_vm.sh - Phase 285W-Syntax-0: WeakRef basic smoke test (VM)
#
# Verifies weak(x) and weak_to_strong() work correctly in VM backend.
# Verifies weak <expr> and weak_to_strong() work correctly in VM backend.
# Note: Full drop semantics test deferred (needs GC/scope analysis)
# SSOT: docs/reference/language/lifecycle.md:179