Skip to content

Commit e145ba8

Browse files
authored
Speed up typechecking of dict, set and list expressions (python#9477)
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 aed5642 commit e145ba8

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

mypy/checkexpr.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3218,8 +3218,42 @@ def visit_list_expr(self, e: ListExpr) -> Type:
32183218
def visit_set_expr(self, e: SetExpr) -> Type:
32193219
return self.check_lst_expr(e.items, 'builtins.set', '<set>', e)
32203220

3221+
def fast_container_type(
3222+
self, items: List[Expression], container_fullname: str
3223+
) -> Optional[Type]:
3224+
"""
3225+
Fast path to determine the type of a list or set literal,
3226+
based on the list of entries. This mostly impacts large
3227+
module-level constant definitions.
3228+
3229+
Limitations:
3230+
- no active type context
3231+
- no star expressions
3232+
- the joined type of all entries must be an Instance type
3233+
"""
3234+
ctx = self.type_context[-1]
3235+
if ctx:
3236+
return None
3237+
values = [] # type: List[Type]
3238+
for item in items:
3239+
if isinstance(item, StarExpr):
3240+
# fallback to slow path
3241+
return None
3242+
values.append(self.accept(item))
3243+
vt = join.join_type_list(values)
3244+
if not isinstance(vt, Instance):
3245+
return None
3246+
# TODO: update tests instead?
3247+
vt.erased = True
3248+
return self.chk.named_generic_type(container_fullname, [vt])
3249+
32213250
def check_lst_expr(self, items: List[Expression], fullname: str,
32223251
tag: str, context: Context) -> Type:
3252+
# fast path
3253+
t = self.fast_container_type(items, fullname)
3254+
if t:
3255+
return t
3256+
32233257
# Translate into type checking a generic function call.
32243258
# Used for list and set expressions, as well as for tuples
32253259
# containing star expressions that don't refer to a
@@ -3301,6 +3335,48 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
33013335
fallback_item = AnyType(TypeOfAny.special_form)
33023336
return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item]))
33033337

3338+
def fast_dict_type(self, e: DictExpr) -> Optional[Type]:
3339+
"""
3340+
Fast path to determine the type of a dict literal,
3341+
based on the list of entries. This mostly impacts large
3342+
module-level constant definitions.
3343+
3344+
Limitations:
3345+
- no active type context
3346+
- only supported star expressions are other dict instances
3347+
- the joined types of all keys and values must be Instance types
3348+
"""
3349+
ctx = self.type_context[-1]
3350+
if ctx:
3351+
return None
3352+
keys = [] # type: List[Type]
3353+
values = [] # type: List[Type]
3354+
stargs = None # type: Optional[Tuple[Type, Type]]
3355+
for key, value in e.items:
3356+
if key is None:
3357+
st = get_proper_type(self.accept(value))
3358+
if (
3359+
isinstance(st, Instance)
3360+
and st.type.fullname == 'builtins.dict'
3361+
and len(st.args) == 2
3362+
):
3363+
stargs = (st.args[0], st.args[1])
3364+
else:
3365+
return None
3366+
else:
3367+
keys.append(self.accept(key))
3368+
values.append(self.accept(value))
3369+
kt = join.join_type_list(keys)
3370+
vt = join.join_type_list(values)
3371+
if not (isinstance(kt, Instance) and isinstance(vt, Instance)):
3372+
return None
3373+
if stargs and (stargs[0] != kt or stargs[1] != vt):
3374+
return None
3375+
# TODO: update tests instead?
3376+
kt.erased = True
3377+
vt.erased = True
3378+
return self.chk.named_generic_type('builtins.dict', [kt, vt])
3379+
33043380
def visit_dict_expr(self, e: DictExpr) -> Type:
33053381
"""Type check a dict expression.
33063382
@@ -3319,6 +3395,11 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
33193395
)
33203396
return typeddict_context.copy_modified()
33213397

3398+
# fast path attempt
3399+
dt = self.fast_dict_type(e)
3400+
if dt:
3401+
return dt
3402+
33223403
# Collect function arguments, watching out for **expr.
33233404
args = [] # type: List[Expression] # Regular "key: value"
33243405
stargs = [] # type: List[Expression] # For "**expr"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy