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

Global variable of non-null type, never assigned, no error if used in function #28013

Closed
fatcerberus opened this issue Oct 20, 2018 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@fatcerberus
Copy link

fatcerberus commented Oct 20, 2018

The issue title is kind of awkward, I know. Basically TypeScript allows a global variable without undefined in its type signature to be accessed inside of a function even if it's not provably assigned, even with strictNullChecks enabled. In other words:

TypeScript Version: 3.2.0-dev.20181020

Search Terms: global undefined function error

Code

let pig: { eatiness: number };

function init()
{
    pig = { eatiness: 812 };
}

function feedPig()
{
    ++pig.eatiness;
}

// init();  // <-- oops! forgot to call this
feedPig();  // runtime error!

Expected behavior:
The TypeScript compiler should produce an error since pig is not provably assigned before first access, strictNullChecks is enabled and its type signature says it can't be undefined.

Actual behavior:
No compile-time error; program throws a TypeError at runtime.

Playground Link:
https://www.typescriptlang.org/play/index.html#src=let%20pig%3A%20%7B%0D%0A%20%20%20%20eatiness%3A%20number%3B%0D%0A%7D%3B%0D%0A%0D%0Afunction%20init()%0D%0A%7B%0D%0A%20%20%20%20pig%20%3D%20%7B%20eatiness%3A%20812%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20feedPig()%0D%0A%7B%0D%0A%20%20%20%20%2B%2Bpig.eatiness%3B%0D%0A%7D%0D%0A%0D%0A%2F%2F%20init()%3B%20%20%2F%2F%20%3C--%20oops!%20forgot%20to%20call%20this%0D%0AfeedPig()%3B%20%20%2F%2F%20error!%0D%0A

Related Issues:
#23305

@mattmccutchen
Copy link
Contributor

Related to #23305.

@fatcerberus
Copy link
Author

I note that if the uninitialized variable is accessed in the same scope it’s declared in (global in the example) then TS does produce an object-not-assigned error. It’s only when moving the first access to a different scope that the problem arises.

@ahejlsberg
Copy link
Member

These are effects of design limitations in the control flow analyzer. See discussion in #9998.

@ahejlsberg ahejlsberg added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Oct 22, 2018
@fatcerberus
Copy link
Author

I understand that there's a design limitation insofar the analyzer can't look into function calls to see what happens inside of them; however it would seem reasonable to me that if it can't be proven that a variable is written to in the same scope it's declared in, then with strictNullChecks on it should be required to include undefined in the variable's type signature. As I understood it that was in fact the entire purpose of --strictNullChecks: if there's any possibility of a variable being observed as undefined at runtime, you must explicitly say so in its type annotation.

I don't see this as much different in principle than requiring a derived class constructor to call super() before returning.

@fatcerberus
Copy link
Author

fatcerberus commented Oct 22, 2018

Having done more experiments, I can see that this also isn't an error when using only --strictNullChecks:

class C {
    x: number;
    constructor() {
        // this.init();  // oops! forgot to call this
    }
    init() { this.x = 812; }
    doStuff() { console.log(this.x); }
}

let c = new C();
c.doStuff();

To catch this mistake there is the option of using --strictPropertyInitialization which does emit an error here, with the tradeoff that it can produce false positives as the CFA can't see inside method calls.

So I guess the sticking point here is that you wouldn't want checks like this by default since they can produce false positives (to which I agree 100%); it might be nice to have an optional --strictVariableInitialization or similar flag, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants