microsoft / pyright

Static Type Checker for Python
Other
13.18k stars 1.41k forks source link

Possible bug in variance inference for recursive type alias #9081

Open samwgoldman opened 4 days ago

samwgoldman commented 4 days ago

Given the following recursive type alias:

type A[T] = Callable[[A[T]],Callable[[T],None]]
# A[T] = A[T] -> (T -> None)
                  -

Pyright seems to infer that A is contravariant with respect to T.

def testA_co(x: A[int]) -> A[int|str]:
    return x # Error

def testA_cn(x: A[int|str]) -> A[int]:
    return x # Pass?

However, if we unroll the definition once, we can more clearly see that T should be invariant:

type B[T] = Callable[[Callable[[B[T]],Callable[[T],None]]],Callable[[T],None]]
# B[T] = (B[T] -> (T -> None)) -> (T -> None)
                   +               -

def testB_co(x: B[int]) -> B[int|str]:
    return x # Error

def testB_cn(x: B[int|str]) -> B[int]:
    return x # Error

Playground

Apologies for the completely unrealistic type definitions here. I was working from first principles, trying to come up with weird definitions that could break, not starting with real-world code. I tried to construct a more realistic example, but was not able to reproduce the issue.

erictraut commented 4 days ago

Thanks for filing the issue. I agree with your analysis.