Skip to content

Commit 4f682eb

Browse files
committed
Finish main part
1 parent 7cf8411 commit 4f682eb

File tree

7 files changed

+155
-100
lines changed

7 files changed

+155
-100
lines changed

mypy/build.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@
4848
from mypy.indirection import TypeIndirectionVisitor
4949
from mypy.messages import MessageBuilder
5050
from mypy.nodes import (
51+
Decorator,
5152
Import,
5253
ImportAll,
5354
ImportBase,
5455
ImportFrom,
5556
MypyFile,
57+
OverloadedFuncDef,
5658
SymbolTable,
57-
TypeAlias,
58-
TypeInfo,
5959
)
6060
from mypy.partially_defined import PossiblyUndefinedVariableVisitor
6161
from mypy.semanal import SemanticAnalyzer
@@ -1767,26 +1767,23 @@ def delete_cache(id: str, path: str, manager: BuildManager) -> None:
17671767
17681768
For single nodes, processing is simple. If the node was cached, we
17691769
deserialize the cache data and fix up cross-references. Otherwise, we
1770-
do semantic analysis followed by type checking. We also handle (c)
1771-
above; if a module has valid cache data *but* any of its
1772-
dependencies was processed from source, then the module should be
1773-
processed from source.
1774-
1775-
A relatively simple optimization (outside SCCs) we might do in the
1776-
future is as follows: if a node's cache data is valid, but one or more
1777-
of its dependencies are out of date so we have to re-parse the node
1778-
from source, once we have fully type-checked the node, we can decide
1779-
whether its symbol table actually changed compared to the cache data
1780-
(by reading the cache data and comparing it to the data we would be
1781-
writing). If there is no change we can declare the node up to date,
1782-
and any node that depends (and for which we have cached data, and
1783-
whose other dependencies are up to date) on it won't need to be
1784-
re-parsed from source.
1770+
do semantic analysis followed by type checking. Once we (re-)processed
1771+
an SCC we check whether its interface (symbol table) is still fresh
1772+
(matches previous cached value). If it is not, we consider dependent SCCs
1773+
stale so that they need to be re-parsed as well.
1774+
1775+
Note on indirect dependencies: normally dependencies are determined from
1776+
imports, but since our type interfaces are "opaque" (i.e. symbol tables can
1777+
contain types identified by name), these are not enough. We *must* also
1778+
add "indirect" dependencies from types to their definitions. For this
1779+
purpose, after we finished processing a module, we travers its type map and
1780+
symbol tables, and for each type we find (transitively) on which opaque/named
1781+
types it depends.
17851782
17861783
Import cycles
17871784
-------------
17881785
1789-
Finally we have to decide how to handle (c), import cycles. Here
1786+
Finally we have to decide how to handle (b), import cycles. Here
17901787
we'll need a modified version of the original state machine
17911788
(build.py), but we only need to do this per SCC, and we won't have to
17921789
deal with changes to the list of nodes while we're processing it.
@@ -2419,23 +2416,23 @@ def finish_passes(self) -> None:
24192416

