microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.19k stars 12.51k forks source link

Reachability analysis of `never` for `yield*` is unchecked #58582

Open rixtox opened 6 months ago

rixtox commented 6 months ago

🔎 Search Terms

reachability never

🕗 Version & Regression Information

It's observed in all TS versions.

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mAVAAjAUwG5oE4AoCUAXMgOJrrYCGUc2APOANZhwDuYANOltgHzIBvAFDJkUABbY2yAOTccMgNxCAvkKGhIsBCgC2lGGAKCRY7AE8To0eZhoANgBMU8vPmWiVyYIcr37lsLWyBAIAM5w9mgAdPZwAOa4Mj5gfgEy7qZqoqFgEVGxCUksUMjYaJQQ4pQARlEZympAA

💻 Code

function* never(): Generator<unknown,never> {
  throw 'never';
}

function* main() {
  try {
    yield* never();
  } finally {
    console.log('finally');
  }
  console.log('not reachable');
}

🙁 Actual behavior

No error reported.

🙂 Expected behavior

There should be an error Unreachable code detected.(7027) at line 11 console.log('not reachable');.

Additional information about the issue

Reachability analysis is working for normal function: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABGApgNxQJwBQEoBcy6WiA3gFCKJQAWmcA7ogOSoabMDc5AvueaEiwEiALYBDGGDxlK1TAE9ZVKmyx5uVHomBTxAG31KKKxBAQBnOPpQA6fXADm2ZrrAGjzXJsR8q5sCsbeycXMDgoREwUcQgacQAjGy9uPiA

It's broken for Promise and we have another issue tracking that one: https://github.com/microsoft/TypeScript/issues/34955

Generator functions are being used as coroutine in libraries like co.js, Effect.js, redux-saga, Effection, and more. It's common to write a coroutine operator that suspend the thread forever, thus taking function never(): Generator<Instruction, never> signature. It would benefit users if the TypeScript control analysis can infer unreachability after yield* never().

fatcerberus commented 6 months ago

Related: https://github.com/microsoft/TypeScript/issues/56363

I think this comes down to the known limitation wherein never-returning functions can only affect control flow for a naked function call (which is the root cause of #34955 too); here, the call is only part of the statement because the return value is being passed to yield*. So it's similar to how

function never(): never { throw Error(); }
const x = never();
console.log("unreachable, not an error");

doesn't have an unreachable-code error either.

SamVerschueren commented 3 months ago

Run into this as well

function foo(): never {
  throw new Error('foo');
}

(foo());

const x = foo();

console.log(foo());

foo();

// Only from this point forward it errors because of "unreachable"

console.log('hello');

I found this bug while I was looking into this bug which is somewhat related.