Open macdjord opened 9 months ago
I agree with your analysis except for j
, which should generate an error based on the typeshed definition of the dict.update
method.
For comparison, pyright generates errors for g
, h
and j
.
@erictraut: i
and j
are functionally identical; I see no reason why one should cause a warning when the other does not.
@macdjord, from a typing perspective, i
and j
are different. With i
, you're passing a value of type dict_items[str, None]
. With j
, you're passing a value of type dict[str, None]
. The dict.update
method accepts a value of type Iterable[tuple[str | int, None]]
. The type dict_items[str, None]
is compatible with this type, but dict[str, None]
is not.
from typing import Iterable
from _collections_abc import dict_items
def func(d_i: dict_items[str, None], d_j: dict[str, None]):
v: Iterable[tuple[str | int, None]]
v = d_i # No type error
v = d_j # Type error
Well, yes, obviously v = d_j
would be a type error, because dict[KT, VT]
is Iterable[KT]
, not Iterable[tuple[KT, VT]]
. But that's not actually a problem in my example because dict.update()
accepts Iterable[tuple[KT, VT]]
or Mapping[KT, VT]
.
... because
dict.update()
acceptsIterable[tuple[KT, VT]]
orMapping[KT, VT]
Actually, it doesn't accept Mapping[KT, VT]
, but it does accept SupportsKeysAndGetItem[KT, VT]
. Regardless, both Mapping
and SupportsKeysAndGetItem
define the KT
type parameter as invariant, which means str
is not compatible with str | int
.
By contrast, the type parameters for Iterable
and tuple
are covariant, which is why I was focusing on the Iterable[tuple[KT, VT]]
overload.
def func(d_j: dict[str, None]):
s: SupportsKeysAndGetItem[str | int, None]
s = d_j # Type error
m: Mapping[str | int, None]
m = d_j # Type error
Bug Report
When creating or updating a new dict with items from another dict with a narrower key type, Mypy complains about some methods which are actually perfectly valid.
To Reproduce
Expected Behavior
The above code shows 10 ways of creating the new dictionary:
a
doesn't actually clone the source dict; it just creates a new dict from a dict literal that just happens to only contain string keysb
copies the source dict using a dict comprehension and.items()
c
uses a dict comprehension and.keys()
(possible in this case because the dict value type isNone
, which is a singleton)d
uses a dict comprehension and star-star-expansione
uses thedict()
constructor and.items()
f
uses thedict()
constructor and the source dict directlyg
uses the built-in.copy()
methodh
uses a user-created dict copying methodi
creates an empty dict and then.update()
s it using.items()
j
creates an empty dict and then.update()
s it using the source dict directlyOf these:
h
; Mypy has no way of knowing that the function in question is returning a fresh dict which is used nowhere else (and thus can safely be implicitly cast to a wider type)g
; since that is a built-in method, it could be special-cased into Mypy that this is safe, but absent such a special case it would have the same semantics asclone_dict()
Actual Behavior
Mypy correctly warns about
g
andh
, but also issues spurious warnings aboutd
,f
, andj
:Your Environment
mypy 1.5.1 (compiled: yes)
mypy.ini
(and other config files): NonePython 3.11.2