Skip to content

Fixed mapped types not being considered as homomorphic with substitution constraints - #4596

Open
Mateusz Burzyński (Andarist) wants to merge 2 commits into
microsoft:mainfrom
Andarist:fix/homomorphic-mapped-types-substitution-constraints
Open

Fixed mapped types not being considered as homomorphic with substitution constraints#4596
Mateusz Burzyński (Andarist) wants to merge 2 commits into
microsoft:mainfrom
Andarist:fix/homomorphic-mapped-types-substitution-constraints

Conversation

@Andarist

Copy link
Copy Markdown
Contributor

Copilot AI review requested due to automatic review settings July 10, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a TypeScript checker behavior in the Go port so that mapped types remain homomorphic even when their keyof T constraint flows through a substitution type (matching the regression described in microsoft/TypeScript#63132). It also adds a targeted compiler regression test and commits the corresponding reference baselines.

Changes:

  • Unwrap substitution types when detecting a homomorphic mapped type variable, so keyof T extends ... ? { [K in keyof T]: ... } : ... continues to be recognized as homomorphic.
  • Add a new compiler test case reproducing the scenario from the linked upstream issue.
  • Add committed .types and .symbols reference baselines for the new test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
internal/checker/checker.go Adjusts homomorphic mapped type detection to use the “actual” (unsubstituted) type variable.
testdata/tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts New regression test covering homomorphic behavior through conditional wrapping.
testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.types Reference type baseline for the new test.
testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.symbols Reference symbol baseline for the new test.

@jakebailey

Copy link
Copy Markdown
Member

TypeScript Bot (@typescript-bot) test it

@typescript-automation

typescript-automation Bot commented Aug 18, 2026

Copy link
Copy Markdown

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

Command Status Results
test top400 ✅ Started 👀 Results
perf test this faster ✅ Started 👀 Results

@typescript-automation

Copy link
Copy Markdown

Jake Bailey (@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 - native
Errors 41 41 ~ ~ ~ p=1.000 n=12
Symbols 115,480 115,480 ~ ~ ~ p=1.000 n=12
Types 96,945 96,945 ~ ~ ~ p=1.000 n=12
Memory Used 149,032k (± 0.61%) 149,199k (± 0.37%) ~ 147,892k 151,190k p=0.590 n=12
Memory Allocs 2,272,044 (± 0.00%) 2,271,952 (± 0.00%) ~ 2,271,785 2,272,168 p=0.198 n=12
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Parse Time 0.040s (± 5.52%) 0.041s (± 3.56%) ~ 0.038s 0.044s p=0.170 n=12
Bind Time 0.014s (±12.11%) 0.012s (±10.62%) ~ 0.011s 0.018s p=0.231 n=12
Check Time 0.488s (± 0.81%) 0.492s (± 0.87%) ~ 0.481s 0.507s p=0.297 n=12
Emit Time 0.272s (± 1.73%) 0.268s (± 1.58%) ~ 0.255s 0.281s p=0.192 n=12
Total Time 0.821s (± 0.53%) 0.820s (± 0.69%) ~ 0.809s 0.838s p=0.679 n=12
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=12
Symbols 847,058 (± 0.08%) 847,226 (± 0.07%) ~ 845,783 848,492 p=0.551 n=12
Types 249,913 (± 0.00%) 249,911 (± 0.00%) ~ 249,910 249,915 p=0.054 n=12
Memory Used 793,328k (± 0.06%) 793,307k (± 0.09%) ~ 792,309k 796,267k p=0.799 n=12
Memory Allocs 13,038,905 (± 0.05%) 13,035,618 (± 0.02%) ~ 13,031,041 13,041,920 p=0.443 n=12
Config Time 0.016s (± 1.13%) 0.016s (± 1.13%) ~ 0.016s 0.017s p=1.000 n=12
Parse Time 0.278s (± 2.80%) 0.280s (± 2.40%) ~ 0.259s 0.293s p=0.639 n=12
Bind Time 0.063s (± 7.75%) 0.062s (± 7.23%) ~ 0.058s 0.083s p=0.971 n=12
Check Time 0s 0s ~ ~ ~ p=1.000 n=12
Emit Time 1.641s (± 1.05%) 1.631s (± 0.76%) ~ 1.612s 1.685s p=0.368 n=12
Total Time 2.015s (± 1.19%) 2.007s (± 0.87%) ~ 1.972s 2.077s p=0.887 n=12
mui-docs - native
Errors 11,274 (± 0.05%) 11,280 (± 0.02%) ~ 11,275 11,282 p=0.099 n=12
Symbols 4,602,406 4,602,406 ~ ~ ~ p=1.000 n=12
Types 1,639,118 1,639,118 ~ ~ ~ p=1.000 n=12
Memory Used 5,614,823k (± 0.02%) 5,614,123k (± 0.03%) ~ 5,610,308k 5,619,648k p=0.347 n=12
Memory Allocs 53,528,405 (± 0.03%) 53,522,370 (± 0.04%) ~ 53,479,002 53,577,370 p=0.443 n=12
Config Time 0.016s (± 1.14%) 0.016s (± 1.81%) ~ 0.015s 0.016s p=0.590 n=12
Parse Time 0.558s (± 1.91%) 0.549s (± 2.19%) ~ 0.516s 0.582s p=0.355 n=12
Bind Time 0.002s 0.002s ~ ~ ~ p=1.000 n=12
Check Time 17.200s (± 0.37%) 17.224s (± 0.43%) ~ 17.043s 17.355s p=0.418 n=12
Emit Time 0.457s (± 2.73%) 0.454s (± 2.78%) ~ 0.436s 0.479s p=0.968 n=12
Total Time 18.967s (± 0.38%) 18.994s (± 0.46%) ~ 18.788s 19.149s p=0.418 n=12
strada-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,394,411 1,394,411 ~ ~ ~ p=1.000 n=12
Types 443,627 443,627 ~ ~ ~ p=1.000 n=12
Memory Used 1,649,226k (± 0.27%) 1,649,935k (± 0.35%) ~ 1,631,111k 1,662,554k p=0.887 n=12
Memory Allocs 97,227,912 (± 0.04%) 97,204,615 (± 0.04%) ~ 97,110,379 97,310,198 p=0.347 n=12
Config Time 0.004s (±16.26%) 0.003s (±19.95%) ~ 0.002s 0.006s p=0.654 n=12
Parse Time 0.193s (± 2.72%) 0.201s (± 2.25%) 🔻+0.009s (+ 4.46%) 0.185s 0.209s p=0.023 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.782s (± 0.46%) 1.780s (± 0.41%) ~ 1.762s 1.796s p=0.744 n=12
Emit Time 0.280s (± 5.76%) 0.282s (± 6.50%) ~ 0.243s 0.332s p=1.000 n=12
Total Time 24.522s (± 0.79%) 24.652s (± 0.87%) ~ 24.314s 25.463s p=0.551 n=12
strada-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 337,618 337,618 ~ ~ ~ p=1.000 n=12
Types 198,941 198,941 ~ ~ ~ p=1.000 n=12
Memory Used 319,686k (± 0.07%) 319,609k (± 0.02%) ~ 319,436k 319,809k p=0.843 n=12
Memory Allocs 4,676,745 (± 0.01%) 4,676,702 (± 0.01%) ~ 4,675,500 4,678,242 p=0.887 n=12
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=12
Parse Time 0.118s (± 4.84%) 0.114s (± 3.26%) ~ 0.105s 0.124s p=0.202 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.034s (± 0.36%) 1.029s (± 0.35%) -0.005s (- 0.48%) 1.023s 1.042s p=0.029 n=12
Emit Time 0.125s (±12.74%) 0.147s (± 7.16%) ~ 0.101s 0.163s p=0.097 n=12
Total Time 1.336s (± 1.14%) 1.340s (± 0.72%) ~ 1.310s 1.361s p=0.469 n=12
ts-pre-modules - native
Errors 87 87 ~ ~ ~ p=1.000 n=12
Symbols 305,341 305,341 ~ ~ ~ p=1.000 n=12
Types 182,038 182,038 ~ ~ ~ p=1.000 n=12
Memory Used 277,362k (± 0.02%) 277,351k (± 0.02%) ~ 277,185k 277,487k p=0.854 n=12
Memory Allocs 1,644,397 (± 0.01%) 1,644,392 (± 0.01%) ~ 1,644,096 1,644,770 p=0.843 n=12
Config Time 0.000s (±113.79%) 0.000s ~ ~ ~ p=0.217 n=12
Parse Time 0.099s (± 4.56%) 0.100s (± 3.38%) ~ 0.092s 0.109s p=0.722 n=12
Bind Time 0.036s (±11.42%) 0.037s (±12.24%) ~ 0.028s 0.050s p=0.722 n=12
Check Time 0.817s (± 0.69%) 0.816s (± 0.77%) ~ 0.803s 0.834s p=0.831 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 0.967s (± 0.69%) 0.968s (± 0.71%) ~ 0.950s 0.986s p=0.560 n=12
vscode - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 7,930,740 7,930,740 ~ ~ ~ p=1.000 n=12
Types 2,712,367 2,712,367 ~ ~ ~ p=1.000 n=12
Memory Used 5,313,282k (± 0.01%) 5,313,892k (± 0.02%) ~ 5,312,075k 5,317,562k p=0.590 n=12
Memory Allocs 41,082,165 (± 0.01%) 41,084,873 (± 0.01%) ~ 41,077,510 41,110,174 p=0.590 n=12
Config Time 0.061s (± 0.53%) 0.061s (± 0.53%) ~ 0.061s 0.062s p=1.000 n=12
Parse Time 1.622s (± 1.57%) 1.616s (± 2.51%) ~ 1.477s 1.700s p=1.000 n=12
Bind Time 0.335s (± 0.37%) 0.376s (±17.69%) 🔻+0.041s (+12.33%) 0.333s 0.699s p=0.030 n=12
Check Time 9.647s (± 0.59%) 9.580s (± 1.43%) ~ 9.054s 9.700s p=0.989 n=12
Emit Time 3.051s (± 4.61%) 3.273s (±11.86%) 🔻+0.222s (+ 7.26%) 2.978s 4.797s p=0.034 n=12
Total Time 14.794s (± 0.54%) 14.989s (± 1.91%) ~ 14.750s 16.195s p=0.110 n=12
webpack - native
Errors 2 2 ~ ~ ~ p=1.000 n=12
Symbols 1,380 1,380 ~ ~ ~ p=1.000 n=12
Types 340 340 ~ ~ ~ p=1.000 n=12
Memory Used 199,009k (± 0.06%) 199,071k (± 0.06%) ~ 198,714k 199,343k p=0.443 n=12
Memory Allocs 843,155 (± 0.08%) 843,268 (± 0.14%) ~ 841,207 848,076 p=0.932 n=12
Config Time 0.008s 0.008s ~ ~ ~ p=1.000 n=12
Parse Time 0.210s (± 2.89%) 0.209s (± 2.89%) ~ 0.190s 0.227s p=0.522 n=12
Bind Time 0s 0s ~ ~ ~ p=1.000 n=12
Check Time 0s 0s ~ ~ ~ p=1.000 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 0.219s (± 2.77%) 0.217s (± 2.81%) ~ 0.198s 0.235s p=0.540 n=12
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,020,005 1,020,005 ~ ~ ~ p=1.000 n=12
Types 376,891 376,891 ~ ~ ~ p=1.000 n=12
Memory Used 603,497k (± 0.02%) 603,549k (± 0.01%) ~ 603,402k 603,735k p=0.378 n=12
Memory Allocs 4,751,663 (± 0.03%) 4,751,274 (± 0.03%) ~ 4,748,046 4,754,055 p=0.799 n=12
Config Time 0.003s 0.003s (± 6.23%) ~ 0.002s 0.003s p=1.000 n=12
Parse Time 0.143s (± 2.12%) 0.146s (± 3.87%) ~ 0.131s 0.156s p=0.520 n=12
Bind Time 0.050s (±15.33%) 0.045s (±17.58%) ~ 0.033s 0.069s p=0.245 n=12
Check Time 1.028s (± 0.56%) 1.015s (± 0.79%) -0.013s (- 1.25%) 0.994s 1.037s p=0.006 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 1.236s (± 0.73%) 1.218s (± 0.95%) -0.018s (- 1.42%) 1.191s 1.244s p=0.025 n=12
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • strada-build-src - native
  • strada-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 12
Baseline baseline 12

Developer Information:

Download Benchmarks

@typescript-automation

Copy link
Copy Markdown

Jake Bailey (@jakebailey) Here are the results of running the top 400 repos with tsc comparing main and refs/pull/4596/merge:

Something interesting changed - please have a look.

Details

sindresorhus/type-fest

tsconfig.json

  • error TS2345: Argument of type '{ tuple: [number]; } | { tuple: [number, string]; foo: any; }' is not assignable to parameter of type '{ tuple: [number]; }'.
  • error TS2345: Argument of type '{ tuple: boolean[]; } | { tuple: [number, string]; }' is not assignable to parameter of type '{ tuple: [number | boolean, string | boolean]; }'.
  • error TS2345: Argument of type '{ tuple: [number, string]; } | { tuple: [number, string, boolean]; }' is not assignable to parameter of type '{ tuple: [number, string]; }'.
  • error TS2345: Argument of type '{ tuple: [number, string]; } | { tuple: []; }' is not assignable to parameter of type '{ tuple: []; }'.
  • error TS2345: Argument of type '{ tuple: [number, string, number]; } | { tuple: [number, boolean, ...string[]]; }' is not assignable to parameter of type '{ tuple: [number, string | boolean, string | number]; }'.
  • error TS2345: Argument of type '{ tuple: [number, string]; } | { tuple: [number, string, boolean]; } | { tuple: number[]; }' is not assignable to parameter of type '{ tuple: [number, string | number]; }'.
  • error TS2345: Argument of type '{ tuple: [number, ...string[]]; } | { tuple: boolean[]; }' is not assignable to parameter of type '{ tuple: [number | boolean, ...(string | boolean)[]]; }'.
  • error TS2345: Argument of type '{ tuple: [number, ...string[]]; } | { tuple: [number, string, ...boolean[]]; }' is not assignable to parameter of type '{ tuple: [number, string, ...(string | boolean)[]]; }'.

@jakebailey

Copy link
Copy Markdown
Member

Hmmmm, this is a break?

@jakebailey Jake Bailey (jakebailey) added the Unmigrated PR This PR was open at the time of the repo move back to TypeScript label Aug 18, 2026
@Andarist

Copy link
Copy Markdown
Contributor Author

Jake Bailey (@jakebailey) this is kinda expected.

A distributive homomorphic type requires to be written in this form (roughly):

type M<T> = {
  [K in keyof T]: T[K]
}

A type-fest-based repro case can be seen here. As we can see, that exact form ([K in keyof T]: ...) is used there. Therefore, I think the expectation should be that this would be treated as a distributive homomorphic type.

But because of the issue fixed by this PR, that mapped type was not recognized as homomorphic and distributive. That happened because keyof T in [K in keyof T] became (invisibly!) a substitution type internally. With the fix here, it gets recognized properly~ so it started to distribute and thus it broke type-fest's implementation as it was accidentally leveraging the incorrect behavior.

It's common to change the code a little bit to enable/disable distributivity of conditional types and I think the same principle kinda applied to mapped types, given the distributivity (or the lack of it) is also an implied trait of any mapped type.

I just pushed out a test documenting this and showcasing 2 possible workarounds for the type-fest's case, see this commit. If this PR lands then I will go to type-fest and PR a fix for them.

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

Labels

Unmigrated PR This PR was open at the time of the repo move back to TypeScript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: Mapped types are no longer homomorphic when wrapped in certain conditional types

3 participants