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

Can't infer TypeVar through lambda that returns None #6619

Open
chkno opened this issue Apr 3, 2019 · 2 comments
Open

Can't infer TypeVar through lambda that returns None #6619

chkno opened this issue Apr 3, 2019 · 2 comments

Comments

@chkno
Copy link

@chkno chkno commented Apr 3, 2019

Using a TypeVar to forward the return type of a Callable argument doesn't work when the argument is a lambda with return type None.

For example, this benchmark function calls its argument & reports how long it ran, forwarding the return value normally. This works fine.

import time
from typing import Callable, TypeVar

T = TypeVar('T')

def benchmark(f: Callable[[], T]) -> T:
    """Call f.  Report how long it ran."""
    start = time.time()
    ret = f()
    stop = time.time()
    print('%f seconds' % (stop - start))
    return ret

def fib(n: int) -> int:
    return 1 if n <= 1 else fib(n-1) + fib(n-2)

print(benchmark(lambda: fib(10)))  # Prints: 0.000062 seconds, 89
print(benchmark(lambda: fib(20)))  # Prints: 0.007462 seconds, 10946
print(benchmark(lambda: fib(30)))  # Prints: 0.961943 seconds, 1346269

Things go awry when the Callable passed in is a lambda with return type None:

benchmark(lambda: None)           # error: Cannot infer type argument 1 of "benchmark"

import random
benchmark(lambda: random.seed(4)) # error: Cannot infer type argument 1 of "benchmark"

import re
benchmark(lambda: re.purge())     # error: Cannot infer type argument 1 of "benchmark"

The problem seems specifically related to inline lambdas. Passing a function that returns None works fine. Storing the lambda in a variable before calling it also works fine.

def nop() -> None:
    pass

def nop2() -> None:
    return None

named_lambda = lambda: None
named_seed   = lambda: random.seed(4)
named_purge  = lambda: re.purge()

# These are all fine
benchmark(nop)
benchmark(nop2)
benchmark(re.purge)
benchmark(named_lambda)
benchmark(named_seed)
benchmark(named_purge)

The problem is not related to holding the return value in a variable; this direct-return variant has the same problem:

def benchmark2(f: Callable[[], T]) -> T:
    """Call f.  Report how long it ran."""
    start = time.time()
    try:
        return f()
    finally:
        stop = time.time()
        print('%f seconds' % (stop - start))

The problem occurs when mypy is run with no arguments, with --strict, with --strict-optional, and with both flags. I observe the problem in both mypy-0.620 and mypy-0.670 (the latest release).

@ilevkivskyi

This comment has been minimized.

Copy link
Collaborator

@ilevkivskyi ilevkivskyi commented Apr 6, 2019

I can reproduce this. It is hard to tell what causes this bug. But inference for inline lambdas is generally tricky.

@JukkaL

This comment has been minimized.

Copy link
Collaborator

@JukkaL JukkaL commented Nov 27, 2019

Increasing priority since this breaks a relatively straightforward-looking use case, and there's no trivial workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.