Skip to content

Add conditional expressions in value position#37

Open
thiremani wants to merge 6 commits intomasterfrom
feat/cond-expr
Open

Add conditional expressions in value position#37
thiremani wants to merge 6 commits intomasterfrom
feat/cond-expr

Conversation

@thiremani
Copy link
Owner

Summary

Implements conditional expressions — comparisons in the value position of let statements that extract the LHS operand when the condition holds and skip the entire statement when it fails.

Semantics

  • x = a > 2 → if a > 2, then x = a; else skip (x = 0 if new variable)
  • x = a > 2 + b < 10x = a + b if both conditions hold; else skip
  • res = f(i < 15) → call f(i) only if i < 15; else skip
  • x = 5 > 3 a > 2 → statement condition AND embedded conditions both must hold
  • Comparisons have higher precedence than arithmetic (LESSGREATER=12 vs SUM=7), so a + b > 3 parses as a + (b > 3) → returns a + b if b > 3
  • 2 < a returns 2 (always LHS operand)
  • Short-circuit cascade evaluation: later conditions only evaluated if earlier ones pass
  • Undefined output variables still get zero-initialized on skip

Implementation

Test coverage

  • Created tests/cond_expr/ with 24 test cases covering:
    • Basic true/false for new/existing variables
    • Arithmetic with multiple cond-exprs
    • Combined statement + embedded conditions
    • Multi-target assignments
    • All 6 comparison operators
    • Floats
    • Function calls with conditional args
    • Nested cond-expr (function with cond-expr called from cond-expr context)

All 47 existing tests pass with zero regressions.

Deferred

  • Array filtering (arr > 3 → element-wise filter) deferred for later

🤖 Generated with Claude Code

thiremani and others added 6 commits February 13, 2026 13:11
Comparisons inside the value side of let statements now act as
conditional value extractors — returning the LHS operand when the
comparison holds and skipping the entire statement when it fails.

🔧 How it works:
  x = a > 2           → x = a if a > 2; else skip (x = 0 if new)
  z = a > 2 + b < 10  → z = a + b if both hold; else skip
  res = f(i < 15)     → call f(i) only when i < 15; else skip

⚡ Short-circuit cascade evaluation ensures later conditions are only
evaluated when earlier ones pass. Undefined output variables are
zero-initialized on skip (existing temp-slot seeding behaviour).

📝 Key changes:
- solver.go: IsCondExpr flag on ExprInfo; InValueExpr context in
  TypeSolver so comparisons in value position return the LHS type
  instead of i1, while stmt.Condition comparisons remain unchanged.
- cond.go: cascadeCondExprs emits cascading branches per comparison;
  compileCondExprStatement orchestrates temp slots, cascade, and
  assignment reusing the existing conditional infrastructure.
- compiler.go: compileInfixExpression returns pre-extracted LHS values
  for conditional comparisons; compileLetStatement dispatches to the
  new path when embedded conditions are detected.

🧪 All 47 existing tests pass with zero regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l issues

## Changes

### Fix #1: Isolated cond-expr state per function context
**Problem**: `condExprValues` was a shared compiler field that got clobbered when
lazy function compilation occurred during cond-expr lowering. If a callee function
had cond-exprs in its body, it would overwrite the caller's substitution map,
causing the caller to see `nil` and fall through to normal comparison (producing i1).

**Solution**: Moved LHS value storage from shared field to `ExprInfo.CondLHS`. Since
`ExprCache` is keyed by `(FuncNameMangled, Expression)`, each function context has
isolated entries—callee compilation can't clobber caller state.

- Removed `condExprValues map[ast.Expression]*Symbol` from Compiler struct
- Added `CondLHS *Symbol` to ExprInfo for per-expression LHS storage
- Updated `cascadeCondExprs` to write to `info.CondLHS`
- Updated `compileInfixExpression` to read from `info.CondLHS`
- Removed init/cleanup of condExprValues from `compileCondExprStatement`

### Fix #2: Added RangeLiteral to cond-expr tree traversal
**Problem**: `condExprChildren` didn't handle `*ast.RangeLiteral`, so comparisons
nested in range bounds (e.g., `r = a > 3:10`) were invisible to `hasCondExprInTree`
and `cascadeCondExprs`. Type solver marked them as extractors, but codegen emitted
i1 comparisons instead of value extraction.

**Solution**: Extended `condExprChildren` to return Start/Stop/Step expressions
for RangeLiteral nodes.

### Test coverage
- Added `CondDouble` function with cond-expr in body to cond_expr.pt
- Added nested cond-expr test cases: function with cond-expr called from
  multi-target cond-expr context (`m, n = CondDouble(v > 0), v > 0`)
- Verifies callee's cond-expr compilation doesn't corrupt caller's state

All 47 tests pass with zero regressions.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the multi-branch cascade approach with a simpler extract-and-AND
architecture for conditional expression lowering:

- extractCondExprs walks the expression tree, evaluates each comparison,
  ANDs all results into a single combined condition (including stmt
  conditions), and stores LHS values in ExprInfo.CondLHS for substitution
- Single branch on the combined condition instead of N cascade branches
- CondLHS changed from *Symbol to []*Symbol to support multi-return
  comparisons (element-wise AND, all-or-nothing skip)
- Array types gracefully skipped in extraction (filtering deferred)
- Removed hasStmtCond parameter since stmtCond is now naturally combined

This is simpler (fewer basic blocks, no cascade state), produces
equivalent results for scalar comparisons, and naturally extends to
multi-return without additional branching complexity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…isons in value position

Remove the single-result restriction from isCondExpr so multi-return
comparisons like Pair(5,7) > Pair(1,2) use extraction semantics.
Remove the compile error for array comparisons in value position;
arrays now flow through normal infix as boolean arrays and are
excluded from cond-expr tagging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… time

Statement conditions must produce scalar i1 values for branching.
Previously, array comparisons in condition position (e.g. x = a > b 5
where a,b are arrays) passed type-checking but produced invalid IR
(br ptr). Now the solver emits a clear compile error instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant