Skip to content

Commit 77e3237

Browse files
pkchilevkivskyi
authored andcommitted
Warn on type arguments in isinstance (#3040)
* Warn on type arguments in isinstance * CR fixes * Fix test typo * Add explanatory comments
1 parent 24a026b commit 77e3237

File tree

4 files changed

+56
-0
lines changed

4 files changed

+56
-0
lines changed

mypy/checkexpr.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,19 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
178178
e.callee.node.typeddict_type is not None:
179179
return self.check_typeddict_call(e.callee.node.typeddict_type,
180180
e.arg_kinds, e.arg_names, e.args, e)
181+
if isinstance(e.callee, NameExpr) and e.callee.name in ('isinstance', 'issubclass'):
182+
for typ in mypy.checker.flatten(e.args[1]):
183+
if isinstance(typ, NameExpr):
184+
try:
185+
node = self.chk.lookup_qualified(typ.name)
186+
except KeyError:
187+
# Undefined names should already be reported in semantic analysis.
188+
node = None
189+
if (isinstance(typ, IndexExpr)
190+
and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))
191+
# node.kind == TYPE_ALIAS only for aliases like It = Iterable[int].
192+
or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS):
193+
self.msg.type_arguments_not_allowed(e)
181194
self.try_infer_partial_type(e)
182195
callee_type = self.accept(e.callee)
183196
if (self.chk.options.disallow_untyped_calls and

mypy/messages.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,9 @@ def typeddict_item_name_not_found(self,
881881
self.fail('\'{}\' is not a valid item name; expected one of {}'.format(
882882
item_name, format_item_name_list(typ.items.keys())), context)
883883

884+
def type_arguments_not_allowed(self, context: Context) -> None:
885+
self.fail('Parameterized generics cannot be used with class or instance checks', context)
886+
884887

885888
def capitalize(s: str) -> str:
886889
"""Capitalize the first character of a string."""

test-data/unit/check-isinstance.test

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,45 @@ def f(x: Union[int, A], a: Type[A]) -> None:
14231423
[builtins fixtures/isinstancelist.pyi]
14241424

14251425

1426+
[case testIsinstanceTypeArgs]
1427+
from typing import Iterable, TypeVar
1428+
x = 1
1429+
T = TypeVar('T')
1430+
1431+
isinstance(x, Iterable)
1432+
isinstance(x, Iterable[int]) # E: Parameterized generics cannot be used with class or instance checks
1433+
isinstance(x, Iterable[T]) # E: Parameterized generics cannot be used with class or instance checks
1434+
isinstance(x, (int, Iterable[int])) # E: Parameterized generics cannot be used with class or instance checks
1435+
isinstance(x, (int, (str, Iterable[int]))) # E: Parameterized generics cannot be used with class or instance checks
1436+
1437+
[builtins fixtures/isinstancelist.pyi]
1438+
1439+
[case testIsinstanceTypeArgsAliases]
1440+
from typing import Iterable, TypeVar
1441+
x = 1
1442+
T = TypeVar('T')
1443+
It = Iterable
1444+
It2 = Iterable[T]
1445+
1446+
isinstance(x, It[int]) # E: Parameterized generics cannot be used with class or instance checks
1447+
isinstance(x, It)
1448+
isinstance(x, It2[int]) # E: Parameterized generics cannot be used with class or instance checks
1449+
isinstance(x, It2) # E: Parameterized generics cannot be used with class or instance checks
1450+
1451+
[builtins fixtures/isinstance.pyi]
1452+
1453+
1454+
[case testIssubclassTypeArgs]
1455+
from typing import Iterable, TypeVar
1456+
x = int
1457+
T = TypeVar('T')
1458+
issubclass(x, Iterable)
1459+
issubclass(x, Iterable[int]) # E: Parameterized generics cannot be used with class or instance checks
1460+
issubclass(x, Iterable[T]) # E: Parameterized generics cannot be used with class or instance checks
1461+
issubclass(x, (int, Iterable[int])) # E: Parameterized generics cannot be used with class or instance checks
1462+
1463+
[builtins fixtures/isinstance.pyi]
1464+
14261465
[case testIsinstanceAndNarrowTypeVariable]
14271466
from typing import TypeVar
14281467

test-data/unit/fixtures/isinstance.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class tuple(Generic[T]): pass
1313
class function: pass
1414

1515
def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass
16+
def issubclass(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass
1617

1718
class int:
1819
def __add__(self, other: 'int') -> 'int': pass

0 commit comments

Comments
 (0)