Skip to content

TS2.9 narrows {}|undefined to never after string checks (regression from 2.8) #25179

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
mprobst opened this issue Jun 24, 2018 · 7 comments
Closed
Assignees
Labels
Bug A bug in TypeScript

Comments

@mprobst
Copy link
Contributor

mprobst commented Jun 24, 2018

TypeScript Version: 2.9.2

Search Terms: string never undefined jQuery

Code

declare const childSelector: {}|undefined;
declare const elem: HTMLElement;
if (typeof childSelector === 'string') {
  // childSelector is never (bug?)
  const childElement: HTMLElement|null = elem.querySelector(childSelector);
  // childElement is now null
  if (!childElement) {
    throw new Error('...');
  }
  // childElement is now never
  childElement.addEventListener('click', () => 1);
}

Expected behavior:

TypeScript understands that childSelector must be string in the block. This is what TS 2.8 did.

Actual behavior:

TypeScript infers childSelector to be never, then outsmarts the user's type annotation of HTMLElement|null to be just null, then deducts that in the last line, it must be never.

Playground Link: http://www.typescriptlang.org/play/#src=declare%20const%20childSelector%3A%20%7B%7D%7Cundefined%3B%0D%0Adeclare%20const%20elem%3A%20HTMLElement%3B%0D%0Aif%20(typeof%20childSelector%20%3D%3D%3D%20'string')%20%7B%0D%0A%20%20%2F%2F%20childSelector%20is%20never%20(bug%3F)%0D%0A%20%20const%20childElement%3A%20HTMLElement%7Cnull%20%3D%20elem.querySelector(childSelector)%3B%0D%0A%20%20%2F%2F%20childElement%20is%20now%20null%0D%0A%20%20if%20(!childElement)%20%7B%0D%0A%20%20%20%20throw%20new%20Error('...')%3B%0D%0A%20%20%7D%0D%0A%20%20%2F%2F%20childElement%20is%20now%20never%0D%0A%20%20childElement.addEventListener('click'%2C%20()%20%3D%3E%201)%3B%0D%0A%7D

@ghost ghost added the Bug A bug in TypeScript label Jun 25, 2018
@ghost
Copy link

ghost commented Jun 25, 2018

The real problem is narrowing childSelector to never. If you just take function f(childElement: HTMLElement|null) { ... } the rest of the code works.

So the bug is basically:

function stringify(anything: {} | undefined): string {
    return typeof anything === "string" ? anything.toUpperCase() : ""; // Property 'toUpperCase' does not exist on type 'never'.
}

@mhegazy mhegazy added this to the TypeScript 3.0 milestone Jun 25, 2018
@weswigham
Copy link
Member

weswigham commented Jun 26, 2018

@mhegazy did we want to change our narrowing behavior? typeof doesn't narrow to subtypes right now (when also narrowing a union) - it just picks relevant union members. I could also say

function stringify(anything: { toString(): string } | undefined): string {
    return typeof anything === "string" ? anything.toUpperCase() : ""; // Property 'toUpperCase' does not exist on type 'never'.
}

and expect narrowing to work, but today it will not.

@mhegazy
Copy link
Contributor

mhegazy commented Jun 26, 2018

So what changed since 2.8?

@weswigham
Copy link
Member

weswigham commented Jun 26, 2018

AFAIK it didn't work in 2.8.

image

And looking at the git blame, it's worked like this since at least 1.8.

@mprobst
Copy link
Contributor Author

mprobst commented Jun 26, 2018 via email

@weswigham
Copy link
Member

Was strictNullChecks only enabled with 2.9? That'd cause the discrepancy.

@mprobst
Copy link
Contributor Author

mprobst commented Jun 26, 2018 via email

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

3 participants