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 | 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 13 matching lines...) Expand all Loading... |
24 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 24 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
25 | 25 |
26 import 'package:dev_compiler/src/checker/rules.dart'; | 26 import 'package:dev_compiler/src/checker/rules.dart'; |
27 import 'package:dev_compiler/src/dependency_graph.dart'; | 27 import 'package:dev_compiler/src/dependency_graph.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_field_storage.dart'; | 33 import 'js_field_storage.dart'; |
34 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; | 34 import 'js_names.dart' as JS; |
35 import 'js_metalet.dart'; | 35 import 'js_metalet.dart' as JS; |
36 import 'js_printer.dart' show writeJsLibrary; | 36 import 'js_printer.dart' show writeJsLibrary; |
37 import 'side_effect_analysis.dart'; | 37 import 'side_effect_analysis.dart'; |
38 | 38 |
39 // Various dynamic helpers we call. | 39 // Various dynamic helpers we call. |
40 // If renaming these, make sure to check other places like the | 40 // If renaming these, make sure to check other places like the |
41 // dart_runtime.js file and comments. | 41 // dart_runtime.js file and comments. |
42 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 42 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
43 // import and generate calls to, rather than dart_runtime.js | 43 // import and generate calls to, rather than dart_runtime.js |
44 const DPUT = 'dput'; | 44 const DPUT = 'dput'; |
45 const DLOAD = 'dload'; | 45 const DLOAD = 'dload'; |
(...skipping 18 matching lines...) Expand all Loading... |
64 SimpleIdentifier _cascadeTarget; | 64 SimpleIdentifier _cascadeTarget; |
65 | 65 |
66 /// The variable for the current catch clause | 66 /// The variable for the current catch clause |
67 SimpleIdentifier _catchParameter; | 67 SimpleIdentifier _catchParameter; |
68 | 68 |
69 ConstantEvaluator _constEvaluator; | 69 ConstantEvaluator _constEvaluator; |
70 | 70 |
71 final _exports = new Set<String>(); | 71 final _exports = new Set<String>(); |
72 final _lazyFields = <VariableDeclaration>[]; | 72 final _lazyFields = <VariableDeclaration>[]; |
73 final _properties = <FunctionDeclaration>[]; | 73 final _properties = <FunctionDeclaration>[]; |
74 final _privateNames = new HashMap<String, JSTemporary>(); | 74 final _privateNames = new HashMap<String, JS.TemporaryId>(); |
75 final _extensionMethodNames = new HashSet<String>(); | 75 final _extensionMethodNames = new HashSet<String>(); |
76 final _pendingStatements = <JS.Statement>[]; | 76 final _pendingStatements = <JS.Statement>[]; |
77 final _temps = new HashMap<Element, JSTemporary>(); | 77 final _temps = new HashMap<Element, JS.TemporaryId>(); |
78 | 78 |
79 /// The name for the library's exports inside itself. | 79 /// The name for the library's exports inside itself. |
80 /// This much be a constant because we interpolate it into template strings, | 80 /// This much be a constant because we interpolate it into template strings, |
81 /// and otherwise it would break caching for them. | 81 /// and otherwise it would break caching for them. |
82 /// `exports` was chosen as the most similar to ES module patterns. | 82 /// `exports` was chosen as the most similar to ES module patterns. |
83 final JSTemporary _exportsVar = new JSTemporary('exports'); | 83 final _exportsVar = new JS.TemporaryId('exports'); |
84 final JSTemporary _namedArgTemp = new JSTemporary('opts'); | 84 final _namedArgTemp = new JS.TemporaryId('opts'); |
85 | 85 |
86 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 86 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or |
87 /// [ClassTypeAlias]. | 87 /// [ClassTypeAlias]. |
88 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | 88 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); |
89 | 89 |
90 /// Memoized results of [_lazyClass]. | 90 /// Memoized results of [_lazyClass]. |
91 final _lazyClassMemo = new HashMap<Element, bool>(); | 91 final _lazyClassMemo = new HashMap<Element, bool>(); |
92 | 92 |
93 /// Memoized results of [_inLibraryCycle]. | 93 /// Memoized results of [_inLibraryCycle]. |
94 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | 94 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); |
(...skipping 995 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1090 if (name[0] == '#') { | 1090 if (name[0] == '#') { |
1091 return new JS.InterpolatedExpression(name.substring(1)); | 1091 return new JS.InterpolatedExpression(name.substring(1)); |
1092 } else { | 1092 } else { |
1093 return _getTemp(element, name); | 1093 return _getTemp(element, name); |
1094 } | 1094 } |
1095 } | 1095 } |
1096 | 1096 |
1097 return new JS.Identifier(name); | 1097 return new JS.Identifier(name); |
1098 } | 1098 } |
1099 | 1099 |
1100 JSTemporary _getTemp(Object key, String name) => | 1100 JS.TemporaryId _getTemp(Object key, String name) => |
1101 _temps.putIfAbsent(key, () => new JSTemporary(name)); | 1101 _temps.putIfAbsent(key, () => new JS.TemporaryId(name)); |
1102 | 1102 |
1103 JS.ArrayInitializer _emitTypeNames(List<DartType> types) { | 1103 JS.ArrayInitializer _emitTypeNames(List<DartType> types) { |
1104 return new JS.ArrayInitializer(types.map(_emitTypeName).toList()); | 1104 return new JS.ArrayInitializer(types.map(_emitTypeName).toList()); |
1105 } | 1105 } |
1106 | 1106 |
1107 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { | 1107 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { |
1108 var properties = <JS.Property>[]; | 1108 var properties = <JS.Property>[]; |
1109 types.forEach((name, type) { | 1109 types.forEach((name, type) { |
1110 var key = new JS.LiteralString(name); | 1110 var key = new JS.LiteralString(name); |
1111 var value = _emitTypeName(type); | 1111 var value = _emitTypeName(type); |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1190 @override | 1190 @override |
1191 JS.Expression visitAssignmentExpression(AssignmentExpression node) { | 1191 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
1192 var left = node.leftHandSide; | 1192 var left = node.leftHandSide; |
1193 var right = node.rightHandSide; | 1193 var right = node.rightHandSide; |
1194 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); | 1194 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
1195 return _emitOpAssign( | 1195 return _emitOpAssign( |
1196 left, right, node.operator.lexeme[0], node.staticElement, | 1196 left, right, node.operator.lexeme[0], node.staticElement, |
1197 context: node); | 1197 context: node); |
1198 } | 1198 } |
1199 | 1199 |
1200 JSMetaLet _emitOpAssign( | 1200 JS.MetaLet _emitOpAssign( |
1201 Expression left, Expression right, String op, ExecutableElement element, | 1201 Expression left, Expression right, String op, ExecutableElement element, |
1202 {Expression context}) { | 1202 {Expression context}) { |
1203 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions | 1203 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions |
1204 // (for example, x is IndexExpression) we evaluate those once. | 1204 // (for example, x is IndexExpression) we evaluate those once. |
1205 var vars = {}; | 1205 var vars = {}; |
1206 var lhs = _bindLeftHandSide(vars, left, context: context); | 1206 var lhs = _bindLeftHandSide(vars, left, context: context); |
1207 var inc = AstBuilder.binaryExpression(lhs, op, right); | 1207 var inc = AstBuilder.binaryExpression(lhs, op, right); |
1208 inc.staticElement = element; | 1208 inc.staticElement = element; |
1209 inc.staticType = getStaticType(left); | 1209 inc.staticType = getStaticType(left); |
1210 return new JSMetaLet(vars, [_emitSet(lhs, inc)]); | 1210 return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); |
1211 } | 1211 } |
1212 | 1212 |
1213 JS.Expression _emitSet(Expression lhs, Expression rhs) { | 1213 JS.Expression _emitSet(Expression lhs, Expression rhs) { |
1214 if (lhs is IndexExpression) { | 1214 if (lhs is IndexExpression) { |
1215 return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]); | 1215 return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]); |
1216 } | 1216 } |
1217 | 1217 |
1218 Expression target = null; | 1218 Expression target = null; |
1219 SimpleIdentifier id; | 1219 SimpleIdentifier id; |
1220 if (lhs is PropertyAccess) { | 1220 if (lhs is PropertyAccess) { |
(...skipping 550 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1771 /// We also need to ensure we can return the original value of the expression, | 1771 /// We also need to ensure we can return the original value of the expression, |
1772 /// and that it is only evaluated once. | 1772 /// and that it is only evaluated once. |
1773 /// | 1773 /// |
1774 /// We desugar this using let*. | 1774 /// We desugar this using let*. |
1775 /// | 1775 /// |
1776 /// For example, `expr1[expr2]++` can be transformed to this: | 1776 /// For example, `expr1[expr2]++` can be transformed to this: |
1777 /// | 1777 /// |
1778 /// // psuedocode mix of Scheme and JS: | 1778 /// // psuedocode mix of Scheme and JS: |
1779 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) | 1779 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
1780 /// | 1780 /// |
1781 /// The [JSMetaLet] nodes automatically simplify themselves if they can. | 1781 /// The [JS.JS.MetaLet] nodes automatically simplify themselves if they can. |
1782 /// For example, if the result value is not used, then `t` goes away. | 1782 /// For example, if the result value is not used, then `t` goes away. |
1783 @override | 1783 @override |
1784 JS.Expression visitPostfixExpression(PostfixExpression node) { | 1784 JS.Expression visitPostfixExpression(PostfixExpression node) { |
1785 var op = node.operator; | 1785 var op = node.operator; |
1786 var expr = node.operand; | 1786 var expr = node.operand; |
1787 | 1787 |
1788 var dispatchType = getStaticType(expr); | 1788 var dispatchType = getStaticType(expr); |
1789 if (unaryOperationIsPrimitive(dispatchType)) { | 1789 if (unaryOperationIsPrimitive(dispatchType)) { |
1790 if (_isNonNullableExpression(expr)) { | 1790 if (_isNonNullableExpression(expr)) { |
1791 return js.call('#$op', _visit(expr)); | 1791 return js.call('#$op', _visit(expr)); |
(...skipping 10 matching lines...) Expand all Loading... |
1802 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value | 1802 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value |
1803 // and `x1` is the new value for `x`. | 1803 // and `x1` is the new value for `x`. |
1804 var x = _bindValue(vars, 'x', left, context: expr); | 1804 var x = _bindValue(vars, 'x', left, context: expr); |
1805 | 1805 |
1806 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; | 1806 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
1807 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) | 1807 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) |
1808 ..staticElement = node.staticElement | 1808 ..staticElement = node.staticElement |
1809 ..staticType = getStaticType(expr); | 1809 ..staticType = getStaticType(expr); |
1810 | 1810 |
1811 var body = [_emitSet(left, increment), _visit(x)]; | 1811 var body = [_emitSet(left, increment), _visit(x)]; |
1812 return new JSMetaLet(vars, body, statelessResult: true); | 1812 return new JS.MetaLet(vars, body, statelessResult: true); |
1813 } | 1813 } |
1814 | 1814 |
1815 @override | 1815 @override |
1816 JS.Expression visitPrefixExpression(PrefixExpression node) { | 1816 JS.Expression visitPrefixExpression(PrefixExpression node) { |
1817 var op = node.operator; | 1817 var op = node.operator; |
1818 var expr = node.operand; | 1818 var expr = node.operand; |
1819 | 1819 |
1820 var dispatchType = getStaticType(expr); | 1820 var dispatchType = getStaticType(expr); |
1821 if (unaryOperationIsPrimitive(dispatchType)) { | 1821 if (unaryOperationIsPrimitive(dispatchType)) { |
1822 if (_isNonNullableExpression(expr)) { | 1822 if (_isNonNullableExpression(expr)) { |
1823 return js.call('$op#', _visit(expr)); | 1823 return js.call('$op#', _visit(expr)); |
1824 } else if (op.lexeme == '++' || op.lexeme == '--') { | 1824 } else if (op.lexeme == '++' || op.lexeme == '--') { |
1825 // We need a null check, so the increment must be expanded out. | 1825 // We need a null check, so the increment must be expanded out. |
1826 var mathop = op.lexeme[0]; | 1826 var mathop = op.lexeme[0]; |
1827 var vars = {}; | 1827 var vars = {}; |
1828 var x = _bindLeftHandSide(vars, expr, context: expr); | 1828 var x = _bindLeftHandSide(vars, expr, context: expr); |
1829 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); | 1829 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); |
1830 return new JSMetaLet(vars, [body]); | 1830 return new JS.MetaLet(vars, [body]); |
1831 } else { | 1831 } else { |
1832 return js.call('$op#', notNull(expr)); | 1832 return js.call('$op#', notNull(expr)); |
1833 } | 1833 } |
1834 } | 1834 } |
1835 | 1835 |
1836 if (op.lexeme == '++' || op.lexeme == '--') { | 1836 if (op.lexeme == '++' || op.lexeme == '--') { |
1837 // Increment or decrement requires expansion. | 1837 // Increment or decrement requires expansion. |
1838 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions | 1838 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions |
1839 // (for example, x is IndexExpression) we evaluate those once. | 1839 // (for example, x is IndexExpression) we evaluate those once. |
1840 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; | 1840 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
1841 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement, | 1841 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement, |
1842 context: expr); | 1842 context: expr); |
1843 } | 1843 } |
1844 | 1844 |
1845 return _emitSend(expr, op.lexeme[0], []); | 1845 return _emitSend(expr, op.lexeme[0], []); |
1846 } | 1846 } |
1847 | 1847 |
1848 // Cascades can contain [IndexExpression], [MethodInvocation] and | 1848 // Cascades can contain [IndexExpression], [MethodInvocation] and |
1849 // [PropertyAccess]. The code generation for those is handled in their | 1849 // [PropertyAccess]. The code generation for those is handled in their |
1850 // respective visit methods. | 1850 // respective visit methods. |
1851 @override | 1851 @override |
1852 JS.Node visitCascadeExpression(CascadeExpression node) { | 1852 JS.Node visitCascadeExpression(CascadeExpression node) { |
1853 var savedCascadeTemp = _cascadeTarget; | 1853 var savedCascadeTemp = _cascadeTarget; |
1854 | 1854 |
1855 var vars = {}; | 1855 var vars = {}; |
1856 _cascadeTarget = _bindValue(vars, '_', node.target, context: node); | 1856 _cascadeTarget = _bindValue(vars, '_', node.target, context: node); |
1857 var sections = _visitList(node.cascadeSections); | 1857 var sections = _visitList(node.cascadeSections); |
1858 sections.add(_visit(_cascadeTarget)); | 1858 sections.add(_visit(_cascadeTarget)); |
1859 var result = new JSMetaLet(vars, sections, statelessResult: true); | 1859 var result = new JS.MetaLet(vars, sections, statelessResult: true); |
1860 _cascadeTarget = savedCascadeTemp; | 1860 _cascadeTarget = savedCascadeTemp; |
1861 return result; | 1861 return result; |
1862 } | 1862 } |
1863 | 1863 |
1864 @override | 1864 @override |
1865 visitParenthesizedExpression(ParenthesizedExpression node) => | 1865 visitParenthesizedExpression(ParenthesizedExpression node) => |
1866 // The printer handles precedence so we don't need to. | 1866 // The printer handles precedence so we don't need to. |
1867 _visit(node.expression); | 1867 _visit(node.expression); |
1868 | 1868 |
1869 @override | 1869 @override |
(...skipping 483 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2353 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed | 2353 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed |
2354 /// for this transformation to happen, otherwise binary minus is assumed. | 2354 /// for this transformation to happen, otherwise binary minus is assumed. |
2355 /// | 2355 /// |
2356 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2356 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
2357 /// helper, that checks for null. The user defined method is called '=='. | 2357 /// helper, that checks for null. The user defined method is called '=='. |
2358 /// | 2358 /// |
2359 JS.Expression _emitMemberName(String name, | 2359 JS.Expression _emitMemberName(String name, |
2360 {DartType type, bool unary: false, bool isStatic: false}) { | 2360 {DartType type, bool unary: false, bool isStatic: false}) { |
2361 if (name.startsWith('_')) { | 2361 if (name.startsWith('_')) { |
2362 return _privateNames.putIfAbsent( | 2362 return _privateNames.putIfAbsent( |
2363 name, () => _initSymbol(new JSTemporary(name))); | 2363 name, () => _initSymbol(new JS.TemporaryId(name))); |
2364 } | 2364 } |
2365 | 2365 |
2366 // Check for extension method: | 2366 // Check for extension method: |
2367 var extLibrary = _findExtensionLibrary(name, type); | 2367 var extLibrary = _findExtensionLibrary(name, type); |
2368 | 2368 |
2369 if (name == '[]') { | 2369 if (name == '[]') { |
2370 name = 'get'; | 2370 name = 'get'; |
2371 } else if (name == '[]=') { | 2371 } else if (name == '[]=') { |
2372 name = 'set'; | 2372 name = 'set'; |
2373 } else if (name == '-' && unary) { | 2373 } else if (name == '-' && unary) { |
2374 name = 'unary-'; | 2374 name = 'unary-'; |
2375 } | 2375 } |
2376 | 2376 |
2377 if (isStatic && invalidJSStaticMethodName(name)) { | 2377 if (isStatic && JS.invalidStaticMethodName(name)) { |
2378 // Choose an string name. Use an invalid identifier so it won't conflict | 2378 // Choose an string name. Use an invalid identifier so it won't conflict |
2379 // with any valid member names. | 2379 // with any valid member names. |
2380 // TODO(jmesserly): this works around the problem, but I'm pretty sure we | 2380 // TODO(jmesserly): this works around the problem, but I'm pretty sure we |
2381 // don't need it, as static methods seemed to work. The only concrete | 2381 // don't need it, as static methods seemed to work. The only concrete |
2382 // issue we saw was in the defineNamedConstructor helper function. | 2382 // issue we saw was in the defineNamedConstructor helper function. |
2383 name = '$name*'; | 2383 name = '$name*'; |
2384 } | 2384 } |
2385 | 2385 |
2386 if (extLibrary != null) { | 2386 if (extLibrary != null) { |
2387 return _extensionMethodName(name, extLibrary); | 2387 return _extensionMethodName(name, extLibrary); |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2511 // TODO(jmesserly): validate the library. See issue #135. | 2511 // TODO(jmesserly): validate the library. See issue #135. |
2512 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2512 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2513 | 2513 |
2514 bool _isJsPeerInterface(DartObjectImpl value) => | 2514 bool _isJsPeerInterface(DartObjectImpl value) => |
2515 value.type.name == 'JsPeerInterface'; | 2515 value.type.name == 'JsPeerInterface'; |
2516 | 2516 |
2517 // TODO(jacobr): we would like to do something like the following | 2517 // TODO(jacobr): we would like to do something like the following |
2518 // but we don't have summary support yet. | 2518 // but we don't have summary support yet. |
2519 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2519 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2520 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2520 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |