-
-
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
Add support for namedtuple methods (issue #1076) #1810
Changes from 79 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,11 @@ | |
traverse the entire AST. | ||
""" | ||
|
||
from functools import partial | ||
|
||
import sys | ||
from typing import ( | ||
List, Dict, Set, Tuple, cast, Any, overload, TypeVar, Union, Optional, Callable | ||
List, Dict, Set, Tuple, cast, Any, TypeVar, Union, Optional, Callable | ||
) | ||
|
||
from mypy.nodes import ( | ||
|
@@ -60,29 +62,27 @@ | |
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, MroError, type_aliases, | ||
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_OPT, MroError, type_aliases, | ||
YieldFromExpr, NamedTupleExpr, NonlocalDecl, | ||
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, | ||
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, | ||
IntExpr, FloatExpr, UnicodeExpr, | ||
Expression, EllipsisExpr, namedtuple_type_info, | ||
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, | ||
) | ||
from mypy.visitor import NodeVisitor | ||
from mypy.traverser import TraverserVisitor | ||
from mypy.errors import Errors, report_internal_error | ||
from mypy.types import ( | ||
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, | ||
FunctionLike, UnboundType, TypeList, ErrorType, TypeVarDef, Void, | ||
FunctionLike, UnboundType, TypeList, TypeVarDef, Void, | ||
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType | ||
) | ||
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 | ||
from mypy.lex import lex | ||
from mypy.parsetype import parse_type | ||
from mypy.sametypes import is_same_type | ||
from mypy.erasetype import erase_typevars | ||
from mypy import defaults | ||
from mypy.options import Options | ||
|
||
|
||
|
@@ -751,38 +751,40 @@ def analyze_base_classes(self, defn: ClassDef) -> None: | |
""" | ||
|
||
base_types = [] # type: List[Instance] | ||
info = defn.info | ||
for base_expr in defn.base_type_exprs: | ||
try: | ||
base = self.expr_to_analyzed_type(base_expr) | ||
except TypeTranslationError: | ||
self.fail('Invalid base class', base_expr) | ||
defn.info.fallback_to_any = True | ||
info.fallback_to_any = True | ||
continue | ||
|
||
if isinstance(base, TupleType): | ||
if defn.info.tuple_type: | ||
if info.tuple_type: | ||
self.fail("Class has two incompatible bases derived from tuple", defn) | ||
defn.has_incompatible_baseclass = True | ||
if (not self.is_stub_file | ||
and not defn.info.is_named_tuple | ||
and not info.is_named_tuple | ||
and base.fallback.type.fullname() == 'builtins.tuple'): | ||
self.fail("Tuple[...] not supported as a base class outside a stub file", defn) | ||
defn.info.tuple_type = base | ||
info.tuple_type = base | ||
base_types.append(base.fallback) | ||
elif isinstance(base, Instance): | ||
if base.type.is_newtype: | ||
self.fail("Cannot subclass NewType", defn) | ||
base_types.append(base) | ||
elif isinstance(base, AnyType): | ||
defn.info.fallback_to_any = True | ||
info.fallback_to_any = True | ||
else: | ||
self.fail('Invalid base class', base_expr) | ||
defn.info.fallback_to_any = True | ||
info.fallback_to_any = True | ||
|
||
# Add 'object' as implicit base if there is no other base class. | ||
if (not base_types and defn.fullname != 'builtins.object'): | ||
base_types.append(self.object_type()) | ||
|
||
defn.info.bases = base_types | ||
info.bases = base_types | ||
|
||
# Calculate the MRO. It might be incomplete at this point if | ||
# the bases of defn include classes imported from other | ||
|
@@ -794,8 +796,8 @@ def analyze_base_classes(self, defn: ClassDef) -> None: | |
calculate_class_mro(defn, self.fail_blocker) | ||
# If there are cyclic imports, we may be missing 'object' in | ||
# the MRO. Fix MRO if needed. | ||
if defn.info.mro and defn.info.mro[-1].fullname() != 'builtins.object': | ||
defn.info.mro.append(self.object_type().type) | ||
if info.mro and info.mro[-1].fullname() != 'builtins.object': | ||
info.mro.append(self.object_type().type) | ||
|
||
def expr_to_analyzed_type(self, expr: Node) -> Type: | ||
if isinstance(expr, CallExpr): | ||
|
@@ -1627,6 +1629,7 @@ def parse_namedtuple_args(self, call: CallExpr, | |
if len(args) < 2: | ||
return self.fail_namedtuple_arg("Too few arguments for namedtuple()", call) | ||
if len(args) > 2: | ||
# FIX incorrect. There are two additional parameters | ||
return self.fail_namedtuple_arg("Too many arguments for namedtuple()", call) | ||
if call.arg_kinds != [ARG_POS, ARG_POS]: | ||
return self.fail_namedtuple_arg("Unexpected arguments to namedtuple()", call) | ||
|
@@ -1690,46 +1693,77 @@ def fail_namedtuple_arg(self, message: str, | |
def build_namedtuple_typeinfo(self, name: str, items: List[str], | ||
types: List[Type]) -> TypeInfo: | ||
symbols = SymbolTable() | ||
tup = TupleType(types, self.named_type('__builtins__.tuple', types)) | ||
class_def = ClassDef(name, Block([])) | ||
class_def.fullname = self.qualified_name(name) | ||
info = TypeInfo(symbols, class_def) | ||
# Add named tuple items as attributes. | ||
# TODO: Make them read-only. | ||
for item, typ in zip(items, types): | ||
var = Var(item) | ||
var.info = info | ||
var.type = typ | ||
symbols[item] = SymbolTableNode(MDEF, var) | ||
# Add a __init__ method. | ||
init = self.make_namedtuple_init(info, items, types) | ||
symbols['__init__'] = SymbolTableNode(MDEF, init) | ||
info.tuple_type = TupleType(types, self.named_type('__builtins__.tuple', [AnyType()])) | ||
info.is_named_tuple = True | ||
info.mro = [info] + info.tuple_type.fallback.type.mro | ||
info.bases = [info.tuple_type.fallback] | ||
return info | ||
|
||
def make_argument(self, name: str, type: Type) -> Argument: | ||
return Argument(Var(name), type, None, ARG_POS) | ||
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') | ||
|
||
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) | ||
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. Calls to |
||
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), | ||
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. Hmm 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.
|
||
args=[]) | ||
|
||
# TODO: refine signature | ||
union = UnboundType('Iterable', [UnionType(types)]) | ||
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. The union could be problematic, as union types are not generally produced by type inference. For example, the type of
I'd suggest using |
||
add_method('_make', ret=this_type, is_classmethod=True, | ||
args=[Argument(Var('iterable', union), union, None, ARG_POS), | ||
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED), | ||
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)]) | ||
return info | ||
|
||
def make_namedtuple_init(self, info: TypeInfo, items: List[str], | ||
types: List[Type]) -> FuncDef: | ||
args = [self.make_argument(item, type) for item, type in zip(items, types)] | ||
# TODO: Make sure that the self argument name is not visible? | ||
args = [Argument(Var('__self'), NoneTyp(), None, ARG_POS)] + args | ||
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] | ||
|
||
def add_namedtuple_method(self, symbols: SymbolTable, info: TypeInfo, this_type: Type, | ||
funcname: str, ret: Type, args: List[Argument], name=None, | ||
is_classmethod=False) -> None: | ||
if not is_classmethod: | ||
args = [Argument(Var('self'), this_type, None, ARG_POS)] + args | ||
types = [arg.type_annotation for arg in args] | ||
items = [arg.variable.name() for arg in args] | ||
arg_kinds = [arg.kind for arg in args] | ||
signature = CallableType([cast(Type, None)] + types, | ||
arg_kinds, | ||
['__self'] + items, | ||
NoneTyp(), | ||
signature = CallableType(types, arg_kinds, items, ret, | ||
self.named_type('__builtins__.function'), | ||
name=info.name()) | ||
func = FuncDef('__init__', | ||
args, | ||
Block([]), | ||
typ=signature) | ||
name=name or info.name() + '.' + funcname) | ||
signature.is_classmethod_class = is_classmethod | ||
func = FuncDef(funcname, args, Block([]), typ=signature) | ||
func.info = info | ||
return func | ||
func.is_class = is_classmethod | ||
symbols[funcname] = SymbolTableNode(MDEF, func) | ||
|
||
def make_argument(self, name: str, type: Type) -> Argument: | ||
return Argument(Var(name), type, None, ARG_POS) | ||
|
||
def analyze_types(self, items: List[Node]) -> List[Type]: | ||
result = [] # type: List[Type] | ||
|
@@ -2832,8 +2866,7 @@ def self_type(typ: TypeInfo) -> Union[Instance, TupleType]: | |
inst = Instance(typ, tv) | ||
if typ.tuple_type is None: | ||
return inst | ||
else: | ||
return TupleType(typ.tuple_type.items, inst) | ||
return typ.tuple_type.copy_modified(fallback=inst) | ||
|
||
|
||
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: | ||
|
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.
*args
won't be type checked. It would be nice if the arguments would be spelled out -- alternatively, add an explicitAny
annotation.