| 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; | 7 import 'dart:collection' show HashSet, HashMap; |
| 8 import 'dart:io' show Directory, File; | 8 import 'dart:io' show Directory, File; |
| 9 | 9 |
| 10 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 24 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
| 25 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 25 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
| 26 | 26 |
| 27 import 'package:dev_compiler/src/checker/rules.dart'; | 27 import 'package:dev_compiler/src/checker/rules.dart'; |
| 28 import 'package:dev_compiler/src/info.dart'; | 28 import 'package:dev_compiler/src/info.dart'; |
| 29 import 'package:dev_compiler/src/options.dart'; | 29 import 'package:dev_compiler/src/options.dart'; |
| 30 import 'package:dev_compiler/src/utils.dart'; | 30 import 'package:dev_compiler/src/utils.dart'; |
| 31 | 31 |
| 32 import 'code_generator.dart'; | 32 import 'code_generator.dart'; |
| 33 import 'js_names.dart'; | 33 import 'js_names.dart'; |
| 34 import 'js_metalet.dart'; |
| 34 | 35 |
| 35 bool _isAnnotationType(Annotation m, String name) => m.name.name == name; | 36 bool _isAnnotationType(Annotation m, String name) => m.name.name == name; |
| 36 | 37 |
| 37 Annotation _getAnnotation(AnnotatedNode node, String name) => node.metadata | 38 Annotation _getAnnotation(AnnotatedNode node, String name) => node.metadata |
| 38 .firstWhere((annotation) => _isAnnotationType(annotation, name), | 39 .firstWhere((annotation) => _isAnnotationType(annotation, name), |
| 39 orElse: () => null); | 40 orElse: () => null); |
| 40 | 41 |
| 41 Annotation _getJsNameAnnotation(AnnotatedNode node) => | 42 Annotation _getJsNameAnnotation(AnnotatedNode node) => |
| 42 _getAnnotation(node, "JsName"); | 43 _getAnnotation(node, "JsName"); |
| 43 | 44 |
| (...skipping 1037 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1081 } | 1082 } |
| 1082 | 1083 |
| 1083 // initializing formal parameter, e.g. `Point(this.x)` | 1084 // initializing formal parameter, e.g. `Point(this.x)` |
| 1084 if (e is ParameterElement && e.isInitializingFormal && e.isPrivate) { | 1085 if (e is ParameterElement && e.isInitializingFormal && e.isPrivate) { |
| 1085 /// Rename private names so they don't shadow the private field symbol. | 1086 /// Rename private names so they don't shadow the private field symbol. |
| 1086 /// The renamer would handle this, but it would prefer to rename the | 1087 /// The renamer would handle this, but it would prefer to rename the |
| 1087 /// temporary used for the private symbol. Instead rename the parameter. | 1088 /// temporary used for the private symbol. Instead rename the parameter. |
| 1088 return new JSTemporary('${name.substring(1)}'); | 1089 return new JSTemporary('${name.substring(1)}'); |
| 1089 } | 1090 } |
| 1090 | 1091 |
| 1091 if (_isTemporary(e)) return new JSTemporary(e.name); | 1092 if (_isTemporary(e)) { |
| 1093 if (name[0] == '#') { |
| 1094 return new JS.InterpolatedExpression(name.substring(1)); |
| 1095 } else { |
| 1096 return new JSTemporary(e.name); |
| 1097 } |
| 1098 } |
| 1092 | 1099 |
| 1093 return new JS.Identifier(name); | 1100 return new JS.Identifier(name); |
| 1094 } | 1101 } |
| 1095 | 1102 |
| 1096 JS.Expression _emitTypeName(DartType type) { | 1103 JS.Expression _emitTypeName(DartType type) { |
| 1097 var name = type.name; | 1104 var name = type.name; |
| 1098 var element = type.element; | 1105 var element = type.element; |
| 1099 if (name == '') { | 1106 if (name == '') { |
| 1100 // TODO(jmesserly): remove when we're using coercion reifier. | 1107 // TODO(jmesserly): remove when we're using coercion reifier. |
| 1101 return _unimplementedCall('Unimplemented type $type'); | 1108 return _unimplementedCall('Unimplemented type $type'); |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1143 _visit(target), | 1150 _visit(target), |
| 1144 js.string(id.name, "'"), | 1151 js.string(id.name, "'"), |
| 1145 _visit(rhs) | 1152 _visit(rhs) |
| 1146 ]); | 1153 ]); |
| 1147 } else { | 1154 } else { |
| 1148 return null; | 1155 return null; |
| 1149 } | 1156 } |
| 1150 } | 1157 } |
| 1151 | 1158 |
| 1152 @override | 1159 @override |
| 1153 JS.Node visitAssignmentExpression(AssignmentExpression node) { | 1160 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
| 1154 var lhs = node.leftHandSide; | 1161 var left = node.leftHandSide; |
| 1155 var rhs = node.rightHandSide; | 1162 var right = node.rightHandSide; |
| 1156 return _emitSet(lhs, rhs, node.parent); | 1163 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
| 1164 return _emitOpAssign(left, right, node.operator.lexeme[0], context: node); |
| 1157 } | 1165 } |
| 1158 | 1166 |
| 1159 JS.Node _emitSet(Expression lhs, Expression rhs, [AstNode parent]) { | 1167 JSMetaLet _emitOpAssign(Expression left, Expression right, String op, |
| 1168 {Expression context}) { |
| 1169 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions |
| 1170 // (for example, x is IndexExpression) we evaluate those once. |
| 1171 var vars = {}; |
| 1172 var lhs = _bindLeftHandSide(vars, left, context: context); |
| 1173 var inc = AstBuilder.binaryExpression(lhs, op, right); |
| 1174 inc.staticType = rules.getStaticType(left); |
| 1175 return new JSMetaLet(vars, [_emitSet(lhs, inc)]); |
| 1176 } |
| 1177 |
| 1178 JS.Expression _emitSet(Expression lhs, Expression rhs) { |
| 1160 if (lhs is IndexExpression) { | 1179 if (lhs is IndexExpression) { |
| 1161 String code; | 1180 String code; |
| 1162 var target = _getTarget(lhs); | 1181 var target = _getTarget(lhs); |
| 1163 if (rules.isDynamicTarget(target)) { | 1182 if (rules.isDynamicTarget(target)) { |
| 1164 code = 'dart.dsetindex(#, #, #)'; | 1183 code = 'dart.dsetindex(#, #, #)'; |
| 1165 return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]); | 1184 return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]); |
| 1166 } | 1185 } |
| 1167 return js.call('#.#(#, #)', [ | 1186 return js.call('#.#(#, #)', [ |
| 1168 _visit(target), | 1187 _visit(target), |
| 1169 _emitMemberName('[]=', target: target), | 1188 _emitMemberName('[]=', target: target), |
| 1170 _visit(lhs.index), | 1189 _visit(lhs.index), |
| 1171 _visit(rhs) | 1190 _visit(rhs) |
| 1172 ]); | 1191 ]); |
| 1173 } | 1192 } |
| 1174 | 1193 |
| 1175 if (lhs is PropertyAccess) { | 1194 if (lhs is PropertyAccess) { |
| 1176 var result = _emitDSetIfDynamic(_getTarget(lhs), lhs.propertyName, rhs); | 1195 var result = _emitDSetIfDynamic(_getTarget(lhs), lhs.propertyName, rhs); |
| 1177 if (result != null) return result; | 1196 if (result != null) return result; |
| 1178 } else if (lhs is PrefixedIdentifier) { | 1197 } else if (lhs is PrefixedIdentifier) { |
| 1179 // TODO(vsm): Is this the right code if the prefix is a library? | 1198 // TODO(vsm): Is this the right code if the prefix is a library? |
| 1180 var result = _emitDSetIfDynamic(lhs.prefix, lhs.identifier, rhs); | 1199 var result = _emitDSetIfDynamic(lhs.prefix, lhs.identifier, rhs); |
| 1181 if (result != null) return result; | 1200 if (result != null) return result; |
| 1182 } | 1201 } |
| 1183 | 1202 return _visit(rhs).toAssignExpression(_visit(lhs)); |
| 1184 if (parent is ExpressionStatement && | |
| 1185 rhs is CascadeExpression && | |
| 1186 _isStateless(lhs, rhs)) { | |
| 1187 // Special case: cascade assignment to a variable in a statement. | |
| 1188 // We can reuse the variable to desugar it: | |
| 1189 // result = []..length = length; | |
| 1190 // becomes: | |
| 1191 // result = []; | |
| 1192 // result.length = length; | |
| 1193 var savedCascadeTemp = _cascadeTarget; | |
| 1194 _cascadeTarget = lhs; | |
| 1195 | |
| 1196 var body = []; | |
| 1197 body.add(js.statement('# = #;', [_visit(lhs), _visit(rhs.target)])); | |
| 1198 for (var section in rhs.cascadeSections) { | |
| 1199 body.add(new JS.ExpressionStatement(_visit(section))); | |
| 1200 } | |
| 1201 | |
| 1202 _cascadeTarget = savedCascadeTemp; | |
| 1203 return _statement(body); | |
| 1204 } | |
| 1205 | |
| 1206 return js.call('# = #', [_visit(lhs), _visit(rhs)]); | |
| 1207 } | 1203 } |
| 1208 | 1204 |
| 1209 @override | 1205 @override |
| 1210 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { | 1206 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| 1211 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); | 1207 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
| 1212 var ret = new JS.Return(_visit(node.expression)); | 1208 var ret = new JS.Return(_visit(node.expression)); |
| 1213 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); | 1209 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
| 1214 } | 1210 } |
| 1215 | 1211 |
| 1216 @override | 1212 @override |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1326 result.add(_namedArgTemp); | 1322 result.add(_namedArgTemp); |
| 1327 break; | 1323 break; |
| 1328 } | 1324 } |
| 1329 result.add(_visit(param)); | 1325 result.add(_visit(param)); |
| 1330 } | 1326 } |
| 1331 return result; | 1327 return result; |
| 1332 } | 1328 } |
| 1333 | 1329 |
| 1334 @override | 1330 @override |
| 1335 JS.Statement visitExpressionStatement(ExpressionStatement node) => | 1331 JS.Statement visitExpressionStatement(ExpressionStatement node) => |
| 1336 _expressionStatement(_visit(node.expression)); | 1332 _visit(node.expression).toStatement(); |
| 1337 | |
| 1338 // Some expressions may choose to generate themselves as JS statements | |
| 1339 // if their parent is in a statement context. | |
| 1340 // TODO(jmesserly): refactor so we handle the special cases here, and | |
| 1341 // can use better return types on the expression visit methods. | |
| 1342 JS.Statement _expressionStatement(expr) => | |
| 1343 expr is JS.Statement ? expr : new JS.ExpressionStatement(expr); | |
| 1344 | 1333 |
| 1345 @override | 1334 @override |
| 1346 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => | 1335 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => |
| 1347 new JS.EmptyStatement(); | 1336 new JS.EmptyStatement(); |
| 1348 | 1337 |
| 1349 @override | 1338 @override |
| 1350 JS.Statement visitAssertStatement(AssertStatement node) => | 1339 JS.Statement visitAssertStatement(AssertStatement node) => |
| 1351 // TODO(jmesserly): only emit in checked mode. | 1340 // TODO(jmesserly): only emit in checked mode. |
| 1352 js.statement('dart.assert(#);', _visit(node.condition)); | 1341 js.statement('dart.assert(#);', _visit(node.condition)); |
| 1353 | 1342 |
| 1354 @override | 1343 @override |
| 1355 JS.Return visitReturnStatement(ReturnStatement node) => | 1344 JS.Statement visitReturnStatement(ReturnStatement node) { |
| 1356 new JS.Return(_visit(node.expression)); | 1345 var e = node.expression; |
| 1346 if (e == null) return new JS.Return(); |
| 1347 return _visit(e).toReturn(); |
| 1348 } |
| 1357 | 1349 |
| 1358 @override | 1350 @override |
| 1359 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | 1351 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| 1360 var body = <JS.Statement>[]; | 1352 var body = <JS.Statement>[]; |
| 1361 | 1353 |
| 1362 for (var field in node.variables.variables) { | 1354 for (var field in node.variables.variables) { |
| 1363 if (field.isConst) { | 1355 if (field.isConst) { |
| 1364 // constant fields don't change, so we can generate them as `let` | 1356 // constant fields don't change, so we can generate them as `let` |
| 1365 // but add them to the module's exports | 1357 // but add them to the module's exports |
| 1366 var name = field.name.name; | 1358 var name = field.name.name; |
| 1367 body.add(js.statement( | 1359 body.add(js.statement( |
| 1368 'let # = #;', [new JS.Identifier(name), _visitInitializer(field)])); | 1360 'let # = #;', [new JS.Identifier(name), _visitInitializer(field)])); |
| 1369 if (isPublic(name)) _addExport(name); | 1361 if (isPublic(name)) _addExport(name); |
| 1370 } else if (_isFieldInitConstant(field)) { | 1362 } else if (_isFieldInitConstant(field)) { |
| 1371 body.add(js.statement( | 1363 body.add(js.statement( |
| 1372 '# = #;', [_visit(field.name), _visitInitializer(field)])); | 1364 '# = #;', [_visit(field.name), _visitInitializer(field)])); |
| 1373 } else { | 1365 } else { |
| 1374 _lazyFields.add(field); | 1366 _lazyFields.add(field); |
| 1375 } | 1367 } |
| 1376 } | 1368 } |
| 1377 | 1369 |
| 1378 return _statement(body); | 1370 return _statement(body); |
| 1379 } | 1371 } |
| 1380 | 1372 |
| 1381 _addExport(String name) { | 1373 _addExport(String name) { |
| 1382 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; | 1374 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; |
| 1383 } | 1375 } |
| 1384 | 1376 |
| 1385 @override | 1377 @override |
| 1378 JS.Statement visitVariableDeclarationStatement( |
| 1379 VariableDeclarationStatement node) { |
| 1380 // Special case a single variable with an initializer. |
| 1381 // This helps emit cleaner code for things like: |
| 1382 // var result = []..add(1)..add(2); |
| 1383 if (node.variables.variables.length == 1) { |
| 1384 var v = node.variables.variables.single; |
| 1385 if (v.initializer != null) { |
| 1386 var name = new JS.Identifier(v.name.name); |
| 1387 return _visit(v.initializer).toVariableDeclaration(name); |
| 1388 } |
| 1389 } |
| 1390 return _visit(node.variables).toStatement(); |
| 1391 } |
| 1392 |
| 1393 @override |
| 1386 visitVariableDeclarationList(VariableDeclarationList node) { | 1394 visitVariableDeclarationList(VariableDeclarationList node) { |
| 1387 var last = node.variables.last; | 1395 return new JS.VariableDeclarationList('let', _visitList(node.variables)); |
| 1388 var lastInitializer = last.initializer; | |
| 1389 | |
| 1390 List<JS.VariableInitialization> variables; | |
| 1391 if (lastInitializer is CascadeExpression && | |
| 1392 node.parent is VariableDeclarationStatement) { | |
| 1393 // Special case: cascade as variable initializer | |
| 1394 // | |
| 1395 // We can reuse the variable to desugar it: | |
| 1396 // var result = []..length = length; | |
| 1397 // becomes: | |
| 1398 // var result = []; | |
| 1399 // result.length = length; | |
| 1400 var savedCascadeTemp = _cascadeTarget; | |
| 1401 _cascadeTarget = last.name; | |
| 1402 | |
| 1403 variables = _visitList(node.variables.take(node.variables.length - 1)); | |
| 1404 variables.add(new JS.VariableInitialization( | |
| 1405 new JS.Identifier(last.name.name), _visit(lastInitializer.target))); | |
| 1406 | |
| 1407 var result = | |
| 1408 <JS.Expression>[new JS.VariableDeclarationList('let', variables)]; | |
| 1409 result.addAll(_visitList(lastInitializer.cascadeSections)); | |
| 1410 _cascadeTarget = savedCascadeTemp; | |
| 1411 return _statement(result.map((e) => new JS.ExpressionStatement(e))); | |
| 1412 } else { | |
| 1413 variables = _visitList(node.variables); | |
| 1414 } | |
| 1415 | |
| 1416 return new JS.VariableDeclarationList('let', variables); | |
| 1417 } | 1396 } |
| 1418 | 1397 |
| 1419 @override | 1398 @override |
| 1420 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { | 1399 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { |
| 1421 var name = new JS.Identifier(node.name.name); | 1400 var name = new JS.Identifier(node.name.name); |
| 1422 return new JS.VariableInitialization(name, _visitInitializer(node)); | 1401 return new JS.VariableInitialization(name, _visitInitializer(node)); |
| 1423 } | 1402 } |
| 1424 | 1403 |
| 1425 JS.Expression _visitInitializer(VariableDeclaration node) { | 1404 JS.Expression _visitInitializer(VariableDeclaration node) { |
| 1426 var value = _visit(node.initializer); | 1405 var value = _visit(node.initializer); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1460 void _flushLibraryProperties(List<JS.Statement> body) { | 1439 void _flushLibraryProperties(List<JS.Statement> body) { |
| 1461 if (_properties.isEmpty) return; | 1440 if (_properties.isEmpty) return; |
| 1462 body.add(js.statement('dart.copyProperties(#, { # });', [ | 1441 body.add(js.statement('dart.copyProperties(#, { # });', [ |
| 1463 _exportsVar, | 1442 _exportsVar, |
| 1464 _properties.map(_emitTopLevelProperty) | 1443 _properties.map(_emitTopLevelProperty) |
| 1465 ])); | 1444 ])); |
| 1466 _properties.clear(); | 1445 _properties.clear(); |
| 1467 } | 1446 } |
| 1468 | 1447 |
| 1469 @override | 1448 @override |
| 1470 JS.Statement visitVariableDeclarationStatement( | |
| 1471 VariableDeclarationStatement node) => | |
| 1472 _expressionStatement(_visit(node.variables)); | |
| 1473 | |
| 1474 @override | |
| 1475 visitConstructorName(ConstructorName node) { | 1449 visitConstructorName(ConstructorName node) { |
| 1476 var typeName = _visit(node.type); | 1450 var typeName = _visit(node.type); |
| 1477 if (node.name != null) { | 1451 if (node.name != null) { |
| 1478 return js.call( | 1452 return js.call( |
| 1479 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); | 1453 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); |
| 1480 } | 1454 } |
| 1481 return typeName; | 1455 return typeName; |
| 1482 } | 1456 } |
| 1483 | 1457 |
| 1484 @override | 1458 @override |
| (...skipping 29 matching lines...) Expand all Loading... |
| 1514 | 1488 |
| 1515 // TODO(vsm): Revisit whether we really need this when we get | 1489 // TODO(vsm): Revisit whether we really need this when we get |
| 1516 // better non-nullability in the type system. | 1490 // better non-nullability in the type system. |
| 1517 | 1491 |
| 1518 if (expr is Literal && expr is! NullLiteral) { | 1492 if (expr is Literal && expr is! NullLiteral) { |
| 1519 return true; | 1493 return true; |
| 1520 } | 1494 } |
| 1521 if (expr is ParenthesizedExpression) { | 1495 if (expr is ParenthesizedExpression) { |
| 1522 return _isNonNullableExpression(expr.expression); | 1496 return _isNonNullableExpression(expr.expression); |
| 1523 } | 1497 } |
| 1498 if (expr is Conversion) { |
| 1499 return _isNonNullableExpression(expr.expression); |
| 1500 } |
| 1524 DartType type = null; | 1501 DartType type = null; |
| 1525 if (expr is BinaryExpression) { | 1502 if (expr is BinaryExpression) { |
| 1526 type = rules.getStaticType(expr.leftOperand); | 1503 type = rules.getStaticType(expr.leftOperand); |
| 1527 } else if (expr is PrefixExpression) { | 1504 } else if (expr is PrefixExpression) { |
| 1528 type = rules.getStaticType(expr.operand); | 1505 type = rules.getStaticType(expr.operand); |
| 1529 } else if (expr is PostfixExpression) { | 1506 } else if (expr is PostfixExpression) { |
| 1530 type = rules.getStaticType(expr.operand); | 1507 type = rules.getStaticType(expr.operand); |
| 1531 } | 1508 } |
| 1532 if (type != null && typeIsPrimitiveInJS(type)) { | 1509 if (type != null && typeIsPrimitiveInJS(type)) { |
| 1533 return true; | 1510 return true; |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1656 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 1633 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
| 1657 var id = | 1634 var id = |
| 1658 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); | 1635 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
| 1659 id.staticElement = new LocalVariableElementImpl.forNode(id); | 1636 id.staticElement = new LocalVariableElementImpl.forNode(id); |
| 1660 id.staticType = type; | 1637 id.staticType = type; |
| 1661 return id; | 1638 return id; |
| 1662 } | 1639 } |
| 1663 | 1640 |
| 1664 bool _isTemporary(Element node) => node.nameOffset == -1; | 1641 bool _isTemporary(Element node) => node.nameOffset == -1; |
| 1665 | 1642 |
| 1666 JS.Expression _emitPostfixIncrement(Expression expr, Token op) { | 1643 /// Desugars postfix increment. |
| 1644 /// |
| 1645 /// In the general case [expr] can be one of [IndexExpression], |
| 1646 /// [PrefixExpression] or [PropertyAccess] and we need to |
| 1647 /// ensure sub-expressions are evaluated once. |
| 1648 /// |
| 1649 /// We also need to ensure we can return the original value of the expression, |
| 1650 /// and that it is only evaluated once. |
| 1651 /// |
| 1652 /// We desugar this using let*. |
| 1653 /// |
| 1654 /// For example, `expr1[expr2]++` can be transformed to this: |
| 1655 /// |
| 1656 /// // psuedocode mix of Scheme and JS: |
| 1657 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
| 1658 /// |
| 1659 /// The [JSMetaLet] nodes automatically simplify themselves if they can. |
| 1660 /// For example, if the result value is not used, then `t` goes away. |
| 1661 JSMetaLet _emitPostfixIncrement(Expression expr, Token op) { |
| 1667 var type = rules.getStaticType(expr); | 1662 var type = rules.getStaticType(expr); |
| 1668 assert(type != null); | 1663 assert(type != null); |
| 1669 var tmp = _createTemporary('x', type); | |
| 1670 | 1664 |
| 1671 // Increment and write | 1665 // Handle the left hand side, to ensure each of its subexpressions are |
| 1666 // evaluated only once. |
| 1667 var vars = {}; |
| 1668 var left = _bindLeftHandSide(vars, expr, context: expr); |
| 1669 |
| 1670 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value |
| 1671 // and `x1` is the new value for `x`. |
| 1672 var x = _bindValue(vars, 'x', left, context: expr); |
| 1673 |
| 1672 var one = AstBuilder.integerLiteral(1); | 1674 var one = AstBuilder.integerLiteral(1); |
| 1673 one.staticType = rules.provider.intType; | 1675 one.staticType = rules.provider.intType; |
| 1674 var increment = AstBuilder.binaryExpression(tmp, op.lexeme[0], one); | 1676 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one); |
| 1675 increment.staticType = type; | 1677 increment.staticType = type; |
| 1676 var write = _emitSet(expr, increment); | |
| 1677 | 1678 |
| 1678 return js.call( | 1679 var body = [_emitSet(left, increment), _visit(x)]; |
| 1679 "((#) => (#, #))(#)", [_visit(tmp), write, _visit(tmp), _visit(expr)]); | 1680 return new JSMetaLet(vars, body, statelessResult: true); |
| 1681 } |
| 1682 |
| 1683 /// Returns a new expression, which can be be used safely *once* on the |
| 1684 /// left hand side, and *once* on the right side of an assignment. |
| 1685 /// For example: `expr1[expr2] += y` can be compiled as |
| 1686 /// `expr1[expr2] = expr1[expr2] + y`. |
| 1687 /// |
| 1688 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated |
| 1689 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. |
| 1690 /// |
| 1691 /// If the expression does not end up using `x1` or `x2` more than once, or |
| 1692 /// if those expressions can be treated as stateless (e.g. they are |
| 1693 /// non-mutated variables), then the resulting code will be simplified |
| 1694 /// automatically. |
| 1695 /// |
| 1696 /// [scope] can be mutated to contain any new temporaries that were created, |
| 1697 /// unless [expr] is a SimpleIdentifier, in which case a temporary is not |
| 1698 /// needed. |
| 1699 Expression _bindLeftHandSide( |
| 1700 Map<String, JS.Expression> scope, Expression expr, {Expression context}) { |
| 1701 if (expr is IndexExpression) { |
| 1702 IndexExpression index = expr; |
| 1703 return new IndexExpression.forTarget( |
| 1704 _bindValue(scope, 'o', index.target, context: context), |
| 1705 index.leftBracket, |
| 1706 _bindValue(scope, 'i', index.index, context: context), |
| 1707 index.rightBracket)..staticType = expr.staticType; |
| 1708 } else if (expr is PropertyAccess) { |
| 1709 PropertyAccess prop = expr; |
| 1710 return new PropertyAccess( |
| 1711 _bindValue(scope, 'o', prop.target, context: context), prop.operator, |
| 1712 prop.propertyName)..staticType = expr.staticType; |
| 1713 } else if (expr is PrefixedIdentifier) { |
| 1714 PrefixedIdentifier ident = expr; |
| 1715 return new PrefixedIdentifier( |
| 1716 _bindValue(scope, 'o', ident.prefix, context: context), ident.period, |
| 1717 ident.identifier)..staticType = expr.staticType; |
| 1718 } |
| 1719 return expr as SimpleIdentifier; |
| 1720 } |
| 1721 |
| 1722 /// Creates a temporary to contain the value of [expr]. The temporary can be |
| 1723 /// used multiple times in the resulting expression. For example: |
| 1724 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
| 1725 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
| 1726 /// |
| 1727 /// If the expression does not end up using `x` more than once, or if those |
| 1728 /// expressions can be treated as stateless (e.g. they are non-mutated |
| 1729 /// variables), then the resulting code will be simplified automatically. |
| 1730 /// |
| 1731 /// [scope] will be mutated to contain the new temporary's initialization. |
| 1732 Expression _bindValue( |
| 1733 Map<String, JS.Expression> scope, String name, Expression expr, |
| 1734 {Expression context}) { |
| 1735 // No need to do anything for stateless expressions. |
| 1736 if (_isStateless(expr, context)) return expr; |
| 1737 |
| 1738 var t = _createTemporary('#$name', expr.staticType); |
| 1739 scope[name] = _visit(expr); |
| 1740 return t; |
| 1680 } | 1741 } |
| 1681 | 1742 |
| 1682 @override | 1743 @override |
| 1683 JS.Expression visitPostfixExpression(PostfixExpression node) { | 1744 JS.Expression visitPostfixExpression(PostfixExpression node) { |
| 1684 var op = node.operator; | 1745 var op = node.operator; |
| 1685 var expr = node.operand; | 1746 var expr = node.operand; |
| 1686 | 1747 |
| 1687 if (node.parent is Statement) { | |
| 1688 // Prefix code is simpler. If the expr result isn't used, fall to that. | |
| 1689 return _emitPrefixExpression(op, expr); | |
| 1690 } | |
| 1691 | |
| 1692 var dispatchType = rules.getStaticType(expr); | 1748 var dispatchType = rules.getStaticType(expr); |
| 1693 if (unaryOperationIsPrimitive(dispatchType)) { | 1749 if (unaryOperationIsPrimitive(dispatchType)) { |
| 1694 if (_isNonNullableExpression(expr)) { | 1750 if (_isNonNullableExpression(expr)) { |
| 1695 return js.call('#$op', _visit(expr)); | 1751 return js.call('#$op', _visit(expr)); |
| 1696 } | 1752 } |
| 1697 } | 1753 } |
| 1698 | 1754 |
| 1699 assert(op.lexeme == '++' || op.lexeme == '--'); | 1755 assert(op.lexeme == '++' || op.lexeme == '--'); |
| 1700 return _emitPostfixIncrement(expr, op); | 1756 return _emitPostfixIncrement(expr, op); |
| 1701 } | 1757 } |
| 1702 | 1758 |
| 1703 JS.Expression _emitPrefixIncrement(Token op, Expression expr) { | |
| 1704 var one = AstBuilder.integerLiteral(1); | |
| 1705 one.staticType = rules.provider.intType; | |
| 1706 var increment = AstBuilder.binaryExpression(expr, op.lexeme[0], one); | |
| 1707 return _emitSet(expr, increment); | |
| 1708 } | |
| 1709 | |
| 1710 @override | 1759 @override |
| 1711 JS.Expression visitPrefixExpression(PrefixExpression node) { | 1760 JS.Expression visitPrefixExpression(PrefixExpression node) { |
| 1712 return _emitPrefixExpression(node.operator, node.operand); | 1761 return _emitPrefixExpression(node.operator, node.operand); |
| 1713 } | 1762 } |
| 1714 | 1763 |
| 1715 JS.Expression _emitPrefixExpression(Token op, Expression expr) { | 1764 JS.Expression _emitPrefixExpression(Token op, Expression expr) { |
| 1716 var dispatchType = rules.getStaticType(expr); | 1765 var dispatchType = rules.getStaticType(expr); |
| 1717 if (unaryOperationIsPrimitive(dispatchType)) { | 1766 if (unaryOperationIsPrimitive(dispatchType)) { |
| 1718 if (_isNonNullableExpression(expr)) { | 1767 if (_isNonNullableExpression(expr)) { |
| 1719 return js.call('$op#', _visit(expr)); | 1768 return js.call('$op#', _visit(expr)); |
| 1720 } else if (op.lexeme == '++' || op.lexeme == '--') { | 1769 } else if (op.lexeme == '++' || op.lexeme == '--') { |
| 1721 // We need a null check, so the increment must be expanded out. | 1770 // We need a null check, so the increment must be expanded out. |
| 1722 var mathop = op.lexeme[0]; | 1771 var mathop = op.lexeme[0]; |
| 1723 return js.call('# = # $mathop 1', [_visit(expr), notNull(expr)]); | 1772 var vars = {}; |
| 1773 var x = _bindLeftHandSide(vars, expr, context: expr); |
| 1774 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); |
| 1775 return new JSMetaLet(vars, [body]); |
| 1724 } else { | 1776 } else { |
| 1725 return js.call('$op#', notNull(expr)); | 1777 return js.call('$op#', notNull(expr)); |
| 1726 } | 1778 } |
| 1727 } else { | 1779 } |
| 1780 |
| 1781 if (op.lexeme == '++' || op.lexeme == '--') { |
| 1728 // Increment or decrement requires expansion. | 1782 // Increment or decrement requires expansion. |
| 1729 if (op.lexeme == '++' || op.lexeme == '--') { | 1783 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions |
| 1730 return _emitPrefixIncrement(op, expr); | 1784 // (for example, x is IndexExpression) we evaluate those once. |
| 1731 } | 1785 var one = AstBuilder.integerLiteral(1) |
| 1786 ..staticType = rules.provider.intType; |
| 1787 return _emitOpAssign(expr, one, op.lexeme[0], context: expr); |
| 1732 } | 1788 } |
| 1733 | 1789 |
| 1734 // Call the operator | 1790 // Call the operator |
| 1735 var opString = _emitMemberName(op.lexeme, unary: true); | 1791 var opString = _emitMemberName(op.lexeme, unary: true); |
| 1736 if (rules.isDynamicTarget(expr)) { | 1792 if (rules.isDynamicTarget(expr)) { |
| 1737 // dynamic dispatch | 1793 // dynamic dispatch |
| 1738 return js.call('dart.dunary(#, #)', [opString, _visit(expr)]); | 1794 return js.call('dart.dunary(#, #)', [opString, _visit(expr)]); |
| 1739 } else if (_isJSBuiltinType(dispatchType)) { | 1795 } else if (_isJSBuiltinType(dispatchType)) { |
| 1740 return js.call( | 1796 return js.call( |
| 1741 '#.#(#)', [_emitTypeName(dispatchType), opString, _visit(expr)]); | 1797 '#.#(#)', [_emitTypeName(dispatchType), opString, _visit(expr)]); |
| 1742 } else { | 1798 } else { |
| 1743 // Generic static-dispatch, user-defined operator code path. | 1799 // Generic static-dispatch, user-defined operator code path. |
| 1744 return js.call('#.#()', [_visit(expr), opString]); | 1800 return js.call('#.#()', [_visit(expr), opString]); |
| 1745 } | 1801 } |
| 1746 } | 1802 } |
| 1747 | 1803 |
| 1748 // Cascades can contain [IndexExpression], [MethodInvocation] and | 1804 // Cascades can contain [IndexExpression], [MethodInvocation] and |
| 1749 // [PropertyAccess]. The code generation for those is handled in their | 1805 // [PropertyAccess]. The code generation for those is handled in their |
| 1750 // respective visit methods. | 1806 // respective visit methods. |
| 1751 @override | 1807 @override |
| 1752 JS.Node visitCascadeExpression(CascadeExpression node) { | 1808 JS.Node visitCascadeExpression(CascadeExpression node) { |
| 1753 var savedCascadeTemp = _cascadeTarget; | 1809 var savedCascadeTemp = _cascadeTarget; |
| 1754 | 1810 |
| 1755 var parent = node.parent; | 1811 var vars = {}; |
| 1756 JS.Node result; | 1812 _cascadeTarget = _bindValue(vars, '_', node.target, context: node); |
| 1757 if (_isStateless(node.target, node)) { | 1813 var sections = _visitList(node.cascadeSections); |
| 1758 // Special case: target is stateless, so we can just reuse it. | 1814 sections.add(_visit(_cascadeTarget)); |
| 1759 _cascadeTarget = node.target; | 1815 var result = new JSMetaLet(vars, sections, statelessResult: true); |
| 1760 | |
| 1761 if (parent is ExpressionStatement) { | |
| 1762 var sections = _visitList(node.cascadeSections); | |
| 1763 result = _statement(sections.map((e) => new JS.ExpressionStatement(e))); | |
| 1764 } else { | |
| 1765 // Use comma expression. For example: | |
| 1766 // (sb.write(1), sb.write(2), sb) | |
| 1767 var sections = _visitListToBinary(node.cascadeSections, ','); | |
| 1768 result = new JS.Binary(',', sections, _visit(_cascadeTarget)); | |
| 1769 } | |
| 1770 } else { | |
| 1771 // In the general case we need to capture the target expression into | |
| 1772 // a temporary. This uses a lambda to get a temporary scope, and it also | |
| 1773 // remains valid in an expression context. | |
| 1774 _cascadeTarget = _createTemporary('_', node.target.staticType); | |
| 1775 | |
| 1776 var body = _visitList(node.cascadeSections); | |
| 1777 if (node.parent is! ExpressionStatement) { | |
| 1778 body.add(js.statement('return #;', _visit(_cascadeTarget))); | |
| 1779 } | |
| 1780 | |
| 1781 result = js.call('((#) => { # })(#)', [ | |
| 1782 _visit(_cascadeTarget), | |
| 1783 body, | |
| 1784 _visit(node.target) | |
| 1785 ]); | |
| 1786 } | |
| 1787 | |
| 1788 _cascadeTarget = savedCascadeTemp; | 1816 _cascadeTarget = savedCascadeTemp; |
| 1789 return result; | 1817 return result; |
| 1790 } | 1818 } |
| 1791 | 1819 |
| 1792 /// True is the expression can be evaluated multiple times without causing | |
| 1793 /// code execution. This is true for final fields. This can be true for local | |
| 1794 /// variables, if: | |
| 1795 /// | |
| 1796 /// * they are not assigned within the [context] scope. | |
| 1797 /// * they are not assigned in a function closure anywhere. | |
| 1798 /// | |
| 1799 /// This method is used to avoid creating temporaries in cases where we know | |
| 1800 /// we can safely re-evaluate [node] multiple times in [context]. This lets | |
| 1801 /// us generate prettier code. | |
| 1802 /// | |
| 1803 /// This method is conservative: it should never return `true` unless it is | |
| 1804 /// certain the [node] is stateless, because generated code may rely on the | |
| 1805 /// correctness of a `true` value. However it may return `false` for things | |
| 1806 /// that are in fact, stateless. | |
| 1807 bool _isStateless(Expression node, [AstNode context]) { | |
| 1808 if (node is SimpleIdentifier) { | |
| 1809 var e = node.staticElement; | |
| 1810 if (e is PropertyAccessorElement) e = e.variable; | |
| 1811 if (e is VariableElement && !e.isSynthetic) { | |
| 1812 if (e.isFinal) return true; | |
| 1813 | |
| 1814 // TODO(jmesserly): remove this when isPotentiallyMutated* is available | |
| 1815 // without the implementation class. Technically we shouldn't hit the | |
| 1816 // ParameterMember case based on current usage of _isStateless, but this | |
| 1817 // makes it clear we shouldn't rely on *Impl class. | |
| 1818 if (e is Member) e = e.baseElement; | |
| 1819 | |
| 1820 if (e is LocalVariableElementImpl || e is ParameterElementImpl) { | |
| 1821 // make sure the local isn't mutated in the context. | |
| 1822 return !_isPotentiallyMutated(e, context); | |
| 1823 } | |
| 1824 } | |
| 1825 } | |
| 1826 return false; | |
| 1827 } | |
| 1828 | |
| 1829 @override | 1820 @override |
| 1830 visitParenthesizedExpression(ParenthesizedExpression node) => | 1821 visitParenthesizedExpression(ParenthesizedExpression node) => |
| 1831 // The printer handles precedence so we don't need to. | 1822 // The printer handles precedence so we don't need to. |
| 1832 _visit(node.expression); | 1823 _visit(node.expression); |
| 1833 | 1824 |
| 1834 @override | 1825 @override |
| 1835 visitFormalParameter(FormalParameter node) => | 1826 visitFormalParameter(FormalParameter node) => |
| 1836 visitSimpleIdentifier(node.identifier); | 1827 visitSimpleIdentifier(node.identifier); |
| 1837 | 1828 |
| 1838 @override | 1829 @override |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1923 @override | 1914 @override |
| 1924 JS.If visitIfStatement(IfStatement node) { | 1915 JS.If visitIfStatement(IfStatement node) { |
| 1925 return new JS.If(_visit(node.condition), _visit(node.thenStatement), | 1916 return new JS.If(_visit(node.condition), _visit(node.thenStatement), |
| 1926 _visit(node.elseStatement)); | 1917 _visit(node.elseStatement)); |
| 1927 } | 1918 } |
| 1928 | 1919 |
| 1929 @override | 1920 @override |
| 1930 JS.For visitForStatement(ForStatement node) { | 1921 JS.For visitForStatement(ForStatement node) { |
| 1931 var init = _visit(node.initialization); | 1922 var init = _visit(node.initialization); |
| 1932 if (init == null) init = _visit(node.variables); | 1923 if (init == null) init = _visit(node.variables); |
| 1933 return new JS.For(init, _visit(node.condition), | 1924 var update = _visitListToBinary(node.updaters, ','); |
| 1934 _visitListToBinary(node.updaters, ','), _visit(node.body)); | 1925 if (update != null) update = update.toVoidExpression(); |
| 1926 return new JS.For(init, _visit(node.condition), update, _visit(node.body)); |
| 1935 } | 1927 } |
| 1936 | 1928 |
| 1937 @override | 1929 @override |
| 1938 JS.While visitWhileStatement(WhileStatement node) { | 1930 JS.While visitWhileStatement(WhileStatement node) { |
| 1939 return new JS.While(_visit(node.condition), _visit(node.body)); | 1931 return new JS.While(_visit(node.condition), _visit(node.body)); |
| 1940 } | 1932 } |
| 1941 | 1933 |
| 1942 @override | 1934 @override |
| 1943 JS.Do visitDoStatement(DoStatement node) { | 1935 JS.Do visitDoStatement(DoStatement node) { |
| 1944 return new JS.Do(_visit(node.body), _visit(node.condition)); | 1936 return new JS.Do(_visit(node.body), _visit(node.condition)); |
| (...skipping 261 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2206 List _visitList(Iterable<AstNode> nodes) { | 2198 List _visitList(Iterable<AstNode> nodes) { |
| 2207 if (nodes == null) return null; | 2199 if (nodes == null) return null; |
| 2208 var result = []; | 2200 var result = []; |
| 2209 for (var node in nodes) result.add(_visit(node)); | 2201 for (var node in nodes) result.add(_visit(node)); |
| 2210 return result; | 2202 return result; |
| 2211 } | 2203 } |
| 2212 | 2204 |
| 2213 /// Visits a list of expressions, creating a comma expression if needed in JS. | 2205 /// Visits a list of expressions, creating a comma expression if needed in JS. |
| 2214 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { | 2206 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { |
| 2215 if (nodes == null || nodes.isEmpty) return null; | 2207 if (nodes == null || nodes.isEmpty) return null; |
| 2216 | 2208 return new JS.Expression.binary(_visitList(nodes), operator); |
| 2217 JS.Expression result = null; | |
| 2218 for (var node in nodes) { | |
| 2219 var jsExpr = _visit(node); | |
| 2220 if (result == null) { | |
| 2221 result = jsExpr; | |
| 2222 } else { | |
| 2223 result = new JS.Binary(operator, result, jsExpr); | |
| 2224 } | |
| 2225 } | |
| 2226 return result; | |
| 2227 } | 2209 } |
| 2228 | 2210 |
| 2229 /// This handles member renaming for private names and operators. | 2211 /// This handles member renaming for private names and operators. |
| 2230 /// | 2212 /// |
| 2231 /// Private names are generated using ES6 symbols: | 2213 /// Private names are generated using ES6 symbols: |
| 2232 /// | 2214 /// |
| 2233 /// // At the top of the module: | 2215 /// // At the top of the module: |
| 2234 /// let _x = Symbol('_x'); | 2216 /// let _x = Symbol('_x'); |
| 2235 /// let _y = Symbol('_y'); | 2217 /// let _y = Symbol('_y'); |
| 2236 /// ... | 2218 /// ... |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2312 JS.Identifier _libraryName(LibraryElement library) { | 2294 JS.Identifier _libraryName(LibraryElement library) { |
| 2313 if (library == libraryInfo.library) return _exportsVar; | 2295 if (library == libraryInfo.library) return _exportsVar; |
| 2314 return new JS.Identifier(jsLibraryName(library)); | 2296 return new JS.Identifier(jsLibraryName(library)); |
| 2315 } | 2297 } |
| 2316 | 2298 |
| 2317 static bool _needsImplicitThis(Element e) => | 2299 static bool _needsImplicitThis(Element e) => |
| 2318 e is PropertyAccessorElement && !e.variable.isStatic || | 2300 e is PropertyAccessorElement && !e.variable.isStatic || |
| 2319 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; | 2301 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; |
| 2320 } | 2302 } |
| 2321 | 2303 |
| 2322 /// Returns true if the local variable is potentially mutated within [context]. | |
| 2323 /// This accounts for closures that may have been created outside of [context]. | |
| 2324 // TODO(jmesserly): change type annotation to not be *Impl once | |
| 2325 // isPotentiallyMutated is available on VariableElement. | |
| 2326 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { | |
| 2327 if (e.isPotentiallyMutatedInClosure) { | |
| 2328 // TODO(jmesserly): this returns true incorrectly in some cases, because | |
| 2329 // VariableResolverVisitor only checks that enclosingElement is not the | |
| 2330 // function element, but enclosingElement can be something else in some | |
| 2331 // cases (the block scope?). So it's more conservative than it could be. | |
| 2332 return true; | |
| 2333 } | |
| 2334 if (e.isPotentiallyMutatedInScope) { | |
| 2335 // Need to visit the context looking for assignment to this local. | |
| 2336 if (context != null) { | |
| 2337 var visitor = new _AssignmentFinder(e); | |
| 2338 context.accept(visitor); | |
| 2339 return visitor._potentiallyMutated; | |
| 2340 } | |
| 2341 return true; | |
| 2342 } | |
| 2343 return false; | |
| 2344 } | |
| 2345 | |
| 2346 /// Adapted from VariableResolverVisitor. Finds an assignment to a given | |
| 2347 /// local variable. | |
| 2348 class _AssignmentFinder extends RecursiveAstVisitor { | |
| 2349 final VariableElementImpl _variable; | |
| 2350 bool _potentiallyMutated = false; | |
| 2351 | |
| 2352 _AssignmentFinder(this._variable); | |
| 2353 | |
| 2354 @override | |
| 2355 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 2356 // Ignore if qualified. | |
| 2357 AstNode parent = node.parent; | |
| 2358 if (parent is PrefixedIdentifier && | |
| 2359 identical(parent.identifier, node)) return; | |
| 2360 if (parent is PropertyAccess && | |
| 2361 identical(parent.propertyName, node)) return; | |
| 2362 if (parent is MethodInvocation && | |
| 2363 identical(parent.methodName, node)) return; | |
| 2364 if (parent is ConstructorName) return; | |
| 2365 if (parent is Label) return; | |
| 2366 | |
| 2367 if (node.inSetterContext() && node.staticElement == _variable) { | |
| 2368 _potentiallyMutated = true; | |
| 2369 } | |
| 2370 } | |
| 2371 } | |
| 2372 | |
| 2373 class JSGenerator extends CodeGenerator { | 2304 class JSGenerator extends CodeGenerator { |
| 2374 final JSCodeOptions options; | 2305 final JSCodeOptions options; |
| 2375 | 2306 |
| 2376 JSGenerator(String outDir, Uri root, TypeRules rules, this.options) | 2307 JSGenerator(String outDir, Uri root, TypeRules rules, this.options) |
| 2377 : super(outDir, root, rules); | 2308 : super(outDir, root, rules); |
| 2378 | 2309 |
| 2379 String generateLibrary(LibraryUnit unit, LibraryInfo info) { | 2310 String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
| 2380 var jsTree = new JSCodegenVisitor(info, rules).emitLibrary(unit); | 2311 var jsTree = new JSCodegenVisitor(info, rules).emitLibrary(unit); |
| 2381 | 2312 |
| 2382 var outputPath = path.join(outDir, jsOutputPath(info, root)); | 2313 var outputPath = path.join(outDir, jsOutputPath(info, root)); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 2400 return computeHash(text); | 2331 return computeHash(text); |
| 2401 } | 2332 } |
| 2402 } | 2333 } |
| 2403 } | 2334 } |
| 2404 | 2335 |
| 2405 void _writeNode(JS.JavaScriptPrintingContext context, JS.Node node) { | 2336 void _writeNode(JS.JavaScriptPrintingContext context, JS.Node node) { |
| 2406 var opts = new JS.JavaScriptPrintingOptions(allowKeywordsInProperties: true); | 2337 var opts = new JS.JavaScriptPrintingOptions(allowKeywordsInProperties: true); |
| 2407 node.accept(new JS.Printer(opts, context, localNamer: new JSNamer(node))); | 2338 node.accept(new JS.Printer(opts, context, localNamer: new JSNamer(node))); |
| 2408 } | 2339 } |
| 2409 | 2340 |
| 2410 /// This is a debugging helper to print a JS node. | |
| 2411 String jsNodeToString(JS.Node node) { | 2341 String jsNodeToString(JS.Node node) { |
| 2412 var context = new JS.SimpleJavaScriptPrintingContext(); | 2342 var context = new JS.SimpleJavaScriptPrintingContext(); |
| 2413 _writeNode(context, node); | 2343 _writeNode(context, node); |
| 2414 return context.getText(); | 2344 return context.getText(); |
| 2415 } | 2345 } |
| 2416 | 2346 |
| 2417 /// Choose a canonical name from the library element. | 2347 /// Choose a canonical name from the library element. |
| 2418 /// This never uses the library's name (the identifier in the `library` | 2348 /// This never uses the library's name (the identifier in the `library` |
| 2419 /// declaration) as it doesn't have any meaningful rules enforced. | 2349 /// declaration) as it doesn't have any meaningful rules enforced. |
| 2420 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 2350 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2494 | 2424 |
| 2495 // TODO(jmesserly): in many cases marking the end will be unncessary. | 2425 // TODO(jmesserly): in many cases marking the end will be unncessary. |
| 2496 printer.mark(_location(node.end)); | 2426 printer.mark(_location(node.end)); |
| 2497 } | 2427 } |
| 2498 | 2428 |
| 2499 String _getIdentifier(AstNode node) { | 2429 String _getIdentifier(AstNode node) { |
| 2500 if (node is SimpleIdentifier) return node.name; | 2430 if (node is SimpleIdentifier) return node.name; |
| 2501 return null; | 2431 return null; |
| 2502 } | 2432 } |
| 2503 } | 2433 } |
| 2434 |
| 2435 /// True is the expression can be evaluated multiple times without causing |
| 2436 /// code execution. This is true for final fields. This can be true for local |
| 2437 /// variables, if: |
| 2438 /// * they are not assigned within the [context]. |
| 2439 /// * they are not assigned in a function closure anywhere. |
| 2440 /// True is the expression can be evaluated multiple times without causing |
| 2441 /// code execution. This is true for final fields. This can be true for local |
| 2442 /// variables, if: |
| 2443 /// |
| 2444 /// * they are not assigned within the [context] scope. |
| 2445 /// * they are not assigned in a function closure anywhere. |
| 2446 /// |
| 2447 /// This method is used to avoid creating temporaries in cases where we know |
| 2448 /// we can safely re-evaluate [node] multiple times in [context]. This lets |
| 2449 /// us generate prettier code. |
| 2450 /// |
| 2451 /// This method is conservative: it should never return `true` unless it is |
| 2452 /// certain the [node] is stateless, because generated code may rely on the |
| 2453 /// correctness of a `true` value. However it may return `false` for things |
| 2454 /// that are in fact, stateless. |
| 2455 bool _isStateless(Expression node, [AstNode context]) { |
| 2456 if (node is SimpleIdentifier) { |
| 2457 var e = node.staticElement; |
| 2458 if (e is PropertyAccessorElement) e = e.variable; |
| 2459 if (e is VariableElement && !e.isSynthetic) { |
| 2460 if (e.isFinal) return true; |
| 2461 |
| 2462 // TODO(jmesserly): remove this when isPotentiallyMutated* is available |
| 2463 // without the implementation class. Technically we shouldn't hit the |
| 2464 // ParameterMember case based on current usage of _isStateless, but this |
| 2465 // makes it clear we shouldn't rely on *Impl class. |
| 2466 if (e is Member) e = e.baseElement; |
| 2467 |
| 2468 if (e is LocalVariableElementImpl || e is ParameterElementImpl) { |
| 2469 // make sure the local isn't mutated in the context. |
| 2470 return !_isPotentiallyMutated(e, context); |
| 2471 } |
| 2472 } |
| 2473 } |
| 2474 return false; |
| 2475 } |
| 2476 |
| 2477 /// Returns true if the local variable is potentially mutated within [context]. |
| 2478 /// This accounts for closures that may have been created outside of [context]. |
| 2479 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { |
| 2480 if (e.isPotentiallyMutatedInClosure) { |
| 2481 // TODO(jmesserly): this returns true incorrectly in some cases, because |
| 2482 // VariableResolverVisitor only checks that enclosingElement is not the |
| 2483 // function element, but enclosingElement can be something else in some |
| 2484 // cases (the block scope?). So it's more conservative than it could be. |
| 2485 return true; |
| 2486 } |
| 2487 if (e.isPotentiallyMutatedInScope) { |
| 2488 // Need to visit the context looking for assignment to this local. |
| 2489 if (context != null) { |
| 2490 var visitor = new _AssignmentFinder(e); |
| 2491 context.accept(visitor); |
| 2492 return visitor._potentiallyMutated; |
| 2493 } |
| 2494 return true; |
| 2495 } |
| 2496 return false; |
| 2497 } |
| 2498 |
| 2499 /// Adapted from VariableResolverVisitor. Finds an assignment to a given |
| 2500 /// local variable. |
| 2501 // TODO(jmesserly): change type annotation to not be *Impl once |
| 2502 // isPotentiallyMutated is available on VariableElement. |
| 2503 class _AssignmentFinder extends RecursiveAstVisitor { |
| 2504 final VariableElementImpl _variable; |
| 2505 bool _potentiallyMutated = false; |
| 2506 |
| 2507 _AssignmentFinder(this._variable); |
| 2508 |
| 2509 @override |
| 2510 visitSimpleIdentifier(SimpleIdentifier node) { |
| 2511 // Ignore if qualified. |
| 2512 AstNode parent = node.parent; |
| 2513 if (parent is PrefixedIdentifier && |
| 2514 identical(parent.identifier, node)) return; |
| 2515 if (parent is PropertyAccess && |
| 2516 identical(parent.propertyName, node)) return; |
| 2517 if (parent is MethodInvocation && |
| 2518 identical(parent.methodName, node)) return; |
| 2519 if (parent is ConstructorName) return; |
| 2520 if (parent is Label) return; |
| 2521 |
| 2522 if (node.inSetterContext() && node.staticElement == _variable) { |
| 2523 _potentiallyMutated = true; |
| 2524 } |
| 2525 } |
| 2526 } |
| OLD | NEW |