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:
16
apps/tests/phase285w_weak_call_rejected.hako
Normal file
16
apps/tests/phase285w_weak_call_rejected.hako
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
Reference in New Issue
Block a user