Inference of identity lambda into union of keyword-only function and its identity
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.6k
- Forks
- 1.8k
- Avg merge
- 12h 13m
- Merged PRs (30d)
- 52
Description
**Description**
In the code below, Pyright infers the first lambda as `(x: int) -> int` which fails to assign to either union member.
```python
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](https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoDKApgDbABQokUAxmCSUTTEmCgM4B0AhgEY2ZsufAGFuDPo3LSaJbu3ZQAYtyQkAjAC5yUXVAAmRYFAD6JmuJJmAFO1LAANFABUTgNYB3biDSbMKGABKKABaAD5Cez9OGMotKDEJXkYAbRSVNXUAXScMjSyoAB9lVQ0oAF4oOQhefW4oAA8-BvIAYigASXx2AAswAFcSfSheIn9gIhAQImH5WB6kLkoABj9EuWSiNLzs3NLsiqruGrrG5ulyWXlFPIAmbT0DI1NzSxs7MidXKE9vNHjUDB3F4fPd-EFQhFiGRorFgGD1pItulSrcciU1GiihiSLdDtVavUmo0ZHIFFAAArXLQ6PSGYxmCwMd72L7Av5%2BQHBcLg2GccgIeKIzZpKkKXaU6kFYpi9jqfHHQlnEmk66ShT3Wm6ekvJlWEy2VlQDng7lQqJQGL8hAIyxI0XXNFOWVYmWOhUnInnVXk2UAZgedOejLeBo%2Bjhcvx8nICX2cUbQ7BjEJ50OAfIFAYSdpFKX96Pz2P9HqVxJaPsUsoALIHtcHXsyw-YzZEYZbYgga9mkqk89cqwX%2B9L1ewqyXTmXyEA)
```python
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
```
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied repro.py with the Pyright CLI and compare the inferred types and diagnostics for f1 and f0. Review the lambda inference and union-assignment behavior implicated by the repro, then add a regression test covering the keyword-only callable case and verify that both lambdas type-check as expected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100