Skip to content

gh-150700: Fix class-scope inline comprehensions when nested scopes reference __class__ and friends#150735

Merged
carljm merged 7 commits into
python:mainfrom
johnslavik:gh-150700-class-scope-comprehension-with-lambda-raises-syste
Jun 9, 2026
Merged

gh-150700: Fix class-scope inline comprehensions when nested scopes reference __class__ and friends#150735
carljm merged 7 commits into
python:mainfrom
johnslavik:gh-150700-class-scope-comprehension-with-lambda-raises-syste

Conversation

@johnslavik

@johnslavik johnslavik commented Jun 1, 2026

Copy link
Copy Markdown
Member

Follow up to GH-120295.

In inline_comprehension(), when __class__ / __classdict__ / __conditional_annotations__ appears as FREE in a comprehension's symbol table because a nested scope captured it (e.g. nested lambdas), this name is still discarded from comp_free unconditionally. This prevents drop_class_free() from seeing it, so the appropriate ste_needs_(...) flag is never set on the enclosing class. That leads to codegen_make_closure() throwing SystemError when it couldn't find __class__ / __classdict__ /
__conditional_annotations__ in the class's cellvars.

From now on we just discard from comp_free when no child scope (e.g. a lambda) still needs the name as FREE. When a child scope does need it, keep it in comp_free so drop_class_free() can set the appropriate flag and the class creates the implicit cell.

