Skip to content

Commit e328fdb

Browse files
committed
Cache type_object_type()
1 parent afcf6b2 commit e328fdb

File tree

5 files changed

+33
-5
lines changed

5 files changed

+33
-5
lines changed

mypy/checker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def __init__(
397397
self.is_stub = tree.is_stub
398398
self.is_typeshed_stub = tree.is_typeshed_file(options)
399399
self.inferred_attribute_types = None
400+
self.allow_constructor_cache = True
400401

401402
# If True, process function definitions. If False, don't. This is used
402403
# for processing module top levels in fine-grained incremental mode.
@@ -500,12 +501,16 @@ def check_first_pass(self) -> None:
500501
)
501502

502503
def check_second_pass(
503-
self, todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None
504+
self,
505+
todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None,
506+
*,
507+
allow_constructor_cache: bool = True,
504508
) -> bool:
505509
"""Run second or following pass of type checking.
506510
507511
This goes through deferred nodes, returning True if there were any.
508512
"""
513+
self.allow_constructor_cache = allow_constructor_cache
509514
self.recurse_into_functions = True
510515
with state.strict_optional_set(self.options.strict_optional), checker_state.set(self):
511516
if not todo and not self.deferred_nodes:

mypy/checker_shared.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class TypeCheckerSharedApi(CheckerPluginInterface):
137137
module_refs: set[str]
138138
scope: CheckerScope
139139
checking_missing_await: bool
140+
allow_constructor_cache: bool
140141

141142
@property
142143
@abstractmethod

mypy/nodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,6 +3022,7 @@ class is generic then it will be a type constructor of higher kind.
30223022
"dataclass_transform_spec",
30233023
"is_type_check_only",
30243024
"deprecated",
3025+
"type_object_type",
30253026
)
30263027

30273028
_fullname: str # Fully qualified name
@@ -3178,6 +3179,10 @@ class is generic then it will be a type constructor of higher kind.
31783179
# The type's deprecation message (in case it is deprecated)
31793180
deprecated: str | None
31803181

3182+
# Cached value of class constructor type, i.e. the type of class object when it
3183+
# appears in runtime context.
3184+
type_object_type: mypy.types.FunctionLike | None
3185+
31813186
FLAGS: Final = [
31823187
"is_abstract",
31833188
"is_enum",
@@ -3236,6 +3241,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
32363241
self.dataclass_transform_spec = None
32373242
self.is_type_check_only = False
32383243
self.deprecated = None
3244+
self.type_object_type = None
32393245

32403246
def add_type_vars(self) -> None:
32413247
self.has_type_var_tuple_type = False

mypy/server/update.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,10 +1025,10 @@ def key(node: FineGrainedDeferredNode) -> int:
10251025
# We seem to need additional passes in fine-grained incremental mode.
10261026
checker.pass_num = 0
10271027
checker.last_pass = 3
1028-
more = checker.check_second_pass(nodes)
1028+
more = checker.check_second_pass(nodes, allow_constructor_cache=False)
10291029
while more:
10301030
more = False
1031-
if graph[module_id].type_checker().check_second_pass():
1031+
if graph[module_id].type_checker().check_second_pass(allow_constructor_cache=False):
10321032
more = True
10331033

10341034
if manager.options.export_types:

mypy/typeops.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from collections.abc import Iterable, Sequence
1212
from typing import Any, Callable, TypeVar, cast
1313

14+
from mypy.checker_state import checker_state
1415
from mypy.copytype import copy_type
1516
from mypy.expandtype import expand_type, expand_type_by_instance
1617
from mypy.maptype import map_instance_to_supertype
@@ -145,6 +146,15 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
145146
where ... are argument types for the __init__/__new__ method (without the self
146147
argument). Also, the fallback type will be 'type' instead of 'function'.
147148
"""
149+
allow_cache = (
150+
checker_state.type_checker is not None
151+
and checker_state.type_checker.allow_constructor_cache
152+
)
153+
154+
if info.type_object_type is not None:
155+
if allow_cache:
156+
return info.type_object_type
157+
info.type_object_type = None
148158

149159
# We take the type from whichever of __init__ and __new__ is first
150160
# in the MRO, preferring __init__ if there is a tie.
@@ -189,7 +199,10 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
189199
is_bound=True,
190200
fallback=named_type("builtins.function"),
191201
)
192-
return class_callable(sig, info, fallback, None, is_new=False)
202+
result: FunctionLike = class_callable(sig, info, fallback, None, is_new=False)
203+
if allow_cache:
204+
info.type_object_type = result
205+
return result
193206

194207
# Otherwise prefer __init__ in a tie. It isn't clear that this
195208
# is the right thing, but __new__ caused problems with
@@ -204,7 +217,10 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
204217
assert isinstance(method.type, ProperType)
205218
assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this
206219
t = method.type
207-
return type_object_type_from_function(t, info, method.info, fallback, is_new)
220+
result = type_object_type_from_function(t, info, method.info, fallback, is_new)
221+
if allow_cache:
222+
info.type_object_type = result
223+
return result
208224

209225

210226
def is_valid_constructor(n: SymbolNode | None) -> bool:

0 commit comments

Comments
 (0)