Skip to content

Commit 251ba45

Browse files
elazarggvanrossum
authored andcommitted
Infer something for lambda without context (#2634)
Fixes #2631
1 parent dbacb8c commit 251ba45

File tree

6 files changed

+46
-33
lines changed

6 files changed

+46
-33
lines changed

mypy/checkexpr.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
88
TupleType, TypedDictType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType,
99
PartialType, DeletedType, UnboundType, UninhabitedType, TypeType,
10-
true_only, false_only, is_named_instance, function_type, FunctionLike,
10+
true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike,
1111
get_typ_args, set_typ_args,
1212
)
1313
from mypy.nodes import (
@@ -18,7 +18,7 @@
1818
ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator,
1919
ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
2020
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr,
21-
TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF,
21+
TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF,
2222
UNBOUND_TVAR, BOUND_TVAR,
2323
)
2424
from mypy import nodes
@@ -1743,15 +1743,11 @@ def visit_func_expr(self, e: FuncExpr) -> Type:
17431743
inferred_type = self.infer_lambda_type_using_context(e)
17441744
if not inferred_type:
17451745
# No useful type context.
1746-
ret_type = e.expr().accept(self.chk)
1747-
if not e.arguments:
1748-
# Form 'lambda: e'; just use the inferred return type.
1749-
return CallableType([], [], [], ret_type, self.named_type('builtins.function'))
1750-
else:
1751-
# TODO: Consider reporting an error. However, this is fine if
1752-
# we are just doing the first pass in contextual type
1753-
# inference.
1754-
return AnyType()
1746+
ret_type = self.accept(e.expr())
1747+
if isinstance(ret_type, NoneTyp):
1748+
ret_type = Void()
1749+
fallback = self.named_type('builtins.function')
1750+
return callable_type(e, fallback, ret_type)
17551751
else:
17561752
# Type context available.
17571753
self.chk.check_func_item(e, type_override=inferred_type)
@@ -1765,7 +1761,7 @@ def visit_func_expr(self, e: FuncExpr) -> Type:
17651761
return inferred_type
17661762
return replace_callable_return_type(inferred_type, ret_type)
17671763

