From df0449b4e5b8197819203d3024df26a12a0e4748 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 6 Jul 2025 11:25:15 +0100 Subject: [PATCH 1/3] Optimize default plugin --- mypy/plugins/default.py | 48 +++++++++++++++++----------- mypy/plugins/singledispatch.py | 15 ++------- mypy/plugins/singledispatch_const.py | 13 ++++++++ 3 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 mypy/plugins/singledispatch_const.py diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 2002a4f06093..62850e184259 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -15,6 +15,7 @@ MethodSigContext, Plugin, ) +from mypy.plugins import singledispatch_const from mypy.plugins.common import try_getting_str_literals from mypy.subtypes import is_subtype from mypy.typeops import is_literal_type_like, make_simplified_union @@ -36,6 +37,16 @@ get_proper_types, ) +TD_SETDEFAULT_NAMES: Final = {n + ".setdefault" for n in TPDICT_FB_NAMES} +TD_POP_NAMES: Final = {n + ".pop" for n in TPDICT_FB_NAMES} + +TD_UPDATE_METHOD_NAMES: Final = ( + {n + ".update" for n in TPDICT_FB_NAMES} + | {n + ".__or__" for n in TPDICT_FB_NAMES} + | {n + ".__ror__" for n in TPDICT_FB_NAMES} + | {n + ".__ior__" for n in TPDICT_FB_NAMES} +) + class DefaultPlugin(Plugin): """Type checker plugin that is enabled by default.""" @@ -72,34 +83,25 @@ def get_function_signature_hook( def get_method_signature_hook( self, fullname: str ) -> Callable[[MethodSigContext], FunctionLike] | None: - from mypy.plugins import ctypes, singledispatch - if fullname == "typing.Mapping.get": return typed_dict_get_signature_callback - elif fullname in {n + ".setdefault" for n in TPDICT_FB_NAMES}: + elif fullname in TD_SETDEFAULT_NAMES: return typed_dict_setdefault_signature_callback - elif fullname in {n + ".pop" for n in TPDICT_FB_NAMES}: + elif fullname in TD_POP_NAMES: return typed_dict_pop_signature_callback elif fullname == "_ctypes.Array.__setitem__": - return ctypes.array_setitem_callback - elif fullname == singledispatch.SINGLEDISPATCH_CALLABLE_CALL_METHOD: - return singledispatch.call_singledispatch_function_callback + from mypy.plugins import ctypes - typed_dict_updates = set() - for n in TPDICT_FB_NAMES: - typed_dict_updates.add(n + ".update") - typed_dict_updates.add(n + ".__or__") - typed_dict_updates.add(n + ".__ror__") - typed_dict_updates.add(n + ".__ior__") + return ctypes.array_setitem_callback + elif fullname == singledispatch_const.SINGLEDISPATCH_CALLABLE_CALL_METHOD: + from mypy.plugins import singledispatch - if fullname in typed_dict_updates: + return singledispatch.call_singledispatch_function_callback + elif fullname in TD_UPDATE_METHOD_NAMES: return typed_dict_update_signature_callback - return None def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: - from mypy.plugins import ctypes, singledispatch - if fullname == "typing.Mapping.get": return typed_dict_get_callback elif fullname == "builtins.int.__pow__": @@ -117,12 +119,20 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No elif fullname in {n + ".__delitem__" for n in TPDICT_FB_NAMES}: return typed_dict_delitem_callback elif fullname == "_ctypes.Array.__getitem__": + from mypy.plugins import ctypes + return ctypes.array_getitem_callback elif fullname == "_ctypes.Array.__iter__": + from mypy.plugins import ctypes + return ctypes.array_iter_callback - elif fullname == singledispatch.SINGLEDISPATCH_REGISTER_METHOD: + elif fullname == singledispatch_const.SINGLEDISPATCH_REGISTER_METHOD: + from mypy.plugins import singledispatch + return singledispatch.singledispatch_register_callback - elif fullname == singledispatch.REGISTER_CALLABLE_CALL_METHOD: + elif fullname == singledispatch_const.REGISTER_CALLABLE_CALL_METHOD: + from mypy.plugins import singledispatch + return singledispatch.call_singledispatch_function_after_register_argument elif fullname == "functools.partial.__call__": import mypy.plugins.functools diff --git a/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index be4b405ce610..e197334eb9c1 100644 --- a/mypy/plugins/singledispatch.py +++ b/mypy/plugins/singledispatch.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import Final, NamedTuple, TypeVar, Union +from typing import NamedTuple, TypeVar, Union from typing_extensions import TypeAlias as _TypeAlias from mypy.messages import format_type @@ -9,6 +9,7 @@ from mypy.options import Options from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext, MethodSigContext from mypy.plugins.common import add_method_to_class +from mypy.plugins.singledispatch_const import REGISTER_RETURN_CLASS from mypy.subtypes import is_subtype from mypy.types import ( AnyType, @@ -33,13 +34,6 @@ class RegisterCallableInfo(NamedTuple): singledispatch_obj: Instance -SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable" - -SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register" - -SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__" - - def get_singledispatch_info(typ: Instance) -> SingledispatchTypeVars | None: if len(typ.args) == 2: return SingledispatchTypeVars(*typ.args) # type: ignore[arg-type] @@ -56,11 +50,6 @@ def get_first_arg(args: list[list[T]]) -> T | None: return None -REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable" - -REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{REGISTER_RETURN_CLASS}.__call__" - - def make_fake_register_class_instance( api: CheckerPluginInterface, type_args: Sequence[Type] ) -> Instance: diff --git a/mypy/plugins/singledispatch_const.py b/mypy/plugins/singledispatch_const.py new file mode 100644 index 000000000000..b766c3e72bf1 --- /dev/null +++ b/mypy/plugins/singledispatch_const.py @@ -0,0 +1,13 @@ +"""Constant definitions for singledispatch plugin moved here to help with import cycle.""" + +from typing import Final + +SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable" + +SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register" + +SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__" + +REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable" + +REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{REGISTER_RETURN_CLASS}.__call__" From ad68de52a6d39dcd24d0a0601d0121593c1f82c9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 6 Jul 2025 11:32:23 +0100 Subject: [PATCH 2/3] Optimize default plugin more --- mypy/plugins/constants.py | 18 +++++++++++++ mypy/plugins/default.py | 38 +++++++++++++++++++--------- mypy/plugins/enums.py | 8 ------ mypy/plugins/singledispatch.py | 6 ++--- mypy/plugins/singledispatch_const.py | 13 ---------- 5 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 mypy/plugins/constants.py delete mode 100644 mypy/plugins/singledispatch_const.py diff --git a/mypy/plugins/constants.py b/mypy/plugins/constants.py new file mode 100644 index 000000000000..36ee28000306 --- /dev/null +++ b/mypy/plugins/constants.py @@ -0,0 +1,18 @@ +"""Constant definitions for plugins kept here to help with import cycles.""" + +from typing import Final + +from mypy.semanal_enum import ENUM_BASES + +SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable" +SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register" +SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__" +SINGLEDISPATCH_REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable" +SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}.__call__" + +ENUM_NAME_ACCESS: Final = {f"{prefix}.name" for prefix in ENUM_BASES} | { + f"{prefix}._name_" for prefix in ENUM_BASES +} +ENUM_VALUE_ACCESS: Final = {f"{prefix}.value" for prefix in ENUM_BASES} | { + f"{prefix}._value_" for prefix in ENUM_BASES +} diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 62850e184259..09f9795b593e 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -15,7 +15,7 @@ MethodSigContext, Plugin, ) -from mypy.plugins import singledispatch_const +from mypy.plugins import constants from mypy.plugins.common import try_getting_str_literals from mypy.subtypes import is_subtype from mypy.typeops import is_literal_type_like, make_simplified_union @@ -52,17 +52,21 @@ class DefaultPlugin(Plugin): """Type checker plugin that is enabled by default.""" def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: - from mypy.plugins import ctypes, enums, singledispatch - if fullname == "_ctypes.Array": + from mypy.plugins import ctypes + return ctypes.array_constructor_callback elif fullname == "functools.singledispatch": + from mypy.plugins import singledispatch + return singledispatch.create_singledispatch_function_callback elif fullname == "functools.partial": import mypy.plugins.functools return mypy.plugins.functools.partial_new_callback elif fullname == "enum.member": + from mypy.plugins import enums + return enums.enum_member_callback return None @@ -70,13 +74,17 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] def get_function_signature_hook( self, fullname: str ) -> Callable[[FunctionSigContext], FunctionLike] | None: - from mypy.plugins import attrs, dataclasses - if fullname in ("attr.evolve", "attrs.evolve", "attr.assoc", "attrs.assoc"): + from mypy.plugins import attrs + return attrs.evolve_function_sig_callback elif fullname in ("attr.fields", "attrs.fields"): + from mypy.plugins import attrs + return attrs.fields_function_sig_callback elif fullname == "dataclasses.replace": + from mypy.plugins import dataclasses + return dataclasses.replace_function_sig_callback return None @@ -93,7 +101,7 @@ def get_method_signature_hook( from mypy.plugins import ctypes return ctypes.array_setitem_callback - elif fullname == singledispatch_const.SINGLEDISPATCH_CALLABLE_CALL_METHOD: + elif fullname == constants.SINGLEDISPATCH_CALLABLE_CALL_METHOD: from mypy.plugins import singledispatch return singledispatch.call_singledispatch_function_callback @@ -126,11 +134,11 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No from mypy.plugins import ctypes return ctypes.array_iter_callback - elif fullname == singledispatch_const.SINGLEDISPATCH_REGISTER_METHOD: + elif fullname == constants.SINGLEDISPATCH_REGISTER_METHOD: from mypy.plugins import singledispatch return singledispatch.singledispatch_register_callback - elif fullname == singledispatch_const.REGISTER_CALLABLE_CALL_METHOD: + elif fullname == constants.SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD: from mypy.plugins import singledispatch return singledispatch.call_singledispatch_function_after_register_argument @@ -141,15 +149,21 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No return None def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None: - from mypy.plugins import ctypes, enums - if fullname == "_ctypes.Array.value": + from mypy.plugins import ctypes + return ctypes.array_value_callback elif fullname == "_ctypes.Array.raw": + from mypy.plugins import ctypes + return ctypes.array_raw_callback - elif fullname in enums.ENUM_NAME_ACCESS: + elif fullname in constants.ENUM_NAME_ACCESS: + from mypy.plugins import enums + return enums.enum_name_callback - elif fullname in enums.ENUM_VALUE_ACCESS: + elif fullname in constants.ENUM_VALUE_ACCESS: + from mypy.plugins import enums + return enums.enum_value_callback return None diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 8b7c5df6f51f..f26493cd5b15 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -18,7 +18,6 @@ import mypy.plugin # To avoid circular imports. from mypy.nodes import TypeInfo -from mypy.semanal_enum import ENUM_BASES from mypy.subtypes import is_equivalent from mypy.typeops import fixup_partial_type, make_simplified_union from mypy.types import ( @@ -31,13 +30,6 @@ is_named_instance, ) -ENUM_NAME_ACCESS: Final = {f"{prefix}.name" for prefix in ENUM_BASES} | { - f"{prefix}._name_" for prefix in ENUM_BASES -} -ENUM_VALUE_ACCESS: Final = {f"{prefix}.value" for prefix in ENUM_BASES} | { - f"{prefix}._value_" for prefix in ENUM_BASES -} - def enum_name_callback(ctx: mypy.plugin.AttributeContext) -> Type: """This plugin refines the 'name' attribute in enums to act as if diff --git a/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index e197334eb9c1..eb2bbe133bf0 100644 --- a/mypy/plugins/singledispatch.py +++ b/mypy/plugins/singledispatch.py @@ -9,7 +9,7 @@ from mypy.options import Options from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext, MethodSigContext from mypy.plugins.common import add_method_to_class -from mypy.plugins.singledispatch_const import REGISTER_RETURN_CLASS +from mypy.plugins.constants import SINGLEDISPATCH_REGISTER_RETURN_CLASS from mypy.subtypes import is_subtype from mypy.types import ( AnyType, @@ -53,8 +53,8 @@ def get_first_arg(args: list[list[T]]) -> T | None: def make_fake_register_class_instance( api: CheckerPluginInterface, type_args: Sequence[Type] ) -> Instance: - defn = ClassDef(REGISTER_RETURN_CLASS, Block([])) - defn.fullname = f"functools.{REGISTER_RETURN_CLASS}" + defn = ClassDef(SINGLEDISPATCH_REGISTER_RETURN_CLASS, Block([])) + defn.fullname = f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}" info = TypeInfo(SymbolTable(), defn, "functools") obj_type = api.named_generic_type("builtins.object", []).type info.bases = [Instance(obj_type, [])] diff --git a/mypy/plugins/singledispatch_const.py b/mypy/plugins/singledispatch_const.py deleted file mode 100644 index b766c3e72bf1..000000000000 --- a/mypy/plugins/singledispatch_const.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Constant definitions for singledispatch plugin moved here to help with import cycle.""" - -from typing import Final - -SINGLEDISPATCH_TYPE: Final = "functools._SingleDispatchCallable" - -SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register" - -SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__" - -REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable" - -REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{REGISTER_RETURN_CLASS}.__call__" From 2ca2470050afd787b201c3ed22ba96a26ac34d42 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 6 Jul 2025 12:36:46 +0100 Subject: [PATCH 3/3] Lint --- mypy/plugins/constants.py | 4 +++- mypy/plugins/enums.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/plugins/constants.py b/mypy/plugins/constants.py index 36ee28000306..9a09e89202de 100644 --- a/mypy/plugins/constants.py +++ b/mypy/plugins/constants.py @@ -8,7 +8,9 @@ SINGLEDISPATCH_REGISTER_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.register" SINGLEDISPATCH_CALLABLE_CALL_METHOD: Final = f"{SINGLEDISPATCH_TYPE}.__call__" SINGLEDISPATCH_REGISTER_RETURN_CLASS: Final = "_SingleDispatchRegisterCallable" -SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD: Final = f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}.__call__" +SINGLEDISPATCH_REGISTER_CALLABLE_CALL_METHOD: Final = ( + f"functools.{SINGLEDISPATCH_REGISTER_RETURN_CLASS}.__call__" +) ENUM_NAME_ACCESS: Final = {f"{prefix}.name" for prefix in ENUM_BASES} | { f"{prefix}._name_" for prefix in ENUM_BASES diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index f26493cd5b15..dc58fc8110a5 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import Final, TypeVar, cast +from typing import TypeVar, cast import mypy.plugin # To avoid circular imports. from mypy.nodes import TypeInfo