Skip to content

Commit e05e518

Browse files
committed
Incorporate more fixes
1 parent ff981eb commit e05e518

File tree

9 files changed

+98
-109
lines changed

9 files changed

+98
-109
lines changed

mypy/build.py

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,7 @@
4747
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
4848
from mypy.indirection import TypeIndirectionVisitor
4949
from mypy.messages import MessageBuilder
50-
from mypy.nodes import (
51-
Decorator,
52-
Import,
53-
ImportAll,
54-
ImportBase,
55-
ImportFrom,
56-
MypyFile,
57-
OverloadedFuncDef,
58-
SymbolTable,
59-
)
50+
from mypy.nodes import Import, ImportAll, ImportBase, ImportFrom, MypyFile, SymbolTable
6051
from mypy.partially_defined import PossiblyUndefinedVariableVisitor
6152
from mypy.semanal import SemanticAnalyzer
6253
from mypy.semanal_pass1 import SemanticAnalyzerPreAnalysis
@@ -1773,12 +1764,13 @@ def delete_cache(id: str, path: str, manager: BuildManager) -> None:
17731764
stale so that they need to be re-parsed as well.
17741765
17751766
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.
1767+
imports, but since our interfaces are "opaque" (i.e. symbol tables can
1768+
contain cross-references as well as types identified by name), these are not
1769+
enough. We *must* also add "indirect" dependencies from symbols and types to
1770+
their definitions. For this purpose, we record all accessed symbols during
1771+
semantic analysis, and after we finished processing a module, we traverse its
1772+
type map, and for each type we find (transitively) on which named types it
1773+
depends.
17821774
17831775
Import cycles
17841776
-------------
@@ -2416,22 +2408,14 @@ def finish_passes(self) -> None:
24162408

24172409
# We should always patch indirect dependencies, even in full (non-incremental) builds,
24182410
# because the cache still may be written, and it must be correct.
2419-
all_types = set(self.type_map().values())
2420-
for _, sym, _ in self.tree.local_definitions():
2421-
if sym.type is not None:
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.
24332411
self._patch_indirect_dependencies(
2434-
self.type_checker().module_refs | self.tree.mod_alias_deps, all_types
2412+
# Two possible sources of indirect dependencies:
2413+
# * Symbols not directly imported in this module but accessed via an attribute
2414+
# or via a re-export (vast majority of these recorded in semantic analysis).
2415+
# * For each expression type we need to record definitions of type components
2416+
# since "meaning" of the type may be updated when definitions are updated.
2417+
self.tree.module_refs | self.type_checker().module_refs,
2418+
set(self.type_map().values()),
24352419
)
24362420

24372421
if self.options.dump_inference_stats:

mypy/checker.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,11 +378,9 @@ class TypeChecker(NodeVisitor[None], TypeCheckerSharedApi):
378378
inferred_attribute_types: dict[Var, Type] | None = None
379379
# Don't infer partial None types if we are processing assignment from Union
380380
no_partial_types: bool = False
381-
382-
# The set of all dependencies (suppressed or not) that this module accesses, either
383-
# directly or indirectly.
381+
# Extra module references not detected during semantic analysis (these are rare cases
382+
# e.g. access to class-level import via instance).
384383
module_refs: set[str]
385-
386384
# A map from variable nodes to a snapshot of the frame ids of the
387385
# frames that were active when the variable was declared. This can
388386
# be used to determine nearest common ancestor frame of a variable's

mypy/checkexpr.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@
198198
)
199199
from mypy.typestate import type_state
200200
from mypy.typevars import fill_typevars
201-
from mypy.util import split_module_names
202201
from mypy.visitor import ExpressionVisitor
203202

204203
# Type of callback user for checking individual function arguments. See
@@ -248,36 +247,6 @@ def allow_fast_container_literal(t: Type) -> bool:
248247
)
249248

250249

