diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 4fd3ab97c..dbca83803 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -3872,6 +3872,24 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left rhs = lb_emit_byte_swap(p, {rhs, pt}, pt).value; } + if (is_type_boolean(a) && is_type_boolean(b) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) { + // anything not 0 is true, which is what control flow already tests for + bool lhs_is_const = LLVMIsAConstantInt(lhs) != nullptr; + bool rhs_is_const = LLVMIsAConstantInt(rhs) != nullptr; + if (lhs_is_const != rhs_is_const) { + // against a literal, the truthiness test is the whole comparison + LLVMValueRef v = rhs_is_const ? lhs : rhs; + LLVMValueRef c = rhs_is_const ? rhs : lhs; + bool is_true = LLVMConstIntGetZExtValue(c) != 0; + pred = ((op_kind == Token_CmpEq) == is_true) ? LLVMIntNE : LLVMIntEQ; + lhs = v; + rhs = LLVMConstNull(LLVMTypeOf(v)); + } else { + lhs = LLVMBuildICmp(p->builder, LLVMIntNE, lhs, LLVMConstNull(LLVMTypeOf(lhs)), ""); + rhs = LLVMBuildICmp(p->builder, LLVMIntNE, rhs, LLVMConstNull(LLVMTypeOf(rhs)), ""); + } + } + res.value = LLVMBuildICmp(p->builder, pred, lhs, rhs, ""); } else if (is_type_float(a)) { LLVMRealPredicate pred = {}; diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 4cbb424b4..f211e153b 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -43,6 +43,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin test ..\test_issue_7008.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7260.odin -no-entry-point %COMMON% || exit /b +..\..\..\odin test ..\test_issue_bool_comparison_truthiness.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration_mismatch.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b diff --git a/tests/issues/test_issue_bool_comparison_truthiness.odin b/tests/issues/test_issue_bool_comparison_truthiness.odin new file mode 100644 index 000000000..6b218bdc2 --- /dev/null +++ b/tests/issues/test_issue_bool_comparison_truthiness.odin @@ -0,0 +1,57 @@ +package test_issues + +import "core:testing" + +// A boolean is true when its payload is non-zero, which is what `if`, `!` and `&&` test for. +// `==`, `!=` and `switch` compared the payload against 1 instead, so a boolean decoded from +// bytes -- transmuted, read through a pointer, or returned by a foreign procedure -- was true +// under `if` yet matched neither arm of its own switch. + +@(test) +bool_comparison_uses_truthiness :: proc(t: ^testing.T) { + two: u8 = 2 + one: u8 = 1 + nil_: u8 = 0 + + b := transmute(bool)two + c := transmute(bool)one + f := transmute(bool)nil_ + + testing.expect(t, b, "a non-zero payload is true") + + testing.expect_value(t, b == true, true) + testing.expect_value(t, b != true, false) + testing.expect_value(t, b == false, false) + testing.expect_value(t, b != false, true) + + // both operands are normalised, not just the literal one + testing.expect_value(t, b == c, true) + testing.expect_value(t, b != c, false) + testing.expect_value(t, b == f, false) + testing.expect_value(t, b != f, true) + + arm := 2 + switch b { + case true: arm = 1 + case false: arm = 0 + } + testing.expect_value(t, arm, 1) + + // the wider boolean types share the path + two16: u16 = 2 + two32: u32 = 2 + two64: u64 = 2 + testing.expect_value(t, transmute(b8)two == true, true) + testing.expect_value(t, transmute(b16)two16 == true, true) + testing.expect_value(t, transmute(b32)two32 == true, true) + testing.expect_value(t, transmute(b64)two64 == true, true) + + // the normal 0/1 payloads keep behaving + yes := true + no := false + testing.expect_value(t, yes == true, true) + testing.expect_value(t, yes == false, false) + testing.expect_value(t, no == false, true) + testing.expect_value(t, yes == no, false) + testing.expect_value(t, yes != no, true) +}