Index: pkg/dev_compiler/lib/src/compiler/js_metalet.dart |
diff --git a/pkg/dev_compiler/lib/src/compiler/js_metalet.dart b/pkg/dev_compiler/lib/src/compiler/js_metalet.dart |
index 7ae35a70be63671a786da028b8245c7774fee808..7ec0f6ec3c7834211473f85266d3f41d72df7a58 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/js_metalet.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/js_metalet.dart |
@@ -65,22 +65,43 @@ class MetaLet extends Expression { |
return _toInvokedFunction(block); |
} |
- Expression toAssignExpression(Expression left) { |
+ Expression toAssignExpression(Expression left, [String op]) { |
if (left is Identifier) { |
- var simple = _simplifyAssignment(left); |
- if (simple != null) return simple; |
- |
- var exprs = body.toList(); |
- exprs.add(exprs.removeLast().toAssignExpression(left)); |
- return new MetaLet(variables, exprs); |
+ return _simplifyAssignment(left, op: op) ?? _toAssign(left, op); |
+ } else if (left is PropertyAccess && |
+ left.receiver is This && |
+ (left.selector is Identifier || left.selector is LiteralString)) { |
+ return _toAssign(left, op); |
} |
- return super.toAssignExpression(left); |
+ return super.toAssignExpression(left, op); |
+ } |
+ |
+ Expression _toAssign(Expression left, [String op]) { |
+ var exprs = body.toList(); |
+ exprs.add(exprs.removeLast().toAssignExpression(left, op)); |
+ return new MetaLet(variables, exprs); |
} |
Statement toVariableDeclaration(Identifier name) { |
var simple = _simplifyAssignment(name, isDeclaration: true); |
if (simple != null) return simple.toStatement(); |
- return super.toVariableDeclaration(name); |
+ |
+ // We can still optimize something like: |
+ // |
+ // let x = ((l) => l == null ? null : l.xyz)(some.expr); |
+ // |
+ // can be transformed to: |
+ // |
+ // let l = some.expr; |
+ // let x = l == null ? null : l.xyz; |
+ // |
+ // Because `x` is a declaration, we know it is safe to move. |
+ // (see also _toAssign) |
+ var statements = body |
+ .map((e) => |
+ e == body.last ? e.toVariableDeclaration(name) : e.toStatement()) |
+ .toList(); |
+ return _finishStatement(statements); |
} |
Expression toExpression() { |
@@ -198,8 +219,11 @@ class MetaLet extends Expression { |
// Interpolate the body. |
node = _substitute(node, substitutions); |
if (initializers.isNotEmpty) { |
+ var first = initializers[0]; |
node = new Block([ |
- new VariableDeclarationList('let', initializers).toStatement(), |
+ initializers.length == 1 |
+ ? first.value.toVariableDeclaration(first.declaration) |
+ : new VariableDeclarationList('let', initializers).toStatement(), |
node |
]); |
} |
@@ -209,7 +233,7 @@ class MetaLet extends Expression { |
/// If we finish with an assignment to an identifier, try to simplify the |
/// block. For example: |
/// |
- /// ((_) => _.add(1), _.add(2), result = _)([]) |
+ /// result = ((_) => _.add(1), _.add(2), _)([]) |
/// |
/// Can be transformed to: |
/// |
@@ -217,48 +241,37 @@ class MetaLet extends Expression { |
/// |
/// However we should not simplify in this case because `result` is read: |
/// |
- /// ((_) => _.addAll(result), _.add(2), result = _)([]) |
+ /// result = ((_) => _.addAll(result), _.add(2), _)([]) |
/// |
- MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { |
+ MetaLet _simplifyAssignment(Identifier left, |
+ {String op, bool isDeclaration: false}) { |
// See if the result value is a let* temporary variable. |
- if (body.last is! MetaLetVariable) return null; |
- |
- MetaLetVariable result = body.last; |
- if (!variables.containsKey(result)) return null; |
- |
- // Variables declared can't be used inside their initializer, so make |
- // sure we don't transform an assignment into an initializer. |
- // If this already was a declaration, then we know it's legal, so we can |
- // skip the check. |
- if (!isDeclaration) { |
- var finder = new _IdentFinder(left.name); |
- for (var expr in body) { |
- if (finder.found) break; |
- expr.accept(finder); |
+ var result = body.last; |
+ if (result is MetaLetVariable && variables.containsKey(result)) { |
+ // For assignments, make sure the identifier isn't used in body, as that |
+ // would change the assignment order and be an invalid optimization. |
+ if (!isDeclaration && _IdentFinder.foundIn(left.name, body)) return null; |
+ |
+ var vars = new Map<MetaLetVariable, Expression>.from(variables); |
+ var value = vars.remove(result); |
+ Expression assign; |
+ if (isDeclaration) { |
+ // Technically, putting one of these in a comma expression is not |
+ // legal. However when isDeclaration is true, toStatement will be |
+ // called immediately on the MetaLet, which results in legal JS. |
+ assign = new VariableDeclarationList( |
+ 'let', [new VariableInitialization(left, value)]); |
+ } else { |
+ assign = value.toAssignExpression(left, op); |
} |
- // If the identifier was used elsewhere, bail, because we're going to |
- // change the order of when the assignment happens. |
- if (finder.found) return null; |
- } |
- var vars = new Map<MetaLetVariable, Expression>.from(variables); |
- var value = vars.remove(result); |
- Expression assign; |
- if (isDeclaration) { |
- // Technically, putting one of these in a comma expression is not |
- // legal. However when isDeclaration is true, toStatement will be |
- // called immediately on the MetaLet, which results in legal JS. |
- assign = new VariableDeclarationList( |
- 'let', [new VariableInitialization(left, value)]); |
- } else { |
- assign = value.toAssignExpression(left); |
+ assert(body.isNotEmpty); |
+ Binary newBody = new Expression.binary([assign]..addAll(body), ','); |
+ newBody = _substitute(newBody, {result: left}); |
+ return new MetaLet(vars, newBody.commaToExpressionList(), |
+ statelessResult: statelessResult); |
} |
- |
- assert(body.isNotEmpty); |
- Binary newBody = new Expression.binary([assign]..addAll(body), ','); |
- newBody = _substitute(newBody, {result: left}); |
- return new MetaLet(vars, newBody.commaToExpressionList(), |
- statelessResult: statelessResult); |
+ return null; |
} |
} |
@@ -317,6 +330,15 @@ class _IdentFinder extends BaseVisitor { |
bool found = false; |
_IdentFinder(this.name); |
+ static bool foundIn(String name, List<Node> body) { |
+ var finder = new _IdentFinder(name); |
+ for (var expr in body) { |
+ expr.accept(finder); |
+ if (finder.found) return true; |
+ } |
+ return false; |
+ } |
+ |
@override |
visitIdentifier(Identifier node) { |
if (node.name == name) found = true; |