| Index: lib/src/codegen/js_codegen.dart | 
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart | 
| index 628d7a1e95a80bb5c7a80475896653929a4c6611..c4ee4abd98939e4bc085b7502e25d1c3390e8edc 100644 | 
| --- a/lib/src/codegen/js_codegen.dart | 
| +++ b/lib/src/codegen/js_codegen.dart | 
| @@ -4,6 +4,7 @@ | 
|  | 
| library ddc.src.codegen.js_codegen; | 
|  | 
| +import 'dart:collection' show HashSet; | 
| import 'dart:io' show Directory, File; | 
|  | 
| import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 
| @@ -46,6 +47,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| final _exports = <String>[]; | 
| final _lazyFields = <VariableDeclaration>[]; | 
| final _properties = <FunctionDeclaration>[]; | 
| +  final _privateNames = new HashSet<String>(); | 
| +  final _pendingPrivateNames = <String>[]; | 
|  | 
| JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules) | 
| : libraryInfo = libraryInfo, | 
| @@ -77,15 +80,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| var name = jsLibraryName(libraryInfo.library); | 
| return new JS.Block([ | 
| js.statement('var #;', name), | 
| -      js.statement(''' | 
| -        (function ($_EXPORTS) { | 
| -          'use strict'; | 
| -          #; | 
| -        })(# || (# = {})); | 
| -      ''', [body, name, name]) | 
| +      js.statement("(function($_EXPORTS) { 'use strict'; #; })(# || (# = {}));", | 
| +          [body, name, name]) | 
| ]); | 
| } | 
|  | 
| +  JS.Statement _initPrivateSymbol(String name) => | 
| +      js.statement('let # = Symbol(#);', [name, js.string(name, "'")]); | 
| + | 
| @override | 
| JS.Statement visitCompilationUnit(CompilationUnit node) { | 
| // TODO(jmesserly): scriptTag, directives. | 
| @@ -96,11 +98,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 
|  | 
| var code = _visit(child); | 
| -      if (code != null) body.add(code); | 
| + | 
| +      if (code != null) { | 
| +        if (_pendingPrivateNames.isNotEmpty) { | 
| +          body.addAll(_pendingPrivateNames.map(_initPrivateSymbol)); | 
| +          _pendingPrivateNames.clear(); | 
| +        } | 
| +        body.add(code); | 
| +      } | 
| } | 
| + | 
| // Flush any unwritten fields/properties. | 
| _flushLazyFields(body); | 
| _flushLibraryProperties(body); | 
| + | 
| +    assert(_pendingPrivateNames.isEmpty); | 
| return _statement(body); | 
| } | 
|  | 
| @@ -402,7 +414,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| var className = classDecl.name.name; | 
|  | 
| var name = _constructorName(className, node.constructorName); | 
| -    return js.statement('this.#(#);', [name, _visit(node.argumentList)]); | 
| +    return js.statement( | 
| +        'this.#(#);', [_jsMemberName(name), _visit(node.argumentList)]); | 
| } | 
|  | 
| JS.Statement _superConstructorCall(ClassDeclaration clazz, | 
| @@ -452,7 +465,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| if (p is DefaultFormalParameter) p = p.parameter; | 
| if (p is FieldFormalParameter) { | 
| var name = p.identifier.name; | 
| -          body.add(js.statement('this.# = #;', [name, name])); | 
| +          body.add( | 
| +              js.statement('this.# = #;', [_jsMemberName(name), _visit(p)])); | 
| unsetFields.remove(name); | 
| } | 
| } | 
| @@ -482,7 +496,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| value = new JS.LiteralNull(); | 
| } | 
| } | 
| -      body.add(js.statement('this.# = #;', [name, value])); | 
| +      body.add(js.statement('this.# = #;', [_jsMemberName(name), value])); | 
| }); | 
|  | 
| return _statement(body); | 
| @@ -550,8 +564,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| var params = _visit(node.parameters); | 
| if (params == null) params = []; | 
|  | 
| -    return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)), | 
| -        new JS.Fun(params, _visit(node.body)), | 
| +    return new JS.Method( | 
| +        _jsMemberName(node.name.name), new JS.Fun(params, _visit(node.body)), | 
| isGetter: node.isGetter, | 
| isSetter: node.isSetter, | 
| isStatic: node.isStatic); | 
| @@ -643,7 +657,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| (e.library != libraryInfo.library || _needsModuleGetter(e))) { | 
| return js.call('#.#', [_libraryName(e.library), name]); | 
| } else if (currentClass != null && _needsImplicitThis(e)) { | 
| -      return js.call('this.#', name); | 
| +      return js.call('this.#', _jsMemberName(name)); | 
| +    } else if (e is ParameterElement && e.isInitializingFormal) { | 
| +      name = _fieldParameterName(name); | 
| } | 
| return new JS.VariableUse(name); | 
| } | 
| @@ -850,7 +866,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| result.add(new JS.Parameter(_jsNamedParameterName)); | 
| break; | 
| } | 
| -      result.add(new JS.Parameter(param.identifier.name)); | 
| +      result.add(_visit(param)); | 
| } | 
| return result; | 
| } | 
| @@ -1213,12 +1229,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| _visit(node.expression); | 
|  | 
| @override | 
| -  visitSimpleFormalParameter(SimpleFormalParameter node) => | 
| -      _visit(node.identifier); | 
| +  visitFormalParameter(FormalParameter node) => | 
| +      new JS.Parameter(node.identifier.name); | 
|  | 
| @override | 
| -  visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) => | 
| -      _visit(node.identifier); | 
| +  visitFieldFormalParameter(FieldFormalParameter node) => | 
| +      new JS.Parameter(_fieldParameterName(node.identifier.name)); | 
| + | 
| +  /// Rename private names so they don't shadow the private field symbol. | 
| +  // TODO(jmesserly): replace this ad-hoc rename with a general strategy. | 
| +  _fieldParameterName(name) => name.startsWith('_') ? '\$$name' : name; | 
|  | 
| @override | 
| JS.This visitThisExpression(ThisExpression node) => new JS.This(); | 
| @@ -1245,7 +1265,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| return js.call( | 
| 'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]); | 
| } else { | 
| -      return js.call('#.#', [_visit(target), name.name]); | 
| +      return js.call('#.#', [_visit(target), _jsMemberName(name.name)]); | 
| } | 
| } | 
|  | 
| @@ -1289,7 +1309,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| } | 
|  | 
| @override | 
| -  visitRethrowExpression(RethrowExpression node){ | 
| +  visitRethrowExpression(RethrowExpression node) { | 
| if (node.parent is ExpressionStatement) { | 
| return js.statement('throw #;', _catchParameter); | 
| } else { | 
| @@ -1611,11 +1631,34 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| return result; | 
| } | 
|  | 
| -  /// The following names are allowed for user-defined operators: | 
| +  /// This handles member renaming for private names and operators. | 
| +  /// | 
| +  /// Private names are generated using ES6 symbols: | 
| +  /// | 
| +  ///     // At the top of the module: | 
| +  ///     let _x = Symbol('_x'); | 
| +  ///     let _y = Symbol('_y'); | 
| +  ///     ... | 
| +  /// | 
| +  ///     class Point { | 
| +  ///       Point(x, y) { | 
| +  ///         this[_x] = x; | 
| +  ///         this[_y] = y; | 
| +  ///       } | 
| +  ///       get x() { return this[_x]; } | 
| +  ///       get y() { return this[_y]; } | 
| +  ///     } | 
| +  /// | 
| +  /// For user-defined operators the following names are allowed: | 
| /// | 
| ///     <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜ | 
| /// | 
| -  /// For the indexing operators, we use `get` and `set` instead: | 
| +  /// They generate code like: | 
| +  /// | 
| +  ///     x['+'](y) | 
| +  /// | 
| +  /// There are three exceptions: [], []= and unary -. | 
| +  /// The indexing operators we use `get` and `set` instead: | 
| /// | 
| ///     x.get('hi') | 
| ///     x.set('hi', 123) | 
| @@ -1623,16 +1666,25 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 
| /// This follows the same pattern as EcmaScript 6 Map: | 
| /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map> | 
| /// | 
| -  /// For all others we use the operator name: | 
| -  /// | 
| -  ///     x['+'](y) | 
| +  /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed | 
| +  /// for this transformation to happen, otherwise binary minus is assumed. | 
| /// | 
| /// Equality is a bit special, it is generated via the Dart `equals` runtime | 
| /// helper, that checks for null. The user defined method is called '=='. | 
| -  String _jsMethodName(String name) { | 
| -    if (name == '[]') return 'get'; | 
| -    if (name == '[]=') return 'set'; | 
| -    return name; | 
| +  /// | 
| +  JS.Expression _jsMemberName(String name, {bool unary: false}) { | 
| +    if (name.startsWith('_')) { | 
| +      if (_privateNames.add(name)) _pendingPrivateNames.add(name); | 
| +      return new JS.VariableUse(name); | 
| +    } | 
| +    if (name == '[]') { | 
| +      name = 'get'; | 
| +    } else if (name == '[]=') { | 
| +      name = 'set'; | 
| +    } else if (unary && name == '-') { | 
| +      name = 'unary-'; | 
| +    } | 
| +    return new JS.PropertyName(name); | 
| } | 
|  | 
| bool _externalOrNative(node) => | 
|  |