24202417
# We should always patch indirect dependencies, even in full (non-incremental) builds,
24212418
# because the cache still may be written, and it must be correct.
2422-
# TODO: find a more robust way to traverse *all* relevant types?
2423-
all_types = list(self.type_map().values())
2419+
all_types = set(self.type_map().values())
24242420
for _, sym, _ in self.tree.local_definitions():
24252421
if sym.type is not None:
2426-
all_types.append(sym.type)
2427-
if isinstance(sym.node, TypeAlias):
2428-
all_types.append(sym.node.target)
2429-
if isinstance(sym.node, TypeInfo):
2430-
# TypeInfo symbols have some extra relevant types.
2431-
all_types.extend(sym.node.bases)
2432-
if sym.node.metaclass_type:
2433-
all_types.append(sym.node.metaclass_type)
2434-
if sym.node.typeddict_type:
2435-
all_types.append(sym.node.typeddict_type)
2436-
if sym.node.tuple_type:
2437-
all_types.append(sym.node.tuple_type)
2438-
self._patch_indirect_dependencies(self.type_checker().module_refs, all_types)
2422+
all_types.add(sym.type)
2423+
# Special case: settable properties may have two types.
2424+
if isinstance(sym.node, OverloadedFuncDef) and sym.node.is_property:
2425+
assert isinstance(first_node := sym.node.items[0], Decorator)
2426+
if first_node.var.setter_type:
2427+
all_types.add(first_node.var.setter_type)
2428+
# Using mod_alias_deps is unfortunate but needed, since it is highly impractical
2429+
# (and practically impossible) to avoid all get_proper_type() calls. For example,
2430+
# TypeInfo.bases and metaclass, *args and **kwargs, Overloaded.items, and trivial
2431+
# aliases like Text = str, etc. all currently forced to proper types. Thus, we need
2432+
# to record the original definitions as they are first seen in semanal.py.
2433+
self._patch_indirect_dependencies(
2434+
self.type_checker().module_refs | self.tree.mod_alias_deps, all_types
2435+
)
24392436

