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

@ -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
}
}