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) => |