Repository: python/mypy

Issue:
lambda doesn't propagate NoReturn behavior
I have code like this:
```
class Possibility(Generic[T]):
    def __init__(self, produce: Callable[[], T]):
        self._produce = produce
    def produce(self) -> T:
        return self._produce()
def reject() -> NoReturn:
    raise RuntimeError
def nothing() -> Possibility[NoReturn]:
    return Possibility(lambda: reject())  # Error here
```
On the last line I get an error, "Return statement in function which does not return".

This is about the lambda, not about the explicit return statement! If I write `return Possibility(reject)` it works. (But in the real code, the lambda takes a parameter and reject is a method of that parameter, so I can't do that. My workaround is to turn the lambda into a function, but that's verbose.)

If you can't fix things so that the lambda inherits the "never returns" bit, then how about making the error clearer, e.g. "Lambda never returns"?
Return of `NoReturn`
<!--
If you're not sure whether what you're experiencing is a mypy bug, please see the "Question and Help" form instead.
Please consider:

- checking our common issues page: https://mypy.readthedocs.io/en/stable/common_issues.html
- searching our issue tracker: https://github.com/python/mypy/issues to see if it's already been reported
- asking on gitter chat: https://gitter.im/python/typing
-->

**Bug Report**

<!--
If you're reporting a problem with a specific library function, the typeshed tracker is better suited for this report: https://github.com/python/typeshed/issues

If the project you encountered the issue in is open source, please provide a link to the project.
-->

When a function receives as argument another function that does not return, then the error `Return statement in function which does not return` can raise or not depending on the argument construction:
- if is lambda raises error
- if is defined using  `def` error does not raise

**To Reproduce**

```python
from typing import Callable, NoReturn

def _raise(err: Exception) -> NoReturn:
    raise err

def _foo(f: Callable[[], NoReturn]) -> NoReturn:
    f()

def _raise_2() -> NoReturn:
    raise Exception("foo")

value: bool = True # dummy var to enable to put the two cases
if value:
    y: NoReturn = _foo(_raise_2) # mypy does not complain
else:
    x: NoReturn = _foo(lambda: _raise(Exception("foo"))) # mypy complains
```

**Expected Behavior**

<!--
How did you expect mypy to behave? It’s fine if you’re not sure your understanding is correct.
Write down what you thought would happen. If you expected no errors, delete this section.
-->
- mypy should not complain about non-existent return statements in the lambda definition (case of variable `x`) as the lambda definition is equivalent to the `def _raise_2` definition.
- even if `return f()` is used in `_foo` this should not trigger an error when the type being returned is also `NoReturn`

**Actual Behavior**
- mypy sees an implicit return statement in lambdas definition
- return of a `NoReturn` is not allowed

<!-- What went wrong? Paste mypy's output. -->

**Your Environment**
`error: Return statement in function which does not return  [misc]`
<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: 1.0.1
- Mypy command-line flags: --show-error-codes
- Mypy configuration options from `mypy.ini` (and other config files):
```
[mypy]
warn_no_return = True
```
- Python version used: 3.11

<!-- You can freely edit this text, please remove all the lines you believe are unnecessary. -->



Discussion:
I think this is a duplicate of #9590, information about a lambda seems to get lost when used inline (not assigned to a variable).

The code works if you change it to:
```
a = lambda: reject()
return Possibility(a)
```


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -4494,7 +4494,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
             is_lambda = isinstance(self.scope.top_function(), LambdaExpr)
             if isinstance(return_type, UninhabitedType):
                 # Avoid extra error messages for failed inference in lambdas
-                if not is_lambda or not return_type.ambiguous:
+                if not is_lambda and not return_type.ambiguous:
                     self.fail(message_registry.NO_RETURN_EXPECTED, s)
                     return
 


Test patch:
diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test
--- a/test-data/unit/check-unreachable-code.test
+++ b/test-data/unit/check-unreachable-code.test
@@ -1502,15 +1502,8 @@ from typing import Callable, NoReturn
 def foo() -> NoReturn:
     raise
 
-f = lambda: foo()
+f1 = 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)
+f2: Callable[[], NoReturn] = lambda: foo()
 x = 0  # not unreachable