Describe the Bug
When assigning the result of a generic function to a class attribute, Pyrefly can fail with a spurious bad-assignment error if the function involves nested closures and Type Aliases.
Reproduction
import typing
from typing import Callable, TypeVar, reveal_type
T = TypeVar('T')
A = T | Callable[[], T]
B = A | list[T]
def bar(x: int) -> None:
pass
def f(x: B[T], g: Callable[[T], None]) -> A[T]:
def if_callable() -> T:
r = x() # pyrefly: ignore[not-callable]
g(r)
return r
if callable(x):
return if_callable
elif isinstance(x, list):
g(x[0])
return x[0]
else:
g(x)
return x
class Test:
def __init__(self, arg: B[int]):
self.foo = f(arg, bar) # Fails with `(() -> A) | (() -> int) | int` is not assignable to attribute `foo` with type `(() -> int) | int` [bad-assignment]
reveal_type(self.foo)
def standalone(arg: B[int]):
foo = f(arg, bar) # This works
reveal_type(foo)
reveal_type diagnostics:
INFO revealed type: (() -> int) | int [reveal-type]
Details
It seems that in the class attribute case, Pyrefly goes into the function body to resolve the generics for the call. When checking the code, the call to x() (where we had to ignore the not-callable error) seems to cause some confusion. This breaks the generic resolution, making Pyrefly fall back to the raw alias A and generate the conflicting () -> A type.
It appears to be a limitation of generic solving. However, it is unclear why this wasn't reported as a bad-return-type inside the function itself instead of a bad-assignment at the use site. Additionally, it is confusing why this fails in the class attribute case but works perfectly in the standalone function case.
Sandbox Link
https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIEAtsbgE4AuABA6cROgOYA66YduKizYdOTarUZMAwqihRU2WABomAFTYwAaqjqq6MAG4w5AfVbEYvXmqYBedZp10AFAHI1bgJS8AgvfUmAB8ZOQUlGABtSIBdVTUY3gAhAP8QqAg4BkiE6ywYMCZsXRd8RHF0Bi8mAFoAPiYAOVx0GEReJk6mYlQ4ODzMAqYwUvKknLimTnLZeUVYaITVZtaY6vqmXwn29C6mQcKIMFMAYzD5mBd1hrUdvb26APwrzoBibtIDMChScohOdD0KKAhg1M5zCKJXb3TqcFx0HzQmEGBgAVzouzoHS6RyY4PCsFKXjuMKYKPRuyOp3OEWxnRgGUOcA4WQwJ0u%2BFUGSyxLp9zh%2BEiAAY1nyHjA0RimIKRXyGXA2mKugLEaSyRKKdK8icFH11PAGCTOgcmKZTBwIAwzS4FVAwKpdNMmOMOAw1kb7rawIQwLhcAERo7VMUEUwlW8mAAxVDQOBMADuloAFkwAAYuF4bXzVEIZ64VKrBAup8RxkFMXrMgEXFj%2B1AMBh0CDYVEMGBp324EuJhgpizt9OZhqunPFpiRYqYGqV-7oKgwSpQtUGYxmfs2hnezuI3gm1lYOQtS6OsaRV3uvmdgMuINFXTVd5qJOZBP0ADW-SR6tXUHMmhc24gMoIBkF8PyEAwghQBQ7wAAqfAUPxMGgWB4PgeItJAnDovWEAtIQvDvAAyjA7ZJg2xBwIgAD01GgYhpCEPQnDUQu1GYLgJxwNRJyYf8OEMHh6DUcM9AVoYMYEu2vF8PxdC4S0TC4MQgktHABHoGQvYtDUxh0MyikONwIAAMyEAAjAATMZvCRDAdACPpUKougEj0G2U6YBABgnIJxgBG4s5Am4vAgjUBgAI6ot5MBTm%2BMCkNOJzsnqDhuPGujoCF6AgAAvsBqC%2BRAxiRtAMAUChOAECQ5C5UAA
(Only applicable for extension issues) IDE Information
No response
Describe the Bug
When assigning the result of a generic function to a class attribute, Pyrefly can fail with a spurious
bad-assignmenterror if the function involves nested closures and Type Aliases.Reproduction
reveal_typediagnostics:Details
It seems that in the class attribute case, Pyrefly goes into the function body to resolve the generics for the call. When checking the code, the call to
x()(where we had to ignore thenot-callableerror) seems to cause some confusion. This breaks the generic resolution, making Pyrefly fall back to the raw aliasAand generate the conflicting() -> Atype.It appears to be a limitation of generic solving. However, it is unclear why this wasn't reported as a
bad-return-typeinside the function itself instead of abad-assignmentat the use site. Additionally, it is confusing why this fails in the class attribute case but works perfectly in the standalone function case.Sandbox Link
https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIEAtsbgE4AuABA6cROgOYA66YduKizYdOTarUZMAwqihRU2WABomAFTYwAaqjqq6MAG4w5AfVbEYvXmqYBedZp10AFAHI1bgJS8AgvfUmAB8ZOQUlGABtSIBdVTUY3gAhAP8QqAg4BkiE6ywYMCZsXRd8RHF0Bi8mAFoAPiYAOVx0GEReJk6mYlQ4ODzMAqYwUvKknLimTnLZeUVYaITVZtaY6vqmXwn29C6mQcKIMFMAYzD5mBd1hrUdvb26APwrzoBibtIDMChScohOdD0KKAhg1M5zCKJXb3TqcFx0HzQmEGBgAVzouzoHS6RyY4PCsFKXjuMKYKPRuyOp3OEWxnRgGUOcA4WQwJ0u%2BFUGSyxLp9zh%2BEiAAY1nyHjA0RimIKRXyGXA2mKugLEaSyRKKdK8icFH11PAGCTOgcmKZTBwIAwzS4FVAwKpdNMmOMOAw1kb7rawIQwLhcAERo7VMUEUwlW8mAAxVDQOBMADuloAFkwAAYuF4bXzVEIZ64VKrBAup8RxkFMXrMgEXFj%2B1AMBh0CDYVEMGBp324EuJhgpizt9OZhqunPFpiRYqYGqV-7oKgwSpQtUGYxmfs2hnezuI3gm1lYOQtS6OsaRV3uvmdgMuINFXTVd5qJOZBP0ADW-SR6tXUHMmhc24gMoIBkF8PyEAwghQBQ7wAAqfAUPxMGgWB4PgeItJAnDovWEAtIQvDvAAyjA7ZJg2xBwIgAD01GgYhpCEPQnDUQu1GYLgJxwNRJyYf8OEMHh6DUcM9AVoYMYEu2vF8PxdC4S0TC4MQgktHABHoGQvYtDUxh0MyikONwIAAMyEAAjAATMZvCRDAdACPpUKougEj0G2U6YBABgnIJxgBG4s5Am4vAgjUBgAI6ot5MBTm%2BMCkNOJzsnqDhuPGujoCF6AgAAvsBqC%2BRAxiRtAMAUChOAECQ5C5UAA
(Only applicable for extension issues) IDE Information
No response