Skip to content

Commit 77e62cc

Browse files
author
John Messerly
committed
implement private members, fixes #74
naturally, lots of things to iterate on, but this expresses the key idea (per-library ES6 symbols for privacy). [email protected] Review URL: https://codereview.chromium.org/963343002
1 parent a3aebf0 commit 77e62cc

File tree

15 files changed

+4318
-3693
lines changed

15 files changed

+4318
-3693
lines changed

pkg/dev_compiler/lib/src/codegen/js_codegen.dart

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
library ddc.src.codegen.js_codegen;
66

7+
import 'dart:collection' show HashSet;
78
import 'dart:io' show Directory, File;
89

910
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
@@ -46,6 +47,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
4647
final _exports = <String>[];
4748
final _lazyFields = <VariableDeclaration>[];
4849
final _properties = <FunctionDeclaration>[];
50+
final _privateNames = new HashSet<String>();
51+
final _pendingPrivateNames = <String>[];
4952

5053
JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules)
5154
: libraryInfo = libraryInfo,
@@ -77,15 +80,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
7780
var name = jsLibraryName(libraryInfo.library);
7881
return new JS.Block([
7982
js.statement('var #;', name),
80-
js.statement('''
81-
(function ($_EXPORTS) {
82-
'use strict';
83-
#;
84-
})(# || (# = {}));
85-
''', [body, name, name])
83+
js.statement("(function($_EXPORTS) { 'use strict'; #; })(# || (# = {}));",
84+
[body, name, name])
8685
]);
8786
}
8887

88+
JS.Statement _initPrivateSymbol(String name) =>
89+
js.statement('let # = Symbol(#);', [name, js.string(name, "'")]);
90+
8991
@override
9092
JS.Statement visitCompilationUnit(CompilationUnit node) {
9193
// TODO(jmesserly): scriptTag, directives.
@@ -96,11 +98,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
9698
if (child is! FunctionDeclaration) _flushLibraryProperties(body);
9799

98100
var code = _visit(child);
99-
if (code != null) body.add(code);
101+
102+
if (code != null) {
103+
if (_pendingPrivateNames.isNotEmpty) {
104+
body.addAll(_pendingPrivateNames.map(_initPrivateSymbol));
105+
_pendingPrivateNames.clear();
106+
}
107+
body.add(code);
108+
}
100109
}
110+
101111
// Flush any unwritten fields/properties.
102112
_flushLazyFields(body);
103113
_flushLibraryProperties(body);
114+
115+
assert(_pendingPrivateNames.isEmpty);
104116
return _statement(body);
105117
}
106118

@@ -402,7 +414,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
402414
var className = classDecl.name.name;
403415

404416
var name = _constructorName(className, node.constructorName);
405-
return js.statement('this.#(#);', [name, _visit(node.argumentList)]);
417+
return js.statement(
418+
'this.#(#);', [_jsMemberName(name), _visit(node.argumentList)]);
406419
}
407420

408421
JS.Statement _superConstructorCall(ClassDeclaration clazz,
@@ -452,7 +465,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
452465
if (p is DefaultFormalParameter) p = p.parameter;
453466
if (p is FieldFormalParameter) {
454467
var name = p.identifier.name;
455-
body.add(js.statement('this.# = #;', [name, name]));
468+
body.add(
469+
js.statement('this.# = #;', [_jsMemberName(name), _visit(p)]));
456470
unsetFields.remove(name);
457471
}
458472
}
@@ -482,7 +496,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
482496
value = new JS.LiteralNull();
483497
}
484498
}
485-
body.add(js.statement('this.# = #;', [name, value]));
499+
body.add(js.statement('this.# = #;', [_jsMemberName(name), value]));
486500
});
487501

488502
return _statement(body);
@@ -550,8 +564,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
550564
var params = _visit(node.parameters);
551565
if (params == null) params = [];
552566

