Skip to content

Commit ff42f32

Browse files
committed
Fix generic function overloading with tuple types
Fixes #1595. There were actually two issues: * Erasing tuple types was incorrect, as the fallback type was not erased. * Some tuple types got generated without a type argument, even though a uniform tuple type is internally represented as instance type tuple[t]. Similar issues might still arise in other contexts but at least this is improves things a little. Also, we generate fallback types that are like Tuple[Any, ...]. We could potentially generate a more precise fallback type, but that would potentially require joins and I'm not sure if we can use them during semantic analysis.
1 parent a9aeacc commit ff42f32

File tree

5 files changed

+34
-9
lines changed

5 files changed

+34
-9
lines changed

mypy/erasetype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
6767
return t.items()[0].accept(self)
6868

6969
def visit_tuple_type(self, t: TupleType) -> Type:
70-
return t.fallback
70+
return t.fallback.accept(self)
7171

7272
def visit_union_type(self, t: UnionType) -> Type:
7373
return AnyType() # XXX: return underlying type if only one?

mypy/test/data/check-overloading.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,20 @@ def f(x: int) -> None: pass
678678
def f(x: B) -> str: pass # E: Overloaded function signatures 2 and 3 overlap with incompatible return types
679679
@overload
680680
def f(x: A) -> int: pass
681+
682+
[case testOverloadWithTupleMatchingTypeVar]
683+
from typing import TypeVar, Generic, Tuple, overload
684+
685+
T = TypeVar('T')
686+
687+
class A(Generic[T]):
688+
@overload
689+
def f(self, arg: T) -> None:
690+
pass
691+
@overload
692+
def f(self, arg: T, default: int) -> None:
693+
pass
694+
695+
b = A() # type: A[Tuple[int, int]]
696+
b.f((0, 0))
697+
b.f((0, '')) # E: Argument 1 to "f" of "A" has incompatible type "Tuple[int, str]"; expected "Tuple[int, int]"

mypy/test/data/check-tuples.test

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,16 +594,18 @@ b = bool()
594594
s = t.__len__() # E: Incompatible types in assignment (expression has type "int", variable has type "str")
595595
i = t.__str__() # E: Incompatible types in assignment (expression has type "str", variable has type "int")
596596
i = s in t # E: Incompatible types in assignment (expression has type "bool", variable has type "int")
597-
t.foo # E: "tuple" has no attribute "foo"
597+
t.foo # E: Tuple[Any, ...] has no attribute "foo"
598598

599599
i = t.__len__()
600600
s = t.__str__()
601601
b = s in t
602602

603603
[file builtins.py]
604+
from typing import TypeVar, Generic
605+
_T = TypeVar('_T')
604606
class object:
605607
def __init__(self) -> None: pass
606-
class tuple:
608+
class tuple(Generic[_T]):
607609
def __len__(self) -> int: pass
608610
def __str__(self) -> str: pass
609611
def __contains__(self, o: object) -> bool: pass
@@ -669,6 +671,7 @@ class A(Tuple[int, str]):
669671
a, b = self
670672
b, a = self # Error
671673
self.f('') # Error
674+
[builtins fixtures/tuple.py]
672675
[out]
673676
main:1: note: In module imported here:
674677
tmp/m.pyi: note: In member "f" of class "A":
@@ -679,6 +682,7 @@ tmp/m.pyi:7: error: Argument 1 to "f" of "A" has incompatible type "str"; expect
679682
[case testInvalidTupleBaseClass]
680683
from typing import Tuple
681684
class A(Tuple[int, str]): pass # E: Tuple[...] not supported as a base class outside a stub file
685+
[builtins fixtures/tuple.py]
682686
[out]
683687

684688
[case testValidTupleBaseClass]
@@ -704,6 +708,7 @@ from typing import TypeVar, Generic, Tuple
704708
T = TypeVar('T')
705709
class Test(Generic[T], Tuple[T]): pass
706710
x = Test() # type: Test[int]
711+
[builtins fixtures/tuple.py]
707712
[out]
708713
main:3: error: Tuple[...] not supported as a base class outside a stub file
709714
main:4: error: Generic tuple types not supported

mypy/test/data/semanal-classes.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ import m
572572
[file m.pyi]
573573
from typing import Tuple
574574
class A(Tuple[int, str]): pass
575+
[builtins fixtures/tuple.py]
575576
[out]
576577
MypyFile:1(
577578
Import:1(m))
@@ -583,7 +584,7 @@ MypyFile:1(
583584
TupleType(
584585
Tuple[builtins.int, builtins.str])
585586
BaseType(
586-
builtins.tuple)
587+
builtins.tuple[Any])
587588
PassStmt:2()))
588589

589590
[case testBaseClassFromIgnoredModule]

mypy/typeanal.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
100100
node = self.lookup_fqn_func('builtins.tuple')
101101
info = cast(TypeInfo, node.node)
102102
return Instance(info, [t.args[0].accept(self)], t.line)
103-
return TupleType(self.anal_array(t.args),
104-
self.builtin_type('builtins.tuple'))
103+
return self.tuple_type(self.anal_array(t.args))
105104
elif fullname == 'typing.Union':
106105
items = self.anal_array(t.args)
107106
items = [item for item in items if not isinstance(item, Void)]
@@ -191,7 +190,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:
191190
if star_count > 1:
192191
self.fail('At most one star type allowed in a tuple', t)
193192
return AnyType()
194-
fallback = t.fallback if t.fallback else self.builtin_type('builtins.tuple')
193+
fallback = t.fallback if t.fallback else self.builtin_type('builtins.tuple', [AnyType()])
195194
return TupleType(self.anal_array(t.items), fallback, t.line)
196195

197196
def visit_star_type(self, t: StarType) -> Type:
@@ -257,10 +256,13 @@ def anal_var_defs(self, var_defs: List[TypeVarDef]) -> List[TypeVarDef]:
257256
vd.line))
258257
return a
259258

260-
def builtin_type(self, fully_qualified_name: str) -> Instance:
259+
def builtin_type(self, fully_qualified_name: str, args: List[Type] = None) -> Instance:
261260
node = self.lookup_fqn_func(fully_qualified_name)
262261
info = cast(TypeInfo, node.node)
263-
return Instance(info, [])
262+
return Instance(info, args or [])
263+
264+
def tuple_type(self, items: List[Type]) -> TupleType:
265+
return TupleType(items, fallback=self.builtin_type('builtins.tuple', [AnyType()]))
264266

265267

266268
class TypeAnalyserPass3(TypeVisitor[None]):

0 commit comments

Comments
 (0)