From 1efb3850a5c18ded24f3147968d8588f3c19dd54 Mon Sep 17 00:00:00 2001 From: Kunal Sali <141454228+KunalLikesAI@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:28:31 +0530 Subject: [PATCH 1/5] Fix false negative with walrus operator in tuples --- mypy/checker.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 20a825e9cc5e..f7256981a101 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4928,6 +4928,8 @@ def infer_context_dependent( return typ # If there are errors with the original type context, try re-inferring in empty context. + # However, skip this fallback if the expression contains assignment expressions (walrus + # operator), as they can cause incorrect type inference when the context is removed. original_messages = msg.filtered_errors() original_type_map = type_map with self.msg.filter_errors( @@ -4937,10 +4939,11 @@ def infer_context_dependent( alt_typ = get_proper_type( self.expr_checker.accept(expr, None, allow_none_return=allow_none_func_call) ) - if not msg.has_new_errors() and is_subtype(alt_typ, type_ctx): + + if not msg.has_new_errors() and is_subtype(alt_typ, type_ctx) and not self.contains_assignment_expr(expr): self.store_types(type_map) return alt_typ - + # If empty fallback didn't work, use results from the original type context. self.msg.add_errors(original_messages) self.store_types(original_type_map) @@ -4979,7 +4982,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: # Return with a value. if ( - isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr)) + isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr, AssignmentExpr)) or isinstance(s.expr, AwaitExpr) and isinstance(s.expr.expr, CallExpr) ): @@ -5056,6 +5059,80 @@ def check_return_stmt(self, s: ReturnStmt) -> None: if self.in_checked_function(): self.fail(message_registry.RETURN_VALUE_EXPECTED, s) + + def contains_assignment_expr(self, expr: Expression) -> bool: + """Check if expression contains any AssignmentExpr (walrus operator).""" + # Base case: found an assignment expression + if isinstance(expr, AssignmentExpr): + return True + + # Recursively check nested expressions in various expression types + + # Container expressions + if isinstance(expr, (TupleExpr, ListExpr, SetExpr)): + return any(self.contains_assignment_expr(item) for item in expr.items) + + if isinstance(expr, DictExpr): + # Check both keys and values + for k, v in zip(expr.items, expr.values): + if self.contains_assignment_expr(k) or self.contains_assignment_expr(v): + return True + return False + + # Binary operations (left and right operands) + if isinstance(expr, OpExpr): + return ( + self.contains_assignment_expr(expr.left) + or self.contains_assignment_expr(expr.right) + ) + + # Unary operations + if isinstance(expr, UnaryExpr): + return self.contains_assignment_expr(expr.expr) + + # Comparison expressions (multiple operands) + if isinstance(expr, ComparisonExpr): + return any(self.contains_assignment_expr(operand) for operand in expr.operands) + + # Function calls (check arguments) + if isinstance(expr, CallExpr): + # Check callee and all arguments + if self.contains_assignment_expr(expr.callee): + return True + return any(self.contains_assignment_expr(arg) for arg in expr.args) + + # Index expressions (subscripts) + if isinstance(expr, IndexExpr): + if self.contains_assignment_expr(expr.base): + return True + if expr.index is not None: + return self.contains_assignment_expr(expr.index) + return False + + # Member access + if isinstance(expr, MemberExpr): + return self.contains_assignment_expr(expr.expr) + + # Starred expressions (unpacking) + if isinstance(expr, StarExpr): + return self.contains_assignment_expr(expr.expr) + + # Await expressions + if isinstance(expr, AwaitExpr): + return self.contains_assignment_expr(expr.expr) + + # Yield expressions + if isinstance(expr, YieldExpr): + if expr.expr is not None: + return self.contains_assignment_expr(expr.expr) + return False + + # Conditional expressions (ternary operator) + # Note: ConditionalExpr might not be in imports, but if it exists, handle it + # For now, we'll skip it if it's not imported + + # All other expression types (NameExpr, IntExpr, StrExpr, etc.) don't contain nested expressions + return False def visit_if_stmt(self, s: IfStmt) -> None: """Type check an if statement.""" From b2e5ccf51413ae7ebcea3fed3029faebe542b988 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:10:22 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f7256981a101..4bd9c62bf2dc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4940,10 +4940,14 @@ def infer_context_dependent( self.expr_checker.accept(expr, None, allow_none_return=allow_none_func_call) ) - if not msg.has_new_errors() and is_subtype(alt_typ, type_ctx) and not self.contains_assignment_expr(expr): + if ( + not msg.has_new_errors() + and is_subtype(alt_typ, type_ctx) + and not self.contains_assignment_expr(expr) + ): self.store_types(type_map) return alt_typ - + # If empty fallback didn't work, use results from the original type context. self.msg.add_errors(original_messages) self.store_types(original_type_map) @@ -4982,7 +4986,10 @@ def check_return_stmt(self, s: ReturnStmt) -> None: # Return with a value. if ( - isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr, AssignmentExpr)) + isinstance( + s.expr, + (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr, AssignmentExpr), + ) or isinstance(s.expr, AwaitExpr) and isinstance(s.expr.expr, CallExpr) ): @@ -5059,48 +5066,47 @@ def check_return_stmt(self, s: ReturnStmt) -> None: if self.in_checked_function(): self.fail(message_registry.RETURN_VALUE_EXPECTED, s) - + def contains_assignment_expr(self, expr: Expression) -> bool: """Check if expression contains any AssignmentExpr (walrus operator).""" # Base case: found an assignment expression if isinstance(expr, AssignmentExpr): return True - + # Recursively check nested expressions in various expression types - + # Container expressions if isinstance(expr, (TupleExpr, ListExpr, SetExpr)): return any(self.contains_assignment_expr(item) for item in expr.items) - + if isinstance(expr, DictExpr): # Check both keys and values for k, v in zip(expr.items, expr.values): if self.contains_assignment_expr(k) or self.contains_assignment_expr(v): return True return False - + # Binary operations (left and right operands) if isinstance(expr, OpExpr): - return ( - self.contains_assignment_expr(expr.left) - or self.contains_assignment_expr(expr.right) + return self.contains_assignment_expr(expr.left) or self.contains_assignment_expr( + expr.right ) - + # Unary operations if isinstance(expr, UnaryExpr): return self.contains_assignment_expr(expr.expr) - + # Comparison expressions (multiple operands) if isinstance(expr, ComparisonExpr): return any(self.contains_assignment_expr(operand) for operand in expr.operands) - + # Function calls (check arguments) if isinstance(expr, CallExpr): # Check callee and all arguments if self.contains_assignment_expr(expr.callee): return True return any(self.contains_assignment_expr(arg) for arg in expr.args) - + # Index expressions (subscripts) if isinstance(expr, IndexExpr): if self.contains_assignment_expr(expr.base): @@ -5108,29 +5114,29 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if expr.index is not None: return self.contains_assignment_expr(expr.index) return False - + # Member access if isinstance(expr, MemberExpr): return self.contains_assignment_expr(expr.expr) - + # Starred expressions (unpacking) if isinstance(expr, StarExpr): return self.contains_assignment_expr(expr.expr) - + # Await expressions if isinstance(expr, AwaitExpr): return self.contains_assignment_expr(expr.expr) - + # Yield expressions if isinstance(expr, YieldExpr): if expr.expr is not None: return self.contains_assignment_expr(expr.expr) return False - + # Conditional expressions (ternary operator) # Note: ConditionalExpr might not be in imports, but if it exists, handle it # For now, we'll skip it if it's not imported - + # All other expression types (NameExpr, IntExpr, StrExpr, etc.) don't contain nested expressions return False From a4df749f5a6e58b4bd5fb96906ba07cb2bb1516f Mon Sep 17 00:00:00 2001 From: Kunal Sali <141454228+KunalLikesAI@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:55:43 +0530 Subject: [PATCH 3/5] Fix: Handle all expression types in contains_assignment_expr for walrus operator detection - Add missing imports: ConditionalExpr, GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension, SliceExpr - Handle ConditionalExpr (ternary operator) to check cond, if_expr, and else_expr - Handle SliceExpr to check begin_index, end_index, and stride - Handle GeneratorExpr and comprehensions (ListComprehension, SetComprehension, DictionaryComprehension) - Ensures comprehensive detection of assignment expressions (walrus operators) in all nested expression contexts - Fixes CI failures related to incomplete expression type handling --- mypy/checker.py | 59 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4bd9c62bf2dc..fdaa4e8a34f1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -85,11 +85,13 @@ CallExpr, ClassDef, ComparisonExpr, + ConditionalExpr, Context, ContinueStmt, Decorator, DelStmt, DictExpr, + DictionaryComprehension, EllipsisExpr, Expression, ExpressionStmt, @@ -98,6 +100,7 @@ FuncBase, FuncDef, FuncItem, + GeneratorExpr, GlobalDecl, IfStmt, Import, @@ -107,6 +110,7 @@ IndexExpr, IntExpr, LambdaExpr, + ListComprehension, ListExpr, Lvalue, MatchStmt, @@ -124,7 +128,9 @@ RaiseStmt, RefExpr, ReturnStmt, + SetComprehension, SetExpr, + SliceExpr, StarExpr, Statement, StrExpr, @@ -5132,11 +5138,54 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if expr.expr is not None: return self.contains_assignment_expr(expr.expr) return False - - # Conditional expressions (ternary operator) - # Note: ConditionalExpr might not be in imports, but if it exists, handle it - # For now, we'll skip it if it's not imported - + + # Conditional expressions (ternary operator: x if cond else y) + if isinstance(expr, ConditionalExpr): + return ( + self.contains_assignment_expr(expr.cond) + or self.contains_assignment_expr(expr.if_expr) + or self.contains_assignment_expr(expr.else_expr) + ) + + # Slice expressions (x:y:z) + if isinstance(expr, SliceExpr): + return ( + (expr.begin_index is not None and self.contains_assignment_expr(expr.begin_index)) + or (expr.end_index is not None and self.contains_assignment_expr(expr.end_index)) + or (expr.stride is not None and self.contains_assignment_expr(expr.stride)) + ) + + # Generator expressions and comprehensions + if isinstance(expr, GeneratorExpr): + if self.contains_assignment_expr(expr.left_expr): + return True + for seq in expr.sequences: + if self.contains_assignment_expr(seq): + return True + for condlist in expr.condlists: + for cond in condlist: + if self.contains_assignment_expr(cond): + return True + return False + + if isinstance(expr, ListComprehension): + return self.contains_assignment_expr(expr.generator) + + if isinstance(expr, SetComprehension): + return self.contains_assignment_expr(expr.generator) + + if isinstance(expr, DictionaryComprehension): + if self.contains_assignment_expr(expr.key) or self.contains_assignment_expr(expr.value): + return True + for seq in expr.sequences: + if self.contains_assignment_expr(seq): + return True + for condlist in expr.condlists: + for cond in condlist: + if self.contains_assignment_expr(cond): + return True + return False + # All other expression types (NameExpr, IntExpr, StrExpr, etc.) don't contain nested expressions return False From 227ba5f1ab429275da1526a999596bd4830666df Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:29:23 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index fdaa4e8a34f1..24b941aabec4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5138,7 +5138,7 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if expr.expr is not None: return self.contains_assignment_expr(expr.expr) return False - + # Conditional expressions (ternary operator: x if cond else y) if isinstance(expr, ConditionalExpr): return ( @@ -5146,7 +5146,7 @@ def contains_assignment_expr(self, expr: Expression) -> bool: or self.contains_assignment_expr(expr.if_expr) or self.contains_assignment_expr(expr.else_expr) ) - + # Slice expressions (x:y:z) if isinstance(expr, SliceExpr): return ( @@ -5154,7 +5154,7 @@ def contains_assignment_expr(self, expr: Expression) -> bool: or (expr.end_index is not None and self.contains_assignment_expr(expr.end_index)) or (expr.stride is not None and self.contains_assignment_expr(expr.stride)) ) - + # Generator expressions and comprehensions if isinstance(expr, GeneratorExpr): if self.contains_assignment_expr(expr.left_expr): @@ -5167,15 +5167,17 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if self.contains_assignment_expr(cond): return True return False - + if isinstance(expr, ListComprehension): return self.contains_assignment_expr(expr.generator) - + if isinstance(expr, SetComprehension): return self.contains_assignment_expr(expr.generator) - + if isinstance(expr, DictionaryComprehension): - if self.contains_assignment_expr(expr.key) or self.contains_assignment_expr(expr.value): + if self.contains_assignment_expr(expr.key) or self.contains_assignment_expr( + expr.value + ): return True for seq in expr.sequences: if self.contains_assignment_expr(seq): @@ -5185,7 +5187,7 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if self.contains_assignment_expr(cond): return True return False - + # All other expression types (NameExpr, IntExpr, StrExpr, etc.) don't contain nested expressions return False From af5e7ded2ba4a0faddfe0969350c906fae16e474 Mon Sep 17 00:00:00 2001 From: Kunal Sali <141454228+KunalLikesAI@users.noreply.github.com> Date: Tue, 20 Jan 2026 01:08:28 +0530 Subject: [PATCH 5/5] Fix type checking errors in contains_assignment_expr - Fix DictExpr iteration: iterate over items directly (items is list[tuple[Expression | None, Expression]]) - Fix IndexExpr: remove unnecessary None check since index is always Expression, not Optional - Addresses mypy self-check failures: - DictExpr has no attribute 'values' - Unreachable code after return statement - Type mismatch in zip iteration --- mypy/checker.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 24b941aabec4..a4e7619e4e65 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5087,8 +5087,11 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if isinstance(expr, DictExpr): # Check both keys and values - for k, v in zip(expr.items, expr.values): - if self.contains_assignment_expr(k) or self.contains_assignment_expr(v): + # DictExpr.items is list[tuple[Expression | None, Expression]] + for key_expr, value_expr in expr.items: + if key_expr is not None and self.contains_assignment_expr(key_expr): + return True + if self.contains_assignment_expr(value_expr): return True return False @@ -5117,9 +5120,7 @@ def contains_assignment_expr(self, expr: Expression) -> bool: if isinstance(expr, IndexExpr): if self.contains_assignment_expr(expr.base): return True - if expr.index is not None: - return self.contains_assignment_expr(expr.index) - return False + return self.contains_assignment_expr(expr.index) # Member access if isinstance(expr, MemberExpr):