I've got a function with a generic that is a tuple of 1 or a tuple of 2 elements.
I want to ensure that all properties used in the function use the same length tuple.
type TypeA = [string] // Tuple of 1 element
type TypeB = [string, string] // Tuple of 2 elements
type Header = TypeA | TypeB
interface SomeObject<H extends Header> {
prop1: H
prop2: H
}
function useHeader<H extends Header>(someObject:SomeObject<H>) {
// do something
}
useHeader({
prop1: ["tuple of 1 element"],
prop2: [
"tuple of",
"2 elements"
] // <-- I want an error here, because prop1 and prop2 use diffrent tuples
})
I noticed that when I change TypeA
to number
and TypeB
to string
, then Typescript gives an error when I mix numbers and strings.
Is it possible to make Typescript generate an error when tuples of different length are used?
CodePudding user response:
Consider this approach:
type TypeA = [string] // Tuple of 1 element
type TypeB = [string, string] // Tuple of 2 elements
type Header = TypeA | TypeB
interface SomeObject<H extends Header> {
prop1: H
prop2: H & {} // <------- CHANGE IS HERE
}
function useHeader<H extends Header>(someObject: SomeObject<H>) {
// do something
}
useHeader({
prop1: ["tuple of 1 element"],
prop2: [
"tuple of",
"2 elements"
] // <-- I want an error here, because prop1 and prop2 use diffrent tuples
})
This line H & {}
means "make inference priority lower". In other words, TS will infer first H
for prop1
and only then second prop2
and not simultaneously.
While this is not documented feature, Ryan Cavanaugh (development lead of the TypeScript team) says here that it's "...by design..." (w/emphasis) and "...probably going to work for the foreseeable future."
More undocumented features you can find in my blog. This particular hack was provided in this answer
WIhout undocumented tricks:
type TypeA = [string] // Tuple of 1 element
type TypeB = [string, string] // Tuple of 2 elements
type Header = TypeA | TypeB
type IsLengthEqual<T extends any[], U extends any[]> = U extends { length: T['length'] } ? U : never
interface SomeObject<H extends Header, H2 extends Header> {
prop1: H
prop2: IsLengthEqual<H, H2>
}
function useHeader<H extends Header, H2 extends Header>(someObject: SomeObject<H, H2>) {
// do something
}
useHeader({
prop1: ["tuple of 1 element"],
prop2: [
"tuple of",
'dfg'
] // error
})
And with only one generic
type TypeA = [string] // Tuple of 1 element
type TypeB = [string, string] // Tuple of 2 elements
type Header = TypeA | TypeB
interface SomeObject<H extends Header> {
prop1: H
prop2: { [Prop in keyof H]: string }
}
function useHeader<H extends Header>(someObject: SomeObject<[...H]>) {
// do something
}
useHeader({
prop1: ["tuple of 1 element"],
prop2: [
"tuple of",
'dfg'
] // error
})