251-
def extract_refexpr_names(expr: RefExpr, output: set[str]) -> None:
252-
"""Recursively extracts all module references from a reference expression.
253-
254-
Note that currently, the only two subclasses of RefExpr are NameExpr and
255-
MemberExpr."""
256-
while isinstance(expr.node, MypyFile) or expr.fullname:
257-
if isinstance(expr.node, MypyFile) and expr.fullname:
258-
# If it's None, something's wrong (perhaps due to an
259-
# import cycle or a suppressed error). For now we just
260-
# skip it.
261-
output.add(expr.fullname)
262-
263-
if isinstance(expr, NameExpr):
264-
is_suppressed_import = isinstance(expr.node, Var) and expr.node.is_suppressed_import
265-
if isinstance(expr.node, TypeInfo):
266-
# Reference to a class or a nested class
267-
output.update(split_module_names(expr.node.module_name))
268-
elif "." in expr.fullname and not is_suppressed_import:
269-
# Everything else (that is not a silenced import within a class)
270-
output.add(expr.fullname.rsplit(".", 1)[0])
271-
break
272-
elif isinstance(expr, MemberExpr):
273-
if isinstance(expr.expr, RefExpr):
274-
expr = expr.expr
275-
else:
276-
break
277-
else:
278-
raise AssertionError(f"Unknown RefExpr subclass: {type(expr)}")
279-
280-
281250
class Finished(Exception):
282251
"""Raised if we can terminate overload argument check early (no match)."""
283252

@@ -370,7 +339,6 @@ def visit_name_expr(self, e: NameExpr) -> Type:
370339
371340
It can be of any kind: local, member or global.
372341
"""
373-
extract_refexpr_names(e, self.chk.module_refs)
374342
result = self.analyze_ref_expr(e)
375343
narrowed = self.narrow_type_from_binder(e, result)
376344
self.chk.check_deprecated(e.node, e)
@@ -3344,7 +3312,6 @@ def check_union_call(
33443312

33453313
def visit_member_expr(self, e: MemberExpr, is_lvalue: bool = False) -> Type:
33463314
"""Visit member expression (of form e.id)."""
3347-
extract_refexpr_names(e, self.chk.module_refs)
33483315
result = self.analyze_ordinary_member_access(e, is_lvalue)
33493316
narrowed = self.narrow_type_from_binder(e, result)
33503317
self.chk.warn_deprecated(e.node, e)

mypy/checkmember.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ def analyze_member_var_access(
543543
if isinstance(v, FuncDef):
544544
assert False, "Did not expect a function"
545545
if isinstance(v, MypyFile):
546+
# Special case: accessing module on instances is allowed, but will not
547+
# be recorded by semantic analyzer.
546548
mx.chk.module_refs.add(v.fullname)
547549

548550
if isinstance(vv, (TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)):

mypy/indirection.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,6 @@
77
from mypy.util import split_module_names
88

99

10-
def extract_module_names(type_name: str | None) -> list[str]:
11-
"""Returns the module names of a fully qualified type name."""
12-
if type_name is not None:
13-
# Discard the first one, which is just the qualified name of the type
14-
possible_module_names = split_module_names(type_name)
15-
return possible_module_names[1:]
16-
else:
17-
return []
18-
19-
2010
class TypeIndirectionVisitor(TypeVisitor[None]):
2111
"""Returns all module references within a particular type."""
2212

@@ -25,12 +15,9 @@ def __init__(self) -> None:
2515
self.modules: set[str] = set()
2616
# User to avoid infinite recursion with recursive types
2717
self.seen_types: set[types.TypeAliasType | types.Instance] = set()
28-
# Used to avoid redundant work
29-
self.seen_fullnames: set[str] = set()
3018

3119
def find_modules(self, typs: Iterable[types.Type]) -> set[str]:
3220
self.modules = set()
33-
self.seen_fullnames = set()
3421
self.seen_types = set()
3522
for typ in typs:
3623
self._visit(typ)
@@ -145,11 +132,7 @@ def visit_instance(self, t: types.Instance) -> None:
145132
def visit_callable_type(self, t: types.CallableType) -> None:
146133
self._visit_type_list(t.arg_types)
147134
self._visit(t.ret_type)
148-
if t.definition is not None:
149-
fullname = t.definition.fullname
150-
if fullname not in self.seen_fullnames:
151-
self.modules.update(extract_module_names(t.definition.fullname))
152-
self.seen_fullnames.add(fullname)
135+
self._visit_type_tuple(t.variables)
153136

154137
def visit_overloaded(self, t: types.Overloaded) -> None:
155138
for item in t.items:

mypy/nodes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ class MypyFile(SymbolNode):
288288
"path",
289289
"defs",
290290
"alias_deps",
291-
"mod_alias_deps",
291+
"module_refs",
292292
"is_bom",
293293
"names",
294294
"imports",
@@ -312,8 +312,9 @@ class MypyFile(SymbolNode):
312312
defs: list[Statement]
313313
# Type alias dependencies as mapping from target to set of alias full names
314314
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]
315+
# The set of all dependencies (suppressed or not) that this module accesses, either
316+
# directly or indirectly.
317+
module_refs: set[str]
317318
# Is there a UTF-8 BOM at the start?
318319
is_bom: bool
319320
names: SymbolTable
@@ -354,7 +355,7 @@ def __init__(
354355
self.imports = imports
355356
self.is_bom = is_bom
356357
self.alias_deps = defaultdict(set)
357-
self.mod_alias_deps = set()
358+
self.module_refs = set()
358359
self.plugin_deps = {}
359360
if ignored_lines:
360361
self.ignored_lines = ignored_lines

mypy/semanal.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,14 @@
305305
)
306306
from mypy.types_utils import is_invalid_recursive_alias, store_argument_type
307307
from mypy.typevars import fill_typevars
308-
from mypy.util import correct_relative_import, is_dunder, module_prefix, unmangle, unnamed_function
308+
from mypy.util import (
309+
correct_relative_import,
310+
is_dunder,
311+
module_prefix,
312+
split_module_names,
313+
unmangle,
314+
unnamed_function,
315+
)
309316
from mypy.visitor import NodeVisitor
310317

311318
T = TypeVar("T")
@@ -2808,7 +2815,6 @@ def get_declared_metaclass(
28082815
and not sym.node.alias_tvars
28092816
):
28102817
target = get_proper_type(sym.node.target)
2811-
self.add_type_alias_deps({(sym.node.module, sym.node.fullname)})
28122818
if isinstance(target, Instance):
28132819
metaclass_info = target.type
28142820

@@ -3887,15 +3893,15 @@ def analyze_alias(
38873893
declared_type_vars: TypeVarLikeList | None = None,
38883894
all_declared_type_params_names: list[str] | None = None,
38893895
python_3_12_type_alias: bool = False,
3890-
) -> tuple[Type | None, list[TypeVarLikeType], set[tuple[str, str]], bool]:
3896+
) -> tuple[Type | None, list[TypeVarLikeType], set[str], bool]:
38913897
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
38923898
38933899
If yes, return the corresponding type, a list of type variables for generic aliases,
38943900
a set of names the alias depends on, and True if the original type has empty tuple index.
38953901
An example for the dependencies:
38963902
A = int
38973903
B = str
3898-
analyze_alias(dict[A, B])[2] == {('mod', 'mod.A'), ('mod', 'mod.B')}
3904+
analyze_alias(dict[A, B])[2] == {'__main__.A', '__main__.B'}
38993905
"""
39003906
dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
39013907
global_scope = not self.type and not self.function_stack
@@ -4040,7 +4046,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
40404046
if self.is_none_alias(rvalue):
40414047
res = NoneType()
40424048
alias_tvars: list[TypeVarLikeType] = []
4043-
depends_on: set[tuple[str, str]] = set()
4049+
depends_on: set[str] = set()
40444050
empty_tuple_index = False
40454051
else:
40464052
tag = self.track_incomplete_refs()
@@ -5929,6 +5935,8 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
59295935
if isinstance(sym.node, PlaceholderNode):
59305936
self.process_placeholder(expr.name, "attribute", expr)
59315937
return
5938+
if sym.node is not None:
5939+
self.record_imported_symbol(sym.node)
59325940
expr.kind = sym.kind
59335941
expr.fullname = sym.fullname or ""
59345942
expr.node = sym.node
@@ -5959,8 +5967,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
59595967
if type_info:
59605968
n = type_info.names.get(expr.name)
59615969
if n is not None and isinstance(n.node, (MypyFile, TypeInfo, TypeAlias)):
5962-
if not n:
5963-
return
5970+
self.record_imported_symbol(n.node)
59645971
expr.kind = n.kind
59655972
expr.fullname = n.fullname or ""
59665973
expr.node = n.node
@@ -6280,6 +6287,24 @@ def visit_class_pattern(self, p: ClassPattern) -> None:
62806287

