Skip to content

pylint does not honor decorator return type when using @ syntax #10848

@bnsh

Description

@bnsh

Bug description

When a decorator changes the return type of a function, pylint correctly infers
the new type if the decorator is applied via explicit assignment
(`f = decorator(f_)`), but ignores the decorator's return type annotation when
applied via `@decorator` syntax. This causes a spurious `E1101 (no-member)` error.

Both `mypy` and `pyright` handle both forms correctly.

A complete, self-contained reproduction (including Dockerfile) is available as a gist:
https://gist.github.com/bnsh/e73e546469ec3d796fd22da0b7c9a31b

**`test_fail.py`**uses `@type_changing_decorator` syntax (pylint fails):


from typing import Callable

def type_changing_decorator(func: Callable[[int], int]) -> Callable[[int], str]:
    """This is just a decorator to demonstrate the problem."""
    def wrapper(val: int) -> str:
        res = func(val)
        return f"the result is {res:d}"
    return wrapper

@type_changing_decorator
def add1(val: int) -> int:
    """This just adds 1 to its input."""
    return val+1

def main() -> None:
    """This is the main program."""
    res = add1(5)
    # This _works_, but pylint complains that int has no ".replace"
    print(res.replace("the", "a"))

if __name__ == "__main__":
    main()


**`test_succeed.py`**uses explicit `add1 = type_changing_decorator(add1_)` (pylint succeeds):


from typing import Callable

def type_changing_decorator(func: Callable[[int], int]) -> Callable[[int], str]:
    """This is just a decorator to demonstrate the problem."""
    def wrapper(val: int) -> str:
        res = func(val)
        return f"the result is {res:d}"
    return wrapper

def add1_(val: int) -> int:
    """This just adds 1 to its input."""
    return val+1
add1 = type_changing_decorator(add1_)

def main() -> None:
    """This is the main program."""
    res = add1(5)
    # This _works_, and pylint accepts it, since I don't use
    # `type_changing_decorator` as a _decorator_.
    print(res.replace("the", "a"))

if __name__ == "__main__":
    main()

Configuration

Default pylint configuration (no custom rcfile).

Command used

pylint test_fail.py

Pylint output

************* Module test_fail
test_fail.py:27:10: E1101: Instance of 'int' has no 'replace' member (no-member)

-----------------------------------
Your code has been rated at 6.15/10


For comparison, `test_succeed.py` (identical logic, explicit call instead of `@` syntax):


Your code has been rated at 10.00/10


Both `mypy` and `pyright` pass cleanly on both files.

Expected behavior

pylint should infer the return type of a decorated function from the decorator's
return type annotation. In this case, type_changing_decorator is annotated as
(Callable[[int], int]) -> Callable[[int], str], so add1 should be understood
as returning str regardless of whether the decorator is applied via @ syntax
or explicit assignment.

Pylint version

pylint 4.0.4
astroid 4.0.4
Python 3.14.3 (main, Feb  4 2026, 20:08:31) [GCC 14.2.0]

OS / Environment

Debian GNU/Linux 13 (trixie) (python:3.14 Docker image)

Additional dependencies

None required to reproduce. The only installed packages are:


astroid==4.0.4
dill==0.4.1
isort==7.0.0
mccabe==0.7.0
platformdirs==4.5.1
pylint==4.0.4
tomlkit==0.14.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions