Describe the Bug
When passing a generic function (carrying a scoped TypeVar in its return type) into functools.partial using keyword arguments, Pyrefly's constraint solver fails to unify the generic return type with the caller's type parameter.
Instead, Pyrefly generates a deferred type placeholder: GenericResidual@_T. Passing this partial callback downstream results in an incorrect false positive downstram e.g. this causes the following to produce a false positive bad-return:
import functools
from typing import Callable, Generic, TypeVar
_S = TypeVar('_S')
class Box(Generic[_S]):
pass
def build(x: int, factory: Callable[[], _S]) -> _S:
return factory()
def run(f: Callable[[int], _S]) -> Box[_S]:
return Box()
def test(factory: Callable[[], _S]) -> Box[_S]:
partial_fn = functools.partial(build, factory=factory) # Annotating with functools.partial[_S] fixes the issue.
reveal_type(partial_fn) # partial[GenericResidual@_T]
reveal_type(run(partial_fn)) # Box[GenericResidual@_T]
return run(partial_fn) # Returned type `Box[GenericResidual@_T]` is not assignable to declared return type `Box[_S]` [[bad-return]
My guess is that Pyrefly is unable to propagate generic unification constraints through the generic constructor of functools.partial. Since build is not invoked directly, its return TypeVar _S remains free and unbound at the partial assignment site, leaking a deferred GenericResidual@_T downstream rather than mapping it to the outer scope's _S type. It is worth noting that annotating partial_fn: functools.partial[_S] makes the above type checks as Pyrefly is not able to unify the GenericResidual@_T.
Sandbox Link
https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIEAtsbgE4AuABGAK7oDGDuuUcAOujB1cVJg1LEI6AOZNqtRkwDCqKFFTZYAGiYBxGOhh0IHXQBVJMAGqo6gwQH0AykwC8TS8Rt2AFAHIXfwBKB04NODgmACECXwMjEw4AbRcAXWDEQSYmYlRIsMwYMCZsVmhMX3xEeXQGXTBULnpSGtV1TVhk5LTddOCmAFoAPiYXLPQcuhgGVjpJxua6Ul9Q9EEikrp2XzA2tQ0tGG7pBl6x5wyh0dj8VMuJqZm5ydvVwuLxeAZdpu5l-YdI7dc79a4xAj3NKPXJ2BgQNSOMCTDxsTjcXhwEhwhFQXxlCoNP4tNyLf6kAYAYiYAEF0OhcAxUPDZEwAO4QBgACxY7GamOxjFxUJYEHw8HEXJg8kirBghCY2SY0wAbjBERJvL48kLEci1k81RqrL5tuhtTi9ehggblc95sqdjr4VbgiBtCAyNMwFBSIRuFQoBRqQAFUje31MNBYPD4JgcXBCCAyObMiCJwiCanOGDSrkMBjEOCIAD0Ja9xV9hHoMhLhhLmFwHDgJYTSZTdDTiZLLHoTFQKtQ0E60rbkA7XcmuGI8MTWMEZG5icGarocHTKMVIAAzIQAIwAJn4IEEyWMIjXaUE7AU9AYMEwg0wEGmXAgavcTH8yYZ038ggZBhBmmABHcppkfABrGBSEGJoOHgKIPH8Nk7HQf90BAABfD0-nfGAADFoBgChoxwAgSHILCgA
(Only applicable for extension issues) IDE Information
No response
Describe the Bug
When passing a generic function (carrying a scoped
TypeVarin its return type) intofunctools.partialusing keyword arguments, Pyrefly's constraint solver fails to unify the generic return type with the caller's type parameter.Instead, Pyrefly generates a deferred type placeholder:
GenericResidual@_T. Passing this partial callback downstream results in an incorrect false positive downstram e.g. this causes the following to produce a false positive bad-return:My guess is that Pyrefly is unable to propagate generic unification constraints through the generic constructor of
functools.partial. Sincebuildis not invoked directly, its return TypeVar_Sremains free and unbound at thepartialassignment site, leaking a deferredGenericResidual@_Tdownstream rather than mapping it to the outer scope's_Stype. It is worth noting that annotatingpartial_fn: functools.partial[_S]makes the above type checks as Pyrefly is not able to unify theGenericResidual@_T.Sandbox Link
https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIEAtsbgE4AuABGAK7oDGDuuUcAOujB1cVJg1LEI6AOZNqtRkwDCqKFFTZYAGiYBxGOhh0IHXQBVJMAGqo6gwQH0AykwC8TS8Rt2AFAHIXfwBKB04NODgmACECXwMjEw4AbRcAXWDEQSYmYlRIsMwYMCZsVmhMX3xEeXQGXTBULnpSGtV1TVhk5LTddOCmAFoAPiYXLPQcuhgGVjpJxua6Ul9Q9EEikrp2XzA2tQ0tGG7pBl6x5wyh0dj8VMuJqZm5ydvVwuLxeAZdpu5l-YdI7dc79a4xAj3NKPXJ2BgQNSOMCTDxsTjcXhwEhwhFQXxlCoNP4tNyLf6kAYAYiYAEF0OhcAxUPDZEwAO4QBgACxY7GamOxjFxUJYEHw8HEXJg8kirBghCY2SY0wAbjBERJvL48kLEci1k81RqrL5tuhtTi9ehggblc95sqdjr4VbgiBtCAyNMwFBSIRuFQoBRqQAFUje31MNBYPD4JgcXBCCAyObMiCJwiCanOGDSrkMBjEOCIAD0Ja9xV9hHoMhLhhLmFwHDgJYTSZTdDTiZLLHoTFQKtQ0E60rbkA7XcmuGI8MTWMEZG5icGarocHTKMVIAAzIQAIwAJn4IEEyWMIjXaUE7AU9AYMEwg0wEGmXAgavcTH8yYZ038ggZBhBmmABHcppkfABrGBSEGJoOHgKIPH8Nk7HQf90BAABfD0-nfGAADFoBgChoxwAgSHILCgA
(Only applicable for extension issues) IDE Information
No response