From fc16e15898dbe7b3ede8e4f1f6dd48d27ee8f49a Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 1 Aug 2026 15:28:33 +0300 Subject: [PATCH 1/5] gh-154902: Add an intrinsic for conditional annotations --- Include/internal/pycore_intrinsics.h | 3 +- Lib/test/test_dis.py | 4 +-- Lib/test/test_type_annotations.py | 31 +++++++++++++++++++ ...-08-01-15-07-10.gh-issue-154902.DIi6sf.rst | 2 ++ Python/codegen.c | 4 ++- Python/intrinsics.c | 18 +++++++++++ 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst diff --git a/Include/internal/pycore_intrinsics.h b/Include/internal/pycore_intrinsics.h index 59a7b16073f886c..447cea91716eca9 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -30,8 +30,9 @@ #define INTRINSIC_TYPEVAR_WITH_CONSTRAINTS 3 #define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4 #define INTRINSIC_SET_TYPEPARAM_DEFAULT 5 +#define INTRINSIC_ADD_CONDITIONAL_ANNOTATION 6 -#define MAX_INTRINSIC_2 5 +#define MAX_INTRINSIC_2 6 typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value); typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, PyObject *value2); diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index c75992761d1334f..3e562a26ad586aa 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -395,12 +395,12 @@ def wrap_func_w_kwargs(): STORE_NAME 1 (x) LOAD_NAME 0 (__conditional_annotations__) LOAD_SMALL_INT 0 - SET_ADD 1 + CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION) POP_TOP 3 LOAD_NAME 0 (__conditional_annotations__) LOAD_SMALL_INT 1 - SET_ADD 1 + CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION) POP_TOP 4 LOAD_SMALL_INT 1 diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index b751f825bb97d59..1adfc0258783930 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -896,3 +896,34 @@ class Generic: mod = build_module(code) annos = mod.__annotations__ self.assertEqual(annos, {"annotated_name": 0}) + + # gh-154902 + def test_conditional_annotations_rebound(self): + # user code can rebind __conditional_annotations__ to any object + for value in ("0", "{}", "[]", "''", "object()"): + with self.subTest(value=value): + code = f""" + __conditional_annotations__ = {value} + x: int + """ + with self.assertRaises(TypeError): + run_code(code) + + # gh-154902 + def test_conditional_annotations_rebound_via_globals(self): + code = """ + globals()["__conditional_annotations__"] = 0 + x: int + """ + with self.assertRaises(TypeError): + run_code(code) + + # gh-154902 + def test_conditional_annotations_rebound_to_frozenset(self): + marker = frozenset() + code = """ + __conditional_annotations__ = marker + x: int + """ + with self.assertRaises(TypeError): + run_code(code, {"marker": marker}) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst new file mode 100644 index 000000000000000..49f34eaeddec4e4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst @@ -0,0 +1,2 @@ +Fix a crash when ``__conditional_annotations__`` is rebound to a non-set +object. diff --git a/Python/codegen.c b/Python/codegen.c index f2c2b21d106fbdd..bedf3b17c52ce44 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5807,7 +5807,9 @@ codegen_annassign(compiler *c, stmt_ty s) ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__conditional_annotations__), names); } ADDOP_LOAD_CONST_NEW(c, loc, conditional_annotation_index); - ADDOP_I(c, loc, SET_ADD, 1); + // gh-154902: change SET_ADD to new INTRINSIC_ADD_CONDITIONAL_ANNOTATION intrinsic + ADDOP_I(c, loc, CALL_INTRINSIC_2, + INTRINSIC_ADD_CONDITIONAL_ANNOTATION); ADDOP(c, loc, POP_TOP); } } diff --git a/Python/intrinsics.c b/Python/intrinsics.c index f081f33cc83b88c..11a103b0fc568b9 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -270,6 +270,23 @@ make_typevar_with_constraints(PyThreadState* Py_UNUSED(ignored), PyObject *name, return _Py_make_typevar(name, NULL, evaluate_constraints); } +static PyObject * +add_conditional_annotation(PyThreadState* tstate, PyObject *conditional_annotations, + PyObject *index) +{ + // gh-154902: user code can rebind __conditional_annotations__ to any object + if (!PySet_Check(conditional_annotations)) { + _PyErr_Format(tstate, PyExc_TypeError, + "__conditional_annotations__ must be a set, not %T", + conditional_annotations); + return NULL; + } + if (PySet_Add(conditional_annotations, index) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + const intrinsic_func2_info _PyIntrinsics_BinaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_2_INVALID, no_intrinsic2) @@ -278,6 +295,7 @@ _PyIntrinsics_BinaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEVAR_WITH_CONSTRAINTS, make_typevar_with_constraints) INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_FUNCTION_TYPE_PARAMS, _Py_set_function_type_params) INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_TYPEPARAM_DEFAULT, _Py_set_typeparam_default) + INTRINSIC_FUNC_ENTRY(INTRINSIC_ADD_CONDITIONAL_ANNOTATION, add_conditional_annotation) }; #undef INTRINSIC_FUNC_ENTRY From 46c3fbfc45fa89645e78d0898e9552ec08d7301f Mon Sep 17 00:00:00 2001 From: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:05:39 +0300 Subject: [PATCH 2/5] Update Python/intrinsics.c Co-authored-by: sobolevn --- Python/intrinsics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/intrinsics.c b/Python/intrinsics.c index 11a103b0fc568b9..52e2dc8a99d8640 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -275,7 +275,7 @@ add_conditional_annotation(PyThreadState* tstate, PyObject *conditional_annotati PyObject *index) { // gh-154902: user code can rebind __conditional_annotations__ to any object - if (!PySet_Check(conditional_annotations)) { + if (!PySet_CheckExact(conditional_annotations)) { _PyErr_Format(tstate, PyExc_TypeError, "__conditional_annotations__ must be a set, not %T", conditional_annotations); From 9207dba589d4104486ca119b78e9f30ed4f9ebd5 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 1 Aug 2026 16:19:42 +0300 Subject: [PATCH 3/5] review change --- Lib/test/test_type_annotations.py | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 1adfc0258783930..54db123a5dc2bf3 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,5 +1,6 @@ import annotationlib import inspect +import itertools import textwrap import types import unittest @@ -900,30 +901,14 @@ class Generic: # gh-154902 def test_conditional_annotations_rebound(self): # user code can rebind __conditional_annotations__ to any object - for value in ("0", "{}", "[]", "''", "object()"): - with self.subTest(value=value): + lefts = ("__conditional_annotations__", + 'globals()["__conditional_annotations__"]') + values = ("0", "{}", "[]", "''", "object()", "frozenset()") + for left, value in itertools.product(lefts, values): + with self.subTest(left=left, value=value): code = f""" - __conditional_annotations__ = {value} + {left} = {value} x: int """ with self.assertRaises(TypeError): run_code(code) - - # gh-154902 - def test_conditional_annotations_rebound_via_globals(self): - code = """ - globals()["__conditional_annotations__"] = 0 - x: int - """ - with self.assertRaises(TypeError): - run_code(code) - - # gh-154902 - def test_conditional_annotations_rebound_to_frozenset(self): - marker = frozenset() - code = """ - __conditional_annotations__ = marker - x: int - """ - with self.assertRaises(TypeError): - run_code(code, {"marker": marker}) From d91aa55afaa3d730fdd6f63105c961c0259f208a Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 1 Aug 2026 16:45:34 +0300 Subject: [PATCH 4/5] bump PYC_MAGIC_NUMBER --- Include/internal/pycore_magic_number.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index e0c5cfc02bbc359..b6945f2bc5f6e0d 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -302,7 +302,7 @@ Known values: Python 3.16a1 3702 (Replace DELETE_NAME with PUSH_NULL; STORE_NAME) Python 3.16a1 3703 (Replace DELETE_GLOBAL with PUSH_NULL; STORE_GLOBAL) Python 3.16a1 3704 (Replace DELETE_ATTR with PUSH_NULL; STORE_ATTR) - + Python 3.16a1 3705 (Add INTRINSIC_ADD_CONDITIONAL_ANNOTATION) Python 3.17 will start with 3750 @@ -312,7 +312,7 @@ Known values: */ -#define PYC_MAGIC_NUMBER 3704 +#define PYC_MAGIC_NUMBER 3705 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \ From 231fb8d5f1e50dfdf67dd62b2a1dd9cbbb31c162 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 1 Aug 2026 16:54:18 +0300 Subject: [PATCH 5/5] added INTRINSIC_ADD_CONDITIONAL_ANNOTATION row to documentation column --- Doc/library/dis.rst | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5cc0d68a24acee9..af654f7f82323af 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1899,25 +1899,29 @@ iterations of the loop. The operand determines which intrinsic function is called: - +----------------------------------------+-----------------------------------+ - | Operand | Description | - +========================================+===================================+ - | ``INTRINSIC_2_INVALID`` | Not valid | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the | - | | :exc:`ExceptionGroup` to raise | - | | from a ``try-except*``. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` | - | | with a bound. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a | - | | :class:`typing.TypeVar` with | - | | constraints. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` | - | | attribute of a function. | - +----------------------------------------+-----------------------------------+ + +------------------------------------------+-----------------------------------+ + | Operand | Description | + +==========================================+===================================+ + | ``INTRINSIC_2_INVALID`` | Not valid | + +------------------------------------------+-----------------------------------+ + | ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the | + | | :exc:`ExceptionGroup` to raise | + | | from a ``try-except*``. | + +------------------------------------------+-----------------------------------+ + | ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` | + | | with a bound. | + +------------------------------------------+-----------------------------------+ + | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a | + | | :class:`typing.TypeVar` with | + | | constraints. | + +------------------------------------------+-----------------------------------+ + | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` | + | | attribute of a function. | + +------------------------------------------+-----------------------------------+ + | ``INTRINSIC_ADD_CONDITIONAL_ANNOTATION`` | Adds an annotation index to the | + | | ``__conditional_annotations__`` | + | | set. | + +------------------------------------------+-----------------------------------+ .. versionadded:: 3.12