Skip to content

Commit a7f0727

Browse files
authored
gh-124502: Add PyUnicode_Equal() function (#124504)
1 parent c5df1cb commit a7f0727

File tree

11 files changed

+111
-2
lines changed

11 files changed

+111
-2
lines changed

Doc/c-api/unicode.rst

+25
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,31 @@ They all return ``NULL`` or ``-1`` if an exception occurs.
14381438
This function returns ``-1`` upon failure, so one should call
14391439
:c:func:`PyErr_Occurred` to check for errors.
14401440
1441+
.. seealso::
1442+
1443+
The :c:func:`PyUnicode_Equal` function.
1444+
1445+
1446+
.. c:function:: int PyUnicode_Equal(PyObject *a, PyObject *b)
1447+
1448+
Test if two strings are equal:
1449+
1450+
* Return ``1`` if *a* is equal to *b*.
1451+
* Return ``0`` if *a* is not equal to *b*.
1452+
* Set a :exc:`TypeError` exception and return ``-1`` if *a* or *b* is not a
1453+
:class:`str` object.
1454+
1455+
The function always succeeds if *a* and *b* are :class:`str` objects.
1456+
1457+
The function works for :class:`str` subclasses, but does not honor custom
1458+
``__eq__()`` method.
1459+
1460+
.. seealso::
1461+
1462+
The :c:func:`PyUnicode_Compare` function.
1463+
1464+
.. versionadded:: 3.14
1465+
14411466
14421467
.. c:function:: int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *string, Py_ssize_t size)
14431468

Doc/data/stable_abi.dat

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.14.rst

+4
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,10 @@ New Features
687687
<https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`
688688
(:gh:`124153`).
689689

690+
* Add :c:func:`PyUnicode_Equal` function to the limited C API:
691+
test if two strings are equal.
692+
(Contributed by Victor Stinner in :gh:`124502`.)
693+
690694

691695
Porting to Python 3.14
692696
----------------------

Include/unicodeobject.h

+4
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,10 @@ PyAPI_FUNC(int) PyUnicode_EqualToUTF8(PyObject *, const char *);
966966
PyAPI_FUNC(int) PyUnicode_EqualToUTF8AndSize(PyObject *, const char *, Py_ssize_t);
967967
#endif
968968

969+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
970+
PyAPI_FUNC(int) PyUnicode_Equal(PyObject *str1, PyObject *str2);
971+
#endif
972+
969973
/* Rich compare two strings and return one of the following:
970974
971975
- NULL in case an exception was raised

Lib/test/test_capi/test_unicode.py

+33
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,39 @@ def test_recover_error(self):
19031903

19041904
self.assertEqual(writer.finish(), 'Hello World.')
19051905

1906+
def test_unicode_equal(self):
1907+
unicode_equal = _testlimitedcapi.unicode_equal
1908+
1909+
def copy(text):
1910+
return text.encode().decode()
1911+
1912+
self.assertTrue(unicode_equal("", ""))
1913+
self.assertTrue(unicode_equal("abc", "abc"))
1914+
self.assertTrue(unicode_equal("abc", copy("abc")))
1915+
self.assertTrue(unicode_equal("\u20ac", copy("\u20ac")))
1916+
self.assertTrue(unicode_equal("\U0010ffff", copy("\U0010ffff")))
1917+
1918+
self.assertFalse(unicode_equal("abc", "abcd"))
1919+
self.assertFalse(unicode_equal("\u20ac", "\u20ad"))
1920+
self.assertFalse(unicode_equal("\U0010ffff", "\U0010fffe"))
1921+
1922+
# str subclass
1923+
self.assertTrue(unicode_equal("abc", Str("abc")))
1924+
self.assertTrue(unicode_equal(Str("abc"), "abc"))
1925+
self.assertFalse(unicode_equal("abc", Str("abcd")))
1926+
self.assertFalse(unicode_equal(Str("abc"), "abcd"))
1927+
1928+
# invalid type
1929+
for invalid_type in (b'bytes', 123, ("tuple",)):
1930+
with self.subTest(invalid_type=invalid_type):
1931+
with self.assertRaises(TypeError):
1932+
unicode_equal("abc", invalid_type)
1933+
with self.assertRaises(TypeError):
1934+
unicode_equal(invalid_type, "abc")
1935+
1936+
# CRASHES unicode_equal("abc", NULL)
1937+
# CRASHES unicode_equal(NULL, "abc")
1938+
19061939

19071940
if __name__ == "__main__":
19081941
unittest.main()

Lib/test/test_stable_abi_ctypes.py

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyUnicode_Equal` function to the limited C API: test if two
2+
strings are equal. Patch by Victor Stinner.

