diff --git a/mypy/build.py b/mypy/build.py index 4f22e0703d97..9997d92a1b07 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -91,7 +91,7 @@ from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats from mypy.stubinfo import is_module_from_legacy_bundled_package, stub_distribution_name -from mypy.types import Type +from mypy.types import Type, instance_cache from mypy.typestate import reset_global_state, type_state from mypy.util import json_dumps, json_loads from mypy.version import __version__ @@ -180,6 +180,9 @@ def build( # fields for callers that want the traditional API. messages = [] + # This is mostly for the benefit of tests that use builtins fixtures. + instance_cache.reset() + def default_flush_errors( filename: str | None, new_messages: list[str], is_serious: bool ) -> None: diff --git a/mypy/checker.py b/mypy/checker.py index ae6ae591ed8c..8d58fdda0219 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -227,6 +227,7 @@ flatten_nested_unions, get_proper_type, get_proper_types, + instance_cache, is_literal_type, is_named_instance, ) @@ -465,12 +466,6 @@ def __init__( self, self.msg, self.plugin, per_line_checking_time_ns ) - self._str_type: Instance | None = None - self._function_type: Instance | None = None - self._int_type: Instance | None = None - self._bool_type: Instance | None = None - self._object_type: Instance | None = None - self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options) self._unique_id = 0 @@ -7415,25 +7410,25 @@ def named_type(self, name: str) -> Instance: For example, named_type('builtins.object') produces the 'object' type. """ if name == "builtins.str": - if self._str_type is None: - self._str_type = self._named_type(name) - return self._str_type + if instance_cache.str_type is None: + instance_cache.str_type = self._named_type(name) + return instance_cache.str_type if name == "builtins.function": - if self._function_type is None: - self._function_type = self._named_type(name) - return self._function_type + if instance_cache.function_type is None: + instance_cache.function_type = self._named_type(name) + return instance_cache.function_type if name == "builtins.int": - if self._int_type is None: - self._int_type = self._named_type(name) - return self._int_type + if instance_cache.int_type is None: + instance_cache.int_type = self._named_type(name) + return instance_cache.int_type if name == "builtins.bool": - if self._bool_type is None: - self._bool_type = self._named_type(name) - return self._bool_type + if instance_cache.bool_type is None: + instance_cache.bool_type = self._named_type(name) + return instance_cache.bool_type if name == "builtins.object": - if self._object_type is None: - self._object_type = self._named_type(name) - return self._object_type + if instance_cache.object_type is None: + instance_cache.object_type = self._named_type(name) + return instance_cache.object_type return self._named_type(name) def _named_type(self, name: str) -> Instance: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fd83b6359ddc..0a3e8d5c59e7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -496,7 +496,8 @@ def module_type(self, node: MypyFile) -> Instance: # In test cases might 'types' may not be available. # Fall back to a dummy 'object' type instead to # avoid a crash. - result = self.named_type("builtins.object") + # Make a copy so that we don't set extra_attrs (below) on a shared instance. + result = self.named_type("builtins.object").copy_modified() module_attrs: dict[str, Type] = {} immutable = set() for name, n in node.names.items(): diff --git a/mypy/types.py b/mypy/types.py index e0265e601e0c..43b115742cc8 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1708,6 +1708,23 @@ def deserialize(cls, data: JsonDict | str) -> Instance: def write(self, data: Buffer) -> None: write_tag(data, INSTANCE) + if not self.args and not self.last_known_value and not self.extra_attrs: + type_ref = self.type.fullname + if type_ref == "builtins.str": + write_tag(data, INSTANCE_STR) + elif type_ref == "builtins.function": + write_tag(data, INSTANCE_FUNCTION) + elif type_ref == "builtins.int": + write_tag(data, INSTANCE_INT) + elif type_ref == "builtins.bool": + write_tag(data, INSTANCE_BOOL) + elif type_ref == "builtins.object": + write_tag(data, INSTANCE_OBJECT) + else: + write_tag(data, INSTANCE_SIMPLE) + write_str(data, type_ref) + return + write_tag(data, INSTANCE_GENERIC) write_str(data, self.type.fullname) write_type_list(data, self.args) write_type_opt(data, self.last_known_value) @@ -1719,6 +1736,39 @@ def write(self, data: Buffer) -> None: @classmethod def read(cls, data: Buffer) -> Instance: + tag = read_tag(data) + # This is quite verbose, but this is very hot code, so we are not + # using dictionary lookups here. + if tag == INSTANCE_STR: + if instance_cache.str_type is None: + instance_cache.str_type = Instance(NOT_READY, []) + instance_cache.str_type.type_ref = "builtins.str" + return instance_cache.str_type + if tag == INSTANCE_FUNCTION: + if instance_cache.function_type is None: + instance_cache.function_type = Instance(NOT_READY, []) + instance_cache.function_type.type_ref = "builtins.function" + return instance_cache.function_type + if tag == INSTANCE_INT: + if instance_cache.int_type is None: + instance_cache.int_type = Instance(NOT_READY, []) + instance_cache.int_type.type_ref = "builtins.int" + return instance_cache.int_type + if tag == INSTANCE_BOOL: + if instance_cache.bool_type is None: + instance_cache.bool_type = Instance(NOT_READY, []) + instance_cache.bool_type.type_ref = "builtins.bool" + return instance_cache.bool_type + if tag == INSTANCE_OBJECT: + if instance_cache.object_type is None: + instance_cache.object_type = Instance(NOT_READY, []) + instance_cache.object_type.type_ref = "builtins.object" + return instance_cache.object_type + if tag == INSTANCE_SIMPLE: + inst = Instance(NOT_READY, []) + inst.type_ref = read_str(data) + return inst + assert tag == INSTANCE_GENERIC type_ref = read_str(data) inst = Instance(NOT_READY, read_type_list(data)) inst.type_ref = type_ref @@ -1769,6 +1819,25 @@ def is_singleton_type(self) -> bool: ) +class InstanceCache: + def __init__(self) -> None: + self.str_type: Instance | None = None + self.function_type: Instance | None = None + self.int_type: Instance | None = None + self.bool_type: Instance | None = None + self.object_type: Instance | None = None + + def reset(self) -> None: + self.str_type = None + self.function_type = None + self.int_type = None + self.bool_type = None + self.object_type = None + + +instance_cache: Final = InstanceCache() + + class FunctionLike(ProperType): """Abstract base class for function types.""" @@ -4140,6 +4209,14 @@ def type_vars_as_args(type_vars: Sequence[TypeVarLikeType]) -> tuple[Type, ...]: TYPE_TYPE: Final[Tag] = 18 PARAMETERS: Final[Tag] = 19 +INSTANCE_STR: Final[Tag] = 101 +INSTANCE_FUNCTION: Final[Tag] = 102 +INSTANCE_INT: Final[Tag] = 103 +INSTANCE_BOOL: Final[Tag] = 104 +INSTANCE_OBJECT: Final[Tag] = 105 +INSTANCE_SIMPLE: Final[Tag] = 106 +INSTANCE_GENERIC: Final[Tag] = 107 + def read_type(data: Buffer) -> Type: tag = read_tag(data)