Repository: python/mypy

Issue:
Can't infer TypeVar through lambda that returns None
Using a TypeVar to forward the return type of a Callable argument doesn't work when the argument is a lambda with return type None.

For example, this benchmark function calls its argument & reports how long it ran, forwarding the return value normally.  This works fine.

```python
import time
from typing import Callable, TypeVar

T = TypeVar('T')

def benchmark(f: Callable[[], T]) -> T:
    """Call f.  Report how long it ran."""
    start = time.time()
    ret = f()
    stop = time.time()
    print('%f seconds' % (stop - start))
    return ret

def fib(n: int) -> int:
    return 1 if n <= 1 else fib(n-1) + fib(n-2)

print(benchmark(lambda: fib(10)))  # Prints: 0.000062 seconds, 89
print(benchmark(lambda: fib(20)))  # Prints: 0.007462 seconds, 10946
print(benchmark(lambda: fib(30)))  # Prints: 0.961943 seconds, 1346269
```

Things go awry when the Callable passed in is a lambda with return type None:

```python
benchmark(lambda: None)           # error: Cannot infer type argument 1 of "benchmark"

import random
benchmark(lambda: random.seed(4)) # error: Cannot infer type argument 1 of "benchmark"

import re
benchmark(lambda: re.purge())     # error: Cannot infer type argument 1 of "benchmark"
```

The problem seems specifically related to _inline_ lambdas.  Passing a function that returns None works fine.  Storing the lambda in a variable before calling it also works fine.

```python
def nop() -> None:
    pass

def nop2() -> None:
    return None

named_lambda = lambda: None
named_seed   = lambda: random.seed(4)
named_purge  = lambda: re.purge()

# These are all fine
benchmark(nop)
benchmark(nop2)
benchmark(re.purge)
benchmark(named_lambda)
benchmark(named_seed)
benchmark(named_purge)
```
The problem is _not_ related to holding the return value in a variable; this direct-return variant has the same problem:

```python
def benchmark2(f: Callable[[], T]) -> T:
    """Call f.  Report how long it ran."""
    start = time.time()
    try:
        return f()
    finally:
        stop = time.time()
        print('%f seconds' % (stop - start))
```
The problem occurs when mypy is run with no arguments, with `--strict`, with `--strict-optional`, and with both flags.  I observe the problem in both mypy-0.620 and mypy-0.670 (the latest release).


Discussion:
I can reproduce this. It is hard to tell what causes this bug. But inference for inline lambdas is generally tricky.
Increasing priority since this breaks a relatively straightforward-looking use case, and there's no trivial workaround.

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -3368,12 +3368,6 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
                 # TODO: return expression must be accepted before exiting function scope.
                 self.accept(e.expr(), allow_none_return=True)
             ret_type = self.chk.type_map[e.expr()]
-            if isinstance(get_proper_type(ret_type), NoneType):
-                # For "lambda ...: None", just use type from the context.
-                # Important when the context is Callable[..., None] which
-                # really means Void. See #1425.
-                self.chk.return_types.pop()
-                return inferred_type
             self.chk.return_types.pop()
             return replace_callable_return_type(inferred_type, ret_type)
 


Test patch:
diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test
--- a/test-data/unit/check-inference.test
+++ b/test-data/unit/check-inference.test
@@ -3144,3 +3144,35 @@ reveal_type(S() if bool() else '')  # N: Revealed type is 'builtins.unicode'
 reveal_type('' if bool() else S())  # N: Revealed type is 'builtins.unicode'
 reveal_type(S() if bool() else str())  # N: Revealed type is 'builtins.unicode'
 reveal_type(str() if bool() else S())  # N: Revealed type is 'builtins.unicode'
+
+[case testInferCallableReturningNone1]
+# flags: --no-strict-optional
+from typing import Callable, TypeVar
+
+T = TypeVar("T")
+
+def f(x: Callable[[], T]) -> T:
+    return x()
+
+reveal_type(f(lambda: None))  # N: Revealed type is 'None'
+reveal_type(f(lambda: 1))  # N: Revealed type is 'builtins.int*'
+
+def g() -> None: pass
+
+reveal_type(f(g))  # N: Revealed type is 'None'
+
+[case testInferCallableReturningNone2]
+# flags: --strict-optional
+from typing import Callable, TypeVar
+
+T = TypeVar("T")
+
+def f(x: Callable[[], T]) -> T:
+    return x()
+
+reveal_type(f(lambda: None))  # N: Revealed type is 'None'
+reveal_type(f(lambda: 1))  # N: Revealed type is 'builtins.int*'
+
+def g() -> None: pass
+
+reveal_type(f(g))  # N: Revealed type is 'None'