windjammer 0.48.0

A simple language inspired by Go, Ruby, and Elixir that transpiles to Rust - 80% of Rust's power with 20% of the complexity
Documentation
# Compiler Bugs Found via Dogfooding - TO FIX WITH TDD

## ✅ FIXED BUGS

## Bug #6: Enum match on `&self` creates borrowed bindings needing deref [FIXED ✅]

**Status:** ✅ FIXED - 2026-02-26
**Test Case:** `tests/bug_enum_self_borrow.wj` (PASSING)
**Severity:** High (blocked dialogue system compilation)

**Problem:**
When matching on `&self` inside impl methods, enum variant destructuring creates borrowed bindings (`&T`). These weren't being auto-dereferenced in comparisons.

**Example:**
```windjammer
impl Condition {
    pub fn check(self) -> bool {  // codegen converts to &self
        match self {
            Condition::ThresholdCheck(threshold) => {
                return get_value() > threshold;  // ERROR: expected i32, found &i32
            }
        }
    }
}
```

**Root Cause:** `match_expression_binds_refs()` didn't check if the matched expression is a borrowed parameter (like `&self`), so `borrowed_iterator_vars` wasn't populated.

**Fix:**
- Added `Expression::Identifier` case to `match_expression_binds_refs`
- Check if identifier is in `inferred_borrowed_params`
- This correctly populates `borrowed_iterator_vars` with enum bindings
- Existing Bug #5 deref logic then applies auto-deref in comparisons

**Generated Code (After Fix):**
```rust
return get_value() > *threshold;  // ✅ Auto-deref applied!
```

**Impact:** Dialogue system enum conditions now compile correctly.

---

## Bug #1: Method self-by-value incorrectly infers &mut [FIXED ✅]

**Status**: ✅ FIXED - Test passing as of 2026-02-25

**Discovered**: 2026-02-24 during camera matrices test compilation

**Symptom**:
```windjammer
impl Mat4 {
    fn multiply(self, other: Mat4) -> Mat4 { ... }
}

fn test() {
    let identity = Mat4::identity()  // Compiler says: needs 'mut'
    let result = identity.multiply(other)  // Error: cannot borrow as mutable
}
```

**Root Cause**:
In `windjammer/src/analyzer.rs` lines 937-981, when `param.ownership == OwnershipHint::Owned` (user wrote `self` not `&self`), the analyzer incorrectly checks if the method modifies fields and downgrades to `OwnershipMode::MutBorrowed` (&mut self).

**The Fix**:
```rust
// CURRENT (BUGGY):
OwnershipHint::Owned => {
    if param.name == "self" {
        let modifies_fields = self.function_modifies_self_fields(func);
        if modifies_fields {
            OwnershipMode::MutBorrowed  // ❌ WRONG!
        } else {
            OwnershipMode::Owned
        }
    } else {
        OwnershipMode::Owned
    }
}

// SHOULD BE:
OwnershipHint::Owned => {
    // When user explicitly writes `self` (Owned), RESPECT IT!
    // Don't analyze or downgrade. User wants owned.
    OwnershipMode::Owned
}
```

**Test Case**: `windjammer/tests/method_self_by_value.wj`

**Impact**: Blocks clean implementation of math libraries (Mat4, Vec3, etc.) that use self-by-value for transforms.

**Workaround**: Mark variables as `mut` even though not needed, or use `&self` instead of `self`.

**Update (2026-02-24)**: Fixed parameter inference (analyzer.rs:937-943) ✅  
**Remaining**: Method call site still infers &mut for receiver. Need to trace codegen.

**Priority**: HIGH - Common pattern in game math

**Next Steps**: 
1. Find where method call receivers get mutability inference
2. Check method signature to see if it takes `self` vs `&mut self`
3. Don't add `&mut` if method takes `self` by value

---

## Bug #2: format! in temp variable generates &_temp instead of _temp [FIXED ✅]

**Status**: ✅ FIXED - Verified in game library as of 2026-02-25

**Discovered**: 2026-02-25 during assets/loader.wj compilation

**Symptom**:
```windjammer
enum AssetError {
    InvalidFormat(String),
}

fn validate() -> Result<(), AssetError> {
    Err(AssetError::InvalidFormat(format!("Error: {}", code)))
}
```

**Generated Rust** (BUGGY):
```rust
Err({ let _temp0 = format!("Error: {}", code); AssetError::InvalidFormat(&_temp0) })
//                                                                        ^^^^^^^^ BUG!
```

**Root Cause**:
In `windjammer/src/codegen/rust/generator.rs`, when generating code for `format!()` in an expression context, the compiler creates a temporary variable but incorrectly passes `&_temp0` instead of `_temp0`.

This causes two problems:
1. **Type mismatch**: Expected `String`, found `&String`
2. **Lifetime error**: `_temp0` goes out of scope immediately

**The Fix**:
```rust
// Should generate:
Err(AssetError::InvalidFormat(format!("Error: {}", code)))

// Or if temp is needed:
Err({ let _temp0 = format!("Error: {}", code); AssetError::InvalidFormat(_temp0) })
//                                                                         ^^^^^^^^ No &!
```

