diff --git a/crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md b/crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md new file mode 100644 index 0000000000..b7e7c0d7f8 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md @@ -0,0 +1,184 @@ +# Invalid argument type 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] +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap new file mode 100644 index 0000000000..95cc2f4d85 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap new file mode 100644 index 0000000000..bc96318e16 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap new file mode 100644 index 0000000000..e7a3ea56ca --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap new file mode 100644 index 0000000000..a37e40dd63 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap new file mode 100644 index 0000000000..c574436126 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -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: + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap new file mode 100644 index 0000000000..fe91855369 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap new file mode 100644 index 0000000000..e69ae8d1e3 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -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, + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap new file mode 100644 index 0000000000..3a493cd027 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap new file mode 100644 index 0000000000..4fd84c4761 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap new file mode 100644 index 0000000000..810d2982f2 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap new file mode 100644 index 0000000000..8ef56dab0a --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap new file mode 100644 index 0000000000..2d675d6314 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -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 + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap new file mode 100644 index 0000000000..a31ce73230 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -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) + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap new file mode 100644 index 0000000000..0c070a8466 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -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) + | + +``` diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index eba60e76f9..b98ae52249 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -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, 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, ); }