Repository navigation
Infer generic types from property valueΒ #46329
Description
Suggestion
π Search Terms
Infer generics from property enum
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
Consider the following example
interface OptsMap {
numString: {
num: Number;
boolOrString: String;
},
boolBool: {
bool: Boolean;
boolOrString: Boolean;
},
}
interface OptsContainer<T extends keyof OptsMap> {
type: T;
opts: OptsMap[T];
}
type OptsList = OptsContainer<keyof OptsMap>[];I would then like to be able to have intellisense show only the possible values based on the first type specified:

Rather than showing bool, boolOrString and num, it should only show bool and boolOrString.
Furthermore, boolOrString should be of type bool rather than bool | string.
Also, when parsing an array like this, it should infer the type of entries by their type value. I.e. the following should not error:
const x : OptsList = [];
for (const entry of x) {
if (entry.type == "boolBool") {
console.log(entry.opts.bool);
// ^^^^
// Property 'bool' does not exist on type '{ num: number; boolOrString: string; }'
}
}π Motivating Example
π» Use Cases
I have an api that quickly generates a list of user interfaces which looks a little something like this:
createGuiList([
{
type: "num",
guiOpts: {
min: 10,
max: 50,
},
}, {
type: "string",
guiOpts: {
maxLength: 100,
multiLine: true,
},
}
]);It would be nice to not have to provide a type for guiOpts every time it's used. I'm using jsdoc and currently that looks like this:
createGuiList([
{
type: "num",
/** @type {import("../../../GuiManager.js").GuiOpts} */
guiOpts: {
min: 10,
max: 50,
},
}, {
type: "string",
/** @type {import("../../../GuiManager.js").GuiOpts} */
guiOpts: {
maxLength: 100,
multiLine: true,
},
}
]);
Activity
andrewbranch commentedon Nov 2, 2021
The type
OptsListdoes not correlate itstypeandoptsproperties to a single constituent when instantiated with a union (thatβs not possible). Youβve instantiatedOptsContainerwith a union type (keyof OptsMap), and sotypeis allowed to be either"numString"or"boolBool", andoptscan be either{num: Number; boolOrString: String}or{bool: Boolean; boolOrString: Boolean;}. They donβt have to match up. This is the type system working exactly as itβs supposed to.andrewbranch commentedon Nov 2, 2021
Try distributing over the union instead: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgPIAcwGcCyd3IDeAUMmciAK4C2AymFKAOYBcRp5nV1bAcjQCNoAbg6cyAgPaSANqij1GIVskXNRnAL4AaMcimyAQtJlsS48gdPJjsiHBAaL+k-LXK2tmfcd6dxTWJiMABPdBQMbABhSXA4UGgAHgAVZAgAD0gQABMsZABrCBDJGDRMXHwAPmQAXmRUjKzc5EoQfJBJAHcQZAB+dk5Q8LZkpzJJcrZIivQAbWSAXVFNZDYQCAA3ESChiPKAGWAsMFqy6NiwePWoRMLi0um8dErZpaCEWOPkdKmDo5O6rNzORdmwAERWLxg3ScCbYMx6Lg0NgAZhhzisbgYzHBYL8AQWQA
jespertheend commentedon Nov 2, 2021
Huh, not sure how this happened but I somehow managed to unknowingly duplicate my own issue (#46552). Sorry about that.
Either way, thanks for the extra explanation! This helps a lot :)