ty_python_semantic: report all union diagnostic

This makes one very simple change: we report all call binding
errors from each union variant.

This does result in duplicate-seeming diagnostics. For example,
when two union variants are invalid for the same reason.
This commit is contained in:
Andrew Gallant 2025-05-09 09:44:00 -04:00 committed by Andrew Gallant
parent 90272ad85a
commit 5ea3a52c8a
6 changed files with 152 additions and 19 deletions

View file

@ -100,8 +100,10 @@ def _(flag: bool) -> None:
reveal_type(Foo(1)) # revealed: Foo
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`"
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`"
reveal_type(Foo("1")) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2"
reveal_type(Foo(1, 2)) # revealed: Foo
@ -231,8 +233,10 @@ def _(flag: bool) -> None:
reveal_type(Foo(1)) # revealed: Foo
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`"
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`"
reveal_type(Foo("1")) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2"
reveal_type(Foo(1, 2)) # revealed: Foo

View file

@ -56,8 +56,8 @@ def _(flag: bool, flag2: bool):
else:
def f() -> int:
return 1
# TODO we should mention all non-callable elements of the union
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
# error: [call-non-callable] "Object of type `Literal["foo"]` is not callable"
# revealed: Unknown | int
reveal_type(f())
```
@ -125,8 +125,8 @@ def _(flag: bool):
else:
f = f2
# TODO: we should show all errors from the union, not arbitrarily pick one union element
# error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1"
# error: [too-many-positional-arguments] "Too many positional arguments to function `f2`: expected 0, got 1"
x = f(3)
reveal_type(x) # revealed: Unknown
```
@ -143,8 +143,8 @@ def _(flag: bool):
else:
f = C()
# TODO: we should either show all union errors here, or prioritize the not-callable error
# error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1"
# error: [call-non-callable] "Object of type `C` is not callable"
x = f(3)
reveal_type(x) # revealed: Unknown
```

View file

@ -22,6 +22,7 @@ def _(flag: bool):
else:
f = f2
# error: [too-many-positional-arguments]
# error: [invalid-argument-type]
x = f(3)
```
@ -98,6 +99,12 @@ def _(n: int):
else:
f = PossiblyNotCallable()
# error: [too-many-positional-arguments]
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`"
# error: [missing-argument]
# error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`"
# error: [call-non-callable] "Object of type `Literal[5]` is not callable"
# error: [no-matching-overload]
# error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
x = f(3)
```

View file

@ -24,18 +24,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m
10 | else:
11 | f = f2
12 | # error: [too-many-positional-arguments]
13 | x = f(3)
13 | # error: [invalid-argument-type]
14 | x = f(3)
```
# Diagnostics
```
error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1
--> src/mdtest_snippet.py:13:11
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:14:11
|
11 | f = f2
12 | # error: [too-many-positional-arguments]
13 | x = f(3)
13 | # error: [invalid-argument-type]
14 | x = f(3)
| ^ Expected `str`, found `Literal[3]`
|
info: Function defined here
--> src/mdtest_snippet.py:4:5
|
2 | return 0
3 |
4 | def f2(name: str) -> int:
| ^^ --------- Parameter declared here
5 | return 0
|
info: `lint:invalid-argument-type` is enabled by default
```
```
error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1
--> src/mdtest_snippet.py:14:11
|
12 | # error: [too-many-positional-arguments]
13 | # error: [invalid-argument-type]
14 | x = f(3)
| ^
|
info: `lint:too-many-positional-arguments` is enabled by default

View file

@ -53,18 +53,120 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m
39 | else:
40 | f = PossiblyNotCallable()
41 | # error: [too-many-positional-arguments]
42 | x = f(3)
42 | # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`"
43 | # error: [missing-argument]
44 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`"
45 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
```
# Diagnostics
```
error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1
--> src/mdtest_snippet.py:42:11
error: lint:call-non-callable: Object of type `Literal[5]` is not callable
--> src/mdtest_snippet.py:48:9
|
40 | f = PossiblyNotCallable()
41 | # error: [too-many-positional-arguments]
42 | x = f(3)
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^^^^
|
info: `lint:call-non-callable` is enabled by default
```
```
error: lint:call-non-callable: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)
--> src/mdtest_snippet.py:48:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^^^^
|
info: `lint:call-non-callable` is enabled by default
```
```
error: lint:missing-argument: No argument provided for required parameter `b` of function `f3`
--> src/mdtest_snippet.py:48:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^^^^
|
info: `lint:missing-argument` is enabled by default
```
```
error: lint:no-matching-overload: No overload of method wrapper `__get__` of function `f` matches arguments
--> src/mdtest_snippet.py:48:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^^^^
|
info: `lint:no-matching-overload` is enabled by default
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:48:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^ Expected `str`, found `Literal[3]`
|
info: Function defined here
--> src/mdtest_snippet.py:6:5
|
4 | return 0
5 |
6 | def f2(name: str) -> int:
| ^^ --------- Parameter declared here
7 | return 0
|
info: `lint:invalid-argument-type` is enabled by default
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:48:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^ Argument type `Literal[3]` does not satisfy upper bound of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:12:8
|
10 | return 0
11 |
12 | def f4[T: str](x: T) -> int:
| ^^^^^^
13 | return 0
|
info: `lint:invalid-argument-type` is enabled by default
```
```
error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1
--> src/mdtest_snippet.py:48:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
| ^
|
info: `lint:too-many-positional-arguments` is enabled by default

View file

@ -199,11 +199,8 @@ impl<'db> Bindings<'db> {
}
}
// TODO: We currently only report errors for the first union element. Ideally, we'd report
// an error saying that the union type can't be called, followed by subdiagnostics
// explaining why.
if let Some(first) = self.into_iter().find(|b| b.as_result().is_err()) {
first.report_diagnostics(context, node);
for binding in self {
binding.report_diagnostics(context, node);
}
}