From 09c137113b0358c5992cf793f2dd59500d6943c3 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 1 Sep 2022 00:04:40 -0700 Subject: [PATCH] Make `infer_lambda_type_using_context` always set lambda fallback Fixes #9234 This diff fixes a bug in `infer_lambda_type_using_context` where it blindly trusted and reused whatever fallback the context callable was using. This causes mypy to crash in the case where the context was a dynamic constructor. This is because... 1. The constructor has a fallback of `builtins.type` 2. The `infer_lambda_type_using_context` returns a CallableType with this fallback. 3. The join of the LHS and RHS of the ternary ends up being a `def (Any) -> Any` with a fallback of `builtins.type` -- see https://github.com/python/mypy/blob/7ffaf230a3984faaf848fe314cf275b854a0cdb0/mypy/join.py#L578 4. Later, we call `CallableType.is_type_obj()` and `CallableType.type_object()`. The former is supposed to be a guard for the former, but what happens instead is that the former succeeds due to the fallback and the latter fails an assert because the return type is Any, not an Instance: https://github.com/python/mypy/blob/7ffaf230a3984faaf848fe314cf275b854a0cdb0/mypy/types.py#L1771 I opted to fix this by modifying `infer_lambda_type_using_context` so it overrides the fallback to always be `builtins.function` -- I don't think it makes sense for it to be anything else. --- mypy/checkexpr.py | 4 ++++ test-data/unit/check-inference.test | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fba6caec4072..e4b50b87b1f2 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4230,6 +4230,10 @@ def infer_lambda_type_using_context( callable_ctx = get_proper_type(replace_meta_vars(ctx, ErasedType())) assert isinstance(callable_ctx, CallableType) + # The callable_ctx may have a fallback of builtins.type if the context + # is a constructor -- but this fallback doesn't make sense for lambdas. + callable_ctx = callable_ctx.copy_modified(fallback=self.named_type("builtins.function")) + if callable_ctx.type_guard is not None: # Lambda's return type cannot be treated as a `TypeGuard`, # because it is implicit. And `TypeGuard`s must be explicit. diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index c09424138e49..1e6d947d6113 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1276,6 +1276,21 @@ class A: def h(x: Callable[[], int]) -> None: pass +[case testLambdaJoinWithDynamicConstructor] +from typing import Any, Union + +class Wrapper: + def __init__(self, x: Any) -> None: ... + +def f(cond: bool) -> Any: + f = Wrapper if cond else lambda x: x + reveal_type(f) # N: Revealed type is "def (x: Any) -> Any" + return f(3) + +def g(cond: bool) -> Any: + f = lambda x: x if cond else Wrapper + reveal_type(f) # N: Revealed type is "def (x: Any) -> Any" + return f(3) -- Boolean operators -- -----------------