Skip to content

Commit 582ab70

Browse files
elazargJukkaL
authored andcommitted
Add support for namedtuple methods (#1810)
Fixes #1076.
1 parent fbcdc02 commit 582ab70

15 files changed

+283
-93
lines changed

mypy/checker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,9 @@ def visit_class_def(self, defn: ClassDef) -> Type:
914914
with self.binder.frame_context():
915915
self.accept(defn.defs)
916916
self.binder = old_binder
917-
self.check_multiple_inheritance(typ)
917+
if not defn.has_incompatible_baseclass:
918+
# Otherwise we've already found errors; more errors are not useful
919+
self.check_multiple_inheritance(typ)
918920
self.leave_partial_types()
919921
self.errors.pop_type()
920922

mypy/checkexpr.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,8 +1302,7 @@ def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Typ
13021302
slic.stride)
13031303
return AnyType()
13041304

1305-
return TupleType(left_type.items[begin:end:stride], left_type.fallback,
1306-
left_type.line, left_type.implicit)
1305+
return left_type.slice(begin, stride, end)
13071306

13081307
def _get_value(self, index: Node) -> Optional[int]:
13091308
if isinstance(index, IntExpr):

mypy/checkmember.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
187187
if isinstance(vv, Decorator):
188188
# The associated Var node of a decorator contains the type.
189189
v = vv.var
190-
191190
if isinstance(v, Var):
192191
return analyze_var(name, v, itype, info, node, is_lvalue, msg, not_ready_callback)
193192
elif isinstance(v, FuncDef):
@@ -229,6 +228,9 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
229228
if isinstance(typ, PartialType):
230229
return handle_partial_attribute_type(typ, is_lvalue, msg, var)
231230
t = expand_type_by_instance(typ, itype)
231+
if is_lvalue and var.is_property and not var.is_settable_property:
232+
# TODO allow setting attributes in subclass (although it is probably an error)
233+
msg.read_only_property(name, info, node)
232234
if var.is_initialized_in_class and isinstance(t, FunctionLike):
233235
if is_lvalue:
234236
if var.is_property:

mypy/expandtype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
8888
return Overloaded(items)
8989

9090
def visit_tuple_type(self, t: TupleType) -> Type:
91-
return TupleType(self.expand_types(t.items), t.fallback, t.line)
91+
return t.copy_modified(items=self.expand_types(t.items))
9292

9393
def visit_union_type(self, t: UnionType) -> Type:
9494
# After substituting for type variables in t.items,

mypy/join.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Calculation of the least upper bound types (joins)."""
22

3-
from typing import cast, List
3+
from typing import List
44

55
from mypy.types import (
66
Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType,
@@ -231,8 +231,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
231231
items = [] # type: List[Type]
232232
for i in range(t.length()):
233233
items.append(self.join(t.items[i], self.s.items[i]))
234-
# TODO: What if the fallback types are different?
235-
return TupleType(items, t.fallback)
234+
# join fallback types if they are different
235+
from typing import cast
236+
return TupleType(items, cast(Instance, join_instances(self.s.fallback, t.fallback)))
236237
else:
237238
return self.default(self.s)
238239

mypy/nodes.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ class ClassDef(Statement):
646646
decorators = None # type: List[Expression]
647647
# Built-in/extension class? (single implementation inheritance only)
648648
is_builtinclass = False
649+
has_incompatible_baseclass = False
649650

650651
def __init__(self,
651652
name: str,
@@ -1191,6 +1192,7 @@ def __init__(self, callee: Expression, args: List[Expression], arg_kinds: List[i
11911192
arg_names: List[str] = None, analyzed: Expression = None) -> None:
11921193
if not arg_names:
11931194
arg_names = [None] * len(args)
1195+
11941196
self.callee = callee
11951197
self.args = args
11961198
self.arg_kinds = arg_kinds
@@ -2025,6 +2027,16 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo':
20252027
return ti
20262028

20272029

2030+
def namedtuple_type_info(tup: 'mypy.types.TupleType',
2031+
names: 'SymbolTable', defn: ClassDef) -> TypeInfo:
2032+
info = TypeInfo(names, defn)
2033+
info.tuple_type = tup
2034+
info.bases = [tup.fallback]
2035+
info.is_named_tuple = True
2036+
info.mro = [info] + tup.fallback.type.mro
2037+
return info
2038+
2039+
20282040
class SymbolTableNode:
20292041
# Kind of node. Possible values:
20302042
# - LDEF: local definition (of any kind)

mypy/semanal.py

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@
4343
traverse the entire AST.
4444
"""
4545

