-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Add support for namedtuple methods (issue #1076) #1810
Changes from 2 commits
ecca142
8ef445f
5e429cd
0214e3c
fbb75c3
17d7f8b
6dd370c
1370fbf
560eb3e
2627511
46475d4
c1dc1c7
763e0c0
4b10a35
ee047f8
725d53c
044e640
eb6534e
aa0d5da
e12bd01
dc9d8da
158b068
90ffb08
9a6e6cd
76d709e
20bf222
a2696c2
9e1b866
f7b531f
67bdafe
39bce74
a2db6a0
8784ce0
191de7c
6d2d6d1
8819af9
9fb5137
35c30a2
a642a7f
c505cbc
9f96ca7
5c2d3ac
41ed075
6dd568f
b18fc25
d7dbd3f
2b67380
525c081
15acfe0
fb9ba7f
99635c5
8f61c13
ff21b0c
4c1bfe4
b0d78ba
87c804c
6adc2f9
7ed7b0e
c7dc9a7
b39925b
030307e
12d3407
1f944fc
7825f79
8a0ce40
7978357
badc3f9
c051f3f
44c124b
d067c9e
42342de
a5ccc50
eaf4b99
9829b25
b1988f8
8ced59e
053ee56
94bb8d0
71f0d78
66a77fb
922dd91
6460839
cd5b933
4b93761
500a847
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 |
---|---|---|
|
@@ -43,9 +43,6 @@ | |
traverse the entire AST. | ||
""" | ||
|
||
from functools import partial | ||
|
||
import sys | ||
from typing import ( | ||
List, Dict, Set, Tuple, cast, Any, TypeVar, Union, Optional, Callable | ||
) | ||
|
@@ -62,7 +59,7 @@ | |
SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr, | ||
FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr, | ||
StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, | ||
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_OPT, MroError, type_aliases, | ||
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases, | ||
YieldFromExpr, NamedTupleExpr, NonlocalDecl, | ||
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, | ||
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, | ||
|
@@ -75,9 +72,8 @@ | |
from mypy.errors import Errors, report_internal_error | ||
from mypy.types import ( | ||
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, | ||
FunctionLike, UnboundType, TypeList, TypeVarDef, Void, | ||
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType | ||
) | ||
FunctionLike, UnboundType, TypeList, TypeVarDef, | ||
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType) | ||
from mypy.nodes import function_type, implicit_module_attrs | ||
from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias | ||
from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError | ||
|
@@ -319,9 +315,8 @@ def visit_func_def(self, defn: FuncDef) -> None: | |
# A coroutine defined as `async def foo(...) -> T: ...` | ||
# has external return type `Awaitable[T]`. | ||
defn.type = defn.type.copy_modified( | ||
ret_type=Instance( | ||
self.named_type_or_none('typing.Awaitable').type, | ||
[defn.type.ret_type])) | ||
ret_type=self.external_instance('typing.Awaitable', | ||
[defn.type.ret_type])) | ||
self.errors.pop_function() | ||
|
||
def prepare_method_signature(self, func: FuncDef) -> None: | ||
|
@@ -874,6 +869,9 @@ def named_type_or_none(self, qualified_name: str) -> Instance: | |
return None | ||
return Instance(cast(TypeInfo, sym.node), []) | ||
|
||
def external_instance(self, qualified_name: str, args: List[Type] = None) -> Instance: | ||
return Instance(self.named_type_or_none(qualified_name).type, args or None) | ||
|
||
def is_instance_type(self, t: Type) -> bool: | ||
return isinstance(t, Instance) | ||
|
||
|
@@ -1642,7 +1640,7 @@ def parse_namedtuple_args(self, call: CallExpr, | |
if (fullname == 'collections.namedtuple' | ||
and isinstance(args[1], (StrExpr, BytesExpr, UnicodeExpr))): | ||
str_expr = cast(StrExpr, args[1]) | ||
items = str_expr.value.split() | ||
items = str_expr.value.replace(',', ' ').split() | ||
else: | ||
return self.fail_namedtuple_arg( | ||
"List literal expected as the second argument to namedtuple()", call) | ||
|
@@ -1696,51 +1694,53 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str], | |
tup = TupleType(types, self.named_type('__builtins__.tuple', types)) | ||
class_def = ClassDef(name, Block([])) | ||
class_def.fullname = self.qualified_name(name) | ||
|
||
info = namedtuple_type_info(tup, symbols, class_def) | ||
|
||
vars = [Var(item, typ) for item, typ in zip(items, types)] | ||
this_type = self_type(info) | ||
add_field = partial(self.add_namedtuple_field, symbols, info) | ||
strtype = self.named_type('__builtins__.str') | ||
strtype = self.named_type('__builtins__.str') # type: Type | ||
|
||
def add_field(var: Var, is_initialized_in_class: bool = False, | ||
is_property: bool = False) -> None: | ||
var.info = info | ||
var.is_initialized_in_class = is_initialized_in_class | ||
var.is_property = is_property | ||
symbols[var.name()] = SymbolTableNode(MDEF, var) | ||
|
||
for var in vars: | ||
add_field(var, is_property=True) | ||
|
||
tuple_of_strings = TupleType([strtype for _ in items], | ||
self.named_type('__builtins__.tuple', [AnyType()])) | ||
add_field(Var('_fields', tuple_of_strings), | ||
is_initialized_in_class=True) | ||
add_field(Var('_field_types', UnboundType('Dict', [strtype, AnyType()])), | ||
is_initialized_in_class=True) | ||
add_field(Var('_source', strtype), | ||
is_initialized_in_class=True) | ||
|
||
add_method = partial(self.add_namedtuple_method, symbols, info, this_type) | ||
# TODO: refine type of values | ||
dictype = self.external_instance('typing.Dict', [strtype, AnyType()]) | ||
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 should probably be |
||
add_field(Var('_fields', tuple_of_strings), is_initialized_in_class=True) | ||
add_field(Var('_field_types', dictype), is_initialized_in_class=True) | ||
add_field(Var('_source', strtype), is_initialized_in_class=True) | ||
|
||
def add_method(funcname: str, ret: Type, args: List[Argument], name=None, | ||
is_classmethod=False) -> None: | ||
self.add_namedtuple_method(symbols, info, this_type, | ||
funcname, ret, args, name, is_classmethod) | ||
|
||
add_method('_replace', ret=this_type, | ||
args=self.factory_args(vars, ARG_NAMED, initializer=EllipsisExpr())) | ||
add_method('__init__', ret=NoneTyp(), | ||
args=self.factory_args(vars, ARG_POS), name=info.name()) | ||
# TODO: refine to OrderedDict[str, Union[types]] | ||
add_method('_asdict', ret=UnboundType('collections.OrderedDict', is_ret_type=True), | ||
args=[]) | ||
|
||
# TODO: refine signature | ||
union = UnboundType('Iterable', [UnionType(types)]) | ||
# actual signature should return OrderedDict[str, Union[types]] | ||
dictype = self.external_instance('typing.Dict', [strtype, AnyType()]) | ||
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. Use 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. But the lookup fails for it. 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. (Similarly the lookup 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. Actually, it seems that The only easy way to make sure that 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. That's unfortunate that namedtuple is in collections when it isn't one, and builtins.dict does not work. I don't know why. 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. I'll play around with the code to see if I can figure out what is going on with 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. Adding
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. It does, but it means adding this fixture to every test case (in some, replacing What's wrong with And 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.
Adding 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. You may not need to have We may want to reconsider adding It's unfortunate that you've encountered a bunch of shard edges when working on this PR -- sorry about that! |
||
add_method('_asdict', args=[], ret=dictype) | ||
|
||
# note: actual signature should accept an invariant version of Iterable[UnionType[types]]. | ||
# but it can't be expressed | ||
# 'new' and 'len' as function types. | ||
iterable_type = self.external_instance('typing.Iterable', [AnyType()]) | ||
add_method('_make', ret=this_type, is_classmethod=True, | ||
args=[Argument(Var('iterable', union), union, None, ARG_POS), | ||
args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS), | ||
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED), | ||
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)]) | ||
return info | ||
|
||
def add_namedtuple_field(self, symbols: SymbolTable, info: TypeInfo, var: Var, | ||
is_initialized_in_class: bool = False, | ||
is_property: bool = False) -> None: | ||
var.info = info | ||
var.is_initialized_in_class = is_initialized_in_class | ||
var.is_property = is_property | ||
symbols[var.name()] = SymbolTableNode(MDEF, var) | ||
|
||
def factory_args(self, vars: List[Var], kind: int, | ||
initializer: Expression = None) -> List[Argument]: | ||
return [Argument(var, var.type, initializer, kind) for var in vars] | ||
|
@@ -2511,6 +2511,8 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *, | |
self.function_stack and | ||
self.function_stack[-1].is_dynamic()): | ||
return | ||
# In case it's a bug and we don't really have context | ||
assert ctx is not None, msg | ||
self.errors.report(ctx.get_line(), msg, blocker=blocker) | ||
|
||
def fail_blocker(self, msg: str, ctx: Context) -> None: | ||
|
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.
The second argument shouldn't be
None
-- it should be an empty list if there are no arguments.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.
Oops.