Skip to content

Commit 29f1009

Browse files
committed
Merge branch 'namedtuple'
2 parents a99b5e7 + 3bddee6 commit 29f1009

28 files changed

+547
-18
lines changed

lib-typing/3.2/test_typing.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
AbstractGeneric, Protocol, Sized, Iterable, Iterator, Sequence,
77
AbstractSet, Mapping, BinaryIO, TextIO, SupportsInt, SupportsFloat,
88
SupportsAbs, SupportsRound, Reversible, Undefined, AnyStr, builtinclass,
9-
cast, disjointclass, ducktype, forwardref, overload, typevar
9+
cast, disjointclass, ducktype, forwardref, overload, typevar,
10+
NamedTuple
1011
)
1112

1213

@@ -806,6 +807,11 @@ class A: pass
806807
self.assertIs(disjointclass(str)(A), A)
807808
self.assertIs(disjointclass('str')(A), A)
808809

810+
def test_NamedTuple(self):
811+
n = NamedTuple('n', [('a', int), ('b', str)])
812+
assert n(a=1, b='x') == (1, 'x')
813+
assert n(1, 'x')[1] == 'x'
814+
809815

810816
@overload
811817
def global_overload(x:str) -> str:

lib-typing/3.2/typing.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Static type checking helpers"""
22

33
from abc import ABCMeta, abstractmethod, abstractproperty
4+
import collections
45
import inspect
56
import sys
67
import re
@@ -19,6 +20,7 @@
1920
'IO',
2021
'List',
2122
'Match',
23+
'NamedTuple',
2224
'Pattern',
2325
'Protocol',
2426
'Set',
@@ -173,10 +175,18 @@ def __getitem__(self, typeargs):
173175
Pattern = TypeAlias(type(re.compile('')))
174176
Match = TypeAlias(type(re.match('', '')))
175177

178+
176179
def union(x): return x
177180

181+
178182
Union = TypeAlias(union)
179183

184+
185+
def NamedTuple(typename, fields):
186+
return collections.namedtuple(typename,
187+
(name for name, type in fields))
188+
189+
180190
class typevar:
181191
def __init__(self, name, *, values=None):
182192
self.name = name

mypy/checker.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Context, ListComprehension, ConditionalExpr, GeneratorExpr,
1818
Decorator, SetExpr, PassStmt, TypeVarExpr, UndefinedExpr, PrintStmt,
1919
LITERAL_TYPE, BreakStmt, ContinueStmt, ComparisonExpr, StarExpr,
20-
YieldFromExpr, YieldFromStmt
20+
YieldFromExpr, YieldFromStmt, NamedTupleExpr
2121
)
2222
from mypy.nodes import function_type, method_type
2323
from mypy import nodes
@@ -1740,7 +1740,11 @@ def visit_type_application(self, e: TypeApplication) -> Type:
17401740
return self.expr_checker.visit_type_application(e)
17411741

17421742
def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
1743-
# TODO Perhaps return a special type used for type variables only?
1743+
# TODO: Perhaps return a special type used for type variables only?
1744+
return AnyType()
1745+
1746+
def visit_namedtuple_expr(self, e: NamedTupleExpr) -> Type:
1747+
# TODO: Perhaps return a type object type?
17441748
return AnyType()
17451749

17461750
def visit_list_expr(self, e: ListExpr) -> Type:
@@ -1929,13 +1933,15 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo,
19291933
19301934
For example, assume
19311935
1932-
class D(Generic[S]) ...
1933-
class C(D[E[T]], Generic[T]) ...
1936+
. class D(Generic[S]) ...
1937+
. class C(D[E[T]], Generic[T]) ...
19341938
19351939
Now S in the context of D would be mapped to E[T] in the context of C.
19361940
"""
19371941
# Create the type of self in subtype, of form t[a1, ...].
19381942
inst_type = self_type(sub_info)
1943+
if isinstance(inst_type, TupleType):
1944+
inst_type = inst_type.fallback
19391945
# Map the type of self to supertype. This gets us a description of the
19401946
# supertype type variables in terms of subtype variables, i.e. t[t1, ...]
19411947
# so that any type variables in tN are to be interpreted in subtype

