Typescript: [Bug] Union type compilation failed in Array.map()

Created on 11 Feb 2019  路  5Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.3.3


Search Terms:
Array.map, union type
Code

// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.

interface fruit_t {
    name: string
    color: string
}

interface result_t {
    category: 'fruit' | 'vegetable' | 'animal'   // If you change this union type into `string`, it will pass the compilation.
    name: string
}

const fruits: fruit_t[] = [
    { name: 'Apple', color: 'red' },
    { name: 'Banana', color: 'yellow' },
    { name: 'Citrus', color: 'orange' },    
]

const results: result_t[] = fruits.map(x => ({ category: 'fruit', name: x.name }))
console.log(results)

Expected behavior:

Has no error.

Actual behavior:

  Type '{ category: string; name: string; }' is not assignable to type 'result_t'.
    Types of property 'category' are incompatible.
      Type 'string' is not assignable to type '"fruit" | "vegetable" | "animal"'.

17 const results: result_t[] = fruits.map(x => ({ category: 'fruit', name: x.name }))
         ~~~~~~~

Playground Link:

https://www.typescriptlang.org/play/index.html#src=interface%20fruit_t%20%7B%0D%0A%20%20%20%20name%3A%20string%0D%0A%20%20%20%20color%3A%20string%0D%0A%7D%0D%0A%0D%0Ainterface%20result_t%20%7B%0D%0A%20%20%20%20category%3A%20'fruit'%20%7C%20'vegetable'%20%7C%20'animal'%20%20%2F%2F%20If%20you%20change%20this%20union%20type%20into%20%60string%60%2C%20it%20will%20pass%20the%20compilation.%0D%0A%20%20%20%20name%3A%20string%0D%0A%7D%0D%0A%0D%0Aconst%20fruits%3A%20fruit_t%5B%5D%20%3D%20%5B%0D%0A%20%20%20%20%7B%20name%3A%20'Apple'%2C%20color%3A%20'red'%20%7D%2C%0D%0A%20%20%20%20%7B%20name%3A%20'Banana'%2C%20color%3A%20'yellow'%20%7D%2C%0D%0A%20%20%20%20%7B%20name%3A%20'Citrus'%2C%20color%3A%20'orange'%20%7D%2C%20%20%20%20%0D%0A%5D%0D%0A%0D%0Aconst%20results%3A%20result_t%5B%5D%20%3D%20fruits.map(x%20%3D%3E%20(%7B%20category%3A%20'fruit'%2C%20name%3A%20x.name%20%7D))%0D%0Aconsole.log(results)

Related Issues:

Duplicate

Most helpful comment

Duplicate of #11152. As @jack-williams says above, this is fixed in 3.4 by #29478 (and no need for as const in 3.4).

All 5 comments

Maybe for a temporary workaround you can do

const results: result_t[] = fruits.map(x => ({
  category: 'fruit' as 'fruit',
  name: x.name
}))

or

const results: result_t[] = fruits.map(function(x): result_t  {
  return {
    category: 'fruit',
    name: x.name
  }
})

or

const results: result_t[] = fruits.map(x => {
    const r: result_t = { category: 'fruit', name: x.name }
    return r
})

I would prefer using the 2nd and 3rd option as you don't use the as which is almost equivalent to overriding Typescript type inference.

It's not really a bug more of a design limitation. The arrow function is typed without any relation to the expected result, so there is no reason to keep the literal type 'fruit' it is widened to string. Then the map call is typed and the result is Array<{ category : string, name: string }> which is not assignable to results.

Beside the options mentioned by @azizhk, I would also mention the syntax to specify the return type of an arrow function which would be the most succint in this case I believe.:

const results: result_t[] = fruits.map((x):result_t => ({ category: 'fruit', name: x.name }))

In the upcoming [email protected] you can use the following to prevent widening:

const results: result_t[] = fruits.map(x => ({ category: 'fruit', name: x.name } as const))

This is fixed in 3.4 by #29478.

Duplicate of #11152. As @jack-williams says above, this is fixed in 3.4 by #29478 (and no need for as const in 3.4).

Was this page helpful?
0 / 5 - 0 ratings