Skip to content

Commit e85c78e

Browse files
elazargilevkivskyi
authored andcommitted
Fix access to operators on metaclass through typevar (#5009)
Fix #4929: typevars were not handled correctly on has_member.
1 parent f97b98e commit e85c78e

File tree

5 files changed

+51
-9
lines changed

5 files changed

+51
-9
lines changed

mypy/checkexpr.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,10 @@ def has_member(self, typ: Type, member: str) -> bool:
24312431
"""Does type have member with the given name?"""
24322432
# TODO: refactor this to use checkmember.analyze_member_access, otherwise
24332433
# these two should be carefully kept in sync.
2434+
if isinstance(typ, TypeVarType):
2435+
typ = typ.upper_bound
2436+
if isinstance(typ, TupleType):
2437+
typ = typ.fallback
24342438
if isinstance(typ, Instance):
24352439
return typ.type.has_readable_member(member)
24362440
if isinstance(typ, CallableType) and typ.is_type_obj():
@@ -2440,14 +2444,17 @@ def has_member(self, typ: Type, member: str) -> bool:
24402444
elif isinstance(typ, UnionType):
24412445
result = all(self.has_member(x, member) for x in typ.relevant_items())
24422446
return result
2443-
elif isinstance(typ, TupleType):
2444-
return self.has_member(typ.fallback, member)
24452447
elif isinstance(typ, TypeType):
24462448
# Type[Union[X, ...]] is always normalized to Union[Type[X], ...],
24472449
# so we don't need to care about unions here.
2448-
if isinstance(typ.item, Instance) and typ.item.type.metaclass_type is not None:
2449-
return self.has_member(typ.item.type.metaclass_type, member)
2450-
if isinstance(typ.item, AnyType):
2450+
item = typ.item
2451+
if isinstance(item, TypeVarType):
2452+
item = item.upper_bound
2453+
if isinstance(item, TupleType):
2454+
item = item.fallback
2455+
if isinstance(item, Instance) and item.type.metaclass_type is not None:
2456+
return self.has_member(item.type.metaclass_type, member)
2457+
if isinstance(item, AnyType):
24512458
return True
24522459
return False
24532460
else:

test-data/unit/check-classes.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3459,6 +3459,23 @@ class Concrete(metaclass=Meta):
34593459
reveal_type(Concrete + X()) # E: Revealed type is 'builtins.str'
34603460
Concrete + "hello" # E: Unsupported operand types for + ("Type[Concrete]" and "str")
34613461

3462+
[case testMetaclassOperatorTypeVar]
3463+
from typing import Type, TypeVar
3464+
3465+
class MetaClass(type):
3466+
def __mul__(cls, other: int) -> str:
3467+
return ""
3468+
3469+
class Test(metaclass=MetaClass):
3470+
pass
3471+
3472+
S = TypeVar("S", bound=Test)
3473+
3474+
def f(x: Type[Test]) -> str:
3475+
return x * 0
3476+
def g(x: Type[S]) -> str:
3477+
return reveal_type(x * 0) # E: Revealed type is 'builtins.str'
3478+
34623479
[case testMetaclassGetitem]
34633480
class M(type):
34643481
def __getitem__(self, key) -> int: return 1

test-data/unit/check-generics.test

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,17 +1604,33 @@ def f(a: T, b: T) -> T:
16041604
return b
16051605
[builtins fixtures/ops.pyi]
16061606

1607-
[case testTypeVariableTypeLessThan]
1607+
[case testTypeVarLessThan]
16081608
from typing import TypeVar
16091609
T = TypeVar('T')
16101610
def f(a: T, b: T) -> T:
1611-
if a < b:
1611+
if a < b: # E: Unsupported left operand type for < ("T")
16121612
return a
16131613
else:
16141614
return b
16151615
[builtins fixtures/ops.pyi]
1616-
[out]
1617-
main:4: error: Unsupported left operand type for < ("T")
1616+
1617+
[case testTypeVarReversibleOperator]
1618+
from typing import TypeVar
1619+
class A:
1620+
def __mul__(cls, other: int) -> str: return ""
1621+
T = TypeVar("T", bound=A)
1622+
def f(x: T) -> str:
1623+
return reveal_type(x * 0) # E: Revealed type is 'builtins.str'
1624+
1625+
[case testTypeVarReversibleOperatorTuple]
1626+
from typing import TypeVar, Tuple
1627+
class A(Tuple[int, int]):
1628+
def __mul__(cls, other: Tuple[int, int]) -> str: return ""
1629+
T = TypeVar("T", bound=A)
1630+
def f(x: T) -> str:
1631+
return reveal_type(x * (1, 2) ) # E: Revealed type is 'builtins.str'
1632+
1633+
[builtins fixtures/tuple.pyi]
16181634

16191635

16201636
-- Subtyping generic callables

test-data/unit/fixtures/tuple.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class tuple(Sequence[Tco], Generic[Tco]):
1414
def __iter__(self) -> Iterator[Tco]: pass
1515
def __contains__(self, item: object) -> bool: pass
1616
def __getitem__(self, x: int) -> Tco: pass
17+
def __rmul__(self, n: int) -> tuple: pass
1718
def count(self, obj: Any) -> int: pass
1819
class function: pass
1920
class ellipsis: pass

test-data/unit/lib-stub/builtins.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class type:
77
# These are provided here for convenience.
88
class int:
99
def __add__(self, other: 'int') -> 'int': pass
10+
def __rmul__(self, other: 'int') -> 'int': pass
1011
class float: pass
1112

1213
class str:

0 commit comments

Comments
 (0)