Skip to content

Conversation

@jakebailey
Copy link
Member

Yoinked out of #62556 / tsgo-port.

The Go port of the compiler does concurrent checking with multiple checkers. But because they can visit nodes/types/symbols in a different order, their results are not stable when compared to each other due to lazy ID assignment.

The way the port addresses this is to enforce a strict ordering of types, symbols, and nodes, that does not rely on IDs. This causes changes in behavior, however, since the ordering is not a defined part of the language.

This PR adds a flag that enables the same algorithm that TS7 has, at the cost of perf.

On main, this flag is defaulted to false, of course, but it'd be flipped on in tsgo-port (which is more convenient than maintaining a patch).

This requires TypeFlags to change order, but this is not a part of the API per se; nobody should depend on the exact values of these as they are not declared const in the published .d.ts.

Copilot AI review requested due to automatic review settings February 3, 2026 21:09
@github-project-automation github-project-automation bot moved this to Not started in PR Backlog Feb 3, 2026
@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Feb 3, 2026
@typescript-bot
Copy link
Collaborator

Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page.

Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up.

@jakebailey
Copy link
Member Author

The flag is disabled; checking perf to verify that it isn't harmful.

@typescript-bot perf test this faster

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 3, 2026

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
perf test this faster ✅ Started 👀 Results

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new --stableTypeOrdering compiler flag intended to make type/symbol/node ordering deterministic (TS7 ordering compatibility) to support concurrent checking in the tsgo port, at a performance cost.

Changes:

  • Introduces the stableTypeOrdering compiler option (CLI + CompilerOptions) and its help/diagnostic text.
  • Reorders TypeFlags (and adjusts related flag groupings) to support a deterministic sort strategy.
  • Implements stable ordering logic in the checker (type/symbol/node comparators and stable sorting in several flows).

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/baselines/reference/tsc/commandLine/help-all.js Updates --help --all baseline to include --stableTypeOrdering.
tests/baselines/reference/api/typescript.d.ts Updates public API baselines for reordered TypeFlags and new CompilerOptions.stableTypeOrdering.
src/compiler/types.ts Reorders TypeFlags, tweaks ObjectFlags.ObjectTypeKindMask, adds stableTypeOrdering to CompilerOptions.
src/compiler/diagnosticMessages.json Adds the option description message used by the CLI help output.
src/compiler/core.ts Makes binarySearchKey tolerant of non-Comparison comparer returns; exports compareComparableValues.
src/compiler/commandLineParser.ts Registers the new stableTypeOrdering option for CLI/config parsing.
src/compiler/checker.ts Adds stable ordering machinery and wires it behind compilerOptions.stableTypeOrdering.

