feat(phase285w): Phase 285W-Syntax-0.1 - Reject weak(...) syntax (Parser-level Fail-Fast)

- Parser guard: Reject weak(...) with LPAREN check in parse_unary()
  - Error: "Use 'weak expr', not 'weak(expr)'" (helpful message)
  - Location: src/parser/expressions.rs:248-256
- MIR builder guard: Defense-in-depth for any bypassed cases
  - Location: src/mir/builder/calls/build.rs:37-46
- Rejection test: apps/tests/phase285w_weak_call_rejected.hako
- Smoke test: phase285w_weak_call_rejected_vm.sh (PASS )
- Documentation:
  - EBNF.md: Add ~ (BitNot) to unary operators
  - lifecycle.md: Document weak(expr) as invalid syntax
  - phase-285/README.md: Add Phase 285W-Syntax-0.1 entry

Test results: 5/6 phase285 tests PASS (1 unrelated failure)
SSOT: docs/reference/language/lifecycle.md

Closes: Phase 285W-Syntax-0.1
This commit is contained in:
2025-12-25 00:04:55 +09:00
parent 9227673ef7
commit cc05c37ae3
7 changed files with 73 additions and 13 deletions

View File

@ -0,0 +1,16 @@
// Phase 285W-Syntax-0.1: Verify weak(x) function call syntax is rejected
// Expected: Parse error with helpful message
// SSOT: docs/reference/language/lifecycle.md (weak <expr> only)
box SomeBox {
x
}
static box Main {
main() {
local obj = new SomeBox()
obj.x = 42
local w = weak(obj) // ❌ ERROR: Should be "weak obj"
return 0
}
}

View File

