Skip to content

Enable warn_unreachable for mypy self-check #18523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 14, 2025
4 changes: 2 additions & 2 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ def read_plugins_snapshot(manager: BuildManager) -> dict[str, str] | None:
if snapshot is None:
return None
if not isinstance(snapshot, dict):
manager.log(f"Could not load plugins snapshot: cache is not a dict: {type(snapshot)}")
manager.log(f"Could not load plugins snapshot: cache is not a dict: {type(snapshot)}") # type: ignore[unreachable]
return None
return snapshot

Expand Down Expand Up @@ -1285,7 +1285,7 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> CacheMeta | No
if meta is None:
return None
if not isinstance(meta, dict):
manager.log(f"Could not load cache for {id}: meta cache is not a dict: {repr(meta)}")
manager.log(f"Could not load cache for {id}: meta cache is not a dict: {repr(meta)}") # type: ignore[unreachable]
return None
m = cache_meta_from_dict(meta, data_json)
t2 = time.time()
Expand Down
7 changes: 5 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
LITERAL_TYPE,
MDEF,
NOT_ABSTRACT,
SYMBOL_FUNCBASE_TYPES,
AssertStmt,
AssignmentExpr,
AssignmentStmt,
Expand Down Expand Up @@ -2865,7 +2866,7 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None:
if sym.type is not None:
return sym.type
if isinstance(sym.node, FuncBase):
if isinstance(sym.node, SYMBOL_FUNCBASE_TYPES):
return self.function_type(sym.node)
if isinstance(sym.node, TypeInfo):
if sym.node.typeddict_type:
Expand Down Expand Up @@ -4459,7 +4460,9 @@ def simple_rvalue(self, rvalue: Expression) -> bool:
if isinstance(rvalue, (IntExpr, StrExpr, BytesExpr, FloatExpr, RefExpr)):
return True
if isinstance(rvalue, CallExpr):
if isinstance(rvalue.callee, RefExpr) and isinstance(rvalue.callee.node, FuncBase):
if isinstance(rvalue.callee, RefExpr) and isinstance(
rvalue.callee.node, SYMBOL_FUNCBASE_TYPES
):
typ = rvalue.callee.node.type
if isinstance(typ, CallableType):
return not typ.variables
Expand Down
2 changes: 0 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2580,8 +2580,6 @@ def check_argument_types(
for actual, actual_type, actual_kind, callee_arg_type, callee_arg_kind in zip(
actuals, actual_types, actual_kinds, callee_arg_types, callee_arg_kinds
):
if actual_type is None:
continue # Some kind of error was already reported.
# Check that a *arg is valid as varargs.
expanded_actual = mapper.expand_actual_type(
actual_type,
Expand Down
8 changes: 4 additions & 4 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,10 +1095,10 @@ def analyze_class_attribute_access(
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})

is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
isinstance(node.node, FuncBase) and node.node.is_class
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class
)
is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or (
isinstance(node.node, FuncBase) and node.node.is_static
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_static
)
t = get_proper_type(t)
if isinstance(t, FunctionLike) and is_classmethod:
Expand Down Expand Up @@ -1148,7 +1148,7 @@ def analyze_class_attribute_access(
mx.not_ready_callback(name, mx.context)
return AnyType(TypeOfAny.from_error)
else:
assert isinstance(node.node, FuncBase)
assert isinstance(node.node, SYMBOL_FUNCBASE_TYPES)
typ = function_type(node.node, mx.named_type("builtins.function"))
# Note: if we are accessing class method on class object, the cls argument is bound.
# Annotated and/or explicit class methods go through other code paths above, for
Expand Down Expand Up @@ -1427,7 +1427,7 @@ def is_valid_constructor(n: SymbolNode | None) -> bool:
This includes normal functions, overloaded functions, and decorators
that return a callable type.
"""
if isinstance(n, FuncBase):
if isinstance(n, SYMBOL_FUNCBASE_TYPES):
return True
if isinstance(n, Decorator):
return isinstance(get_proper_type(n.type), FunctionLike)
Expand Down
12 changes: 4 additions & 8 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ def infer_constraints_for_callable(
param_spec_arg_kinds = []

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

for i, actuals in enumerate(formal_to_actual):
Expand Down Expand Up @@ -545,11 +545,7 @@ def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list
for option in valid_options:
if option in trivial_options:
continue
if option is not None:
merged_option: list[Constraint] | None = [merge_with_any(c) for c in option]
else:
merged_option = None
merged_options.append(merged_option)
merged_options.append([merge_with_any(c) for c in option])
return any_constraints(list(merged_options), eager)

# If normal logic didn't work, try excluding trivially unsatisfiable constraint (due to
Expand Down
40 changes: 12 additions & 28 deletions mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from mypy import errorcodes as codes
from mypy.error_formatter import ErrorFormatter
from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode, mypy_error_codes
from mypy.message_registry import ErrorMessage
from mypy.options import Options
from mypy.scope import Scope
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
Expand Down Expand Up @@ -1069,34 +1068,19 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]:
(file, -1, -1, -1, -1, "note", f'In class "{e.type}":', e.allow_dups, None)
)

if isinstance(e.message, ErrorMessage):
result.append(
(
file,
e.line,
e.column,
e.end_line,
e.end_column,
e.severity,
e.message.value,
e.allow_dups,
e.code,
)
)
else:
result.append(
(
file,
e.line,
e.column,
e.end_line,
e.end_column,
e.severity,
e.message,
e.allow_dups,
e.code,
)
result.append(
(
file,
e.line,
e.column,
e.end_line,
e.end_column,
e.severity,
e.message,
e.allow_dups,
e.code,
)
)

prev_import_context = e.import_ctx
prev_function_or_member = e.function_or_member
Expand Down
4 changes: 0 additions & 4 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2151,12 +2151,8 @@ def report_protocol_problems(
is_module = False
skip = []
if isinstance(subtype, TupleType):
if not isinstance(subtype.partial_fallback, Instance):
return
subtype = subtype.partial_fallback
elif isinstance(subtype, TypedDictType):
if not isinstance(subtype.fallback, Instance):
return
subtype = subtype.fallback
elif isinstance(subtype, TypeType):
if not isinstance(subtype.item, Instance):
Expand Down
19 changes: 12 additions & 7 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,7 @@ class Node(Context):
__slots__ = ()

def __str__(self) -> str:
a = self.accept(mypy.strconv.StrConv(options=Options()))
if a is None:
return repr(self)
return a
return self.accept(mypy.strconv.StrConv(options=Options()))

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

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


class Decorator(SymbolNode, Statement):
Expand Down Expand Up @@ -2575,6 +2574,11 @@ def fullname(self) -> str:
return self._fullname


# All types that are both SymbolNodes and Expressions.
# Use when common children of them are needed.
SYMBOL_NODE_EXPRESSION_TYPES: Final = (TypeVarLikeExpr,)


class TypeVarExpr(TypeVarLikeExpr):
"""Type variable expression TypeVar(...).

Expand Down Expand Up @@ -3273,7 +3277,7 @@ def get_method(self, name: str) -> FuncBase | Decorator | None:
for cls in self.mro:
if name in cls.names:
node = cls.names[name].node
if isinstance(node, FuncBase):
if isinstance(node, SYMBOL_FUNCBASE_TYPES):
return node
elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy
return node
Expand Down Expand Up @@ -4032,7 +4036,8 @@ def __str__(self) -> str:
):
a.append(" " + str(key) + " : " + str(value))
else:
a.append(" <invalid item>")
# Used in debugging:
a.append(" <invalid item>") # type: ignore[unreachable]
a = sorted(a)
a.insert(0, "SymbolTable(")
a[-1] += ")"
Expand Down
13 changes: 11 additions & 2 deletions mypy/plugins/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
import mypy.plugin
import mypy.semanal
from mypy.argmap import map_actuals_to_formals
from mypy.nodes import ARG_POS, ARG_STAR2, ArgKind, Argument, CallExpr, FuncItem, NameExpr, Var
from mypy.nodes import (
ARG_POS,
ARG_STAR2,
SYMBOL_FUNCBASE_TYPES,
ArgKind,
Argument,
CallExpr,
NameExpr,
Var,
)
from mypy.plugins.common import add_method_to_class
from mypy.typeops import get_all_type_vars
from mypy.types import (
Expand Down Expand Up @@ -108,7 +117,7 @@ def _analyze_class(ctx: mypy.plugin.ClassDefContext) -> dict[str, _MethodInfo |
for name in _ORDERING_METHODS:
if name in cls.names and name not in comparison_methods:
node = cls.names[name].node
if isinstance(node, FuncItem) and isinstance(node.type, CallableType):
if isinstance(node, SYMBOL_FUNCBASE_TYPES) and isinstance(node.type, CallableType):
comparison_methods[name] = _MethodInfo(node.is_static, node.type)
continue

Expand Down
5 changes: 2 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
REVEAL_LOCALS,
REVEAL_TYPE,
RUNTIME_PROTOCOL_DECOS,
SYMBOL_FUNCBASE_TYPES,
TYPE_VAR_KIND,
TYPE_VAR_TUPLE_KIND,
VARIANCE_NOT_READY,
Expand Down Expand Up @@ -3082,8 +3083,6 @@ def visit_import_all(self, i: ImportAll) -> None:
for name, node in m.names.items():
fullname = i_id + "." + name
self.set_future_import_flags(fullname)
if node is None:
continue
# if '__all__' exists, all nodes not included have had module_public set to
# False, and we can skip checking '_' because it's been explicitly included.
if node.module_public and (not name.startswith("_") or "__all__" in m.names):
Expand Down Expand Up @@ -5719,7 +5718,7 @@ def visit_call_expr(self, expr: CallExpr) -> None:
reveal_type_node = self.lookup("reveal_type", expr, suppress_errors=True)
if (
reveal_type_node
and isinstance(reveal_type_node.node, FuncBase)
and isinstance(reveal_type_node.node, SYMBOL_FUNCBASE_TYPES)
and reveal_type_node.fullname in IMPORTED_REVEAL_TYPE_NAMES
):
reveal_imported = True
Expand Down
8 changes: 4 additions & 4 deletions mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'

from mypy.expandtype import expand_type
from mypy.nodes import (
SYMBOL_FUNCBASE_TYPES,
UNBOUND_IMPORTED,
Decorator,
FuncBase,
FuncDef,
FuncItem,
MypyFile,
Expand Down Expand Up @@ -234,16 +234,16 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
The representation is nested tuples and dicts. Only externally
visible attributes are included.
"""
if isinstance(node, FuncBase):
if isinstance(node, SYMBOL_FUNCBASE_TYPES):
# TODO: info
if node.type:
signature = snapshot_type(node.type)
signature: tuple[object, ...] = snapshot_type(node.type)
else:
signature = snapshot_untyped_signature(node)
impl: FuncDef | None = None
if isinstance(node, FuncDef):
impl = node
elif isinstance(node, OverloadedFuncDef) and node.impl:
elif node.impl:
impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl
setter_type = None
if isinstance(node, OverloadedFuncDef) and node.items:
Expand Down
3 changes: 2 additions & 1 deletion mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@

from mypy.nodes import (
MDEF,
SYMBOL_NODE_EXPRESSION_TYPES,
AssertTypeExpr,
AssignmentStmt,
Block,
Expand Down Expand Up @@ -301,7 +302,7 @@ def visit_super_expr(self, node: SuperExpr) -> None:

def visit_call_expr(self, node: CallExpr) -> None:
super().visit_call_expr(node)
if isinstance(node.analyzed, SymbolNode):
if isinstance(node.analyzed, SYMBOL_NODE_EXPRESSION_TYPES):
node.analyzed = self.fixup(node.analyzed)

def visit_newtype_expr(self, node: NewTypeExpr) -> None:
Expand Down
3 changes: 2 additions & 1 deletion mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a
GDEF,
LDEF,
MDEF,
SYMBOL_FUNCBASE_TYPES,
AssertTypeExpr,
AssignmentStmt,
AwaitExpr,
Expand Down Expand Up @@ -501,7 +502,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
if isinstance(rvalue.callee.node, TypeInfo):
# use actual __init__ as a dependency source
init = rvalue.callee.node.get("__init__")
if init and isinstance(init.node, FuncBase):
if init and isinstance(init.node, SYMBOL_FUNCBASE_TYPES):
fname = init.node.fullname
else:
fname = rvalue.callee.fullname
Expand Down
7 changes: 4 additions & 3 deletions mypy/server/mergecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ def check_consistency(o: object) -> None:
continue

fn = sym.fullname
# Skip None names, since they are ambiguous.
# Skip None and empty names, since they are ambiguous.
# TODO: Everything should have a proper full name?
if fn is None:
if not fn:
continue

# Skip stuff that should be expected to have duplicate names
if isinstance(sym, (Var, Decorator)):
continue
if isinstance(sym, FuncDef) and sym.is_overload:
continue

if fn not in m:
m[sym.fullname] = sym
m[fn] = sym
continue

# We have trouble and need to decide what to do about it.
Expand Down
4 changes: 1 addition & 3 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1504,9 +1504,7 @@ def is_blacklisted_path(path: str) -> bool:


def normalize_path_separators(path: str) -> str:
if sys.platform == "win32":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If #18539 or a similar fix is merged, then this change should no longer be necessary I believe.

return path.replace("\\", "/")
return path
return path.replace("\\", "/") if sys.platform == "win32" else path


def collect_build_targets(
Expand Down
Loading