diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index 1746fe95eaaca9..3f38411a52de6b 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -112,6 +112,7 @@ Other Objects picklebuffer.rst weakref.rst capsule.rst + sentinel.rst frame.rst gen.rst coro.rst diff --git a/Doc/c-api/sentinel.rst b/Doc/c-api/sentinel.rst new file mode 100644 index 00000000000000..890706fe3f2272 --- /dev/null +++ b/Doc/c-api/sentinel.rst @@ -0,0 +1,31 @@ +.. highlight:: c + +.. _sentinelobjects: + +Sentinel Objects +---------------- + +.. c:var:: PyTypeObject PySentinel_Type + + This instance of :c:type:`PyTypeObject` represents the Python + :class:`sentinel` type. This is the same object as :class:`sentinel`. + + .. versionadded:: next + +.. c:function:: int PySentinel_Check(PyObject *o) + + Return true if *o* is a :class:`sentinel` object. The :class:`sentinel` type + does not allow subclasses, so this check is exact. + + .. versionadded:: next + +.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name) + + Return a new :class:`sentinel` object with :attr:`~sentinel.__name__` set to + *name* and :attr:`~sentinel.__module__` set to *module_name*. + Return ``NULL`` with an exception set on failure. + + *module_name* should be the name of an importable module if the sentinel + should support pickling. + + .. versionadded:: next diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 2a6e6b963134bb..663b79e45eec17 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -2037,6 +2037,10 @@ PySeqIter_Check:PyObject *:op:0: PySeqIter_New:PyObject*::+1: PySeqIter_New:PyObject*:seq:0: +PySentinel_New:PyObject*::+1: +PySentinel_New:const char*:name:: +PySentinel_New:const char*:module_name:: + PySequence_Check:int::: PySequence_Check:PyObject*:o:0: diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 119141d2e6daf3..ca9f5d4c8575e1 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -19,13 +19,13 @@ are always available. They are listed here in alphabetical order. | | :func:`ascii` | | :func:`filter` | | :func:`map` | | **S** | | | | | :func:`float` | | :func:`max` | | |func-set|_ | | | **B** | | :func:`format` | | |func-memoryview|_ | | :func:`setattr` | -| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`slice` | -| | :func:`bool` | | | | | | :func:`sorted` | -| | :func:`breakpoint` | | **G** | | **N** | | :func:`staticmethod` | -| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | |func-str|_ | -| | |func-bytes|_ | | :func:`globals` | | | | :func:`sum` | -| | | | | | **O** | | :func:`super` | -| | **C** | | **H** | | :func:`object` | | | +| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`sentinel` | +| | :func:`bool` | | | | | | :func:`slice` | +| | :func:`breakpoint` | | **G** | | **N** | | :func:`sorted` | +| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | :func:`staticmethod` | +| | |func-bytes|_ | | :func:`globals` | | | | |func-str|_ | +| | | | | | **O** | | :func:`sum` | +| | **C** | | **H** | | :func:`object` | | :func:`super` | | | :func:`callable` | | :func:`hasattr` | | :func:`oct` | | **T** | | | :func:`chr` | | :func:`hash` | | :func:`open` | | |func-tuple|_ | | | :func:`classmethod` | | :func:`help` | | :func:`ord` | | :func:`type` | @@ -1827,6 +1827,45 @@ are always available. They are listed here in alphabetical order. :func:`setattr`. +.. class:: sentinel(name, /) + + Return a new unique sentinel object. *name* must be a :class:`str`, and is + used as the returned object's representation:: + + >>> MISSING = sentinel("MISSING") + >>> MISSING + MISSING + + Sentinel objects are truthy and compare equal only to themselves. They are + intended to be compared with the :keyword:`is` operator. + + Shallow and deep copies of a sentinel object return the object itself. + + Sentinels importable from their defining module by name preserve their + identity when pickled and unpickled. Sentinels that are not importable by + module and name are not picklable. + + Sentinels are conventionally assigned to a variable with a matching name. + Sentinels defined in this way can be used in :term:`type hints`:: + + MISSING = sentinel("MISSING") + + def next_value(default: int | MISSING = MISSING): + ... + + Sentinel objects support the :ref:`| ` operator for use in type expressions. + + .. attribute:: __name__ + + The sentinel's name. + + .. attribute:: __module__ + + The name of the module where the sentinel was created. + + .. versionadded:: next + + .. class:: slice(stop, /) slice(start, stop, step=None, /) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 7ea7c901eceb19..b8cd97d4ed4c6d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -69,6 +69,8 @@ Summary -- Release highlights ` * :pep:`814`: :ref:`Add frozendict built-in type ` +* :pep:`661`: :ref:`Add sentinel built-in type + ` * :pep:`799`: :ref:`A dedicated profiling package for organizing Python profiling tools ` * :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler @@ -234,6 +236,18 @@ to accept also other mapping types such as :class:`~types.MappingProxyType`. (Contributed by Victor Stinner and Donghee Na in :gh:`141510`.) +.. _whatsnew315-sentinel: + +:pep:`661`: Add sentinel built-in type +-------------------------------------- + +A new :class:`sentinel` type is added to the :mod:`builtins` module for +creating unique sentinel values with a concise representation. Sentinel +objects preserve identity when copied, support use in type expressions with +the ``|`` operator, and can be pickled when they are importable by module and +name. + + .. _whatsnew315-profiling-package: :pep:`799`: A dedicated profiling package diff --git a/Include/Python.h b/Include/Python.h index e6e5cab67e2045..79252192a1dea1 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -117,6 +117,7 @@ __pragma(warning(disable: 4201)) #include "cpython/genobject.h" #include "descrobject.h" #include "genericaliasobject.h" +#include "sentinelobject.h" #include "warnings.h" #include "weakrefobject.h" #include "structseq.h" diff --git a/Include/sentinelobject.h b/Include/sentinelobject.h new file mode 100644 index 00000000000000..9d8577767b7485 --- /dev/null +++ b/Include/sentinelobject.h @@ -0,0 +1,22 @@ +/* Sentinel object interface */ + +#ifndef Py_SENTINELOBJECT_H +#define Py_SENTINELOBJECT_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_LIMITED_API +PyAPI_DATA(PyTypeObject) PySentinel_Type; + +#define PySentinel_Check(op) Py_IS_TYPE((op), &PySentinel_Type) + +PyAPI_FUNC(PyObject *) PySentinel_New( + const char *name, + const char *module_name); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* !Py_SENTINELOBJECT_H */ diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 844656eb0e2c2e..03220de05d50e8 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -4,6 +4,7 @@ import builtins import collections import contextlib +import copy import decimal import fractions import gc @@ -21,6 +22,7 @@ import typing import unittest import warnings +import weakref from contextlib import ExitStack from functools import partial from inspect import CO_COROUTINE @@ -52,6 +54,10 @@ # used as proof of globals being used A_GLOBAL_VALUE = 123 +A_SENTINEL = sentinel("A_SENTINEL") + +class SentinelContainer: + CLASS_SENTINEL = sentinel("SentinelContainer.CLASS_SENTINEL") class Squares: @@ -1903,6 +1909,79 @@ class C: __repr__ = None self.assertRaises(TypeError, repr, C()) + def test_sentinel(self): + missing = sentinel("MISSING") + other = sentinel("MISSING") + + self.assertIsInstance(missing, sentinel) + self.assertIs(type(missing), sentinel) + self.assertEqual(missing.__name__, "MISSING") + self.assertEqual(missing.__module__, __name__) + self.assertIsNot(missing, other) + self.assertEqual(repr(missing), "MISSING") + self.assertTrue(missing) + self.assertIs(copy.copy(missing), missing) + self.assertIs(copy.deepcopy(missing), missing) + self.assertEqual(missing, missing) + self.assertNotEqual(missing, other) + self.assertRaises(TypeError, sentinel) + self.assertRaises(TypeError, sentinel, "MISSING", "EXTRA") + self.assertRaises(TypeError, sentinel, name="MISSING") + with self.assertRaisesRegex(TypeError, "must be str"): + sentinel(1) + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_IMMUTABLETYPE) + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_HAVE_GC) + self.assertFalse(sentinel.__flags__ & support._TPFLAGS_BASETYPE) + with self.assertRaises(TypeError): + class SubSentinel(sentinel): + pass + with self.assertRaises(TypeError): + sentinel.attribute = "value" + with self.assertRaises(AttributeError): + missing.__name__ = "CHANGED" + with self.assertRaises(AttributeError): + missing.__module__ = "changed" + + def test_sentinel_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + self.assertIs( + pickle.loads(pickle.dumps(A_SENTINEL, protocol=proto)), + A_SENTINEL) + self.assertIs( + pickle.loads(pickle.dumps( + SentinelContainer.CLASS_SENTINEL, protocol=proto)), + SentinelContainer.CLASS_SENTINEL) + + missing = sentinel("MISSING") + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + with self.assertRaises(pickle.PicklingError): + pickle.dumps(missing, protocol=proto) + + def test_sentinel_str_subclass_name_cycle(self): + class Name(str): + pass + + name = Name("MISSING") + missing = sentinel(name) + self.assertIs(missing.__name__, name) + self.assertTrue(gc.is_tracked(missing)) + + name.missing = missing + ref = weakref.ref(name) + del name, missing + support.gc_collect() + self.assertIsNone(ref()) + + def test_sentinel_union(self): + missing = sentinel("MISSING") + + self.assertEqual((missing | int).__args__, (missing, int)) + self.assertEqual((int | missing).__args__, (int, missing)) + self.assertIs(missing | missing, missing) + self.assertEqual(repr(int | missing), "int | MISSING") + def test_round(self): self.assertEqual(round(0.0), 0.0) self.assertEqual(type(round(0.0)), int) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 67572ab1ba268d..252768cd281f2b 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -63,6 +63,24 @@ def test_get_constant_borrowed(self): self.check_get_constant(_testlimitedcapi.get_constant_borrowed) +class SentinelTest(unittest.TestCase): + + def test_pysentinel_new(self): + import pickle + + marker = _testcapi.pysentinel_new("CAPI_SENTINEL", __name__) + self.assertIs(type(marker), sentinel) + self.assertTrue(_testcapi.pysentinel_check(marker)) + self.assertFalse(_testcapi.pysentinel_check(object())) + self.assertEqual(marker.__name__, "CAPI_SENTINEL") + self.assertEqual(marker.__module__, __name__) + self.assertEqual(repr(marker), "CAPI_SENTINEL") + + globals()["CAPI_SENTINEL"] = marker + self.addCleanup(globals().pop, "CAPI_SENTINEL", None) + self.assertIs(pickle.loads(pickle.dumps(marker)), marker) + + class PrintTest(unittest.TestCase): def testPyObjectPrintObject(self): diff --git a/Lib/typing.py b/Lib/typing.py index e78fb8b71a996c..10a3f8117575ec 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3122,31 +3122,7 @@ def _namedtuple_mro_entries(bases): NamedTuple.__mro_entries__ = _namedtuple_mro_entries -class _SingletonMeta(type): - def __setattr__(cls, attr, value): - # TypeError is consistent with the behavior of NoneType - raise TypeError( - f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}" - ) - - -class _NoExtraItemsType(metaclass=_SingletonMeta): - """The type of the NoExtraItems singleton.""" - - __slots__ = () - - def __new__(cls): - return globals().get("NoExtraItems") or object.__new__(cls) - - def __repr__(self): - return 'typing.NoExtraItems' - - def __reduce__(self): - return 'NoExtraItems' - -NoExtraItems = _NoExtraItemsType() -del _NoExtraItemsType -del _SingletonMeta +NoExtraItems = sentinel("NoExtraItems") def _get_typeddict_qualifiers(annotation_type): diff --git a/Makefile.pre.in b/Makefile.pre.in index f869c1f7c93776..e5a75be972da7a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -559,6 +559,7 @@ OBJECT_OBJS= \ Objects/picklebufobject.o \ Objects/rangeobject.o \ Objects/setobject.o \ + Objects/sentinelobject.o \ Objects/sliceobject.o \ Objects/structseq.o \ Objects/templateobject.o \ @@ -1238,6 +1239,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/rangeobject.h \ $(srcdir)/Include/refcount.h \ $(srcdir)/Include/setobject.h \ + $(srcdir)/Include/sentinelobject.h \ $(srcdir)/Include/sliceobject.h \ $(srcdir)/Include/structmember.h \ $(srcdir)/Include/structseq.h \ diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 9160005e00654f..0d8be128f7f36e 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -555,6 +555,23 @@ pyobject_dump(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject * +pysentinel_new(PyObject *self, PyObject *args) +{ + const char *name; + const char *module_name; + if (!PyArg_ParseTuple(args, "ss", &name, &module_name)) { + return NULL; + } + return PySentinel_New(name, module_name); +} + +static PyObject * +pysentinel_check(PyObject *self, PyObject *obj) +{ + return PyBool_FromLong(PySentinel_Check(obj)); +} + static PyMethodDef test_methods[] = { {"call_pyobject_print", call_pyobject_print, METH_VARARGS}, @@ -585,6 +602,8 @@ static PyMethodDef test_methods[] = { {"clear_managed_dict", clear_managed_dict, METH_O, NULL}, {"is_uniquely_referenced", is_uniquely_referenced, METH_O}, {"pyobject_dump", pyobject_dump, METH_VARARGS}, + {"pysentinel_new", pysentinel_new, METH_VARARGS}, + {"pysentinel_check", pysentinel_check, METH_O}, {NULL}, }; diff --git a/Objects/clinic/sentinelobject.c.h b/Objects/clinic/sentinelobject.c.h new file mode 100644 index 00000000000000..51fd35a5979e31 --- /dev/null +++ b/Objects/clinic/sentinelobject.c.h @@ -0,0 +1,34 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +static PyObject * +sentinel_new_impl(PyTypeObject *type, PyObject *name); + +static PyObject * +sentinel_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = &PySentinel_Type; + PyObject *name; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("sentinel", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("sentinel", PyTuple_GET_SIZE(args), 1, 1)) { + goto exit; + } + if (!PyUnicode_Check(PyTuple_GET_ITEM(args, 0))) { + _PyArg_BadArgument("sentinel", "argument 1", "str", PyTuple_GET_ITEM(args, 0)); + goto exit; + } + name = PyTuple_GET_ITEM(args, 0); + return_value = sentinel_new_impl(type, name); + +exit: + return return_value; +} +/*[clinic end generated code: output=7f28fc0bf0259cba input=a9049054013a1b77]*/ diff --git a/Objects/object.c b/Objects/object.c index 3166254f6f640b..86a522fcb0769b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2600,6 +2600,7 @@ static PyTypeObject* static_types[] = { &PySeqIter_Type, &PySetIter_Type, &PySet_Type, + &PySentinel_Type, &PySlice_Type, &PyStdPrinter_Type, &PySuper_Type, diff --git a/Objects/sentinelobject.c b/Objects/sentinelobject.c new file mode 100644 index 00000000000000..5ef76e4566366a --- /dev/null +++ b/Objects/sentinelobject.c @@ -0,0 +1,215 @@ +/* Sentinel object implementation */ + +#include "Python.h" +#include "pycore_ceval.h" // _PyThreadState_GET() +#include "pycore_interpframe.h" // _PyFrame_IsIncomplete() +#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK() +#include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow() +#include "pycore_typeobject.h" // _Py_BaseObject_RichCompare() +#include "pycore_unionobject.h" // _Py_union_from_tuple() +#include "structmember.h" // PyMemberDef + +typedef struct { + PyObject_HEAD + PyObject *name; + PyObject *module; +} sentinelobject; + +#define sentinelobject_CAST(op) \ + (assert(PySentinel_Check(op)), _Py_CAST(sentinelobject *, (op))) + +/*[clinic input] +class sentinel "sentinelobject *" "&PySentinel_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=8b88f8268d3b5775]*/ + +#include "clinic/sentinelobject.c.h" + + +static PyObject * +caller(void) +{ + _PyInterpreterFrame *f = _PyThreadState_GET()->current_frame; + if (f == NULL) { + Py_RETURN_NONE; + } + if (f == NULL || PyStackRef_IsNull(f->f_funcobj)) { + Py_RETURN_NONE; + } + PyObject *r = PyFunction_GetModule(PyStackRef_AsPyObjectBorrow(f->f_funcobj)); + if (!r) { + PyErr_Clear(); + Py_RETURN_NONE; + } + return Py_NewRef(r); +} + +static PyObject * +sentinel_new_with_module(PyTypeObject *type, PyObject *name, PyObject *module) +{ + assert(PyUnicode_Check(name)); + + Py_INCREF(name); + Py_INCREF(module); + sentinelobject *self = PyObject_GC_New(sentinelobject, type); + if (self == NULL) { + Py_DECREF(name); + Py_DECREF(module); + return NULL; + } + self->name = name; + self->module = module; + _PyObject_GC_TRACK(self); + return (PyObject *)self; +} + +/*[clinic input] +@classmethod +sentinel.__new__ as sentinel_new + + name: object(subclass_of='&PyUnicode_Type') + / +[clinic start generated code]*/ + +static PyObject * +sentinel_new_impl(PyTypeObject *type, PyObject *name) +/*[clinic end generated code: output=4af55c6048bed30d input=3ab75704f39c119c]*/ +{ + PyObject *module = caller(); + if (module == NULL) { + return NULL; + } + + PyObject *self = sentinel_new_with_module(type, name, module); + Py_DECREF(module); + return self; +} + +PyObject * +PySentinel_New(const char *name, const char *module_name) +{ + PyObject *name_obj = PyUnicode_FromString(name); + if (name_obj == NULL) { + return NULL; + } + PyObject *module_obj = PyUnicode_FromString(module_name); + if (module_obj == NULL) { + Py_DECREF(name_obj); + return NULL; + } + + PyObject *sentinel = sentinel_new_with_module( + &PySentinel_Type, name_obj, module_obj); + Py_DECREF(module_obj); + Py_DECREF(name_obj); + return sentinel; +} + +static void +sentinel_dealloc(PyObject *op) +{ + _PyObject_GC_UNTRACK(op); + sentinelobject *self = sentinelobject_CAST(op); + Py_CLEAR(self->name); + Py_CLEAR(self->module); + Py_TYPE(op)->tp_free(op); +} + +static int +sentinel_traverse(PyObject *op, visitproc visit, void *arg) +{ + sentinelobject *self = sentinelobject_CAST(op); + Py_VISIT(self->name); + Py_VISIT(self->module); + return 0; +} + +static int +sentinel_clear(PyObject *op) +{ + sentinelobject *self = sentinelobject_CAST(op); + Py_CLEAR(self->name); + Py_CLEAR(self->module); + return 0; +} + +static PyObject * +sentinel_repr(PyObject *op) +{ + sentinelobject *self = sentinelobject_CAST(op); + return Py_NewRef(self->name); +} + +static PyObject * +sentinel_copy(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return Py_NewRef(self); +} + +static PyObject * +sentinel_deepcopy(PyObject *self, PyObject *Py_UNUSED(memo)) +{ + return Py_NewRef(self); +} + +static PyObject * +sentinel_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + sentinelobject *self = sentinelobject_CAST(op); + return Py_NewRef(self->name); +} + +static PyObject * +sentinel_or(PyObject *self, PyObject *other) +{ + PyObject *args = PyTuple_Pack(2, self, other); + if (args == NULL) { + return NULL; + } + PyObject *result = _Py_union_from_tuple(args); + Py_DECREF(args); + return result; +} + +static PyMethodDef sentinel_methods[] = { + {"__copy__", sentinel_copy, METH_NOARGS, NULL}, + {"__deepcopy__", sentinel_deepcopy, METH_O, NULL}, + {"__reduce__", sentinel_reduce, METH_NOARGS, NULL}, + {NULL, NULL} +}; + +static PyMemberDef sentinel_members[] = { + {"__name__", Py_T_OBJECT_EX, offsetof(sentinelobject, name), Py_READONLY}, + {"__module__", Py_T_OBJECT_EX, offsetof(sentinelobject, module), Py_READONLY}, + {NULL} +}; + +static PyNumberMethods sentinel_as_number = { + .nb_or = sentinel_or, +}; + +PyDoc_STRVAR(sentinel_doc, +"sentinel(name, /)\n" +"--\n\n" +"Create a unique sentinel object with the given name."); + +PyTypeObject PySentinel_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "sentinel", + .tp_basicsize = sizeof(sentinelobject), + .tp_dealloc = sentinel_dealloc, + .tp_repr = sentinel_repr, + .tp_as_number = &sentinel_as_number, + .tp_hash = PyObject_GenericHash, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC, + .tp_doc = sentinel_doc, + .tp_traverse = sentinel_traverse, + .tp_clear = sentinel_clear, + .tp_richcompare = _Py_BaseObject_RichCompare, + .tp_methods = sentinel_methods, + .tp_members = sentinel_members, + .tp_new = sentinel_new, + .tp_free = PyObject_GC_Del, +}; diff --git a/Objects/unionobject.c b/Objects/unionobject.c index d33d581f049c5b..0f6b1e44bc2402 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -245,6 +245,7 @@ is_unionable(PyObject *obj) { if (obj == Py_None || PyType_Check(obj) || + PySentinel_Check(obj) || _PyGenericAlias_Check(obj) || _PyUnion_Check(obj) || Py_IS_TYPE(obj, &_PyTypeAlias_Type)) { diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 38236922a523db..0286a5984360ab 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -159,6 +159,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 73861dbb0c9e7e..a2fc5554c6bd29 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -403,6 +403,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 61bee29c0af3d6..6d0ba6a8177417 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -381,6 +381,7 @@ + @@ -558,6 +559,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 664788e69af19a..81e64b1877d046 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -222,6 +222,9 @@ Include + + Include + Include @@ -1274,6 +1277,9 @@ Objects + + Objects + Objects diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index fec64e1ff9d25f..51b864fd414629 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -3554,6 +3554,7 @@ _PyBuiltin_Init(PyInterpreterState *interp) SETBUILTIN("range", &PyRange_Type); SETBUILTIN("reversed", &PyReversed_Type); SETBUILTIN("set", &PySet_Type); + SETBUILTIN("sentinel", &PySentinel_Type); SETBUILTIN("slice", &PySlice_Type); SETBUILTIN("staticmethod", &PyStaticMethod_Type); SETBUILTIN("str", &PyUnicode_Type); diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 74ca562824012b..8dabceafe9a57a 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -86,6 +86,7 @@ Objects/rangeobject.c - PyRange_Type - Objects/setobject.c - PyFrozenSet_Type - Objects/setobject.c - PySetIter_Type - Objects/setobject.c - PySet_Type - +Objects/sentinelobject.c - PySentinel_Type - Objects/sliceobject.c - PyEllipsis_Type - Objects/sliceobject.c - PySlice_Type - Objects/templateobject.c - _PyTemplateIter_Type -