| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index 80e21de15f17c33ce2ff5dc7e77304955f9cf7cd..9caf6308ad721839d488a3f632dbd33588069a40 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -1195,8 +1195,27 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| var params = _visit(node.parameters) as List<JS.Parameter>;
|
| if (params == null) params = <JS.Parameter>[];
|
|
|
| - return new JS.Method(
|
| - _elementMemberName(node.element), _emitFunctionBody(params, node.body),
|
| + JS.Fun fn = _emitFunctionBody(params, node.body);
|
| + if (node.operatorKeyword != null &&
|
| + node.name.name == '[]=' &&
|
| + params.isNotEmpty) {
|
| + // []= methods need to return the value. We could also address this at
|
| + // call sites, but it's cleaner to instead transform the operator method.
|
| + var returnValue = new JS.Return(params.last);
|
| + var body = fn.body;
|
| + if (JS.Return.foundIn(fn)) {
|
| + // If a return is inside body, transform `(params) { body }` to
|
| + // `(params) { (() => { body })(); return value; }`.
|
| + // TODO(jmesserly): we could instead generate the return differently,
|
| + // and avoid the immediately invoked function.
|
| + body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement();
|
| + }
|
| + // Rewrite the function to include the return.
|
| + fn = new JS.Fun(fn.params, new JS.Block([body, returnValue]))
|
| + ..sourceInformation = fn.sourceInformation;
|
| + }
|
| +
|
| + return new JS.Method(_elementMemberName(node.element), fn,
|
| isGetter: node.isGetter,
|
| isSetter: node.isSetter,
|
| isStatic: node.isStatic);
|
| @@ -1620,14 +1639,32 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| var left = node.leftHandSide;
|
| var right = node.rightHandSide;
|
| if (node.operator.type == TokenType.EQ) return _emitSet(left, right);
|
| - return _emitOpAssign(
|
| - left, right, node.operator.lexeme[0], node.staticElement,
|
| - context: node);
|
| + var op = node.operator.lexeme;
|
| + assert(op.endsWith('='));
|
| + op = op.substring(0, op.length - 1); // remove trailing '='
|
| + return _emitOpAssign(left, right, op, node.staticElement, context: node);
|
| }
|
|
|
| JS.MetaLet _emitOpAssign(
|
| Expression left, Expression right, String op, MethodElement element,
|
| {Expression context}) {
|
| + if (op == '??') {
|
| + // Desugar `l ??= r` as ((x) => x == null ? l = r : x)(l)
|
| + // Note that if `x` contains subexpressions, we need to ensure those
|
| + // are also evaluated only once. This is similar to desguaring for
|
| + // postfix expressions like `i++`.
|
| +
|
| + // Handle the left hand side, to ensure each of its subexpressions are
|
| + // evaluated only once.
|
| + var vars = <String, JS.Expression>{};
|
| + var x = _bindLeftHandSide(vars, left, context: left);
|
| + // Capture the result of evaluating the left hand side in a temp.
|
| + var t = _bindValue(vars, 't', x, context: x);
|
| + return new JS.MetaLet(vars, [
|
| + js.call('# == null ? # : #', [_visit(t), _emitSet(x, right), _visit(t)])
|
| + ]);
|
| + }
|
| +
|
| // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
|
| // (for example, x is IndexExpression) we evaluate those once.
|
| var vars = <String, JS.Expression>{};
|
| @@ -1651,6 +1688,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| Expression target = null;
|
| SimpleIdentifier id;
|
| if (lhs is PropertyAccess) {
|
| + if (lhs.operator.lexeme == '?.') {
|
| + return _emitNullSafeSet(lhs, rhs);
|
| + }
|
| +
|
| target = _getTarget(lhs);
|
| id = lhs.propertyName;
|
| } else if (lhs is PrefixedIdentifier) {
|
| @@ -1669,6 +1710,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| return _visit(rhs).toAssignExpression(_visit(lhs));
|
| }
|
|
|
| + JS.Expression _emitNullSafeSet(PropertyAccess node, Expression right) {
|
| + // Emit `obj?.prop = expr` as:
|
| + //
|
| + // (_ => _ == null ? null : _.prop = expr)(obj).
|
| + //
|
| + // We could use a helper, e.g.: `nullSafeSet(e1, _ => _.v = e2)`
|
| + //
|
| + // However with MetaLet, we get clean code in statement or void context,
|
| + // or when one of the expressions is stateless, which seems common.
|
| + var vars = <String, JS.Expression>{};
|
| + var left = _bindValue(vars, 'l', node.target);
|
| + var body = js.call('# == null ? null : #',
|
| + [_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]);
|
| + return new JS.MetaLet(vars, [body]);
|
| + }
|
| +
|
| @override
|
| JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
| var initArgs = _emitArgumentInitializers(node.parent);
|
| @@ -1693,8 +1750,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
|
|
| @override
|
| visitMethodInvocation(MethodInvocation node) {
|
| - var target = node.isCascaded ? _cascadeTarget : node.target;
|
| + if (node.operator != null && node.operator.lexeme == '?.') {
|
| + return _emitNullSafe(node);
|
| + }
|
|
|
| + var target = _getTarget(node);
|
| var result = _emitForeignJS(node);
|
| if (result != null) return result;
|
|
|
| @@ -2078,6 +2138,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
|
|
| // TODO(vsm): Revisit whether we really need this when we get
|
| // better non-nullability in the type system.
|
| + // TODO(jmesserly): we do recursive calls in a few places. This could
|
| + // leads to O(depth) cost for calling this function. We could store the
|
| + // resulting value if that becomes an issue, so we maintain the invariant
|
| + // that each node is visited once.
|
|
|
| if (expr is Literal && expr is! NullLiteral) return true;
|
| if (expr is IsExpression) return true;
|
| @@ -2099,6 +2163,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| case TokenType.AMPERSAND_AMPERSAND:
|
| case TokenType.BAR_BAR:
|
| return true;
|
| + case TokenType.QUESTION_QUESTION:
|
| + return _isNonNullableExpression(expr.rightOperand);
|
| }
|
| type = getStaticType(expr.leftOperand);
|
| } else if (expr is PrefixExpression) {
|
| @@ -2166,6 +2232,19 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| return js.call(code, [_visit(left), _visit(right)]);
|
| }
|
|
|
| + if (op.type.lexeme == '??') {
|
| + // TODO(jmesserly): leave RHS for debugging?
|
| + // This should be a hint or warning for dead code.
|
| + if (_isNonNullableExpression(left)) return _visit(left);
|
| +
|
| + var vars = <String, JS.Expression>{};
|
| + // Desugar `l ?? r` as `l != null ? l : r`
|
| + var l = _visit(_bindValue(vars, 'l', left, context: left));
|
| + return new JS.MetaLet(vars, [
|
| + js.call('# != null ? # : #', [l, l, _visit(right)])
|
| + ]);
|
| + }
|
| +
|
| if (binaryOperationIsPrimitive(leftType, rightType) ||
|
| rules.isStringType(leftType) && op.type == TokenType.PLUS) {
|
| // special cases where we inline the operation
|
| @@ -2262,6 +2341,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| prop.propertyName);
|
| } else if (expr is PrefixedIdentifier) {
|
| PrefixedIdentifier ident = expr;
|
| + if (isLibraryPrefix(ident.prefix)) {
|
| + return expr;
|
| + }
|
| result = new PrefixedIdentifier(
|
| _bindValue(scope, 'o', ident.prefix, context: context)
|
| as SimpleIdentifier,
|
| @@ -2291,7 +2373,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| // No need to do anything for stateless expressions.
|
| if (isStateless(expr, context)) return expr;
|
|
|
| - var t = _createTemporary('#$name', expr.staticType);
|
| + var t = _createTemporary('#$name', getStaticType(expr));
|
| scope[name] = _visit(expr);
|
| return t;
|
| }
|
| @@ -2357,11 +2439,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| return js.call('$op#', _visit(expr));
|
| } else if (op.lexeme == '++' || op.lexeme == '--') {
|
| // We need a null check, so the increment must be expanded out.
|
| - var mathop = op.lexeme[0];
|
| var vars = <String, JS.Expression>{};
|
| var x = _bindLeftHandSide(vars, expr, context: expr);
|
| - var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]);
|
| - return new JS.MetaLet(vars, [body]);
|
| +
|
| + var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
|
| + var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
|
| + ..staticElement = node.staticElement
|
| + ..staticType = getStaticType(expr);
|
| +
|
| + return new JS.MetaLet(vars, [_emitSet(x, increment)]);
|
| } else {
|
| return js.call('$op#', notNull(expr));
|
| }
|
| @@ -2421,8 +2507,64 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| }
|
|
|
| @override
|
| - visitPropertyAccess(PropertyAccess node) =>
|
| - _emitGet(_getTarget(node), node.propertyName);
|
| + visitPropertyAccess(PropertyAccess node) {
|
| + if (node.operator.lexeme == '?.') {
|
| + return _emitNullSafe(node);
|
| + }
|
| + return _emitGet(_getTarget(node), node.propertyName);
|
| + }
|
| +
|
| + JS.Expression _emitNullSafe(Expression node) {
|
| + // Desugar ?. sequence by passing a sequence of callbacks that applies
|
| + // each operation in sequence:
|
| + //
|
| + // obj?.foo()?.bar
|
| + // -->
|
| + // nullSafe(obj, _ => _.foo(), _ => _.bar);
|
| + //
|
| + // This pattern has the benefit of preserving order, as well as minimizing
|
| + // code expansion: each `?.` becomes `, _ => _`, plus one helper call.
|
| + //
|
| + // TODO(jmesserly): we could desugar with MetaLet instead, which may
|
| + // lead to higher performing code, but at the cost of readability.
|
| + var tail = <JS.Expression>[];
|
| + for (;;) {
|
| + var op = _getOperator(node);
|
| + if (op != null && op.lexeme == '?.') {
|
| + var nodeTarget = _getTarget(node);
|
| + if (_isNonNullableExpression(nodeTarget)) {
|
| + node = _stripNullAwareOp(node, nodeTarget);
|
| + break;
|
| + }
|
| +
|
| + var param = _createTemporary('_', nodeTarget.staticType);
|
| + var baseNode = _stripNullAwareOp(node, param);
|
| + tail.add(new JS.ArrowFun([_visit(param)], _visit(baseNode)));
|
| + node = nodeTarget;
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| + if (tail.isEmpty) return _visit(node);
|
| + return js.call('dart.nullSafe(#, #)', [_visit(node), tail.reversed]);
|
| + }
|
| +
|
| + static Token _getOperator(Expression node) {
|
| + if (node is PropertyAccess) return node.operator;
|
| + if (node is MethodInvocation) return node.operator;
|
| + return null;
|
| + }
|
| +
|
| + // TODO(jmesserly): this is dropping source location.
|
| + Expression _stripNullAwareOp(Expression node, Expression newTarget) {
|
| + if (node is PropertyAccess) {
|
| + return AstBuilder.propertyAccess(newTarget, node.propertyName);
|
| + } else {
|
| + var invoke = node as MethodInvocation;
|
| + return AstBuilder.methodInvoke(
|
| + newTarget, invoke.methodName, invoke.argumentList.arguments);
|
| + }
|
| + }
|
|
|
| bool _requiresStaticDispatch(Expression target, String memberName) {
|
| var type = getStaticType(target);
|
| @@ -2509,11 +2651,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| bool _useNativeJsIndexer(DartType type) =>
|
| findAnnotation(type.element, _isJsNameAnnotation) != null;
|
|
|
| - /// Gets the target of a [PropertyAccess] or [IndexExpression].
|
| - /// Those two nodes are special because they're both allowed on left side of
|
| - /// an assignment expression and cascades.
|
| + /// Gets the target of a [PropertyAccess], [IndexExpression], or
|
| + /// [MethodInvocation]. These three nodes can appear in a [CascadeExpression].
|
| Expression _getTarget(node) {
|
| - assert(node is IndexExpression || node is PropertyAccess);
|
| + assert(node is IndexExpression ||
|
| + node is PropertyAccess ||
|
| + node is MethodInvocation);
|
| return node.isCascaded ? _cascadeTarget : node.target;
|
| }
|
|
|
|
|