Skip to content

Commit 3557e22

Browse files
authored
Speed up the default plugin (#19385)
Fix two kinds of inefficiency in the default plugin: * Pre-calculate various set objects, since set construction is pretty slow * Nested imports are pretty slow, so avoid doing them in the fast path I also had to refactor things a little in order to optimize the nested imports. This speeds up self check by about 2.2%. The default plugin is called a lot.
1 parent 8df94d2 commit 3557e22

File tree

4 files changed

+76
-51
lines changed

4 files changed

+76
-51
lines changed

mypy/plugins/constants.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Constant definitions for plugins kept here to help with import cycles."""
2+
3+
from typing import Final
4+
5+
from mypy.semanal_enum import ENUM_BASES
6+
7+
SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable"
8+
SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register"
9+
SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__"
10+
SINGLEDISPATCH_REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable"
11+
SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD: Final = (
12+
f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}.__call__"
13+
)
14+
15+
ENUM_NAME_ACCESS: Final = {f"{prefix}.name" for prefix in ENUM_BASES} | {
16+
f"{prefix}._name_" for prefix in ENUM_BASES
17+
}
18+
ENUM_VALUE_ACCESS: Final = {f"{prefix}.value" for prefix in ENUM_BASES} | {
19+
f"{prefix}._value_" for prefix in ENUM_BASES
20+
}

mypy/plugins/default.py

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
MethodSigContext,
1616
Plugin,
1717
)
18+
from mypy.plugins import constants
1819
from mypy.plugins.common import try_getting_str_literals
1920
from mypy.subtypes import is_subtype
2021
from mypy.typeops import is_literal_type_like, make_simplified_union
@@ -36,70 +37,79 @@
3637
get_proper_types,
3738
)
3839

40+
TD_SETDEFAULT_NAMES: Final = {n + ".setdefault" for n in TPDICT_FB_NAMES}
41+
TD_POP_NAMES: Final = {n + ".pop" for n in TPDICT_FB_NAMES}
42+
43+
TD_UPDATE_METHOD_NAMES: Final = (
44+
{n + ".update" for n in TPDICT_FB_NAMES}
45+
| {n + ".__or__" for n in TPDICT_FB_NAMES}
46+
| {n + ".__ror__" for n in TPDICT_FB_NAMES}
47+
| {n + ".__ior__" for n in TPDICT_FB_NAMES}
48+
)
49+
3950

4051
class DefaultPlugin(Plugin):
4152
"""Type checker plugin that is enabled by default."""
4253

4354
def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
44-
from mypy.plugins import ctypes, enums, singledispatch
45-
4655
if fullname == "_ctypes.Array":
56+
from mypy.plugins import ctypes
57+
4758
return ctypes.array_constructor_callback
4859
elif fullname == "functools.singledispatch":
60+
from mypy.plugins import singledispatch
61+
4962
return singledispatch.create_singledispatch_function_callback
5063
elif fullname == "functools.partial":
5164
import mypy.plugins.functools
5265

5366
return mypy.plugins.functools.partial_new_callback
5467
elif fullname == "enum.member":
68+
from mypy.plugins import enums
69+
5570
return enums.enum_member_callback
5671

5772
return None
5873

5974
def get_function_signature_hook(
6075
self, fullname: str
6176
) -> Callable[[FunctionSigContext], FunctionLike] | None:
62-
from mypy.plugins import attrs, dataclasses
63-
6477
if fullname in ("attr.evolve", "attrs.evolve", "attr.assoc", "attrs.assoc"):
78+
from mypy.plugins import attrs
79+
6580
return attrs.evolve_function_sig_callback
6681
elif fullname in ("attr.fields", "attrs.fields"):
82+
from mypy.plugins import attrs
83+
6784
return attrs.fields_function_sig_callback
6885
elif fullname == "dataclasses.replace":
86+
from mypy.plugins import dataclasses
87+
6988
return dataclasses.replace_function_sig_callback
7089
return None
7190

7291
def get_method_signature_hook(
7392
self, fullname: str
7493
) -> Callable[[MethodSigContext], FunctionLike] | None:
75-
from mypy.plugins import ctypes, singledispatch
76-
7794
if fullname == "typing.Mapping.get":
7895
return typed_dict_get_signature_callback
79-
elif fullname in {n + ".setdefault" for n in TPDICT_FB_NAMES}:
96+
elif fullname in TD_SETDEFAULT_NAMES:
8097
return typed_dict_setdefault_signature_callback
81-
elif fullname in {n + ".pop" for n in TPDICT_FB_NAMES}:
98+
elif fullname in TD_POP_NAMES:
8299
return typed_dict_pop_signature_callback
83100
elif fullname == "_ctypes.Array.__setitem__":
84-
return ctypes.array_setitem_callback
85-
elif fullname == singledispatch.SINGLEDISPATCH_CALLABLE_CALL_METHOD:
86-
return singledispatch.call_singledispatch_function_callback
101+
from mypy.plugins import ctypes
87102

88-
typed_dict_updates = set()
89-
for n in TPDICT_FB_NAMES:
90-
typed_dict_updates.add(n + ".update")
91-
typed_dict_updates.add(n + ".__or__")
92-
typed_dict_updates.add(n + ".__ror__")
93-
typed_dict_updates.add(n + ".__ior__")
103+
return ctypes.array_setitem_callback
104+
elif fullname == constants.SINGLEDISPATCH_CALLABLE_CALL_METHOD:
105+
from mypy.plugins import singledispatch
94106

95-
if fullname in typed_dict_updates:
107+
return singledispatch.call_singledispatch_function_callback
108+
elif fullname in TD_UPDATE_METHOD_NAMES:
96109
return typed_dict_update_signature_callback
97-
98110
return None
99111

100112
def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None:
101-
from mypy.plugins import ctypes, singledispatch
102-
103113
if fullname == "typing.Mapping.get":
104114
return typed_dict_get_callback
105115
elif fullname == "builtins.int.__pow__":
@@ -117,12 +127,20 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No
117127
elif fullname in {n + ".__delitem__" for n in TPDICT_FB_NAMES}:
118128
return typed_dict_delitem_callback
119129
elif fullname == "_ctypes.Array.__getitem__":
130+
from mypy.plugins import ctypes
131+
120132
return ctypes.array_getitem_callback
121133
elif fullname == "_ctypes.Array.__iter__":
134+
from mypy.plugins import ctypes
135+
122136
return ctypes.array_iter_callback
123-
elif fullname == singledispatch.SINGLEDISPATCH_REGISTER_METHOD:
137+
elif fullname == constants.SINGLEDISPATCH_REGISTER_METHOD:
138+
from mypy.plugins import singledispatch
139+
124140
return singledispatch.singledispatch_register_callback
125-
elif fullname == singledispatch.REGISTER_CALLABLE_CALL_METHOD:
141+
elif fullname == constants.SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD:
142+
from mypy.plugins import singledispatch
143+
126144
return singledispatch.call_singledispatch_function_after_register_argument
127145
elif fullname == "functools.partial.__call__":
128146
import mypy.plugins.functools
@@ -131,15 +149,21 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No
131149
return None
132150

133151
def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
134-
from mypy.plugins import ctypes, enums
135-
136152
if fullname == "_ctypes.Array.value":
153+
from mypy.plugins import ctypes
154+
137155
return ctypes.array_value_callback
138156
elif fullname == "_ctypes.Array.raw":
157+
from mypy.plugins import ctypes
158+
139159
return ctypes.array_raw_callback
140-
elif fullname in enums.ENUM_NAME_ACCESS:
160+
elif fullname in constants.ENUM_NAME_ACCESS:
161+
from mypy.plugins import enums
162+
141163
return enums.enum_name_callback
142-
elif fullname in enums.ENUM_VALUE_ACCESS:
164+
elif fullname in constants.ENUM_VALUE_ACCESS:
165+
from mypy.plugins import enums
166+
143167
return enums.enum_value_callback
144168
return None
145169

mypy/plugins/enums.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
from __future__ import annotations
1515

1616
from collections.abc import Iterable, Sequence
17-
from typing import Final, TypeVar, cast
17+
from typing import TypeVar, cast
1818

1919
import mypy.plugin # To avoid circular imports.
2020
from mypy.nodes import TypeInfo
21-
from mypy.semanal_enum import ENUM_BASES
2221
from mypy.subtypes import is_equivalent
2322
from mypy.typeops import fixup_partial_type, make_simplified_union
2423
from mypy.types import (
@@ -31,13 +30,6 @@
3130
is_named_instance,
3231
)
3332

34-
ENUM_NAME_ACCESS: Final = {f"{prefix}.name" for prefix in ENUM_BASES} | {
35-
f"{prefix}._name_" for prefix in ENUM_BASES
36-
}
37-
ENUM_VALUE_ACCESS: Final = {f"{prefix}.value" for prefix in ENUM_BASES} | {
38-
f"{prefix}._value_" for prefix in ENUM_BASES
39-
}
40-
4133

4234
def enum_name_callback(ctx: mypy.plugin.AttributeContext) -> Type:
4335
"""This plugin refines the 'name' attribute in enums to act as if

mypy/plugins/singledispatch.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from __future__ import annotations
22

33
from collections.abc import Sequence
4-
from typing import Final, NamedTuple, TypeVar, Union
4+
from typing import NamedTuple, TypeVar, Union
55
from typing_extensions import TypeAlias as _TypeAlias
66

77
from mypy.messages import format_type
88
from mypy.nodes import ARG_POS, Argument, Block, ClassDef, Context, SymbolTable, TypeInfo, Var
99
from mypy.options import Options
1010
from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext, MethodSigContext
1111
from mypy.plugins.common import add_method_to_class
12+
from mypy.plugins.constants import SINGLEDISPATCH_REGISTER_RETURN_CLASS
1213
from mypy.subtypes import is_subtype
1314
from mypy.types import (
1415
AnyType,
@@ -33,13 +34,6 @@ class RegisterCallableInfo(NamedTuple):
3334
singledispatch_obj: Instance
3435

3536

36-
SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable"
37-
38-
SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register"
39-
40-
SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__"
41-
42-
4337
def get_singledispatch_info(typ: Instance) -> SingledispatchTypeVars | None:
4438
if len(typ.args) == 2:
4539
return SingledispatchTypeVars(*typ.args) # type: ignore[arg-type]
@@ -56,16 +50,11 @@ def get_first_arg(args: list[list[T]]) -> T | None:
5650
return None
5751

5852

59-
REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable"
60-
61-
REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{REGISTER_RETURN_CLASS}.__call__"
62-
63-
6453
def make_fake_register_class_instance(
6554
api: CheckerPluginInterface, type_args: Sequence[Type]
6655
) -> Instance:
67-
defn = ClassDef(REGISTER_RETURN_CLASS, Block([]))
68-
defn.fullname = f"functools.{REGISTER_RETURN_CLASS}"
56+
defn = ClassDef(SINGLEDISPATCH_REGISTER_RETURN_CLASS, Block([]))
57+
defn.fullname = f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}"
6958
info = TypeInfo(SymbolTable(), defn, "functools")
7059
obj_type = api.named_generic_type("builtins.object", []).type
7160
info.bases = [Instance(obj_type, [])]

0 commit comments

Comments
 (0)