From cc05c37ae3f36e2d3e34fd842f409812a8f34a88 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Thu, 25 Dec 2025 00:04:55 +0900 Subject: [PATCH] feat(phase285w): Phase 285W-Syntax-0.1 - Reject weak(...) syntax (Parser-level Fail-Fast) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- apps/tests/phase285w_weak_call_rejected.hako | 16 +++++++++++ .../current/main/phases/phase-285/README.md | 2 ++ docs/reference/language/EBNF.md | 2 +- docs/reference/language/lifecycle.md | 27 ++++++++++--------- src/mir/builder/calls/build.rs | 11 ++++++++ src/parser/expressions.rs | 9 +++++++ .../phase285w_weak_call_rejected_vm.sh | 19 +++++++++++++ 7 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 apps/tests/phase285w_weak_call_rejected.hako create mode 100644 tools/smokes/v2/profiles/quick/lifecycle/phase285w_weak_call_rejected_vm.sh diff --git a/apps/tests/phase285w_weak_call_rejected.hako b/apps/tests/phase285w_weak_call_rejected.hako new file mode 100644 index 00000000..943d6bbc --- /dev/null +++ b/apps/tests/phase285w_weak_call_rejected.hako @@ -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 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 + } +} diff --git a/docs/development/current/main/phases/phase-285/README.md b/docs/development/current/main/phases/phase-285/README.md index 0d2b6257..9051622c 100644 --- a/docs/development/current/main/phases/phase-285/README.md +++ b/docs/development/current/main/phases/phase-285/README.md @@ -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 --- diff --git a/docs/reference/language/EBNF.md b/docs/reference/language/EBNF.md index 2d6bd841..8656c214 100644 --- a/docs/reference/language/EBNF.md +++ b/docs/reference/language/EBNF.md @@ -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 diff --git a/docs/reference/language/lifecycle.md b/docs/reference/language/lifecycle.md index 67288a44..aad8cdb7 100644 --- a/docs/reference/language/lifecycle.md +++ b/docs/reference/language/lifecycle.md @@ -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 ` (not `weak()`). ### 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 ` produces a `WeakRef` to the target (the target must be Alive). + - **Syntax**: `weak ` (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 ` 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 `, `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 ` 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 } } diff --git a/src/mir/builder/calls/build.rs b/src/mir/builder/calls/build.rs index 2b56bdb8..a7e46a2b 100644 --- a/src/mir/builder/calls/build.rs +++ b/src/mir/builder/calls/build.rs @@ -34,6 +34,17 @@ impl MirBuilder { ); } + // 0. Phase 285W-Syntax-0.1: Reject weak(...) function call syntax + // SSOT: docs/reference/language/lifecycle.md - weak 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 \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); diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 76f1a40b..98f722f8 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -245,6 +245,15 @@ impl NyashParser { // Phase 285W-Syntax-0: weak 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, diff --git a/tools/smokes/v2/profiles/quick/lifecycle/phase285w_weak_call_rejected_vm.sh b/tools/smokes/v2/profiles/quick/lifecycle/phase285w_weak_call_rejected_vm.sh new file mode 100644 index 00000000..975a0d1f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/lifecycle/phase285w_weak_call_rejected_vm.sh @@ -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 ' 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