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 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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, JSTemporary>(); |
75 final _extensionMethodNames = new HashSet<String>(); | 75 final _extensionMethodNames = new HashSet<String>(); |
76 final _pendingSymbols = <JS.Identifier>[]; | 76 final _pendingStatements = <JS.Statement>[]; |
77 final _temps = new HashMap<Element, JSTemporary>(); | 77 final _temps = new HashMap<Element, JSTemporary>(); |
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 JSTemporary _exportsVar = new JSTemporary('exports'); |
84 final JSTemporary _namedArgTemp = new JSTemporary('opts'); | 84 final JSTemporary _namedArgTemp = new JSTemporary('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 |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ | 149 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ |
150 _exportsVar, | 150 _exportsVar, |
151 body, | 151 body, |
152 name, | 152 name, |
153 name, | 153 name, |
154 defaultValue | 154 defaultValue |
155 ]) | 155 ]) |
156 ]); | 156 ]); |
157 } | 157 } |
158 | 158 |
159 JS.Statement _initSymbol(JS.Identifier id) => | 159 JS.Identifier _initSymbol(JS.Identifier id) { |
160 js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); | 160 var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
| 161 _pendingStatements.add(s); |
| 162 return id; |
| 163 } |
161 | 164 |
162 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 165 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
163 // until we have better name tracking. | 166 // until we have better name tracking. |
164 String get _SYMBOL { | 167 String get _SYMBOL { |
165 var name = currentLibrary.name; | 168 var name = currentLibrary.name; |
166 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 169 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
167 return 'Symbol'; | 170 return 'Symbol'; |
168 } | 171 } |
169 | 172 |
170 @override | 173 @override |
171 JS.Statement visitCompilationUnit(CompilationUnit node) { | 174 JS.Statement visitCompilationUnit(CompilationUnit node) { |
172 var source = node.element.source; | 175 var source = node.element.source; |
173 | 176 |
174 _constEvaluator = new ConstantEvaluator(source, types); | 177 _constEvaluator = new ConstantEvaluator(source, types); |
175 | 178 |
176 // TODO(jmesserly): scriptTag, directives. | 179 // TODO(jmesserly): scriptTag, directives. |
177 var body = <JS.Statement>[]; | 180 var body = <JS.Statement>[]; |
178 for (var child in node.declarations) { | 181 for (var child in node.declarations) { |
179 // Attempt to group adjacent fields/properties. | 182 // Attempt to group adjacent fields/properties. |
180 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 183 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
181 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 184 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
182 | 185 |
183 var code = _visit(child); | 186 var code = _visit(child); |
184 if (code != null) { | 187 if (code != null) { |
185 if (_pendingSymbols.isNotEmpty) { | 188 if (_pendingStatements.isNotEmpty) { |
186 body.addAll(_pendingSymbols.map(_initSymbol)); | 189 body.addAll(_pendingStatements); |
187 _pendingSymbols.clear(); | 190 _pendingStatements.clear(); |
188 } | 191 } |
189 body.add(code); | 192 body.add(code); |
190 } | 193 } |
191 } | 194 } |
192 | 195 |
193 // Flush any unwritten fields/properties. | 196 // Flush any unwritten fields/properties. |
194 _flushLazyFields(body); | 197 _flushLazyFields(body); |
195 _flushLibraryProperties(body); | 198 _flushLibraryProperties(body); |
196 | 199 |
197 assert(_pendingSymbols.isEmpty); | 200 assert(_pendingStatements.isEmpty); |
198 return _statement(body); | 201 return _statement(body); |
199 } | 202 } |
200 | 203 |
201 bool isPublic(String name) => !name.startsWith('_'); | 204 bool isPublic(String name) => !name.startsWith('_'); |
202 | 205 |
203 /// Conversions that we don't handle end up here. | 206 /// Conversions that we don't handle end up here. |
204 @override | 207 @override |
205 visitConversion(Conversion node) { | 208 visitConversion(Conversion node) { |
206 throw 'Unlowered conversion ${node.runtimeType}: $node'; | 209 throw 'Unlowered conversion ${node.runtimeType}: $node'; |
207 } | 210 } |
(...skipping 1263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1471 visitConstructorName(ConstructorName node) { | 1474 visitConstructorName(ConstructorName node) { |
1472 var typeName = _visit(node.type); | 1475 var typeName = _visit(node.type); |
1473 if (node.name != null) { | 1476 if (node.name != null) { |
1474 return js.call('#.#', [typeName, _emitMemberName(node.name.name)]); | 1477 return js.call('#.#', [typeName, _emitMemberName(node.name.name)]); |
1475 } | 1478 } |
1476 return typeName; | 1479 return typeName; |
1477 } | 1480 } |
1478 | 1481 |
1479 @override | 1482 @override |
1480 visitInstanceCreationExpression(InstanceCreationExpression node) { | 1483 visitInstanceCreationExpression(InstanceCreationExpression node) { |
1481 return js.call( | 1484 var newExpr = js.call( |
1482 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); | 1485 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); |
| 1486 if (node.isConst) return _const(node, newExpr); |
| 1487 return newExpr; |
1483 } | 1488 } |
1484 | 1489 |
1485 /// True if this type is built-in to JS, and we use the values unwrapped. | 1490 /// True if this type is built-in to JS, and we use the values unwrapped. |
1486 /// For these types we generate a calling convention via static | 1491 /// For these types we generate a calling convention via static |
1487 /// "extension methods". This allows types to be extended without adding | 1492 /// "extension methods". This allows types to be extended without adding |
1488 /// extensions directly on the prototype. | 1493 /// extensions directly on the prototype. |
1489 bool _isJSBuiltinType(DartType t) => | 1494 bool _isJSBuiltinType(DartType t) => |
1490 typeIsPrimitiveInJS(t) || rules.isStringType(t); | 1495 typeIsPrimitiveInJS(t) || rules.isStringType(t); |
1491 | 1496 |
1492 bool typeIsPrimitiveInJS(DartType t) => rules.isIntType(t) || | 1497 bool typeIsPrimitiveInJS(DartType t) => rules.isIntType(t) || |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1633 // LocalVariableElementImpl, so we could repurpose to mean "temp". | 1638 // LocalVariableElementImpl, so we could repurpose to mean "temp". |
1634 // * add a new property to LocalVariableElementImpl. | 1639 // * add a new property to LocalVariableElementImpl. |
1635 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 1640 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
1636 var id = | 1641 var id = |
1637 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); | 1642 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
1638 id.staticElement = new LocalVariableElementImpl.forNode(id); | 1643 id.staticElement = new LocalVariableElementImpl.forNode(id); |
1639 id.staticType = type; | 1644 id.staticType = type; |
1640 return id; | 1645 return id; |
1641 } | 1646 } |
1642 | 1647 |
| 1648 JS.Expression _const(Expression node, JS.Expression expr, [String nameHint]) { |
| 1649 var value = js.call('dart.const(#)', expr); |
| 1650 |
| 1651 // If we're inside a method or function, capture the value into a |
| 1652 // global temporary, so we don't do the expensive canonicalization step. |
| 1653 var ancestor = node.getAncestor((n) => n is FunctionBody || |
| 1654 (n is FieldDeclaration && n.staticKeyword == null)); |
| 1655 if (ancestor == null) return value; |
| 1656 |
| 1657 if (nameHint == null) { |
| 1658 nameHint = 'const_' + getStaticType(node).name; |
| 1659 } |
| 1660 |
| 1661 // TODO(jmesserly): enable this once we fix |
| 1662 // https://github.com/dart-lang/dev_compiler/issues/131 |
| 1663 /*var temp = new JSTemporary(nameHint); |
| 1664 _pendingStatements.add(js.statement('let # = #;', [temp, value])); |
| 1665 return temp;*/ |
| 1666 assert(nameHint != null); // so it's not marked unused |
| 1667 return value; |
| 1668 } |
| 1669 |
1643 bool _isTemporary(Element node) => node.nameOffset == -1; | 1670 bool _isTemporary(Element node) => node.nameOffset == -1; |
1644 | 1671 |
1645 /// Returns a new expression, which can be be used safely *once* on the | 1672 /// Returns a new expression, which can be be used safely *once* on the |
1646 /// left hand side, and *once* on the right side of an assignment. | 1673 /// left hand side, and *once* on the right side of an assignment. |
1647 /// For example: `expr1[expr2] += y` can be compiled as | 1674 /// For example: `expr1[expr2] += y` can be compiled as |
1648 /// `expr1[expr2] = expr1[expr2] + y`. | 1675 /// `expr1[expr2] = expr1[expr2] + y`. |
1649 /// | 1676 /// |
1650 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated | 1677 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated |
1651 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. | 1678 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. |
1652 /// | 1679 /// |
(...skipping 439 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2092 @override | 2119 @override |
2093 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); | 2120 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); |
2094 | 2121 |
2095 @override | 2122 @override |
2096 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); | 2123 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); |
2097 | 2124 |
2098 @override | 2125 @override |
2099 visitSymbolLiteral(SymbolLiteral node) { | 2126 visitSymbolLiteral(SymbolLiteral node) { |
2100 // TODO(vsm): When we canonicalize, we need to treat private symbols | 2127 // TODO(vsm): When we canonicalize, we need to treat private symbols |
2101 // correctly. | 2128 // correctly. |
2102 // TODO(vsm): Make this core.Symbol instead. | 2129 var name = js.string(node.components.join('.'), "'"); |
2103 var name = js.escapedString(node.components.join('.')); | 2130 var nameHint = 'symbol_' + node.components.join('_'); |
2104 var symbol = js.call('new _internal.Symbol(#)', name); | 2131 return _const(node, js.call('new core.Symbol(#)', name), nameHint); |
2105 return js.commentExpression('Unimplemented const', symbol); | |
2106 } | 2132 } |
2107 | 2133 |
2108 @override | 2134 @override |
2109 visitListLiteral(ListLiteral node) { | 2135 visitListLiteral(ListLiteral node) { |
2110 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); | 2136 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); |
2111 | 2137 |
2112 ParameterizedType type = node.staticType; | 2138 ParameterizedType type = node.staticType; |
2113 if (type.typeArguments.any((a) => a != types.dynamicType)) { | 2139 if (type.typeArguments.any((a) => a != types.dynamicType)) { |
2114 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); | 2140 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); |
2115 } | 2141 } |
2116 if (node.constKeyword != null) { | 2142 if (node.constKeyword != null) return _const(node, list); |
2117 list = js.commentExpression('Unimplemented const', list); | |
2118 } | |
2119 return list; | 2143 return list; |
2120 } | 2144 } |
2121 | 2145 |
2122 @override | 2146 @override |
2123 visitMapLiteral(MapLiteral node) { | 2147 visitMapLiteral(MapLiteral node) { |
2124 // TODO(jmesserly): we can likely make these faster. | 2148 // TODO(jmesserly): we can likely make these faster. |
2125 var entries = node.entries; | 2149 var entries = node.entries; |
2126 var mapArguments = null; | 2150 var mapArguments = null; |
2127 if (entries.isEmpty) { | 2151 if (entries.isEmpty) { |
2128 mapArguments = []; | 2152 mapArguments = []; |
2129 } else if (entries.every((e) => e.key is StringLiteral)) { | 2153 } else if (entries.every((e) => e.key is StringLiteral)) { |
2130 // Use JS object literal notation if possible, otherwise use an array. | 2154 // Use JS object literal notation if possible, otherwise use an array. |
2131 // We could do this any time all keys are non-nullable String type. | 2155 // We could do this any time all keys are non-nullable String type. |
2132 // For now, support StringLiteral as the common non-nullable String case. | 2156 // For now, support StringLiteral as the common non-nullable String case. |
2133 var props = []; | 2157 var props = []; |
2134 for (var e in entries) { | 2158 for (var e in entries) { |
2135 props.add(new JS.Property(_visit(e.key), _visit(e.value))); | 2159 props.add(new JS.Property(_visit(e.key), _visit(e.value))); |
2136 } | 2160 } |
2137 mapArguments = new JS.ObjectInitializer(props); | 2161 mapArguments = new JS.ObjectInitializer(props); |
2138 } else { | 2162 } else { |
2139 var values = []; | 2163 var values = []; |
2140 for (var e in entries) { | 2164 for (var e in entries) { |
2141 values.add(_visit(e.key)); | 2165 values.add(_visit(e.key)); |
2142 values.add(_visit(e.value)); | 2166 values.add(_visit(e.value)); |
2143 } | 2167 } |
2144 mapArguments = new JS.ArrayInitializer(values); | 2168 mapArguments = new JS.ArrayInitializer(values); |
2145 } | 2169 } |
2146 // TODO(jmesserly): add generic types args. | 2170 // TODO(jmesserly): add generic types args. |
2147 var map = js.call('dart.map(#)', [mapArguments]); | 2171 var map = js.call('dart.map(#)', [mapArguments]); |
2148 if (node.constKeyword != null) { | 2172 if (node.constKeyword != null) return _const(node, map); |
2149 map = js.commentExpression('Unimplemented const', map); | |
2150 } | |
2151 return map; | 2173 return map; |
2152 } | 2174 } |
2153 | 2175 |
2154 @override | 2176 @override |
2155 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => | 2177 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
2156 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); | 2178 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
2157 | 2179 |
2158 @override | 2180 @override |
2159 JS.Expression visitAdjacentStrings(AdjacentStrings node) => | 2181 JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
2160 _visitListToBinary(node.strings, '+'); | 2182 _visitListToBinary(node.strings, '+'); |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2279 /// | 2301 /// |
2280 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed | 2302 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed |
2281 /// for this transformation to happen, otherwise binary minus is assumed. | 2303 /// for this transformation to happen, otherwise binary minus is assumed. |
2282 /// | 2304 /// |
2283 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2305 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
2284 /// helper, that checks for null. The user defined method is called '=='. | 2306 /// helper, that checks for null. The user defined method is called '=='. |
2285 /// | 2307 /// |
2286 JS.Expression _emitMemberName(String name, | 2308 JS.Expression _emitMemberName(String name, |
2287 {DartType type, bool unary: false, bool isStatic: false}) { | 2309 {DartType type, bool unary: false, bool isStatic: false}) { |
2288 if (name.startsWith('_')) { | 2310 if (name.startsWith('_')) { |
2289 return _privateNames.putIfAbsent(name, () { | 2311 return _privateNames.putIfAbsent( |
2290 var t = new JSTemporary(name); | 2312 name, () => _initSymbol(new JSTemporary(name))); |
2291 _pendingSymbols.add(t); | |
2292 return t; | |
2293 }); | |
2294 } | 2313 } |
2295 | 2314 |
2296 // Check for extension method: | 2315 // Check for extension method: |
2297 var extLibrary = _findExtensionLibrary(name, type); | 2316 var extLibrary = _findExtensionLibrary(name, type); |
2298 | 2317 |
2299 if (name == '[]') { | 2318 if (name == '[]') { |
2300 name = 'get'; | 2319 name = 'get'; |
2301 } else if (name == '[]=') { | 2320 } else if (name == '[]=') { |
2302 name = 'set'; | 2321 name = 'set'; |
2303 } else if (name == '-' && unary) { | 2322 } else if (name == '-' && unary) { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2338 return extLibrary; | 2357 return extLibrary; |
2339 } | 2358 } |
2340 | 2359 |
2341 JS.Expression _extensionMethodName(String name, LibraryElement library) { | 2360 JS.Expression _extensionMethodName(String name, LibraryElement library) { |
2342 var extName = '\$$name'; | 2361 var extName = '\$$name'; |
2343 if (library == currentLibrary) { | 2362 if (library == currentLibrary) { |
2344 // TODO(jacobr): need to do a better job ensuring that extension method | 2363 // TODO(jacobr): need to do a better job ensuring that extension method |
2345 // name symbols do not conflict with other symbols before we can let | 2364 // name symbols do not conflict with other symbols before we can let |
2346 // user defined libraries define extension methods. | 2365 // user defined libraries define extension methods. |
2347 if (_extensionMethodNames.add(extName)) { | 2366 if (_extensionMethodNames.add(extName)) { |
2348 _pendingSymbols.add(new JS.Identifier(extName)); | 2367 _initSymbol(new JS.Identifier(extName)); |
2349 _addExport(extName); | 2368 _addExport(extName); |
2350 } | 2369 } |
2351 return new JS.Identifier(extName); | 2370 return new JS.Identifier(extName); |
2352 } | 2371 } |
2353 return js.call('#.#', [_libraryName(library), _propertyName(extName)]); | 2372 return js.call('#.#', [_libraryName(library), _propertyName(extName)]); |
2354 } | 2373 } |
2355 | 2374 |
2356 bool _externalOrNative(node) => | 2375 bool _externalOrNative(node) => |
2357 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; | 2376 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
2358 | 2377 |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2441 // TODO(jmesserly): validate the library. See issue #135. | 2460 // TODO(jmesserly): validate the library. See issue #135. |
2442 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2461 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2443 | 2462 |
2444 bool _isJsPeerInterface(DartObjectImpl value) => | 2463 bool _isJsPeerInterface(DartObjectImpl value) => |
2445 value.type.name == 'JsPeerInterface'; | 2464 value.type.name == 'JsPeerInterface'; |
2446 | 2465 |
2447 // TODO(jacobr): we would like to do something like the following | 2466 // TODO(jacobr): we would like to do something like the following |
2448 // but we don't have summary support yet. | 2467 // but we don't have summary support yet. |
2449 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2468 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2450 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2469 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |