Skip to content

global declaration in except has incorrect prior use #111123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
15r10nk opened this issue Oct 20, 2023 · 5 comments · Fixed by #111142
Closed

global declaration in except has incorrect prior use #111123

15r10nk opened this issue Oct 20, 2023 · 5 comments · Fixed by #111142
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@15r10nk
Copy link
Contributor

15r10nk commented Oct 20, 2023

Bug report

Bug description:

I think the global a has no prior use in this code (and pyright tells me the same). But I don't understand why cpython thinks it has a prior use.

a=5

def f():
    try:
        pass
    except:
        global a
    else:
        print(a)

output (Python 3.12.0):

  File "/home/frank/projects/pysource-playground/pysource-codegen/bug.py", line 8
    global a
    ^^^^^^^^
SyntaxError: name 'a' is used prior to global declaration

the following code has no syntax error:

a=5

def f():
    try:
        pass
    except:
        global a
    print(a)

I can also reproduce this issue in 3.7.

I also don't know what the exact semantic of global/nonlocal inside statements like if/while/try/... is. I would like to know more about it because I'm currently writing pysource-codegen where I generate such cases.

CPython versions tested on:

3.12

Operating systems tested on:

Linux

Linked PRs

@15r10nk 15r10nk added the type-bug An unexpected behavior, bug, or error label Oct 20, 2023
@AlexWaygood AlexWaygood added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Oct 20, 2023
@sobolevn
Copy link
Member

sobolevn commented Oct 20, 2023

I feel like this issue might be interesting to @iritkatriel and @pablogsal.

@iritkatriel
Copy link
Member

I think this is because symtable_visit_stmt visits the else clause before the handlers:

    case Try_kind:      
        VISIT_SEQ(st, stmt, s->v.Try.body);
        VISIT_SEQ(st, stmt, s->v.Try.orelse);
        VISIT_SEQ(st, excepthandler, s->v.Try.handlers);
        VISIT_SEQ(st, stmt, s->v.Try.finalbody);
        break;

@JelleZijlstra
Copy link
Member

The converse of this bug is that this code currently works (and prints 1):

a = 0

def f():
    try:
        1 / 0
    except:
        a = 1
    else:
        global a

f()
print(a)

With Irit's fix, this would instead be a SyntaxError.

I don't know if any code in existence would rely on this weird behavior, but we should probably fix this only on main (not the bugfix branches) and call out the change in behavior in What's New.

@15r10nk
Copy link
Contributor Author

15r10nk commented Oct 21, 2023

I agree @JelleZijlstra

I think I understood how the logic behind global works.

a=0

def f(cnd,value):
    if cnd:
        global a
    a=value

f(True,1)
print(a)
f(False,2)
print(a)

output (Python 3.12.0):

1
2

global is not affected by the actual order of execution. It is more "the line" in which it was declared.

The syntax error for prior use exists mainly to safe the user from thinking that he could put the global behind the use of some variable and to expect a different behavior.
Leaving out a lot of other wired uses like the if above.

A SyntaxWarning might be useful here, but I also don't know if this generated code is representative for real world problem. Hard to tell if such a SyntaxWarning could help someone.

@iritkatriel I saw your fix and I think TryStar is also affected by this bug.

@iritkatriel
Copy link
Member

@iritkatriel I saw your fix and I think TryStar is also affected by this bug.

Right, thanks.

15r10nk added a commit to 15r10nk/pysource-codegen that referenced this issue Nov 29, 2023
15r10nk added a commit to 15r10nk/pysource-codegen that referenced this issue Nov 29, 2023
15r10nk added a commit to 15r10nk/pysource-codegen that referenced this issue Nov 29, 2023
15r10nk added a commit to 15r10nk/pysource-codegen that referenced this issue Nov 29, 2023
aisk pushed a commit to aisk/cpython that referenced this issue Feb 11, 2024
Glyphack pushed a commit to Glyphack/cpython that referenced this issue Sep 2, 2024
ntBre added a commit to astral-sh/ruff that referenced this issue Apr 9, 2025
…odes before 3.13 (#17285)

Summary
--

This PR extends the documentation of the `LoadBeforeGlobalDeclaration`
check to specify the behavior on versions of Python before 3.13. Namely,
on Python 3.12, the `else` clause of a `try` statement is visited before
the `except` handlers:

```pycon
Python 3.12.9 (main, Feb 12 2025, 14:50:50) [Clang 19.1.6 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 10
>>> def g():
...     try:
...         1 / 0
...     except:
...         a = 1
...     else:
...         global a
...
>>> def f():
...     try:
...         pass
...     except:
...         global a
...     else:
...         print(a)
...
  File "<stdin>", line 5
SyntaxError: name 'a' is used prior to global declaration

```

The order is swapped on 3.13 (see
[CPython#111123](python/cpython#111123)):

```pycon
Python 3.13.2 (main, Feb  5 2025, 08:05:21) [GCC 14.2.1 20250128] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 10
... def g():
...     try:
...         1 / 0
...     except:
...         a = 1
...     else:
...         global a
...
  File "<python-input-0>", line 8
    global a
    ^^^^^^^^
SyntaxError: name 'a' is assigned to before global declaration
>>> def f():
...     try:
...         pass
...     except:
...         global a
...     else:
...         print(a)
...
>>>
```

The current implementation of PLE0118 is correct for 3.13 but not 3.12:
[playground](https://play.ruff.rs/d7467ea6-f546-4a76-828f-8e6b800694c9)
(it flags the first case regardless of Python version).

We decided to maintain this incorrect diagnostic for Python versions
before 3.13 because the pre-3.13 behavior is very unintuitive and
confirmed to be a bug, although the bug fix was not backported to
earlier versions. This can lead to false positives and false negatives
for pre-3.13 code, but we also expect that to be very rare, as
demonstrated by the ecosystem check (before the version-dependent check
was reverted here).

Test Plan
--

N/a
Glyphack pushed a commit to Glyphack/ruff that referenced this issue Apr 9, 2025
…odes before 3.13 (astral-sh#17285)

Summary
--

This PR extends the documentation of the `LoadBeforeGlobalDeclaration`
check to specify the behavior on versions of Python before 3.13. Namely,
on Python 3.12, the `else` clause of a `try` statement is visited before
the `except` handlers:

```pycon
Python 3.12.9 (main, Feb 12 2025, 14:50:50) [Clang 19.1.6 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 10
>>> def g():
...     try:
...         1 / 0
...     except:
...         a = 1
...     else:
...         global a
...
>>> def f():
...     try:
...         pass
...     except:
...         global a
...     else:
...         print(a)
...
  File "<stdin>", line 5
SyntaxError: name 'a' is used prior to global declaration

```

The order is swapped on 3.13 (see
[CPython#111123](python/cpython#111123)):

```pycon
Python 3.13.2 (main, Feb  5 2025, 08:05:21) [GCC 14.2.1 20250128] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 10
... def g():
...     try:
...         1 / 0
...     except:
...         a = 1
...     else:
...         global a
...
  File "<python-input-0>", line 8
    global a
    ^^^^^^^^
SyntaxError: name 'a' is assigned to before global declaration
>>> def f():
...     try:
...         pass
...     except:
...         global a
...     else:
...         print(a)
...
>>>
```

The current implementation of PLE0118 is correct for 3.13 but not 3.12:
[playground](https://play.ruff.rs/d7467ea6-f546-4a76-828f-8e6b800694c9)
(it flags the first case regardless of Python version).

We decided to maintain this incorrect diagnostic for Python versions
before 3.13 because the pre-3.13 behavior is very unintuitive and
confirmed to be a bug, although the bug fix was not backported to
earlier versions. This can lead to false positives and false negatives
for pre-3.13 code, but we also expect that to be very rare, as
demonstrated by the ecosystem check (before the version-dependent check
was reverted here).

Test Plan
--

N/a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants