Skip to content

Inference of identity lambda into union of keyword-only function and its identity #11603

Description

@steovd

Description

In the code below, Pyright infers the first lambda as (x: int) -> int which fails to assign to either union member.

import typing

class Fail1:
    def __call__(self, *, kwarg: int) -> typing.Self: ...

f1: typing.Callable[[Fail1], Fail1] | Fail1 = lambda x: x  # ERROR
f0: typing.Callable[[Fail1], Fail1] = lambda x: x  # OK
$ pyright repro.py
repro.py:7:40 - error: Type "(x: int) -> int" is not assignable to declared type "((Fail1) -> Fail1) | Fail1"
    Type "(x: int) -> int" is not assignable to type "((Fail1) -> Fail1) | Fail1"
      Type "(x: int) -> int" is not assignable to type "(Fail1) -> Fail1"
        Parameter 1: type "Fail1" is incompatible with type "int"
          "Fail1" is not assignable to "int"
        Function return type "int" is incompatible with type "Fail1"
          "int" is not assignable to "Fail1"
      "FunctionType" is not assignable to "Fail1" (reportAssignmentType)
1 error, 0 warnings, 0 notes

Expected behavior

Both lambdas are inferred as (x: Fail1) -> Fail1 and the code type-checks, as it does with mypy (v2.3.0).

VS Code extension or command-line
pyright CLI v1.1.411

More info

I'm guessing (x: int) -> int comes from trying to unify the lambda with (*, kwarg: int) -> Fail1.

Here are some variations I tried when looking for minimal repro:
Code sample in pyright playground

from typing import Self
from collections.abc import Callable


class Fail1:
    def __call__(self, *, kwarg: int) -> Self: ...
f1: Callable[[Fail1], Fail1] | Fail1 = lambda x: x
# It should be inferred as this.
f0: Callable[[Fail1], Fail1] = lambda x: x


class Fail2:
    def __call__(self, *, kwarg1: int, kwarg2: int) -> Self: ...
f2: Callable[[Fail2], Fail2] | Fail2 = lambda x: x

class Pass1:
    def __call__(self, *, kwarg: int) -> int: ...
p1: Callable[[Pass1], Pass1] | Pass1 = lambda x: x


class Pass2:
    def __call__(self, arg: int) -> Self: ...
p2: Callable[[Pass2], Pass2] | Pass2 = lambda x: x


class Pass3:
    def __call__(self, *kwarg: int, **kwargs: int) -> Self: ...
p3: Callable[[Pass3], Pass3] | Pass3 = lambda x: x


class Pass4:
    def __call__(self) -> Self: ...
p4: Callable[[Pass4], Pass4] | Pass4 = lambda x: x

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions