diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 25629b4da053da..4640fd2727b063 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -823,10 +823,12 @@ function,Py_CompileString,3.2,, function,Py_DecRef,3.2,, function,Py_DecodeLocale,3.7,, macro,Py_END_ALLOW_THREADS,3.2,, +var,Py_Ellipsis,3.13,, function,Py_EncodeLocale,3.7,, function,Py_EndInterpreter,3.2,, function,Py_EnterRecursiveCall,3.9,, function,Py_Exit,3.2,, +var,Py_False,3.13,, function,Py_FatalError,3.2,, var,Py_FileSystemDefaultEncodeErrors,3.10,, var,Py_FileSystemDefaultEncoding,3.2,, @@ -861,9 +863,12 @@ function,Py_Main,3.2,, function,Py_MakePendingCalls,3.2,, function,Py_NewInterpreter,3.2,, function,Py_NewRef,3.10,, +var,Py_None,3.13,, +var,Py_NotImplemented,3.13,, function,Py_ReprEnter,3.2,, function,Py_ReprLeave,3.2,, function,Py_SetRecursionLimit,3.2,, +var,Py_True,3.13,, type,Py_UCS4,3.2,, macro,Py_UNBLOCK_THREADS,3.2,, var,Py_UTF8Mode,3.8,, diff --git a/Include/boolobject.h b/Include/boolobject.h index 19aef5b1b87c6a..2a739812fa278b 100644 --- a/Include/boolobject.h +++ b/Include/boolobject.h @@ -17,9 +17,15 @@ extern "C" { PyAPI_DATA(PyLongObject) _Py_FalseStruct; PyAPI_DATA(PyLongObject) _Py_TrueStruct; -/* Use these macros */ +// Export symbols in the stable ABI +PyAPI_DATA(PyLongObject*) Py_False; +PyAPI_DATA(PyLongObject*) Py_True; + +#ifndef Py_LIMITED_API +// Implement Py_False and Py_True as macros in the non-limited C API #define Py_False _PyObject_CAST(&_Py_FalseStruct) #define Py_True _PyObject_CAST(&_Py_TrueStruct) +#endif // Test if an object is the True singleton, the same as "x is True" in Python. PyAPI_FUNC(int) Py_IsTrue(PyObject *x); diff --git a/Include/object.h b/Include/object.h index 05187fe5dc4f20..097c35df8d7915 100644 --- a/Include/object.h +++ b/Include/object.h @@ -1070,7 +1070,14 @@ _Py_NoneStruct is an object of undefined type which can be used in contexts where NULL (nil) is not suitable (since NULL often means 'error'). */ PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */ + +// Export the symbol in the stable ABI +PyAPI_DATA(PyObject*) Py_None; + +#ifndef Py_LIMITED_API +// Implement Py_None as a macro in the non-limited C API #define Py_None (&_Py_NoneStruct) +#endif // Test if an object is the None singleton, the same as "x is None" in Python. PyAPI_FUNC(int) Py_IsNone(PyObject *x); @@ -1084,7 +1091,14 @@ Py_NotImplemented is a singleton used to signal that an operation is not implemented for a given type combination. */ PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */ + +// Export the symbol in the stable ABI +PyAPI_DATA(PyObject*) Py_NotImplemented; + +#ifndef Py_LIMITED_API +// Implement Py_NotImplemented as a macro in the non-limited C API #define Py_NotImplemented (&_Py_NotImplementedStruct) +#endif /* Macro for returning Py_NotImplemented from a function */ #define Py_RETURN_NOTIMPLEMENTED return Py_NotImplemented diff --git a/Include/sliceobject.h b/Include/sliceobject.h index c13863f27c2e63..031dbe9e15bf6f 100644 --- a/Include/sliceobject.h +++ b/Include/sliceobject.h @@ -8,7 +8,13 @@ extern "C" { PyAPI_DATA(PyObject) _Py_EllipsisObject; /* Don't use this directly */ +// Export the symbol in the stable ABI +PyAPI_DATA(PyObject*) Py_Ellipsis; + +#ifndef Py_LIMITED_API +// Implement Py_Ellipsis as a macro in the non-limited C API #define Py_Ellipsis (&_Py_EllipsisObject) +#endif /* Slice object interface */ diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 8bd373976426ef..eff3c468fc3463 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -40,6 +40,18 @@ def test_windows_feature_macros(self): with self.subTest(name): self.assertEqual(feature_macros[name], value) + def test_constants(self): + process = ctypes_test.pythonapi + def get_object(name): + return ctypes_test.py_object.in_dll(process, name).value + + self.assertIs(get_object("Py_None"), None) + self.assertIs(get_object("Py_False"), False) + self.assertIs(get_object("Py_True"), True) + self.assertIs(get_object("Py_Ellipsis"), Ellipsis) + self.assertIs(get_object("Py_NotImplemented"), NotImplemented) + + SYMBOL_NAMES = ( "PyAIter_Check", @@ -840,10 +852,12 @@ def test_windows_feature_macros(self): "Py_CompileString", "Py_DecRef", "Py_DecodeLocale", + "Py_Ellipsis", "Py_EncodeLocale", "Py_EndInterpreter", "Py_EnterRecursiveCall", "Py_Exit", + "Py_False", "Py_FatalError", "Py_FileSystemDefaultEncodeErrors", "Py_FileSystemDefaultEncoding", @@ -879,12 +893,15 @@ def test_windows_feature_macros(self): "Py_MakePendingCalls", "Py_NewInterpreter", "Py_NewRef", + "Py_None", + "Py_NotImplemented", "Py_ReprEnter", "Py_ReprLeave", "Py_SetPath", "Py_SetProgramName", "Py_SetPythonHome", "Py_SetRecursionLimit", + "Py_True", "Py_UTF8Mode", "Py_VaBuildValue", "Py_Version", diff --git a/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst b/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst new file mode 100644 index 00000000000000..e99e4a83e80553 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst @@ -0,0 +1,6 @@ +In the limited C API and the stable ABI, implement ``Py_None``, ``Py_False`` +``Py_True``, ``Py_Ellipsis`` and ``Py_NotImplemented`` constants are symbols, +rather than implementing them as macros. So they can be loaded by dlopen/dlsym +in an embedded in Python, rather than having to reimplement these macros +manually. In the non-limited C API, these constants are still implemented as +macros. Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index ca7cf02961571e..c7dc646b661aa7 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2496,3 +2496,13 @@ [typedef.PyCFunctionFastWithKeywords] added = '3.13' # "abi-only" since 3.10. (Same story as PyCFunctionFast.) +[data.Py_False] + added = '3.13' +[data.Py_True] + added = '3.13' +[data.Py_None] + added = '3.13' +[data.Py_Ellipsis] + added = '3.13' +[data.Py_NotImplemented] + added = '3.13' diff --git a/Objects/boolobject.c b/Objects/boolobject.c index fb48dcbeca7850..c05f8981216547 100644 --- a/Objects/boolobject.c +++ b/Objects/boolobject.c @@ -225,3 +225,10 @@ struct _longobject _Py_TrueStruct = { { 1 } } }; + +// Stable ABI: export symbols + +#undef Py_False +#undef Py_True +PyLongObject *Py_False = _Py_CAST(PyLongObject*, &_Py_FalseStruct); +PyLongObject *Py_True = _Py_CAST(PyLongObject*, &_Py_TrueStruct); diff --git a/Objects/object.c b/Objects/object.c index 23eab8288a41e8..2fde85b1167f30 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2970,3 +2970,10 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt) { Py_SET_REFCNT(ob, refcnt); } + + +// Export symbols in the stable ABI +#undef Py_None +#undef Py_NotImplemented +PyObject *Py_None = &_Py_NoneStruct; +PyObject *Py_NotImplemented = &_Py_NotImplementedStruct; diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 7333aea91e5648..6aeeb8021b5f98 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -721,3 +721,8 @@ PyTypeObject PySlice_Type = { 0, /* tp_alloc */ slice_new, /* tp_new */ }; + + +// Export the symbol in the stable ABI +#undef Py_Ellipsis +PyObject *Py_Ellipsis = &_Py_EllipsisObject; diff --git a/PC/python3dll.c b/PC/python3dll.c index aa6bfe2c4022db..95c2de2bc71e13 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -775,10 +775,15 @@ EXPORT_DATA(_Py_TrueStruct) EXPORT_DATA(_PyWeakref_CallableProxyType) EXPORT_DATA(_PyWeakref_ProxyType) EXPORT_DATA(_PyWeakref_RefType) +EXPORT_DATA(Py_Ellipsis) +EXPORT_DATA(Py_False) EXPORT_DATA(Py_FileSystemDefaultEncodeErrors) EXPORT_DATA(Py_FileSystemDefaultEncoding) EXPORT_DATA(Py_GenericAliasType) EXPORT_DATA(Py_HasFileSystemDefaultEncoding) +EXPORT_DATA(Py_None) +EXPORT_DATA(Py_NotImplemented) +EXPORT_DATA(Py_True) EXPORT_DATA(Py_UTF8Mode) EXPORT_DATA(Py_Version) EXPORT_DATA(PyBaseObject_Type) diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 83146622c74f94..87d461cc520c9f 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -309,6 +309,18 @@ def test_windows_feature_macros(self): with self.subTest(name): self.assertEqual(feature_macros[name], value) + def test_constants(self): + process = ctypes_test.pythonapi + def get_object(name): + return ctypes_test.py_object.in_dll(process, name).value + + self.assertIs(get_object("Py_None"), None) + self.assertIs(get_object("Py_False"), False) + self.assertIs(get_object("Py_True"), True) + self.assertIs(get_object("Py_Ellipsis"), Ellipsis) + self.assertIs(get_object("Py_NotImplemented"), NotImplemented) + + SYMBOL_NAMES = ( ''')) items = manifest.select( diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index e9b81cf4c7d653..e71e639cae28a3 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -310,6 +310,8 @@ Modules/pyexpat.c - error_info_of - Modules/pyexpat.c - handler_info - Modules/termios.c - termios_constants - Modules/timemodule.c init_timezone YEAR - +Objects/boolobject.c - Py_False - +Objects/boolobject.c - Py_True - Objects/bytearrayobject.c - _PyByteArray_empty_string - Objects/complexobject.c - c_1 - Objects/exceptions.c - static_exceptions - @@ -320,6 +322,8 @@ Objects/object.c - _Py_SwappedOp - Objects/object.c - _Py_abstract_hack - Objects/object.c - last_final_reftotal - Objects/object.c - static_types - +Objects/object.c - Py_None - +Objects/object.c - Py_NotImplemented - Objects/obmalloc.c - _PyMem - Objects/obmalloc.c - _PyMem_Debug - Objects/obmalloc.c - _PyMem_Raw - @@ -327,6 +331,7 @@ Objects/obmalloc.c - _PyObject - Objects/obmalloc.c - last_final_leaks - Objects/obmalloc.c - obmalloc_state_main - Objects/obmalloc.c - obmalloc_state_initialized - +Objects/sliceobject.c - Py_Ellipsis - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - Objects/unicodeobject.c - stripfuncnames -