553-
return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)),
554-
new JS.Fun(params, _visit(node.body)),
567+
return new JS.Method(
568+
_jsMemberName(node.name.name), new JS.Fun(params, _visit(node.body)),
555569
isGetter: node.isGetter,
556570
isSetter: node.isSetter,
557571
isStatic: node.isStatic);
@@ -643,7 +657,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
643657
(e.library != libraryInfo.library || _needsModuleGetter(e))) {
644658
return js.call('#.#', [_libraryName(e.library), name]);
645659
} else if (currentClass != null && _needsImplicitThis(e)) {
646-
return js.call('this.#', name);
660+
return js.call('this.#', _jsMemberName(name));
661+
} else if (e is ParameterElement && e.isInitializingFormal) {
662+
name = _fieldParameterName(name);
647663
}
648664
return new JS.VariableUse(name);
649665
}
@@ -850,7 +866,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
850866
result.add(new JS.Parameter(_jsNamedParameterName));
851867
break;
852868
}
853-
result.add(new JS.Parameter(param.identifier.name));
869+
result.add(_visit(param));
854870
}
855871
return result;
856872
}
@@ -1213,12 +1229,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
12131229
_visit(node.expression);
12141230

12151231
@override
1216-
visitSimpleFormalParameter(SimpleFormalParameter node) =>
1217-
_visit(node.identifier);
1232+
visitFormalParameter(FormalParameter node) =>
1233+
new JS.Parameter(node.identifier.name);
12181234

12191235
@override
1220-
visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) =>
1221-
_visit(node.identifier);
1236+
visitFieldFormalParameter(FieldFormalParameter node) =>
1237+
new JS.Parameter(_fieldParameterName(node.identifier.name));
1238+
1239+
/// Rename private names so they don't shadow the private field symbol.
1240+
// TODO(jmesserly): replace this ad-hoc rename with a general strategy.
1241+
_fieldParameterName(name) => name.startsWith('_') ? '\$$name' : name;
12221242

12231243
@override
12241244
JS.This visitThisExpression(ThisExpression node) => new JS.This();
@@ -1245,7 +1265,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
12451265
return js.call(
12461266
'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]);
12471267
} else {
1248-
return js.call('#.#', [_visit(target), name.name]);
1268+
return js.call('#.#', [_visit(target), _jsMemberName(name.name)]);
12491269
}
12501270
}
12511271

@@ -1289,7 +1309,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
12891309
}
12901310

12911311
@override
1292-
visitRethrowExpression(RethrowExpression node){
1312+
visitRethrowExpression(RethrowExpression node) {
12931313
if (node.parent is ExpressionStatement) {
12941314
return js.statement('throw #;', _catchParameter);
12951315
} else {
@@ -1611,28 +1631,60 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
16111631
return result;
16121632
}
16131633

1614-
/// The following names are allowed for user-defined operators:
1634+
/// This handles member renaming for private names and operators.
1635+
///
1636+
/// Private names are generated using ES6 symbols:
1637+
///
1638+
/// // At the top of the module:
1639+
/// let _x = Symbol('_x');
1640+
/// let _y = Symbol('_y');
1641+
/// ...
1642+
///
1643+
/// class Point {
1644+
/// Point(x, y) {
1645+
/// this[_x] = x;
1646+
/// this[_y] = y;
1647+
/// }
1648+
/// get x() { return this[_x]; }
1649+
/// get y() { return this[_y]; }
1650+
/// }
1651+
///
1652+
/// For user-defined operators the following names are allowed:
16151653
///
16161654
/// <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜
16171655
///
1618-
/// For the indexing operators, we use `get` and `set` instead:
1656+
/// They generate code like:
1657+
///
1658+
/// x['+'](y)
1659+
///
1660+
/// There are three exceptions: [], []= and unary -.
1661+
/// The indexing operators we use `get` and `set` instead:
16191662
///
16201663
/// x.get('hi')
16211664
/// x.set('hi', 123)
16221665
///
16231666
/// This follows the same pattern as EcmaScript 6 Map:
16241667
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map>
16251668
///
1626-
/// For all others we use the operator name:
1627-
///
1628-
/// x['+'](y)
1669+
/// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed
1670+
/// for this transformation to happen, otherwise binary minus is assumed.
16291671
///
16301672
/// Equality is a bit special, it is generated via the Dart `equals` runtime
16311673
/// helper, that checks for null. The user defined method is called '=='.
1632-
String _jsMethodName(String name) {
1633-
if (name == '[]') return 'get';
1634-
if (name == '[]=') return 'set';
1635-
return name;
1674+
///
1675+
JS.Expression _jsMemberName(String name, {bool unary: false}) {
1676+
if (name.startsWith('_')) {
1677+
if (_privateNames.add(name)) _pendingPrivateNames.add(name);
1678+
return new JS.VariableUse(name);
1679+
}
1680+
if (name == '[]') {
1681+
name = 'get';
1682+
} else if (name == '[]=') {
1683+
name = 'set';
1684+
} else if (unary && name == '-') {
1685+
name = 'unary-';
1686+
}
1687+
return new JS.PropertyName(name);
16361688
}
16371689

16381690
bool _externalOrNative(node) =>

pkg/dev_compiler/lib/src/js/printer.dart

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -867,21 +867,7 @@ class Printer implements NodeVisitor {
867867
visitNestedExpression(access.receiver, CALL,
868868
newInForInit: inForInit,
869869
newAtStatementBegin: atStatementBegin);
870-
Node selector = access.selector;
871-
if (selector is LiteralString) {
872-
LiteralString selectorString = selector;
873-
String fieldWithQuotes = selectorString.value;
874-
if (isValidJavaScriptId(fieldWithQuotes)) {
875-
if (access.receiver is LiteralNumber) out(" ");
876-
out(".");
877-
out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
878-
return;
879-
}
880-
}
881-
out("[");
882-
visitNestedExpression(selector, EXPRESSION,
883-
newInForInit: false, newAtStatementBegin: false);
884-
out("]");
870+
propertyNameOut(access.selector, inAccess: true);
885871
}
886872

887873
visitNamedFunction(NamedFunction namedFunction) {
@@ -1084,13 +1070,14 @@ class Printer implements NodeVisitor {
10841070

10851071
visitPropertyName(PropertyName node) => propertyNameOut(node);
10861072

1087-
void propertyNameOut(Expression node, {bool inMethod: false}) {
1088-
inForInit = false;
1089-
atStatementBegin = false;
1073+
void propertyNameOut(Expression node, {bool inMethod: false,
1074+
bool inAccess: false}) {
10901075

10911076
if (node is LiteralNumber) {
10921077
LiteralNumber nameNumber = node;
1078+
if (inAccess) out('[');
10931079
out(nameNumber.value);
1080+
if (inAccess) out(']');
10941081
} else {
10951082
String quotedName;
10961083
if (node is PropertyName) {
@@ -1100,16 +1087,18 @@ class Printer implements NodeVisitor {
11001087
}
11011088
if (quotedName != null) {
11021089
if (isValidJavaScriptId(quotedName)) {
1090+
if (inAccess) out('.');
11031091
out(quotedName.substring(1, quotedName.length - 1));
11041092
} else {
1105-
if (inMethod) out("[");
1093+
if (inMethod || inAccess) out("[");
11061094
out(quotedName);
1107-
if (inMethod) out("]");
1095+
if (inMethod || inAccess) out("]");
11081096
}
11091097
} else {
11101098
// ComputedPropertyName
11111099
out("[");
1112-
visit(node);
1100+
visitNestedExpression(node, EXPRESSION,
1101+
newInForInit: false, newAtStatementBegin: false);
11131102
out("]");
11141103
}
11151104
}

0 commit comments

Comments
 (0)