pyright
pyright copied to clipboard
Pyright incorrectly binds type variables in higher-order functions
For the following code, pyright correctly binds the type variables for the nested higher order functions call. The revealed type of map2
is correct.
from collections.abc import Callable
def curry[First, *Rest, Result](function: Callable[[First, *Rest], Result]) -> Callable[[*Rest], Callable[[First], Result]]:
return lambda *rest: lambda first: function(first, *rest)
@curry
@curry
def map1[From, To, Arg](value: Arg, first: Callable[[Arg], From], second: Callable[[From], To]) -> To:
return second(first(value))
@curry
@curry
def map1_copy[From, To, Arg](value: Arg, first: Callable[[Arg], From], second: Callable[[From], To]) -> To:
return second(first(value))
# While `map1` is used to map the result of a function of one argument like this:
# (From -> To) -> (Arg1 -> From) -> (Arg1 -> To)
# `map2` is meant to map the result of a function of two arguments like this:
# (From -> To) -> (Arg1 -> Arg2 -> From) -> (Arg1 -> Arg2 -> To)
map2 = map1(map1)(map1)
reveal_type(map2) # ((From(2)@map1) -> To(2)@map1) -> ((((Arg(1)@map1) -> ((Arg(2)@map1) -> From(2)@map1))) -> ((Arg(1)@map1) -> ((Arg(2)@map1) -> To(2)@map1)))
# Negate the sum of two ints
reveal_type(map2(int.__neg__)(curry(int.__add__))) # (int) -> ((int) -> int)
However, if I change the definition of map2
using the exact copy of map1
, pyright is no longer able to successfully bind the type variables resulting in the wrong type of map2
.
map2 = map1(map1_copy)(map1_copy)
reveal_type(map2) # ((From(1)@map1_copy) -> To(1)@map1_copy) -> ((((((From(1)@map1_copy) -> To(1)@map1_copy)) -> ((Arg(1)@map1_copy) -> From(1)@map1_copy))) -> ((((From(1)@map1_copy) -> To(1)@map1_copy)) -> ((Arg(1)@map1_copy) -> To(1)@map1_copy)))
reveal_type(map2(int.__neg__)(curry(int.__add__))) # ((int) -> int) -> ((int) -> int)
As a consequence, this also results in a false positive error.
Argument of type "(int) -> ((int) -> int)" cannot be assigned to parameter of type "((int) -> int) -> ((Arg(1)@map1_copy) -> int)"
Type "(int) -> ((int) -> int)" is incompatible with type "((int) -> int) -> ((int) -> int)"
Parameter 1: type "(int) -> int" is incompatible with type "int"
"function" is incompatible with "int"
For comparison, mypy's behavior doesn't change depending on whether I use map1
or map1_copy
to define map2
which is the expected behavior.