From a1e79737da7fbd04cf64e155abe626cc31d2579e Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 02:25:23 -0700 Subject: [PATCH 1/4] Warn on type arguments in isinstance --- mypy/checkexpr.py | 10 ++++++++++ mypy/messages.py | 5 +++++ test-data/unit/check-isinstance.test | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index ba598f166a81a..12f6d1d478bf6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -182,6 +182,16 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: e.callee.node.typeddict_type is not None: return self.check_typeddict_call(e.callee.node.typeddict_type, e.arg_kinds, e.arg_names, e.args, e) + if isinstance(e.callee, NameExpr) and e.callee.name in ('isinstance', 'issubclass'): + for typ in mypy.checker.flatten(e.args[1]): + try: + if (isinstance(typ, IndexExpr) + and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) + or isinstance(typ, NameExpr) + and self.chk.lookup_qualified(typ.name).kind == nodes.TYPE_ALIAS): + self.msg.type_arguments_not_allowed(e) + except KeyError: + pass self.try_infer_partial_type(e) callee_type = self.accept(e.callee) if (self.chk.options.disallow_untyped_calls and diff --git a/mypy/messages.py b/mypy/messages.py index fdbf922d4016a..f7c66a8a4d937 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -864,6 +864,11 @@ def typeddict_item_name_not_found(self, self.fail('\'{}\' is not a valid item name; expected one of {}'.format( item_name, format_item_name_list(typ.items.keys())), context) + def type_arguments_not_allowed(self, + context: Context, + ) -> None: + self.fail('Type arguments not allowed in this context', context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 973d463d6085d..11cb21603af25 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1412,3 +1412,24 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] + +[case testNotTypeArgsInIsinstance] +from typing import Iterable, TypeVar +x = 1 +isinstance(x, Iterable[int]) # E: Type arguments not allowed in this context +isinstance(x, Iterable) +isinstance(x, (int, Iterable[int])) # E: Type arguments not allowed in this context +isinstance(x, (int, (str, Iterable[int]))) # E: Type arguments not allowed in this context + +It = Iterable +isinstance(x, It[int]) # E: Type arguments not allowed in this context +isinstance(x, It) + +T = TypeVar('T') +isinstance(x, Iterable[T]) # E: Type arguments not allowed in this context +It2 = Iterable[T] +isinstance(x, It2[int]) # E: Type arguments not allowed in this context +isinstance(x, It2) # E: Type arguments not allowed in this context + +[builtins fixtures/isinstancelist.pyi] + From 04095e0650759c51af70efd733b3ee177cff1dac Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 12:02:07 -0700 Subject: [PATCH 2/4] CR fixes --- mypy/checkexpr.py | 17 +++++------ mypy/messages.py | 6 ++-- test-data/unit/check-isinstance.test | 39 +++++++++++++++++++------- test-data/unit/fixtures/isinstance.pyi | 1 + 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 12f6d1d478bf6..14f5c792c4884 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -184,14 +184,15 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: e.arg_kinds, e.arg_names, e.args, e) if isinstance(e.callee, NameExpr) and e.callee.name in ('isinstance', 'issubclass'): for typ in mypy.checker.flatten(e.args[1]): - try: - if (isinstance(typ, IndexExpr) - and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) - or isinstance(typ, NameExpr) - and self.chk.lookup_qualified(typ.name).kind == nodes.TYPE_ALIAS): - self.msg.type_arguments_not_allowed(e) - except KeyError: - pass + if isinstance(typ, NameExpr): + try: + node = self.chk.lookup_qualified(typ.name) + except KeyError: + node = None + if (isinstance(typ, IndexExpr) + and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) + or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS): + self.msg.type_arguments_not_allowed(e) self.try_infer_partial_type(e) callee_type = self.accept(e.callee) if (self.chk.options.disallow_untyped_calls and diff --git a/mypy/messages.py b/mypy/messages.py index f7c66a8a4d937..9638dff86407f 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -864,10 +864,8 @@ def typeddict_item_name_not_found(self, self.fail('\'{}\' is not a valid item name; expected one of {}'.format( item_name, format_item_name_list(typ.items.keys())), context) - def type_arguments_not_allowed(self, - context: Context, - ) -> None: - self.fail('Type arguments not allowed in this context', context) + def type_arguments_not_allowed(self, context: Context) -> None: + self.fail('Parameterized generics cannot be used with class or instance checks', context) def capitalize(s: str) -> str: diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 11cb21603af25..4d1761c7fd1a2 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1413,23 +1413,42 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] -[case testNotTypeArgsInIsinstance] +[case testIsinstanceTypeArgs] from typing import Iterable, TypeVar x = 1 -isinstance(x, Iterable[int]) # E: Type arguments not allowed in this context +T = TypeVar('T') + isinstance(x, Iterable) -isinstance(x, (int, Iterable[int])) # E: Type arguments not allowed in this context -isinstance(x, (int, (str, Iterable[int]))) # E: Type arguments not allowed in this context +isinstance(x, Iterable[int]) # E: Parameterized generics cannot be used with class or instance checks +isinstance(x, Iterable[T]) # E: Parameterized generics cannot be used with class or instance checks +isinstance(x, (int, Iterable[int])) # E: Parameterized generics cannot be used with class or instance checks +isinstance(x, (int, (str, Iterable[int]))) # E: Parameterized generics cannot be used with class or instance checks + +[builtins fixtures/isinstance.pyi] +[case testIsinstanceTypeArgsAliases] +from typing import Iterable, TypeVar +x = 1 +T = TypeVar('T') It = Iterable -isinstance(x, It[int]) # E: Type arguments not allowed in this context +It2 = Iterable[T] + +isinstance(x, It[int]) # E: Parameterized generics cannot be used with class or instance checks isinstance(x, It) +isinstance(x, It2[int]) # E: Parameterized generics cannot be used with class or instance checks +isinstance(x, It2) # E: Parameterized generics cannot be used with class or instance checks + +[builtins fixtures/isinstance.pyi] + +[case testIssubclassTypeArgs] +from typing import Iterable, TypeVar +x = int T = TypeVar('T') -isinstance(x, Iterable[T]) # E: Type arguments not allowed in this context -It2 = Iterable[T] -isinstance(x, It2[int]) # E: Type arguments not allowed in this context -isinstance(x, It2) # E: Type arguments not allowed in this context +issubclass(x, Iterable) +issubclass(x, Iterable[int]) # E: Parameterized generics cannot be used with class or instance checks +issubclass(x, Iterable[T]) # E: Parameterized generics cannot be used with class or instance checks +issubclass(x, (int, Iterable[int])) # E: Parameterized generics cannot be used with class or instance checks -[builtins fixtures/isinstancelist.pyi] +[builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/fixtures/isinstance.pyi b/test-data/unit/fixtures/isinstance.pyi index c155a9724ad43..47d40d93a7366 100644 --- a/test-data/unit/fixtures/isinstance.pyi +++ b/test-data/unit/fixtures/isinstance.pyi @@ -13,6 +13,7 @@ class tuple(Generic[T]): pass class function: pass def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass +def issubclass(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass class int: def __add__(self, other: 'int') -> 'int': pass From 68fcbc5f25ef2b3d830f1a3f8acf89ca4a4db44b Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 12:48:55 -0700 Subject: [PATCH 3/4] Fix test typo --- test-data/unit/check-isinstance.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 4d1761c7fd1a2..ad3d8a09bde1e 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1424,7 +1424,7 @@ isinstance(x, Iterable[T]) # E: Parameterized generics cannot be used with clas isinstance(x, (int, Iterable[int])) # E: Parameterized generics cannot be used with class or instance checks isinstance(x, (int, (str, Iterable[int]))) # E: Parameterized generics cannot be used with class or instance checks -[builtins fixtures/isinstance.pyi] +[builtins fixtures/isinstancelist.pyi] [case testIsinstanceTypeArgsAliases] from typing import Iterable, TypeVar From a4bbc9d5705d26c48e9cb494af9e344f18cec04c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 13 Apr 2017 12:06:05 +0200 Subject: [PATCH 4/4] Add explanatory comments --- mypy/checkexpr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 038eb93e9ea29..1cc785f5064f3 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -184,9 +184,11 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: try: node = self.chk.lookup_qualified(typ.name) except KeyError: + # Undefined names should already be reported in semantic analysis. node = None if (isinstance(typ, IndexExpr) and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) + # node.kind == TYPE_ALIAS only for aliases like It = Iterable[int]. or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS): self.msg.type_arguments_not_allowed(e) self.try_infer_partial_type(e)