microsoft / pyright

Static Type Checker for Python
Other
12.7k stars 1.35k forks source link

Spurious length mismatch errors for variadic generics (bidirectional inference?) #8285

Closed brentyi closed 2 weeks ago

brentyi commented 2 weeks ago

When working with variadic generics, I frequently run into length mismatch errors that seem to be (1) associated with bidirectional inference and (2) how some edge case handles SomeClass[Unknown] for SomeClass[*T].

I've done my best to produce a minimal example (Python 3.12, pyright 1.1.369):

class MyClass[*T]:
    def __init__(self, args: tuple[*T]) -> None:
        ...

def create[*T](args: tuple[*T]) -> MyClass[*T]:
    return MyClass(args)

annotated_instance: MyClass

# Ex. 1: OK
annotated_instance = MyClass(args=(1, 2))  # Length 2

# Ex. 2: OK
tmp = create(args=(1, 2))  # Length 2
annotated_instance = tmp

# Ex. 3: OK
annotated_instance = create(args=(1,))  # This pattern works for length 1 only!

# Ex. 4: Not OK
annotated_instance = create(args=(1, 2))

Specifically, I get an error on the last line about the length of the args argument:

error: Argument of type "tuple[Literal[1], Literal[2]]" cannot be assigned to parameter "args" of type "tuple[*T@create]" in function "create"
    "tuple[Literal[1], Literal[2]]" is incompatible with "tuple[Unknown]"
      Tuple size mismatch; expected 1 but received 2 (reportArgumentType

The reason this matters is that I frequently have things like list[MyClass[Unknown]], which I then can't append create(args=(1,2)) to.

We can also reproduce this with a Callable signature:

from typing import Callable

class MyClass[*T]:
    def __init__(self, args: Callable[[*T], None]) -> None:
        ...

def create[*T](args: Callable[[*T], None]) -> MyClass[*T]:
    return MyClass(args)

annotated_instance: MyClass

# OK
def one_arg_func(x: int) -> None:
    ...
annotated_instance = create(one_arg_func)

# Not OK
def two_arg_func(x: int, y: int) -> None:
    ...
annotated_instance = create(two_arg_func)

Thanks for reading!!

erictraut commented 2 weeks ago

Thanks for the issue. Yes, this is a bug. If you don't specify a type argument for a variadic type parameter, the value of the type argument should default to *tuple[Unknown, ...] rather than *tuple[Unknown], as pyright is currently doing.

This will be fixed in the next release of pyright.

erictraut commented 2 weeks ago

This is addressed in pyright 1.1.370