Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
index 91b8c018766099a7ae1822a9b7501dbd96a481d4..7f0f5b4b43d4bea3aec0470587e3267d247e99b7 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/codegen/js_codegen.dart |
@@ -35,6 +35,7 @@ import 'js_interop.dart'; |
import 'js_names.dart' as JS; |
import 'js_metalet.dart' as JS; |
import 'js_module_item_order.dart'; |
+import 'js_names.dart'; |
import 'js_printer.dart' show writeJsLibrary; |
import 'side_effect_analysis.dart'; |
import 'package:collection/equality.dart'; |
@@ -942,9 +943,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
// Pass along all arguments verbatim, and let the callee handle them. |
// TODO(jmesserly): we'll need something different once we have |
// rest/spread support, but this should work for now. |
- var params = _visit(node.parameters); |
+ var params = |
+ _emitFormalParameterList(node.parameters, allowDestructuring: false); |
+ |
var fun = js.call('function(#) { return $newKeyword #(#); }', |
- [params, _visit(redirect), params,]) as JS.Fun; |
+ [params, _visit(redirect), params]) as JS.Fun; |
return annotate( |
new JS.Method(name, fun, isStatic: true)..sourceInformation = node, |
node.element); |
@@ -1205,21 +1208,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var jsParam = _visit(param.identifier); |
if (param.kind == ParameterKind.NAMED) { |
- // Parameters will be passed using their real names, not the (possibly |
- // renamed) local variable. |
- var paramName = js.string(param.identifier.name, "'"); |
- |
- // TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming |
- // (either test if `obj.prop !== void 0`, or use JSCompiler_renameProperty). |
- body.add(js.statement('let # = # && # in # ? #.# : #;', [ |
- jsParam, |
- _namedArgTemp, |
- paramName, |
- _namedArgTemp, |
- _namedArgTemp, |
- paramName, |
- _defaultParamValue(param), |
- ])); |
+ if (!_isDestructurableNamedParam(param)) { |
+ // Parameters will be passed using their real names, not the (possibly |
+ // renamed) local variable. |
+ var paramName = js.string(param.identifier.name, "'"); |
+ |
+ // TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming. |
+ body.add(js.statement('let # = # && # in # ? #.# : #;', [ |
+ jsParam, |
+ _namedArgTemp, |
+ paramName, |
+ _namedArgTemp, |
+ _namedArgTemp, |
+ paramName, |
+ _defaultParamValue(param), |
+ ])); |
+ } |
} else if (param.kind == ParameterKind.POSITIONAL) { |
body.add(js.statement('if (# === void 0) # = #;', |
[jsParam, jsParam, _defaultParamValue(param)])); |
@@ -1422,17 +1426,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
if (parent is FunctionDeclaration) { |
return _emitFunctionBody(params, node.body); |
} else { |
- String code; |
+ // Chrome Canary does not accept default values with destructuring in |
+ // arrow functions yet (e.g. `({a} = {}) => 1`) but happily accepts them |
+ // with regular functions (e.g. `function({a} = {}) { return 1 }`). |
+ // Note that Firefox accepts both syntaxes just fine. |
+ // TODO(ochafik): Simplify this code when Chrome Canary catches up. |
+ var canUseArrowFun = !node.parameters.parameters.any(_isNamedParam); |
+ |
+ String code = canUseArrowFun ? '(#) => #' : 'function(#) { return # }'; |
JS.Node jsBody; |
var body = node.body; |
if (body.isGenerator || body.isAsynchronous) { |
- code = '(#) => #'; |
jsBody = _emitGeneratorFunctionBody(params, body); |
} else if (body is ExpressionFunctionBody) { |
- code = '(#) => #'; |
jsBody = _visit(body.expression); |
} else { |
- code = '(#) => { #; }'; |
+ code = canUseArrowFun ? '(#) => { #; }' : 'function(#) { #; }'; |
jsBody = _visit(body); |
} |
var clos = js.call(code, [params, jsBody]); |
@@ -1973,19 +1982,80 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
_propertyName(node.name.label.name), _visit(node.expression)); |
} |
+ bool _isNamedParam(FormalParameter param) => |
+ param.kind == ParameterKind.NAMED; |
+ |
+ /// We cannot destructure named params that clash with JS reserved names: |
+ /// see discussion in https://github.com/dart-lang/dev_compiler/issues/392. |
+ bool _isDestructurableNamedParam(FormalParameter param) => |
+ _isNamedParam(param) && !invalidVariableName(param.identifier.name); |
+ |
@override |
- List<JS.Parameter> visitFormalParameterList(FormalParameterList node) { |
+ List<JS.Parameter> visitFormalParameterList(FormalParameterList node) => |
+ _emitFormalParameterList(node); |
+ |
+ List<JS.Parameter> _emitFormalParameterList(FormalParameterList node, |
+ {bool allowDestructuring: true}) { |
var result = <JS.Parameter>[]; |
+ |
+ var namedVars = <JS.DestructuredVariable>[]; |
+ var destructure = allowDestructuring && |
+ node.parameters.where(_isNamedParam).every(_isDestructurableNamedParam); |
+ var hasNamedArgsConflictingWithObjectProperties = false; |
+ var needsOpts = false; |
+ |
for (FormalParameter param in node.parameters) { |
if (param.kind == ParameterKind.NAMED) { |
- result.add(_namedArgTemp); |
- break; |
+ if (destructure) { |
+ if (_jsObjectProperties.contains(param.identifier.name)) { |
+ hasNamedArgsConflictingWithObjectProperties = true; |
+ } |
+ namedVars.add(new JS.DestructuredVariable( |
+ name: _visit(param.identifier), |
+ defaultValue: _defaultParamValue(param))); |
+ } else { |
+ needsOpts = true; |
+ } |
+ } else { |
+ result.add(_visit(param)); |
} |
- result.add(_visit(param)); |
+ } |
+ |
+ if (needsOpts) { |
+ result.add(_namedArgTemp); |
+ } else if (namedVars.isNotEmpty) { |
+ // Note: `var {valueOf} = {}` extracts `Object.prototype.valueOf`, so |
+ // in case there are conflicting names we create an object without |
+ // any prototype. |
+ var defaultOpts = hasNamedArgsConflictingWithObjectProperties |
+ ? js.call('Object.create(null)') |
+ : js.call('{}'); |
+ result.add(new JS.DestructuredVariable( |
+ structure: new JS.ObjectBindingPattern(namedVars), |
+ defaultValue: defaultOpts)); |
} |
return result; |
} |
+ /// See ES6 spec (and `Object.getOwnPropertyNames(Object.prototype)`): |
+ /// http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-object-prototype-object |
+ /// http://www.ecma-international.org/ecma-262/6.0/#sec-additional-properties-of-the-object.prototype-object |
+ static final Set<String> _jsObjectProperties = new Set<String>() |
+ ..addAll([ |
+ "constructor", |
+ "toString", |
+ "toLocaleString", |
+ "valueOf", |
+ "hasOwnProperty", |
+ "isPrototypeOf", |
+ "propertyIsEnumerable", |
+ "__defineGetter__", |
+ "__lookupGetter__", |
+ "__defineSetter__", |
+ "__lookupSetter__", |
+ "__proto__" |
+ ]); |
+ |
@override |
JS.Statement visitExpressionStatement(ExpressionStatement node) => |
_visit(node.expression).toStatement(); |