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

Incorrect post-narrowing merge of typeof symbol | {} | (() => void) #45467

Closed
iliubinskii opened this issue Aug 16, 2021 · 5 comments
Closed
Labels
Bug A bug in TypeScript
Milestone

Comments

@iliubinskii
Copy link

Bug Report

πŸ”Ž Search Terms

switch incorrectly narrows type

πŸ•— Version & Regression Information

v4.3.5

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

const symbol = Symbol("Test");

function f(x: 1 | "a" | typeof symbol | {} | (() => void)): void {
  switch (typeof x) {
    case "number":
    case "object":
    case "string":
    case "symbol":
  }

  switch (typeof x) {
    case "function":
    case "object":
    case "string":
    case "symbol":
  }

  switch (typeof x) {
    case "function":
    case "number":
    case "string":
    case "symbol":
  }

  switch (typeof x) {
    case "function":
    case "number":
    case "object":
    case "symbol":
  }

  switch (typeof x) {
    case "function":
    case "number":
    case "object":
    case "string":
  }

  switch (typeof x) {
    case "function":
    case "number":
    case "object":
    case "string":
    case "symbol":
  }
}

πŸ™ Actual behavior

Starting from 4th switch the type of x is narrowed to {}.

Screen

πŸ™‚ Expected behavior

The type of x should not be narrowed

@IllusionMH
Copy link
Contributor

More like it gave up on keeping union and used {} as common type ("widened"?).

Because everything from that union is assignable to {}

const symbol = Symbol("Test");

// no errors
const x1: 1 | "a" | typeof symbol | {} | (() => void) = 123;
const x2: 1 | "a" | typeof symbol | {} | (() => void) = "not a";
const x3: 1 | "a" | typeof symbol | {} | (() => void) = Symbol("not Test");
const x4: 1 | "a" | typeof symbol | {} | (() => void) = (a: number) => a;

// no errors
const y1: {} = 1;
const y2: {} = "a";
const y3: {} = Symbol("a");
const y4: {} = () => {};

// errors as expected
const z1: 1 | "a" | typeof symbol | (() => void) = 123;
const z2: 1 | "a" | typeof symbol | (() => void) = "not a";
const z3: 1 | "a" | typeof symbol | (() => void) = Symbol("not Test");
const z4: 1 | "a" | typeof symbol | (() => void) = (a: number) => a;

Playground

Not sure about your use case but object instead of {} might be closer to what you are looking for.

@RyanCavanaugh
Copy link
Member

Simpler repro

const symbol = Symbol("Test");

function f(x: typeof symbol | {} | (() => void)): void {
  if (typeof x === "function") { }
  x;
}

These issues happen when the re-unioning logic gets messed up when merging the two incoming control flow edges. But there's not much reason to write this code in first place.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Aug 16, 2021
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Aug 16, 2021
@RyanCavanaugh RyanCavanaugh changed the title Switch incorrectly narrows type Incorrect post-narrowing merge of typeof symbol | {} | (() => void) Aug 16, 2021
@fatcerberus
Copy link

This looks like subtype reduction, similar to what happens in #39434.

@Andarist
Copy link
Contributor

@RyanCavanaugh @jakebailey should this still be classified as a bug? the weird behavior with multiple switch cases from the OP was already "fixed" by #49119 . As Ryan's answer suggests - this is just union reduction at play when merging control flow paths. I don't exactly see this particular example to be a good motivation for reconsidering the used algorithm.

@RyanCavanaugh
Copy link
Member

I think ideally we would still go back to the declared type -- you could imagine an algorithm that detects that all incoming paths have an uninterrupted flow from the origin and does this -- but yeah, this isn't wrong per our normal stance of "subtype reduction can occur at any time".

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