Files
hakorune/docs/development/current/main/phase44-shape-capabilities-design.md
nyash-codex c82ae2365f refactor(joinir): Phase 44 - Capability-based shape guard
Refactor shape detection from function-name-based to capability-based
architecture for better extensibility and maintainability.

Key changes:
- ShapeCapabilityKind enum: P2CoreSimple/SkipWs/Atoi/ParseNumber
- ShapeCapability: Capability descriptor with future extensibility
- Helper functions:
  - capability_for_shape(): Map shape to capability
  - is_canonical_shape(): Exact shape-level check
  - is_p2_core_capability(): Broad capability family check
  - is_supported_by_normalized(): Normalized dev support

Benefits:
- Extensibility: Easy to add new capability kinds
- Clarity: Shape purpose explicit in kind names
- Maintainability: Centralized mapping vs scattered ifs
- Future-ready: Infrastructure for carrier roles, method signatures

Backward compatibility:
- Zero behavioral changes (pure refactoring)
- Canonical set preserved: Pattern2Mini, skip_ws mini/real, atoi mini
- All existing code paths unchanged

Tests: 937/937 PASS
Files: +78 lines (shape_guard.rs), design doc created
2025-12-12 04:06:03 +09:00

307 lines
8.4 KiB
Markdown

# Phase 44: Shape Capabilities Design
**Status**: Implemented
**Date**: 2025-12-12
## Overview
Phase 44 converts function-name-based shape detection to capability-based ShapeCapability model.
## Motivation
The previous approach used direct shape enum matching throughout the codebase:
```rust
// Old approach: if explosion when adding new shapes
if shape == Pattern2Mini || shape == JsonparserSkipWsMini || ... {
// canonical handling
}
```
This led to:
- **If explosion**: Every new shape required updates to multiple match expressions
- **Unclear intent**: Shape names don't express purpose/capability
- **Hard to extend**: Adding new shape variants required widespread changes
## Solution: Capability-Based Architecture
Phase 44 introduces a two-level architecture:
1. **Shape Level** (`NormalizedDevShape`): Concrete implementation patterns
2. **Capability Level** (`ShapeCapability`): Abstract capabilities/features
```rust
// New approach: capability-based filtering
let cap = capability_for_shape(&shape);
if is_canonical_p2_core(&cap) {
// canonical handling
}
```
## ShapeCapabilityKind Mapping
| Kind | Shapes | Description |
|------|--------|-------------|
| `P2CoreSimple` | Pattern2Mini, Pattern1Mini | Simple P2 mini patterns (i/acc/n) |
| `P2CoreSkipWs` | JsonparserSkipWsMini, JsonparserSkipWsReal | skip_whitespace loops |
| `P2CoreAtoi` | JsonparserAtoiMini, JsonparserAtoiReal | _atoi number parsing |
| `P2MidParseNumber` | JsonparserParseNumberReal | _parse_number with num_str |
### Future Extensibility
Capability struct designed for future extensions:
```rust
pub struct ShapeCapability {
pub kind: ShapeCapabilityKind,
// Future fields (not yet used):
// pub pattern_kind: LoopPatternKind,
// pub loop_param_count: usize,
// pub carrier_roles: Vec<CarrierRole>,
// pub method_calls: Vec<MethodCallSignature>,
}
```
## Canonical vs Dev Support
### Canonical P2-Core (Phase 41 Definition)
**Always use Normalized→MIR direct path** (mode-independent):
- Pattern2Mini (P2CoreSimple)
- JsonparserSkipWsMini, JsonparserSkipWsReal (P2CoreSkipWs)
- JsonparserAtoiMini (P2CoreAtoi)
**Excluded from canonical** (future expansion candidates):
- Pattern1Mini (also P2CoreSimple, but minimal fallback pattern)
- JsonparserAtoiReal (P2CoreAtoi, but not yet canonical)
- JsonparserParseNumberReal (P2MidParseNumber, mid-tier pattern)
### Supported by NormalizedDev
**All P2-Core capabilities** (canonical + dev):
- P2CoreSimple, P2CoreSkipWs, P2CoreAtoi, P2MidParseNumber
## API Design
### Core Functions
```rust
/// Map shape to capability (primary mapping)
pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability
/// Check if shape is canonical (shape-level, exact)
pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool
/// Check if capability is in P2-Core family (capability-level, broad)
pub fn is_p2_core_capability(cap: &ShapeCapability) -> bool
/// Check if capability is supported by Normalized dev
pub fn is_supported_by_normalized(cap: &ShapeCapability) -> bool
```
### Why Both Shape-Level and Capability-Level?
**Shape-level** (`is_canonical_shape`):
- **Granular control**: Pattern1Mini vs Pattern2Mini (both P2CoreSimple)
- **Exact filtering**: Phase 41 canonical set definition
- **Backward compatible**: Preserves existing behavior exactly
**Capability-level** (`is_p2_core_capability`):
- **Future expansion**: Easy to add new capability kinds
- **Intent clarity**: P2CoreSimple vs P2MidParseNumber
- **Extensibility**: Prepare for carrier roles, method signatures, etc.
## Implementation Notes
### Backward Compatibility
Phase 44 is **pure refactoring** - zero behavioral changes:
1. **`canonical_shapes()`**: Still returns exact same shapes
- Uses `is_canonical_shape()` internally (shape-level check)
- Capability mapping is internal implementation detail
2. **All tests pass**: 937/937 tests (zero regression)
3. **Bridge routing unchanged**: Mode-based routing logic preserved
### Key Files Modified
1. **`src/mir/join_ir/normalized/shape_guard.rs`**:
- Added `ShapeCapability`, `ShapeCapabilityKind`
- Added capability mapping functions
- Updated `canonical_shapes()` to use `is_canonical_shape()`
2. **`src/mir/join_ir_vm_bridge/bridge.rs`**:
- No changes needed (uses `canonical_shapes()` helper)
## Benefits
### 1. Extensibility
```rust
// Adding new capability kind:
enum ShapeCapabilityKind {
P2CoreSimple,
P2CoreSkipWs,
P2CoreAtoi,
P2MidParseNumber,
P2HeavyString, // NEW: Just add here
}
// Update mapping:
fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability {
match shape {
HeavyStringPattern => ShapeCapability::new(P2HeavyString), // NEW
// ... existing mappings
}
}
```
### 2. Clarity
```rust
// Old: What does this mean?
if shape == JsonparserAtoiMini { ... }
// New: Intent is clear
let cap = capability_for_shape(&shape);
if cap.kind == P2CoreAtoi { ... }
```
### 3. Maintainability
```rust
// Old: Update multiple locations when adding shape
// bridge.rs: add to if expression
// shape_guard.rs: add to another if expression
// normalized.rs: add to yet another if expression
// New: Update one mapping function
fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability {
// Add new shape here only
}
```
## Future Work
### Phase 45+: Capability-Based Routing
Once more patterns migrate to Normalized path:
```rust
// Current (Phase 44): Still uses shape-level filtering
pub(crate) fn canonical_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
detect_shapes(module).into_iter()
.filter(|s| is_canonical_shape(s)) // Shape-level
.collect()
}
// Future (Phase 46+): Pure capability-level filtering
pub(crate) fn canonical_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
detect_shapes(module).into_iter()
.filter(|s| {
let cap = capability_for_shape(s);
cap.kind.is_canonical() // Capability-level
})
.collect()
}
```
### Carrier Role Analysis
```rust
// Future: Carrier role detection
pub struct ShapeCapability {
pub kind: ShapeCapabilityKind,
pub carrier_roles: Vec<CarrierRole>, // Enable this field
}
pub enum CarrierRole {
LoopVar, // i, pos
Accumulator, // sum, count
HostReference, // p (pointer to external state)
StateCarrier, // num_str (intermediate state)
}
// Automatic role detection
fn detect_carrier_roles(loop_func: &JoinFunction) -> Vec<CarrierRole> {
// Analyze param usage patterns
}
```
### Method Call Signatures
```rust
// Future: Track required Box methods
pub struct ShapeCapability {
pub kind: ShapeCapabilityKind,
pub method_calls: Vec<MethodCallSignature>, // Enable this field
}
pub struct MethodCallSignature {
pub box_name: String, // "StringBox"
pub method: String, // "get"
pub arity: usize, // 2 (self + index)
}
```
## Testing
### Verification Strategy
1. **Build test**: Zero compilation errors
2. **Regression test**: 937/937 library tests pass
3. **Canonical set verification**: Same shapes as Phase 41
4. **Smoke test**: Integration tests (if applicable)
### Test Results
```
cargo build --release
✓ Compiled successfully
cargo test --release --lib
✓ 937 passed; 0 failed; 56 ignored
```
## Lessons Learned
### Design Trade-offs
**Why not pure capability-level filtering immediately?**
Answer: **Gradual migration strategy**
- Phase 44: Introduce capability infrastructure (backward compatible)
- Phase 45+: Expand canonical set incrementally
- Phase 46+: Pure capability-level routing when migration complete
**Why keep shape-level API?**
Answer: **Multiple P2CoreSimple shapes**
- Pattern1Mini (minimal fallback)
- Pattern2Mini (canonical core)
- Both map to same capability, but different canonical status
- Shape-level check provides necessary granularity
### Anti-Patterns Avoided
**Don't**: Rewrite all filtering logic at once
```rust
// Risky: Big-bang rewrite, hard to verify
pub(crate) fn canonical_shapes(...) {
// Complete rewrite with new logic
}
```
**Do**: Add capability layer, preserve existing behavior
```rust
// Safe: Capability-based implementation, same output
pub(crate) fn canonical_shapes(...) {
shapes.filter(|s| is_canonical_shape(s)) // Uses capabilities internally
}
```
## References
- **Phase 41**: Canonical P2-Core definition
- **Phase 45**: JoinIrMode routing integration
- **JoinIR Architecture**: [joinir-architecture-overview.md](joinir-architecture-overview.md)