Summary
As I continue my saga of getting ty to analyze some code that uses SQLAlchemy, I encountered and reduced this bug. sqlalchemy has some stylistically regrettable code in which the name sum refers to a class and, in a nested scope, names a set of overloaded functions. Python has IMO highly regrettable rules about name resolution. And ty is getting those rules wrong in this particular case.
from typing import overload, reveal_type
# Represents an expression in an expression tree
class sum[T]:
def __init__(self) -> None:
pass
# Massively simplified expression generator
# The types below (str and int) make no sense in context -- SQLAlchemy
# uses much more complex types that actually serve a purpose. But to demonstrate
# the bug, we just need something we can overload on.
class FunctionGenerator:
# Poison the well: introduce a symbol called 'sum' that will
# confuse ty. ty parses this signature correctly.
@overload
def sum(self, value: str, /) -> sum[str]: ...
# Now drink from the poisoned well. This overload's signature
# will be misinterpreted.
@overload
def sum(self, value: int, /) -> sum[int]: ...
# ty says: ^^^^^
# Invalid subscript of object of type `def sum(self, value: str, /) -> sum[str]` in type expression
# Let's make stricter type checkers happy. pyright wants the actual callable
# function to exist. Ty still gets hung up on the return type, though.
def sum(self, *args) -> sum:
return sum()
# SQLAlchemy wants users to type `func.sum`
func = FunctionGenerator()
reveal_type(func.sum)
# pyright says Type of "func.sum" is "Overload[(value: str, /) -> sum[str], (value: int, /) -> sum[int]]"
# ty says `Overload[(value: str, /) -> sum[str], (value: int, /) -> Unknown]`
reveal_type(func.sum(1))
# pyright says Type of "func.sum(1)" is "sum[int]"
# ty says `Unknown`
https://play.ty.dev/b22d4df4-e6a9-43c7-93d9-dbcd799c878e
P.S. The failure mode here is IMO quite unpleasant. In the actual case I'm hitting, the overload set is in an distribution package that I'm depending on, and, unlike the type checkers built in to C++, Rust, etc., ty does not report errors that appear to be in imported modules. So ty accepts the Unknown that it generates due to the error as being valid, and ty has no strict mode, so Unknown silently infects my code. And ty reports "All checks passed!" despite the fact that I have actual incorrect code in my module. And ty does not have a strict mode or any other feature I'm aware of that will change its behavior to actually notice this type of error. Yuck.
P.P.S. I think there is yet another bug that is causing Unknown to infect even more of my code than it should due to this bug. It boils down to select(sum(...)) resulting in Unknown instead of Select[Unknown]. My attempts thus far to reproduce it have failed. My attempts to get an LLM to reproduce it have also failed, presumably because (a) they're not actually smart and brute-force isn't working and (b) they are way to dumb to be able to reliably distinguish a successful reproduction from garbage code that correctly generates Unknown. If I manage to reproduce the issue cleanly I can file it. Or I could give a very short but not self-contained example as a separate bug.
Version
ty 0.0.29
Summary
As I continue my saga of getting ty to analyze some code that uses SQLAlchemy, I encountered and reduced this bug. sqlalchemy has some stylistically regrettable code in which the name
sumrefers to a class and, in a nested scope, names a set of overloaded functions. Python has IMO highly regrettable rules about name resolution. And ty is getting those rules wrong in this particular case.https://play.ty.dev/b22d4df4-e6a9-43c7-93d9-dbcd799c878e
P.S. The failure mode here is IMO quite unpleasant. In the actual case I'm hitting, the overload set is in an distribution package that I'm depending on, and, unlike the type checkers built in to C++, Rust, etc., ty does not report errors that appear to be in imported modules. So ty accepts the
Unknownthat it generates due to the error as being valid, and ty has no strict mode, soUnknownsilently infects my code. And ty reports "All checks passed!" despite the fact that I have actual incorrect code in my module. And ty does not have a strict mode or any other feature I'm aware of that will change its behavior to actually notice this type of error. Yuck.P.P.S. I think there is yet another bug that is causing
Unknownto infect even more of my code than it should due to this bug. It boils down to select(sum(...)) resulting inUnknowninstead ofSelect[Unknown]. My attempts thus far to reproduce it have failed. My attempts to get an LLM to reproduce it have also failed, presumably because (a) they're not actually smart and brute-force isn't working and (b) they are way to dumb to be able to reliably distinguish a successful reproduction from garbage code that correctly generatesUnknown. If I manage to reproduce the issue cleanly I can file it. Or I could give a very short but not self-contained example as a separate bug.Version
ty 0.0.29