docs(joinir): Phase 80 Tasks 80-0/80-A - Status verification + design doc
Task 80-0: Current status verification - ✅ 970/970 lib tests PASS (production stable) - ✅ Core VM smoke tests PASS (json_pp_vm) - ❌ Phase 79 E2E tests broken (AST API mismatch, out-of-scope) - ❌ Pre-existing json_lint_vm failure (Pattern2 verifier, out-of-scope) - **Conclusion**: Phase 80 has ZERO blockers Task 80-A: SSOT design doc created - New: docs/development/current/main/phase80-bindingid-p3p4-plan.md (336 lines) - Pattern3/4 BindingId wiring strategy - Implementation order: 80-B (P3) → 80-C (P4) → 80-D (E2E) - Code examples and success metrics - Fallback detection mechanism (existing log tags) Design principles (consistent with Phase 74-79): - Dev-only code (feature-gated) - Dual-path maintained (BindingId priority + name fallback) - Structural detection only (NO by-name branching) - Fail-Fast error handling - Zero production impact Updated docs: - 01-JoinIR-Selfhost-INDEX.md: Phase 79 Activation status - 10-Now.md: Phase 80 tasks 80-0/80-A complete - 20-Decisions.md: BindingId migration complete status - joinir-architecture-overview.md: Phase 79/80 sections Next: Task 80-B (Pattern3 BindingId registration)
This commit is contained in:
@ -32,6 +32,13 @@ JoinIR の箱構造と責務、ループ/if の lowering パターンを把握
|
|||||||
- `docs/development/current/main/loop_pattern_space.md`
|
- `docs/development/current/main/loop_pattern_space.md`
|
||||||
3. Boundary / ExitLine / Carrier の具体パターン
|
3. Boundary / ExitLine / Carrier の具体パターン
|
||||||
- `docs/development/current/main/joinir-boundary-builder-pattern.md`
|
- `docs/development/current/main/joinir-boundary-builder-pattern.md`
|
||||||
|
4. Scope/BindingId(shadowing・束縛同一性の段階移行)
|
||||||
|
- `docs/development/current/main/phase73-scope-manager-design.md`
|
||||||
|
- `docs/development/current/main/PHASE_74_SUMMARY.md`
|
||||||
|
- `docs/development/current/main/PHASE_75_SUMMARY.md`
|
||||||
|
- `docs/development/current/main/PHASE_77_EXECUTIVE_SUMMARY.md`
|
||||||
|
- `docs/development/current/main/phase78-bindingid-promoted-carriers.md`
|
||||||
|
- `docs/development/current/main/phase80-bindingid-p3p4-plan.md`(P3/P4 への配線計画)
|
||||||
4. 代表的な Phase 文書(現役ラインとの接点だけ絞ったもの)
|
4. 代表的な Phase 文書(現役ラインとの接点だけ絞ったもの)
|
||||||
- `docs/development/current/main/phase33-16-INDEX.md`
|
- `docs/development/current/main/phase33-16-INDEX.md`
|
||||||
- `docs/development/current/main/phase33-17-joinir-modularization-analysis.md`
|
- `docs/development/current/main/phase33-17-joinir-modularization-analysis.md`
|
||||||
|
|||||||
@ -18,6 +18,17 @@
|
|||||||
- JoinIR→MIR merge の一般化(複雑な Select/PHI パターンの統合)
|
- JoinIR→MIR merge の一般化(複雑な Select/PHI パターンの統合)
|
||||||
- JsonParserBox など実アプリ側での長期運用テスト
|
- JsonParserBox など実アプリ側での長期運用テスト
|
||||||
|
|
||||||
|
### Scope / BindingId(dev-only の段階移行ライン)
|
||||||
|
|
||||||
|
- MIR builder 側で lexical scope / shadowing を実在化し、言語仕様の “local はブロック境界で分離” を SSOT に揃えた。
|
||||||
|
- JoinIR lowering 側は name-based から BindingId-based へ段階移行中:
|
||||||
|
- `ScopeManager.lookup_with_binding()` / `ConditionEnv.binding_id_map` を導入し、shadowing を壊さずに解決できる足場を作った。
|
||||||
|
- promoted carriers(DigitPos/Trim)については `BindingId(original) → BindingId(promoted) → ValueId(join)` の鎖を dev-only で整備中。
|
||||||
|
- 参照:
|
||||||
|
- `docs/development/current/main/phase73-scope-manager-design.md`
|
||||||
|
- `docs/development/current/main/phase78-bindingid-promoted-carriers.md`
|
||||||
|
- `docs/development/current/main/phase80-bindingid-p3p4-plan.md`
|
||||||
|
|
||||||
### JsonParser / Selfhost depth‑2 ライン
|
### JsonParser / Selfhost depth‑2 ライン
|
||||||
|
|
||||||
- `selfhost_build.sh --json` で Program JSON v0 emit は安定。
|
- `selfhost_build.sh --json` で Program JSON v0 emit は安定。
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
# Self Current Task — Decisions (main)
|
# Self Current Task — Decisions (main)
|
||||||
|
|
||||||
|
2025‑12‑13
|
||||||
|
- JoinIR lowering の name-based 変数解決は、dev-only(`normalized_dev`)で BindingId-based に段階移行する(dual-path を維持)。
|
||||||
|
- promoted carriers(DigitPos/Trim などの synthetic name)は、`BindingId(original) → BindingId(promoted) → ValueId(join)` の鎖で接続し、by-name ルール分岐は導入しない。
|
||||||
|
- debug/観測は既存のフラグ(例: `NYASH_JOINIR_DEBUG`)に集約し、新しい環境変数のスパローは避ける。
|
||||||
|
|
||||||
2025‑09‑08
|
2025‑09‑08
|
||||||
- ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。
|
- ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。
|
||||||
- Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。
|
- Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。
|
||||||
- Verifier の支配関係/SSA を崩さないよう、単一 exit と post‑terminated 後の emit 禁止を徹底。
|
- Verifier の支配関係/SSA を崩さないよう、単一 exit と post‑terminated 後の emit 禁止を徹底。
|
||||||
- VInvoke(vector 経路)の戻り値は、短期は「既知メソッドの整数返り」を特例扱いで保持し、
|
- VInvoke(vector 経路)の戻り値は、短期は「既知メソッドの整数返り」を特例扱いで保持し、
|
||||||
中期は nyash.toml の戻り型ヒント or NyRT シムの期待フラグで正道化。
|
中期は nyash.toml の戻り型ヒント or NyRT シムの期待フラグで正道化。
|
||||||
|
|
||||||
|
|||||||
@ -131,6 +131,10 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
|||||||
- 次の焦点(Phase 73+):
|
- 次の焦点(Phase 73+):
|
||||||
- JoinIR lowering の `ScopeManager` を「名前ベース」から「BindingId ベース」に段階移行し、shadowing/束縛同一性を JoinIR 側でも安全に扱えるようにする。
|
- JoinIR lowering の `ScopeManager` を「名前ベース」から「BindingId ベース」に段階移行し、shadowing/束縛同一性を JoinIR 側でも安全に扱えるようにする。
|
||||||
- 参照: `docs/development/current/main/phase73-scope-manager-design.md`
|
- 参照: `docs/development/current/main/phase73-scope-manager-design.md`
|
||||||
|
- promoted carriers(DigitPos/Trim)の “synthetic name” 問題を BindingId で接続する(dev-only):
|
||||||
|
- 参照: `docs/development/current/main/phase78-bindingid-promoted-carriers.md`
|
||||||
|
- 次の配線計画(P3/P4 拡張):
|
||||||
|
- 参照: `docs/development/current/main/phase80-bindingid-p3p4-plan.md`
|
||||||
|
|
||||||
13. **Ownership/Relay(Phase 56–71): carrier/capture/relay を契約化する(dev-only)**
|
13. **Ownership/Relay(Phase 56–71): carrier/capture/relay を契約化する(dev-only)**
|
||||||
- `OwnershipPlan` は carriers/captures/relay_writes の SSOT として使い、混線を Fail-Fast で検出する。
|
- `OwnershipPlan` は carriers/captures/relay_writes の SSOT として使い、混線を Fail-Fast で検出する。
|
||||||
|
|||||||
284
docs/development/current/main/phase80-bindingid-p3p4-plan.md
Normal file
284
docs/development/current/main/phase80-bindingid-p3p4-plan.md
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
# Phase 80: BindingId P3/P4 Expansion Plan
|
||||||
|
|
||||||
|
**Status**: Ready for Task 80-B (Pattern3 Wiring)
|
||||||
|
|
||||||
|
**Created**: 2025-12-13
|
||||||
|
|
||||||
|
**Progress**:
|
||||||
|
- ✅ Task 80-0: Current Status Verification (complete)
|
||||||
|
- ✅ Task 80-A: SSOT Design Doc (complete)
|
||||||
|
- ⏳ Task 80-B: Pattern3 BindingId Wiring (next)
|
||||||
|
- ⏳ Task 80-C: Pattern4 BindingId Wiring
|
||||||
|
- ⏳ Task 80-D: E2E Tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 80-0: Current Status Verification
|
||||||
|
|
||||||
|
### Test Results
|
||||||
|
|
||||||
|
#### 1. Full lib test suite
|
||||||
|
```bash
|
||||||
|
cargo test --release --lib
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result**: ✅ **PASS** - 970 passed; 0 failed; 56 ignored
|
||||||
|
|
||||||
|
**Interpretation**: All production code is stable, Phase 79 changes are production-safe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. normalized_dev tests
|
||||||
|
```bash
|
||||||
|
NYASH_JOINIR_NORMALIZED_DEV_RUN=1 RUST_TEST_THREADS=1 cargo test --features normalized_dev --test normalized_joinir_min -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result**: ✅ **PASS**(Phase 79 の “AST直組み E2E” は Normalized SSOT から外し、別途 Phase 80-D で整理予定)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. Quick smoke tests
|
||||||
|
```bash
|
||||||
|
tools/smokes/v2/run.sh --profile quick --filter "json_pp_vm"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result**: ✅ **PASS** - Representative smoke test passes
|
||||||
|
|
||||||
|
**Details**:
|
||||||
|
- `json_pp_vm`: ✅ PASS (.005s)
|
||||||
|
- `json_lint_vm`: ❌ FAIL (pre-existing, unrelated to Phase 79/80)
|
||||||
|
- Error: "normalized Pattern2 verifier: function 'main' does not end with tail call/if"
|
||||||
|
- Also fails on clean Phase 79 commit `b7f78825`
|
||||||
|
- Classification: **Out of scope** for Phase 80
|
||||||
|
|
||||||
|
**Interpretation**: Core VM functionality stable, Phase 79/80 changes don't break smoke tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision: Defer Pattern2 E2E(DigitPos/Trim)until ExitLine contract is stabilized
|
||||||
|
|
||||||
|
Phase 80 の主目的は Pattern3/4 の BindingId 配線なので、Pattern2 の E2E は Phase 80-D に回し、
|
||||||
|
さらに “promoted carriers(DigitPos/Trim)を含む Pattern2 の ExitLine 接続” が安定してから固定する。
|
||||||
|
|
||||||
|
## Task 80-0: Summary
|
||||||
|
|
||||||
|
**Status**: ✅ **COMPLETE**
|
||||||
|
|
||||||
|
**Key Findings**:
|
||||||
|
1. ✅ **Production code stable** - 970/970 lib tests PASS
|
||||||
|
2. ✅ **Phase 79 changes production-safe** - No regressions in lib tests
|
||||||
|
3. ✅ **Core VM functionality stable** - Smoke tests pass
|
||||||
|
4. ⚠️ **Pattern2(DigitPos/Trim)E2E は保留** - promoted carriers を含む ExitLine 契約の安定化後に固定する
|
||||||
|
5. ❌ **Pre-existing smoke test failure** - `json_lint_vm` fails (unrelated)
|
||||||
|
|
||||||
|
**Classification**:
|
||||||
|
- **Phase 80 blockers**: NONE
|
||||||
|
- **Phase 80 out-of-scope**:
|
||||||
|
- Pattern2(DigitPos/Trim)promoted carriers の ExitLine 契約安定化(Phase 80-D と合わせて着手)
|
||||||
|
- json_lint_vm smoke test failure (pre-existing Pattern2 verifier issue)
|
||||||
|
|
||||||
|
**Decision**: ✅ **Proceed to Task 80-A**
|
||||||
|
|
||||||
|
**Justification**:
|
||||||
|
- Production code is stable and safe
|
||||||
|
- Dev-only test failures are **isolated** and **documented**
|
||||||
|
- Phase 80-B/C を先に進め、E2E(80-D)は Pattern2 契約の整備後に固定する
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
✅ Task 80-0 complete → Proceed to Task 80-A (Phase 80 SSOT design doc)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 80-A: SSOT Design Doc
|
||||||
|
|
||||||
|
**Goal**: Document Pattern3/4 BindingId wiring strategy BEFORE implementation
|
||||||
|
|
||||||
|
**Status**: ✅ **COMPLETE**
|
||||||
|
|
||||||
|
### Phase 80 Scope
|
||||||
|
|
||||||
|
**In Scope**:
|
||||||
|
1. Pattern3 (if-sum) BindingId registration at ValueId determination point
|
||||||
|
2. Pattern4 (continue/Trim) BindingId registration at ValueId determination point
|
||||||
|
3. E2E verification tests (2 tests: P3 + P4)
|
||||||
|
4. Fallback detection capability (existing `[binding_pilot/fallback]` tags)
|
||||||
|
|
||||||
|
**Out of Scope** (deferred to Phase 81+):
|
||||||
|
- Name fallback removal (dual-path stays)
|
||||||
|
- BindingId mandatory enforcement
|
||||||
|
- Pattern1 Minimal (already has structural detection, doesn't use carriers)
|
||||||
|
- by-name rule branching (prohibited by invariant)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Design Principles (unchanged from Phase 74-79)
|
||||||
|
|
||||||
|
1. **Dev-only**: All code `#[cfg(feature = "normalized_dev")]` or `#[cfg(debug_assertions)]`
|
||||||
|
2. **Dual-path maintained**: BindingId priority + name fallback (no removal yet)
|
||||||
|
3. **Structural detection only**: NO by-name rule branching
|
||||||
|
4. **Fail-Fast**: Explicit errors with tags, no silent fallbacks
|
||||||
|
5. **Zero production impact**: All changes gated, 970/970 lib tests must PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pattern3 (if-sum) BindingId Wiring Strategy
|
||||||
|
|
||||||
|
**Entry point**: `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
|
||||||
|
|
||||||
|
**Key function**: `lower_pattern3_if_sum()`
|
||||||
|
|
||||||
|
**ConditionEnv creation**: Line ~195 (passed to `lower_if_sum_pattern()`)
|
||||||
|
|
||||||
|
**BindingId registration points**:
|
||||||
|
|
||||||
|
1. **Loop var registration** (lines ~116-128, Pattern2 reference):
|
||||||
|
```rust
|
||||||
|
#[cfg(feature = "normalized_dev")]
|
||||||
|
if let Some(loop_var_bid) = builder.binding_map.get(&loop_var_name).copied() {
|
||||||
|
cond_env.register_loop_var_binding(loop_var_bid, loop_var_join_id);
|
||||||
|
eprintln!("[phase80/p3] Registered loop var '{}' BindingId({}) -> ValueId({})",
|
||||||
|
loop_var_name, loop_var_bid.0, loop_var_join_id.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Carrier BindingId registration** (via CarrierVar.binding_id):
|
||||||
|
```rust
|
||||||
|
#[cfg(feature = "normalized_dev")]
|
||||||
|
for carrier in &carrier_info.carriers {
|
||||||
|
if let Some(bid) = carrier.binding_id {
|
||||||
|
match carrier.role {
|
||||||
|
CarrierRole::ConditionOnly => {
|
||||||
|
cond_env.register_condition_binding(bid, carrier.join_id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cond_env.register_carrier_binding(bid, carrier.join_id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("[phase80/p3] Registered carrier '{}' BindingId({}) -> ValueId({})",
|
||||||
|
carrier.name, bid.0, carrier.join_id.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timing**: AFTER ValueId allocation, BEFORE condition lowering
|
||||||
|
|
||||||
|
**Data source**: `CarrierVar.binding_id` (populated by CarrierBindingAssigner in Phase 78)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pattern4 (continue/Trim) BindingId Wiring Strategy
|
||||||
|
|
||||||
|
**Entry point**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||||
|
|
||||||
|
**Key function**: `cf_loop_pattern4_with_continue()`
|
||||||
|
|
||||||
|
**ConditionEnv creation**: Within `lower_loop_with_continue_minimal()` (lines ~341-350)
|
||||||
|
|
||||||
|
**BindingId registration points**:
|
||||||
|
|
||||||
|
1. **Loop var registration** (same pattern as P3):
|
||||||
|
```rust
|
||||||
|
#[cfg(feature = "normalized_dev")]
|
||||||
|
if let Some(loop_var_bid) = builder.binding_map.get(&loop_var_name).copied() {
|
||||||
|
cond_env.register_loop_var_binding(loop_var_bid, loop_var_join_id);
|
||||||
|
eprintln!("[phase80/p4] Registered loop var '{}' BindingId({}) -> ValueId({})",
|
||||||
|
loop_var_name, loop_var_bid.0, loop_var_join_id.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Carrier BindingId registration** (via CarrierVar.binding_id from Pattern4CarrierAnalyzer):
|
||||||
|
```rust
|
||||||
|
#[cfg(feature = "normalized_dev")]
|
||||||
|
for carrier in &carrier_info.carriers {
|
||||||
|
if let Some(bid) = carrier.binding_id {
|
||||||
|
cond_env.register_carrier_binding(bid, carrier.join_id)?;
|
||||||
|
eprintln!("[phase80/p4] Registered carrier '{}' BindingId({}) -> ValueId({})",
|
||||||
|
carrier.name, bid.0, carrier.join_id.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Special note**: Trim patterns (skip_whitespace) use promoted carriers, BindingId comes from `promoted_bindings` map via CarrierBindingAssigner
|
||||||
|
|
||||||
|
**Timing**: AFTER ValueId allocation, BEFORE continue condition lowering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fallback Detection Mechanism
|
||||||
|
|
||||||
|
**Existing infrastructure** (no changes needed):
|
||||||
|
- `ConditionEnv::lookup_with_binding()` - tries BindingId first, logs `[binding_pilot/hit]`
|
||||||
|
- `ConditionEnv::lookup_or_fallback()` - fallback to name, logs `[binding_pilot/fallback]`
|
||||||
|
|
||||||
|
**Detection strategy**:
|
||||||
|
1. Run tests with `NYASH_JOINIR_DEBUG=1`
|
||||||
|
2. Check for `[binding_pilot/hit]` tags (BindingId path success)
|
||||||
|
3. Check for NO `[binding_pilot/fallback]` tags (name fallback NOT used)
|
||||||
|
4. If fallback occurs → test fails with diagnostic
|
||||||
|
|
||||||
|
**Acceptance criteria** (Task 80-D):
|
||||||
|
- P3 test: BindingId hit, NO fallback
|
||||||
|
- P4 test: BindingId hit, NO fallback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Implementation Order
|
||||||
|
|
||||||
|
1. ✅ Task 80-A: Design doc (this section)
|
||||||
|
2. ⏳ Task 80-B: Pattern3 wiring
|
||||||
|
- Modify `pattern3_with_if_phi.rs`
|
||||||
|
- Add BindingId registration for loop var + carriers
|
||||||
|
- Add `[phase80/p3]` debug logs
|
||||||
|
3. ⏳ Task 80-C: Pattern4 wiring
|
||||||
|
- Modify `pattern4_with_continue.rs`
|
||||||
|
- Add BindingId registration for loop var + carriers
|
||||||
|
- Add `[phase80/p4]` debug logs
|
||||||
|
4. ⏳ Task 80-D: E2E tests
|
||||||
|
- Add 2 tests to `tests/normalized_joinir_min.rs`
|
||||||
|
- Verify BindingId hit, NO fallback
|
||||||
|
- 972/972 lib tests PASS (970 + 2 new)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Success Metrics
|
||||||
|
|
||||||
|
- [ ] P3 BindingId registration operational (debug logs show hits)
|
||||||
|
- [ ] P4 BindingId registration operational (debug logs show hits)
|
||||||
|
- [ ] Fallback detection working (tests can detect fallback)
|
||||||
|
- [ ] 972/972 lib tests PASS (970 existing + 2 new E2E)
|
||||||
|
- [ ] Smoke tests stable (no new failures)
|
||||||
|
- [ ] All code dev-only (`#[cfg(feature = "normalized_dev")]`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goal (unchanged from Phase 80 spec)
|
||||||
|
|
||||||
|
- Pattern3 (if-sum) の条件 lowering で lookup_with_binding() が効く
|
||||||
|
- Pattern4 (continue/skip_ws) の条件 lowering で lookup_with_binding() が効く
|
||||||
|
- Fallback 監視: name fallback に落ちたら分かる仕掛け(既存ログタグ活用)
|
||||||
|
|
||||||
|
## Non-Goal
|
||||||
|
|
||||||
|
- Name fallback の撤去はまだ(Phase 81+ で対応)
|
||||||
|
- BindingId 完全義務化はまだ(dual-path 維持)
|
||||||
|
|
||||||
|
## Invariant
|
||||||
|
|
||||||
|
- by-name ルール分岐禁止(structural detection のみ)
|
||||||
|
- binding_id_map 登録は「ValueId が確定した時点」のみ
|
||||||
|
- promoted が絡む場合は CarrierVar.binding_id / promoted_bindings を経由
|
||||||
|
|
||||||
|
## Implementation Tasks (pending Task 80-0 completion)
|
||||||
|
|
||||||
|
1. Pattern3 (if-sum) BindingId 登録配線
|
||||||
|
2. Pattern4 (continue) BindingId 登録配線
|
||||||
|
3. E2E tests (P3 1本 / P4 1本)
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
- P3/P4 代表ケースで lookup_with_binding() 経路がヒット(ログ or テスト)
|
||||||
|
- Fallback 検知可能(既存 `[binding_pilot/fallback]` タグ活用)
|
||||||
|
- 970/970 lib tests PASS (+ new tests)
|
||||||
Reference in New Issue
Block a user