Skip to content

User invisible decorators should not trigger Protocol type conflicts. #10502

Closed as not planned

Description

Whether this is a bug report or an enhancement request depends on whether the current behavior is as designed.

The unwanted behavior

Let me illustrate the behavior I am talking about with very contrived example. The error is reported for the def mass in the Spam class (line 26).

from functools import cache
from typing import Protocol


class SupportsMass(Protocol):
    @property
    def density(self) -> float:
        """Grams per milliliter."""
        ...

    def mass(self, volume: float) -> float:
        """Mass of object in grams given volume in milliliters."""
        ...


class Spam(SupportsMass):
    def __init__(self) -> None:
        # Spam is allegedly 237 grams per metric cup.
        self._density = 237 / 240  # g/ml

    @property
    def density(self) -> float:
        return self._density

    @cache
    def mass(self, volume: float) -> float:  # pyright reports error here
        return volume * self._density

Running pyright on that file yields this:

% pyright cache_protocol.py      
/PATH_TO/cache_protocol.py
  /PATH_TO/cache_protocol.py:26:9 - error: "mass" overrides method of same name in class "SupportsMass" with incompatible type "_lru_cache_wrapper[float]" (reportIncompatibleMethodOverride)
1 error, 0 warnings, 0 informations

I do understand that using the @cache decorator from functools changes the type of mass(), but it doesn't do so from the perspective of the user of the protocol and the class. That is, the use of a decorator like @cache is an implementation detail of the Spam class that should not be relevant for the protocol.

Contrast this with @property for density(), where it has a direct impact on how the method is to be called. Pyright behaves correctly in insisting that the decorator be specified the same in both the definition of the protocol and in the definition of a subclass of the protocol.

Note: if you are reporting a wrong signature of a function or a class in the standard library, then the typeshed tracker is better suited for this report: https://github.com/python/typeshed/issues.

If you have a question about typing or a behavior that you’re seeing in Pyright (as opposed to a bug report or enhancement request), consider posting to the Pyright discussion forum.

VS Code extension or command-line

I first noticed the behavior within VS Code, but I have reproduced on the command-line

% pyright --version
pyright 1.1.401
% python --version
Python 3.13.3

Activity

  1. erictraut commented on May 25, 2025

    @erictraut
    Collaborator

    Pyright is working as designed here. It's applying the normal typing rules based on the type definition of functools.cache. The return type of functools.cache is _lru_cache_wrapper[_T].

    There is a long discussion among typeshed maintainers about whether _lru_cache_wrapper should use a ParamSpec to preserve the signature of the function it is wrapping. I think it should for the reasons that you point out. The way it's currently defined, it defines the __call__ method using in such a way that all parameters are typed as Hashable, which is a requirement of functools.cache. If _lru_cache_wrapper were redefined to use a ParamSpec, it would lose the ability to check for hashability. I personally think that it should be changed because the current approach creates significant usability problems, as you've highlighted. If you'd like to voice your opinion, feel free to post to this thread.

    In any case, I don't consider this a pyright bug. It's a choice that the typing community (or at least the typeshed maintainers) have made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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