-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
add support for dataclasses #5010
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
23 commits
Select commit
Hold shift + click to select a range
92b5347
add basic support for dataclasses
Bogdanp 98becd6
clean up code comments
Bogdanp 3979713
avoid reusing types for comparison methods
Bogdanp 4ab42a1
drop extra newlines from tests
Bogdanp 58ae577
add tests for multiple init arg combos
Bogdanp 7f7415e
add InitVar to lib stub
Bogdanp addde6e
add generic dataclass test
Bogdanp 3004fac
drop is_initialized_in_class block since it serves no purpose
Bogdanp f5f52a8
add more checks to generic dataclass test
Bogdanp 74436ea
refactor KeyError blocks
Bogdanp 2918ebd
add incremental tests for dataclasses
Bogdanp 7c857cb
fix failing type check
Bogdanp 37eaae8
remove accidental modification to setup.cfg
Bogdanp afbeb78
Merge branch 'master' into dataclasses
ilevkivskyi 8d2dd6d
drop now-unnecessary "or" clause on dataclass plugin init
Bogdanp 9d08b7a
implement InitVar support
Bogdanp 8b20ceb
update dataclasses.field
Bogdanp 5b78ad7
update typeshed
Bogdanp 3b9c215
Revert "drop now-unnecessary "or" clause on dataclass plugin init"
Bogdanp 55dfc7e
update incremental tests
Bogdanp 988c6e8
Some polish
f007a78
Merge remote-tracking branch 'upstream/master' into add-dataclasses
747473e
Fix newline
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 |
---|---|---|
@@ -0,0 +1,110 @@ | ||
from typing import List, Optional | ||
|
||
from mypy.nodes import ( | ||
ARG_OPT, ARG_POS, MDEF, Argument, Block, CallExpr, Expression, FuncBase, | ||
FuncDef, PassStmt, RefExpr, SymbolTableNode, Var | ||
) | ||
from mypy.plugin import ClassDefContext | ||
from mypy.semanal import set_callable_name | ||
from mypy.types import CallableType, Overloaded, Type, TypeVarDef | ||
from mypy.typevars import fill_typevars | ||
|
||
|
||
def _get_decorator_bool_argument( | ||
ctx: ClassDefContext, | ||
name: str, | ||
default: bool, | ||
) -> bool: | ||
"""Return the bool argument for the decorator. | ||
|
||
This handles both @decorator(...) and @decorator. | ||
""" | ||
if isinstance(ctx.reason, CallExpr): | ||
return _get_bool_argument(ctx, ctx.reason, name, default) | ||
else: | ||
return default | ||
|
||
|
||
def _get_bool_argument(ctx: ClassDefContext, expr: CallExpr, | ||
name: str, default: bool) -> bool: | ||
"""Return the boolean value for an argument to a call or the | ||
default if it's not found. | ||
""" | ||
attr_value = _get_argument(expr, name) | ||
if attr_value: | ||
ret = ctx.api.parse_bool(attr_value) | ||
if ret is None: | ||
ctx.api.fail('"{}" argument must be True or False.'.format(name), expr) | ||
return default | ||
return ret | ||
return default | ||
|
||
|
||
def _get_argument(call: CallExpr, name: str) -> Optional[Expression]: | ||
"""Return the expression for the specific argument.""" | ||
# To do this we use the CallableType of the callee to find the FormalArgument, | ||
# then walk the actual CallExpr looking for the appropriate argument. | ||
# | ||
# Note: I'm not hard-coding the index so that in the future we can support other | ||
# attrib and class makers. | ||
callee_type = None | ||
if (isinstance(call.callee, RefExpr) | ||
and isinstance(call.callee.node, (Var, FuncBase)) | ||
and call.callee.node.type): | ||
callee_node_type = call.callee.node.type | ||
if isinstance(callee_node_type, Overloaded): | ||
# We take the last overload. | ||
callee_type = callee_node_type.items()[-1] | ||
elif isinstance(callee_node_type, CallableType): | ||
callee_type = callee_node_type | ||
|
||
if not callee_type: | ||
return None | ||
|
||
argument = callee_type.argument_by_name(name) | ||
if not argument: | ||
return None | ||
assert argument.name | ||
|
||
for i, (attr_name, attr_value) in enumerate(zip(call.arg_names, call.args)): | ||
if argument.pos is not None and not attr_name and i == argument.pos: | ||
return attr_value | ||
if attr_name == argument.name: | ||
return attr_value | ||
return None | ||
|
||
|
||
def _add_method( | ||
ctx: ClassDefContext, | ||
name: str, | ||
args: List[Argument], | ||
return_type: Type, | ||
self_type: Optional[Type] = None, | ||
tvar_def: Optional[TypeVarDef] = None, | ||
) -> None: | ||
"""Adds a new method to a class. | ||
""" | ||
info = ctx.cls.info | ||
self_type = self_type or fill_typevars(info) | ||
function_type = ctx.api.named_type('__builtins__.function') | ||
|
||
args = [Argument(Var('self'), self_type, None, ARG_POS)] + args | ||
arg_types, arg_names, arg_kinds = [], [], [] | ||
for arg in args: | ||
assert arg.type_annotation, 'All arguments must be fully typed.' | ||
arg_types.append(arg.type_annotation) | ||
arg_names.append(arg.variable.name()) | ||
arg_kinds.append(arg.kind) | ||
|
||
signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) | ||
if tvar_def: | ||
signature.variables = [tvar_def] | ||
|
||
func = FuncDef(name, args, Block([PassStmt()])) | ||
func.info = info | ||
func.type = set_callable_name(signature, func) | ||
func._fullname = info.fullname() + '.' + name | ||
func.line = info.line | ||
|
||
info.names[name] = SymbolTableNode(MDEF, func) | ||
info.defn.defs.body.append(func) |
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.
Why do you need the local import? If there is an import cycle, you should try breaking it. At mypy we try hard to not introduce import cycles, because they complicate the (already complex) code logic.
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.
There had already been a circular dependency that was being resolved by this line. When I extracted
mypy.plugins.common
the same thing continued to work fine for the test suite, but the dynamically-generated test to ensureimport mypy.plugins.common
could be imported failed. I think the circular dependency can be fixed by extractingClassDefContext
into a separate module (likemypy.plugin_context
). Let me know if you'd like me to do that!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.
I think moving out all the interfaces to a separate file
mypy.api
is a more permanent solution. But it is a big refactoring, so it is better to do this in a separate PR.