forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproper_plugin.py
175 lines (147 loc) · 6.29 KB
/
proper_plugin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""
This plugin is helpful for mypy development itself.
By default, it is not enabled for mypy users.
It also can be used by plugin developers as a part of their CI checks.
It finds missing ``get_proper_type()`` call, which can lead to multiple errors.
"""
from __future__ import annotations
from typing import Callable
from mypy.checker import TypeChecker
from mypy.nodes import TypeInfo
from mypy.plugin import FunctionContext, Plugin
from mypy.subtypes import is_proper_subtype
from mypy.types import (
AnyType,
FunctionLike,
Instance,
NoneTyp,
ProperType,
TupleType,
Type,
UnionType,
get_proper_type,
get_proper_types,
)
class ProperTypePlugin(Plugin):
"""
A plugin to ensure that every type is expanded before doing any special-casing.
This solves the problem that we have hundreds of call sites like:
if isinstance(typ, UnionType):
... # special-case union
But after introducing a new type TypeAliasType (and removing immediate expansion)
all these became dangerous because typ may be e.g. an alias to union.
"""
def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
if fullname == "builtins.isinstance":
return isinstance_proper_hook
if fullname == "mypy.types.get_proper_type":
return proper_type_hook
if fullname == "mypy.types.get_proper_types":
return proper_types_hook
return None
def isinstance_proper_hook(ctx: FunctionContext) -> Type:
if len(ctx.arg_types) != 2 or not ctx.arg_types[1]:
return ctx.default_return_type
right = get_proper_type(ctx.arg_types[1][0])
for arg in ctx.arg_types[0]:
if (
is_improper_type(arg) or isinstance(get_proper_type(arg), AnyType)
) and is_dangerous_target(right):
if is_special_target(right):
return ctx.default_return_type
ctx.api.fail(
"Never apply isinstance() to unexpanded types;"
" use mypy.types.get_proper_type() first",
ctx.context,
)
ctx.api.note( # type: ignore[attr-defined]
"If you pass on the original type"
" after the check, always use its unexpanded version",
ctx.context,
)
return ctx.default_return_type
def is_special_target(right: ProperType) -> bool:
"""Whitelist some special cases for use in isinstance() with improper types."""
if isinstance(right, FunctionLike) and right.is_type_obj():
if right.type_object().fullname == "builtins.tuple":
# Used with Union[Type, Tuple[Type, ...]].
return True
if right.type_object().fullname in (
"mypy.types.Type",
"mypy.types.ProperType",
"mypy.types.TypeAliasType",
):
# Special case: things like assert isinstance(typ, ProperType) are always OK.
return True
if right.type_object().fullname in (
"mypy.types.UnboundType",
"mypy.types.TypeVarLikeType",
"mypy.types.TypeVarType",
"mypy.types.UnpackType",
"mypy.types.TypeVarTupleType",
"mypy.types.ParamSpecType",
"mypy.types.Parameters",
"mypy.types.RawExpressionType",
"mypy.types.EllipsisType",
"mypy.types.StarType",
"mypy.types.TypeList",
"mypy.types.CallableArgument",
"mypy.types.PartialType",
"mypy.types.ErasedType",
"mypy.types.DeletedType",
"mypy.types.RequiredType",
):
# Special case: these are not valid targets for a type alias and thus safe.
# TODO: introduce a SyntheticType base to simplify this?
return True
elif isinstance(right, TupleType):
return all(is_special_target(t) for t in get_proper_types(right.items))
return False
def is_improper_type(typ: Type) -> bool:
"""Is this a type that is not a subtype of ProperType?"""
typ = get_proper_type(typ)
if isinstance(typ, Instance):
info = typ.type
return info.has_base("mypy.types.Type") and not info.has_base("mypy.types.ProperType")
if isinstance(typ, UnionType):
return any(is_improper_type(t) for t in typ.items)
return False
def is_dangerous_target(typ: ProperType) -> bool:
"""Is this a dangerous target (right argument) for an isinstance() check?"""
if isinstance(typ, TupleType):
return any(is_dangerous_target(get_proper_type(t)) for t in typ.items)
if isinstance(typ, FunctionLike) and typ.is_type_obj():
return typ.type_object().has_base("mypy.types.Type")
return False
def proper_type_hook(ctx: FunctionContext) -> Type:
"""Check if this get_proper_type() call is not redundant."""
arg_types = ctx.arg_types[0]
if arg_types:
arg_type = get_proper_type(arg_types[0])
proper_type = get_proper_type_instance(ctx)
if is_proper_subtype(arg_type, UnionType.make_union([NoneTyp(), proper_type])):
# Minimize amount of spurious errors from overload machinery.
# TODO: call the hook on the overload as a whole?
if isinstance(arg_type, (UnionType, Instance)):
ctx.api.fail("Redundant call to get_proper_type()", ctx.context)
return ctx.default_return_type
def proper_types_hook(ctx: FunctionContext) -> Type:
"""Check if this get_proper_types() call is not redundant."""
arg_types = ctx.arg_types[0]
if arg_types:
arg_type = arg_types[0]
proper_type = get_proper_type_instance(ctx)
item_type = UnionType.make_union([NoneTyp(), proper_type])
ok_type = ctx.api.named_generic_type("typing.Iterable", [item_type])
if is_proper_subtype(arg_type, ok_type):
ctx.api.fail("Redundant call to get_proper_types()", ctx.context)
return ctx.default_return_type
def get_proper_type_instance(ctx: FunctionContext) -> Instance:
checker = ctx.api
assert isinstance(checker, TypeChecker)
types = checker.modules["mypy.types"]
proper_type_info = types.names["ProperType"]
assert isinstance(proper_type_info.node, TypeInfo)
return Instance(proper_type_info.node, [])
def plugin(version: str) -> type[ProperTypePlugin]:
return ProperTypePlugin