Skip to content

Commit 91e9f40

Browse files
committed
Merge branch 'master' into nonlocal2
Conflicts: mypy/semanal.py mypy/treetransform.py
2 parents 4c58107 + 29f1009 commit 91e9f40

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
@@ -1321,6 +1321,20 @@ def accept(self, visitor: NodeVisitor[T]) -> T:
13211321
return visitor.visit_type_var_expr(self)
13221322

13231323

1324+
class NamedTupleExpr(Node):
1325+
"""Named tuple expression namedtuple(...)."""
1326+
1327+
# The class representation of this named tuple (its tuple_type attribute contains
1328+
# the tuple item types)
1329+
info = Undefined('TypeInfo')
1330+
1331+
def __init__(self, info: 'TypeInfo') -> None:
1332+
self.info = info
1333+
1334+
def accept(self, visitor: NodeVisitor[T]) -> T:
1335+
return visitor.visit_namedtuple_expr(self)
1336+
1337+
13241338
class DucktypeExpr(Node):
13251339
"""Ducktype class decorator expression ducktype(...)."""
13261340

@@ -1395,6 +1409,13 @@ class TypeInfo(SymbolNode):
13951409
# Duck type compatibility (ducktype decorator)
13961410
ducktype = None # type: mypy.types.Type
13971411

1412+
# Representation of a Tuple[...] base class, if the class has any
1413+
# (e.g., for named tuples). If this is not None, the actual Type
1414+
# object used for this class is not an Instance but a TupleType;
1415+
# the corresponding Instance is set as the fallback type of the
1416+
# tuple type.
1417+
tuple_type = None # type: mypy.types.TupleType
1418+
13981419
def __init__(self, names: 'SymbolTable', defn: ClassDef) -> None:
13991420
"""Initialize a TypeInfo."""
14001421
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, NonlocalDecl
60+
YieldFromStmt, YieldFromExpr, NamedTupleExpr, NonlocalDecl
6161
)
6262
from mypy.visitor import NodeVisitor
6363
from mypy.traverser import TraverserVisitor
@@ -502,6 +502,11 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
502502
bases = List[Instance]()
503503
for i in range(len(defn.base_types)):
504504
base = self.anal_type(defn.base_types[i])
505+
if isinstance(base, TupleType):
506+
if defn.info.tuple_type:
507+
self.fail("Class has two incompatible bases derived from tuple", defn)
508+
defn.info.tuple_type = base
509+
base = base.fallback
505510
if isinstance(base, Instance):
506511
defn.base_types[i] = base
507512
bases.append(base)
@@ -710,6 +715,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
710715
self.store_declared_types(lvalue, s.type)
711716
self.check_and_set_up_type_alias(s)
712717
self.process_typevar_declaration(s)
718+
self.process_namedtuple_definition(s)
713719

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

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

17341882

1735-
def self_type(typ: TypeInfo) -> Instance:
1883+
def self_type(typ: TypeInfo) -> Union[Instance, TupleType]:
17361884
"""For a non-generic type, return instance type representing the type.
17371885
For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn].
17381886
"""
@@ -1741,7 +1889,11 @@ def self_type(typ: TypeInfo) -> Instance:
17411889
tv.append(TypeVar(typ.type_vars[i], i + 1,
17421890
typ.defn.type_vars[i].values,
17431891
typ.defn.type_vars[i].upper_bound))
1744-
return Instance(typ, tv)
1892+
inst = Instance(typ, tv)
1893+
if typ.tuple_type is None:
1894+
return inst
1895+
else:
1896+
return TupleType(typ.tuple_type.items, inst)
17451897

17461898

17471899
@overload

mypy/strconv.py

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

400+
def visit_namedtuple_expr(self, o):
401+
return 'NamedTupleExpr:{}({}, {})'.format(o.line,
402+
o.info.name(),
403+
o.info.tuple_type)
404+
400405
def visit_ducktype_expr(self, o):
401406
return 'DucktypeExpr:{}({})'.format(o.line, o.type)
402407

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)