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

Proposal for proper top type #23838

Closed
SimonMeskens opened this issue May 2, 2018 · 9 comments
Closed

Proposal for proper top type #23838

SimonMeskens opened this issue May 2, 2018 · 9 comments
Labels
Duplicate An existing issue was already created

Comments

@SimonMeskens
Copy link

SimonMeskens commented May 2, 2018

Right now there are three interesting types for unknown values:

never can be assigned to anything, but nothing can be assigned to it
any can be assigned to anything and anything can be assigned to it
unknown can't be assigned to anything, but everything can be assigned to it

In type theory terms, never is a bottom, unknown is a top and any is something you don't usually see, but is really handy for a language like TS.

Here are the properties for never and any, aside from the abovementioned ones:

(A ranges over all types, including null and undefined, but excluding never, any, {} and unknown itself)

A | never = A
A & never = never
never extends A ? true : false = true

A | any = any
A & any = any
any extends A ? true : false = true | false

Now, I've been using type unknown = {} | null | undefined as my top, like most people. It works mostly fine for assignment, but it fails when it comes to unions, intersections and conditionals. For a proper top type, I need the following properties:

A | unknown = unknown
A & unknown = A
unknown extends A ? true : false = false

Can we get a type in the language called unknown that fits these properties? Another name that might work would be all, but I think that's confusing, as the properties it has would confuse people (it's not immediately clear why A & all = A in that case.

It's become clear to me as of late, I shouldn't use any in my code at all, except when I'm absolutely sure I want to turn off type checking. The problem is, a lot of the cases where I use any, it's because I need a top type. Especially intersections are hard to deal with, without a top type.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented May 2, 2018

Difference between this and #10715 ?

@SimonMeskens
Copy link
Author

SimonMeskens commented May 2, 2018

I'll have a read through, cursory scanning seems to indicate it's a similar concept, but not quite the same. My unknown is exactly the same as {}, but with the following properties:

  • isn't separate from defined and null, even under strict null rules
  • properly erases under intersection
  • properly absorbs under union

Here's an example of why {} doesn't behave correctly under these rules either:

type Test<T> = T extends infer U
    ? U extends number ? true : false
    : never;

type ThisShouldBeFalse = Test<number | {}>; // But it's 'boolean' instead

I've run into this peculiarity more than once and it takes quite a bit of digging to figure out the distributive properties of conditionals interact with a union of {} somewhere. Moreover, if we have this proper top type, we could also use it when infer fails. Currently it returns {}, which is how these weird unions tend to happen in the first place.

@SimonMeskens
Copy link
Author

Okay, after reading through the other proposal, it seems like it's a case of and-and. The other proposal assumes something top-like exists and then specifies how it acts under flow-checking. In other words, this proposal makes the other one possible.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels May 2, 2018
@jack-williams
Copy link
Collaborator

In your examples does A range over all types? I'm not sure I follow the property:

  • unknown extends A ? true : false = false

what if A = unknown? I would expect that to return true.

@SimonMeskens
Copy link
Author

@jack-williams

A ranges over all types, including null and undefined, but excluding never, any, {} and unknown itself.

For the excluded cases and how they interact with each other, it's not always clear. In some cases it is, but I refer you to things like any extends never ? true : false; (try guessing what the result of that one is!).

The specific case you mention is simple, because it has to match what {} does. It's true like you suspected.

@jack-williams
Copy link
Collaborator

jack-williams commented May 3, 2018

Ah ok, under that definition of A then it makes sense.

Does Ryan's comment here not include all the properties you want for unknown?

For the property unknown extends A ? true : false = false, to what extent are your current issues caused by conditional type distribution?

@SimonMeskens
Copy link
Author

For the property unknown extends A ? true : false = false, to what extent are your current issues caused by conditional type distribution?

To a very small extent. I'm currently using type unknown = {} | null | undefined, which just plain doesn't act correctly under either union, intersection or conditional, even without type distribution. When it comes to just using type unknown = {}, obviously the first issue is that it excludes null and undefined, which causes a huge mess all over the place. I've been trying to fix one such case today, where I've just spent 5 hours fiddling with extremely arcane errors, because I have to manually figure out at each call site whether to include or exclude null | undefined in my mapped types, etc.

Excluding the null issue, {} will act fine, except under type distribution. The real issue is the null/undefined cases, although I personally think {}'s interaction under union/intersection is broken too and type distribution just causes that issue to pop up in remote areas of your codebase unexpectedly.

@SimonMeskens
Copy link
Author

It seems like Ryan's comment does indeed include all properties. It's not in the proposal and it's very far down the page, so I didn't see that post yet.

@SimonMeskens
Copy link
Author

I'll summarize my thoughts, cross-post them to the other issue and close this one. If anyone disagrees, I'll reopen this one.

@RyanCavanaugh RyanCavanaugh added Duplicate An existing issue was already created and removed In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels May 3, 2018
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants