question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Variable isn't narrowed within a capturing closure

See original GitHub issue

TypeScript 3.7.2 Playground link

Compiler Options:

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Input:

let i: number | undefined;
i = 0;
let j:number = i+1; // works
(k: number) => k === i+1; // error: Object i is possibly undefined

Output:

"use strict";
let i;
i = 0;
let j = i + 1; // works
(k) => k === i + 1; // error: Object i is possibly undefined

Expected behavior:

The compiler should not complain about the last i+1 because it’s clearly a number type after assigning 0 to it.

I suspect the closure uses the type of i from the let statement and ignores it being narrowed down later on. Is this expected behavior?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:1
  • Comments:12 (4 by maintainers)

github_iconTop GitHub Comments

3reactions
RyanCavanaughcommented, Nov 15, 2019

If I had to draw the most realistic picture of a warning on a closed-over variable that really shouldn’t exist, it’d look like this:

function makeAdder(n?: number) {
  n = n ?? 0;
  return (m: number) => n + m;
}

It seems like the rule is “If a closure is lexically after all assignments to a bare identifier, then the last narrowing can apply”.

You can break this, though:

function adderWrapper(n?: number) {
  function setValue(v?: number) {
    n = v;
  }
  n = n ?? 0;
  return {
    setValue,
    add: (m: number) => m + n
  }
}
const k = adderWrapper(0);
k.setValue(undefined);
k.add(3); // NaN

So the rule would need to be amended to “All assignments to a bare identifier occur lexically and CFA-before the closure expression”, though possibly that could be broken too.

1reaction
j-oliverascommented, Nov 15, 2019

Duplicate of #9998.

Typescript warns you that the i can be undefined when the function is called:

let i: number | undefined;
i = 0;
let j:number = i+1; // works
const func = (k: number) => k === i + 1; // error: Object i is possibly undefined
i = undefined;
func(3);

Playground.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Narrowing - TypeScript
Argument of type 'string | number' is not assignable to parameter of type ... and TypeScript can understand it to narrow types in...
Read more >
Closures: Anonymous Functions that Can Capture Their ...
Closures are usually short and relevant only within a narrow context rather than in any arbitrary scenario. Within these limited contexts, the compiler...
Read more >
Why and when an unexecuted closure can capture outer ...
Compile time is not necessarily the same as definition time. The closure captures variables when it is created, not when its body is...
Read more >
Closures: Anonymous Functions that Can Capture Their ...
Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure...
Read more >
A lambda is not necessarily a closure | Hacker News
A more aggressive method would be to discover that all references to f are within (fun q -> ...) and inline the free...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found