Skip to content

Commit cb943ea

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 cb943ea

File tree

1 file changed

+67
-13
lines changed

1 file changed

+67
-13
lines changed

mypy/checkexpr.py

Lines changed: 67 additions & 13 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,
@@ -3994,18 +4000,36 @@ 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_no_context(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
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+
rt = self.fast_container_type_no_context(e, container_fullname)
4025+
if rt and is_proper_subtype(rt.args[0], vt):
4026+
# let the type context win if we inferred a more precise type (List is invariant...)
4027+
return ctx
4028+
return None
4029+
4030+
def fast_container_type_no_context(
4031+
self, e: ListExpr | SetExpr | TupleExpr, container_fullname: str
4032+
) -> Instance | None:
40094033
rt = self.resolved_type.get(e, None)
40104034
if rt is not None:
40114035
return rt if isinstance(rt, Instance) else None
@@ -4116,18 +4140,48 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
41164140

41174141
def fast_dict_type(self, e: DictExpr) -> Type | None:
41184142
"""
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.
4143+
Fast path to determine the type of a dict literal, based on the list of entries.
4144+
This mostly impacts large module-level constant definitions.
4145+
4146+
Will only trigger for the following type contexts:
4147+
- None (i.e no type context)
4148+
- AnyType
4149+
- Dict[K, V] where K and V are both Instance or Tuple types
4150+
"""
4151+
ctx = get_proper_type(self.type_context[-1])
4152+
# TODO: can we safely allow TypeVarType (with appropriate upper_bound or values) ?
4153+
if not ctx or isinstance(ctx, AnyType):
4154+
return self.fast_dict_type_no_context(e)
4155+
4156+
# TODO: support relevant typing base classes (Mapping, MutableMapping, ...) ?
4157+
if not (isinstance(ctx, Instance) and ctx.type.fullname == "builtins.dict"):
4158+
return None
4159+
4160+
kt = get_proper_type(ctx.args[0])
4161+
vt = get_proper_type(ctx.args[1])
4162+
if not (
4163+
isinstance(kt, Instance)
4164+
and isinstance(vt, Instance)
4165+
and allow_fast_container_literal(kt)
4166+
and allow_fast_container_literal(vt)
4167+
):
4168+
return None
4169+
4170+
rt = self.fast_dict_type_no_context(e)
4171+
if rt and is_proper_subtype(rt.args[0], kt) and is_proper_subtype(rt.args[1], vt):
4172+
# let the type context win if we inferred a more precise type (Dict is invariant...)
4173+
return ctx
4174+
return None
4175+
4176+
def fast_dict_type_no_context(self, e: DictExpr) -> Instance | None:
4177+
"""
4178+
Fast path to determine the type of a dict literal, based on the list of entries.
4179+
This mostly impacts large constant definitions.
41224180
41234181
Limitations:
4124-
- no active type context
41254182
- only supported star expressions are other dict instances
41264183
- the joined types of all keys and values must be Instance or Tuple types
41274184
"""
4128-
ctx = self.type_context[-1]
4129-
if ctx:
4130-
return None
41314185
rt = self.resolved_type.get(e, None)
41324186
if rt is not None:
41334187
return rt if isinstance(rt, Instance) else None

0 commit comments

Comments
 (0)