Skip to content

Infer something for lambda without context #2634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how does the shadowing work, but it seems to work (lambda x: x does not return the type of an outer x)

else:
# Type context available.
self.chk.check_func_item(e, type_override=inferred_type)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silence warning in line 562: "object" not callable



def parse_config_file(options: Options, filename: str) -> None:
Expand Down
32 changes: 18 additions & 14 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-inference-context.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we don't need this error message now?

Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down