Misc/stable_abi.toml

+2
Original file line numberDiff line numberDiff line change
@@ -2536,3 +2536,5 @@
25362536
added = '3.14'
25372537
[const.Py_TP_USE_SPEC]
25382538
added = '3.14'
2539+
[function.PyUnicode_Equal]
2540+
added = '3.14'

Modules/_testlimitedcapi/unicode.c

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "pyconfig.h" // Py_GIL_DISABLED
22
#ifndef Py_GIL_DISABLED
3-
// Need limited C API 3.13 to test PyUnicode_EqualToUTF8()
4-
# define Py_LIMITED_API 0x030d0000
3+
// Need limited C API 3.14 to test PyUnicode_Equal()
4+
# define Py_LIMITED_API 0x030e0000
55
#endif
66

77
#include "parts.h"
@@ -1837,6 +1837,23 @@ test_string_from_format(PyObject *self, PyObject *Py_UNUSED(ignored))
18371837
#undef CHECK_FORMAT_0
18381838
}
18391839

1840+
1841+
/* Test PyUnicode_Equal() */
1842+
static PyObject *
1843+
unicode_equal(PyObject *module, PyObject *args)
1844+
{
1845+
PyObject *str1, *str2;
1846+
if (!PyArg_ParseTuple(args, "OO", &str1, &str2)) {
1847+
return NULL;
1848+
}
1849+
1850+
NULLABLE(str1);
1851+
NULLABLE(str2);
1852+
RETURN_INT(PyUnicode_Equal(str1, str2));
1853+
}
1854+
1855+
1856+
18401857
static PyMethodDef TestMethods[] = {
18411858
{"codec_incrementalencoder", codec_incrementalencoder, METH_VARARGS},
18421859
{"codec_incrementaldecoder", codec_incrementaldecoder, METH_VARARGS},
@@ -1924,6 +1941,7 @@ static PyMethodDef TestMethods[] = {
19241941
{"unicode_format", unicode_format, METH_VARARGS},
19251942
{"unicode_contains", unicode_contains, METH_VARARGS},
19261943
{"unicode_isidentifier", unicode_isidentifier, METH_O},
1944+
{"unicode_equal", unicode_equal, METH_VARARGS},
19271945
{NULL},
19281946
};
19291947

Objects/unicodeobject.c

+18
Original file line numberDiff line numberDiff line change
@@ -11001,6 +11001,24 @@ _PyUnicode_Equal(PyObject *str1, PyObject *str2)
1100111001
}
1100211002

1100311003

11004+
int
11005+
PyUnicode_Equal(PyObject *str1, PyObject *str2)
11006+
{
11007+
if (!PyUnicode_Check(str1)) {
11008+
PyErr_Format(PyExc_TypeError,
11009+
"first argument must be str, not %T", str1);
11010+
return -1;
11011+
}
11012+
if (!PyUnicode_Check(str2)) {
11013+
PyErr_Format(PyExc_TypeError,
11014+
"second argument must be str, not %T", str2);
11015+
return -1;
11016+
}
11017+
11018+
return _PyUnicode_Equal(str1, str2);
11019+
}
11020+
11021+
1100411022
int
1100511023
PyUnicode_Compare(PyObject *left, PyObject *right)
1100611024
{

PC/python3dll.c

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)