Skip to content

Commit 5907df8

Browse files
implement logical assignment
1 parent 77dc12c commit 5907df8

File tree

6 files changed

+361
-32
lines changed

6 files changed

+361
-32
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ jobs:
2323
env:
2424
- TEST_NO_SANDBOX=1
2525

26-
- node_js: 14
26+
- node_js: 15
2727
env:
2828
- TERSER_TEST_ALL=1
2929
cache:
3030
directories:
3131
- node_modules
3232
- tmp
3333

34-
- node_js: 14
34+
- node_js: 15
3535
os: windows
3636
env:
3737
- TEST_NO_SANDBOX=1

lib/ast.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,8 +1075,11 @@ var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative",
10751075
},
10761076
});
10771077

1078-
var AST_Assign = DEFNODE("Assign", null, {
1078+
var AST_Assign = DEFNODE("Assign", "logical", {
10791079
$documentation: "An assignment expression — `a = b + 5`",
1080+
$propdoc: {
1081+
logical: "Whether it's a logical assignment"
1082+
}
10801083
}, AST_Binary);
10811084

10821085
var AST_DefaultAssign = DEFNODE("DefaultAssign", null, {

lib/compress/index.js

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
528528
function read_property(obj, key) {
529529
key = get_value(key);
530530
if (key instanceof AST_Node) return;
531+
531532
var value;
532533
if (obj instanceof AST_Array) {
533534
var elements = obj.elements;
@@ -542,6 +543,7 @@ function read_property(obj, key) {
542543
if (!value && props[i].key === key) value = props[i].value;
543544
}
544545
}
546+
545547
return value instanceof AST_SymbolRef && value.fixed_value() || value;
546548
}
547549

@@ -674,38 +676,56 @@ function is_modified(compressor, tw, node, value, level, immutable) {
674676
|| value instanceof AST_This;
675677
}
676678

677-
function mark_escaped(tw, d, scope, node, value, level, depth) {
679+
// A definition "escapes" when its value can leave the point of use.
680+
// Example: `a = b || c`
681+
// In this example, "b" and "c" are escaping, because they're going into "a"
682+
//
683+
// def.escaped is != 0 when it escapes.
684+
//
685+
// When greater than 1, it means that N chained properties will be read off
686+
// of that def before an escape occurs. This is useful for evaluating
687+
// property accesses, where you need to know when to stop.
688+
function mark_escaped(tw, d, scope, node, value, level = 0, depth = 1) {
678689
var parent = tw.parent(level);
679690
if (value) {
680691
if (value.is_constant()) return;
681692
if (value instanceof AST_ClassExpression) return;
682693
}
683-
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
694+
695+
if (
696+
parent instanceof AST_Assign && (parent.operator === "=" || parent.logical) && node === parent.right
684697
|| parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
685698
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
686699
|| parent instanceof AST_VarDef && node === parent.value
687-
|| parent instanceof AST_Yield && node === parent.value && node.scope !== d.scope) {
700+
|| parent instanceof AST_Yield && node === parent.value && node.scope !== d.scope
701+
) {
688702
if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
689703
if (!d.escaped || d.escaped > depth) d.escaped = depth;
690704
return;
691-
} else if (parent instanceof AST_Array
705+
} else if (
706+
parent instanceof AST_Array
692707
|| parent instanceof AST_Await
693708
|| parent instanceof AST_Binary && lazy_op.has(parent.operator)
694709
|| parent instanceof AST_Conditional && node !== parent.condition
695710
|| parent instanceof AST_Expansion
696-
|| parent instanceof AST_Sequence && node === parent.tail_node()) {
711+
|| parent instanceof AST_Sequence && node === parent.tail_node()
712+
) {
697713
mark_escaped(tw, d, scope, parent, parent, level + 1, depth);
698714
} else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
699715
var obj = tw.parent(level + 1);
716+
700717
mark_escaped(tw, d, scope, obj, obj, level + 2, depth);
701718
} else if (parent instanceof AST_PropAccess && node === parent.expression) {
702719
value = read_property(value, parent.property);
720+
703721
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
704722
if (value) return;
705723
}
724+
706725
if (level > 0) return;
707726
if (parent instanceof AST_Sequence && node !== parent.tail_node()) return;
708727
if (parent instanceof AST_SimpleStatement) return;
728+
709729
d.direct_access = true;
710730
}
711731

@@ -729,32 +749,64 @@ function is_modified(compressor, tw, node, value, level, immutable) {
729749
suppress(node.left);
730750
return;
731751
}
752+
753+
const finish_walk = () => {
754+
if (node.logical) {
755+
node.left.walk(tw);
756+
757+
push(tw);
758+
node.right.walk(tw);
759+
pop(tw);
760+
761+
return true;
762+
}
763+
};
764+
732765
var sym = node.left;
733-
if (!(sym instanceof AST_SymbolRef)) return;
766+
if (!(sym instanceof AST_SymbolRef)) return finish_walk();
767+
734768
var def = sym.definition();
735769
var safe = safe_to_assign(tw, def, sym.scope, node.right);
736770
def.assignments++;
737-
if (!safe) return;
771+
if (!safe) return finish_walk();
772+
738773
var fixed = def.fixed;
739-
if (!fixed && node.operator != "=") return;
774+
if (!fixed && node.operator != "=" && !node.logical) return finish_walk();
775+
740776
var eq = node.operator == "=";
741777
var value = eq ? node.right : node;
742-
if (is_modified(compressor, tw, node, value, 0)) return;
778+
if (is_modified(compressor, tw, node, value, 0)) return finish_walk();
779+
743780
def.references.push(sym);
744-
if (!eq) def.chained = true;
745-
def.fixed = eq ? function() {
746-
return node.right;
747-
} : function() {
748-
return make_node(AST_Binary, node, {
749-
operator: node.operator.slice(0, -1),
750-
left: fixed instanceof AST_Node ? fixed : fixed(),
751-
right: node.right
752-
});
753-
};
781+
782+
if (!node.logical) {
783+
if (!eq) def.chained = true;
784+
785+
def.fixed = eq ? function() {
786+
return node.right;
787+
} : function() {
788+
return make_node(AST_Binary, node, {
789+
operator: node.operator.slice(0, -1),
790+
left: fixed instanceof AST_Node ? fixed : fixed(),
791+
right: node.right
792+
});
793+
};
794+
}
795+
796+
if (node.logical) {
797+
mark(tw, def, false);
798+
push(tw);
799+
node.right.walk(tw);
800+
pop(tw);
801+
return true;
802+
}
803+
754804
mark(tw, def, false);
755805
node.right.walk(tw);
756806
mark(tw, def, true);
807+
757808
mark_escaped(tw, def, sym.scope, node, value, 0, 1);
809+
758810
return true;
759811
});
760812
def_reduce_vars(AST_Binary, function(tw) {
@@ -1357,7 +1409,8 @@ function tighten_body(statements, compressor) {
13571409
}
13581410
// Stop immediately if these node types are encountered
13591411
var parent = scanner.parent();
1360-
if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
1412+
if (node instanceof AST_Assign
1413+
&& (node.logical || node.operator != "=" && lhs.equivalent_to(node.left))
13611414
|| node instanceof AST_Await
13621415
|| node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
13631416
|| node instanceof AST_Debugger
@@ -1425,6 +1478,7 @@ function tighten_body(statements, compressor) {
14251478
}
14261479
return make_node(AST_Assign, candidate, {
14271480
operator: "=",
1481+
logical: false,
14281482
left: make_node(AST_SymbolRef, candidate.name, candidate.name),
14291483
right: value
14301484
});
@@ -1729,6 +1783,7 @@ function tighten_body(statements, compressor) {
17291783
var parent = scanner.parent(level);
17301784
if (parent instanceof AST_Assign) {
17311785
if (write_only
1786+
&& !parent.logical
17321787
&& !(parent.left instanceof AST_PropAccess
17331788
|| lvalues.has(parent.left.name))) {
17341789
return find_stop(parent, level + 1, write_only);
@@ -1783,7 +1838,9 @@ function tighten_body(statements, compressor) {
17831838
}
17841839

17851840
function get_lhs(expr) {
1786-
if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
1841+
if (expr instanceof AST_Assign && expr.logical) {
1842+
return false;
1843+
} else if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
17871844
var def = expr.name.definition();
17881845
if (!member(expr.name, def.orig)) return;
17891846
var referenced = def.references.length - def.replaced;
@@ -1794,14 +1851,20 @@ function tighten_body(statements, compressor) {
17941851
return make_node(AST_SymbolRef, expr.name, expr.name);
17951852
}
17961853
} else {
1797-
const lhs = expr[expr instanceof AST_Assign ? "left" : "expression"];
1854+
const lhs = expr instanceof AST_Assign
1855+
? expr.left
1856+
: expr.expression;
17981857
return !is_ref_of(lhs, AST_SymbolConst)
17991858
&& !is_ref_of(lhs, AST_SymbolLet) && lhs;
18001859
}
18011860
}
18021861

18031862
function get_rvalue(expr) {
1804-
return expr[expr instanceof AST_Assign ? "right" : "value"];
1863+
if (expr instanceof AST_Assign) {
1864+
return expr.right;
1865+
} else {
1866+
return expr.value;
1867+
}
18051868
}
18061869

18071870
function get_lvalues(expr) {
@@ -1860,7 +1923,9 @@ function tighten_body(statements, compressor) {
18601923
&& !(in_loop
18611924
&& (lvalues.has(lhs.name)
18621925
|| candidate instanceof AST_Unary
1863-
|| candidate instanceof AST_Assign && candidate.operator != "="));
1926+
|| (candidate instanceof AST_Assign
1927+
&& !candidate.logical
1928+
&& candidate.operator != "=")));
18641929
}
18651930

18661931
function value_has_side_effects(expr) {
@@ -2287,7 +2352,7 @@ function tighten_body(statements, compressor) {
22872352
var def = defn.definitions[defn.definitions.length - 1];
22882353
if (!(def.value instanceof AST_Object)) return;
22892354
var exprs;
2290-
if (body instanceof AST_Assign) {
2355+
if (body instanceof AST_Assign && !body.logical) {
22912356
exprs = [ body ];
22922357
} else if (body instanceof AST_Sequence) {
22932358
exprs = body.expressions.slice();
@@ -2510,6 +2575,8 @@ function is_undefined(node, compressor) {
25102575
&& (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
25112576
});
25122577
def_may_throw_on_access(AST_Assign, function(compressor) {
2578+
if (this.logical) return true;
2579+
25132580
return this.operator == "="
25142581
&& this.right._dot_throw(compressor);
25152582
});
@@ -3734,6 +3801,7 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
37343801
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
37353802
const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
37363803
if (node instanceof AST_Assign
3804+
&& !node.logical
37373805
&& (has_flag(node, WRITE_ONLY) || node.operator == "=")
37383806
) {
37393807
return node.left;
@@ -3952,6 +4020,7 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
39524020
sym.references.push(ref);
39534021
var assign = make_node(AST_Assign, def, {
39544022
operator: "=",
4023+
logical: false,
39554024
left: ref,
39564025
right: def.value
39574026
});
@@ -4387,6 +4456,8 @@ AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
43874456
}
43884457
});
43894458
def_drop_side_effect_free(AST_Assign, function(compressor) {
4459+
if (this.logical) return this;
4460+
43904461
var left = this.left;
43914462
if (left.has_side_effects(compressor)
43924463
|| compressor.has_directive("use strict")
@@ -4983,6 +5054,7 @@ AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
49835054
var name = make_node(AST_SymbolRef, def.name, def.name);
49845055
assignments.push(make_node(AST_Assign, def, {
49855056
operator : "=",
5057+
logical: false,
49865058
left : name,
49875059
right : def.value
49885060
}));
@@ -5597,6 +5669,7 @@ def_optimize(AST_Call, function(self, compressor) {
55975669
def.references.push(sym);
55985670
if (value) expressions.push(make_node(AST_Assign, self, {
55995671
operator: "=",
5672+
logical: false,
56005673
left: sym,
56015674
right: value.clone()
56025675
}));
@@ -5640,6 +5713,7 @@ def_optimize(AST_Call, function(self, compressor) {
56405713
def.references.push(sym);
56415714
expressions.splice(pos++, 0, make_node(AST_Assign, var_def, {
56425715
operator: "=",
5716+
logical: false,
56435717
left: sym,
56445718
right: make_node(AST_Undefined, name)
56455719
}));
@@ -6597,6 +6671,10 @@ function is_reachable(self, defs) {
65976671
const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
65986672
const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
65996673
def_optimize(AST_Assign, function(self, compressor) {
6674+
if (self.logical) {
6675+
return self.lift_sequences(compressor);
6676+
}
6677+
66006678
var def;
66016679
if (compressor.option("dead_code")
66026680
&& self.left instanceof AST_SymbolRef
@@ -6814,16 +6892,20 @@ def_optimize(AST_Conditional, function(self, compressor) {
68146892
// |
68156893
// v
68166894
// exp = foo ? something : something_else;
6817-
if (consequent instanceof AST_Assign
6895+
if (
6896+
consequent instanceof AST_Assign
68186897
&& alternative instanceof AST_Assign
6819-
&& consequent.operator == alternative.operator
6898+
&& consequent.operator === alternative.operator
6899+
&& consequent.logical === alternative.logical
68206900
&& consequent.left.equivalent_to(alternative.left)
68216901
&& (!self.condition.has_side_effects(compressor)
68226902
|| consequent.operator == "="
6823-
&& !consequent.left.has_side_effects(compressor))) {
6903+
&& !consequent.left.has_side_effects(compressor))
6904+
) {
68246905
return make_node(AST_Assign, self, {
68256906
operator: consequent.operator,
68266907
left: consequent.left,
6908+
logical: consequent.logical,
68276909
right: make_node(AST_Conditional, self, {
68286910
condition: self.condition,
68296911
consequent: consequent.right,

0 commit comments

Comments
 (0)