typescript

Control flow analysis only works for const alias condition


Why does the TypeScript control flow analysis only work for const? Why isn't it consistent with this rule for vFunc?

Given the single threaded nature of JS shouldn't it keep variable narrowed types up until the first potentially reassigning code (async, function call, property access)?

  const func = undefined as Function | undefined
  const done = false as boolean;

  const OK = !func || done;
  if (!OK) {
    func(); // As expected
  }

  let ok = !func || done;
  if (!ok) {
    func(); // Fails. Cannot invoke an object which is possibly 'undefined'.(2722)
  }

  
(async () => {
  let vFunc = (() => console.log(1))  as (Function | undefined)
  if (vFunc) {
    await new Promise((resolve) => {
      vFunc = undefined; 
      resolve(1);
    });
    vFunc(); // TS ok, runtime not ok
  }
})();

Playground


Solution

  • Control flow analysis of aliased conditions as implemented in microsoft/TypeScript#44730 only works in the following situations:

    Narrowing through indirect references occurs only when the conditional expression or discriminant property access is declared in a const variable declaration with no type annotation, and the reference being narrowed is a const variable, a readonly property, or a parameter for which there are no assignments in the function body.

    These restrictions were chosen because they limit the analysis the compiler needs to perform. Relaxing any of them means that TypeScript then has to try to keep track of whether something has mutated by the time the aliased condition is used. These always seem easy enough to a human being thinking about it, but can lead to compiler performance problems when analysis triggers further analysis. I don't see a particular description here of why the variable declaration must be const, but for readonly properties there is microsoft/TypeScript#46412 which says:

    Is this a use case that is intended to be fixed in the future or is the limitation too much work to resolve?

    It's basically a cost/benefit trade-off in control flow analysis. In order to support mutable properties we'd have to check that there are no assignments to a property between the declaration of the aliased condition that references the property and the check of that aliased condition. It's possible to do so (anything is possible), but it is non-trivial and it's not clear the added complexity and potential performance cost is worth it.

    And the same reasoning applies to all these restrictions.