Skip to content

Commit cc5f1e1

Browse files
authored
Cache common instances (#19621)
These few types account for a significant proportion of all types created: * `str` is just everywhere * `object` and `function` are used as fallbacks in many places * `int` and `bool` are coming from various literals This gives around 1.5% performance improvement on my desktop. This is a bit ugly, but also looks like an easy win. Note that during semantic analysis I am caching types more conservatively, just in case some plugins modify them in place (`named_type()` is a part of semantic analyzer plugin interface).
1 parent 186515f commit cc5f1e1

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

mypy/checker.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,13 @@ def __init__(
431431
self._expr_checker = mypy.checkexpr.ExpressionChecker(
432432
self, self.msg, self.plugin, per_line_checking_time_ns
433433
)
434+
435+
self._str_type: Instance | None = None
436+
self._function_type: Instance | None = None
437+
self._int_type: Instance | None = None
438+
self._bool_type: Instance | None = None
439+
self._object_type: Instance | None = None
440+
434441
self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options)
435442
self._unique_id = 0
436443

@@ -7369,6 +7376,29 @@ def named_type(self, name: str) -> Instance:
73697376
73707377
For example, named_type('builtins.object') produces the 'object' type.
73717378
"""
7379+
if name == "builtins.str":
7380+
if self._str_type is None:
7381+
self._str_type = self._named_type(name)
7382+
return self._str_type
7383+
if name == "builtins.function":
7384+
if self._function_type is None:
7385+
self._function_type = self._named_type(name)
7386+
return self._function_type
7387+
if name == "builtins.int":
7388+
if self._int_type is None:
7389+
self._int_type = self._named_type(name)
7390+
return self._int_type
7391+
if name == "builtins.bool":
7392+
if self._bool_type is None:
7393+
self._bool_type = self._named_type(name)
7394+
return self._bool_type
7395+
if name == "builtins.object":
7396+
if self._object_type is None:
7397+
self._object_type = self._named_type(name)
7398+
return self._object_type
7399+
return self._named_type(name)
7400+
7401+
def _named_type(self, name: str) -> Instance:
73727402
# Assume that the name refers to a type.
73737403
sym = self.lookup_qualified(name)
73747404
node = sym.node

mypy/checkexpr.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ def __init__(
360360
] = {}
361361
self.in_lambda_expr = False
362362

363+
self._literal_true: Instance | None = None
364+
self._literal_false: Instance | None = None
365+
363366
def reset(self) -> None:
364367
self.resolved_type = {}
365368
self.expr_cache.clear()
@@ -3428,11 +3431,19 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty
34283431
if self.is_literal_context():
34293432
return LiteralType(value=value, fallback=typ)
34303433
else:
3431-
return typ.copy_modified(
3432-
last_known_value=LiteralType(
3433-
value=value, fallback=typ, line=typ.line, column=typ.column
3434-
)
3435-
)
3434+
if value is True:
3435+
if self._literal_true is None:
3436+
self._literal_true = typ.copy_modified(
3437+
last_known_value=LiteralType(value=value, fallback=typ)
3438+
)
3439+
return self._literal_true
3440+
if value is False:
3441+
if self._literal_false is None:
3442+
self._literal_false = typ.copy_modified(
3443+
last_known_value=LiteralType(value=value, fallback=typ)
3444+
)
3445+
return self._literal_false
3446+
return typ.copy_modified(last_known_value=LiteralType(value=value, fallback=typ))
34363447

34373448
def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType:
34383449
"""Concatenate two fixed length tuples."""

mypy/semanal.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ def __init__(
497497
# Used to track edge case when return is still inside except* if it enters a loop
498498
self.return_stmt_inside_except_star_block: bool = False
499499

500+
self._str_type: Instance | None = None
501+
self._function_type: Instance | None = None
502+
self._object_type: Instance | None = None
503+
500504
# mypyc doesn't properly handle implementing an abstractproperty
501505
# with a regular attribute so we make them properties
502506
@property
@@ -1241,7 +1245,7 @@ def analyze_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
12411245
# This is a property.
12421246
first_item.func.is_overload = True
12431247
bare_setter_type = self.analyze_property_with_multi_part_definition(defn)
1244-
typ = function_type(first_item.func, self.named_type("builtins.function"))
1248+
typ = function_type(first_item.func, self.function_type())
12451249
assert isinstance(typ, CallableType)
12461250
typ.definition = first_item
12471251
types = [typ]
@@ -1373,7 +1377,7 @@ def analyze_overload_sigs_and_impl(
13731377
item.accept(self)
13741378
# TODO: support decorated overloaded functions properly
13751379
if isinstance(item, Decorator):
1376-
callable = function_type(item.func, self.named_type("builtins.function"))
1380+
callable = function_type(item.func, self.function_type())
13771381
assert isinstance(callable, CallableType)
13781382
callable.definition = item
13791383
if not any(refers_to_fullname(dec, OVERLOAD_NAMES) for dec in item.decorators):
@@ -1536,9 +1540,7 @@ def analyze_property_with_multi_part_definition(
15361540
if first_node.name == "setter":
15371541
# The first item represents the entire property.
15381542
first_item.var.is_settable_property = True
1539-
setter_func_type = function_type(
1540-
item.func, self.named_type("builtins.function")
1541-
)
1543+
setter_func_type = function_type(item.func, self.function_type())
15421544
assert isinstance(setter_func_type, CallableType)
15431545
bare_setter_type = setter_func_type
15441546
defn.setter_index = i + 1
@@ -6630,10 +6632,19 @@ def lookup_fully_qualified_or_none(self, fullname: str) -> SymbolTableNode | Non
66306632
return result
66316633

66326634
def object_type(self) -> Instance:
6633-
return self.named_type("builtins.object")
6635+
if self._object_type is None:
6636+
self._object_type = self.named_type("builtins.object")
6637+
return self._object_type
66346638

66356639
def str_type(self) -> Instance:
6636-
return self.named_type("builtins.str")
6640+
if self._str_type is None:
6641+
self._str_type = self.named_type("builtins.str")
6642+
return self._str_type
6643+
6644+
def function_type(self) -> Instance:
6645+
if self._function_type is None:
6646+
self._function_type = self.named_type("builtins.function")
6647+
return self._function_type
66376648

66386649
def named_type(self, fullname: str, args: list[Type] | None = None) -> Instance:
66396650
sym = self.lookup_fully_qualified(fullname)

0 commit comments

Comments
 (0)