1
1
from mypy .plugin import Plugin , FunctionContext
2
- from mypy .types import Type , Instance , CallableType , UnionType , get_proper_type
2
+ from mypy .types import (
3
+ Type , Instance , CallableType , UnionType , get_proper_type , ProperType ,
4
+ get_proper_types , TupleType , NoneTyp , AnyType
5
+ )
6
+ from mypy .nodes import TypeInfo
7
+ from mypy .subtypes import is_proper_subtype
3
8
4
- import os .path
5
9
from typing_extensions import Type as typing_Type
6
10
from typing import Optional , Callable
7
11
8
- FILE_WHITELIST = [
9
- 'checker.py' ,
10
- 'checkexpr.py' ,
11
- 'checkmember.py' ,
12
- 'messages.py' ,
13
- 'semanal.py' ,
14
- 'typeanal.py'
15
- ]
16
-
17
12
18
13
class ProperTypePlugin (Plugin ):
19
14
"""
@@ -31,30 +26,53 @@ def get_function_hook(self, fullname: str
31
26
) -> Optional [Callable [[FunctionContext ], Type ]]:
32
27
if fullname == 'builtins.isinstance' :
33
28
return isinstance_proper_hook
29
+ if fullname == 'mypy.types.get_proper_type' :
30
+ return proper_type_hook
31
+ if fullname == 'mypy.types.get_proper_types' :
32
+ return proper_types_hook
34
33
return None
35
34
36
35
37
36
def isinstance_proper_hook (ctx : FunctionContext ) -> Type :
38
- if os .path .split (ctx .api .path )[- 1 ] in FILE_WHITELIST :
39
- return ctx .default_return_type
37
+ right = get_proper_type (ctx .arg_types [1 ][0 ])
40
38
for arg in ctx .arg_types [0 ]:
41
- if is_improper_type (arg ):
42
- right = get_proper_type (ctx .arg_types [1 ][0 ])
43
- if isinstance (right , CallableType ) and right .is_type_obj ():
44
- if right .type_object ().fullname () in ('mypy.types.Type' ,
45
- 'mypy.types.ProperType' ,
46
- 'mypy.types.TypeAliasType' ):
47
- # Special case: things like assert isinstance(typ, ProperType) are always OK.
48
- return ctx .default_return_type
49
- if right .type_object ().fullname () in ('mypy.types.UnboundType' ,
50
- 'mypy.types.TypeVarType' ):
51
- # Special case: these are not valid targets for a type alias and thus safe.
52
- return ctx .default_return_type
39
+ if (is_improper_type (arg ) or
40
+ isinstance (get_proper_type (arg ), AnyType ) and is_dangerous_target (right )):
41
+ if is_special_target (right ):
42
+ return ctx .default_return_type
53
43
ctx .api .fail ('Never apply isinstance() to unexpanded types;'
54
44
' use mypy.types.get_proper_type() first' , ctx .context )
55
45
return ctx .default_return_type
56
46
57
47
48
+ def is_special_target (right : ProperType ) -> bool :
49
+ """Whitelist some special cases for use in isinstance() with improper types."""
50
+ if isinstance (right , CallableType ) and right .is_type_obj ():
51
+ if right .type_object ().fullname () == 'builtins.tuple' :
52
+ # Used with Union[Type, Tuple[Type, ...]].
53
+ return True
54
+ if right .type_object ().fullname () in ('mypy.types.Type' ,
55
+ 'mypy.types.ProperType' ,
56
+ 'mypy.types.TypeAliasType' ):
57
+ # Special case: things like assert isinstance(typ, ProperType) are always OK.
58
+ return True
59
+ if right .type_object ().fullname () in ('mypy.types.UnboundType' ,
60
+ 'mypy.types.TypeVarType' ,
61
+ 'mypy.types.RawExpressionType' ,
62
+ 'mypy.types.EllipsisType' ,
63
+ 'mypy.types.StarType' ,
64
+ 'mypy.types.TypeList' ,
65
+ 'mypy.types.CallableArgument' ,
66
+ 'mypy.types.PartialType' ,
67
+ 'mypy.types.ErasedType' ):
68
+ # Special case: these are not valid targets for a type alias and thus safe.
69
+ # TODO: introduce a SyntheticType base to simplify this?
70
+ return True
71
+ elif isinstance (right , TupleType ):
72
+ return all (is_special_target (t ) for t in get_proper_types (right .items ))
73
+ return False
74
+
75
+
58
76
def is_improper_type (typ : Type ) -> bool :
59
77
"""Is this a type that is not a subtype of ProperType?"""
60
78
typ = get_proper_type (typ )
@@ -66,5 +84,48 @@ def is_improper_type(typ: Type) -> bool:
66
84
return False
67
85
68
86
87
+ def is_dangerous_target (typ : ProperType ) -> bool :
88
+ """Is this a dangerous target (right argument) for an isinstance() check?"""
89
+ if isinstance (typ , TupleType ):
90
+ return any (is_dangerous_target (get_proper_type (t )) for t in typ .items )
91
+ if isinstance (typ , CallableType ) and typ .is_type_obj ():
92
+ return typ .type_object ().has_base ('mypy.types.Type' )
93
+ return False
94
+
95
+
96
+ def proper_type_hook (ctx : FunctionContext ) -> Type :
97
+ """Check if this get_proper_type() call is not redundant."""
98
+ arg_types = ctx .arg_types [0 ]
99
+ if arg_types :
100
+ arg_type = get_proper_type (arg_types [0 ])
101
+ proper_type = get_proper_type_instance (ctx )
102
+ if is_proper_subtype (arg_type , UnionType .make_union ([NoneTyp (), proper_type ])):
103
+ # Minimize amount of spurious errors from overload machinery.
104
+ # TODO: call the hook on the overload as a whole?
105
+ if isinstance (arg_type , (UnionType , Instance )):
106
+ ctx .api .fail ('Redundant call to get_proper_type()' , ctx .context )
107
+ return ctx .default_return_type
108
+
109
+
110
+ def proper_types_hook (ctx : FunctionContext ) -> Type :
111
+ """Check if this get_proper_types() call is not redundant."""
112
+ arg_types = ctx .arg_types [0 ]
113
+ if arg_types :
114
+ arg_type = arg_types [0 ]
115
+ proper_type = get_proper_type_instance (ctx )
116
+ item_type = UnionType .make_union ([NoneTyp (), proper_type ])
117
+ ok_type = ctx .api .named_generic_type ('typing.Iterable' , [item_type ])
118
+ if is_proper_subtype (arg_type , ok_type ):
119
+ ctx .api .fail ('Redundant call to get_proper_types()' , ctx .context )
120
+ return ctx .default_return_type
121
+
122
+
123
+ def get_proper_type_instance (ctx : FunctionContext ) -> Instance :
124
+ types = ctx .api .modules ['mypy.types' ] # type: ignore
125
+ proper_type_info = types .names ['ProperType' ]
126
+ assert isinstance (proper_type_info .node , TypeInfo )
127
+ return Instance (proper_type_info .node , [])
128
+
129
+
69
130
def plugin (version : str ) -> typing_Type [ProperTypePlugin ]:
70
131
return ProperTypePlugin
0 commit comments