Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow objects matching SupportsKeysAndGetItem to be unpacked #14990

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2126,7 +2126,9 @@ def check_argument_types(
if actual_kind == nodes.ARG_STAR2 and not self.is_valid_keyword_var_arg(
actual_type
):
is_mapping = is_subtype(actual_type, self.chk.named_type("typing.Mapping"))
is_mapping = is_subtype(
actual_type, self.chk.named_type("_typeshed.SupportsKeysAndGetItem")
)
self.msg.invalid_keyword_var_arg(actual_type, is_mapping, context)
expanded_actual = mapper.expand_actual_type(
actual_type, actual_kind, callee.arg_names[i], callee_arg_kind
Expand Down Expand Up @@ -4936,14 +4938,14 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool:
is_subtype(
typ,
self.chk.named_generic_type(
"typing.Mapping",
"_typeshed.SupportsKeysAndGetItem",
[self.named_type("builtins.str"), AnyType(TypeOfAny.special_form)],
),
)
or is_subtype(
typ,
self.chk.named_generic_type(
"typing.Mapping", [UninhabitedType(), UninhabitedType()]
"_typeshed.SupportsKeysAndGetItem", [UninhabitedType(), UninhabitedType()]
),
)
or isinstance(typ, ParamSpecType)
Expand Down
6 changes: 6 additions & 0 deletions mypy/test/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def parse_test_case(case: DataDrivenTestCase) -> None:
src_path = join(os.path.dirname(case.file), item.arg)
with open(src_path, encoding="utf8") as f:
files.append((join(base_path, "typing.pyi"), f.read()))
elif item.id == "_typeshed":
# Use an alternative stub file for the typing module.
bryanforbes marked this conversation as resolved.
Show resolved Hide resolved
assert item.arg is not None
src_path = join(os.path.dirname(case.file), item.arg)
with open(src_path, encoding="utf8") as f:
files.append((join(base_path, "_typeshed.pyi"), f.read()))
elif re.match(r"stale[0-9]*$", item.id):
passnum = 1 if item.id == "stale" else int(item.id[len("stale") :])
assert passnum > 0
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/check-generic-subtyping.test
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ main:13: note: Revealed type is "builtins.dict[builtins.int, builtins.str]"
main:14: error: Keywords must be strings
main:14: error: Argument 1 to "func_with_kwargs" has incompatible type "**X1[str, int]"; expected "int"
[builtins fixtures/dict.pyi]
[typing fixtures/typing-medium.pyi]

[case testSubtypingMappingUnpacking3]
from typing import Generic, TypeVar, Mapping, Iterable
Expand Down
16 changes: 8 additions & 8 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -3699,8 +3699,8 @@ cache_fine_grained = False
[file mypy.ini.2]
\[mypy]
cache_fine_grained = True
[rechecked a, builtins, typing]
[stale a, builtins, typing]
[rechecked _typeshed, a, builtins, typing]
[stale _typeshed, a, builtins, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalPackageNameOverload]
Expand Down Expand Up @@ -3751,8 +3751,8 @@ Signature: 8a477f597d28d172789f06886806bc55
[file b.py.2]
# uh
-- Every file should get reloaded, since the cache was invalidated
[stale a, b, builtins, typing]
[rechecked a, b, builtins, typing]
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalBustedFineGrainedCache2]
Expand All @@ -3764,8 +3764,8 @@ import b
[file b.py.2]
# uh
-- Every file should get reloaded, since the settings changed
[stale a, b, builtins, typing]
[rechecked a, b, builtins, typing]
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalBustedFineGrainedCache3]
Expand All @@ -3780,8 +3780,8 @@ import b
[file b.py.2]
# uh
-- Every file should get reloaded, since the cache was invalidated
[stale a, b, builtins, typing]
[rechecked a, b, builtins, typing]
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalWorkingFineGrainedCache]
Expand Down
22 changes: 15 additions & 7 deletions test-data/unit/check-kwargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ g(**{})

[case testKeywordUnpackWithDifferentTypes]
# https://github.com/python/mypy/issues/11144
from typing import Dict, Generic, TypeVar, Mapping
from typing import Dict, Generic, TypeVar, Mapping, Iterable

T = TypeVar("T")
T2 = TypeVar("T2")
Expand All @@ -516,21 +516,29 @@ class C(Generic[T, T2]):
class D:
...

