Skip to content

Unreachable 'for' clause doesn't correctly narrow #11821

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

Closed
zpdDG4gta8XKpMCd opened this issue Oct 24, 2016 · 20 comments
Closed

Unreachable 'for' clause doesn't correctly narrow #11821

zpdDG4gta8XKpMCd opened this issue Oct 24, 2016 · 20 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@zpdDG4gta8XKpMCd
Copy link

zpdDG4gta8XKpMCd commented Oct 24, 2016

nightly build Oct 24, 2016

// works as expected
function shouldNodeBeConsidered(ruleKey: string, node: ts.Node): boolean {
    for (let current: ts.Node | undefined = node; current !== undefined; current = current.parent) { // works
    }
}
// weirdly doesn't work
function shouldNodeBeConsidered(ruleKey: string, node: ts.Node): boolean {
    for (
        let current: ts.Node | undefined = node;
        current !== undefined;
        current = current.parent // <-- [ts] Object is possibly 'undefined'.
                                 //          let current: ts.Node | undefined

     ) {
        if (shouldImmediateNodeBeConsidered(ruleKey, current)) {
            return true;
        } else {
            continue;
        }
    }
    return false;
}
@masaeedu
Copy link
Contributor

masaeedu commented Oct 24, 2016

@Aleksey-Bykov Not super familiar with the flow based type checking, but did you mean to have an if statement there?

        let current: ts.Node | undefined = node;
        if (current !== undefined) {
            current = current.parent;
        }

@RyanCavanaugh
Copy link
Member

@masaeedu the comparison is the middle clause of a for loop, not an expression statement

@RyanCavanaugh
Copy link
Member

@Aleksey-Bykov I can't reproduce this. As usual, we'd really prefer a self-contained repro and a descriptive title on bugs

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Oct 24, 2016
@masaeedu
Copy link
Contributor

@RyanCavanaugh My bad. I can reproduce this, you need to enable strictNullChecks. I'll set up a repo.

@masaeedu
Copy link
Contributor

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd changed the title narrowing doesn't work flow analysis fail to narrow a nullable in the incrimination clause of a for statement in a presence of a function call with the loop variable as an angument and the continue keyword in it's body Oct 24, 2016
@zpdDG4gta8XKpMCd
Copy link
Author

zpdDG4gta8XKpMCd commented Oct 24, 2016

@RyanCavanaugh updated the title,
what is a self contained repro? is it something git issue templates are for? didn't i follow it?

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd changed the title flow analysis fail to narrow a nullable in the incrimination clause of a for statement in a presence of a function call with the loop variable as an angument and the continue keyword in it's body flow analysis fail to narrow a nullable in the incrimination clause of a for statement in presence of a function call with the loop variable as an angument and the continue keyword in it's body Oct 24, 2016
@zpdDG4gta8XKpMCd
Copy link
Author

it's like you are giving me a favor of considering bugs that i found in your product, sorry for doing it and making you upset

@RyanCavanaugh RyanCavanaugh changed the title flow analysis fail to narrow a nullable in the incrimination clause of a for statement in presence of a function call with the loop variable as an angument and the continue keyword in it's body Unreachable 'for' clause doesn't correctly narrow Oct 24, 2016
@RyanCavanaugh
Copy link
Member

function foo() {
    for (let current: string | undefined = 'oops'; true; current = current.substr(1)) {
    // Incorrect error in third clause
        return;
    }
}

@RyanCavanaugh
Copy link
Member

@Aleksey-Bykov apology accepted

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript and removed Needs More Info The issue still hasn't been fully clarified labels Oct 24, 2016
@masaeedu
Copy link
Contributor

@RyanCavanaugh Isn't the error only incorrect in the case where the middle clause narrows the loop variable?

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Oct 24, 2016

The narrowing of the middle clause is irrelevant because we never assign anything possibly-undefined to current in the first place

@masaeedu
Copy link
Contributor

masaeedu commented Oct 24, 2016

@RyanCavanaugh Well in the original case current.parent, and in your case current.substr (I assume), can return undefined. It is true that in your case you have a return that prevents the final loop clause, but this isn't just a problem with returns, you get the problem if you use continue as well, in which case the final loop clause will be evaluated. I added some more scenarios to the repo.

@zpdDG4gta8XKpMCd
Copy link
Author

@RyanCavanaugh, you made a repro that doens't really cover the reported case: notice that the first snippet works, the problem reveals itself in the presence of a body

@RyanCavanaugh
Copy link
Member

substr can't return undefined.

More accurately, this seems to happen when the end flow point of the for loop body is unreachable.

@zpdDG4gta8XKpMCd
Copy link
Author

oh, you used the return for a body, nevermind

@masaeedu
Copy link
Contributor

masaeedu commented Oct 24, 2016

@RyanCavanaugh Here's what I'm trying to say

interface Node {
    parent: Node | undefined;
}
// This currently fails to compile. After the fix it SHOULD compile
function foo1(node: Node): boolean {
    for (let current: Node | undefined = node; current !== undefined; current = current.parent) {
        continue;
    }
    return false;
}
// This currently fails to compile. After the fix it SHOULD NOT compile
function foo2(node: Node): boolean {
    for (let current: Node | undefined = node; 1 === 1; current = current.parent) {
        continue;
    }
    return false;
}

The second case should fail because the conditional clause does not do the appropriate narrowing, regardless of whether the bottom of the loop body is reachable.

@RyanCavanaugh
Copy link
Member

The second case shouldn't fail. There are no assignments to current which don't have an RHS of a non-undefined type. See how this does compile without error:

interface Node {
    parent: Node;
}
function foo2(node: Node): boolean {
    for (let current: Node | undefined = node; 1 === 1; current = current.parent) {
        // continue;
    }
    return false;
}

Note that there should never be a difference between a for loop like this

for(a; ; c) {
  // code
  // block-terminal continue
  continue;
}

and a for loop like this (all other expressions being equal)

for(a; ; c) {
  // code
}

@masaeedu
Copy link
Contributor

There are no assignments to current which don't have an RHS of a non-undefined type.

current.parent is Node | undefined in the example I gave you (not Node), so there is one assignment to current which does not have an RHS of a non-undefined type.

We're travelling up the parent chain of a Node: eventually you'll run out of parents and get undefined. If you don't have a condition that ensures you stop iterating at that point, there is no guarantee that current is not undefined when the final clause is evaluated.

@masaeedu
Copy link
Contributor

masaeedu commented Oct 24, 2016

@RyanCavanaugh You can verify that the latter fails at runtime where the former does not:

interface N {
    parent?: N;
}

let n1: N = {}, n2: N = {};
n1.parent = n2;

// This currently fails to compile. After the fix it SHOULD compile
function foo1(node: N): boolean {
    for (let current: N | undefined = node; current !== undefined; current = current.parent) {
        continue;
    }
    return false;
}

// This currently fails to compile. After the fix it SHOULD NOT compile
function foo2(node: N): boolean {
    for (let current: N | undefined = node; 1 === 1; current = current.parent) {
        continue;
    }
    return false;
}

console.log('foo1: ', foo1(n1)); // false
console.log('foo2: ', foo2(n1)); // Uncaught TypeError: Cannot read property 'parent' of undefined

@RyanCavanaugh
Copy link
Member

Forward-duping this to #26914

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

5 participants