Files
hakorune/docs/development/current/main/phase78-bindingid-promoted-carriers.md
nyash-codex 8b48bec962 feat(joinir): Phase 78 - BindingId infrastructure for promoted carriers (dev-only)
Phase 78 adds infrastructure to assign BindingIds to synthetic promoted
carriers (e.g., is_digit_pos, is_ch_match), enabling type-safe promoted
variable lookup without string-based naming conventions.

Key Changes:
1. CarrierVar.binding_id field (dev-only):
   - Added Option<BindingId> to track BindingId for each carrier
   - Updated all constructors and struct instantiations

2. CarrierBindingAssigner Box (new file, 273 lines):
   - Allocates BindingIds for promoted carriers via builder.allocate_binding_id()
   - Records original → promoted mapping in promoted_bindings
   - Sets binding_id field on promoted CarrierVar
   - Includes 3 comprehensive unit tests

3. ConditionEnv.register_carrier_binding() (new method):
   - Registers carrier BindingId → ValueId mappings
   - Enables type-safe lookup via binding_id_map

4. Logging cleanup:
   - Gated 6 eprintln! statements with NYASH_JOINIR_DEBUG
   - Unified logging tags to [binding_pilot/*]

Design Decisions:
- Promoters create CarrierInfo, lowering code assigns BindingIds
- CarrierBindingAssigner called from Pattern2/4 lowering (has builder access)
- Clear documentation prevents misuse (promoters lack builder access)

Files modified (18):
- carrier_info.rs: binding_id field added to CarrierVar
- carrier_binding_assigner.rs: New Box for BindingId allocation
- condition_env.rs: register_carrier_binding() method
- mod.rs: Module exports
- pattern2_with_break.rs, pattern4_with_continue.rs: Updated for binding_id
- loop_body_*_promoter.rs: Logging cleanup + binding_id in structs
- phase78-bindingid-promoted-carriers.md: Architecture documentation

Tests: 970/970 PASS (zero regressions)
Status: Infrastructure complete, integration deferred to Phase 79

Next Phase: Wire CarrierBindingAssigner in Pattern2/4 lowering + E2E tests

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 16:20:33 +09:00

3.0 KiB
Raw Blame History

Phase 78: BindingId for Promoted Carriers (dev-only)

Goal

Make LoopBodyLocal promotion (DigitPos/Trim) BindingId-aware without relying on fragile name-based hacks like format!("is_{}", name).

This phase introduces a dev-only identity link:

  • BindingId(original LoopBodyLocal)BindingId(promoted carrier)ValueId(join carrier param)

Problem

Promotion creates synthetic carrier names (例: is_digit_pos, is_ch_match) that do not exist as source-level bindings.

  • PromotedBindingRecorder expects both original/promoted names in MirBuilder.binding_map.
  • For promoted carriers this is not true by construction.
  • For LoopBodyLocal variables, promotion can happen before their local is lowered, so the original name may also be absent.

Solution (current state)

1) CarrierVar gets optional BindingId (dev-only)

  • src/mir/join_ir/lowering/carrier_info.rs
    • CarrierVar.binding_id: Option<BindingId> (feature normalized_dev)

2) CarrierBindingAssigner box

  • src/mir/join_ir/lowering/carrier_binding_assigner.rs
    • Ensures both sides have BindingIds:
      • If original_name is missing in builder.binding_map, allocate a temporary BindingId.
      • If promoted_carrier_name is missing, allocate a temporary BindingId.
    • Records:
      • carrier_info.promoted_bindings[original_bid] = promoted_bid
      • CarrierVar.binding_id = Some(promoted_bid) for the promoted carrier
    • Restores builder.binding_map after recording (synthetic names do not leak into the source map).

3) Pattern2/Pattern4 wiring

  • Pattern2 (break): src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs

    • Calls CarrierBindingAssigner::assign_promoted_binding() immediately after promotion.
    • Registers BindingId → ValueId via ConditionEnv.register_carrier_binding() after carrier join_id allocation.
    • Keeps legacy name-based aliasing (digit_posis_digit_pos) for now, but removes local “guess naming” by using the promoted carrier name returned by the promoter.
  • Pattern4 (continue): src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs

    • Calls CarrierBindingAssigner::assign_promoted_binding() after promotion (analysis metadata only, since Pattern4s current lowering path does not use JoinValueSpace params).

Tests

  • Unit test (dev-only): src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs
    • phase78_promoted_binding_is_recorded_for_digitpos
    • Asserts:
      • promoted_bindings has one mapping
      • promoted carrier has binding_id
      • ConditionEnv.binding_id_map[promoted_bid] == promoted_carrier.join_id

Open Follow-ups (Phase 79+)

  1. Wire ScopeManager::lookup_with_binding() into ExprLowerer/ConditionLoweringBox call-sites (so BindingId actually drives resolution).
  2. Extend coverage to Trim and other promotion shapes (additional unit/E2E tests).
  3. Shrink/remove legacy name-based promoted lookup (resolve_promoted_join_id) after call-sites consistently provide BindingId.