**Test Case**: `windjammer/tests/bug_format_temp_var_lifetime.wj`

**Impact**: Blocks any use of `format!()` in enum variants, function args, or struct fields that expect `String`.

**Patterns Affected**:
- `Err(EnumVariant(format!(...)))`
- `func_call(format!(...))`
- `Struct { field: format!(...) }`

**Priority**: HIGH - Common pattern in error handling

**Next Steps**:
1. Find where format! generates temp variables in codegen
2. Check if the result is being borrowed when it shouldn't be
3. Remove the `&` prefix or eliminate temp variable entirely

---

## Bug #3: While-loop index incorrectly inferred as i64 instead of usize [HIGH PRIORITY]

**Status**: ✅ FIXED (2026-02-26 01:15 PST) - TDD test passing, game library compiles

**Discovered**: 2026-02-25 during animation/clip.wj compilation
**Fixed**: 2026-02-26 01:15 PST (usize_variables now persists during statement generation)

---

## Bug #4: Array indexing with expression (i + 1) incorrectly typed as i64 [HIGH PRIORITY]

**Status**: ✅ FIXED (2026-02-26 01:40 PST) - TDD test passing, game library compiles

**Discovered**: 2026-02-26 01:20 PST during animation/clip.wj compilation (dogfooding session)
**Fixed**: 2026-02-26 01:40 PST (expression_produces_usize() now handles Binary expressions properly)
**Test Case**: `tests/bug_array_index_expression_type.wj` ✅ PASSING

---

## Bug #5: Parameter ownership not inferred for type aliases / newtype wrappers

**Status**: 🔴 ACTIVE - TDD test created, investigating fix

**Discovered**: 2026-02-26 01:45 PST during dialogue module compilation (dogfooding session)
**Test Case**: `tests/bug_newtype_wrapper_inference.wj` ✅ CREATED

**Symptom**:
```rust
error[E0308]: mismatched types
  --> bug_newtype_wrapper_inference.rs:29:33
   |
29 |         self.is_quest_completed(quest_id)
   |              ------------------ ^^^^^^^^ expected `String`, found `&String`
```

**Windjammer Code**:
```windjammer
type QuestId = String

fn is_quest_completed(&self, quest_id: QuestId) -> bool {
    self.completed_quests[i] == quest_id  // Only reads quest_id
}

fn check_with_ref(&self, quest_id: &QuestId) -> bool {
    self.is_quest_completed(quest_id)  // ERROR: expected QuestId, found &QuestId
}
```

**Generated Rust** (BUGGY):
```rust
fn is_quest_completed(&self, quest_id: QuestId) -> bool {  // Owned
    self.completed_quests[i] == quest_id
}
```

**Expected Rust**:
```rust
fn is_quest_completed(&self, quest_id: &QuestId) -> bool {  // Borrowed
    self.completed_quests[i] == quest_id
}
```

**Root Cause**:
Ownership inference doesn't recognize that `quest_id` should be `&QuestId` because:
1. It's only used in comparisons (read-only)
2. Type aliases (like `QuestId = String`) should follow same rules as the underlying type
3. Parameter is passed by reference at call sites

**Fix Strategy**:
Update parameter ownership inference in analyzer to treat type alias parameters same as their underlying types for ownership analysis.

**Symptom**:
```windjammer
let mut after_idx = keyframes.len() - 1  // usize
for i in 0..keyframes.len() {
    after_idx = i + 1  // Error: expected usize, found i64
}
```

**Generated Rust** (BUGGY):
```rust
let mut after_idx = self.keyframes.len() - 1;  // usize
let mut i = 0;
while i < ((self.keyframes.len() - 1) as i64) {  // BUG: i is i64!
    after_idx = i + 1;  // ERROR: expected usize, found i64
    i += 1;
}
```

**Root Cause**:
In `windjammer/src/codegen/rust/generator.rs`, when converting for-loops to while-loops, the compiler:
1. Defaults loop index `i` to `i64`
2. Casts `.len()` to `i64` for comparison
3. SHOULD infer `i` as `usize` when:
   - Loop bound is `.len()` (which is usize)
   - Index assigned to usize variable
   - Index used for array indexing

**The Fix**:
Improve type inference for loop indices:
1. Check if loop bound is `.len()` or other usize expression
2. Check if index is used with usize variables/indexing
3. Infer `i` as `usize` instead of defaulting to `i64`

**Test Case**: `windjammer/tests/bug_loop_index_usize_inference.wj`

**Impact**: Blocks any pattern where loop index is assigned to usize variables (common in animation, pathfinding, searching).

**Priority**: HIGH - Common pattern in game code

**Next Steps**:
1. Find loop index type inference logic in codegen
2. Add usize inference when bound is .len()
3. Propagate usize type through arithmetic (i + 1, i - 1)

---

## Future Bugs to Document Here

(Add more as we find them via dogfooding)

---

## The Windjammer Way

**"No workarounds, only proper fixes."**

Every bug found via dogfooding should:
1. Have a failing test case
2. Be documented here
3. Have the fix location identified  
4. Be fixed with TDD before shipping

This file is temporary - bugs should be fixed and removed, not accumulated!