@ -11,9 +11,11 @@ Status: In progress (A1 series implemented; LLVM sub-phases ongoing)
| 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) |
| **285W-Syntax-0.1** | ✅ **COMPLETE** | **weak(x) 完全拒否 (Parser-level Fail-Fast)** (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
**Syntax Enforcement**: Phase 285W-Syntax-0.1 enforces parser-level rejection of `weak(...)` syntax with helpful error message
---

View File

@ -17,7 +17,7 @@ logic := compare (('&&' | '||') compare)*
compare := sum (( '==' | '!=' | '<' | '>' | '<=' | '>=' ) sum)?
sum := term (('+' | '-') term)*
term := unary (('*' | '/') unary)*
unary := ('-' | '!' | 'not') unary | factor
unary := ('-' | '!' | 'not' | '~' | 'weak') unary | factor
factor := INT
| STRING

View File

@ -94,7 +94,8 @@ Forbidden on **Dead** (Fail-Fast, UseAfterFini):
- Method calls
- ByRef (`RefGet/RefSet`) operations
- Conversions / truthiness (`if dead_box { ... }` is an error)
- Creating new weak references from a dead object (`weak(dead)` is an error)
- Creating new weak references from a dead object (`weak dead` is an error)
- Note: the surface form is `weak <expr>` (not `weak(<expr>)`).
### Finalization precedence
@ -175,7 +176,9 @@ Note:
Weak references exist to avoid strong cycles and to model back-pointers.
SSOT operations:
- `weak(x)` produces a `WeakRef` to `x` (x must be Alive).
- `weak <expr>` produces a `WeakRef` to the target (the target must be Alive).
- **Syntax**: `weak <expr>` (unary operator, Phase 285W-Syntax-0)
- **Invalid**: `weak(expr)` ❌ (compile error: "Use 'weak expr', not 'weak(expr)'")
- `weakRef.weak_to_strong()` returns the target box if it is usable, otherwise `null` (none).
- It returns `null` if the target is **Dead** (finalized) or **Freed** (collected).
- Note: `null` and `void` are equivalent at runtime (SSOT: `docs/reference/language/types.md`).
@ -201,22 +204,22 @@ WeakRef equality:
Weak fields enforce strict type requirements at compile time:
**Allowed assignments** (3 cases):
1. **Explicit weak reference**: `me.parent = weak(p)`
1. **Explicit weak reference**: `me.parent = weak p`
2. **WeakRef variable**: `me.parent = other.parent` (where `other.parent` is weak field)
3. **Void**: `me.parent = Void` (clear operation; null is sugar for Void)
**Forbidden assignments** (Fail-Fast compile error):
- Direct BoxRef: `me.parent = p` where `p` is BoxRef
- Primitives: `me.parent = 42`
- Any non-WeakRef type without explicit `weak()`
- Any non-WeakRef type without explicit `weak` conversion
**Error message example**:
```
Cannot assign Box (NodeBox) to weak field 'Tree.parent'.
Use weak(...) to create weak reference: me.parent = weak(value)
Use `weak <expr>` to create weak reference: me.parent = weak value
```
**Rationale**: Explicit `weak()` calls make the semantic difference between strong and weak references visible. This prevents:
**Rationale**: Explicit `weak` conversions make the semantic difference between strong and weak references visible. This prevents:
- Accidental strong references in weak fields (reference cycles)
- Confusion about object lifetime and ownership
- Silent bugs from automatic conversions
@ -228,7 +231,7 @@ box Node {
set_parent(p) {
// ❌ me.parent = p // Compile error
// ✅ me.parent = weak(p) // Explicit weak()
// ✅ me.parent = weak p // Explicit weak conversion
// ✅ me.parent = Void // Clear operation (SSOT: Void primary)
}
@ -301,7 +304,7 @@ This section documents current backend reality so we can detect drift as bugs.
| Feature | VM | LLVM | WASM |
|---------|-----|------|------|
| WeakRef (`weak(x)`, `weak_to_strong()`) | ✅ | ❌ unsupported (285LLVM-1) | ❌ unsupported |
| WeakRef (`weak <expr>`, `weak_to_strong()`) | ✅ | ❌ unsupported (285LLVM-1) | ❌ unsupported |
| Leak Report (`NYASH_LEAK_LOG`) | ✅ | ⚠️ Parent process roots only (285LLVM-0) | ❌ |
**LLVM Leak Report の制限** (Phase 285LLVM-0):
@ -313,7 +316,7 @@ This section documents current backend reality so we can detect drift as bugs.
### Notes
- **Block-scoped locals** are the language model (`local` drops at `}`), but the *observable* effects depend on where the last strong reference is held.
- **WeakRef** (Phase 285A0): VM backend fully supports `weak(x)` and `weak_to_strong()`. LLVM harness support is planned for Phase 285LLVM-1.
- **WeakRef** (Phase 285A0): VM backend fully supports `weak <expr>` and `weak_to_strong()`. LLVM harness support is planned for Phase 285LLVM-1.
- **WASM backend** currently treats MIR `WeakNew/WeakLoad` as plain copies (weak behaves like strong). This does not satisfy the SSOT weak semantics yet (see also: `docs/guides/wasm-guide/planning/unsupported_features.md`).
- **Leak Report** (Phase 285): `NYASH_LEAK_LOG={1|2}` prints exit-time diagnostics showing global roots still held (modules, host_handles, plugin_boxes). See `docs/reference/environment-variables.md`.
- Conformance gaps (any backend differences from this document) must be treated as bugs and tracked explicitly; do not "paper over" differences by changing this SSOT without a decision.
@ -330,7 +333,7 @@ box SomeBox { }
static box Main {
main() {
local x = new SomeBox()
local w = weak(x)
local w = weak x
x = null
local y = w.weak_to_strong()
if y == null { print("ok: dropped") }
@ -345,8 +348,8 @@ static box Main {
main() {
local a = new Node()
local b = new Node()
a.next_weak = weak(b)
b.next_weak = weak(a)
a.next_weak = weak b
b.next_weak = weak a
return 0
}
}

View File

@ -34,6 +34,17 @@ impl MirBuilder {
);
}
// 0. Phase 285W-Syntax-0.1: Reject weak(...) function call syntax
// SSOT: docs/reference/language/lifecycle.md - weak <expr> is the ONLY valid syntax
if name == "weak" {
eprintln!("[Phase285W-0.1] Rejecting weak(...) function call");
return Err(format!(
"Invalid syntax: weak(...). Use unary operator: weak <expr>\n\
Help: Change 'weak(obj)' to 'weak obj' (unary operator, no parentheses)\n\
SSOT: docs/reference/language/lifecycle.md"
));
}
// 1. TypeOp wiring: isType(value, "Type"), asType(value, "Type")
if let Some(result) = self.try_build_typeop_function(&name, &args)? {
return Ok(result);

View File

@ -245,6 +245,15 @@ impl NyashParser {
// Phase 285W-Syntax-0: weak <expr> unary operator
if self.match_token(&TokenType::WEAK) {
self.advance(); // consume 'weak'
// Phase 285W-Syntax-0.1: Reject weak(...) function call syntax
if self.match_token(&TokenType::LPAREN) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: TokenType::LPAREN,
expected: "expression after 'weak' unary operator. Use 'weak expr', not 'weak(expr)'".to_string(),
line,
});
}
let operand = self.parse_unary()?; // 再帰的に単項演算をパース
return Ok(ASTNode::UnaryOp {
operator: UnaryOperator::Weak,

View File

@ -0,0 +1,19 @@
#!/bin/bash
# phase285w_weak_call_rejected_vm.sh - Verify weak(x) syntax is rejected with helpful error
# SSOT: Phase 285W-Syntax-0.1
source "$(dirname "$0")/../../../lib/test_runner.sh"
source "$(dirname "$0")/../../../lib/result_checker.sh"
require_env || exit 2
FIXTURE="$NYASH_ROOT/apps/tests/phase285w_weak_call_rejected.hako"
# Expect parse error AND helpful message mentioning 'weak <expr>' or unary operator
# Pattern matches: "Unexpected token LPAREN" OR "Use 'weak expr'"
if check_error_pattern "$FIXTURE" "Unexpected token LPAREN.*weak.*expr|Use.*weak expr" "phase285w_weak_call_rejected"; then
log_success "phase285w_weak_call_rejected: weak(x) correctly rejected with helpful error"
exit 0
else
log_error "phase285w_weak_call_rejected: weak(x) not rejected or error message unhelpful"
exit 1
fi