diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5cc0d68a24acee..af654f7f82323a 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 59a7b16073f886..447cea91716eca 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 e0c5cfc02bbc35..b6945f2bc5f6e0 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 c75992761d1334..3e562a26ad586a 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 b751f825bb97d5..54db123a5dc2bf 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 00000000000000..49f34eaeddec4e --- /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 f2c2b21d106fbd..bedf3b17c52ce4 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 f081f33cc83b88..52e2dc8a99d864 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