Skip to content
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

Order of clause in one-line if/else statement yields different mypy behaviors #8519

Closed
fgmehlin opened this issue Mar 9, 2020 · 3 comments
Closed
Labels
bug mypy got something wrong topic-join-v-union Using join vs. using unions topic-ternary-expression a if b else c

Comments

@fgmehlin
Copy link

fgmehlin commented Mar 9, 2020

I may have encountered a bug in Mypy's inference of types when using a one-line if/else statement:

The issue appears when returning a lambda function or a standard built-in (in that case print), depending on a bool.

import typing as t

def get_print_fn(quiet: bool) -> t.Callable[..., None]:
    """Return the standard print function if quiet is False, else an empty function."""
    return (lambda *args, **kwargs: None) if quiet else print

^^^ The above raises the following Mypy error:
Incompatible return value type (got "function", expected "Callable[..., None]")

However, reversing the condition like below does not show any error.

import typing as t

def get_print_fn(quiet: bool) -> t.Callable[..., None]:
    """Return the standard print function if quiet is False, else an empty function."""
    return print if not quiet else lambda *args, **kwargs: None

This somehow suggests that mypy is using the type of the if block to infer the return type? I'm not sure whether this is intended or indeed a bug.

Meta information

  • mypy version: 0.761
  • mypy.ini
[mypy]
python_version = 3.7
warn_return_any = True
warn_unused_ignores = True
warn_unreachable = True
namespace_packages = True
show_column_numbers = True
strict_equality = True
no_implicit_optional = True
disallow_untyped_defs = True
disallow_any_generics = True
disallow_untyped_calls = True
@msullivan
Copy link
Collaborator

The issue here is that typechecking of conditional expressions works by computing the type of the if branch, and then using that as "context" when computing the type of the else branch. When print's type is used as context, that causes the lambda to get inferred as that type, but if the lambda's type is the context, that doesn't change anything.

Then, the two types get joined, and the join ends up as function because the sides aren't similar. So maybe the bug here is that def (*Any, **Any) should be compatible with everything?

@AlexWaygood AlexWaygood added bug mypy got something wrong topic-ternary-expression a if b else c topic-join-v-union Using join vs. using unions labels Mar 28, 2022
@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 28, 2022

Both examples cause mypy to emit an error on 0.941:

import typing as t

def get_print_fn(quiet: bool) -> t.Callable[..., None]:
    """Return the standard print function if quiet is False, else an empty function."""
    return (lambda *args, **kwargs: None) if quiet else print  # error: Incompatible return value type (got "function", expected "Callable[..., None]")

def get_print_fn2(quiet: bool) -> t.Callable[..., None]:
    """Return the standard print function if quiet is False, else an empty function."""
    return print if not quiet else lambda *args, **kwargs: None  # error: Incompatible return value type (got "function", expected "Callable[..., None]")

I think this might be due to changes to the print stub in typeshed.

@hauntsaninja
Copy link
Collaborator

Fixed by #17427

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-join-v-union Using join vs. using unions topic-ternary-expression a if b else c
Projects
None yet
Development

No branches or pull requests

4 participants