Skip to content

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

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

Open
5 tasks done
ExE-Boss opened this issue Jul 9, 2020 · 6 comments
Open
5 tasks done

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

ExE-Boss opened this issue Jul 9, 2020 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Jul 9, 2020

Search Terms

  • primitive type
  • primitive type union

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 #29317.

Examples

From DefinitelyTyped/DefinitelyTyped#44805:
/**
 * @see https://tc39.es/ecma262/#sec-createhtml
 */
export function CreateHTML(
	string: object | Exclude<primitive, string>, // unknown & not string
	tag: string,
	attribute: string,
	value?: object | Exclude<primitive, string>, // 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<primitive, number | bigint>, // unknown & not (number | bigint)
	y: object | Exclude<primitive, number | bigint>, // unknown & not (number | bigint)
): boolean;

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Related

@ljharb
Copy link
Contributor

ljharb commented Jul 9, 2020

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
Copy link
Contributor Author

ExE-Boss commented Jul 9, 2020

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

@ljharb
Copy link
Contributor

ljharb commented Jul 9, 2020

aha, good point

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jul 9, 2020

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?

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jul 9, 2020
@ljharb
Copy link
Contributor

ljharb commented Jul 9, 2020

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
Copy link

matthew-dean commented May 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants