-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Summary
At runtime, any object can be **
-unpacked if that object has a .keys()
method and a __getitem__
method:
>>> from typing import KeysView
>>> class HasKeysAndGetItem:
... def keys(self) -> KeysView[str]:
... return {"foo": 42}.keys()
...
... def __getitem__(self, arg: str) -> int:
... return 42
...
>>> {**HasKeysAndGetItem()}
{'foo': 42}
Functions that have **kwargs
parameters have the additional requirement that the keys of the mapping must be strings:
>>> def f(**kwargs): ...
...
>>> f(**HasKeysAndGetItem())
>>> f(**{42: 42})
Traceback (most recent call last):
File "<python-input-7>", line 1, in <module>
f(**{42: 42})
~^^^^^^^^^^^^
TypeError: keywords must be strings
Ty should attempt to emulate this exactly:
- All mappings used with
**
splats should be treated the same way, assuming they have akeys
method and a__getitem__
method - Invalid
**
splats should be detected and should cause us to emit diagnostics
We currently get this right for the **kwargs
function-call case, but not for **
splats inside dictionary literals, where it appears we treat dict
s different to other mappings and we do not emit diagnostics for invalid **
splats:
from typing import Mapping, KeysView
class HasKeysAndGetItem:
def keys(self) -> KeysView[str]:
return {}.keys()
def __getitem__(self, arg: str) -> int:
return 42
def h(**kwargs): ...
def f(
a: dict[str, int],
b: Mapping[str, int],
c: HasKeysAndGetItem,
d: object
):
reveal_type({**a}) # dict[Unknown | str, Unknown | int] (good!)
reveal_type({**b}) # dict[Unknown, Unknown] (bad!)
reveal_type({**c}) # dict[Unknown, Unknown] (bad!)
reveal_type({**d}) # dict[Unknown, Unknown] (good, but no diagnostic emitted!)
h(**a)
h(**b)
h(**c)
h(**d) # error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `object`"
The logic for **
splats passed to function calls looks correct to me -- I think we probably want to do exactly the same thing inside dictionary literals, except that we don't need to check whether the type of the keys is assignable to str
. So we may want to extract that logic out into a helper function somewhere: https://github.com/astral-sh/ruff/blob/69f918203359f834e0ca76764a502f3421b2c2c7/crates/ty_python_semantic/src/types/call/bind.rs#L2691-L2742
Cc. @ibraheemdev -- this looks related to astral-sh/ruff#20523
Version
No response