diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e1beea078ca9..176ab8a9198d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -248,6 +248,9 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> except KeyError: # Undefined names should already be reported in semantic analysis. pass + if is_expr_literal_type(typ): + self.msg.cannot_use_function_with_type(e.callee.name, "Literal", e) + continue if ((isinstance(typ, IndexExpr) and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))) or (isinstance(typ, NameExpr) and node and @@ -255,9 +258,9 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> 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) + self.msg.cannot_use_function_with_type(e.callee.name, "TypedDict", e) elif typ.node.is_newtype: - self.msg.fail(messages.CANNOT_ISINSTANCE_NEWTYPE, e) + self.msg.cannot_use_function_with_type(e.callee.name, "NewType", e) self.try_infer_partial_type(e) type_context = None if isinstance(e.callee, LambdaExpr): @@ -3629,3 +3632,15 @@ def is_literal_type_like(t: Optional[Type]) -> bool: return any(is_literal_type_like(item) for item in t.items) else: return False + + +def is_expr_literal_type(node: Expression) -> bool: + """Returns 'true' if the given node is a Literal""" + valid = ('typing.Literal', 'typing_extensions.Literal') + if isinstance(node, IndexExpr): + base = node.base + return isinstance(base, RefExpr) and base.fullname in valid + if isinstance(node, NameExpr): + underlying = node.node + return isinstance(underlying, TypeAlias) and isinstance(underlying.target, LiteralType) + return False diff --git a/mypy/messages.py b/mypy/messages.py index 6554d2dc163d..0bb754501a68 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -104,8 +104,6 @@ DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' # type: Final GENERIC_INSTANCE_VAR_CLASS_ACCESS = \ 'Access to generic instance variables via class is ambiguous' # type: Final -CANNOT_ISINSTANCE_TYPEDDICT = 'Cannot use isinstance() with a TypedDict type' # type: Final -CANNOT_ISINSTANCE_NEWTYPE = 'Cannot use isinstance() with a NewType type' # type: Final BARE_GENERIC = 'Missing type parameters for generic type' # type: Final IMPLICIT_GENERIC_ANY_BUILTIN = \ 'Implicit generic "Any". Use \'{}\' and specify generic parameters' # type: Final @@ -892,7 +890,9 @@ def incompatible_type_application(self, expected_arg_count: int, def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else - ' to Union' if isinstance(item, UnionType) else '') + ' to Union' if isinstance(item, UnionType) else + ' to Literal' if isinstance(item, LiteralType) else + '') self.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, @@ -1238,6 +1238,10 @@ def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail("Only concrete class can be given where {} is expected" .format(self.format(typ)), context) + def cannot_use_function_with_type( + self, method_name: str, type_name: str, context: Context) -> None: + self.fail("Cannot use {}() with a {} type".format(method_name, type_name), context) + def report_non_method_protocol(self, tp: TypeInfo, members: List[str], context: Context) -> None: self.fail("Only protocols that don't have non-method members can be" diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0c79c11c10c6..c87b500776ed 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -45,6 +45,8 @@ 'typing.Tuple', 'typing.Type', 'typing.Union', + 'typing.Literal', + 'typing_extensions.Literal', } # type: Final ARG_KINDS_BY_CONSTRUCTOR = { diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index c7f35c94ec5e..fbb50cad0d7c 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -336,8 +336,7 @@ d: 3j + 2 # E: invalid type comment or annotation [case testLiteralDisallowComplexNumbersTypeAlias] from typing_extensions import Literal -at = Literal[3j] # E: Invalid type alias \ - # E: The type "Type[Literal]" is not generic and not indexable +at = Literal[3j] # E: Invalid type alias a: at # E: Invalid type "__main__.at" [builtins fixtures/complex.pyi] [out] @@ -367,8 +366,7 @@ c: [1, 2, 3] # E: Invalid type [case testLiteralDisallowCollectionsTypeAlias] from typing_extensions import Literal -at = Literal[{"a": 1, "b": 2}] # E: Invalid type alias \ - # E: The type "Type[Literal]" is not generic and not indexable +at = Literal[{"a": 1, "b": 2}] # E: Invalid type alias bt = {"a": 1, "b": 2} a: at # E: Invalid type "__main__.at" b: bt # E: Invalid type "__main__.bt" @@ -377,8 +375,7 @@ b: bt # E: Invalid type "__main__.bt" [case testLiteralDisallowCollectionsTypeAlias2] from typing_extensions import Literal -at = Literal[{1, 2, 3}] # E: Invalid type alias \ - # E: The type "Type[Literal]" is not generic and not indexable +at = Literal[{1, 2, 3}] # E: Invalid type alias bt = {1, 2, 3} a: at # E: Invalid type "__main__.at" b: bt # E: Invalid type "__main__.bt" @@ -1180,3 +1177,66 @@ b = b * a c = c.strip() # E: Incompatible types in assignment (expression has type "str", variable has type "Literal['foo']") [builtins fixtures/ops.pyi] [out] + + +-- +-- Tests that check we report errors when we try using Literal[...] +-- in invalid places. +-- + +[case testLiteralErrorsWithIsInstanceAndIsSubclass] +from typing_extensions import Literal +from typing_extensions import Literal as Renamed +import typing_extensions as indirect + +Alias = Literal[3] + +isinstance(3, Literal[3]) # E: Cannot use isinstance() with a Literal type +isinstance(3, Alias) # E: Cannot use isinstance() with a Literal type \ + # E: The type alias to Literal is invalid in runtime context +isinstance(3, Renamed[3]) # E: Cannot use isinstance() with a Literal type +isinstance(3, indirect.Literal[3]) # E: Cannot use isinstance() with a Literal type + +issubclass(int, Literal[3]) # E: Cannot use issubclass() with a Literal type +issubclass(int, Alias) # E: Cannot use issubclass() with a Literal type \ + # E: The type alias to Literal is invalid in runtime context +issubclass(int, Renamed[3]) # E: Cannot use issubclass() with a Literal type +issubclass(int, indirect.Literal[3]) # E: Cannot use issubclass() with a Literal type +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testLiteralErrorsWhenSubclassed] +from typing_extensions import Literal +from typing_extensions import Literal as Renamed +import typing_extensions as indirect + +Alias = Literal[3] + +class Bad1(Literal[3]): pass # E: Invalid base class +class Bad2(Renamed[3]): pass # E: Invalid base class +class Bad3(indirect.Literal[3]): pass # E: Invalid base class +class Bad4(Alias): pass # E: Invalid base class +[out] + +[case testLiteralErrorsWhenInvoked-skip] +# TODO: We don't seem to correctly handle invoking types like +# 'Final' and 'Protocol' as well. When fixing this, also fix +# those types? +from typing_extensions import Literal +from typing_extensions import Literal as Renamed +import typing_extensions as indirect + +Alias = Literal[3] + +Literal[3]() # E: The type "Type[Literal]" is not generic and not indexable +Renamed[3]() # E: The type "Type[Literal]" is not generic and not indexable +indirect.Literal[3]() # E: The type "Type[Literal]" is not generic and not indexable +Alias() # E: The type alias to Literal is invalid in runtime context + +# TODO: Add appropriate error messages to the following lines +Literal() +Renamed() +indirect.Literal() +[builtins fixtures/isinstancelist.pyi] +[out] + diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index c7f43017003a..bc444c68ee03 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -357,12 +357,13 @@ from typing import NewType Any = NewType('Any', int) Any(5) -[case testNewTypeAndIsInstance] +[case testNewTypeWithIsInstanceAndIsSubclass] 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' +if isinstance(d, T): # E: Cannot use isinstance() with a NewType type + reveal_type(d) # E: Revealed type is '__main__.T' +issubclass(object, T) # E: Cannot use issubclass() with a NewType type [builtins fixtures/isinstancelist.pyi] [case testInvalidNewTypeCrash] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 4fabf234d534..9910b356f7d9 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -713,12 +713,13 @@ def set_coordinate(p: TaggedPoint, key: str, value: int) -> None: -- isinstance -[case testTypedDictAndInstance] +[case testTypedDictWithIsInstanceAndIsSubclass] 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' +if isinstance(d, D): # E: Cannot use isinstance() with a TypedDict type + reveal_type(d) # E: Revealed type is '__main__.D' +issubclass(object, D) # E: Cannot use issubclass() with a TypedDict type [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index 2b75d3305ce2..fafcc6481dcd 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -1,11 +1,15 @@ -from typing import TypeVar +from typing import TypeVar, Any _T = TypeVar('_T') -class Protocol: pass +class _SpecialForm: + def __getitem__(self, typeargs: Any) -> Any: + pass + +Protocol: _SpecialForm = ... def runtime(x: _T) -> _T: pass -class Final: pass +Final: _SpecialForm = ... def final(x: _T) -> _T: pass -class Literal: pass +Literal: _SpecialForm = ...