GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Annotation scopes containing nested scopes · Issue #109118 · python/cpython

Annotation scopes containing nested scopes #109118

New issue
Copy link
New issue
Copy link
Closed
#109196
Closed
Annotation scopes containing nested scopes#109118
#109196
Copy link
Assignees
JelleZijlstra
Labels
3.13only security fixesonly security fixestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@15r10nk
15r10nk
opened on Sep 7, 2023
Issue body actions

Bug report

Bug description:

The following code causes a SystemError during compilation.

class name_2[*name_5, name_3: int]:
    (name_3 := name_4)

    class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
        pass

output (Python 3.12.0rc2+):

SystemError: compiler_lookup_arg(name='name_3') with reftype=3 failed in <generic parameters of name_4>; freevars of code <genexpr>: ('name_3',)

removing the named-expression makes it worse and causes a crash

class name_2[*name_5, name_3: int]:

    class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
        pass

output (Python 3.12.0rc2+):

Traceback (most recent call last):
  File "/home/frank/projects/pysource-codegen/bug2.py", line 2, in <module>
    class name_2[*name_5, name_3: int]:
  File "/home/frank/projects/pysource-codegen/bug2.py", line 2, in <generic parameters of name_2>
    class name_2[*name_5, name_3: int]:
  File "/home/frank/projects/pysource-codegen/bug2.py", line 4, in name_2
    class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
  File "/home/frank/projects/pysource-codegen/bug2.py", line 4, in <generic parameters of name_4>
    class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
                                                       ^^^^^^
NameError: name 'name_0' is not defined
Modules/gcmodule.c:113: gc_decref: Assertion "gc_get_refs(g) > 0" failed: refcount is too small
Enable tracemalloc to get the memory block allocation traceback

object address  : 0x7f4c77059a30
object refcount : 1
object type     : 0x560ea4b6cfe0
object type name: dict
object repr     : {'__module__': '__main__', '__qualname__': 'name_2', '__type_params__': (name_5, name_3)}

Fatal Python error: _PyObject_AssertFailed: _PyObject_AssertFailed
Python runtime state: finalizing (tstate=0x0000560ea4cc9ba0)

Current thread 0x00007f4c7756c280 (most recent call first):
  Garbage-collecting
  <no Python frame>

side note:
The reason for the strange looking language constructs and variable names is that I generated this code. I found it while working on my pysource-codegen library.

CPython versions tested on:

3.12

Operating systems tested on:

Linux

Linked PRs

  • gh-109118: Fix runtime crash when NameError happens in PEP 695 function #109123
  • [3.12] gh-109118: Fix runtime crash when NameError happens in PEP 695 function (GH-109123) #109173
  • gh-109118: Disallow nested scopes within PEP 695 scopes within classes #109196
  • [3.12] gh-109118: Disallow nested scopes within PEP 695 scopes within classes (GH-109196) #109297
  • gh-109118: Allow lambdas in annotation scopes in classes #118019
  • gh-109118: Make comprehensions work within annotation scopes, but without inlining #118160

Activity