…_class__` and friends

In `inline_comprehension()`, when `__class__` / `__classdict__` /
`__conditional_annotations__` appears as `FREE` in a comprehension's
symbol table because a nested scope captured it (e.g. nested lambdas),
this name is still discarded from `comp_free` unconditionally.

This prevents `drop_class_free()` from seeing it, so the appropriate
`ste_needs_(...)` flag is never set on the enclosing class.
That leads to `codegen_make_closure()` throwing `SystemError` when it
couldn't find `__class__` / `__classdict__` /
`__conditional_annotations__` in the class's cellvars.

From now on we just discard from `comp_free` when no child scope
(e.g. a lambda) still needs the name as `FREE`. When a child scope does
need it, keep it in `comp_free` so `drop_class_free()` can set the
appropriate flag and the class creates the implicit cell.
@johnslavik johnslavik added needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Jun 1, 2026
@johnslavik

Copy link
Copy Markdown
Member Author

Updating branch to see if it fixes doc build issue.

Comment thread Lib/test/test_listcomps.py Outdated

@carljm carljm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Might be nice to have some tests that validate more than "compilation doesn't fail" but actually check we are getting the right value out of __class__.

@johnslavik

Copy link
Copy Markdown
Member Author

Might be nice to have some tests that validate more than "compilation doesn't fail" but actually check we are getting the right value out of __class__.

Excellent, added.

@carljm

carljm commented Jun 9, 2026

Copy link
Copy Markdown
Member

Thanks for the fix!

@carljm carljm merged commit ce916dc into python:main Jun 9, 2026
54 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @johnslavik for the PR, and @carljm for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14, 3.15.
🐍🍒⛏🤖

@bedevere-app

bedevere-app Bot commented Jun 9, 2026

Copy link
Copy Markdown

GH-151211 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Jun 9, 2026
@bedevere-app

bedevere-app Bot commented Jun 9, 2026

Copy link
Copy Markdown

GH-151212 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Jun 9, 2026
carljm pushed a commit that referenced this pull request Jun 9, 2026
…copes reference `__class__` and friends (GH-150735) (#151212)

gh-150700: Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends (GH-150735)

* Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends

In `inline_comprehension()`, when `__class__` / `__classdict__` /
`__conditional_annotations__` appears as `FREE` in a comprehension's
symbol table because a nested scope captured it (e.g. nested lambdas),
this name is still discarded from `comp_free` unconditionally.

This prevents `drop_class_free()` from seeing it, so the appropriate
`ste_needs_(...)` flag is never set on the enclosing class.
That leads to `codegen_make_closure()` throwing `SystemError` when it
couldn't find `__class__` / `__classdict__` /
`__conditional_annotations__` in the class's cellvars.

From now on we just discard from `comp_free` when no child scope
(e.g. a lambda) still needs the name as `FREE`. When a child scope does
need it, keep it in `comp_free` so `drop_class_free()` can set the
appropriate flag and the class creates the implicit cell.

* Fix tests

* Fix typo

* Fix formatting

* Add test checking validity of `__class__` returned

* Prefer 'used' to 'deferred'
(cherry picked from commit ce916dc)

Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
@bedevere-bot

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot aarch64 Android 3.14 (tier-3) has failed when building commit 51e83ca.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/1794/builds/1170) and take a look at the build logs.
  4. Check if the failure is related to this commit (51e83ca) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/1794/builds/1170

Failed tests:

  • test_urllib2
  • test_urllibnet

Failed subtests:

  • test_ftp_error - test.test_urllib2.HandlerTests.test_ftp_error
  • test_getcode - test.test_urllibnet.urlopenNetworkTests.test_getcode

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 1529, in ftp_open
    host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllib2.py", line 798, in test_ftp_error
    urlopen("ftp://www.pythontest.net/")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 487, in open
    response = self._open(req, data)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 504, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
                              '_open', req)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 464, in _call_chain
    result = func(*args)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 1531, in ftp_open
    raise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllib2.py", line 800, in test_ftp_error
    self.assertEqual(raised.reason,
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
                     f"ftp error: {exception.args[0]}")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
 
----------------------------------------------------------------------
Ran 80 tests in 0.085s
 
FAILED (failures=1, skipped=3)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 1529, in ftp_open
    host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllib2.py", line 798, in test_ftp_error
    urlopen("ftp://www.pythontest.net/")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 487, in open
    response = self._open(req, data)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 504, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
                              '_open', req)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 464, in _call_chain
    result = func(*args)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/urllib/request.py", line 1531, in ftp_open
    raise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllib2.py", line 800, in test_ftp_error
    self.assertEqual(raised.reason,
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
                     f"ftp error: {exception.args[0]}")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
 
----------------------------------------------------------------------
Ran 80 tests in 0.047s
 
FAILED (failures=1, skipped=3)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllibnet.py", line 107, in test_getcode
    self.assertEqual(e.exception.code, 404)
                     ^^^^^^^^^^^^^^^^
AttributeError: 'URLError' object has no attribute 'code'
 
----------------------------------------------------------------------
Ran 12 tests in 0.026s
 
FAILED (errors=1, skipped=10)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_urllibnet.py", line 107, in test_getcode
    self.assertEqual(e.exception.code, 404)
                     ^^^^^^^^^^^^^^^^
AttributeError: 'URLError' object has no attribute 'code'
 
----------------------------------------------------------------------
Ran 12 tests in 0.018s
 
FAILED (errors=1, skipped=10)

carljm pushed a commit that referenced this pull request Jun 9, 2026
…copes reference `__class__` and friends (GH-150735) (#151211)

gh-150700: Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends (GH-150735)

* Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends

In `inline_comprehension()`, when `__class__` / `__classdict__` /
`__conditional_annotations__` appears as `FREE` in a comprehension's
symbol table because a nested scope captured it (e.g. nested lambdas),
this name is still discarded from `comp_free` unconditionally.

This prevents `drop_class_free()` from seeing it, so the appropriate
`ste_needs_(...)` flag is never set on the enclosing class.
That leads to `codegen_make_closure()` throwing `SystemError` when it
couldn't find `__class__` / `__classdict__` /
`__conditional_annotations__` in the class's cellvars.

From now on we just discard from `comp_free` when no child scope
(e.g. a lambda) still needs the name as `FREE`. When a child scope does
need it, keep it in `comp_free` so `drop_class_free()` can set the
appropriate flag and the class creates the implicit cell.

* Fix tests

* Fix typo

* Fix formatting

* Add test checking validity of `__class__` returned

* Prefer 'used' to 'deferred'
(cherry picked from commit ce916dc)

Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants