2

When placing a type guard for arg1 I'd expect arg2 to be inferred automatically, but it doesn't.

Anyone an idea why this isn't working? what's the best alternative? Thanks so much!

You can see the types in action here. Simply hover over arg2 to see the inferred type.

function example<T extends 'foo' | 'bar'>(
  arg1: T, 
  arg2: T extends 'foo' ? string : number
) {
  if (arg1 === 'foo') {
    arg2 // should be string?
  }

  if (arg1 === 'bar') {
    arg2 // should be number?
  }
}

However, invoking the function does correctly apply the second arg type:

enter image description here

enter image description here

Here's another example using a typed object based on generics:

type Test <T extends boolean> = {
  multiple: T;
  value: T extends true ? string : number;
};

// this doesn't work?
function test <T extends boolean>(a: Test<T>) {
  if (a.multiple) {
    a.value // string | number?
  }else{
    a.value // string | number?
  }
}

// this works!
function test2 (a: Test<true> | Test<false>) {
  if (a.multiple) {
    a.value // is string
  }else{
    a.value // is number
  }
}

See playground

2 Answers 2

2
+250

I use these 2 solutions to have conditional arguments:

Solution 1 (recommended): Use object for arguments

type Args = { arg1: 'foo'; arg2: string } | { arg1: 'bar'; arg2: number };
function example(args: Args): void {
  if (args.arg1 === 'foo') {
    args.arg2; // It's string
  }
  if (args.arg1 === 'bar') {
    args.arg2; // It's number
  }
}

Solution 2: Use arguments object

type Args = ['foo', string] | ['bar', number];
function example(...args: Args): void {
  if (args[0] === 'foo') {
    args[1]; // It's string
  }
  if (args[0] === 'bar') {
    args[1]; // It's number
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your help! Union types seems to work fine (object or tuple), but it doesn’t seem to work with generics. Conclusion: what I try to achieve seems to be impossible. Hopefully this will be supported in a future typescript release. Thanks!
Although my question hasn't really been answered, I find this answer the most clear
1

Please consider this snippet:

const k = 'foo' as 'foo' | 'bar'

example(k, 'abc') // compiles ok
example(k, 1) // compiles ok

arg2 is not tightly coupled with arg1. Still when you call example with a literal string: 'foo' or 'bar' as arg1 Typescript can restrict type of arg1 and infer exact type of arg2. But in general case this is not possible.

As a possible fix you could restrict args of example, like that:

function example2(...args: ['foo', string] | ['bar', number]) {
  if (args[0] === 'foo') {
    const s = args[1] // string
  }

  if (args[0] === 'bar') {
    const n = args[1] // number
  }
}
example2('foo', '') // ok
example2('bar', 0) // ok
example('foo', 0) // error
example2(k, 'abc') // error
example2(k, 1) // error

Playground

Situation with type Test<T extends boolean> is very similar. The definition of type does not restrict strictly enough possible values. For example:

const v1: Test<boolean> = {
  multiple: true,
  value: 'a'
}

const v2: Test<boolean> = {
  multiple: true,
  value: 1
}

Using Test<true> | Test<false> makes use of literal type and restricts set of possible values.

Playground

3 Comments

Thanks @artur-grzesiak! I'm aware that it works with union types, however this not really answers the question why generics aren't working as expected. Honestly I'm baffled that it doesn't work with generics in the actual function implementation. There should be enough information for TS to know the value (in type guards).
@codemirror Hmm, arg1 might be of type 'foo' or 'foo' | 'bar'. Only in case of 'foo' it restricts type of arg2 to string. As I showed, it is possible to call your example function with 'foo' and a number, so when you check for arg1 === 'foo' you cannot be sure arg2 is a string.
Thanks! So type guard based on a generic (inferred) value seems to be impossible, only union types will work (like in your answer). Honestly, it’s still not clear to me “why” this isn’t possible, but at least I now know the only way to solve it is with union types (or with function overloads).

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.