diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md index c71e608e6e..19627f8351 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md @@ -278,3 +278,29 @@ def f( f(b"foo") # error: [no-matching-overload] ``` + +## A method call with unmatched overloads + +```py +from typing import overload + +class Foo: + @overload + def bar(self, x: int) -> int: ... + @overload + def bar(self, x: str) -> str: ... + def bar(self, x: int | str) -> int | str: + return x + +foo = Foo() +foo.bar(b"wat") # error: [no-matching-overload] +``` + +## A class constructor with unmatched overloads + +TODO: At time of writing (2025-05-15), this has non-ideal diagnostics that doesn't show the +unmatched overloads. + +```py +type() # error: [no-matching-overload] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_class_constructor_…_(dd9f8a8f736a329).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_class_constructor_…_(dd9f8a8f736a329).snap new file mode 100644 index 0000000000..9a446afe71 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_class_constructor_…_(dd9f8a8f736a329).snap @@ -0,0 +1,29 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - A class constructor with unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | type() # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of class `type` matches arguments + --> src/mdtest_snippet.py:1:1 + | +1 | type() # error: [no-matching-overload] + | ^^^^^^ + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_method_call_with_u…_(31cb5f881221158e).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_method_call_with_u…_(31cb5f881221158e).snap new file mode 100644 index 0000000000..66eccf602a --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_A_method_call_with_u…_(31cb5f881221158e).snap @@ -0,0 +1,63 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - A method call with unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import overload + 2 | + 3 | class Foo: + 4 | @overload + 5 | def bar(self, x: int) -> int: ... + 6 | @overload + 7 | def bar(self, x: str) -> str: ... + 8 | def bar(self, x: int | str) -> int | str: + 9 | return x +10 | +11 | foo = Foo() +12 | foo.bar(b"wat") # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of bound method `bar` matches arguments + --> src/mdtest_snippet.py:12:1 + | +11 | foo = Foo() +12 | foo.bar(b"wat") # error: [no-matching-overload] + | ^^^^^^^^^^^^^^^ + | +info: First overload defined here + --> src/mdtest_snippet.py:5:9 + | +3 | class Foo: +4 | @overload +5 | def bar(self, x: int) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^ +6 | @overload +7 | def bar(self, x: str) -> str: ... + | +info: Possible overloads for bound method `bar`: +info: (self, x: int) -> int +info: (self, x: str) -> str +info: Overload implementation defined here + --> src/mdtest_snippet.py:8:9 + | +6 | @overload +7 | def bar(self, x: str) -> str: ... +8 | def bar(self, x: int | str) -> int | str: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +9 | return x + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 52189b9602..7f0500815b 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1120,7 +1120,19 @@ impl<'db> CallableBinding<'db> { String::new() } )); - if let Some(function) = self.signature_type.into_function_literal() { + // TODO: This should probably be adapted to handle more + // types of callables[1]. At present, it just handles + // standard function and method calls. + // + // [1]: https://github.com/astral-sh/ty/issues/274#issuecomment-2881856028 + let function_type_and_kind = match self.signature_type { + Type::FunctionLiteral(function) => Some(("function", function)), + Type::BoundMethod(bound_method) => { + Some(("bound method", bound_method.function(context.db()))) + } + _ => None, + }; + if let Some((kind, function)) = function_type_and_kind { if let Some(overloaded_function) = function.to_overloaded(context.db()) { if let Some(spans) = overloaded_function .overloads @@ -1134,7 +1146,7 @@ impl<'db> CallableBinding<'db> { } diag.info(format_args!( - "Possible overloads for function `{}`:", + "Possible overloads for {kind} `{}`:", function.name(context.db()) ));