Dual-use decorator return unions alternative heuristic (#2824)#2824
Dual-use decorator return unions alternative heuristic (#2824)#2824grievejia wants to merge 1 commit intofacebook:mainfrom
Conversation
|
@grievejia has exported this pull request. If you are a Meta employee, you can view the originating Diff in D97025245. |
This comment has been minimized.
This comment has been minimized.
Summary: Dual-use decorators often infer a return type that is the union of the final wrapped callable and the leftover decorator factory branch. Preserving that raw union is not useful at decoration sites: every use of the decorated symbol then has to satisfy both branches, even though only the wrapped callable branch can actually represent the final decorated value. This change normalizes decorator return unions in `apply_function_decorator` before we finalize the decorated type. When a union member still looks like a decorator factory, meaning it accepts the decoratee as its first argument and itself returns another callable, we treat that member as an artifact of the dual-use control flow and prune it away. If pruning leaves a single unannotated passthrough callable, we preserve the original decoratee type; otherwise we keep the remaining normalized union. This keeps the fix local to decorator application instead of adding a broad type-level heuristic for all `(*args, **kwargs) -> Any` callables. That makes the behavior match the motivating dual-use decorator pattern without changing unrelated callable typing. NOTE: The other alternative approach to this is D96672235 Differential Revision: D97025245
228abfe to
2d2a956
Compare
|
Diff from mypy_primer, showing the effect of this PR on open source code: scikit-learn (https://github.com/scikit-learn/scikit-learn)
- ERROR sklearn/utils/tests/test_validation.py:1758:15-16: Expected 1 positional argument, got 3 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1758:18-19: Expected 2 positional arguments, got 3 in function `f1` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1761:15-16: Expected 1 positional argument, got 4 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1761:18-19: Expected 2 positional arguments, got 4 in function `f1` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1768:15-16: Expected 1 positional argument, got 2 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1768:15-16: Expected 1 positional argument, got 2 in function `f2` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1776:15-16: Expected 1 positional argument, got 2 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1776:15-16: Expected argument `b` to be passed by name in function `f3` [unexpected-positional-argument]
+ ERROR sklearn/utils/tests/test_validation.py:1787:15-16: Expected argument `b` to be passed by name in function `f1` [unexpected-positional-argument]
- ERROR sklearn/utils/tests/test_validation.py:1797:12-13: Expected 0 positional arguments, got 3 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1797:18-19: Expected 2 positional arguments, got 3 in function `A1.__init__` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1800:12-13: Expected 0 positional arguments, got 4 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1800:18-19: Expected 2 positional arguments, got 4 in function `A1.__init__` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1808:12-13: Expected 0 positional arguments, got 3 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1808:18-19: Expected 2 positional arguments, got 3 in function `A2.__init__` [bad-argument-count]
- ERROR sklearn/utils/tests/test_validation.py:1811:12-13: Expected 0 positional arguments, got 4 in function `sklearn.utils.validation._inner_deprecate_positional_args` [bad-argument-count]
+ ERROR sklearn/utils/tests/test_validation.py:1811:18-19: Expected 2 positional arguments, got 4 in function `A2.__init__` [bad-argument-count]
pwndbg (https://github.com/pwndbg/pwndbg)
- ERROR pwndbg/commands/cpsr.py:23:1-61: Argument `(((cpsr_value: Unknown | None = None) -> None) -> (cpsr_value: Unknown | None = None) -> None) | ((cpsr_value: Unknown | None = None) -> None)` is not assignable to parameter with type `((cpsr_value: Unknown | None = None) -> None) -> (cpsr_value: Unknown | None = None) -> None` [bad-argument-type]
- ERROR pwndbg/commands/onegadget.py:27:1-63: Argument `(((show_unsat: bool = False, no_unknown: bool = False, verbose: bool = False) -> None) -> (show_unsat: bool = False, no_unknown: bool = False, verbose: bool = False) -> None) | ((show_unsat: bool = False, no_unknown: bool = False, verbose: bool = False) -> None)` is not assignable to parameter with type `((show_unsat: bool = False, no_unknown: bool = False, verbose: bool = False) -> None) -> (show_unsat: bool = False, no_unknown: bool = False, verbose: bool = False) -> None` [bad-argument-type]
|
Primer Diff Classification✅ 2 improvement(s) | 2 project(s) total | +9, -10 errors 2 improvement(s) across scikit-learn, pwndbg.
Detailed analysis✅ Improvement (2)scikit-learn (+9, -8)
pwndbg (-2)
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (2 LLM) |
Summary:
Dual-use decorators often infer a return type that is the union of the final wrapped callable and the leftover decorator factory branch. Preserving that raw union is not useful at decoration sites: every use of the decorated symbol then has to satisfy both branches, even though only the wrapped callable branch can actually represent the final decorated value.
This change normalizes decorator return unions in
apply_function_decoratorbefore we finalize the decorated type. When a union member still looks like a decorator factory, meaning it accepts the decoratee as its first argument and itself returns another callable, we treat that member as an artifact of the dual-use control flow and prune it away. If pruning leaves a single unannotated passthrough callable, we preserve the original decoratee type; otherwise we keep the remaining normalized union.This keeps the fix local to decorator application instead of adding a broad type-level heuristic for all
(*args, **kwargs) -> Anycallables. That makes the behavior match the motivating dual-use decorator pattern without changing unrelated callable typing.NOTE: The other alternative approach to this is D96672235
Differential Revision: D97025245