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

Type narrowing is lost when using logical OR assignment #44553

Open
Oaphi opened this issue Jun 11, 2021 · 2 comments
Open

Type narrowing is lost when using logical OR assignment #44553

Oaphi opened this issue Jun 11, 2021 · 2 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@Oaphi
Copy link

Oaphi commented Jun 11, 2021

Bug Report

🔎 Search Terms

logical assignment, narrowing

🕗 Version & Regression Information

The behaviour is consistent across all versions since the introduction of logical assignment operators in TypeScript 4.0.
Checked range from 4.0.5 to Nightly, no changes in behaviour observed.

⏯ Playground Link

Playground link with relevant code

💻 Code

type F = () => number;

type R = {
  [ x: string ]: R | F | undefined
}

const isFunc = <T extends (...args: any[]) => any>(maybe:unknown) : maybe is T => typeof maybe === "function";

const check = (i: R) => {

  let tmp:R = i;

  let curr = tmp["something"]; // R | F | undefined, expected

  if( isFunc<F>(curr) ) return;

  curr // R | undefined, expected

  tmp = curr || (curr = {}); //ok, expected

  tmp = curr ||= {}; // Type 'R | F' is not assignable to type 'R'.
};

🙁 Actual behavior

Error 2322 on the second tmp assignment because R | F is not narrowed to R as with the curr || (curr = {})

🙂 Expected behavior

No error since both assignments to tmp are functionally equivalent
(I might be missing something obvious about how logical assignment is intended to work from the type system standpoint)

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jun 11, 2021
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jun 11, 2021
@RyanCavanaugh
Copy link
Member

This is a bit tricky. In curr || (curr = { }), the LHS contributes the control-flowed type of curr, and the RHS contributes { }, so no problems.

In curr ||= { }, no control-flow analysis is done on the type of curr because it's an assignment target and thus we need to allow any source assignable to the declared type.

This is in-principle fixable - we need to compute the CFA type of curr when producing the type of the resulting expression rather than using the declared type for both assignability and expression result - but might be a bit annoying to thread through the code (or might be trivial, not sure).

@Oaphi
Copy link
Author

Oaphi commented Jun 11, 2021

@RyanCavanaugh yeah, I expected the issue to lie in the control-flow analysis (well, lack thereof) in the case of logical assignment operators... Thank you for confirming this is something to consider fixing - I personally have no issue in a more verbose syntax that takes advantage of the traditional way of short-circuiting assignment but I assume the more people start using these newish operators, the higher the chances this will trip someone up.

I can try poking around to see if I can come up with a PR if you'd accept one but not sure if that is not out of my depth

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

2 participants