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

Variable is used before assigned, but we're checking whether it's been assigned #12916

Closed
ghost opened this issue Dec 14, 2016 · 9 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@ghost
Copy link

ghost commented Dec 14, 2016

Here's a pattern people might use:

function f(): number {
    let x: number;

    if (1 + 1 === 2) {
        if (2 + 2 === 4) {
            x = 1;
        }
    }

    if (!x) {
        x = 2;
    }

    return x;
}

This pattern consists of:

  • Declare a variable: let x: number;
  • Do some complicated logic to possibly assign it
  • If it wasn't assigned, provide a default value.

Currently, we can't compile this function. We get an error for !x saying Variable 'x' is used before being assigned.

This could be fixed by #11463, but if (!x!) is ugly...

This can also be fixed with let x: number | undefined;, but that is unintuitive since we don't normally require | undefined, as in the following, which compiles fine:

function f(): number {
    let x: number;
    if (1 + 1 === 2) {
        x = 1;
    }
    else {
        x = 2;
    }
    return x;
}

CC @sandersn

@gcnew
Copy link
Contributor

gcnew commented Dec 14, 2016

A dupe of #9568?

@ghost
Copy link
Author

ghost commented Dec 14, 2016

Looks like it. Although the comments in #9568 make it sound like the second example here would have a compile error (as it too would require x: number | undefined). So maybe the issue here is that it's inconsistent.

@gcnew
Copy link
Contributor

gcnew commented Dec 15, 2016

IMHO the second example should not require x: number | undefined as there is no read of uninitialized value. CFA determines that x is always assigned before being read. That's not true for the first example - it relies on implicit falsiness and it might be even wrong in the case of x = 0.

@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Dec 28, 2016
@ScallyGames
Copy link

ScallyGames commented Jan 20, 2017

I just came across this issue in our code when trying to switch to --strictNullChecks.

IMHO it might even be valid to not throw this error on variable usage in any conditional checks (as long as you don't do any property accesses on it) as a non defined variable would always be undefined the check would be valid at runtime.

Another question is if

if(x === undefined)
{
    x; // x is inferred as undefined
    x = 2
}
x; // x is of type number as it will always be set

would be legitimate or rather confusing.

In any way I think it is a valid use-case to not set let x: number | undefined as one might not want the variable to be assignable to undefined but only have the initial undefined state.

@gcnew obviously strict checking x === undefined would solve that problem. Maybe only allow it after strict check for undefined?

@gcnew
Copy link
Contributor

gcnew commented Jan 20, 2017

@Aides359 From my point of view allowing it is strictly a bad thing. You've declared the type of the variable to be number and then compare it to undefined. That's dead code by my definition. If it was allowed I'd have been really disappointed.

PS: if you want dynamic/inferred behaviour, just skip the type annotation. Compilers should enforce what they are told. A compiler that doesn't is both confusing and unreliable.

@ScallyGames
Copy link

@gcnew well if you follow that strictly then let x: number; should not be allowed either without assigning a value on declaration because it would then indeed be undefined - a not allowed value.

In any case, I think the stated code blocks are a valid and probably commonly used logic construct and I on the contrary would be rather disappointed if my compiler would force me to generally allow a type it won't have and shouldn't be allowed at the end of the day or disregard typing at all (which defeats the point of Typescript).

What I just realized:

{
    let x: number | undefined;

    if (x === undefined)
    {
        x = 3;
    }
    return x; // x inferred as number
}
// test inferred as () => number

function test2()
{
    let x: number | undefined;

    if (x === undefined)
    {
        x = 3;
    }
    x = undefined; // is allowed which is rather unwanted
    return x;
}

function test3(): number // explicit return type
{
    let x: number | undefined;

    if (x === undefined)
    {
        x = 3;
    }
    x = undefined;
    return x; // throws because Type 'undefined' is not assignable to type 'number'.
}

which might be just good enough.

However I still think it would be nice to allow just the number type on x and check for undefined just to be more explicit in stating intent (since from a function flow perspective the problem would be the undefined assignment, not the return).

@gcnew
Copy link
Contributor

gcnew commented Jan 23, 2017

well if you follow that strictly then let x: number; should not be allowed either without assigning a value on declaration because it would then indeed be undefined - a not allowed value.

The compiler does not allow you to read from it unless it's 100% sure that you've already assigned to the variable. Implicit unassigned value and the type of a well initialised variable are two distinct things. The variable has a single type and you must conform to it in all your code. Making sure the variable is well assigned is the job for the Control Flow Analyser (CFA).

@gcnew
Copy link
Contributor

gcnew commented Jan 23, 2017

Check #9568 again. It has some good points and it was agreed there that such a behaviour is questionable and confusing.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 23, 2017

thanks @gcnew. this is indeed a duplicate of #9568

@mhegazy mhegazy added Duplicate An existing issue was already created and removed Bug A bug in TypeScript labels Jan 23, 2017
@mhegazy mhegazy closed this as completed Feb 28, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants