Optimize recursive sequence protocol rejection - #11695
Open
Bill Schnurr (bschnurr) wants to merge 7 commits into
Open
Optimize recursive sequence protocol rejection#11695Bill Schnurr (bschnurr) wants to merge 7 commits into
Bill Schnurr (bschnurr) wants to merge 7 commits into
Conversation
Member
Author
|
/benchmark |
Bill Schnurr (bschnurr)
force-pushed
the
perf/recursive-sequence-protocol-fast-reject
branch
from
September 2, 2026 21:14
b214864 to
a5239b7
Compare
This comment has been minimized.
This comment has been minimized.
Bill Schnurr (bschnurr)
force-pushed
the
perf/recursive-sequence-protocol-fast-reject
branch
from
September 2, 2026 21:56
a5239b7 to
5118d44
Compare
This comment has been minimized.
This comment has been minimized.
Bill Schnurr (bschnurr)
force-pushed
the
perf/recursive-sequence-protocol-fast-reject
branch
from
September 2, 2026 22:42
5118d44 to
b25be82
Compare
This comment has been minimized.
This comment has been minimized.
Bill Schnurr (bschnurr)
force-pushed
the
perf/recursive-sequence-protocol-fast-reject
branch
from
September 3, 2026 00:48
b25be82 to
4f7b0ba
Compare
This comment has been minimized.
This comment has been minimized.
Bill Schnurr (bschnurr)
force-pushed
the
perf/recursive-sequence-protocol-fast-reject
branch
from
September 3, 2026 20:57
4f7b0ba to
e63dc1c
Compare
This comment has been minimized.
This comment has been minimized.
Fall back to normal structural protocol matching when a list element is an unresolved unification TypeVar, and add regression coverage for the fast-path decision.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Skip constraint solving for types that cannot be specialized while preserving generic alias transformation. Track fast protocol-cache rejections separately so full checks can replace them and they cannot become universal incompatibility entries.\n\nFailure classification: B (Pyright performance limitation). The change preserves inferred type precision; focused tests cover concrete types, nested generics, aliases, and cache replacement.
This comment has been minimized.
This comment has been minimized.
Cover cache reuse in both assignment orders, constraint preservation, generic overload fallback, concrete solver targets, and bound alias arguments. Classification B: Pyright performance/inference coverage. No typeshed changes or weakened type expectations.
Contributor
|
Diff from mypy_primer, showing the effect of this PR on open source code: sympy (https://github.com/sympy/sympy)
- .../projects/sympy/sympy/solvers/bivariate.py:135:15 - error: Operator "-" not supported for "None" (reportOptionalOperand)
- .../projects/sympy/sympy/solvers/bivariate.py:139:17 - error: Operator "-" not supported for type "Basic | Unknown" (reportOperatorIssue)
- .../projects/sympy/sympy/solvers/bivariate.py:144:23 - error: Operator "-" not supported for "None" (reportOptionalOperand)
- .../projects/sympy/sympy/solvers/deutils.py:234:14 - error: Operator "not in" not supported for types "str" and "Unknown | int"
- Operator "not in" not supported for types "str" and "int" (reportOperatorIssue)
- .../projects/sympy/sympy/solvers/diophantine/diophantine.py:423:38 - error: Argument of type "Unknown | Expr | Literal[0]" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:423:38 - error: Argument of type "int | Expr" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
- Type "Unknown | Expr | Literal[0]" is not assignable to type "Expr"
+ Type "int | Expr" is not assignable to type "Expr"
- "Literal[0]" is not assignable to "Expr" (reportArgumentType)
+ "int" is not assignable to "Expr" (reportArgumentType)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:504:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:505:19 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:506:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:569:42 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+ Operator "*" not supported for types "int" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:569:50 - error: Operator "*" not supported for types "Expr" and "Unknown | Basic"
+ Operator "*" not supported for types "Expr" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:725:42 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:735:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:736:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:737:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:814:47 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:841:26 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:842:22 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:36 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+ Operator "*" not supported for types "int" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:42 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:51 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+ Operator "*" not supported for types "int" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:57 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:66 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:845:53 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:854:18 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:856:22 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:865:22 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:865:36 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:867:27 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:868:27 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:869:27 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:870:27 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:871:27 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:872:27 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:876:24 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:877:24 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:878:24 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+ Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:879:24 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:880:24 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:881:24 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+ Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:888:37 - error: Operator "*" not supported for types "int" and "Unknown | None"
+ Operator "*" not supported for types "int" and "None" (reportOperatorIssue)
+ .../projects/sympy/sympy/solvers/diophantine/diophantine.py:888:45 - error: Operator "*" not supported for types "int" and "Unknown | None"
+ Operator "*" not supported for types "int" and "None" (reportOperatorIssue)
... (truncated 1745 lines) ...
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Improve checking performance for lists of overloaded callables, including NumPy's
np.array([np.sum, np.mean])and pandas'test_series_apply.py, without adding NumPy-specific logic.Recursive sequence rejection
listassignments to a recognized recursive sequence protocol with one covariant type parameter. Tuples use ordinary protocol matching.__getitem__return type to be exactlyLeaf | Protocol[Leaf]. Similar member names alone are not sufficient.Constraint solving
solveAndApplyConstraintswhen the target type requires no specialization and has no alias metadata, avoiding full constraint solves whose results would not transform the target.Protocol cache provenance
Problem and Correctness Argument
Why checking is slow
For
np.array([np.sum, np.mean]), Pyright must determine which NumPy overload accepts a list of overloaded functions. Some candidates describe recursive sequences: each element can be a supported leaf value or another sequence. Rejecting an unsuitable candidate can trigger expensive recursive protocol matching, overloaded-function comparison, and repeated constraint solving. Profiling also identified substantial work solving constraints even when the particular type being transformed could not change.The goal is to eliminate unnecessary work without rejecting valid candidates, accepting invalid ones, or losing inferred type precision. There are two independent changes: skip solving when substitution cannot transform the target (excluding alias metadata), and reject narrowly recognized recursive-sequence candidates using an element-compatibility condition. A successful element check never establishes full protocol compatibility; ordinary structural matching still runs.
Necessary-condition argument
Suppose the source is
list[E]and protocolP[L]promises:For integer indexing, the built-in list signature returns$\preceq$ for assignability:
E. Implementing the protocol therefore requires that this return type be assignable to the promised return type. WritingTaking the contrapositive gives the early-rejection rule:
This justifies rejection, not acceptance: other protocol members can still be incompatible.
The integer-index premise is essential. A slice returns another list, not an element. A slice-only recursive protocol supplied a real counterexample to the return-type-only guard: the shortcut selected fallback
str, whereas full matching selectedLiteral['slice']. A local follow-up adds a built-in-integer parameter guard and a precise slice regression. As of this description update (2026-09-04), that fix is in local commitea13f4cc3, not the published PR head34179d476. The published implementation therefore does not yet enforce this premise. The approximately 4.1-second post-fix pandas measurements and additional local tests must not be mistaken for validation of a published integer-index fix.What an exception-free proof still requires
The implication above is not a complete proof of the implementation. The remaining obligations are:
The proof target is that every early rejection is justified under the applicable typing rules and inference state, and subsequent decisions remain unaffected. Each obligation needs a code-backed argument or an explicit fallback when its premises cannot be established. Passing suites, adversarial cases, performance measurements, and no-shortcut comparisons provide evidence, not a universal guarantee. The discovered slice counterexample demonstrates the distinction. These obligations remain merge gates, not completed claims of correctness.
Performance
Latest local measurements used Node 26.5.0, Python 3.12.3, NumPy 2.4.6, and pandas revision
82a712a52a55e9ac99c59a818e36c4d400bc5dbe. The core was compiled before each CLI bundle. Base checker:c77393240247e017db6538d35876e723c518e06c; measured implementation revision:9577c1663acf0751c858a8cd63d4b36c4145b911. The subsequent test-only commit34179d47639970e3d8be13ca21949bde2af2f6a6does not change production code; benchmarks have not been rerun for it.The base singleton lists containing only
np.sumor onlynp.meanchecked in 0.32 and 0.34 seconds. The combined trigger revealedndarray[tuple[Any, ...], dtype[Any]]with no errors or warnings across these measurements. The final pandas runs also reported no errors or warnings.These are single-run local observations, not statistical benchmarks or Node 24 CI comparisons. The historical pandas base timing is not a fresh paired baseline. The independent solver guard provides a substantial partial improvement; it does not by itself retain the approximately four-second pandas result.
Tests and Local Validation
SolveAndApplyConstraintsConcreteType: concrete types, Any, and Unknown avoid constraint-set traversal; bound TypeVars stay bound; free TypeVars specialize precisely through nestedlist[tuple[T, int]]and generic alias metadata.34179d476, plus expanded existing assertions:intwith fresh constraint trackers.Literal['array']selection for valid array-like sequences andstrfallback for overloaded callables, scopedlist[T], nestedlist[tuple[T, int]], ParamSpec callables, and TypeVarTuple tuples.git diff --checkalso passing on34179d476.Coverage Progress and Remaining Before Merge
list[T]and nestedlist[tuple[T, int]], callable fallback, and precise positive overload selection. These model the relevant matching behavior rather than importing NumPy.dict[Dummy, Basic | Unknown] -> dict[Dummy, Basic]change with a reproducible, semantically justified expectation. Repeated unchanged-base full-project runs produced different diagnostics, so those broad diffs do not yet establish causality.Correct typing behavior is the acceptance criterion, not exact parity with every old diagnostic. Any reproducible semantic change needs an explanation and focused coverage; passing local tests and having pushed the code do not close the remaining cache/inference gates.