Skip to content

Commit ac11374

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 9f8b814 commit ac11374

File tree

2 files changed

+38
-17
lines changed

2 files changed

+38
-17
lines changed

mypy/checkexpr.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -3259,13 +3259,13 @@ def apply_type_arguments_to_callable(
32593259

32603260
def visit_list_expr(self, e: ListExpr) -> Type:
32613261
"""Type check a list expression [...]."""
3262-
return self.check_lst_expr(e.items, 'builtins.list', '<list>', e)
3262+
return self.check_lst_expr(e, 'builtins.list', '<list>')
32633263

32643264
def visit_set_expr(self, e: SetExpr) -> Type:
3265-
return self.check_lst_expr(e.items, 'builtins.set', '<set>', e)
3265+
return self.check_lst_expr(e, 'builtins.set', '<set>')
32663266

32673267
def fast_container_type(
3268-
self, items: List[Expression], container_fullname: str
3268+
self, e: Union[ListExpr, SetExpr, TupleExpr], container_fullname: str
32693269
) -> Optional[Type]:
32703270
"""
32713271
Fast path to determine the type of a list or set literal,
@@ -3280,21 +3280,27 @@ def fast_container_type(
32803280
ctx = self.type_context[-1]
32813281
if ctx:
32823282
return None
3283+
if e._resolved_type is not None:
3284+
return e._resolved_type if isinstance(e._resolved_type, Instance) else None
32833285
values: List[Type] = []
3284-
for item in items:
3286+
for item in e.items:
32853287
if isinstance(item, StarExpr):
32863288
# fallback to slow path
3289+
e._resolved_type = NoneType()
32873290
return None
32883291
values.append(self.accept(item))
32893292
vt = join.join_type_list(values)
32903293
if not allow_fast_container_literal(vt):
3294+
e._resolved_type = NoneType()
32913295
return None
3292-
return self.chk.named_generic_type(container_fullname, [vt])
3296+
ct = self.chk.named_generic_type(container_fullname, [vt])
3297+
e._resolved_type = ct
3298+
return ct
32933299

3294-
def check_lst_expr(self, items: List[Expression], fullname: str,
3295-
tag: str, context: Context) -> Type:
3300+
def check_lst_expr(self, e: Union[ListExpr, SetExpr, TupleExpr], fullname: str,
3301+
tag: str) -> Type:
32963302
# fast path
3297-
t = self.fast_container_type(items, fullname)
3303+
t = self.fast_container_type(e, fullname)
32983304
if t:
32993305
return t
33003306

@@ -3313,10 +3319,10 @@ def check_lst_expr(self, items: List[Expression], fullname: str,
33133319
variables=[tv])
33143320
out = self.check_call(constructor,
33153321
[(i.expr if isinstance(i, StarExpr) else i)
3316-
for i in items],
3322+
for i in e.items],
33173323
[(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS)
3318-
for i in items],
3319-
context)[0]
3324+
for i in e.items],
3325+
e)[0]
33203326
return remove_instance_last_known_values(out)
33213327

33223328
def visit_tuple_expr(self, e: TupleExpr) -> Type:
@@ -3366,7 +3372,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
33663372
else:
33673373
# A star expression that's not a Tuple.
33683374
# Treat the whole thing as a variable-length tuple.
3369-
return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
3375+
return self.check_lst_expr(e, 'builtins.tuple', '<tuple>')
33703376
else:
33713377
if not type_context_items or j >= len(type_context_items):
33723378
tt = self.accept(item)
@@ -3392,6 +3398,8 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
33923398
ctx = self.type_context[-1]
33933399
if ctx:
33943400
return None
3401+
if e._resolved_type is not None:
3402+
return e._resolved_type if isinstance(e._resolved_type, Instance) else None
33953403
keys: List[Type] = []
33963404
values: List[Type] = []
33973405
stargs: Optional[Tuple[Type, Type]] = None
@@ -3405,17 +3413,22 @@ def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
34053413
):
34063414
stargs = (st.args[0], st.args[1])
34073415
else:
3416+
e._resolved_type = NoneType()
34083417
return None
34093418
else:
34103419
keys.append(self.accept(key))
34113420
values.append(self.accept(value))
34123421
kt = join.join_type_list(keys)
34133422
vt = join.join_type_list(values)
34143423
if not (allow_fast_container_literal(kt) and allow_fast_container_literal(vt)):
3424+
e._resolved_type = NoneType()
34153425
return None
34163426
if stargs and (stargs[0] != kt or stargs[1] != vt):
3427+
e._resolved_type = NoneType()
34173428
return None
3418-
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3429+
dt = self.chk.named_generic_type('builtins.dict', [kt, vt])
3430+
e._resolved_type = dt
3431+
return dt
34193432

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

mypy/nodes.py

+12-4
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)