1768-
def infer_lambda_type_using_context(self, e: FuncExpr) -> CallableType:
1764+
def infer_lambda_type_using_context(self, e: FuncExpr) -> Optional[CallableType]:
17691765
"""Try to infer lambda expression type using context.
17701766
17711767
Return None if could not infer type.
@@ -1799,6 +1795,9 @@ def infer_lambda_type_using_context(self, e: FuncExpr) -> CallableType:
17991795
arg_kinds=arg_kinds
18001796
)
18011797

1798+
if ARG_STAR in arg_kinds or ARG_STAR2 in arg_kinds:
1799+
# TODO treat this case appropriately
1800+
return None
18021801
if callable_ctx.arg_kinds != arg_kinds:
18031802
# Incompatible context; cannot use it to infer types.
18041803
self.chk.fail(messages.CANNOT_INFER_LAMBDA_TYPE, e)

mypy/main.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
import time
1010

11-
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple
11+
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Type, cast
1212

1313
from mypy import build
1414
from mypy import defaults
@@ -128,8 +128,8 @@ def process_options(args: List[str],
128128
"""Parse command line arguments."""
129129

130130
# Make the help output a little less jarring.
131-
help_factory = (lambda prog:
132-
argparse.RawDescriptionHelpFormatter(prog=prog, max_help_position=28))
131+
help_factory = cast(Type[argparse.HelpFormatter], (lambda prog:
132+
argparse.RawDescriptionHelpFormatter(prog=prog, max_help_position=28)))
133133
parser = argparse.ArgumentParser(prog='mypy', epilog=FOOTER,
134134
fromfile_prefix_chars='@',
135135
formatter_class=help_factory)
@@ -480,7 +480,7 @@ def get_init_file(dir: str) -> Optional[str]:
480480
# These two are for backwards compatibility
481481
'silent_imports': bool,
482482
'almost_silent': bool,
483-
}
483+
} # type: Dict[str, Any]
484484

485485

486486
def parse_config_file(options: Options, filename: str) -> None:

mypy/types.py

+18-14
Original file line numberDiff line numberDiff line change
@@ -1720,20 +1720,24 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike
17201720
# Implicit type signature with dynamic types.
17211721
# Overloaded functions always have a signature, so func must be an ordinary function.
17221722
assert isinstance(func, mypy.nodes.FuncItem), str(func)
1723-
fdef = cast(mypy.nodes.FuncItem, func)
1724-
name = func.name()
1725-
if name:
1726-
name = '"{}"'.format(name)
1727-
1728-
return CallableType(
1729-
[AnyType()] * len(fdef.arg_names),
1730-
fdef.arg_kinds,
1731-
[None if argument_elide_name(n) else n for n in fdef.arg_names],
1732-
AnyType(),
1733-
fallback,
1734-
name,
1735-
implicit=True,
1736-
)
1723+
return callable_type(cast(mypy.nodes.FuncItem, func), fallback)
1724+
1725+
1726+
def callable_type(fdef: mypy.nodes.FuncItem, fallback: Instance,
1727+
ret_type: Type = None) -> CallableType:
1728+
name = fdef.name()
1729+
if name:
1730+
name = '"{}"'.format(name)
1731+
1732+
return CallableType(
1733+
[AnyType()] * len(fdef.arg_names),
1734+
fdef.arg_kinds,
1735+
[None if argument_elide_name(n) else n for n in fdef.arg_names],
1736+
ret_type or AnyType(),
1737+
fallback,
1738+
name,
1739+
implicit=True,
1740+
)
17371741

17381742

17391743
def get_typ_args(tp: Type) -> List[Type]:

test-data/unit/check-inference-context.test

+10
Original file line numberDiff line numberDiff line change
@@ -577,12 +577,22 @@ list_a = [] # type: List[A]
577577
f(list_a, lambda a: a.x)
578578
[builtins fixtures/list.pyi]
579579

580+
[case testLambdaWithoutContext]
581+
reveal_type(lambda x: x) # E: Revealed type is 'def (x: Any) -> Any'
582+
reveal_type(lambda x: 1) # E: Revealed type is 'def (x: Any) -> builtins.int'
583+
584+
[case testLambdaContextVararg]
585+
from typing import Callable
586+
def f(t: Callable[[str], str]) -> str: ''
587+
f(lambda *_: '')
588+
580589
[case testInvalidContextForLambda]
581590
from typing import Callable
582591
f = lambda x: A() # type: Callable[[], A]
583592
f2 = lambda: A() # type: Callable[[A], A]
584593
class A: pass
585594
[out]
595+
main:2: error: Incompatible types in assignment (expression has type Callable[[Any], A], variable has type Callable[[], A])
586596
main:2: error: Cannot infer type of lambda
587597
main:3: error: Incompatible types in assignment (expression has type Callable[[], A], variable has type Callable[[A], A])
588598
main:3: error: Cannot infer type of lambda

test-data/unit/check-inference.test

+2-2
Original file line numberDiff line numberDiff line change
@@ -1027,8 +1027,8 @@ from typing import Callable
10271027
def f(x: Callable[[], None]) -> None: pass
10281028
def g(x: Callable[[], int]) -> None: pass
10291029
a = lambda: None
1030-
f(a) # E: Argument 1 to "f" has incompatible type Callable[[], None]; expected Callable[[], None]
1031-
g(a)
1030+
f(a)
1031+
g(a) # E: Argument 1 to "g" has incompatible type Callable[[], None]; expected Callable[[], int]
10321032
b = lambda: None # type: Callable[[], None]
10331033
f(b)
10341034
g(b) # E: Argument 1 to "g" has incompatible type Callable[[], None]; expected Callable[[], int]

test-data/unit/check-optional.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ reveal_type(z2) # E: Revealed type is 'Union[builtins.int, builtins.str, builti
103103

104104
[case testLambdaReturningNone]
105105
f = lambda: None
106-
x = f()
106+
x = f() # E: Function does not return a value
107107

108108
[case testNoneArgumentType]
109109
def f(x: None) -> None: pass

0 commit comments

Comments
 (0)