Skip to content

Commit 660d911

Browse files
authored
Two more micro-optimizations (#19627)
This has two things (totalling 1.5% locally, but see caveat below): * Do not use `@contextmanger` (that is relatively slow) for `local_type_map`, since it appears in multiple hot paths. * Do not show name suggestions for import errors in third party packages (since those errors are ignored anyway). It calls `difflib` that can be extremely slow with large modules. Btw the second will probably not affect self-check, although it did affect _my_ self-check, since apparently `pytest` depends on `numpy`. Well, they don't specify it as a package dependency, but https://github.com/pytest-dev/pytest/blob/main/src/_pytest/python_api.py#L17-L18 ```python if TYPE_CHECKING: from numpy import ndarray ``` (and I have numpy installed in all my environments, LOL)
1 parent cc5f1e1 commit 660d911

File tree

3 files changed

+43
-24
lines changed

3 files changed

+43
-24
lines changed

mypy/checker.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@
66
from collections import defaultdict
77
from collections.abc import Iterable, Iterator, Mapping, Sequence, Set as AbstractSet
88
from contextlib import ExitStack, contextmanager
9-
from typing import Callable, Final, Generic, NamedTuple, Optional, TypeVar, Union, cast, overload
9+
from typing import (
10+
Callable,
11+
Final,
12+
Generic,
13+
Literal,
14+
NamedTuple,
15+
Optional,
16+
TypeVar,
17+
Union,
18+
cast,
19+
overload,
20+
)
1021
from typing_extensions import TypeAlias as _TypeAlias, TypeGuard
1122

1223
import mypy.checkexpr
@@ -277,6 +288,26 @@ class PartialTypeScope(NamedTuple):
277288
is_local: bool
278289

279290

291+
class LocalTypeMap:
292+
"""Store inferred types into a temporary type map (returned).
293+
294+
This can be used to perform type checking "experiments" without
295+
affecting exported types (which are used by mypyc).
296+
"""
297+
298+
def __init__(self, chk: TypeChecker) -> None:
299+
self.chk = chk
300+
301+
def __enter__(self) -> dict[Expression, Type]:
302+
temp_type_map: dict[Expression, Type] = {}
303+
self.chk._type_maps.append(temp_type_map)
304+
return temp_type_map
305+
306+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> Literal[False]:
307+
self.chk._type_maps.pop()
308+
return False
309+
310+
280311
class TypeChecker(NodeVisitor[None], TypeCheckerSharedApi):
281312
"""Mypy type checker.
282313
@@ -402,6 +433,7 @@ def __init__(
402433
self.is_typeshed_stub = tree.is_typeshed_file(options)
403434
self.inferred_attribute_types = None
404435
self.allow_constructor_cache = True
436+
self.local_type_map = LocalTypeMap(self)
405437

406438
# If True, process function definitions. If False, don't. This is used
407439
# for processing module top levels in fine-grained incremental mode.
@@ -4631,7 +4663,7 @@ def check_simple_assignment(
46314663
# may cause some perf impact, plus we want to partially preserve
46324664
# the old behavior. This helps with various practical examples, see
46334665
# e.g. testOptionalTypeNarrowedByGenericCall.
4634-
with self.msg.filter_errors() as local_errors, self.local_type_map() as type_map:
4666+
with self.msg.filter_errors() as local_errors, self.local_type_map as type_map:
46354667
alt_rvalue_type = self.expr_checker.accept(
46364668
rvalue, None, always_allow_any=always_allow_any
46374669
)
@@ -7458,18 +7490,6 @@ def lookup_type(self, node: Expression) -> Type:
74587490
def store_types(self, d: dict[Expression, Type]) -> None:
74597491
self._type_maps[-1].update(d)
74607492

7461-
@contextmanager
7462-
def local_type_map(self) -> Iterator[dict[Expression, Type]]:
7463-
"""Store inferred types into a temporary type map (returned).
7464-
7465-
This can be used to perform type checking "experiments" without
7466-
affecting exported types (which are used by mypyc).
7467-
"""
7468-
temp_type_map: dict[Expression, Type] = {}
7469-
self._type_maps.append(temp_type_map)
7470-
yield temp_type_map
7471-
self._type_maps.pop()
7472-
74737493
def in_checked_function(self) -> bool:
74747494
"""Should we type-check the current function?
74757495

mypy/checkexpr.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,7 +1075,7 @@ def check_typeddict_call_with_kwargs(
10751075

10761076
# We don't show any errors, just infer types in a generic TypedDict type,
10771077
# a custom error message will be given below, if there are errors.
1078-
with self.msg.filter_errors(), self.chk.local_type_map():
1078+
with self.msg.filter_errors(), self.chk.local_type_map:
10791079
orig_ret_type, _ = self.check_callable_call(
10801080
infer_callee,
10811081
# We use first expression for each key to infer type variables of a generic
@@ -1440,7 +1440,7 @@ def is_generic_decorator_overload_call(
14401440
return None
14411441
if not isinstance(get_proper_type(callee_type.ret_type), CallableType):
14421442
return None
1443-
with self.chk.local_type_map():
1443+
with self.chk.local_type_map:
14441444
with self.msg.filter_errors():
14451445
arg_type = get_proper_type(self.accept(args[0], type_context=None))
14461446
if isinstance(arg_type, Overloaded):
@@ -2920,7 +2920,7 @@ def infer_overload_return_type(
29202920
for typ in plausible_targets:
29212921
assert self.msg is self.chk.msg
29222922
with self.msg.filter_errors() as w:
2923-
with self.chk.local_type_map() as m:
2923+
with self.chk.local_type_map as m:
29242924
ret_type, infer_type = self.check_call(
29252925
callee=typ,
29262926
args=args,
@@ -5367,7 +5367,7 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
53675367
return self.check_typeddict_literal_in_context(e, typeddict_contexts[0])
53685368
# Multiple items union, check if at least one of them matches cleanly.
53695369
for typeddict_context in typeddict_contexts:
5370-
with self.msg.filter_errors() as err, self.chk.local_type_map() as tmap:
5370+
with self.msg.filter_errors() as err, self.chk.local_type_map as tmap:
53715371
ret_type = self.check_typeddict_literal_in_context(e, typeddict_context)
53725372
if err.has_new_errors():
53735373
continue
@@ -6095,15 +6095,12 @@ def accept(
60956095

60966096
def accept_maybe_cache(self, node: Expression, type_context: Type | None = None) -> Type:
60976097
binder_version = self.chk.binder.version
6098-
# Micro-optimization: inline local_type_map() as it is somewhat slow in mypyc.
6099-
type_map: dict[Expression, Type] = {}
6100-
self.chk._type_maps.append(type_map)
61016098
with self.msg.filter_errors(filter_errors=True, save_filtered_errors=True) as msg:
6102-
typ = node.accept(self)
6099+
with self.chk.local_type_map as type_map:
6100+
typ = node.accept(self)
61036101
messages = msg.filtered_errors()
61046102
if binder_version == self.chk.binder.version and not self.chk.current_node_deferred:
61056103
self.expr_cache[(node, type_context)] = (binder_version, typ, messages, type_map)
6106-
self.chk._type_maps.pop()
61076104
self.chk.store_types(type_map)
61086105
self.msg.add_errors(messages)
61096106
return typ

mypy/semanal.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3048,7 +3048,9 @@ def report_missing_module_attribute(
30483048
message = (
30493049
f'Module "{import_id}" does not explicitly export attribute "{source_id}"'
30503050
)
3051-
else:
3051+
elif not (
3052+
self.options.ignore_errors or self.cur_mod_node.path in self.errors.ignored_files
3053+
):
30523054
alternatives = set(module.names.keys()).difference({source_id})
30533055
matches = best_matches(source_id, alternatives, n=3)
30543056
if matches:

0 commit comments

Comments
 (0)