Skip to content

Commit b6b4ad9

Browse files
carljmMichaReiser
andauthored
[red-knot] @OverRide lint rule (#11282)
## Summary Lots of TODOs and things to clean up here, but it demonstrates the working lint rule. ## Test Plan ``` ➜ cat main.py from typing import override from base import B class C(B): @OverRide def method(self): pass ➜ cat base.py class B: pass ➜ cat typing.py def override(func): return func ``` (We provide our own `typing.py` since we don't have typeshed vendored or type stub support yet.) ``` ➜ ./target/debug/red_knot main.py ... 1 0.012086s TRACE red_knot Main Loop: Tick [crates/red_knot/src/main.rs:157:21] diagnostics = [ "Method C.method is decorated with `typing.override` but does not override any base class method", ] ``` If we add `def method(self): pass` to class `B` in `base.py` and run red_knot again, there is no lint error. --------- Co-authored-by: Micha Reiser <[email protected]>
1 parent dd42961 commit b6b4ad9

File tree

4 files changed

+462
-114
lines changed

4 files changed

+462
-114
lines changed

crates/red_knot/src/lint.rs

+64-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ use ruff_python_ast::{ModModule, StringLiteral};
99
use crate::cache::KeyValueCache;
1010
use crate::db::{LintDb, LintJar, QueryResult};
1111
use crate::files::FileId;
12+
use crate::module::ModuleName;
1213
use crate::parse::{parse, Parsed};
1314
use crate::source::{source_text, Source};
14-
use crate::symbols::{symbol_table, Definition, SymbolId, SymbolTable};
15-
use crate::types::{infer_symbol_type, Type};
15+
use crate::symbols::{
16+
resolve_global_symbol, symbol_table, Definition, GlobalSymbolId, SymbolId, SymbolTable,
17+
};
18+
use crate::types::{infer_definition_type, infer_symbol_type, Type};
1619

1720
#[tracing::instrument(level = "debug", skip(db))]
1821
pub(crate) fn lint_syntax(db: &dyn LintDb, file_id: FileId) -> QueryResult<Diagnostics> {
@@ -90,6 +93,7 @@ pub(crate) fn lint_semantic(db: &dyn LintDb, file_id: FileId) -> QueryResult<Dia
9093
};
9194

9295
lint_unresolved_imports(&context)?;
96+
lint_bad_overrides(&context)?;
9397

9498
Ok(Diagnostics::from(context.diagnostics.take()))
9599
})
@@ -136,6 +140,57 @@ fn lint_unresolved_imports(context: &SemanticLintContext) -> QueryResult<()> {
136140
Ok(())
137141
}
138142

143+
fn lint_bad_overrides(context: &SemanticLintContext) -> QueryResult<()> {
144+
// TODO we should have a special marker on the real typing module (from typeshed) so if you
145+
// have your own "typing" module in your project, we don't consider it THE typing module (and
146+
// same for other stdlib modules that our lint rules care about)
147+
let Some(typing_override) =
148+
resolve_global_symbol(context.db.upcast(), ModuleName::new("typing"), "override")?
149+
else {
150+
// TODO once we bundle typeshed, this should be unreachable!()
151+
return Ok(());
152+
};
153+
154+
// TODO we should maybe index definitions by type instead of iterating all, or else iterate all
155+
// just once, match, and branch to all lint rules that care about a type of definition
156+
for (symbol, definition) in context.symbols().all_definitions() {
157+
if !matches!(definition, Definition::FunctionDef(_)) {
158+
continue;
159+
}
160+
let ty = infer_definition_type(
161+
context.db.upcast(),
162+
GlobalSymbolId {
163+
file_id: context.file_id,
164+
symbol_id: symbol,
165+
},
166+
definition.clone(),
167+
)?;
168+
let Type::Function(func) = ty else {
169+
unreachable!("type of a FunctionDef should always be a Function");
170+
};
171+
let Some(class) = func.get_containing_class(context.db.upcast())? else {
172+
// not a method of a class
173+
continue;
174+
};
175+
if func.has_decorator(context.db.upcast(), typing_override)? {
176+
let method_name = func.name(context.db.upcast())?;
177+
if class
178+
.get_super_class_member(context.db.upcast(), &method_name)?
179+
.is_none()
180+
{
181+
// TODO should have a qualname() method to support nested classes
182+
context.push_diagnostic(
183+
format!(
184+
"Method {}.{} is decorated with `typing.override` but does not override any base class method",
185+
class.name(context.db.upcast())?,
186+
method_name,
187+
));
188+
}
189+
}
190+
}
191+
Ok(())
192+
}
193+
139194
pub struct SemanticLintContext<'a> {
140195
file_id: FileId,
141196
source: Source,
@@ -163,7 +218,13 @@ impl<'a> SemanticLintContext<'a> {
163218
}
164219

165220
pub fn infer_symbol_type(&self, symbol_id: SymbolId) -> QueryResult<Type> {
166-
infer_symbol_type(self.db.upcast(), self.file_id, symbol_id)
221+
infer_symbol_type(
222+
self.db.upcast(),
223+
GlobalSymbolId {
224+
file_id: self.file_id,
225+
symbol_id,
226+
},
227+
)
167228
}
168229

169230
pub fn push_diagnostic(&self, diagnostic: String) {

0 commit comments

Comments
 (0)