-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[pylint] Implement global-variable-undefined (W0601) #10820
base: main
Are you sure you want to change the base?
Conversation
181ccaa
to
4ea1948
Compare
|
code | total | + violation | - violation | + fix | - fix |
---|---|---|---|---|---|
PLW0601 | 4 | 4 | 0 | 0 | 0 |
Does this break on wildcard imports? from os import *
def foo():
global system # W0601 should not be raised
system = lambda _: None |
Yes, it does. I have not found a function in ruff which resolves star-imports, e.g. there is F403 and F405 which are raised for star-imports and warn about "...unable to detect..." and "...may be undefined...". |
I think there's a way to detect if there's a star import in which case you could avoid creating any diagnostics (to avoid false-positives). The main open question to me are
|
@MichaReiser |
@tibor-reiss my preference would be that we don't flag any variables if a file contains a star import. I think there's already precedence for this in Ruff but I would need to search it to. @charliermarsh should know where to find it. |
6d1d4ca
to
68bb046
Compare
@MichaReiser thanks for feedback, I added the check for star imports together with a new test file. |
68bb046
to
5a61a5b
Compare
Hi @MichaReiser, this got lost in translation. I have updated now to the latest master, could you please have a look and let me know if something is still missing? |
Thanks @tibor-reiss I took a quick look at the ecosystem result and it's not clear why the rule raises
Is it because there's only a declaration of |
let Some(module_scope) = checker | ||
.semantic() | ||
.current_scopes() | ||
.find(|scope| scope.kind.is_module()) | ||
else { | ||
return; | ||
}; |
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.
Do you think we could use global_scope
here instead?
let Some(module_scope) = checker | |
.semantic() | |
.current_scopes() | |
.find(|scope| scope.kind.is_module()) | |
else { | |
return; | |
}; | |
let module_scope = checker | |
.semantic() | |
.global_scope(); |
} | ||
|
||
fn get_imports<'a>(checker: &'a Checker) -> Vec<&'a str> { | ||
// Get all names imported in the current 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.
Why do we only look at imports of the current scope? What about imports in the enclosing 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.
This covers the case when the global
statement is before the import
statement - see tests. I additionally added the check for imports in is_global_binding
.
Co-authored-by: Micha Reiser <[email protected]>
1eed643
to
f1dc57c
Compare
You are right: there was a case missing, i.e. the global variable was just declared (with type annotation). I have added this case now (with test). @MichaReiser what do you think about this case? I have it now covered. However, the rule reads undefined, so I think there is a strong case for raising the warning if the global was only declared. |
56b56c6
to
7b9de3e
Compare
7b9de3e
to
3d4dfd2
Compare
let Some(fs) = checker | ||
.semantic() | ||
.current_scopes() | ||
.find(|scope| scope.kind.is_function()) | ||
else { | ||
return vec![]; | ||
}; | ||
let ScopeKind::Function(ast::StmtFunctionDef { body, .. }) = fs.kind else { | ||
return vec![]; | ||
}; |
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.
Nit:
let Some(fs) = checker | |
.semantic() | |
.current_scopes() | |
.find(|scope| scope.kind.is_function()) | |
else { | |
return vec![]; | |
}; | |
let ScopeKind::Function(ast::StmtFunctionDef { body, .. }) = fs.kind else { | |
return vec![]; | |
}; | |
let Some(ast::StmtFunctionDef { body, .. }) = checker | |
.semantic() | |
.current_scopes() | |
.find_map(|scope| scope.kind.as_function()) | |
else { | |
return vec![]; | |
}; |
for stmt in body { | ||
match stmt { | ||
Stmt::Import(ast::StmtImport { names, .. }) | ||
| Stmt::ImportFrom(ast::StmtImportFrom { names, .. }) => { | ||
for name in names { | ||
import_names.push(name.name.as_str()); | ||
} | ||
} | ||
_ => (), | ||
} | ||
} |
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.
Should this also respect imports in conditional branches. E.g. what about:
def test(a):
if a:
from b import bar
globals bar
# pylint: disable=invalid-name, import-outside-toplevel, too-few-public-methods, unused-import | ||
# pylint: disable=missing-module-docstring, missing-function-docstring, missing-class-docstring | ||
# pylint: disable=global-at-module-level, global-statement, global-variable-not-assigned | ||
# pylint: diable=redefined-outer-name |
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.
We can remove those comments because our tests run in isolation
|
||
for name in names { | ||
// Skip if imported names | ||
if imported_names.contains(&name.as_str()) { |
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.
My python knowledge is too limited to understand why it is okay to ignore imports from the enclosing function's scope. Could you help me understand why it is okay to ignore imports?
I read https://docs.python.org/3/reference/simple_stmts.html#the-global-statement and couldn't find any special handling of imports in the enclosing scope.
Add pylint rule global-variable-undefined (W0601)
See #970 for rules
Test Plan:
cargo test