-
Notifications
You must be signed in to change notification settings - Fork 243
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added test for
**kwargs
with Unpack TypedDict annotation.
- Loading branch information
Showing
10 changed files
with
248 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
conformant = "Pass" | ||
output = """ | ||
callables_kwargs.py:22: note: "func1" defined here | ||
callables_kwargs.py:43: error: Missing named argument "v1" for "func1" [call-arg] | ||
callables_kwargs.py:43: error: Missing named argument "v3" for "func1" [call-arg] | ||
callables_kwargs.py:48: error: Unexpected keyword argument "v4" for "func1" [call-arg] | ||
callables_kwargs.py:49: error: Too many positional arguments for "func1" [misc] | ||
callables_kwargs.py:55: error: Argument 1 to "func1" has incompatible type "**dict[str, str]"; expected "int" [arg-type] | ||
callables_kwargs.py:58: error: Argument 1 to "func1" has incompatible type "**dict[str, object]"; expected "int" [arg-type] | ||
callables_kwargs.py:58: error: Argument 1 to "func1" has incompatible type "**dict[str, object]"; expected "str" [arg-type] | ||
callables_kwargs.py:60: error: "func1" gets multiple values for keyword argument "v1" [misc] | ||
callables_kwargs.py:61: error: "func2" gets multiple values for keyword argument "v3" [misc] | ||
callables_kwargs.py:61: error: Argument 1 to "func2" has incompatible type "int"; expected "str" [arg-type] | ||
callables_kwargs.py:62: error: "func2" gets multiple values for keyword argument "v1" [misc] | ||
callables_kwargs.py:98: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol3") [assignment] | ||
callables_kwargs.py:98: note: "TDProtocol3.__call__" has type "Callable[[NamedArg(int, 'v1'), NamedArg(int, 'v2'), NamedArg(str, 'v3')], None]" | ||
callables_kwargs.py:99: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol4") [assignment] | ||
callables_kwargs.py:99: note: "TDProtocol4.__call__" has type "Callable[[NamedArg(int, 'v1')], None]" | ||
callables_kwargs.py:100: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol5") [assignment] | ||
callables_kwargs.py:100: note: "TDProtocol5.__call__" has type "Callable[[Arg(int, 'v1'), Arg(str, 'v3')], None]" | ||
callables_kwargs.py:109: error: Overlap between argument names and ** TypedDict items: "v1" [misc] | ||
callables_kwargs.py:121: error: Unpack item in ** argument must be a TypedDict [misc] | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
version = "mypy 1.8.0" | ||
test_duration = 0.33256983757019043 | ||
test_duration = 0.4829740524291992 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
conformant = "Unsupported" | ||
note = """ | ||
Does not understand Unpack in the context of **kwargs annotation. | ||
""" | ||
output = """ | ||
callables_kwargs.py:22:20 Undefined or invalid type [11]: Annotation `Unpack` is not defined as a type. | ||
callables_kwargs.py:49:4 Too many arguments [19]: Call `func1` expects 1 positional argument, 4 were provided. | ||
callables_kwargs.py:59:12 Incompatible parameter type [6]: In call `func2`, for 1st positional argument, expected `str` but got `object`. | ||
callables_kwargs.py:61:10 Incompatible parameter type [6]: In call `func2`, for 1st positional argument, expected `str` but got `int`. | ||
callables_kwargs.py:62:18 Incompatible parameter type [6]: In call `func2`, for 2nd positional argument, expected `str` but got `object`. | ||
callables_kwargs.py:121:20 Invalid type variable [34]: The type variable `Variable[T (bound to callables_kwargs.TD2)]` isn't present in the function's parameters. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
version = "pyre 0.9.19" | ||
test_duration = 1.4521031379699707 | ||
test_duration = 1.5891530513763428 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
conformant = "Pass" | ||
output = """ | ||
callables_kwargs.py:26:5 - error: Could not access item in TypedDict | ||
"v2" is not a required key in "*TD2", so access may result in runtime exception (reportTypedDictNotRequiredAccess) | ||
callables_kwargs.py:43:5 - error: Arguments missing for parameters "v1", "v3" (reportGeneralTypeIssues) | ||
callables_kwargs.py:48:32 - error: No parameter named "v4" (reportGeneralTypeIssues) | ||
callables_kwargs.py:49:11 - error: Expected 0 positional arguments (reportGeneralTypeIssues) | ||
callables_kwargs.py:55:13 - error: Argument of type "str" cannot be assigned to parameter "v1" of type "int" in function "func1" | ||
"str" is incompatible with "int" (reportGeneralTypeIssues) | ||
callables_kwargs.py:60:19 - error: Unable to match unpacked TypedDict argument to parameters | ||
Parameter "v1" is already assigned (reportGeneralTypeIssues) | ||
callables_kwargs.py:61:16 - error: Unable to match unpacked TypedDict argument to parameters | ||
Parameter "v3" is already assigned (reportGeneralTypeIssues) | ||
callables_kwargs.py:62:19 - error: Unable to match unpacked TypedDict argument to parameters | ||
Parameter "v1" is already assigned (reportGeneralTypeIssues) | ||
callables_kwargs.py:98:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol3" | ||
Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(*, v1: int, v2: int, v3: str) -> None" | ||
Keyword parameter "v2" of type "int" cannot be assigned to type "str" | ||
"int" is incompatible with "str" (reportGeneralTypeIssues) | ||
callables_kwargs.py:99:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol4" | ||
Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(*, v1: int) -> None" | ||
Keyword parameter "v3" is missing in destination (reportGeneralTypeIssues) | ||
callables_kwargs.py:100:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol5" | ||
Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(v1: int, v3: str) -> None" | ||
Function accepts too many positional parameters; expected 0 but received 2 | ||
Keyword parameter "v1" is missing in destination | ||
Keyword parameter "v3" is missing in destination (reportGeneralTypeIssues) | ||
callables_kwargs.py:109:30 - error: Typed dictionary overlaps with keyword parameter: v1 (reportGeneralTypeIssues) | ||
callables_kwargs.py:121:21 - error: Expected TypedDict type argument for Unpack (reportGeneralTypeIssues) | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
version = "pyright 1.1.343" | ||
test_duration = 0.8812670707702637 | ||
test_duration = 0.8890669345855713 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
conformant = "Unsupported" | ||
note = """ | ||
Does not understand Unpack in the context of **kwargs annotation. | ||
""" | ||
output = """ | ||
File "callables_kwargs.py", line 10, in <module>: typing.NotRequired not supported yet [not-supported-yet] | ||
File "callables_kwargs.py", line 10, in <module>: typing.Required not supported yet [not-supported-yet] | ||
File "callables_kwargs.py", line 10, in <module>: typing.Unpack not supported yet [not-supported-yet] | ||
File "callables_kwargs.py", line 24, in func1: Unpack[TD2] [assert-type] | ||
Expected: int | ||
Actual: Unpack[TD2] | ||
File "callables_kwargs.py", line 30, in func1: Unpack[TD2] [assert-type] | ||
Expected: str | ||
Actual: Unpack[TD2] | ||
File "callables_kwargs.py", line 33, in func1: Unpack[TD2] [assert-type] | ||
Expected: str | ||
Actual: Unpack[TD2] | ||
File "callables_kwargs.py", line 39, in func2: Dict[str, Unpack[TD1]] [assert-type] | ||
Expected: TD1 | ||
Actual: Dict[str, Unpack[TD1]] | ||
File "callables_kwargs.py", line 44, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v1: Unpack[TD2], ...) | ||
Actually passed: (v1: int, ...) | ||
File "callables_kwargs.py", line 46, in func3: Function TD2.__init__ was called with the wrong arguments [wrong-arg-types] | ||
Expected: (*, v1: Required[int], ...) | ||
Actually passed: (v1: int, ...) | ||
File "callables_kwargs.py", line 48, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v1: Unpack[TD2], ...) | ||
Actually passed: (v1: int, ...) | ||
File "callables_kwargs.py", line 49, in func3: Function func1 expects 0 arg(s), got 3 [wrong-arg-count] | ||
Expected: (**kwargs) | ||
Actually passed: (_, _, _) | ||
File "callables_kwargs.py", line 55, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (**kwargs: Mapping[str, Unpack[TD2]]) | ||
Actually passed: (kwargs: Dict[str, str]) | ||
File "callables_kwargs.py", line 58, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v1: Unpack[TD2], ...) | ||
Actually passed: (v1: int, ...) | ||
File "callables_kwargs.py", line 60, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v1: Unpack[TD2], ...) | ||
Actually passed: (v1: int, ...) | ||
File "callables_kwargs.py", line 61, in func3: Function func2 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v3: str, ...) | ||
Actually passed: (v3: int, ...) | ||
File "callables_kwargs.py", line 62, in func3: Function func2 was called with the wrong arguments [wrong-arg-types] | ||
Expected: (v3, v1: Unpack[TD1], ...) | ||
Actually passed: (v1: int, ...) | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
version = "pytype 2023.12.18" | ||
test_duration = 40.140138149261475 | ||
test_duration = 41.63518166542053 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
""" | ||
Tests the use of an unpacked TypedDict for annotating **kwargs. | ||
""" | ||
|
||
# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments | ||
|
||
# This sample tests the handling of Unpack[TypedDict] when used with | ||
# a **kwargs parameter in a function signature. | ||
|
||
from typing import Protocol, TypeVar, TypedDict, NotRequired, Required, Unpack, assert_type | ||
|
||
|
||
class TD1(TypedDict): | ||
v1: Required[int] | ||
v2: NotRequired[str] | ||
|
||
|
||
class TD2(TD1): | ||
v3: Required[str] | ||
|
||
|
||
def func1(**kwargs: Unpack[TD2]) -> None: | ||
v1 = kwargs["v1"] | ||
assert_type(v1, int) | ||
|
||
kwargs["v2"] # Type error: v2 may not be present | ||
|
||
if "v2" in kwargs: | ||
v2 = kwargs["v2"] | ||
assert_type(v2, str) | ||
|
||
v3 = kwargs["v3"] | ||
assert_type(v3, str) | ||
|
||
|
||
def func2(v3: str, **kwargs: Unpack[TD1]) -> None: | ||
# > When Unpack is used, type checkers treat kwargs inside the function | ||
# > body as a TypedDict. | ||
assert_type(kwargs, TD1) | ||
|
||
|
||
def func3() -> None: | ||
func1() # Type error: missing required keyword args | ||
func1(v1=1, v2="", v3="5") # OK | ||
|
||
td2 = TD2(v1=2, v3="4") | ||
func1(**td2) # OK | ||
func1(v1=1, v2="", v3="5", v4=5) # Type error: v4 is not in TD2 | ||
func1(1, "", "5") # Type error: args not passed by position | ||
|
||
# > Passing a dictionary of type dict[str, object] as a **kwargs argument | ||
# > to a function that has **kwargs annotated with Unpack must generate a | ||
# > type checker error. | ||
my_dict: dict[str, str] = {} | ||
func1(**my_dict) # Type error: untyped dict | ||
|
||
d1 = {"v1": 2, "v3": "4", "v4": 4} | ||
func1(**d1) # OK or Type error (spec allows either) | ||
func2(**td2) # OK | ||
func1(v1=2, **td2) # Type error: v1 is already specified | ||
func2(1, **td2) # Type error: v1 is already specified | ||
func2(v1=1, **td2) # Type error: v1 is already specified | ||
|
||
|
||
class TDProtocol1(Protocol): | ||
def __call__(self, *, v1: int, v3: str) -> None: | ||
... | ||
|
||
|
||
class TDProtocol2(Protocol): | ||
def __call__(self, *, v1: int, v3: str, v2: str = "") -> None: | ||
... | ||
|
||
|
||
class TDProtocol3(Protocol): | ||
def __call__(self, *, v1: int, v2: int, v3: str) -> None: | ||
... | ||
|
||
|
||
class TDProtocol4(Protocol): | ||
def __call__(self, *, v1: int) -> None: | ||
... | ||
|
||
|
||
class TDProtocol5(Protocol): | ||
def __call__(self, v1: int, v3: str) -> None: | ||
... | ||
|
||
|
||
class TDProtocol6(Protocol): | ||
def __call__(self, **kwargs: Unpack[TD2]) -> None: | ||
... | ||
|
||
# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#assignment | ||
|
||
v1: TDProtocol1 = func1 # OK | ||
v2: TDProtocol2 = func1 # OK | ||
v3: TDProtocol3 = func1 # Type error: v2 is wrong type | ||
v4: TDProtocol4 = func1 # Type error: v3 is missing | ||
v5: TDProtocol5 = func1 # Type error: params are positional | ||
v6: TDProtocol6 = func1 # OK | ||
|
||
|
||
def func4(v1: int, /, **kwargs: Unpack[TD2]) -> None: | ||
... | ||
|
||
|
||
# Type error: parameter v1 overlaps with the TypedDict. | ||
def func5(v1: int, **kwargs: Unpack[TD2]) -> None: | ||
... | ||
|
||
|
||
T = TypeVar("T", bound=TD2) | ||
|
||
# > TypedDict is the only permitted heterogeneous type for typing **kwargs. | ||
# > Therefore, in the context of typing **kwargs, using Unpack with types other | ||
# > than TypedDict should not be allowed and type checkers should generate | ||
# > errors in such cases. | ||
|
||
# Type error: unpacked value must be a TypedDict, not a TypeVar bound to TypedDict. | ||
def func6(**kwargs: Unpack[T]) -> None: | ||
... | ||
|