gh-104602: ensure all cellvars are known up front (#104603)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Carl Meyer 2023-05-18 18:07:35 -06:00 committed by GitHub
parent 3fadd7d585
commit 86e6f16ccb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 22 deletions

View File

@ -120,14 +120,15 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name);
#define DEF_ANNOT 2<<7 /* this name is annotated */ #define DEF_ANNOT 2<<7 /* this name is annotated */
#define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */ #define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */
#define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */ #define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */
#define DEF_COMP_CELL 2<<10 /* this name is a cell in an inlined comprehension */
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT) #define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
/* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol /* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol
table. GLOBAL is returned from PyST_GetScope() for either of them. table. GLOBAL is returned from PyST_GetScope() for either of them.
It is stored in ste_symbols at bits 12-15. It is stored in ste_symbols at bits 13-16.
*/ */
#define SCOPE_OFFSET 11 #define SCOPE_OFFSET 12
#define SCOPE_MASK (DEF_GLOBAL | DEF_LOCAL | DEF_PARAM | DEF_NONLOCAL) #define SCOPE_MASK (DEF_GLOBAL | DEF_LOCAL | DEF_PARAM | DEF_NONLOCAL)
#define LOCAL 1 #define LOCAL 1

View File

@ -381,6 +381,32 @@ def f():
with self.assertRaises(UnboundLocalError): with self.assertRaises(UnboundLocalError):
f() f()
def test_global_outside_cellvar_inside_plus_freevar(self):
code = """
a = 1
def f():
func, = [(lambda: b) for b in [a]]
return b, func()
x = f()
"""
self._check_in_scopes(
code, {"x": (2, 1)}, ns={"b": 2}, scopes=["function", "module"])
# inside a class, the `a = 1` assignment is not visible
self._check_in_scopes(code, raises=NameError, scopes=["class"])
def test_cell_in_nested_comprehension(self):
code = """
a = 1
def f():
(func, inner_b), = [[lambda: b for b in c] + [b] for c in [[a]]]
return b, inner_b, func()
x = f()
"""
self._check_in_scopes(
code, {"x": (2, 2, 1)}, ns={"b": 2}, scopes=["function", "module"])
# inside a class, the `a = 1` assignment is not visible
self._check_in_scopes(code, raises=NameError, scopes=["class"])
def test_name_error_in_class_scope(self): def test_name_error_in_class_scope(self):
code = """ code = """
y = 1 y = 1

View File

@ -1252,7 +1252,7 @@ compiler_enter_scope(struct compiler *c, identifier name,
} }
u->u_metadata.u_name = Py_NewRef(name); u->u_metadata.u_name = Py_NewRef(name);
u->u_metadata.u_varnames = list2dict(u->u_ste->ste_varnames); u->u_metadata.u_varnames = list2dict(u->u_ste->ste_varnames);
u->u_metadata.u_cellvars = dictbytype(u->u_ste->ste_symbols, CELL, 0, 0); u->u_metadata.u_cellvars = dictbytype(u->u_ste->ste_symbols, CELL, DEF_COMP_CELL, 0);
if (!u->u_metadata.u_varnames || !u->u_metadata.u_cellvars) { if (!u->u_metadata.u_varnames || !u->u_metadata.u_cellvars) {
compiler_unit_free(u); compiler_unit_free(u);
return ERROR; return ERROR;

View File

@ -632,7 +632,7 @@ is_free_in_any_child(PySTEntryObject *entry, PyObject *key)
static int static int
inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
PyObject *scopes, PyObject *comp_free, PyObject *scopes, PyObject *comp_free,
PyObject *promote_to_cell) PyObject *inlined_cells)
{ {
PyObject *k, *v; PyObject *k, *v;
Py_ssize_t pos = 0; Py_ssize_t pos = 0;
@ -645,6 +645,11 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
} }
int scope = (comp_flags >> SCOPE_OFFSET) & SCOPE_MASK; int scope = (comp_flags >> SCOPE_OFFSET) & SCOPE_MASK;
int only_flags = comp_flags & ((1 << SCOPE_OFFSET) - 1); int only_flags = comp_flags & ((1 << SCOPE_OFFSET) - 1);
if (scope == CELL || only_flags & DEF_COMP_CELL) {
if (PySet_Add(inlined_cells, k) < 0) {
return 0;
}
}
PyObject *existing = PyDict_GetItemWithError(ste->ste_symbols, k); PyObject *existing = PyDict_GetItemWithError(ste->ste_symbols, k);
if (existing == NULL && PyErr_Occurred()) { if (existing == NULL && PyErr_Occurred()) {
return 0; return 0;
@ -665,14 +670,6 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
} }
else { else {
if (PyLong_AsLong(existing) & DEF_BOUND) { if (PyLong_AsLong(existing) & DEF_BOUND) {
// cell vars in comprehension that are locals in outer scope
// must be promoted to cell so u_cellvars isn't wrong
if (scope == CELL && _PyST_IsFunctionLike(ste)) {
if (PySet_Add(promote_to_cell, k) < 0) {
return 0;
}
}
// free vars in comprehension that are locals in outer scope can // free vars in comprehension that are locals in outer scope can
// now simply be locals, unless they are free in comp children, // now simply be locals, unless they are free in comp children,
// or if the outer scope is a class block // or if the outer scope is a class block
@ -698,7 +695,7 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
*/ */
static int static int
analyze_cells(PyObject *scopes, PyObject *free, PyObject *promote_to_cell) analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells)
{ {
PyObject *name, *v, *v_cell; PyObject *name, *v, *v_cell;
int success = 0; int success = 0;
@ -713,7 +710,7 @@ analyze_cells(PyObject *scopes, PyObject *free, PyObject *promote_to_cell)
scope = PyLong_AS_LONG(v); scope = PyLong_AS_LONG(v);
if (scope != LOCAL) if (scope != LOCAL)
continue; continue;
if (!PySet_Contains(free, name) && !PySet_Contains(promote_to_cell, name)) if (!PySet_Contains(free, name) && !PySet_Contains(inlined_cells, name))
continue; continue;
/* Replace LOCAL with CELL for this name, and remove /* Replace LOCAL with CELL for this name, and remove
from free. It is safe to replace the value of name from free. It is safe to replace the value of name
@ -753,7 +750,8 @@ drop_class_free(PySTEntryObject *ste, PyObject *free)
*/ */
static int static int
update_symbols(PyObject *symbols, PyObject *scopes, update_symbols(PyObject *symbols, PyObject *scopes,
PyObject *bound, PyObject *free, int classflag) PyObject *bound, PyObject *free,
PyObject *inlined_cells, int classflag)
{ {
PyObject *name = NULL, *itr = NULL; PyObject *name = NULL, *itr = NULL;
PyObject *v = NULL, *v_scope = NULL, *v_new = NULL, *v_free = NULL; PyObject *v = NULL, *v_scope = NULL, *v_new = NULL, *v_free = NULL;
@ -764,6 +762,9 @@ update_symbols(PyObject *symbols, PyObject *scopes,
long scope, flags; long scope, flags;
assert(PyLong_Check(v)); assert(PyLong_Check(v));
flags = PyLong_AS_LONG(v); flags = PyLong_AS_LONG(v);
if (PySet_Contains(inlined_cells, name)) {
flags |= DEF_COMP_CELL;
}
v_scope = PyDict_GetItemWithError(scopes, name); v_scope = PyDict_GetItemWithError(scopes, name);
assert(v_scope && PyLong_Check(v_scope)); assert(v_scope && PyLong_Check(v_scope));
scope = PyLong_AS_LONG(v_scope); scope = PyLong_AS_LONG(v_scope);
@ -870,7 +871,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
PySTEntryObject *class_entry) PySTEntryObject *class_entry)
{ {
PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL; PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL;
PyObject *newglobal = NULL, *newfree = NULL, *promote_to_cell = NULL; PyObject *newglobal = NULL, *newfree = NULL, *inlined_cells = NULL;
PyObject *temp; PyObject *temp;
int success = 0; int success = 0;
Py_ssize_t i, pos = 0; Py_ssize_t i, pos = 0;
@ -902,8 +903,8 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
newbound = PySet_New(NULL); newbound = PySet_New(NULL);
if (!newbound) if (!newbound)
goto error; goto error;
promote_to_cell = PySet_New(NULL); inlined_cells = PySet_New(NULL);
if (!promote_to_cell) if (!inlined_cells)
goto error; goto error;
/* Class namespace has no effect on names visible in /* Class namespace has no effect on names visible in
@ -997,7 +998,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
goto error; goto error;
} }
if (inline_comp) { if (inline_comp) {
if (!inline_comprehension(ste, entry, scopes, child_free, promote_to_cell)) { if (!inline_comprehension(ste, entry, scopes, child_free, inlined_cells)) {
Py_DECREF(child_free); Py_DECREF(child_free);
goto error; goto error;
} }
@ -1028,12 +1029,12 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
} }
/* Check if any local variables must be converted to cell variables */ /* Check if any local variables must be converted to cell variables */
if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree, promote_to_cell)) if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree, inlined_cells))
goto error; goto error;
else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree)) else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
goto error; goto error;
/* Records the results of the analysis in the symbol table entry */ /* Records the results of the analysis in the symbol table entry */
if (!update_symbols(ste->ste_symbols, scopes, bound, newfree, if (!update_symbols(ste->ste_symbols, scopes, bound, newfree, inlined_cells,
ste->ste_type == ClassBlock)) ste->ste_type == ClassBlock))
goto error; goto error;
@ -1048,7 +1049,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
Py_XDECREF(newbound); Py_XDECREF(newbound);
Py_XDECREF(newglobal); Py_XDECREF(newglobal);
Py_XDECREF(newfree); Py_XDECREF(newfree);
Py_XDECREF(promote_to_cell); Py_XDECREF(inlined_cells);
if (!success) if (!success)
assert(PyErr_Occurred()); assert(PyErr_Occurred());
return success; return success;