Next
  1. 15r10nk
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Sep 7, 2023
  2. JelleZijlstra
    self-assigned this
    on Sep 7, 2023
  3. AlexWaygood
    added
    3.12only security fixesonly security fixes
    3.13only security fixesonly security fixes
    on Sep 7, 2023
  4. JelleZijlstra commented on Sep 8, 2023

    @JelleZijlstra
    JelleZijlstra
    on Sep 8, 2023
    Member
    More actions

    Thanks, this is very helpful!

    Minimized version:

    class name_2[name_3]:
        name_3 = name_4
        class name_4[name_5]((name_3 for name_5 in name_0), name_3):
            pass

    The runtime crash when removing the name_3 = name_4 line is a bug in the interpreter that I will send a PR for. The compiler crash (SystemError) is a different bug that I haven't yet tracked down.

  5. JelleZijlstra
    added
    release-blocker
    on Sep 8, 2023
  6. ezio-melotti
    added this to Release and Deferred blockers 🚫on Sep 8, 2023
  7. github-project-automation
    moved this to Todo in Release and Deferred blockers 🚫on Sep 8, 2023
  8. JelleZijlstra
    added a commit that references this issue on Sep 8, 2023

    pythongh-109118: Fix runtime crash when NameError happens in PEP 695 …

    3c448e8
  9. JelleZijlstra commented on Sep 8, 2023

    @JelleZijlstra
    JelleZijlstra
    on Sep 8, 2023
    Member
    More actions

    The first thing to figure out with the compiler crash is how the code should behave, as class scopes are weird. We have a genexp within the type parameters of a generic class nested inside another generic class.

    Here's the test I wrote, demonstrating how this works if the inner class is not generic:

    +    def test_gen_exp_in_nested_class(self):
    +        class C[T]:
    +            T = "class"
    +            class Inner(make_base(T for _ in (1,)), make_base(T)):
    +                pass
    +        T, = C.__type_params__
    +        base1, base2 = C.Inner.__bases__
    +        self.assertEqual(list(base1.__arg__), [T])
    +        self.assertEqual(base2.__arg__, "class")
    +
    +    def test_gen_exp_in_nested_generic_class(self):
    +        class C[T]:
    +            T = "class"
    +            class Inner[U](make_base(T for _ in (1,)), make_base(T)):
    +                pass
    +        T, = C.__type_params__
    +        base1, base2, _ = C.Inner.__bases__
    +        self.assertEqual(list(base1.__arg__), [T])
    +        self.assertEqual(base2.__arg__, "class")
    +
    +
    +def make_base(arg):
    +    class Base:
    +        __arg__ = arg
    +    return Base
    +
    

    In short, the genexp should not see the definition in the outer class body, only the one in the generic parameters of the outer class. However, the other bases should see the definition in the class body. This is how it works now if the inner class is not generic, and this is correct.

    If the inner class is generic, the behavior should be analogous, but evidently there's something wrong in the bookkeeping.

  10. JelleZijlstra
    added 3 commits that reference this issue on Sep 9, 2023

    gh-109118: Fix runtime crash when NameError happens in PEP 695 functi…

    17f9941

    pythongh-109118: Fix runtime crash when NameError happens in PEP 695 …

    546f56d

    [3.12] pythongh-109118: Fix runtime crash when NameError happens in P…

    e28d114
  11. JelleZijlstra
    changed the title [-]SystemError/crash while compiling python code[/-] [+]SystemError with generator expression within PEP 695 scope within class[/+] on Sep 9, 2023
  12. JelleZijlstra commented on Sep 9, 2023

    @JelleZijlstra
    JelleZijlstra
    on Sep 9, 2023
    Member
    More actions

    Thinking about it more, I recommend that we should disallow nested scopes (comprehensions, generator expressions, and lambdas) within PEP 695 scopes (bases of generic classes, annotations of generic functions, values of type aliases) within classes. The normal scoping rules would produce very weird outcomes and would I think be difficult to implement. Also, disallowing them is a safe option this late in the Python 3.12 release cycle: if we can implement it correctly, we can always allow these constructs later for 3.13.

    We would disallow the following constructs:

    class C:
         type T = lambda: T  # SyntaxError
         type T = [T for T in T]  # SyntaxError
         type T[U]: (T for T in U)] = int  # SyntaxError
    
         def f[T](x: lambda: T): ...  # SyntaxError
         class I[T](T for T in T): ...  # SyntaxError

    All of these constructs would still be allowed by the interpreter outside of class scopes.

    Why do I say the outcomes are weird? Consider this code:

    def make_base(arg):
        class Base:
            __arg__ = arg
        return Base
    
    
    class C[T]:
        T = "class"
        U = "class"
        class Inner[U](make_base((T, U)), make_base((T, U) for _ in (1,))):
            pass

    There are four occurrences of T and U in the bases of Inner. What should they mean (referring to https://peps.python.org/pep-0695/#scoping-behavior)?

    • The first T is inside the annotation scope for Inner, but annotation scopes (unlike normal function scopes) see names defined in the enclosing class scope, so it should refer to the class-scoped variable "class".
    • The first U is also inside the annotation scope, so it should refer to the type parameter U for the inner class.
    • The second T is inside a generator expression that is in turn inside an annotation scope. It should not see the class scope, because genexps inside classes cannot normally see variables in that class scope (as discussed in List comprehensions now have access to the enclosing class scope #104374). Therefore, here T should refer to the type parameter for the outer generic class C.
    • The second U is also inside a genexp inside an annotation scope. I think the intuitive answer is that it should refer to the inner type parameter U. But that creates a weird result where the genexp can see the enclosing annotation scope, but not all names that are visible to that annotation scope. This is, as far as I can tell, unlike any other scope in Python, and it would require some changes to the abstractions in symtable.c to implement correctly.

    Why is it OK to disallow comprehensions/lambdas/genexps in PEP 695 scopes within classes?

    • This is new syntax, so we won't be breaking anyone's code by disallowing it. We can always allow this later.
    • There are few conceivable use cases for nested scopes within annotation scopes. Annotation scopes can include the following:
      • The right-hand side of type aliases. This should be a type, which cannot include a lambda or comprehension if you follow the standard type system.
      • The bounds and constraints of TypeVars. These are also supposed to be valid as types.
      • The parameter and return annotations of generic functions. Same.
      • The bases and keyword arguments of generic classes. While these could contain any code and therefore also comprehensions or lambdas, that's going to be very rare.

    I will submit a PR implementing this restriction.

    Reacted by Guido van Rossum
  13. 17 remaining items

  14. Yhg1s
    added a commit that references this issue on Sep 12, 2023

    [3.12] gh-109118: Disallow nested scopes within PEP 695 scopes within…

    8208657
  15. vstinner
    added a commit that references this issue on Sep 13, 2023

    pythongh-109118: Disallow nested scopes within PEP 695 scopes within …

    cc1f90e
  16. carljm
    closed this as completedon Sep 14, 2023
  17. github-project-automation
    moved this from In Progress to Done in Release and Deferred blockers 🚫on Sep 14, 2023
  18. JelleZijlstra commented on Apr 18, 2024

    @JelleZijlstra
    JelleZijlstra
    on Apr 18, 2024
    Member
    More actions

    Reopening this to consider changing some of the behavior for Python 3.13, where actual annotations will also be implemented using annotation scopes (PEP-649; cc @larryhastings).

    I now think the right behavior is that if a scope is nested in an annotation scope that is itself in a class scope, it can see names defined in the annotation scope, but not names defined in the enclosing class scope. After all, that's how generic methods currently work:

    T = "global T"
    U = "global U"
    class C:
        T = "class T"
        U = "class U"
        def meth[T](self):
            print(T, U)
    C().meth()

    This currently picks up the type variable for T and the global for U.

    This is also the natural behavior of the current implementation, at least for lambdas. I'll send a PR first to change the behavior for lambdas, then work on comprehensions.

    Reacted by Carl Meyer
  19. JelleZijlstra
    reopened this on Apr 18, 2024
  20. github-project-automation
    moved this from Done to In Progress in Release and Deferred blockers 🚫on Apr 18, 2024
  21. JelleZijlstra
    changed the title [-]SystemError with generator expression within PEP 695 scope within class[/-] [+]Annotation scopes containing nested scopes[/+] on Apr 18, 2024
  22. JelleZijlstra
    removed
    release-blocker
    3.12only security fixesonly security fixes
    on Apr 18, 2024
  23. JelleZijlstra
    added 3 commits that reference this issue on Apr 18, 2024

    pythongh-109118: Allow lambdas in annotation scopes in classes

    0da5933

    gh-109118: Allow lambdas in annotation scopes in classes (#118019)

    85f727c

    gh-109118: Make comprehensions work within annotation scopes, but wit…

    2326d6c
  24. JelleZijlstra
    closed this as completedon Apr 28, 2024
  25. github-project-automation
    moved this from In Progress to Done in Release and Deferred blockers 🚫on Apr 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @JelleZijlstra
    JelleZijlstra

Labels

3.13only security fixesonly security fixestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

  • Release and Deferred blockers 🚫
    Status
    Done
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.