From f45652d63d40c8bfb6a330d5545f70e5af782be2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Jan 2025 12:20:15 +0300 Subject: [PATCH 1/9] Enable `warn_unreachable` for `mypy` self-check --- mypy/build.py | 4 +-- mypy/checker.py | 5 ++-- mypy/checkexpr.py | 2 -- mypy/checkmember.py | 8 +++--- mypy/constraints.py | 35 +++++++------------------ mypy/errors.py | 39 +++++++++------------------- mypy/messages.py | 4 --- mypy/nodes.py | 16 +++++++----- mypy/plugins/default.py | 2 +- mypy/plugins/functools.py | 4 +-- mypy/semanal.py | 5 ++-- mypy/server/astdiff.py | 7 ++--- mypy/server/astmerge.py | 3 ++- mypy/server/deps.py | 3 ++- mypy/server/mergecheck.py | 8 ++---- mypy/stubtest.py | 6 +++-- mypy/test/data.py | 4 +-- mypy/test/testfinegrained.py | 1 - mypy/test/testmerge.py | 5 +--- mypy/test/testpep561.py | 35 ------------------------- mypy/types.py | 21 +++------------ mypy_self_check.ini | 4 +++ test-data/unit/check-namedtuple.test | 2 +- 23 files changed, 70 insertions(+), 153 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index a7a76a51f958..f6272ed808cf 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -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 @@ -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() diff --git a/mypy/checker.py b/mypy/checker.py index 7b0b88186f76..99be60994c7c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -43,6 +43,7 @@ from mypy.mro import MroError, calculate_mro from mypy.nodes import ( ARG_NAMED, + SYMBOL_FUNCBASE_TYPES, ARG_POS, ARG_STAR, CONTRAVARIANT, @@ -2780,7 +2781,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: @@ -4340,7 +4341,7 @@ 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 diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0752fa0b466f..abfc20bb44af 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2581,8 +2581,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, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 19ebe07b1032..413da57fefa3 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1111,10 +1111,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: @@ -1156,7 +1156,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 @@ -1415,7 +1415,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) diff --git a/mypy/constraints.py b/mypy/constraints.py index 45a96b993563..b46fa40c524a 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -126,15 +126,6 @@ def infer_constraints_for_callable( param_spec_arg_names = [] param_spec_arg_kinds = [] - incomplete_star_mapping = False - for i, actuals in enumerate(formal_to_actual): - for actual in actuals: - if actual is None and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2): - # We can't use arguments to infer ParamSpec constraint, if only some - # are present in the current inference pass. - incomplete_star_mapping = True - break - for i, actuals in enumerate(formal_to_actual): if isinstance(callee.arg_types[i], UnpackType): unpack_type = callee.arg_types[i] @@ -230,17 +221,16 @@ def infer_constraints_for_callable( # constraints, instead store them and infer single constraint at the end. # It is impossible to map actual kind to formal kind, so use some heuristic. # This inference is used as a fallback, so relying on heuristic should be OK. - if not incomplete_star_mapping: - param_spec_arg_types.append( - mapper.expand_actual_type( - actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] - ) + param_spec_arg_types.append( + mapper.expand_actual_type( + actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] ) - actual_kind = arg_kinds[actual] - param_spec_arg_kinds.append( - ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind - ) - param_spec_arg_names.append(arg_names[actual] if arg_names else None) + ) + actual_kind = arg_kinds[actual] + param_spec_arg_kinds.append( + ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind + ) + param_spec_arg_names.append(arg_names[actual] if arg_names else None) else: actual_type = mapper.expand_actual_type( actual_arg_type, @@ -253,7 +243,6 @@ def infer_constraints_for_callable( if ( param_spec and not any(c.type_var == param_spec.id for c in constraints) - and not incomplete_star_mapping ): # Use ParamSpec constraint from arguments only if there are no other constraints, # since as explained above it is quite ad-hoc. @@ -545,11 +534,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 diff --git a/mypy/errors.py b/mypy/errors.py index 2dd5af96eeef..45edf8a1ae86 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -1066,34 +1066,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 diff --git a/mypy/messages.py b/mypy/messages.py index 8e614f02277a..e9baa85d26c8 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2132,12 +2132,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): diff --git a/mypy/nodes.py b/mypy/nodes.py index b7b09f506c35..b6dd5fe34f91 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -171,10 +171,7 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - ans = self.accept(mypy.strconv.StrConv(options=Options())) - if ans is None: - return repr(self) - return ans + return self.accept(mypy.strconv.StrConv(options=Options())) def str_with_options(self, options: Options) -> str: ans = self.accept(mypy.strconv.StrConv(options=options)) @@ -870,7 +867,7 @@ 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) +SYMBOL_FUNCBASE_TYPES: Final = (OverloadedFuncDef, FuncDef) class Decorator(SymbolNode, Statement): @@ -2551,6 +2548,10 @@ def name(self) -> str: 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(...). @@ -3250,7 +3251,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 @@ -4009,7 +4010,8 @@ def __str__(self) -> str: ): a.append(" " + str(key) + " : " + str(value)) else: - a.append(" ") + # Used in debugging: + a.append(" ") # type: ignore[unreachable] a = sorted(a) a.insert(0, "SymbolTable(") a[-1] += ")" diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 03cb379a8173..81d2f19dc17b 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -554,7 +554,7 @@ def tuple_mul_callback(ctx: MethodContext) -> Type: value = arg_type.last_known_value.value if isinstance(value, int): return ctx.type.copy_modified(items=ctx.type.items * value) - elif isinstance(ctx.type, LiteralType): + elif isinstance(arg_type, LiteralType): value = arg_type.value if isinstance(value, int): return ctx.type.copy_modified(items=ctx.type.items * value) diff --git a/mypy/plugins/functools.py b/mypy/plugins/functools.py index 6a063174bfcb..195f2d3c5ee8 100644 --- a/mypy/plugins/functools.py +++ b/mypy/plugins/functools.py @@ -8,7 +8,7 @@ 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 SYMBOL_FUNCBASE_TYPES, ARG_POS, ARG_STAR2, ArgKind, Argument, CallExpr, FuncItem, NameExpr, Var from mypy.plugins.common import add_method_to_class from mypy.typeops import get_all_type_vars from mypy.types import ( @@ -108,7 +108,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 diff --git a/mypy/semanal.py b/mypy/semanal.py index febb9590887e..1b018160b755 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -71,6 +71,7 @@ from mypy.mro import MroError, calculate_mro from mypy.nodes import ( ARG_NAMED, + SYMBOL_FUNCBASE_TYPES, ARG_POS, ARG_STAR2, CONTRAVARIANT, @@ -3076,8 +3077,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): @@ -5704,7 +5703,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 diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index f91687823841..3f379d51b1c3 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -59,6 +59,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from mypy.expandtype import expand_type from mypy.nodes import ( UNBOUND_IMPORTED, + SYMBOL_FUNCBASE_TYPES, Decorator, FuncBase, FuncDef, @@ -234,16 +235,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 is_trivial_body = impl.is_trivial_body if impl else False dataclass_transform_spec = find_dataclass_transform_spec(node) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 5dc254422328..9b8afde328ef 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -51,6 +51,7 @@ from mypy.nodes import ( MDEF, + SYMBOL_NODE_EXPRESSION_TYPES, AssertTypeExpr, AssignmentStmt, Block, @@ -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: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f4e7b86abf63..b994a214f67a 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -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, @@ -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 diff --git a/mypy/server/mergecheck.py b/mypy/server/mergecheck.py index 6f044a5ea8b9..ce162859d017 100644 --- a/mypy/server/mergecheck.py +++ b/mypy/server/mergecheck.py @@ -25,19 +25,15 @@ def check_consistency(o: object) -> None: if isinstance(sym, FakeInfo): continue - fn = sym.fullname - # Skip None names, since they are ambiguous. - # TODO: Everything should have a proper full name? - if fn is None: - 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 + fn = sym.fullname 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. diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 5d19c4777916..f1347c47ca40 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -338,7 +338,8 @@ def verify_mypyfile( yield Error(object_path, "is not present at runtime", stub, runtime) return if not isinstance(runtime, types.ModuleType): - yield Error(object_path, "is not a module", stub, runtime) + # Can possible happen: + yield Error(object_path, "is not a module", stub, runtime) # type: ignore[unreachable] return runtime_all_as_set: set[str] | None @@ -524,7 +525,8 @@ def verify_typeinfo( yield Error(object_path, "is not present at runtime", stub, runtime, stub_desc=repr(stub)) return if not isinstance(runtime, type): - yield Error(object_path, "is not a type", stub, runtime, stub_desc=repr(stub)) + # Yes, some runtime objects can be not types, no way to tell mypy about that. + yield Error(object_path, "is not a type", stub, runtime, stub_desc=repr(stub)) # type: ignore[unreachable] return yield from _verify_final(stub, runtime, object_path) diff --git a/mypy/test/data.py b/mypy/test/data.py index 50e452de4c0a..d30686747b64 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -246,7 +246,7 @@ class DataDrivenTestCase(pytest.Item): """Holds parsed data-driven test cases, and handles directory setup and teardown.""" # Override parent member type - parent: DataSuiteCollector + parent: DataFileCollector input: list[str] output: list[str] # Output for the first pass @@ -277,7 +277,7 @@ class DataDrivenTestCase(pytest.Item): def __init__( self, - parent: DataSuiteCollector, + parent: DataFileCollector, suite: DataSuite, *, file: str, diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index cb8672dfaf29..b098c1fb0ad2 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -75,7 +75,6 @@ def should_skip(self, testcase: DataDrivenTestCase) -> bool: def run_case(self, testcase: DataDrivenTestCase) -> None: if self.should_skip(testcase): pytest.skip() - return main_src = "\n".join(testcase.input) main_path = os.path.join(test_temp_dir, "main") diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 0582c9ed5882..248ad59587d0 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -172,10 +172,7 @@ def format_symbol_table_node(self, node: SymbolTableNode) -> str: if node.kind == UNBOUND_IMPORTED: return "UNBOUND_IMPORTED" return "None" - if isinstance(node.node, Node): - s = f"{str(type(node.node).__name__)}<{self.id_mapper.id(node.node)}>" - else: - s = f"? ({type(node.node)})" + s = f"{str(type(node.node).__name__)}<{self.id_mapper.id(node.node)}>" if ( isinstance(node.node, Var) and node.node.type diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 4a5301d2cdb8..e3f729729f0b 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -173,38 +173,3 @@ def parse_mypy_args(line: str) -> list[str]: if not m: return [] # No args; mypy will spit out an error. return m.group(1).split() - - -def test_mypy_path_is_respected() -> None: - assert False - packages = "packages" - pkg_name = "a" - with tempfile.TemporaryDirectory() as temp_dir: - old_dir = os.getcwd() - os.chdir(temp_dir) - try: - # Create the pkg for files to go into - full_pkg_name = os.path.join(temp_dir, packages, pkg_name) - os.makedirs(full_pkg_name) - - # Create the empty __init__ file to declare a package - pkg_init_name = os.path.join(temp_dir, packages, pkg_name, "__init__.py") - open(pkg_init_name, "w", encoding="utf8").close() - - mypy_config_path = os.path.join(temp_dir, "mypy.ini") - with open(mypy_config_path, "w") as mypy_file: - mypy_file.write("[mypy]\n") - mypy_file.write(f"mypy_path = ./{packages}\n") - - with virtualenv() as venv: - venv_dir, python_executable = venv - - cmd_line_args = [] - if python_executable != sys.executable: - cmd_line_args.append(f"--python-executable={python_executable}") - cmd_line_args.extend(["--config-file", mypy_config_path, "--package", pkg_name]) - - out, err, returncode = mypy.api.run(cmd_line_args) - assert returncode == 0 - finally: - os.chdir(old_dir) diff --git a/mypy/types.py b/mypy/types.py index f3745695889f..ad847f6bb84e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3314,12 +3314,7 @@ def visit_instance(self, t: Instance, /) -> str: return s def visit_type_var(self, t: TypeVarType, /) -> str: - if t.name is None: - # Anonymous type variable type (only numeric id). - s = f"`{t.id}" - else: - # Named type variable type. - s = f"{t.name}`{t.id}" + s = f"{t.name}`{t.id}" if self.id_mapper and t.upper_bound: s += f"(upper_bound={t.upper_bound.accept(self)})" if t.has_default(): @@ -3331,12 +3326,7 @@ def visit_param_spec(self, t: ParamSpecType, /) -> str: s = "" if t.prefix.arg_types: s += f"[{self.list_str(t.prefix.arg_types)}, **" - if t.name is None: - # Anonymous type variable type (only numeric id). - s += f"`{t.id}" - else: - # Named type variable type. - s += f"{t.name_with_suffix()}`{t.id}" + s += f"{t.name_with_suffix()}`{t.id}" if t.prefix.arg_types: s += "]" if t.has_default(): @@ -3373,12 +3363,7 @@ def visit_parameters(self, t: Parameters, /) -> str: return f"[{s}]" def visit_type_var_tuple(self, t: TypeVarTupleType, /) -> str: - if t.name is None: - # Anonymous type variable type (only numeric id). - s = f"`{t.id}" - else: - # Named type variable type. - s = f"{t.name}`{t.id}" + s = f"{t.name}`{t.id}" if t.has_default(): s += f" = {t.default.accept(self)}" return s diff --git a/mypy_self_check.ini b/mypy_self_check.ini index f54c1f17f025..02f20be6fc3f 100644 --- a/mypy_self_check.ini +++ b/mypy_self_check.ini @@ -13,6 +13,10 @@ enable_error_code = ignore-without-code,redundant-expr enable_incomplete_feature = PreciseTupleTypes show_error_code_links = True +[mypy-mypy.*] +# TODO: enable for `mypyc` and other files as well +warn_unreachable = True + [mypy-mypy.visitor] # See docstring for NodeVisitor for motivation. disable_error_code = empty-body diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 172228820add..8b0658b4165e 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -6,7 +6,7 @@ x: X a, b = x b = x[0] a = x[1] -a, b, c = x # E: Need more than 2 values to unpack (3 expected) +a, b, c = x # E: Need more than 2 values to unpack (3 expected) x[2] # E: Tuple index out of range [builtins fixtures/tuple.pyi] From 867958eeeb34a1a02a250cc210ed45e3df0c4702 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:22:33 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 6 ++++-- mypy/constraints.py | 5 +---- mypy/errors.py | 1 - mypy/nodes.py | 3 ++- mypy/plugins/functools.py | 11 ++++++++++- mypy/semanal.py | 2 +- mypy/server/astdiff.py | 3 +-- mypy/test/testmerge.py | 1 - 8 files changed, 19 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 99be60994c7c..227dfdea2953 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -43,7 +43,6 @@ from mypy.mro import MroError, calculate_mro from mypy.nodes import ( ARG_NAMED, - SYMBOL_FUNCBASE_TYPES, ARG_POS, ARG_STAR, CONTRAVARIANT, @@ -57,6 +56,7 @@ LITERAL_TYPE, MDEF, NOT_ABSTRACT, + SYMBOL_FUNCBASE_TYPES, AssertStmt, AssignmentExpr, AssignmentStmt, @@ -4341,7 +4341,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, SYMBOL_FUNCBASE_TYPES): + 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 diff --git a/mypy/constraints.py b/mypy/constraints.py index b46fa40c524a..6c7297f79387 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -240,10 +240,7 @@ def infer_constraints_for_callable( ) c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF) constraints.extend(c) - if ( - param_spec - and not any(c.type_var == param_spec.id for c in constraints) - ): + if param_spec and not any(c.type_var == param_spec.id for c in constraints): # Use ParamSpec constraint from arguments only if there are no other constraints, # since as explained above it is quite ad-hoc. constraints.append( diff --git a/mypy/errors.py b/mypy/errors.py index 45edf8a1ae86..b00a237cee2a 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -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 diff --git a/mypy/nodes.py b/mypy/nodes.py index b6dd5fe34f91..b0c06092a85d 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2548,9 +2548,10 @@ def name(self) -> str: 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, ) +SYMBOL_NODE_EXPRESSION_TYPES: Final = (TypeVarLikeExpr,) class TypeVarExpr(TypeVarLikeExpr): diff --git a/mypy/plugins/functools.py b/mypy/plugins/functools.py index 195f2d3c5ee8..c435dde7fde7 100644 --- a/mypy/plugins/functools.py +++ b/mypy/plugins/functools.py @@ -8,7 +8,16 @@ import mypy.plugin import mypy.semanal from mypy.argmap import map_actuals_to_formals -from mypy.nodes import SYMBOL_FUNCBASE_TYPES, 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 ( diff --git a/mypy/semanal.py b/mypy/semanal.py index 1b018160b755..f3bbe51afa8a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -71,7 +71,6 @@ from mypy.mro import MroError, calculate_mro from mypy.nodes import ( ARG_NAMED, - SYMBOL_FUNCBASE_TYPES, ARG_POS, ARG_STAR2, CONTRAVARIANT, @@ -87,6 +86,7 @@ REVEAL_LOCALS, REVEAL_TYPE, RUNTIME_PROTOCOL_DECOS, + SYMBOL_FUNCBASE_TYPES, TYPE_VAR_KIND, TYPE_VAR_TUPLE_KIND, VARIANCE_NOT_READY, diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 3f379d51b1c3..ac5bd361087a 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -58,10 +58,9 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from mypy.expandtype import expand_type from mypy.nodes import ( - UNBOUND_IMPORTED, SYMBOL_FUNCBASE_TYPES, + UNBOUND_IMPORTED, Decorator, - FuncBase, FuncDef, FuncItem, MypyFile, diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 248ad59587d0..51a4ff39dd9a 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -13,7 +13,6 @@ UNBOUND_IMPORTED, Expression, MypyFile, - Node, SymbolTable, SymbolTableNode, TypeInfo, From 792ba284718c991854c4eb123d0d478ca3237873 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Jan 2025 12:44:52 +0300 Subject: [PATCH 3/9] Fix CI --- mypy/stubgen.py | 4 +--- mypy/util.py | 55 ++++++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 1f8a1a4740f1..fb25e2491790 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -1507,9 +1507,7 @@ def is_blacklisted_path(path: str) -> bool: def normalize_path_separators(path: str) -> str: - if sys.platform == "win32": - return path.replace("\\", "/") - return path + return path.replace("\\", "/") if sys.platform == "win32" else path def collect_build_targets( diff --git a/mypy/util.py b/mypy/util.py index f79d7113ca91..e8b29da112c9 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -28,6 +28,8 @@ except ImportError: CURSES_ENABLED = False +IS_WIN: Final = sys.platform == "win32" + T = TypeVar("T") TYPESHED_DIR: Final = str(importlib_resources.files("mypy") / "typeshed") @@ -571,8 +573,7 @@ def hash_digest(data: bytes) -> str: def parse_gray_color(cup: bytes) -> str: """Reproduce a gray color in ANSI escape sequence""" - if sys.platform == "win32": - assert False, "curses is not available on Windows" + assert sys.platform != "win32", "curses is not available on Windows" set_color = "".join([cup[:-1].decode(), "m"]) gray = curses.tparm(set_color.encode("utf-8"), 1, 9).decode() return gray @@ -605,7 +606,7 @@ def __init__( if not should_force_color() and (not f_out.isatty() or not f_err.isatty()): self.dummy_term = True return - if sys.platform == "win32": + if IS_WIN: self.dummy_term = not self.initialize_win_colors() elif sys.platform == "emscripten": self.dummy_term = not self.initialize_vt100_colors() @@ -639,34 +640,32 @@ def initialize_win_colors(self) -> bool: # Windows ANSI escape sequences are only supported on Threshold 2 and above. # we check with an assert at runtime and an if check for mypy, as asserts do not # yet narrow platform - assert sys.platform == "win32" - if sys.platform == "win32": - winver = sys.getwindowsversion() - if ( - winver.major < MINIMUM_WINDOWS_MAJOR_VT100 - or winver.build < MINIMUM_WINDOWS_BUILD_VT100 - ): - return False - import ctypes - - kernel32 = ctypes.windll.kernel32 - ENABLE_PROCESSED_OUTPUT = 0x1 - ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 - STD_OUTPUT_HANDLE = -11 - kernel32.SetConsoleMode( - kernel32.GetStdHandle(STD_OUTPUT_HANDLE), - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) - self.initialize_vt100_colors() - return True - return False + assert IS_WIN, "Running not on Windows" + winver = sys.getwindowsversion() + if ( + winver.major < MINIMUM_WINDOWS_MAJOR_VT100 + or winver.build < MINIMUM_WINDOWS_BUILD_VT100 + ): + return False + import ctypes + + kernel32 = ctypes.windll.kernel32 + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + STD_OUTPUT_HANDLE = -11 + kernel32.SetConsoleMode( + kernel32.GetStdHandle(STD_OUTPUT_HANDLE), + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + self.initialize_vt100_colors() + return True def initialize_unix_colors(self) -> bool: """Return True if initialization was successful and we can use colors, False otherwise""" - if sys.platform == "win32" or not CURSES_ENABLED: + if IS_WIN or not CURSES_ENABLED: return False try: # setupterm wants a fd to potentially write an "initialization sequence". From b65e27abcf48f6906d3d801f9f6adbaaa9f38530 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Jan 2025 13:02:47 +0300 Subject: [PATCH 4/9] Fix CI --- mypy/util.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index e8b29da112c9..c4cf6c8fa406 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -28,8 +28,6 @@ except ImportError: CURSES_ENABLED = False -IS_WIN: Final = sys.platform == "win32" - T = TypeVar("T") TYPESHED_DIR: Final = str(importlib_resources.files("mypy") / "typeshed") @@ -606,7 +604,7 @@ def __init__( if not should_force_color() and (not f_out.isatty() or not f_err.isatty()): self.dummy_term = True return - if IS_WIN: + if sys.platform == "win32": self.dummy_term = not self.initialize_win_colors() elif sys.platform == "emscripten": self.dummy_term = not self.initialize_vt100_colors() @@ -640,32 +638,34 @@ def initialize_win_colors(self) -> bool: # Windows ANSI escape sequences are only supported on Threshold 2 and above. # we check with an assert at runtime and an if check for mypy, as asserts do not # yet narrow platform - assert IS_WIN, "Running not on Windows" - winver = sys.getwindowsversion() - if ( - winver.major < MINIMUM_WINDOWS_MAJOR_VT100 - or winver.build < MINIMUM_WINDOWS_BUILD_VT100 - ): - return False - import ctypes - - kernel32 = ctypes.windll.kernel32 - ENABLE_PROCESSED_OUTPUT = 0x1 - ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 - STD_OUTPUT_HANDLE = -11 - kernel32.SetConsoleMode( - kernel32.GetStdHandle(STD_OUTPUT_HANDLE), - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) - self.initialize_vt100_colors() + assert sys.platform == "win32", "Running not on Windows" + if sys.platform == "win32": # needed to find win specific sys apis + winver = sys.getwindowsversion() + if ( + winver.major < MINIMUM_WINDOWS_MAJOR_VT100 + or winver.build < MINIMUM_WINDOWS_BUILD_VT100 + ): + return False + import ctypes + + kernel32 = ctypes.windll.kernel32 + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + STD_OUTPUT_HANDLE = -11 + kernel32.SetConsoleMode( + kernel32.GetStdHandle(STD_OUTPUT_HANDLE), + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + self.initialize_vt100_colors() return True def initialize_unix_colors(self) -> bool: """Return True if initialization was successful and we can use colors, False otherwise""" - if IS_WIN or not CURSES_ENABLED: + is_win = sys.platform == "win32" + if is_win or not CURSES_ENABLED: return False try: # setupterm wants a fd to potentially write an "initialization sequence". From 8a73b5d0ecf8632d9b94f6dcc9c5717a981d1ba1 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Jan 2025 13:04:48 +0300 Subject: [PATCH 5/9] Fix CI --- mypy/stubtest.py | 2 +- test-data/unit/check-namedtuple.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index f1347c47ca40..e2a6a06f6bf2 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -338,7 +338,7 @@ def verify_mypyfile( yield Error(object_path, "is not present at runtime", stub, runtime) return if not isinstance(runtime, types.ModuleType): - # Can possible happen: + # Can possibly happen: yield Error(object_path, "is not a module", stub, runtime) # type: ignore[unreachable] return diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 8b0658b4165e..172228820add 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -6,7 +6,7 @@ x: X a, b = x b = x[0] a = x[1] -a, b, c = x # E: Need more than 2 values to unpack (3 expected) +a, b, c = x # E: Need more than 2 values to unpack (3 expected) x[2] # E: Tuple index out of range [builtins fixtures/tuple.pyi] From bbb830fc73ea46cd68bdc9b288d783139311cecb Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 25 Jan 2025 00:11:15 +0300 Subject: [PATCH 6/9] Address review --- mypy/test/data.py | 1 + mypy/util.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/test/data.py b/mypy/test/data.py index d30686747b64..5b0ad84c0ba7 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -291,6 +291,7 @@ def __init__( data: str, line: int, ) -> None: + assert isinstance(parent, DataFileCollector) super().__init__(name, parent) self.suite = suite self.file = file diff --git a/mypy/util.py b/mypy/util.py index c4cf6c8fa406..d3f49f74bbae 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -638,7 +638,6 @@ def initialize_win_colors(self) -> bool: # Windows ANSI escape sequences are only supported on Threshold 2 and above. # we check with an assert at runtime and an if check for mypy, as asserts do not # yet narrow platform - assert sys.platform == "win32", "Running not on Windows" if sys.platform == "win32": # needed to find win specific sys apis winver = sys.getwindowsversion() if ( @@ -660,7 +659,8 @@ def initialize_win_colors(self) -> bool: | ENABLE_VIRTUAL_TERMINAL_PROCESSING, ) self.initialize_vt100_colors() - return True + return True + assert False, "Running not on Windows" def initialize_unix_colors(self) -> bool: """Return True if initialization was successful and we can use colors, False otherwise""" From 768b54cfb669da7ba22ea660cf79359eda7d6dd1 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 25 Jan 2025 12:33:56 +0300 Subject: [PATCH 7/9] Address reviews --- mypy/constraints.py | 34 ++++++++++++++++++++++++---------- mypy/nodes.py | 8 +++++++- mypy/server/mergecheck.py | 7 ++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 6c7297f79387..1b7a2052c783 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -126,6 +126,15 @@ def infer_constraints_for_callable( param_spec_arg_names = [] param_spec_arg_kinds = [] + incomplete_star_mapping = False + 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): # 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 # type: ignore[unreachable] + break + for i, actuals in enumerate(formal_to_actual): if isinstance(callee.arg_types[i], UnpackType): unpack_type = callee.arg_types[i] @@ -221,16 +230,17 @@ def infer_constraints_for_callable( # constraints, instead store them and infer single constraint at the end. # It is impossible to map actual kind to formal kind, so use some heuristic. # This inference is used as a fallback, so relying on heuristic should be OK. - param_spec_arg_types.append( - mapper.expand_actual_type( - actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] + if not incomplete_star_mapping: + param_spec_arg_types.append( + mapper.expand_actual_type( + actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] + ) ) - ) - actual_kind = arg_kinds[actual] - param_spec_arg_kinds.append( - ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind - ) - param_spec_arg_names.append(arg_names[actual] if arg_names else None) + actual_kind = arg_kinds[actual] + param_spec_arg_kinds.append( + ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind + ) + param_spec_arg_names.append(arg_names[actual] if arg_names else None) else: actual_type = mapper.expand_actual_type( actual_arg_type, @@ -240,7 +250,11 @@ def infer_constraints_for_callable( ) c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF) constraints.extend(c) - if param_spec and not any(c.type_var == param_spec.id for c in constraints): + if ( + param_spec + and not any(c.type_var == param_spec.id for c in constraints) + and not incomplete_star_mapping + ): # Use ParamSpec constraint from arguments only if there are no other constraints, # since as explained above it is quite ad-hoc. constraints.append( diff --git a/mypy/nodes.py b/mypy/nodes.py index b0c06092a85d..403973c5833e 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -171,7 +171,11 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - return self.accept(mypy.strconv.StrConv(options=Options())) + ans = self.accept(mypy.strconv.StrConv(options=Options())) + if ans is None: + # Some visitors might have empty bodies and actually return `None` + return repr(self) # type: ignore[unreachable] + return ans def str_with_options(self, options: Options) -> str: ans = self.accept(mypy.strconv.StrConv(options=options)) @@ -867,6 +871,8 @@ def deserialize(cls, data: JsonDict) -> FuncDef: # All types that are both SymbolNodes and FuncBases. See the FuncBase # docstring for the rationale. +# 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) diff --git a/mypy/server/mergecheck.py b/mypy/server/mergecheck.py index ce162859d017..11e00213d05a 100644 --- a/mypy/server/mergecheck.py +++ b/mypy/server/mergecheck.py @@ -25,13 +25,18 @@ def check_consistency(o: object) -> None: if isinstance(sym, FakeInfo): continue + fn = sym.fullname + # Skip None and empty names, since they are ambiguous. + # TODO: Everything should have a proper full name? + 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 - fn = sym.fullname if fn not in m: m[fn] = sym continue From 4859d918df5b262be71c599699ec7a58dff11861 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 13 Feb 2025 18:19:31 +0300 Subject: [PATCH 8/9] Update nodes.py --- mypy/nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index fd7141a301c9..da6380f7ffb4 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -176,11 +176,11 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - ans = self.accept(mypy.strconv.StrConv(options=Options())) - if ans is None: + a = self.accept(mypy.strconv.StrConv(options=Options())) + if a is None: # Some visitors might have empty bodies and actually return `None` return repr(self) # type: ignore[unreachable] - return ans + return a def str_with_options(self, options: Options) -> str: a = self.accept(mypy.strconv.StrConv(options=options)) From c5e56cbdaf0b6bd3256313e3a336a945b6f6f98a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 14 Feb 2025 01:24:04 +0300 Subject: [PATCH 9/9] Address review --- mypy/nodes.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index da6380f7ffb4..5e6fe73a293e 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -176,11 +176,7 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - a = self.accept(mypy.strconv.StrConv(options=Options())) - if a is None: - # Some visitors might have empty bodies and actually return `None` - return repr(self) # type: ignore[unreachable] - 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))