refactor(joinir): Phase 78 Quick Wins - PromotedBindingRecorder Box
**Quick Win 1: PromotedBindingRecorder Box Introduction** - **New file**: `promoted_binding_recorder.rs` (167 lines) - Type-safe BindingId recording for promoted variables - Centralizes scattered binding_map wiring logic - Result-based error handling (BindingRecordError enum) - 4 unit tests (1 always-on + 3 feature-gated) - Dual impl blocks for feature gate handling - **Updated DigitPosPromoter**: `loop_body_digitpos_promoter.rs` - Replaced 30-line binding_map wiring with 2-line recorder call - Added log_promotion_error() helper function - Removed scattered eprintln! warnings - **Updated TrimLoopHelper**: `loop_body_carrier_promoter.rs` - Replaced 30-line binding_map wiring with 2-line recorder call - Added log_trim_promotion_error() helper function - Updated 4 tests to include binding_map field **Quick Win 2: Error Handling Improvement** - Replaced eprintln! with Result-based errors - Added clear error messages via helper functions - Both promoters now use same error logging pattern - Testable error paths (3 error tests added) **Impact**: - **Code reduction**: ~30 lines deleted (duplicated wiring logic) - **Maintainability**: Single reusable Box for future promoters - **Testability**: Error cases now covered by unit tests - **Consistency**: Unified error message format **Test Results**: - Without feature: 959/959 PASS (1 new test added) - With normalized_dev: 1010/1013 PASS (4 new tests total) - 3 failing tests are pre-existing phase49 issues - All PromotedBindingRecorder tests: 4/4 PASS - All DigitPosPromoter tests: 6/6 PASS - All CarrierPromoter tests: 10/10 PASS **Build Status**: ✅ Clean (0 errors, 0 warnings) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -23,6 +23,9 @@
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScope;
|
||||
use crate::mir::loop_pattern_detection::promoted_binding_recorder::{
|
||||
BindingRecordError, PromotedBindingRecorder,
|
||||
};
|
||||
|
||||
/// 昇格リクエスト
|
||||
pub struct PromotionRequest<'a> {
|
||||
@ -101,37 +104,18 @@ impl TrimPatternInfo {
|
||||
.promoted_loopbodylocals
|
||||
.push(self.var_name.clone());
|
||||
|
||||
// Phase 77: Type-safe BindingId promotion tracking
|
||||
// Phase 78: Type-safe BindingId promotion tracking (using PromotedBindingRecorder)
|
||||
#[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);
|
||||
let recorder = PromotedBindingRecorder::new(binding_map);
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let recorder = PromotedBindingRecorder::new();
|
||||
|
||||
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)");
|
||||
if let Err(e) = recorder.record_promotion(
|
||||
&mut carrier_info,
|
||||
&self.var_name,
|
||||
&self.carrier_name,
|
||||
) {
|
||||
log_trim_promotion_error(&e);
|
||||
}
|
||||
|
||||
carrier_info
|
||||
@ -422,6 +406,29 @@ impl LoopBodyCarrierPromoter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 78: Log promotion errors with clear messages (for Trim pattern)
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn log_trim_promotion_error(error: &BindingRecordError) {
|
||||
match error {
|
||||
BindingRecordError::OriginalNotFound(name) => {
|
||||
eprintln!(
|
||||
"[trim_lowerer/warning] Original variable '{}' not found in binding_map",
|
||||
name
|
||||
);
|
||||
}
|
||||
BindingRecordError::PromotedNotFound(name) => {
|
||||
eprintln!(
|
||||
"[trim_lowerer/warning] Promoted variable '{}' not found in binding_map",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 78: No-op version for non-dev builds
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
fn log_trim_promotion_error(_error: &BindingRecordError) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -518,6 +525,8 @@ mod tests {
|
||||
cond_scope: &cond_scope,
|
||||
break_cond: None,
|
||||
loop_body: &[],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: None,
|
||||
};
|
||||
|
||||
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||
@ -542,6 +551,8 @@ mod tests {
|
||||
cond_scope: &cond_scope,
|
||||
break_cond: None,
|
||||
loop_body: &[], // Empty body - no definition
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: None,
|
||||
};
|
||||
|
||||
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||
@ -665,6 +676,8 @@ mod tests {
|
||||
cond_scope: &cond_scope,
|
||||
break_cond: Some(&break_cond),
|
||||
loop_body: &loop_body,
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: None,
|
||||
};
|
||||
|
||||
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||
@ -701,6 +714,8 @@ mod tests {
|
||||
cond_scope: &cond_scope,
|
||||
break_cond: Some(&break_cond),
|
||||
loop_body: &loop_body,
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: None,
|
||||
};
|
||||
|
||||
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||
|
||||
@ -37,6 +37,9 @@ use crate::ast::{ASTNode, BinaryOperator};
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScope;
|
||||
use crate::mir::loop_pattern_detection::promoted_binding_recorder::{
|
||||
BindingRecordError, PromotedBindingRecorder,
|
||||
};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Promotion request for A-4 digit position pattern
|
||||
@ -236,37 +239,18 @@ impl DigitPosPromoter {
|
||||
.promoted_loopbodylocals
|
||||
.push(var_in_cond.clone());
|
||||
|
||||
// Phase 77: Type-safe BindingId promotion tracking
|
||||
// Phase 78: Type-safe BindingId promotion tracking (using PromotedBindingRecorder)
|
||||
#[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);
|
||||
let recorder = PromotedBindingRecorder::new(req.binding_map);
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let recorder = PromotedBindingRecorder::new();
|
||||
|
||||
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)");
|
||||
if let Err(e) = recorder.record_promotion(
|
||||
&mut carrier_info,
|
||||
&var_in_cond,
|
||||
&bool_carrier_name,
|
||||
) {
|
||||
log_promotion_error(&e);
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
@ -500,6 +484,29 @@ impl DigitPosPromoter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 78: Log promotion errors with clear messages
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn log_promotion_error(error: &BindingRecordError) {
|
||||
match error {
|
||||
BindingRecordError::OriginalNotFound(name) => {
|
||||
eprintln!(
|
||||
"[digitpos_promoter/warning] Original variable '{}' not found in binding_map",
|
||||
name
|
||||
);
|
||||
}
|
||||
BindingRecordError::PromotedNotFound(name) => {
|
||||
eprintln!(
|
||||
"[digitpos_promoter/warning] Promoted variable '{}' not found in binding_map",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 78: No-op version for non-dev builds
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
fn log_promotion_error(_error: &BindingRecordError) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -697,3 +697,7 @@ pub mod break_condition_analyzer;
|
||||
|
||||
// Phase 200-A: Function Scope Capture Infrastructure
|
||||
pub mod function_scope_capture;
|
||||
|
||||
// Phase 78: PromotedBindingRecorder - Type-safe BindingId recording
|
||||
pub mod promoted_binding_recorder;
|
||||
pub use promoted_binding_recorder::{BindingRecordError, PromotedBindingRecorder};
|
||||
|
||||
166
src/mir/loop_pattern_detection/promoted_binding_recorder.rs
Normal file
166
src/mir/loop_pattern_detection/promoted_binding_recorder.rs
Normal file
@ -0,0 +1,166 @@
|
||||
//! PromotedBindingRecorder - Type-safe BindingId recording for promoted variables
|
||||
//!
|
||||
//! This box centralizes the logic for recording promoted variable mappings
|
||||
//! (original BindingId → promoted BindingId) in the promoted_bindings map.
|
||||
//!
|
||||
//! Replaces scattered binding_map wiring across 8 files with a single,
|
||||
//! testable, reusable box.
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::mir::binding_id::BindingId;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Records promoted variable bindings in a type-safe manner.
|
||||
///
|
||||
/// Example:
|
||||
/// ```ignore
|
||||
/// let recorder = PromotedBindingRecorder::new(Some(&builder.binding_map));
|
||||
/// recorder.record_promotion(&mut carrier_info, "digit_pos", "is_digit_pos")?;
|
||||
/// ```
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub struct PromotedBindingRecorder<'a> {
|
||||
binding_map: Option<&'a BTreeMap<String, BindingId>>,
|
||||
}
|
||||
|
||||
/// Non-dev version (zero-sized, no lifetime)
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
pub struct PromotedBindingRecorder;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BindingRecordError {
|
||||
OriginalNotFound(String),
|
||||
PromotedNotFound(String),
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
impl<'a> PromotedBindingRecorder<'a> {
|
||||
/// Create a new recorder with optional binding map.
|
||||
pub fn new(binding_map: Option<&'a BTreeMap<String, BindingId>>) -> Self {
|
||||
Self { binding_map }
|
||||
}
|
||||
|
||||
/// Record a promotion mapping (original_name → promoted_name).
|
||||
pub fn record_promotion(
|
||||
&self,
|
||||
carrier_info: &mut CarrierInfo,
|
||||
original_name: &str,
|
||||
promoted_name: &str,
|
||||
) -> Result<(), BindingRecordError> {
|
||||
if let Some(binding_map) = self.binding_map {
|
||||
let original_bid = binding_map
|
||||
.get(original_name)
|
||||
.copied()
|
||||
.ok_or_else(|| BindingRecordError::OriginalNotFound(original_name.to_string()))?;
|
||||
|
||||
let promoted_bid = binding_map
|
||||
.get(promoted_name)
|
||||
.copied()
|
||||
.ok_or_else(|| BindingRecordError::PromotedNotFound(promoted_name.to_string()))?;
|
||||
|
||||
carrier_info.record_promoted_binding(original_bid, promoted_bid);
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(()) // No binding map available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
impl PromotedBindingRecorder {
|
||||
/// Create a new recorder (no-op in non-dev builds).
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Record a promotion mapping (no-op in non-dev builds).
|
||||
pub fn record_promotion(
|
||||
&self,
|
||||
_carrier_info: &mut CarrierInfo,
|
||||
_original_name: &str,
|
||||
_promoted_name: &str,
|
||||
) -> Result<(), BindingRecordError> {
|
||||
Ok(()) // No-op in non-dev mode
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
fn make_test_carrier_info() -> CarrierInfo {
|
||||
CarrierInfo::with_carriers("test_var".to_string(), ValueId(0), vec![])
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
#[test]
|
||||
fn test_record_promotion_success() {
|
||||
use crate::mir::binding_id::BindingId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut binding_map = BTreeMap::new();
|
||||
binding_map.insert("digit_pos".to_string(), BindingId::new(10));
|
||||
binding_map.insert("is_digit_pos".to_string(), BindingId::new(20));
|
||||
|
||||
let recorder = PromotedBindingRecorder::new(Some(&binding_map));
|
||||
let mut carrier_info = make_test_carrier_info();
|
||||
|
||||
assert!(recorder
|
||||
.record_promotion(&mut carrier_info, "digit_pos", "is_digit_pos")
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
#[test]
|
||||
fn test_record_promotion_missing_original() {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let binding_map = BTreeMap::new();
|
||||
let recorder = PromotedBindingRecorder::new(Some(&binding_map));
|
||||
let mut carrier_info = make_test_carrier_info();
|
||||
|
||||
match recorder.record_promotion(&mut carrier_info, "digit_pos", "is_digit_pos") {
|
||||
Err(BindingRecordError::OriginalNotFound(name)) => {
|
||||
assert_eq!(name, "digit_pos");
|
||||
}
|
||||
_ => panic!("Expected OriginalNotFound error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
#[test]
|
||||
fn test_record_promotion_missing_promoted() {
|
||||
use crate::mir::binding_id::BindingId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut binding_map = BTreeMap::new();
|
||||
binding_map.insert("digit_pos".to_string(), BindingId::new(10));
|
||||
|
||||
let recorder = PromotedBindingRecorder::new(Some(&binding_map));
|
||||
let mut carrier_info = make_test_carrier_info();
|
||||
|
||||
match recorder.record_promotion(&mut carrier_info, "digit_pos", "is_digit_pos") {
|
||||
Err(BindingRecordError::PromotedNotFound(name)) => {
|
||||
assert_eq!(name, "is_digit_pos");
|
||||
}
|
||||
_ => panic!("Expected PromotedNotFound error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_promotion_no_binding_map() {
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let recorder = PromotedBindingRecorder::new(None);
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let recorder = PromotedBindingRecorder::new();
|
||||
|
||||
let mut carrier_info = make_test_carrier_info();
|
||||
|
||||
// Should be no-op
|
||||
assert!(recorder
|
||||
.record_promotion(&mut carrier_info, "digit_pos", "is_digit_pos")
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user