Skip to content

Commit fc16e15

Browse files
committed
gh-154902: Add an intrinsic for conditional annotations
1 parent afd10f0 commit fc16e15

6 files changed

Lines changed: 58 additions & 4 deletions

File tree

Include/internal/pycore_intrinsics.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
#define INTRINSIC_TYPEVAR_WITH_CONSTRAINTS 3
3131
#define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4
3232
#define INTRINSIC_SET_TYPEPARAM_DEFAULT 5
33+
#define INTRINSIC_ADD_CONDITIONAL_ANNOTATION 6
3334

34-
#define MAX_INTRINSIC_2 5
35+
#define MAX_INTRINSIC_2 6
3536

3637
typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value);
3738
typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, PyObject *value2);

Lib/test/test_dis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,12 @@ def wrap_func_w_kwargs():
395395
STORE_NAME 1 (x)
396396
LOAD_NAME 0 (__conditional_annotations__)
397397
LOAD_SMALL_INT 0
398-
SET_ADD 1
398+
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
399399
POP_TOP
400400
401401
3 LOAD_NAME 0 (__conditional_annotations__)
402402
LOAD_SMALL_INT 1
403-
SET_ADD 1
403+
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
404404
POP_TOP
405405
406406
4 LOAD_SMALL_INT 1

Lib/test/test_type_annotations.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,3 +896,34 @@ class Generic:
896896
mod = build_module(code)
897897
annos = mod.__annotations__
898898
self.assertEqual(annos, {"annotated_name": 0})
899+
900+
# gh-154902
901+
def test_conditional_annotations_rebound(self):
902+
# user code can rebind __conditional_annotations__ to any object
903+
for value in ("0", "{}", "[]", "''", "object()"):
904+
with self.subTest(value=value):
905+
code = f"""
906+
__conditional_annotations__ = {value}
907+
x: int
908+
"""
909+
with self.assertRaises(TypeError):
910+
run_code(code)
911+
912+
# gh-154902
913+
def test_conditional_annotations_rebound_via_globals(self):
914+
code = """
915+
globals()["__conditional_annotations__"] = 0
916+
x: int
917+
"""
918+
with self.assertRaises(TypeError):
919+
run_code(code)
920+
921+
# gh-154902
922+
def test_conditional_annotations_rebound_to_frozenset(self):
923+
marker = frozenset()
924+
code = """
925+
__conditional_annotations__ = marker
926+
x: int
927+
"""
928+
with self.assertRaises(TypeError):
929+
run_code(code, {"marker": marker})
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash when ``__conditional_annotations__`` is rebound to a non-set
2+
object.

Python/codegen.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5807,7 +5807,9 @@ codegen_annassign(compiler *c, stmt_ty s)
58075807
ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__conditional_annotations__), names);
58085808
}
58095809
ADDOP_LOAD_CONST_NEW(c, loc, conditional_annotation_index);
5810-
ADDOP_I(c, loc, SET_ADD, 1);
5810+
// gh-154902: change SET_ADD to new INTRINSIC_ADD_CONDITIONAL_ANNOTATION intrinsic
5811+
ADDOP_I(c, loc, CALL_INTRINSIC_2,
5812+
INTRINSIC_ADD_CONDITIONAL_ANNOTATION);
58115813
ADDOP(c, loc, POP_TOP);
58125814
}
58135815
}

Python/intrinsics.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ make_typevar_with_constraints(PyThreadState* Py_UNUSED(ignored), PyObject *name,
270270
return _Py_make_typevar(name, NULL, evaluate_constraints);
271271
}
272272

273+
static PyObject *
274+
add_conditional_annotation(PyThreadState* tstate, PyObject *conditional_annotations,
275+
PyObject *index)
276+
{
277+
// gh-154902: user code can rebind __conditional_annotations__ to any object
278+
if (!PySet_Check(conditional_annotations)) {
279+
_PyErr_Format(tstate, PyExc_TypeError,
280+
"__conditional_annotations__ must be a set, not %T",
281+
conditional_annotations);
282+
return NULL;
283+
}
284+
if (PySet_Add(conditional_annotations, index) < 0) {
285+
return NULL;
286+
}
287+
Py_RETURN_NONE;
288+
}
289+
273290
const intrinsic_func2_info
274291
_PyIntrinsics_BinaryFunctions[] = {
275292
INTRINSIC_FUNC_ENTRY(INTRINSIC_2_INVALID, no_intrinsic2)
@@ -278,6 +295,7 @@ _PyIntrinsics_BinaryFunctions[] = {
278295
INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEVAR_WITH_CONSTRAINTS, make_typevar_with_constraints)
279296
INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_FUNCTION_TYPE_PARAMS, _Py_set_function_type_params)
280297
INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_TYPEPARAM_DEFAULT, _Py_set_typeparam_default)
298+
INTRINSIC_FUNC_ENTRY(INTRINSIC_ADD_CONDITIONAL_ANNOTATION, add_conditional_annotation)
281299
};
282300

283301
#undef INTRINSIC_FUNC_ENTRY

0 commit comments

Comments
 (0)