Skip to content

overload + decorator to transform callable into class protocol w/ attribute does not see the attribute #18715

Open
@jakkdl

Description

@jakkdl

Bug Report
Trying to combine function attributes (a la #2087) where we add the attribute with a decorator fails to apply the decorator and the attribute is dropped on the ground.

To Reproduce

from __future__ import annotations
from typing import overload, Callable, TypeVar, Protocol
from typing_extensions import ParamSpec, reveal_type

P = ParamSpec("P")
R = TypeVar("R", covariant=True)

class WithExtra(Protocol[P, R]):
    extra: str
    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...

def add_extra(func: Callable[P, R]) -> WithExtra[P, R]:
    func.extra = "added"  # type: ignore
    return func  # type: ignore

@overload
@add_extra
def foo(x: int) -> str: ...

@overload
@add_extra
def foo(x: str) -> str: ...

@add_extra
def foo(x: int | str) -> str:
    return str(x)

assert foo.extra  # type: ignore[attr-defined]
reveal_type(foo)  # Revealed type is "Overload(def (x: builtins.int) -> builtins.str, def (x: builtins.str) -> builtins.str)"

This works in pyright.
I feel like it should be possible to work around it with a callable class, but I've yet to figure out the proper incantation.

Real-life use: https://github.com/pytest-dev/pytest/blob/b0caf3d7adc45f773177424593431869fd2f82d8/src/_pytest/python_api.py#L1025

Your Environment

  • Mypy version used: 1.14.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: python 3.13.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions