[ty] Preserve deprecation on replacement functions by charliermarsh · Pull Request #25688 · astral-sh/ruff (original) (raw)
Summary
An outer @deprecated decorator applies to the value returned by inner decorators. We currently record deprecation on the original function before applying decorators, so an inner decorator that replaces that function can cause the public binding to lose its warning.
@deprecated("use replacement directly") @replace_with(replacement) def old() -> None: pass
old() # Previously: no warning replacement() # No warning
This handles @deprecated in the post-decoration pass. When the current decorated value is another function literal, we copy that function type with the outer deprecation metadata. old() now warns while direct uses of replacement() remain unchanged.
This is a pared-down alternative to #25619, following Doug's suggestion. The original PR attached deprecation to place and binding metadata so it could survive decorator replacements whose returned value was not already deprecated. That required broad changes across place lookup and binding propagation, including special handling for aliases and synthetic bindings, and it introduced retained-memory and benchmark concerns. This version keeps the behavior local to function decorator inference and avoids changing the place representation or assignment semantics.
The narrower implementation deliberately preserves deprecation only when an inner decorator returns a function literal. Callable-instance replacements remain documented as a TODO.
Closes astral-sh/ty#3623.