Typescript: Variable can be used before declaration

Created on 29 Nov 2015  Â·  17Comments  Â·  Source: microsoft/TypeScript

function foo():number {
    var v2 = 5 + v1
    var v1 = 3
    return v2
}

When compiled, I expect this to output a warning because the variable v1 is used before it is declared. Instead it compiles without complaint, and emits a function that returns NaN.

message TS6029: Version 1.6.2

Question

Most helpful comment

It is correct behavior in specification of JavaScript(EcmaScript). Let's use let and const keyword:

function foo():number {
    let v2 = 5 + v1 // error
    let v1 = 3
    return v2
}
function foo():number {
    const v2 = 5 + v1 // error
    const v1 = 3
    return v2
}

All 17 comments

It is correct behavior in specification of JavaScript(EcmaScript). Let's use let and const keyword:

function foo():number {
    let v2 = 5 + v1 // error
    let v1 = 3
    return v2
}
function foo():number {
    const v2 = 5 + v1 // error
    const v1 = 3
    return v2
}

I understand TypeScript is a strict superset of JavaScript and is therefore bound to participate in this insanity, but can the TS compiler at least emit a warning? This use-before-declaration feature has no value and causes real bugs.

The reason this is allowed is because the value of a variable that is declared (i.e. var ... for the variable occurs anywhere in the function) but unassigned is undefined which always satisfies the type for the variable, because all types are nullable.

So I consider this a duplicate of #185.

You're better off using let, or the Flow type checker.

It is a superset, which means it follows the rules... It is valid JavaScript therefore it is valid TypeScript. Use let or const.

A linting tool such as tslint would also warn you of such things.

Restriction of @ridiculousfish wants may be a compiler option proposal such as --noVarDeclaration.

It is valid JavaScript therefore it is valid TypeScript

function f() {
    var equal = foo == bar
    var foo = 3
    var bar = "abc"
    return equal
}

is valid JavaScript (and returns true!) but not valid TypeScript. So if we can disallow nonsense constructs like comparing strings and ints, maybe there's room for disallowing nonsensical constructs like use-before-declaration.

Anyways I don't mean to insert myself into the language design. I guess I can offer only my experience: speaking as an outsider and new user, I hoped that TypeScript would protect me from JavaScript's landmines, and I was disappointed to find that this one is replicated in TypeScript. Feel free to use or discard that data point as you see fit, and thanks for your consideration either way.

It is a superset, which means it follows the rules... It is valid JavaScript therefore it is valid TypeScript.

The syntax itself is a superset, but the set of valid programs is a subset. The whole point of TypeScript is to add static typing, i.e. to restrict the valid programs to those which are statically type safe, i.e. a subset. Together with the additional syntax, that makes it an overlapping set.

The only problem this thread is highlighting is the lack of null safety, i.e. #185.

maybe there's room for disallowing nonsensical constructs like use-before-declaration

To be precise, there's nothing wrong with use before declaration, since var always applies to the entire function (unlike let). Eg this is fine:

function foo() {
    x = 7;
    bar(x);
    var x;
}

The problem is use before assignment. Eg this is not fine, but TypeScript says it is, because x is a number which includes undefined:

function foo() {
    bar(x);
    x = 7;
    var x;
}

To be precise, there's nothing wrong with use before declaration

Are you wearing your ECMAScript-Standards-Lawyer hat or language-designer hat? I'd wager that 100% of use-before-declaration are either accidental shadowing of a global variable, or cut-and-paste code motion where "it works" is surprising and probably not what's intended. That's true even if there's assignment-before-use: probably the assignment was intended for a global variable, and the local variable declaration 50 lines later was not expected.

I'm racking my brains trying to think of a legitimate use for use-before-declaration. The best I can think of is that it simplifies the implementation, since now variables are scoped to a function instead of to part of a function.

Sorry, since you mentioned that TypeScript "emits a function that returns NaN" as the concrete problem, and the NaN comes from the use-before-assignment (not the use-before-declaration), I thought your real problem _was_ use-before-assignment and you just didn't already know declaration and first assignment were different things.

It would be easy to make your code pass a use-before-declaration test without actually fixing the problem by putting the variable declarations at the top, like some people do.

function foo():number {
    var v1, v2;
    v2 = 5 + v1
    v1 = 3
    return v2
}

But that's pretty rare and you're right in saying use-before-declaration is strongly correlated with real problems (in this case use-before-assignment) and therefore such a warning would be productive overall. However, I think such "code looks suspicious" heuristics are much better suited to https://github.com/palantir/tslint (which already has a no-use-before-declare rule) than TypeScript proper, which is really focused on adding type checking and extra syntax to JavaScript in a way that keeps a strong connection between the languages and conversion costs low.

@ridiculousfish but this is valid typescript:

function f() {
    var equal = foo == bar
    var foo: any = 3
    var bar = "abc"
    return equal
}

If you want eliminate code issue like that... leverage the language and use let and var. Why impose additional arbitrary rules when there are already sufficient constructs in the language that do exactly what you want? If you don't want use before assignment, use the proper language construct, but I have seen lots of old code the exploited hoisting and abused variable initialisation.

To be honest this is the first I've heard of let in TypeScript! I can't find a single mention of it in the tutorial or handbook: all of their examples use var exclusively, so that's what I used. Why do all of the TypeScript resources use var when let is so much better?

let was only recently added to JavaScript (ES6, June 2015) and TypeScript inherited it from there. The handbook and tutorials would have been written earlier, and var is more common and thus less surprising to see (for tutorial/handbook purposes).

¯\_(ツ)_/¯

I suspect the team need to overhaul their documentation. In fact we have no-var-keyword for TSLint turned on in our projects, since as you said earlier, it is a bit of JavaScript silliness that was finally addressed in ES6. Even though when emitting to ES5, TypeScript will "save" you:

function foo(qat: number) {
    let bar = 1;
    if (qat) {
        let bar = 2;
        return bar;
    }
    return bar;
}

The second bar get emitted as bar_1.

@ridiculousfish yeah, as @kitsonk said, I'd suggest trying out TSLint for stricter control over TS semantics like this. Some relevant lint rules: no-var-keyword, no-use-before-declare, no-unused-variable, no-duplicate-variable, no-shadowed-variable, etc.

There is indeed a fundamental tension here between "all JS is TS" and "report errors on suspicious code" caused by reasonably-common JS code that _is_ suspicious.

At this point it doesn't seem worth it to break a bunch of previously-compiling code when there's already the let keyword available for those who want saner scoping rules. We should definitely update the docs to use let everywhere, though.

Thanks for your thoughtful consideration guys!

What about this situation?

let t = new T()
class T {
    toString() : string { return "T" }
}

let s = new S()
function S(){}
S.prototype.toString = function(){ return "S" }

alert(t.toString()) // T is not a constructor
alert(s.toString())
Was this page helpful?
0 / 5 - 0 ratings