[red-knot] add maybe-undefined lint rule (#12414)

Add a lint rule to detect if a name is definitely or possibly undefined
at a given usage.

If I create the file `undef/main.py` with contents:

```python
x = int
def foo():
    z
    return x
if flag:
    y = x
y
```

And then run `cargo run --bin red_knot -- --current-directory
../ruff-examples/undef`, I get the output:

```
Name 'z' used when not defined.
Name 'flag' used when not defined.
Name 'y' used when possibly not defined.
```

If I modify the file to add `y = 0` at the top, red-knot re-checks it
and I get the new output:

```
Name 'z' used when not defined.
Name 'flag' used when not defined.
```

Note that `int` is not flagged, since it's a builtin, and `return x` in
the function scope is not flagged, since it refers to the global `x`.
This commit is contained in:
Carl Meyer 2024-07-22 13:53:59 -07:00 committed by GitHub
parent 2a8f95c437
commit f22c8ab811
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 199 additions and 10 deletions

View file

@ -49,7 +49,6 @@ pub(crate) mod tests {
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast};
use ruff_python_trivia::textwrap;
use super::{Db, Jar};
@ -91,12 +90,6 @@ pub(crate) mod tests {
pub(crate) fn clear_salsa_events(&mut self) {
self.take_salsa_events();
}
/// Write auto-dedented text to a file.
pub(crate) fn write_dedented(&mut self, path: &str, content: &str) -> anyhow::Result<()> {
self.write_file(path, textwrap::dedent(content))?;
Ok(())
}
}
impl DbWithTestSystem for TestDb {

View file

@ -239,6 +239,12 @@ pub struct UnionType<'db> {
elements: FxOrderSet<Type<'db>>,
}
impl<'db> UnionType<'db> {
pub fn contains(&self, db: &'db dyn Db, ty: Type<'db>) -> bool {
self.elements(db).contains(&ty)
}
}
struct UnionTypeBuilder<'db> {
elements: FxOrderSet<Type<'db>>,
db: &'db dyn Db,

View file

@ -314,6 +314,7 @@ impl<'db> TypeInferenceBuilder<'db> {
ast::Stmt::For(for_statement) => self.infer_for_statement(for_statement),
ast::Stmt::Import(import) => self.infer_import_statement(import),
ast::Stmt::ImportFrom(import) => self.infer_import_from_statement(import),
ast::Stmt::Return(ret) => self.infer_return_statement(ret),
ast::Stmt::Break(_) | ast::Stmt::Continue(_) | ast::Stmt::Pass(_) => {
// No-op
}
@ -551,6 +552,12 @@ impl<'db> TypeInferenceBuilder<'db> {
self.types.definitions.insert(definition, ty);
}
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
if let Some(value) = &ret.value {
self.infer_expression(value);
}
}
fn module_ty_from_name(&self, name: &ast::Identifier) -> Type<'db> {
let module_name = ModuleName::new(&name.id);
let module =