Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(230)

Side by Side Diff: lib/src/codegen/js_codegen.dart

Issue 1316723003: implement null aware ops, fixes #249 (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: format Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698