Skip to content

Commit edf928a

Browse files
committed
checkexpr: broader fast path for literal container expressions
The original implementation was very conservative and bailed whenever a type context was present, drastically limiting its reach. There are a number of type contexts where the fast path can safely be taken. Most importantly: - Any - matching container types
1 parent 695ea30 commit edf928a

File tree

1 file changed

+92
-27
lines changed

1 file changed

+92
-27
lines changed

mypy/checkexpr.py

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@
9898
)
9999
from mypy.semanal_enum import ENUM_BASES
100100
from mypy.state import state
101-
from mypy.subtypes import is_equivalent, is_same_type, is_subtype, non_method_protocol_members
101+
from mypy.subtypes import (
102+
is_equivalent,
103+
is_proper_subtype,
104+
is_same_type,
105+
is_subtype,
106+
non_method_protocol_members,
107+
)
102108
from mypy.traverser import has_await_expression
103109
from mypy.typeanal import (
104110
check_for_explicit_any,
@@ -259,7 +265,7 @@ class ExpressionChecker(ExpressionVisitor[Type]):
259265
type_context: list[Type | None]
260266

261267
# cache resolved types in some cases
262-
resolved_type: dict[Expression, ProperType]
268+
resolved_type: dict[tuple[Expression, Type | None], ProperType]
263269

264270
strfrm_checker: StringFormatterChecker
265271
plugin: Plugin
@@ -3994,34 +4000,59 @@ def fast_container_type(
39944000
self, e: ListExpr | SetExpr | TupleExpr, container_fullname: str
39954001
) -> Type | None:
39964002
"""
3997-
Fast path to determine the type of a list or set literal,
3998-
based on the list of entries. This mostly impacts large
3999-
module-level constant definitions.
4003+
Fast path to determine the type of a list or set literal, based on the list of entries.
4004+
This mostly impacts large constant definitions.
40004005
40014006
Limitations:
40024007
- no active type context
40034008
- no star expressions
40044009
- the joined type of all entries must be an Instance or Tuple type
40054010
"""
4006-
ctx = self.type_context[-1]
4007-
if ctx:
4011+
ctx = get_proper_type(self.type_context[-1])
4012+
# TODO: can we safely allow TypeVarType (with appropriate upper_bound or values) ?
4013+
if not ctx or isinstance(ctx, AnyType):
4014+
return self._fast_container_type(e, container_fullname)
4015+
4016+
# TODO: support relevant typing base classes (Sequence, AbstractSet, ...) ?
4017+
if not (isinstance(ctx, Instance) and ctx.type.fullname == container_fullname):
40084018
return None
4009-
rt = self.resolved_type.get(e, None)
4019+
4020+
vt = get_proper_type(ctx.args[0])
4021+
if not (isinstance(vt, Instance) and allow_fast_container_literal(vt)):
4022+
return None
4023+
4024+
with self.msg.filter_errors() as w:
4025+
rt = self._fast_container_type(e, container_fullname)
4026+
if w.has_new_errors():
4027+
# fallback to slow path if we run into any errors here
4028+
return None
4029+
if rt and is_proper_subtype(rt.args[0], vt):
4030+
# let the type context win if we inferred a more precise type (List is invariant...)
4031+
return ctx
4032+
return None
4033+
4034+
def _fast_container_type(
4035+
self,
4036+
e: ListExpr | SetExpr | TupleExpr,
4037+
container_fullname: str,
4038+
ctx: Optional[Instance] = None,
4039+
) -> Instance | None:
4040+
rt = self.resolved_type.get((e, ctx), None)
40104041
if rt is not None:
40114042
return rt if isinstance(rt, Instance) else None
40124043
values: list[Type] = []
40134044
for item in e.items:
40144045
if isinstance(item, StarExpr):
40154046
# fallback to slow path
4016-
self.resolved_type[e] = NoneType()
4047+
self.resolved_type[(e, ctx)] = NoneType()
40174048
return None
4018-
values.append(self.accept(item))
4049+
values.append(self.accept(item, type_context=ctx.args[0] if ctx else None))
40194050
vt = join.join_type_list(values)
40204051
if not allow_fast_container_literal(vt):
4021-
self.resolved_type[e] = NoneType()
4052+
self.resolved_type[(e, ctx)] = NoneType()
40224053
return None
40234054
ct = self.chk.named_generic_type(container_fullname, [vt])
4024-
self.resolved_type[e] = ct
4055+
self.resolved_type[(e, ctx)] = ct
40254056
return ct
40264057

40274058
def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: str) -> Type:
@@ -4116,49 +4147,83 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
41164147

