https://github.com/python/cpython/commit/a9d42dceee5b8d2afba3223d633f5d9b6dad01f3
commit: a9d42dceee5b8d2afba3223d633f5d9b6dad01f3
branch: main
author: Irit Katriel <[email protected]>
committer: iritkatriel <[email protected]>
date: 2026-09-23T20:31:57+01:00
summary:
gh-124697: Represent inlined comprehensions as subscopes in the symbol table
(#156819)
files:
A InternalDocs/inlined_comprehensions.md
A Misc/NEWS.d/next/Library/2026-09-02-12-11-00.gh-issue-124697.sUbScp.rst
M Doc/library/symtable.rst
M Doc/whatsnew/3.16.rst
M Include/internal/pycore_compile.h
M Include/internal/pycore_symtable.h
M InternalDocs/README.md
M InternalDocs/compiler.md
M Lib/symtable.py
M Lib/test/test_compiler_assemble.py
M Lib/test/test_listcomps.py
M Lib/test/test_symtable.py
M Lib/test/test_syntax.py
M Modules/_testinternalcapi.c
M Modules/symtablemodule.c
M Objects/frameobject.c
M Python/assemble.c
M Python/codegen.c
M Python/compile.c
M Python/symtable.c
diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst
index 859687340882de9..a5cc0f854ab4c3b 100644
--- a/Doc/library/symtable.rst
+++ b/Doc/library/symtable.rst
@@ -57,6 +57,16 @@ Examining Symbol Tables
Used for the symbol table of a class.
+ .. attribute:: INLINED_COMPREHENSION
+ :value: "inlined comprehension"
+
+ Used for the symbol table of a list, set or dict comprehension that
+ is inlined into the enclosing code unit (see :pep:`709`). A symbol
+ table of this type represents a sub-scope of the enclosing code unit's
+ scope, and it does not correspond to a separate compilation unit.
+
+ .. versionadded:: next
+
The following members refer to different flavors of
:ref:`annotation scopes <annotation-scopes>`.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 8362b1ef7e312b0..6c38beb04e8d27e 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -573,6 +573,16 @@ symtable
like the builtin :func:`compile`.
(Contributed by Serhiy Storchaka in :gh:`153844`.)
+* Inlined list, set and dict comprehensions (:pep:`709`) are now represented
+ as their own symbol table entries, of type
+ :attr:`~symtable.SymbolTableType.INLINED_COMPREHENSION`. Each such entry is
+ a lexical child of the enclosing scope and records the comprehension's own
+ locals, cells, and free names. It does not correspond to a separate
+ compilation unit. For names loaded only inside an inlined list, set, or
+ dict comprehension, :meth:`symtable.Symbol.is_referenced` can now return
+ ``True`` on the enclosing function.
+ (Contributed by Irit Katriel in :gh:`124697`.)
+
tkinter
-------
diff --git a/Include/internal/pycore_compile.h
b/Include/internal/pycore_compile.h
index 4597ae2763ad77a..31738f2074dbbf8 100644
--- a/Include/internal/pycore_compile.h
+++ b/Include/internal/pycore_compile.h
@@ -69,9 +69,8 @@ typedef struct {
PyObject *u_varnames; /* local variables */
PyObject *u_cellvars; /* cell variables */
PyObject *u_freevars; /* free variables */
- PyObject *u_fasthidden; /* dict; keys are names that are fast-locals only
- temporarily within an inlined comprehension.
When
- value is True, treat as fast-local. */
+ PyObject *u_fasthidden; /* set of names that are fast-locals only
+ temporarily within an inlined comprehension. */
Py_ssize_t u_argcount; /* number of arguments for block */
Py_ssize_t u_posonlyargcount; /* number of positional only
arguments for block */
@@ -152,11 +151,10 @@ PyObject *_PyCompile_MaybeMangle(struct _PyCompiler *c,
PyObject *name);
int _PyCompile_MaybeAddStaticAttributeToClass(struct _PyCompiler *c, expr_ty
e);
int _PyCompile_GetRefType(struct _PyCompiler *c, PyObject *name);
int _PyCompile_LookupCellvar(struct _PyCompiler *c, PyObject *name);
-int _PyCompile_ResolveNameop(struct _PyCompiler *c, PyObject *mangled, int
scope,
+int _PyCompile_ResolveNameop(struct _PyCompiler *c, PyObject *mangled,
_PyCompile_optype *optype, Py_ssize_t *arg);
int _PyCompile_IsInteractiveTopLevel(struct _PyCompiler *c);
-int _PyCompile_IsInInlinedComp(struct _PyCompiler *c);
int _PyCompile_ScopeType(struct _PyCompiler *c);
int _PyCompile_OptimizationLevel(struct _PyCompiler *c);
int _PyCompile_LookupArg(struct _PyCompiler *c, PyCodeObject *co, PyObject
*name);
@@ -180,16 +178,15 @@ enum {
typedef struct {
PyObject *pushed_locals;
- PyObject *temp_symbols;
- PyObject *fast_hidden;
_PyJumpTargetLabel cleanup;
+ PySTEntryObject *saved_ste;
} _PyCompile_InlinedComprehensionState;
-int _PyCompile_TweakInlinedComprehensionScopes(struct _PyCompiler *c,
_Py_SourceLocation loc,
- PySTEntryObject *entry,
-
_PyCompile_InlinedComprehensionState *state);
-int _PyCompile_RevertInlinedComprehensionScopes(struct _PyCompiler *c,
_Py_SourceLocation loc,
-
_PyCompile_InlinedComprehensionState *state);
+int _PyCompile_EnterInlinedComprehensionScope(struct _PyCompiler *c,
+ PySTEntryObject *entry,
+
_PyCompile_InlinedComprehensionState *state);
+int _PyCompile_ExitInlinedComprehensionScope(struct _PyCompiler *c,
+
_PyCompile_InlinedComprehensionState *state);
int _PyCompile_AddDeferredAnnotation(struct _PyCompiler *c, stmt_ty s,
PyObject **conditional_annotation_index);
void _PyCompile_EnterConditionalBlock(struct _PyCompiler *c);
diff --git a/Include/internal/pycore_symtable.h
b/Include/internal/pycore_symtable.h
index c650a94a1eab2e1..01afe5bb935b996 100644
--- a/Include/internal/pycore_symtable.h
+++ b/Include/internal/pycore_symtable.h
@@ -33,6 +33,10 @@ typedef enum _block_type {
// i.e., a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two
// do not support a bound or a constraint tuple).
TypeVariableBlock,
+ // List/set/dict comprehension inlined into the enclosing compilation unit
+ // (PEP 709). Lexical child of that unit, not a separate code object.
+ // See InternalDocs/inlined_comprehensions.md.
+ InlinedComprehensionBlock,
} _Py_block_ty;
typedef enum _comprehension_type {
@@ -119,7 +123,6 @@ typedef struct _symtable_entry {
should be created */
unsigned ste_needs_classdict : 1; /* for class scopes, true if a closure
over the class dict should be created
*/
- unsigned ste_comp_inlined : 1; /* true if this comprehension is inlined */
unsigned ste_comp_iter_target : 1; /* true if visiting comprehension
target */
unsigned ste_can_see_class_scope : 1; /* true if this block can see names
bound in an
enclosing class scope */
@@ -132,6 +135,7 @@ typedef struct _symtable_entry {
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range
expression */
_Py_SourceLocation ste_loc; /* source location of block */
struct _symtable_entry *ste_annotation_block; /* symbol table entry for
this entry's annotations */
+ struct _symtable_entry *ste_parent; /* st entry for the enclosing block if
this entry is a sub-scope, NULL otherwise */
struct symtable *ste_table;
} PySTEntryObject;
@@ -142,6 +146,7 @@ extern PyTypeObject PySTEntry_Type;
extern long _PyST_GetSymbol(PySTEntryObject *, PyObject *);
extern int _PyST_GetScope(PySTEntryObject *, PyObject *);
extern int _PyST_IsFunctionLike(PySTEntryObject *);
+extern int _PyST_IsClassClosureName(PyObject *);
extern struct symtable* _PySymtable_Build(
struct _mod *mod,
@@ -172,7 +177,6 @@ _Py_IsPrivateName(PyObject *);
#define DEF_ANNOT (2<<7) /* this name is annotated */
#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_COMP_CELL (2<<10) /* this name is a cell in an inlined
comprehension */
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
diff --git a/InternalDocs/README.md b/InternalDocs/README.md
index 3e8ab4423157534..5de575b0f44952d 100644
--- a/InternalDocs/README.md
+++ b/InternalDocs/README.md
@@ -23,6 +23,8 @@ Compiling Python Source Code
- [Compiler Design](compiler.md)
+- [Inlined comprehensions](inlined_comprehensions.md)
+
- [Changing Python's Grammar](changing_grammar.md)
Runtime Objects
diff --git a/InternalDocs/compiler.md b/InternalDocs/compiler.md
index 9ed4d0eb65a0bdd..de9e76ec9fcdb13 100644
--- a/InternalDocs/compiler.md
+++ b/InternalDocs/compiler.md
@@ -354,6 +354,10 @@ AST node type). Next, the AST tree is walked with the
various code blocks that
delineate the reach of a local variable as blocks are entered and exited using
`symtable_enter_block()` and `symtable_exit_block()`, respectively.
+See [Inlined comprehensions](inlined_comprehensions.md) for how list, set,
+and dict comprehensions are represented as lexical subscopes of the
+enclosing unit.
+
Once the symbol table is created, the `AST` is transformed by
`compiler_codegen()`
in [Python/compile.c](../Python/compile.c) into a sequence of pseudo
instructions.
These are similar to bytecode, but in some cases they are more abstract, and
are
diff --git a/InternalDocs/inlined_comprehensions.md
b/InternalDocs/inlined_comprehensions.md
new file mode 100644
index 000000000000000..e1ccd485c165b17
--- /dev/null
+++ b/InternalDocs/inlined_comprehensions.md
@@ -0,0 +1,132 @@
+Inlined comprehensions
+======================
+
+Since [PEP 709](https://peps.python.org/pep-0709/), list, set, and dict
+comprehensions are compiled into the enclosing compilation unit instead of
+creating a nested function and calling it. Generator expressions are not
+inlined; they still become their own code object.
+
+The resulting bytecode lives in the enclosing unit, but the comprehension
+still has its own locals: iteration variables must not leak into, or
+overwrite, names in the enclosing scope. The symbol table models that as a
+nested lexical scope; codegen then emits the comprehension inlined into its
+containing compilation unit.
+
+Which comprehensions are inlined
+--------------------------------
+
+`symtable_handle_comprehension()` in
+[`Python/symtable.c`](../Python/symtable.c) inlines a comprehension when it
+is not a generator expression and the current block cannot see class scope
+(`!ste_can_see_class_scope`). Annotation scopes that can see a class keep
+the historical nested-function compilation so class-local names are not
+treated as comprehension locals.
+
+The outermost iterator expression is always evaluated in the enclosing
+scope. The rest of the comprehension (targets, `if` clauses, inner
+generators, and the element/value expressions) is visited in the
+comprehension's own block.
+
+Symbol table
+------------
+
+An inlined comprehension gets an `InlinedComprehensionBlock` entry
+([`pycore_symtable.h`](../Include/internal/pycore_symtable.h)). That entry
+is a child of the enclosing block, with `ste_parent` pointing at the
+enclosing `PySTEntryObject`. It is a lexical subscope, not a compilation
+unit: there is no separate code object, `co_consts` entry, or compiler
+scope for it.
+
+Uses and bindings inside the comprehension are recorded on that child
+table. Because those loads are in the enclosing compilation unit, a
+`FREE` use is also marked `USE` on the parent table. For
+`def inner(): return [x for y in ()]`, both `inner.lookup("x")` and the
+inlined child's lookup are `FREE|USE`.
+
+### Analysis
+
+`analyze_block()` records this block's declarations, analyzes children,
+then classifies uses. `finalize_inlined_comprehension()` copies
+`USE` from an inlined child onto the parent before that second pass, and
+drops inlined-only frees so `analyze_cells()` does not promote those
+names to cells.
+
+A name that is `FREE` in the comprehension and bound in the parent is
+dropped from the parent's free set unless:
+
+* a real nested unit (function, lambda, or genexp) still needs it as a
+ cell, or
+* a sibling nested scope already marked it free.
+
+That keeps iteration variables as fast locals when they are only used by
+nested inlined comprehensions. A nested lambda that captures the name
+still forces a cell on the binding comprehension.
+
+Class-closure names (`__class__` and friends) loaded from an inlined
+comprehension do not require a class cell unless a nested function,
+lambda, or genexp captures them. Compile treats the inlined loads as
+implicit globals. `is_free_in_any_child()` walks through inlined children
+and only counts `FREE` on non-inlined descendants.
+
+Compiler
+--------
+
+Codegen stays in the enclosing compiler unit. Around the inlined region,
+`_PyCompile_EnterInlinedComprehensionScope()` /
+`_PyCompile_ExitInlinedComprehensionScope()` in
+[`Python/compile.c`](../Python/compile.c) swap `c->u->u_ste` so name
+lookup uses the comprehension's symbol table. The saved `u_ste` is
+restored on both success and error.
+
+`_PyCompile_ResolveNameop()` calls `compiler_resolve_inlined_free()`,
+which walks `ste_parent` while the current table is inlined and the name
+is `FREE` or missing (scope `0`). Missing names include loads synthesized
+by codegen, such as the implicit receiver for zero-argument `super()`.
+The walk stops at a class: nested scopes do not see class locals.
+Class-closure names that would otherwise be free through a class become
+`GLOBAL_IMPLICIT`.
+
+### Isolating iteration variables
+
+`codegen_push_inlined_comprehension_locals()` in
+[`Python/codegen.c`](../Python/codegen.c) isolates names bound in the
+comprehension:
+
+* `LOAD_FAST_AND_CLEAR` saves the enclosing value (possibly `NULL`) and
+ clears the slot.
+* `MAKE_CELL` runs if the name is a cell for this comprehension.
+* In module and class units the name is added to `u_fasthidden` so
+ assemble can set `CO_FAST_HIDDEN`.
+
+A `SETUP_FINALLY` / `COMPILE_FBLOCK_INLINED_COMPREHENSION` handler
+restores those slots if the comprehension raises, so an enclosing `except`
+or `finally` sees the original values.
+
+Runtime
+-------
+
+An inlined comprehension cell can share a localsplus name with an
+enclosing free variable (for example `[lambda: x for x in x]` inside a
+nested function). `FrameLocalsProxy` keys, values, items, and `len`
+keep the first slot of each name so they agree with `getitem`.
+
+Source
+------
+
+* [`Python/symtable.c`](../Python/symtable.c):
+ `symtable_handle_comprehension()`, `analyze_block()`,
+ `finalize_inlined_comprehension()`, `is_free_in_any_child()`
+* [`Python/compile.c`](../Python/compile.c):
+ `compiler_resolve_inlined_free()`,
+ `_PyCompile_EnterInlinedComprehensionScope()`,
+ `compiler_cellvars()`
+* [`Python/codegen.c`](../Python/codegen.c):
+ `codegen_comprehension()`,
+ `push_inlined_comprehension_state()`,
+ `codegen_push_inlined_comprehension_locals()`
+*
[`Include/internal/pycore_symtable.h`](../Include/internal/pycore_symtable.h):
+ `InlinedComprehensionBlock`
+* [`Include/internal/pycore_compile.h`](../Include/internal/pycore_compile.h):
+ `_PyCompile_InlinedComprehensionState`
+* [`Objects/frameobject.c`](../Objects/frameobject.c):
+ `FrameLocalsProxy` duplicate-name handling
diff --git a/Lib/symtable.py b/Lib/symtable.py
index 18bb355d86b09e0..3e56e8ab99c1c37 100644
--- a/Lib/symtable.py
+++ b/Lib/symtable.py
@@ -7,7 +7,7 @@
DEF_NONLOCAL, DEF_LOCAL,
DEF_PARAM, DEF_TYPE_PARAM, DEF_FREE_CLASS,
DEF_IMPORT, DEF_BOUND, DEF_ANNOT,
- DEF_COMP_ITER, DEF_COMP_CELL,
+ DEF_COMP_ITER,
SCOPE_OFF, SCOPE_MASK,
FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL
)
@@ -56,6 +56,7 @@ class SymbolTableType(StrEnum):
TYPE_ALIAS = "type alias"
TYPE_PARAMETERS = "type parameters"
TYPE_VARIABLE = "type variable"
+ INLINED_COMPREHENSION = "inlined comprehension"
class SymbolTable:
@@ -98,6 +99,8 @@ def get_type(self):
return SymbolTableType.TYPE_PARAMETERS
if self._table.type == _symtable.TYPE_TYPE_VARIABLE:
return SymbolTableType.TYPE_VARIABLE
+ if self._table.type == _symtable.TYPE_INLINED_COMPREHENSION:
+ return SymbolTableType.INLINED_COMPREHENSION
assert False, f"unexpected type: {self._table.type}"
def get_id(self):
@@ -151,8 +154,10 @@ def lookup(self, name):
flags = self._table.symbols[name]
namespaces = self.__check_children(name)
module_scope = (self._table.name == "top")
+ inlined = (self._table.type ==
_symtable.TYPE_INLINED_COMPREHENSION)
sym = self._symbols[name] = Symbol(name, flags, namespaces,
- module_scope=module_scope)
+ module_scope=module_scope,
+ inlined_comprehension=inlined)
return sym
def get_symbols(self):
@@ -246,12 +251,14 @@ class Class(SymbolTable):
class Symbol:
- def __init__(self, name, flags, namespaces=None, *, module_scope=False):
+ def __init__(self, name, flags, namespaces=None, *, module_scope=False,
+ inlined_comprehension=False):
self.__name = name
self.__flags = flags
self.__scope = _get_scope(flags)
self.__namespaces = namespaces or ()
self.__module_scope = module_scope
+ self.__inlined_comprehension = inlined_comprehension
def __repr__(self):
flags_str = '|'.join(self._flags_str())
@@ -345,7 +352,7 @@ def is_comp_iter(self):
def is_comp_cell(self):
"""Return *True* if the symbol is a cell in an inlined comprehension.
"""
- return bool(self.__flags & DEF_COMP_CELL)
+ return self.is_cell() and self.__inlined_comprehension
def is_namespace(self):
"""Returns *True* if name binding introduces new namespace.
diff --git a/Lib/test/test_compiler_assemble.py
b/Lib/test/test_compiler_assemble.py
index 99a11e99d564852..6e04df99b453ecf 100644
--- a/Lib/test/test_compiler_assemble.py
+++ b/Lib/test/test_compiler_assemble.py
@@ -17,8 +17,9 @@ def complete_metadata(self, metadata, filename="myfile.py"):
metadata.setdefault(key, key)
for key in ['consts']:
metadata.setdefault(key, [])
- for key in ['names', 'varnames', 'cellvars', 'freevars', 'fasthidden']:
+ for key in ['names', 'varnames', 'cellvars', 'freevars']:
metadata.setdefault(key, {})
+ metadata.setdefault('fasthidden', None)
for key in ['argcount', 'posonlyargcount', 'kwonlyargcount']:
metadata.setdefault(key, 0)
metadata.setdefault('firstlineno', 1)
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index fca9acbc6b1ef6c..f02f3223a513d2d 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -4,6 +4,7 @@
import types
import unittest
+from test import support
from test.support import BrokenIter
@@ -165,6 +166,30 @@ def test_references_super(self):
"""
self._check_in_scopes(code, outputs={"res": [super]})
+ def test_zero_arg_super_in_inlined_comprehension(self):
+ class A:
+ def f(self):
+ return 42
+
+ class B(A):
+ def f(self):
+ return [super().f() for _ in (0,)]
+
+ def nested(self):
+ return [[super().f() for _ in (0,)] for _ in (0,)]
+
+ def setcomp(self):
+ return [{super().f() for _ in (0,)}]
+
+ def dictcomp(self):
+ return {0: {1: super().f() for _ in (0,)} for _ in (0,)}
+
+ b = B()
+ self.assertEqual(b.f(), [42])
+ self.assertEqual(b.nested(), [[42]])
+ self.assertEqual(b.setcomp(), [{42}])
+ self.assertEqual(b.dictcomp(), {0: {1: 42}})
+
def test_references___class__(self):
code = """
res = [__class__ for x in [1]]
@@ -277,6 +302,20 @@ def f():
outputs = {"y": [1]}
self._check_in_scopes(code, outputs, scopes=["module", "function"])
+ def test_inlined_comp_cell_with_enclosing_free(self):
+ # The listcomp cell and the enclosing free must not share an index.
+ code = """
+ def outer(y):
+ def inner():
+ return [lambda: x for x in (1, 2)], y
+ return inner()
+ funcs, val = outer(99)
+ z = [f() for f in funcs]
+ w = val
+ """
+ outputs = {"z": [2, 2], "w": 99}
+ self._check_in_scopes(code, outputs)
+
def test_free_inner_cell_outer(self):
code = """
g = 2
@@ -407,6 +446,175 @@ def test_nested(self):
outputs = {"y": [[0, 1], [0, 1, 4]]}
self._check_in_scopes(code, outputs)
+ def test_nested_inner_uses_outer_iter(self):
+ # Inner comprehension reads the outer iteration variable. In a class
+ # this must not be treated as a class-level name of the same name.
+ code = """
+ x = 99
+ y = [[x for _ in (0,)] for x in (42,)]
+ """
+ outputs = {"y": [[42]]}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_mixed_comprehensions_use_outer_iter(self):
+ cases = [
+ ("""
+ x = 99
+ y = [{x for _ in (0,)} for x in (42,)]
+ """, {"y": [{42}]}),
+ ("""
+ x = 99
+ y = [{x: x for _ in (0,)} for x in (42,)]
+ """, {"y": [{42: 42}]}),
+ ("""
+ x = 99
+ y = {[x for _ in (0,)][0] for x in (42,)}
+ """, {"y": {42}}),
+ ("""
+ x = 99
+ y = {x: [x for _ in (0,)] for x in (42,)}
+ """, {"y": {42: [42]}}),
+ ]
+ for code, outputs in cases:
+ with self.subTest(code=code):
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_triple_inner_uses_outer_iter(self):
+ code = """
+ x = 99
+ y = [[[x for _ in (0,)] for _ in (0,)] for x in (42,)]
+ """
+ outputs = {"y": [[[42]]]}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_inner_uses_outer_iter_in_iter(self):
+ code = """
+ x = 99
+ y = [[_ for _ in (x,)] for x in (42,)]
+ """
+ outputs = {"y": [[42]]}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_inner_uses_outer_iter_in_if(self):
+ code = """
+ x = 99
+ y = [[1 for _ in (0,) if x] for x in (42,)]
+ """
+ outputs = {"y": [[1]]}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_sibling_inners_use_outer_iter(self):
+ code = """
+ x = 99
+ y = [([x for _ in (0,)], [x for _ in (1,)]) for x in (42,)]
+ """
+ outputs = {"y": [([42], [42])]}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_lambda_captures_outer_iter(self):
+ code = """
+ x = 99
+ y = [[lambda: x for _ in (0,)] for x in (42,)]
+ z = y[0][0]()
+ """
+ outputs = {"z": 42}
+ self._check_in_scopes(code, outputs)
+
+ def test_nested_inlined_comp_iter_var_is_fast_local(self):
+ def f(n):
+ return [[x for _ in range(2)] for x in range(n)]
+ self.assertEqual(f.__code__.co_cellvars, ())
+ self.assertEqual(f(2), [[0, 0], [1, 1]])
+
+ def g(n):
+ return [[(lambda: x) for _ in range(2)] for x in range(n)]
+ self.assertEqual(g.__code__.co_cellvars, ("x",))
+ self.assertEqual([fn() for fn in g(2)[1]], [1, 1])
+
+ @support.requires_working_socket()
+ def test_nested_inlined_async_comp_iter_var_is_fast_local(self):
+ import asyncio
+
+ async def agen(n):
+ for i in range(n):
+ yield i
+
+ async def f(n):
+ return [[x async for _ in agen(2)] async for x in agen(n)]
+
+ self.assertEqual(f.__code__.co_cellvars, ())
+ self.assertEqual(asyncio.run(f(2)), [[0, 0], [1, 1]])
+
+ async def g(n):
+ return [[(lambda: x) async for _ in agen(2)] async for x in
agen(n)]
+
+ self.assertEqual(g.__code__.co_cellvars, ("x",))
+ out = asyncio.run(g(2))
+ self.assertEqual([fn() for fn in out[1]], [1, 1])
+
+ def test_inlined_comprehension_name_mangling_in_method_scope(self):
+ class C:
+ def f(self):
+ __x = 42
+ return [__x for _ in (0,)]
+
+ self.assertEqual(C().f(), [42])
+
+ def test_nested_references___class__(self):
+ code = """
+ res = [[__class__ for _ in (0,)] for _ in (1,)]
+ """
+ self._check_in_scopes(code, raises=NameError)
+
+ def test_nested_references___class___via_lambda(self):
+ class _C:
+ res = [[lambda: __class__ for _ in (0,)] for _ in (1,)]
+ self.assertIs(_C.res[0][0](), _C)
+
+ def test_nested_references_super(self):
+ code = """
+ res = [[super for _ in (0,)] for _ in (1,)]
+ """
+ self._check_in_scopes(code, outputs={"res": [[super]]})
+
+ def test_nested_inlined_super_does_not_require_class_cell(self):
+ # Nested inlined comps compile super/__class__ as global lookups.
+ # They must not inject __classcell__ the way a nested function would.
+ class Meta(type):
+ def __new__(mcls, name, bases, ns):
+ ns.pop('__classcell__', None)
+ return type.__new__(mcls, name, bases, ns)
+
+ cases = [
+ ("[[super for _ in (0,)] for _ in (0,)]", [[super]]),
+ ("[{super for _ in (0,)} for _ in (0,)]", [{super}]),
+ ("{0: {1: super for _ in (0,)} for _ in (0,)}", {0: {1: super}}),
+ ]
+ for expr, expected in cases:
+ with self.subTest(expr=expr):
+ ns = {"Meta": Meta}
+ exec(f"class C(metaclass=Meta):\n result = {expr}", ns)
+ self.assertEqual(ns["C"].result, expected)
+
+ def test_nested_inlined_lambda_class_ref_requires_class_cell(self):
+ class Meta(type):
+ def __new__(mcls, name, bases, ns):
+ ns.pop('__classcell__', None)
+ return type.__new__(mcls, name, bases, ns)
+
+ exprs = [
+ "[[lambda: __class__ for _ in (0,)] for _ in (0,)]",
+ "[{lambda: __class__ for _ in (0,)} for _ in (0,)]",
+ "{0: {1: (lambda: __class__) for _ in (0,)} for _ in (0,)}",
+ ]
+ for expr in exprs:
+ with self.subTest(expr=expr):
+ with self.assertRaisesRegex(
+ RuntimeError,
+ r"__class__ not set.*__classcell__ propagated"):
+ exec(f"class C(metaclass=Meta):\n result = {expr}",
+ {"Meta": Meta})
+
def test_nested_2(self):
code = """
l = [1, 2, 3]
@@ -703,6 +911,79 @@ def test_frame_locals(self):
"""
self._check_in_scopes(code, {"val": 0}, ns={"sys": sys})
+ def test_frame_locals_comp_cell_and_enclosing_free(self):
+ # The inlined listcomp cell and the enclosing free share a name.
+ # f_locals keys must still be unique so dict(**f_locals) works.
+ # keys(), values(), items(), and len() must agree (first slot wins).
+ code = """
+ def outer(x):
+ def inner():
+ return [(lambda: x,
+ dict(**sys._getframe().f_locals),
+ len(sys._getframe().f_locals),
+ list(sys._getframe().f_locals.keys()),
+ list(sys._getframe().f_locals.values()),
+ list(sys._getframe().f_locals.items()),
+ dict(sys._getframe().f_locals.items()))
+ for x in x]
+ return inner()
+ result = outer([1, 2])
+ snaps = [d['x'] for _, d, *_ in result]
+ vals = [fn() for fn, *_ in result]
+ consistent = []
+ for _, d, n, ks, vs, it, d_items in result:
+ consistent.append(
+ n == len(ks) == len(vs) == len(it)
+ and ks.count('x') == 1
+ and d == d_items == dict(zip(ks, vs))
+ )
+ """
+ import sys
+ self._check_in_scopes(
+ code,
+ {"snaps": [1, 2], "vals": [2, 2], "consistent": [True, True]},
+ ns={"sys": sys}, scopes=["module", "function"])
+
+ def test_frame_locals_nested_comp_cell_and_enclosing_free(self):
+ # Stress a nested inlined shape where a comp cell and enclosing free
+ # share a name; all f_locals views must stay consistent.
+ code = """
+ def outer(x):
+ def inner():
+ return [(
+ lambda: x,
+ [[x for _ in (0,)] for _ in (0,)][0][0],
+ dict(**sys._getframe().f_locals),
+ len(sys._getframe().f_locals),
+ list(sys._getframe().f_locals.keys()),
+ list(sys._getframe().f_locals.values()),
+ list(sys._getframe().f_locals.items()),
+ dict(sys._getframe().f_locals.items()))
+ for x in x]
+ return inner()
+ result = outer([1, 2])
+ snaps = [d['x'] for _, _, d, *_ in result]
+ vals = [fn() for fn, *_ in result]
+ nested_vals = [nested for _, nested, *_ in result]
+ consistent = []
+ for _, _, d, n, ks, vs, it, d_items in result:
+ consistent.append(
+ n == len(ks) == len(vs) == len(it)
+ and ks.count('x') == 1
+ and d == d_items == dict(zip(ks, vs))
+ )
+ """
+ import sys
+ self._check_in_scopes(
+ code,
+ {
+ "snaps": [1, 2],
+ "vals": [2, 2],
+ "nested_vals": [1, 2],
+ "consistent": [True, True],
+ },
+ ns={"sys": sys}, scopes=["module", "function"])
+
def _recursive_replace(self, maybe_code):
if not isinstance(maybe_code, types.CodeType):
return maybe_code
diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py
index ce02b27c599c420..da00b0bfa74b125 100644
--- a/Lib/test/test_symtable.py
+++ b/Lib/test/test_symtable.py
@@ -426,12 +426,12 @@ def test_symbol_repr(self):
"<symbol 'T': LOCAL, DEF_LOCAL|DEF_TYPE_PARAM>")
st1 = symtable.symtable("[x for x in [1]]", "?", "exec")
- self.assertEqual(repr(st1.lookup("x")),
+ self.assertEqual(repr(st1.get_children()[0].lookup("x")),
"<symbol 'x': LOCAL, USE|DEF_LOCAL|DEF_COMP_ITER>")
st2 = symtable.symtable("[(lambda: x) for x in [1]]", "?", "exec")
- self.assertEqual(repr(st2.lookup("x")),
- "<symbol 'x': CELL,
DEF_LOCAL|DEF_COMP_ITER|DEF_COMP_CELL>")
+ self.assertEqual(repr(st2.get_children()[0].lookup("x")),
+ "<symbol 'x': CELL, DEF_LOCAL|DEF_COMP_ITER>")
st3 = symtable.symtable("def f():\n"
" x = 1\n"
@@ -502,6 +502,255 @@ def test_nested_genexpr(self):
self.assertEqual(sorted(st.get_identifiers()), [".0", "y"])
self.assertEqual(st.get_children(), [])
+ def test_inlined_comprehension_in_genexpr(self):
+ st = symtable.symtable("([y for y in x] for x in a)", "?", "exec")
+ self.assertEqual(len(st.get_children()), 1)
+ st = st.get_children()[0]
+ self.assertIs(st.get_type(), symtable.SymbolTableType.FUNCTION)
+ self.assertEqual(st.get_name(), "<genexpr>")
+ self.assertFalse(st.is_nested())
+ self.assertEqual(sorted(st.get_identifiers()), [".0", "x"])
+ children = st.get_children()
+ self.assertEqual(len(children), 1)
+ self.check_inlined_listcomp(children[0], ["y"], nested=True)
+
+ def check_inlined_listcomp(self, st, identifiers, *, nested, nchildren=0):
+ self.assertIs(st.get_type(),
symtable.SymbolTableType.INLINED_COMPREHENSION)
+ self.assertEqual(st.get_name(), "<listcomp>")
+ self.assertEqual(st.is_nested(), nested)
+ self.assertEqual(sorted(st.get_identifiers()), identifiers)
+ children = st.get_children()
+ self.assertEqual(len(children), nchildren)
+ return children
+
+ def check_nested_inlined_listcomp(self, outer, outer_ids, inner_ids, *,
nested):
+ inner, = self.check_inlined_listcomp(
+ outer, outer_ids, nested=nested, nchildren=1)
+ self.check_inlined_listcomp(inner, inner_ids, nested=True)
+ return inner
+
+ def test_inlined_comprehension(self):
+ st = symtable.symtable("[x for x in [1]]", "?", "exec")
+ self.assertEqual(sorted(st.get_identifiers()), [])
+ children = st.get_children()
+ self.assertEqual(len(children), 1)
+ self.check_inlined_listcomp(children[0], ["x"], nested=False)
+
+ def test_inlined_nested_comprehension(self):
+ st = symtable.symtable("[[y for y in x] for x in [1]]", "?", "exec")
+ self.assertEqual(sorted(st.get_identifiers()), [])
+ children = st.get_children()
+ self.assertEqual(len(children), 1)
+ self.check_nested_inlined_listcomp(
+ children[0], ["x"], ["y"], nested=False)
+
+ def test_inlined_comprehension_use_of_enclosing_free_in_function(self):
+ # x is used only in the inlined listcomp; inner still reports USE.
+ st = symtable.symtable(
+ "def outer(x):\n"
+ " def inner():\n"
+ " return [x for y in ()]",
+ "?", "exec")
+ inner = find_block(find_block(st, "outer"), "inner")
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertTrue(inner.lookup("x").is_referenced())
+ comp, = inner.get_children()
+ self.assertTrue(comp.lookup("x").is_free())
+ self.assertTrue(comp.lookup("x").is_referenced())
+
+ def test_inlined_comprehension_use_of_nonlocal_in_function(self):
+ st = symtable.symtable(
+ "def outer():\n"
+ " x = 1\n"
+ " def inner():\n"
+ " nonlocal x\n"
+ " return [x for _ in ()]",
+ "?", "exec")
+ inner = find_block(find_block(st, "outer"), "inner")
+ self.assertTrue(inner.lookup("x").is_nonlocal())
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertTrue(inner.lookup("x").is_referenced())
+ comp, = inner.get_children()
+ self.assertTrue(comp.lookup("x").is_free())
+ self.assertTrue(comp.lookup("x").is_referenced())
+
+ def test_inlined_comprehension_use_of_explicit_global_in_function(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " global g\n"
+ " return [g for _ in ()]",
+ "?", "exec")
+ f = find_block(st, "f")
+ self.assertTrue(f.lookup("g").is_global())
+ self.assertTrue(f.lookup("g").is_declared_global())
+ self.assertFalse(f.lookup("g").is_referenced())
+ comp, = f.get_children()
+ self.assertTrue(comp.lookup("g").is_global())
+ self.assertTrue(comp.lookup("g").is_referenced())
+
+ def test_inlined_comprehension_nested_function_use_not_on_enclosing(self):
+ # The load of x is in the lambda's code object, not inner's.
+ st = symtable.symtable(
+ "def outer(x):\n"
+ " def inner():\n"
+ " return [(lambda: x) for y in ()]",
+ "?", "exec")
+ inner = find_block(find_block(st, "outer"), "inner")
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertFalse(inner.lookup("x").is_referenced())
+ comp, = inner.get_children()
+ self.assertTrue(comp.lookup("x").is_free())
+ self.assertFalse(comp.lookup("x").is_referenced())
+ lam, = comp.get_children()
+ self.assertTrue(lam.lookup("x").is_free())
+ self.assertTrue(lam.lookup("x").is_referenced())
+
+ def test_inlined_comprehension_comp_cell_not_on_enclosing(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " x = 1\n"
+ " return [(lambda: x) for x in [1]]",
+ "?", "exec")
+ f = find_block(st, "f")
+ self.assertTrue(f.lookup("x").is_cell())
+ self.assertFalse(f.lookup("x").is_comp_cell())
+ comp, = (c for c in f.get_children()
+ if c.get_type() is
symtable.SymbolTableType.INLINED_COMPREHENSION)
+ self.assertTrue(comp.lookup("x").is_cell())
+ self.assertTrue(comp.lookup("x").is_comp_cell())
+
+ def test_inlined_comprehension_use_of_enclosing_free_in_class(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " y = 1\n"
+ " class C:\n"
+ " y = 2\n"
+ " vals = [(x, y) for x in range(2)]",
+ "?", "exec")
+ f = find_block(st, "f")
+ self.assertTrue(f.lookup("y").is_cell())
+ C = find_block(f, "C")
+ self.assertTrue(C.lookup("y").is_local())
+ self.assertFalse(C.lookup("y").is_free())
+ self.assertTrue(C.lookup("y").is_free_class())
+ comp, = C.get_children()
+ self.assertTrue(comp.lookup("y").is_free())
+ self.assertTrue(comp.lookup("y").is_referenced())
+ self.assertTrue(comp.lookup("x").is_local())
+
+ def test_inlined_comprehension_class_closure_names_are_free(self):
+ st = symtable.symtable(
+ "class C:\n"
+ " [__class__ for x in [1]]",
+ "?", "exec")
+ C = find_block(st, "C")
+ comp, = C.get_children()
+ self.assertTrue(comp.lookup("__class__").is_free())
+ self.assertTrue(comp.lookup("__class__").is_referenced())
+ with self.assertRaises(KeyError):
+ C.lookup("__class__")
+
+ def test_inlined_nested_comprehension_class_iter_var(self):
+ st = symtable.symtable(
+ "class C:\n"
+ " x = 99\n"
+ " [[x for _ in (0,)] for x in (42,)]",
+ "?", "exec")
+ C = find_block(st, "C")
+ children = C.get_children()
+ self.assertEqual(len(children), 1)
+ inner = self.check_nested_inlined_listcomp(
+ children[0], ["x"], ["_", "x"], nested=False)
+ self.assertFalse(C.lookup("x").is_free())
+ self.assertTrue(C.lookup("x").is_local())
+ self.assertFalse(C.lookup("x").is_comp_cell())
+ self.assertFalse(children[0].lookup("x").is_free())
+ self.assertTrue(children[0].lookup("x").is_local())
+ self.assertFalse(children[0].lookup("x").is_cell())
+ self.assertFalse(children[0].lookup("x").is_comp_cell())
+ self.assertTrue(children[0].lookup("x").is_referenced())
+ self.assertFalse(inner.lookup("_").is_free())
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertTrue(inner.lookup("x").is_referenced())
+
+ def test_inlined_nested_comprehension_iter_var_is_fast_local(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " return [[x for _ in (0,)] for x in (42,)]",
+ "?", "exec")
+ f = find_block(st, "f")
+ with self.assertRaises(KeyError):
+ f.lookup("x")
+ outer, = f.get_children()
+ inner = self.check_nested_inlined_listcomp(
+ outer, ["x"], ["_", "x"], nested=True)
+ self.assertTrue(outer.lookup("x").is_local())
+ self.assertFalse(outer.lookup("x").is_cell())
+ self.assertFalse(outer.lookup("x").is_comp_cell())
+ self.assertTrue(outer.lookup("x").is_referenced())
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertTrue(inner.lookup("x").is_referenced())
+
+ def test_inlined_nested_mixed_comprehension_iter_var_is_referenced(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " return [{x for _ in (0,)} for x in (42,)]",
+ "?", "exec")
+ f = find_block(st, "f")
+ with self.assertRaises(KeyError):
+ f.lookup("x")
+ outer, = f.get_children()
+ self.assertTrue(outer.lookup("x").is_local())
+ self.assertTrue(outer.lookup("x").is_referenced())
+ inner, = outer.get_children()
+ self.assertTrue(inner.lookup("x").is_free())
+ self.assertTrue(inner.lookup("x").is_referenced())
+
+ def test_inlined_nested_comprehension_captured_iter_is_cell(self):
+ st = symtable.symtable(
+ "def f():\n"
+ " return [[(lambda: x) for _ in (0,)] for x in (1, 2)]",
+ "?", "exec")
+ f = find_block(st, "f")
+ with self.assertRaises(KeyError):
+ f.lookup("x")
+ outer, = (c for c in f.get_children()
+ if c.get_type() is
symtable.SymbolTableType.INLINED_COMPREHENSION)
+ inner, = (c for c in outer.get_children()
+ if c.get_type() is
symtable.SymbolTableType.INLINED_COMPREHENSION)
+ self.assertTrue(outer.lookup("x").is_cell())
+ self.assertTrue(outer.lookup("x").is_comp_cell())
+ self.assertTrue(inner.lookup("x").is_free())
+ lam, = inner.get_children()
+ self.assertTrue(lam.lookup("x").is_free())
+
+ def test_inlined_sibling_nested_comprehensions(self):
+ st = symtable.symtable(
+ "def f(): [[y for y in x] for x in [1]]; [[w for w in z] for z in
[2]]",
+ "?", "exec")
+ f = find_block(st, "f")
+ self.assertIs(f.get_type(), symtable.SymbolTableType.FUNCTION)
+ self.assertEqual(sorted(f.get_identifiers()), [])
+ children = f.get_children()
+ self.assertEqual(len(children), 2)
+ self.check_nested_inlined_listcomp(
+ children[0], ["x"], ["y"], nested=True)
+ self.check_nested_inlined_listcomp(
+ children[1], ["z"], ["w"], nested=True)
+
+ def test_deeply_nested_inlined_comprehensions(self):
+ depth = 24
+ source = "[" * depth + "0" + " for x in ()]" * depth
+ st = symtable.symtable(source, "?", "exec")
+ cur = st
+ for _ in range(depth):
+ children = cur.get_children()
+ self.assertEqual(len(children), 1)
+ cur = children[0]
+ self.assertIs(cur.get_type(),
+ symtable.SymbolTableType.INLINED_COMPREHENSION)
+ self.assertEqual(cur.get_children(), [])
+
def test__symtable_refleak(self):
# Regression test for reference leak in PyUnicode_FSDecoder.
# See https://github.com/python/cpython/issues/139748.
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 5358b6f5ee8fcdd..2001b70739fd26e 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -3538,6 +3538,23 @@ def src(depth):
self._check_error(src(CO_MAXBLOCKS + 1),
"too many statically nested blocks")
+ def test_invalid_starred_for_target_in_async_comprehension(self):
+ sources = [
+ "async def f():\n {a async for b in d for *(b,) in e}",
+ "async def f():\n [a async for b in d for *(b,) in e]",
+ "async def f():\n {a: a async for b in d for *(b,) in e}",
+ ]
+ for src in sources:
+ with self.subTest(src=src):
+ self._check_error(
+ src, "starred assignment target must be in a list or
tuple")
+
+ def test_syntax_error_in_nested_inlined_async_comprehension(self):
+ self._check_error(
+ "async def f(it):\n"
+ " return [[f(a=1, a=2) for y in z] async for x in it]\n",
+ "keyword argument repeated")
+
@support.cpython_only
def test_error_on_parser_stack_overflow(self):
source = "-" * 100000 + "4"
diff --git
a/Misc/NEWS.d/next/Library/2026-09-02-12-11-00.gh-issue-124697.sUbScp.rst
b/Misc/NEWS.d/next/Library/2026-09-02-12-11-00.gh-issue-124697.sUbScp.rst
new file mode 100644
index 000000000000000..8dfdbeaf1856fc6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-02-12-11-00.gh-issue-124697.sUbScp.rst
@@ -0,0 +1,3 @@
+The :mod:`symtable` module now represents inlined list, set and dict
+comprehensions (:pep:`709`) as their own symbol table entries of type
+:attr:`~symtable.SymbolTableType.INLINED_COMPREHENSION`.
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index d30affdbf621392..c01ac65dd4cc042 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -1357,13 +1357,16 @@ _testinternalcapi_assemble_code_object_impl(PyObject
*module,
umd.u_cellvars = PyDict_GetItemString(metadata, "cellvars");
umd.u_freevars = PyDict_GetItemString(metadata, "freevars");
umd.u_fasthidden = PyDict_GetItemString(metadata, "fasthidden");
+ if (umd.u_fasthidden == Py_None) {
+ umd.u_fasthidden = NULL;
+ }
assert(PyDict_Check(umd.u_consts));
assert(PyDict_Check(umd.u_names));
assert(PyDict_Check(umd.u_varnames));
assert(PyDict_Check(umd.u_cellvars));
assert(PyDict_Check(umd.u_freevars));
- assert(PyDict_Check(umd.u_fasthidden));
+ assert(umd.u_fasthidden == NULL || PySet_Check(umd.u_fasthidden));
umd.u_argcount = get_nonnegative_int_from_dict(metadata, "argcount");
umd.u_posonlyargcount = get_nonnegative_int_from_dict(metadata,
"posonlyargcount");
diff --git a/Modules/symtablemodule.c b/Modules/symtablemodule.c
index 7e20b5c7173ae5d..3028158fc917e1d 100644
--- a/Modules/symtablemodule.c
+++ b/Modules/symtablemodule.c
@@ -128,7 +128,6 @@ symtable_init_constants(PyObject *m)
if (PyModule_AddIntMacro(m, DEF_BOUND) < 0) return -1;
if (PyModule_AddIntMacro(m, DEF_ANNOT) < 0) return -1;
if (PyModule_AddIntMacro(m, DEF_COMP_ITER) < 0) return -1;
- if (PyModule_AddIntMacro(m, DEF_COMP_CELL) < 0) return -1;
if (PyModule_AddIntConstant(m, "TYPE_FUNCTION", FunctionBlock) < 0)
return -1;
@@ -144,6 +143,8 @@ symtable_init_constants(PyObject *m)
return -1;
if (PyModule_AddIntConstant(m, "TYPE_TYPE_VARIABLE", TypeVariableBlock) <
0)
return -1;
+ if (PyModule_AddIntConstant(m, "TYPE_INLINED_COMPREHENSION",
InlinedComprehensionBlock) < 0)
+ return -1;
if (PyModule_AddIntMacro(m, LOCAL) < 0) return -1;
if (PyModule_AddIntMacro(m, GLOBAL_EXPLICIT) < 0) return -1;
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index c920e6cfc89c3be..a4cc14a6eaad457 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -94,6 +94,22 @@ framelocalsproxy_hasval(_PyInterpreterFrame *frame,
PyCodeObject *co, int i)
return true;
}
+static int
+framelocalsproxy_is_first_occurrence(PyObject *seen, PyObject *name)
+{
+ int found = PySet_Contains(seen, name);
+ if (found < 0) {
+ return -1;
+ }
+ if (found) {
+ return 0;
+ }
+ if (PySet_Add(seen, name) < 0) {
+ return -1;
+ }
+ return 1;
+}
+
static int
framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject *key, bool read,
PyObject **value_ptr)
{
@@ -380,16 +396,28 @@ framelocalsproxy_keys(PyObject *self, PyObject
*Py_UNUSED(ignored))
if (names == NULL) {
return NULL;
}
+ // An inlined comprehension cell can share a name with a free var.
+ PyObject *seen = PySet_New(NULL);
+ if (seen == NULL) {
+ Py_DECREF(names);
+ return NULL;
+ }
for (int i = 0; i < co->co_nlocalsplus; i++) {
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
- if (PyList_Append(names, name) < 0) {
- Py_DECREF(names);
- return NULL;
+ int first = framelocalsproxy_is_first_occurrence(seen, name);
+ if (first < 0) {
+ goto error;
+ }
+ if (first) {
+ if (PyList_Append(names, name) < 0) {
+ goto error;
+ }
}
}
}
+ Py_DECREF(seen);
// Iterate through the extra locals
if (frame->f_extra_locals) {
@@ -408,6 +436,11 @@ framelocalsproxy_keys(PyObject *self, PyObject
*Py_UNUSED(ignored))
}
return names;
+
+error:
+ Py_DECREF(seen);
+ Py_DECREF(names);
+ return NULL;
}
static void
@@ -589,18 +622,30 @@ framelocalsproxy_values(PyObject *self, PyObject
*Py_UNUSED(ignored))
if (values == NULL) {
return NULL;
}
+ PyObject *seen = PySet_New(NULL);
+ if (seen == NULL) {
+ Py_DECREF(values);
+ return NULL;
+ }
for (int i = 0; i < co->co_nlocalsplus; i++) {
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
if (value) {
- if (PyList_Append(values, value) < 0) {
- Py_DECREF(values);
- Py_DECREF(value);
- return NULL;
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ int first = framelocalsproxy_is_first_occurrence(seen, name);
+ if (first == 1) {
+ if (PyList_Append(values, value) < 0) {
+ Py_DECREF(value);
+ goto error;
+ }
}
Py_DECREF(value);
+ if (first < 0) {
+ goto error;
+ }
}
}
+ Py_DECREF(seen);
// Iterate through the extra locals
if (frame->f_extra_locals) {
@@ -616,6 +661,11 @@ framelocalsproxy_values(PyObject *self, PyObject
*Py_UNUSED(ignored))
}
return values;
+
+error:
+ Py_DECREF(seen);
+ Py_DECREF(values);
+ return NULL;
}
static PyObject *
@@ -627,22 +677,37 @@ framelocalsproxy_items(PyObject *self, PyObject
*Py_UNUSED(ignored))
if (items == NULL) {
return NULL;
}
+ PyObject *seen = PySet_New(NULL);
+ if (seen == NULL) {
+ Py_DECREF(items);
+ return NULL;
+ }
for (int i = 0; i < co->co_nlocalsplus; i++) {
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
if (value) {
- PyObject *pair = _PyTuple_FromPairSteal(Py_NewRef(name), value);
- if (pair == NULL) {
- goto error;
+ int first = framelocalsproxy_is_first_occurrence(seen, name);
+ if (first == 1) {
+ PyObject *pair = _PyTuple_FromPairSteal(Py_NewRef(name),
value);
+ if (pair == NULL) {
+ goto error;
+ }
+ if (_PyList_AppendTakeRef((PyListObject *)items, pair) < 0) {
+ goto error;
+ }
}
-
- if (_PyList_AppendTakeRef((PyListObject *)items, pair) < 0) {
- goto error;
+ else {
+ Py_DECREF(value);
+ if (first < 0) {
+ goto error;
+ }
}
}
}
+ Py_DECREF(seen);
+ seen = NULL;
// Iterate through the extra locals
if (frame->f_extra_locals) {
@@ -664,6 +729,7 @@ framelocalsproxy_items(PyObject *self, PyObject
*Py_UNUSED(ignored))
return items;
error:
+ Py_XDECREF(seen);
Py_DECREF(items);
return NULL;
}
@@ -680,11 +746,24 @@ framelocalsproxy_length(PyObject *self)
size += PyDict_Size(frame->f_extra_locals);
}
+ PyObject *seen = PySet_New(NULL);
+ if (seen == NULL) {
+ return -1;
+ }
for (int i = 0; i < co->co_nlocalsplus; i++) {
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
- size++;
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ int first = framelocalsproxy_is_first_occurrence(seen, name);
+ if (first < 0) {
+ Py_DECREF(seen);
+ return -1;
+ }
+ else if (first) {
+ size++;
+ }
}
}
+ Py_DECREF(seen);
return size;
}
diff --git a/Python/assemble.c b/Python/assemble.c
index 8b92042345f150c..db9efff5e08ca96 100644
--- a/Python/assemble.c
+++ b/Python/assemble.c
@@ -534,13 +534,15 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd,
int nlocalsplus,
_PyLocals_Kind kind = CO_FAST_LOCAL | argvarkinds[i].kind;
- int has_key = PyDict_Contains(umd->u_fasthidden, k);
- RETURN_IF_ERROR(has_key);
- if (has_key) {
- kind |= CO_FAST_HIDDEN;
+ if (umd->u_fasthidden != NULL) {
+ int hidden = PySet_Contains(umd->u_fasthidden, k);
+ RETURN_IF_ERROR(hidden);
+ if (hidden) {
+ kind |= CO_FAST_HIDDEN;
+ }
}
- has_key = PyDict_Contains(umd->u_cellvars, k);
+ int has_key = PyDict_Contains(umd->u_cellvars, k);
RETURN_IF_ERROR(has_key);
if (has_key) {
kind |= CO_FAST_CELL;
diff --git a/Python/codegen.c b/Python/codegen.c
index c4b749a539efdd4..0ae13e40d4a1ee5 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -3326,27 +3326,20 @@ codegen_nameop(compiler *c, location loc,
return ERROR;
}
- int scope = _PyST_GetScope(SYMTABLE_ENTRY(c), mangled);
- if (scope == -1) {
- goto error;
- }
-
_PyCompile_optype optype;
Py_ssize_t arg = 0;
- if (_PyCompile_ResolveNameop(c, mangled, scope, &optype, &arg) < 0) {
+ int scope = _PyCompile_ResolveNameop(c, mangled, &optype, &arg);
+ if (scope < 0) {
Py_DECREF(mangled);
return ERROR;
}
- /* XXX Leave assert here, but handle __doc__ and the like better */
- assert(scope || PyUnicode_READ_CHAR(name, 0) == '_');
-
int op = 0;
switch (optype) {
case COMPILE_OP_DEREF:
switch (ctx) {
case Load:
- if (SYMTABLE_ENTRY(c)->ste_type == ClassBlock &&
!_PyCompile_IsInInlinedComp(c)) {
+ if (SYMTABLE_ENTRY(c)->ste_type == ClassBlock) {
op = LOAD_FROM_DICT_OR_DEREF;
// First load the locals
if (codegen_addop_noarg(INSTR_SEQUENCE(c), LOAD_LOCALS, loc) <
0) {
@@ -3399,8 +3392,9 @@ codegen_nameop(compiler *c, location loc,
case COMPILE_OP_NAME:
switch (ctx) {
case Load:
- op = (SYMTABLE_ENTRY(c)->ste_type == ClassBlock
- && _PyCompile_IsInInlinedComp(c))
+ /* LOAD_NAME in a class reads the class dict; inlined comps must
not. */
+ op = (SCOPE_TYPE(c) == COMPILE_SCOPE_CLASS
+ && SYMTABLE_ENTRY(c)->ste_type == InlinedComprehensionBlock)
? LOAD_GLOBAL
: LOAD_NAME;
break;
@@ -4949,9 +4943,6 @@ codegen_push_inlined_comprehension_locals(compiler *c,
location loc,
PySTEntryObject *comp,
_PyCompile_InlinedComprehensionState
*state)
{
- int in_class_block = (SYMTABLE_ENTRY(c)->ste_type == ClassBlock) &&
- !_PyCompile_IsInInlinedComp(c);
- PySTEntryObject *outer = SYMTABLE_ENTRY(c);
// iterate over names bound in the comprehension and ensure we isolate
// them from the outer scope as needed
PyObject *k, *v;
@@ -4962,11 +4953,7 @@ codegen_push_inlined_comprehension_locals(compiler *c,
location loc,
RETURN_IF_ERROR(symbol);
long scope = SYMBOL_TO_SCOPE(symbol);
- long outsymbol = _PyST_GetSymbol(outer, k);
- RETURN_IF_ERROR(outsymbol);
- long outsc = SYMBOL_TO_SCOPE(outsymbol);
-
- if ((symbol & DEF_LOCAL && !(symbol & DEF_NONLOCAL)) ||
in_class_block) {
+ if ((symbol & DEF_LOCAL) && !(symbol & DEF_NONLOCAL)) {
// local names bound in comprehension must be isolated from
// outer scope; push existing value (which may be NULL if
// not defined) on stack
@@ -4981,15 +4968,17 @@ codegen_push_inlined_comprehension_locals(compiler *c,
location loc,
// comprehension and restore the original one after
ADDOP_NAME(c, loc, LOAD_FAST_AND_CLEAR, k, varnames);
if (scope == CELL) {
- if (outsc == FREE) {
- ADDOP_NAME(c, loc, MAKE_CELL, k, freevars);
- } else {
- ADDOP_NAME(c, loc, MAKE_CELL, k, cellvars);
- }
+ ADDOP_NAME(c, loc, MAKE_CELL, k, cellvars);
}
if (PyList_Append(state->pushed_locals, k) < 0) {
return ERROR;
}
+ if (METADATA(c)->u_fasthidden != NULL) {
+ /* For Module/Class scopes, assemble needs to set
CO_FAST_HIDDEN on these names */
+ if (PySet_Add(METADATA(c)->u_fasthidden, k) < 0) {
+ return ERROR;
+ }
+ }
}
}
if (state->pushed_locals) {
@@ -5021,9 +5010,11 @@ push_inlined_comprehension_state(compiler *c, location
loc,
_PyCompile_InlinedComprehensionState *state)
{
RETURN_IF_ERROR(
- _PyCompile_TweakInlinedComprehensionScopes(c, loc, comp, state));
- RETURN_IF_ERROR(
- codegen_push_inlined_comprehension_locals(c, loc, comp, state));
+ _PyCompile_EnterInlinedComprehensionScope(c, comp, state));
+ if (codegen_push_inlined_comprehension_locals(c, loc, comp, state) < 0){
+ _PyCompile_ExitInlinedComprehensionScope(c, state);
+ return ERROR;
+ }
return SUCCESS;
}
@@ -5080,9 +5071,9 @@ static int
pop_inlined_comprehension_state(compiler *c, location loc,
_PyCompile_InlinedComprehensionState *state)
{
- RETURN_IF_ERROR(codegen_pop_inlined_comprehension_locals(c, loc, state));
- RETURN_IF_ERROR(_PyCompile_RevertInlinedComprehensionScopes(c, loc,
state));
- return SUCCESS;
+ int result = codegen_pop_inlined_comprehension_locals(c, loc, state);
+ RETURN_IF_ERROR(_PyCompile_ExitInlinedComprehensionScope(c, state));
+ return result;
}
static int
@@ -5123,13 +5114,13 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
expr_ty val, bool avoid_creation)
{
PyCodeObject *co = NULL;
- _PyCompile_InlinedComprehensionState inline_state = {NULL, NULL, NULL,
NO_LABEL};
+ _PyCompile_InlinedComprehensionState inline_state = {NULL, NO_LABEL, NULL};
comprehension_ty outermost;
PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), (void *)e);
if (entry == NULL) {
goto error;
}
- int is_inlined = entry->ste_comp_inlined;
+ int is_inlined = (entry->ste_type == InlinedComprehensionBlock);
int is_async_comprehension = entry->ste_coroutine;
location loc = LOC(e);
@@ -5138,7 +5129,7 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
IterStackPosition iter_state;
if (is_inlined) {
VISIT(c, expr, outermost->iter);
- if (push_inlined_comprehension_state(c, loc, entry, &inline_state)) {
+ if (push_inlined_comprehension_state(c, loc, entry, &inline_state) <
0) {
goto error;
}
iter_state = ITERABLE_ON_STACK;
@@ -5191,8 +5182,8 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
}
if (is_inlined) {
- if (pop_inlined_comprehension_state(c, loc, &inline_state)) {
- goto error;
+ if (pop_inlined_comprehension_state(c, loc, &inline_state) < 0) {
+ goto error_in_scope;
}
return SUCCESS;
}
@@ -5232,15 +5223,18 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
return SUCCESS;
error_in_scope:
- if (!is_inlined) {
+ if (is_inlined) {
+ if (inline_state.saved_ste != NULL) {
+ _PyCompile_ExitInlinedComprehensionScope(c, &inline_state);
+ }
+ }
+ else {
_PyCompile_ExitScope(c);
}
error:
Py_XDECREF(co);
Py_XDECREF(entry);
Py_XDECREF(inline_state.pushed_locals);
- Py_XDECREF(inline_state.temp_symbols);
- Py_XDECREF(inline_state.fast_hidden);
return ERROR;
}
diff --git a/Python/compile.c b/Python/compile.c
index f3852041bce69ca..ee29f7a9a5d5895 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -68,7 +68,6 @@ struct compiler_unit {
instr_sequence *u_stashed_instr_sequence; /* temporarily stashed parent
instruction sequence */
int u_nfblocks;
- int u_in_inlined_comp;
int u_in_conditional_block;
_PyCompile_FBlockInfo u_fblock[CO_MAXBLOCKS];
@@ -596,6 +595,75 @@ dictbytype(PyObject *src, int scope_type, int flag,
Py_ssize_t offset)
return dest;
}
+static int
+add_cell_names_from_symbols(PyObject *symbols, PyObject *names)
+{
+ Py_ssize_t pos = 0;
+ PyObject *k, *v;
+ while (PyDict_Next(symbols, &pos, &k, &v)) {
+ long flags = PyLong_AsLong(v);
+ if (flags == -1 && PyErr_Occurred()) {
+ return ERROR;
+ }
+ if (SYMBOL_TO_SCOPE(flags) == CELL) {
+ if (PySet_Add(names, k) < 0) {
+ return ERROR;
+ }
+ }
+ }
+ return SUCCESS;
+}
+
+static int
+add_inlined_comprehension_cell_names(PySTEntryObject *ste, PyObject *names)
+{
+ for (Py_ssize_t i = 0; i < PyList_GET_SIZE(ste->ste_children); i++) {
+ PySTEntryObject *child =
+ (PySTEntryObject *)PyList_GET_ITEM(ste->ste_children, i);
+ if (child->ste_type != InlinedComprehensionBlock) {
+ continue;
+ }
+ if (add_cell_names_from_symbols(child->ste_symbols, names) < 0) {
+ return ERROR;
+ }
+ if (add_inlined_comprehension_cell_names(child, names) < 0) {
+ return ERROR;
+ }
+ }
+ return SUCCESS;
+}
+
+/* Cells of the shared unit: this table's CELL names, plus cells that live
+ * only on inlined comprehension children. */
+static PyObject *
+compiler_cellvars(PySTEntryObject *ste)
+{
+ PyObject *names = PySet_New(NULL);
+ if (names == NULL) {
+ return NULL;
+ }
+ if (add_cell_names_from_symbols(ste->ste_symbols, names) < 0) {
+ Py_DECREF(names);
+ return NULL;
+ }
+ if (add_inlined_comprehension_cell_names(ste, names) < 0) {
+ Py_DECREF(names);
+ return NULL;
+ }
+ PyObject *sorted = PySequence_List(names);
+ Py_DECREF(names);
+ if (sorted == NULL) {
+ return NULL;
+ }
+ if (PyList_Sort(sorted) < 0) {
+ Py_DECREF(sorted);
+ return NULL;
+ }
+ PyObject *cellvars = list2dict(sorted);
+ Py_DECREF(sorted);
+ return cellvars;
+}
+
int
_PyCompile_EnterScope(compiler *c, identifier name, int scope_type,
void *key, int lineno, PyObject *private,
@@ -627,7 +695,7 @@ _PyCompile_EnterScope(compiler *c, identifier name, int
scope_type,
compiler_unit_free(u);
return ERROR;
}
- u->u_metadata.u_cellvars = dictbytype(u->u_ste->ste_symbols, CELL,
DEF_COMP_CELL, 0);
+ u->u_metadata.u_cellvars = compiler_cellvars(u->u_ste);
if (!u->u_metadata.u_cellvars) {
compiler_unit_free(u);
return ERROR;
@@ -670,14 +738,18 @@ _PyCompile_EnterScope(compiler *c, identifier name, int
scope_type,
return ERROR;
}
- u->u_metadata.u_fasthidden = PyDict_New();
- if (!u->u_metadata.u_fasthidden) {
- compiler_unit_free(u);
- return ERROR;
+ if (scope_type == COMPILE_SCOPE_MODULE || scope_type ==
COMPILE_SCOPE_CLASS) {
+ u->u_metadata.u_fasthidden = PySet_New(NULL);
+ if (!u->u_metadata.u_fasthidden) {
+ compiler_unit_free(u);
+ return ERROR;
+ }
+ }
+ else {
+ u->u_metadata.u_fasthidden = NULL;
}
u->u_nfblocks = 0;
- u->u_in_inlined_comp = 0;
u->u_metadata.u_firstlineno = lineno;
u->u_metadata.u_consts = PyDict_New();
if (!u->u_metadata.u_consts) {
@@ -907,17 +979,46 @@ compiler_mod(compiler *c, mod_ty mod)
return co;
}
+/* Inlined comprehensions are compiled in the enclosing unit. If a name is
+ * FREE in the comprehension, or is absent from its table (scope 0), resolve
+ * it in enclosing tables until it is bound. Stop if the next table is a class:
+ * nested scopes (including inlined comprehensions) do not see class locals, so
+ * the name stays FREE. __class__ and friends are not allowed to be free
+ * through a class; treat those loads as implicit globals.
+ *
+ * Names with no entry (scope 0) include loads synthesized by codegen, such as
+ * the implicit receiver for zero-arg super(). */
+static int
+compiler_resolve_inlined_free(PySTEntryObject **ste, PyObject *name)
+{
+ int scope = _PyST_GetScope(*ste, name);
+ RETURN_IF_ERROR(scope);
+ while ((*ste)->ste_type == InlinedComprehensionBlock &&
+ (scope == FREE || scope == 0)) {
+ PySTEntryObject *parent = (*ste)->ste_parent;
+ assert(parent != NULL);
+ if (parent->ste_type == ClassBlock) {
+ if (_PyST_IsClassClosureName(name)) {
+ return GLOBAL_IMPLICIT;
+ }
+ break;
+ }
+ *ste = parent;
+ scope = _PyST_GetScope(*ste, name);
+ RETURN_IF_ERROR(scope);
+ }
+ return scope;
+}
+
int
_PyCompile_GetRefType(compiler *c, PyObject *name)
{
- if (c->u->u_scope_type == COMPILE_SCOPE_CLASS &&
- (_PyUnicode_EqualToASCIIString(name, "__class__") ||
- _PyUnicode_EqualToASCIIString(name, "__classdict__") ||
- _PyUnicode_EqualToASCIIString(name, "__conditional_annotations__"))) {
+ if (c->u->u_scope_type == COMPILE_SCOPE_CLASS &&
_PyST_IsClassClosureName(name)) {
return CELL;
}
PySTEntryObject *ste = c->u->u_ste;
- int scope = _PyST_GetScope(ste, name);
+ int scope = compiler_resolve_inlined_free(&ste, name);
+ RETURN_IF_ERROR(scope);
if (scope == 0) {
PyErr_Format(PyExc_SystemError,
"_PyST_GetScope(name=%R) failed: "
@@ -1007,13 +1108,18 @@ _PyCompile_StaticAttributesAsTuple(compiler *c)
}
int
-_PyCompile_ResolveNameop(compiler *c, PyObject *mangled, int scope,
+_PyCompile_ResolveNameop(compiler *c, PyObject *mangled,
_PyCompile_optype *optype, Py_ssize_t *arg)
{
PyObject *dict = c->u->u_metadata.u_names;
*optype = COMPILE_OP_NAME;
- assert(scope >= 0);
+ PySTEntryObject *ste = c->u->u_ste;
+ assert(ste != NULL);
+
+ int scope = compiler_resolve_inlined_free(&ste, mangled);
+ RETURN_IF_ERROR(scope);
+
switch (scope) {
case FREE:
dict = c->u->u_metadata.u_freevars;
@@ -1024,24 +1130,24 @@ _PyCompile_ResolveNameop(compiler *c, PyObject
*mangled, int scope,
*optype = COMPILE_OP_DEREF;
break;
case LOCAL:
- if (_PyST_IsFunctionLike(c->u->u_ste)) {
+ /* Inlined comprehensions isolate their locals as FAST, even when
+ * nested in class or module scope. */
+ if (_PyST_IsFunctionLike(ste) || ste->ste_type ==
InlinedComprehensionBlock) {
*optype = COMPILE_OP_FAST;
}
- else {
- PyObject *item;
- RETURN_IF_ERROR(PyDict_GetItemRef(c->u->u_metadata.u_fasthidden,
mangled,
- &item));
- if (item == Py_True) {
- *optype = COMPILE_OP_FAST;
- }
- Py_XDECREF(item);
- }
break;
- case GLOBAL_IMPLICIT:
- if (_PyST_IsFunctionLike(c->u->u_ste)) {
+ case GLOBAL_IMPLICIT: {
+ /* Opcode depends on the enclosing non-inlined scope. */
+ PySTEntryObject *enclosing = ste;
+ while (enclosing->ste_parent != NULL) {
+ assert(enclosing->ste_type == InlinedComprehensionBlock);
+ enclosing = enclosing->ste_parent;
+ }
+ if (_PyST_IsFunctionLike(enclosing)) {
*optype = COMPILE_OP_GLOBAL;
}
break;
+ }
case GLOBAL_EXPLICIT:
*optype = COMPILE_OP_GLOBAL;
break;
@@ -1049,128 +1155,33 @@ _PyCompile_ResolveNameop(compiler *c, PyObject
*mangled, int scope,
/* scope can be 0 */
break;
}
+ /* XXX Handle __doc__ and the like better */
+ assert(scope || PyUnicode_READ_CHAR(mangled, 0) == '_');
if (*optype != COMPILE_OP_FAST) {
*arg = _PyCompile_DictAddObj(dict, mangled);
RETURN_IF_ERROR(*arg);
}
- return SUCCESS;
+ return scope;
}
int
-_PyCompile_TweakInlinedComprehensionScopes(compiler *c, location loc,
- PySTEntryObject *entry,
-
_PyCompile_InlinedComprehensionState *state)
+_PyCompile_EnterInlinedComprehensionScope(compiler *c, PySTEntryObject *entry,
+ _PyCompile_InlinedComprehensionState
*state)
{
- int in_class_block = (c->u->u_ste->ste_type == ClassBlock) &&
!c->u->u_in_inlined_comp;
- c->u->u_in_inlined_comp++;
-
- PyObject *k, *v;
- Py_ssize_t pos = 0;
- while (PyDict_Next(entry->ste_symbols, &pos, &k, &v)) {
- long symbol = PyLong_AsLong(v);
- assert(symbol >= 0 || PyErr_Occurred());
- RETURN_IF_ERROR(symbol);
- long scope = SYMBOL_TO_SCOPE(symbol);
-
- long outsymbol = _PyST_GetSymbol(c->u->u_ste, k);
- RETURN_IF_ERROR(outsymbol);
- long outsc = SYMBOL_TO_SCOPE(outsymbol);
-
- // If a name has different scope inside than outside the comprehension,
- // we need to temporarily handle it with the right scope while
- // compiling the comprehension. If it's free in the comprehension
- // scope, no special handling; it should be handled the same as the
- // enclosing scope. (If it's free in outer scope and cell in inner
- // scope, we can't treat it as both cell and free in the same function,
- // but treating it as free throughout is fine; it's *_DEREF
- // either way.)
- if ((scope != outsc && scope != FREE && !(scope == CELL && outsc ==
FREE))
- || in_class_block) {
- if (state->temp_symbols == NULL) {
- state->temp_symbols = PyDict_New();
- if (state->temp_symbols == NULL) {
- return ERROR;
- }
- }
- // update the symbol to the in-comprehension version and save
- // the outer version; we'll restore it after running the
- // comprehension
- if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) {
- return ERROR;
- }
- PyObject *outv = PyLong_FromLong(outsymbol);
- if (outv == NULL) {
- return ERROR;
- }
- int res = PyDict_SetItem(state->temp_symbols, k, outv);
- Py_DECREF(outv);
- RETURN_IF_ERROR(res);
- }
- // locals handling for names bound in comprehension (DEF_LOCAL |
- // DEF_NONLOCAL occurs in assignment expression to nonlocal)
- if ((symbol & DEF_LOCAL && !(symbol & DEF_NONLOCAL)) ||
in_class_block) {
- if (!_PyST_IsFunctionLike(c->u->u_ste)) {
- // non-function scope: override this name to use fast locals
- PyObject *orig;
- if (PyDict_GetItemRef(c->u->u_metadata.u_fasthidden, k, &orig)
< 0) {
- return ERROR;
- }
- assert(orig == NULL || orig == Py_True || orig == Py_False);
- if (orig != Py_True) {
- if (PyDict_SetItem(c->u->u_metadata.u_fasthidden, k,
Py_True) < 0) {
- Py_XDECREF(orig);
- return ERROR;
- }
- if (state->fast_hidden == NULL) {
- state->fast_hidden = PySet_New(NULL);
- if (state->fast_hidden == NULL) {
- Py_XDECREF(orig);
- return ERROR;
- }
- }
- if (PySet_Add(state->fast_hidden, k) < 0) {
- Py_XDECREF(orig);
- return ERROR;
- }
- }
- Py_XDECREF(orig);
- }
- }
- }
+ assert(state->saved_ste == NULL);
+ state->saved_ste = c->u->u_ste;
+ c->u->u_ste = (PySTEntryObject *)Py_NewRef(entry);
return SUCCESS;
}
int
-_PyCompile_RevertInlinedComprehensionScopes(compiler *c, location loc,
-
_PyCompile_InlinedComprehensionState *state)
+_PyCompile_ExitInlinedComprehensionScope(compiler *c,
+ _PyCompile_InlinedComprehensionState
*state)
{
- c->u->u_in_inlined_comp--;
- if (state->temp_symbols) {
- PyObject *k, *v;
- Py_ssize_t pos = 0;
- while (PyDict_Next(state->temp_symbols, &pos, &k, &v)) {
- if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v)) {
- return ERROR;
- }
- }
- Py_CLEAR(state->temp_symbols);
- }
- if (state->fast_hidden) {
- while (PySet_Size(state->fast_hidden) > 0) {
- PyObject *k = PySet_Pop(state->fast_hidden);
- if (k == NULL) {
- return ERROR;
- }
- // we set to False instead of clearing, so we can track which names
- // were temporarily fast-locals and should use CO_FAST_HIDDEN
- if (PyDict_SetItem(c->u->u_metadata.u_fasthidden, k, Py_False)) {
- Py_DECREF(k);
- return ERROR;
- }
- Py_DECREF(k);
- }
- Py_CLEAR(state->fast_hidden);
- }
+ assert(state->saved_ste != NULL);
+ Py_DECREF(c->u->u_ste);
+ c->u->u_ste = state->saved_ste;
+ state->saved_ste = NULL;
return SUCCESS;
}
@@ -1362,12 +1373,6 @@ _PyCompile_ScopeType(compiler *c)
return c->u->u_scope_type;
}
-int
-_PyCompile_IsInInlinedComp(compiler *c)
-{
- return c->u->u_in_inlined_comp;
-}
-
PyObject *
_PyCompile_Qualname(compiler *c)
{
@@ -1513,10 +1518,7 @@ _PyCompile_OptimizeAndAssemble(compiler *c, int addNone)
PyObject *filename = c->c_filename;
int code_flags = compute_code_flags(c);
- if (code_flags < 0) {
- return NULL;
- }
-
+ assert(code_flags >= 0);
if (_PyCodegen_AddReturnAtEnd(c, addNone) < 0) {
return NULL;
}
diff --git a/Python/symtable.c b/Python/symtable.c
index 8da04b40e8ad142..b97778ada20c04b 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -5,8 +5,10 @@
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_symtable.h" // PySTEntryObject
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString
+#include "setobject.h"
#include <stddef.h> // offsetof()
+#include <stdint.h>
// Set this to 1 to dump all symtables to stdout for debugging
@@ -89,6 +91,12 @@
#define IS_ASYNC_DEF(st) ((st)->st_cur->ste_type == FunctionBlock &&
(st)->st_cur->ste_coroutine)
+static int
+ste_uses_fast_locals(PySTEntryObject *ste)
+{
+ return _PyST_IsFunctionLike(ste) || ste->ste_type ==
InlinedComprehensionBlock;
+}
+
static PySTEntryObject *
ste_new(struct symtable *st, identifier name, _Py_block_ty block,
void *key, _Py_SourceLocation loc)
@@ -128,14 +136,14 @@ ste_new(struct symtable *st, identifier name,
_Py_block_ty block,
if (st->st_cur != NULL &&
(st->st_cur->ste_nested ||
- _PyST_IsFunctionLike(st->st_cur)))
+ ste_uses_fast_locals(st->st_cur)))
ste->ste_nested = 1;
ste->ste_generator = 0;
ste->ste_coroutine = 0;
ste->ste_comprehension = NoComprehension;
ste->ste_returns_value = 0;
ste->ste_needs_class_closure = 0;
- ste->ste_comp_inlined = 0;
+ ste->ste_parent = (block == InlinedComprehensionBlock) ? st->st_cur : NULL;
ste->ste_comp_iter_target = 0;
ste->ste_can_see_class_scope = 0;
ste->ste_comp_iter_expr = 0;
@@ -295,6 +303,7 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject*
prefix)
case TypeVariableBlock: blocktype = "TypeVariableBlock"; break;
case TypeAliasBlock: blocktype = "TypeAliasBlock"; break;
case TypeParametersBlock: blocktype = "TypeParametersBlock"; break;
+ case InlinedComprehensionBlock: blocktype =
"InlinedComprehensionBlock"; break;
}
const char *comptype = "";
switch (ste->ste_comprehension) {
@@ -308,7 +317,7 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject*
prefix)
(
"%U=== Symtable for %U ===\n"
"%U%s%s\n"
- "%U%s%s%s%s%s%s%s%s%s%s%s\n"
+ "%U%s%s%s%s%s%s%s%s%s%s\n"
"%Ulineno: %d col_offset: %d\n"
"%U--- Symbols ---\n"
),
@@ -326,7 +335,6 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject*
prefix)
ste->ste_returns_value ? " returns_value" : "",
ste->ste_needs_class_closure ? " needs_class_closure" : "",
ste->ste_needs_classdict ? " needs_classdict" : "",
- ste->ste_comp_inlined ? " comp_inlined" : "",
ste->ste_comp_iter_target ? " comp_iter_target" : "",
ste->ste_can_see_class_scope ? " can_see_class_scope" : "",
prefix,
@@ -353,7 +361,6 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject*
prefix)
if (flags & DEF_ANNOT) printf(" DEF_ANNOT");
if (flags & DEF_COMP_ITER) printf(" DEF_COMP_ITER");
if (flags & DEF_TYPE_PARAM) printf(" DEF_TYPE_PARAM");
- if (flags & DEF_COMP_CELL) printf(" DEF_COMP_CELL");
switch (scope) {
case LOCAL: printf(" LOCAL"); break;
case GLOBAL_EXPLICIT: printf(" GLOBAL_EXPLICIT"); break;
@@ -541,7 +548,7 @@ _PyST_GetSymbol(PySTEntryObject *ste, PyObject *name)
if (PyDict_GetItemRef(ste->ste_symbols, name, &v) < 0) {
return -1;
}
- if (!v) {
+ if (v == NULL) {
return 0;
}
long symbol = PyLong_AsLong(v);
@@ -575,6 +582,14 @@ _PyST_IsFunctionLike(PySTEntryObject *ste)
|| ste->ste_type == TypeParametersBlock;
}
+int
+_PyST_IsClassClosureName(PyObject *name)
+{
+ return _PyUnicode_EqualToASCIIString(name, "__class__")
+ || _PyUnicode_EqualToASCIIString(name, "__classdict__")
+ || _PyUnicode_EqualToASCIIString(name, "__conditional_annotations__");
+}
+
static int
error_at_directive(PySTEntryObject *ste, PyObject *name)
{
@@ -784,12 +799,22 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes,
PyObject *name, long flags,
return 1;
}
+/* See InternalDocs/inlined_comprehensions.md. */
+
static int
is_free_in_any_child(PySTEntryObject *entry, PyObject *key)
{
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(entry->ste_children); i++) {
PySTEntryObject *child_ste = (PySTEntryObject *)PyList_GET_ITEM(
entry->ste_children, i);
+ if (child_ste->ste_type == InlinedComprehensionBlock) {
+ /* this is the same scope, so we check its children */
+ int nested = is_free_in_any_child(child_ste, key);
+ if (nested != 0) {
+ return nested;
+ }
+ continue;
+ }
long scope = _PyST_GetScope(child_ste, key);
if (scope < 0) {
return -1;
@@ -802,109 +827,115 @@ is_free_in_any_child(PySTEntryObject *entry, PyObject
*key)
}
static int
-inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
- PyObject *scopes, PyObject *comp_free,
- PyObject *inlined_cells)
+symtable_add_flag(PyObject *dict, PyObject *name, int flag)
+{
+ PyObject *o = PyDict_GetItemWithError(dict, name);
+ long val;
+ if (o != NULL) {
+ val = PyLong_AsLong(o);
+ if (val == -1 && PyErr_Occurred()) {
+ return 0;
+ }
+ val |= flag;
+ }
+ else if (PyErr_Occurred()) {
+ return 0;
+ }
+ else {
+ val = flag;
+ }
+ o = PyLong_FromLong(val);
+ if (o == NULL) {
+ return 0;
+ }
+ int rc = PyDict_SetItem(dict, name, o);
+ Py_DECREF(o);
+ return rc >= 0;
+}
+
+static int
+finalize_inlined_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
+ PyObject *comp_free, PyObject *outer_newfree,
+ PyObject *inlined_cells)
{
PyObject *k, *v;
Py_ssize_t pos = 0;
- int remove_dunder_class = 0;
- int remove_dunder_classdict = 0;
- int remove_dunder_cond_annotations = 0;
+
+ assert(comp->ste_type == InlinedComprehensionBlock);
+ assert(comp->ste_parent != NULL);
while (PyDict_Next(comp->ste_symbols, &pos, &k, &v)) {
- // skip comprehension parameter
long comp_flags = PyLong_AsLong(v);
if (comp_flags == -1 && PyErr_Occurred()) {
- return 0;
- }
- if (comp_flags & DEF_PARAM) {
- assert(_PyUnicode_EqualToASCIIString(k, ".0"));
- continue;
+ goto error;
}
int scope = SYMBOL_TO_SCOPE(comp_flags);
- int only_flags = comp_flags & ((1 << SCOPE_OFFSET) - 1);
- if (scope == CELL || only_flags & DEF_COMP_CELL) {
+ if (scope == CELL) {
if (PySet_Add(inlined_cells, k) < 0) {
- return 0;
+ goto error;
}
}
PyObject *existing = PyDict_GetItemWithError(ste->ste_symbols, k);
if (existing == NULL && PyErr_Occurred()) {
- return 0;
+ goto error;
}
- // __class__, __classdict__ and __conditional_annotations__ are
- // not allowed to be free through a class scope (see
- // drop_class_free) unless children scopes need it
+ // These names are not allowed to be free through a class (see
+ // drop_class_free) unless a nested child needs them. Keep FREE
+ // on this table; compile treats the load as a global.
if (scope == FREE && ste->ste_type == ClassBlock &&
- (_PyUnicode_EqualToASCIIString(k, "__class__") ||
- _PyUnicode_EqualToASCIIString(k, "__classdict__") ||
- _PyUnicode_EqualToASCIIString(k,
"__conditional_annotations__"))) {
- scope = GLOBAL_IMPLICIT;
+ _PyST_IsClassClosureName(k)) {
int child_needs_free = is_free_in_any_child(comp, k);
if (child_needs_free < 0) {
- return 0;
+ goto error;
}
if (!child_needs_free) {
if (PySet_Discard(comp_free, k) < 0) {
- return 0;
+ goto error;
}
}
- if (_PyUnicode_EqualToASCIIString(k, "__class__")) {
- remove_dunder_class = 1;
- }
- else if (_PyUnicode_EqualToASCIIString(k,
"__conditional_annotations__")) {
- remove_dunder_cond_annotations = 1;
- }
- else {
- remove_dunder_classdict = 1;
- }
- }
- if (!existing) {
- // name does not exist in scope, copy from comprehension
- assert(scope != FREE || PySet_Contains(comp_free, k) == 1);
- PyObject *v_flags = PyLong_FromLong(only_flags);
- if (v_flags == NULL) {
- return 0;
- }
- int ok = PyDict_SetItem(ste->ste_symbols, k, v_flags);
- Py_DECREF(v_flags);
- if (ok < 0) {
- return 0;
- }
- SET_SCOPE(scopes, k, scope);
+ continue;
}
- else {
+ int is_def_bound = 0;
+ if (existing) {
long flags = PyLong_AsLong(existing);
if (flags == -1 && PyErr_Occurred()) {
- return 0;
+ goto error;
}
- if ((flags & DEF_BOUND) && ste->ste_type != ClassBlock) {
- // free vars in comprehension that are locals in outer scope
can
- // now simply be locals, unless they are free in comp children,
- // or if the outer scope is a class block
- int ok = is_free_in_any_child(comp, k);
- if (ok < 0) {
- return 0;
+ is_def_bound = flags & DEF_BOUND;
+ }
+ // Loads here are in the enclosing compilation unit.
+ if (scope == FREE && (comp_flags & USE) &&
+ !symtable_add_flag(ste->ste_symbols, k, USE)) {
+ goto error;
+ }
+ if (is_def_bound && ste->ste_type != ClassBlock) {
+ // free vars in comprehension that are locals in outer scope can
+ // now simply be locals, unless they are free in comp children,
+ // needed as cells by sibling nested scopes, or if the outer
+ // scope is a class block
+ int ok = is_free_in_any_child(comp, k);
+ if (ok < 0) {
+ goto error;
+ }
+ if (!ok) {
+ int in_newfree = PySet_Contains(outer_newfree, k);
+ if (in_newfree < 0) {
+ goto error;
}
- if (!ok) {
+ if (!in_newfree) {
if (PySet_Discard(comp_free, k) < 0) {
- return 0;
+ goto error;
}
}
}
}
- }
- if (remove_dunder_class && PyDict_DelItemString(comp->ste_symbols,
"__class__") < 0) {
- return 0;
- }
- if (remove_dunder_classdict && PyDict_DelItemString(comp->ste_symbols,
"__classdict__") < 0) {
- return 0;
- }
- if (remove_dunder_cond_annotations &&
PyDict_DelItemString(comp->ste_symbols, "__conditional_annotations__") < 0) {
- return 0;
+ else if (!existing) {
+ assert(scope != FREE || PySet_Contains(comp_free, k) == 1);
+ }
}
return 1;
+error:
+ return 0;
}
#undef SET_SCOPE
@@ -992,7 +1023,7 @@ drop_class_free(PySTEntryObject *ste, PyObject *free)
static int
update_symbols(PyObject *symbols, PyObject *scopes,
PyObject *bound, PyObject *free,
- PyObject *inlined_cells, int classflag)
+ int classflag)
{
PyObject *name = NULL, *itr = NULL;
PyObject *v = NULL, *v_scope = NULL, *v_new = NULL, *v_free = NULL;
@@ -1004,13 +1035,6 @@ update_symbols(PyObject *symbols, PyObject *scopes,
if (flags == -1 && PyErr_Occurred()) {
return 0;
}
- int contains = PySet_Contains(inlined_cells, name);
- if (contains < 0) {
- return 0;
- }
- if (contains) {
- flags |= DEF_COMP_CELL;
- }
if (PyDict_GetItemRef(scopes, name, &v_scope) < 0) {
return 0;
}
@@ -1158,7 +1182,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
ClassBlocks, the bound and global names are initialized
before analyzing names, because class bindings aren't
visible in methods. For other blocks, they are initialized
- after names are analyzed.
+ after this block's declarations are recorded.
*/
/* TODO(jhylton): Package these dicts in a struct so that we
@@ -1176,7 +1200,6 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
inlined_cells = PySet_New(NULL);
if (!inlined_cells)
goto error;
-
/* Class namespace has no effect on names visible in
nested functions, so populate the global and bound
sets to be passed to child blocks before analyzing
@@ -1197,11 +1220,17 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
}
}
+ /* Record bindings and global/nonlocal declarations first so child
+ blocks see this scope's locals. Uses are classified after children
+ so inlined comprehensions can add USE flags to this table. */
while (PyDict_Next(ste->ste_symbols, &pos, &name, &v)) {
long flags = PyLong_AsLong(v);
if (flags == -1 && PyErr_Occurred()) {
goto error;
}
+ if (!(flags & (DEF_GLOBAL | DEF_NONLOCAL | DEF_BOUND))) {
+ continue;
+ }
if (!analyze_name(ste, scopes, name, flags,
bound, local, free, global, type_params,
class_entry))
goto error;
@@ -1210,7 +1239,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
/* Populate global and bound sets to be passed to children. */
if (ste->ste_type != ClassBlock) {
/* Add function locals to bound set */
- if (_PyST_IsFunctionLike(ste)) {
+ if (ste_uses_fast_locals(ste)) {
temp = PyNumber_InPlaceOr(newbound, local);
if (!temp)
goto error;
@@ -1262,24 +1291,20 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
}
}
- // we inline all non-generator-expression comprehensions,
- // except those in annotation scopes that are nested in classes
- int inline_comp =
- entry->ste_comprehension &&
- !entry->ste_generator &&
- !ste->ste_can_see_class_scope;
-
+ // Finalize inlined children before classifying this block's uses
+ // and analyze_cells, so their loads are USE here and do not force
+ // a cell unless a real nested unit needs one.
if (!analyze_child_block(entry, newbound, newfree, newglobal,
type_params, new_class_entry, &child_free))
{
goto error;
}
- if (inline_comp) {
- if (!inline_comprehension(ste, entry, scopes, child_free,
inlined_cells)) {
+ if (entry->ste_type == InlinedComprehensionBlock) {
+ if (!finalize_inlined_comprehension(ste, entry, child_free,
newfree,
+ inlined_cells)) {
Py_DECREF(child_free);
goto error;
}
- entry->ste_comp_inlined = 1;
}
temp = PyNumber_InPlaceOr(newfree, child_free);
Py_DECREF(child_free);
@@ -1288,30 +1313,36 @@ analyze_block(PySTEntryObject *ste, PyObject *bound,
PyObject *free,
Py_DECREF(temp);
}
- /* Splice children of inlined comprehensions into our children list */
- for (i = PyList_GET_SIZE(ste->ste_children) - 1; i >= 0; --i) {
- PyObject* c = PyList_GET_ITEM(ste->ste_children, i);
- PySTEntryObject* entry;
- assert(c && PySTEntry_Check(c));
- entry = (PySTEntryObject*)c;
- if (entry->ste_comp_inlined &&
- PyList_SetSlice(ste->ste_children, i, i + 1,
- entry->ste_children) < 0)
- {
+ /* Complete the classification. */
+ pos = 0;
+ while (PyDict_Next(ste->ste_symbols, &pos, &name, &v)) {
+ long flags = PyLong_AsLong(v);
+ if (flags == -1 && PyErr_Occurred()) {
goto error;
}
+ int contains = PyDict_Contains(scopes, name);
+ if (contains < 0) {
+ goto error;
+ }
+ if (contains) {
+ continue;
+ }
+ if (!analyze_name(ste, scopes, name, flags,
+ bound, local, free, global, type_params,
class_entry))
+ goto error;
}
/* Check if any local variables must be converted to cell variables */
- if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree,
inlined_cells))
+ if (ste_uses_fast_locals(ste) && !analyze_cells(scopes, newfree,
inlined_cells)) {
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;
+ }
/* Records the results of the analysis in the symbol table entry */
- if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
inlined_cells,
+ if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
(ste->ste_type == ClassBlock) ||
ste->ste_can_see_class_scope))
goto error;
-
temp = PyNumber_InPlaceOr(free, newfree);
if (!temp)
goto error;
@@ -1515,14 +1546,12 @@ symtable_add_def_helper(struct symtable *st, PyObject
*name, int flag, struct _s
_Py_SourceLocation loc)
{
PyObject *o;
- PyObject *dict;
- long val;
+ long val = 0;
PyObject *mangled = _Py_MaybeMangle(st->st_private, st->st_cur, name);
if (!mangled)
return 0;
- dict = ste->ste_symbols;
- if ((o = PyDict_GetItemWithError(dict, mangled))) {
+ if ((o = PyDict_GetItemWithError(ste->ste_symbols, mangled))) {
val = PyLong_AsLong(o);
if (val == -1 && PyErr_Occurred()) {
goto error;
@@ -1538,62 +1567,40 @@ symtable_add_def_helper(struct symtable *st, PyObject
*name, int flag, struct _s
SET_ERROR_LOCATION(st->st_filename, loc);
goto error;
}
- val |= flag;
}
else if (PyErr_Occurred()) {
goto error;
}
- else {
- val = flag;
- }
+
+ int to_add = flag;
if (ste->ste_comp_iter_target) {
/* This name is an iteration variable in a comprehension,
* so check for a binding conflict with any named expressions.
* Otherwise, mark it as an iteration variable so subsequent
* named expressions can check for conflicts.
*/
- if (val & (DEF_GLOBAL | DEF_NONLOCAL)) {
+ if ((val | flag) & (DEF_GLOBAL | DEF_NONLOCAL)) {
PyErr_Format(PyExc_SyntaxError,
NAMED_EXPR_COMP_INNER_LOOP_CONFLICT, name);
SET_ERROR_LOCATION(st->st_filename, loc);
goto error;
}
- val |= DEF_COMP_ITER;
+ to_add |= DEF_COMP_ITER;
}
- o = PyLong_FromLong(val);
- if (o == NULL)
- goto error;
- if (PyDict_SetItem(dict, mangled, o) < 0) {
- Py_DECREF(o);
+ if (!symtable_add_flag(ste->ste_symbols, mangled, to_add)) {
goto error;
}
- Py_DECREF(o);
if (flag & DEF_PARAM) {
if (PyList_Append(ste->ste_varnames, mangled) < 0)
goto error;
- } else if (flag & DEF_GLOBAL) {
+ }
+ else if (flag & DEF_GLOBAL) {
/* XXX need to update DEF_GLOBAL for other flags too;
perhaps only DEF_FREE_GLOBAL */
- val = 0;
- if ((o = PyDict_GetItemWithError(st->st_global, mangled))) {
- val = PyLong_AsLong(o);
- if (val == -1 && PyErr_Occurred()) {
- goto error;
- }
- }
- else if (PyErr_Occurred()) {
+ if (!symtable_add_flag(st->st_global, mangled, flag)) {
goto error;
}
- val |= flag;
- o = PyLong_FromLong(val);
- if (o == NULL)
- goto error;
- if (PyDict_SetItem(st->st_global, mangled, o) < 0) {
- Py_DECREF(o);
- goto error;
- }
- Py_DECREF(o);
}
Py_DECREF(mangled);
return 1;
@@ -2582,7 +2589,7 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
return 0;
}
if (!allows_top_level_await(st)) {
- if (!_PyST_IsFunctionLike(st->st_cur)) {
+ if (!ste_uses_fast_locals(st->st_cur)) {
PyErr_SetString(PyExc_SyntaxError,
"'await' outside function");
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
@@ -2660,7 +2667,7 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
}
/* Special-case super: it counts as a use of __class__ */
if (e->v.Name.ctx == Load &&
- _PyST_IsFunctionLike(st->st_cur) &&
+ ste_uses_fast_locals(st->st_cur) &&
_PyUnicode_EqualToASCIIString(e->v.Name.id, "super")) {
if (!symtable_add_def(st, &_Py_ID(__class__), USE,
LOCATION(e)))
return 0;
@@ -3103,9 +3110,16 @@ symtable_handle_comprehension(struct symtable *st,
expr_ty e,
st->st_cur->ste_comp_iter_expr++;
VISIT(st, expr, outermost->iter);
st->st_cur->ste_comp_iter_expr--;
+
+ /* Non-generator comprehensions are inlined into the enclosing compilation
+ * unit (including generator expressions), except in annotation scopes
+ * that can see a class. */
+ int will_inline = !is_generator && !st->st_cur->ste_can_see_class_scope;
+ _Py_block_ty block = will_inline ? InlinedComprehensionBlock :
FunctionBlock;
+
/* Create comprehension scope for the rest */
if (!scope_name ||
- !symtable_enter_block(st, scope_name, FunctionBlock, (void *)e,
LOCATION(e))) {
+ !symtable_enter_block(st, scope_name, block, (void *)e, LOCATION(e))) {
return 0;
}
switch(e->kind) {
@@ -3126,8 +3140,8 @@ symtable_handle_comprehension(struct symtable *st,
expr_ty e,
st->st_cur->ste_coroutine = 1;
}
- /* Outermost iter is received as an argument */
- if (!symtable_implicit_arg(st, 0)) {
+ /* Outermost iter is received as an argument for non-inlined comps */
+ if (!will_inline && !symtable_implicit_arg(st, 0)) {
symtable_exit_block(st);
return 0;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]