[ty] check that starred arguments in function calls are iterable#22805
[ty] check that starred arguments in function calls are iterable#22805AlexWaygood merged 2 commits intoastral-sh:mainfrom
Conversation
Typing conformance resultsNo changes detected ✅ |
|
|
Thank you! Interestingly, we already emit a diagnostic if you attempt to ruff/crates/ty_python_semantic/src/types/call/bind.rs Lines 3589 to 3616 in f4b84a9 KeywordNotAMapping is listed as one of the errors that affects overload resolution, so I'm not sure how this new error should interact with the overload evaluation call algorithm and whether it should also affect overload resolution...?
ruff/crates/ty_python_semantic/src/types/call/bind.rs Lines 4287 to 4314 in f4b84a9 Paging @dhruvmanila as our expert in this area! |
|
@AlexWaygood |
dhruvmanila
left a comment
There was a problem hiding this comment.
It's an interesting point on how to think about whether it affects overload resolution. Looking at the KeywordsNotAMapping error in the following case:
from typing import overload
@overload
def h(**kwargs: int) -> int: ...
@overload
def h(**kwargs: str) -> str: ...
def h(**kwargs): ...
h(**None)ty currently reports it as no-matching-overload but I think it'd be much better if we could report the actual error which is that the argument after ** isn't a mapping type. Another option is to report no-matching-overload and add the invalid-argument-type as a sub-diagnostic.
It's interesting that in the above case only mypy raises the actual error while other type checkers (Pyright, pyrefly) raises no-matching-overload.
I think the only way to have this behavior of raising the argument type error instead of no-matching-overload is to extract out the diagnostic reporting to where this PR has added the not-iterable diagnostic.
So, the location in this PR looks good to me.
| reveal_type(f(**Foo(a=1, b="b"))) # revealed: int | str | ||
| ``` | ||
|
|
||
| ## Non-iterable variadic argument |
There was a problem hiding this comment.
Can you add an example with overloads?
overloaded.pyi:
from typing import overload
@overload
def foo(a: int) -> tuple[int]: ...
@overload
def foo(a: int, b: int) -> tuple[int, int]: ...from overloaded import foo
foo(*None)
def _(arg: int):
foo(*arg)|
Oh, actually, this would be problematic because it would pick some overload then which you can see in the revealed type. Considering the example in my above comment related to overloads with variadic, the reveal type for |
|
I think the reason it's picking the first overload is because none of the overloads got filtered out and at the end it fallback to picking the first overload. I still think that the location of this and If you're interested, a follow-up PR could be to move the |
Yes, would love to do that change in a follow-up PR |
|
@dhruvmanila to avoid confusion, do you want me to change anything as of now for this PR or it's fine as is? |
|
I think we'd still like you to add a test that uses overloads, as per #22805 (comment)! Just to demonstrate what our current behaviour is. But after that, this should be good to go. |
Summary
Closes #2582
some_fn(*None) incorrectly passed type checking. Now starred arguments in function calls are validated for iterability, matching the existing behavior for starred expressions in assignments.
Test Plan
Added test cases in call/function.md covering: