Benjamin M
Benjamin M

Reputation: 24567

TypeScript Conditional Generic Interface as Parameter (Array or Object, based on boolean)

I have a function which should be able to receive two similar types as argument. Depending on if props.multi is true or false.

The problem is that it still allows me to pass value = [] with multi = false. How can I get rid of this?

interface PropsSingle<T> {
    value: T,
    multi: false
}

interface PropsMulti<T> {
    value: T[],
    multi: true
}

function foo<T>(props: PropsSingle<T> | PropsMulti<T>) {
    if(props.multi) {
        let arr = props.value // type detection works fine
    } else {
        let obj = props.value // type detection works fine
    }
}

foo({multi: true, value: []}) // works as expected
foo({multi: true, value: {}}) // throws error as expected
foo({multi: false, value: {}}) // works as expected
foo({multi: false, value: []}) // THIS SHOULD THROW AN ERROR

I already have tried foo<T extends object>(...) and foo<T extends {[key: string]: any}>(...)

The only kinda solution / workaround I found is this:

interface PropsSingle<T> {
    value: T extends any[] ? never : T,
    multi: false
}

But that looks weird to me.

Why can't I restrict the type at function foo?

Upvotes: 2

Views: 2840

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250136

The reason pretty simple, there is nothing preventing T in PropsSingle to be any[]. And there is no simple way to let typescript know that a generic parameter can't extends a given type, we can only specify what T extends.

The solution you have should work, I usually add the constraint on the function and use a string literal type to offer a more sugestive error message when dealing with this kind of scenario:

interface PropsSingle<T> {
    value: T,
    multi: false
}

interface PropsMulti<T> {
    value: T[],
    multi: true
}

type ErrorIf<T, U, TError> = T extends U ? TError : {} 

function foo<T>(props: (PropsSingle<T> & ErrorIf<T, any[], "Argument to PropsSingle can't be []")| PropsMulti<T>) {
    if(props.multi) {
        let arr = props.value // type detection works fine
    } else {
        let obj = props.value // type detection works fine
    }
}

foo({multi: true, value: []}) // works as expected
foo({multi: true, value: {}}) // throws error as expected
foo({multi: false, value: {}}) // works as expected
foo({multi: false, value: []}) // Error Type '{ multi: false; value: undefined[]; }' is not assignable to type '"Argument to PropsSingle can't be []"'.

Upvotes: 2

Related Questions