2

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?

1 Answer 1

2

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 []"'.
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you :) . I thought about something like foo<T, M extends boolean>(props: M extends true ? PropsSingle<T> : PropsMulti<T>) or something like this. But I couldn't get it to work.
@BenjaminM I don't think that will work unless you specify M and T explicitly, which you probably want to avoid. I have seen similar questions to this several times on SO, I have not seen a better solution unfortunately

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.