-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add support for additional TypedDict methods #6011
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
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
b0ca5c1
WIP
JukkaL 8dcbfcf
More WIP
JukkaL 3df4401
More WIP
JukkaL de0de96
Support del x['key'] with TypedDict
JukkaL 4e655cc
Remove **kwargs from stub due to stub issues
JukkaL fabbc6b
Update docstring
JukkaL 59395de
Filter out some TypedDict fine-grained dependencies
JukkaL 50fc0db
Fix anonymous TypedDict types
JukkaL afd0f12
Fix binder test case
JukkaL fbf9a3b
Fix lint
JukkaL 820b19e
Fix semantic analyzer tests
JukkaL bc68360
Add minimal support for TypedDict.update(x)
JukkaL 9132178
Remove references to _TypedDict from error messages
JukkaL 5626a8d
Reject some unsafe method calls
JukkaL 25c3e6d
Remove debug print
JukkaL 8dd642c
Improve some error messages
JukkaL a793d6f
Catch more non-literal keys
JukkaL 6975fce
Some cleanup
JukkaL 7b776fa
Update python evalution tests
JukkaL 576f923
Fix type check
JukkaL 810815a
Add comment
JukkaL 02d5f31
Fix some unsafety when using bound dict methods
JukkaL 51edafa
Fix unsafety issues with TypedDict "del x[y]"
JukkaL ab7a485
Fix type signature
JukkaL 82189dd
Update based on review
JukkaL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp, TypeVarType, | ||
AnyType, TypeList, UnboundType, TypeOfAny, TypeType, | ||
) | ||
from mypy import messages | ||
from mypy.messages import MessageBuilder | ||
from mypy.options import Options | ||
import mypy.interpreted_plugin | ||
|
@@ -61,6 +62,10 @@ class CheckerPluginInterface: | |
msg = None # type: MessageBuilder | ||
options = None # type: Options | ||
|
||
@abstractmethod | ||
def fail(self, msg: str, ctx: Context) -> None: | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def named_generic_type(self, name: str, args: List[Type]) -> Instance: | ||
raise NotImplementedError | ||
|
@@ -400,6 +405,14 @@ def get_method_signature_hook(self, fullname: str | |
|
||
if fullname == 'typing.Mapping.get': | ||
return typed_dict_get_signature_callback | ||
elif fullname == 'mypy_extensions._TypedDict.setdefault': | ||
return typed_dict_setdefault_signature_callback | ||
elif fullname == 'mypy_extensions._TypedDict.pop': | ||
return typed_dict_pop_signature_callback | ||
elif fullname == 'mypy_extensions._TypedDict.update': | ||
return typed_dict_update_signature_callback | ||
elif fullname == 'mypy_extensions._TypedDict.__delitem__': | ||
return typed_dict_delitem_signature_callback | ||
elif fullname == 'ctypes.Array.__setitem__': | ||
return ctypes.array_setitem_callback | ||
return None | ||
|
@@ -412,6 +425,12 @@ def get_method_hook(self, fullname: str | |
return typed_dict_get_callback | ||
elif fullname == 'builtins.int.__pow__': | ||
return int_pow_callback | ||
elif fullname == 'mypy_extensions._TypedDict.setdefault': | ||
return typed_dict_setdefault_callback | ||
elif fullname == 'mypy_extensions._TypedDict.pop': | ||
return typed_dict_pop_callback | ||
elif fullname == 'mypy_extensions._TypedDict.__delitem__': | ||
return typed_dict_delitem_callback | ||
elif fullname == 'ctypes.Array.__getitem__': | ||
return ctypes.array_getitem_callback | ||
elif fullname == 'ctypes.Array.__iter__': | ||
|
@@ -544,6 +563,136 @@ def typed_dict_get_callback(ctx: MethodContext) -> Type: | |
return ctx.default_return_type | ||
|
||
|
||
def typed_dict_pop_signature_callback(ctx: MethodSigContext) -> CallableType: | ||
"""Try to infer a better signature type for TypedDict.pop. | ||
|
||
This is used to get better type context for the second argument that | ||
depends on a TypedDict value type. | ||
""" | ||
signature = ctx.default_signature | ||
str_type = ctx.api.named_generic_type('builtins.str', []) | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(ctx.args) == 2 | ||
and len(ctx.args[0]) == 1 | ||
and isinstance(ctx.args[0][0], StrExpr) | ||
and len(signature.arg_types) == 2 | ||
and len(signature.variables) == 1 | ||
and len(ctx.args[1]) == 1): | ||
key = ctx.args[0][0].value | ||
value_type = ctx.type.items.get(key) | ||
if value_type: | ||
# Tweak the signature to include the value type as context. It's | ||
# only needed for type inference since there's a union with a type | ||
# variable that accepts everything. | ||
tv = TypeVarType(signature.variables[0]) | ||
typ = UnionType.make_simplified_union([value_type, tv]) | ||
return signature.copy_modified( | ||
arg_types=[str_type, typ], | ||
ret_type=typ) | ||
return signature.copy_modified(arg_types=[str_type, signature.arg_types[1]]) | ||
|
||
|
||
def typed_dict_pop_callback(ctx: MethodContext) -> Type: | ||
"""Type check and infer a precise return type for TypedDict.pop.""" | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(ctx.arg_types) >= 1 | ||
and len(ctx.arg_types[0]) == 1): | ||
if isinstance(ctx.args[0][0], StrExpr): | ||
key = ctx.args[0][0].value | ||
if key in ctx.type.required_keys: | ||
ctx.api.msg.typeddict_key_cannot_be_deleted(ctx.type, key, ctx.context) | ||
value_type = ctx.type.items.get(key) | ||
if value_type: | ||
if len(ctx.args[1]) == 0: | ||
return value_type | ||
elif (len(ctx.arg_types) == 2 and len(ctx.arg_types[1]) == 1 | ||
and len(ctx.args[1]) == 1): | ||
return UnionType.make_simplified_union([value_type, ctx.arg_types[1][0]]) | ||
else: | ||
ctx.api.msg.typeddict_key_not_found(ctx.type, key, ctx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
else: | ||
ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
return ctx.default_return_type | ||
|
||
|
||
def typed_dict_setdefault_signature_callback(ctx: MethodSigContext) -> CallableType: | ||
"""Try to infer a better signature type for TypedDict.setdefault. | ||
|
||
This is used to get better type context for the second argument that | ||
depends on a TypedDict value type. | ||
""" | ||
signature = ctx.default_signature | ||
str_type = ctx.api.named_generic_type('builtins.str', []) | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(ctx.args) == 2 | ||
and len(ctx.args[0]) == 1 | ||
and isinstance(ctx.args[0][0], StrExpr) | ||
and len(signature.arg_types) == 2 | ||
and len(ctx.args[1]) == 1): | ||
key = ctx.args[0][0].value | ||
value_type = ctx.type.items.get(key) | ||
if value_type: | ||
return signature.copy_modified(arg_types=[str_type, value_type]) | ||
return signature.copy_modified(arg_types=[str_type, signature.arg_types[1]]) | ||
|
||
|
||
def typed_dict_setdefault_callback(ctx: MethodContext) -> Type: | ||
"""Type check TypedDict.setdefault and infer a precise return type.""" | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(ctx.arg_types) == 2 | ||
and len(ctx.arg_types[0]) == 1): | ||
if isinstance(ctx.args[0][0], StrExpr): | ||
key = ctx.args[0][0].value | ||
value_type = ctx.type.items.get(key) | ||
if value_type: | ||
return value_type | ||
else: | ||
ctx.api.msg.typeddict_key_not_found(ctx.type, key, ctx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
else: | ||
ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
return ctx.default_return_type | ||
|
||
|
||
def typed_dict_delitem_signature_callback(ctx: MethodSigContext) -> CallableType: | ||
# Replace NoReturn as the argument type. | ||
str_type = ctx.api.named_generic_type('builtins.str', []) | ||
return ctx.default_signature.copy_modified(arg_types=[str_type]) | ||
|
||
|
||
def typed_dict_delitem_callback(ctx: MethodContext) -> Type: | ||
"""Type check TypedDict.__delitem__.""" | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(ctx.arg_types) == 1 | ||
and len(ctx.arg_types[0]) == 1): | ||
if isinstance(ctx.args[0][0], StrExpr): | ||
key = ctx.args[0][0].value | ||
if key in ctx.type.required_keys: | ||
ctx.api.msg.typeddict_key_cannot_be_deleted(ctx.type, key, ctx.context) | ||
elif key not in ctx.type.items: | ||
ctx.api.msg.typeddict_key_not_found(ctx.type, key, ctx.context) | ||
else: | ||
ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
return ctx.default_return_type | ||
|
||
|
||
def typed_dict_update_signature_callback(ctx: MethodSigContext) -> CallableType: | ||
"""Try to infer a better signature type for TypedDict.update.""" | ||
signature = ctx.default_signature | ||
if (isinstance(ctx.type, TypedDictType) | ||
and len(signature.arg_types) == 1): | ||
arg_type = signature.arg_types[0] | ||
assert isinstance(arg_type, TypedDictType) | ||
arg_type = arg_type.as_anonymous() | ||
arg_type = arg_type.copy_modified(required_keys=set()) | ||
return signature.copy_modified(arg_types=[arg_type]) | ||
return signature | ||
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 TODO somewhere for 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. |
||
|
||
|
||
def int_pow_callback(ctx: MethodContext) -> Type: | ||
"""Infer a more precise return type for int.__pow__.""" | ||
if (len(ctx.arg_types) == 1 | ||
|
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
At some point we should also start using "constant folding", not in this PR however.