Home > Net >  What is the reason for this `Type 'any[]' is not assignable to type '[number]` TypeSc
What is the reason for this `Type 'any[]' is not assignable to type '[number]` TypeSc

Time:12-28

interface Fn {
  (data: Data): void
}

interface Data<Value = any, Deps extends any[] = [...any]> {
  value: Value
  list: Deps
}

function fn(data: Data<string, [number]>): void {
  console.log(data)
}

const foo: Fn[] = [fn]
console.log(foo)

When tested against the TypeScript Compiler this code throws an error because Type 'any[]' is not assignable to type '[number]'.

My intuition is that the defined fn is being assigned to the generic Data value, but the error states that the assignment goes in the other direction. Is this a bug or limitation with the TypeScript compiler? Or how should I think about this to have a better intuition?

CodePudding user response:

Vastly simplified example of the same problem:

type FruitSmasher = (fruit: 'apple' | 'orange' | 'pear') => void
const appleSmasher: FruitSmasher = (apple: 'apple') => undefined
// Type '"apple" | "orange" | "pear"' is not assignable to type '"apple"'.

appleSmasher('orange') // this is bad.

appleSmasher is not assignable to type FruitSmasher because fruit smasher may smash any fruit, but appleSmasher only knows how to smash apples. So you cannot treat appleSmasher like a FruitSmasher.

Playground


It's backwards to your intuition here because Fn here needs to be callable with any[] as the Deps. That means [123], or ['asd', 123] or [] all need to be allowed as the Deps in order to be considered the same type as Fn.

However, fn actually requires Deps be [number], which means that any[] isn't going to going to work.

So according to the type of Fn. This should be valid:

function fn(data: Data<string, [number]>): void {
  console.log(data.list[0].toString()) // this will crash
}

const foo: Fn[] = [fn] // ignore this very valid type error for now

// Valid for type `Fn`, catastrophic for function `fn`.
foo[0]({ value: 123, list: [] })

However fn's Deps are now unsatisfied. data.list[0] would return undefined, then would crash on .toString().

So yes [number] is assignable to any[]. But when the argument to a function is declared as any[], then anything more specific than that will break that since that function can't be guaranteed the type that it requires.

CodePudding user response:

Here's what's going on. You declare your interface for the Data type, saying that the second value of the type is an array that can have any type:

interface Data<Value = any, Deps extends any[] = [...any]> {
  value: Value
  list: Deps
}

Then you go to use that Data type but say that the second value will be an array of numbers:

function fn(data: Data<string, [number]>): void {
  console.log(data)
}

So the type says the array can be anything, but the function declaration says the Data type guarantees the array will only be numbers, because you are passing an object that conforms to the Data interface into something that cannot accept some of the possible forms of that interface.

Remember that typescript really only functions in the IDE and compiler. It ends up being built into JavaScript, an interpreted language with no data types. This means that the function fn will actually not throw an error if you try to pass it an object that has strings in the array once it gets compiled, so the IDE and compiler need to flag it and refuse to accept the structural issue.

  • Related