Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions crates/red_knot_python_semantic/resources/mdtest/overloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
```

Expand Down Expand Up @@ -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: ...
```

Expand All @@ -422,13 +424,15 @@ class PartialFoo1(ABC):
@abstractmethod
def f(self, x: int) -> int: ...
@overload
# error: [invalid-overload]
def f(self, x: str) -> str: ...

class PartialFoo(ABC):
@overload
def f(self, x: int) -> int: ...
@overload
@abstractmethod
# error: [invalid-overload]
def f(self, x: str) -> str: ...
```

Expand Down
Original file line number Diff line number Diff line change
@@ -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: ...
| ^^^^^^
|

```
7 changes: 7 additions & 0 deletions crates/red_knot_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>]> {
Expand Down
44 changes: 42 additions & 2 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
));
}
}
}
}
}

Expand Down
Loading