javascripttypescripttypeerrortsctypechecking

Why doesn't type assertion get rid of "Element implicitly has an 'any' type" in this particular case?


I have this code (it's a bit concocted, but I need it to be this way and with this type structure). Basically I want an array to be sorted this way:

I.e. if there are two elements with the same itemType, then owner is considered and so on. This is just in case, it should not have any direct relation to the tsc error in question.

'use strict';

type Item = {
  itemType: 'itemTypeA' | 'itemTypeB';
  belongsTo: 'ownerA' | 'ownerB';
  value: number;
};

type Argument = {
  items: Item[];
};

const a: Argument = {
  items: [
    {
      itemType: 'itemTypeB',
      belongsTo: 'ownerA',
      value: 0,
    },
    {
      itemType: 'itemTypeA',
      belongsTo: 'ownerB',
      value: 1,
    },
    {
      itemType: 'itemTypeB',
      belongsTo: 'ownerA',
      value: 10,
    },
    {
      itemType: 'itemTypeB',
      belongsTo: 'ownerA',
      value: 0,
    },
  ],
};
console.log(testFunc(a));

function testFunc(arg: Argument): string {
  const sortByData = new Map([
    ['itemType', { itemTypeA: 0, itemTypeB: 1, },],
    ['belongsTo', { ownerA: 0, ownerB: 1, },],
    ['value', null,],
  ]);
  arg.items.sort((a, b) => {
    for (const [propName, sortValues] of sortByData) {
      //// @ts-ignore: failed to make it work with type checks
      const aValue = sortValues ? sortValues[a[propName]] as any as number : a[propName];
      ////// @ts-ignore: failed to make it work with type checks
      //const bValue = sortValues ? sortValues[b[propName]] : b[propName];
      //if (aValue > bValue) {
      //  return 1;
      //} else if (aValue < bValue) {
      //  return -1;
      //}
    }
    return 0;
  });
  console.log('AAA', arg.items);
  return '123';
}

The problem is that this line:

const aValue = sortValues ? sortValues[a[propName]] as any as number : a[propName];
                            ~~~~~~~~~~~~~~~~~~~~~~~

causes this error:

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ itemTypeA: number; itemTypeB: number; ownerA?: never; ownerB?: never; } | { ownerA: number; ownerB: number; itemTypeA?: never; itemTypeB?: …

I know that this (the ~~ underlined) expression is a number. It'd suffice for me just to assert it's a number, but even this didn't work.

So could someone please explain, why the type assertion doesn't work in this case, or, more generally, what is the best way to get rid of the error? (The only one I've found is to just disable the type checking with // @ts-ignore: failed to make it work with type checks)


Solution

  • You had multiple type errors in different locations, so you would need multiple type assertions to resolve them. In something like sortValues[a[propName]], you are relying on propName to be seen as a valid key of a, and a[propName] to be seen as a valid key of sortValues. Neither of these are true for the code you've written. Going into why that's the case is out of scope here, but if you want to resolve via type assertion, you'll need to assert each one of those:

    const aValue = sortValues ?
        sortValues[a[propName as keyof Item] as keyof typeof sortValues] as number :
        a[propName as keyof Item];
    

    If you're just trying to resolve errors by turning off type checking you can reduce the number of assertions by making use of the any type:

    arg.items.sort((a: any, b) => {
        for (const [propName, sortValues] of sortByData as Map<string, any>) {
            const aValue = sortValues ? sortValues[a[propName]] : a[propName];
        }
        return 0;
    });
    

    You can use the //@ts-ignore comment directive to suppress errors, but I would never recommend this unless you've exhausted all other options. Doing so doesn't turn off type checking; it just disables displaying the error. And while an error might be displayed in some line of your code, the underlying problem might not be confined to that line... so you could potentially see strange problems in other places in your code. In other words, compiler errors are often indicative of some actual problem, and that actual problem would still be there if you //@ts-ignore it. For something as trivial as a type assertion there probably wouldn't be such non-local effects, but for something as trivial as a type assertion you should just use the type assertion in the first place. As it says in the documentation, "we recommend you use these comments very sparingly."

    Playground link to code