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

Destructuring assignments generate unreachable code #7017

Closed
mweststrate opened this issue Feb 11, 2016 · 8 comments
Closed

Destructuring assignments generate unreachable code #7017

mweststrate opened this issue Feb 11, 2016 · 8 comments
Labels
Fixed A PR has been merged for this issue Help Wanted You can do this Suggestion An idea for TypeScript

Comments

@mweststrate
Copy link

The two functions in the typescript snippet below both generate an unreachable var declaration. Although this will work as expected due to hoisting, javascript minifiers might try to remove this dead code and unintentionally introduce global variables. So it would beneficial to move this declarations to the top of the function to eliminate the (valid) warnings.

I tested with the Closure and UglifyJS2 minifiers. Both warn about this declaration but leave it (correctly) in place:

>  uglifyjs c.js -c
WARN: Dropping unreachable code [c.js:5,4]
WARN: Declarations in unreachable code! [c.js:5,4]
WARN: Dropping unreachable code [c.js:15,4]
WARN: Declarations in unreachable code! [c.js:15,4]

Closure:

JSC_UNREACHABLE_CODE: unreachable code at line 5 character 4
    var _a; // unreachable!
    ^
JSC_UNREACHABLE_CODE: unreachable code at line 15 character 4
    var _a; // unreachable!
    ^

c.ts:

export function unreachableVar(a:any[]) {
    const res = this.a.splice(0, 0, ...a);
    return res;
}

export function anotherUnreachableVar(a,b) {
    let c,d;
    switch(a) {
      case true:
        [c,d] = someFunc(b);
    }
    return 3 + c;
}

function someFunc(x) {
    return [1,2]
}

transpiles to c.js:

"use strict";
function unreachableVar(a) {
    var res = (_a = this.a).splice.apply(_a, [0, 0].concat(a));
    return res;
    var _a; // unreachable!
}
exports.unreachableVar = unreachableVar;
function anotherUnreachableVar(a, b) {
    var c, d;
    switch (a) {
        case true:
            _a = someFunc(b), c = _a[0], d = _a[1];
    }
    return 3 + c;
    var _a; // unreachable!
}
exports.anotherUnreachableVar = anotherUnreachableVar;
function someFunc(x) {
    return [1, 2];
}

Tested in tsc 1.7.5 and 1.9.0 (@next)

@mhegazy
Copy link
Contributor

mhegazy commented Feb 11, 2016

"unreachable" applies to statements, i.e. things that will execute. this is a declaration. var declarations are hoisted to the top of their scope. This is similar to function declaration hoisting, e.g:

function foo () {
   inner();
   return;

   function inner() { // this is not unreachable. the declaration is hoisted to the top of the scope
   }
}

So I would say this is perfectly legal JavaScript, and the checks in uglify/closure are too strict.

@Arnavion
Copy link
Contributor

In case it helps, I've been working around this by moving the declarations to the top using UJS API (I run the TS compiler's output through some fixup steps using UJS, and this is one of them):

// Move var statements at the end of blocks (generated by TS for rest parameters) to the start of the block.
// This is needed to prevent unreachable-code warnings from UJS
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
    if (
        node instanceof UglifyJS.AST_Block &&
        node.body[node.body.length - 1] instanceof UglifyJS.AST_Var
    ) {
        node.body.unshift(node.body.pop());
    }
}));

@mweststrate
Copy link
Author

@mhegazy Thanks for the quick response! I am aware the generated code is technically correct. But I think it is nice to produce linter passing / best practice code nonetheless. Just to play nice with other tooling. Especially since typescript is usually combined with post processing tools in serious projects (like uglify, webpack etc etc). Actually I like it that typescript compiled code is a lot less alien than babel compiled code. So I would say keep up the high standard and move the declarations to the top 😉

(By the way, it would be really cool if there was a typescript minifier, which could take into account private fields and such +:100:)

@Arnavion thanks for the work around

@mhegazy
Copy link
Contributor

mhegazy commented Feb 11, 2016

with #5595 in place, this should be a simple change. but before that, it requires two passes over the tree and a lot of synchronization between the naming logic and the emit logic. in short it will be hard to do and will make the compiler slower if done. #5595 is planned for 2.0, so until then, closing this issue.

@mhegazy mhegazy closed this as completed Feb 11, 2016
@mhegazy mhegazy added Revisit An issue worth coming back to Suggestion An idea for TypeScript labels Feb 11, 2016
@mweststrate
Copy link
Author

Sounds great, thanks for looking in to this!

@evmar
Copy link
Contributor

evmar commented Jan 18, 2017

Should we reopen this now that #5595 landed?

@mhegazy mhegazy reopened this Jan 18, 2017
@mhegazy mhegazy added Help Wanted You can do this and removed Revisit An issue worth coming back to labels Jan 18, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Jan 18, 2017

PRs are welcomed.

@albyrock87
Copy link

Seems to be fixed in Typescript 2.9.2 (you can try it in the playground).

@mhegazy mhegazy closed this as completed Jul 5, 2018
@mhegazy mhegazy modified the milestones: Community, TypeScript 2.9 Jul 5, 2018
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Jul 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed A PR has been merged for this issue Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants