Skip to content

Commit c21c721

Browse files
authored
Merge branch 'master' into bugfix/gh-19686-enum-unanotated
2 parents f311cc0 + dce8e1c commit c21c721

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1786
-892
lines changed

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ on:
1212
# so it's important to do the docs build on all PRs touching mypy/errorcodes.py
1313
# in case somebody's adding a new error code without any docs
1414
- 'mypy/errorcodes.py'
15+
# Part of the documentation is automatically generated from the options
16+
# definitions in mypy/main.py
17+
- 'mypy/main.py'
1518
- 'mypyc/doc/**'
1619
- '**/*.rst'
1720
- '**/*.md'

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: 4 additions & 6 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
@@ -5682,8 +5680,8 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
56825680
self.push_type_map(else_map, from_assignment=False)
56835681
unmatched_types = else_map
56845682

5685-
if unmatched_types is not None:
5686-
for typ in list(unmatched_types.values()):
5683+
if unmatched_types is not None and not self.current_node_deferred:
5684+
for typ in unmatched_types.values():
56875685
self.msg.match_statement_inexhaustive_match(typ, s)
56885686

56895687
# This is needed due to a quirk in frame_context. Without it types will stay narrowed

mypy/checkexpr.py

Lines changed: 1 addition & 34 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)
@@ -6435,7 +6402,7 @@ def visit_type_var(self, t: TypeVarType) -> bool:
64356402

64366403
def visit_param_spec(self, t: ParamSpecType) -> bool:
64376404
default = [t.default] if t.has_default() else []
6438-
return self.query_types([t.upper_bound, *default])
6405+
return self.query_types([t.upper_bound, *default, t.prefix])
64396406

64406407
def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool:
64416408
default = [t.default] if t.has_default() else []

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
222222
return t
223223

224224
def visit_param_spec(self, t: ParamSpecType) -> Type:
225+
# TODO: we should probably preserve prefix here.
225226
if self.erase_id is None or self.erase_id(t.id):
226227
return self.replacement
227228
return t

mypy/fixup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ def visit_type_var(self, tvt: TypeVarType) -> None:
347347
def visit_param_spec(self, p: ParamSpecType) -> None:
348348
p.upper_bound.accept(self)
349349
p.default.accept(self)
350+
p.prefix.accept(self)
350351

351352
def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
352353
t.tuple_fallback.accept(self)
@@ -440,4 +441,4 @@ def missing_info(modules: dict[str, MypyFile]) -> TypeInfo:
440441

441442
def missing_alias() -> TypeAlias:
442443
suggestion = _SUGGESTION.format("alias")
443-
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: 57 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

@@ -93,6 +89,7 @@ def visit_type_var(self, t: types.TypeVarType) -> None:
9389
def visit_param_spec(self, t: types.ParamSpecType) -> None:
9490
self._visit(t.upper_bound)
9591
self._visit(t.default)
92+
self._visit(t.prefix)
9693

9794
def visit_type_var_tuple(self, t: types.TypeVarTupleType) -> None:
9895
self._visit(t.upper_bound)
@@ -105,27 +102,36 @@ def visit_parameters(self, t: types.Parameters) -> None:
105102
self._visit_type_list(t.arg_types)
106103

107104
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.
108111
self._visit_type_tuple(t.args)
109112
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.
113+
# Important optimization: instead of simply recording the definition and
114+
# recursing into bases, record the MRO and only traverse generic bases.
113115
for s in t.type.mro:
114-
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)
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)
117126

118127
def visit_callable_type(self, t: types.CallableType) -> None:
119128
self._visit_type_list(t.arg_types)
120129
self._visit(t.ret_type)
121-
if t.definition is not None:
122-
fullname = t.definition.fullname
123-
if fullname not in self.seen_fullnames:
124-
self.modules.update(extract_module_names(t.definition.fullname))
125-
self.seen_fullnames.add(fullname)
130+
self._visit_type_tuple(t.variables)
126131

127132
def visit_overloaded(self, t: types.Overloaded) -> None:
128-
self._visit_type_list(list(t.items))
133+
for item in t.items:
134+
self._visit(item)
129135
self._visit(t.fallback)
130136

131137
def visit_tuple_type(self, t: types.TupleType) -> None:
@@ -149,4 +155,9 @@ def visit_type_type(self, t: types.TypeType) -> None:
149155
self._visit(t.item)
150156

151157
def visit_type_alias_type(self, t: types.TypeAliasType) -> None:
152-
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)

0 commit comments

Comments
 (0)