[ty] Avoid reporting overload errors for successful union variants (#22688)

## Summary

Consider `x: str | bytes` and then `x.split(" ")`. Because we have a
union, and at least one variant errors (`bytes` expects a `Buffer`, not
a `str`), we call `binding.report_diagnostics` for each variant. For the
`str` variant, it has two overloads that both match arity, but only one
actually matches the signature... So
`matching_overload_before_type_checking` is `None` (because they both
match arity), but we don't actually have an error, and we fall through
to `NO_MATCHING_OVERLOAD`.

If one variant succeeds, we should avoid reporting errors for it, even
if not _all_ variants matched.
This commit is contained in:
Charlie Marsh
2026-01-19 08:40:31 -05:00
committed by GitHub
parent ebc59e81f6
commit 52ce26bd41
3 changed files with 81 additions and 0 deletions

View File

@@ -184,3 +184,22 @@ def _(x: T, y: int) -> T:
# error: [invalid-argument-type]
return x.foo(y)
```
## Union with overloaded method and incompatible variant
When calling a method on a union type where:
- One variant has the method with compatible arguments (`str.split`)
- Another variant has the method but with incompatible arguments (`bytes.split` expects `Buffer`,
not `str`)
- Other variants don't have the method at all (contributing `Unknown` to the callable)
We should only report the specific error for the incompatible variant (`invalid-argument-type` for
`bytes.split`), not a spurious `no-matching-overload` for the compatible variant.
```py
def _(x: bytes | str | int):
# error: [invalid-argument-type]
# error: [possibly-missing-attribute]
x.split(" ")
```

View File

@@ -0,0 +1,59 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: union_call.md - Calling a union of function types - Union with overloaded method and incompatible variant
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md
---
# Python source files
## mdtest_snippet.py
```
1 | def _(x: bytes | str | int):
2 | # error: [invalid-argument-type]
3 | # error: [possibly-missing-attribute]
4 | x.split(" ")
```
# Diagnostics
```
warning[possibly-missing-attribute]: Attribute `split` may be missing on object of type `bytes | str | int`
--> src/mdtest_snippet.py:4:5
|
2 | # error: [invalid-argument-type]
3 | # error: [possibly-missing-attribute]
4 | x.split(" ")
| ^^^^^^^
|
info: rule `possibly-missing-attribute` is enabled by default
```
```
error[invalid-argument-type]: Argument to bound method `split` is incorrect
--> src/mdtest_snippet.py:4:13
|
2 | # error: [invalid-argument-type]
3 | # error: [possibly-missing-attribute]
4 | x.split(" ")
| ^^^ Expected `Buffer | None`, found `Literal[" "]`
|
info: Method defined here
--> stdlib/builtins.pyi:1761:9
|
1759 | """
1760 |
1761 | def split(self, sep: ReadableBuffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytes]:
| ^^^^^ --------------------------------- Parameter declared here
1762 | """Return a list of the sections in the bytes, using sep as the delimiter.
|
info: Union variant `bound method bytes.split(sep: Buffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytes]` is incompatible with this call site
info: Attempted to call union type `(bound method bytes.split(sep: Buffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytes]) | (Overload[(sep: LiteralString | None = None, maxsplit: SupportsIndex = -1) -> list[LiteralString], (sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]])`
info: rule `invalid-argument-type` is enabled by default
```

View File

@@ -327,6 +327,9 @@ impl<'db> Bindings<'db> {
}
for binding in self {
if binding.as_result().is_ok() {
continue;
}
let union_diag = UnionDiagnostic {
callable_type: self.callable_type(),
binding,