properly drop mutually-referring declarations that are not otherwise

referenced and have no side effects
This commit is contained in:
Mihai Bazon
2012-09-23 12:47:34 +03:00
parent 76d88b59dc
commit a83b28503f
5 changed files with 137 additions and 67 deletions

View File

@@ -64,8 +64,7 @@ function Compressor(options, false_by_default) {
evaluate : !false_by_default,
booleans : !false_by_default,
loops : !false_by_default,
unused_func : !false_by_default,
unused_vars : !false_by_default,
unused : !false_by_default,
hoist_funs : !false_by_default,
hoist_vars : !false_by_default,
if_return : !false_by_default,
@@ -816,50 +815,113 @@ function Compressor(options, false_by_default) {
return self;
});
AST_Scope.DEFMETHOD("drop_unused_vars", function(compressor){
if (compressor.option("unused_vars")) {
var self = this;
var tw = new TreeWalker(function(node){
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
var self = this;
if (compressor.option("unused")
&& !(self instanceof AST_Toplevel)
&& !self.uses_eval
) {
var in_use = [];
// pass 1: find out which symbols are directly used in
// this scope (not in nested scopes).
var scope = this;
var tw = new TreeWalker(function(node, descend){
if (node !== self) {
if (node instanceof AST_Scope)
if (node instanceof AST_Defun) {
return true; // don't go in nested scopes
if (node instanceof AST_Definitions) {
if (!(tw.parent() instanceof AST_ForIn)) {
var a = node.definitions;
for (var i = a.length; --i >= 0;) {
var def = a[i];
var sym = def.name;
if (sym.unreferenced()) {
var warn = {
name: sym.name,
file: sym.start.file,
line: sym.start.line,
col: sym.start.col
};
if (def.value && def.value.has_side_effects()) {
compressor.warn("Side effects in initialization of unreferenced variable {name} [{file}:{line},{col}]", warn);
} else {
compressor.warn("Dropping unreferenced variable {name} [{file}:{line},{col}]", warn);
a.splice(i, 1);
}
}
}
if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){
if (def.value && def.value.has_side_effects()) {
def.value.walk(tw);
}
}
});
return true;
}
if (!(node instanceof AST_Statement)) {
return true; // pointless to visit expressions
if (node instanceof AST_SymbolRef && !(node instanceof AST_LabelRef)) {
push_uniq(in_use, node.definition());
return true;
}
if (node instanceof AST_Scope) {
var save_scope = scope;
scope = node;
descend();
scope = save_scope;
return true;
}
}
});
this.walk(tw);
self.walk(tw);
// pass 2: for every used symbol we need to walk its
// initialization code to figure out if it uses other
// symbols (that may not be in_use).
for (var i = 0; i < in_use.length; ++i) {
in_use[i].orig.forEach(function(decl){
// undeclared globals will be instanceof AST_SymbolRef
if (decl instanceof AST_SymbolDeclaration) {
decl.init.forEach(function(init){
var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef
&& node.definition().scope.$self === self.$self) {
push_uniq(in_use, node.definition());
}
});
init.walk(tw);
});
}
});
}
// pass 3: we should drop declarations not in_use
var tt = new TreeTransformer(
function before(node, descend) {
if (node instanceof AST_Defun && node !== self) {
if (!member(node.name.definition(), in_use)) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name : node.name.name,
file : node.name.start.file,
line : node.name.start.line,
col : node.name.start.col
});
return make_node(AST_EmptyStatement, node);
}
return node;
}
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){
if (member(def.name.definition(), in_use)) return true;
var w = {
name : def.name.name,
file : def.name.start.file,
line : def.name.start.line,
col : def.name.start.col
};
if (def.value && def.value.has_side_effects()) {
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
return true;
}
compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w);
return false;
});
if (def.length == 0) {
return make_node(AST_EmptyStatement, node);
}
if (def.length != node.definitions.length) {
node.definitions = def;
return node;
}
}
if (node instanceof AST_Scope && node !== self)
return node;
}
);
self.transform(tt);
}
});
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
var hoist_funs = compressor.option("hoist_funs");
var hoist_vars = compressor.option("hoist_vars");
this.drop_unused_vars(compressor);
this.drop_unused(compressor);
if (hoist_funs || hoist_vars) {
var self = this;
var hoisted = [];
@@ -1264,7 +1326,7 @@ function Compressor(options, false_by_default) {
});
AST_Function.DEFMETHOD("optimize", function(compressor){
if (compressor.option("unused_func")) {
if (compressor.option("unused")) {
if (this.name && this.name.unreferenced()) {
this.name = null;
}
@@ -1272,22 +1334,6 @@ function Compressor(options, false_by_default) {
return this;
});
AST_Defun.DEFMETHOD("optimize", function(compressor){
if (compressor.option("unused_func")) {
if (this.name.unreferenced()
&& !(this.parent_scope instanceof AST_Toplevel)) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name: this.name.name,
file: this.start.file,
line: this.start.line,
col: this.start.col
});
return make_node(AST_EmptyStatement, this);
}
}
return this;
});
SQUEEZE(AST_Call, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);