Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dev_compiler.src.codegen.js_codegen; | 5 library dev_compiler.src.codegen.js_codegen; |
| 6 | 6 |
| 7 import 'dart:collection' show HashSet, HashMap, SplayTreeSet; | 7 import 'dart:collection' show HashSet, HashMap, SplayTreeSet; |
| 8 | 8 |
| 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
| 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
| (...skipping 1177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1188 } | 1188 } |
| 1189 | 1189 |
| 1190 JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) { | 1190 JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) { |
| 1191 if (node.isAbstract || _externalOrNative(node)) { | 1191 if (node.isAbstract || _externalOrNative(node)) { |
| 1192 return null; | 1192 return null; |
| 1193 } | 1193 } |
| 1194 | 1194 |
| 1195 var params = _visit(node.parameters) as List<JS.Parameter>; | 1195 var params = _visit(node.parameters) as List<JS.Parameter>; |
| 1196 if (params == null) params = <JS.Parameter>[]; | 1196 if (params == null) params = <JS.Parameter>[]; |
| 1197 | 1197 |
| 1198 return new JS.Method( | 1198 JS.Fun fn = _emitFunctionBody(params, node.body); |
| 1199 _elementMemberName(node.element), _emitFunctionBody(params, node.body), | 1199 if (node.operatorKeyword != null && |
| 1200 node.name.name == '[]=' && | |
| 1201 params.isNotEmpty) { | |
| 1202 // []= methods need to return the value. We could also address this at | |
| 1203 // call sites, but it's cleaner to instead transform the operator method. | |
| 1204 var returnValue = new JS.Return(params.last); | |
| 1205 var body = fn.body; | |
| 1206 if (JS.Return.foundIn(fn)) { | |
| 1207 // If a return is inside body, transform `(params) { body }` to | |
| 1208 // `(params) { (() => { body })(); return value; }`. | |
| 1209 // TODO(jmesserly): we could instead generate the return differently, | |
| 1210 // and avoid the immediately invoked function. | |
| 1211 body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement(); | |
| 1212 } | |
| 1213 // Rewrite the function to include the return. | |
| 1214 fn = new JS.Fun(fn.params, new JS.Block([body, returnValue])) | |
| 1215 ..sourceInformation = fn.sourceInformation; | |
| 1216 } | |
| 1217 | |
| 1218 return new JS.Method(_elementMemberName(node.element), fn, | |
| 1200 isGetter: node.isGetter, | 1219 isGetter: node.isGetter, |
| 1201 isSetter: node.isSetter, | 1220 isSetter: node.isSetter, |
| 1202 isStatic: node.isStatic); | 1221 isStatic: node.isStatic); |
| 1203 } | 1222 } |
| 1204 | 1223 |
| 1205 @override | 1224 @override |
| 1206 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { | 1225 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { |
| 1207 assert(node.parent is CompilationUnit); | 1226 assert(node.parent is CompilationUnit); |
| 1208 | 1227 |
| 1209 if (_externalOrNative(node)) return null; | 1228 if (_externalOrNative(node)) return null; |
| (...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1613 } | 1632 } |
| 1614 | 1633 |
| 1615 return idTable.putIfAbsent(e, () => new JS.MaybeQualifiedId(libName, name)); | 1634 return idTable.putIfAbsent(e, () => new JS.MaybeQualifiedId(libName, name)); |
| 1616 } | 1635 } |
| 1617 | 1636 |
| 1618 @override | 1637 @override |
| 1619 JS.Expression visitAssignmentExpression(AssignmentExpression node) { | 1638 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
| 1620 var left = node.leftHandSide; | 1639 var left = node.leftHandSide; |
| 1621 var right = node.rightHandSide; | 1640 var right = node.rightHandSide; |
| 1622 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); | 1641 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
| 1623 return _emitOpAssign( | 1642 var op = node.operator.lexeme; |
| 1624 left, right, node.operator.lexeme[0], node.staticElement, | 1643 assert(op.endsWith('=')); |
| 1625 context: node); | 1644 op = op.substring(0, op.length - 1); // remove trailing '=' |
| 1645 return _emitOpAssign(left, right, op, node.staticElement, context: node); | |
| 1626 } | 1646 } |
| 1627 | 1647 |
| 1628 JS.MetaLet _emitOpAssign( | 1648 JS.MetaLet _emitOpAssign( |
| 1629 Expression left, Expression right, String op, MethodElement element, | 1649 Expression left, Expression right, String op, MethodElement element, |
| 1630 {Expression context}) { | 1650 {Expression context}) { |
| 1651 if (op == '??') { | |
| 1652 // Desugar `l ??= r` as ((x) => x == null ? l = r : x)(l) | |
| 1653 // Note that if `x` contains subexpressions, we need to ensure those | |
| 1654 // are also evaluated only once. This is similar to desguaring for | |
| 1655 // postfix expressions like `i++`. | |
| 1656 | |
| 1657 // Handle the left hand side, to ensure each of its subexpressions are | |
| 1658 // evaluated only once. | |
| 1659 var vars = <String, JS.Expression>{}; | |
| 1660 var x = _bindLeftHandSide(vars, left, context: left); | |
| 1661 // Capture the result of evaluating the left hand side in a temp. | |
| 1662 var t = _bindValue(vars, 't', x, context: x); | |
| 1663 return new JS.MetaLet(vars, [ | |
| 1664 js.call('# == null ? # : #', [_visit(t), _emitSet(x, right), _visit(t)]) | |
| 1665 ]); | |
| 1666 } | |
| 1667 | |
| 1631 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions | 1668 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions |
| 1632 // (for example, x is IndexExpression) we evaluate those once. | 1669 // (for example, x is IndexExpression) we evaluate those once. |
| 1633 var vars = <String, JS.Expression>{}; | 1670 var vars = <String, JS.Expression>{}; |
| 1634 var lhs = _bindLeftHandSide(vars, left, context: context); | 1671 var lhs = _bindLeftHandSide(vars, left, context: context); |
| 1635 var inc = AstBuilder.binaryExpression(lhs, op, right); | 1672 var inc = AstBuilder.binaryExpression(lhs, op, right); |
| 1636 inc.staticElement = element; | 1673 inc.staticElement = element; |
| 1637 inc.staticType = getStaticType(left); | 1674 inc.staticType = getStaticType(left); |
| 1638 return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); | 1675 return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); |
| 1639 } | 1676 } |
| 1640 | 1677 |
| 1641 JS.Expression _emitSet(Expression lhs, Expression rhs) { | 1678 JS.Expression _emitSet(Expression lhs, Expression rhs) { |
| 1642 if (lhs is IndexExpression) { | 1679 if (lhs is IndexExpression) { |
| 1643 var target = _getTarget(lhs); | 1680 var target = _getTarget(lhs); |
| 1644 if (_useNativeJsIndexer(target.staticType)) { | 1681 if (_useNativeJsIndexer(target.staticType)) { |
| 1645 return js.call( | 1682 return js.call( |
| 1646 '#[#] = #', [_visit(target), _visit(lhs.index), _visit(rhs)]); | 1683 '#[#] = #', [_visit(target), _visit(lhs.index), _visit(rhs)]); |
| 1647 } | 1684 } |
| 1648 return _emitSend(target, '[]=', [lhs.index, rhs]); | 1685 return _emitSend(target, '[]=', [lhs.index, rhs]); |
| 1649 } | 1686 } |
| 1650 | 1687 |
| 1651 Expression target = null; | 1688 Expression target = null; |
| 1652 SimpleIdentifier id; | 1689 SimpleIdentifier id; |
| 1653 if (lhs is PropertyAccess) { | 1690 if (lhs is PropertyAccess) { |
| 1691 if (lhs.operator.lexeme == '?.') { | |
| 1692 return _emitNullSafeSet(lhs, rhs); | |
| 1693 } | |
| 1694 | |
| 1654 target = _getTarget(lhs); | 1695 target = _getTarget(lhs); |
| 1655 id = lhs.propertyName; | 1696 id = lhs.propertyName; |
| 1656 } else if (lhs is PrefixedIdentifier) { | 1697 } else if (lhs is PrefixedIdentifier) { |
| 1657 target = lhs.prefix; | 1698 target = lhs.prefix; |
| 1658 id = lhs.identifier; | 1699 id = lhs.identifier; |
| 1659 } | 1700 } |
| 1660 | 1701 |
| 1661 if (target != null && DynamicInvoke.get(target)) { | 1702 if (target != null && DynamicInvoke.get(target)) { |
| 1662 return js.call('dart.$DPUT(#, #, #)', [ | 1703 return js.call('dart.$DPUT(#, #, #)', [ |
| 1663 _visit(target), | 1704 _visit(target), |
| 1664 _emitMemberName(id.name, type: getStaticType(target)), | 1705 _emitMemberName(id.name, type: getStaticType(target)), |
| 1665 _visit(rhs) | 1706 _visit(rhs) |
| 1666 ]); | 1707 ]); |
| 1667 } | 1708 } |
| 1668 | 1709 |
| 1669 return _visit(rhs).toAssignExpression(_visit(lhs)); | 1710 return _visit(rhs).toAssignExpression(_visit(lhs)); |
| 1670 } | 1711 } |
| 1671 | 1712 |
| 1713 JS.Expression _emitNullSafeSet(PropertyAccess node, Expression right) { | |
| 1714 // Emit `obj?.prop = expr` as: | |
| 1715 // | |
| 1716 // (_ => _ == null ? null : _.prop = expr)(obj). | |
| 1717 // | |
| 1718 // We could use a helper, e.g.: `nullSafeSet(e1, _ => _.v = e2)` | |
| 1719 // | |
| 1720 // However with MetaLet, we get clean code in statement or void context, | |
| 1721 // or when one of the expressions is stateless, which seems common. | |
| 1722 var vars = <String, JS.Expression>{}; | |
| 1723 var left = _bindValue(vars, 'l', node.target); | |
| 1724 var body = js.call('# == null ? null : #', | |
| 1725 [_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]); | |
| 1726 return new JS.MetaLet(vars, [body]); | |
| 1727 } | |
| 1728 | |
| 1672 @override | 1729 @override |
| 1673 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { | 1730 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| 1674 var initArgs = _emitArgumentInitializers(node.parent); | 1731 var initArgs = _emitArgumentInitializers(node.parent); |
| 1675 var ret = new JS.Return(_visit(node.expression)); | 1732 var ret = new JS.Return(_visit(node.expression)); |
| 1676 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); | 1733 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
| 1677 } | 1734 } |
| 1678 | 1735 |
| 1679 @override | 1736 @override |
| 1680 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); | 1737 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); |
| 1681 | 1738 |
| 1682 @override | 1739 @override |
| 1683 JS.Block visitBlockFunctionBody(BlockFunctionBody node) { | 1740 JS.Block visitBlockFunctionBody(BlockFunctionBody node) { |
| 1684 var initArgs = _emitArgumentInitializers(node.parent); | 1741 var initArgs = _emitArgumentInitializers(node.parent); |
| 1685 var block = visitBlock(node.block); | 1742 var block = visitBlock(node.block); |
| 1686 if (initArgs != null) return new JS.Block([initArgs, block]); | 1743 if (initArgs != null) return new JS.Block([initArgs, block]); |
| 1687 return block; | 1744 return block; |
| 1688 } | 1745 } |
| 1689 | 1746 |
| 1690 @override | 1747 @override |
| 1691 JS.Block visitBlock(Block node) => | 1748 JS.Block visitBlock(Block node) => |
| 1692 new JS.Block(_visitList(node.statements) as List<JS.Statement>); | 1749 new JS.Block(_visitList(node.statements) as List<JS.Statement>); |
| 1693 | 1750 |
| 1694 @override | 1751 @override |
| 1695 visitMethodInvocation(MethodInvocation node) { | 1752 visitMethodInvocation(MethodInvocation node) { |
| 1696 var target = node.isCascaded ? _cascadeTarget : node.target; | 1753 if (node.operator != null && node.operator.lexeme == '?.') { |
| 1754 return _emitNullSafe(node); | |
| 1755 } | |
| 1697 | 1756 |
| 1757 var target = _getTarget(node); | |
| 1698 var result = _emitForeignJS(node); | 1758 var result = _emitForeignJS(node); |
| 1699 if (result != null) return result; | 1759 if (result != null) return result; |
| 1700 | 1760 |
| 1701 String code; | 1761 String code; |
| 1702 if (target == null || isLibraryPrefix(target)) { | 1762 if (target == null || isLibraryPrefix(target)) { |
| 1703 if (DynamicInvoke.get(node.methodName)) { | 1763 if (DynamicInvoke.get(node.methodName)) { |
| 1704 code = 'dart.$DCALL(#, #)'; | 1764 code = 'dart.$DCALL(#, #)'; |
| 1705 } else { | 1765 } else { |
| 1706 code = '#(#)'; | 1766 code = '#(#)'; |
| 1707 } | 1767 } |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2071 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); | 2131 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
| 2072 | 2132 |
| 2073 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); | 2133 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
| 2074 | 2134 |
| 2075 bool _isNonNullableExpression(Expression expr) { | 2135 bool _isNonNullableExpression(Expression expr) { |
| 2076 // If the type is non-nullable, no further checking needed. | 2136 // If the type is non-nullable, no further checking needed. |
| 2077 if (rules.isNonNullableType(getStaticType(expr))) return true; | 2137 if (rules.isNonNullableType(getStaticType(expr))) return true; |
| 2078 | 2138 |
| 2079 // TODO(vsm): Revisit whether we really need this when we get | 2139 // TODO(vsm): Revisit whether we really need this when we get |
| 2080 // better non-nullability in the type system. | 2140 // better non-nullability in the type system. |
| 2141 // TODO(jmesserly): we do recursive calls in a few places. This could | |
| 2142 // leads to O(depth) cost for calling this function. We could store the | |
| 2143 // resulting value if that becomes an issue, so we maintain the invariant | |
| 2144 // that each node is visited once. | |
| 2081 | 2145 |
| 2082 if (expr is Literal && expr is! NullLiteral) return true; | 2146 if (expr is Literal && expr is! NullLiteral) return true; |
| 2083 if (expr is IsExpression) return true; | 2147 if (expr is IsExpression) return true; |
| 2084 if (expr is ThisExpression) return true; | 2148 if (expr is ThisExpression) return true; |
| 2085 if (expr is SuperExpression) return true; | 2149 if (expr is SuperExpression) return true; |
| 2086 if (expr is ParenthesizedExpression) { | 2150 if (expr is ParenthesizedExpression) { |
| 2087 return _isNonNullableExpression(expr.expression); | 2151 return _isNonNullableExpression(expr.expression); |
| 2088 } | 2152 } |
| 2089 if (expr is SimpleIdentifier) { | 2153 if (expr is SimpleIdentifier) { |
| 2090 // Type literals are not null. | 2154 // Type literals are not null. |
| 2091 Element e = expr.staticElement; | 2155 Element e = expr.staticElement; |
| 2092 if (e is ClassElement || e is FunctionTypeAliasElement) return true; | 2156 if (e is ClassElement || e is FunctionTypeAliasElement) return true; |
| 2093 } | 2157 } |
| 2094 DartType type = null; | 2158 DartType type = null; |
| 2095 if (expr is BinaryExpression) { | 2159 if (expr is BinaryExpression) { |
| 2096 switch (expr.operator.type) { | 2160 switch (expr.operator.type) { |
| 2097 case TokenType.EQ_EQ: | 2161 case TokenType.EQ_EQ: |
| 2098 case TokenType.BANG_EQ: | 2162 case TokenType.BANG_EQ: |
| 2099 case TokenType.AMPERSAND_AMPERSAND: | 2163 case TokenType.AMPERSAND_AMPERSAND: |
| 2100 case TokenType.BAR_BAR: | 2164 case TokenType.BAR_BAR: |
| 2101 return true; | 2165 return true; |
| 2166 case TokenType.QUESTION_QUESTION: | |
| 2167 return _isNonNullableExpression(expr.rightOperand); | |
| 2102 } | 2168 } |
| 2103 type = getStaticType(expr.leftOperand); | 2169 type = getStaticType(expr.leftOperand); |
| 2104 } else if (expr is PrefixExpression) { | 2170 } else if (expr is PrefixExpression) { |
| 2105 if (expr.operator.type == TokenType.BANG) return true; | 2171 if (expr.operator.type == TokenType.BANG) return true; |
| 2106 type = getStaticType(expr.operand); | 2172 type = getStaticType(expr.operand); |
| 2107 } else if (expr is PostfixExpression) { | 2173 } else if (expr is PostfixExpression) { |
| 2108 type = getStaticType(expr.operand); | 2174 type = getStaticType(expr.operand); |
| 2109 } | 2175 } |
| 2110 if (type != null && _isJSBuiltinType(type)) { | 2176 if (type != null && _isJSBuiltinType(type)) { |
| 2111 return true; | 2177 return true; |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2159 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; | 2225 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; |
| 2160 } else if (left is SuperExpression) { | 2226 } else if (left is SuperExpression) { |
| 2161 return _emitSend(left, op.lexeme, [right]); | 2227 return _emitSend(left, op.lexeme, [right]); |
| 2162 } else { | 2228 } else { |
| 2163 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; | 2229 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
| 2164 code = '${bang}dart.equals(#, #)'; | 2230 code = '${bang}dart.equals(#, #)'; |
| 2165 } | 2231 } |
| 2166 return js.call(code, [_visit(left), _visit(right)]); | 2232 return js.call(code, [_visit(left), _visit(right)]); |
| 2167 } | 2233 } |
| 2168 | 2234 |
| 2235 if (op.type.lexeme == '??') { | |
| 2236 // TODO(jmesserly): leave RHS for debugging? | |
| 2237 // This should be a hint or warning for dead code. | |
| 2238 if (_isNonNullableExpression(left)) return _visit(left); | |
| 2239 | |
| 2240 var vars = <String, JS.Expression>{}; | |
| 2241 // Desugar `l ?? r` as `l != null ? l : r` | |
| 2242 var l = _visit(_bindValue(vars, 'l', left, context: left)); | |
| 2243 return new JS.MetaLet(vars, [ | |
| 2244 js.call('# != null ? # : #', [l, l, _visit(right)]) | |
| 2245 ]); | |
| 2246 } | |
| 2247 | |
| 2169 if (binaryOperationIsPrimitive(leftType, rightType) || | 2248 if (binaryOperationIsPrimitive(leftType, rightType) || |
| 2170 rules.isStringType(leftType) && op.type == TokenType.PLUS) { | 2249 rules.isStringType(leftType) && op.type == TokenType.PLUS) { |
| 2171 // special cases where we inline the operation | 2250 // special cases where we inline the operation |
| 2172 // these values are assumed to be non-null (determined by the checker) | 2251 // these values are assumed to be non-null (determined by the checker) |
| 2173 // TODO(jmesserly): it would be nice to just inline the method from core, | 2252 // TODO(jmesserly): it would be nice to just inline the method from core, |
| 2174 // instead of special cases here. | 2253 // instead of special cases here. |
| 2175 if (op.type == TokenType.TILDE_SLASH) { | 2254 if (op.type == TokenType.TILDE_SLASH) { |
| 2176 // `a ~/ b` is equivalent to `(a / b).truncate()` | 2255 // `a ~/ b` is equivalent to `(a / b).truncate()` |
| 2177 var div = AstBuilder.binaryExpression(left, '/', right) | 2256 var div = AstBuilder.binaryExpression(left, '/', right) |
| 2178 ..staticType = node.staticType; | 2257 ..staticType = node.staticType; |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2255 _bindValue(scope, 'i', index.index, context: context), | 2334 _bindValue(scope, 'i', index.index, context: context), |
| 2256 index.rightBracket); | 2335 index.rightBracket); |
| 2257 } else if (expr is PropertyAccess) { | 2336 } else if (expr is PropertyAccess) { |
| 2258 PropertyAccess prop = expr; | 2337 PropertyAccess prop = expr; |
| 2259 result = new PropertyAccess( | 2338 result = new PropertyAccess( |
| 2260 _bindValue(scope, 'o', _getTarget(prop), context: context), | 2339 _bindValue(scope, 'o', _getTarget(prop), context: context), |
| 2261 prop.operator, | 2340 prop.operator, |
| 2262 prop.propertyName); | 2341 prop.propertyName); |
| 2263 } else if (expr is PrefixedIdentifier) { | 2342 } else if (expr is PrefixedIdentifier) { |
| 2264 PrefixedIdentifier ident = expr; | 2343 PrefixedIdentifier ident = expr; |
| 2344 if (isLibraryPrefix(ident.prefix)) { | |
| 2345 return expr; | |
| 2346 } | |
| 2265 result = new PrefixedIdentifier( | 2347 result = new PrefixedIdentifier( |
| 2266 _bindValue(scope, 'o', ident.prefix, context: context) | 2348 _bindValue(scope, 'o', ident.prefix, context: context) |
| 2267 as SimpleIdentifier, | 2349 as SimpleIdentifier, |
| 2268 ident.period, | 2350 ident.period, |
| 2269 ident.identifier); | 2351 ident.identifier); |
| 2270 } else { | 2352 } else { |
| 2271 return expr as SimpleIdentifier; | 2353 return expr as SimpleIdentifier; |
| 2272 } | 2354 } |
| 2273 result.staticType = expr.staticType; | 2355 result.staticType = expr.staticType; |
| 2274 DynamicInvoke.set(result, DynamicInvoke.get(expr)); | 2356 DynamicInvoke.set(result, DynamicInvoke.get(expr)); |
| 2275 return result; | 2357 return result; |
| 2276 } | 2358 } |
| 2277 | 2359 |
| 2278 /// Creates a temporary to contain the value of [expr]. The temporary can be | 2360 /// Creates a temporary to contain the value of [expr]. The temporary can be |
| 2279 /// used multiple times in the resulting expression. For example: | 2361 /// used multiple times in the resulting expression. For example: |
| 2280 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will | 2362 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
| 2281 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. | 2363 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
| 2282 /// | 2364 /// |
| 2283 /// If the expression does not end up using `x` more than once, or if those | 2365 /// If the expression does not end up using `x` more than once, or if those |
| 2284 /// expressions can be treated as stateless (e.g. they are non-mutated | 2366 /// expressions can be treated as stateless (e.g. they are non-mutated |
| 2285 /// variables), then the resulting code will be simplified automatically. | 2367 /// variables), then the resulting code will be simplified automatically. |
| 2286 /// | 2368 /// |
| 2287 /// [scope] will be mutated to contain the new temporary's initialization. | 2369 /// [scope] will be mutated to contain the new temporary's initialization. |
| 2288 Expression _bindValue( | 2370 Expression _bindValue( |
| 2289 Map<String, JS.Expression> scope, String name, Expression expr, | 2371 Map<String, JS.Expression> scope, String name, Expression expr, |
| 2290 {Expression context}) { | 2372 {Expression context}) { |
| 2291 // No need to do anything for stateless expressions. | 2373 // No need to do anything for stateless expressions. |
| 2292 if (isStateless(expr, context)) return expr; | 2374 if (isStateless(expr, context)) return expr; |
| 2293 | 2375 |
| 2294 var t = _createTemporary('#$name', expr.staticType); | 2376 var t = _createTemporary('#$name', getStaticType(expr)); |
| 2295 scope[name] = _visit(expr); | 2377 scope[name] = _visit(expr); |
| 2296 return t; | 2378 return t; |
| 2297 } | 2379 } |
| 2298 | 2380 |
| 2299 /// Desugars postfix increment. | 2381 /// Desugars postfix increment. |
| 2300 /// | 2382 /// |
| 2301 /// In the general case [expr] can be one of [IndexExpression], | 2383 /// In the general case [expr] can be one of [IndexExpression], |
| 2302 /// [PrefixExpression] or [PropertyAccess] and we need to | 2384 /// [PrefixExpression] or [PropertyAccess] and we need to |
| 2303 /// ensure sub-expressions are evaluated once. | 2385 /// ensure sub-expressions are evaluated once. |
| 2304 /// | 2386 /// |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2350 JS.Expression visitPrefixExpression(PrefixExpression node) { | 2432 JS.Expression visitPrefixExpression(PrefixExpression node) { |
| 2351 var op = node.operator; | 2433 var op = node.operator; |
| 2352 var expr = node.operand; | 2434 var expr = node.operand; |
| 2353 | 2435 |
| 2354 var dispatchType = getStaticType(expr); | 2436 var dispatchType = getStaticType(expr); |
| 2355 if (unaryOperationIsPrimitive(dispatchType)) { | 2437 if (unaryOperationIsPrimitive(dispatchType)) { |
| 2356 if (_isNonNullableExpression(expr)) { | 2438 if (_isNonNullableExpression(expr)) { |
| 2357 return js.call('$op#', _visit(expr)); | 2439 return js.call('$op#', _visit(expr)); |
| 2358 } else if (op.lexeme == '++' || op.lexeme == '--') { | 2440 } else if (op.lexeme == '++' || op.lexeme == '--') { |
| 2359 // We need a null check, so the increment must be expanded out. | 2441 // We need a null check, so the increment must be expanded out. |
| 2360 var mathop = op.lexeme[0]; | |
| 2361 var vars = <String, JS.Expression>{}; | 2442 var vars = <String, JS.Expression>{}; |
| 2362 var x = _bindLeftHandSide(vars, expr, context: expr); | 2443 var x = _bindLeftHandSide(vars, expr, context: expr); |
| 2363 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); | 2444 |
| 2364 return new JS.MetaLet(vars, [body]); | 2445 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
| 2446 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) | |
| 2447 ..staticElement = node.staticElement | |
| 2448 ..staticType = getStaticType(expr); | |
| 2449 | |
| 2450 return new JS.MetaLet(vars, [_emitSet(x, increment)]); | |
| 2365 } else { | 2451 } else { |
| 2366 return js.call('$op#', notNull(expr)); | 2452 return js.call('$op#', notNull(expr)); |
| 2367 } | 2453 } |
| 2368 } | 2454 } |
| 2369 | 2455 |
| 2370 if (op.lexeme == '++' || op.lexeme == '--') { | 2456 if (op.lexeme == '++' || op.lexeme == '--') { |
| 2371 // Increment or decrement requires expansion. | 2457 // Increment or decrement requires expansion. |
| 2372 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions | 2458 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions |
| 2373 // (for example, x is IndexExpression) we evaluate those once. | 2459 // (for example, x is IndexExpression) we evaluate those once. |
| 2374 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; | 2460 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2414 @override | 2500 @override |
| 2415 visitPrefixedIdentifier(PrefixedIdentifier node) { | 2501 visitPrefixedIdentifier(PrefixedIdentifier node) { |
| 2416 if (isLibraryPrefix(node.prefix)) { | 2502 if (isLibraryPrefix(node.prefix)) { |
| 2417 return _visit(node.identifier); | 2503 return _visit(node.identifier); |
| 2418 } else { | 2504 } else { |
| 2419 return _emitGet(node.prefix, node.identifier); | 2505 return _emitGet(node.prefix, node.identifier); |
| 2420 } | 2506 } |
| 2421 } | 2507 } |
| 2422 | 2508 |
| 2423 @override | 2509 @override |
| 2424 visitPropertyAccess(PropertyAccess node) => | 2510 visitPropertyAccess(PropertyAccess node) { |
| 2425 _emitGet(_getTarget(node), node.propertyName); | 2511 if (node.operator.lexeme == '?.') { |
| 2512 return _emitNullSafe(node); | |
| 2513 } | |
| 2514 return _emitGet(_getTarget(node), node.propertyName); | |
| 2515 } | |
| 2516 | |
| 2517 JS.Expression _emitNullSafe(Expression node) { | |
| 2518 // Desugar ?. sequence by passing a sequence of callbacks that applies | |
| 2519 // each operation in sequence: | |
| 2520 // | |
| 2521 // obj?.foo()?.bar | |
| 2522 // --> | |
| 2523 // nullSafe(obj, _ => _.foo(), _ => _.bar); | |
| 2524 // | |
| 2525 // This pattern has the benefit of preserving order, as well as minimizing | |
| 2526 // code expansion: each `?.` becomes `, _ => _`, plus one helper call. | |
|
vsm
2015/08/25 21:47:26
Very nice!
| |
| 2527 // | |
| 2528 // TODO(jmesserly): we could desugar with MetaLet instead, which may | |
| 2529 // lead to higher performing code, but at the cost of readability. | |
| 2530 var tail = <JS.Expression>[]; | |
| 2531 for (;;) { | |
| 2532 var op = _getOperator(node); | |
| 2533 if (op != null && op.lexeme == '?.') { | |
| 2534 var nodeTarget = _getTarget(node); | |
| 2535 if (_isNonNullableExpression(nodeTarget)) { | |
| 2536 node = _stripNullAwareOp(node, nodeTarget); | |
| 2537 break; | |
| 2538 } | |
| 2539 | |
| 2540 var param = _createTemporary('_', nodeTarget.staticType); | |
| 2541 var baseNode = _stripNullAwareOp(node, param); | |
| 2542 tail.add(new JS.ArrowFun([_visit(param)], _visit(baseNode))); | |
| 2543 node = nodeTarget; | |
| 2544 } else { | |
| 2545 break; | |
| 2546 } | |
| 2547 } | |
| 2548 if (tail.isEmpty) return _visit(node); | |
| 2549 return js.call('dart.nullSafe(#, #)', [_visit(node), tail.reversed]); | |
| 2550 } | |
| 2551 | |
| 2552 static Token _getOperator(Expression node) { | |
| 2553 if (node is PropertyAccess) return node.operator; | |
| 2554 if (node is MethodInvocation) return node.operator; | |
| 2555 return null; | |
| 2556 } | |
| 2557 | |
| 2558 // TODO(jmesserly): this is dropping source location. | |
| 2559 Expression _stripNullAwareOp(Expression node, Expression newTarget) { | |
| 2560 if (node is PropertyAccess) { | |
| 2561 return AstBuilder.propertyAccess(newTarget, node.propertyName); | |
| 2562 } else { | |
| 2563 var invoke = node as MethodInvocation; | |
| 2564 return AstBuilder.methodInvoke( | |
| 2565 newTarget, invoke.methodName, invoke.argumentList.arguments); | |
| 2566 } | |
| 2567 } | |
| 2426 | 2568 |
| 2427 bool _requiresStaticDispatch(Expression target, String memberName) { | 2569 bool _requiresStaticDispatch(Expression target, String memberName) { |
| 2428 var type = getStaticType(target); | 2570 var type = getStaticType(target); |
| 2429 if (!rules.objectMembers.containsKey(memberName)) { | 2571 if (!rules.objectMembers.containsKey(memberName)) { |
| 2430 return false; | 2572 return false; |
| 2431 } | 2573 } |
| 2432 if (!type.isObject && | 2574 if (!type.isObject && |
| 2433 !_isJSBuiltinType(type) && | 2575 !_isJSBuiltinType(type) && |
| 2434 _isNonNullableExpression(target)) { | 2576 _isNonNullableExpression(target)) { |
| 2435 return false; | 2577 return false; |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2502 return new JS.PropertyAccess(_visit(target), _visit(node.index)); | 2644 return new JS.PropertyAccess(_visit(target), _visit(node.index)); |
| 2503 } | 2645 } |
| 2504 return _emitSend(target, '[]', [node.index]); | 2646 return _emitSend(target, '[]', [node.index]); |
| 2505 } | 2647 } |
| 2506 | 2648 |
| 2507 // TODO(jmesserly): ideally we'd check the method and see if it is marked | 2649 // TODO(jmesserly): ideally we'd check the method and see if it is marked |
| 2508 // `external`, but that doesn't work because it isn't in the element model. | 2650 // `external`, but that doesn't work because it isn't in the element model. |
| 2509 bool _useNativeJsIndexer(DartType type) => | 2651 bool _useNativeJsIndexer(DartType type) => |
| 2510 findAnnotation(type.element, _isJsNameAnnotation) != null; | 2652 findAnnotation(type.element, _isJsNameAnnotation) != null; |
| 2511 | 2653 |
| 2512 /// Gets the target of a [PropertyAccess] or [IndexExpression]. | 2654 /// Gets the target of a [PropertyAccess], [IndexExpression], or |
| 2513 /// Those two nodes are special because they're both allowed on left side of | 2655 /// [MethodInvocation]. These three nodes can appear in a [CascadeExpression]. |
| 2514 /// an assignment expression and cascades. | |
| 2515 Expression _getTarget(node) { | 2656 Expression _getTarget(node) { |
| 2516 assert(node is IndexExpression || node is PropertyAccess); | 2657 assert(node is IndexExpression || |
| 2658 node is PropertyAccess || | |
| 2659 node is MethodInvocation); | |
| 2517 return node.isCascaded ? _cascadeTarget : node.target; | 2660 return node.isCascaded ? _cascadeTarget : node.target; |
| 2518 } | 2661 } |
| 2519 | 2662 |
| 2520 @override | 2663 @override |
| 2521 visitConditionalExpression(ConditionalExpression node) { | 2664 visitConditionalExpression(ConditionalExpression node) { |
| 2522 return js.call('# ? # : #', [ | 2665 return js.call('# ? # : #', [ |
| 2523 notNull(node.condition), | 2666 notNull(node.condition), |
| 2524 _visit(node.thenExpression), | 2667 _visit(node.thenExpression), |
| 2525 _visit(node.elseExpression) | 2668 _visit(node.elseExpression) |
| 2526 ]); | 2669 ]); |
| (...skipping 548 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3075 | 3218 |
| 3076 /// A special kind of element created by the compiler, signifying a temporary | 3219 /// A special kind of element created by the compiler, signifying a temporary |
| 3077 /// variable. These objects use instance equality, and should be shared | 3220 /// variable. These objects use instance equality, and should be shared |
| 3078 /// everywhere in the tree where they are treated as the same variable. | 3221 /// everywhere in the tree where they are treated as the same variable. |
| 3079 class TemporaryVariableElement extends LocalVariableElementImpl { | 3222 class TemporaryVariableElement extends LocalVariableElementImpl { |
| 3080 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); | 3223 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); |
| 3081 | 3224 |
| 3082 int get hashCode => identityHashCode(this); | 3225 int get hashCode => identityHashCode(this); |
| 3083 bool operator ==(Object other) => identical(this, other); | 3226 bool operator ==(Object other) => identical(this, other); |
| 3084 } | 3227 } |
| OLD | NEW |