diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md index 9ca10a05c7..3abc301372 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md @@ -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 diff --git a/crates/red_knot_python_semantic/resources/mdtest/overloads.md b/crates/red_knot_python_semantic/resources/mdtest/overloads.md index 4bbef31b1d..4089040d9b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/overloads.md +++ b/crates/red_knot_python_semantic/resources/mdtest/overloads.md @@ -336,23 +336,25 @@ def func(x: int) -> int: ... #### Regular modules + + 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: ... ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap new file mode 100644 index 0000000000..f92141e10c --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap @@ -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: ... + | ^^^^^^ + | + +``` diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index a2ccc66e8d..b68e6b9798 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -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>]> { diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index d9a15f0cc6..b9c56a02af 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -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 + )); + } + } + } } }