Skip to content

null extends object inconsistent between ts and d.ts #33174

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

Closed
remcohaszing opened this issue Aug 31, 2019 · 11 comments
Closed

null extends object inconsistent between ts and d.ts #33174

remcohaszing opened this issue Aug 31, 2019 · 11 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@remcohaszing
Copy link

TypeScript Version: 3.5.3

Search Terms: null extends object

Code

export type SchrodingersCat = null extends object ? 'alive' : 'dead';

Expected behavior:

SchrodingersCat is either 'dead' or 'alive'.

Actual behavior:

If the code is in a file named box.ts, SchrodingersCat is 'dead'. If the code is in a file named box.d.ts, SchrodingersCat is 'alive'.

Playground Link: N/A

Related Issues: N/A

@MartinJohns
Copy link
Contributor

I can not reproduce this behavior with TypeScript 3.5.3 or 3.6.2. It makes no difference whether it's in a .ts file or a .d.ts file. But it does make a difference whether you have strictNullChecks enabled or not.

Can you make sure that strictNullChecks is enabled in both cases?

@remcohaszing
Copy link
Author

The behaviour as described by @MartinJohns is correct.

Toggling strictNullChecks in the tsconfig.json compilerOptions changes the value of null extends object for .ts files, but for .d.ts files null always extends object.

@sandersn sandersn added the Bug A bug in TypeScript label Sep 3, 2019
@sandersn
Copy link
Member

sandersn commented Sep 3, 2019

I could only repro this at first, but it turned out that my tsconfig with strictNullChecks on only included my .ts file. It didn't include my d.ts file, which caused the type to be different. Can you check to see if that's what is happening? Once I got both files covered by the same tsconfig, they started behaving the same.

@sandersn sandersn added this to the Backlog milestone Sep 3, 2019
@remcohaszing
Copy link
Author

remcohaszing commented Sep 18, 2019

box.ts

In the TS file, SchrodingersCat is always 'dead'.

export type SchrodingersCat = null extends object ? 'alive' : 'dead';

box.d.ts

In a DTS file which has a matching TS file, SchrodingersCat is always 'alive'.

export type SchrodingersCat = null extends object ? 'alive' : 'dead';

box2.d.ts

In a DTS file which has no matching TS file, SchrodingersCat is always 'dead'.

Creating an empty file named box2.ts, turns SchrodingersCat to 'alive'.

export type SchrodingersCat = null extends object ? 'alive' : 'dead';

package.json

{
  "dependencies": {
    "typescript": "3.6.3"
  }
}

tsconfig.json

Setting "strictNullChecks" to false, turns SchrodingersCat to 'alive' everywhere.

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

issue.zip

@sandersn
Copy link
Member

I'm pretty sure that's because when you don't explicitly list files in your tsconfig, and you have box.ts and box.d.ts, only box.ts is part of the compilation.

@sheetalkamat can you confirm?

@sheetalkamat
Copy link
Member

@sandersn yes. .ts file takes precedence over .d.ts

c:\temp\test4>dir
 Volume in drive C is OSDisk
 Volume Serial Number is F41A-DBC9

 Directory of c:\temp\test4

09/25/2019  09:24 AM    <DIR>          .
09/25/2019  09:24 AM    <DIR>          ..
09/25/2019  09:23 AM                69 box.d.ts
09/25/2019  09:23 AM                69 box.ts
09/25/2019  09:24 AM                69 box2.d.ts
09/25/2019  09:23 AM                51 tsconfig.json
               4 File(s)            258 bytes
               2 Dir(s)  798,106,771,456 bytes free

c:\temp\test4>node c:\TypeScript\built\local\tsc.js --listFiles
c:/TypeScript/built/local/lib.d.ts
c:/TypeScript/built/local/lib.es5.d.ts
c:/TypeScript/built/local/lib.dom.d.ts
c:/TypeScript/built/local/lib.webworker.importscripts.d.ts
c:/TypeScript/built/local/lib.scripthost.d.ts
c:/temp/test4/box.ts
c:/temp/test4/box2.d.ts

@ljharb
Copy link
Contributor

ljharb commented Aug 26, 2023

um, shouldn’t it be dead everywhere because null doesn’t extend object?

@remcohaszing
Copy link
Author

Despite my love for cats, I agree logically this one should be 'dead'.

However, most of the ecosystem is non-strict. By this I mean all type definition files published to npm. This means it could be a massive breaking change. Making the cat always 'alive' would only affect unpublished source code where strictNullChecks is enabled. DefinitelyTyped is probably a good test case to see the real impact of changing such behaviour.

This could also affect the decision to eventually enable strictNullChecks by default per #54500 (comment).

@sandersn
Copy link
Member

@ljharb null: any when strictNullChecks is off; it's only null: null with it on. So the type system doesn't know that null is actually null and gives wrong answers here. Advanced types often don't work as expected with strictNullChecks off.

@remcohaszing I checked this again and the difference in .d.ts files is also whether strictNullChecks is true or false, not whether it's in a .d.ts file or not. I'm going to close this bug.

@ljharb
Copy link
Contributor

ljharb commented Aug 28, 2023

@sandersn ok, but when strictNullChecks is on, null extends object should be false, right?

@sandersn
Copy link
Member

Yep, it's 100% "dead" with strictNullChecks on, because null and undefined are not subtypes of object (they are siblings with {} right at the top).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants