Repository: python/mypy

Issue:
Mypy complains about lambda that returns None
Mypy gives a bogus error when giving a lambda that returns `None` when a callable returning `None` is expected:

```
from typing import Callable

def f(x: Callable[[], None]) -> None: pass

# Argument 1 to "f" has incompatible type Callable[[], None]; expected Callable[[], None]
f(lambda: None)  
```

Likely this is due to `Void` vs. `NoneTyp`, and we'd have to replace an inferred `NoneTyp` of a lambda expression with `Void`.



Discussion:
Possibly this would also be fixed once we introduce strict Optional checking.

Maybe -- we'd likely still want to keep no return value from a function and a regular `None` value separate concepts, though.

Would be fixed by #1278, which will likely happen as part of strict Optional checking.

I actually think we will not want to have no return value distinct from regular `None`s once we have strict Optional checking.  It adds extra complexity (both in code and for users), and shouldn't check any important cases that Optional checking wouldn't catch.

> I actually think we will not want to have no return value distinct from regular `None` [...]

But [PEP 8](https://www.python.org/dev/peps/pep-0008/#id48) states:

> Either all return statements in a function should return an expression, or none of them should.

It then shows two "No" examples. I would like mypy to catch those:

```
def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)
```

even though they would be annotated with `-> None`. (The "Yes" examples are uninteresting -- they should just use `-> Optional[something]`.)

For `foo()`:  I'd like mypy to catch that too.  I think it's reasonable to expect all functions that don't return `None` (even if they do return some `Optional` type) to have explicit `return`s on all possible execution paths.  That'll require some control flow analysis, though, so I don't think we should aim to have it in the first version of Optional checking.

For `bar()`:  If the type of `bar()` is `Optional[float]`, I we should only produce an error on this code if we provide a flag to turn it off.  It's solely a style issue and not one of correctness -- if there are people out there who have been inconsistent about `return` vs `return None`, I don't think we should force them to fix it before adopting mypy.  In general, I think enforcing style constraints should be considered secondary to mypy's primary goal of enforcing correctness, and that any purely stylistic error should have a flag that turns it off (and that we should perhaps also have an aggregate flag that turns off all style warnings).
If the type of `bar()` is not an `Optional` type, then the no-argument return statement should produce a error.

Both foo() and bar() have Optional[float] as type. Both have a style error; I don't consider falling of the end different from a return without value in the middle.

I think it's reasonable if mypy enforces things that could be considered style issues, and I don't think we always need to provide a way to turn them off. (Taken to the extreme, being consistent in the return type is a style issue too.)

Hmm.  I agree that both of those are style errors.  I think the former is slightly more likely to be accidental than the latter, but I think I was wrong to propose that they be treated differently.

I also think that it's reasonable for mypy to enforce style issues, especially when they may have correctness implications (e.g. forgetting to actually write a return statement is pretty common in my experience).  I'm not sure we _always_ need to provide a way to turn them off, but I think we should consider it on a case-by-case basis, because they have the potential to be unnecessary roadblocks to people using mypy.

I actually do think there's a reasonable line we can draw between style and correctness issues -- it can be fuzzy at times, but by and large I think it's a distinction worth making.


Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -1347,6 +1347,11 @@ def visit_func_expr(self, e: FuncExpr) -> Type:
             if e.expr() not in self.chk.type_map:
                 self.accept(e.expr())
             ret_type = self.chk.type_map[e.expr()]
+            if isinstance(ret_type, NoneTyp):
+                # For "lambda ...: None", just use type from the context.
+                # Important when the context is Callable[..., None] which
+                # really means Void. See #1425.
+                return inferred_type
             return replace_callable_return_type(inferred_type, ret_type)
 
     def infer_lambda_type_using_context(self, e: FuncExpr) -> CallableType:


Test patch:
diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test
--- a/test-data/unit/check-inference-context.test
+++ b/test-data/unit/check-inference-context.test
@@ -611,6 +611,13 @@ T = TypeVar('T')
 def foo(arg: Callable[..., T]) -> None: pass
 foo(lambda: 1)
 
+[case testLambdaNoneInContext]
+from typing import Callable
+def f(x: Callable[[], None]) -> None: pass
+def g(x: Callable[[], int]) -> None: pass
+f(lambda: None)
+g(lambda: None)
+
 
 -- Overloads + generic functions
 -- -----------------------------
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
@@ -1041,6 +1041,17 @@ y = f(lambda x: x, 1) # Fail
 main:4: error: Cannot infer type argument 1 of "f"
 main:4: error: Argument 2 to "f" has incompatible type "int"; expected "str"
 
+[case testInferLambdaNone]
+from typing import Callable
+def f(x: Callable[[], None]) -> None: pass
+def g(x: Callable[[], int]) -> None: pass
+a = lambda: None
+f(a) # E: Argument 1 to "f" has incompatible type Callable[[], None]; expected Callable[[], None]
+g(a)
+b = lambda: None  # type: Callable[[], None]
+f(b)
+g(b) # E: Argument 1 to "g" has incompatible type Callable[[], None]; expected Callable[[], int]
+
 
 -- Boolean operators
 -- -----------------