diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 475d490a48f2..4b85c13892c1 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -94,7 +94,6 @@ c_pyssize_t_rprimitive, c_size_t_rprimitive, check_native_int_range, - dict_rprimitive, float_rprimitive, int_rprimitive, is_bool_or_bit_rprimitive, @@ -110,13 +109,13 @@ is_list_rprimitive, is_none_rprimitive, is_object_rprimitive, + is_optional_type, is_set_rprimitive, is_short_int_rprimitive, is_str_rprimitive, is_tagged, is_tuple_rprimitive, is_uint8_rprimitive, - list_rprimitive, none_rprimitive, object_pointer_rprimitive, object_rprimitive, @@ -1684,6 +1683,44 @@ def unary_not(self, value: Value, line: int, *, likely_bool: bool = False) -> Va if is_bool_or_bit_rprimitive(typ): mask = Integer(1, typ, line) return self.int_op(typ, value, mask, IntOp.XOR, line) + if is_tagged(typ) or is_fixed_width_rtype(typ): + return self.binary_op(value, Integer(0), "==", line) + if ( + is_str_rprimitive(typ) + or is_list_rprimitive(typ) + or is_tuple_rprimitive(typ) + or is_dict_rprimitive(typ) + or isinstance(typ, RInstance) + ): + bool_val = self.bool_value(value) + return self.unary_not(bool_val, line) + if is_optional_type(typ): + value_typ = optional_value_type(typ) + assert value_typ + if ( + is_str_rprimitive(value_typ) + or is_list_rprimitive(value_typ) + or is_tuple_rprimitive(value_typ) + or is_dict_rprimitive(value_typ) + or isinstance(value_typ, RInstance) + ): + # 'X | None' type: Check for None first and then specialize for X. + res = Register(bit_rprimitive) + cmp = self.add(ComparisonOp(value, self.none_object(), ComparisonOp.EQ, line)) + none, not_none, out = BasicBlock(), BasicBlock(), BasicBlock() + self.add(Branch(cmp, none, not_none, Branch.BOOL)) + self.activate_block(none) + self.add(Assign(res, self.true())) + self.goto(out) + self.activate_block(not_none) + val = self.unary_not( + self.unbox_or_cast(value, value_typ, line, can_borrow=True, unchecked=True), + line, + ) + self.add(Assign(res, val)) + self.goto(out) + self.activate_block(out) + return res if likely_bool and is_object_rprimitive(typ): # First quickly check if it's a bool, and otherwise fall back to generic op. res = Register(bit_rprimitive) @@ -1882,10 +1919,12 @@ def bool_value(self, value: Value) -> Value: elif is_fixed_width_rtype(value.type): zero = Integer(0, value.type) result = self.add(ComparisonOp(value, zero, ComparisonOp.NEQ)) - elif is_same_type(value.type, str_rprimitive): + elif is_str_rprimitive(value.type): result = self.call_c(str_check_if_true, [value], value.line) - elif is_same_type(value.type, list_rprimitive) or is_same_type( - value.type, dict_rprimitive + elif ( + is_list_rprimitive(value.type) + or is_dict_rprimitive(value.type) + or is_tuple_rprimitive(value.type) ): length = self.builtin_len(value, value.line) zero = Integer(0) diff --git a/mypyc/test-data/irbuild-bool.test b/mypyc/test-data/irbuild-bool.test index 9810daf487fa..5eac6d8db24f 100644 --- a/mypyc/test-data/irbuild-bool.test +++ b/mypyc/test-data/irbuild-bool.test @@ -473,3 +473,181 @@ L0: r0 = x == y r1 = r0 ^ 1 return r1 + +[case testUnaryNotWithPrimitiveTypes] +def not_obj(x: object) -> bool: + return not x + +def not_int(x: int) -> bool: + return not x + +def not_str(x: str) -> bool: + return not x + +def not_list(x: list[int]) -> bool: + return not x + +def not_tuple(x: tuple[int, ...]) -> bool: + return not x + +def not_dict(x: dict[str, int]) -> bool: + return not x +[out] +def not_obj(x): + x :: object + r0 :: i32 + r1 :: bit + r2 :: bool +L0: + r0 = PyObject_Not(x) + r1 = r0 >= 0 :: signed + r2 = truncate r0: i32 to builtins.bool + return r2 +def not_int(x): + x :: int + r0 :: bit +L0: + r0 = int_eq x, 0 + return r0 +def not_str(x): + x :: str + r0, r1 :: bit +L0: + r0 = CPyStr_IsTrue(x) + r1 = r0 ^ 1 + return r1 +def not_list(x): + x :: list + r0 :: native_int + r1 :: short_int + r2, r3 :: bit +L0: + r0 = var_object_size x + r1 = r0 << 1 + r2 = int_ne r1, 0 + r3 = r2 ^ 1 + return r3 +def not_tuple(x): + x :: tuple + r0 :: native_int + r1 :: short_int + r2, r3 :: bit +L0: + r0 = var_object_size x + r1 = r0 << 1 + r2 = int_ne r1, 0 + r3 = r2 ^ 1 + return r3 +def not_dict(x): + x :: dict + r0 :: native_int + r1 :: short_int + r2, r3 :: bit +L0: + r0 = PyDict_Size(x) + r1 = r0 << 1 + r2 = int_ne r1, 0 + r3 = r2 ^ 1 + return r3 + +[case testUnaryNotWithNativeClass] +from __future__ import annotations + +class C: + def __bool__(self) -> bool: + return True + +def not_c(x: C) -> bool: + return not x + +def not_c_opt(x: C | None) -> bool: + return not x +[out] +def C.__bool__(self): + self :: __main__.C +L0: + return 1 +def not_c(x): + x :: __main__.C + r0, r1 :: bool +L0: + r0 = x.__bool__() + r1 = r0 ^ 1 + return r1 +def not_c_opt(x): + x :: union[__main__.C, None] + r0 :: object + r1, r2 :: bit + r3 :: __main__.C + r4, r5 :: bool +L0: + r0 = load_address _Py_NoneStruct + r1 = x == r0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = 1 + goto L3 +L2: + r3 = unchecked borrow cast(__main__.C, x) + r4 = r3.__bool__() + r5 = r4 ^ 1 + r2 = r5 +L3: + keep_alive x + return r2 + +[case testUnaryNotWithOptionalPrimitiveTypes] +from __future__ import annotations + +def not_str(x: str | None) -> bool: + return not x + +def not_list(x: list[int] | None) -> bool: + return not x +[out] +def not_str(x): + x :: union[str, None] + r0 :: object + r1, r2 :: bit + r3 :: str + r4, r5 :: bit +L0: + r0 = load_address _Py_NoneStruct + r1 = x == r0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = 1 + goto L3 +L2: + r3 = unchecked borrow cast(str, x) + r4 = CPyStr_IsTrue(r3) + r5 = r4 ^ 1 + r2 = r5 +L3: + keep_alive x + return r2 +def not_list(x): + x :: union[list, None] + r0 :: object + r1, r2 :: bit + r3 :: list + r4 :: native_int + r5 :: short_int + r6, r7 :: bit +L0: + r0 = load_address _Py_NoneStruct + r1 = x == r0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = 1 + goto L3 +L2: + r3 = unchecked borrow cast(list, x) + r4 = var_object_size r3 + r5 = r4 << 1 + r6 = int_ne r5, 0 + r7 = r6 ^ 1 + r2 = r7 +L3: + keep_alive x + return r2 diff --git a/mypyc/test-data/irbuild-i64.test b/mypyc/test-data/irbuild-i64.test index e55c3bfe2acc..955f8b658f0e 100644 --- a/mypyc/test-data/irbuild-i64.test +++ b/mypyc/test-data/irbuild-i64.test @@ -947,6 +947,8 @@ def f(x: i64) -> i64: elif not x: return 6 return 3 +def unary_not(x: i64) -> bool: + return not x [out] def f(x): x :: i64 @@ -964,6 +966,12 @@ L3: L4: L5: return 3 +def unary_not(x): + x :: i64 + r0 :: bit +L0: + r0 = x == 0 + return r0 [case testI64AssignMixed_64bit] from mypy_extensions import i64 diff --git a/mypyc/test-data/run-bools.test b/mypyc/test-data/run-bools.test index b34fedebaa9f..45bf861e71e3 100644 --- a/mypyc/test-data/run-bools.test +++ b/mypyc/test-data/run-bools.test @@ -15,6 +15,8 @@ True False [case testBoolOps] +from __future__ import annotations + from typing import Optional, Any MYPY = False if MYPY: @@ -117,6 +119,29 @@ def test_optional_to_bool() -> None: assert not optional_to_bool3(F(False)) assert optional_to_bool3(F(True)) +def not_c(c: C) -> bool: + return not c + +def not_c_opt(c: C | None) -> bool: + return not c + +def not_d(d: D) -> bool: + return not d + +def not_d_opt(d: D | None) -> bool: + return not d + +def test_not_instance() -> None: + assert not not_c(C()) + assert not_c_opt(None) + assert not not_c_opt(C()) + + assert not_d(D(False)) + assert not not_d(D(True)) + assert not_d_opt(D(False)) + assert not_d_opt(None) + assert not not_d_opt(D(True)) + def test_any_to_bool() -> None: a: Any = int() b: Any = a + 1 @@ -222,6 +247,93 @@ def test_mixed_comparisons_i64() -> None: assert lt_mixed_i64(x, n) == (int(x) < n) assert gt_mixed_i64(n, x) == (n > int(x)) +def not_object(x: object) -> bool: + return not x + +def not_str(x: str) -> bool: + return not x + +def not_int(x: int) -> bool: + return not x + +def not_list(x: list[int]) -> bool: + return not x + +def not_tuple(x: tuple[int, ...]) -> bool: + return not x + +def not_dict(x: dict[str, int]) -> bool: + return not x + +def test_not_object() -> None: + assert not_object(None) + assert not_object([]) + assert not_object(0) + assert not not_object(1) + assert not not_object([1]) + +def test_not_str() -> None: + assert not_str(str()) + assert not not_str('x' + str()) + +def test_not_int() -> None: + assert not_int(int('0')) + assert not not_int(int('1')) + assert not not_int(int('-1')) + +def test_not_list() -> None: + assert not_list([]) + assert not not_list([1]) + +def test_not_tuple() -> None: + assert not_tuple(()) + assert not not_tuple((1,)) + +def test_not_dict() -> None: + assert not_dict({}) + assert not not_dict({'x': 1}) + +def not_str_opt(x: str | None) -> bool: + return not x + +def not_int_opt(x: int | None) -> bool: + return not x + +def not_list_opt(x: list[int] | None) -> bool: + return not x + +def not_tuple_opt(x: tuple[int, ...] | None) -> bool: + return not x + +def not_dict_opt(x: dict[str, int] | None) -> bool: + return not x + +def test_not_str_opt() -> None: + assert not_str_opt(str()) + assert not_str_opt(None) + assert not not_str_opt('x' + str()) + +def test_not_int_opt() -> None: + assert not_int_opt(int('0')) + assert not_int_opt(None) + assert not not_int_opt(int('1')) + assert not not_int_opt(int('-1')) + +def test_not_list_opt() -> None: + assert not_list_opt([]) + assert not_list_opt(None) + assert not not_list_opt([1]) + +def test_not_tuple_opt() -> None: + assert not_tuple_opt(()) + assert not_tuple_opt(None) + assert not not_tuple_opt((1,)) + +def test_not_dict_opt() -> None: + assert not_dict_opt({}) + assert not_dict_opt(None) + assert not not_dict_opt({'x': 1}) + [case testBoolMixInt] def test_mix() -> None: y = False