Skip to content

Incorrect type-var unification of generic function return type decorated with @lru_cache #7594

@strangemonad

Description

@strangemonad

Describe the bug
This is easiest illustrated with the repro-case below. In short, if you decorate an invariant generic method, the type-var unification doesn't seem to propagate across the LRU wrapped method. In the following code, the store_of method works fine but cached_store_of's type is still captured as the "T@cached_store_of" fresh Ty var instead of being unified with the Account type.

Note that in this case, the type var T must be invariant but that's incidental to this example. Custom decorators seem to work properly. I also believe I've confidently rule out that this is unrelated to TypeForm and any of the improvements that might bring to the table.

Code

import typing
from functools import lru_cache
from typing import Any, Generic, TypeVar

T = TypeVar("T")

class Store(Generic[T]):
    def __init__(self, entity_cls: type[T], db_session: Any):
        super().__init__()
        self._entity_cls = entity_cls
        self._db_session = db_session

    def get(self, pkey: str) -> T:
        ...

    def create(self, value: T) -> T:
        ...


class DB:
    def __init__(self, db_session: Any):
        super().__init__()
        self._db_session = db_session

    def store_of(self, entity_cls: type[T]) -> Store[T]:
        return Store(entity_cls=entity_cls, db_session=self._db_session)

    @my_decorator
    def my_decorated_store_of(self, entity_cls: type[T]) -> Store[T]:
        return Store(entity_cls=entity_cls, db_session=self._db_session)

    @lru_cache
    def cached_store_of(self, entity_cls: type[T]) -> Store[T]:
        return Store(entity_cls=entity_cls, db_session=self._db_session)


class Account:
    ...


typing.reveal_type(DB(db_session="connection").store_of(entity_cls=Account))
account_store_1: Store[Account] = DB(db_session="connection").store_of(
    entity_cls=Account,
)

typing.reveal_type(DB(db_session="connection").my_decorated_store_of(entity_cls=Account))
account_store_2: Store[Account] = DB(db_session="connection").my_decorated_store_of(
    entity_cls=Account,
)

typing.reveal_type(DB(db_session="connection").cached_store_of(entity_cls=Account))
account_store_3: Store[Account] = DB(db_session="connection").cached_store_of(
    entity_cls=Account,
)

Version

pyright --version  
pyright 1.1.356

Output
❯ pyright generic_bug.py
/Users/shawn/Code/instance-bio/instance/services/web/generic_bug.py
/Users/shawn/Code/instance-bio/instance/services/web/generic_bug.py:51:20 - information: Type of "DB(db_session="connection").store_of(entity_cls=Account)" is "Store[Account]"
/Users/shawn/Code/instance-bio/instance/services/web/generic_bug.py:54:20 - information: Type of "DB(db_session="connection").my_decorated_store_of(entity_cls=Account)" is "Store[Account]"
/Users/shawn/Code/instance-bio/instance/services/web/generic_bug.py:59:20 - information: Type of "DB(db_session="connection").cached_store_of(entity_cls=Account)" is "Store[T@cached_store_of]"
/Users/shawn/Code/instance-bio/instance/services/web/generic_bug.py:60:35 - error: Expression of type "Store[T@cached_store_of]" cannot be assigned to declared type "Store[Account]"
  "Store[T@cached_store_of]" is incompatible with "Store[Account]"
    Type parameter "T@Store" is invariant, but "T@cached_store_of" is not the same as "Account" (reportAssignmentType)
1 error, 0 warnings, 3 informations

Metadata

Metadata

Assignees

No one assigned

    Labels

    as designedNot a bug, working as intendedbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions