Skip to content

Commit 3acbc1f

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 3acbc1f

File tree

2 files changed

+38
-17
lines changed

2 files changed

+38
-17
lines changed

mypy/checkexpr.py

Lines changed: 26 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,27 @@ 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):
3284+
e._resolved_type = NoneType()
32813285
return None
3282-
return self.chk.named_generic_type(container_fullname, [vt])
3286+
ct = self.chk.named_generic_type(container_fullname, [vt])
3287+
e._resolved_type = ct
3288+
return ct
32833289

3284-
def check_lst_expr(self, items: List[Expression], fullname: str,
3285-
tag: str, context: Context) -> Type:
3290+
def check_lst_expr(self, e: Union[ListExpr, SetExpr, TupleExpr], fullname: str,
3291+
tag: str) -> Type:
32863292
# fast path
3287-
t = self.fast_container_type(items, fullname)
3293+
t = self.fast_container_type(e, fullname)
32883294
if t:
32893295
return t
32903296

@@ -3303,10 +3309,10 @@ def check_lst_expr(self, items: List[Expression], fullname: str,
33033309
variables=[tv])
33043310
out = self.check_call(constructor,
33053311
[(i.expr if isinstance(i, StarExpr) else i)
3306-
for i in items],
3312+
for i in e.items],
33073313
[(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS)
3308-
for i in items],
3309-
context)[0]
3314+
for i in e.items],
3315+
e)[0]
33103316
return remove_instance_last_known_values(out)
33113317

33123318
def visit_tuple_expr(self, e: TupleExpr) -> Type:
@@ -3356,7 +3362,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
33563362
else:
33573363
# A star expression that's not a Tuple.
33583364
# Treat the whole thing as a variable-length tuple.
3359-
return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
3365+
return self.check_lst_expr(e, 'builtins.tuple', '<tuple>')
33603366
else:
33613367
if not type_context_items or j >= len(type_context_items):
33623368
tt = self.accept(item)
@@ -3382,6 +3388,8 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
33823388
ctx = self.type_context[-1]
33833389
if ctx:
33843390
return None
3391+
if e._resolved_type is not None:
3392+
return e._resolved_type if isinstance(e._resolved_type, Instance) else None
33853393
keys: List[Type] = []
33863394
values: List[Type] = []
33873395
stargs: Optional[Tuple[Type, Type]] = None
@@ -3395,17 +3403,22 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
33953403
):
33963404
stargs = (st.args[0], st.args[1])
33973405
else:
3406+
e._resolved_type = NoneType()
33983407
return None
33993408
else:
34003409
keys.append(self.accept(key))
34013410
values.append(self.accept(value))
34023411
kt = join.join_type_list(keys)
34033412
vt = join.join_type_list(values)
34043413
if not (isinstance(kt, Instance) and isinstance(vt, Instance)):
3414+
e._resolved_type = NoneType()
34053415
return None
34063416
if stargs and (stargs[0] != kt or stargs[1] != vt):
3417+
e._resolved_type = NoneType()
34073418
return None
3408-
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3419+
dt = self.chk.named_generic_type('builtins.dict', [kt, vt])
3420+
e._resolved_type = dt
3421+
return dt
34093422

34103423
def visit_dict_expr(self, e: DictExpr) -> Type:
34113424
"""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)