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

NonNullable<null> is null without strict null checks #50519

Closed
dummdidumm opened this issue Aug 29, 2022 · 10 comments · Fixed by #50553
Closed

NonNullable<null> is null without strict null checks #50519

dummdidumm opened this issue Aug 29, 2022 · 10 comments · Fixed by #50553
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@dummdidumm
Copy link

Bug Report

NonNullable<null> was never in all cases previously, since 4.8 it's null when strictNullChecks: false. From reading the PR that changed the implementation of NonNullable I can't really tell if this is now expected or an oversight. If it's expected, maybe the blog post could be updated because I think this is a significant difference in behavior.

🔎 Search Terms

NonNullable never null

🕗 Version & Regression Information

  • This changed between versions 4.7 and 4.8

⏯ Playground Link

Playground link with relevant code (same behavior for 4.7, but the implementation of NonNullable was different there)

💻 Code

type NonNullableNew<T> = T & {};
type NonNullableOld<T> = T extends null | undefined ? never : T;

type IsNullWithoutStrictNullChecks = NonNullableNew<null>;
type IsAlwaysNever = NonNullableOld<null>;

🙁 Actual behavior

NonNullable<null> returns null with strictNullChecks: false

🙂 Expected behavior

NonNullable<null> always returns never

@RyanCavanaugh RyanCavanaugh added the Docs The issue relates to how you learn TypeScript label Aug 29, 2022
@DanielRosenwasser
Copy link
Member

To be honest, I think that the behavior outside of strictNullChecks wasn't well-considered here - but in part because it's hard for us to imagine how this caused a meaningful change in type-checking behavior. Can you explain how this broke user code? sveltejs/kit#6387 doesn't give a lot of detail/context.

@fatcerberus
Copy link

fatcerberus commented Aug 29, 2022

@DanielRosenwasser I was skeptical about this at first too, but notably, null is not assignable to never — even under strictNullChecks: false — but is assignable to NonNullable<null> with strictNullChecks off (in 4.8). I could imagine that hiding some errors that would otherwise be caught.

@DanielRosenwasser
Copy link
Member

Well... it probably should be the case that null and undefined are assignable to never outside of strictNullChecks.

@DanielRosenwasser
Copy link
Member

I'm going to regret that comment I bet.

@fatcerberus
Copy link

fatcerberus commented Aug 29, 2022

Regardless of whether anyone feels null should be assignable to never or not, it currently isn't:
https://www.typescriptlang.org/play?strictNullChecks=false&ts=4.8.0-beta#code/DYUwLgBAHgXBB2IBuIBOEC8CCuxgG4AoUSATzkRXS23gBMQAzAS0TqJIgC84A5Ae3i9cwAIYAjUAB54IgHyYceIkA

@dummdidumm
Copy link
Author

dummdidumm commented Aug 30, 2022

Can you explain how this broke user code?

It broke because we use NonNullable in combination with a helper type to either resolve to type X or Y that looks something like this:

export type EnsureDefined<T> = NonNullable<T> extends never ? {} : T;

We need this because {} & null === null, but in our situation we have some types that should be merged and null should not make the whole object go null, instead it should be like a noop. Therefore we do something like EnsureDefined<X> & EnsureDefined<Y>. This worked with 4.7 and broke with 4.8. It's not tragic because we can adjust this easily but there might be other helper types out there relying on this behavior being consistent whether or not strict null checks are enabled.

dummdidumm added a commit to sveltejs/kit that referenced this issue Aug 30, 2022
Fixes a breaking change introduced by TS: NonNullable<null> is no longer never when strict null checks are false: microsoft/TypeScript#50519
dummdidumm added a commit to sveltejs/kit that referenced this issue Aug 30, 2022
Fixes a breaking change introduced by TS: NonNullable<null> is no longer never when strict null checks are false: microsoft/TypeScript#50519
@ahejlsberg ahejlsberg added Bug A bug in TypeScript and removed Docs The issue relates to how you learn TypeScript labels Aug 30, 2022
@ahejlsberg ahejlsberg added this to the TypeScript 4.9.0 milestone Aug 30, 2022
@ahejlsberg
Copy link
Member

NonNullable<null> and NonNullable<undefined> should resolve to never as they used to. Which of course implies that null & {} and undefined & {} should resolve to never.

@fatcerberus
Copy link

fatcerberus commented Aug 31, 2022

By the rules of assignability, shouldn’t null & {} be null without strictNullChecks? NonNullable<null> should definitely be never but the intersection case seems less clear-cut.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 31, 2022

It is all pretty hard to reason about, and I mixed it up a few times - but null & {} needs to be null since that is the type that is compatible with both constituents. {} is assignable to nothing else, whereas null and undefined are assignable to everything else (meaning they're the bottom types and are sort of equivalent to never).

@ahejlsberg
Copy link
Member

In 4.8 NonNullable<T> is defined as T & {}, so there's no distinction between the two. In non-strictNullChecks mode, the set of possible values for null is null or undefined, and the set of possible values for {} is anything, including null and undefined. The intersection of these sets is null and undefined, which argues that null & {} should be null. However, the pre-4.8 definition of NonNullable<T> as a conditional type produced never for null and undefined, so in this PR we're aligning with that behavior and producing never for null & {}. I think that's perfectly fine, particularly since the non-strictNullChecks behavior isn't really reflecting reality anyway.

mattlehrer pushed a commit to mattlehrer/adapter-cloudflare-node-compat that referenced this issue Oct 31, 2023
Fixes a breaking change introduced by TS: NonNullable<null> is no longer never when strict null checks are false: microsoft/TypeScript#50519
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants