Skip to content

Commit 644d5f6

Browse files
hugues-affJukkaL
authored andcommitted
checkexpr: cache type of container literals when possible (#12707)
When a container (list, set, tuple, or dict) literal expression is used as an argument to an overloaded function it will get repeatedly typechecked. This becomes particularly problematic when the expression is somewhat large, as seen in #9427 To avoid repeated work, add a new cache in ExprChecker, mapping the AST node to the resolved type of the expression. Right now the cache is only used in the fast path, although it could conceivably be leveraged for the slow path as well in a follow-up commit. To further reduce duplicate work, when the fast-path doesn't work, we use the cache to make a note of that, to avoid repeatedly attempting to take the fast path. Fixes #9427
1 parent b673366 commit 644d5f6

File tree

2 files changed

+37
-13
lines changed

2 files changed

+37
-13
lines changed

mypy/checker.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ def reset(self) -> None:
293293
self._type_maps[1:] = []
294294
self._type_maps[0].clear()
295295
self.temp_type_map = None
296+
self.expr_checker.reset()
296297

297298
assert self.inferred_attribute_types is None
298299
assert self.partial_types == []

mypy/checkexpr.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ class ExpressionChecker(ExpressionVisitor[Type]):
177177
# Type context for type inference
178178
type_context: List[Optional[Type]]
179179

180+
# cache resolved types in some cases
181+
resolved_type: Dict[Expression, ProperType]
182+
180183
strfrm_checker: StringFormatterChecker
181184
plugin: Plugin
182185

@@ -197,6 +200,11 @@ def __init__(self,
197200
self.type_overrides: Dict[Expression, Type] = {}
198201
self.strfrm_checker = StringFormatterChecker(self, self.chk, self.msg)
199202

203+
self.resolved_type = {}
204+
205+
def reset(self) -> None:
206+
self.resolved_type = {}
207+
200208
def visit_name_expr(self, e: NameExpr) -> Type:
201209
"""Type check a name expression.
202210
@@ -3269,13 +3277,13 @@ def apply_type_arguments_to_callable(
32693277

32703278
def visit_list_expr(self, e: ListExpr) -> Type:
32713279
"""Type check a list expression [...]."""
3272-
return self.check_lst_expr(e.items, 'builtins.list', '<list>', e)
3280+
return self.check_lst_expr(e, 'builtins.list', '<list>')
32733281

32743282
def visit_set_expr(self, e: SetExpr) -> Type:
3275-
return self.check_lst_expr(e.items, 'builtins.set', '<set>', e)
3283+
return self.check_lst_expr(e, 'builtins.set', '<set>')
32763284

32773285
def fast_container_type(
3278-
self, items: List[Expression], container_fullname: str
3286+
self, e: Union[ListExpr, SetExpr, TupleExpr], container_fullname: str
32793287
) -> Optional[Type]:
32803288
"""
32813289
Fast path to determine the type of a list or set literal,
@@ -3290,21 +3298,28 @@ def fast_container_type(
32903298
ctx = self.type_context[-1]
32913299
if ctx:
32923300
return None
3301+
rt = self.resolved_type.get(e, None)
3302+
if rt is not None:
3303+
return rt if isinstance(rt, Instance) else None
32933304
values: List[Type] = []
3294-
for item in items:
3305+
for item in e.items:
32953306
if isinstance(item, StarExpr):
32963307
# fallback to slow path
3308+
self.resolved_type[e] = NoneType()
32973309
return None
32983310
values.append(self.accept(item))
32993311
vt = join.join_type_list(values)
33003312
if not allow_fast_container_literal(vt):
3313+
self.resolved_type[e] = NoneType()
33013314
return None
3302-
return self.chk.named_generic_type(container_fullname, [vt])
3315+
ct = self.chk.named_generic_type(container_fullname, [vt])
3316+
self.resolved_type[e] = ct
3317+
return ct
33033318

3304-
def check_lst_expr(self, items: List[Expression], fullname: str,
3305-
tag: str, context: Context) -> Type:
3319+
def check_lst_expr(self, e: Union[ListExpr, SetExpr, TupleExpr], fullname: str,
3320+
tag: str) -> Type:
33063321
# fast path
3307-
t = self.fast_container_type(items, fullname)
3322+
t = self.fast_container_type(e, fullname)
33083323
if t:
33093324
return t
33103325

@@ -3323,10 +3338,10 @@ def check_lst_expr(self, items: List[Expression], fullname: str,
33233338
variables=[tv])
33243339
out = self.check_call(constructor,
33253340
[(i.expr if isinstance(i, StarExpr) else i)
3326-
for i in items],
3341+
for i in e.items],
33273342
[(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS)
3328-
for i in items],
3329-
context)[0]
3343+
for i in e.items],
3344+
e)[0]
33303345
return remove_instance_last_known_values(out)
33313346

33323347
def visit_tuple_expr(self, e: TupleExpr) -> Type:
@@ -3376,7 +3391,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
33763391
else:
33773392
# A star expression that's not a Tuple.
33783393
# Treat the whole thing as a variable-length tuple.
3379-
return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
3394+
return self.check_lst_expr(e, 'builtins.tuple', '<tuple>')
33803395
else:
33813396
if not type_context_items or j >= len(type_context_items):
33823397
tt = self.accept(item)
@@ -3402,6 +3417,9 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
34023417
ctx = self.type_context[-1]
34033418
if ctx:
34043419
return None
3420+
rt = self.resolved_type.get(e, None)
3421+
if rt is not None:
3422+
return rt if isinstance(rt, Instance) else None
34053423
keys: List[Type] = []
34063424
values: List[Type] = []
34073425
stargs: Optional[Tuple[Type, Type]] = None
@@ -3415,17 +3433,22 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
34153433
):
34163434
stargs = (st.args[0], st.args[1])
34173435
else:
3436+
self.resolved_type[e] = NoneType()
34183437
return None
34193438
else:
34203439
keys.append(self.accept(key))
34213440
values.append(self.accept(value))
34223441
kt = join.join_type_list(keys)
34233442
vt = join.join_type_list(values)
34243443
if not (allow_fast_container_literal(kt) and allow_fast_container_literal(vt)):
3444+
self.resolved_type[e] = NoneType()
34253445
return None
34263446
if stargs and (stargs[0] != kt or stargs[1] != vt):
3447+
self.resolved_type[e] = NoneType()
34273448
return None
3428-
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3449+
dt = self.chk.named_generic_type('builtins.dict', [kt, vt])
3450+
self.resolved_type[e] = dt
3451+
return dt
34293452

34303453
def visit_dict_expr(self, e: DictExpr) -> Type:
34313454
"""Type check a dict expression.

0 commit comments

Comments
 (0)