From 9e31c27458de07ba2a97be2809f0da90c2559c39 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 4 Jul 2017 13:44:29 +0100 Subject: [PATCH] Reject isinstance() with TypedDict and NewType These can't be used for runtime type checking at runtime. --- mypy/checkexpr.py | 11 ++++++++--- mypy/messages.py | 2 ++ mypy/test/testextensions.py | 6 +++--- test-data/unit/check-newtype.test | 8 ++++++++ test-data/unit/check-typeddict.test | 10 ++++++++-- test-data/unit/fixtures/isinstancelist.pyi | 1 + 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 69b2582619cb..1a5d78033311 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -200,11 +200,16 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: except KeyError: # Undefined names should already be reported in semantic analysis. node = None - if (isinstance(typ, IndexExpr) - and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) + 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): + or (isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS)): self.msg.type_arguments_not_allowed(e) + if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo): + if typ.node.typeddict_type: + self.msg.fail(messages.CANNOT_ISINSTANCE_TYPEDDICT, e) + elif typ.node.is_newtype: + self.msg.fail(messages.CANNOT_ISINSTANCE_NEWTYPE, e) self.try_infer_partial_type(e) callee_type = self.accept(e.callee, always_allow_any=True) if (self.chk.options.disallow_untyped_calls and diff --git a/mypy/messages.py b/mypy/messages.py index 8d53d12ce651..d3861309a759 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -88,6 +88,8 @@ NON_BOOLEAN_IN_CONDITIONAL = 'Condition must be a boolean' DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' GENERIC_INSTANCE_VAR_CLASS_ACCESS = 'Access to generic instance variables via class is ambiguous' +CANNOT_ISINSTANCE_TYPEDDICT = 'Cannot use isinstance() with a TypedDict type' +CANNOT_ISINSTANCE_NEWTYPE = 'Cannot use isinstance() with a NewType type' ARG_CONSTRUCTOR_NAMES = { ARG_POS: "Arg", diff --git a/mypy/test/testextensions.py b/mypy/test/testextensions.py index 2203cf814f00..cf0c99c011a3 100644 --- a/mypy/test/testextensions.py +++ b/mypy/test/testextensions.py @@ -84,11 +84,11 @@ def test_typeddict_errors(self): self.assertEqual(TypedDict.__module__, 'mypy_extensions') jim = Emp(name='Jim', id=1) with self.assertRaises(TypeError): - isinstance({}, Emp) + isinstance({}, Emp) # type: ignore with self.assertRaises(TypeError): - isinstance(jim, Emp) + isinstance(jim, Emp) # type: ignore with self.assertRaises(TypeError): - issubclass(dict, Emp) + issubclass(dict, Emp) # type: ignore with self.assertRaises(TypeError): TypedDict('Hi', x=1) with self.assertRaises(TypeError): diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index 32b25558c84f..2af1fbfb8acf 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -336,3 +336,11 @@ class C(B): pass # E: Cannot subclass NewType from typing import NewType Any = NewType('Any', int) Any(5) + +[case testNewTypeAndIsInstance] +from typing import NewType +T = NewType('T', int) +d: object +if isinstance(d, T): # E: Cannot use isinstance() with a NewType type + reveal_type(d) # E: Revealed type is '__main__.T' +[builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index a923a403b63d..7f40af54b078 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -717,8 +717,14 @@ def set_coordinate(p: TaggedPoint, key: str, value: int) -> None: -- isinstance --- TODO: Implement support for this case. ---[case testCannotIsInstanceTypedDictType] +[case testTypedDictAndInstance] +from mypy_extensions import TypedDict +D = TypedDict('D', {'x': int}) +d: object +if isinstance(d, D): # E: Cannot use isinstance() with a TypedDict type + reveal_type(d) # E: Revealed type is '__main__.D' +[builtins fixtures/isinstancelist.pyi] + -- scoping [case testTypedDictInClassNamespace] diff --git a/test-data/unit/fixtures/isinstancelist.pyi b/test-data/unit/fixtures/isinstancelist.pyi index f60010a6125f..5ee49b86f7fd 100644 --- a/test-data/unit/fixtures/isinstancelist.pyi +++ b/test-data/unit/fixtures/isinstancelist.pyi @@ -8,6 +8,7 @@ class type: class tuple: pass class function: pass +class ellipsis: pass def isinstance(x: object, t: Union[type, Tuple]) -> bool: pass def issubclass(x: object, t: Union[type, Tuple]) -> bool: pass