Comment on lines 54150 to 54154
switch (kind1) {
case TypeMapKind.Simple: {
const c = compareTypes(m1.source, (m2 as typeof m1).source);
if (c !== 0) {
return c;
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

TypeMapKind includes Deferred, Function, and Composite (see src/compiler/types.ts:7076+), but compareTypeMappers doesn’t handle them. For those kinds, the switch falls through and returns 0, causing many distinct mappers to compare equal and forcing later fallback to unstable IDs. Add deterministic comparisons for the missing kinds (and consider handling Composite similarly to Merged).

Copilot uses AI. Check for mistakes.
Comment on lines 18075 to 18079
return stableTypeOrdering ? binarySearch(types, type, identity, compareTypes) >= 0 : binarySearch(types, type, getTypeId, compareValues) >= 0;
}

function insertType(types: Type[], type: Type): boolean {
const index = binarySearch(types, type, getTypeId, compareValues);
const index = stableTypeOrdering ? binarySearch(types, type, identity, compareTypes) : binarySearch(types, type, getTypeId, compareValues);
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

binarySearch expects a Comparer returning Comparison (-1/0/1), but compareTypes returns arbitrary numbers (e.g. ID differences). This is a type error and also violates the Comparer contract. Wrap with Math.sign/toComparison at the call site (or change compareTypes to return Comparison by using compareValues/compareComparableValues and converting numeric deltas).

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

This is true, but I updated binarySearch to math.Sign, which is theoretically sufficient, although I could define a compareTypes helper that does that

Comment on lines 54102 to 54106
for (let i = 0; i < (t1.labeledElementDeclarations?.length ?? 0); i++) {
const c = compareElementLabels(t1.labeledElementDeclarations![i], t2.labeledElementDeclarations![i]);
if (c !== 0) {
return c;
}
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

Tuple label comparison only iterates up to t1.labeledElementDeclarations?.length and doesn’t account for t2 having more (or fewer) labels, so tuples can compare equal despite differing label metadata and then fall back to unstable IDs. Compare the label-declaration lengths first (or iterate up to max(len1, len2) and treat missing as undefined).

Copilot uses AI. Check for mistakes.
@typescript-bot
Copy link
Collaborator

@jakebailey
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 62,370 62,370 ~ ~ ~ p=1.000 n=6
Types 50,387 50,387 ~ ~ ~ p=1.000 n=6
Memory used 195,063k (± 0.96%) 193,373k (± 0.77%) ~ 192,618k 196,397k p=0.378 n=6
Parse Time 1.30s (± 0.42%) 1.30s (± 0.75%) ~ 1.29s 1.31s p=0.662 n=6
Bind Time 0.76s (± 0.72%) 0.75s (± 0.68%) ~ 0.75s 0.76s p=0.640 n=6
Check Time 9.93s (± 0.24%) 9.90s (± 0.28%) ~ 9.87s 9.94s p=0.121 n=6
Emit Time 2.75s (± 0.76%) 2.76s (± 0.76%) ~ 2.74s 2.80s p=0.807 n=6
Total Time 14.74s (± 0.24%) 14.72s (± 0.16%) ~ 14.70s 14.76s p=0.331 n=6
angular-1 - node (v18.15.0, x64)
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 955,823 955,823 ~ ~ ~ p=1.000 n=6
Types 415,853 415,853 ~ ~ ~ p=1.000 n=6
Memory used 1,254,764k (± 0.01%) 1,254,815k (± 0.00%) ~ 1,254,792k 1,254,887k p=0.873 n=6
Parse Time 6.54s (± 0.73%) 6.57s (± 0.42%) ~ 6.52s 6.60s p=0.467 n=6
Bind Time 1.96s (± 0.50%) 1.96s (± 0.26%) ~ 1.96s 1.97s p=0.386 n=6
Check Time 32.47s (± 0.22%) 32.44s (± 0.32%) ~ 32.27s 32.56s p=0.572 n=6
Emit Time 14.96s (± 0.43%) 15.11s (± 0.47%) +0.15s (+ 0.99%) 15.03s 15.21s p=0.008 n=6
Total Time 55.92s (± 0.16%) 56.08s (± 0.25%) ~ 55.87s 56.20s p=0.092 n=6
mui-docs - node (v18.15.0, x64)
Errors 11,439 11,439 ~ ~ ~ p=1.000 n=6
Symbols 2,701,943 2,701,943 ~ ~ ~ p=1.000 n=6
Types 930,574 930,574 ~ ~ ~ p=1.000 n=6
Memory used 3,038,225k (± 0.00%) 3,039,209k (± 0.00%) +984k (+ 0.03%) 3,039,068k 3,039,296k p=0.005 n=6
Parse Time 8.52s (± 0.18%) 8.51s (± 0.32%) ~ 8.46s 8.54s p=0.871 n=6
Bind Time 2.31s (± 0.27%) 2.30s (± 0.45%) ~ 2.29s 2.32s p=0.203 n=6
Check Time 93.11s (± 0.37%) 92.82s (± 0.23%) ~ 92.52s 93.12s p=0.078 n=6
Emit Time 0.31s (± 2.41%) 0.31s (± 2.60%) ~ 0.30s 0.32s p=0.729 n=6
Total Time 104.25s (± 0.32%) 103.95s (± 0.19%) ~ 103.67s 104.26s p=0.093 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,252,487 1,252,975 +488 (+ 0.04%) ~ ~ p=0.001 n=6
Types 259,934 260,045 +111 (+ 0.04%) ~ ~ p=0.001 n=6
Memory used 2,515,165k (±11.82%) 2,396,253k (± 0.04%) ~ 2,395,037k 2,397,489k p=0.066 n=6
Parse Time 5.18s (± 1.35%) 5.15s (± 0.97%) ~ 5.09s 5.21s p=0.872 n=6
Bind Time 1.82s (± 0.97%) 1.85s (± 1.12%) +0.03s (+ 1.74%) 1.82s 1.87s p=0.029 n=6
Check Time 35.62s (± 0.32%) 35.69s (± 0.67%) ~ 35.51s 36.10s p=0.936 n=6
Emit Time 3.01s (± 0.83%) 2.98s (± 2.09%) ~ 2.88s 3.05s p=0.575 n=6
Total Time 45.63s (± 0.33%) 45.70s (± 0.59%) ~ 45.45s 46.20s p=0.689 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,252,487 1,252,975 +488 (+ 0.04%) ~ ~ p=0.001 n=6
Types 259,934 260,045 +111 (+ 0.04%) ~ ~ p=0.001 n=6
Memory used 3,190,421k (± 0.02%) 3,193,190k (± 0.02%) +2,769k (+ 0.09%) 3,192,324k 3,194,186k p=0.005 n=6
Parse Time 8.45s (± 0.98%) 8.48s (± 0.87%) ~ 8.35s 8.55s p=0.423 n=6
Bind Time 2.76s (± 1.23%) 2.78s (± 0.72%) ~ 2.76s 2.81s p=0.226 n=6
Check Time 53.27s (± 0.38%) 53.67s (± 0.41%) +0.40s (+ 0.75%) 53.28s 53.92s p=0.031 n=6
Emit Time 4.24s (± 2.15%) 4.33s (± 2.34%) ~ 4.17s 4.44s p=0.229 n=6
Total Time 68.72s (± 0.26%) 69.26s (± 0.38%) +0.54s (+ 0.79%) 68.92s 69.57s p=0.008 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 264,631 265,000 +369 (+ 0.14%) ~ ~ p=0.001 n=6
Types 104,072 104,172 +100 (+ 0.10%) ~ ~ p=0.001 n=6
Memory used 443,625k (± 0.01%) 444,383k (± 0.01%) +758k (+ 0.17%) 444,339k 444,451k p=0.005 n=6
Parse Time 3.53s (± 0.28%) 3.52s (± 1.29%) ~ 3.46s 3.56s p=0.803 n=6
Bind Time 1.38s (± 0.65%) 1.38s (± 1.25%) ~ 1.36s 1.40s p=0.676 n=6
Check Time 19.18s (± 0.24%) 19.22s (± 0.39%) ~ 19.15s 19.36s p=0.572 n=6
Emit Time 1.55s (± 1.27%) 1.55s (± 0.86%) ~ 1.53s 1.57s p=0.802 n=6
Total Time 25.64s (± 0.15%) 25.67s (± 0.35%) ~ 25.57s 25.82s p=0.688 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 74 74 ~ ~ ~ p=1.000 n=6
Symbols 225,493 225,493 ~ ~ ~ p=1.000 n=6
Types 94,373 94,373 ~ ~ ~ p=1.000 n=6
Memory used 370,197k (± 0.06%) 370,284k (± 0.05%) ~ 370,120k 370,598k p=0.173 n=6
Parse Time 2.82s (± 0.53%) 2.82s (± 0.95%) ~ 2.80s 2.87s p=0.413 n=6
Bind Time 1.65s (± 1.19%) 1.63s (± 0.84%) ~ 1.61s 1.64s p=0.080 n=6
Check Time 16.64s (± 0.47%) 16.70s (± 0.41%) ~ 16.59s 16.77s p=0.148 n=6
Emit Time 0.00s 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 21.12s (± 0.42%) 21.15s (± 0.37%) ~ 21.06s 21.27s p=0.688 n=6
vscode - node (v18.15.0, x64)
Errors 11 11 ~ ~ ~ p=1.000 n=6
Symbols 4,193,553 4,193,553 ~ ~ ~ p=1.000 n=6
Types 1,329,128 1,329,128 ~ ~ ~ p=1.000 n=6
Memory used 3,988,153k (± 0.00%) 3,988,417k (± 0.00%) +264k (+ 0.01%) 3,988,230k 3,988,746k p=0.013 n=6
Parse Time 16.35s (± 3.47%) 16.12s (± 0.80%) ~ 16.03s 16.36s p=0.198 n=6
Bind Time 5.51s (± 2.43%) 5.57s (± 2.77%) ~ 5.44s 5.79s p=0.415 n=6
Check Time 119.41s (± 5.67%) 118.00s (± 4.77%) ~ 113.41s 127.61s p=0.748 n=6
Emit Time 58.54s (± 8.48%) 58.08s (±14.53%) ~ 47.50s 66.57s p=0.936 n=6
Total Time 199.82s (± 3.94%) 197.77s (± 6.43%) ~ 183.09s 216.33s p=0.810 n=6
webpack - node (v18.15.0, x64)
Errors 41 41 ~ ~ ~ p=1.000 n=6
Symbols 397,465 397,465 ~ ~ ~ p=1.000 n=6
Types 175,750 175,750 ~ ~ ~ p=1.000 n=6
Memory used 550,753k (± 0.01%) 550,858k (± 0.01%) +106k (+ 0.02%) 550,730k 550,911k p=0.030 n=6
Parse Time 4.60s (± 0.61%) 4.64s (± 0.64%) ~ 4.61s 4.69s p=0.076 n=6
Bind Time 2.02s (± 0.93%) 2.02s (± 1.70%) ~ 1.98s 2.07s p=1.000 n=6
Check Time 24.22s (± 0.57%) 24.14s (± 0.49%) ~ 23.98s 24.27s p=0.521 n=6
Emit Time 0.00s (±244.70%) 0.00s (±244.70%) ~ 0.00s 0.01s p=1.000 n=6
Total Time 30.84s (± 0.52%) 30.80s (± 0.36%) ~ 30.65s 30.93s p=0.873 n=6
xstate-main - node (v18.15.0, x64)
Errors 30 30 ~ ~ ~ p=1.000 n=6
Symbols 731,941 731,941 ~ ~ ~ p=1.000 n=6
Types 219,279 219,279 ~ ~ ~ p=1.000 n=6
Memory used 635,922k (± 0.00%) 636,028k (± 0.01%) +107k (+ 0.02%) 635,916k 636,090k p=0.031 n=6
Parse Time 4.57s (± 0.55%) 4.56s (± 0.38%) ~ 4.54s 4.59s p=0.809 n=6
Bind Time 1.49s (± 0.78%) 1.49s (± 0.37%) ~ 1.48s 1.49s p=0.342 n=6
Check Time 22.13s (± 0.51%) 22.15s (± 0.42%) ~ 22.00s 22.26s p=0.630 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 28.19s (± 0.44%) 28.20s (± 0.30%) ~ 28.05s 28.28s p=0.572 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

if (c !== 0) {
return c;
}
c = (t1 as IndexType).flags - (t2 as IndexType).flags;
Copy link
Member Author

Choose a reason for hiding this comment

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

Per microsoft/typescript-go#2663 I'm pretty sure this should be a different set of flags

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

3 participants