mirror of https://github.com/astral-sh/ruff
red_knot_python_semantic: improve diagnostic message for "invalid argument type"
This uses the refactoring and support for secondary diagnostic messages to improve the diagnostic for "invalid argument type." The main improvement here is that we show where the function being called is defined, and annotate the span corresponding to the invalid parameter.
This commit is contained in:
parent
87668e24b1
commit
3ea32e2cdd
|
|
@ -0,0 +1,184 @@
|
|||
# Invalid argument type diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Basic
|
||||
|
||||
This is a basic test demonstrating that a diagnostic points to the function definition corresponding
|
||||
to the invalid argument.
|
||||
|
||||
```py
|
||||
def foo(x: int) -> int:
|
||||
return x * x
|
||||
|
||||
foo("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Different source order
|
||||
|
||||
This is like the basic test, except we put the call site above the function definition.
|
||||
|
||||
```py
|
||||
def bar():
|
||||
foo("hello") # error: [invalid-argument-type]
|
||||
|
||||
def foo(x: int) -> int:
|
||||
return x * x
|
||||
```
|
||||
|
||||
## Different files
|
||||
|
||||
This tests that a diagnostic can point to a function definition in a different file in which an
|
||||
invalid call site was found.
|
||||
|
||||
`package.py`:
|
||||
|
||||
```py
|
||||
def foo(x: int) -> int:
|
||||
return x * x
|
||||
```
|
||||
|
||||
```py
|
||||
import package
|
||||
|
||||
package.foo("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Many parameters
|
||||
|
||||
This checks that a diagnostic renders reasonably when there are multiple parameters.
|
||||
|
||||
```py
|
||||
def foo(x: int, y: int, z: int) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Many parameters across multiple lines
|
||||
|
||||
This checks that a diagnostic renders reasonably when there are multiple parameters spread out
|
||||
across multiple lines.
|
||||
|
||||
```py
|
||||
def foo(
|
||||
x: int,
|
||||
y: int,
|
||||
z: int,
|
||||
) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Many parameters with multiple invalid arguments
|
||||
|
||||
This checks that a diagnostic renders reasonably when there are multiple parameters and multiple
|
||||
invalid argument types.
|
||||
|
||||
```py
|
||||
def foo(x: int, y: int, z: int) -> int:
|
||||
return x * y * z
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
foo("a", "b", "c")
|
||||
```
|
||||
|
||||
At present (2025-02-18), this renders three different diagnostic messages. But arguably, these could
|
||||
all be folded into one diagnostic. Fixing this requires at least better support for multi-spans in
|
||||
the diagnostic model and possibly also how diagnostics are emitted by the type checker itself.
|
||||
|
||||
## Test calling a function whose type is vendored from `typeshed`
|
||||
|
||||
This tests that diagnostic rendering is reasonable when the function being called is from the
|
||||
standard library.
|
||||
|
||||
```py
|
||||
import json
|
||||
|
||||
json.loads(5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Tests for a variety of argument types
|
||||
|
||||
These tests check that diagnostic output is reasonable regardless of the kinds of arguments used in
|
||||
a function definition.
|
||||
|
||||
### Only positional
|
||||
|
||||
Tests a function definition with only positional parameters.
|
||||
|
||||
```py
|
||||
def foo(x: int, y: int, z: int, /) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Variadic arguments
|
||||
|
||||
Tests a function definition with variadic arguments.
|
||||
|
||||
```py
|
||||
def foo(*numbers: int) -> int:
|
||||
return len(numbers)
|
||||
|
||||
foo(1, 2, 3, "hello", 5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Keyword only arguments
|
||||
|
||||
Tests a function definition with keyword-only arguments.
|
||||
|
||||
```py
|
||||
def foo(x: int, y: int, *, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### One keyword argument
|
||||
|
||||
Tests a function definition with keyword-only arguments.
|
||||
|
||||
```py
|
||||
def foo(x: int, y: int, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, 2, "hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Variadic keyword arguments
|
||||
|
||||
```py
|
||||
def foo(**numbers: int) -> int:
|
||||
return len(numbers)
|
||||
|
||||
foo(a=1, b=2, c=3, d="hello", e=5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Mix of arguments
|
||||
|
||||
Tests a function definition with multiple different kinds of arguments.
|
||||
|
||||
```py
|
||||
def foo(x: int, /, y: int, *, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Synthetic arguments
|
||||
|
||||
Tests a function call with synthetic arguments.
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __call__(self, x: int) -> int:
|
||||
return 1
|
||||
|
||||
c = C()
|
||||
c("wrong") # error: [invalid-argument-type]
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Basic
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int) -> int:
|
||||
2 | return x * x
|
||||
3 |
|
||||
4 | foo("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:5
|
||||
|
|
||||
2 | return x * x
|
||||
3 |
|
||||
4 | foo("hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 1 (`x`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:9
|
||||
|
|
||||
1 | def foo(x: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * x
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Different files
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## package.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int) -> int:
|
||||
2 | return x * x
|
||||
```
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | import package
|
||||
2 |
|
||||
3 | package.foo("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:3:13
|
||||
|
|
||||
1 | import package
|
||||
2 |
|
||||
3 | package.foo("hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 1 (`x`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/package.py:1:9
|
||||
|
|
||||
1 | def foo(x: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * x
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Different source order
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def bar():
|
||||
2 | foo("hello") # error: [invalid-argument-type]
|
||||
3 |
|
||||
4 | def foo(x: int) -> int:
|
||||
5 | return x * x
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:2:9
|
||||
|
|
||||
1 | def bar():
|
||||
2 | foo("hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 1 (`x`) of function `foo`; expected type `int`
|
||||
3 |
|
||||
4 | def foo(x: int) -> int:
|
||||
|
|
||||
::: /src/mdtest_snippet.py:4:9
|
||||
|
|
||||
2 | foo("hello") # error: [invalid-argument-type]
|
||||
3 |
|
||||
4 | def foo(x: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
5 | return x * x
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Many parameters
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:8
|
||||
|
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 2 (`y`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:17
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Many parameters across multiple lines
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(
|
||||
2 | x: int,
|
||||
3 | y: int,
|
||||
4 | z: int,
|
||||
5 | ) -> int:
|
||||
6 | return x * y * z
|
||||
7 |
|
||||
8 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:8:8
|
||||
|
|
||||
6 | return x * y * z
|
||||
7 |
|
||||
8 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 2 (`y`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:3:5
|
||||
|
|
||||
1 | def foo(
|
||||
2 | x: int,
|
||||
3 | y: int,
|
||||
| ------ info: parameter declared in function definition here
|
||||
4 | z: int,
|
||||
5 | ) -> int:
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Many parameters with multiple invalid arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | # error: [invalid-argument-type]
|
||||
5 | # error: [invalid-argument-type]
|
||||
6 | # error: [invalid-argument-type]
|
||||
7 | foo("a", "b", "c")
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:7:5
|
||||
|
|
||||
5 | # error: [invalid-argument-type]
|
||||
6 | # error: [invalid-argument-type]
|
||||
7 | foo("a", "b", "c")
|
||||
| ^^^ Object of type `Literal["a"]` cannot be assigned to parameter 1 (`x`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:9
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:7:10
|
||||
|
|
||||
5 | # error: [invalid-argument-type]
|
||||
6 | # error: [invalid-argument-type]
|
||||
7 | foo("a", "b", "c")
|
||||
| ^^^ Object of type `Literal["b"]` cannot be assigned to parameter 2 (`y`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:17
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:7:15
|
||||
|
|
||||
5 | # error: [invalid-argument-type]
|
||||
6 | # error: [invalid-argument-type]
|
||||
7 | foo("a", "b", "c")
|
||||
| ^^^ Object of type `Literal["c"]` cannot be assigned to parameter 3 (`z`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:25
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Test calling a function whose type is vendored from `typeshed`
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | import json
|
||||
2 |
|
||||
3 | json.loads(5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:3:12
|
||||
|
|
||||
1 | import json
|
||||
2 |
|
||||
3 | json.loads(5) # error: [invalid-argument-type]
|
||||
| ^ Object of type `Literal[5]` cannot be assigned to parameter 1 (`s`) of function `loads`; expected type `str | bytes | bytearray`
|
||||
|
|
||||
::: vendored://stdlib/json/__init__.pyi:40:5
|
||||
|
|
||||
38 | ) -> None: ...
|
||||
39 | def loads(
|
||||
40 | s: str | bytes | bytearray,
|
||||
| -------------------------- info: parameter declared in function definition here
|
||||
41 | *,
|
||||
42 | cls: type[JSONDecoder] | None = None,
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Keyword only arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, y: int, *, z: int = 0) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:11
|
||||
|
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter `z` of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:28
|
||||
|
|
||||
1 | def foo(x: int, y: int, *, z: int = 0) -> int:
|
||||
| ---------- info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Mix of arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, /, y: int, *, z: int = 0) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:11
|
||||
|
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter `z` of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:31
|
||||
|
|
||||
1 | def foo(x: int, /, y: int, *, z: int = 0) -> int:
|
||||
| ---------- info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - One keyword argument
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, y: int, z: int = 0) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, "hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:11
|
||||
|
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, 2, "hello") # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 3 (`z`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:25
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int = 0) -> int:
|
||||
| ---------- info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Only positional
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(x: int, y: int, z: int, /) -> int:
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:8
|
||||
|
|
||||
2 | return x * y * z
|
||||
3 |
|
||||
4 | foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter 2 (`y`) of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:17
|
||||
|
|
||||
1 | def foo(x: int, y: int, z: int, /) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
2 | return x * y * z
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Synthetic arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class C:
|
||||
2 | def __call__(self, x: int) -> int:
|
||||
3 | return 1
|
||||
4 |
|
||||
5 | c = C()
|
||||
6 | c("wrong") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:6:3
|
||||
|
|
||||
5 | c = C()
|
||||
6 | c("wrong") # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["wrong"]` cannot be assigned to parameter 2 (`x`) of function `__call__`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:2:24
|
||||
|
|
||||
1 | class C:
|
||||
2 | def __call__(self, x: int) -> int:
|
||||
| ------ info: parameter declared in function definition here
|
||||
3 | return 1
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Variadic arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(*numbers: int) -> int:
|
||||
2 | return len(numbers)
|
||||
3 |
|
||||
4 | foo(1, 2, 3, "hello", 5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:14
|
||||
|
|
||||
2 | return len(numbers)
|
||||
3 |
|
||||
4 | foo(1, 2, 3, "hello", 5) # error: [invalid-argument-type]
|
||||
| ^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter `*numbers` of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:9
|
||||
|
|
||||
1 | def foo(*numbers: int) -> int:
|
||||
| ------------- info: parameter declared in function definition here
|
||||
2 | return len(numbers)
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Tests for a variety of argument types - Variadic keyword arguments
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def foo(**numbers: int) -> int:
|
||||
2 | return len(numbers)
|
||||
3 |
|
||||
4 | foo(a=1, b=2, c=3, d="hello", e=5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type
|
||||
--> /src/mdtest_snippet.py:4:20
|
||||
|
|
||||
2 | return len(numbers)
|
||||
3 |
|
||||
4 | foo(a=1, b=2, c=3, d="hello", e=5) # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^ Object of type `Literal["hello"]` cannot be assigned to parameter `**numbers` of function `foo`; expected type `int`
|
||||
|
|
||||
::: /src/mdtest_snippet.py:1:9
|
||||
|
|
||||
1 | def foo(**numbers: int) -> int:
|
||||
| -------------- info: parameter declared in function definition here
|
||||
2 | return len(numbers)
|
||||
|
|
||||
|
||||
```
|
||||
|
|
@ -6,7 +6,9 @@ use crate::types::diagnostic::{
|
|||
};
|
||||
use crate::types::signatures::Parameter;
|
||||
use crate::types::{todo_type, UnionType};
|
||||
use ruff_db::diagnostic::{SecondaryDiagnosticMessage, Span};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// Bind a [`CallArguments`] against a callable [`Signature`].
|
||||
///
|
||||
|
|
@ -76,6 +78,7 @@ pub(crate) fn bind_call<'db>(
|
|||
if let Some(expected_ty) = parameter.annotated_type() {
|
||||
if !argument_ty.is_assignable_to(db, expected_ty) {
|
||||
errors.push(CallBindingError::InvalidArgumentType {
|
||||
callable_ty,
|
||||
parameter: ParameterContext::new(parameter, index, positional),
|
||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||
expected_ty,
|
||||
|
|
@ -269,6 +272,7 @@ pub(crate) enum CallBindingError<'db> {
|
|||
/// The type of an argument is not assignable to the annotated type of its corresponding
|
||||
/// parameter.
|
||||
InvalidArgumentType {
|
||||
callable_ty: Type<'db>,
|
||||
parameter: ParameterContext,
|
||||
argument_index: Option<usize>,
|
||||
expected_ty: Type<'db>,
|
||||
|
|
@ -303,14 +307,35 @@ impl<'db> CallBindingError<'db> {
|
|||
) {
|
||||
match self {
|
||||
Self::InvalidArgumentType {
|
||||
callable_ty,
|
||||
parameter,
|
||||
argument_index,
|
||||
expected_ty,
|
||||
provided_ty,
|
||||
} => {
|
||||
let mut messages = vec![];
|
||||
if let Some(func_lit) = callable_ty.into_function_literal() {
|
||||
let func_lit_scope = func_lit.body_scope(context.db());
|
||||
let mut span = Span::from(func_lit_scope.file(context.db()));
|
||||
let node = func_lit_scope.node(context.db());
|
||||
if let Some(func_def) = node.as_function() {
|
||||
let range = func_def
|
||||
.parameters
|
||||
.iter()
|
||||
.nth(parameter.index)
|
||||
.map(|param| param.range())
|
||||
.unwrap_or(func_def.parameters.range);
|
||||
span = span.with_range(range);
|
||||
messages.push(SecondaryDiagnosticMessage::new(
|
||||
span,
|
||||
"parameter declared in function definition here",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let provided_ty_display = provided_ty.display(context.db());
|
||||
let expected_ty_display = expected_ty.display(context.db());
|
||||
context.report_lint(
|
||||
context.report_lint_with_secondary_messages(
|
||||
&INVALID_ARGUMENT_TYPE,
|
||||
Self::get_node(node, *argument_index),
|
||||
format_args!(
|
||||
|
|
@ -322,6 +347,7 @@ impl<'db> CallBindingError<'db> {
|
|||
String::new()
|
||||
}
|
||||
),
|
||||
messages,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue