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/engine.dart' show RecordingErrorListener; | |
13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | 14 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
14 import 'package:analyzer/src/generated/scanner.dart' | 15 import 'package:analyzer/src/generated/scanner.dart' |
15 show StringToken, Token, TokenType; | 16 show StringToken, Token, TokenType; |
16 import 'package:path/path.dart' as path; | 17 import 'package:path/path.dart' as path; |
17 | 18 |
18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; | 19 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; |
19 import 'package:dev_compiler/src/codegen/reify_coercions.dart' | 20 import 'package:dev_compiler/src/codegen/reify_coercions.dart' |
20 show CoercionReifier; | 21 show CoercionReifier; |
21 | 22 |
22 // TODO(jmesserly): import from its own package | 23 // TODO(jmesserly): import from its own package |
23 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 24 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
24 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 25 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
25 | 26 |
26 import 'package:dev_compiler/src/checker/rules.dart'; | 27 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'; | 28 import 'package:dev_compiler/src/info.dart'; |
29 import 'package:dev_compiler/src/options.dart'; | 29 import 'package:dev_compiler/src/options.dart'; |
30 import 'package:dev_compiler/src/utils.dart'; | 30 import 'package:dev_compiler/src/utils.dart'; |
31 | 31 |
32 import 'code_generator.dart'; | 32 import 'code_generator.dart'; |
33 import 'js_field_storage.dart'; | 33 import 'js_field_storage.dart'; |
34 import 'js_names.dart' as JS; | 34 import 'js_names.dart' as JS; |
35 import 'js_metalet.dart' as JS; | 35 import 'js_metalet.dart' as JS; |
36 import 'js_module_item_order.dart'; | |
36 import 'js_printer.dart' show writeJsLibrary; | 37 import 'js_printer.dart' show writeJsLibrary; |
37 import 'side_effect_analysis.dart'; | 38 import 'side_effect_analysis.dart'; |
38 | 39 |
39 // Various dynamic helpers we call. | 40 // Various dynamic helpers we call. |
40 // If renaming these, make sure to check other places like the | 41 // If renaming these, make sure to check other places like the |
41 // dart_runtime.js file and comments. | 42 // dart_runtime.js file and comments. |
42 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 43 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
43 // import and generate calls to, rather than dart_runtime.js | 44 // import and generate calls to, rather than dart_runtime.js |
44 const DPUT = 'dput'; | 45 const DPUT = 'dput'; |
45 const DLOAD = 'dload'; | 46 const DLOAD = 'dload'; |
46 const DINDEX = 'dindex'; | 47 const DINDEX = 'dindex'; |
47 const DSETINDEX = 'dsetindex'; | 48 const DSETINDEX = 'dsetindex'; |
48 const DCALL = 'dcall'; | 49 const DCALL = 'dcall'; |
49 const DSEND = 'dsend'; | 50 const DSEND = 'dsend'; |
50 | 51 |
52 class ConstFieldVisitor { | |
Jennifer Messerly
2015/05/12 16:23:45
I forgot to move this to side_effect_analysis.dart
vsm
2015/05/12 18:00:11
+1 to moving out. Not really js_cg specific.
Jennifer Messerly
2015/05/12 18:47:04
Done.
| |
53 final ConstantVisitor _constantVisitor; | |
54 | |
55 ConstFieldVisitor(TypeProvider types, CompilationUnit unit) | |
56 : _constantVisitor = new ConstantVisitor.con1(types, | |
57 new ErrorReporter(new RecordingErrorListener(), unit.element.source)); | |
58 | |
59 // TODO(jmesserly): this is used to determine if the field initialization is | |
60 // side effect free. We should make the check more general, as things like | |
61 // list/map literals/regexp are also side effect free and fairly common | |
62 // to use as field initializers. | |
63 bool isFieldInitConstant(VariableDeclaration field) => | |
64 field.initializer == null || computeConstant(field) != null; | |
65 | |
66 DartObjectImpl computeConstant(VariableDeclaration field) { | |
67 // If the constant is already computed by ConstantEvaluator, just return it. | |
68 VariableElementImpl element = field.element; | |
69 var result = element.evaluationResult; | |
70 if (result != null) return result.value; | |
71 | |
72 // ConstantEvaluator will not compute constants for non-const fields | |
73 // at least for cases like `int x = 0;`, so run ConstantVisitor for those. | |
74 // TODO(jmesserly): ideally we'd only do this if we're sure it was skipped | |
75 // by ConstantEvaluator. | |
vsm
2015/05/12 18:00:11
Is that equivalent to only doing this if !field.is
Jennifer Messerly
2015/05/12 18:47:04
Ah, yes. I think so. I'll add an assert in place o
| |
76 var initializer = field.initializer; | |
77 if (initializer == null) return null; | |
78 return initializer.accept(_constantVisitor); | |
79 } | |
80 } | |
81 | |
51 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 82 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
52 final CompilerOptions options; | 83 final CompilerOptions options; |
53 final TypeRules rules; | 84 final TypeRules rules; |
54 final LibraryInfo libraryInfo; | 85 final LibraryInfo libraryInfo; |
55 | 86 |
56 /// The global extension method table. | 87 /// The global extension method table. |
57 final HashMap<String, List<InterfaceType>> _extensionMethods; | 88 final HashMap<String, List<InterfaceType>> _extensionMethods; |
58 | 89 |
59 /// Information that is precomputed for this library, indicates which fields | 90 /// Information that is precomputed for this library, indicates which fields |
60 /// need storage slots. | 91 /// need storage slots. |
61 final HashSet<FieldElement> _fieldsNeedingStorage; | 92 final HashSet<FieldElement> _fieldsNeedingStorage; |
62 | 93 |
63 /// The variable for the target of the current `..` cascade expression. | 94 /// The variable for the target of the current `..` cascade expression. |
64 SimpleIdentifier _cascadeTarget; | 95 SimpleIdentifier _cascadeTarget; |
65 | 96 |
66 /// The variable for the current catch clause | 97 /// The variable for the current catch clause |
67 SimpleIdentifier _catchParameter; | 98 SimpleIdentifier _catchParameter; |
68 | 99 |
69 ConstantEvaluator _constEvaluator; | |
70 | |
71 ClassElement _currentClassElement = null; | |
72 | |
73 /// Imported libraries, and the temporaries used to refer to them. | 100 /// Imported libraries, and the temporaries used to refer to them. |
74 final _imports = new Map<LibraryElement, JS.TemporaryId>(); | 101 final _imports = new Map<LibraryElement, JS.TemporaryId>(); |
75 final _exports = new Set<String>(); | 102 final _exports = new Set<String>(); |
76 final _lazyFields = <VariableDeclaration>[]; | 103 final _lazyFields = <VariableDeclaration>[]; |
77 final _properties = <FunctionDeclaration>[]; | 104 final _properties = <FunctionDeclaration>[]; |
78 final _privateNames = new HashMap<String, JS.TemporaryId>(); | 105 final _privateNames = new HashMap<String, JS.TemporaryId>(); |
79 final _extensionMethodNames = new HashSet<String>(); | 106 final _extensionMethodNames = new HashSet<String>(); |
80 final _pendingStatements = <JS.Statement>[]; | 107 final _moduleItems = <JS.Statement>[]; |
81 final _temps = new HashMap<Element, JS.TemporaryId>(); | 108 final _temps = new HashMap<Element, JS.TemporaryId>.identity(); |
109 final _qualifiedIds = new HashMap<Element, JS.MaybeQualifiedId>(); | |
110 final _qualifiedGenericIds = new HashMap<Element, JS.MaybeQualifiedId>(); | |
82 | 111 |
83 /// The name for the library's exports inside itself. | 112 /// The name for the library's exports inside itself. |
84 /// `exports` was chosen as the most similar to ES module patterns. | 113 /// `exports` was chosen as the most similar to ES module patterns. |
85 final _exportsVar = new JS.TemporaryId('exports'); | 114 final _exportsVar = new JS.TemporaryId('exports'); |
86 final _namedArgTemp = new JS.TemporaryId('opts'); | 115 final _namedArgTemp = new JS.TemporaryId('opts'); |
87 | 116 |
88 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 117 ConstFieldVisitor _constField; |
89 /// [ClassTypeAlias]. | |
90 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | |
91 | 118 |
92 /// Memoized results of [_lazyClass]. | 119 ModuleItemLoadOrder _loader; |
93 final _lazyClassMemo = new HashMap<Element, bool>(); | |
94 | |
95 /// Memoized results of [_inLibraryCycle]. | |
96 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | |
97 | 120 |
98 JSCodegenVisitor(this.options, this.rules, this.libraryInfo, | 121 JSCodegenVisitor(this.options, this.rules, this.libraryInfo, |
99 this._extensionMethods, this._fieldsNeedingStorage); | 122 this._extensionMethods, this._fieldsNeedingStorage) { |
123 _loader = new ModuleItemLoadOrder(_emitModuleItem); | |
Jennifer Messerly
2015/05/12 16:23:45
alternate design: this could be a mixin which decl
| |
124 } | |
100 | 125 |
101 LibraryElement get currentLibrary => libraryInfo.library; | 126 LibraryElement get currentLibrary => libraryInfo.library; |
102 TypeProvider get types => rules.provider; | 127 TypeProvider get types => rules.provider; |
103 | 128 |
104 JS.Program emitLibrary(LibraryUnit library) { | 129 JS.Program emitLibrary(LibraryUnit library) { |
105 String jsDefaultValue = null; | 130 String jsDefaultValue = null; |
106 | 131 |
107 // Modify the AST to make coercions explicit. | 132 // Modify the AST to make coercions explicit. |
108 new CoercionReifier(library, rules, options).reify(); | 133 new CoercionReifier(library, rules, options).reify(); |
109 | 134 |
110 var unit = library.library; | 135 var unit = library.library; |
111 if (unit.directives.isNotEmpty) { | 136 if (unit.directives.isNotEmpty) { |
112 var libraryDir = unit.directives.first; | 137 var libraryDir = unit.directives.first; |
113 if (libraryDir is LibraryDirective) { | 138 if (libraryDir is LibraryDirective) { |
114 var jsName = getAnnotationValue(libraryDir, _isJsNameAnnotation); | 139 var jsName = getAnnotationValue(libraryDir, _isJsNameAnnotation); |
115 jsDefaultValue = getConstantField(jsName, 'name', types.stringType); | 140 jsDefaultValue = getConstantField(jsName, 'name', types.stringType); |
116 } | 141 } |
117 } | 142 } |
118 if (jsDefaultValue == null) jsDefaultValue = '{}'; | 143 if (jsDefaultValue == null) jsDefaultValue = '{}'; |
119 | 144 |
120 var body = <JS.Statement>[]; | 145 // TODO(jmesserly): visit scriptTag, directives? |
Jennifer Messerly
2015/05/12 16:23:45
this TODO just moved from somewhere else
| |
121 | 146 |
122 // Collect classes we need to emit, used for: | 147 _loader.collectElements(currentLibrary, library.partsThenLibrary); |
123 // * tracks what we've emitted so we don't emit twice | 148 |
124 // * provides a mapping from ClassElement back to the ClassDeclaration. | |
125 for (var unit in library.partsThenLibrary) { | 149 for (var unit in library.partsThenLibrary) { |
150 _constField = new ConstFieldVisitor(types, unit); | |
151 | |
126 for (var decl in unit.declarations) { | 152 for (var decl in unit.declarations) { |
127 if (decl is ClassDeclaration || | 153 if (decl is TopLevelVariableDeclaration) { |
128 decl is ClassTypeAlias || | 154 _visit(decl); |
129 decl is FunctionTypeAlias) { | 155 } else { |
130 _pendingClasses[decl.element] = decl; | 156 _loader.loadDeclaration(decl, decl.element); |
157 } | |
158 if (decl is ClassDeclaration) { | |
159 // Static fields can be emitted into the top-level code, so they need | |
160 // to potentially be ordered independently of the class. | |
161 for (var member in decl.members) { | |
162 if (member is FieldDeclaration && member.isStatic) { | |
163 for (var f in member.fields.variables) { | |
164 _loader.loadDeclaration(f, f.element); | |
165 } | |
166 } | |
167 } | |
131 } | 168 } |
132 } | 169 } |
133 } | 170 } |
134 | 171 |
135 for (var unit in library.partsThenLibrary) body.add(_visit(unit)); | 172 // Flush any unwritten fields/properties. |
173 _flushLazyFields(_moduleItems); | |
174 _flushLibraryProperties(_moduleItems); | |
136 | 175 |
137 assert(_pendingClasses.isEmpty); | 176 // Mark all qualified names as qualified or not, depending on if they need |
177 // to be loaded lazily or not. | |
178 unqualifyIfNeeded(Element e, JS.MaybeQualifiedId id) { | |
179 id.setQualified(!_loader.isLoaded(e)); | |
180 } | |
181 _qualifiedIds.forEach(unqualifyIfNeeded); | |
182 _qualifiedGenericIds.forEach(unqualifyIfNeeded); | |
138 | 183 |
139 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); | 184 if (_exports.isNotEmpty) _moduleItems.add(js.comment('Exports:')); |
140 | 185 |
141 // TODO(jmesserly): make these immutable in JS? | 186 // TODO(jmesserly): make these immutable in JS? |
142 for (var name in _exports) { | 187 for (var name in _exports) { |
143 body.add(js.statement('#.# = #;', [_exportsVar, name, name])); | 188 _moduleItems.add(js.statement('#.# = #;', [_exportsVar, name, name])); |
144 } | 189 } |
145 | 190 |
146 var name = new JS.Identifier(jsLibraryName(currentLibrary)); | 191 var name = new JS.Identifier(jsLibraryName(currentLibrary)); |
147 | 192 |
148 // TODO(jmesserly): it would be great to run the renamer on the body, | 193 // 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. | 194 // then figure out if we really need each of these parameters. |
150 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 | 195 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 |
151 var program = [ | 196 var program = [ |
152 js.statement('var # = dart.defineLibrary(#, #);', [ | 197 js.statement('var # = dart.defineLibrary(#, #);', [ |
153 name, | 198 name, |
154 name, | 199 name, |
155 js.call(jsDefaultValue) | 200 js.call(jsDefaultValue) |
156 ]) | 201 ]) |
157 ]; | 202 ]; |
158 | 203 |
159 var params = [_exportsVar]; | 204 var params = [_exportsVar]; |
160 var args = [name]; | 205 var args = [name]; |
161 _imports.forEach((library, temp) { | 206 _imports.forEach((library, temp) { |
162 var name = new JS.Identifier(temp.name); | 207 var name = new JS.Identifier(temp.name); |
163 params.add(temp); | 208 params.add(temp); |
164 args.add(name); | 209 args.add(name); |
165 var helper = _libraryMightNotBeLoaded(library) ? 'lazyImport' : 'import'; | 210 var helper = _loader.libraryIsLoaded(library) ? 'import' : 'lazyImport'; |
166 program.add(js.statement('var # = dart.#(#);', [name, helper, name])); | 211 program.add(js.statement('var # = dart.#(#);', [name, helper, name])); |
167 }); | 212 }); |
168 | 213 |
169 program.add(js.statement( | 214 program.add(js.statement("(function(#) { 'use strict'; #; })(#);", [ |
170 "(function(#) { 'use strict'; #; })(#);", [params, body, args])); | 215 params, |
216 _moduleItems, | |
217 args | |
218 ])); | |
171 | 219 |
172 return new JS.Program(program); | 220 return new JS.Program(program); |
173 } | 221 } |
174 | 222 |
223 void _emitModuleItem(AstNode node) { | |
224 // Attempt to group adjacent fields/properties. | |
225 if (node is! VariableDeclaration) _flushLazyFields(_moduleItems); | |
226 if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems); | |
227 | |
228 var code = _visit(node); | |
229 if (code != null) _moduleItems.add(code); | |
230 } | |
231 | |
175 JS.Identifier _initSymbol(JS.Identifier id) { | 232 JS.Identifier _initSymbol(JS.Identifier id) { |
176 var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); | 233 var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
177 _pendingStatements.add(s); | 234 _moduleItems.add(s); |
178 return id; | 235 return id; |
179 } | 236 } |
180 | 237 |
181 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 238 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
182 // until we have better name tracking. | 239 // until we have better name tracking. |
183 String get _SYMBOL { | 240 String get _SYMBOL { |
184 var name = currentLibrary.name; | 241 var name = currentLibrary.name; |
185 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 242 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
186 return 'Symbol'; | 243 return 'Symbol'; |
187 } | 244 } |
188 | 245 |
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('_'); | 246 bool isPublic(String name) => !name.startsWith('_'); |
218 | 247 |
219 /// Conversions that we don't handle end up here. | 248 /// Conversions that we don't handle end up here. |
220 @override | 249 @override |
221 visitConversion(Conversion node) { | 250 visitConversion(Conversion node) { |
222 throw 'Unlowered conversion ${node.runtimeType}: $node'; | 251 throw 'Unlowered conversion ${node.runtimeType}: $node'; |
223 } | 252 } |
224 | 253 |
225 @override | 254 @override |
226 visitAsExpression(AsExpression node) { | 255 visitAsExpression(AsExpression node) { |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
273 String _jsTypeofName(DartType t) { | 302 String _jsTypeofName(DartType t) { |
274 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; | 303 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; |
275 if (rules.isStringType(t)) return 'string'; | 304 if (rules.isStringType(t)) return 'string'; |
276 if (rules.isBoolType(t)) return 'boolean'; | 305 if (rules.isBoolType(t)) return 'boolean'; |
277 return null; | 306 return null; |
278 } | 307 } |
279 | 308 |
280 @override | 309 @override |
281 visitFunctionTypeAlias(FunctionTypeAlias node) { | 310 visitFunctionTypeAlias(FunctionTypeAlias node) { |
282 // If we've already emitted this class, skip it. | 311 // If we've already emitted this class, skip it. |
283 var type = node.element.type; | 312 var element = node.element; |
284 if (_pendingClasses.remove(node.element) == null) return null; | 313 var type = element.type; |
314 var name = element.name; | |
285 | 315 |
286 var name = type.name; | 316 _loader.startTopLevel(element); |
287 var result = js.statement('let # = dart.typedef(#, () => #);', [ | 317 var fnType = js.statement('let # = dart.typedef(#, #);', [ |
288 new JS.Identifier(name), | 318 name, |
289 js.string(name, "'"), | 319 js.string(name, "'"), |
290 _emitTypeName(node.element.type, lowerTypedef: true) | 320 _emitTypeName(type, lowerTypedef: true) |
291 ]); | 321 ]); |
322 _loader.finishTopLevel(element); | |
292 | 323 |
293 return _finishClassDef(type, result); | 324 return _finishClassDef(type, fnType); |
294 } | 325 } |
295 | 326 |
296 @override | 327 @override |
297 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); | 328 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); |
298 | 329 |
299 @override | 330 @override |
300 JS.Statement visitClassTypeAlias(ClassTypeAlias node) { | 331 JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
301 // If we've already emitted this class, skip it. | 332 // If we've already emitted this class, skip it. |
302 var type = node.element.type; | 333 var element = node.element; |
303 if (_pendingClasses.remove(node.element) == null) return null; | |
304 | 334 |
305 var name = node.name.name; | |
306 var classDecl = new JS.ClassDeclaration(new JS.ClassExpression( | 335 var classDecl = new JS.ClassDeclaration(new JS.ClassExpression( |
307 new JS.Identifier(name), _classHeritage(node), [])); | 336 new JS.Identifier(element.name), _classHeritage(element), [])); |
308 | 337 |
309 return _finishClassDef(type, classDecl); | 338 return _finishClassDef(element.type, classDecl); |
310 } | 339 } |
311 | 340 |
312 JS.Statement _emitJsType(String dartClassName, DartObjectImpl jsName) { | 341 JS.Statement _emitJsType(String dartClassName, DartObjectImpl jsName) { |
313 var jsTypeName = getConstantField(jsName, 'name', types.stringType); | 342 var jsTypeName = getConstantField(jsName, 'name', types.stringType); |
314 | 343 |
315 if (jsTypeName != null && jsTypeName != dartClassName) { | 344 if (jsTypeName != null && jsTypeName != dartClassName) { |
316 // We export the JS type as if it was a Dart type. For example this allows | 345 // We export the JS type as if it was a Dart type. For example this allows |
317 // `dom.InputElement` to actually be HTMLInputElement. | 346 // `dom.InputElement` to actually be HTMLInputElement. |
318 // TODO(jmesserly): if we had the JsName on the Element, we could just | 347 // TODO(jmesserly): if we had the JsName on the Element, we could just |
319 // generate it correctly when we refer to it. | 348 // generate it correctly when we refer to it. |
320 if (isPublic(dartClassName)) _addExport(dartClassName); | 349 if (isPublic(dartClassName)) _addExport(dartClassName); |
321 return js.statement('let # = #;', [dartClassName, jsTypeName]); | 350 return js.statement('let # = #;', [dartClassName, jsTypeName]); |
322 } | 351 } |
323 return null; | 352 return null; |
324 } | 353 } |
325 | 354 |
326 @override | 355 @override |
327 JS.Statement visitClassDeclaration(ClassDeclaration node) { | 356 JS.Statement visitClassDeclaration(ClassDeclaration node) { |
328 // If we've already emitted this class, skip it. | 357 // If we've already emitted this class, skip it. |
329 var classElem = node.element; | 358 var classElem = node.element; |
330 var type = classElem.type; | 359 var type = classElem.type; |
331 if (_pendingClasses.remove(classElem) == null) return null; | |
332 | |
333 var jsName = getAnnotationValue(node, _isJsNameAnnotation); | 360 var jsName = getAnnotationValue(node, _isJsNameAnnotation); |
334 | 361 |
335 if (jsName != null) return _emitJsType(node.name.name, jsName); | 362 if (jsName != null) return _emitJsType(node.name.name, jsName); |
336 | 363 |
337 // Set current class | |
338 assert(_currentClassElement == null); | |
339 _currentClassElement = classElem; | |
340 | |
341 var ctors = <ConstructorDeclaration>[]; | 364 var ctors = <ConstructorDeclaration>[]; |
342 var fields = <FieldDeclaration>[]; | 365 var fields = <FieldDeclaration>[]; |
343 var staticFields = <FieldDeclaration>[]; | |
344 for (var member in node.members) { | 366 for (var member in node.members) { |
345 if (member is ConstructorDeclaration) { | 367 if (member is ConstructorDeclaration) { |
346 ctors.add(member); | 368 ctors.add(member); |
347 } else if (member is FieldDeclaration) { | 369 } else if (member is FieldDeclaration && !member.isStatic) { |
348 (member.isStatic ? staticFields : fields).add(member); | 370 fields.add(member); |
349 } | 371 } |
350 } | 372 } |
351 | 373 |
352 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), | 374 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
353 _classHeritage(node), _emitClassMethods(node, ctors, fields)); | 375 _classHeritage(classElem), _emitClassMethods(node, ctors, fields)); |
354 | 376 |
355 String jsPeerName; | 377 String jsPeerName; |
356 var jsPeer = getAnnotationValue(node, _isJsPeerInterface); | 378 var jsPeer = getAnnotationValue(node, _isJsPeerInterface); |
357 if (jsPeer != null) { | 379 if (jsPeer != null) { |
358 jsPeerName = getConstantField(jsPeer, 'name', types.stringType); | 380 jsPeerName = getConstantField(jsPeer, 'name', types.stringType); |
359 } | 381 } |
360 | 382 |
361 var body = _finishClassMembers( | 383 var body = |
362 classElem, classExpr, ctors, fields, staticFields, jsPeerName); | 384 _finishClassMembers(classElem, classExpr, ctors, fields, jsPeerName); |
363 | |
364 // Unset current class | |
365 assert(_currentClassElement == classElem); | |
366 _currentClassElement = null; | |
367 | 385 |
368 var result = _finishClassDef(type, body); | 386 var result = _finishClassDef(type, body); |
369 | 387 |
370 if (jsPeerName != null) { | 388 if (jsPeerName != null) { |
371 // This class isn't allowed to be lazy, because we need to set up | 389 // This class isn't allowed to be lazy, because we need to set up |
372 // the native JS type eagerly at this point. | 390 // the native JS type eagerly at this point. |
373 // If we wanted to support laziness, we could defer the hookup until | 391 // If we wanted to support laziness, we could defer the hookup until |
374 // the end of the Dart library cycle load. | 392 // the end of the Dart library cycle load. |
375 assert(!_lazyClass(type)); | 393 assert(_loader.isLoaded(classElem)); |
376 | 394 |
377 // TODO(jmesserly): this copies the dynamic members. | 395 // TODO(jmesserly): this copies the dynamic members. |
378 // Probably fine for objects coming from JS, but not if we actually | 396 // Probably fine for objects coming from JS, but not if we actually |
379 // want to support construction of instances with generic types other | 397 // want to support construction of instances with generic types other |
380 // than dynamic. See issue #154 for Array and List<E> related bug. | 398 // than dynamic. See issue #154 for Array and List<E> related bug. |
381 var copyMembers = js.statement( | 399 var copyMembers = js.statement( |
382 'dart.registerExtension(dart.global.#, #);', [ | 400 'dart.registerExtension(dart.global.#, #);', [ |
383 _propertyName(jsPeerName), | 401 _propertyName(jsPeerName), |
384 classElem.name | 402 classElem.name |
385 ]); | 403 ]); |
386 return _statement([result, copyMembers]); | 404 return _statement([result, copyMembers]); |
387 } | 405 } |
388 return result; | 406 return result; |
389 } | 407 } |
390 | 408 |
391 @override | 409 @override |
392 JS.Statement visitEnumDeclaration(EnumDeclaration node) => | 410 JS.Statement visitEnumDeclaration(EnumDeclaration node) => |
393 _unimplementedCall("Unimplemented enum: $node").toStatement(); | 411 _unimplementedCall("Unimplemented enum: $node").toStatement(); |
394 | 412 |
395 /// Given a class element and body, complete the class declaration. | 413 /// Given a class element and body, complete the class declaration. |
396 /// This handles generic type parameters, laziness (in library-cycle cases), | 414 /// This handles generic type parameters, laziness (in library-cycle cases), |
397 /// and ensuring dependencies are loaded first. | 415 /// and ensuring dependencies are loaded first. |
398 JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) { | 416 JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) { |
399 var name = type.name; | 417 var name = type.name; |
400 var genericName = '$name\$'; | 418 var genericName = '$name\$'; |
401 | 419 |
402 JS.Statement genericDef; | 420 JS.Statement genericDef = null; |
403 JS.Expression genericInst; | |
404 if (type.typeParameters.isNotEmpty) { | 421 if (type.typeParameters.isNotEmpty) { |
405 genericDef = _emitGenericClassDef(type, body); | 422 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 } | 423 } |
412 | 424 |
413 // The base class and all mixins must be declared before this class. | 425 // The base class and all mixins must be declared before this class. |
414 if (_lazyClass(type)) { | 426 if (!_loader.isLoaded(type.element)) { |
415 // TODO(jmesserly): the lazy class def is a simple solution for now. | 427 // TODO(jmesserly): the lazy class def is a simple solution for now. |
416 // We may want to consider other options in the future. | 428 // We may want to consider other options in the future. |
417 | 429 |
418 if (genericDef != null) { | 430 if (genericDef != null) { |
419 return js.statement( | 431 return js.statement( |
420 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ | 432 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ |
421 genericDef, | 433 genericDef, |
422 _exportsVar, | 434 _exportsVar, |
423 _propertyName(name), | 435 _propertyName(name), |
424 genericName | 436 genericName |
425 ]); | 437 ]); |
426 } | 438 } |
427 | 439 |
428 return js.statement( | 440 return js.statement( |
429 'dart.defineLazyClass(#, { get #() { #; return #; } });', [ | 441 'dart.defineLazyClass(#, { get #() { #; return #; } });', [ |
430 _exportsVar, | 442 _exportsVar, |
431 _propertyName(name), | 443 _propertyName(name), |
432 body, | 444 body, |
433 name | 445 name |
434 ]); | 446 ]); |
435 } | 447 } |
436 | 448 |
437 if (isPublic(name)) _addExport(name); | 449 if (isPublic(name)) _addExport(name); |
438 | 450 |
439 if (genericDef != null) { | 451 if (genericDef != null) { |
440 body = js.statement('{ #; let # = #; }', [genericDef, name, genericInst]); | 452 var dynType = fillDynamicTypeArgs(type, types); |
453 var genericInst = _emitTypeName(dynType, lowerGeneric: true); | |
454 return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]); | |
441 } | 455 } |
442 | 456 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 } | 457 } |
553 | 458 |
554 JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) { | 459 JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) { |
555 var name = type.name; | 460 var name = type.name; |
556 var genericName = '$name\$'; | 461 var genericName = '$name\$'; |
557 var typeParams = type.typeParameters.map((p) => p.name); | 462 var typeParams = type.typeParameters.map((p) => p.name); |
558 if (isPublic(name)) _exports.add(genericName); | 463 if (isPublic(name)) _exports.add(genericName); |
559 return js.statement('let # = dart.generic(function(#) { #; return #; });', [ | 464 return js.statement('let # = dart.generic(function(#) { #; return #; });', [ |
560 genericName, | 465 genericName, |
561 typeParams, | 466 typeParams, |
562 body, | 467 body, |
563 name | 468 name |
564 ]); | 469 ]); |
565 } | 470 } |
566 | 471 |
567 JS.Expression _classHeritage(node) { | 472 JS.Expression _classHeritage(ClassElement element) { |
568 if (node.element.type.isObject) return null; | 473 var type = element.type; |
474 if (type.isObject) return null; | |
569 | 475 |
570 DartType supertype; | 476 // Assume we can load eagerly, until proven otherwise. |
571 if (node is ClassDeclaration) { | 477 _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 | 478 |
578 JS.Expression heritage = _emitTypeName(supertype); | 479 JS.Expression heritage = _emitTypeName(type.superclass); |
579 | 480 if (type.mixins.isNotEmpty) { |
580 if (node.withClause != null) { | 481 var mixins = type.mixins.map(_emitTypeName).toList(); |
581 var mixins = _visitList(node.withClause.mixinTypes); | |
582 mixins.insert(0, heritage); | 482 mixins.insert(0, heritage); |
583 heritage = js.call('dart.mixin(#)', [mixins]); | 483 heritage = js.call('dart.mixin(#)', [mixins]); |
584 } | 484 } |
585 | 485 |
486 _loader.finishTopLevel(element); | |
586 return heritage; | 487 return heritage; |
587 } | 488 } |
588 | 489 |
589 List<JS.Method> _emitClassMethods(ClassDeclaration node, | 490 List<JS.Method> _emitClassMethods(ClassDeclaration node, |
590 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { | 491 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
591 var element = node.element; | 492 var element = node.element; |
592 var type = element.type; | 493 var type = element.type; |
593 var isObject = type.isObject; | 494 var isObject = type.isObject; |
594 var name = node.name.name; | 495 var name = node.name.name; |
595 | 496 |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
656 return new JS.Method(js.call('$_SYMBOL.iterator'), js.call( | 557 return new JS.Method(js.call('$_SYMBOL.iterator'), js.call( |
657 'function() { return new dart.JsIterator(this.#); }', | 558 'function() { return new dart.JsIterator(this.#); }', |
658 [_emitMemberName('iterator', type: t)])); | 559 [_emitMemberName('iterator', type: t)])); |
659 } | 560 } |
660 | 561 |
661 /// Emit class members that need to come after the class declaration, such | 562 /// Emit class members that need to come after the class declaration, such |
662 /// as static fields. See [_emitClassMethods] for things that are emitted | 563 /// as static fields. See [_emitClassMethods] for things that are emitted |
663 /// inside the ES6 `class { ... }` node. | 564 /// inside the ES6 `class { ... }` node. |
664 JS.Statement _finishClassMembers(ClassElement classElem, | 565 JS.Statement _finishClassMembers(ClassElement classElem, |
665 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, | 566 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, |
666 List<FieldDeclaration> fields, List<FieldDeclaration> staticFields, | 567 List<FieldDeclaration> fields, String jsPeerName) { |
667 String jsPeerName) { | |
668 var name = classElem.name; | 568 var name = classElem.name; |
669 var body = <JS.Statement>[]; | 569 var body = <JS.Statement>[]; |
670 body.add(new JS.ClassDeclaration(cls)); | 570 body.add(new JS.ClassDeclaration(cls)); |
671 | 571 |
672 // TODO(jmesserly): we should really just extend native Array. | 572 // TODO(jmesserly): we should really just extend native Array. |
673 if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { | 573 if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { |
674 body.add(js.statement('dart.setBaseClass(#, dart.global.#);', [ | 574 body.add(js.statement('dart.setBaseClass(#, dart.global.#);', [ |
675 classElem.name, | 575 classElem.name, |
676 _propertyName(jsPeerName) | 576 _propertyName(jsPeerName) |
677 ])); | 577 ])); |
(...skipping 21 matching lines...) Expand all Loading... | |
699 // Instance fields, if they override getter/setter pairs | 599 // Instance fields, if they override getter/setter pairs |
700 for (FieldDeclaration member in fields) { | 600 for (FieldDeclaration member in fields) { |
701 for (VariableDeclaration fieldDecl in member.fields.variables) { | 601 for (VariableDeclaration fieldDecl in member.fields.variables) { |
702 var field = fieldDecl.element; | 602 var field = fieldDecl.element; |
703 if (_fieldsNeedingStorage.contains(field)) { | 603 if (_fieldsNeedingStorage.contains(field)) { |
704 body.add(_overrideField(field)); | 604 body.add(_overrideField(field)); |
705 } | 605 } |
706 } | 606 } |
707 } | 607 } |
708 | 608 |
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); | 609 return _statement(body); |
727 } | 610 } |
728 | 611 |
729 JS.Statement _overrideField(FieldElement e) { | 612 JS.Statement _overrideField(FieldElement e) { |
730 var cls = e.enclosingElement; | 613 var cls = e.enclosingElement; |
731 return js.statement('dart.virtualField(#, #)', [ | 614 return js.statement('dart.virtualField(#, #)', [ |
732 cls.name, | 615 cls.name, |
733 _emitMemberName(e.name, type: cls.type) | 616 _emitMemberName(e.name, type: cls.type) |
734 ]); | 617 ]); |
735 } | 618 } |
736 | 619 |
737 /// Generates the implicit default constructor for class C of the form | 620 /// Generates the implicit default constructor for class C of the form |
738 /// `C() : super() {}`. | 621 /// `C() : super() {}`. |
739 JS.Method _emitImplicitConstructor( | 622 JS.Method _emitImplicitConstructor( |
740 ClassDeclaration node, String name, List<FieldDeclaration> fields) { | 623 ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
741 assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty); | 624 assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty); |
742 | 625 |
743 // If we don't have a method body, skip this. | 626 // If we don't have a method body, skip this. |
744 var superCall = _superConstructorCall(node); | 627 var superCall = _superConstructorCall(node); |
745 if (fields.isEmpty && superCall == null) return null; | 628 if (fields.isEmpty && superCall == null) return null; |
746 | 629 |
747 dynamic body = _initializeFields(fields); | 630 dynamic body = _initializeFields(node, fields); |
748 if (superCall != null) body = [[body, superCall]]; | 631 if (superCall != null) body = [[body, superCall]]; |
749 return new JS.Method( | 632 return new JS.Method( |
750 _propertyName(name), js.call('function() { #; }', body)); | 633 _propertyName(name), js.call('function() { #; }', body)); |
751 } | 634 } |
752 | 635 |
753 JS.Method _emitConstructor(ConstructorDeclaration node, String className, | 636 JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
754 List<FieldDeclaration> fields, bool isObject) { | 637 List<FieldDeclaration> fields, bool isObject) { |
755 if (_externalOrNative(node)) return null; | 638 if (_externalOrNative(node)) return null; |
756 | 639 |
757 var name = _constructorName(className, node.name); | 640 var name = _constructorName(className, node.name); |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
826 body.add(_visit(redirectCall)); | 709 body.add(_visit(redirectCall)); |
827 return new JS.Block(body); | 710 return new JS.Block(body); |
828 } | 711 } |
829 | 712 |
830 // Initializers only run for non-factory constructors. | 713 // Initializers only run for non-factory constructors. |
831 if (node.factoryKeyword == null) { | 714 if (node.factoryKeyword == null) { |
832 // Generate field initializers. | 715 // Generate field initializers. |
833 // These are expanded into each non-redirecting constructor. | 716 // These are expanded into each non-redirecting constructor. |
834 // In the future we may want to create an initializer function if we have | 717 // 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. | 718 // multiple constructors, but it needs to be balanced against readability. |
836 body.add(_initializeFields(fields, node.parameters, node.initializers)); | 719 body.add(_initializeFields(node, fields)); |
837 | 720 |
838 var superCall = node.initializers.firstWhere( | 721 var superCall = node.initializers.firstWhere( |
839 (i) => i is SuperConstructorInvocation, orElse: () => null); | 722 (i) => i is SuperConstructorInvocation, orElse: () => null); |
840 | 723 |
841 // If no superinitializer is provided, an implicit superinitializer of the | 724 // If no superinitializer is provided, an implicit superinitializer of the |
842 // form `super()` is added at the end of the initializer list, unless the | 725 // form `super()` is added at the end of the initializer list, unless the |
843 // enclosing class is class Object. | 726 // enclosing class is class Object. |
844 var jsSuper = _superConstructorCall(node.parent, superCall); | 727 var jsSuper = _superConstructorCall(node.parent, superCall); |
845 if (jsSuper != null) body.add(jsSuper); | 728 if (jsSuper != null) body.add(jsSuper); |
846 } | 729 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
890 if (!e.unnamedConstructor.isSynthetic) return true; | 773 if (!e.unnamedConstructor.isSynthetic) return true; |
891 return e.fields.any((f) => !f.isStatic && !f.isSynthetic); | 774 return e.fields.any((f) => !f.isStatic && !f.isSynthetic); |
892 } | 775 } |
893 | 776 |
894 /// Initialize fields. They follow the sequence: | 777 /// Initialize fields. They follow the sequence: |
895 /// | 778 /// |
896 /// 1. field declaration initializer if non-const, | 779 /// 1. field declaration initializer if non-const, |
897 /// 2. field initializing parameters, | 780 /// 2. field initializing parameters, |
898 /// 3. constructor field initializers, | 781 /// 3. constructor field initializers, |
899 /// 4. initialize fields not covered in 1-3 | 782 /// 4. initialize fields not covered in 1-3 |
900 JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls, | 783 JS.Statement _initializeFields( |
901 [FormalParameterList parameters, | 784 AstNode node, List<FieldDeclaration> fieldDecls) { |
902 NodeList<ConstructorInitializer> initializers]) { | 785 var unit = node.getAncestor((a) => a is CompilationUnit); |
786 var constField = new ConstFieldVisitor(types, unit); | |
903 | 787 |
904 // Run field initializers if they can have side-effects. | 788 // Run field initializers if they can have side-effects. |
905 var fields = new Map<FieldElement, JS.Expression>(); | 789 var fields = new Map<FieldElement, JS.Expression>(); |
906 var unsetFields = new Map<FieldElement, VariableDeclaration>(); | 790 var unsetFields = new Map<FieldElement, VariableDeclaration>(); |
907 for (var declaration in fieldDecls) { | 791 for (var declaration in fieldDecls) { |
908 for (var fieldNode in declaration.fields.variables) { | 792 for (var fieldNode in declaration.fields.variables) { |
909 var element = fieldNode.element; | 793 var element = fieldNode.element; |
910 if (_isFieldInitConstant(fieldNode)) { | 794 if (constField.isFieldInitConstant(fieldNode)) { |
911 unsetFields[element] = fieldNode; | 795 unsetFields[element] = fieldNode; |
912 } else { | 796 } else { |
913 fields[element] = _visitInitializer(fieldNode); | 797 fields[element] = _visitInitializer(fieldNode); |
914 } | 798 } |
915 } | 799 } |
916 } | 800 } |
917 | 801 |
918 // Initialize fields from `this.fieldName` parameters. | 802 // Initialize fields from `this.fieldName` parameters. |
919 if (parameters != null) { | 803 if (node is ConstructorDeclaration) { |
804 var parameters = node.parameters; | |
805 var initializers = node.initializers; | |
806 | |
920 for (var p in parameters.parameters) { | 807 for (var p in parameters.parameters) { |
921 var element = p.element; | 808 var element = p.element; |
922 if (element is FieldFormalParameterElement) { | 809 if (element is FieldFormalParameterElement) { |
923 fields[element.field] = _visit(p); | 810 fields[element.field] = _visit(p); |
924 } | 811 } |
925 } | 812 } |
926 } | |
927 | 813 |
928 // Run constructor field initializers such as `: foo = bar.baz` | 814 // Run constructor field initializers such as `: foo = bar.baz` |
929 if (initializers != null) { | |
930 for (var init in initializers) { | 815 for (var init in initializers) { |
931 if (init is ConstructorFieldInitializer) { | 816 if (init is ConstructorFieldInitializer) { |
932 fields[init.fieldName.staticElement] = _visit(init.expression); | 817 fields[init.fieldName.staticElement] = _visit(init.expression); |
933 } | 818 } |
934 } | 819 } |
935 } | 820 } |
936 | 821 |
937 for (var f in fields.keys) unsetFields.remove(f); | 822 for (var f in fields.keys) unsetFields.remove(f); |
938 | 823 |
939 // Initialize all remaining fields | 824 // Initialize all remaining fields |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1122 var accessor = node.staticElement; | 1007 var accessor = node.staticElement; |
1123 if (accessor == null) { | 1008 if (accessor == null) { |
1124 return js.commentExpression( | 1009 return js.commentExpression( |
1125 'Unimplemented unknown name', new JS.Identifier(node.name)); | 1010 'Unimplemented unknown name', new JS.Identifier(node.name)); |
1126 } | 1011 } |
1127 | 1012 |
1128 // Get the original declaring element. If we had a property accessor, this | 1013 // Get the original declaring element. If we had a property accessor, this |
1129 // indirects back to a (possibly synthetic) field. | 1014 // indirects back to a (possibly synthetic) field. |
1130 var element = accessor; | 1015 var element = accessor; |
1131 if (element is PropertyAccessorElement) element = accessor.variable; | 1016 if (element is PropertyAccessorElement) element = accessor.variable; |
1017 | |
1018 _loader.declareBeforeUse(element); | |
1019 | |
1132 var name = element.name; | 1020 var name = element.name; |
1133 | 1021 |
1134 // library member | 1022 // library member |
1135 if (element.enclosingElement is CompilationUnitElement && | 1023 if (element.enclosingElement is CompilationUnitElement) { |
1136 (element.library != currentLibrary || | 1024 return _maybeQualifiedName( |
1137 element is TopLevelVariableElement && !element.isConst)) { | 1025 element, _emitMemberName(name, isStatic: true)); |
1138 var memberName = _emitMemberName(name, isStatic: true); | |
1139 return js.call('#.#', [_libraryName(element.library), memberName]); | |
1140 } | 1026 } |
1141 | 1027 |
1142 // Unqualified class member. This could mean implicit-this, or implicit | 1028 // Unqualified class member. This could mean implicit-this, or implicit |
1143 // call to a static from the same class. | 1029 // call to a static from the same class. |
1144 if (element is ClassMemberElement && element is! ConstructorElement) { | 1030 if (element is ClassMemberElement && element is! ConstructorElement) { |
1145 bool isStatic = element.isStatic; | 1031 bool isStatic = element.isStatic; |
1146 var type = element.enclosingElement.type; | 1032 var type = element.enclosingElement.type; |
1147 var member = _emitMemberName(name, isStatic: isStatic, type: type); | 1033 var member = _emitMemberName(name, isStatic: isStatic, type: type); |
1148 | 1034 |
1149 // For static methods, we add the raw type name, without generics or | 1035 // 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 | 1036 // library prefix. We don't need those because static calls can't use |
1151 // the generic type. | 1037 // the generic type. |
1152 if (isStatic) { | 1038 if (isStatic) { |
1153 return js.call('#.#', [type.name, member]); | 1039 var dynType = _emitTypeName(fillDynamicTypeArgs(type, types)); |
1040 return new JS.PropertyAccess(dynType, member); | |
1154 } | 1041 } |
1155 | 1042 |
1156 // For instance members, we add implicit-this. | 1043 // For instance members, we add implicit-this. |
1157 // For method tear-offs, we ensure it's a bound method. | 1044 // For method tear-offs, we ensure it's a bound method. |
1158 var code = 'this.#'; | 1045 var code = 'this.#'; |
1159 if (element is MethodElement && !inInvocationContext(node)) { | 1046 if (element is MethodElement && !inInvocationContext(node)) { |
1160 code += '.bind(this)'; | 1047 code += '.bind(this)'; |
1161 } | 1048 } |
1162 return js.call(code, member); | 1049 return js.call(code, member); |
1163 } | 1050 } |
(...skipping 29 matching lines...) Expand all Loading... | |
1193 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { | 1080 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { |
1194 var properties = <JS.Property>[]; | 1081 var properties = <JS.Property>[]; |
1195 types.forEach((name, type) { | 1082 types.forEach((name, type) { |
1196 var key = new JS.LiteralString(name); | 1083 var key = new JS.LiteralString(name); |
1197 var value = _emitTypeName(type); | 1084 var value = _emitTypeName(type); |
1198 properties.add(new JS.Property(key, value)); | 1085 properties.add(new JS.Property(key, value)); |
1199 }); | 1086 }); |
1200 return new JS.ObjectInitializer(properties); | 1087 return new JS.ObjectInitializer(properties); |
1201 } | 1088 } |
1202 | 1089 |
1203 JS.Expression _emitTypeName(DartType type, {bool lowerTypedef: false}) { | 1090 /// Emits a Dart [type] into code. |
1091 /// | |
1092 /// If [lowerTypedef] is set, a typedef will be expanded as if it were a | |
1093 /// function type. Similarly if [lowerGeneric] is set, the `List$()` form | |
1094 /// will be used instead of `List`. These flags are used when generating | |
1095 /// the definitions for typedefs and generic types, respectively. | |
1096 JS.Expression _emitTypeName(DartType type, | |
1097 {bool lowerTypedef: false, bool lowerGeneric: false}) { | |
1098 | |
1204 // The void and dynamic types are not defined in core. | 1099 // The void and dynamic types are not defined in core. |
1205 if (type.isVoid) { | 1100 if (type.isVoid) { |
1206 return js.call('dart.void'); | 1101 return js.call('dart.void'); |
1207 } else if (type.isDynamic) { | 1102 } else if (type.isDynamic) { |
1208 return js.call('dart.dynamic'); | 1103 return js.call('dart.dynamic'); |
1209 } | 1104 } |
1210 | 1105 |
1106 _loader.declareBeforeUse(type.element); | |
1107 | |
1108 // TODO(jmesserly): like constants, should we hoist function types out of | |
1109 // methods? Similar issue with generic types. For all of these, we may want | |
1110 // to canonicalize them too, at least when inside the same library. | |
1211 var name = type.name; | 1111 var name = type.name; |
1212 var element = type.element; | 1112 var element = type.element; |
1213 if (name == '' || lowerTypedef && type is FunctionType) { | 1113 if (name == '' || lowerTypedef) { |
1214 if (type is FunctionType) { | 1114 var fnType = type as FunctionType; |
Jennifer Messerly
2015/05/12 16:23:45
if we get here, type has to be FunctionType.
| |
1215 var returnType = type.returnType; | 1115 var returnType = fnType.returnType; |
1216 var parameterTypes = type.normalParameterTypes; | 1116 var parameterTypes = fnType.normalParameterTypes; |
1217 var optionalTypes = type.optionalParameterTypes; | 1117 var optionalTypes = fnType.optionalParameterTypes; |
1218 var namedTypes = type.namedParameterTypes; | 1118 var namedTypes = fnType.namedParameterTypes; |
1219 if (namedTypes.isEmpty) { | 1119 if (namedTypes.isEmpty) { |
1220 if (optionalTypes.isEmpty) { | 1120 if (optionalTypes.isEmpty) { |
1221 return js.call('dart.functionType(#, #)', [ | 1121 return js.call('dart.functionType(#, #)', [ |
1222 _emitTypeName(returnType), | 1122 _emitTypeName(returnType), |
1223 _emitTypeNames(parameterTypes) | 1123 _emitTypeNames(parameterTypes) |
1224 ]); | 1124 ]); |
1225 } else { | |
1226 return js.call('dart.functionType(#, #, #)', [ | |
1227 _emitTypeName(returnType), | |
1228 _emitTypeNames(parameterTypes), | |
1229 _emitTypeNames(optionalTypes) | |
1230 ]); | |
1231 } | |
1232 } else { | 1125 } else { |
1233 assert(optionalTypes.isEmpty); | |
1234 return js.call('dart.functionType(#, #, #)', [ | 1126 return js.call('dart.functionType(#, #, #)', [ |
1235 _emitTypeName(returnType), | 1127 _emitTypeName(returnType), |
1236 _emitTypeNames(parameterTypes), | 1128 _emitTypeNames(parameterTypes), |
1237 _emitTypeProperties(namedTypes) | 1129 _emitTypeNames(optionalTypes) |
1238 ]); | 1130 ]); |
1239 } | 1131 } |
1132 } else { | |
1133 assert(optionalTypes.isEmpty); | |
1134 return js.call('dart.functionType(#, #, #)', [ | |
1135 _emitTypeName(returnType), | |
1136 _emitTypeNames(parameterTypes), | |
1137 _emitTypeProperties(namedTypes) | |
1138 ]); | |
1240 } | 1139 } |
1241 // TODO(jmesserly): remove when we're using coercion reifier. | |
1242 return _unimplementedCall('Unimplemented type $type'); | |
1243 } | 1140 } |
1244 | 1141 |
1245 var typeArgs = null; | 1142 if (type is TypeParameterType) { |
1143 return new JS.Identifier(name); | |
1144 } | |
1145 | |
1246 if (type is ParameterizedType) { | 1146 if (type is ParameterizedType) { |
1247 var args = type.typeArguments; | 1147 var args = type.typeArguments; |
1248 var isCurrentClass = | 1148 var isCurrentClass = |
1249 type is InterfaceType ? type.element == _currentClassElement : false; | 1149 args.isNotEmpty && _loader.isCurrentElement(type.element); |
1150 Iterable jsArgs = null; | |
1250 if (args.any((a) => a != types.dynamicType)) { | 1151 if (args.any((a) => a != types.dynamicType)) { |
1251 name = '$name\$'; | 1152 jsArgs = args.map(_emitTypeName); |
1252 typeArgs = args.map(_emitTypeName); | 1153 } else if (lowerGeneric || isCurrentClass) { |
1253 } else if (args.isNotEmpty && isCurrentClass) { | |
1254 // When creating a `new S<dynamic>` we try and use the raw form | 1154 // 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, | 1155 // `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. | 1156 // because `S` refers to the current S<T> we are generating. |
1257 name = '$name\$'; | 1157 jsArgs = []; |
1258 typeArgs = []; | 1158 } |
1159 if (jsArgs != null) { | |
1160 var genericName = _maybeQualifiedName( | |
1161 element, _propertyName('$name\$'), _qualifiedGenericIds); | |
1162 return js.call('#(#)', [genericName, jsArgs]); | |
1259 } | 1163 } |
1260 } | 1164 } |
1261 | 1165 |
1262 JS.Expression result; | 1166 return _maybeQualifiedName(element, _propertyName(name)); |
1263 if (_needQualifiedName(element)) { | 1167 } |
1264 result = js.call('#.#', [_libraryName(element.library), name]); | 1168 |
1265 } else { | 1169 JS.Expression _maybeQualifiedName(Element e, JS.Expression name, |
Jennifer Messerly
2015/05/12 16:23:45
we can now make name qualification decisions after
| |
1266 result = new JS.Identifier(name); | 1170 [Map<Element, JS.MaybeQualifiedId> idTable]) { |
1171 var libName = _libraryName(e.library); | |
1172 if (idTable == null) idTable = _qualifiedIds; | |
1173 | |
1174 // Mutable top-level fields should always be qualified. | |
1175 bool mutableTopLevel = e is TopLevelVariableElement && !e.isConst; | |
1176 if (e.library != currentLibrary || mutableTopLevel) { | |
1177 return new JS.PropertyAccess(libName, name); | |
1267 } | 1178 } |
1268 | 1179 |
1269 if (typeArgs != null) { | 1180 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 } | 1181 } |
1283 | 1182 |
1284 @override | 1183 @override |
1285 JS.Expression visitAssignmentExpression(AssignmentExpression node) { | 1184 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
1286 var left = node.leftHandSide; | 1185 var left = node.leftHandSide; |
1287 var right = node.rightHandSide; | 1186 var right = node.rightHandSide; |
1288 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); | 1187 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
1289 return _emitOpAssign( | 1188 return _emitOpAssign( |
1290 left, right, node.operator.lexeme[0], node.staticElement, | 1189 left, right, node.operator.lexeme[0], node.staticElement, |
1291 context: node); | 1190 context: node); |
(...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1481 | 1380 |
1482 @override | 1381 @override |
1483 JS.Statement visitReturnStatement(ReturnStatement node) { | 1382 JS.Statement visitReturnStatement(ReturnStatement node) { |
1484 var e = node.expression; | 1383 var e = node.expression; |
1485 if (e == null) return new JS.Return(); | 1384 if (e == null) return new JS.Return(); |
1486 return _visit(e).toReturn(); | 1385 return _visit(e).toReturn(); |
1487 } | 1386 } |
1488 | 1387 |
1489 @override | 1388 @override |
1490 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | 1389 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
1491 var body = <JS.Statement>[]; | 1390 for (var v in node.variables.variables) { |
1492 | 1391 _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 } | 1392 } |
1508 | |
1509 return _statement(body); | |
1510 } | 1393 } |
1511 | 1394 |
1512 _addExport(String name) { | 1395 _addExport(String name) { |
1513 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; | 1396 if (!_exports.add(name)) throw 'Duplicate top level name found: $name'; |
1514 } | 1397 } |
1515 | 1398 |
1516 @override | 1399 @override |
1517 JS.Statement visitVariableDeclarationStatement( | 1400 JS.Statement visitVariableDeclarationStatement( |
1518 VariableDeclarationStatement node) { | 1401 VariableDeclarationStatement node) { |
1519 // Special case a single variable with an initializer. | 1402 // Special case a single variable with an initializer. |
1520 // This helps emit cleaner code for things like: | 1403 // This helps emit cleaner code for things like: |
1521 // var result = []..add(1)..add(2); | 1404 // var result = []..add(1)..add(2); |
1522 if (node.variables.variables.length == 1) { | 1405 if (node.variables.variables.length == 1) { |
1523 var v = node.variables.variables.single; | 1406 var v = node.variables.variables.single; |
1524 if (v.initializer != null) { | 1407 if (v.initializer != null) { |
1525 var name = new JS.Identifier(v.name.name); | 1408 var name = new JS.Identifier(v.name.name); |
1526 return _visit(v.initializer).toVariableDeclaration(name); | 1409 return _visit(v.initializer).toVariableDeclaration(name); |
1527 } | 1410 } |
1528 } | 1411 } |
1529 return _visit(node.variables).toStatement(); | 1412 return _visit(node.variables).toStatement(); |
1530 } | 1413 } |
1531 | 1414 |
1532 @override | 1415 @override |
1533 visitVariableDeclarationList(VariableDeclarationList node) { | 1416 visitVariableDeclarationList(VariableDeclarationList node) { |
1534 return new JS.VariableDeclarationList('let', _visitList(node.variables)); | 1417 return new JS.VariableDeclarationList('let', _visitList(node.variables)); |
1535 } | 1418 } |
1536 | 1419 |
1537 @override | 1420 @override |
1538 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { | 1421 visitVariableDeclaration(VariableDeclaration node) { |
1422 if (node.element is PropertyInducingElement) return _emitStaticField(node); | |
1423 | |
1539 var name = new JS.Identifier(node.name.name); | 1424 var name = new JS.Identifier(node.name.name); |
1540 return new JS.VariableInitialization(name, _visitInitializer(node)); | 1425 return new JS.VariableInitialization(name, _visitInitializer(node)); |
1541 } | 1426 } |
1542 | 1427 |
1428 /// Emits a static or top-level field. | |
1429 JS.Statement _emitStaticField(VariableDeclaration field) { | |
1430 PropertyInducingElement element = field.element; | |
1431 assert(element.isStatic); | |
1432 | |
1433 bool eagerInit; | |
1434 JS.Expression jsInit; | |
1435 if (field.isConst || _constField.isFieldInitConstant(field)) { | |
1436 // If the field is constant, try and generate it at the top level. | |
1437 _loader.startTopLevel(element); | |
1438 jsInit = _visitInitializer(field); | |
1439 _loader.finishTopLevel(element); | |
1440 eagerInit = _loader.isLoaded(element); | |
1441 } else { | |
1442 jsInit = _visitInitializer(field); | |
1443 eagerInit = false; | |
1444 } | |
1445 | |
1446 var fieldName = field.name.name; | |
1447 if (field.isConst && eagerInit && element is TopLevelVariableElement) { | |
1448 // constant fields don't change, so we can generate them as `let` | |
1449 // but add them to the module's exports. However, make sure we generate | |
1450 // anything they depend on first. | |
1451 | |
1452 if (isPublic(fieldName)) _addExport(fieldName); | |
1453 return js.statement('let # = #;', [new JS.Identifier(fieldName), jsInit]); | |
1454 } | |
1455 | |
1456 if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { | |
1457 return js.statement('# = #;', [_visit(field.name), jsInit]); | |
1458 } | |
1459 | |
1460 var body = []; | |
1461 if (_lazyFields.isNotEmpty) { | |
1462 var existingTarget = _lazyFields[0].element.enclosingElement; | |
1463 if (existingTarget != element.enclosingElement) { | |
1464 _flushLazyFields(body); | |
1465 } | |
1466 } | |
1467 | |
1468 _lazyFields.add(field); | |
1469 | |
1470 return _statement(body); | |
1471 } | |
1472 | |
1543 JS.Expression _visitInitializer(VariableDeclaration node) { | 1473 JS.Expression _visitInitializer(VariableDeclaration node) { |
1544 var value = _visit(node.initializer); | 1474 var value = _visit(node.initializer); |
1545 // explicitly initialize to null, to avoid getting `undefined`. | 1475 // explicitly initialize to null, to avoid getting `undefined`. |
1546 // TODO(jmesserly): do this only for vars that aren't definitely assigned. | 1476 // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
1547 return value != null ? value : new JS.LiteralNull(); | 1477 return value != null ? value : new JS.LiteralNull(); |
1548 } | 1478 } |
1549 | 1479 |
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) { | 1480 void _flushLazyFields(List<JS.Statement> body) { |
1558 var code = _emitLazyFields(_exportsVar, _lazyFields); | 1481 if (_lazyFields.isEmpty) return; |
1559 if (code != null) { | 1482 body.add(_emitLazyFields(_lazyFields)); |
1560 // Ensure symbols for private fields are defined. | |
1561 _flushPendingStatements(body); | |
1562 body.add(code); | |
1563 } | |
1564 _lazyFields.clear(); | 1483 _lazyFields.clear(); |
1565 } | 1484 } |
1566 | 1485 |
1567 JS.Statement _emitLazyFields( | 1486 JS.Statement _emitLazyFields(List<VariableDeclaration> fields) { |
1568 JS.Expression objExpr, List<VariableDeclaration> fields) { | |
1569 if (fields.isEmpty) return null; | |
1570 | |
1571 var methods = []; | 1487 var methods = []; |
1572 for (var node in fields) { | 1488 for (var node in fields) { |
1573 var name = node.name.name; | 1489 var name = node.name.name; |
1574 var element = node.element; | 1490 var element = node.element; |
1575 var access = _emitMemberName(name, type: element.type, isStatic: true); | 1491 var access = _emitMemberName(name, type: element.type, isStatic: true); |
1576 methods.add(new JS.Method( | 1492 methods.add(new JS.Method( |
1577 access, js.call('function() { return #; }', _visit(node.initializer)), | 1493 access, js.call('function() { return #; }', _visit(node.initializer)), |
1578 isGetter: true)); | 1494 isGetter: true)); |
1579 | 1495 |
1580 // TODO(jmesserly): use a dummy setter to indicate writable. | 1496 // TODO(jmesserly): use a dummy setter to indicate writable. |
1581 if (!node.isFinal) { | 1497 if (!node.isFinal) { |
1582 methods.add( | 1498 methods.add( |
1583 new JS.Method(access, js.call('function(_) {}'), isSetter: true)); | 1499 new JS.Method(access, js.call('function(_) {}'), isSetter: true)); |
1584 } | 1500 } |
1585 } | 1501 } |
1586 | 1502 |
1503 JS.Expression objExpr = _exportsVar; | |
1504 var target = _lazyFields[0].element.enclosingElement; | |
1505 if (target is ClassElement) { | |
1506 objExpr = new JS.Identifier(target.type.name); | |
1507 } | |
1508 | |
1587 return js.statement( | 1509 return js.statement( |
1588 'dart.defineLazyProperties(#, { # });', [objExpr, methods]); | 1510 'dart.defineLazyProperties(#, { # });', [objExpr, methods]); |
1589 } | 1511 } |
1590 | 1512 |
1591 void _flushLibraryProperties(List<JS.Statement> body) { | 1513 void _flushLibraryProperties(List<JS.Statement> body) { |
1592 if (_properties.isEmpty) return; | 1514 if (_properties.isEmpty) return; |
1593 body.add(js.statement('dart.copyProperties(#, { # });', [ | 1515 body.add(js.statement('dart.copyProperties(#, { # });', [ |
1594 _exportsVar, | 1516 _exportsVar, |
1595 _properties.map(_emitTopLevelProperty) | 1517 _properties.map(_emitTopLevelProperty) |
1596 ])); | 1518 ])); |
1597 _properties.clear(); | 1519 _properties.clear(); |
1598 } | 1520 } |
1599 | 1521 |
1600 @override | 1522 @override |
1601 visitConstructorName(ConstructorName node) { | 1523 visitConstructorName(ConstructorName node) { |
1602 var typeName = _visit(node.type); | 1524 var typeName = _visit(node.type); |
1603 if (node.name != null) { | 1525 if (node.name != null) { |
1604 return js.call( | 1526 return js.call( |
1605 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); | 1527 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]); |
1606 } | 1528 } |
1607 return typeName; | 1529 return typeName; |
1608 } | 1530 } |
1609 | 1531 |
1610 @override | 1532 @override |
1611 visitInstanceCreationExpression(InstanceCreationExpression node) { | 1533 visitInstanceCreationExpression(InstanceCreationExpression node) { |
1612 var newExpr = js.call( | 1534 emitNew() => js.call( |
1613 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); | 1535 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); |
1614 if (node.isConst) return _const(node, newExpr); | 1536 if (node.isConst) return _emitConst(node, emitNew); |
1615 return newExpr; | 1537 return emitNew(); |
1616 } | 1538 } |
1617 | 1539 |
1618 /// True if this type is built-in to JS, and we use the values unwrapped. | 1540 /// 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 | 1541 /// For these types we generate a calling convention via static |
1620 /// "extension methods". This allows types to be extended without adding | 1542 /// "extension methods". This allows types to be extended without adding |
1621 /// extensions directly on the prototype. | 1543 /// extensions directly on the prototype. |
1622 bool _isJSBuiltinType(DartType t) => | 1544 bool _isJSBuiltinType(DartType t) => |
1623 typeIsPrimitiveInJS(t) || rules.isStringType(t); | 1545 typeIsPrimitiveInJS(t) || rules.isStringType(t); |
1624 | 1546 |
1625 bool typeIsPrimitiveInJS(DartType t) => rules.isIntType(t) || | 1547 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". | 1695 // LocalVariableElementImpl, so we could repurpose to mean "temp". |
1774 // * add a new property to LocalVariableElementImpl. | 1696 // * add a new property to LocalVariableElementImpl. |
1775 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 1697 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
1776 var id = | 1698 var id = |
1777 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); | 1699 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
1778 id.staticElement = new TemporaryVariableElement.forNode(id); | 1700 id.staticElement = new TemporaryVariableElement.forNode(id); |
1779 id.staticType = type; | 1701 id.staticType = type; |
1780 return id; | 1702 return id; |
1781 } | 1703 } |
1782 | 1704 |
1783 JS.Expression _const(Expression node, JS.Expression expr, [String nameHint]) { | 1705 JS.Expression _emitConst(Expression node, JS.Expression expr()) { |
1784 var value = js.call('dart.const(#)', expr); | 1706 // TODO(jmesserly): emit the constants at top level if possible. |
1785 | 1707 // This wasn't quite working, so disabled for now. |
1786 // If we're inside a method or function, capture the value into a | 1708 return js.call('dart.const(#)', expr()); |
Jennifer Messerly
2015/05/12 16:23:45
the idea here is: _emitConst can tell _loader it's
| |
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 } | 1709 } |
1804 | 1710 |
1805 /// Returns a new expression, which can be be used safely *once* on the | 1711 /// 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. | 1712 /// left hand side, and *once* on the right side of an assignment. |
1807 /// For example: `expr1[expr2] += y` can be compiled as | 1713 /// For example: `expr1[expr2] += y` can be compiled as |
1808 /// `expr1[expr2] = expr1[expr2] + y`. | 1714 /// `expr1[expr2] = expr1[expr2] + y`. |
1809 /// | 1715 /// |
1810 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated | 1716 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated |
1811 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. | 1717 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. |
1812 /// | 1718 /// |
(...skipping 457 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2270 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); | 2176 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); |
2271 | 2177 |
2272 @override | 2178 @override |
2273 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); | 2179 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); |
2274 | 2180 |
2275 @override | 2181 @override |
2276 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); | 2182 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); |
2277 | 2183 |
2278 @override | 2184 @override |
2279 visitSymbolLiteral(SymbolLiteral node) { | 2185 visitSymbolLiteral(SymbolLiteral node) { |
2280 // TODO(vsm): When we canonicalize, we need to treat private symbols | 2186 emitSymbol() { |
2281 // correctly. | 2187 // TODO(vsm): When we canonicalize, we need to treat private symbols |
2282 var name = js.string(node.components.join('.'), "'"); | 2188 // correctly. |
2283 var nameHint = 'symbol_' + node.components.join('_'); | 2189 var name = js.string(node.components.join('.'), "'"); |
2284 return _const( | 2190 return new JS.New(_emitTypeName(types.symbolType), [name]); |
2285 node, new JS.New(_emitTypeName(types.symbolType), [name]), nameHint); | 2191 } |
2192 return _emitConst(node, emitSymbol); | |
2286 } | 2193 } |
2287 | 2194 |
2288 @override | 2195 @override |
2289 visitListLiteral(ListLiteral node) { | 2196 visitListLiteral(ListLiteral node) { |
2290 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); | 2197 emitList() { |
2291 | 2198 JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); |
2292 ParameterizedType type = node.staticType; | 2199 ParameterizedType type = node.staticType; |
2293 if (type.typeArguments.any((a) => a != types.dynamicType)) { | 2200 if (type.typeArguments.any((a) => a != types.dynamicType)) { |
2294 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); | 2201 list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); |
2202 } | |
2203 return list; | |
2295 } | 2204 } |
2296 if (node.constKeyword != null) return _const(node, list); | 2205 if (node.constKeyword != null) return _emitConst(node, emitList); |
2297 return list; | 2206 return emitList(); |
2298 } | 2207 } |
2299 | 2208 |
2300 @override | 2209 @override |
2301 visitMapLiteral(MapLiteral node) { | 2210 visitMapLiteral(MapLiteral node) { |
2302 // TODO(jmesserly): we can likely make these faster. | 2211 // TODO(jmesserly): we can likely make these faster. |
2303 var entries = node.entries; | 2212 emitMap() { |
2304 var mapArguments = null; | 2213 var entries = node.entries; |
2305 if (entries.isEmpty) { | 2214 var mapArguments = null; |
2306 mapArguments = []; | 2215 if (entries.isEmpty) { |
2307 } else if (entries.every((e) => e.key is StringLiteral)) { | 2216 mapArguments = []; |
2308 // Use JS object literal notation if possible, otherwise use an array. | 2217 } else if (entries.every((e) => e.key is StringLiteral)) { |
2309 // We could do this any time all keys are non-nullable String type. | 2218 // Use JS object literal notation if possible, otherwise use an array. |
2310 // For now, support StringLiteral as the common non-nullable String case. | 2219 // We could do this any time all keys are non-nullable String type. |
2311 var props = []; | 2220 // For now, support StringLiteral as the common non-nullable String case . |
2312 for (var e in entries) { | 2221 var props = []; |
2313 props.add(new JS.Property(_visit(e.key), _visit(e.value))); | 2222 for (var e in entries) { |
2223 props.add(new JS.Property(_visit(e.key), _visit(e.value))); | |
2224 } | |
2225 mapArguments = new JS.ObjectInitializer(props); | |
2226 } else { | |
2227 var values = []; | |
2228 for (var e in entries) { | |
2229 values.add(_visit(e.key)); | |
2230 values.add(_visit(e.value)); | |
2231 } | |
2232 mapArguments = new JS.ArrayInitializer(values); | |
2314 } | 2233 } |
2315 mapArguments = new JS.ObjectInitializer(props); | 2234 // TODO(jmesserly): add generic types args. |
2316 } else { | 2235 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 } | 2236 } |
2324 // TODO(jmesserly): add generic types args. | 2237 if (node.constKeyword != null) return _emitConst(node, emitMap); |
2325 var map = js.call('dart.map(#)', [mapArguments]); | 2238 return emitMap(); |
2326 if (node.constKeyword != null) return _const(node, map); | |
2327 return map; | |
2328 } | 2239 } |
2329 | 2240 |
2330 @override | 2241 @override |
2331 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => | 2242 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
2332 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); | 2243 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
2333 | 2244 |
2334 @override | 2245 @override |
2335 JS.Expression visitAdjacentStrings(AdjacentStrings node) => | 2246 JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
2336 _visitListToBinary(node.strings, '+'); | 2247 _visitListToBinary(node.strings, '+'); |
2337 | 2248 |
(...skipping 30 matching lines...) Expand all Loading... | |
2368 JS.Expression _unimplementedCall(String comment) { | 2279 JS.Expression _unimplementedCall(String comment) { |
2369 return js.call('dart.throw_(#)', [js.escapedString(comment)]); | 2280 return js.call('dart.throw_(#)', [js.escapedString(comment)]); |
2370 } | 2281 } |
2371 | 2282 |
2372 @override | 2283 @override |
2373 visitNode(AstNode node) { | 2284 visitNode(AstNode node) { |
2374 // TODO(jmesserly): verify this is unreachable. | 2285 // TODO(jmesserly): verify this is unreachable. |
2375 throw 'Unimplemented ${node.runtimeType}: $node'; | 2286 throw 'Unimplemented ${node.runtimeType}: $node'; |
2376 } | 2287 } |
2377 | 2288 |
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) { | 2289 _visit(AstNode node) { |
2402 if (node == null) return null; | 2290 if (node == null) return null; |
2403 var result = node.accept(this); | 2291 var result = node.accept(this); |
2404 if (result is JS.Node) result.sourceInformation = node; | 2292 if (result is JS.Node) result.sourceInformation = node; |
2405 return result; | 2293 return result; |
2406 } | 2294 } |
2407 | 2295 |
2408 List _visitList(Iterable<AstNode> nodes) { | 2296 List _visitList(Iterable<AstNode> nodes) { |
2409 if (nodes == null) return null; | 2297 if (nodes == null) return null; |
2410 var result = []; | 2298 var result = []; |
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2620 | 2508 |
2621 /// A special kind of element created by the compiler, signifying a temporary | 2509 /// A special kind of element created by the compiler, signifying a temporary |
2622 /// variable. These objects use instance equality, and should be shared | 2510 /// variable. These objects use instance equality, and should be shared |
2623 /// everywhere in the tree where they are treated as the same variable. | 2511 /// everywhere in the tree where they are treated as the same variable. |
2624 class TemporaryVariableElement extends LocalVariableElementImpl { | 2512 class TemporaryVariableElement extends LocalVariableElementImpl { |
2625 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); | 2513 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); |
2626 | 2514 |
2627 int get hashCode => identityHashCode(this); | 2515 int get hashCode => identityHashCode(this); |
2628 bool operator ==(Object other) => identical(this, other); | 2516 bool operator ==(Object other) => identical(this, other); |
2629 } | 2517 } |
OLD | NEW |