55751

Why does Typescript infer 'never' instead of intersection type?

<h3>Question</h3>

Given the following example:

<pre class="lang-js prettyprint-override">interface Data { name: string; value: number; } const data :Data = { name: 'name', value: 1, } const updateValue = (key: keyof Data, value: string | number): void => { data[key] = value; };

link to ts-playgound

Typescript shows the following error:

Type 'string | number' is not assignable to type 'string & number'. Type 'string' is not assignable to type 'string & number'. Type 'string' is not assignable to type 'number'.

Which is clear and understandable. However if I add a union type to the interface like so:

<pre class="lang-js prettyprint-override">type MultiType = 'x' | 'y'; interface Data { name: string; value: number; multiType: MultiType | null; } const data :Data = { name: 'name', value: 1, multiType: null, } const updateValue = (key: keyof Data, value: string | number): void => { data[key] = value; };

link to ts-playgound

I get the following error:

Type 'string | number' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'.

Typescript accepts it if I use the intersection type string & number & MultiType but it also accepts never.

This seems inconsistent to me. Is this maybe a bug?


<h3>Answer1:</h3>

string & number is equivalent to never, and so is string & number & (MultiType | null). There are no values which are both a string and a number, so no values satisfy string & number or string & number & AnythingElse.

Right now the latter explicitly reduces to never because it contains unions of these equivalent-to-never types, which is really ugly to leave like that. Specifically, the compiler distributes intersections over unions, so

string & number & ('x' | 'y' | null)

becomes

(string & number & 'x') | (string & number & 'y') | (string & number & null)

That type isn't particularly enlightening to people, so the compiler checks that each of those union constituents is equivalent to never and reduces the type to

never | never | never

which is just

never

as you saw.

<hr />

So why doesn't string & number by itself get immediately reduced to never? Well originally the idea was that it would help people understand where the bug in their code came from, since string is not assignable to number is more enlightening that string is not assignable to never.

Unfortunately while string & number is <em>equivalent</em> to never in terms of what values are assignable to and from it, in TS3.5 and below the compiler doesn't always treat them the same, which is confusing.

Therefore it looks like, starting in TS3.6, empty intersections like string & number will be reduced to never explicitly. Once TS3.6 is released, your above code will act the same in both cases, and you'll get the string | number is not assignable to never error.

<hr />

Okay, hope that helps; good luck!


<h3>Answer2:</h3>

Two things :

<ul><li>

It's only logical that TypeScript doesn't accept string | number as type for your value parameter, because all of the following :

<ul><li>

string | number is not assignable to string because number is not assignable to string in case the key is 'name'

</li> <li>

string | number is not assignable to number because string is not assignable to number in case the key is 'value' (both of there were already true in your first example)

</li> <li>

string | number is not assignable to MultiType | null because both number and string are not assignable to 'x' | 'y' | null in case the key is 'multiType'

</li> </ul></li> </ul>

For some reason however, in your first example TypeScript simply stops on the first "wrong" case and gives you this error (Even though there are really two things wrong already).

In the second case, you could have seen the same error message because the incompatibility of types is still there, it seems like the inference goes a little deeper and tells you that the problem is deeper than this. As to why is the error message formatted like this, I don't really know and I guess it would require going deeper into how are things inferred by the compiler here. In the end, the compiler is right, but the error message could be clearer.

<hr />

For your second question, the never type documentation says :

<blockquote>

The never type is a subtype of, and assignable to, every type

</blockquote>

So that's why you can specify your value to be of type never, and assign it to your data. Because it will <em>never</em> happen.

来源:https://stackoverflow.com/questions/57198077/why-does-typescript-infer-never-instead-of-intersection-type

Recommend

  • SSL/TLS operations failing PHP5.6 - curl-ca-bundle.crt & cacert.pem
  • How does Jnotify works
  • What's the best building tool for java besides ant?
  • Groovy usage of named arguments
  • Binding a callback in Backbone.js and Underscore.js
  • How get a a value from a Lambda expression?
  • How to break numpy array into smaller chunks/batches, then iterate through them
  • How to use GetWindowRect
  • Best way to sum concurrently
  • R: use min() within dplyr::mutate()
  • Can you block a website from being in a browser's history?
  • Nfc Toggle in Android 4.x+?
  • 'self' seems to be hogging one of my arguments
  • How do I use the private key from a PFX certificate stored in Azure Key Vault in .NET Core 2?
  • Android/IOS Secret expiration management with client credentials flow
  • Jquery - convert to work with multiple tabs on one page?
  • How to start the automatically stopped android service?
  • How to get the name of a file downloaded with Angular5
  • Show Context Menu after condition Check in Primeng Angular 4
  • spring-data-redis Jackson serialization
  • Plot KMeans clusters and classification for 1-dimensional data
  • Youtube API Actionscript 3 and Thumbnails
  • How to access a bundled ES6 class in inline
  • Python: Why this error is coming?
  • I am trying to create an app in android to insert data into sql server through a web service.
  • Image insertion from SQL info
  • How to work with Mailgun API in CodeIgniter; Forbidden error in curl_exe()
  • Why does packing not work across sibling unions or structs
  • pass sessionid through jquery ajax call to php
  • Custom Data Generator for Keras LSTM with TimeSeriesGenerator
  • Facebook Error (#200) The user hasn't authorized the application to perform this action (PHP)
  • Ruby on Rails: Get mediaplayer information (iTunes, TRAKTOR, Cog; current song + playlist)
  • VSTS work items list through REST API
  • How to handle div that is created dynamically in a table
  • multiple button click in asp.net MVC 3
  • Sql - ON DUPLICATE KEY UPDATE