TypeScript: Type 'any' is not assignable to type 'never' with boolean in interface in TS 3.5

TypeScript Version: 3.5.1

Search Terms:

  • Type ‘any’ is not assignable to type ‘never’

Code

interface T {
  str: string;
  bool: boolean;
}

const initState: T = {
  str: 'date',
  bool: true,
};

   let k: keyof T;
   initState[k] = 'test' as any;
// ~~~~~~~~~~~~ Type 'any' is not assignable to type 'never'.ts(2322)

This seems to happen specifically with boolean properties. If I get rid of the boolean-valued property from the interface it works fine. If I have a larger interface and select non-boolean properties, it also works fine. But boolean and another type produces this error.

This error happens with TS 3.5 but not 3.4 (see playground link).

Expected behavior:

No error.

Actual behavior:

Type 'any' is not assignable to type 'never'

Playground Link: link

Related Issues:

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 18
  • Comments: 15 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@KeithHenry if you put the as any after the property access, TypeScript will check that k is a valid key. This is more similar to what TS did before 3.5.

(initState[k] as any) = 'test';  // index is checked
(initState as any)[k] = 'test';  // index is not checked

This has caused a very large number of errors for us with insufficient guidance of how to fix (from the release notes)…

Most instances of this error represent potential errors in the relevant code. If you are convinced that you are not dealing with an error, you can use a type assertion instead.

OK, so we have large numbers of a pattern that looks like (from the OP):

initState[k] = v; // where k: keyof T and v: T[k]

And these all fail because we have lots of boolean properties, and so TypeScript thinks the type of initState[k] is never (surely that’s wrong?)

Is the solution:

  • Remove boolean from all interfaces? What should be used instead? Is boolean deprecated now?
  • Some way to explicitly handle when the value of property k is boolean that then gates it out of the initState[k] check?
  • Switch all instances that use keyof to just deal with any instead and lose type checking? Something like…
        (initState as any)[k] = v;

@KeithHenry the gist of the breaking change was that you never had type safety in the assignment to begin with. If you want the old behavior, you can put an as any around the initState[k]:

interface T {
  str: string;
  bool: boolean;
}

const initState: T = {
  str: 'date',
  bool: true,
};

declare let k: keyof T;
initState[k] = 'test';  // Type '"test"' is not assignable to type 'never'.
initState[k] = 'test' as any;  // Type 'any' is not assignable to type 'never'.
(initState[k] as any) = 'test';  // ok

Alternatively, you can construct a narrower type than keyof T which consists of only those keys whose values are assignable to boolean. You can do this quite generally with a conditional type:

interface T {
  str: string;
  bool: boolean;
  bool2: boolean;
  d: Date;
}

type KeysOfType<T, U> = { [k in keyof T]: T[k] extends U ? k : never }[keyof T];

const initState: T = {
  str: 'date',
  bool: true,
  bool2: false,
  d: new Date(),
};

declare let k: KeysOfType<T, boolean>;  // let k: "bool" | "bool2"
initState[k] = 'test';  // Type '"test"' is not assignable to type 'boolean'.
initState[k] = true;  // ok

@Zarepheth

Try this:

type KeysOfType<T, U> = { [k in keyof T]-?: T[k] extends U ? k : never }[keyof T];

(I added a -? in the mapped type to remove the optional-ness of the properties.)