basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

NamedTuple to support Callable lambdas

Open CoderJoshDK opened this issue 4 months ago • 1 comments

Description

pyright doesn't support inferring the type of lambda from a NamedTuple:

from typing import NamedTuple, Callable

class Foo(NamedTuple):
  a: int
  b: Callable[[str], str]

bar: Foo = Foo(10, lambda x: x.lower()) # Type of parameter "x" is unknown

You can see this in the plyaground with a normal tuple working just fine

foo: tuple[int, Callable[[str], str]] = (10, lambda x: x.lower())

Upstream has decided that

[...] pyright is working as intended here, so I don't consider this a bug.

Original closed issue https://github.com/microsoft/pyright/issues/9110

Seeing as they don't intend to fix this upstream, I figured I may as well open an issue here.

mypy handles this case just fine

from collections.abc import Callable
from typing import NamedTuple

class Foo(NamedTuple):
    a: int
    b: Callable[[str], str]


bar: Foo = Foo(10, lambda x: x + 5)
> uvx mypy .
main.py:10: error: Unsupported operand types for + ("str" and "int")  [operator]
Found 1 error in 1 file (checked 1 source file)

CoderJoshDK avatar Oct 13 '25 19:10 CoderJoshDK

yeah i don't see why this shouldn't work. i don't understand the argument in https://github.com/microsoft/pyright/issues/9110#issuecomment-2383770629 about the __init__ and __new__ methods. since the signatures are supposed to always match i don't see why pyright would need to check both when validating the call.

it also seems like the stub has changed since that comment was made. NamedTuple's __init__ now looks like this:

class NamedTuple(tuple[Any, ...]):
    @overload
    def __init__(self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None:
        ...
    @overload
    @typing_extensions.deprecated(
        "Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15"
    )
    def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None:
        ...

DetachHead avatar Oct 14 '25 10:10 DetachHead