Repository navigation
Limit of 25 members when using union of mapped type #40803
Copy link
Copy link
Closed
Labels
Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
Description
TypeScript Version: 4.0.2
Search Terms: 25, limit, mappings, mapped, label:"Domain: Big Unions", keyof
Expected behavior: Unions of mapped types (or keyof said mapped type) should work through assignment
Actual behavior: Compiler raises error when one keyof parameter is assigned to another keyof parameter. Removing any of the 26 entries in the mapped type solves the issue.
Related Issues: #35493 is similar; this question on StackOverflow appears to deal with the same behavior
Code
// Commenting out any of these fields fixes the error
type AWSObjectPayloads = {
'ec2/vpc': { a: number },
'ec2/image': { b: number },
'ec2/subnet': { c: number },
'ec2/security-group': { d: number },
'ec2/instance': { e: number },
'ec2/prefix-list': { f: number },
'ec2/network-interface': { g: number },
'ec2/vpc-endpoint': { h: number },
'ec2/nat-gateway': { i: number },
'ec2/dhcp-options': { j: number },
'ec2/elastic-ip': { k: number },
'ec2/flow-log': { l: number },
'ec2/internet-gateway': { m: number },
'ec2/network-acl': { n: number },
'ec2/peering-connection': { o: number },
'ec2/route-table': { p: number },
'ec2/vpn-gateway': { q: number },
'elb/load-balancer': { r: number },
'elb-v2/load-balancer': { s: number },
'elb-v2/target-group': { t: number },
'rds/subnet-group': { u: number },
'rds/cluster': { v: number },
'rds/instance': { w: number },
'redshift/subnet-group': { x: number },
'redshift/cluster': { y: number },
'cloudtrail/trail': { z: number },
};
interface AWSObjectImpl<Name, Desc> {
objectType: Name;
desc: Desc;
}
type AWSObjectTypesByName = {
[K in keyof AWSObjectPayloads]: AWSObjectImpl<K, AWSObjectPayloads[K]>
};
type AWSObjectTypeName = keyof AWSObjectTypesByName;
type AWSObject<T extends AWSObjectTypeName = AWSObjectTypeName> = AWSObjectTypesByName[T];
// Using this definition for AWSObject corrects the assignment error, but
// eliminates the type narrowing:
//
// type AWSObject<T extends AWSObjectTypeName = AWSObjectTypeName> = AWSObjectImpl<T, AWSObjectPayloads[T]>;
function foo(test: AWSObjectTypeName) {
const testA: AWSObject = {
objectType: test,
desc: null as any,
};
if(testA.objectType === 'ec2/vpc')
console.log(testA.desc.a);
}Output
"use strict";
// Using this definition for AWSObject corrects the assignment error, but
// eliminates the type narrowing:
//
// type AWSObject<T extends AWSObjectTypeName = AWSObjectTypeName> = AWSObjectImpl<T, AWSObjectPayloads[T]>;
function foo(test) {
const testA = {
objectType: test,
desc: null,
};
if (testA.objectType === 'ec2/vpc')
console.log(testA.desc.a);
}Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": 2,
"target": "ES2017",
"jsx": "React",
"module": "ESNext"
}
}Playground Link: Provided
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
Activity
RyanCavanaugh commentedon Sep 28, 2020
When the source object doesn't have
anyit's pretty straightforward to demonstrate that this kind of initialization is rarely sound, but we try to make this particular kind of assignment work anyway by enumerating all possible values that the initializer could have, and seeing if that value would be assignable toAWSObject. There's a hardcoded limit here of 24 candidates (more typically hit when you have e.g. 2 x 3 x 3 possible values) for this process to avoid combinatorial explosions.fluggo commentedon Sep 28, 2020
I don't know what you mean by your first sentence. Was there a better way to achieve this same effect?
I can work around this error by casting the test parameter to "any" first, and that's no major loss, but if I'm running into a design limitation, I sure would like a way to do this correctly.
etlovett commentedon Jan 27, 2021
Here's another example of this limit of 25 union entries causing a false type error: #42518 / Playground.
NoriSte commentedon Feb 12, 2021
Here another example: Gist - Playground.
Thanks
elias6 commentedon Mar 7, 2023
This is a bit annoying and confusing. Is there any way to increase or remove this limit?
zaquille-oneil commentedon May 1, 2023
instead of having the typescript error "malfunction", can we at least have it indicate that there is some sort of a limit? We had only found out about this limit by commenting out 1 mapped type at a time until the error message went away.
Pablo1107 commentedon Apr 15, 2024
Ryan Cavanaugh (@RyanCavanaugh) what was the reason this issue got closed in 2021? The project I'm working on it's using TypeScript v5.0.4 (released 2023-04-07) and this error it's still happening.
If there's a technical limitation that makes this issue difficult to resolve, at least there should be a clearer error than the current behaviour is showing.
TypeScript Error
I write here because it's the oldest issue posted with this behavior. But I'm aware of this response.
RyanCavanaugh commentedon Apr 15, 2024
There's no upper limit on how much work could be incurred here; you could have an object with 16 properties each with 16 possible values; do you expect TS to do the full expansion of 18,446,744,073,709,551,616 different possible values? Certainly not. So there has to be an arbitrary limit somewhere; the limit we chose is 24 because that's always going to be reasonably fast.
Pablo1107 commentedon Apr 16, 2024
And what about improving the error message showed when this happens? Is it possible or even reasonable to expect that a better error message should be shown? I imagine something along the lines of:
Txxx: cannot compare more than 24 members of an union of type xRyanCavanaugh commentedon Apr 16, 2024
It's extremely common for this limiter to hit and for nothing undesired to happen as a result. If we knew that the non-assignability only occurred because we hadn't expanded the union, we would have simply not reported that it was non-assignable in the first place.
What you're describing would imply that you would see that message in the majority of cases where the source type was a nontrivial union, even though only a tiny fraction of the time would a full expansion have meant a successful assignment
zaquille-oneil commentedon Apr 17, 2024
Maybe I'm understanding this incorrectly, but I think most commenters in this thread would feel that something like a warning message shown when the union exceeds 25 would be extremely helpful instead of the current error being shown.
Perhaps "Warning: exceeding 24 members can not be accurately compared"? it would be more honest than what's being shown - is this possible, desirable? could this possibly be achieved via a configurable option?
RyanCavanaugh commentedon Apr 17, 2024
I don't think you're understanding it correctly, or have a very different perspective on whether or not TypeScript's error messages have enough noise in them. The only way we could do this would be to issue the error every time the source union was reasonably big, which is an incredibly common occurrence.
In effect, nearly every time you saw an assignability error, it would say "We couldn't expand it because it was too big", and ~99% of the time, expanding the union wouldn't have caused the assignment to succeed anyway.
mitchazj commentedon Apr 18, 2024
is a flag for increasing the limit a little above 25 a possibility? eg, to 30, or 100
this flag would be really helpful for people who
Thinking for example of large teams who have critical code blocked by this limitation, and who need to either buy themselves some time to re-architecture or just swallow a small performance hit (costs of having a huge codebase etc)
Pablo1107 commentedon Apr 22, 2024
Well, in my case I change my code to compare in batches of unions to avoid having one union of more than 25 items. Maybe this is better than increasing the limit performance-wise?
jozefchutka commentedon Jul 16, 2024
After spending hours on detective work on my complex code base, I also ended up here. I just wonder how many dev-hours has been wasted on this
design limitationwhich at the same time isextremely common for this limiter to hit, yet there is no interest to resolve this limitation or even provide reasonable error message.My case is as follows:
I was looking around and try to figure out any workaround whatsoever, just to figure out that such "design is wrong", but no examples of good design, and no workarounds.
The workaround I eventually came up with is something as silly as:
I hope some more people can share their ideas on good design or workaround
Solve TypeScript build error. This was related to the union type of e…
Fix decorations bug, with inline and widget decorations supported in …
br3nt commentedon May 13, 2025
Jozef Chutka (@jozefchutka) just got hit by this at my workplace. At least 4 separate devs scratching their heads until one of us came upon this issue. I think you can add an extra 4 hours to your
# of dev-hours wasted on this design limitation which at the same time is extremely common for this limiter to hit, yet there is no interest to resolve this limitation or even provide reasonable error messagecounter.To be fair, we seem to be overusing this discriminator pattern, and should probably instead switch to a strategy pattern. It's clear that our use case has no actual bounds.... we would be better off switching to an interface.
Regarding "good design", if you are finding yourself in a situation where you are constantly adding to your discriminated unions, this is an indication that your design isn't actually correctly modeled by a discriminated union! Instead, you likely want to create an interface. This allows arbitrary numbers of implementations to be defined.