Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prohibit some illegal uses of Literal #6034

Merged
merged 1 commit into from
Dec 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,19 @@ 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
isinstance(node.node, TypeAlias) and not node.node.no_args)):
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):
Expand Down Expand Up @@ -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
10 changes: 7 additions & 3 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
'typing.Tuple',
'typing.Type',
'typing.Union',
'typing.Literal',
'typing_extensions.Literal',
} # type: Final

ARG_KINDS_BY_CONSTRUCTOR = {
Expand Down
72 changes: 66 additions & 6 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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]

7 changes: 4 additions & 3 deletions test-data/unit/check-newtype.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
7 changes: 4 additions & 3 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]


Expand Down
12 changes: 8 additions & 4 deletions test-data/unit/lib-stub/typing_extensions.pyi
Original file line number Diff line number Diff line change
@@ -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 = ...