Skip to content

Commit

Permalink
implement private members, fixes #74
Browse files Browse the repository at this point in the history
naturally, lots of things to iterate on, but this expresses the key idea (per-library ES6 symbols for privacy).

R=vsm@google.com

Review URL: https://codereview.chromium.org/963343002
  • Loading branch information
John Messerly committed Mar 3, 2015
1 parent a3aebf0 commit 77e62cc
Show file tree
Hide file tree
Showing 15 changed files with 4,318 additions and 3,693 deletions.
110 changes: 81 additions & 29 deletions pkg/dev_compiler/lib/src/codegen/js_codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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);
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand All @@ -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)]);
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1611,28 +1631,60 @@ 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)
///
/// 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) =>
Expand Down
31 changes: 10 additions & 21 deletions pkg/dev_compiler/lib/src/js/printer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -867,21 +867,7 @@ class Printer implements NodeVisitor {
visitNestedExpression(access.receiver, CALL,
newInForInit: inForInit,
newAtStatementBegin: atStatementBegin);
Node selector = access.selector;
if (selector is LiteralString) {
LiteralString selectorString = selector;
String fieldWithQuotes = selectorString.value;
if (isValidJavaScriptId(fieldWithQuotes)) {
if (access.receiver is LiteralNumber) out(" ");
out(".");
out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
return;
}
}
out("[");
visitNestedExpression(selector, EXPRESSION,
newInForInit: false, newAtStatementBegin: false);
out("]");
propertyNameOut(access.selector, inAccess: true);
}

visitNamedFunction(NamedFunction namedFunction) {
Expand Down Expand Up @@ -1084,13 +1070,14 @@ class Printer implements NodeVisitor {

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

void propertyNameOut(Expression node, {bool inMethod: false}) {
inForInit = false;
atStatementBegin = false;
void propertyNameOut(Expression node, {bool inMethod: false,
bool inAccess: false}) {

if (node is LiteralNumber) {
LiteralNumber nameNumber = node;
if (inAccess) out('[');
out(nameNumber.value);
if (inAccess) out(']');
} else {
String quotedName;
if (node is PropertyName) {
Expand All @@ -1100,16 +1087,18 @@ class Printer implements NodeVisitor {
}
if (quotedName != null) {
if (isValidJavaScriptId(quotedName)) {
if (inAccess) out('.');
out(quotedName.substring(1, quotedName.length - 1));
} else {
if (inMethod) out("[");
if (inMethod || inAccess) out("[");
out(quotedName);
if (inMethod) out("]");
if (inMethod || inAccess) out("]");
}
} else {
// ComputedPropertyName
out("[");
visit(node);
visitNestedExpression(node, EXPRESSION,
newInForInit: false, newAtStatementBegin: false);
out("]");
}
}
Expand Down
Loading

0 comments on commit 77e62cc

Please sign in to comment.