feat(joinir): Phase 77 - BindingId expansion implementation (dev-only)

Phase 77 implements promoted_bindings population in DigitPos/Trim promoters
and deprecates legacy name-based resolution paths.

Changes:
- DigitPosPromoter: Added binding_map field and record_promoted_binding() calls
- TrimLoopHelper: Added binding_map field for ch → is_ch_match tracking
- Pattern2/4: Wired binding_map from builder.binding_map to promoters
- Legacy deprecation: Added #[deprecated] to resolve_promoted_join_id()
- Dual-path design: BindingId priority + name fallback maintained

Files modified (8):
- pattern2_with_break.rs, pattern4_with_continue.rs: binding_map wiring
- trim_loop_lowering.rs: Trim promoter integration
- carrier_info.rs: Deprecation warnings
- scope_manager.rs: Deprecated fallback paths
- loop_body_carrier_promoter.rs: Trim binding_map support
- loop_body_cond_promoter.rs: Orchestrator updates
- loop_body_digitpos_promoter.rs: DigitPos binding_map support

Tests: 958/958 PASS (zero regressions)
Design: Type-safe BindingId mapping replaces string-based promoted variable resolution

Phase 78+ will delete deprecated code (~50 lines) and make binding_map required.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-13 05:58:57 +09:00
parent 65ecd75529
commit 72173c1ac8
8 changed files with 153 additions and 2 deletions

View File

@ -250,6 +250,8 @@ fn promote_and_prepare_carriers(
break_cond: Some(&inputs.break_condition_node), break_cond: Some(&inputs.break_condition_node),
continue_cond: None, continue_cond: None,
loop_body: body, loop_body: body,
#[cfg(feature = "normalized_dev")]
binding_map: Some(&builder.binding_map),
}; };
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {

View File

@ -239,6 +239,8 @@ fn prepare_pattern4_context(
break_cond: None, // Pattern 4 has no break break_cond: None, // Pattern 4 has no break
continue_cond, continue_cond,
loop_body: &normalized_body, loop_body: &normalized_body,
#[cfg(feature = "normalized_dev")]
binding_map: Some(&builder.binding_map),
}; };
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {

View File

@ -226,6 +226,8 @@ impl TrimLoopLowerer {
cond_scope: &cond_scope, cond_scope: &cond_scope,
break_cond: Some(break_cond), break_cond: Some(break_cond),
loop_body: body, loop_body: body,
#[cfg(feature = "normalized_dev")]
binding_map: Some(&builder.binding_map),
}; };
match LoopBodyCarrierPromoter::try_promote(&request) { match LoopBodyCarrierPromoter::try_promote(&request) {
@ -236,6 +238,9 @@ impl TrimLoopLowerer {
); );
// Step 3: Convert to CarrierInfo and merge // Step 3: Convert to CarrierInfo and merge
#[cfg(feature = "normalized_dev")]
let promoted_carrier = trim_info.to_carrier_info(Some(&builder.binding_map));
#[cfg(not(feature = "normalized_dev"))]
let promoted_carrier = trim_info.to_carrier_info(); let promoted_carrier = trim_info.to_carrier_info();
carrier_info.merge_from(&promoted_carrier); carrier_info.merge_from(&promoted_carrier);

View File

@ -491,7 +491,22 @@ impl CarrierInfo {
/// ///
/// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合 /// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合
/// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合 /// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合
///
/// # Phase 77: DEPRECATED
///
/// This method uses fragile naming conventions ("is_*", "is_*_match") and will
/// be removed in Phase 78+ when all call sites migrate to BindingId-based lookup.
/// Use `resolve_promoted_with_binding()` for type-safe BindingId lookup.
#[deprecated(
since = "phase77",
note = "Use resolve_promoted_with_binding() for type-safe BindingId lookup"
)]
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> { pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
#[cfg(feature = "normalized_dev")]
eprintln!(
"[phase77/legacy/carrier_info] WARNING: Using deprecated name-based promoted lookup for '{}'",
original_name
);
if !self if !self
.promoted_loopbodylocals .promoted_loopbodylocals
.contains(&original_name.to_string()) .contains(&original_name.to_string())

View File

@ -290,6 +290,13 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
} }
// Step 3: Fallback to legacy name-based lookup (Phase 75 fallback path) // Step 3: Fallback to legacy name-based lookup (Phase 75 fallback path)
// Phase 77: DEPRECATED - Will be removed in Phase 78+
#[cfg(feature = "normalized_dev")]
eprintln!(
"[phase77/fallback] WARNING: BindingId({}) for '{}' not resolved, falling back to name-based lookup (DEPRECATED)",
bid.0, name
);
#[cfg(not(feature = "normalized_dev"))]
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() { if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
eprintln!( eprintln!(
"[phase76/fallback] BindingId({}) miss, falling back to name '{}' lookup", "[phase76/fallback] BindingId({}) miss, falling back to name '{}' lookup",
@ -298,7 +305,8 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
} }
} }
// Step 4: Legacy name-based lookup (Phase 75 behavior, will be removed in Phase 77) // Step 4: Legacy name-based lookup (Phase 75 behavior)
// Phase 77: DEPRECATED - Will be removed in Phase 78+ after all call sites use BindingId
self.lookup(name) self.lookup(name)
} }
} }

View File

@ -35,6 +35,10 @@ pub struct PromotionRequest<'a> {
pub break_cond: Option<&'a ASTNode>, pub break_cond: Option<&'a ASTNode>,
/// ループ本体の AST /// ループ本体の AST
pub loop_body: &'a [ASTNode], pub loop_body: &'a [ASTNode],
/// Phase 77: Optional BindingId map for type-safe promotion tracking (dev-only)
#[cfg(feature = "normalized_dev")]
pub binding_map: Option<&'a std::collections::BTreeMap<String, crate::mir::BindingId>>,
} }
/// Phase 171-C-2: 検出された Trim パターン情報 /// Phase 171-C-2: 検出された Trim パターン情報
@ -54,6 +58,10 @@ impl TrimPatternInfo {
/// Creates a CarrierInfo containing a single bool carrier representing /// Creates a CarrierInfo containing a single bool carrier representing
/// the Trim pattern match condition (e.g., "is_whitespace"). /// the Trim pattern match condition (e.g., "is_whitespace").
/// ///
/// # Arguments
///
/// * `binding_map` - Optional BindingId map for Phase 77 type-safe promotion tracking
///
/// # Returns /// # Returns
/// ///
/// CarrierInfo with: /// CarrierInfo with:
@ -67,7 +75,11 @@ impl TrimPatternInfo {
/// - This is JoinIR-local ID space (not host ValueId space) /// - This is JoinIR-local ID space (not host ValueId space)
/// - The actual host ValueId will be assigned during merge_joinir_mir_blocks /// - The actual host ValueId will be assigned during merge_joinir_mir_blocks
/// - JoinInlineBoundary will handle the boundary mapping /// - JoinInlineBoundary will handle the boundary mapping
pub fn to_carrier_info(&self) -> crate::mir::join_ir::lowering::carrier_info::CarrierInfo { pub fn to_carrier_info(
&self,
#[cfg(feature = "normalized_dev")]
binding_map: Option<&std::collections::BTreeMap<String, crate::mir::BindingId>>,
) -> crate::mir::join_ir::lowering::carrier_info::CarrierInfo {
use super::trim_loop_helper::TrimLoopHelper; use super::trim_loop_helper::TrimLoopHelper;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
use crate::mir::ValueId; use crate::mir::ValueId;
@ -89,6 +101,39 @@ impl TrimPatternInfo {
.promoted_loopbodylocals .promoted_loopbodylocals
.push(self.var_name.clone()); .push(self.var_name.clone());
// Phase 77: Type-safe BindingId promotion tracking
#[cfg(feature = "normalized_dev")]
if let Some(binding_map) = binding_map {
// Look up original and promoted BindingIds
let original_binding_opt = binding_map.get(&self.var_name);
let promoted_binding_opt = binding_map.get(&self.carrier_name);
match (original_binding_opt, promoted_binding_opt) {
(Some(&original_bid), Some(&promoted_bid)) => {
carrier_info.record_promoted_binding(original_bid, promoted_bid);
eprintln!(
"[trim_lowerer/phase77] Recorded promoted binding: {} (BindingId({:?})) → {} (BindingId({:?}))",
self.var_name, original_bid, self.carrier_name, promoted_bid
);
}
(None, _) => {
eprintln!(
"[trim_lowerer/phase77] WARNING: Original variable '{}' not found in binding_map",
self.var_name
);
}
(_, None) => {
eprintln!(
"[trim_lowerer/phase77] WARNING: Promoted carrier '{}' not found in binding_map",
self.carrier_name
);
}
}
} else {
#[cfg(feature = "normalized_dev")]
eprintln!("[trim_lowerer/phase77] INFO: binding_map not provided (legacy mode)");
}
carrier_info carrier_info
} }
} }

View File

@ -53,6 +53,10 @@ pub struct ConditionPromotionRequest<'a> {
/// Loop body statements /// Loop body statements
pub loop_body: &'a [ASTNode], pub loop_body: &'a [ASTNode],
/// Phase 77: Optional BindingId map for type-safe promotion tracking (dev-only)
#[cfg(feature = "normalized_dev")]
pub binding_map: Option<&'a std::collections::BTreeMap<String, crate::mir::BindingId>>,
} }
/// Promotion result /// Promotion result
@ -175,6 +179,8 @@ impl LoopBodyCondPromoter {
cond_scope: req.cond_scope, cond_scope: req.cond_scope,
break_cond: condition_for_promotion, break_cond: condition_for_promotion,
loop_body: req.loop_body, loop_body: req.loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: req.binding_map,
}; };
match LoopBodyCarrierPromoter::try_promote(&promotion_request) { match LoopBodyCarrierPromoter::try_promote(&promotion_request) {
@ -185,6 +191,9 @@ impl LoopBodyCondPromoter {
); );
// Convert TrimPatternInfo to CarrierInfo // Convert TrimPatternInfo to CarrierInfo
#[cfg(feature = "normalized_dev")]
let carrier_info = trim_info.to_carrier_info(req.binding_map);
#[cfg(not(feature = "normalized_dev"))]
let carrier_info = trim_info.to_carrier_info(); let carrier_info = trim_info.to_carrier_info();
return ConditionPromotionResult::Promoted { return ConditionPromotionResult::Promoted {
@ -207,6 +216,8 @@ impl LoopBodyCondPromoter {
break_cond: req.break_cond, break_cond: req.break_cond,
continue_cond: req.continue_cond, continue_cond: req.continue_cond,
loop_body: req.loop_body, loop_body: req.loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: req.binding_map,
}; };
match DigitPosPromoter::try_promote(digitpos_request) { match DigitPosPromoter::try_promote(digitpos_request) {
@ -373,6 +384,8 @@ mod tests {
break_cond: None, break_cond: None,
continue_cond: None, continue_cond: None,
loop_body: &[], loop_body: &[],
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match LoopBodyCondPromoter::try_promote_for_condition(req) { match LoopBodyCondPromoter::try_promote_for_condition(req) {
@ -395,6 +408,8 @@ mod tests {
break_cond: None, break_cond: None,
continue_cond: None, continue_cond: None,
loop_body: &[], loop_body: &[],
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match LoopBodyCondPromoter::try_promote_for_condition(req) { match LoopBodyCondPromoter::try_promote_for_condition(req) {
@ -426,6 +441,8 @@ mod tests {
break_cond: None, break_cond: None,
continue_cond: Some(&continue_cond), continue_cond: Some(&continue_cond),
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match LoopBodyCondPromoter::try_promote_for_condition(req) { match LoopBodyCondPromoter::try_promote_for_condition(req) {
@ -463,6 +480,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match LoopBodyCondPromoter::try_promote_for_condition(req) { match LoopBodyCondPromoter::try_promote_for_condition(req) {
@ -493,6 +512,8 @@ mod tests {
break_cond: None, break_cond: None,
continue_cond: Some(&continue_cond), continue_cond: Some(&continue_cond),
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match LoopBodyCondPromoter::try_promote_for_condition(req) { match LoopBodyCondPromoter::try_promote_for_condition(req) {

View File

@ -60,6 +60,14 @@ pub struct DigitPosPromotionRequest<'a> {
/// Loop body statements /// Loop body statements
pub loop_body: &'a [ASTNode], pub loop_body: &'a [ASTNode],
/// Phase 77: Optional BindingId map for type-safe promotion tracking (dev-only)
///
/// When provided, the promoter will record promoted bindings
/// (e.g., BindingId(5) for "digit_pos" → BindingId(10) for "is_digit_pos")
/// in CarrierInfo.promoted_bindings.
#[cfg(feature = "normalized_dev")]
pub binding_map: Option<&'a std::collections::BTreeMap<String, crate::mir::BindingId>>,
} }
/// Promotion result /// Promotion result
@ -228,6 +236,39 @@ impl DigitPosPromoter {
.promoted_loopbodylocals .promoted_loopbodylocals
.push(var_in_cond.clone()); .push(var_in_cond.clone());
// Phase 77: Type-safe BindingId promotion tracking
#[cfg(feature = "normalized_dev")]
if let Some(binding_map) = req.binding_map {
// Look up original and promoted BindingIds
let original_binding_opt = binding_map.get(&var_in_cond);
let promoted_bool_binding_opt = binding_map.get(&bool_carrier_name);
match (original_binding_opt, promoted_bool_binding_opt) {
(Some(&original_bid), Some(&promoted_bid)) => {
carrier_info.record_promoted_binding(original_bid, promoted_bid);
eprintln!(
"[digitpos_promoter/phase77] Recorded promoted binding: {} (BindingId({:?})) → {} (BindingId({:?}))",
var_in_cond, original_bid, bool_carrier_name, promoted_bid
);
}
(None, _) => {
eprintln!(
"[digitpos_promoter/phase77] WARNING: Original variable '{}' not found in binding_map",
var_in_cond
);
}
(_, None) => {
eprintln!(
"[digitpos_promoter/phase77] WARNING: Promoted carrier '{}' not found in binding_map",
bool_carrier_name
);
}
}
} else {
#[cfg(feature = "normalized_dev")]
eprintln!("[digitpos_promoter/phase77] INFO: binding_map not provided (legacy mode)");
}
eprintln!( eprintln!(
"[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {}{} (bool) + {} (i64)", "[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {}{} (bool) + {} (i64)",
var_in_cond, bool_carrier_name, int_carrier_name var_in_cond, bool_carrier_name, int_carrier_name
@ -526,6 +567,8 @@ mod tests {
break_cond: None, break_cond: None,
continue_cond: None, continue_cond: None,
loop_body: &[], loop_body: &[],
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {
@ -563,6 +606,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {
@ -601,6 +646,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {
@ -639,6 +686,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {
@ -680,6 +729,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {
@ -722,6 +773,8 @@ mod tests {
break_cond: Some(&break_cond), break_cond: Some(&break_cond),
continue_cond: None, continue_cond: None,
loop_body: &loop_body, loop_body: &loop_body,
#[cfg(feature = "normalized_dev")]
binding_map: None,
}; };
match DigitPosPromoter::try_promote(req) { match DigitPosPromoter::try_promote(req) {