Skip to content

Commit 8b2bf54

Browse files
committed
checkexpr: cache type of container literals when possible
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 field in the relevant AST nodes to cache 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 a56ebec commit 8b2bf54

File tree

2 files changed

+37
-17
lines changed

2 files changed

+37
-17
lines changed

mypy/checkexpr.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3249,13 +3249,13 @@ def apply_type_arguments_to_callable(
32493249

32503250
def visit_list_expr(self, e: ListExpr) -> Type:
32513251
"""Type check a list expression [...]."""
3252-
return self.check_lst_expr(e.items, 'builtins.list', '<list>', e)
3252+
return self.check_lst_expr(e, 'builtins.list', '<list>')
32533253

32543254
def visit_set_expr(self, e: SetExpr) -> Type:
3255-
return self.check_lst_expr(e.items, 'builtins.set', '<set>', e)
3255+
return self.check_lst_expr(e, 'builtins.set', '<set>')
32563256

32573257
def fast_container_type(
3258-
self, items: List[Expression], container_fullname: str
3258+
self, e: Union[ListExpr, SetExpr, TupleExpr], container_fullname: str
32593259
) -> Optional[Type]:
32603260
"""
32613261
Fast path to determine the type of a list or set literal,
@@ -3270,21 +3270,26 @@ def fast_container_type(
32703270
ctx = self.type_context[-1]
32713271
if ctx:
32723272
return None
3273+
if e._resolved_type is not None:
3274+
return e._resolved_type if isinstance(e._resolved_type, Instance) else None
32733275
values: List[Type] = []
3274-
for item in items:
3276+
for item in e.items:
32753277
if isinstance(item, StarExpr):
32763278
# fallback to slow path
3279+
e._resolved_type = NoneType()
32773280
return None
32783281
values.append(self.accept(item))
32793282
vt = join.join_type_list(values)
32803283
if not isinstance(vt, Instance):
32813284
return None
3282-
return self.chk.named_generic_type(container_fullname, [vt])
3285+
ct = self.chk.named_generic_type(container_fullname, [vt])
3286+
e._resolved_type = ct
3287+
return ct
32833288

3284-
def check_lst_expr(self, items: List[Expression], fullname: str,
3285-
tag: str, context: Context) -> Type:
3289+
def check_lst_expr(self, e: Union[ListExpr, SetExpr, TupleExpr], fullname: str,
3290+
tag: str) -> Type:
32863291
# fast path
3287-
t = self.fast_container_type(items, fullname)
3292+
t = self.fast_container_type(e, fullname)
32883293
if t:
32893294
return t
32903295

@@ -3303,10 +3308,10 @@ def check_lst_expr(self, items: List[Expression], fullname: str,
33033308
variables=[tv])
33043309
out = self.check_call(constructor,
33053310
[(i.expr if isinstance(i, StarExpr) else i)
3306-
for i in items],
3311+
for i in e.items],
33073312
[(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS)
3308-
for i in items],
3309-
context)[0]
3313+
for i in e.items],
3314+
e)[0]
33103315
return remove_instance_last_known_values(out)
33113316

33123317
def visit_tuple_expr(self, e: TupleExpr) -> Type:
@@ -3356,7 +3361,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
33563361
else:
33573362
# A star expression that's not a Tuple.
33583363
# Treat the whole thing as a variable-length tuple.
3359-
return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
3364+
return self.check_lst_expr(e, 'builtins.tuple', '<tuple>')
33603365
else:
33613366
if not type_context_items or j >= len(type_context_items):
33623367
tt = self.accept(item)
@@ -3382,6 +3387,8 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
33823387
ctx = self.type_context[-1]
33833388
if ctx:
33843389
return None
3390+
if e._resolved_type is not None:
3391+
return e._resolved_type if isinstance(e._resolved_type, Instance) else None
33853392
keys: List[Type] = []
33863393
values: List[Type] = []
33873394
stargs: Optional[Tuple[Type, Type]] = None
@@ -3395,17 +3402,22 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
33953402
):
33963403
stargs = (st.args[0], st.args[1])
33973404
else:
3405+
e._resolved_type = NoneType()
33983406
return None
33993407
else:
34003408
keys.append(self.accept(key))
34013409
values.append(self.accept(value))
34023410
kt = join.join_type_list(keys)
34033411
vt = join.join_type_list(values)
34043412
if not (isinstance(kt, Instance) and isinstance(vt, Instance)):
3413+
e._resolved_type = NoneType()
34053414
return None
34063415
if stargs and (stargs[0] != kt or stargs[1] != vt):
3416+
e._resolved_type = NoneType()
34073417
return None
3408-
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3418+
dt = self.chk.named_generic_type('builtins.dict', [kt, vt])
3419+
e._resolved_type = dt
3420+
return dt
34093421

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

mypy/nodes.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,13 +2026,15 @@ def is_dynamic(self) -> bool:
20262026
class ListExpr(Expression):
20272027
"""List literal expression [...]."""
20282028

2029-
__slots__ = ('items',)
2029+
__slots__ = ('items', '_resolved_type')
20302030

20312031
items: List[Expression]
2032+
_resolved_type: Optional["mypy.types.ProperType"]
20322033

20332034
def __init__(self, items: List[Expression]) -> None:
20342035
super().__init__()
20352036
self.items = items
2037+
self._resolved_type = None
20362038

20372039
def accept(self, visitor: ExpressionVisitor[T]) -> T:
20382040
return visitor.visit_list_expr(self)
@@ -2041,13 +2043,15 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
20412043
class DictExpr(Expression):
20422044
"""Dictionary literal expression {key: value, ...}."""
20432045

2044-
__slots__ = ('items',)
2046+
__slots__ = ('items', '_resolved_type')
20452047

20462048
items: List[Tuple[Optional[Expression], Expression]]
2049+
_resolved_type: Optional["mypy.types.ProperType"]
20472050

20482051
def __init__(self, items: List[Tuple[Optional[Expression], Expression]]) -> None:
20492052
super().__init__()
20502053
self.items = items
2054+
self._resolved_type = None
20512055

20522056
def accept(self, visitor: ExpressionVisitor[T]) -> T:
20532057
return visitor.visit_dict_expr(self)
@@ -2058,13 +2062,15 @@ class TupleExpr(Expression):
20582062
20592063
Also lvalue sequences (..., ...) and [..., ...]"""
20602064

2061-
__slots__ = ('items',)
2065+
__slots__ = ('items', '_resolved_type')
20622066

20632067
items: List[Expression]
2068+
_resolved_type: Optional["mypy.types.ProperType"]
20642069

20652070
def __init__(self, items: List[Expression]) -> None:
20662071
super().__init__()
20672072
self.items = items
2073+
self._resolved_type = None
20682074

20692075
def accept(self, visitor: ExpressionVisitor[T]) -> T:
20702076
return visitor.visit_tuple_expr(self)
@@ -2073,13 +2079,15 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
20732079
class SetExpr(Expression):
20742080
"""Set literal expression {value, ...}."""
20752081

2076-
__slots__ = ('items',)
2082+
__slots__ = ('items', '_resolved_type')
20772083

20782084
items: List[Expression]
2085+
_resolved_type: Optional["mypy.types.ProperType"]
20792086

20802087
def __init__(self, items: List[Expression]) -> None:
20812088
super().__init__()
20822089
self.items = items
2090+
self._resolved_type = None
20832091

20842092
def accept(self, visitor: ExpressionVisitor[T]) -> T:
20852093
return visitor.visit_set_expr(self)

0 commit comments

Comments
 (0)