Skip to content

Commit 47747f2

Browse files
authored
Some semantic analyzer micro-optimizations (#14367)
The biggest change is replacing some calls to bound methods with trait method calls, which are faster when compiled. Also remove an unused argument to TypeVarLikeQuery and make a few misc tweaks. (Various small optimizations, including these, together netted a 6% performance improvement in self check.)
1 parent 0070071 commit 47747f2

File tree

3 files changed

+40
-26
lines changed

3 files changed

+40
-26
lines changed

mypy/semanal.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,7 +1915,7 @@ def get_all_bases_tvars(
19151915
except TypeTranslationError:
19161916
# This error will be caught later.
19171917
continue
1918-
base_tvars = base.accept(TypeVarLikeQuery(self.lookup_qualified, self.tvar_scope))
1918+
base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope))
19191919
tvars.extend(base_tvars)
19201920
return remove_dups(tvars)
19211921

@@ -1933,7 +1933,7 @@ def get_and_bind_all_tvars(self, type_exprs: list[Expression]) -> list[TypeVarLi
19331933
except TypeTranslationError:
19341934
# This error will be caught later.
19351935
continue
1936-
base_tvars = base.accept(TypeVarLikeQuery(self.lookup_qualified, self.tvar_scope))
1936+
base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope))
19371937
tvars.extend(base_tvars)
19381938
tvars = remove_dups(tvars) # Variables are defined in order of textual appearance.
19391939
tvar_defs = []
@@ -3294,7 +3294,7 @@ def analyze_alias(
32943294
)
32953295
return None, [], set(), []
32963296

3297-
found_type_vars = typ.accept(TypeVarLikeQuery(self.lookup_qualified, self.tvar_scope))
3297+
found_type_vars = typ.accept(TypeVarLikeQuery(self, self.tvar_scope))
32983298
tvar_defs: list[TypeVarLikeType] = []
32993299
namespace = self.qualified_name(name)
33003300
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):

mypy/type_visitor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ class TypeQuery(SyntheticTypeVisitor[T]):
315315
# TODO: check that we don't have existing violations of this rule.
316316
"""
317317

318-
def __init__(self, strategy: Callable[[Iterable[T]], T]) -> None:
318+
def __init__(self, strategy: Callable[[list[T]], T]) -> None:
319319
self.strategy = strategy
320320
# Keep track of the type aliases already visited. This is needed to avoid
321321
# infinite recursion on types like A = Union[int, List[A]].

mypy/typeanal.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import itertools
66
from contextlib import contextmanager
7-
from itertools import chain
87
from typing import Callable, Iterable, Iterator, List, Sequence, Tuple, TypeVar
98
from typing_extensions import Final, Protocol
109

@@ -203,8 +202,6 @@ def __init__(
203202
allow_type_any: bool = False,
204203
) -> None:
205204
self.api = api
206-
self.lookup_qualified = api.lookup_qualified
207-
self.lookup_fqn_func = api.lookup_fully_qualified
208205
self.fail_func = api.fail
209206
self.note_func = api.note
210207
self.tvar_scope = tvar_scope
@@ -244,6 +241,14 @@ def __init__(
244241
# Allow variables typed as Type[Any] and type (useful for base classes).
245242
self.allow_type_any = allow_type_any
246243

244+
def lookup_qualified(
245+
self, name: str, ctx: Context, suppress_errors: bool = False
246+
) -> SymbolTableNode | None:
247+
return self.api.lookup_qualified(name, ctx, suppress_errors)
248+
249+
def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
250+
return self.api.lookup_fully_qualified(name)
251+
247252
def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) -> Type:
248253
typ = self.visit_unbound_type_nonoptional(t, defining_literal)
249254
if t.optional:
@@ -1408,27 +1413,31 @@ def tvar_scope_frame(self) -> Iterator[None]:
14081413
yield
14091414
self.tvar_scope = old_scope
14101415

1416+
def find_type_var_likes(self, t: Type, include_callables: bool = True) -> TypeVarLikeList:
1417+
return t.accept(
1418+
TypeVarLikeQuery(self.api, self.tvar_scope, include_callables=include_callables)
1419+
)
1420+
14111421
def infer_type_variables(self, type: CallableType) -> list[tuple[str, TypeVarLikeExpr]]:
14121422
"""Return list of unique type variables referred to in a callable."""
14131423
names: list[str] = []
14141424
tvars: list[TypeVarLikeExpr] = []
14151425
for arg in type.arg_types:
1416-
for name, tvar_expr in arg.accept(
1417-
TypeVarLikeQuery(self.lookup_qualified, self.tvar_scope)
1418-
):
1426+
for name, tvar_expr in self.find_type_var_likes(arg):
14191427
if name not in names:
14201428
names.append(name)
14211429
tvars.append(tvar_expr)
14221430
# When finding type variables in the return type of a function, don't
14231431
# look inside Callable types. Type variables only appearing in
14241432
# functions in the return type belong to those functions, not the
14251433
# function we're currently analyzing.
1426-
for name, tvar_expr in type.ret_type.accept(
1427-
TypeVarLikeQuery(self.lookup_qualified, self.tvar_scope, include_callables=False)
1428-
):
1434+
for name, tvar_expr in self.find_type_var_likes(type.ret_type, include_callables=False):
14291435
if name not in names:
14301436
names.append(name)
14311437
tvars.append(tvar_expr)
1438+
1439+
if not names:
1440+
return [] # Fast path
14321441
return list(zip(names, tvars))
14331442

14341443
def bind_function_type_variables(
@@ -1546,7 +1555,7 @@ def named_type(
15461555
line: int = -1,
15471556
column: int = -1,
15481557
) -> Instance:
1549-
node = self.lookup_fqn_func(fully_qualified_name)
1558+
node = self.lookup_fully_qualified(fully_qualified_name)
15501559
assert isinstance(node.node, TypeInfo)
15511560
any_type = AnyType(TypeOfAny.special_form)
15521561
if args is not None:
@@ -1785,7 +1794,9 @@ def set_any_tvars(
17851794
return TypeAliasType(node, [any_type] * len(node.alias_tvars), newline, newcolumn)
17861795

17871796

1788-
def remove_dups(tvars: Iterable[T]) -> list[T]:
1797+
def remove_dups(tvars: list[T]) -> list[T]:
1798+
if len(tvars) <= 1:
1799+
return tvars
17891800
# Get unique elements in order of appearance
17901801
all_tvars: set[T] = set()
17911802
new_tvars: list[T] = []
@@ -1796,26 +1807,29 @@ def remove_dups(tvars: Iterable[T]) -> list[T]:
17961807
return new_tvars
17971808

17981809

1799-
def flatten_tvars(ll: Iterable[list[T]]) -> list[T]:
1800-
return remove_dups(chain.from_iterable(ll))
1810+
def flatten_tvars(lists: list[list[T]]) -> list[T]:
1811+
result: list[T] = []
1812+
for lst in lists:
1813+
for item in lst:
1814+
if item not in result:
1815+
result.append(item)
1816+
return result
18011817

18021818

18031819
class TypeVarLikeQuery(TypeQuery[TypeVarLikeList]):
18041820
"""Find TypeVar and ParamSpec references in an unbound type."""
18051821

18061822
def __init__(
18071823
self,
1808-
lookup: Callable[[str, Context], SymbolTableNode | None],
1824+
api: SemanticAnalyzerCoreInterface,
18091825
scope: TypeVarLikeScope,
18101826
*,
18111827
include_callables: bool = True,
1812-
include_bound_tvars: bool = False,
18131828
) -> None:
1814-
self.include_callables = include_callables
1815-
self.lookup = lookup
1816-
self.scope = scope
1817-
self.include_bound_tvars = include_bound_tvars
18181829
super().__init__(flatten_tvars)
1830+
self.api = api
1831+
self.scope = scope
1832+
self.include_callables = include_callables
18191833
# Only include type variables in type aliases args. This would be anyway
18201834
# that case if we expand (as target variables would be overridden with args)
18211835
# and it may cause infinite recursion on invalid (diverging) recursive aliases.
@@ -1833,16 +1847,16 @@ def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList:
18331847
if name.endswith("args"):
18341848
if name.endswith(".args") or name.endswith(".kwargs"):
18351849
base = ".".join(name.split(".")[:-1])
1836-
n = self.lookup(base, t)
1850+
n = self.api.lookup_qualified(base, t)
18371851
if n is not None and isinstance(n.node, ParamSpecExpr):
18381852
node = n
18391853
name = base
18401854
if node is None:
1841-
node = self.lookup(name, t)
1855+
node = self.api.lookup_qualified(name, t)
18421856
if (
18431857
node
18441858
and isinstance(node.node, TypeVarLikeExpr)
1845-
and (self.include_bound_tvars or self.scope.get_binding(node) is None)
1859+
and self.scope.get_binding(node) is None
18461860
):
18471861
assert isinstance(node.node, TypeVarLikeExpr)
18481862
return [(name, node.node)]

0 commit comments

Comments
 (0)