7
votes

To the best of my understanding TypeScript views a const string variable as an immutable typed variable with only that value and no other possible value. I always thought that adding as const to that was redundant.

Why am I getting the following in the 2nd part of the example?

Argument of type 'string' is not assignable to parameter of type...

Example:

declare function each<T extends [any] | any[]>(cases: ReadonlyArray<T>): (name: string, fn: (...args: T) => any, timeout?: number) => void;

const foo1 = 'FOO' as const;
const bar1 = 'BAR' as const;

declare function action1(value: typeof foo1 | typeof bar1): void;

each([
  [foo1],
])('test name', (value) => {
  // okay
  action1(value);
});

const foo2 = 'FOO';
const bar2 = 'BAR';

declare function action2(value: typeof foo2 | typeof bar2): void;

each([
  [foo2],
])('test name', (value) => {
  // Argument of type 'string' is not assignable to parameter of type '"FOO" | "BAR"'.(2345)
  action2(value);
});

Playground sample above is here.

3

3 Answers

1
votes

Don't confuse const as a keyword for declaring immutable variables (same in JS by the way) with as const, which called const assertions.

Const assertions behaves slightly different depending on the type (see attached link to the documentation), but in case of literal types it basically means that it cannot be extended (or widened) to string. It narrows it to that specific literal type, which signals to compiler not to accept string and this is the exact error you are getting.

0
votes

Even if the first part of your question (with as const) does not warn about any error, it still does not work as each method is not compiled to javascript code and therefore the execution fails.

Although your example tries to address an issue with how const behaves during narrowing I think it is too complicated. You probably will find useful the following suggestion issue on typescript project

Support Const Type Constraint

0
votes

TS is able to infer all types. You just should give a little bit of love ))) Just add extra generics to help TS figure out all types. TS is smart enough to figure out that const foo='FOO' is constant even without as const.

declare function each<U extends string, V extends U[], T extends V>(cases: ReadonlyArray<T>): (name: string, fn: (...args: T) => any, timeout?: number) => void;


const foo1 = 'FOO' as const;
const bar1 = 'BAR' as const;

declare function action1(value: typeof foo1 | typeof bar1): void;

each([
  [foo1],
])('test name', (value) => {
  action1(value);
});

const foo2 = 'FOO';
const bar2 = 'BAR';

declare function action2(value: typeof foo2 | typeof bar2): void;

each([
  [foo2],
])('test name', value => action2(value)); // ok

As you see, I added extra U and V generics.

Please keep in mind, if you can't infer some type, try to add more generics )) This rule works 80% of time )

Playground

Why am I getting "Argument of type 'string' is not assignable to parameter of type..." in the second part of the example?

This is because value is infered to string. action2 expects only foo or bar. Because string type is much wider than foo | bar, TS complains