Skip to content

Commit b337e97

Browse files
committed
[Python] Raise a TypeError if C++ nullptr is compared with None
C++ nullptr comparisons with None were deprecated in ROOT 6.38 and will now raise a TypeError. Also adapt the tests, and delete a duplicate test in `roottest/python`.
1 parent 19fa2dc commit b337e97

File tree

6 files changed

+31
-60
lines changed

6 files changed

+31
-60
lines changed

README/ReleaseNotes/v640/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The following people have contributed to this new version:
3535
* The `TH1K` class was removed. `TMath::KNNDensity` can be used in its stead.
3636

3737
* The `TObject` equality operator pythonization (`TObject.__eq__`) that was deprecated in ROOT 6.38 and scheduled for removal in ROOT 6.40 is removed
38+
* Comparing C++ `nullptr` objects with `None` in Python now raises a `TypeError`, as announced in the ROOT 6.38 release notes. Use truth-value checks like `if not x` or `x is None` instead.
3839

3940
## Build System
4041

bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -574,20 +574,16 @@ static PyObject* op_richcompare(CPPInstance* self, PyObject* other, int op)
574574
// special case for None to compare True to a null-pointer
575575
if ((PyObject*)other == Py_None && !self->fObject) {
576576
const char *msg =
577-
"\nComparison of C++ nullptr objects with `None` is deprecated and will raise a TypeError starting from ROOT 6.40."
578-
"\n\nThis currently treats `None` as equivalent to a null C++ pointer, which may lead to confusing results."
579-
"\nFor example, `x == None` may return True even though `x is None` is False."
577+
"\nComparison of C++ nullptr objects with `None` is no longer supported."
578+
"\n\nPreviously, `None` was treated as equivalent to a null C++ pointer, "
579+
"but this led to confusing behavior where `x == None` could be True even though `x is None` was False."
580580
"\n\nTo test whether a C++ object is null or not, check its truth value instead:"
581581
"\n if not x: ..."
582582
"\nor use `x is None` to explicitly check for Python None."
583583
"\n";
584584

585-
// Equivalent to: warnings.warn(msg, FutureWarning, stacklevel=0)
586-
if (PyErr_WarnEx(PyExc_FutureWarning, msg, 0) < 0) {
587-
return NULL; // Propagate the error if warning turned into an exception
588-
}
589-
if (op == Py_EQ) { Py_RETURN_TRUE; }
590-
Py_RETURN_FALSE;
585+
PyErr_SetString(PyExc_TypeError, msg);
586+
return NULL; // stop execution, raise TypeError
591587
}
592588

593589
// use C++-side operators if available

bindings/pyroot/cppyy/cppyy/test/test_datatypes.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -989,17 +989,15 @@ def test19_object_and_pointer_comparisons(self):
989989
gbl = cppyy.gbl
990990

991991
c1 = cppyy.bind_object(0, gbl.CppyyTestData)
992-
assert c1 == None
993-
assert None == c1
992+
assert not c1
994993

995994
c2 = cppyy.bind_object(0, gbl.CppyyTestData)
996995
assert c1 == c2
997996
assert c2 == c1
998997

999998
# FourVector overrides operator==
1000999
l1 = cppyy.bind_object(0, gbl.FourVector)
1001-
assert l1 == None
1002-
assert None == l1
1000+
assert not l1
10031001

10041002
assert c1 != l1
10051003
assert l1 != c1
@@ -1014,10 +1012,9 @@ def test19_object_and_pointer_comparisons(self):
10141012
assert l3 == l4
10151013
assert l4 == l3
10161014

1017-
assert l3 != None # like this to ensure __ne__ is called
1018-
assert None != l3 # id.
1019-
assert l3 != l5
1020-
assert l5 != l3
1015+
assert l3
1016+
assert l3 != l5 # like this to ensure __ne__ is called
1017+
assert l5 != l3 # id.
10211018

10221019
def test20_object_comparisons_with_cpp__eq__(self):
10231020
"""Comparisons with C++ providing __eq__/__ne__"""
@@ -1364,7 +1361,7 @@ def voidf(i):
13641361
retval = i
13651362

13661363
assert retval is None
1367-
assert fv(voidf, 5) == None
1364+
assert not fv(voidf, 5)
13681365
assert retval == 5
13691366

13701367
# call of function with reference argument
@@ -1438,7 +1435,7 @@ def voidf(i):
14381435
retval = i
14391436

14401437
assert retval is None
1441-
assert fv(voidf, 5) == None
1438+
assert not fv(voidf, 5)
14421439
assert retval == 5
14431440

14441441
# call of function with reference argument

bindings/pyroot/pythonizations/test/tobject_comparisonops.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ def test_ne(self):
3939
# Test comparison with None
4040
self.assertTrue(o != None)
4141

42+
def test_nullptr_eq_none_raises(self):
43+
import cppyy
44+
45+
x = cppyy.bind_object(cppyy.nullptr, "TObject")
46+
47+
# Comparing a nullptr to None must raise TypeError in ROOT >= 6.40
48+
# This is important to check, because if we don't raise an error, the
49+
# result might not be equivalent to the confusing behavior in previous
50+
# ROOT versions, which would be a silent behavior change.
51+
# See https://github.com/root-project/root/issues/20283
52+
with self.assertRaises(TypeError):
53+
_ = x == None
54+
55+
with self.assertRaises(TypeError):
56+
_ = x != None
57+
4258
def test_lt(self):
4359
a = TUrl("a")
4460
b = TUrl("b")

roottest/python/basic/PyROOT_datatypetest.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -820,43 +820,6 @@ def test17_respect_privacy(self):
820820

821821
c.__destruct__()
822822

823-
def test18_object_and_pointer_comparisons(self):
824-
"""Object and pointer comparisons"""
825-
826-
import cppyy
827-
gbl = cppyy.gbl
828-
829-
c1 = cppyy.bind_object(0, gbl.CppyyTestData)
830-
assert c1 == None
831-
assert None == c1
832-
833-
c2 = cppyy.bind_object(0, gbl.CppyyTestData)
834-
assert c1 == c2
835-
assert c2 == c1
836-
837-
# FourVector overrides operator==
838-
l1 = cppyy.bind_object(0, gbl.FourVector)
839-
assert l1 == None
840-
assert None == l1
841-
842-
assert c1 != l1
843-
assert l1 != c1
844-
845-
l2 = cppyy.bind_object(0, gbl.FourVector)
846-
assert l1 == l2
847-
assert l2 == l1
848-
849-
l3 = gbl.FourVector(1, 2, 3, 4)
850-
l4 = gbl.FourVector(1, 2, 3, 4)
851-
l5 = gbl.FourVector(4, 3, 2, 1)
852-
assert l3 == l4
853-
assert l4 == l3
854-
855-
assert l3 != None # like this to ensure __ne__ is called
856-
assert None != l3 # id.
857-
assert l3 != l5
858-
assert l5 != l3
859-
860823
def test19_object_validity(self):
861824
"""Object validity checking"""
862825

roottest/python/cpp/PyROOT_cpptests.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,17 +292,15 @@ def test15ObjectAndPointerComparisons( self ):
292292
"""Verify object and pointer comparisons"""
293293

294294
c1 = MakeNullPointer( TCanvas )
295-
self.assertEqual( c1, None )
296-
self.assertEqual( None, c1 )
295+
self.assertFalse( c1 )
297296

298297
c2 = MakeNullPointer( TCanvas )
299298
self.assertEqual( c1, c2 )
300299
self.assertEqual( c2, c1 )
301300

302301
# TLorentzVector overrides operator==
303302
l1 = MakeNullPointer( TLorentzVector )
304-
self.assertEqual( l1, None )
305-
self.assertEqual( None, l1 )
303+
self.assertFalse( l1 )
306304

307305
self.assertNotEqual( c1, l1 )
308306
self.assertNotEqual( l1, c1 )

0 commit comments

Comments
 (0)