feat(phi): Phase 26-F Step 1 & 3 - BodyLocal if-merge統合(WIP - 過剰フィルタリング発生中)
Step 1完了: - body_local_phi_builder.rs: filter_if_merge_candidates() API追加 - pre_if/then_end/else_end_opt/reachable_preds受け取り - LoopVarClassBox使用して変数分類 Step 3完了: - loop_builder.rs: BodyLocalPhiBuilder作成・PhiBuilderBoxに設定 - phi_builder_box.rs: IfPhiContext拡張・set_body_local_filter()実装 - compute_modified_names_if()でフィルタリング適用 **問題**: LocalScopeInspectorBox空のため全候補フィルタリング(0 candidates) 技術詳細: - inspector定義記録なし → classify誤判定 → 全変数BodyLocalInternal扱い - テスト結果: bb54/bb52→bb38/bb36/bb32(ブロック番号変化=PHI生成影響あり) - mir_stage1_using_resolver_modules_map_continue_break_with_lookup_verifies: PASS - mir_stage1_using_resolver_resolve_with_modules_map_verifies: FAIL(domination error残存) 次のステップ: 1. filter_if_merge_candidates()単純実装(inspector不要) 2. または変数定義トラッキング実装 3. ChatGPT相談推奨
This commit is contained in:
98
docs/development/roadmap/phases/phase-25.1n/README.md
Normal file
98
docs/development/roadmap/phases/phase-25.1n/README.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Phase 25.1n — MirBuilder Self‑Host 移植ライン(Rust SSOT → .hako 実装)
|
||||||
|
|
||||||
|
Status: planning(設計フェーズ。実装は 25.2 系と並行で段階移行)
|
||||||
|
|
||||||
|
## ゴール
|
||||||
|
|
||||||
|
- Rust 側で固めた **SSA/PHI SSOT(LoopForm v2 / IfForm / BodyLocal / PhiBuilderBox)** を、
|
||||||
|
`.hako` 側の `MirBuilderBox` / `LoopFormBox` / `PhiBuilderBox` に「構造そのまま」移植できるようにするフェーズだよ。
|
||||||
|
- このフェーズでは:
|
||||||
|
- Rust `MirBuilder` を **唯一のオラクル** として扱い、
|
||||||
|
- その挙動を「表(ケース表+制御構造の形)」と「テスト」で固定する。
|
||||||
|
- `.hako` 側はその表とテストを見ながら、同じ SSA/PHI を組み立てる実装に寄せていく。
|
||||||
|
- 25.1/26‑E まででやってきた **LoopForm v2 / ExitPhiBuilder / BodyLocalPhiBuilder / IfForm / PhiBuilderBox** の成果を、
|
||||||
|
Self‑Host 実装に届けるための「橋渡しフェーズ」だよ。
|
||||||
|
|
||||||
|
## スコープ(25.1n でやること)
|
||||||
|
|
||||||
|
### N‑A: SSA/PHI SSOT の「表」化(Rust 側設計をテーブルに落とす)
|
||||||
|
|
||||||
|
- ファイル候補:
|
||||||
|
- `docs/development/architecture/loops/loopform_ssot.md`(既存の A/B/C/D ケース表を拡張)
|
||||||
|
- `docs/development/architecture/ssa/phi_cases_stage1.md`(新規)
|
||||||
|
- やること:
|
||||||
|
- すでに存在する LoopForm ケース表(Case A/B/C/D)に対して、
|
||||||
|
- `LoopVarClass`(Pinned / Carrier / BodyLocalExit / BodyLocalInternal)×
|
||||||
|
- LoopCase (A/B/C/D) ×
|
||||||
|
- place(header / exit / body‑if‑merge)
|
||||||
|
を軸に「どこに PHI を張るか」を表にする。
|
||||||
|
- If についても:
|
||||||
|
- `then/else` の到達可否(break/continue/early‑return)と、
|
||||||
|
- 変数のクラス(Pinned/Carrier/BodyLocal)
|
||||||
|
から、「PHI / direct bind / pre 値そのまま」の 3パターンを表で決める。
|
||||||
|
- これらを Rust コードに依存しない形で書き下し、
|
||||||
|
- 「Rust 実装はこの表を実現しているだけ」という関係にする(SSOT = docs + テスト)。
|
||||||
|
|
||||||
|
### N‑B: Rust MirBuilder オラクルテストの整備
|
||||||
|
|
||||||
|
- ファイル候補:
|
||||||
|
- `src/tests/mir_loopform_conditional_reassign.rs`
|
||||||
|
- `src/tests/mir_stage1_using_resolver_verify.rs`
|
||||||
|
- `src/tests/mir_stage1_cli_emit_program_min.rs`
|
||||||
|
- やること:
|
||||||
|
- 代表的な構造(LoopCase A〜D / Stage‑1 UsingResolver / Stage‑B fib/defs)について、
|
||||||
|
- `.hako` 入力 → Rust `MirCompiler` → `MirVerifier` の結果を **MIR テキストとして固定**するテスト(golden テストに近い)を 1〜2 本ずつ用意する。
|
||||||
|
- これらのテストは「Rust MirBuilder の挙動を凍結する」役割のみを持ち、
|
||||||
|
- `.hako` 側実装が追いつくまでは「期待値 = Rust 実装」の位置付けにする。
|
||||||
|
- 将来は、`.hako` 実装の MIR と diff を取る比較テストに発展させる(本フェーズでは準備だけ)。
|
||||||
|
|
||||||
|
### N‑C: .hako MirBuilderBox への API 設計(移植用インターフェース定義)
|
||||||
|
|
||||||
|
- ファイル候補:
|
||||||
|
- `lang/src/compiler/mir/mir_builder_box.hako`(仮)
|
||||||
|
- `lang/src/compiler/mir/loopform_box.hako`(仮)
|
||||||
|
- `lang/src/compiler/mir/phi_builder_box.hako`(仮)
|
||||||
|
- やること:
|
||||||
|
- Rust の `MirBuilder` / `LoopFormBuilder` / `PhiBuilderBox` の公開インターフェースから、
|
||||||
|
- `.hako` 側で必要になる API を抜き出し、Nyash の Box としてのシグネチャだけ先に決める。
|
||||||
|
- 例:
|
||||||
|
- `MirBuilderBox.emit_block(fn_name, ast)` → MirModule にブロック/関数を追加。
|
||||||
|
- `LoopFormBox.build_loop(condition, body_ast)` → LoopForm v2 構造を Nyash 側で組み立て。
|
||||||
|
- `PhiBuilderBox.emit_if_phi(pre_snapshot, then_snapshot, else_snapshot, control_form)` → 既存表に沿って PHI を配置。
|
||||||
|
- このフェーズでは **実装はまだ書かず**、I/F と責務コメント、簡単な docs のみを `.hako` 側に置く。
|
||||||
|
|
||||||
|
### N‑D: Self‑Host 用ミニパイプラインの設計
|
||||||
|
|
||||||
|
- ファイル候補:
|
||||||
|
- `docs/development/runtime/cli-hakorune-stage1.md`
|
||||||
|
- `docs/development/architecture/mir-selfhost-pipeline.md`(新規)
|
||||||
|
- やること:
|
||||||
|
- Self‑Host MVP のパイプラインを定義する:
|
||||||
|
- Stage‑0 Rust CLI → Stage‑B (.hako) → Stage‑1 MirBuilderBox (.hako) → MIR(JSON) → VM 実行。
|
||||||
|
- MVP では:
|
||||||
|
- 1〜2 の代表ケース(fib/defs, minimal_program)だけを対象とし、
|
||||||
|
- `.hako` MirBuilder は Rust MirBuilder の完全互換ではなく「代表ケースに十分」な subset に留める。
|
||||||
|
- これを Phase 25.2 以降の実装フェーズのターゲットとして書き切る。
|
||||||
|
|
||||||
|
## このフェーズで「やらない」こと
|
||||||
|
|
||||||
|
- Rust MirBuilder 実装のロジック変更:
|
||||||
|
- 25.1n はあくまで「Rust 実装の挙動を SSOT として表+テストに落とす」フェーズであり、
|
||||||
|
MirBuilder/LoopForm/IfForm/BodyLocalPhiBuilder のロジック変更は 26.x までに終わっていることを前提にする。
|
||||||
|
- `.hako` MirBuilder 実装の本格実装:
|
||||||
|
- ここでは Box のシグネチャと責務、テスト用の I/F だけを決める。実装は 25.2/25.2b などのフェーズで段階的に行う。
|
||||||
|
- GC や Region/RefSlotKind の統合:
|
||||||
|
- 25.1l の Region 観測レイヤーはあくまで Rust 側のみ。
|
||||||
|
`.hako` 側 GC/寿命管理は別フェーズ(25.1m 以降)の仕事とし、MirBuilder Self‑Host とは分離する。
|
||||||
|
|
||||||
|
## 受け入れ条件(25.1n)
|
||||||
|
|
||||||
|
- Docs:
|
||||||
|
- LoopForm/IfForm/BodyLocal/PhiBuilder について、SSA/PHI の挙動が表形式で整理されている(Rust コードを読まずに「この形ならどの PHI が立つか」が分かる)。
|
||||||
|
- Self‑Host 用 MirBuilderBox / LoopFormBox / PhiBuilderBox の .hako 側 I/F が定義されている(未実装でも良い)。
|
||||||
|
- テスト:
|
||||||
|
- Rust MirBuilder オラクルテストが 2〜3 本(LoopForm ケース / UsingResolver / Stage‑1 CLI minimal)追加され、安定して緑になっている。
|
||||||
|
- これらのテストは「将来 .hako 実装と比較する」前提で、MIR 構造を固定する役割を持つ。
|
||||||
|
- 実装範囲:
|
||||||
|
- Rust 側の MirBuilder ロジックには手を入れていない(設計とテストの “凍結フェーズ” として完了できている)。
|
||||||
|
|
||||||
@ -1147,9 +1147,23 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
let mut ops = Ops(self);
|
let mut ops = Ops(self);
|
||||||
|
|
||||||
|
// Phase 26-F: BodyLocalPhiBuilder setup for if-merge PHI filtering
|
||||||
|
// Purpose: Filter out BodyLocalInternal variables (defined in only some branches)
|
||||||
|
let inspector = crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new();
|
||||||
|
let classifier = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
|
||||||
|
let body_local_builder =
|
||||||
|
crate::mir::phi_core::body_local_phi_builder::BodyLocalPhiBuilder::new(
|
||||||
|
classifier,
|
||||||
|
inspector,
|
||||||
|
);
|
||||||
|
|
||||||
// Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成)
|
// Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成)
|
||||||
// Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis()
|
// Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis()
|
||||||
let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new();
|
let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new();
|
||||||
|
|
||||||
|
// Phase 26-F: Set BodyLocal filter for PHI generation
|
||||||
|
phi_builder.set_body_local_filter(body_local_builder);
|
||||||
|
|
||||||
let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt {
|
let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt {
|
||||||
vec![then_var_map_end.clone(), else_map.clone()]
|
vec![then_var_map_end.clone(), else_map.clone()]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ use crate::mir::BasicBlockId;
|
|||||||
/// // Generate exit PHI
|
/// // Generate exit PHI
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BodyLocalPhiBuilder {
|
pub struct BodyLocalPhiBuilder {
|
||||||
/// Variable classifier
|
/// Variable classifier
|
||||||
classifier: LoopVarClassBox,
|
classifier: LoopVarClassBox,
|
||||||
@ -151,6 +152,86 @@ impl BodyLocalPhiBuilder {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Filter variables for if-merge PHI generation (Phase 26-F)
|
||||||
|
///
|
||||||
|
/// # Purpose
|
||||||
|
/// IfForm body内のif-merge地点でPHI生成すべき変数をフィルタリング。
|
||||||
|
/// BodyLocalInternal変数(一部ブランチでのみ定義)はPHI候補から除外。
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `pre_if` - if直前のvariable_map
|
||||||
|
/// * `then_end` - thenブランチ終端時のvariable_map
|
||||||
|
/// * `else_end_opt` - elseブランチ終端時のvariable_map(なければNone)
|
||||||
|
/// * `reachable_preds` - mergeに到達するpredブロック一覧(breakで終わるブランチは含めない)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// PHI生成すべき変数名のリスト
|
||||||
|
///
|
||||||
|
/// # Classification Logic
|
||||||
|
/// - Pinned: 常にPHI生成(ループ外から来る変数)
|
||||||
|
/// - Carrier: 常にPHI生成(ループ内で更新される変数)
|
||||||
|
/// - BodyLocalExit: 全ブランチで定義 → PHI生成
|
||||||
|
/// - BodyLocalInternal: 一部ブランチでのみ定義 → PHI生成しない
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```ignore
|
||||||
|
/// // if (cond) { ch = read() } else { /* ch未定義 */ }
|
||||||
|
/// // この場合、chはBodyLocalInternal → PHI候補から除外
|
||||||
|
///
|
||||||
|
/// use std::collections::BTreeMap;
|
||||||
|
/// let pre_if = BTreeMap::new();
|
||||||
|
/// let mut then_end = BTreeMap::new();
|
||||||
|
/// then_end.insert("ch".to_string(), ValueId(5));
|
||||||
|
/// let else_end_opt = Some(BTreeMap::new()); // chなし
|
||||||
|
///
|
||||||
|
/// let phi_vars = builder.filter_if_merge_candidates(
|
||||||
|
/// &pre_if,
|
||||||
|
/// &then_end,
|
||||||
|
/// &else_end_opt,
|
||||||
|
/// &[BasicBlockId(2), BasicBlockId(3)],
|
||||||
|
/// );
|
||||||
|
/// // Result: [] - "ch"はBodyLocalInternalなので除外
|
||||||
|
/// ```
|
||||||
|
pub fn filter_if_merge_candidates(
|
||||||
|
&self,
|
||||||
|
pre_if: &std::collections::BTreeMap<String, crate::mir::ValueId>,
|
||||||
|
then_end: &std::collections::BTreeMap<String, crate::mir::ValueId>,
|
||||||
|
else_end_opt: &Option<std::collections::BTreeMap<String, crate::mir::ValueId>>,
|
||||||
|
reachable_preds: &[BasicBlockId],
|
||||||
|
) -> Vec<String> {
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
// 1. 全ての変数名を収集(pre_if + then_end + else_end_opt)
|
||||||
|
let mut all_vars = BTreeSet::new();
|
||||||
|
all_vars.extend(pre_if.keys().cloned());
|
||||||
|
all_vars.extend(then_end.keys().cloned());
|
||||||
|
if let Some(else_end) = else_end_opt {
|
||||||
|
all_vars.extend(else_end.keys().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 各変数を分類してBodyLocalInternal以外を残す
|
||||||
|
all_vars
|
||||||
|
.into_iter()
|
||||||
|
.filter(|var_name| {
|
||||||
|
// LoopVarClassBox::classify を使用
|
||||||
|
// pinned_vars/carrier_varsは空(if内のローカル変数のみ対象)
|
||||||
|
let class = self.classifier.classify(
|
||||||
|
var_name,
|
||||||
|
&[], // pinned_vars(if-merge時は通常空)
|
||||||
|
&[], // carrier_vars(if-merge時は通常空)
|
||||||
|
&self.inspector,
|
||||||
|
reachable_preds,
|
||||||
|
);
|
||||||
|
|
||||||
|
// BodyLocalInternalはスキップ、それ以外はPHI生成
|
||||||
|
match class {
|
||||||
|
LoopVarClass::BodyLocalInternal => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get mutable reference to inspector
|
/// Get mutable reference to inspector
|
||||||
///
|
///
|
||||||
/// # Purpose
|
/// # Purpose
|
||||||
|
|||||||
@ -55,11 +55,19 @@ pub struct PhiBuilderBox {
|
|||||||
loop_context: Option<LoopPhiContext>,
|
loop_context: Option<LoopPhiContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If PHI生成コンテキスト(将来拡張用)
|
/// If PHI生成コンテキスト(Phase 26-F拡張)
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
struct IfPhiContext {
|
struct IfPhiContext {
|
||||||
/// 予約済み(Phase 2で実装)
|
/// Phase 26-F: BodyLocal変数フィルター(BodyLocalInternal除外用)
|
||||||
_reserved: (),
|
body_local_filter: Option<crate::mir::phi_core::body_local_phi_builder::BodyLocalPhiBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for IfPhiContext {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("IfPhiContext")
|
||||||
|
.field("body_local_filter", &self.body_local_filter.is_some())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loop PHI生成コンテキスト(将来拡張用)
|
/// Loop PHI生成コンテキスト(将来拡張用)
|
||||||
@ -78,6 +86,30 @@ impl PhiBuilderBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set BodyLocal filter for If PHI generation (Phase 26-F)
|
||||||
|
///
|
||||||
|
/// # Purpose
|
||||||
|
/// If-merge PHI生成時にBodyLocalInternal変数をフィルタリング。
|
||||||
|
/// BodyLocalPhiBuilderを使って、一部ブランチでのみ定義された変数を除外。
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `filter` - BodyLocalPhiBuilder instance for filtering
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
/// ```ignore
|
||||||
|
/// let mut phi_builder = PhiBuilderBox::new();
|
||||||
|
/// phi_builder.set_body_local_filter(body_local_builder);
|
||||||
|
/// phi_builder.generate_phis(&mut ops, &form, &pre, &post)?;
|
||||||
|
/// ```
|
||||||
|
pub fn set_body_local_filter(
|
||||||
|
&mut self,
|
||||||
|
filter: crate::mir::phi_core::body_local_phi_builder::BodyLocalPhiBuilder,
|
||||||
|
) {
|
||||||
|
self.if_context = Some(IfPhiContext {
|
||||||
|
body_local_filter: Some(filter),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// ControlFormベースの統一PHI生成エントリーポイント
|
/// ControlFormベースの統一PHI生成エントリーポイント
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -162,7 +194,12 @@ impl PhiBuilderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute modified variables (決定的順序: BTreeSet使用)
|
// Compute modified variables (決定的順序: BTreeSet使用)
|
||||||
let modified_vars = self.compute_modified_names_if(pre_snapshot, then_end, &else_end_opt);
|
let modified_vars = self.compute_modified_names_if(
|
||||||
|
pre_snapshot,
|
||||||
|
then_end,
|
||||||
|
&else_end_opt,
|
||||||
|
if_shape,
|
||||||
|
);
|
||||||
|
|
||||||
for var_name in modified_vars {
|
for var_name in modified_vars {
|
||||||
// Conservative strategy: get values with void fallback
|
// Conservative strategy: get values with void fallback
|
||||||
@ -209,16 +246,24 @@ impl PhiBuilderBox {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute modified variable names for If (決定的順序)
|
/// Compute modified variable names for If (決定的順序 + Phase 26-F filtering)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `pre_snapshot` - if直前の変数スナップショット
|
||||||
|
/// * `then_end` - thenブランチ終端の変数スナップショット
|
||||||
|
/// * `else_end_opt` - elseブランチ終端の変数スナップショット(なければNone)
|
||||||
|
/// * `if_shape` - IfShape(reachable_preds取得用)
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// ソート済みの変更変数名リスト(BTreeSetにより決定的)
|
/// ソート済みの変更変数名リスト(BTreeSetにより決定的)
|
||||||
|
/// Phase 26-F: BodyLocalInternal変数はフィルタリングで除外
|
||||||
fn compute_modified_names_if(
|
fn compute_modified_names_if(
|
||||||
&self,
|
&self,
|
||||||
pre_snapshot: &BTreeMap<String, ValueId>,
|
pre_snapshot: &BTreeMap<String, ValueId>,
|
||||||
then_end: &BTreeMap<String, ValueId>,
|
then_end: &BTreeMap<String, ValueId>,
|
||||||
else_end_opt: &Option<&BTreeMap<String, ValueId>>,
|
else_end_opt: &Option<&BTreeMap<String, ValueId>>,
|
||||||
|
if_shape: &IfShape,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
@ -248,6 +293,39 @@ impl PhiBuilderBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 26-F: BodyLocalPhiBuilderフィルター適用
|
||||||
|
if let Some(ref ctx) = self.if_context {
|
||||||
|
if let Some(ref filter) = ctx.body_local_filter {
|
||||||
|
// reachable_preds取得(then_block と else_block)
|
||||||
|
let mut reachable_preds = Vec::new();
|
||||||
|
if let Some(then_block) = if_shape.then_block.into() {
|
||||||
|
reachable_preds.push(then_block);
|
||||||
|
}
|
||||||
|
if let Some(else_block) = if_shape.else_block {
|
||||||
|
reachable_preds.push(else_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else_end_optをOption<BTreeMap>に変換
|
||||||
|
let else_end_owned = else_end_opt.map(|m| m.clone());
|
||||||
|
|
||||||
|
// BodyLocalPhiBuilderでフィルタリング
|
||||||
|
changed = filter.filter_if_merge_candidates(
|
||||||
|
pre_snapshot,
|
||||||
|
then_end,
|
||||||
|
&else_end_owned,
|
||||||
|
&reachable_preds,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Debug trace
|
||||||
|
if std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!(
|
||||||
|
"[PhiBuilderBox/if] BodyLocal filtering applied, {} candidates",
|
||||||
|
changed.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user