Skip to content

Commit 26db7f5

Browse files
Merge branch 'master' into for-filter
2 parents a3b65b3 + 8bfecd4 commit 26db7f5

34 files changed

+1566
-841
lines changed

CHANGELOG.md

Lines changed: 316 additions & 0 deletions
Large diffs are not rendered by default.

mypy/build.py

Lines changed: 97 additions & 138 deletions
Large diffs are not rendered by default.

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/constraints.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ArgKind,
2222
TypeInfo,
2323
)
24+
from mypy.type_visitor import ALL_STRATEGY, BoolTypeQuery
2425
from mypy.types import (
2526
TUPLE_LIKE_INSTANCE_NAMES,
2627
AnyType,
@@ -41,7 +42,6 @@
4142
TypeAliasType,
4243
TypedDictType,
4344
TypeOfAny,
44-
TypeQuery,
4545
TypeType,
4646
TypeVarId,
4747
TypeVarLikeType,
@@ -670,9 +670,9 @@ def is_complete_type(typ: Type) -> bool:
670670
return typ.accept(CompleteTypeVisitor())
671671

672672

673-
class CompleteTypeVisitor(TypeQuery[bool]):
673+
class CompleteTypeVisitor(BoolTypeQuery):
674674
def __init__(self) -> None:
675-
super().__init__(all)
675+
super().__init__(ALL_STRATEGY)
676676

677677
def visit_uninhabited_type(self, t: UninhabitedType) -> bool:
678678
return False

mypy/fixup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,4 +441,4 @@ def missing_info(modules: dict[str, MypyFile]) -> TypeInfo:
441441

442442
def missing_alias() -> TypeAlias:
443443
suggestion = _SUGGESTION.format("alias")
444-
return TypeAlias(AnyType(TypeOfAny.special_form), suggestion, line=-1, column=-1)
444+
return TypeAlias(AnyType(TypeOfAny.special_form), suggestion, "<missing>", line=-1, column=-1)

mypy/indirection.py

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,6 @@
44

55
import mypy.types as types
66
from mypy.types import TypeVisitor
7-
from mypy.util import split_module_names
8-
9-
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 []
187

198

209
class TypeIndirectionVisitor(TypeVisitor[None]):
@@ -23,50 +12,57 @@ class TypeIndirectionVisitor(TypeVisitor[None]):
2312
def __init__(self) -> None:
2413
# Module references are collected here
2514
self.modules: set[str] = set()
26-
# User to avoid infinite recursion with recursive type aliases
27-
self.seen_aliases: set[types.TypeAliasType] = set()
28-
# Used to avoid redundant work
29-
self.seen_fullnames: set[str] = set()
15+
# User to avoid infinite recursion with recursive types
16+
self.seen_types: set[types.TypeAliasType | types.Instance] = set()
3017

3118
def find_modules(self, typs: Iterable[types.Type]) -> set[str]:
3219
self.modules = set()
33-
self.seen_fullnames = set()
34-
self.seen_aliases = set()
20+
self.seen_types = set()
3521
for typ in typs:
3622
self._visit(typ)
3723
return self.modules
3824

3925
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)
26+
# Note: instances are needed for `class str(Sequence[str]): ...`
27+
if (
28+
isinstance(typ, types.TypeAliasType)
29+
or isinstance(typ, types.ProperType)
30+
and isinstance(typ, types.Instance)
31+
):
32+
# Avoid infinite recursion for recursive types.
33+
if typ in self.seen_types:
34+
return
35+
self.seen_types.add(typ)
4436
typ.accept(self)
4537

4638
def _visit_type_tuple(self, typs: tuple[types.Type, ...]) -> None:
4739
# Micro-optimization: Specialized version of _visit for lists
4840
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:
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:
5248
continue
53-
self.seen_aliases.add(typ)
49+
self.seen_types.add(typ)
5450
typ.accept(self)
5551

5652
def _visit_type_list(self, typs: list[types.Type]) -> None:
5753
# Micro-optimization: Specialized version of _visit for tuples
5854
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:
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:
6262
continue
63-
self.seen_aliases.add(typ)
63+
self.seen_types.add(typ)
6464
typ.accept(self)
6565

66-
def _visit_module_name(self, module_name: str) -> None:
67-
if module_name not in self.modules:
68-
self.modules.update(split_module_names(module_name))
69-
7066
def visit_unbound_type(self, t: types.UnboundType) -> None:
7167
self._visit_type_tuple(t.args)
7268

@@ -106,27 +102,36 @@ def visit_parameters(self, t: types.Parameters) -> None:
106102
self._visit_type_list(t.arg_types)
107103

108104
def visit_instance(self, t: types.Instance) -> None:
105+
# Instance is named, record its definition and continue digging into
106+
# components that constitute semantic meaning of this type: bases, metaclass,
107+
# tuple type, and typeddict type.
108+
# Note: we cannot simply record the MRO, in case an intermediate base contains
109+
# a reference to type alias, this affects meaning of map_instance_to_supertype(),
110+
# see e.g. testDoubleReexportGenericUpdated.
109111
self._visit_type_tuple(t.args)
110112
if t.type:
111-
# Uses of a class depend on everything in the MRO,
112-
# as changes to classes in the MRO can add types to methods,
113-
# change property types, change the MRO itself, etc.
113+
# Important optimization: instead of simply recording the definition and
114+
# recursing into bases, record the MRO and only traverse generic bases.
114115
for s in t.type.mro:
115-
self._visit_module_name(s.module_name)
116-
if t.type.metaclass_type is not None:
117-
self._visit_module_name(t.type.metaclass_type.type.module_name)
116+
self.modules.add(s.module_name)
117+
for base in s.bases:
118+
if base.args:
119+
self._visit_type_tuple(base.args)
120+
if t.type.metaclass_type:
121+
self._visit(t.type.metaclass_type)
122+
if t.type.typeddict_type:
123+
self._visit(t.type.typeddict_type)
124+
if t.type.tuple_type:
125+
self._visit(t.type.tuple_type)
118126

119127
def visit_callable_type(self, t: types.CallableType) -> None:
120128
self._visit_type_list(t.arg_types)
121129
self._visit(t.ret_type)
122-
if t.definition is not None:
123-
fullname = t.definition.fullname
124-
if fullname not in self.seen_fullnames:
125-
self.modules.update(extract_module_names(t.definition.fullname))
126-
self.seen_fullnames.add(fullname)
130+
self._visit_type_tuple(t.variables)
127131

128132
def visit_overloaded(self, t: types.Overloaded) -> None:
129-
self._visit_type_list(list(t.items))
133+
for item in t.items:
134+
self._visit(item)
130135
self._visit(t.fallback)
131136

132137
def visit_tuple_type(self, t: types.TupleType) -> None:
@@ -150,4 +155,9 @@ def visit_type_type(self, t: types.TypeType) -> None:
150155
self._visit(t.item)
151156

152157
def visit_type_alias_type(self, t: types.TypeAliasType) -> None:
153-
self._visit(types.get_proper_type(t))
158+
# Type alias is named, record its definition and continue digging into
159+
# components that constitute semantic meaning of this type: target and args.
160+
if t.alias:
161+
self.modules.add(t.alias.module)
162+
self._visit(t.alias.target)
163+
self._visit_type_list(t.args)

mypy/main.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,13 @@ def add_invertible_flag(
10641064
help="Include fine-grained dependency information in the cache for the mypy daemon",
10651065
)
10661066
incremental_group.add_argument(
1067-
"--fixed-format-cache", action="store_true", help=argparse.SUPPRESS
1067+
"--fixed-format-cache",
1068+
action="store_true",
1069+
help=(
1070+
"Use experimental fast and compact fixed format cache"
1071+
if compilation_status == "yes"
1072+
else argparse.SUPPRESS
1073+
),
10681074
)
10691075
incremental_group.add_argument(
10701076
"--skip-version-check",
@@ -1198,13 +1204,6 @@ def add_invertible_flag(
11981204
)
11991205

12001206
if server_options:
1201-
# TODO: This flag is superfluous; remove after a short transition (2018-03-16)
1202-
other_group.add_argument(
1203-
"--experimental",
1204-
action="store_true",
1205-
dest="fine_grained_incremental",
1206-
help="Enable fine-grained incremental mode",
1207-
)
12081207
other_group.add_argument(
12091208
"--use-fine-grained-cache",
12101209
action="store_true",

mypy/mixedtraverser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def visit_class_def(self, o: ClassDef, /) -> None:
4747
if info:
4848
for base in info.bases:
4949
base.accept(self)
50+
if info.special_alias:
51+
info.special_alias.accept(self)
5052

5153
def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None:
5254
super().visit_type_alias_expr(o)

0 commit comments

Comments
 (0)