python / python/mypy

Incorrect specialisation of inferred lambda types based on early binding assumption (sometimes)

Open
#9,658 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

If a lambda is defined in a partially specialised context, its type implicitly captures the specialisation of the context, even though the specialisation may no longer hold at the point it gets executed. This changes unexpectedly if a type hint is given.

For example

def example_1(p1: Optional[int]):
    if p1 is not None:
        late_p1 = (lambda: p1)
        reveal_type(late_p1)

Revealed type is 'def () -> builtins.int'

However, if we give it a type annotation on the assignment line, it complains.

def example_2(p1: Optional[int]):
    if p1 is not None:
        late_p1: Callable[[], int] = (lambda: p1)
        reveal_type(late_p1)

This time it abandons the local context that p1 has been checked against None, and considers that the broadest type of p1 is Optional. And even though that signature is exactly what it otherwise infers, it now raises two errors on the same assignment line:

error: Incompatible types in assignment (expression has type "Callable[[], Optional[int]]", variable has type "Callable[[], int]")
error: Incompatible return value type (got "Optional[int]", expected "int")

The behaviour in example_1 is buggy: because of late binding it is possible for the former behaviour to leak wider types.

def example_3(p1: Optional[int]):
    if p1 is not None:
        late_p1 = (lambda: p1)
    p1 = None
    inc = late_p1() + 1 # This will crash, but MyPy is perfectly confident that `late_p1()` is an integer.

Example_2 considered in isolation exhibits expected behaviour.
It is a bit odd that type a type hint that matches the original inferred type changes and indeed is suddenly incompatible with the inferred type.

Seen on this StackOverflow question: https://stackoverflow.com/q/64203221/1688786

  • Mypy version used: mypy 0.790
  • Python version used: 3.8.2
  • Operating system and version: Windows 10

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing examples 1–3 with the reported mypy version and compare lambda inference with and without the Callable annotation. Trace the type-checking path for narrowed variables captured by lambdas; done means late-bound captures do not retain invalid narrowing, while the annotated case behaves consistently without duplicate or contradictory errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.