Skip to content

Commit e39d0b4

Browse files
committed
Make type comparisons work
This commit introduces a workaround to fix the bug discussed in python#1787 Previously, code where you compared two types (eg `int == int`) would cause mypy to incorrectly report a "too few arguments" error.
1 parent be0e8f2 commit e39d0b4

File tree

3 files changed

+50
-5
lines changed

3 files changed

+50
-5
lines changed

mypy/checkmember.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,15 @@ def analyze_member_access(name: str, typ: Type, node: Context, is_lvalue: bool,
9191
if isinstance(ret_type, TupleType):
9292
ret_type = ret_type.fallback
9393
if isinstance(ret_type, Instance):
94-
result = analyze_class_attribute_access(ret_type, name, node, is_lvalue,
95-
builtin_type, not_ready_callback, msg)
96-
if result:
97-
return result
94+
if name not in {'__eq__', '__ne__'}:
95+
# We skip here so that when mypy sees `type(foo) == type(bar)`, it doesn't try
96+
# and typecheck against `foo.__eq__`. This workaround makes sure that mypy falls
97+
# through and uses `foo.__class__.__eq__`.instead. See the bug discussed in
98+
# https://github.com/python/mypy/pull/1787 for more info.
99+
result = analyze_class_attribute_access(ret_type, name, node, is_lvalue,
100+
builtin_type, not_ready_callback, msg)
101+
if result:
102+
return result
98103
# Look up from the 'type' type.
99104
return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super,
100105
builtin_type, not_ready_callback, msg,
@@ -121,7 +126,8 @@ def analyze_member_access(name: str, typ: Type, node: Context, is_lvalue: bool,
121126
elif isinstance(typ.item, TypeVarType):
122127
if isinstance(typ.item.upper_bound, Instance):
123128
item = typ.item.upper_bound
124-
if item:
129+
if item and name not in {'__eq__', '__ne__'}:
130+
# See comment above for why __eq__ and __ne__ are skipped
125131
result = analyze_class_attribute_access(item, name, node, is_lvalue,
126132
builtin_type, not_ready_callback, msg)
127133
if result:

test-data/unit/check-classes.test

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,3 +2006,39 @@ reveal_type(User) # E: Revealed type is 'builtins.type'
20062006
[builtins fixtures/args.py]
20072007
[out]
20082008

2009+
[case testTypeTypeComparisonWorks]
2010+
class User: pass
2011+
2012+
User == User
2013+
User == type(User())
2014+
type(User()) == User
2015+
type(User()) == type(User())
2016+
2017+
User != User
2018+
User != type(User())
2019+
type(User()) != User
2020+
type(User()) != type(User())
2021+
2022+
int == int
2023+
int == type(3)
2024+
type(3) == int
2025+
type(3) == type(3)
2026+
2027+
int != int
2028+
int != type(3)
2029+
type(3) != int
2030+
type(3) != type(3)
2031+
2032+
User is User
2033+
User is type(User)
2034+
type(User) is User
2035+
type(User) is type(User)
2036+
2037+
int is int
2038+
int is type(3)
2039+
type(3) is int
2040+
type(3) is type(3)
2041+
[builtins fixtures/args.py]
2042+
[out]
2043+
2044+

test-data/unit/fixtures/args.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88

99
class object:
1010
def __init__(self) -> None: pass
11+
def __eq__(self, o: object) -> bool: pass
12+
def __ne__(self, o: object) -> bool: pass
1113

1214
class type:
1315
@overload
1416
def __init__(self, o: object) -> None: pass
1517
@overload
1618
def __init__(self, name: str, bases: Tuple[type, ...], dict: Dict[str, Any]) -> None: pass
19+
def __call__(self, *args: Any, **kwargs: Any) -> Any: pass
1720

1821
class tuple(Iterable[Tco], Generic[Tco]): pass
1922
class dict(Generic[T, S]): pass

0 commit comments

Comments
 (0)