| Index: lib/src/compiler/js_metalet.dart
 | 
| diff --git a/lib/src/compiler/js_metalet.dart b/lib/src/compiler/js_metalet.dart
 | 
| index 5bddf5a8e65fea24f71ee543016f750da4c3ab60..c0581d638a1ed6f1ac10fdcff68c98b7c63f954c 100644
 | 
| --- a/lib/src/compiler/js_metalet.dart
 | 
| +++ b/lib/src/compiler/js_metalet.dart
 | 
| @@ -36,7 +36,7 @@ class MetaLet extends Expression {
 | 
|    /// If the expression does not end up using `x` more than once, or if those
 | 
|    /// expressions can be treated as [stateless] (e.g. they are non-mutated
 | 
|    /// variables), then the resulting code will be simplified automatically.
 | 
| -  final Map<String, Expression> variables;
 | 
| +  final Map<MetaLetVariable, Expression> variables;
 | 
|  
 | 
|    /// A list of expressions in the body.
 | 
|    /// The last value should represent the returned value.
 | 
| @@ -171,49 +171,40 @@ class MetaLet extends Expression {
 | 
|    }
 | 
|  
 | 
|    Block _finishStatement(List<Statement> statements) {
 | 
| -    var params = <TemporaryId>[];
 | 
| -    var values = <Expression>[];
 | 
| -    var block = _build(params, values, new Block(statements));
 | 
| -    if (params.isEmpty) return block;
 | 
| -
 | 
| -    var vars = [];
 | 
| -    for (int i = 0; i < params.length; i++) {
 | 
| -      vars.add(new VariableInitialization(params[i], values[i]));
 | 
| -    }
 | 
| -
 | 
| -    return new Block(<Statement>[
 | 
| -      new VariableDeclarationList('let', vars).toStatement(),
 | 
| -      block
 | 
| -    ]);
 | 
| -  }
 | 
| -
 | 
| -  Node _build(List<TemporaryId> params, List<Expression> values, Node node) {
 | 
|      // Visit the tree and count how many times each temp was used.
 | 
|      var counter = new _VariableUseCounter();
 | 
| +    var node = new Block(statements);
 | 
|      node.accept(counter);
 | 
|      // Also count the init expressions.
 | 
|      for (var init in variables.values) init.accept(counter);
 | 
|  
 | 
| -    var substitutions = {};
 | 
| -    _substitute(node) => new Template(null, node).safeCreate(substitutions);
 | 
| -
 | 
| -    variables.forEach((name, init) {
 | 
| +    var initializers = <VariableInitialization>[];
 | 
| +    var substitutions = <MetaLetVariable, Expression>{};
 | 
| +    variables.forEach((variable, init) {
 | 
|        // Since this is let*, subsequent variables can refer to previous ones,
 | 
|        // so we need to substitute here.
 | 
| -      init = _substitute(init);
 | 
| -      int n = counter.counts[name];
 | 
| -      if (n == null || n < 2) {
 | 
| -        substitutions[name] = _substitute(init);
 | 
| +      init = _substitute(init, substitutions);
 | 
| +      int n = counter.counts[variable];
 | 
| +      if (n == 1) {
 | 
| +        // Replace interpolated exprs with their value, if it only occurs once.
 | 
| +        substitutions[variable] = init;
 | 
|        } else {
 | 
| -        params.add(substitutions[name] = new TemporaryId(name));
 | 
| -        values.add(init);
 | 
| +        // Otherwise replace it with a temp, which will be assigned once.
 | 
| +        var temp = new TemporaryId(variable.displayName);
 | 
| +        substitutions[variable] = temp;
 | 
| +        initializers.add(new VariableInitialization(temp, init));
 | 
|        }
 | 
|      });
 | 
|  
 | 
| -    // Interpolate the body:
 | 
| -    // Replace interpolated exprs with their value, if it only occurs once.
 | 
| -    // Otherwise replace it with a temp, which will be assigned once.
 | 
| -    return _substitute(node);
 | 
| +    // Interpolate the body.
 | 
| +    node = _substitute(node, substitutions);
 | 
| +    if (initializers.isNotEmpty) {
 | 
| +      node = new Block([
 | 
| +        new VariableDeclarationList('let', initializers).toStatement(),
 | 
| +        node
 | 
| +      ]);
 | 
| +    }
 | 
| +    return node;
 | 
|    }
 | 
|  
 | 
|    /// If we finish with an assignment to an identifier, try to simplify the
 | 
| @@ -231,11 +222,10 @@ class MetaLet extends Expression {
 | 
|    ///
 | 
|    MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) {
 | 
|      // See if the result value is a let* temporary variable.
 | 
| -    if (body.last is! InterpolatedExpression) return null;
 | 
| +    if (body.last is! MetaLetVariable) return null;
 | 
|  
 | 
| -    InterpolatedExpression last = body.last;
 | 
| -    String name = last.nameOrPosition;
 | 
| -    if (!variables.containsKey(name)) 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.
 | 
| @@ -252,8 +242,8 @@ class MetaLet extends Expression {
 | 
|        if (finder.found) return null;
 | 
|      }
 | 
|  
 | 
| -    var vars = new Map<String, Expression>.from(variables);
 | 
| -    var value = vars.remove(name);
 | 
| +    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
 | 
| @@ -266,18 +256,59 @@ class MetaLet extends Expression {
 | 
|      }
 | 
|  
 | 
|      var newBody = new Expression.binary([assign]..addAll(body), ',');
 | 
| -    Binary comma = new Template(null, newBody).safeCreate({name: left});
 | 
| -    return new MetaLet(vars, comma.commaToExpressionList(),
 | 
| +    newBody = _substitute(newBody, {result: left});
 | 
| +    return new MetaLet(vars, newBody.commaToExpressionList(),
 | 
|          statelessResult: statelessResult);
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +/// Similar to [Template.instantiate] but works with free variables.
 | 
| +Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) {
 | 
| +  var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false);
 | 
| +  var instantiator = generator.compile(tree);
 | 
| +  var nodes = new List<MetaLetVariable>.from(generator
 | 
| +      .analysis.containsInterpolatedNode
 | 
| +      .where((n) => n is MetaLetVariable));
 | 
| +  if (nodes.isEmpty) return tree;
 | 
| +
 | 
| +  return instantiator(new Map.fromIterable(nodes,
 | 
| +      key: (v) => (v as MetaLetVariable).nameOrPosition,
 | 
| +      value: (v) => substitutions[v] ?? v));
 | 
| +}
 | 
| +
 | 
| +/// A temporary variable used in a [MetaLet].
 | 
| +///
 | 
| +/// Each instance of this class represents a fresh variable. The same object
 | 
| +/// should be used everywhere to refer to the same variable. Different variables
 | 
| +/// with the same name are different, and will be renamed later on, if needed.
 | 
| +///
 | 
| +/// These variables will be replaced when the `let*` is complete, depending on
 | 
| +/// how often they occur and whether they can be optimized away. See [MetaLet]
 | 
| +/// for more information.
 | 
| +///
 | 
| +/// This class should never reach our final JS code.
 | 
| +class MetaLetVariable extends InterpolatedExpression {
 | 
| +  /// The suggested display name of this variable.
 | 
| +  ///
 | 
| +  /// This name should not be used
 | 
| +  final String displayName;
 | 
| +
 | 
| +  /// Compute fresh IDs to avoid
 | 
| +  static int _uniqueId = 0;
 | 
| +
 | 
| +  MetaLetVariable(String displayName)
 | 
| +      : displayName = displayName,
 | 
| +        super(displayName + '@${++_uniqueId}');
 | 
| +}
 | 
| +
 | 
|  class _VariableUseCounter extends BaseVisitor {
 | 
| -  final counts = <String, int>{};
 | 
| +  final counts = <MetaLetVariable, int>{};
 | 
|    @override
 | 
|    visitInterpolatedExpression(InterpolatedExpression node) {
 | 
| -    int n = counts[node.nameOrPosition];
 | 
| -    counts[node.nameOrPosition] = n == null ? 1 : n + 1;
 | 
| +    if (node is MetaLetVariable) {
 | 
| +      int n = counts[node];
 | 
| +      counts[node] = n == null ? 1 : n + 1;
 | 
| +    }
 | 
|    }
 | 
|  }
 | 
|  
 | 
| 
 |