mypy/checkmember.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def type_object_type(info: TypeInfo, builtin_type: Function[[str], Instance]) ->
239239
240240
def [T, S](...) -> G[T, S],
241241
242-
where ... are argument types for the __init__ method.
242+
where ... are argument types for the __init__ method (without the self argument).
243243
"""
244244
init_method = info.get_method('__init__')
245245
if not init_method:

mypy/messages.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ def format_simple(self, typ: Type) -> str:
195195
# This is similar to non-generic instance types.
196196
return '"{}"'.format((cast(TypeVar, typ)).name)
197197
elif isinstance(typ, TupleType):
198+
fallback = self.format_simple(typ.fallback)
199+
if fallback != '"tuple"':
200+
# Prefer the name of the fallback class (if not tuple), as it's more informative.
201+
return fallback
198202
items = []
199203
for t in (cast(TupleType, typ)).items:
200204
items.append(strip_quotes(self.format(t)))

mypy/nodes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,20 @@ def accept(self, visitor: NodeVisitor[T]) -> T:
13091309
return visitor.visit_type_var_expr(self)
13101310

13111311

1312+
class NamedTupleExpr(Node):
1313+
"""Named tuple expression namedtuple(...)."""
1314+
1315+
# The class representation of this named tuple (its tuple_type attribute contains
1316+
# the tuple item types)
1317+
info = Undefined('TypeInfo')
1318+
1319+
def __init__(self, info: 'TypeInfo') -> None:
1320+
self.info = info
1321+
1322+
def accept(self, visitor: NodeVisitor[T]) -> T:
1323+
return visitor.visit_namedtuple_expr(self)
1324+
1325+
13121326
class DucktypeExpr(Node):
13131327
"""Ducktype class decorator expression ducktype(...)."""
13141328

@@ -1383,6 +1397,13 @@ class TypeInfo(SymbolNode):
13831397
# Duck type compatibility (ducktype decorator)
13841398
ducktype = None # type: mypy.types.Type
13851399

1400+
# Representation of a Tuple[...] base class, if the class has any
1401+
# (e.g., for named tuples). If this is not None, the actual Type
1402+
# object used for this class is not an Instance but a TupleType;
1403+
# the corresponding Instance is set as the fallback type of the
1404+
# tuple type.
1405+
tuple_type = None # type: mypy.types.TupleType
1406+
13861407
def __init__(self, names: 'SymbolTable', defn: ClassDef) -> None:
13871408
"""Initialize a TypeInfo."""
13881409
self.names = names

mypy/semanal.py

Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
FuncExpr, MDEF, FuncBase, Decorator, SetExpr, UndefinedExpr, TypeVarExpr,
5858
StrExpr, PrintStmt, ConditionalExpr, DucktypeExpr, DisjointclassExpr,
5959
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases,
60-
YieldFromStmt, YieldFromExpr
60+
YieldFromStmt, YieldFromExpr, NamedTupleExpr
6161
)
6262
from mypy.visitor import NodeVisitor
6363
from mypy.traverser import TraverserVisitor
@@ -500,6 +500,11 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
500500
bases = List[Instance]()
501501
for i in range(len(defn.base_types)):
502502
base = self.anal_type(defn.base_types[i])
503+
if isinstance(base, TupleType):
504+
if defn.info.tuple_type:
505+
self.fail("Class has two incompatible bases derived from tuple", defn)
506+
defn.info.tuple_type = base
507+
base = base.fallback
503508
if isinstance(base, Instance):
504509
defn.base_types[i] = base
505510
bases.append(base)
@@ -708,6 +713,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
708713
self.store_declared_types(lvalue, s.type)
709714
self.check_and_set_up_type_alias(s)
710715
self.process_typevar_declaration(s)
716+
self.process_namedtuple_definition(s)
711717

712718
def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
713719
"""Check if assignment creates a type alias and set it up as needed."""
@@ -952,14 +958,156 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None:
952958
return
953959
else:
954960
values = []
955-
# Yes, it's a valid type variable definition!
961+
# Yes, it's a valid type variable definition! Add it to the symbol table.
956962
node = self.lookup(name, s)
957963
node.kind = UNBOUND_TVAR
958964
typevar = TypeVarExpr(name, node.fullname, values)
959965
typevar.line = call.line
960966
call.analyzed = typevar
961967
node.node = typevar
962968

969+
def process_namedtuple_definition(self, s: AssignmentStmt) -> None:
970+
"""Check if s defines a namedtuple; if yes, store the definition in symbol table."""
971+
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
972+
return
973+
if not isinstance(s.rvalue, CallExpr):
974+
return
975+
call = cast(CallExpr, s.rvalue)
976+
named_tuple = self.check_namedtuple(call)
977+
if named_tuple is None:
978+
return
979+
# Yes, it's a valid namedtuple definition. Add it to the symbol table.
980+
lvalue = cast(NameExpr, s.lvalues[0])
981+
name = lvalue.name
982+
node = self.lookup(name, s)
983+
node.kind = GDEF # TODO locally defined namedtuple
984+
# TODO call.analyzed
985+
node.node = named_tuple
986+
987+
def check_namedtuple(self, call: CallExpr) -> TypeInfo:
988+
"""Check if a call defines a namedtuple.
989+
990+
If it does, return the corresponding TypeInfo. Return None otherwise.
991+
992+
If the definition is invalid but looks like a namedtuple,
993+
report errors but return (some) TypeInfo.
994+
"""
995+
if not isinstance(call.callee, RefExpr):
996+
return None
997+
callee = cast(RefExpr, call.callee)
998+
fullname = callee.fullname
999+
if fullname not in ('collections.namedtuple', 'typing.NamedTuple'):
1000+
return None
1001+
items, types = self.parse_namedtuple_args(call, fullname)
1002+
if not items:
1003+
# Error. Construct dummy return value.
1004+
error_classdef = ClassDef('namedtuple', Block([]))
1005+
info = TypeInfo(SymbolTable(), error_classdef)
1006+
else:
1007+
listexpr = cast(ListExpr, call.args[1])
1008+
name = cast(StrExpr, call.args[0]).value
1009+
info = self.build_namedtuple_typeinfo(name, items, types)
1010+
call.analyzed = NamedTupleExpr(info).set_line(call.line)
1011+
return info
1012+
1013+
def parse_namedtuple_args(self, call: CallExpr,
1014+
fullname: str) -> Tuple[List[str], List[Type]]:
1015+
# TODO Share code with check_argument_count in checkexpr.py?
1016+
args = call.args
1017+
if len(args) < 2:
1018+
return self.fail_namedtuple_arg("Too few arguments for namedtuple()", call)
1019+
if len(args) > 2:
1020+
return self.fail_namedtuple_arg("Too many arguments for namedtuple()", call)
1021+
if call.arg_kinds != [ARG_POS, ARG_POS]:
1022+
return self.fail_namedtuple_arg("Unexpected arguments to namedtuple()", call)
1023+
if not isinstance(args[0], StrExpr):
1024+
return self.fail_namedtuple_arg(
1025+
"namedtuple() expects a string literal as the first argument", call)
1026+
if not isinstance(args[1], ListExpr):
1027+
return self.fail_namedtuple_arg(
1028+
"List literal expected as the second argument to namedtuple()", call)
1029+
listexpr = cast(ListExpr, args[1])
1030+
if fullname == 'collections.namedtuple':
1031+
# The fields argument contains just names, with implicit Any types.
1032+
if any(not isinstance(item, StrExpr) for item in listexpr.items):
1033+
return self.fail_namedtuple_arg("String literal expected as namedtuple() item",
1034+
call)
1035+
items = [cast(StrExpr, item).value for item in listexpr.items]
1036+
types = [AnyType() for _ in listexpr.items] # type: List[Type]
1037+
else:
1038+
# The fields argument contains (name, type) tuples.
1039+
items, types = self.parse_namedtuple_fields_with_types(listexpr.items, call)
1040+
return items, types
1041+
1042+
def parse_namedtuple_fields_with_types(self, nodes: List[Node],
1043+
context: Context) -> Tuple[List[str], List[Type]]:
1044+
items = [] # type: List[str]
1045+
types = [] # type: List[Type]
1046+
for item in nodes:
1047+
while isinstance(item, ParenExpr):
1048+
item = item.expr
1049+
if isinstance(item, TupleExpr):
1050+
if len(item.items) != 2:
1051+
return self.fail_namedtuple_arg("Invalid NamedTuple field definition",
1052+
item)
1053+
name, type_node = item.items
1054+
if isinstance(name, StrExpr):
1055+
items.append(name.value)
1056+
else:
1057+
return self.fail_namedtuple_arg("Invalid NamedTuple() field name", item)
1058+
try:
1059+
type = expr_to_unanalyzed_type(type_node)
1060+
except TypeTranslationError:
1061+
return self.fail_namedtuple_arg('Invalid field type', type_node)
1062+
types.append(self.anal_type(type))
1063+
else:
1064+
return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item)
1065+
return items, types
1066+
1067+
def fail_namedtuple_arg(self, message: str, context: Context) -> Tuple[List[str], List[Type]]:
1068+
self.fail(message, context)
1069+
return [], []
1070+
1071+
def build_namedtuple_typeinfo(self, name: str, items: List[str],
1072+
types: List[Type]) -> TypeInfo:
1073+
symbols = SymbolTable()
1074+
class_def = ClassDef(name, Block([]))
1075+
class_def.fullname = self.qualified_name(name)
1076+
info = TypeInfo(symbols, class_def)
1077+
# Add named tuple items as attributes.
1078+
# TODO: Make them read-only.
1079+
for item, typ in zip(items, types):
1080+
var = Var(item)
1081+
var.info = info
1082+
var.type = typ
1083+
symbols[item] = SymbolTableNode(MDEF, var)
1084+
# Add a __init__ method.
1085+
init = self.make_namedtuple_init(info, items, types)
1086+
symbols['__init__'] = SymbolTableNode(MDEF, init)
1087+
info.tuple_type = TupleType(types, self.named_type('__builtins__.tuple'))
1088+
info.mro = [info] + info.tuple_type.fallback.type.mro
1089+
return info
1090+
1091+
def make_namedtuple_init(self, info: TypeInfo, items: List[str],
1092+
types: List[Type]) -> FuncDef:
1093+
args = [Var(item) for item in items]
1094+
for arg, type in zip(args, types):
1095+
arg.type = type
1096+
# TODO: Make sure that the self argument name is not visible?
1097+
args = [Var('__self')] + args
1098+
arg_kinds = [ARG_POS] * (len(items) + 1)
1099+
signature = Callable([cast(Type, None)] + types,
1100+
arg_kinds,
1101+
['__self'] + items,
1102+
NoneTyp(),
1103+
self.named_type('__builtins__.function'),
1104+
name=info.name())
1105+
return FuncDef('__init__',
1106+
args, arg_kinds,
1107+
[None] * (len(items) + 1),
1108+
Block([]),
1109+
typ=signature)
1110+
9631111
def analyze_types(self, items: List[Node]) -> List[Type]:
9641112
result = List[Type]()
9651113
for node in items:
@@ -1697,7 +1845,7 @@ def fail(self, msg: str, ctx: Context) -> None:
16971845
self.errors.report(ctx.get_line(), msg)
16981846

16991847

1700-
def self_type(typ: TypeInfo) -> Instance:
1848+
def self_type(typ: TypeInfo) -> Union[Instance, TupleType]:
17011849
"""For a non-generic type, return instance type representing the type.
17021850
For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn].
17031851
"""
@@ -1706,7 +1854,11 @@ def self_type(typ: TypeInfo) -> Instance:
17061854
tv.append(TypeVar(typ.type_vars[i], i + 1,
17071855
typ.defn.type_vars[i].values,
17081856
typ.defn.type_vars[i].upper_bound))
1709-
return Instance(typ, tv)
1857+
inst = Instance(typ, tv)
1858+
if typ.tuple_type is None:
1859+
return inst
1860+
else:
1861+
return TupleType(typ.tuple_type.items, inst)
17101862

17111863

17121864
@overload

mypy/strconv.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,11 @@ def visit_type_var_expr(self, o):
394394
else:
395395
return 'TypeVarExpr:{}()'.format(o.line)
396396

397+
def visit_namedtuple_expr(self, o):
398+
return 'NamedTupleExpr:{}({}, {})'.format(o.line,
399+
o.info.name(),
400+
o.info.tuple_type)
401+
397402
def visit_ducktype_expr(self, o):
398403
return 'DucktypeExpr:{}({})'.format(o.line, o.type)
399404

mypy/subtypes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def visit_tuple_type(self, left: TupleType) -> bool:
132132
for i in range(len(left.items)):
133133
if not is_subtype(left.items[i], right.items[i]):
134134
return False
135+
if not is_subtype(left.fallback, right.fallback):
136+
return False
135137
return True
136138
else:
137139
return False

0 commit comments

Comments
 (0)