Skip to content

gh-122982: Prohibit bitwise inversion on bool type #122983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
1 change: 0 additions & 1 deletion Doc/deprecations/pending-removal-in-future.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ although there is currently no date scheduled for their removal.

* :mod:`builtins`:

* ``~bool``, bitwise inversion on bool.
Copy link
Member

@AA-Turner AA-Turner Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this gets merged, you should open a new PR to move this note to the 3.14 pending removals & backport that.

* ``bool(NotImplemented)``.
* Generators: ``throw(type, exc, tb)`` and ``athrow(type, exc, tb)``
signature is deprecated: use ``throw(exc)`` and ``athrow(exc)`` instead,
Expand Down
6 changes: 3 additions & 3 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -865,10 +865,10 @@ return a bool equivalent to the logical operations "and", "or", "xor". However,
the logical operators ``and``, ``or`` and ``!=`` should be preferred
over ``&``, ``|`` and ``^``.

.. deprecated:: 3.12
.. versionchanged:: 3.14

The use of the bitwise inversion operator ``~`` is deprecated and will
raise an error in Python 3.14.
The use of the bitwise inversion operator ``~`` is prohibited and raises
a :exc:`TypeError` now.

:class:`bool` is a subclass of :class:`int` (see :ref:`typesnumeric`). In
many numeric contexts, ``False`` and ``True`` behave like the integers 0 and 1, respectively.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ asyncio

(Contributed by Kumar Aditya in :gh:`120804`.)

builtins
--------

* Bitwise inversion (``~``) is prohibited for :data:`True` and :data:`False`
which had previously raised a :exc:`DeprecationWarning` since Python 3.12.

collections.abc
---------------

Expand Down
42 changes: 26 additions & 16 deletions Lib/test/test_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,32 @@ def test_math(self):
self.assertEqual(-True, -1)
self.assertEqual(abs(True), 1)
self.assertIsNot(abs(True), True)
with self.assertWarns(DeprecationWarning):
# We need to put the bool in a variable, because the constant
# ~False is evaluated at compile time due to constant folding;
# consequently the DeprecationWarning would be issued during
# module loading and not during test execution.
false = False
self.assertEqual(~false, -1)
with self.assertWarns(DeprecationWarning):
# also check that the warning is issued in case of constant
# folding at compile time
self.assertEqual(eval("~False"), -1)
with self.assertWarns(DeprecationWarning):
true = True
self.assertEqual(~true, -2)
with self.assertWarns(DeprecationWarning):
self.assertEqual(eval("~True"), -2)

# Bitwise inversion is prohibited on bool type.
with self.assertRaises(TypeError):
~True
with self.assertRaises(TypeError):
~False

true = True
false = False
with self.assertRaises(TypeError):
~true
with self.assertRaises(TypeError):
~false
with self.assertRaises(TypeError):
eval("~True")
with self.assertRaises(TypeError):
eval("~False")
with self.assertRaises(TypeError):
true.__invert__()
with self.assertRaises(TypeError):
false.__invert__()

with self.assertRaisesRegex(TypeError, r"Did you mean 'not' instead of '~'\?"):
~true
with self.assertRaisesRegex(TypeError, r"Did you mean 'not' instead of '~'\?"):
~false

self.assertEqual(False+2, 2)
self.assertEqual(True+2, 3)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bitwise inversion (``~``) is prohibited for :data:`True` and :data:`False`.
16 changes: 5 additions & 11 deletions Objects/boolobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,11 @@ bool_vectorcall(PyObject *type, PyObject * const*args,
static PyObject *
bool_invert(PyObject *v)
{
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Bitwise inversion '~' on bool is deprecated. This "
"returns the bitwise inversion of the underlying int "
"object and is usually not what you expect from negating "
"a bool. Use the 'not' operator for boolean negation or "
"~int(x) if you really want the bitwise inversion of the "
"underlying int.",
1) < 0) {
return NULL;
}
return PyLong_Type.tp_as_number->nb_invert(v);
PyErr_SetString(
PyExc_TypeError,
"bad operand type for unary ~: 'bool'. Did you mean 'not' instead of '~'?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the text in the warning was helpful, especially since the previous behaviour has been around forever.

Maybe something like:

bad operand type for unary ~: 'bool'. Did you mean 'not' instead of '~'? Use 'not x' for boolean negation and '~int(x)` in the rare case you want bitwise inversion of the underlying int"

);
return NULL;
}

static PyObject *
Expand Down
Loading