Skip to content

Commit 555bfae

Browse files
Enable warn_unreachable for mypy self-check (#18523)
This check prooved to be useful, since it find a lot of dead / incorrect code. Closes #18079 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1ec3f44 commit 555bfae

23 files changed

+85
-143
lines changed

mypy/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ def read_plugins_snapshot(manager: BuildManager) -> dict[str, str] | None:
10691069
if snapshot is None:
10701070
return None
10711071
if not isinstance(snapshot, dict):
1072-
manager.log(f"Could not load plugins snapshot: cache is not a dict: {type(snapshot)}")
1072+
manager.log(f"Could not load plugins snapshot: cache is not a dict: {type(snapshot)}") # type: ignore[unreachable]
10731073
return None
10741074
return snapshot
10751075

@@ -1285,7 +1285,7 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> CacheMeta | No
12851285
if meta is None:
12861286
return None
12871287
if not isinstance(meta, dict):
1288-
manager.log(f"Could not load cache for {id}: meta cache is not a dict: {repr(meta)}")
1288+
manager.log(f"Could not load cache for {id}: meta cache is not a dict: {repr(meta)}") # type: ignore[unreachable]
12891289
return None
12901290
m = cache_meta_from_dict(meta, data_json)
12911291
t2 = time.time()

mypy/checker.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
LITERAL_TYPE,
5757
MDEF,
5858
NOT_ABSTRACT,
59+
SYMBOL_FUNCBASE_TYPES,
5960
AssertStmt,
6061
AssignmentExpr,
6162
AssignmentStmt,
@@ -2865,7 +2866,7 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
28652866
def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None:
28662867
if sym.type is not None:
28672868
return sym.type
2868-
if isinstance(sym.node, FuncBase):
2869+
if isinstance(sym.node, SYMBOL_FUNCBASE_TYPES):
28692870
return self.function_type(sym.node)
28702871
if isinstance(sym.node, TypeInfo):
28712872
if sym.node.typeddict_type:
@@ -4459,7 +4460,9 @@ def simple_rvalue(self, rvalue: Expression) -> bool:
44594460
if isinstance(rvalue, (IntExpr, StrExpr, BytesExpr, FloatExpr, RefExpr)):
44604461
return True
44614462
if isinstance(rvalue, CallExpr):
4462-
if isinstance(rvalue.callee, RefExpr) and isinstance(rvalue.callee.node, FuncBase):
4463+
if isinstance(rvalue.callee, RefExpr) and isinstance(
4464+
rvalue.callee.node, SYMBOL_FUNCBASE_TYPES
4465+
):
44634466
typ = rvalue.callee.node.type
44644467
if isinstance(typ, CallableType):
44654468
return not typ.variables

mypy/checkexpr.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,8 +2580,6 @@ def check_argument_types(
25802580
for actual, actual_type, actual_kind, callee_arg_type, callee_arg_kind in zip(
25812581
actuals, actual_types, actual_kinds, callee_arg_types, callee_arg_kinds
25822582
):
2583-
if actual_type is None:
2584-
continue # Some kind of error was already reported.
25852583
# Check that a *arg is valid as varargs.
25862584
expanded_actual = mapper.expand_actual_type(
25872585
actual_type,

mypy/checkmember.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,10 +1095,10 @@ def analyze_class_attribute_access(
10951095
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})
10961096

10971097
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
1098-
isinstance(node.node, FuncBase) and node.node.is_class
1098+
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class
10991099
)
11001100
is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or (
1101-
isinstance(node.node, FuncBase) and node.node.is_static
1101+
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_static
11021102
)
11031103
t = get_proper_type(t)
11041104
if isinstance(t, FunctionLike) and is_classmethod:
@@ -1148,7 +1148,7 @@ def analyze_class_attribute_access(
11481148
mx.not_ready_callback(name, mx.context)
11491149
return AnyType(TypeOfAny.from_error)
11501150
else:
1151-
assert isinstance(node.node, FuncBase)
1151+
assert isinstance(node.node, SYMBOL_FUNCBASE_TYPES)
11521152
typ = function_type(node.node, mx.named_type("builtins.function"))
11531153
# Note: if we are accessing class method on class object, the cls argument is bound.
11541154
# Annotated and/or explicit class methods go through other code paths above, for
@@ -1427,7 +1427,7 @@ def is_valid_constructor(n: SymbolNode | None) -> bool:
14271427
This includes normal functions, overloaded functions, and decorators
14281428
that return a callable type.
14291429
"""
1430-
if isinstance(n, FuncBase):
1430+
if isinstance(n, SYMBOL_FUNCBASE_TYPES):
14311431
return True
14321432
if isinstance(n, Decorator):
14331433
return isinstance(get_proper_type(n.type), FunctionLike)

mypy/constraints.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,12 @@ def infer_constraints_for_callable(
127127
param_spec_arg_kinds = []
128128

129129
incomplete_star_mapping = False
130-
for i, actuals in enumerate(formal_to_actual):
130+
for i, actuals in enumerate(formal_to_actual): # TODO: isn't this `enumerate(arg_types)`?
131131
for actual in actuals:
132-
if actual is None and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2):
132+
if actual is None and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2): # type: ignore[unreachable]
133133
# We can't use arguments to infer ParamSpec constraint, if only some
134134
# are present in the current inference pass.
135-
incomplete_star_mapping = True
135+
incomplete_star_mapping = True # type: ignore[unreachable]
136136
break
137137

138138
for i, actuals in enumerate(formal_to_actual):
@@ -545,11 +545,7 @@ def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list
545545
for option in valid_options:
546546
if option in trivial_options:
547547
continue
548-
if option is not None:
549-
merged_option: list[Constraint] | None = [merge_with_any(c) for c in option]
550-
else:
551-
merged_option = None
552-
merged_options.append(merged_option)
548+
merged_options.append([merge_with_any(c) for c in option])
553549
return any_constraints(list(merged_options), eager)
554550

555551
# If normal logic didn't work, try excluding trivially unsatisfiable constraint (due to

mypy/errors.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from mypy import errorcodes as codes
1212
from mypy.error_formatter import ErrorFormatter
1313
from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode, mypy_error_codes
14-
from mypy.message_registry import ErrorMessage
1514
from mypy.options import Options
1615
from mypy.scope import Scope
1716
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
@@ -1069,34 +1068,19 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]:
10691068
(file, -1, -1, -1, -1, "note", f'In class "{e.type}":', e.allow_dups, None)
10701069
)
10711070

1072-
if isinstance(e.message, ErrorMessage):
1073-
result.append(
1074-
(
1075-
file,
1076-
e.line,
1077-
e.column,
1078-
e.end_line,
1079-
e.end_column,
1080-
e.severity,
1081-
e.message.value,
1082-
e.allow_dups,
1083-
e.code,
1084-
)
1085-
)
1086-
else:
1087-
result.append(
1088-
(
1089-
file,
1090-
e.line,
1091-
e.column,
1092-
e.end_line,
1093-
e.end_column,
1094-
e.severity,
1095-
e.message,
1096-
e.allow_dups,
1097-
e.code,
1098-
)
1071+
result.append(
1072+
(
1073+
file,
1074+
e.line,
1075+
e.column,
1076+
e.end_line,
1077+
e.end_column,
1078+
e.severity,
1079+
e.message,
1080+
e.allow_dups,
1081+
e.code,
10991082
)
1083+
)
11001084

11011085
prev_import_context = e.import_ctx
11021086
prev_function_or_member = e.function_or_member

mypy/messages.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,12 +2151,8 @@ def report_protocol_problems(
21512151
is_module = False
21522152
skip = []
21532153
if isinstance(subtype, TupleType):
2154-
if not isinstance(subtype.partial_fallback, Instance):
2155-
return
21562154
subtype = subtype.partial_fallback
21572155
elif isinstance(subtype, TypedDictType):
2158-
if not isinstance(subtype.fallback, Instance):
2159-
return
21602156
subtype = subtype.fallback
21612157
elif isinstance(subtype, TypeType):
21622158
if not isinstance(subtype.item, Instance):

mypy/nodes.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,7 @@ class Node(Context):
176176
__slots__ = ()
177177

178178
def __str__(self) -> str:
179-
a = self.accept(mypy.strconv.StrConv(options=Options()))
180-
if a is None:
181-
return repr(self)
182-
return a
179+
return self.accept(mypy.strconv.StrConv(options=Options()))
183180

184181
def str_with_options(self, options: Options) -> str:
185182
a = self.accept(mypy.strconv.StrConv(options=options))
@@ -875,7 +872,9 @@ def deserialize(cls, data: JsonDict) -> FuncDef:
875872

876873
# All types that are both SymbolNodes and FuncBases. See the FuncBase
877874
# docstring for the rationale.
878-
SYMBOL_FUNCBASE_TYPES = (OverloadedFuncDef, FuncDef)
875+
# See https://github.com/python/mypy/pull/13607#issuecomment-1236357236
876+
# TODO: we want to remove this at some point and just use `FuncBase` ideally.
877+
SYMBOL_FUNCBASE_TYPES: Final = (OverloadedFuncDef, FuncDef)
879878

880879

881880
class Decorator(SymbolNode, Statement):
@@ -2575,6 +2574,11 @@ def fullname(self) -> str:
25752574
return self._fullname
25762575

25772576

2577+
# All types that are both SymbolNodes and Expressions.
2578+
# Use when common children of them are needed.
2579+
SYMBOL_NODE_EXPRESSION_TYPES: Final = (TypeVarLikeExpr,)
2580+
2581+
25782582
class TypeVarExpr(TypeVarLikeExpr):
25792583
"""Type variable expression TypeVar(...).
25802584
@@ -3273,7 +3277,7 @@ def get_method(self, name: str) -> FuncBase | Decorator | None:
32733277
for cls in self.mro:
32743278
if name in cls.names:
32753279
node = cls.names[name].node
3276-
if isinstance(node, FuncBase):
3280+
if isinstance(node, SYMBOL_FUNCBASE_TYPES):
32773281
return node
32783282
elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy
32793283
return node
@@ -4032,7 +4036,8 @@ def __str__(self) -> str:
40324036
):
40334037
a.append(" " + str(key) + " : " + str(value))
40344038
else:
4035-
a.append(" <invalid item>")
4039+
# Used in debugging:
4040+
a.append(" <invalid item>") # type: ignore[unreachable]
40364041
a = sorted(a)
40374042
a.insert(0, "SymbolTable(")
40384043
a[-1] += ")"

mypy/plugins/functools.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@
88
import mypy.plugin
99
import mypy.semanal
1010
from mypy.argmap import map_actuals_to_formals
11-
from mypy.nodes import ARG_POS, ARG_STAR2, ArgKind, Argument, CallExpr, FuncItem, NameExpr, Var
11+
from mypy.nodes import (
12+
ARG_POS,
13+
ARG_STAR2,
14+
SYMBOL_FUNCBASE_TYPES,
15+
ArgKind,
16+
Argument,
17+
CallExpr,
18+
NameExpr,
19+
Var,
20+
)
1221
from mypy.plugins.common import add_method_to_class
1322
from mypy.typeops import get_all_type_vars
1423
from mypy.types import (
@@ -108,7 +117,7 @@ def _analyze_class(ctx: mypy.plugin.ClassDefContext) -> dict[str, _MethodInfo |
108117
for name in _ORDERING_METHODS:
109118
if name in cls.names and name not in comparison_methods:
110119
node = cls.names[name].node
111-
if isinstance(node, FuncItem) and isinstance(node.type, CallableType):
120+
if isinstance(node, SYMBOL_FUNCBASE_TYPES) and isinstance(node.type, CallableType):
112121
comparison_methods[name] = _MethodInfo(node.is_static, node.type)
113122
continue
114123

mypy/semanal.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
REVEAL_LOCALS,
8787
REVEAL_TYPE,
8888
RUNTIME_PROTOCOL_DECOS,
89+
SYMBOL_FUNCBASE_TYPES,
8990
TYPE_VAR_KIND,
9091
TYPE_VAR_TUPLE_KIND,
9192
VARIANCE_NOT_READY,
@@ -3082,8 +3083,6 @@ def visit_import_all(self, i: ImportAll) -> None:
30823083
for name, node in m.names.items():
30833084
fullname = i_id + "." + name
30843085
self.set_future_import_flags(fullname)
3085-
if node is None:
3086-
continue
30873086
# if '__all__' exists, all nodes not included have had module_public set to
30883087
# False, and we can skip checking '_' because it's been explicitly included.
30893088
if node.module_public and (not name.startswith("_") or "__all__" in m.names):
@@ -5719,7 +5718,7 @@ def visit_call_expr(self, expr: CallExpr) -> None:
57195718
reveal_type_node = self.lookup("reveal_type", expr, suppress_errors=True)
57205719
if (
57215720
reveal_type_node
5722-
and isinstance(reveal_type_node.node, FuncBase)
5721+
and isinstance(reveal_type_node.node, SYMBOL_FUNCBASE_TYPES)
57235722
and reveal_type_node.fullname in IMPORTED_REVEAL_TYPE_NAMES
57245723
):
57255724
reveal_imported = True

0 commit comments

Comments
 (0)