https://github.com/python/cpython/commit/dd5a285387eece8cb9667fad5895c3a3468e5e66
commit: dd5a285387eece8cb9667fad5895c3a3468e5e66
branch: main
author: Timofei Ivankov <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-08-05T07:26:20-07:00
summary:

gh-154902: Add an intrinsic for conditional annotations (#155026)

Co-authored-by: sobolevn <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst
M Doc/library/dis.rst
M Include/internal/pycore_intrinsics.h
M Include/internal/pycore_magic_number.h
M Lib/test/test_dis.py
M Lib/test/test_type_annotations.py
M Python/codegen.c
M Python/intrinsics.c

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
 
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/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 \
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..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
@@ -896,3 +897,18 @@ 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
+        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"""
+                    {left} = {value}
+                    x: int
+                """
+                with self.assertRaises(TypeError):
+                    run_code(code)
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..52e2dc8a99d8640 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_CheckExact(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

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to