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
Alexis Sellier committed ago 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 +
}