lang: extend operand-type check to ordering + bitwise/shift (issue 0055 follow-up)
The arithmetic-only check from the previous commit shared a hole with the comparison and bitwise/shift ops: lowerBinaryOp derives the result type from the LHS, so `s64 < string` fed mismatched types to `icmp` (LLVM verifier failure) and `s64 & string` reinterpreted the string's bytes. Add isOrderingOperand (numeric / enum / pointer / bool / vector) and isBitwiseOperand (integer / enum / bool / vector), and route `< <= > >=` and `& | ^ << >>` through them alongside the existing arithmetic check, all sharing one diagnostic + placeholder-sentinel path. Flags-enum bitwise (`.read | .write`, `perm & .read`), enum/pointer comparison, and int literals stay legal (50-smoke unaffected). Equality `== / !=` is deliberately left unchecked — its path is heavily special-cased (str_eq, Any unbox, optional == null); folding a check in without regressing those is a separate change, noted in the issue. Regression test renamed arith→binop and broadened to cover `+ * < & <<` against a string operand: examples/214-binop-operand-type-check.sx.
This commit is contained in:
@@ -1,21 +1,33 @@
|
||||
# 0055 — binary arithmetic accepts mismatched operand types (`s64 + string`)
|
||||
|
||||
**FIXED** (`examples/214-arith-operand-type-check.sx`). `lowerBinaryOp` in
|
||||
**FIXED** (`examples/214-binop-operand-type-check.sx`). `lowerBinaryOp` in
|
||||
[src/ir/lower.zig](../src/ir/lower.zig) now checks operand-type
|
||||
compatibility for the scalar arithmetic ops (`+ - * / %`) before emitting:
|
||||
if either operand (after the existing optional-unwrap / int×float
|
||||
promotion) is not a numeric / vector / pointer type via the new
|
||||
`isArithOperand`, it emits `cannot apply '<op>' to operands of type '<lhs>'
|
||||
and '<rhs>'` and returns a placeholder sentinel instead of an `add` that
|
||||
reinterprets the RHS bytes. `.unresolved` operands are passed through so a
|
||||
type we couldn't infer is never falsely diagnosed. Regression test:
|
||||
`examples/214` (covers `s64 + string` and non-numeric LHS `string * s64`).
|
||||
compatibility for every scalar binary-op group before emitting, via three
|
||||
predicates that pass `.unresolved` through (so a type we couldn't infer is
|
||||
never falsely diagnosed) but reject a concretely incompatible operand:
|
||||
|
||||
Not covered by this fix (same LHS-derived-type shape, separate ops): the
|
||||
ordering comparisons (`< <= > >=`) and bitwise/shift ops (`& | ^ << >>`)
|
||||
have the same hole — `s64 < string` / `s64 & string` still lower without an
|
||||
operand check. Left out to avoid touching enum/flags comparison + bitwise
|
||||
paths in the same change; flag as a follow-up if it bites.
|
||||
- **arithmetic** `+ - * / %` → `isArithOperand` (numeric / vector /
|
||||
pointer). Without it `s64 + string` lowered as `add : s64` and
|
||||
reinterpreted the string's bytes — garbage.
|
||||
- **ordering** `< <= > >=` → `isOrderingOperand` (numeric / enum / pointer
|
||||
/ bool / vector). Without it `s64 < string` fed mismatched LLVM types to
|
||||
`icmp` and tripped the verifier.
|
||||
- **bitwise / shift** `& | ^ << >>` → `isBitwiseOperand` (integer / enum /
|
||||
bool / vector). Without it `s64 & string` reinterpreted the bytes.
|
||||
|
||||
On mismatch it emits `cannot apply '<op>' to operands of type '<lhs>' and
|
||||
'<rhs>'` and returns a placeholder sentinel instead of the corrupting op.
|
||||
The existing optional-unwrap and int×float promotion are applied before the
|
||||
check. Legitimate mixes — flags-enum bitwise (`.read | .write`,
|
||||
`perm & .read`), enum/pointer comparison, int literals — are unaffected
|
||||
(covered by `examples/50-smoke.sx`). Regression test: `examples/214`
|
||||
(rejects `+ * < & <<` against a `string` operand).
|
||||
|
||||
Still NOT covered (left deliberately): equality `==` / `!=`, whose path is
|
||||
heavily special-cased (string `str_eq`, `Any` unbox, `optional == null`).
|
||||
`s64 == string` still slips through. Folding a compatibility check into
|
||||
that path without regressing the special cases is a separate change — open
|
||||
a fresh issue if it bites.
|
||||
|
||||
## Symptom
|
||||
|
||||
|
||||
Reference in New Issue
Block a user