[red-knot] Check overloads without an implementation (#17681)

## Summary

As mentioned in the spec
(https://typing.python.org/en/latest/spec/overload.html#invalid-overload-definitions),
part of #15383:

> The `@overload`-decorated definitions must be followed by an overload
implementation, which does not include an `@overload` decorator. Type
checkers should report an error or warning if an implementation is
missing. Overload definitions within stub files, protocols, and on
abstract methods within abstract base classes are exempt from this
check.

## Test Plan

Remove TODOs from the test; create one diagnostic snapshot.
This commit is contained in:
Dhruv Manilawala 2025-04-30 19:54:21 +05:30 committed by GitHub
parent f584b66824
commit 7825975972
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 114 additions and 6 deletions

View File

@ -205,7 +205,7 @@ reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or
The `__get__` method on `types.FunctionType` has the following overloaded signature in typeshed:
```py
```pyi
from types import FunctionType, MethodType
from typing import overload

View File

@ -336,23 +336,25 @@ def func(x: int) -> int: ...
#### Regular modules
<!-- snapshot-diagnostics -->
In regular modules, a series of `@overload`-decorated definitions must be followed by exactly one
non-`@overload`-decorated definition (for the same function/method).
```py
from typing import overload
# TODO: error because implementation does not exists
@overload
def func(x: int) -> int: ...
@overload
# error: [invalid-overload] "Overloaded non-stub function `func` must have an implementation"
def func(x: str) -> str: ...
class Foo:
# TODO: error because implementation does not exists
@overload
def method(self, x: int) -> int: ...
@overload
# error: [invalid-overload] "Overloaded non-stub function `method` must have an implementation"
def method(self, x: str) -> str: ...
```
@ -405,12 +407,12 @@ from it.
```py
class Foo:
# TODO: Error because implementation does not exists
@overload
@abstractmethod
def f(self, x: int) -> int: ...
@overload
@abstractmethod
# error: [invalid-overload]
def f(self, x: str) -> str: ...
```
@ -422,6 +424,7 @@ class PartialFoo1(ABC):
@abstractmethod
def f(self, x: int) -> int: ...
@overload
# error: [invalid-overload]
def f(self, x: str) -> str: ...
class PartialFoo(ABC):
@ -429,6 +432,7 @@ class PartialFoo(ABC):
def f(self, x: int) -> int: ...
@overload
@abstractmethod
# error: [invalid-overload]
def f(self, x: str) -> str: ...
```

View File

@ -0,0 +1,57 @@
---
source: crates/red_knot_test/src/lib.rs
expression: snapshot
---
---
mdtest name: overloads.md - Overloads - Invalid - Overload without an implementation - Regular modules
mdtest path: crates/red_knot_python_semantic/resources/mdtest/overloads.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import overload
2 |
3 | @overload
4 | def func(x: int) -> int: ...
5 | @overload
6 | # error: [invalid-overload] "Overloaded non-stub function `func` must have an implementation"
7 | def func(x: str) -> str: ...
8 |
9 | class Foo:
10 | @overload
11 | def method(self, x: int) -> int: ...
12 | @overload
13 | # error: [invalid-overload] "Overloaded non-stub function `method` must have an implementation"
14 | def method(self, x: str) -> str: ...
```
# Diagnostics
```
error: lint:invalid-overload: Overloaded non-stub function `func` must have an implementation
--> src/mdtest_snippet.py:7:5
|
5 | @overload
6 | # error: [invalid-overload] "Overloaded non-stub function `func` must have an implementation"
7 | def func(x: str) -> str: ...
| ^^^^
8 |
9 | class Foo:
|
```
```
error: lint:invalid-overload: Overloaded non-stub function `method` must have an implementation
--> src/mdtest_snippet.py:14:9
|
12 | @overload
13 | # error: [invalid-overload] "Overloaded non-stub function `method` must have an implementation"
14 | def method(self, x: str) -> str: ...
| ^^^^^^
|
```

View File

@ -562,6 +562,13 @@ impl<'db> ClassLiteral<'db> {
})
}
/// Determine if this is an abstract class.
pub(super) fn is_abstract(self, db: &'db dyn Db) -> bool {
self.metaclass(db)
.into_class_literal()
.is_some_and(|metaclass| metaclass.is_known(db, KnownClass::ABCMeta))
}
/// Return the types of the decorators on this class
#[salsa::tracked(return_ref)]
fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> {

View File

@ -744,7 +744,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: Only call this function when diagnostics are enabled.
self.check_class_definitions();
self.check_overloaded_functions();
self.check_overloaded_functions(node);
}
/// Iterate over all class definitions to check that the definition will not cause an exception
@ -987,7 +987,7 @@ impl<'db> TypeInferenceBuilder<'db> {
///
/// For (1), this has the consequence of not checking an overloaded function that is being
/// shadowed by another function with the same name in this scope.
fn check_overloaded_functions(&mut self) {
fn check_overloaded_functions(&mut self, scope: &NodeWithScopeKind) {
// Collect all the unique overloaded function symbols in this scope. This requires a set
// because an overloaded function uses the same symbol for each of the overloads and the
// implementation.
@ -1056,6 +1056,46 @@ impl<'db> TypeInferenceBuilder<'db> {
);
}
}
// Check that the overloaded function has an implementation. Overload definitions
// within stub files, protocols, and on abstract methods within abstract base classes
// are exempt from this check.
if overloaded.implementation.is_none() && !self.in_stub() {
let mut implementation_required = true;
if let NodeWithScopeKind::Class(class_node_ref) = scope {
let class = binding_type(
self.db(),
self.index.expect_single_definition(class_node_ref.node()),
)
.expect_class_literal();
if class.is_protocol(self.db())
|| (class.is_abstract(self.db())
&& overloaded.overloads.iter().all(|overload| {
overload.has_known_decorator(
self.db(),
FunctionDecorators::ABSTRACT_METHOD,
)
}))
{
implementation_required = false;
}
}
if implementation_required {
let function_node = function.node(self.db(), self.file());
if let Some(builder) = self
.context
.report_lint(&INVALID_OVERLOAD, &function_node.name)
{
builder.into_diagnostic(format_args!(
"Overloaded non-stub function `{}` must have an implementation",
&function_node.name
));
}
}
}
}
}