Skip to content

Commit b292da4

Browse files
Mark class __conditional_annotations__ as a CELL in the symtable
Stop cooking that cell in the compiler; modules keep it as an implicit global in the module dict so rebinding still works. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 259c321 commit b292da4

4 files changed

Lines changed: 78 additions & 62 deletions

File tree

‎Lib/test/test_dis.py‎

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -382,36 +382,34 @@ def wrap_func_w_kwargs():
382382
# leading newline is for a reason (tests lineno)
383383

384384
dis_annot_stmt_str = """\
385-
-- MAKE_CELL 0 (__conditional_annotations__)
386-
387-
0 RESUME 0
385+
0 RESUME 0
388386
389-
2 LOAD_CONST 1 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
390-
MAKE_FUNCTION
391-
STORE_NAME 4 (__annotate__)
392-
BUILD_SET 0
393-
STORE_NAME 0 (__conditional_annotations__)
394-
LOAD_SMALL_INT 1
395-
STORE_NAME 1 (x)
396-
LOAD_NAME 0 (__conditional_annotations__)
397-
LOAD_SMALL_INT 0
398-
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
399-
POP_TOP
387+
2 LOAD_CONST 1 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
388+
MAKE_FUNCTION
389+
STORE_NAME 4 (__annotate__)
390+
BUILD_SET 0
391+
STORE_NAME 0 (__conditional_annotations__)
392+
LOAD_SMALL_INT 1
393+
STORE_NAME 1 (x)
394+
LOAD_NAME 0 (__conditional_annotations__)
395+
LOAD_SMALL_INT 0
396+
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
397+
POP_TOP
400398
401-
3 LOAD_NAME 0 (__conditional_annotations__)
402-
LOAD_SMALL_INT 1
403-
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
404-
POP_TOP
399+
3 LOAD_NAME 0 (__conditional_annotations__)
400+
LOAD_SMALL_INT 1
401+
CALL_INTRINSIC_2 6 (INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
402+
POP_TOP
405403
406-
4 LOAD_SMALL_INT 1
407-
LOAD_NAME 2 (lst)
408-
LOAD_NAME 3 (fun)
409-
PUSH_NULL
410-
LOAD_SMALL_INT 0
411-
CALL 1
412-
STORE_SUBSCR
413-
LOAD_COMMON_CONSTANT 7 (None)
414-
RETURN_VALUE
404+
4 LOAD_SMALL_INT 1
405+
LOAD_NAME 2 (lst)
406+
LOAD_NAME 3 (fun)
407+
PUSH_NULL
408+
LOAD_SMALL_INT 0
409+
CALL 1
410+
STORE_SUBSCR
411+
LOAD_COMMON_CONSTANT 7 (None)
412+
RETURN_VALUE
415413
"""
416414

