Repository: python/mypy

Issue:
Type of lambda without context should not be inferred as `Any`
See https://github.com/python/mypy/issues/2567#issuecomment-270188135:

A `lambda` should always be at least least inferred as `Callable` (with a specific number of arguments). We know as much about it as we do about an unannotated `def`. This is really strange:
```python
def f(x): return x
g = lambda x: x
reveal_type(f)
reveal_type(g)
```
Gives
```
__tmp__.py:3: error: Revealed type is 'def (x: Any) -> Any'
__tmp__.py:4: error: Revealed type is 'Any'
```
I think they should reveal the same type.


Discussion:

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -7,7 +7,7 @@
     Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
     TupleType, TypedDictType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType,
     PartialType, DeletedType, UnboundType, UninhabitedType, TypeType,
-    true_only, false_only, is_named_instance, function_type, FunctionLike,
+    true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike,
     get_typ_args, set_typ_args,
 )
 from mypy.nodes import (
@@ -18,7 +18,7 @@
     ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator,
     ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
     DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr,
-    TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF,
+    TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF,
     UNBOUND_TVAR, BOUND_TVAR,
 )
 from mypy import nodes
@@ -1743,15 +1743,11 @@ def visit_func_expr(self, e: FuncExpr) -> Type:
         inferred_type = self.infer_lambda_type_using_context(e)
         if not inferred_type:
             # No useful type context.
-            ret_type = e.expr().accept(self.chk)
-            if not e.arguments:
-                # Form 'lambda: e'; just use the inferred return type.
-                return CallableType([], [], [], ret_type, self.named_type('builtins.function'))
-            else:
-                # TODO: Consider reporting an error. However, this is fine if
-                # we are just doing the first pass in contextual type
-                # inference.
-                return AnyType()
+            ret_type = self.accept(e.expr())
+            if isinstance(ret_type, NoneTyp):
+                ret_type = Void()
+            fallback = self.named_type('builtins.function')
+            return callable_type(e, fallback, ret_type)
         else:
             # Type context available.
             self.chk.check_func_item(e, type_override=inferred_type)
@@ -1765,7 +1761,7 @@ def visit_func_expr(self, e: FuncExpr) -> Type:
                 return inferred_type
             return replace_callable_return_type(inferred_type, ret_type)
 
-    def infer_lambda_type_using_context(self, e: FuncExpr) -> CallableType:
+    def infer_lambda_type_using_context(self, e: FuncExpr) -> Optional[CallableType]:
         """Try to infer lambda expression type using context.
 
         Return None if could not infer type.
@@ -1799,6 +1795,9 @@ def infer_lambda_type_using_context(self, e: FuncExpr) -> CallableType:
                 arg_kinds=arg_kinds
             )
 
+        if ARG_STAR in arg_kinds or ARG_STAR2 in arg_kinds:
+            # TODO treat this case appropriately
+            return None
         if callable_ctx.arg_kinds != arg_kinds:
             # Incompatible context; cannot use it to infer types.
             self.chk.fail(messages.CANNOT_INFER_LAMBDA_TYPE, e)
diff --git a/mypy/main.py b/mypy/main.py
--- a/mypy/main.py
+++ b/mypy/main.py
@@ -8,7 +8,7 @@
 import sys
 import time
 
-from typing import Any, Dict, List, Mapping, Optional, Set, Tuple
+from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Type, cast
 
 from mypy import build
 from mypy import defaults
@@ -128,8 +128,8 @@ def process_options(args: List[str],
     """Parse command line arguments."""
 
     # Make the help output a little less jarring.
-    help_factory = (lambda prog:
-                    argparse.RawDescriptionHelpFormatter(prog=prog, max_help_position=28))
+    help_factory = cast(Type[argparse.HelpFormatter], (lambda prog:
+                        argparse.RawDescriptionHelpFormatter(prog=prog, max_help_position=28)))
     parser = argparse.ArgumentParser(prog='mypy', epilog=FOOTER,
                                      fromfile_prefix_chars='@',
                                      formatter_class=help_factory)
@@ -480,7 +480,7 @@ def get_init_file(dir: str) -> Optional[str]:
     # These two are for backwards compatibility
     'silent_imports': bool,
     'almost_silent': bool,
-}
+}  # type: Dict[str, Any]
 
 
 def parse_config_file(options: Options, filename: str) -> None:
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -1720,20 +1720,24 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike
         # Implicit type signature with dynamic types.
         # Overloaded functions always have a signature, so func must be an ordinary function.
         assert isinstance(func, mypy.nodes.FuncItem), str(func)
-        fdef = cast(mypy.nodes.FuncItem, func)
-        name = func.name()
-        if name:
-            name = '"{}"'.format(name)
-
-        return CallableType(
-            [AnyType()] * len(fdef.arg_names),
-            fdef.arg_kinds,
-            [None if argument_elide_name(n) else n for n in fdef.arg_names],
-            AnyType(),
-            fallback,
-            name,
-            implicit=True,
-        )
+        return callable_type(cast(mypy.nodes.FuncItem, func), fallback)
+
+
+def callable_type(fdef: mypy.nodes.FuncItem, fallback: Instance,
+                  ret_type: Type = None) -> CallableType:
+    name = fdef.name()
+    if name:
+        name = '"{}"'.format(name)
+
+    return CallableType(
+        [AnyType()] * len(fdef.arg_names),
+        fdef.arg_kinds,
+        [None if argument_elide_name(n) else n for n in fdef.arg_names],
+        ret_type or AnyType(),
+        fallback,
+        name,
+        implicit=True,
+    )
 
 
 def get_typ_args(tp: Type) -> List[Type]:


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
@@ -577,12 +577,22 @@ list_a = [] # type: List[A]
 f(list_a, lambda a: a.x)
 [builtins fixtures/list.pyi]
 
+[case testLambdaWithoutContext]
+reveal_type(lambda x: x)  # E: Revealed type is 'def (x: Any) -> Any'
+reveal_type(lambda x: 1)  # E: Revealed type is 'def (x: Any) -> builtins.int'
+
+[case testLambdaContextVararg]
+from typing import Callable
+def f(t: Callable[[str], str]) -> str: ''
+f(lambda *_: '')
+
 [case testInvalidContextForLambda]
 from typing import Callable
 f = lambda x: A() # type: Callable[[], A]
 f2 = lambda: A() # type: Callable[[A], A]
 class A: pass
 [out]
+main:2: error: Incompatible types in assignment (expression has type Callable[[Any], A], variable has type Callable[[], A])
 main:2: error: Cannot infer type of lambda
 main:3: error: Incompatible types in assignment (expression has type Callable[[], A], variable has type Callable[[A], A])
 main:3: error: Cannot infer type of lambda
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
@@ -1027,8 +1027,8 @@ 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)
+f(a)
+g(a) # E: Argument 1 to "g" has incompatible type Callable[[], None]; expected Callable[[], int]
 b = lambda: None  # type: Callable[[], None]
 f(b)
 g(b) # E: Argument 1 to "g" has incompatible type Callable[[], None]; expected Callable[[], int]
diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test
--- a/test-data/unit/check-optional.test
+++ b/test-data/unit/check-optional.test
@@ -103,7 +103,7 @@ reveal_type(z2)  # E: Revealed type is 'Union[builtins.int, builtins.str, builti
 
 [case testLambdaReturningNone]
 f = lambda: None
-x = f()
+x = f()  # E: Function does not return a value
 
 [case testNoneArgumentType]
 def f(x: None) -> None: pass