microsoft / microsoft/TypeScript

Type narrowing on object properties lost in async IIFE

Open
#30,625 2 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Experience Enhancement Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

TypeScript Version: 3.4.0-dev.20190327

Search Terms:
narrowing iife control flow

Code

declare const x: string | undefined;
declare const y: { z: string | undefined };
function needsString(it: string) { return it; }

function a() {
  if (!x) {
    throw new Error("Missing x");
  }
  const res1 = (() => needsString(x))(); // all good, per #8849
  const res2 = (async () => needsString(x))();  // ditto

  if (!y.z) {
    throw new Error("Missing z.")
  }

  const res3 = (() => needsString(y.z))();  // still good
  const res4 = (async () => needsString(y.z))();  // now things blow up  
}

Expected behavior:
The call to needsString in the expression for res4 succeeds, like the one in res2 and res3. Even though the function is async, the code in it -- at least that's before an await -- runs immediately, I believe, and so should be subject to the narrowing (esp. given that res2 works).

Actual behavior:
Error for needsString(y.z): Argument of type 'string | undefined' is not assignable to parameter of type 'string'.

Playground Link: https://www.typescriptlang.org/play/index.html#src=declare%20const%20x%3A%20string%20%7C%20undefined%3B%0Adeclare%20const%20y%3A%20%7B%20z%3A%20string%20%7C%20undefined%20%7D%3B%0Afunction%20needsString(it%3A%20string)%20%7B%20return%20it%3B%20%7D%0A%0Afunction%20a()%20%7B%0A%20%20if%20(!x)%20%7B%0A%20%20%20%20throw%20new%20Error(%22Missing%20x%22)%3B%0A%20%20%7D%0A%20%20const%20res1%20%3D%20(()%20%3D%3E%20needsString(x))()%3B%20%2F%2F%20all%20good%2C%20per%20%238849%0A%20%20const%20res2%20%3D%20(async%20()%20%3D%3E%20needsString(x))()%3B%20%20%2F%2F%20ditto%0A%0A%20%20if%20(!y.z)%20%7B%0A%20%20%20%20throw%20new%20Error(%22Missing%20z.%22)%0A%20%20%7D%0A%0A%20%20const%20res3%20%3D%20(()%20%3D%3E%20needsString(y.z))()%3B%20%20%2F%2F%20still%20good%0A%20%20const%20res4%20%3D%20(async%20()%20%3D%3E%20needsString(y.z))()%3B%20%20%2F%2F%20now%20things%20blow%20up%20%20%0A%7D

Related Issues:
https://github.com/Microsoft/TypeScript/pull/8849, which I think was supposed to fix IIFE issues like this, but appears to have missed a case (or I'm missing something).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the example from the issue in the linked TypeScript Playground using the reported 3.4.0-dev version, comparing the x and y.z narrowing cases around the async IIFE. Trace the compiler's control-flow narrowing for object properties and add coverage so the res4 call accepts y.z as a string without changing the existing res2 and res3 behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.