46-
import sys
4746
from typing import (
48-
List, Dict, Set, Tuple, cast, Any, overload, TypeVar, Union, Optional, Callable
47+
List, Dict, Set, Tuple, cast, Any, TypeVar, Union, Optional, Callable
4948
)
5049

5150
from mypy.nodes import (
@@ -65,24 +64,21 @@
6564
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr,
6665
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
6766
IntExpr, FloatExpr, UnicodeExpr,
67+
Expression, EllipsisExpr, namedtuple_type_info,
6868
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES,
6969
)
7070
from mypy.visitor import NodeVisitor
7171
from mypy.traverser import TraverserVisitor
7272
from mypy.errors import Errors, report_internal_error
7373
from mypy.types import (
7474
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType,
75-
FunctionLike, UnboundType, TypeList, ErrorType, TypeVarDef, Void,
76-
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType
77-
)
75+
FunctionLike, UnboundType, TypeList, TypeVarDef,
76+
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType)
7877
from mypy.nodes import function_type, implicit_module_attrs
7978
from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias
8079
from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError
81-
from mypy.lex import lex
82-
from mypy.parsetype import parse_type
8380
from mypy.sametypes import is_same_type
8481
from mypy.erasetype import erase_typevars
85-
from mypy import defaults
8682
from mypy.options import Options
8783

8884

@@ -319,9 +315,8 @@ def visit_func_def(self, defn: FuncDef) -> None:
319315
# A coroutine defined as `async def foo(...) -> T: ...`
320316
# has external return type `Awaitable[T]`.
321317
defn.type = defn.type.copy_modified(
322-
ret_type=Instance(
323-
self.named_type_or_none('typing.Awaitable').type,
324-
[defn.type.ret_type]))
318+
ret_type = self.named_type_or_none('typing.Awaitable',
319+
[defn.type.ret_type]))
325320
self.errors.pop_function()
326321

327322
def prepare_method_signature(self, func: FuncDef) -> None:
@@ -751,38 +746,40 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
751746
"""
752747

753748
base_types = [] # type: List[Instance]
749+
info = defn.info
754750
for base_expr in defn.base_type_exprs:
755751
try:
756752
base = self.expr_to_analyzed_type(base_expr)
757753
except TypeTranslationError:
758754
self.fail('Invalid base class', base_expr)
759-
defn.info.fallback_to_any = True
755+
info.fallback_to_any = True
760756
continue
761757

762758
if isinstance(base, TupleType):
763-
if defn.info.tuple_type:
759+
if info.tuple_type:
764760
self.fail("Class has two incompatible bases derived from tuple", defn)
761+
defn.has_incompatible_baseclass = True
765762
if (not self.is_stub_file
766-
and not defn.info.is_named_tuple
763+
and not info.is_named_tuple
767764
and base.fallback.type.fullname() == 'builtins.tuple'):
768765
self.fail("Tuple[...] not supported as a base class outside a stub file", defn)
769-
defn.info.tuple_type = base
766+
info.tuple_type = base
770767
base_types.append(base.fallback)
771768
elif isinstance(base, Instance):
772769
if base.type.is_newtype:
773770
self.fail("Cannot subclass NewType", defn)
774771
base_types.append(base)
775772
elif isinstance(base, AnyType):
776-
defn.info.fallback_to_any = True
773+
info.fallback_to_any = True
777774
else:
778775
self.fail('Invalid base class', base_expr)
779-
defn.info.fallback_to_any = True
776+
info.fallback_to_any = True
780777

781778
# Add 'object' as implicit base if there is no other base class.
782779
if (not base_types and defn.fullname != 'builtins.object'):
783780
base_types.append(self.object_type())
784781

785-
defn.info.bases = base_types
782+
info.bases = base_types
786783

787784
# Calculate the MRO. It might be incomplete at this point if
788785
# the bases of defn include classes imported from other
@@ -794,8 +791,8 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
794791
calculate_class_mro(defn, self.fail_blocker)
795792
# If there are cyclic imports, we may be missing 'object' in
796793
# the MRO. Fix MRO if needed.
797-
if defn.info.mro and defn.info.mro[-1].fullname() != 'builtins.object':
798-
defn.info.mro.append(self.object_type().type)
794+
if info.mro and info.mro[-1].fullname() != 'builtins.object':
795+
info.mro.append(self.object_type().type)
799796

800797
def expr_to_analyzed_type(self, expr: Node) -> Type:
801798
if isinstance(expr, CallExpr):
@@ -866,11 +863,11 @@ def named_type(self, qualified_name: str, args: List[Type] = None) -> Instance:
866863
sym = self.lookup_qualified(qualified_name, None)
867864
return Instance(cast(TypeInfo, sym.node), args or [])
868865

869-
def named_type_or_none(self, qualified_name: str) -> Instance:
866+
def named_type_or_none(self, qualified_name: str, args: List[Type] = None) -> Instance:
870867
sym = self.lookup_fully_qualified_or_none(qualified_name)
871868
if not sym:
872869
return None
873-
return Instance(cast(TypeInfo, sym.node), [])
870+
return Instance(cast(TypeInfo, sym.node), args or [])
874871

875872
def is_instance_type(self, t: Type) -> bool:
876873
return isinstance(t, Instance)
@@ -1627,6 +1624,7 @@ def parse_namedtuple_args(self, call: CallExpr,
16271624
if len(args) < 2:
16281625
return self.fail_namedtuple_arg("Too few arguments for namedtuple()", call)
16291626
if len(args) > 2:
1627+
# FIX incorrect. There are two additional parameters
16301628
return self.fail_namedtuple_arg("Too many arguments for namedtuple()", call)
16311629
if call.arg_kinds != [ARG_POS, ARG_POS]:
16321630
return self.fail_namedtuple_arg("Unexpected arguments to namedtuple()", call)
@@ -1639,7 +1637,7 @@ def parse_namedtuple_args(self, call: CallExpr,
16391637
if (fullname == 'collections.namedtuple'
16401638
and isinstance(args[1], (StrExpr, BytesExpr, UnicodeExpr))):
16411639
str_expr = cast(StrExpr, args[1])
1642-
items = str_expr.value.split()
1640+
items = str_expr.value.replace(',', ' ').split()
16431641
else:
16441642
return self.fail_namedtuple_arg(
16451643
"List literal expected as the second argument to namedtuple()", call)
@@ -1689,48 +1687,74 @@ def fail_namedtuple_arg(self, message: str,
16891687

16901688
def build_namedtuple_typeinfo(self, name: str, items: List[str],
16911689
types: List[Type]) -> TypeInfo:
1690+
strtype = self.named_type('__builtins__.str') # type: Type
1691+
basetuple_type = self.named_type('__builtins__.tuple', [AnyType()])
1692+
dictype = (self.named_type_or_none('builtins.dict', [strtype, AnyType()])
1693+
or self.object_type())
1694+
# Actual signature should return OrderedDict[str, Union[types]]
1695+
ordereddictype = (self.named_type_or_none('builtins.dict', [strtype, AnyType()])
1696+
or self.object_type())
1697+
fallback = self.named_type('__builtins__.tuple', types)
1698+
# Note: actual signature should accept an invariant version of Iterable[UnionType[types]].
1699+
# but it can't be expressed. 'new' and 'len' should be callable types.
1700+
iterable_type = self.named_type_or_none('typing.Iterable', [AnyType()])
1701+
function_type = self.named_type('__builtins__.function')
1702+
fullname = self.qualified_name(name)
1703+
16921704
symbols = SymbolTable()
16931705
class_def = ClassDef(name, Block([]))
1694-
class_def.fullname = self.qualified_name(name)
1695-
info = TypeInfo(symbols, class_def)
1696-
# Add named tuple items as attributes.
1697-
# TODO: Make them read-only.
1698-
for item, typ in zip(items, types):
1699-
var = Var(item)
1706+
class_def.fullname = fullname
1707+
info = namedtuple_type_info(TupleType(types, fallback), symbols, class_def)
1708+
1709+
def add_field(var: Var, is_initialized_in_class: bool = False,
1710+
is_property: bool = False) -> None:
17001711
var.info = info
1701-
var.type = typ
1702-
symbols[item] = SymbolTableNode(MDEF, var)
1703-
# Add a __init__ method.
1704-
init = self.make_namedtuple_init(info, items, types)
1705-
symbols['__init__'] = SymbolTableNode(MDEF, init)
1706-
info.tuple_type = TupleType(types, self.named_type('__builtins__.tuple', [AnyType()]))
1707-
info.is_named_tuple = True
1708-
info.mro = [info] + info.tuple_type.fallback.type.mro
1709-
info.bases = [info.tuple_type.fallback]
1712+
var.is_initialized_in_class = is_initialized_in_class
1713+
var.is_property = is_property
1714+
symbols[var.name()] = SymbolTableNode(MDEF, var)
1715+
1716+
vars = [Var(item, typ) for item, typ in zip(items, types)]
1717+
for var in vars:
1718+
add_field(var, is_property=True)
1719+
1720+
tuple_of_strings = TupleType([strtype for _ in items], basetuple_type)
1721+
add_field(Var('_fields', tuple_of_strings), is_initialized_in_class=True)
1722+
add_field(Var('_field_types', dictype), is_initialized_in_class=True)
1723+
add_field(Var('_source', strtype), is_initialized_in_class=True)
1724+
1725+
# TODO: SelfType should be bind to actual 'self'
1726+
this_type = self_type(info)
1727+
1728+
def add_method(funcname: str, ret: Type, args: List[Argument], name=None,
1729+
is_classmethod=False) -> None:
1730+
if not is_classmethod:
1731+
args = [Argument(Var('self'), this_type, None, ARG_POS)] + args
1732+
types = [arg.type_annotation for arg in args]
1733+
items = [arg.variable.name() for arg in args]
1734+
arg_kinds = [arg.kind for arg in args]
1735+
signature = CallableType(types, arg_kinds, items, ret, function_type,
1736+
name=name or info.name() + '.' + funcname)
1737+
signature.is_classmethod_class = is_classmethod
1738+
func = FuncDef(funcname, args, Block([]), typ=signature)
1739+
func.info = info
1740+
func.is_class = is_classmethod
1741+
symbols[funcname] = SymbolTableNode(MDEF, func)
1742+
1743+
add_method('_replace', ret=this_type,
1744+
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED) for var in vars])
1745+
add_method('__init__', ret=NoneTyp(), name=info.name(),
1746+
args=[Argument(var, var.type, None, ARG_POS) for var in vars])
1747+
add_method('_asdict', args=[], ret=ordereddictype)
1748+
# FIX: make it actual class method
1749+
add_method('_make', ret=this_type, is_classmethod=True,
1750+
args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS),
1751+
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED),
1752+
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)])
17101753
return info
17111754

17121755
def make_argument(self, name: str, type: Type) -> Argument:
17131756
return Argument(Var(name), type, None, ARG_POS)
17141757

1715-
def make_namedtuple_init(self, info: TypeInfo, items: List[str],
1716-
types: List[Type]) -> FuncDef:
1717-
args = [self.make_argument(item, type) for item, type in zip(items, types)]
1718-
# TODO: Make sure that the self argument name is not visible?
1719-
args = [Argument(Var('__self'), NoneTyp(), None, ARG_POS)] + args
1720-
arg_kinds = [arg.kind for arg in args]
1721-
signature = CallableType([cast(Type, None)] + types,
1722-
arg_kinds,
1723-
['__self'] + items,
1724-
NoneTyp(),
1725-
self.named_type('__builtins__.function'),
1726-
name=info.name())
1727-
func = FuncDef('__init__',
1728-
args,
1729-
Block([]),
1730-
typ=signature)
1731-
func.info = info
1732-
return func
1733-
17341758
def analyze_types(self, items: List[Node]) -> List[Type]:
17351759
result = [] # type: List[Type]
17361760
for node in items:
@@ -2477,6 +2501,8 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *,
24772501
self.function_stack and
24782502
self.function_stack[-1].is_dynamic()):
24792503
return
2504+
# In case it's a bug and we don't really have context
2505+
assert ctx is not None, msg
24802506
self.errors.report(ctx.get_line(), msg, blocker=blocker)
24812507

24822508
def fail_blocker(self, msg: str, ctx: Context) -> None:
@@ -2832,8 +2858,7 @@ def self_type(typ: TypeInfo) -> Union[Instance, TupleType]:
28322858
inst = Instance(typ, tv)
28332859
if typ.tuple_type is None:
28342860
return inst
2835-
else:
2836-
return TupleType(typ.tuple_type.items, inst)
2861+
return typ.tuple_type.copy_modified(fallback=inst)
28372862

28382863

28392864
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:

mypy/subtypes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from mypy.types import (
44
Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp,
5-
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList,
6-
PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance
5+
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded,
6+
ErasedType, TypeList, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance
77
)
88
import mypy.applytype
99
import mypy.constraints
@@ -181,8 +181,8 @@ def visit_tuple_type(self, left: TupleType) -> bool:
181181
elif isinstance(right, TupleType):
182182
if len(left.items) != len(right.items):
183183
return False
184-
for i in range(len(left.items)):
185-
if not is_subtype(left.items[i], right.items[i], self.check_type_parameter):
184+
for l, r in zip(left.items, right.items):
185+
if not is_subtype(l, r, self.check_type_parameter):
186186
return False
187187
if not is_subtype(left.fallback, right.fallback, self.check_type_parameter):
188188
return False

0 commit comments

Comments
 (0)