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; |
11 import 'package:analyzer/src/generated/constant.dart'; | 11 import 'package:analyzer/src/generated/constant.dart'; |
12 import 'package:analyzer/src/generated/element.dart'; | 12 import 'package:analyzer/src/generated/element.dart'; |
13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | 13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
14 import 'package:analyzer/src/generated/scanner.dart' | 14 import 'package:analyzer/src/generated/scanner.dart' |
15 show StringToken, Token, TokenType; | 15 show StringToken, Token, TokenType; |
16 import 'package:path/path.dart' as path; | 16 import 'package:path/path.dart' as path; |
17 | 17 |
18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; | 18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; |
19 import 'package:dev_compiler/src/codegen/reify_coercions.dart' | 19 import 'package:dev_compiler/src/codegen/reify_coercions.dart' |
20 show CoercionReifier; | 20 show CoercionReifier; |
21 | 21 |
22 // TODO(jmesserly): import from its own package | 22 // TODO(jmesserly): import from its own package |
23 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 23 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
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'; | |
28 import 'package:dev_compiler/src/info.dart'; | 27 import 'package:dev_compiler/src/info.dart'; |
29 import 'package:dev_compiler/src/options.dart'; | 28 import 'package:dev_compiler/src/options.dart'; |
30 import 'package:dev_compiler/src/utils.dart'; | 29 import 'package:dev_compiler/src/utils.dart'; |
31 | 30 |
32 import 'code_generator.dart'; | 31 import 'code_generator.dart'; |
33 import 'js_field_storage.dart'; | 32 import 'js_field_storage.dart'; |
34 import 'js_names.dart' as JS; | 33 import 'js_names.dart' as JS; |
35 import 'js_metalet.dart' as JS; | 34 import 'js_metalet.dart' as JS; |
| 35 import 'js_module_item_order.dart'; |
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 13 matching lines...) Expand all Loading... |
59 /// Information that is precomputed for this library, indicates which fields | 59 /// Information that is precomputed for this library, indicates which fields |
60 /// need storage slots. | 60 /// need storage slots. |
61 final HashSet<FieldElement> _fieldsNeedingStorage; | 61 final HashSet<FieldElement> _fieldsNeedingStorage; |
62 | 62 |
63 /// The variable for the target of the current `..` cascade expression. | 63 /// The variable for the target of the current `..` cascade expression. |
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; | |
70 | |
71 ClassElement _currentClassElement = null; | |
72 | |
73 /// Imported libraries, and the temporaries used to refer to them. | 69 /// Imported libraries, and the temporaries used to refer to them. |
74 final _imports = new Map<LibraryElement, JS.TemporaryId>(); | 70 final _imports = new Map<LibraryElement, JS.TemporaryId>(); |
75 final _exports = new Set<String>(); | 71 final _exports = new Set<String>(); |
76 final _lazyFields = <VariableDeclaration>[]; | 72 final _lazyFields = <VariableDeclaration>[]; |
77 final _properties = <FunctionDeclaration>[]; | 73 final _properties = <FunctionDeclaration>[]; |
78 final _privateNames = new HashMap<String, JS.TemporaryId>(); | 74 final _privateNames = new HashMap<String, JS.TemporaryId>(); |
79 final _extensionMethodNames = new HashSet<String>(); | 75 final _extensionMethodNames = new HashSet<String>(); |
80 final _pendingStatements = <JS.Statement>[]; | 76 final _moduleItems = <JS.Statement>[]; |
81 final _temps = new HashMap<Element, JS.TemporaryId>(); | 77 final _temps = new HashMap<Element, JS.TemporaryId>(); |
| 78 final _qualifiedIds = new HashMap<Element, JS.MaybeQualifiedId>(); |
| 79 final _qualifiedGenericIds = new HashMap<Element, JS.MaybeQualifiedId>(); |
82 | 80 |
83 /// The name for the library's exports inside itself. | 81 /// The name for the library's exports inside itself. |
84 /// `exports` was chosen as the most similar to ES module patterns. | 82 /// `exports` was chosen as the most similar to ES module patterns. |
85 final _exportsVar = new JS.TemporaryId('exports'); | 83 final _exportsVar = new JS.TemporaryId('exports'); |
86 final _namedArgTemp = new JS.TemporaryId('opts'); | 84 final _namedArgTemp = new JS.TemporaryId('opts'); |
87 | 85 |
88 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 86 ConstFieldVisitor _constField; |
89 /// [ClassTypeAlias]. | |
90 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | |
91 | 87 |
92 /// Memoized results of [_lazyClass]. | 88 ModuleItemLoadOrder _loader; |
93 final _lazyClassMemo = new HashMap<Element, bool>(); | |
94 | |
95 /// Memoized results of [_inLibraryCycle]. | |
96 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | |
97 | 89 |
98 JSCodegenVisitor(this.options, this.rules, this.libraryInfo, | 90 JSCodegenVisitor(this.options, this.rules, this.libraryInfo, |
99 this._extensionMethods, this._fieldsNeedingStorage); | 91 this._extensionMethods, this._fieldsNeedingStorage) { |
| 92 _loader = new ModuleItemLoadOrder(_emitModuleItem); |
| 93 } |
100 | 94 |
101 LibraryElement get currentLibrary => libraryInfo.library; | 95 LibraryElement get currentLibrary => libraryInfo.library; |
102 TypeProvider get types => rules.provider; | 96 TypeProvider get types => rules.provider; |
103 | 97 |
104 JS.Program emitLibrary(LibraryUnit library) { | 98 JS.Program emitLibrary(LibraryUnit library) { |
105 String jsDefaultValue = null; | 99 String jsDefaultValue = null; |
106 | 100 |
107 // Modify the AST to make coercions explicit. | 101 // Modify the AST to make coercions explicit. |
108 new CoercionReifier(library, rules, options).reify(); | 102 new CoercionReifier(library, rules, options).reify(); |
109 | 103 |
110 var unit = library.library; | 104 var unit = library.library; |
111 if (unit.directives.isNotEmpty) { | 105 if (unit.directives.isNotEmpty) { |
112 var libraryDir = unit.directives.first; | 106 var libraryDir = unit.directives.first; |
113 if (libraryDir is LibraryDirective) { | 107 if (libraryDir is LibraryDirective) { |
114 var jsName = getAnnotationValue(libraryDir, _isJsNameAnnotation); | 108 var jsName = getAnnotationValue(libraryDir, _isJsNameAnnotation); |
115 jsDefaultValue = getConstantField(jsName, 'name', types.stringType); | 109 jsDefaultValue = getConstantField(jsName, 'name', types.stringType); |
116 } | 110 } |
117 } | 111 } |
118 if (jsDefaultValue == null) jsDefaultValue = '{}'; | 112 if (jsDefaultValue == null) jsDefaultValue = '{}'; |
119 | 113 |
120 var body = <JS.Statement>[]; | 114 // TODO(jmesserly): visit scriptTag, directives? |
121 | 115 |
122 // Collect classes we need to emit, used for: | 116 _loader.collectElements(currentLibrary, library.partsThenLibrary); |
123 // * tracks what we've emitted so we don't emit twice | 117 |
124 // * provides a mapping from ClassElement back to the ClassDeclaration. | |
125 for (var unit in library.partsThenLibrary) { | 118 for (var unit in library.partsThenLibrary) { |
| 119 _constField = new ConstFieldVisitor(types, unit); |
| 120 |
126 for (var decl in unit.declarations) { | 121 for (var decl in unit.declarations) { |
127 if (decl is ClassDeclaration || | 122 if (decl is TopLevelVariableDeclaration) { |
128 decl is ClassTypeAlias || | 123 _visit(decl); |
129 decl is FunctionTypeAlias) { | 124 } else { |
130 _pendingClasses[decl.element] = decl; | 125 _loader.loadDeclaration(decl, decl.element); |
| 126 } |
| 127 if (decl is ClassDeclaration) { |
| 128 // Static fields can be emitted into the top-level code, so they need |
| 129 // to potentially be ordered independently of the class. |
| 130 for (var member in decl.members) { |
| 131 if (member is FieldDeclaration && member.isStatic) { |
| 132 for (var f in member.fields.variables) { |
| 133 _loader.loadDeclaration(f, f.element); |
| 134 } |
| 135 } |
| 136 } |
131 } | 137 } |
132 } | 138 } |
133 } | 139 } |
134 | 140 |
135 for (var unit in library.partsThenLibrary) body.add(_visit(unit)); | 141 // Flush any unwritten fields/properties. |
| 142 _flushLazyFields(_moduleItems); |
| 143 _flushLibraryProperties(_moduleItems); |
136 | 144 |
137 assert(_pendingClasses.isEmpty); | 145 // Mark all qualified names as qualified or not, depending on if they need |
| 146 // to be loaded lazily or not. |
| 147 unqualifyIfNeeded(Element e, JS.MaybeQualifiedId id) { |
| 148 id.setQualified(!_loader.isLoaded(e)); |
| 149 } |
| 150 _qualifiedIds.forEach(unqualifyIfNeeded); |
| 151 _qualifiedGenericIds.forEach(unqualifyIfNeeded); |
138 | 152 |
139 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); | 153 if (_exports.isNotEmpty) _moduleItems.add(js.comment('Exports:')); |
140 | 154 |
141 // TODO(jmesserly): make these immutable in JS? | 155 // TODO(jmesserly): make these immutable in JS? |
142 for (var name in _exports) { | 156 for (var name in _exports) { |
143 body.add(js.statement('#.# = #;', [_exportsVar, name, name])); | 157 _moduleItems.add(js.statement('#.# = #;', [_exportsVar, name, name])); |
144 } | 158 } |
145 | 159 |
146 var name = new JS.Identifier(jsLibraryName(currentLibrary)); | 160 var name = new JS.Identifier(jsLibraryName(currentLibrary)); |
147 | 161 |
148 // TODO(jmesserly): it would be great to run the renamer on the body, | 162 // TODO(jmesserly): it would be great to run the renamer on the body, |
149 // then figure out if we really need each of these parameters. | 163 // then figure out if we really need each of these parameters. |
150 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 | 164 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 |
151 var program = [ | 165 var program = [ |
152 js.statement('var # = dart.defineLibrary(#, #);', [ | 166 js.statement('var # = dart.defineLibrary(#, #);', [ |
153 name, | 167 name, |
154 name, | 168 name, |
155 js.call(jsDefaultValue) | 169 js.call(jsDefaultValue) |
156 ]) | 170 ]) |
157 ]; | 171 ]; |
158 | 172 |
159 var params = [_exportsVar]; | 173 var params = [_exportsVar]; |
160 var args = [name]; | 174 var args = [name]; |
161 _imports.forEach((library, temp) { | 175 _imports.forEach((library, temp) { |
162 var name = new JS.Identifier(temp.name); | 176 var name = new JS.Identifier(temp.name); |
163 params.add(temp); | 177 params.add(temp); |
164 args.add(name); | 178 args.add(name); |
165 var helper = _libraryMightNotBeLoaded(library) ? 'lazyImport' : 'import'; | 179 var helper = _loader.libraryIsLoaded(library) ? 'import' : 'lazyImport'; |
166 program.add(js.statement('var # = dart.#(#);', [name, helper, name])); | 180 program.add(js.statement('var # = dart.#(#);', [name, helper, name])); |
167 }); | 181 }); |
168 | 182 |
169 program.add(js.statement( | 183 program.add(js.statement("(function(#) { 'use strict'; #; })(#);", [ |
170 "(function(#) { 'use strict'; #; })(#);", [params, body, args])); | 184 params, |
| 185 _moduleItems, |
| 186 args |
| 187 ])); |
171 | 188 |
172 return new JS.Program(program); | 189 return new JS.Program(program); |
173 } | 190 } |
174 | 191 |
| 192 void _emitModuleItem(AstNode node) { |
| 193 // Attempt to group adjacent fields/properties. |
| 194 if (node is! VariableDeclaration) _flushLazyFields(_moduleItems); |
| 195 if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems); |
| 196 |
| 197 var code = _visit(node); |
| 198 if (code != null) _moduleItems.add(code); |
| 199 } |
| 200 |
175 JS.Identifier _initSymbol(JS.Identifier id) { | 201 JS.Identifier _initSymbol(JS.Identifier id) { |
176 var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); | 202 var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
177 _pendingStatements.add(s); | 203 _moduleItems.add(s); |
178 return id; | 204 return id; |
179 } | 205 } |
180 | 206 |
181 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 207 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
182 // until we have better name tracking. | 208 // until we have better name tracking. |
183 String get _SYMBOL { | 209 String get _SYMBOL { |
184 var name = currentLibrary.name; | 210 var name = currentLibrary.name; |
185 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 211 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
186 return 'Symbol'; | 212 return 'Symbol'; |
187 } | 213 } |
188 | 214 |
189 @override | |
190 JS.Statement visitCompilationUnit(CompilationUnit node) { | |
191 var source = node.element.source; | |
192 | |
193 _constEvaluator = new ConstantEvaluator(source, types); | |
194 | |
195 // TODO(jmesserly): scriptTag, directives. | |
196 var body = <JS.Statement>[]; | |
197 for (var child in node.declarations) { | |
198 // Attempt to group adjacent fields/properties. | |
199 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | |
200 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | |
201 | |
202 var code = _visit(child); | |
203 if (code != null) { | |
204 _flushPendingStatements(body); | |
205 body.add(code); | |
206 } | |
207 } | |
208 | |
209 // Flush any unwritten fields/properties. | |
210 _flushLazyFields(body); | |
211 _flushLibraryProperties(body); | |
212 | |
213 assert(_pendingStatements.isEmpty); | |
214 return _statement(body); | |
215 } | |
216 | |
217 bool isPublic(String name) => !name.startsWith('_'); | 215 bool isPublic(String name) => !name.startsWith('_'); |
218 | 216 |
219 /// Conversions that we don't handle end up here. | 217 /// Conversions that we don't handle end up here. |
220 @override | 218 @override |
221 visitConversion(Conversion node) { | 219 visitConversion(Conversion node) { |
222 throw 'Unlowered conversion ${node.runtimeType}: $node'; | 220 throw 'Unlowered conversion ${node.runtimeType}: $node'; |
223 } | 221 } |
224 | 222 |
225 @override | 223 @override |
226 visitAsExpression(AsExpression node) { | 224 visitAsExpression(AsExpression node) { |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
273 String _jsTypeofName(DartType t) { | 271 String _jsTypeofName(DartType t) { |
274 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; | 272 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; |
275 if (rules.isStringType(t)) return 'string'; | 273 if (rules.isStringType(t)) return 'string'; |
276 if (rules.isBoolType(t)) return 'boolean'; | 274 if (rules.isBoolType(t)) return 'boolean'; |
277 return null; | 275 return null; |
278 } | 276 } |
279 | 277 |
280 @override | 278 @override |
281 visitFunctionTypeAlias(FunctionTypeAlias node) { | 279 visitFunctionTypeAlias(FunctionTypeAlias node) { |
282 // If we've already emitted this class, skip it. | 280 // If we've already emitted this class, skip it. |
283 var type = node.element.type; | 281 var element = node.element; |
284 if (_pendingClasses.remove(node.element) == null) return null; | 282 var type = element.type; |
| 283 var name = element.name; |
285 | 284 |
286 var name = type.name; | 285 _loader.startTopLevel(element); |
287 var result = js.statement('let # = dart.typedef(#, () => #);', [ | 286 var fnType = js.statement('let # = dart.typedef(#, #);', [ |
288 new JS.Identifier(name), | 287 name, |
289 js.string(name, "'"), | 288 js.string(name, "'"), |
290 _emitTypeName(node.element.type, lowerTypedef: true) | 289 _emitTypeName(type, lowerTypedef: true) |
291 ]); | 290 ]); |
| 291 _loader.finishTopLevel(element); |
292 | 292 |
293 return _finishClassDef(type, result); | 293 return _finishClassDef(type, fnType); |
294 } | 294 } |
295 | 295 |
296 @override | 296 @override |
297 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); | 297 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); |
298 | 298 |
299 @override | 299 @override |
300 JS.Statement visitClassTypeAlias(ClassTypeAlias node) { | 300 JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
301 // If we've already emitted this class, skip it. | 301 // If we've already emitted this class, skip it. |
302 var type = node.element.type; | 302 var element = node.element; |
303 if (_pendingClasses.remove(node.element) == null) return null; | |
304 | 303 |
305 var name = node.name.name; | |
306 var classDecl = new JS.ClassDeclaration(new JS.ClassExpression( | 304 var classDecl = new JS.ClassDeclaration(new JS.ClassExpression( |
307 new JS.Identifier(name), _classHeritage(node), [])); | 305 new JS.Identifier(element.name), _classHeritage(element), [])); |
308 | 306 |
309 return _finishClassDef(type, classDecl); | 307 return _finishClassDef(element.type, classDecl); |
310 } | 308 } |
311 | 309 |
312 JS.Statement _emitJsType(String dartClassName, DartObjectImpl jsName) { | 310 JS.Statement _emitJsType(String dartClassName, DartObjectImpl jsName) { |
313 var jsTypeName = getConstantField(jsName, 'name', types.stringType); | 311 var jsTypeName = getConstantField(jsName, 'name', types.stringType); |
314 | 312 |
315 if (jsTypeName != null && jsTypeName != dartClassName) { | 313 if (jsTypeName != null && jsTypeName != dartClassName) { |
316 // We export the JS type as if it was a Dart type. For example this allows | 314 // We export the JS type as if it was a Dart type. For example this allows |
317 // `dom.InputElement` to actually be HTMLInputElement. | 315 // `dom.InputElement` to actually be HTMLInputElement. |
318 // TODO(jmesserly): if we had the JsName on the Element, we could just | 316 // TODO(jmesserly): if we had the JsName on the Element, we could just |
319 // generate it correctly when we refer to it. | 317 // generate it correctly when we refer to it. |
320 if (isPublic(dartClassName)) _addExport(dartClassName); | 318 if (isPublic(dartClassName)) _addExport(dartClassName); |
321 return js.statement('let # = #;', [dartClassName, jsTypeName]); | 319 return js.statement('let # = #;', [dartClassName, jsTypeName]); |
322 } | 320 } |
323 return null; | 321 return null; |
324 } | 322 } |
325 | 323 |
326 @override | 324 @override |
327 JS.Statement visitClassDeclaration(ClassDeclaration node) { | 325 JS.Statement visitClassDeclaration(ClassDeclaration node) { |
328 // If we've already emitted this class, skip it. | 326 // If we've already emitted this class, skip it. |
329 var classElem = node.element; | 327 var classElem = node.element; |
330 var type = classElem.type; | 328 var type = classElem.type; |
331 if (_pendingClasses.remove(classElem) == null) return null; | |
332 | |
333 var jsName = getAnnotationValue(node, _isJsNameAnnotation); | 329 var jsName = getAnnotationValue(node, _isJsNameAnnotation); |
334 | 330 |
335 if (jsName != null) return _emitJsType(node.name.name, jsName); | 331 if (jsName != null) return _emitJsType(node.name.name, jsName); |
336 | 332 |
337 // Set current class | |
338 assert(_currentClassElement == null); | |
339 _currentClassElement = classElem; | |
340 | |
341 var ctors = <ConstructorDeclaration>[]; | 333 var ctors = <ConstructorDeclaration>[]; |
342 var fields = <FieldDeclaration>[]; | 334 var fields = <FieldDeclaration>[]; |
343 var staticFields = <FieldDeclaration>[]; | |
344 for (var member in node.members) { | 335 for (var member in node.members) { |
345 if (member is ConstructorDeclaration) { | 336 if (member is ConstructorDeclaration) { |
346 ctors.add(member); | 337 ctors.add(member); |
347 } else if (member is FieldDeclaration) { | 338 } else if (member is FieldDeclaration && !member.isStatic) { |
348 (member.isStatic ? staticFields : fields).add(member); | 339 fields.add(member); |
349 } | 340 } |
350 } | 341 } |
351 | 342 |
352 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), | 343 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
353 _classHeritage(node), _emitClassMethods(node, ctors, fields)); | 344 _classHeritage(classElem), _emitClassMethods(node, ctors, fields)); |
354 | 345 |
355 String jsPeerName; | 346 String jsPeerName; |
356 var jsPeer = getAnnotationValue(node, _isJsPeerInterface); | 347 var jsPeer = getAnnotationValue(node, _isJsPeerInterface); |
357 if (jsPeer != null) { | 348 if (jsPeer != null) { |
358 jsPeerName = getConstantField(jsPeer, 'name', types.stringType); | 349 jsPeerName = getConstantField(jsPeer, 'name', types.stringType); |
359 } | 350 } |
360 | 351 |
361 var body = _finishClassMembers( | 352 var body = |
362 classElem, classExpr, ctors, fields, staticFields, jsPeerName); | 353 _finishClassMembers(classElem, classExpr, ctors, fields, jsPeerName); |
363 | |
364 // Unset current class | |
365 assert(_currentClassElement == classElem); | |
366 _currentClassElement = null; | |
367 | 354 |
368 var result = _finishClassDef(type, body); | 355 var result = _finishClassDef(type, body); |
369 | 356 |
370 if (jsPeerName != null) { | 357 if (jsPeerName != null) { |
371 // This class isn't allowed to be lazy, because we need to set up | 358 // This class isn't allowed to be lazy, because we need to set up |
372 // the native JS type eagerly at this point. | 359 // the native JS type eagerly at this point. |
373 // If we wanted to support laziness, we could defer the hookup until | 360 // If we wanted to support laziness, we could defer the hookup until |
374 // the end of the Dart library cycle load. | 361 // the end of the Dart library cycle load. |
375 assert(!_lazyClass(type)); | 362 assert(_loader.isLoaded(classElem)); |
376 | 363 |
377 // TODO(jmesserly): this copies the dynamic members. | 364 // TODO(jmesserly): this copies the dynamic members. |
378 // Probably fine for objects coming from JS, but not if we actually | 365 // Probably fine for objects coming from JS, but not if we actually |
379 // want to support construction of instances with generic types other | 366 // want to support construction of instances with generic types other |
380 // than dynamic. See issue #154 for Array and List<E> related bug. | 367 // than dynamic. See issue #154 for Array and List<E> related bug. |
381 var copyMembers = js.statement( | 368 var copyMembers = js.statement( |
382 'dart.registerExtension(dart.global.#, #);', [ | 369 'dart.registerExtension(dart.global.#, #);', [ |
383 _propertyName(jsPeerName), | 370 _propertyName(jsPeerName), |
384 classElem.name | 371 classElem.name |
385 ]); | 372 ]); |
386 return _statement([result, copyMembers]); | 373 return _statement([result, copyMembers]); |
387 } | 374 } |
388 return result; | 375 return result; |
389 } | 376 } |
390 | 377 |
391 @override | 378 @override |
392 JS.Statement visitEnumDeclaration(EnumDeclaration node) => | 379 JS.Statement visitEnumDeclaration(EnumDeclaration node) => |
393 _unimplementedCall("Unimplemented enum: $node").toStatement(); | 380 _unimplementedCall("Unimplemented enum: $node").toStatement(); |
394 | 381 |
395 /// Given a class element and body, complete the class declaration. | 382 /// Given a class element and body, complete the class declaration. |
396 /// This handles generic type parameters, laziness (in library-cycle cases), | 383 /// This handles generic type parameters, laziness (in library-cycle cases), |
397 /// and ensuring dependencies are loaded first. | 384 /// and ensuring dependencies are loaded first. |
398 JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) { | 385 JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) { |
399 var name = type.name; | 386 var name = type.name; |
400 var genericName = '$name\$'; | 387 var genericName = '$name\$'; |
401 | 388 |
402 JS.Statement genericDef; | 389 JS.Statement genericDef = null; |
403 JS.Expression genericInst; | |
404 if (type.typeParameters.isNotEmpty) { | 390 if (type.typeParameters.isNotEmpty) { |
405 genericDef = _emitGenericClassDef(type, body); | 391 genericDef = _emitGenericClassDef(type, body); |
406 var target = genericName; | |
407 if (_needQualifiedName(type.element)) { | |
408 target = js.call('#.#', [_exportsVar, genericName]); | |
409 } | |
410 genericInst = js.call('#()', [target]); | |
411 } | 392 } |
412 | 393 |
413 // The base class and all mixins must be declared before this class. | 394 // The base class and all mixins must be declared before this class. |
414 if (_lazyClass(type)) { | 395 if (!_loader.isLoaded(type.element)) { |
415 // TODO(jmesserly): the lazy class def is a simple solution for now. | 396 // TODO(jmesserly): the lazy class def is a simple solution for now. |
416 // We may want to consider other options in the future. | 397 // We may want to consider other options in the future. |
417 | 398 |
418 if (genericDef != null) { | 399 if (genericDef != null) { |
419 return js.statement( | 400 return js.statement( |
420 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ | 401 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ |
421 genericDef, | 402 genericDef, |
422 _exportsVar, | 403 _exportsVar, |
423 _propertyName(name), | 404 _propertyName(name), |
424 genericName | 405 genericName |
425 ]); | 406 ]); |
426 } | 407 } |
427 | 408 |
428 return js.statement( | 409 return js.statement( |
429 'dart.defineLazyClass(#, { get #() { #; return #; } });', [ | 410 'dart.defineLazyClass(#, { get #() { #; return #; } });', [ |
430 _exportsVar, | 411 _exportsVar, |
431 _propertyName(name), | 412 _propertyName(name), |
432 body, | 413 body, |
433 name | 414 name |
434 ]); | 415 ]); |
435 } | 416 } |
436 | 417 |
437 if (isPublic(name)) _addExport(name); | 418 if (isPublic(name)) _addExport(name); |
438 | 419 |
439 if (genericDef != null) { | 420 if (genericDef != null) { |
440 body = js.statement('{ #; let # = #; }', [genericDef, name, genericInst]); | 421 var dynType = fillDynamicTypeArgs(type, types); |
| 422 var genericInst = _emitTypeName(dynType, lowerGeneric: true); |
| 423 return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]); |
441 } | 424 } |
442 | 425 return body; |
443 if (type.isObject) return body; | |
444 | |
445 // If we're not lazy, we still need to ensure our dependencies are | |
446 // generated first. | |
447 var classDefs = <JS.Statement>[]; | |
448 if (type is InterfaceType) { | |
449 _emitClassIfNeeded(classDefs, type.superclass); | |
450 for (var m in type.element.mixins) { | |
451 _emitClassIfNeeded(classDefs, m); | |
452 } | |
453 } else if (type is FunctionType) { | |
454 _emitClassIfNeeded(classDefs, types.functionType); | |
455 } | |
456 classDefs.add(body); | |
457 return _statement(classDefs); | |
458 } | |
459 | |
460 void _emitClassIfNeeded(List<JS.Statement> defs, DartType base) { | |
461 // We can only emit classes from this library. | |
462 if (base.element.library != currentLibrary) return; | |
463 | |
464 var baseNode = _pendingClasses[base.element]; | |
465 if (baseNode != null) defs.add(visitClassDeclaration(baseNode)); | |
466 } | |
467 | |
468 /// Returns true if the supertype or mixins aren't loaded. | |
469 /// If that is the case, we'll emit a lazy class definition. | |
470 bool _lazyClass(DartType type) { | |
471 if (type.isObject) return false; | |
472 | |
473 // Use the element as the key, as those are unique whereas generic types | |
474 // can have their arguments substituted. | |
475 assert(type.element.library == currentLibrary); | |
476 var result = _lazyClassMemo[type.element]; | |
477 if (result != null) return result; | |
478 | |
479 if (type is InterfaceType) { | |
480 result = _typeMightNotBeLoaded(type.superclass) || | |
481 type.mixins.any(_typeMightNotBeLoaded); | |
482 } else if (type is FunctionType) { | |
483 result = _typeMightNotBeLoaded(types.functionType); | |
484 } | |
485 return _lazyClassMemo[type.element] = result; | |
486 } | |
487 | |
488 /// Returns true if the class might not be loaded. | |
489 /// | |
490 /// If the class is from our library, this can happen because it's lazy. | |
491 /// | |
492 /// If the class is from a different library, it could happen if we're in | |
493 /// a library cycle. In other words, if that different library depends back | |
494 /// on this library via some transitive import path. | |
495 /// | |
496 /// If we could control the global import ordering, we could eliminate some | |
497 /// of these cases, by ordering the imports of the cyclic libraries in an | |
498 /// optimal way. For example, we could order the libraries in a cycle to | |
499 /// minimize laziness. However, we currently assume we cannot control the | |
500 /// order that the cycle of libraries will be loaded in. | |
501 bool _typeMightNotBeLoaded(DartType type) { | |
502 var library = type.element.library; | |
503 if (library == currentLibrary) return _lazyClass(type); | |
504 return _libraryMightNotBeLoaded(library); | |
505 } | |
506 | |
507 bool _libraryMightNotBeLoaded(LibraryElement library) { | |
508 // The SDK is a special case: we optimize the order to prevent laziness. | |
509 if (library.isInSdk) { | |
510 // SDK is loaded before non-SDK libraies | |
511 if (!currentLibrary.isInSdk) return false; | |
512 | |
513 // Compute the order of both SDK libraries. If unknown, assume it's after. | |
514 var classOrder = corelibOrder.indexOf(library.name); | |
515 if (classOrder == -1) classOrder = corelibOrder.length; | |
516 | |
517 var currentOrder = corelibOrder.indexOf(currentLibrary.name); | |
518 if (currentOrder == -1) currentOrder = corelibOrder.length; | |
519 | |
520 // If the dart:* library we are currently compiling is loaded after the | |
521 // class's library, then we know the class is available. | |
522 if (classOrder != currentOrder) return currentOrder < classOrder; | |
523 | |
524 // If we don't know the order of the class's library or the current | |
525 // library, do the normal cycle check. (Not all SDK libs are cycles.) | |
526 } | |
527 | |
528 return _inLibraryCycle(library); | |
529 } | |
530 | |
531 /// Returns true if [library] depends on the [currentLibrary] via some | |
532 /// transitive import. | |
533 bool _inLibraryCycle(LibraryElement library) { | |
534 // SDK libs don't depend on things outside the SDK. | |
535 // (We can reach this via the recursive call below.) | |
536 if (library.isInSdk && !currentLibrary.isInSdk) return false; | |
537 | |
538 var result = _libraryCycleMemo[library]; | |
539 if (result != null) return result; | |
540 | |
541 result = library == currentLibrary; | |
542 _libraryCycleMemo[library] = result; | |
543 for (var e in library.imports) { | |
544 if (result) break; | |
545 result = _inLibraryCycle(e.importedLibrary); | |
546 } | |
547 for (var e in library.exports) { | |
548 if (result) break; | |
549 result = _inLibraryCycle(e.exportedLibrary); | |
550 } | |
551 return _libraryCycleMemo[library] = result; | |
552 } | 426 } |
553 | 427 |
554 JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) { | 428 JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) { |
555 var name = type.name; | 429 var name = type.name; |
556 var genericName = '$name\$'; | 430 var genericName = '$name\$'; |
557 var typeParams = type.typeParameters.map((p) => p.name); | 431 var typeParams = type.typeParameters.map((p) => p.name); |
558 if (isPublic(name)) _exports.add(genericName); | 432 if (isPublic(name)) _exports.add(genericName); |
559 return js.statement('let # = dart.generic(function(#) { #; return #; });', [ | 433 return js.statement('let # = dart.generic(function(#) { #; return #; });', [ |
560 genericName, | 434 genericName, |
561 typeParams, | 435 typeParams, |
562 body, | 436 body, |
563 name | 437 name |
564 ]); | 438 ]); |
565 } | 439 } |
566 | 440 |
567 JS.Expression _classHeritage(node) { | 441 JS.Expression _classHeritage(ClassElement element) { |
568 if (node.element.type.isObject) return null; | 442 var type = element.type; |
| 443 if (type.isObject) return null; |
569 | 444 |
570 DartType supertype; | 445 // Assume we can load eagerly, until proven otherwise. |
571 if (node is ClassDeclaration) { | 446 _loader.startTopLevel(element); |
572 var ext = node.extendsClause; | |
573 supertype = ext != null ? ext.superclass.type : types.objectType; | |
574 } else { | |
575 supertype = (node as ClassTypeAlias).superclass.type; | |
576 } | |
577 | 447 |
578 JS.Expression heritage = _emitTypeName(supertype); | 448 JS.Expression heritage = _emitTypeName(type.superclass); |
579 | 449 if (type.mixins.isNotEmpty) { |
580 if (node.withClause != null) { | 450 var mixins = type.mixins.map(_emitTypeName).toList(); |
581 var mixins = _visitList(node.withClause.mixinTypes); | |
582 mixins.insert(0, heritage); | 451 mixins.insert(0, heritage); |
583 heritage = js.call('dart.mixin(#)', [mixins]); | 452 heritage = js.call('dart.mixin(#)', [mixins]); |
584 } | 453 } |
585 | 454 |
| 455 _loader.finishTopLevel(element); |
586 return heritage; | 456 return heritage; |
587 } | 457 } |
588 | 458 |
589 List<JS.Method> _emitClassMethods(ClassDeclaration node, | 459 List<JS.Method> _emitClassMethods(ClassDeclaration node, |
590 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { | 460 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
591 var element = node.element; | 461 var element = node.element; |
592 var type = element.type; | 462 var type = element.type; |
593 var isObject = type.isObject; | 463 var isObject = type.isObject; |
594 var name = node.name.name; | 464 var name = node.name.name; |
595 | 465 |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
656 return new JS.Method(js.call('$_SYMBOL.iterator'), js.call( | 526 return new JS.Method(js.call('$_SYMBOL.iterator'), js.call( |
657 'function() { return new dart.JsIterator(this.#); }', | 527 'function() { return new dart.JsIterator(this.#); }', |
658 [_emitMemberName('iterator', type: t)])); | 528 [_emitMemberName('iterator', type: t)])); |
659 } | 529 } |
660 | 530 |
661 /// Emit class members that need to come after the class declaration, such | 531 /// Emit class members that need to come after the class declaration, such |
662 /// as static fields. See [_emitClassMethods] for things that are emitted | 532 /// as static fields. See [_emitClassMethods] for things that are emitted |
663 /// inside the ES6 `class { ... }` node. | 533 /// inside the ES6 `class { ... }` node. |
664 JS.Statement _finishClassMembers(ClassElement classElem, | 534 JS.Statement _finishClassMembers(ClassElement classElem, |
665 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, | 535 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, |
666 List<FieldDeclaration> fields, List<FieldDeclaration> staticFields, | 536 List<FieldDeclaration> fields, String jsPeerName) { |
667 String jsPeerName) { | |
668 var name = classElem.name; | 537 var name = classElem.name; |
669 var body = <JS.Statement>[]; | 538 var body = <JS.Statement>[]; |
670 body.add(new JS.ClassDeclaration(cls)); | 539 body.add(new JS.ClassDeclaration(cls)); |
671 | 540 |
672 // TODO(jmesserly): we should really just extend native Array. | 541 // TODO(jmesserly): we should really just extend native Array. |
673 if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { | 542 if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { |
674 body.add(js.statement('dart.setBaseClass(#, dart.global.#);', [ | 543 body.add(js.statement('dart.setBaseClass(#, dart.global.#);', [ |
675 classElem.name, | 544 classElem.name, |
676 _propertyName(jsPeerName) | 545 _propertyName(jsPeerName) |
677 ])); | 546 ])); |
(...skipping 21 matching lines...) Expand all Loading... |
699 // Instance fields, if they override getter/setter pairs | 568 // Instance fields, if they override getter/setter pairs |
700 for (FieldDeclaration member in fields) { | 569 for (FieldDeclaration member in fields) { |
701 for (VariableDeclaration fieldDecl in member.fields.variables) { | 570 for (VariableDeclaration fieldDecl in member.fields.variables) { |
702 var field = fieldDecl.element; | 571 var field = fieldDecl.element; |
703 if (_fieldsNeedingStorage.contains(field)) { | 572 if (_fieldsNeedingStorage.contains(field)) { |
704 body.add(_overrideField(field)); | 573 body.add(_overrideField(field)); |
705 } | 574 } |
706 } | 575 } |
707 } | 576 } |
708 | 577 |
709 // Static fields | |
710 var lazyStatics = <VariableDeclaration>[]; | |
711 for (FieldDeclaration member in staticFields) { | |
712 for (VariableDeclaration field in member.fields.variables) { | |
713 var fieldName = field.name.name; | |
714 if ((field.isConst || _isFieldInitConstant(field)) && | |
715 !JS.invalidStaticFieldName(fieldName)) { | |
716 var init = _visit(field.initializer); | |
717 if (init == null) init = new JS.LiteralNull(); | |
718 body.add(js.statement('#.# = #;', [name, fieldName, init])); | |
719 } else { | |
720 lazyStatics.add(field); | |
721 } | |
722 } | |
723 } | |
724 var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); | |
725 if (lazy != null) body.add(lazy); | |
726 return _statement(body); | 578 return _statement(body); |
727 } | 579 } |
728 | 580 |
729 JS.Statement _overrideField(FieldElement e) { | 581 JS.Statement _overrideField(FieldElement e) { |
730 var cls = e.enclosingElement; | 582 var cls = e.enclosingElement; |
731 return js.statement('dart.virtualField(#, #)', [ | 583 return js.statement('dart.virtualField(#, #)', [ |
732 cls.name, | 584 cls.name, |
733 _emitMemberName(e.name, type: cls.type) | 585 _emitMemberName(e.name, type: cls.type) |
734 ]); | 586 ]); |
735 } | 587 } |
736 | 588 |
737 /// Generates the implicit default constructor for class C of the form | 589 /// Generates the implicit default constructor for class C of the form |
738 /// `C() : super() {}`. | 590 /// `C() : super() {}`. |
739 JS.Method _emitImplicitConstructor( | 591 JS.Method _emitImplicitConstructor( |
740 ClassDeclaration node, String name, List<FieldDeclaration> fields) { | 592 ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
741 assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty); | 593 assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty); |
742 | 594 |
743 // If we don't have a method body, skip this. | 595 // If we don't have a method body, skip this. |
744 var superCall = _superConstructorCall(node); | 596 var superCall = _superConstructorCall(node); |
745 if (fields.isEmpty && superCall == null) return null; | 597 if (fields.isEmpty && superCall == null) return null; |
746 | 598 |
747 dynamic body = _initializeFields(fields); | 599 dynamic body = _initializeFields(node, fields); |
748 if (superCall != null) body = [[body, superCall]]; | 600 if (superCall != null) body = [[body, superCall]]; |
749 return new JS.Method( | 601 return new JS.Method( |
750 _propertyName(name), js.call('function() { #; }', body)); | 602 _propertyName(name), js.call('function() { #; }', body)); |
751 } | 603 } |
752 | 604 |
753 JS.Method _emitConstructor(ConstructorDeclaration node, String className, | 605 JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
754 List<FieldDeclaration> fields, bool isObject) { | 606 List<FieldDeclaration> fields, bool isObject) { |
755 if (_externalOrNative(node)) return null; | 607 if (_externalOrNative(node)) return null; |
756 | 608 |
757 var name = _constructorName(className, node.name); | 609 var name = _constructorName(className, node.name); |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
826 body.add(_visit(redirectCall)); | 678 body.add(_visit(redirectCall)); |
827 return new JS.Block(body); | 679 return new JS.Block(body); |
828 } | 680 } |
829 | 681 |
830 // Initializers only run for non-factory constructors. | 682 // Initializers only run for non-factory constructors. |
831 if (node.factoryKeyword == null) { | 683 if (node.factoryKeyword == null) { |
832 // Generate field initializers. | 684 // Generate field initializers. |
833 // These are expanded into each non-redirecting constructor. | 685 // These are expanded into each non-redirecting constructor. |
834 // In the future we may want to create an initializer function if we have | 686 // In the future we may want to create an initializer function if we have |
835 // multiple constructors, but it needs to be balanced against readability. | 687 // multiple constructors, but it needs to be balanced against readability. |
836 body.add(_initializeFields(fields, node.parameters, node.initializers)); | 688 body.add(_initializeFields(node, fields)); |
837 | 689 |
838 var superCall = node.initializers.firstWhere( | 690 var superCall = node.initializers.firstWhere( |
839 (i) => i is SuperConstructorInvocation, orElse: () => null); | 691 (i) => i is SuperConstructorInvocation, orElse: () => null); |
840 | 692 |
841 // If no superinitializer is provided, an implicit superinitializer of the | 693 // If no superinitializer is provided, an implicit superinitializer of the |
842 // form `super()` is added at the end of the initializer list, unless the | 694 // form `super()` is added at the end of the initializer list, unless the |
843 // enclosing class is class Object. | 695 // enclosing class is class Object. |
844 var jsSuper = _superConstructorCall(node.parent, superCall); | 696 var jsSuper = _superConstructorCall(node.parent, superCall); |
845 if (jsSuper != null) body.add(jsSuper); | 697 if (jsSuper != null) body.add(jsSuper); |
846 } | 698 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
890 if (!e.unnamedConstructor.isSynthetic) return true; | 742 if (!e.unnamedConstructor.isSynthetic) return true; |
891 return e.fields.any((f) => !f.isStatic && !f.isSynthetic); | 743 return e.fields.any((f) => !f.isStatic && !f.isSynthetic); |
892 } | 744 } |
893 | 745 |
894 /// Initialize fields. They follow the sequence: | 746 /// Initialize fields. They follow the sequence: |
895 /// | 747 /// |
896 /// 1. field declaration initializer if non-const, | 748 /// 1. field declaration initializer if non-const, |
897 /// 2. field initializing parameters, | 749 /// 2. field initializing parameters, |
898 /// 3. constructor field initializers, | 750 /// 3. constructor field initializers, |
899 /// 4. initialize fields not covered in 1-3 | 751 /// 4. initialize fields not covered in 1-3 |
900 JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls, | 752 JS.Statement _initializeFields( |
901 [FormalParameterList parameters, | 753 AstNode node, List<FieldDeclaration> fieldDecls) { |
902 NodeList<ConstructorInitializer> initializers]) { | 754 var unit = node.getAncestor((a) => a is CompilationUnit); |
| 755 var constField = new ConstFieldVisitor(types, unit); |
903 | 756 |
904 // Run field initializers if they can have side-effects. | 757 // Run field initializers if they can have side-effects. |
905 var fields = new Map<FieldElement, JS.Expression>(); | 758 var fields = new Map<FieldElement, JS.Expression>(); |
906 var unsetFields = new Map<FieldElement, VariableDeclaration>(); | 759 var unsetFields = new Map<FieldElement, VariableDeclaration>(); |
907 for (var declaration in fieldDecls) { | 760 for (var declaration in fieldDecls) { |
908 for (var fieldNode in declaration.fields.variables) { | 761 for (var fieldNode in declaration.fields.variables) { |
909 var element = fieldNode.element; | 762 var element = fieldNode.element; |
910 if (_isFieldInitConstant(fieldNode)) { | 763 if (constField.isFieldInitConstant(fieldNode)) { |
911 unsetFields[element] = fieldNode; | 764 unsetFields[element] = fieldNode; |
912 } else { | 765 } else { |
913 fields[element] = _visitInitializer(fieldNode); | 766 fields[element] = _visitInitializer(fieldNode); |
914 } | 767 } |
915 } | 768 } |
916 } | 769 } |
917 | 770 |
918 // Initialize fields from `this.fieldName` parameters. | 771 // Initialize fields from `this.fieldName` parameters. |
919 if (parameters != null) { | 772 if (node is ConstructorDeclaration) { |
| 773 var parameters = node.parameters; |
| 774 var initializers = node.initializers; |
| 775 |
920 for (var p in parameters.parameters) { | 776 for (var p in parameters.parameters) { |
921 var element = p.element; | 777 var element = p.element; |
922 if (element is FieldFormalParameterElement) { | 778 if (element is FieldFormalParameterElement) { |
923 fields[element.field] = _visit(p); | 779 fields[element.field] = _visit(p); |
924 } | 780 } |
925 } | 781 } |
926 } | |
927 | 782 |
928 // Run constructor field initializers such as `: foo = bar.baz` | 783 // Run constructor field initializers such as `: foo = bar.baz` |
929 if (initializers != null) { | |
930 for (var init in initializers) { | 784 for (var init in initializers) { |
931 if (init is ConstructorFieldInitializer) { | 785 if (init is ConstructorFieldInitializer) { |
932 fields[init.fieldName.staticElement] = _visit(init.expression); | 786 fields[init.fieldName.staticElement] = _visit(init.expression); |
933 } | 787 } |
934 } | 788 } |
935 } | 789 } |
936 | 790 |
937 for (var f in fields.keys) unsetFields.remove(f); | 791 for (var f in fields.keys) unsetFields.remove(f); |
938 | 792 |
939 // Initialize all remaining fields | 793 // Initialize all remaining fields |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1122 var accessor = node.staticElement; | 976 var accessor = node.staticElement; |
1123 if (accessor == null) { | 977 if (accessor == null) { |
1124 return js.commentExpression( | 978 return js.commentExpression( |
1125 'Unimplemented unknown name', new JS.Identifier(node.name)); | 979 'Unimplemented unknown name', new JS.Identifier(node.name)); |
1126 } | 980 } |
1127 | 981 |
1128 // Get the original declaring element. If we had a property accessor, this | 982 // Get the original declaring element. If we had a property accessor, this |
1129 // indirects back to a (possibly synthetic) field. | 983 // indirects back to a (possibly synthetic) field. |
1130 var element = accessor; | 984 var element = accessor; |
1131 if (element is PropertyAccessorElement) element = accessor.variable; | 985 if (element is PropertyAccessorElement) element = accessor.variable; |
| 986 |
| 987 _loader.declareBeforeUse(element); |
| 988 |
1132 var name = element.name; | 989 var name = element.name; |
1133 | 990 |
1134 // library member | 991 // library member |
1135 if (element.enclosingElement is CompilationUnitElement && | 992 if (element.enclosingElement is CompilationUnitElement) { |
1136 (element.library != currentLibrary || | 993 return _maybeQualifiedName( |
1137 element is TopLevelVariableElement && !element.isConst)) { | 994 element, _emitMemberName(name, isStatic: true)); |
1138 var memberName = _emitMemberName(name, isStatic: true); | |
1139 return js.call('#.#', [_libraryName(element.library), memberName]); | |
1140 } | 995 } |
1141 | 996 |
1142 // Unqualified class member. This could mean implicit-this, or implicit | 997 // Unqualified class member. This could mean implicit-this, or implicit |
1143 // call to a static from the same class. | 998 // call to a static from the same class. |
1144 if (element is ClassMemberElement && element is! ConstructorElement) { | 999 if (element is ClassMemberElement && element is! ConstructorElement) { |
1145 bool isStatic = element.isStatic; | 1000 bool isStatic = element.isStatic; |
1146 var type = element.enclosingElement.type; | 1001 var type = element.enclosingElement.type; |
1147 var member = _emitMemberName(name, isStatic: isStatic, type: type); | 1002 var member = _emitMemberName(name, isStatic: isStatic, type: type); |
1148 | 1003 |
1149 // For static methods, we add the raw type name, without generics or | 1004 // For static methods, we add the raw type name, without generics or |
1150 // library prefix. We don't need those because static calls can't use | 1005 // library prefix. We don't need those because static calls can't use |
1151 // the generic type. | 1006 // the generic type. |
1152 if (isStatic) { | 1007 if (isStatic) { |
1153 return js.call('#.#', [type.name, member]); | 1008 var dynType = _emitTypeName(fillDynamicTypeArgs(type, types)); |
| 1009 return new JS.PropertyAccess(dynType, member); |
1154 } | 1010 } |
1155 | 1011 |
1156 // For instance members, we add implicit-this. | 1012 // For instance members, we add implicit-this. |
1157 // For method tear-offs, we ensure it's a bound method. | 1013 // For method tear-offs, we ensure it's a bound method. |
1158 var code = 'this.#'; | 1014 var code = 'this.#'; |
1159 if (element is MethodElement && !inInvocationContext(node)) { | 1015 if (element is MethodElement && !inInvocationContext(node)) { |
1160 code += '.bind(this)'; | 1016 code += '.bind(this)'; |
1161 } | 1017 } |
1162 return js.call(code, member); | 1018 return js.call(code, member); |
1163 } | 1019 } |
(...skipping 29 matching lines...) Expand all Loading... |
1193 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { | 1049 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { |
1194 var properties = <JS.Property>[]; | 1050 var properties = <JS.Property>[]; |
1195 types.forEach((name, type) { | 1051 types.forEach((name, type) { |
1196 var key = new JS.LiteralString(name); | 1052 var key = new JS.LiteralString(name); |
1197 var value = _emitTypeName(type); | 1053 var value = _emitTypeName(type); |
1198 properties.add(new JS.Property(key, value)); | 1054 properties.add(new JS.Property(key, value)); |
1199 }); | 1055 }); |
1200 return new JS.ObjectInitializer(properties); | 1056 return new JS.ObjectInitializer(properties); |
1201 } | 1057 } |
1202 | 1058 |
1203 JS.Expression _emitTypeName(DartType type, {bool lowerTypedef: false}) { | 1059 /// Emits a Dart [type] into code. |
| 1060 /// |
| 1061 /// If [lowerTypedef] is set, a typedef will be expanded as if it were a |
| 1062 /// function type. Similarly if [lowerGeneric] is set, the `List$()` form |
| 1063 /// will be used instead of `List`. These flags are used when generating |
| 1064 /// the definitions for typedefs and generic types, respectively. |
| 1065 JS.Expression _emitTypeName(DartType type, |
| 1066 {bool lowerTypedef: false, bool lowerGeneric: false}) { |
| 1067 |
1204 // The void and dynamic types are not defined in core. | 1068 // The void and dynamic types are not defined in core. |
1205 if (type.isVoid) { | 1069 if (type.isVoid) { |
1206 return js.call('dart.void'); | 1070 return js.call('dart.void'); |
1207 } else if (type.isDynamic) { | 1071 } else if (type.isDynamic) { |
1208 return js.call('dart.dynamic'); | 1072 return js.call('dart.dynamic'); |
1209 } | 1073 } |
1210 | 1074 |
| 1075 _loader.declareBeforeUse(type.element); |
| 1076 |
| 1077 // TODO(jmesserly): like constants, should we hoist function types out of |
| 1078 // methods? Similar issue with generic types. For all of these, we may want |
| 1079 // to canonicalize them too, at least when inside the same library. |
1211 var name = type.name; | 1080 var name = type.name; |
1212 var element = type.element; | 1081 var element = type.element; |
1213 if (name == '' || lowerTypedef && type is FunctionType) { | 1082 if (name == '' || lowerTypedef) { |
1214 if (type is FunctionType) { | 1083 var fnType = type as FunctionType; |
1215 var returnType = type.returnType; | 1084 var returnType = fnType.returnType; |
1216 var parameterTypes = type.normalParameterTypes; | 1085 var parameterTypes = fnType.normalParameterTypes; |
1217 var optionalTypes = type.optionalParameterTypes; | 1086 var optionalTypes = fnType.optionalParameterTypes; |
1218 var namedTypes = type.namedParameterTypes; | 1087 var namedTypes = fnType.namedParameterTypes; |
1219 if (namedTypes.isEmpty) { | 1088 if (namedTypes.isEmpty) { |
1220 if (optionalTypes.isEmpty) { | 1089 if (optionalTypes.isEmpty) { |
1221 return js.call('dart.functionType(#, #)', [ | 1090 return js.call('dart.functionType(#, #)', [ |
1222 _emitTypeName(returnType), | 1091 _emitTypeName(returnType), |
1223 _emitTypeNames(parameterTypes) | 1092 _emitTypeNames(parameterTypes) |
1224 ]); | 1093 ]); |
1225 } else { | |
1226 return js.call('dart.functionType(#, #, #)', [ | |
1227 _emitTypeName(returnType), | |
1228 _emitTypeNames(parameterTypes), | |
1229 _emitTypeNames(optionalTypes) | |
1230 ]); | |
1231 } | |
1232 } else { | 1094 } else { |
1233 assert(optionalTypes.isEmpty); | |
1234 return js.call('dart.functionType(#, #, #)', [ | 1095 return js.call('dart.functionType(#, #, #)', [ |
1235 _emitTypeName(returnType), | 1096 _emitTypeName(returnType), |
1236 _emitTypeNames(parameterTypes), | 1097 _emitTypeNames(parameterTypes), |
1237 _emitTypeProperties(namedTypes) | 1098 _emitTypeNames(optionalTypes) |
1238 ]); | 1099 ]); |
1239 } | 1100 } |
| 1101 } else { |
| 1102 assert(optionalTypes.isEmpty); |
| 1103 return js.call('dart.functionType(#, #, #)', [ |
| 1104 _emitTypeName(returnType), |
| 1105 _emitTypeNames(parameterTypes), |
| 1106 _emitTypeProperties(namedTypes) |
| 1107 ]); |
1240 } | 1108 } |
1241 // TODO(jmesserly): remove when we're using coercion reifier. | |
1242 return _unimplementedCall('Unimplemented type $type'); | |
1243 } | 1109 } |
1244 | 1110 |
1245 var typeArgs = null; | 1111 if (type is TypeParameterType) { |
| 1112 return new JS.Identifier(name); |
| 1113 } |
| 1114 |
1246 if (type is ParameterizedType) { | 1115 if (type is ParameterizedType) { |
1247 var args = type.typeArguments; | 1116 var args = type.typeArguments; |
1248 var isCurrentClass = | 1117 var isCurrentClass = |
1249 type is InterfaceType ? type.element == _currentClassElement : false; | 1118 args.isNotEmpty && _loader.isCurrentElement(type.element); |
| 1119 Iterable jsArgs = null; |
1250 if (args.any((a) => a != types.dynamicType)) { | 1120 if (args.any((a) => a != types.dynamicType)) { |
1251 name = '$name\$'; | 1121 jsArgs = args.map(_emitTypeName); |
1252 typeArgs = args.map(_emitTypeName); | 1122 } else if (lowerGeneric || isCurrentClass) { |
1253 } else if (args.isNotEmpty && isCurrentClass) { | |
1254 // When creating a `new S<dynamic>` we try and use the raw form | 1123 // When creating a `new S<dynamic>` we try and use the raw form |
1255 // `new S()`, but this does not work if we're inside the same class, | 1124 // `new S()`, but this does not work if we're inside the same class, |
1256 // because `S` refers to the current S<T> we are generating. | 1125 // because `S` refers to the current S<T> we are generating. |
1257 name = '$name\$'; | 1126 jsArgs = []; |
1258 typeArgs = []; | 1127 } |
| 1128 if (jsArgs != null) { |
| 1129 var genericName = _maybeQualifiedName( |
| 1130 element, _propertyName('$name\$'), _qualifiedGenericIds); |
| 1131 return js.call('#(#)', [genericName, jsArgs]); |
1259 } | 1132 } |
1260 } | 1133 } |
1261 | 1134 |
1262 JS.Expression result; | 1135 return _maybeQualifiedName(element, _propertyName(name)); |
1263 if (_needQualifiedName(element)) { | 1136 } |
1264 result = js.call('#.#', [_libraryName(element.library), name]); | 1137 |
1265 } else { | 1138 JS.Expression _maybeQualifiedName(Element e, JS.Expression name, |
1266 result = new JS.Identifier(name); | 1139 [Map<Element, JS.MaybeQualifiedId> idTable]) { |
| 1140 var libName = _libraryName(e.library); |
| 1141 if (idTable == null) idTable = _qualifiedIds; |
| 1142 |
| 1143 // Mutable top-level fields should always be qualified. |
| 1144 bool mutableTopLevel = e is TopLevelVariableElement && !e.isConst; |
| 1145 if (e.library != currentLibrary || mutableTopLevel) { |
| 1146 return new JS.PropertyAccess(libName, name); |
1267 } | 1147 } |
1268 | 1148 |
1269 if (typeArgs != null) { | 1149 return idTable.putIfAbsent(e, () => new JS.MaybeQualifiedId(libName, name)); |
1270 result = js.call('#(#)', [result, typeArgs]); | |
1271 } | |
1272 return result; | |
1273 } | |
1274 | |
1275 bool _needQualifiedName(Element element) { | |
1276 var lib = element.library; | |
1277 if (lib == null) return false; | |
1278 if (lib != currentLibrary) return true; | |
1279 if (element is ClassElement) return _lazyClass(element.type); | |
1280 if (element is FunctionTypeAliasElement) return _lazyClass(element.type); | |
1281 return false; | |
1282 } | 1150 } |
1283 | 1151 |
1284 @override | 1152 @override |
1285 JS.Expression visitAssignmentExpression(AssignmentExpression node) { | 1153 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
1286 var left = node.leftHandSide; | 1154 var left = node.leftHandSide; |
1287 var right = node.rightHandSide; | 1155 var right = node.rightHandSide; |
1288 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); | 1156 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
1289 return _emitOpAssign( | 1157 return _emitOpAssign( |
1290 left, right, node.operator.lexeme[0], node.staticElement, | 1158 left, right, node.operator.lexeme[0], node.staticElement, |
1291 context: node); | 1159 context: node); |
(...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1481 | 1349 |
1482 @override | 1350 @override |
1483 JS.Statement visitReturnStatement(ReturnStatement node) { | 1351 JS.Statement visitReturnStatement(ReturnStatement node) { |
1484 var e = node.expression; | 1352 var e = node.expression; |
1485 if (e == null) return new JS.Return(); | 1353 if (e == null) return new JS.Return(); |
1486 return _visit(e).toReturn(); | 1354 return _visit(e).toReturn(); |
1487 } | 1355 } |
1488 | 1356 |
1489 @override | 1357 @override |
1490 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | 1358 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
1491 var body = <JS.Statement>[]; | 1359 for (var v in node.variables.variables) { |
1492 | 1360 _loader.loadDeclaration(v, v.element); |
1493 for (var field in node.variables.variables) { | |
1494 if (field.isConst) { | |
1495 // constant fields don't change, so we can generate them as `let` | |
1496 // but add them to the module's exports | |
1497 var name = field.name.name; | |
1498 body.add(js.statement( | |
1499 'let # = #;', [new JS.Identifier(name), _visitInitializer(field)])); | |
1500 if (isPublic(name)) _addExport(name); | |
1501 } else if (_isFieldInitConstant(field)) { | |
1502 body.add(js.statement( | |
1503 '# = #;', [_visit(field.name), _visitInitializer(field)])); | |
1504 } else { | |
1505 _lazyFields.add(field); | |
1506 } | |
1507 } | 1361 } |
1508 | |
1509 return _statement(body); | |
1510 } | 1362 } |
1511 | 1363 |
1512 _addExport(String name) { | 1364 _addExport(String name) { |
1513 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; | 1365 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; |
1514 } | 1366 } |
1515 | 1367 |
1516 @override | 1368 @override |
1517 JS.Statement visitVariableDeclarationStatement( | 1369 JS.Statement visitVariableDeclarationStatement( |
1518 VariableDeclarationStatement node) { | 1370 VariableDeclarationStatement node) { |
1519 // Special case a single variable with an initializer. | 1371 // Special case a single variable with an initializer. |
1520 // This helps emit cleaner code for things like: | 1372 // This helps emit cleaner code for things like: |
1521 // var result = []..add(1)..add(2); | 1373 // var result = []..add(1)..add(2); |
1522 if (node.variables.variables.length == 1) { | 1374 if (node.variables.variables.length == 1) { |
1523 var v = node.variables.variables.single; | 1375 var v = node.variables.variables.single; |
1524 if (v.initializer != null) { | 1376 if (v.initializer != null) { |
1525 var name = new JS.Identifier(v.name.name); | 1377 var name = new JS.Identifier(v.name.name); |
1526 return _visit(v.initializer).toVariableDeclaration(name); | 1378 return _visit(v.initializer).toVariableDeclaration(name); |
1527 } | 1379 } |
1528 } | 1380 } |
1529 return _visit(node.variables).toStatement(); | 1381 return _visit(node.variables).toStatement(); |
1530 } | 1382 } |
1531 | 1383 |
1532 @override | 1384 @override |
1533 visitVariableDeclarationList(VariableDeclarationList node) { | 1385 visitVariableDeclarationList(VariableDeclarationList node) { |
1534 return new JS.VariableDeclarationList('let', _visitList(node.variables)); | 1386 return new JS.VariableDeclarationList('let', _visitList(node.variables)); |
1535 } | 1387 } |
1536 | 1388 |
1537 @override | 1389 @override |
1538 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { | 1390 visitVariableDeclaration(VariableDeclaration node) { |
| 1391 if (node.element is PropertyInducingElement) return _emitStaticField(node); |
| 1392 |
1539 var name = new JS.Identifier(node.name.name); | 1393 var name = new JS.Identifier(node.name.name); |
1540 return new JS.VariableInitialization(name, _visitInitializer(node)); | 1394 return new JS.VariableInitialization(name, _visitInitializer(node)); |
1541 } | 1395 } |
1542 | 1396 |
| 1397 /// Emits a static or top-level field. |
| 1398 JS.Statement _emitStaticField(VariableDeclaration field) { |
| 1399 PropertyInducingElement element = field.element; |
| 1400 assert(element.isStatic); |
| 1401 |
| 1402 bool eagerInit; |
| 1403 JS.Expression jsInit; |
| 1404 if (field.isConst || _constField.isFieldInitConstant(field)) { |
| 1405 // If the field is constant, try and generate it at the top level. |
| 1406 _loader.startTopLevel(element); |
| 1407 jsInit = _visitInitializer(field); |
| 1408 _loader.finishTopLevel(element); |
| 1409 eagerInit = _loader.isLoaded(element); |
| 1410 } else { |
| 1411 jsInit = _visitInitializer(field); |
| 1412 eagerInit = false; |
| 1413 } |
| 1414 |
| 1415 var fieldName = field.name.name; |
| 1416 if (field.isConst && eagerInit && element is TopLevelVariableElement) { |
| 1417 // constant fields don't change, so we can generate them as `let` |
| 1418 // but add them to the module's exports. However, make sure we generate |
| 1419 // anything they depend on first. |
| 1420 |
| 1421 if (isPublic(fieldName)) _addExport(fieldName); |
| 1422 return js.statement('let # = #;', [new JS.Identifier(fieldName), jsInit]); |
| 1423 } |
| 1424 |
| 1425 if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
| 1426 return js.statement('# = #;', [_visit(field.name), jsInit]); |
| 1427 } |
| 1428 |
| 1429 var body = []; |
| 1430 if (_lazyFields.isNotEmpty) { |
| 1431 var existingTarget = _lazyFields[0].element.enclosingElement; |
| 1432 if (existingTarget != element.enclosingElement) { |
| 1433 _flushLazyFields(body); |
| 1434 } |
| 1435 } |
| 1436 |
| 1437 _lazyFields.add(field); |
| 1438 |
| 1439 return _statement(body); |
| 1440 } |
| 1441 |
1543 JS.Expression _visitInitializer(VariableDeclaration node) { | 1442 JS.Expression _visitInitializer(VariableDeclaration node) { |
1544 var value = _visit(node.initializer); | 1443 var value = _visit(node.initializer); |
1545 // explicitly initialize to null, to avoid getting `undefined`. | 1444 // explicitly initialize to null, to avoid getting `undefined`. |
1546 // TODO(jmesserly): do this only for vars that aren't definitely assigned. | 1445 // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
1547 return value != null ? value : new JS.LiteralNull(); | 1446 return value != null ? value : new JS.LiteralNull(); |
1548 } | 1447 } |
1549 | 1448 |
1550 void _flushPendingStatements(List<JS.Statement> body) { | |
1551 if (_pendingStatements.isNotEmpty) { | |
1552 body.addAll(_pendingStatements); | |
1553 _pendingStatements.clear(); | |
1554 } | |
1555 } | |
1556 | |
1557 void _flushLazyFields(List<JS.Statement> body) { | 1449 void _flushLazyFields(List<JS.Statement> body) { |
1558 var code = _emitLazyFields(_exportsVar, _lazyFields); | 1450 if (_lazyFields.isEmpty) return; |
1559 if (code != null) { | 1451 body.add(_emitLazyFields(_lazyFields)); |
1560 // Ensure symbols for private fields are defined. | |
1561 _flushPendingStatements(body); | |
1562 body.add(code); | |
1563 } | |
1564 _lazyFields.clear(); | 1452 _lazyFields.clear(); |
1565 } | 1453 } |
1566 | 1454 |
1567 JS.Statement _emitLazyFields( | 1455 JS.Statement _emitLazyFields(List<VariableDeclaration> fields) { |
1568 JS.Expression objExpr, List<VariableDeclaration> fields) { | |
1569 if (fields.isEmpty) return null; | |
1570 | |
1571 var methods = []; | 1456 var methods = []; |
1572 for (var node in fields) { | 1457 for (var node in fields) { |
1573 var name = node.name.name; | 1458 var name = node.name.name; |
1574 var element = node.element; | 1459 var element = node.element; |
1575 var access = _emitMemberName(name, type: element.type, isStatic: true); | 1460 var access = _emitMemberName(name, type: element.type, isStatic: true); |
1576 methods.add(new JS.Method( | 1461 methods.add(new JS.Method( |
1577 access, js.call('function() { return #; }', _visit(node.initializer)), | 1462 access, js.call('function() { return #; }', _visit(node.initializer)), |
1578 isGetter: true)); | 1463 isGetter: true)); |
1579 | 1464 |
1580 // TODO(jmesserly): use a dummy setter to indicate writable. | 1465 // TODO(jmesserly): use a dummy setter to indicate writable. |
1581 if (!node.isFinal) { | 1466 if (!node.isFinal) { |
1582 methods.add( | 1467 methods.add( |
1583 new JS.Method(access, js.call('function(_) {}'), isSetter: true)); | 1468 new JS.Method(access, js.call('function(_) {}'), isSetter: true)); |
1584 } | 1469 } |
1585 } | 1470 } |
1586 | 1471 |
| 1472 JS.Expression objExpr = _exportsVar; |
| 1473 var target = _lazyFields[0].element.enclosingElement; |
| 1474 if (target is ClassElement) { |
| 1475 objExpr = new JS.Identifier(target.type.name); |
| 1476 } |
| 1477 |
1587 return js.statement( | 1478 return js.statement( |
1588 'dart.defineLazyProperties(#, { # });', [objExpr, methods]); | 1479 'dart.defineLazyProperties(#, { # });', [objExpr, methods]); |
1589 } | 1480 } |
1590 | 1481 |
1591 void _flushLibraryProperties(List<JS.Statement> body) { | 1482 void _flushLibraryProperties(List<JS.Statement> body) { |
1592 if (_properties.isEmpty) return; | 1483 if (_properties.isEmpty) return; |
1593 body.add(js.statement('dart.copyProperties(#, { # });', [ | 1484 body.add(js.statement('dart.copyProperties(#, { # });', [ |
1594 _exportsVar, | 1485 _exportsVar, |
1595 _properties.map(_emitTopLevelProperty) | 1486 _properties.map(_emitTopLevelProperty) |
1596 ])); | 1487 ])); |
1597 _properties.clear(); | 1488 _properties.clear(); |
1598 } | 1489 } |
1599 | 1490 |
1600 @override | 1491 @override |
1601 visitConstructorName(ConstructorName node) { | 1492 visitConstructorName(ConstructorName node) { |
1602 var typeName = _visit(node.type); | 1493 var typeName = _visit(node.type); |
1603 if (node.name != null) { | 1494 if (node.name != null) { |
1604 return js.call( | 1495 return js.call( |
1605 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); | 1496 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); |
1606 } | 1497 } |
1607 return typeName; | 1498 return typeName; |
1608 } | 1499 } |
1609 | 1500 |
1610 @override | 1501 @override |
1611 visitInstanceCreationExpression(InstanceCreationExpression node) { | 1502 visitInstanceCreationExpression(InstanceCreationExpression node) { |
1612 var newExpr = js.call( | 1503 emitNew() => js.call( |
1613 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); | 1504 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); |
1614 if (node.isConst) return _const(node, newExpr); | 1505 if (node.isConst) return _emitConst(node, emitNew); |
1615 return newExpr; | 1506 return emitNew(); |
1616 } | 1507 } |
1617 | 1508 |
1618 /// True if this type is built-in to JS, and we use the values unwrapped. | 1509 /// True if this type is built-in to JS, and we use the values unwrapped. |
1619 /// For these types we generate a calling convention via static | 1510 /// For these types we generate a calling convention via static |
1620 /// "extension methods". This allows types to be extended without adding | 1511 /// "extension methods". This allows types to be extended without adding |
1621 /// extensions directly on the prototype. | 1512 /// extensions directly on the prototype. |
1622 bool _isJSBuiltinType(DartType t) => | 1513 bool _isJSBuiltinType(DartType t) => |
1623 typeIsPrimitiveInJS(t) || rules.isStringType(t); | 1514 typeIsPrimitiveInJS(t) || rules.isStringType(t); |
1624 | 1515 |
1625 bool typeIsPrimitiveInJS(DartType t) => rules.isIntType(t) || | 1516 bool typeIsPrimitiveInJS(DartType t) => rules.isIntType(t) || |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1773 // LocalVariableElementImpl, so we could repurpose to mean "temp". | 1664 // LocalVariableElementImpl, so we could repurpose to mean "temp". |
1774 // * add a new property to LocalVariableElementImpl. | 1665 // * add a new property to LocalVariableElementImpl. |
1775 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 1666 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
1776 var id = | 1667 var id = |
1777 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); | 1668 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
1778 id.staticElement = new TemporaryVariableElement.forNode(id); | 1669 id.staticElement = new TemporaryVariableElement.forNode(id); |
1779 id.staticType = type; | 1670 id.staticType = type; |
1780 return id; | 1671 return id; |
1781 } | 1672 } |
1782 | 1673 |
1783 JS.Expression _const(Expression node, JS.Expression expr, [String nameHint]) { | 1674 JS.Expression _emitConst(Expression node, JS.Expression expr()) { |
1784 var value = js.call('dart.const(#)', expr); | 1675 // TODO(jmesserly): emit the constants at top level if possible. |
1785 | 1676 // This wasn't quite working, so disabled for now. |
1786 // If we're inside a method or function, capture the value into a | 1677 return js.call('dart.const(#)', expr()); |
1787 // global temporary, so we don't do the expensive canonicalization step. | |
1788 var ancestor = node.getAncestor((n) => n is FunctionBody || | |
1789 (n is FieldDeclaration && n.staticKeyword == null)); | |
1790 if (ancestor == null) return value; | |
1791 | |
1792 if (nameHint == null) { | |
1793 nameHint = 'const_' + getStaticType(node).name; | |
1794 } | |
1795 | |
1796 // TODO(jmesserly): enable this once we fix | |
1797 // https://github.com/dart-lang/dev_compiler/issues/131 | |
1798 /*var temp = new JSTemporary(nameHint); | |
1799 _pendingStatements.add(js.statement('let # = #;', [temp, value])); | |
1800 return temp;*/ | |
1801 assert(nameHint != null); // so it's not marked unused | |
1802 return value; | |
1803 } | 1678 } |
1804 | 1679 |
1805 /// Returns a new expression, which can be be used safely *once* on the | 1680 /// Returns a new expression, which can be be used safely *once* on the |
1806 /// left hand side, and *once* on the right side of an assignment. | 1681 /// left hand side, and *once* on the right side of an assignment. |
1807 /// For example: `expr1[expr2] += y` can be compiled as | 1682 /// For example: `expr1[expr2] += y` can be compiled as |
1808 /// `expr1[expr2] = expr1[expr2] + y`. | 1683 /// `expr1[expr2] = expr1[expr2] + y`. |
1809 /// | 1684 /// |
1810 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated | 1685 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated |
1811 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. | 1686 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. |
1812 /// | 1687 /// |
(...skipping 457 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2270 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); | 2145 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); |
2271 | 2146 |
2272 @override | 2147 @override |
2273 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); | 2148 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); |
2274 | 2149 |
2275 @override | 2150 @override |
2276 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); | 2151 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); |
2277 | 2152 |
2278 @override | 2153 @override |
2279 visitSymbolLiteral(SymbolLiteral node) { | 2154 visitSymbolLiteral(SymbolLiteral node) { |
2280 // TODO(vsm): When we canonicalize, we need to treat private symbols | 2155 emitSymbol() { |
2281 // correctly. | 2156 // TODO(vsm): When we canonicalize, we need to treat private symbols |
2282 var name = js.string(node.components.join('.'), "'"); | 2157 // correctly. |
2283 var nameHint = 'symbol_' + node.components.join('_'); | 2158 var name = js.string(node.components.join('.'), "'"); |
2284 return _const( | 2159 return new JS.New(_emitTypeName(types.symbolType), [name]); |
2285 node, new JS.New(_emitTypeName(types.symbolType), [name]), nameHint); | 2160 } |
| 2161 return _emitConst(node, emitSymbol); |
2286 } | 2162 } |
2287 | 2163 |
2288 @override | 2164 @override |
2289 visitListLiteral(ListLiteral node) { | 2165 visitListLiteral(ListLiteral node) { |
2290 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); | 2166 emitList() { |
2291 | 2167 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); |
2292 ParameterizedType type = node.staticType; | 2168 ParameterizedType type = node.staticType; |
2293 if (type.typeArguments.any((a) => a != types.dynamicType)) { | 2169 if (type.typeArguments.any((a) => a != types.dynamicType)) { |
2294 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); | 2170 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); |
| 2171 } |
| 2172 return list; |
2295 } | 2173 } |
2296 if (node.constKeyword != null) return _const(node, list); | 2174 if (node.constKeyword != null) return _emitConst(node, emitList); |
2297 return list; | 2175 return emitList(); |
2298 } | 2176 } |
2299 | 2177 |
2300 @override | 2178 @override |
2301 visitMapLiteral(MapLiteral node) { | 2179 visitMapLiteral(MapLiteral node) { |
2302 // TODO(jmesserly): we can likely make these faster. | 2180 // TODO(jmesserly): we can likely make these faster. |
2303 var entries = node.entries; | 2181 emitMap() { |
2304 var mapArguments = null; | 2182 var entries = node.entries; |
2305 if (entries.isEmpty) { | 2183 var mapArguments = null; |
2306 mapArguments = []; | 2184 if (entries.isEmpty) { |
2307 } else if (entries.every((e) => e.key is StringLiteral)) { | 2185 mapArguments = []; |
2308 // Use JS object literal notation if possible, otherwise use an array. | 2186 } else if (entries.every((e) => e.key is StringLiteral)) { |
2309 // We could do this any time all keys are non-nullable String type. | 2187 // Use JS object literal notation if possible, otherwise use an array. |
2310 // For now, support StringLiteral as the common non-nullable String case. | 2188 // We could do this any time all keys are non-nullable String type. |
2311 var props = []; | 2189 // For now, support StringLiteral as the common non-nullable String case
. |
2312 for (var e in entries) { | 2190 var props = []; |
2313 props.add(new JS.Property(_visit(e.key), _visit(e.value))); | 2191 for (var e in entries) { |
| 2192 props.add(new JS.Property(_visit(e.key), _visit(e.value))); |
| 2193 } |
| 2194 mapArguments = new JS.ObjectInitializer(props); |
| 2195 } else { |
| 2196 var values = []; |
| 2197 for (var e in entries) { |
| 2198 values.add(_visit(e.key)); |
| 2199 values.add(_visit(e.value)); |
| 2200 } |
| 2201 mapArguments = new JS.ArrayInitializer(values); |
2314 } | 2202 } |
2315 mapArguments = new JS.ObjectInitializer(props); | 2203 // TODO(jmesserly): add generic types args. |
2316 } else { | 2204 return js.call('dart.map(#)', [mapArguments]); |
2317 var values = []; | |
2318 for (var e in entries) { | |
2319 values.add(_visit(e.key)); | |
2320 values.add(_visit(e.value)); | |
2321 } | |
2322 mapArguments = new JS.ArrayInitializer(values); | |
2323 } | 2205 } |
2324 // TODO(jmesserly): add generic types args. | 2206 if (node.constKeyword != null) return _emitConst(node, emitMap); |
2325 var map = js.call('dart.map(#)', [mapArguments]); | 2207 return emitMap(); |
2326 if (node.constKeyword != null) return _const(node, map); | |
2327 return map; | |
2328 } | 2208 } |
2329 | 2209 |
2330 @override | 2210 @override |
2331 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => | 2211 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
2332 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); | 2212 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
2333 | 2213 |
2334 @override | 2214 @override |
2335 JS.Expression visitAdjacentStrings(AdjacentStrings node) => | 2215 JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
2336 _visitListToBinary(node.strings, '+'); | 2216 _visitListToBinary(node.strings, '+'); |
2337 | 2217 |
(...skipping 30 matching lines...) Expand all Loading... |
2368 JS.Expression _unimplementedCall(String comment) { | 2248 JS.Expression _unimplementedCall(String comment) { |
2369 return js.call('dart.throw_(#)', [js.escapedString(comment)]); | 2249 return js.call('dart.throw_(#)', [js.escapedString(comment)]); |
2370 } | 2250 } |
2371 | 2251 |
2372 @override | 2252 @override |
2373 visitNode(AstNode node) { | 2253 visitNode(AstNode node) { |
2374 // TODO(jmesserly): verify this is unreachable. | 2254 // TODO(jmesserly): verify this is unreachable. |
2375 throw 'Unimplemented ${node.runtimeType}: $node'; | 2255 throw 'Unimplemented ${node.runtimeType}: $node'; |
2376 } | 2256 } |
2377 | 2257 |
2378 // TODO(jmesserly): this is used to determine if the field initialization is | |
2379 // side effect free. We should make the check more general, as things like | |
2380 // list/map literals/regexp are also side effect free and fairly common | |
2381 // to use as field initializers. | |
2382 bool _isFieldInitConstant(VariableDeclaration field) => | |
2383 field.initializer == null || _computeConstant(field).isValid; | |
2384 | |
2385 EvaluationResult _computeConstant(VariableDeclaration field) { | |
2386 // If the constant is already computed by ConstantEvaluator, just return it. | |
2387 VariableElementImpl element = field.element; | |
2388 var result = element.evaluationResult; | |
2389 if (result != null) return result; | |
2390 | |
2391 // ConstantEvaluator will not compute constants for non-const fields | |
2392 // at least for cases like `int x = 0;`, so run ConstantVisitor for those. | |
2393 // TODO(jmesserly): ideally we'd only do this if we're sure it was skipped | |
2394 // by ConstantEvaluator. | |
2395 var initializer = field.initializer; | |
2396 if (initializer == null) return null; | |
2397 | |
2398 return _constEvaluator.evaluate(initializer); | |
2399 } | |
2400 | |
2401 _visit(AstNode node) { | 2258 _visit(AstNode node) { |
2402 if (node == null) return null; | 2259 if (node == null) return null; |
2403 var result = node.accept(this); | 2260 var result = node.accept(this); |
2404 if (result is JS.Node) result.sourceInformation = node; | 2261 if (result is JS.Node) result.sourceInformation = node; |
2405 return result; | 2262 return result; |
2406 } | 2263 } |
2407 | 2264 |
2408 List _visitList(Iterable<AstNode> nodes) { | 2265 List _visitList(Iterable<AstNode> nodes) { |
2409 if (nodes == null) return null; | 2266 if (nodes == null) return null; |
2410 var result = []; | 2267 var result = []; |
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2620 | 2477 |
2621 /// A special kind of element created by the compiler, signifying a temporary | 2478 /// A special kind of element created by the compiler, signifying a temporary |
2622 /// variable. These objects use instance equality, and should be shared | 2479 /// variable. These objects use instance equality, and should be shared |
2623 /// everywhere in the tree where they are treated as the same variable. | 2480 /// everywhere in the tree where they are treated as the same variable. |
2624 class TemporaryVariableElement extends LocalVariableElementImpl { | 2481 class TemporaryVariableElement extends LocalVariableElementImpl { |
2625 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); | 2482 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); |
2626 | 2483 |
2627 int get hashCode => identityHashCode(this); | 2484 int get hashCode => identityHashCode(this); |
2628 bool operator ==(Object other) => identical(this, other); | 2485 bool operator ==(Object other) => identical(this, other); |
2629 } | 2486 } |
OLD | NEW |