class E:
def keys(self) -> Iterable[str]:
...
def __getitem__(self, key: str) -> float:
...

def foo(**i: float) -> float:
...

a: A[str, str]
b: B[str, str]
c: C[str, float]
d: D
e = {"a": "b"}
e: E
f = {"a": "b"}

foo(k=1.5)
foo(**a)
foo(**b)
foo(**c)
foo(**d)
foo(**e)
foo(**f)

# Correct:

Expand All @@ -544,9 +552,9 @@ foo(**good1)
foo(**good2)
foo(**good3)
[out]
main:29: error: Argument 1 to "foo" has incompatible type "**A[str, str]"; expected "float"
main:30: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expected "float"
main:31: error: Argument after ** must be a mapping, not "C[str, float]"
main:32: error: Argument after ** must be a mapping, not "D"
main:33: error: Argument 1 to "foo" has incompatible type "**Dict[str, str]"; expected "float"
main:36: error: Argument 1 to "foo" has incompatible type "**A[str, str]"; expected "float"
main:37: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expected "float"
main:38: error: Argument after ** must be a mapping, not "C[str, float]"
main:39: error: Argument after ** must be a mapping, not "D"
main:41: error: Argument 1 to "foo" has incompatible type "**Dict[str, str]"; expected "float"
[builtins fixtures/dict.pyi]
1 change: 1 addition & 0 deletions test-data/unit/fixtures/args.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Builtins stub used to support *args, **kwargs.

import _typeshed
from typing import TypeVar, Generic, Iterable, Sequence, Tuple, Dict, Any, overload, Mapping

Tco = TypeVar('Tco', covariant=True)
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/dataclasses.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _typeshed
from typing import (
Generic, Iterator, Iterable, Mapping, Optional, Sequence, Tuple,
TypeVar, Union, overload,
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/dict.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Builtins stub used in dictionary-related test cases.

import _typeshed
from typing import (
TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence
)
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/paramspec.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# builtins stub for paramspec-related test cases

import _typeshed
from typing import (
Sequence, Generic, TypeVar, Iterable, Iterator, Tuple, Mapping, Optional, Union, Type, overload,
Protocol
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/primitives.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# builtins stub with non-generic primitive types
import _typeshed
from typing import Generic, TypeVar, Sequence, Iterator, Mapping, Iterable, Tuple, Union

T = TypeVar('T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/tuple.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Builtins stub used in tuple-related test cases.

import _typeshed
from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Type

T = TypeVar("T")
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/typing-medium.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Sequence(Iterable[T_co]):
def __getitem__(self, n: Any) -> T_co: pass

class Mapping(Iterable[T], Generic[T, T_co]):
def keys(self) -> Iterable[T]: pass # Approximate return type
def __getitem__(self, key: T) -> T_co: pass

class SupportsInt(Protocol):
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/typing-typeddict.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Sequence(Iterable[T_co]):
def __getitem__(self, n: Any) -> T_co: pass # type: ignore[misc]

class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta):
def keys(self) -> Iterable[T]: pass # Approximate return type
def __getitem__(self, key: T) -> T_co: pass
@overload
def get(self, k: T) -> Optional[T_co]: pass
Expand Down
8 changes: 8 additions & 0 deletions test-data/unit/lib-stub/_typeshed.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Protocol, TypeVar, Iterable

_KT = TypeVar("_KT")
_VT_co = TypeVar("_VT_co", covariant=True)

class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
def keys(self) -> Iterable[_KT]: ...
def __getitem__(self, __key: _KT) -> _VT_co: ...
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# Use [builtins fixtures/...pyi] if you need more features.

import _typeshed

class object:
def __init__(self) -> None: pass

Expand Down
4 changes: 3 additions & 1 deletion test-data/unit/lib-stub/typing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ class Sequence(Iterable[T_co]):
def __getitem__(self, n: Any) -> T_co: pass

# Mapping type is oversimplified intentionally.
class Mapping(Iterable[T], Generic[T, T_co]): pass
class Mapping(Iterable[T], Generic[T, T_co]):
def keys(self) -> Iterable[T]: pass # Approximate return type
def __getitem__(self, key: T) -> T_co: pass

class Awaitable(Protocol[T]):
def __await__(self) -> Generator[Any, Any, T]: pass
Expand Down