Skip to content

Commit f178a6c

Browse files
elazarggvanrossum
authored andcommitted
Selftype with TupleType (#2436)
(Reopening #2408 after the revert #2414. This fixes parts of #2090) This PR does three things: Fix the handling of TupleType (pass original_type recursively) Fix the handling of TypeType (avoid ad-hoc buggy special casing) Make NamedTuple's _replace() and _make() return selftype, serving as test case for (1) and (2) As a consequence of (1), some error messages are changed, as exemplified in check-isinstance.test. I think it's better now, but if it isn't, perhaps we should separate original_type and report_type as discussed in #2193.
1 parent 6c40613 commit f178a6c

File tree

6 files changed

+109
-55
lines changed

6 files changed

+109
-55
lines changed

mypy/checkexpr.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,9 @@ def check_call(self, callee: Type, args: List[Expression],
323323
callee)
324324
elif isinstance(callee, Instance):
325325
call_function = analyze_member_access('__call__', callee, context,
326-
False, False, False, self.named_type,
327-
self.not_ready_callback, self.msg, chk=self.chk)
326+
False, False, False, self.named_type,
327+
self.not_ready_callback, self.msg,
328+
original_type=callee, chk=self.chk)
328329
return self.check_call(call_function, args, arg_kinds, context, arg_names,
329330
callable_node, arg_messages)
330331
elif isinstance(callee, TypeVarType):
@@ -896,10 +897,11 @@ def analyze_ordinary_member_access(self, e: MemberExpr,
896897
return self.analyze_ref_expr(e)
897898
else:
898899
# This is a reference to a non-module attribute.
899-
return analyze_member_access(e.name, self.accept(e.expr), e,
900+
original_type = self.accept(e.expr)
901+
return analyze_member_access(e.name, original_type, e,
900902
is_lvalue, False, False,
901903
self.named_type, self.not_ready_callback, self.msg,
902-
chk=self.chk)
904+
original_type=original_type, chk=self.chk)
903905

904906
def analyze_external_member_access(self, member: str, base_type: Type,
905907
context: Context) -> Type:
@@ -909,7 +911,7 @@ def analyze_external_member_access(self, member: str, base_type: Type,
909911
# TODO remove; no private definitions in mypy
910912
return analyze_member_access(member, base_type, context, False, False, False,
911913
self.named_type, self.not_ready_callback, self.msg,
912-
chk=self.chk)
914+
original_type=base_type, chk=self.chk)
913915

914916
def visit_int_expr(self, e: IntExpr) -> Type:
915917
"""Type check an integer literal (trivial)."""
@@ -1070,7 +1072,7 @@ def check_op_local(self, method: str, base_type: Type, arg: Expression,
10701072
"""
10711073
method_type = analyze_member_access(method, base_type, context, False, False, True,
10721074
self.named_type, self.not_ready_callback, local_errors,
1073-
chk=self.chk)
1075+
original_type=base_type, chk=self.chk)
10741076
return self.check_call(method_type, [arg], [nodes.ARG_POS],
10751077
context, arg_messages=local_errors)
10761078

@@ -1670,8 +1672,8 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type:
16701672
is_lvalue=False, is_super=True, is_operator=False,
16711673
builtin_type=self.named_type,
16721674
not_ready_callback=self.not_ready_callback,
1673-
msg=self.msg, override_info=base, chk=self.chk,
1674-
original_type=declared_self)
1675+
msg=self.msg, override_info=base,
1676+
original_type=declared_self, chk=self.chk)
16751677
assert False, 'unreachable'
16761678
else:
16771679
# Invalid super. This has been reported by the semantic analyzer.

mypy/checkmember.py

+27-31
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def analyze_member_access(name: str,
3232
is_operator: bool,
3333
builtin_type: Callable[[str], Instance],
3434
not_ready_callback: Callable[[str, Context], None],
35-
msg: MessageBuilder,
35+
msg: MessageBuilder, *,
36+
original_type: Type,
3637
override_info: TypeInfo = None,
37-
original_type: Type = None,
3838
chk: 'mypy.checker.TypeChecker' = None) -> Type:
3939
"""Return the type of attribute `name` of typ.
4040
@@ -50,7 +50,6 @@ def analyze_member_access(name: str,
5050
the fallback type, for example.
5151
original_type is always the type used in the initial call.
5252
"""
53-
original_type = original_type or typ
5453
if isinstance(typ, Instance):
5554
if name == '__init__' and not is_super:
5655
# Accessing __init__ in statically typed code would compromise
@@ -75,7 +74,7 @@ def analyze_member_access(name: str,
7574
if method.is_property:
7675
assert isinstance(method, OverloadedFuncDef)
7776
return analyze_var(name, method.items[0].var, typ, info, node, is_lvalue, msg,
78-
not_ready_callback)
77+
original_type, not_ready_callback)
7978
if is_lvalue:
8079
msg.cant_assign_to_method(node)
8180
signature = function_type(method, builtin_type('builtins.function'))
@@ -108,14 +107,15 @@ def analyze_member_access(name: str,
108107
msg.disable_type_names += 1
109108
results = [analyze_member_access(name, subtype, node, is_lvalue, is_super,
110109
is_operator, builtin_type, not_ready_callback, msg,
111-
chk=chk)
110+
original_type=original_type, chk=chk)
112111
for subtype in typ.items]
113112
msg.disable_type_names -= 1
114113
return UnionType.make_simplified_union(results)
115114
elif isinstance(typ, TupleType):
116115
# Actually look up from the fallback instance type.
117116
return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super,
118-
is_operator, builtin_type, not_ready_callback, msg, chk=chk)
117+
is_operator, builtin_type, not_ready_callback, msg,
118+
original_type=original_type, chk=chk)
119119
elif isinstance(typ, FunctionLike) and typ.is_type_obj():
120120
# Class attribute.
121121
# TODO super?
@@ -190,15 +190,14 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
190190
builtin_type: Callable[[str], Instance],
191191
not_ready_callback: Callable[[str, Context], None],
192192
msg: MessageBuilder,
193-
original_type: Type = None,
193+
original_type: Type,
194194
chk: 'mypy.checker.TypeChecker' = None) -> Type:
195195
"""Analyse attribute access that does not target a method.
196196
197197
This is logically part of analyze_member_access and the arguments are similar.
198198
199199
original_type is the type of E in the expression E.var
200200
"""
201-
original_type = original_type or itype
202201
# It was not a method. Try looking up a variable.
203202
v = lookup_member_var_or_accessor(info, name, is_lvalue)
204203

@@ -207,7 +206,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
207206
# The associated Var node of a decorator contains the type.
208207
v = vv.var
209208
if isinstance(v, Var):
210-
return analyze_var(name, v, itype, info, node, is_lvalue, msg, not_ready_callback)
209+
return analyze_var(name, v, itype, info, node, is_lvalue, msg,
210+
original_type, not_ready_callback)
211211
elif isinstance(v, FuncDef):
212212
assert False, "Did not expect a function"
213213
elif not v and name not in ['__getattr__', '__setattr__']:
@@ -235,16 +235,14 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
235235

236236

237237
def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context,
238-
is_lvalue: bool, msg: MessageBuilder,
239-
not_ready_callback: Callable[[str, Context], None],
240-
original_type: Type = None) -> Type:
238+
is_lvalue: bool, msg: MessageBuilder, original_type: Type,
239+
not_ready_callback: Callable[[str, Context], None]) -> Type:
241240
"""Analyze access to an attribute via a Var node.
242241
243242
This is conceptually part of analyze_member_access and the arguments are similar.
244243
245244
original_type is the type of E in the expression E.var
246245
"""
247-
original_type = original_type or itype
248246
# Found a member variable.
249247
itype = map_instance_to_supertype(itype, var.info)
250248
typ = var.type
@@ -300,7 +298,7 @@ def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: Messag
300298

301299

302300
def lookup_member_var_or_accessor(info: TypeInfo, name: str,
303-
is_lvalue: bool) -> SymbolNode:
301+
is_lvalue: bool) -> Optional[SymbolNode]:
304302
"""Find the attribute/accessor node that refers to a member of a type."""
305303
# TODO handle lvalues
306304
node = info.get(name)
@@ -346,7 +344,7 @@ def analyze_class_attribute_access(itype: Instance,
346344
builtin_type: Callable[[str], Instance],
347345
not_ready_callback: Callable[[str, Context], None],
348346
msg: MessageBuilder,
349-
original_type: Type = None) -> Type:
347+
original_type: Type) -> Type:
350348
"""original_type is the type of E in the expression E.var"""
351349
node = itype.type.get(name)
352350
if not node:
@@ -391,7 +389,7 @@ def analyze_class_attribute_access(itype: Instance,
391389

392390
def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool,
393391
builtin_type: Callable[[str], Instance],
394-
original_type: Type = None) -> Type:
392+
original_type: Type) -> Type:
395393
"""Instantiate type variables during analyze_class_attribute_access,
396394
e.g T and Q in the following:
397395
@@ -405,21 +403,19 @@ class B(A): pass
405403
406404
original_type is the value of the type B in the expression B.foo()
407405
"""
408-
# TODO: verify consistency betweem Q and T
406+
# TODO: verify consistency between Q and T
409407
info = itype.type # type: TypeInfo
410408
if isinstance(t, CallableType):
411409
# TODO: Should we propagate type variable values?
412-
vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance)
413-
for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)]
410+
tvars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance)
411+
for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)]
414412
if is_classmethod:
415-
if not isinstance(original_type, TypeType):
416-
original_type = TypeType(itype)
417413
t = bind_self(t, original_type)
418-
return t.copy_modified(variables=vars + t.variables)
414+
return t.copy_modified(variables=tvars + t.variables)
419415
elif isinstance(t, Overloaded):
420-
return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod,
416+
return Overloaded([cast(CallableType, add_class_tvars(item, itype, is_classmethod,
421417
builtin_type, original_type))
422-
for i in t.items()])
418+
for item in t.items()])
423419
return t
424420

425421

@@ -573,15 +569,15 @@ class B(A): pass
573569
return cast(F, func)
574570
if func.arg_kinds[0] == ARG_STAR:
575571
# The signature is of the form 'def foo(*args, ...)'.
576-
# In this case we shouldn'func drop the first arg,
572+
# In this case we shouldn't drop the first arg,
577573
# since func will be absorbed by the *args.
578574

579575
# TODO: infer bounds on the type of *args?
580576
return cast(F, func)
581577
self_param_type = func.arg_types[0]
582578
if func.variables and (isinstance(self_param_type, TypeVarType) or
583-
(isinstance(self_param_type, TypeType) and
584-
isinstance(self_param_type.item, TypeVarType))):
579+
(isinstance(self_param_type, TypeType) and
580+
isinstance(self_param_type.item, TypeVarType))):
585581
if original_type is None:
586582
# Type check method override
587583
# XXX value restriction as union?
@@ -601,10 +597,10 @@ def expand(target: Type) -> Type:
601597
ret_type = func.ret_type
602598
variables = func.variables
603599
res = func.copy_modified(arg_types=arg_types,
604-
arg_kinds=func.arg_kinds[1:],
605-
arg_names=func.arg_names[1:],
606-
variables=variables,
607-
ret_type=ret_type)
600+
arg_kinds=func.arg_kinds[1:],
601+
arg_names=func.arg_names[1:],
602+
variables=variables,
603+
ret_type=ret_type)
608604
return cast(F, res)
609605

610606

mypy/semanal.py

+20-10
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
from mypy.errors import Errors, report_internal_error
7272
from mypy.types import (
7373
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType,
74-
FunctionLike, UnboundType, TypeList, TypeVarDef,
74+
FunctionLike, UnboundType, TypeList, TypeVarDef, TypeType,
7575
TupleType, UnionType, StarType, EllipsisType, function_type)
7676
from mypy.nodes import implicit_module_attrs
7777
from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias
@@ -1799,31 +1799,41 @@ def add_field(var: Var, is_initialized_in_class: bool = False,
17991799
add_field(Var('_field_types', dictype), is_initialized_in_class=True)
18001800
add_field(Var('_source', strtype), is_initialized_in_class=True)
18011801

1802-
# TODO: SelfType should be bind to actual 'self'
1803-
this_type = fill_typevars(info)
1802+
tvd = TypeVarDef('NT', 1, [], info.tuple_type)
1803+
selftype = TypeVarType(tvd)
18041804

18051805
def add_method(funcname: str, ret: Type, args: List[Argument], name=None,
18061806
is_classmethod=False) -> None:
1807-
if not is_classmethod:
1808-
args = [Argument(Var('self'), this_type, None, ARG_POS)] + args
1807+
if is_classmethod:
1808+
first = [Argument(Var('cls'), TypeType(selftype), None, ARG_POS)]
1809+
else:
1810+
first = [Argument(Var('self'), selftype, None, ARG_POS)]
1811+
args = first + args
1812+
18091813
types = [arg.type_annotation for arg in args]
18101814
items = [arg.variable.name() for arg in args]
18111815
arg_kinds = [arg.kind for arg in args]
18121816
signature = CallableType(types, arg_kinds, items, ret, function_type,
18131817
name=name or info.name() + '.' + funcname)
1814-
signature.is_classmethod_class = is_classmethod
1818+
signature.variables = [tvd]
18151819
func = FuncDef(funcname, args, Block([]), typ=signature)
18161820
func.info = info
18171821
func.is_class = is_classmethod
1818-
info.names[funcname] = SymbolTableNode(MDEF, func)
1822+
if is_classmethod:
1823+
v = Var(funcname, signature)
1824+
v.is_classmethod = True
1825+
v.info = info
1826+
dec = Decorator(func, [NameExpr('classmethod')], v)
1827+
info.names[funcname] = SymbolTableNode(MDEF, dec)
1828+
else:
1829+
info.names[funcname] = SymbolTableNode(MDEF, func)
18191830

1820-
add_method('_replace', ret=this_type,
1831+
add_method('_replace', ret=selftype,
18211832
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED) for var in vars])
18221833
add_method('__init__', ret=NoneTyp(), name=info.name(),
18231834
args=[Argument(var, var.type, None, ARG_POS) for var in vars])
18241835
add_method('_asdict', args=[], ret=ordereddictype)
1825-
# FIX: make it actual class method
1826-
add_method('_make', ret=this_type, is_classmethod=True,
1836+
add_method('_make', ret=selftype, is_classmethod=True,
18271837
args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS),
18281838
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED),
18291839
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)])

test-data/unit/check-isinstance.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def f(x: Union[List[int], List[str], int]) -> None:
391391
else:
392392
x[0] # E: Value of type "int" is not indexable
393393
x + 1
394-
x[0] # E: Value of type "int" is not indexable
394+
x[0] # E: Value of type "Union[List[int], List[str], int]" is not indexable
395395
x + 1 # E: Unsupported operand types for + (likely involving Union)
396396
[builtins fixtures/isinstancelist.pyi]
397397
[out]

test-data/unit/check-namedtuple.test

+48-2
Original file line numberDiff line numberDiff line change
@@ -283,15 +283,14 @@ reveal_type(x._replace()) # E: Revealed type is 'Tuple[builtins.int, builtins.s
283283
x._replace(x=5)
284284
x._replace(y=5) # E: Argument 1 to X._replace has incompatible type "int"; expected "str"
285285

286-
287286
[case testNamedTupleMake]
288287
from typing import NamedTuple
289288

290289
X = NamedTuple('X', [('x', int), ('y', str)])
291290
reveal_type(X._make([5, 'a'])) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
292291
X._make('a b') # E: Argument 1 to X._make has incompatible type "str"; expected Iterable[Any]
293292

294-
-- # FIX: not a proper class method
293+
-- # FIX: not a proper class method
295294
-- x = None # type: X
296295
-- reveal_type(x._make([5, 'a'])) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
297296
-- x._make('a b') # E: Argument 1 to X._make has incompatible type "str"; expected Iterable[Any]
@@ -367,3 +366,50 @@ class D(NamedTuple('D', []), A): pass
367366
g(D()) # E: Argument 1 to "g" has incompatible type "D"; expected "C"
368367
y = None # type: C
369368
y = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C")
369+
370+
[case testNamedTupleSelfTypeMethod]
371+
from typing import TypeVar, NamedTuple
372+
373+
T = TypeVar('T', bound='A')
374+
375+
class A(NamedTuple('A', [('x', str)])):
376+
def member(self: T) -> T:
377+
return self
378+
379+
class B(A):
380+
pass
381+
382+
a = None # type: A
383+
a = A('').member()
384+
b = None # type: B
385+
b = B('').member()
386+
a = B('')
387+
a = B('').member()
388+
389+
[case testNamedTupleSelfTypeReplace]
390+
from typing import NamedTuple, TypeVar
391+
A = NamedTuple('A', [('x', str)])
392+
reveal_type(A('hello')._replace(x='')) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.A]'
393+
a = None # type: A
394+
a = A('hello')._replace(x='')
395+
396+
class B(A):
397+
pass
398+
399+
reveal_type(B('hello')._replace(x='')) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.B]'
400+
b = None # type: B
401+
b = B('hello')._replace(x='')
402+
403+
[case testNamedTupleSelfTypeMake]
404+
from typing import NamedTuple, TypeVar
405+
A = NamedTuple('A', [('x', str)])
406+
reveal_type(A._make([''])) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.A]'
407+
a = A._make(['']) # type: A
408+
409+
class B(A):
410+
pass
411+
412+
reveal_type(B._make([''])) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.B]'
413+
b = B._make(['']) # type: B
414+
415+
[builtins fixtures/list.pyi]

test-data/unit/check-tuples.test

+3-3
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,10 @@ main:15: error: Incompatible types in assignment (expression has type "Tuple[A,
559559

560560
a = None # type: A
561561

562-
(a, a) + a # E: Unsupported left operand type for + (Tuple[A, ...])
562+
(a, a) + a # E: Unsupported left operand type for + ("Tuple[A, A]")
563563
a + (a, a) # E: Unsupported operand types for + ("A" and "Tuple[A, A]")
564564
f((a, a)) # E: Argument 1 to "f" has incompatible type "Tuple[A, A]"; expected "A"
565-
(a, a).foo # E: Tuple[A, ...] has no attribute "foo"
565+
(a, a).foo # E: "Tuple[A, A]" has no attribute "foo"
566566

567567
def f(x: 'A') -> None: pass
568568

@@ -596,7 +596,7 @@ b = bool()
596596
s = t.__len__() # E: Incompatible types in assignment (expression has type "int", variable has type "str")
597597
i = t.__str__() # E: Incompatible types in assignment (expression has type "str", variable has type "int")
598598
i = s in t # E: Incompatible types in assignment (expression has type "bool", variable has type "int")
599-
t.foo # E: Tuple[Any, ...] has no attribute "foo"
599+
t.foo # E: "Tuple[int, str]" has no attribute "foo"
600600

601601
i = t.__len__()
602602
s = t.__str__()

0 commit comments

Comments
 (0)