microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.26k stars 12.31k forks source link

Consider adding `primitive` as a union of all primitive types #39519

Open ExE-Boss opened 4 years ago

ExE-Boss commented 4 years ago

Search Terms

Suggestion

primitive would be a union of all primitive types, e.g.:

type primitive = string | number | bigint | boolean | symbol | null | undefined; // unknown & not object

The advantage of doing this as a TypeScript built‑in rather than defining it in user space is that when a new primitive type gets added to ECMAScript (e.g.: bigdecimal), it will be supported automatically without needing to update the type definitions of the library.

Use Cases

When describing an API that takes all‑but‑one primitive type, it’s useful to be able to do Exclude<primitive, symbol> or Exclude<primitive, number | bigint> without needing https://github.com/microsoft/TypeScript/pull/29317.

Examples

From DefinitelyTyped/DefinitelyTyped#44805:
```ts /** * @see https://tc39.es/ecma262/#sec-createhtml */ export function CreateHTML( string: object | Exclude, // unknown & not string tag: string, attribute: string, value?: object | Exclude, // unknown & not string ): string; /** * @throws {TypeError} If `x` or `y` is a `number` or `bigint` or they're different types. * @see https://tc39.es/ecma262/#sec-samevaluenonnumeric */ export function SameValueNonNumeric( x: object | Exclude, // unknown & not (number | bigint) y: object | Exclude, // unknown & not (number | bigint) ): boolean; ```

Checklist

My suggestion meets these guidelines:

Related

ljharb commented 4 years ago

Given that unknown could be defined as primitive | object, and primitive could be defined as null | undefined | {}, this seems like a pretty obvious addition.

ExE-Boss commented 4 years ago

{} includes object, so you’d want null | undefined | ({} & not object)

ljharb commented 4 years ago

aha, good point

RyanCavanaugh commented 4 years ago

This is obviously coherent but the use case doesn't make sense to me. You support every primitive type except one, yet claim to be able to support any future primitive that appears? How can someone possibly know that the next primitive that gets added doesn't share the same characteristics of the one they don't support?

ljharb commented 4 years ago

For this specific use case that's true - I think primitive is warranted regardless of this use case (typing a function that takes, or returns, a primitive, for example).

Even with object, you can't know that every object will necessarily work; the language has exotic objects that don't conform to what normal objects do.

matthew-dean commented 1 month ago

@RyanCavanaugh

How can someone possibly know that the next primitive that gets added doesn't share the same characteristics of the one they don't support?

The strong arguments for this I would see are:

  1. "Primitive" is a well-defined feature of JavaScript. Whether or not a new type "shares characteristics" is wholly irrelevant, as a type is either a primitive or it isn't, as defined by the language. The characteristic of primitives is that they are immutable values and that is the only characteristic that matters when defining a primitive. This isn't a user-land invention. Having TypeScript identify the type is simply reflecting the behavior / definition of JavaScript.
  2. The use cases are not just in user-land either, as some methods, such as valueOf, require that a primitive and only a primitive be returned. And that type would hold true (presumably) even if JavaScript adds another primitive (such as Tuples and Records). Meaning: the correct output type of Object.prototype.valueOf() is primitive, regardless of whether or not it exists in TypeScript. (It looks like, currently, lib.es5.d.ts, defines valueOf() as returning Object, which I guess is possible, but is ultimately contrary to spec.)
  3. For the two reasons above, many libraries and user-land solutions end up defining a primitive type, including the super-popular type-fest, to fill the gaps left by TypeScript, which is okay, but because "primitive" is an evergreen concept in JavaScript, I agree with the OP that this is most logically and pragmatically a TypeScript feature.