]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-154902: Add an intrinsic for conditional annotations (#155026)
authorTimofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
Wed, 5 Aug 2026 14:26:20 +0000 (17:26 +0300)
committerGitHub <noreply@github.com>
Wed, 5 Aug 2026 14:26:20 +0000 (07:26 -0700)
Co-authored-by: sobolevn <mail@sobolevn.me>
Doc/library/dis.rst
Include/internal/pycore_intrinsics.h
Include/internal/pycore_magic_number.h
Lib/test/test_dis.py
Lib/test/test_type_annotations.py
Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-15-07-10.gh-issue-154902.DIi6sf.rst [new file with mode: 0644]
Python/codegen.c
Python/intrinsics.c

index 5cc0d68a24acee91fc9d9162361cef934ae81d53..af654f7f82323af9ceddb433c59e2d2bc959d89e 100644 (file)
@@ -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
 
index 59a7b16073f886cf16a79f66604fdb999712f133..447cea91716eca9e11bb5bf81e2bb58dc5d197c9 100644 (file)
@@ -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);
index e0c5cfc02bbc359913450c1967b3001b819fa0bd..b6945f2bc5f6e0d039cee56dc55de0e80c8a78d6 100644 (file)
@@ -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 \
index c75992761d1334fa54da8723e47a9fa336540aaa..3e562a26ad586aa8301bbaf8e8116f704b092423 100644 (file)
@@ -395,12 +395,12 @@ dis_annot_stmt_str = """\
                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
index b751f825bb97d594d2cce5bae29a6aba8081c97d..54db123a5dc2bf3498c6b57def79ec0a3d0e2424 100644 (file)
@@ -1,5 +1,6 @@
 import annotationlib
 import inspect
+import itertools
 import textwrap
 import types
 import unittest
@@ -896,3 +897,18 @@ class RegressionTests(unittest.TestCase):
                 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 (file)
index 0000000..49f34ea
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a crash when ``__conditional_annotations__`` is rebound to a non-set
+object.
index f2c2b21d106fbdd17fd73b93875d526acb7d922f..bedf3b17c52ce44f35410b45f01e63ec47a642e4 100644 (file)
@@ -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);
                 }
             }
index f081f33cc83b88c8a0cb76801f94377656edf7e7..52e2dc8a99d86403f11f750092448baaa35c2f91 100644 (file)
@@ -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