Skip to content

Commit 2011f01

Browse files
Merge branch 'master' into fix_match_callable
2 parents 6a1ad26 + ad76e16 commit 2011f01

21 files changed

+540
-123
lines changed

mypy/binder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ def __init__(self, options: Options) -> None:
138138
# flexible inference of variable types (--allow-redefinition-new).
139139
self.bind_all = options.allow_redefinition_new
140140

141+
# This tracks any externally visible changes in binder to invalidate
142+
# expression caches when needed.
143+
self.version = 0
144+
141145
def _get_id(self) -> int:
142146
self.next_id += 1
143147
return self.next_id
@@ -158,6 +162,7 @@ def push_frame(self, conditional_frame: bool = False) -> Frame:
158162
return f
159163

160164
def _put(self, key: Key, type: Type, from_assignment: bool, index: int = -1) -> None:
165+
self.version += 1
161166
self.frames[index].types[key] = CurrentType(type, from_assignment)
162167

163168
def _get(self, key: Key, index: int = -1) -> CurrentType | None:
@@ -185,6 +190,7 @@ def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> N
185190
self._put(key, typ, from_assignment)
186191

187192
def unreachable(self) -> None:
193+
self.version += 1
188194
self.frames[-1].unreachable = True
189195

190196
def suppress_unreachable_warnings(self) -> None:

mypy/build.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def default_flush_errors(
194194
result.errors = messages
195195
return result
196196
except CompileError as e:
197-
# CompileErrors raised from an errors object carry all of the
197+
# CompileErrors raised from an errors object carry all the
198198
# messages that have not been reported out by error streaming.
199199
# Patch it up to contain either none or all none of the messages,
200200
# depending on whether we are flushing errors.
@@ -802,11 +802,11 @@ def correct_rel_imp(imp: ImportFrom | ImportAll) -> str:
802802
res.append((pri, sub_id, imp.line))
803803
else:
804804
all_are_submodules = False
805-
# Add cur_id as a dependency, even if all of the
805+
# Add cur_id as a dependency, even if all the
806806
# imports are submodules. Processing import from will try
807807
# to look through cur_id, so we should depend on it.
808-
# As a workaround for for some bugs in cycle handling (#4498),
809-
# if all of the imports are submodules, do the import at a lower
808+
# As a workaround for some bugs in cycle handling (#4498),
809+
# if all the imports are submodules, do the import at a lower
810810
# priority.
811811
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
812812
res.append((pri, cur_id, imp.line))
@@ -929,7 +929,7 @@ def write_deps_cache(
929929
) -> None:
930930
"""Write cache files for fine-grained dependencies.
931931
932-
Serialize fine-grained dependencies map for fine grained mode.
932+
Serialize fine-grained dependencies map for fine-grained mode.
933933
934934
Dependencies on some module 'm' is stored in the dependency cache
935935
file m.deps.json. This entails some spooky action at a distance:
@@ -943,7 +943,7 @@ def write_deps_cache(
943943
fine-grained dependencies in a global cache file:
944944
* We take a snapshot of current sources to later check consistency
945945
between the fine-grained dependency cache and module cache metadata
946-
* We store the mtime of all of the dependency files to verify they
946+
* We store the mtime of all the dependency files to verify they
947947
haven't changed
948948
"""
949949
metastore = manager.metastore
@@ -1111,7 +1111,7 @@ def read_deps_cache(manager: BuildManager, graph: Graph) -> dict[str, FgDepMeta]
11111111
if deps_meta is None:
11121112
return None
11131113
meta_snapshot = deps_meta["snapshot"]
1114-
# Take a snapshot of the source hashes from all of the metas we found.
1114+
# Take a snapshot of the source hashes from all the metas we found.
11151115
# (Including the ones we rejected because they were out of date.)
11161116
# We use this to verify that they match up with the proto_deps.
11171117
current_meta_snapshot = {

mypy/checker.py

Lines changed: 80 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def __init__(
397397
self.is_stub = tree.is_stub
398398
self.is_typeshed_stub = tree.is_typeshed_file(options)
399399
self.inferred_attribute_types = None
400+
self.allow_constructor_cache = True
400401

401402
# If True, process function definitions. If False, don't. This is used
402403
# for processing module top levels in fine-grained incremental mode.
@@ -448,7 +449,6 @@ def reset(self) -> None:
448449
self.binder = ConditionalTypeBinder(self.options)
449450
self._type_maps[1:] = []
450451
self._type_maps[0].clear()
451-
self.temp_type_map = None
452452
self.expr_checker.reset()
453453
self.deferred_nodes = []
454454
self.partial_types = []
@@ -500,12 +500,16 @@ def check_first_pass(self) -> None:
500500
)
501501

502502
def check_second_pass(
503-
self, todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None
503+
self,
504+
todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None,
505+
*,
506+
allow_constructor_cache: bool = True,
504507
) -> bool:
505508
"""Run second or following pass of type checking.
506509
507510
This goes through deferred nodes, returning True if there were any.
508511
"""
512+
self.allow_constructor_cache = allow_constructor_cache
509513
self.recurse_into_functions = True
510514
with state.strict_optional_set(self.options.strict_optional), checker_state.set(self):
511515
if not todo and not self.deferred_nodes:
@@ -1369,49 +1373,19 @@ def check_func_def(
13691373
)
13701374

13711375
# Store argument types.
1376+
found_self = False
1377+
if isinstance(defn, FuncDef) and not defn.is_decorated:
1378+
found_self = self.require_correct_self_argument(typ, defn)
13721379
for i in range(len(typ.arg_types)):
13731380
arg_type = typ.arg_types[i]
1374-
if (
1375-
isinstance(defn, FuncDef)
1376-
and ref_type is not None
1377-
and i == 0
1378-
and defn.has_self_or_cls_argument
1379-
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]
1380-
):
1381-
if defn.is_class or defn.name == "__new__":
1382-
ref_type = mypy.types.TypeType.make_normalized(ref_type)
1383-
if not is_same_type(arg_type, ref_type):
1384-
# This level of erasure matches the one in checkmember.check_self_arg(),
1385-
# better keep these two checks consistent.
1386-
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1387-
if not is_subtype(ref_type, erased, ignore_type_params=True):
1388-
if (
1389-
isinstance(erased, Instance)
1390-
and erased.type.is_protocol
1391-
or isinstance(erased, TypeType)
1392-
and isinstance(erased.item, Instance)
1393-
and erased.item.type.is_protocol
1394-
):
1395-
# We allow the explicit self-type to be not a supertype of
1396-
# the current class if it is a protocol. For such cases
1397-
# the consistency check will be performed at call sites.
1398-
msg = None
1399-
elif typ.arg_names[i] in {"self", "cls"}:
1400-
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1401-
erased.str_with_options(self.options),
1402-
ref_type.str_with_options(self.options),
1403-
)
1404-
else:
1405-
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1406-
if msg:
1407-
self.fail(msg, defn)
1408-
elif isinstance(arg_type, TypeVarType):
1381+
if isinstance(arg_type, TypeVarType):
14091382
# Refuse covariant parameter type variables
14101383
# TODO: check recursively for inner type variables
14111384
if (
14121385
arg_type.variance == COVARIANT
14131386
and defn.name not in ("__init__", "__new__", "__post_init__")
14141387
and not is_private(defn.name) # private methods are not inherited
1388+
and (i != 0 or not found_self)
14151389
):
14161390
ctx: Context = arg_type
14171391
if ctx.line < 0:
@@ -1561,6 +1535,69 @@ def check_func_def(
15611535

15621536
self.binder = old_binder
15631537

1538+
def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool:
1539+
func = get_proper_type(func)
1540+
if not isinstance(func, CallableType):
1541+
return False
1542+
1543+
# Do not report errors for untyped methods in classes nested in untyped funcs.
1544+
if not (
1545+
self.options.check_untyped_defs
1546+
or len(self.dynamic_funcs) < 2
1547+
or not self.dynamic_funcs[-2]
1548+
or not defn.is_dynamic()
1549+
):
1550+
return bool(func.arg_types)
1551+
1552+
with self.scope.push_function(defn):
1553+
# We temporary push the definition to get the self type as
1554+
# visible from *inside* of this function/method.
1555+
ref_type: Type | None = self.scope.active_self_type()
1556+
if ref_type is None:
1557+
return False
1558+
1559+
if not defn.has_self_or_cls_argument or (
1560+
func.arg_kinds and func.arg_kinds[0] in [nodes.ARG_STAR, nodes.ARG_STAR2]
1561+
):
1562+
return False
1563+
1564+
if not func.arg_types:
1565+
self.fail(
1566+
'Method must have at least one argument. Did you forget the "self" argument?', defn
1567+
)
1568+
return False
1569+
1570+
arg_type = func.arg_types[0]
1571+
if defn.is_class or defn.name == "__new__":
1572+
ref_type = mypy.types.TypeType.make_normalized(ref_type)
1573+
if is_same_type(arg_type, ref_type):
1574+
return True
1575+
1576+
# This level of erasure matches the one in checkmember.check_self_arg(),
1577+
# better keep these two checks consistent.
1578+
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1579+
if not is_subtype(ref_type, erased, ignore_type_params=True):
1580+
if (
1581+
isinstance(erased, Instance)
1582+
and erased.type.is_protocol
1583+
or isinstance(erased, TypeType)
1584+
and isinstance(erased.item, Instance)
1585+
and erased.item.type.is_protocol
1586+
):
1587+
# We allow the explicit self-type to be not a supertype of
1588+
# the current class if it is a protocol. For such cases
1589+
# the consistency check will be performed at call sites.
1590+
msg = None
1591+
elif func.arg_names[0] in {"self", "cls"}:
1592+
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1593+
erased.str_with_options(self.options), ref_type.str_with_options(self.options)
1594+
)
1595+
else:
1596+
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1597+
if msg:
1598+
self.fail(msg, defn)
1599+
return True
1600+
15641601
def is_var_redefined_in_outer_context(self, v: Var, after_line: int) -> bool:
15651602
"""Can the variable be assigned to at module top level or outer function?
15661603
@@ -3019,6 +3056,8 @@ def visit_block(self, b: Block) -> None:
30193056
break
30203057
else:
30213058
self.accept(s)
3059+
# Clear expression cache after each statement to avoid unlimited growth.
3060+
self.expr_checker.expr_cache.clear()
30223061

30233062
def should_report_unreachable_issues(self) -> bool:
30243063
return (
@@ -4000,7 +4039,7 @@ def check_multi_assignment_from_union(
40004039
for t, lv in zip(transposed, self.flatten_lvalues(lvalues)):
40014040
# We can access _type_maps directly since temporary type maps are
40024041
# only created within expressions.
4003-
t.append(self._type_maps[0].pop(lv, AnyType(TypeOfAny.special_form)))
4042+
t.append(self._type_maps[-1].pop(lv, AnyType(TypeOfAny.special_form)))
40044043
union_types = tuple(make_simplified_union(col) for col in transposed)
40054044
for expr, items in assignments.items():
40064045
# Bind a union of types collected in 'assignments' to every expression.
@@ -4659,6 +4698,8 @@ def replace_partial_type(
46594698
) -> None:
46604699
"""Replace the partial type of var with a non-partial type."""
46614700
var.type = new_type
4701+
# Updating a partial type should invalidate expression caches.
4702+
self.binder.version += 1
46624703
del partial_types[var]
46634704
if self.options.allow_redefinition_new:
46644705
# When using --allow-redefinition-new, binder tracks all types of
@@ -5298,6 +5339,7 @@ def visit_decorator_inner(
52985339
)
52995340
if non_trivial_decorator:
53005341
self.check_untyped_after_decorator(sig, e.func)
5342+
self.require_correct_self_argument(sig, e.func)
53015343
sig = set_callable_name(sig, e.func)
53025344
e.var.type = sig
53035345
e.var.is_ready = True

mypy/checker_shared.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class TypeCheckerSharedApi(CheckerPluginInterface):
137137
module_refs: set[str]
138138
scope: CheckerScope
139139
checking_missing_await: bool
140+
allow_constructor_cache: bool
140141

141142
@property
142143
@abstractmethod

mypy/checkexpr.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from mypy.checkmember import analyze_member_access, has_operator
2020
from mypy.checkstrformat import StringFormatterChecker
2121
from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars
22-
from mypy.errors import ErrorWatcher, report_internal_error
22+
from mypy.errors import ErrorInfo, ErrorWatcher, report_internal_error
2323
from mypy.expandtype import (
2424
expand_type,
2525
expand_type_by_instance,
@@ -355,9 +355,15 @@ def __init__(
355355
type_state.infer_polymorphic = not self.chk.options.old_type_inference
356356

357357
self._arg_infer_context_cache = None
358+
self.expr_cache: dict[
359+
tuple[Expression, Type | None],
360+
tuple[int, Type, list[ErrorInfo], dict[Expression, Type]],
361+
] = {}
362+
self.in_lambda_expr = False
358363

359364
def reset(self) -> None:
360365
self.resolved_type = {}
366+
self.expr_cache.clear()
361367

362368
def visit_name_expr(self, e: NameExpr) -> Type:
363369
"""Type check a name expression.
@@ -5402,6 +5408,8 @@ def find_typeddict_context(
54025408

54035409
def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54045410
"""Type check lambda expression."""
5411+
old_in_lambda = self.in_lambda_expr
5412+
self.in_lambda_expr = True
54055413
self.chk.check_default_args(e, body_is_trivial=False)
54065414
inferred_type, type_override = self.infer_lambda_type_using_context(e)
54075415
if not inferred_type:
@@ -5422,6 +5430,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54225430
ret_type = self.accept(e.expr(), allow_none_return=True)
54235431
fallback = self.named_type("builtins.function")
54245432
self.chk.return_types.pop()
5433+
self.in_lambda_expr = old_in_lambda
54255434
return callable_type(e, fallback, ret_type)
54265435
else:
54275436
# Type context available.
@@ -5434,6 +5443,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54345443
self.accept(e.expr(), allow_none_return=True)
54355444
ret_type = self.chk.lookup_type(e.expr())
54365445
self.chk.return_types.pop()
5446+
self.in_lambda_expr = old_in_lambda
54375447
return replace_callable_return_type(inferred_type, ret_type)
54385448

54395449
def infer_lambda_type_using_context(
@@ -5978,6 +5988,24 @@ def accept(
59785988
typ = self.visit_conditional_expr(node, allow_none_return=True)
59795989
elif allow_none_return and isinstance(node, AwaitExpr):
59805990
typ = self.visit_await_expr(node, allow_none_return=True)
5991+
# Deeply nested generic calls can deteriorate performance dramatically.
5992+
# Although in most cases caching makes little difference, in worst case
5993+
# it avoids exponential complexity.
5994+
# We cannot use cache inside lambdas, because they skip immediate type
5995+
# context, and use enclosing one, see infer_lambda_type_using_context().
5996+
# TODO: consider using cache for more expression kinds.
5997+
elif isinstance(node, (CallExpr, ListExpr, TupleExpr)) and not (
5998+
self.in_lambda_expr or self.chk.current_node_deferred
5999+
):
6000+
if (node, type_context) in self.expr_cache:
6001+
binder_version, typ, messages, type_map = self.expr_cache[(node, type_context)]
6002+
if binder_version == self.chk.binder.version:
6003+
self.chk.store_types(type_map)
6004+
self.msg.add_errors(messages)
6005+
else:
6006+
typ = self.accept_maybe_cache(node, type_context=type_context)
6007+
else:
6008+
typ = self.accept_maybe_cache(node, type_context=type_context)
59816009
else:
59826010
typ = node.accept(self)
59836011
except Exception as err:
@@ -6008,6 +6036,21 @@ def accept(
60086036
self.in_expression = False
60096037
return result
60106038

6039+
def accept_maybe_cache(self, node: Expression, type_context: Type | None = None) -> Type:
6040+
binder_version = self.chk.binder.version
6041+
# Micro-optimization: inline local_type_map() as it is somewhat slow in mypyc.
6042+
type_map: dict[Expression, Type] = {}
6043+
self.chk._type_maps.append(type_map)
6044+
with self.msg.filter_errors(filter_errors=True, save_filtered_errors=True) as msg:
6045+
typ = node.accept(self)
6046+
messages = msg.filtered_errors()
6047+
if binder_version == self.chk.binder.version and not self.chk.current_node_deferred:
6048+
self.expr_cache[(node, type_context)] = (binder_version, typ, messages, type_map)
6049+
self.chk._type_maps.pop()
6050+
self.chk.store_types(type_map)
6051+
self.msg.add_errors(messages)
6052+
return typ
6053+
60116054
def named_type(self, name: str) -> Instance:
60126055
"""Return an instance type with type given by the name and no type
60136056
arguments. Alias for TypeChecker.named_type.

0 commit comments

Comments
 (0)