lang: Infer literal-first comparison types
3c89648cb44d17a6578824059423541759f9208c900c19fcfe00cb4161ff5f06
Ordering comparisons always selected the left operand’s type during lowering. An unsuffixed left literal therefore forced signed comparison semantics even when the concrete right operand was unsigned. Validate ordering operand compatibility and select the non-literal operand type in both expression and fused-branch lowering. Assisted-by: Codex:gpt-5.6-sol
1 parent
b2271358
lib/std/lang/lower.rad
+9 -3
| 2255 | 2255 | // Try fused compare-and-branch for simple scalar comparisons. |
|
| 2256 | 2256 | if let case ast::NodeValue::BinOp(binop) = cond.value { |
|
| 2257 | 2257 | let leftTy = try typeOf(self, binop.left); |
|
| 2258 | 2258 | let rightTy = try typeOf(self, binop.right); |
|
| 2259 | 2259 | if not isAggregateType(leftTy) and not isAggregateType(rightTy) { |
|
| 2260 | - | let unsigned = isUnsignedType(leftTy); |
|
| 2260 | + | let operandTy = scalarComparisonType(leftTy, rightTy); |
|
| 2261 | + | let unsigned = isUnsignedType(operandTy); |
|
| 2261 | 2262 | if let op = cmpOpFrom(binop.op, unsigned) { |
|
| 2262 | 2263 | let a = try lowerExpr(self, binop.left); |
|
| 2263 | 2264 | let b = try lowerExpr(self, binop.right); |
|
| 2264 | - | let typ = ilType(self.low, leftTy); |
|
| 2265 | + | let typ = ilType(self.low, operandTy); |
|
| 2265 | 2266 | ||
| 2266 | 2267 | // Swap operands if needed. |
|
| 2267 | 2268 | match binop.op { |
|
| 2268 | 2269 | case ast::BinaryOp::Gt => // `a > b` = `b < a` |
|
| 2269 | 2270 | try emitBrCmp(self, op, typ, b, a, thenBlock, elseBlock), |
| 5883 | 5884 | ||
| 5884 | 5885 | return il::Val::Reg(resultReg); |
|
| 5885 | 5886 | } |
|
| 5886 | 5887 | } |
|
| 5887 | 5888 | ||
| 5889 | + | /// Select the concrete operand type for a scalar comparison. |
|
| 5890 | + | fn scalarComparisonType(left: resolver::Type, right: resolver::Type) -> resolver::Type { |
|
| 5891 | + | return right if left == resolver::Type::Int else left; |
|
| 5892 | + | } |
|
| 5893 | + | ||
| 5888 | 5894 | /// Convert a binary operator to a comparison op, if applicable. |
|
| 5889 | 5895 | /// For `Gt`, caller must swap operands: `a > b = b < a`. |
|
| 5890 | 5896 | /// For `Gte`/`Lte`, caller must swap branch labels: `a >= b = !(a < b)`. |
|
| 5891 | 5897 | /// For `Lte`, caller must also swap operands: `a <= b = !(b < a)`. |
|
| 5892 | 5898 | fn cmpOpFrom(op: ast::BinaryOp, unsigned: bool) -> ?il::CmpOp { |
| 5952 | 5958 | } |
|
| 5953 | 5959 | if isAggregateType(rightTy) { |
|
| 5954 | 5960 | let lhs = try wrapInOptional(self, a, rightTy); |
|
| 5955 | 5961 | return try emitAggregateEqOp(self, binop.op, rightTy, lhs, b); |
|
| 5956 | 5962 | } |
|
| 5957 | - | set resultTy = leftTy; |
|
| 5963 | + | set resultTy = scalarComparisonType(leftTy, rightTy); |
|
| 5958 | 5964 | } |
|
| 5959 | 5965 | return emitScalarBinOp(self, binop.op, ilType(self.low, resultTy), a, b, isUnsignedType(resultTy)); |
|
| 5960 | 5966 | } |
|
| 5961 | 5967 | ||
| 5962 | 5968 | /// Emit an aggregate equality or inequality comparison. |
lib/std/lang/resolver.rad
+8 -1
| 6505 | 6505 | } |
|
| 6506 | 6506 | } |
|
| 6507 | 6507 | let leftTy = try checkNumeric(self, binop.left); |
|
| 6508 | 6508 | let rightTy = try checkNumeric(self, binop.right); |
|
| 6509 | 6509 | ||
| 6510 | - | // Ordering comparisons return `bool`, not the operand type. |
|
| 6510 | + | // Ordering comparisons use the concrete operand type. Only an |
|
| 6511 | + | // unsuffixed integer expression may differ from that type. |
|
| 6511 | 6512 | match binop.op { |
|
| 6512 | 6513 | case ast::BinaryOp::Lt, ast::BinaryOp::Gt, |
|
| 6513 | 6514 | ast::BinaryOp::Lte, ast::BinaryOp::Gte => { |
|
| 6515 | + | if leftTy <> rightTy and leftTy <> Type::Int and rightTy <> Type::Int { |
|
| 6516 | + | throw emitTypeMismatch(self, binop.right, TypeMismatch { |
|
| 6517 | + | expected: leftTy, |
|
| 6518 | + | actual: rightTy, |
|
| 6519 | + | }); |
|
| 6520 | + | } |
|
| 6514 | 6521 | set resultTy = Type::Bool; |
|
| 6515 | 6522 | } else => { |
|
| 6516 | 6523 | if leftTy == rightTy { |
|
| 6517 | 6524 | set resultTy = leftTy; |
|
| 6518 | 6525 | } else if leftTy == Type::Int { |
test/tests/compare.literal.first.u64.rad
added
+16 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Literal-first ordering must use the concrete operand's unsigned type. |
|
| 3 | + | ||
| 4 | + | fn isPositive(value: u64) -> bool { |
|
| 5 | + | if 0 < value { |
|
| 6 | + | return true; |
|
| 7 | + | } |
|
| 8 | + | return false; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | @default fn main() -> i32 { |
|
| 12 | + | if isPositive(0xffffffffffffffff) { |
|
| 13 | + | return 0; |
|
| 14 | + | } |
|
| 15 | + | return 1; |
|
| 16 | + | } |