24402437
if self.options.dump_inference_stats:
24412438
dump_type_stats(
@@ -2464,7 +2461,7 @@ def free_state(self) -> None:
24642461
self._type_checker.reset()
24652462
self._type_checker = None
24662463

2467-
def _patch_indirect_dependencies(self, module_refs: set[str], types: list[Type]) -> None:
2464+
def _patch_indirect_dependencies(self, module_refs: set[str], types: set[Type]) -> None:
24682465
assert None not in types
24692466
valid = self.valid_references()
24702467

@@ -3298,7 +3295,10 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
32983295
for id in scc:
32993296
deps.update(graph[id].dependencies)
33003297
deps -= ascc
3301-
stale_deps = {id for id in deps if id in graph and not graph[id].is_interface_fresh()}
3298+
# Note: if a dependency is not in graph anymore, it should be considered interface-stale.
3299+
# This is important to trigger any relevant updates from indirect dependencies that were
3300+
# removed in load_graph().
3301+
stale_deps = {id for id in deps if id not in graph or not graph[id].is_interface_fresh()}
33023302
fresh = fresh and not stale_deps
33033303
undeps = set()
33043304
if fresh:

mypy/indirection.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,44 +23,58 @@ class TypeIndirectionVisitor(TypeVisitor[None]):
2323
def __init__(self) -> None:
2424
# Module references are collected here
2525
self.modules: set[str] = set()
26-
# User to avoid infinite recursion with recursive type aliases
27-
self.seen_aliases: set[types.TypeAliasType] = set()
26+
# User to avoid infinite recursion with recursive types
27+
self.seen_types: set[types.TypeAliasType | types.Instance] = set()
2828
# Used to avoid redundant work
2929
self.seen_fullnames: set[str] = set()
3030

3131
def find_modules(self, typs: Iterable[types.Type]) -> set[str]:
3232
self.modules = set()
3333
self.seen_fullnames = set()
34-
self.seen_aliases = set()
34+
self.seen_types = set()
3535
for typ in typs:
3636
self._visit(typ)
3737
return self.modules
3838

3939
def _visit(self, typ: types.Type) -> None:
40-
if isinstance(typ, types.TypeAliasType):
41-
# Avoid infinite recursion for recursive type aliases.
42-
if typ not in self.seen_aliases:
43-
self.seen_aliases.add(typ)
40+
# Note: instances are needed for `class str(Sequence[str]): ...`
41+
if (
42+
isinstance(typ, types.TypeAliasType)
43+
or isinstance(typ, types.ProperType)
44+
and isinstance(typ, types.Instance)
45+
):
46+
# Avoid infinite recursion for recursive types.
47+
if typ in self.seen_types:
48+
return
49+
self.seen_types.add(typ)
4450
typ.accept(self)
4551

4652
def _visit_type_tuple(self, typs: tuple[types.Type, ...]) -> None:
4753
# Micro-optimization: Specialized version of _visit for lists
4854
for typ in typs:
49-
if isinstance(typ, types.TypeAliasType):
50-
# Avoid infinite recursion for recursive type aliases.
51-
if typ in self.seen_aliases:
55+
if (
56+
isinstance(typ, types.TypeAliasType)
57+
or isinstance(typ, types.ProperType)
58+
and isinstance(typ, types.Instance)
59+
):
60+
# Avoid infinite recursion for recursive types.
61+
if typ in self.seen_types:
5262
continue
53-
self.seen_aliases.add(typ)
63+
self.seen_types.add(typ)
5464
typ.accept(self)
5565

5666
def _visit_type_list(self, typs: list[types.Type]) -> None:
5767
# Micro-optimization: Specialized version of _visit for tuples
5868
for typ in typs:
59-
if isinstance(typ, types.TypeAliasType):
60-
# Avoid infinite recursion for recursive type aliases.
61-
if typ in self.seen_aliases:
69+
if (
70+
isinstance(typ, types.TypeAliasType)
71+
or isinstance(typ, types.ProperType)
72+
and isinstance(typ, types.Instance)
73+
):
74+
# Avoid infinite recursion for recursive types.
75+
if typ in self.seen_types:
6276
continue
63-
self.seen_aliases.add(typ)
77+
self.seen_types.add(typ)
6478
typ.accept(self)
6579

6680
def _visit_module_name(self, module_name: str) -> None:
@@ -105,15 +119,27 @@ def visit_parameters(self, t: types.Parameters) -> None:
105119
self._visit_type_list(t.arg_types)
106120

107121
def visit_instance(self, t: types.Instance) -> None:
122+
# Instance is named, record its definition and continue digging into
123+
# components that constitute semantic meaning of this type: bases, metaclass,
124+
# tuple type, and typeddict type.
125+
# Note: we cannot simply record the MRO, in case an intermediate base contains
126+
# a reference to type alias, this affects meaning of map_instance_to_supertype(),
127+
# see e.g. testDoubleReexportGenericUpdated.
108128
self._visit_type_tuple(t.args)
109129
if t.type:
110-
# Uses of a class depend on everything in the MRO,
111-
# as changes to classes in the MRO can add types to methods,
112-
# change property types, change the MRO itself, etc.
130+
# Important optimization: instead of simply recording the definition and
131+
# recursing into bases, record the MRO and only traverse generic bases.
113132
for s in t.type.mro:
114133
self._visit_module_name(s.module_name)
115-
if t.type.metaclass_type is not None:
116-
self._visit_module_name(t.type.metaclass_type.type.module_name)
134+
for base in s.bases:
135+
if base.args:
136+
self._visit_type_tuple(base.args)
137+
if t.type.metaclass_type:
138+
self._visit(t.type.metaclass_type)
139+
if t.type.typeddict_type:
140+
self._visit(t.type.typeddict_type)
141+
if t.type.tuple_type:
142+
self._visit(t.type.tuple_type)
117143

118144
def visit_callable_type(self, t: types.CallableType) -> None:
119145
self._visit_type_list(t.arg_types)
@@ -125,7 +151,8 @@ def visit_callable_type(self, t: types.CallableType) -> None:
125151
self.seen_fullnames.add(fullname)
126152

127153
def visit_overloaded(self, t: types.Overloaded) -> None:
128-
self._visit_type_list(list(t.items))
154+
for item in t.items:
155+
self._visit(item)
129156
self._visit(t.fallback)
130157

131158
def visit_tuple_type(self, t: types.TupleType) -> None:
@@ -149,6 +176,8 @@ def visit_type_type(self, t: types.TypeType) -> None:
149176
self._visit(t.item)
150177

151178
def visit_type_alias_type(self, t: types.TypeAliasType) -> None:
179+
# Type alias is named, record its definition and continue digging into
180+
# components that constitute semantic meaning of this type: target and args.
152181
if t.alias:
153182
self._visit_module_name(t.alias.module)
154183
self._visit(t.alias.target)

mypy/nodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ class MypyFile(SymbolNode):
288288
"path",
289289
"defs",
290290
"alias_deps",
291+
"mod_alias_deps",
291292
"is_bom",
292293
"names",
293294
"imports",
@@ -311,6 +312,8 @@ class MypyFile(SymbolNode):
311312
defs: list[Statement]
312313
# Type alias dependencies as mapping from target to set of alias full names
313314
alias_deps: defaultdict[str, set[str]]
315+
# Same as above but for coarse-grained dependencies (i.e. modules instead of full names)
316+
mod_alias_deps: set[str]
314317
# Is there a UTF-8 BOM at the start?
315318
is_bom: bool
316319
names: SymbolTable
@@ -351,6 +354,7 @@ def __init__(
351354
self.imports = imports
352355
self.is_bom = is_bom
353356
self.alias_deps = defaultdict(set)
357+
self.mod_alias_deps = set()
354358
self.plugin_deps = {}
355359
if ignored_lines:
356360
self.ignored_lines = ignored_lines

mypy/semanal.py

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2808,6 +2808,7 @@ def get_declared_metaclass(
28082808
and not sym.node.alias_tvars
28092809
):
28102810
target = get_proper_type(sym.node.target)
2811+
self.add_type_alias_deps({(sym.node.module, sym.node.fullname)})
28112812
if isinstance(target, Instance):
28122813
metaclass_info = target.type
28132814

@@ -3886,16 +3887,15 @@ def analyze_alias(
38863887
declared_type_vars: TypeVarLikeList | None = None,
38873888
all_declared_type_params_names: list[str] | None = None,
38883889
python_3_12_type_alias: bool = False,
3889-
) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]:
3890+
) -> tuple[Type | None, list[TypeVarLikeType], set[tuple[str, str]], bool]:
38903891
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
38913892
3892-
If yes, return the corresponding type, a list of
3893-
qualified type variable names for generic aliases, a set of names the alias depends on,
3894-
and a list of type variables if the alias is generic.
3895-
A schematic example for the dependencies:
3893+
If yes, return the corresponding type, a list of type variables for generic aliases,
3894+
a set of names the alias depends on, and True if the original type has empty tuple index.
3895+
An example for the dependencies:
38963896
A = int
38973897
B = str
3898-
analyze_alias(Dict[A, B])[2] == {'__main__.A', '__main__.B'}
3898+
analyze_alias(dict[A, B])[2] == {('mod', 'mod.A'), ('mod', 'mod.B')}
38993899
"""
39003900
dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
39013901
global_scope = not self.type and not self.function_stack
@@ -3907,10 +3907,9 @@ def analyze_alias(
39073907
self.fail(
39083908
"Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE
39093909
)
3910-
return None, [], set(), [], False
3910+
return None, [], set(), False
39113911

39123912
found_type_vars = self.find_type_var_likes(typ)
3913-
tvar_defs: list[TypeVarLikeType] = []
39143913
namespace = self.qualified_name(name)
39153914
alias_type_vars = found_type_vars if declared_type_vars is None else declared_type_vars
39163915
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
@@ -3946,9 +3945,8 @@ def analyze_alias(
39463945
variadic = True
39473946
new_tvar_defs.append(td)
39483947

3949-
qualified_tvars = [node.fullname for _name, node in alias_type_vars]
39503948
empty_tuple_index = typ.empty_tuple_index if isinstance(typ, UnboundType) else False
3951-
return analyzed, new_tvar_defs, depends_on, qualified_tvars, empty_tuple_index
3949+
return analyzed, new_tvar_defs, depends_on, empty_tuple_index
39523950

39533951
def is_pep_613(self, s: AssignmentStmt) -> bool:
39543952
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
@@ -4042,12 +4040,11 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
40424040
if self.is_none_alias(rvalue):
40434041
res = NoneType()
40444042
alias_tvars: list[TypeVarLikeType] = []
4045-
depends_on: set[str] = set()
4046-
qualified_tvars: list[str] = []
4043+
depends_on: set[tuple[str, str]] = set()
40474044
empty_tuple_index = False
40484045
else:
40494046
tag = self.track_incomplete_refs()
4050-
res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias(
4047+
res, alias_tvars, depends_on, empty_tuple_index = self.analyze_alias(
40514048
lvalue.name,
40524049
rvalue,
40534050
allow_placeholder=True,
@@ -4071,12 +4068,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
40714068
self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True)
40724069
return True
40734070
self.add_type_alias_deps(depends_on)
4074-
# In addition to the aliases used, we add deps on unbound
4075-
# type variables, since they are erased from target type.
4076-
self.add_type_alias_deps(qualified_tvars)
4077-
# The above are only direct deps on other aliases.
4078-
# For subscripted aliases, type deps from expansion are added in deps.py
4079-
# (because the type is stored).
40804071
check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s)
40814072
# When this type alias gets "inlined", the Any is not explicit anymore,
40824073
# so we need to replace it with non-explicit Anys.
@@ -5579,7 +5570,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
55795570
return
55805571

55815572
tag = self.track_incomplete_refs()
5582-
res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias(
5573+
res, alias_tvars, depends_on, empty_tuple_index = self.analyze_alias(
55835574
s.name.name,
55845575
s.value.expr(),
55855576
allow_placeholder=True,
@@ -5608,12 +5599,6 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
56085599
return
56095600

56105601
self.add_type_alias_deps(depends_on)
5611-
# In addition to the aliases used, we add deps on unbound
5612-
# type variables, since they are erased from target type.
5613-
self.add_type_alias_deps(qualified_tvars)
5614-
# The above are only direct deps on other aliases.
5615-
# For subscripted aliases, type deps from expansion are added in deps.py
5616-
# (because the type is stored).
56175602
check_for_explicit_any(
56185603
res, self.options, self.is_typeshed_stub_file, self.msg, context=s
56195604
)
@@ -7541,20 +7526,21 @@ def add_plugin_dependency(self, trigger: str, target: str | None = None) -> None
75417526
self.cur_mod_node.plugin_deps.setdefault(trigger, set()).add(target)
75427527

75437528
def add_type_alias_deps(
7544-
self, aliases_used: Collection[str], target: str | None = None
7529+
self, aliases_used: Collection[tuple[str, str]], target: str | None = None
75457530
) -> None:
75467531
"""Add full names of type aliases on which the current node depends.
75477532
75487533
This is used by fine-grained incremental mode to re-check the corresponding nodes.
7549-
If `target` is None, then the target node used will be the current scope.
7534+
If `target` is None, then the target node used will be the current scope. For
7535+
coarse-grained mode, add just the module names where aliases are defined.
75507536
"""
75517537
if not aliases_used:
7552-
# A basic optimization to avoid adding targets with no dependencies to
7553-
# the `alias_deps` dict.
75547538
return
75557539
if target is None:
75567540
target = self.scope.current_target()
7557-
self.cur_mod_node.alias_deps[target].update(aliases_used)
7541+
for mod, fn in aliases_used:
7542+
self.cur_mod_node.alias_deps[target].add(fn)
7543+
self.cur_mod_node.mod_alias_deps.add(mod)
75587544

75597545
def is_mangled_global(self, name: str) -> bool:
75607546
# A global is mangled if there exists at least one renamed variant.

0 commit comments

Comments
 (0)