41174148
def fast_dict_type(self, e: DictExpr) -> Type | None:
41184149
"""
4119-
Fast path to determine the type of a dict literal,
4120-
based on the list of entries. This mostly impacts large
4121-
module-level constant definitions.
4150+
Fast path to determine the type of a dict literal, based on the list of entries.
4151+
This mostly impacts large module-level constant definitions.
4152+
4153+
Will only trigger for the following type contexts:
4154+
- None (i.e no type context)
4155+
- AnyType
4156+
- Dict[K, V] where K and V are both Instance or Tuple types
4157+
"""
4158+
ctx = get_proper_type(self.type_context[-1])
4159+
# TODO: can we safely allow TypeVarType (with appropriate upper_bound or values) ?
4160+
if not ctx or isinstance(ctx, AnyType):
4161+
return self._fast_dict_type(e)
4162+
4163+
# TODO: support relevant typing base classes (Mapping, MutableMapping, ...) ?
4164+
if not (isinstance(ctx, Instance) and ctx.type.fullname == "builtins.dict"):
4165+
return None
4166+
4167+
kt = get_proper_type(ctx.args[0])
4168+
vt = get_proper_type(ctx.args[1])
4169+
if not (
4170+
isinstance(kt, Instance)
4171+
and isinstance(vt, Instance)
4172+
and allow_fast_container_literal(kt)
4173+
and allow_fast_container_literal(vt)
4174+
):
4175+
return None
4176+
4177+
with self.msg.filter_errors() as w:
4178+
rt = self._fast_dict_type(e, ctx)
4179+
if w.has_new_errors():
4180+
# fallback to slow path if we run into any errors here
4181+
return None
4182+
if rt and is_proper_subtype(rt.args[0], kt) and is_proper_subtype(rt.args[1], vt):
4183+
# let the type context win if we inferred a more precise type (Dict is invariant...)
4184+
return ctx
4185+
return None
4186+
4187+
def _fast_dict_type(self, e: DictExpr, ctx: Optional[Instance] = None) -> Instance | None:
4188+
"""
4189+
Fast path to determine the type of a dict literal, based on the list of entries.
4190+
This mostly impacts large constant definitions.
41224191
41234192
Limitations:
4124-
- no active type context
41254193
- only supported star expressions are other dict instances
41264194
- the joined types of all keys and values must be Instance or Tuple types
41274195
"""
4128-
ctx = self.type_context[-1]
4129-
if ctx:
4130-
return None
4131-
rt = self.resolved_type.get(e, None)
4196+
rt = self.resolved_type.get((e, ctx), None)
41324197
if rt is not None:
41334198
return rt if isinstance(rt, Instance) else None
41344199
keys: list[Type] = []
41354200
values: list[Type] = []
41364201
stargs: tuple[Type, Type] | None = None
41374202
for key, value in e.items:
41384203
if key is None:
4139-
st = get_proper_type(self.accept(value))
4204+
st = get_proper_type(self.accept(value, type_context=ctx.args[1] if ctx else None))
41404205
if (
41414206
isinstance(st, Instance)
41424207
and st.type.fullname == "builtins.dict"
41434208
and len(st.args) == 2
41444209
):
41454210
stargs = (st.args[0], st.args[1])
41464211
else:
4147-
self.resolved_type[e] = NoneType()
4212+
self.resolved_type[(e, ctx)] = NoneType()
41484213
return None
41494214
else:
4150-
keys.append(self.accept(key))
4151-
values.append(self.accept(value))
4215+
keys.append(self.accept(key, type_context=ctx.args[0] if ctx else None))
4216+
values.append(self.accept(value, type_context=ctx.args[1] if ctx else None))
41524217
kt = join.join_type_list(keys)
41534218
vt = join.join_type_list(values)
41544219
if not (allow_fast_container_literal(kt) and allow_fast_container_literal(vt)):
4155-
self.resolved_type[e] = NoneType()
4220+
self.resolved_type[(e, ctx)] = NoneType()
41564221
return None
41574222
if stargs and (stargs[0] != kt or stargs[1] != vt):
4158-
self.resolved_type[e] = NoneType()
4223+
self.resolved_type[(e, ctx)] = NoneType()
41594224
return None
41604225
dt = self.chk.named_generic_type("builtins.dict", [kt, vt])
4161-
self.resolved_type[e] = dt
4226+
self.resolved_type[(e, ctx)] = dt
41624227
return dt
41634228

41644229
def visit_dict_expr(self, e: DictExpr) -> Type:

0 commit comments

Comments
 (0)