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 |