62816288
def lookup(
62826289
self, name: str, ctx: Context, suppress_errors: bool = False
6290+
) -> SymbolTableNode | None:
6291+
node = self._lookup(name, ctx, suppress_errors)
6292+
if node is not None and node.node is not None:
6293+
# This call is unfortunate from performance point of view, but
6294+
# needed for rare cases like e.g. testIncrementalChangingAlias.
6295+
self.record_imported_symbol(node.node)
6296+
return node
6297+
6298+
def record_imported_symbol(self, node: SymbolNode) -> None:
6299+
fullname = node.fullname
6300+
if not isinstance(node, MypyFile):
6301+
while fullname not in self.modules and "." in fullname:
6302+
fullname = fullname.rsplit(".")[0]
6303+
if fullname != self.cur_mod_id and fullname not in self.cur_mod_node.module_refs:
6304+
self.cur_mod_node.module_refs.update(split_module_names(fullname))
6305+
6306+
def _lookup(
6307+
self, name: str, ctx: Context, suppress_errors: bool = False
62836308
) -> SymbolTableNode | None:
62846309
"""Look up an unqualified (no dots) name in all active namespaces.
62856310
@@ -6488,6 +6513,8 @@ def lookup_qualified(
64886513
self.name_not_defined(name, ctx, namespace=namespace)
64896514
return None
64906515
sym = nextsym
6516+
if sym is not None and sym.node is not None:
6517+
self.record_imported_symbol(sym.node)
64916518
return sym
64926519

64936520
def lookup_type_node(self, expr: Expression) -> SymbolTableNode | None:
@@ -7526,21 +7553,18 @@ def add_plugin_dependency(self, trigger: str, target: str | None = None) -> None
75267553
self.cur_mod_node.plugin_deps.setdefault(trigger, set()).add(target)
75277554

75287555
def add_type_alias_deps(
7529-
self, aliases_used: Collection[tuple[str, str]], target: str | None = None
7556+
self, aliases_used: Collection[str], target: str | None = None
75307557
) -> None:
75317558
"""Add full names of type aliases on which the current node depends.
75327559
75337560
This is used by fine-grained incremental mode to re-check the corresponding nodes.
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.
7561+
If `target` is None, then the target node used will be the current scope.
75367562
"""
75377563
if not aliases_used:
75387564
return
75397565
if target is None:
75407566
target = self.scope.current_target()
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)
7567+
self.cur_mod_node.alias_deps[target].update(aliases_used)
75447568

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

mypy/typeanal.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ def analyze_type_alias(
155155
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
156156
alias_type_params_names: list[str] | None = None,
157157
python_3_12_type_alias: bool = False,
158-
) -> tuple[Type, set[tuple[str, str]]]:
158+
) -> tuple[Type, set[str]]:
159159
"""Analyze r.h.s. of a (potential) type alias definition.
160160
161161
If `node` is valid as a type alias rvalue, return the resulting type and a set of
162-
module and full names of type aliases it depends on (directly or indirectly).
162+
full names of type aliases it depends on (directly or indirectly).
163163
'node' must have been semantically analyzed.
164164
"""
165165
analyzer = TypeAnalyser(
@@ -263,9 +263,8 @@ def __init__(
263263
self.options = options
264264
self.cur_mod_node = cur_mod_node
265265
self.is_typeshed_stub = is_typeshed_stub
266-
# Names of type aliases encountered while analysing a type will be collected here
267-
# (each tuple in the set is (module_name, fullname)).
268-
self.aliases_used: set[tuple[str, str]] = set()
266+
# Names of type aliases encountered while analysing a type will be collected here.
267+
self.aliases_used: set[str] = set()
269268
self.prohibit_self_type = prohibit_self_type
270269
# Set when we analyze TypedDicts or NamedTuples, since they are special:
271270
self.prohibit_special_class_field_types = prohibit_special_class_field_types
@@ -458,7 +457,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
458457
if special is not None:
459458
return special
460459
if isinstance(node, TypeAlias):
461-
self.aliases_used.add((node.module, fullname))
460+
self.aliases_used.add(fullname)
462461
an_args = self.anal_array(
463462
t.args,
464463
allow_param_spec=True,

0 commit comments

Comments
 (0)