-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[pylint] Detect global declarations in module scope (PLE0118)
#17411
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
Conversation
Summary -- While going through the syntax errors in [this comment], I was surprised to see the error `name 'x' is assigned to before global declaration`, which corresponds to [load-before-global-declaration (PLE0118)] and has also been reimplemented as a syntax error (#17135). However, it looks like neither of the implementations consider `global` declarations in the top-level module scope, which is a syntax error in CPython: ```python x = None global x ``` ```shell > python -m compileall -f try.py Compiling 'try.py'... *** File "try.py", line 2 global x ^^^^^^^^ SyntaxError: name 'x' is assigned to before global declaration ``` Test Plan -- New PLE0118 test case. We should also check the codspeed results carefully here. `Globals::from_body` visits all of the statements in `body`, so this might be a really bad idea, but it looked like the simplest fix. [this comment]: #7633 (comment) [load-before-global-declaration (PLE0118)]: https://docs.astral.sh/ruff/rules/load-before-global-declaration/#load-before-global-declaration-ple0118
|
|
Can you tell me a bit more about the motivation for adding this error? I'm not quiet convinced that we should emit the extra error at the module level (especially considering that it results in a 1% perf regression) What's not quiet clear to me is how many errors will emit in the case where there's an assignment to a variable before its global declaration at the module level. What I understand from the PR summary is that will emit two errors:
If that's the case, then I'd prefer not to make this change because the only relevant error for users to fix is to not use the globals statement and everything will then work as expected (at least for assignments, uses are a different story). |
The main motivation was just that it came up in the fuzzing results from #7633, and I noticed that we didn't raise any error for it.
I think the summary (and title) was a bit poorly worded. global x
x = 1What I was trying to say in the title/description was more like "extend Ideally this would have been a rule-specific change, but instead it looked like I needed to modify the I don't feel strongly about this, though, especially with the perf hit, and am happy to close the PR. I only opened it for completeness of handling #7633. I think actual code like this would be pretty rare. |
|
Thanks. So there's nothing special about the module level. It's just that Ruff didn't emit the diagnostic |
| // also in the global scope, where we don't want these to count as definitions for rules | ||
| // like `undefined-name` (F821) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe add an example here of what problem would arise if we did run this code at the global level too? It's not entirely clear to me and I'm wondering if not running it creates different problems at the root level (you could try to comment out this code and see what tests fail for the non-global scope and then decide if this is or isn't a problem for the global scope)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I did try deleting this code entirely before because that actually seemed more natural to me, but I didn't write down the results. This is the tricky F821 (F821_6.py) case that fails if we comment out the binding loop entirely:
"""Test: annotated global."""
n: int
def f():
print(n) # F821 false positive here
def g():
global n
n = 1
g()
f()This shorter case also gets a false positive with the whole loop commented out:
def f(): global foo; import foo
def g(): foo.is_used() # false positive hereI don't think we can run into something similar if the global is in the module scope, but this was all quite tricky, so I'm glad to get your eyes on these examples too.
If we run the binding loop in the global scope too, this case, also for F821, fails:
global x
def foo():
print(x) # F821 false negativeThe second and third cases here look like nice additions to the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the more I stare at these examples, the more I think I might have been right to consider deleting this code entirely. It's really the assignment and the import that should be adding the bindings, not the global statements. We can just coincidentally get the right answer by letting global handle it, but it's more obvious that a real binding is needed in the module scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is easily actionable though. I poked around a bit in Checker::add_binding and my attempts at checking for globals didn't work out. I've added the examples for now.
* main: (24 commits) [`airflow`] Extend `AIR301` rule (#17598) [`airflow`] update existing `AIR302` rules with better suggestions (#17542) red_knot_project: sort diagnostics from checking files [red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base (#17621) [red-knot] fix inheritance-cycle detection for generic classes (#17620) [`pylint`] Detect `global` declarations in module scope (`PLE0118`) (#17411) Add Semantic Error Test for LateFutureImport (#17612) [red-knot] change TypeVarInstance to be interned, not tracked (#17616) [red-knot] Special case `@final`, `@override` (#17608) [red-knot] add TODO comment in specialization code (#17615) [`semantic-syntax-errors`] test for `LoadBeforeGlobalDeclaration` - ruff linter (#17592) [syntax-errors] `nonlocal` declaration at module level (#17559) [`airflow`] Apply auto fix to cases where name has been changed in Airflow 3 (`AIR311`) (#17571) [syntax-errors] Make `async-comprehension-in-sync-comprehension` more specific (#17460) Bump 0.11.7 (#17613) [red-knot] Use iterative approach to collect overloads (#17607) red_knot_python_semantic: avoid Rust's screaming snake case convention in mdtest red_knot_python_semantic: improve diagnostics for unsupported boolean conversions red_knot_python_semantic: add "return type span" helper method red_knot_python_semantic: move parameter span helper method ...
* main: (27 commits) [red-knot] Add new property tests for subtyping with "bottom" callable (#17635) [red-knot] Create generic context for generic classes lazily (#17617) ruff_db: add tests for annotations with no ranges [`airflow`] Extend `AIR301` rule (#17598) [`airflow`] update existing `AIR302` rules with better suggestions (#17542) red_knot_project: sort diagnostics from checking files [red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base (#17621) [red-knot] fix inheritance-cycle detection for generic classes (#17620) [`pylint`] Detect `global` declarations in module scope (`PLE0118`) (#17411) Add Semantic Error Test for LateFutureImport (#17612) [red-knot] change TypeVarInstance to be interned, not tracked (#17616) [red-knot] Special case `@final`, `@override` (#17608) [red-knot] add TODO comment in specialization code (#17615) [`semantic-syntax-errors`] test for `LoadBeforeGlobalDeclaration` - ruff linter (#17592) [syntax-errors] `nonlocal` declaration at module level (#17559) [`airflow`] Apply auto fix to cases where name has been changed in Airflow 3 (`AIR311`) (#17571) [syntax-errors] Make `async-comprehension-in-sync-comprehension` more specific (#17460) Bump 0.11.7 (#17613) [red-knot] Use iterative approach to collect overloads (#17607) red_knot_python_semantic: avoid Rust's screaming snake case convention in mdtest ...
Summary
While going through the syntax errors in this comment, I was surprised to see the error
name 'x' is assigned to before global declaration, which corresponds to load-before-global-declaration (PLE0118) and has also been reimplemented as a syntax error (#17135). However, it looks like neither of the implementations considerglobaldeclarations in the top-level module scope, which is a syntax error in CPython:I'm not sure this is the best or most elegant solution, but it was a quick fix that passed all of our tests.
Test Plan
New PLE0118 test case.