The Wayback Machine - https://web.archive.org/web/20220411101956/https://github.com/microsoft/TypeScript/issues/33191
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

Incorrect circularity detection #33191

Open
danvk opened this issue Sep 2, 2019 · 5 comments
Open

Incorrect circularity detection #33191

danvk opened this issue Sep 2, 2019 · 5 comments
Labels
Bug
Milestone

Comments

@danvk
Copy link

@danvk danvk commented Sep 2, 2019

TypeScript Version: 3.6.2

Search Terms:

  • 7022
  • directly or indirectly in its own initializer

Code

function extent(nums: number[]) {
  let result: [number, number] | null = null;
  for (const num of nums) {
    if (!result) {
      result = [num, num];
    } else {
      const [oldMin, oldMax] = result;
      result = [Math.min(num, oldMin), Math.max(num, oldMax)];
    }
  }
  return result;
}

Expected behavior:

No error.

Actual behavior:

'oldMin' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
'oldMax' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)

I'm really struggling to see where the circularity comes in. The type of result is shown as [number, number] in that branch, so I'd assume destructuring it would result in two number types.

Removing the destructuring and using array accesses is equivalent but produces no error:

function extent(nums: number[]) {
  let result: [number, number] | null = null;
  for (const num of nums) {
    if (!result) {
      result = [num, num];
    } else {
      result = [Math.min(num, result[0]), Math.max(num, result[1])];  // ok
    }
  }
  return result;
}

Playground Link: link

Related Issues:

@mprobst
Copy link
Contributor

@mprobst mprobst commented Oct 21, 2019

here's another example from zone (source file wtf.ts):

  function shallowObj(obj: {[k: string]: any} | undefined, depth: number): any {
    if (!obj || !depth) return null;
    const out: {[k: string]: any} = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        let value: any = obj[key];
        switch (typeof value) {
          case 'object':
            const name = value && value.constructor && (<any>value.constructor).name;
            value = name == (<any>Object).name ? shallowObj(value, depth - 1) : name;
            break;
          case 'function':
            value = value.name || undefined;
            break;
        }
        out[key] = value;
      }
    }
    return out;
  }

Gives:

zone.js/lib/zone-spec/wtf.ts:134:13 - error TS7022: 'value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

134         let value = obj[key];
                ~~~~~

@s0
Copy link
Contributor

@s0 s0 commented Mar 14, 2020

Another example i've just come across, TypeScript version 2.8.3:

function testFunction() {
  let state: { value: number } | null = null;
  for (let i = 0; i < 10; i++) {
    const previousState = state;
    //    ^^^^^^^^^^^^^ error: 'previousState' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
    state = null;

    const value = i === 0 ? 0 : previousState?.value;
    //    ^^^^^ error: 'value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)

    if (value !== undefined) {
      state = { value: value + 1 }
    }
  }
  console.log('Test Function:', state?.value);
}

When putting in explicit type annotations, it produces a new error:

function testFunction() {
  let state: { value: number } | null = null;
  for (let i = 0; i < 10; i++) {
    const previousState: { value: number } | null = state;
    state = null;

    const value: number | undefined = i === 0 ? 0 : previousState?.value;
    //   Property 'value' does not exist on type 'never'.ts(2339): ^^^^^
    if (value !== undefined) {
      state = { value: value + 1 }
    }
  }
  console.log('Test Function:', state?.value);
}

@JoostK
Copy link
Contributor

@JoostK JoostK commented Jan 27, 2021

Here's another case, but then with a while loop:

interface Node {
  next: Node | null;
}

function test(node: Node) {
  let currentNode: Node | null = node;
  while (currentNode !== null) {
    const nextNode = currentNode.next;
    //    ~~~~~~~~ error TS7022: 'nextNode' implicitly has type 'any' because it does not have a type annotation
    //                           and is referenced directly or indirectly in its own initializer.
    currentNode = nextNode;
  }
}

Playground link

@tonivj5
Copy link

@tonivj5 tonivj5 commented Jan 27, 2021

BTW, using @JoostK's example, if you removes the asignation, error goes away... 😆

interface Node {
  next: Node | null;
}

function test(node: Node) {
  let currentNode: Node | null = node;
  while (currentNode !== null) {
    const nextNode = currentNode.next; // No error
   // currentNode = nextNode; // Uncomment it, and error comes back
    console.log(nextNode);
  }
}

@JoostK
Copy link
Contributor

@JoostK JoostK commented Jan 27, 2021

BTW, using @JoostK's example, if you removes the asignation, error goes away... 😆

Removing the assignment removes the circular nature of currentNode, so that's expected. It ends up being circular due to the involvement of flow typing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug
Projects
None yet
Development

No branches or pull requests

6 participants