417415
fn_with_annotate_str = """

‎Lib/test/test_super.py‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,9 @@ def method(self):
229229
if isinstance(c, types.CodeType) and c.co_name == "outer")
230230
class_code = next(c for c in outer_code.co_consts
231231
if isinstance(c, types.CodeType) and c.co_name == "C")
232-
self.assertIn("__classcell__", class_code.co_cellvars)
233-
self.assertNotIn("__class__", class_code.co_cellvars)
234-
self.assertIn("__class__", class_code.co_freevars)
235-
localsplus = (class_code.co_varnames + class_code.co_cellvars
236-
+ class_code.co_freevars)
237-
self.assertEqual(len(set(localsplus)), len(localsplus))
232+
self.assertEqual(class_code.co_cellvars,
233+
('__classcell__', '__classdictcell__'))
234+
self.assertEqual(class_code.co_freevars, ('__class__',))
238235

239236
def test_class_cell_distinct_from_inlined_comp_cell(self):
240237
# Private __classcell__ coexists with an inlined-comp cell named
@@ -259,8 +256,8 @@ def test_class_cell_distinct_from_inlined_comp_cell(self):
259256
" lambdas = [lambda: __class__ for __class__ in (1, 2)]\n",
260257
"<test>", "exec").co_consts
261258
if isinstance(c, types.CodeType) and c.co_name == "C")
262-
self.assertIn("__classcell__", class_code.co_cellvars)
263-
self.assertIn("__class__", class_code.co_cellvars)
259+
self.assertEqual(class_code.co_cellvars,
260+
('__class__', '__classcell__', '__classdictcell__'))
264261

265262
def test___classcell___expected_behaviour(self):
266263
# See issue #23722

‎Python/compile.c‎

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -722,17 +722,6 @@ _PyCompile_EnterScope(compiler *c, identifier name, int scope_type,
722722
return ERROR;
723723
}
724724
}
725-
if (u->u_ste->ste_has_conditional_annotations) {
726-
/* Cook up an implicit __conditional_annotations__ cell */
727-
Py_ssize_t res;
728-
assert(u->u_scope_type == COMPILE_SCOPE_CLASS || u->u_scope_type == COMPILE_SCOPE_MODULE);
729-
res = _PyCompile_DictAddObj(u->u_metadata.u_cellvars, &_Py_ID(__conditional_annotations__));
730-
if (res < 0) {
731-
compiler_unit_free(u);
732-
return ERROR;
733-
}
734-
}
735-
736725
u->u_metadata.u_freevars = dictbytype(u->u_ste->ste_symbols, FREE, DEF_FREE_CLASS,
737726
PyDict_GET_SIZE(u->u_metadata.u_cellvars));
738727
if (!u->u_metadata.u_freevars) {
@@ -1001,6 +990,15 @@ compiler_resolve_inlined_free(PySTEntryObject **ste, PyObject *name)
1001990
assert(parent != NULL);
1002991
if (parent->ste_type == ClassBlock) {
1003992
if (_PyST_IsClassClosureName(name)) {
993+
/* __conditional_annotations__ is a real CELL on the class;
994+
* other class-closure names (__class__, ...) are cooked
995+
* under private cellvar names and are not in ste_symbols. */
996+
int parent_scope = _PyST_GetScope(parent, name);
997+
RETURN_IF_ERROR(parent_scope);
998+
if (parent_scope == CELL) {
999+
*ste = parent;
1000+
return CELL;
1001+
}
10041002
return GLOBAL_IMPLICIT;
10051003
}
10061004
break;
@@ -1024,24 +1022,12 @@ compiler_class_closure_cellvar(PyObject *name)
10241022
if (name == &_Py_ID(__classdict__)) {
10251023
return &_Py_ID(__classdictcell__);
10261024
}
1027-
if (name == &_Py_ID(__conditional_annotations__)) {
1028-
return &_Py_ID(__conditional_annotations__);
1029-
}
10301025
return NULL;
10311026
}
10321027

10331028
int
10341029
_PyCompile_GetRefType(compiler *c, PyObject *name)
10351030
{
1036-
/* Synthetic cells cooked into u_cellvars under their public spelling
1037-
* (today only __conditional_annotations__) are not always in ste_symbols. */
1038-
if (c->u->u_scope_type == COMPILE_SCOPE_CLASS ||
1039-
c->u->u_scope_type == COMPILE_SCOPE_MODULE) {
1040-
PyObject *cell_name = compiler_class_closure_cellvar(name);
1041-
if (cell_name != NULL && cell_name == name) {
1042-
return CELL;
1043-
}
1044-
}
10451031
PySTEntryObject *ste = c->u->u_ste;
10461032
int scope = compiler_resolve_inlined_free(&ste, name);
10471033
RETURN_IF_ERROR(scope);
@@ -1081,9 +1067,9 @@ int
10811067
_PyCompile_LookupArg(compiler *c, PyCodeObject *co, PyObject *name)
10821068
{
10831069
/* Class units store the synthetic class-closure cell under a private
1084-
* name (__classcell__, ...). Inside an inlined comprehension, a nested
1085-
* free of the public name usually captures that comprehension's CELL;
1086-
* elsewhere (methods, class-body lambdas) it is the private cell. */
1070+
* name (__classcell__, ...). Map a free of the public name to that
1071+
* private cell, except in an inlined comprehension that already has
1072+
* its own CELL under the public name — prefer that slot. */
10871073
if (c->u->u_scope_type == COMPILE_SCOPE_CLASS) {
10881074
PyObject *cell_name = compiler_class_closure_cellvar(name);
10891075
if (cell_name != NULL) {

‎Python/symtable.c‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,36 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
13391339
else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree)) {
13401340
goto error;
13411341
}
1342+
/* Class scopes skip analyze_cells(); ensure the deferred-annotation
1343+
* name is a CELL (it may appear only via drop_class_free()). Modules
1344+
* keep it as an implicit global in the module dict. */
1345+
if (ste->ste_has_conditional_annotations && ste->ste_type == ClassBlock) {
1346+
PyObject *name = &_Py_ID(__conditional_annotations__);
1347+
int contains = PyDict_Contains(ste->ste_symbols, name);
1348+
if (contains < 0) {
1349+
goto error;
1350+
}
1351+
if (!contains) {
1352+
PyObject *flags = PyLong_FromLong(DEF_LOCAL);
1353+
if (flags == NULL) {
1354+
goto error;
1355+
}
1356+
int rc = PyDict_SetItem(ste->ste_symbols, name, flags);
1357+
Py_DECREF(flags);
1358+
if (rc < 0) {
1359+
goto error;
1360+
}
1361+
}
1362+
PyObject *v_cell = PyLong_FromLong(CELL);
1363+
if (v_cell == NULL) {
1364+
goto error;
1365+
}
1366+
int rc = PyDict_SetItem(scopes, name, v_cell);
1367+
Py_DECREF(v_cell);
1368+
if (rc < 0) {
1369+
goto error;
1370+
}
1371+
}
13421372
/* Records the results of the analysis in the symbol table entry */
13431373
if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
13441374
(ste->ste_type == ClassBlock) || ste->ste_can_see_class_scope))
@@ -2879,7 +2909,12 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key)
28792909
&& !st->st_cur->ste_has_conditional_annotations)
28802910
{
28812911
st->st_cur->ste_has_conditional_annotations = 1;
2882-
if (!symtable_add_def(st, &_Py_ID(__conditional_annotations__), USE, LOCATION(annotation))) {
2912+
/* Class: local binding, promoted to CELL in analyze_block.
2913+
* Module: USE only (implicit global); the set lives in the
2914+
* module dict so user rebinding is visible to the intrinsic. */
2915+
int flag = (st->st_cur->ste_type == ClassBlock) ? DEF_LOCAL : USE;
2916+
if (!symtable_add_def(st, &_Py_ID(__conditional_annotations__),
2917+
flag, LOCATION(annotation))) {
28832918
return 0;
28842919
}
28852920
}

0 commit comments

Comments
 (0)