Skip to content

Optimize type equality fast paths - #11420

Merged
Bill Schnurr (bschnurr) merged 1 commit into
microsoft:mainfrom
bschnurr:perf/is-type-same-fastpaths
Aug 26, 2026
Merged

Optimize type equality fast paths#11420
Bill Schnurr (bschnurr) merged 1 commit into
microsoft:mainfrom
bschnurr:perf/is-type-same-fastpaths

Conversation

@bschnurr

Copy link
Copy Markdown
Member

Summary

This optimizes isTypeSame by avoiding recursive comparisons when two types share the same immutable backing structures. The fast paths cover shared class type arguments, shared function parameter metadata, shared overload arrays, shared union subtype arrays, and shared TypeVar bound/constraint structures.

Details

The change keeps the existing structural comparison behavior for non-shared data, but skips recursive walks when reference identity proves the relevant backing structure is identical. This commonly applies to shallow-cloned or decorated types.

Validation

  • npm run test:norebuild -- --runTestsByPath src/tests/typeEquality.test.ts --forceExit
  • npm run build

Performance Notes

On a local full-project NumPy CPU profile, isTypeSame self time dropped from about 21.98s to 19.02s (-13.5%) after these fast paths. A sampled heap profile did not show the edited helper functions as allocation sites. Whole-program wall-clock timing remains noisy, so this PR is presented as a targeted hotspot reduction rather than a claimed end-to-end throughput win.

@github-actions

This comment has been minimized.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
- An internal error occurred while type checking file ".../projects/sympy/sympy/simplify/tests/test_powsimp.py": TypeError: Cannot read properties of undefined (reading 'isStubFile')
+ An internal error occurred while type checking file ".../projects/sympy/sympy/solvers/deutils.py": TypeError: Cannot read properties of undefined (reading 'isStubFile')
-     at a7 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22204:9)
+     at eR (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1025:9)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1560:33
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:315:33
-     at W.visitImportFrom (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1559:28)
+     at W.visitStatementList (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:310:27)
-     at W.visit (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:188:29)
+     at W.visit (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:290:29)
-     at W.walk (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:296:19)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:685:22
-     at Array.forEach (<anonymous>)
-     at W.walkMultiple (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:683:15)
-     at W.walk (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:678:18)
-     at a7 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22204:9)
+     at eR (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1025:9)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1560:33
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:315:33
-     at W.visitImportFrom (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1559:28)
+     at W.visitStatementList (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:310:27)
-     at W.visit (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:188:29)
+     at W.visit (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:290:29)
-     at W.walk (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:296:19)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:685:22
-     at Array.forEach (<anonymous>)
-     at W.walkMultiple (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:683:15)
-     at W.walk (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:678:18)

core (https://github.com/home-assistant/core)
- An internal error occurred while type checking file ".../projects/core/homeassistant/components/ovhcloud_ai_endpoints/__init__.py": TypeError: Cannot read properties of undefined (reading 'diagnosticRuleSet')
+ An internal error occurred while type checking file ".../projects/core/homeassistant/components/websocket_api/auth.py": Error: Debug Failure. Did not find evaluation scope
+     at w (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeUtils.ts:1035:5)
+     at Object.t.getExecutionScopeNode (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeUtils.ts:1079:27)
+     at nt (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22416:46)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5231:44
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5987:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:6095:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at A (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:1784:46)
+     at t (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:517:29)
+     at Object.getTypeFromCodeFlow (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:941:13)
+     at nt (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22453:25)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5231:44
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at nI (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24342:44)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24797:32
+     at Array.forEach (<anonymous>)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24793:15
+     at nA (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24784:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5173:39
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8911:31
+     at s (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8926:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8791:32
+     at Array.forEach (<anonymous>)
+     at tK (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8790:22)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8218:46
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:8221:36
-     at nS (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24527:16)
-     at no (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:23174:24)
-     at nl (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:23182:26)
-     at aI (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:16909:22)
-     at eW (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:2006:23)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1831:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5173:39
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:7406:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:7502:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:18779:35
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:18763:30
+     at t.invalidateTypeCacheIfCanceled (...pyright_to_test/packages/pyright/dist/pyright-internal/src/common/cancellationUtils.ts:38:16)
+     at az (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:18731:16)
+     at n_ (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:23918:39)
+     at nI (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24310:30)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24797:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24793:15
+     at nA (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24784:5)
+ Error performing analysis: Error: Debug Failure. Did not find evaluation scope
+     at w (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeUtils.ts:1035:5)
+     at Object.t.getExecutionScopeNode (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeUtils.ts:1079:27)
+     at nt (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22416:46)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1829:24
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5231:44
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1929:5
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5987:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:6095:5
-     at aD (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:18379:35)
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
-     at a7 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22220:25)
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24390:17
-     at a9 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22358:9)
-     at a8 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22327:16)
-     at nI (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24389:32)
-     at a5 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:21792:16)
-     at a1 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:21492:26)
-     at a7 (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22300:21)
+     at A (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:1784:46)
+     at t (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:517:29)
+     at Object.getTypeFromCodeFlow (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/codeFlowEngine.ts:941:13)
+     at nt (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:22453:25)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5231:44
-     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1560:33
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5312:5
+     at eB (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:1617:5)
+     at nI (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24342:44)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24797:32
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24793:15
-     at W.visitImportFrom (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/checker.ts:1559:28)
+     at nA (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:24784:5)
-     at W.visit (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:188:29)
+     at ...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/typeEvaluator.ts:5173:39
-     at W.visitNode (...pyright_to_test/packages/pyright/dist/pyright-internal/src/analyzer/parseTreeWalker.ts:694:21)

... (truncated 86 lines) ...

@rchiodo

Rich Chiodo (rchiodo) commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR.

assert.strictEqual(isTypeSame(clone1, distinctConstraints), false);
});

function createClassType(name: string, flags = ClassTypeFlags.None) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning · Non-blocking recommendation

Add cases for shared tuple arguments, overload arrays, and specialized function parameter metadata, plus structurally equal non-shared fallbacks. Include a function case with differing specialized return types to demonstrate that the parameter shortcut still reaches return-type comparison.

[verified]

@rchiodo

Copy link
Copy Markdown
Collaborator

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

@rchiodo Rich Chiodo (rchiodo) added the review-auto:approved Automated review: no blocking findings (approval posted). label Aug 24, 2026
@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@github-actions github-actions Bot added the run-typecheck-benchmark Run the type checker performance benchmark label Aug 24, 2026
@rchiodo

Copy link
Copy Markdown
Collaborator

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@bschnurr

Copy link
Copy Markdown
Member Author

Recovered results from benchmark run 32891326101:

Type checker benchmark

🟢 No performance regressions detected.

Regression threshold: 20.0%
Variance guard: >1.0s time and >100.0 MB memory

Package Checker Files checked Time Time delta Peak memory Memory delta Status
ansible pyright 583 19.654s -9.3% 1101.7 MB -0.8% 🟢 Pass
click pyright 17 2.796s -3.9% 361.6 MB +0.2% 🟢 Pass
homeassistant pyright 9850 140.776s -11.1% 6165.1 MB -0.4% 🟢 Pass
numpy pyright 356 39.532s -9.0% 1843.3 MB -0.4% 🟢 Pass
pandas pyright 1459 911.257s -7.9% 4447.8 MB +0.3% 🟢 Pass
pytest pyright 243 11.795s -6.2% 906.9 MB -2.6% 🟢 Pass
requests pyright 19 2.255s -1.9% 346.6 MB -0.2% 🟢 Pass
torch pyright 2400 143.148s -6.9% 4802.7 MB -1.8% 🟢 Pass
transformers pyright 2901 123.613s -8.1% 5021.8 MB -2.1% 🟢 Pass

Pyright stats

Package Parsed/bound Checked Find Read Tokenize Parse Imports Bind Check Cycles
ansible 989 583 0.040s 0.100s 0.400s 0.720s 0.210s 0.900s 16.680s 0.000s
click 111 17 0.000s 0.010s 0.120s 0.190s 0.030s 0.250s 1.890s 0.000s
homeassistant 11138 9850 0.420s 0.750s 2.900s 4.630s 2.130s 6.570s 120.260s 0.000s
numpy 603 356 0.010s 0.060s 0.370s 0.710s 0.230s 0.810s 36.720s 0.000s
pandas 1895 1459 0.060s 0.260s 1.300s 2.480s 0.300s 2.990s 902.560s 0.000s
pytest 542 243 0.020s 0.060s 0.370s 0.490s 0.110s 0.680s 9.630s 0.000s
requests 168 19 0.000s 0.010s 0.110s 0.220s 0.060s 0.250s 1.290s 0.000s
torch 3076 2400 0.140s 0.390s 2.420s 3.420s 0.560s 4.350s 130.400s 0.000s
transformers 3534 2901 0.120s 0.500s 2.600s 3.640s 0.920s 9.150s 109.680s 0.000s

Automatic chart posting is fixed by #11680.

@bschnurr

Copy link
Copy Markdown
Member Author

/benchmark

@github-actions

Copy link
Copy Markdown
Contributor

Type checker benchmark

🟢 No performance regressions detected.

Regression threshold: 20.0%
Variance guard: >1.0s time and >100.0 MB memory

Package Checker Files checked Time Time delta Peak memory Memory delta Status
ansible pyright 583 19.795s -8.6% 1042.6 MB -6.1% 🟢 Pass
click pyright 17 2.841s -2.3% 361.7 MB +0.2% 🟢 Pass
homeassistant pyright 9850 137.260s -13.3% 6189.7 MB -0.0% 🟢 Pass
numpy pyright 356 39.445s -9.2% 1820.4 MB -1.6% 🟢 Pass
pandas pyright 1459 912.699s -7.7% 4417.1 MB -0.4% 🟢 Pass
pytest pyright 243 11.787s -6.3% 894.6 MB -3.9% 🟢 Pass
requests pyright 19 2.212s -3.8% 346.8 MB -0.1% 🟢 Pass
torch pyright 2400 152.635s -0.7% 4738.8 MB -3.1% 🟢 Pass
transformers pyright 2901 120.454s -10.5% 5298.9 MB +3.3% 🟢 Pass

Pyright stats

Package Parsed/bound Checked Find Read Tokenize Parse Imports Bind Check Cycles
ansible 989 583 0.040s 0.070s 0.490s 0.730s 0.200s 1.270s 16.410s 0.000s
click 111 17 0.000s 0.010s 0.130s 0.190s 0.040s 0.250s 1.920s 0.000s
homeassistant 11138 9850 0.410s 0.750s 3.010s 5.220s 2.050s 6.860s 115.990s 0.000s
numpy 603 356 0.010s 0.070s 0.390s 0.700s 0.220s 0.900s 36.550s 0.000s
pandas 1895 1459 0.070s 0.250s 1.160s 2.190s 0.290s 2.670s 904.790s 0.000s
pytest 542 243 0.020s 0.050s 0.350s 0.510s 0.090s 0.660s 9.640s 0.000s
requests 168 19 0.000s 0.010s 0.110s 0.200s 0.050s 0.240s 1.300s 0.000s
torch 3076 2400 0.140s 0.430s 2.950s 3.570s 0.610s 4.720s 138.780s 0.000s
transformers 3534 2901 0.130s 0.420s 2.700s 3.910s 1.010s 9.630s 105.960s 0.000s

@bschnurr
Bill Schnurr (bschnurr) merged commit 37c0386 into microsoft:main Aug 26, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:approved Automated review: no blocking findings (approval posted). run-typecheck-benchmark Run the type checker performance benchmark

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants