Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Union type compilation failed in Array.map() #29850

Closed
kuanyui opened this issue Feb 11, 2019 · 5 comments
Closed

[Bug] Union type compilation failed in Array.map() #29850

kuanyui opened this issue Feb 11, 2019 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@kuanyui
Copy link

kuanyui commented Feb 11, 2019

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:

@azizhk
Copy link
Contributor

azizhk commented Feb 11, 2019

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.

@dragomirtitian
Copy link
Contributor

dragomirtitian commented Feb 11, 2019

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 }))

@ajafff
Copy link
Contributor

ajafff commented Feb 11, 2019

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))

@jack-williams
Copy link
Collaborator

This is fixed in 3.4 by #29478.

@ahejlsberg
Copy link
Member

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).

@ahejlsberg ahejlsberg added the Duplicate An existing issue was already created label Feb 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

6 participants