-
Notifications
You must be signed in to change notification settings - Fork 219
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 stringsTy should attempt to emulate this exactly:
- All mappings used with
**splats should be treated the same way, assuming they have akeysmethod 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 dicts 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