lang: Infer literal-first comparison types
0268ebd62dfcd70ebda2fd2a5bdf753f8294fee8840173ba4c47b98fe5dfd38d
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
94e18aa5
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 | + | } |