From 5059ffdd5df4702ae5b690a6dfd5f1a70c7964e1 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Sat, 25 May 2024 22:19:51 -0700 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20leak=20unreachability=20from=20?= =?UTF-8?q?lambda=20body=20to=20surrounding=20scope=20(#17287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #17254 Signed-off-by: Anders Kaseorg --- mypy/checkexpr.py | 10 ++++++---- test-data/unit/check-unreachable-code.test | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4b0f5fe533d8..479ef228b038 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5223,15 +5223,16 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type: self.chk.return_types.append(AnyType(TypeOfAny.special_form)) # Type check everything in the body except for the final return # statement (it can contain tuple unpacking before return). - with self.chk.scope.push_function(e): + with self.chk.binder.frame_context( + can_skip=True, fall_through=0 + ), self.chk.scope.push_function(e): # Lambdas can have more than one element in body, # when we add "fictional" AssigmentStatement nodes, like in: # `lambda (a, b): a` for stmt in e.body.body[:-1]: stmt.accept(self.chk) # Only type check the return expression, not the return statement. - # This is important as otherwise the following statements would be - # considered unreachable. There's no useful type context. + # There's no useful type context. ret_type = self.accept(e.expr(), allow_none_return=True) fallback = self.named_type("builtins.function") self.chk.return_types.pop() @@ -5243,7 +5244,8 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type: self.chk.check_func_item(e, type_override=type_override) if not self.chk.has_type(e.expr()): # TODO: return expression must be accepted before exiting function scope. - self.accept(e.expr(), allow_none_return=True) + with self.chk.binder.frame_context(can_skip=True, fall_through=0): + self.accept(e.expr(), allow_none_return=True) ret_type = self.chk.lookup_type(e.expr()) self.chk.return_types.pop() return replace_callable_return_type(inferred_type, ret_type) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index b8b438b979c6..81777f4c0e2b 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1494,3 +1494,23 @@ from typing import Generator def f() -> Generator[None, None, None]: return None yield None + +[case testLambdaNoReturn] +# flags: --warn-unreachable +from typing import Callable, NoReturn + +def foo() -> NoReturn: + raise + +f = lambda: foo() +x = 0 # not unreachable + +[case testLambdaNoReturnAnnotated] +# flags: --warn-unreachable +from typing import Callable, NoReturn + +def foo() -> NoReturn: + raise + +f: Callable[[], NoReturn] = lambda: foo() # E: Return statement in function which does not return # (false positive: https://github.com/python/mypy/issues/17254) +x = 0 # not unreachable