-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Implement type-aware get
for TypedDict
#2620
Changes from all commits
4028dfc
9186c52
79a5114
9e94cff
c5f7481
b1022bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -431,6 +431,90 @@ def set_coordinate(p: TaggedPoint, key: str, value: int) -> None: | |
|
||
-- Special Method: get | ||
|
||
[case testCanUseGetMethodWithStringLiteralKey] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
p = TaggedPoint(type='2d', x=42, y=1337) | ||
reveal_type(p.get('type')) # E: Revealed type is 'Union[builtins.str, builtins.None]' | ||
reveal_type(p.get('x')) # E: Revealed type is 'Union[builtins.int, builtins.None]' | ||
reveal_type(p.get('y', 0)) # E: Revealed type is 'builtins.int' | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testDefaultParameterStillTypeChecked] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
p = TaggedPoint(type='2d', x=42, y=1337) | ||
p.get('x', 1 + 'y') # E: Unsupported operand types for + ("int" and "str") | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testCannotGetMethodWithInvalidStringLiteralKey] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
p = TaggedPoint(type='2d', x=42, y=1337) | ||
p.get('z') # E: 'z' is not a valid item name; expected one of ['type', 'x', 'y'] | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testGetMethodWithVariableKeyFallsBack] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
p = TaggedPoint(type='2d', x=42, y=1337) | ||
key = 'type' | ||
reveal_type(p.get(key)) # E: Revealed type is 'builtins.object*' | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testChainedGetMethodWithDictFallback] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
PointSet = TypedDict('PointSet', {'first_point': TaggedPoint}) | ||
p = PointSet(first_point=TaggedPoint(type='2d', x=42, y=1337)) | ||
reveal_type(p.get('first_point', {}).get('x', 0)) # E: Revealed type is 'builtins.int' | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testGetMethodInvalidDefaultType] | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
PointSet = TypedDict('PointSet', {'first_point': TaggedPoint}) | ||
p = PointSet(first_point=TaggedPoint(type='2d', x=42, y=1337)) | ||
p.get('first_point', 32) # E: Argument 2 to "get" of "Mapping" has incompatible type "int"; expected "Union[TypedDict(type=str, x=int, y=int), Mapping]" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a pretty minor thing, but the error message is a little confusing: it says (If this seems hard to do, we can create as a separate issue for this.) |
||
[builtins fixtures/dict.pyi] | ||
|
||
[case testGetMethodOnList] | ||
from typing import List | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
PointSet = TypedDict('PointSet', {'points': List[TaggedPoint]}) | ||
p = PointSet(points=[TaggedPoint(type='2d', x=42, y=1337)]) | ||
reveal_type(p.get('points', [])) # E: Revealed type is 'builtins.list[TypedDict(type=builtins.str, x=builtins.int, y=builtins.int, _fallback=__main__.TaggedPoint)]' | ||
[builtins fixtures/dict.pyi] | ||
|
||
[case testGetMethodWithListOfStrUnifies] | ||
from typing import List | ||
from mypy_extensions import TypedDict | ||
Items = TypedDict('Items', {'name': str, 'values': List[str]}) | ||
def foo(i: Items) -> None: | ||
reveal_type(i.get('values', [])) # E: Revealed type is 'builtins.list[builtins.str]' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style nit: using 2 spaces for indent here. |
||
[builtins fixtures/dict.pyi] | ||
|
||
[case testDictGetMethodStillCallable] | ||
from typing import Callable | ||
from mypy_extensions import TypedDict | ||
Point = TypedDict('Point', {'x': int, 'y': int}) | ||
p = Point(x=42, y=13) | ||
def invoke_method(method: Callable[[str, int], int]) -> None: | ||
pass | ||
invoke_method(p.get) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a test case where |
||
[builtins fixtures/dict.pyi] | ||
|
||
[case testDictGetMethodStillCallableWithObject] | ||
from typing import Callable | ||
from mypy_extensions import TypedDict | ||
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int}) | ||
p = TaggedPoint(type='2d', x=42, y=1337) | ||
def invoke_method(method: Callable[..., object]) -> None: | ||
pass | ||
invoke_method(p.get) | ||
[builtins fixtures/dict.pyi] | ||
|
||
-- TODO: Implement support for these cases: | ||
--[case testGetOfTypedDictWithValidStringLiteralKeyReturnsPreciseType] | ||
--[case testGetOfTypedDictWithInvalidStringLiteralKeyIsError] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would better to use the type such as
Mapping[str, Any]
. Not having type arguments forMapping
may cause trouble in some cases (e.g. index errors). This still wouldn't be quite safe, but let's deal with that as a separate issue.