Skip to content

Commit f53ee26

Browse files
committed
[ty] Surface matched overload diagnostic directly
1 parent 7ea773d commit f53ee26

File tree

7 files changed

+51
-34
lines changed

7 files changed

+51
-34
lines changed

crates/ty_python_semantic/resources/mdtest/call/builtins.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ The following calls are also invalid, due to incorrect argument types:
4848
```py
4949
class Base: ...
5050

51-
# error: [no-matching-overload] "No overload of class `type` matches arguments"
51+
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
5252
type(b"Foo", (), {})
5353

54-
# error: [no-matching-overload] "No overload of class `type` matches arguments"
54+
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
5555
type("Foo", Base, {})
5656

57-
# error: [no-matching-overload] "No overload of class `type` matches arguments"
57+
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
5858
type("Foo", (1, 2), {})
5959

6060
# TODO: this should be an error
@@ -90,12 +90,18 @@ str(errors="replace")
9090
### Invalid calls
9191

9292
```py
93-
str(1, 2) # error: [no-matching-overload]
94-
str(o=1) # error: [no-matching-overload]
93+
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `bytes | bytearray`, found `Literal[1]`"
94+
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `str`, found `Literal[2]`"
95+
str(1, 2)
96+
97+
# error: [no-matching-overload]
98+
str(o=1)
9599

96100
# First argument is not a bytes-like object:
97-
str("Müsli", "utf-8") # error: [no-matching-overload]
101+
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `bytes | bytearray`, found `Literal["Müsli"]`"
102+
str("Müsli", "utf-8")
98103

99104
# Second argument is not a valid encoding:
100-
str(b"M\xc3\xbcsli", b"utf-8") # error: [no-matching-overload]
105+
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `str`, found `Literal[b"utf-8"]`"
106+
str(b"M\xc3\xbcsli", b"utf-8")
101107
```

crates/ty_python_semantic/resources/mdtest/call/methods.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ method_wrapper(C(), None)
235235
method_wrapper(None, C)
236236

237237
# Passing `None` without an `owner` argument is an
238-
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
238+
# error: [invalid-argument-type] "Argument to method wrapper `__get__` of function `f` is incorrect: Expected `~None`, found `None`"
239239
method_wrapper(None)
240240

241241
# Passing something that is not assignable to `type` as the `owner` argument is an

crates/ty_python_semantic/resources/mdtest/call/overloads.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ from overloaded import f
8585

8686
reveal_type(f()) # revealed: None
8787

88-
# TODO: This should be `invalid-argument-type` instead
89-
# error: [no-matching-overload]
88+
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["a"]`"
9089
reveal_type(f("a")) # revealed: Unknown
9190
```
9291

crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ wrapper_descriptor()
607607
wrapper_descriptor(f)
608608

609609
# Calling it without the `owner` argument if `instance` is not `None` is an
610-
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
610+
# error: [invalid-argument-type] "Argument to wrapper descriptor `FunctionType.__get__` is incorrect: Expected `~None`, found `None`"
611611
wrapper_descriptor(f, None)
612612

613613
# But calling it with an instance is fine (in this case, the `owner` argument is optional):

crates/ty_python_semantic/resources/mdtest/narrow/type.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ No narrowing should occur if `type` is used to dynamically create a class:
7878
def _(x: str | int):
7979
# The following diagnostic is valid, since the three-argument form of `type`
8080
# can only be called with `str` as the first argument.
81-
# error: [no-matching-overload] "No overload of class `type` matches arguments"
81+
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
8282
if type(x, (), {}) is str:
8383
reveal_type(x) # revealed: str | int
8484
else:

crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,6 @@ info: rule `missing-argument` is enabled by default
109109

110110
```
111111

112-
```
113-
error[no-matching-overload]: No overload of method wrapper `__get__` of function `f` matches arguments
114-
--> src/mdtest_snippet.py:48:9
115-
|
116-
46 | # error: [no-matching-overload]
117-
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
118-
48 | x = f(3)
119-
| ^^^^
120-
|
121-
info: Union variant `<method-wrapper `__get__` of `f`>` is incompatible with this call site
122-
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
123-
info: rule `no-matching-overload` is enabled by default
124-
125-
```
126-
127112
```
128113
error[invalid-argument-type]: Argument to function `f2` is incorrect
129114
--> src/mdtest_snippet.py:48:11

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,7 @@ impl<'db> From<Binding<'db>> for Bindings<'db> {
10131013
dunder_call_is_possibly_unbound: false,
10141014
bound_type: None,
10151015
return_type: None,
1016+
matched_overload_index: None,
10161017
overloads: smallvec![from],
10171018
};
10181019
Bindings {
@@ -1053,12 +1054,20 @@ pub(crate) struct CallableBinding<'db> {
10531054

10541055
/// The return type of this callable.
10551056
///
1056-
/// This is only `Some` if it's an overloaded callable, "argument type expansion" was
1057-
/// performed, and one of the expansion evaluated successfully for all of the argument lists.
1057+
/// This is [`Some`] only when all of the following conditions are met:
1058+
/// - This is an overloaded callable
1059+
/// - Argument type expansion was performed
1060+
/// - One of the expansion evaluated successfully for all of the argument lists
1061+
///
10581062
/// This type is then the union of all the return types of the matched overloads for the
10591063
/// expanded argument lists.
10601064
return_type: Option<Type<'db>>,
10611065

1066+
/// The index of the overload that matched for this callable.
1067+
///
1068+
/// This is [`None`] if the callable is not overloaded, or if no overload matched.
1069+
matched_overload_index: Option<usize>,
1070+
10621071
/// The bindings of each overload of this callable. Will be empty if the type is not callable.
10631072
///
10641073
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a
@@ -1081,6 +1090,7 @@ impl<'db> CallableBinding<'db> {
10811090
dunder_call_is_possibly_unbound: false,
10821091
bound_type: None,
10831092
return_type: None,
1093+
matched_overload_index: None,
10841094
overloads,
10851095
}
10861096
}
@@ -1092,6 +1102,7 @@ impl<'db> CallableBinding<'db> {
10921102
dunder_call_is_possibly_unbound: false,
10931103
bound_type: None,
10941104
return_type: None,
1105+
matched_overload_index: None,
10951106
overloads: smallvec![],
10961107
}
10971108
}
@@ -1142,10 +1153,9 @@ impl<'db> CallableBinding<'db> {
11421153
return;
11431154
}
11441155
MatchingOverloadIndex::Single(index) => {
1145-
// If only one candidate overload remains, it is the winning match.
1146-
// TODO: Evaluate it as a regular (non-overloaded) call. This means that any
1147-
// diagnostics reported in this check should be reported directly instead of
1148-
// reporting it as `no-matching-overload`.
1156+
// If only one candidate overload remains, it is the winning match. Evaluate it as
1157+
// a regular (non-overloaded) call.
1158+
self.matched_overload_index = Some(index);
11491159
self.overloads[index].check_types(
11501160
db,
11511161
argument_types.as_ref(),
@@ -1175,8 +1185,9 @@ impl<'db> CallableBinding<'db> {
11751185
MatchingOverloadIndex::None => {
11761186
// If all overloads result in errors, proceed to step 3.
11771187
}
1178-
MatchingOverloadIndex::Single(_) => {
1188+
MatchingOverloadIndex::Single(index) => {
11791189
// If only one overload evaluates without error, it is the winning match.
1190+
self.matched_overload_index = Some(index);
11801191
return;
11811192
}
11821193
MatchingOverloadIndex::Multiple(_) => {
@@ -1356,6 +1367,7 @@ impl<'db> CallableBinding<'db> {
13561367
if let Some(return_type) = self.return_type {
13571368
return return_type;
13581369
}
1370+
// TODO: Should this now use `self.matched_overload_index`?
13591371
if let Some((_, first_overload)) = self.matching_overloads().next() {
13601372
return first_overload.return_type();
13611373
}
@@ -1418,6 +1430,21 @@ impl<'db> CallableBinding<'db> {
14181430
// https://github.com/henribru/google-api-python-client-stubs/blob/master/googleapiclient-stubs/discovery.pyi
14191431
const MAXIMUM_OVERLOADS: usize = 50;
14201432

1433+
// If there is a single matching overload, the diagnostics should be reported
1434+
// directly for that overload.
1435+
if let Some(matched_overload_index) = self.matched_overload_index {
1436+
let callable_description =
1437+
CallableDescription::new(context.db(), self.signature_type);
1438+
self.overloads[matched_overload_index].report_diagnostics(
1439+
context,
1440+
node,
1441+
self.signature_type,
1442+
callable_description.as_ref(),
1443+
union_diag,
1444+
);
1445+
return;
1446+
}
1447+
14211448
let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) else {
14221449
return;
14231450
};

0 commit comments

Comments
 (0)