Skip to content

Commit b01b068

Browse files
committed
Speed up typechecking of dict, set and list expressions
Typechecking of dict, set, and list literals currentlly goes through typechecking of the generic dict/set/list constructor internally. This is usually fine but becomes horrendously slow when the number of items is large: - for generic methods, `infer_arg_types_in_context` is called twice - `infer_arg_types_in_context` is O(n**2) where `n` is the number of arguments, which, in the case of a literal, is the number of items. Add an `O(n)` fast path for deriving the type of simple container literal expressions. This fast path only handle a subset of cases but it provides a tremendous speedup for the relatively common case of large literal constants. The real-world example that motivated this change is a 1889 lines long dict constant representing the parsed value of a mock JSON response from a 3rd party service, where typechecking previously took upwards of 50s and is now down to under 1s with this fast path.
1 parent 835b427 commit b01b068

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed

mypy/checkexpr.py

+61
Original file line numberDiff line numberDiff line change
@@ -3175,8 +3175,32 @@ def visit_list_expr(self, e: ListExpr) -> Type:
31753175
def visit_set_expr(self, e: SetExpr) -> Type:
31763176
return self.check_lst_expr(e.items, 'builtins.set', '<set>', e)
31773177

3178+
def fast_container_type(
3179+
self, items: List[Expression], container_fullname: str
3180+
) -> Optional[Type]:
3181+
ctx = self.type_context[-1]
3182+
if ctx:
3183+
return None
3184+
values = [] # type: List[Type]
3185+
for item in items:
3186+
if isinstance(item, StarExpr):
3187+
# fallback to slow path
3188+
return None
3189+
values.append(self.accept(item))
3190+
vt = join.join_type_list(values)
3191+
if not isinstance(vt, Instance):
3192+
return None
3193+
# TODO: update tests instead?
3194+
vt.erased = True
3195+
return self.chk.named_generic_type(container_fullname, [vt])
3196+
31783197
def check_lst_expr(self, items: List[Expression], fullname: str,
31793198
tag: str, context: Context) -> Type:
3199+
# fast path
3200+
t = self.fast_container_type(items, fullname)
3201+
if t:
3202+
return t
3203+
31803204
# Translate into type checking a generic function call.
31813205
# Used for list and set expressions, as well as for tuples
31823206
# containing star expressions that don't refer to a
@@ -3258,6 +3282,38 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
32583282
fallback_item = AnyType(TypeOfAny.special_form)
32593283
return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item]))
32603284

3285+
def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
3286+
ctx = self.type_context[-1]
3287+
if ctx:
3288+
return None
3289+
keys = [] # type: List[Type]
3290+
values = [] # type: List[Type]
3291+
stargs = None # type: Optional[Tuple[Type, Type]]
3292+
for key, value in e.items:
3293+
if key is None:
3294+
st = get_proper_type(self.accept(value))
3295+
if (
3296+
isinstance(st, Instance)
3297+
and st.type.fullname == 'builtins.dict'
3298+
and len(st.args) == 2
3299+
):
3300+
stargs = (st.args[0], st.args[1])
3301+
else:
3302+
return None
3303+
else:
3304+
keys.append(self.accept(key))
3305+
values.append(self.accept(value))
3306+
kt = join.join_type_list(keys)
3307+
vt = join.join_type_list(values)
3308+
if not (isinstance(kt, Instance) and isinstance(vt, Instance)):
3309+
return None
3310+
if stargs and (stargs[0] != kt or stargs[1] != vt):
3311+
return None
3312+
# TODO: update tests instead?
3313+
kt.erased = True
3314+
vt.erased = True
3315+
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3316+
32613317
def visit_dict_expr(self, e: DictExpr) -> Type:
32623318
"""Type check a dict expression.
32633319
@@ -3276,6 +3332,11 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
32763332
)
32773333
return typeddict_context.copy_modified()
32783334

3335+
# fast path attempt
3336+
dt = self.fast_dict_type(e)
3337+
if dt:
3338+
return dt
3339+
32793340
# Collect function arguments, watching out for **expr.
32803341
args = [] # type: List[Expression] # Regular "key: value"
32813342
stargs = [] # type: List[Expression] # For "**expr"

0 commit comments

Comments
 (0)