Uh oh!
There was an error while loading. Please reload this page.
Reduce TypeForm recognition slowdown (Take 3) - #21833
Open
davidfstr wants to merge 5 commits into
Open
Conversation
…-type in try_parse_as_type_expression() Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n frequency Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ry_parse_as_type_expression() Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e_as_type_expression() Implemented with plain string operations rather than a regular expression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…omponent in try_parse_as_type_expression() Implemented with plain string operations rather than a regular expression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 11, 2026
Contributor
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
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 freeto 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.
References #21262. Replaces #21585 and obsoletes #21596.
Summary
Enabling
TypeFormby default (referenced #21262) madeSemanticAnalyzer.try_parse_as_type_expressionrun eagerly on every expressionin certain syntactic positions. The cost is concentrated in the expensive
full-parse block (
expr_to_analyzed_type+isolated_error_analysis), whichfails ~87% of the time - pure wasted work.
This branch adds early-reject filters that eliminate 74% of full parses
(2570 → 666) on mypy's self-check, recovering ~46% of the regression:
+1.57% → +0.84% CPU time.
No new regexes - per review feedback on replaced #21585. Every filter here
is plain string/
isinstancework, and the two shape tests that were regexesare now helper functions.
Why not do a type-context check?
Review of replaced #21585 suggested skipping the call to
SemanticAnalyzer.try_parse_as_type_expressionentirely when the type contextcannot be a
TypeForm. That optimization already exists, but in the othertype checker pass at
ExpressionChecker.try_parse_as_type_expression.The same skip cannot be used in the semantic analyzer pass's function
because the type context is not yet known.
So cheaply filtering the inputs to
SA.try_parse_as_type_expressionis theonly remaining (obvious) lever to reduce its runtime contribution.
Optimization Results
CPU time, single worker, paired per-round deltas, n=300:
<TypeForm-disabled-commit>5bb72b788<tip-of-this-pr-branch>The feature branch recovers
19.5 ms of the 41.9 ms regression (~46% by paired median) -
leaving +22.4 ms (~54%). Derivation:
A separate 2-way run of master vs
<tip-of-this-pr-branch>measured−21.1 ms ±3.2, consistent with the 19.5 ms recovered that was derived above.
Notes on the measurement
The baseline (
<TypeForm-disabled-commit>) is current master (5bb72b788)with referenced #21262 (SHA:
dd851f559) reverted, so all three arms sharetoday's code and differ only in
TypeForm. Measuring against the originalpre-#21262 master commit instead of today's master would have conflated
optimizations made during the following ~80 commits, including notably
c0cced35c,which optimised
SA.try_parse_as_type_expressionspecifically.Thus runtime regression measured here (+41.9 ms) is smaller than the
+50.2 ms reported in replaced #21585: part of the original regression has
already been absorbed upstream.
Full parses per self-check, identical corpus:
The successful-parse count is unchanged at every commit on the branch,
as expected: No expression that previously parsed as a type stopped doing so.
Overview of changes
SemanticAnalyzer.try_parse_as_type_expressionfunction. All other changes occur within the same file.
The filter commits
Bare-identifier strings (
"Foo"):Varwhose declared type is a concreteInstance- a value, not a type.FuncDef/OverloadedFuncDef/MypyFile- functions and modules are never types.Other strings:
in a type expression - leading/trailing
., or one of!:/<>@%$^?;&~`\,or a
-that is not aLiteral[...]unary minus. Catches"utf-8",".pyi","error:","pkg/mod.py"."builtins.tuple","typing.Mapping"): look up theleftmost component and reject when it does not resolve, or resolves to a
placeholder or a value
Var.Filters 4 and 5 replace
_NONTYPE_PATTERN_REand_DOTTED_IDENTIFIER_REfromreplaced #21585 with the helpers
has_nontype_char()anddotted_identifier_leftmost().Each was verified to agree with the regex it replaces on all 1171 distinct
strings the full-parse profiler observes during a self-check.
Two specific hazards, and how they are handled
var_is_typing_special_formwas extended to recognizetyping.Self/typing_extensions.Self, so filter 1 does not reject a stringified'Self'annotation (otherwise
testSelfRecognizedInOtherSyntacticLocationsregresses).In filter 4,
-is treated as a unary minus wherever the preceding non-spacecharacter is
[or,, so"Literal[-1, -2]"and"Literal[1, -2]"are stillrecognized. (
_NONTYPE_PATTERN_REin replaced #21585 used(?<!\[)-, which rejectedthose.) On the strings observed during a self-check the two rules reject
identical sets, so the (improved) soundness costs nothing.
Notes
I don't think it's worth trying to recover the remaining +22.4 ms:
no common cheap/obvious shape left
OpExprfilters that actuallygave a net slowdown of 1.9ms.
The profiling instrumentation and the
misc/perf_compare.pyimprovements used to produce these numbers are in a separate PR: Enhance/extend general & TypeForm-specific performance instrumentation #21832. Happy to fold them in here instead if that is easier to review.