Closed
Description
I've run into the following type of inferencing problem several times now:
// These are database tables.
type Table = "a" | "b" | "c"
// These are the types of records in the database.
type TableToRecord = {
a: { a: number }
b: { b: string }
c: { c: string[] }
}
// This object tells you a record with its table.
type RecordWithTable<T extends Table> = {
table: T,
record: TableToRecord[T]
}
// I might want to have a function to process any record in my database.
// However, there's a type error that doesn't let you narrow the generic type
// argument.
function processRecord<T extends Table>(recordWithTable: RecordWithTable<T>) {
if (recordWithTable.table === "a") {
const x = recordWithTable.record.a // error
}
}
Not entirely sure why Typescript can't handle this case... I assume someone is going to say it has to do with type widening or something. Regardless, we can solve this problem if we transform the "generic of a union type" to a "union of a generic type". For example, the following will type narrow properly.
function processRecord2(
recordWithTable:
| RecordWithTable<"a">
| RecordWithTable<"b">
| RecordWithTable<"c">
) {
if (recordWithTable.table === "a") {
const x = recordWithTable.record.a // no error
}
}
Suggestion
What would be really cool is if there was a baked in Typescript type that let you make this transformation. Maybe something like this:
type RecordWithTableUnion = MapUnion<Table, RecordWithTable>
// Equivalent to explicitly writing it out.
type RecordWithTableUnion =
| RecordWithTable<"a">
| RecordWithTable<"b">
| RecordWithTable<"c">
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. new expression-level syntax)