-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Incorrect type narrowing with subclassed descriptors #11039
Description
Describe the bug
I was looking at why a VS code refused to tab-complete a property when working with a SQLAlchemy ORM object:
import typing
import sqlalchemy.orm
class Base(sqlalchemy.orm.DeclarativeBase):
pass
class User(Base):
email: sqlalchemy.orm.MappedColumn[str | None]
u = User()
typing.reveal_type(u) # Type of "u" is "User"
typing.reveal_type(u.email) # Type of "u.email" is "str | None"
assert u.email is None
typing.reveal_type(u) # Type of "u" is "Never"
# VS code now refuses to tab-complete any attributes on `u.`After simplifying the relevant classes, it looks like the problem is that MappedColumn indirectly subclasses the ORMDescriptor class. For some reason, the type of the object is narrowed to typing.Never when the descriptor class is accessed through a subclass, instead of directly.
Code or Screenshots
The following code reproduces the problem in the Pyright playground:
import typing
T = typing.TypeVar("T", bound=typing.Any)
class SQLCoreOperations(typing.Generic[T]):
pass
class ORMDescriptor(typing.Generic[T]): # Simplified from https://github.com/sqlalchemy/sqlalchemy/blob/rel_2_0_44/lib/sqlalchemy/orm/base.py#L702-L725
@typing.overload
def __get__(
self, instance: typing.Any, owner: typing.Literal[None]
) -> "ORMDescriptor[T]": ...
@typing.overload
def __get__(
self, instance: typing.Literal[None], owner: typing.Any
) -> SQLCoreOperations[T]: ...
@typing.overload
def __get__(self, instance: object, owner: typing.Any) -> T: ...
def __get__(
self, instance: object, owner: typing.Any
) -> "typing.Union[ORMDescriptor[T], SQLCoreOperations[T], T]": ...
class MappedColumn(ORMDescriptor[T]):
pass
class User:
email: MappedColumn[str | None]
u = User()
typing.reveal_type(u) # Type of "u" is "User"
typing.reveal_type(u.email) # Type of "u.email" is "str | None"
assert u.email is None
typing.reveal_type(u) # Type of "u" is "Never"(Link to code in Pyright Playground)
If email: MappedColumn[str | None] is replaced with email: ORMDescriptor[str | None], u is no longer narrowed to typing.Never:
# [...]
class User:
email: ORMDescriptor[str | None]
u = User()
typing.reveal_type(u) # Type of "u" is "User"
typing.reveal_type(u.email) # Type of "u.email" is "str | None"
assert u.email is None
typing.reveal_type(u) # Type of "u" is "User"(Link to full code in Pyright Playground)
VS Code extension or command-line
Issue reproduced in Pyright playground using Pyright version 1.1.406.