Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
index 0e589ab808dc327876a0fcdfe9c59c5e081d9f29..3cba7a75f96f8198fa8d1fcfca9b793dd279fe6d 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/codegen/js_codegen.dart |
@@ -29,7 +29,9 @@ import 'package:dev_compiler/src/info.dart'; |
import 'package:dev_compiler/src/options.dart'; |
import 'package:dev_compiler/src/report.dart'; |
import 'package:dev_compiler/src/utils.dart'; |
+ |
import 'code_generator.dart'; |
+import 'js_names.dart'; |
bool _isAnnotationType(Annotation m, String name) => m.name.name == name; |
@@ -53,7 +55,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
/// The variable for the target of the current `..` cascade expression. |
SimpleIdentifier _cascadeTarget; |
/// The variable for the current catch clause |
- String _catchParameter; |
+ SimpleIdentifier _catchParameter; |
ClassDeclaration currentClass; |
ConstantEvaluator _constEvaluator; |
@@ -78,6 +80,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
LibraryElement get currentLibrary => libraryInfo.library; |
+ /// The name for the library's exports inside itself. |
+ /// This much be a constant because we interpolate it into template strings, |
+ /// and otherwise it would break caching for them. |
+ /// `exports` was chosen as the most similar to ES module patterns. |
+ final JSTemporary _exportsVar = new JSTemporary('exports'); |
+ final JSTemporary _namedArgTemp = new JSTemporary('opts'); |
+ |
JS.Program emitLibrary(LibraryUnit library) { |
var jsDefaultValue = '{}'; |
var unit = library.library; |
@@ -113,7 +122,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
// TODO(jmesserly): make these immutable in JS? |
for (var name in _exports) { |
- body.add(js.statement('$_EXPORTS.# = #;', [name, name])); |
+ body.add(js.statement('#.# = #;', [_exportsVar, name, name])); |
} |
var name = jsLibraryName(libraryInfo.library); |
@@ -121,13 +130,18 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var defaultValue = js.call(jsDefaultValue); |
return new JS.Program([ |
js.statement('var #;', name), |
- js.statement("(function($_EXPORTS) { 'use strict'; #; })(# || (# = #));", |
- [body, name, name, defaultValue]) |
+ js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ |
+ _exportsVar, |
+ body, |
+ name, |
+ name, |
+ defaultValue |
+ ]) |
]); |
} |
- JS.Statement _initPrivateSymbol(String name) => |
- js.statement('let # = $_SYMBOL(#);', [name, js.string(name, "'")]); |
+ JS.Statement _initPrivateSymbol(String name) => js.statement( |
+ 'let # = $_SYMBOL(#);', [new JSTemporary(name), js.string(name, "'")]); |
// TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
// until we have better name tracking. |
@@ -315,7 +329,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var target = genericName; |
if (_needQualifiedName(classElem)) { |
- target = js.call('#.#', [_EXPORTS, genericName]); |
+ target = js.call('#.#', [_exportsVar, genericName]); |
} |
genericInst = js.call('#(#)', [target, dynamicArgs]); |
} |
@@ -329,7 +343,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return js.statement( |
'{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ |
genericDef, |
- _EXPORTS, |
+ _exportsVar, |
js.string(name, "'"), |
genericName |
]); |
@@ -337,7 +351,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return js.statement( |
'dart.defineLazyClass(#, { get #() { #; return #; } });', [ |
- _EXPORTS, |
+ _exportsVar, |
_propertyName(name), |
body, |
name |
@@ -561,7 +575,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
} |
} |
} |
- var lazy = _emitLazyFields(name, lazyStatics); |
+ var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); |
if (lazy != null) body.add(lazy); |
return _statement(body); |
} |
@@ -801,9 +815,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var name = param.identifier.name; |
if (param.kind == ParameterKind.NAMED) { |
- body.add(js.statement('let # = opt\$ && # in opt\$ ? opt\$.# : #;', [ |
+ body.add(js.statement('let # = # && # in # ? #.# : #;', [ |
name, |
+ _namedArgTemp, |
js.string(name, "'"), |
+ _namedArgTemp, |
+ _namedArgTemp, |
name, |
_defaultParamValue(param), |
])); |
@@ -934,8 +951,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
} else if (variable is ConstFieldElementImpl) { |
var className = (variable.enclosingElement as ClassElement).name; |
return js.call('#.#', [className, name]); |
- } else if (e is ParameterElement && e.isInitializingFormal) { |
- name = _fieldParameterName(name); |
+ } else if (e is ParameterElement && e.isInitializingFormal && e.isPrivate) { |
+ /// Rename private names so they don't shadow the private field symbol. |
+ /// The renamer would handle this, but it would prefer to rename the |
+ /// temporary used for the private symbol. Instead rename the parameter. |
+ return new JSTemporary('${name.substring(1)}'); |
+ } else if (_isTemporary(e)) { |
+ return new JSTemporary(e.name); |
} |
return new JS.Identifier(name); |
} |
@@ -1162,7 +1184,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var result = <JS.Identifier>[]; |
for (FormalParameter param in node.parameters) { |
if (param.kind == ParameterKind.NAMED) { |
- result.add(new JS.Identifier(r'opt$')); |
+ result.add(_namedArgTemp); |
break; |
} |
result.add(_visit(param)); |
@@ -1265,13 +1287,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
} |
void _flushLazyFields(List<JS.Statement> body) { |
- var code = _emitLazyFields(_EXPORTS, _lazyFields); |
+ var code = _emitLazyFields(_exportsVar, _lazyFields); |
if (code != null) body.add(code); |
_lazyFields.clear(); |
} |
JS.Statement _emitLazyFields( |
- String objExpr, List<VariableDeclaration> fields) { |
+ JS.Expression objExpr, List<VariableDeclaration> fields) { |
if (fields.isEmpty) return null; |
var methods = []; |
@@ -1294,8 +1316,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
void _flushLibraryProperties(List<JS.Statement> body) { |
if (_properties.isEmpty) return; |
- body.add(js.statement('dart.copyProperties($_EXPORTS, { # });', |
- [_properties.map(_emitTopLevelProperty)])); |
+ body.add(js.statement('dart.copyProperties(#, { # });', [ |
+ _exportsVar, |
+ _properties.map(_emitTopLevelProperty) |
+ ])); |
_properties.clear(); |
} |
@@ -1466,17 +1490,26 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
// TODO(jmesserly, vsm): Refactor this logic. |
SimpleIdentifier _createTemporary(String name, DartType type) { |
+ // We use an invalid source location to signal that this is a temporary. |
+ // See [_isTemporary]. |
+ // TODO(jmesserly): alternatives are |
+ // * (ab)use Element.isSynthetic, which isn't currently used for |
+ // LocalVariableElementImpl, so we could repurpose to mean "temp". |
+ // * add a new property to LocalVariableElementImpl. |
+ // * create a new subtype of LocalVariableElementImpl to mark a temp. |
var id = |
- new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, 0)); |
+ new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
id.staticElement = new LocalVariableElementImpl.forNode(id); |
id.staticType = type; |
return id; |
} |
+ bool _isTemporary(Element node) => node.nameOffset == -1; |
+ |
JS.Expression _emitPostfixIncrement(Expression expr, Token op) { |
var type = rules.getStaticType(expr); |
assert(type != null); |
- var tmp = _createTemporary('\$tmp', type); |
+ var tmp = _createTemporary('x', type); |
// Increment and write |
var one = AstBuilder.integerLiteral(1); |
@@ -1588,20 +1621,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
// remains valid in an expression context. |
// TODO(jmesserly): need a better way to handle temps. |
// TODO(jmesserly): special case for parent is ExpressionStatement? |
- _cascadeTarget = |
- new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); |
- _cascadeTarget.staticElement = |
- new LocalVariableElementImpl.forNode(_cascadeTarget); |
- _cascadeTarget.staticType = node.target.staticType; |
+ _cascadeTarget = _createTemporary('_', node.target.staticType); |
var body = _visitList(node.cascadeSections); |
if (node.parent is! ExpressionStatement) { |
- body.add(js.statement('return #;', _cascadeTarget.name)); |
+ body.add(js.statement('return #;', _visit(_cascadeTarget))); |
} |
var bindThis = _maybeBindThis(node.cascadeSections); |
result = js.call('((#) => { # })$bindThis(#)', [ |
- _cascadeTarget.name, |
+ _visit(_cascadeTarget), |
body, |
_visit(node.target) |
]); |
@@ -1638,15 +1667,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
@override |
visitFormalParameter(FormalParameter node) => |
- new JS.Identifier(node.identifier.name); |
- |
- @override |
- visitFieldFormalParameter(FieldFormalParameter node) => |
- new JS.Identifier(_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; |
+ visitSimpleIdentifier(node.identifier); |
@override |
JS.This visitThisExpression(ThisExpression node) => new JS.This(); |
@@ -1719,9 +1740,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
@override |
visitRethrowExpression(RethrowExpression node) { |
if (node.parent is ExpressionStatement) { |
- return js.statement('throw #;', _catchParameter); |
+ return js.statement('throw #;', _visit(_catchParameter)); |
} else { |
- return js.call('dart.throw_(#)', _catchParameter); |
+ return js.call('dart.throw_(#)', _visit(_catchParameter)); |
} |
} |
@@ -1782,21 +1803,34 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
// TODO(jmesserly): need a better way to get a temporary variable. |
// This could incorrectly shadow a user's name. |
var savedCatch = _catchParameter; |
- _catchParameter = '\$e'; |
if (clauses.length == 1) { |
// Special case for a single catch. |
- var clause = clauses.single; |
- if (clause.exceptionParameter != null) { |
- _catchParameter = clause.exceptionParameter.name; |
- } |
+ _catchParameter = clauses.single.exceptionParameter; |
+ } else { |
+ _catchParameter = _createTemporary('e', rules.provider.dynamicType); |
+ } |
+ |
+ JS.Statement catchBody = null; |
+ for (var clause in clauses.reversed) { |
+ catchBody = _catchClauseGuard(clause, catchBody); |
} |
- var catchVarDecl = new JS.Identifier(_catchParameter); |
- var catchBody = new JS.Block(_visitList(clauses)); |
+ var catchVarDecl = _visit(_catchParameter); |
_catchParameter = savedCatch; |
+ return new JS.Catch(catchVarDecl, new JS.Block([catchBody])); |
+ } |
+ |
+ JS.Statement _catchClauseGuard(CatchClause clause, JS.Statement otherwise) { |
+ var then = visitCatchClause(clause); |
+ |
+ // Discard following clauses, if any, as they are unreachable. |
+ if (clause.exceptionType == null) return then; |
- return new JS.Catch(catchVarDecl, catchBody); |
+ return new JS.If(js.call('dart.is(#, #)', [ |
+ _visit(_catchParameter), |
+ _emitTypeName(clause.exceptionType.type), |
+ ]), then, otherwise); |
} |
JS.Statement _statement(Iterable stmts) { |
@@ -1807,6 +1841,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return new JS.Block(s); |
} |
+ /// Visits the catch clause body. This skips the exception type guard, if any. |
+ /// That is handled in [_visitCatch]. |
@override |
JS.Statement visitCatchClause(CatchClause node) { |
var body = []; |
@@ -1814,11 +1850,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var savedCatch = _catchParameter; |
if (node.catchKeyword != null) { |
var name = node.exceptionParameter; |
- if (name != null && name.name != _catchParameter) { |
- var decl = new JS.Identifier(name.name); |
- decl.sourceInformation = name; |
- body.add(js.statement('let # = #;', [decl, _catchParameter])); |
- _catchParameter = name.name; |
+ if (name != null && name != _catchParameter) { |
+ body.add(js.statement( |
+ 'let # = #;', [_visit(name), _visit(_catchParameter)])); |
+ _catchParameter = name; |
} |
if (node.stackTraceParameter != null) { |
var stackVar = node.stackTraceParameter.name; |
@@ -1829,15 +1864,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
body.add(_visit(node.body)); |
_catchParameter = savedCatch; |
- |
- if (node.exceptionType != null) { |
- return js.statement('if (dart.is(#, #)) #;', [ |
- _catchParameter, |
- _emitTypeName(node.exceptionType.type), |
- _statement(body) |
- ]); |
- } |
- |
return _statement(body); |
} |
@@ -2071,7 +2097,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
JS.Expression _jsMemberName(String name, {bool unary: false}) { |
if (name.startsWith('_')) { |
if (_privateNames.add(name)) _pendingPrivateNames.add(name); |
- return new JS.Identifier(name); |
+ return new JSTemporary(name); |
} |
if (name == '[]') { |
name = 'get'; |
@@ -2092,9 +2118,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
/// Choose a canonical name from the library element. |
/// This never uses the library's name (the identifier in the `library` |
/// declaration) as it doesn't have any meaningful rules enforced. |
- String _libraryName(LibraryElement library) { |
- if (library == libraryInfo.library) return _EXPORTS; |
- return jsLibraryName(library); |
+ JS.Identifier _libraryName(LibraryElement library) { |
+ if (library == libraryInfo.library) return _exportsVar; |
+ return new JS.Identifier(jsLibraryName(library)); |
} |
String _maybeBindThis(node) { |
@@ -2105,12 +2131,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return visitor._bindThis ? '.bind(this)' : ''; |
} |
- /// The name for the library's exports inside itself. |
- /// This much be a constant because we interpolate it into template strings, |
- /// and otherwise it would break caching for them. |
- /// `exports` was chosen as the most similar to ES module patterns. |
- static const String _EXPORTS = 'exports'; |
- |
static bool _needsImplicitThis(Element e) => |
e is PropertyAccessorElement && !e.variable.isStatic || |
e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; |
@@ -2218,8 +2238,8 @@ class JSGenerator extends CodeGenerator { |
} |
void _writeNode(JS.JavaScriptPrintingContext context, JS.Node node) { |
- var opts = new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); |
- node.accept(new JS.Printer(opts, context)); |
+ var opts = new JS.JavaScriptPrintingOptions(allowKeywordsInProperties: true); |
+ node.accept(new JS.Printer(opts, context, localNamer: new JSNamer(node))); |
} |
/// This is a debugging helper to print a JS node. |