-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Open
pylint-dev/astroid
#2965Labels
Description
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.pyPylint 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.0Reactions are currently unavailable