OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dev_compiler.src.codegen.js_codegen; | 5 library dev_compiler.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:collection' show HashSet, HashMap; | 7 import 'dart:collection' show HashSet, HashMap; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
(...skipping 11 matching lines...) Expand all Loading... |
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/info.dart'; | 27 import 'package:dev_compiler/src/info.dart'; |
28 import 'package:dev_compiler/src/options.dart'; | 28 import 'package:dev_compiler/src/options.dart'; |
29 import 'package:dev_compiler/src/utils.dart'; | 29 import 'package:dev_compiler/src/utils.dart'; |
30 | 30 |
31 import 'code_generator.dart'; | 31 import 'code_generator.dart'; |
| 32 import 'js_field_storage.dart'; |
32 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; | 33 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; |
33 import 'js_metalet.dart'; | 34 import 'js_metalet.dart'; |
34 import 'js_printer.dart' show writeJsLibrary; | 35 import 'js_printer.dart' show writeJsLibrary; |
35 import 'side_effect_analysis.dart'; | 36 import 'side_effect_analysis.dart'; |
36 | 37 |
37 // Various dynamic helpers we call. | 38 // Various dynamic helpers we call. |
38 // If renaming these, make sure to check other places like the | 39 // If renaming these, make sure to check other places like the |
39 // dart_runtime.js file and comments. | 40 // dart_runtime.js file and comments. |
40 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 41 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
41 // import and generate calls to, rather than dart_runtime.js | 42 // import and generate calls to, rather than dart_runtime.js |
42 const DPUT = 'dput'; | 43 const DPUT = 'dput'; |
43 const DLOAD = 'dload'; | 44 const DLOAD = 'dload'; |
44 const DINDEX = 'dindex'; | 45 const DINDEX = 'dindex'; |
45 const DSETINDEX = 'dsetindex'; | 46 const DSETINDEX = 'dsetindex'; |
46 const DCALL = 'dcall'; | 47 const DCALL = 'dcall'; |
47 const DSEND = 'dsend'; | 48 const DSEND = 'dsend'; |
48 | 49 |
49 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 50 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
| 51 final CompilerOptions options; |
| 52 final TypeRules rules; |
50 final LibraryInfo libraryInfo; | 53 final LibraryInfo libraryInfo; |
51 final TypeRules rules; | |
52 | 54 |
53 /// The global extension method table. | 55 /// The global extension method table. |
54 final HashMap<String, List<InterfaceType>> _extensionMethods; | 56 final HashMap<String, List<InterfaceType>> _extensionMethods; |
55 | 57 |
56 final CompilerOptions options; | 58 /// Information that is precomputed for this library, indicates which fields |
| 59 /// need storage slots. |
| 60 final HashSet<FieldElement> _fieldsNeedingStorage; |
| 61 |
57 /// The variable for the target of the current `..` cascade expression. | 62 /// The variable for the target of the current `..` cascade expression. |
58 SimpleIdentifier _cascadeTarget; | 63 SimpleIdentifier _cascadeTarget; |
| 64 |
59 /// The variable for the current catch clause | 65 /// The variable for the current catch clause |
60 SimpleIdentifier _catchParameter; | 66 SimpleIdentifier _catchParameter; |
61 | 67 |
62 ClassDeclaration currentClass; | 68 ClassDeclaration currentClass; |
63 ConstantEvaluator _constEvaluator; | 69 ConstantEvaluator _constEvaluator; |
64 | 70 |
65 final _exports = new Set<String>(); | 71 final _exports = new Set<String>(); |
66 final _lazyFields = <VariableDeclaration>[]; | 72 final _lazyFields = <VariableDeclaration>[]; |
67 final _properties = <FunctionDeclaration>[]; | 73 final _properties = <FunctionDeclaration>[]; |
68 final _privateNames = new HashMap<String, JSTemporary>(); | 74 final _privateNames = new HashMap<String, JSTemporary>(); |
69 final _pendingPrivateNames = <JSTemporary>[]; | |
70 final _extensionMethodNames = new HashSet<String>(); | 75 final _extensionMethodNames = new HashSet<String>(); |
71 final _pendingExtensionMethodNames = <String>[]; | 76 final _pendingSymbols = <JS.Identifier>[]; |
72 final _temps = new HashMap<Element, JSTemporary>(); | 77 final _temps = new HashMap<Element, JSTemporary>(); |
73 | 78 |
74 /// The name for the library's exports inside itself. | 79 /// The name for the library's exports inside itself. |
75 /// This much be a constant because we interpolate it into template strings, | 80 /// This much be a constant because we interpolate it into template strings, |
76 /// and otherwise it would break caching for them. | 81 /// and otherwise it would break caching for them. |
77 /// `exports` was chosen as the most similar to ES module patterns. | 82 /// `exports` was chosen as the most similar to ES module patterns. |
78 final JSTemporary _exportsVar = new JSTemporary('exports'); | 83 final JSTemporary _exportsVar = new JSTemporary('exports'); |
79 final JSTemporary _namedArgTemp = new JSTemporary('opts'); | 84 final JSTemporary _namedArgTemp = new JSTemporary('opts'); |
80 | 85 |
81 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 86 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or |
82 /// [ClassTypeAlias]. | 87 /// [ClassTypeAlias]. |
83 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | 88 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); |
84 | 89 |
85 /// Memoized results of [_lazyClass]. | 90 /// Memoized results of [_lazyClass]. |
86 final _lazyClassMemo = new HashMap<Element, bool>(); | 91 final _lazyClassMemo = new HashMap<Element, bool>(); |
87 | 92 |
88 /// Memoized results of [_inLibraryCycle]. | 93 /// Memoized results of [_inLibraryCycle]. |
89 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | 94 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); |
90 | 95 |
91 JSCodegenVisitor( | 96 JSCodegenVisitor(this.options, this.rules, this.libraryInfo, |
92 this.libraryInfo, this.rules, this.options, this._extensionMethods); | 97 this._extensionMethods, this._fieldsNeedingStorage); |
93 | 98 |
94 LibraryElement get currentLibrary => libraryInfo.library; | 99 LibraryElement get currentLibrary => libraryInfo.library; |
95 TypeProvider get types => rules.provider; | 100 TypeProvider get types => rules.provider; |
96 | 101 |
97 JS.Program emitLibrary(LibraryUnit library) { | 102 JS.Program emitLibrary(LibraryUnit library) { |
98 String jsDefaultValue = null; | 103 String jsDefaultValue = null; |
99 | 104 |
100 // Modify the AST to make coercions explicit. | 105 // Modify the AST to make coercions explicit. |
101 new CoercionReifier(library, rules, options).reify(); | 106 new CoercionReifier(library, rules, options).reify(); |
102 | 107 |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ | 149 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ |
145 _exportsVar, | 150 _exportsVar, |
146 body, | 151 body, |
147 name, | 152 name, |
148 name, | 153 name, |
149 defaultValue | 154 defaultValue |
150 ]) | 155 ]) |
151 ]); | 156 ]); |
152 } | 157 } |
153 | 158 |
154 JS.Statement _initPrivateSymbol(JSTemporary tmp) => | 159 JS.Statement _initSymbol(JS.Identifier id) => |
155 js.statement('let # = $_SYMBOL(#);', [tmp, js.string(tmp.name, "'")]); | 160 js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
156 | |
157 JS.Statement _initExtensionMethodSymbol(String name) => js.statement( | |
158 'let # = $_SYMBOL(#);', [new JS.Identifier(name), js.string(name, "'")]); | |
159 | 161 |
160 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 162 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
161 // until we have better name tracking. | 163 // until we have better name tracking. |
162 String get _SYMBOL { | 164 String get _SYMBOL { |
163 var name = currentLibrary.name; | 165 var name = currentLibrary.name; |
164 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 166 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
165 return 'Symbol'; | 167 return 'Symbol'; |
166 } | 168 } |
167 | 169 |
168 @override | 170 @override |
169 JS.Statement visitCompilationUnit(CompilationUnit node) { | 171 JS.Statement visitCompilationUnit(CompilationUnit node) { |
170 var source = node.element.source; | 172 var source = node.element.source; |
171 | 173 |
172 _constEvaluator = new ConstantEvaluator(source, types); | 174 _constEvaluator = new ConstantEvaluator(source, types); |
173 | 175 |
174 // TODO(jmesserly): scriptTag, directives. | 176 // TODO(jmesserly): scriptTag, directives. |
175 var body = <JS.Statement>[]; | 177 var body = <JS.Statement>[]; |
176 for (var child in node.declarations) { | 178 for (var child in node.declarations) { |
177 // Attempt to group adjacent fields/properties. | 179 // Attempt to group adjacent fields/properties. |
178 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 180 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
179 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 181 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
180 | 182 |
181 var code = _visit(child); | 183 var code = _visit(child); |
182 | |
183 if (code != null) { | 184 if (code != null) { |
184 if (_pendingPrivateNames.isNotEmpty) { | 185 if (_pendingSymbols.isNotEmpty) { |
185 body.addAll(_pendingPrivateNames.map(_initPrivateSymbol)); | 186 body.addAll(_pendingSymbols.map(_initSymbol)); |
186 _pendingPrivateNames.clear(); | 187 _pendingSymbols.clear(); |
187 } | |
188 if (_pendingExtensionMethodNames.isNotEmpty) { | |
189 body.addAll( | |
190 _pendingExtensionMethodNames.map(_initExtensionMethodSymbol)); | |
191 _pendingExtensionMethodNames.clear(); | |
192 } | 188 } |
193 body.add(code); | 189 body.add(code); |
194 } | 190 } |
195 } | 191 } |
196 | 192 |
197 // Flush any unwritten fields/properties. | 193 // Flush any unwritten fields/properties. |
198 _flushLazyFields(body); | 194 _flushLazyFields(body); |
199 _flushLibraryProperties(body); | 195 _flushLibraryProperties(body); |
200 | 196 |
201 assert(_pendingPrivateNames.isEmpty); | 197 assert(_pendingSymbols.isEmpty); |
202 return _statement(body); | 198 return _statement(body); |
203 } | 199 } |
204 | 200 |
205 bool isPublic(String name) => !name.startsWith('_'); | 201 bool isPublic(String name) => !name.startsWith('_'); |
206 | 202 |
207 /// Conversions that we don't handle end up here. | 203 /// Conversions that we don't handle end up here. |
208 @override | 204 @override |
209 visitConversion(Conversion node) { | 205 visitConversion(Conversion node) { |
210 throw 'Unlowered conversion ${node.runtimeType}: $node'; | 206 throw 'Unlowered conversion ${node.runtimeType}: $node'; |
211 } | 207 } |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
309 // generate it correctly when we refer to it. | 305 // generate it correctly when we refer to it. |
310 if (isPublic(dartClassName)) _addExport(dartClassName); | 306 if (isPublic(dartClassName)) _addExport(dartClassName); |
311 return js.statement('let # = #;', [dartClassName, jsTypeName]); | 307 return js.statement('let # = #;', [dartClassName, jsTypeName]); |
312 } | 308 } |
313 return null; | 309 return null; |
314 } | 310 } |
315 | 311 |
316 @override | 312 @override |
317 JS.Statement visitClassDeclaration(ClassDeclaration node) { | 313 JS.Statement visitClassDeclaration(ClassDeclaration node) { |
318 // If we've already emitted this class, skip it. | 314 // If we've already emitted this class, skip it. |
319 var type = node.element.type; | 315 var classElem = node.element; |
320 if (_pendingClasses.remove(node.element) == null) return null; | 316 var type = classElem.type; |
| 317 if (_pendingClasses.remove(classElem) == null) return null; |
321 | 318 |
322 var jsName = getAnnotationValue(node, _isJsNameAnnotation); | 319 var jsName = getAnnotationValue(node, _isJsNameAnnotation); |
323 if (jsName != null) return _emitJsType(node.name.name, jsName); | 320 if (jsName != null) return _emitJsType(node.name.name, jsName); |
324 | 321 |
325 currentClass = node; | 322 currentClass = node; |
326 | 323 |
327 var ctors = <ConstructorDeclaration>[]; | 324 var ctors = <ConstructorDeclaration>[]; |
328 var fields = <FieldDeclaration>[]; | 325 var fields = <FieldDeclaration>[]; |
329 var staticFields = <FieldDeclaration>[]; | 326 var staticFields = <FieldDeclaration>[]; |
330 for (var member in node.members) { | 327 for (var member in node.members) { |
331 if (member is ConstructorDeclaration) { | 328 if (member is ConstructorDeclaration) { |
332 ctors.add(member); | 329 ctors.add(member); |
333 } else if (member is FieldDeclaration) { | 330 } else if (member is FieldDeclaration) { |
334 (member.isStatic ? staticFields : fields).add(member); | 331 (member.isStatic ? staticFields : fields).add(member); |
335 } | 332 } |
336 } | 333 } |
337 | 334 |
338 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), | 335 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
339 _classHeritage(node), _emitClassMethods(node, ctors, fields)); | 336 _classHeritage(node), _emitClassMethods(node, ctors, fields)); |
340 | 337 |
341 var body = | 338 var body = |
342 _finishClassMembers(node.element, classExpr, ctors, staticFields); | 339 _finishClassMembers(classElem, classExpr, ctors, fields, staticFields); |
343 currentClass = null; | 340 currentClass = null; |
344 | 341 |
345 return _finishClassDef(type, body); | 342 return _finishClassDef(type, body); |
346 } | 343 } |
347 | 344 |
348 @override | 345 @override |
349 JS.Statement visitEnumDeclaration(EnumDeclaration node) => | 346 JS.Statement visitEnumDeclaration(EnumDeclaration node) => |
350 _unimplementedCall("Unimplemented enum: $node").toStatement(); | 347 _unimplementedCall("Unimplemented enum: $node").toStatement(); |
351 | 348 |
352 /// Given a class element and body, complete the class declaration. | 349 /// Given a class element and body, complete the class declaration. |
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
543 return heritage; | 540 return heritage; |
544 } | 541 } |
545 | 542 |
546 List<JS.Method> _emitClassMethods(ClassDeclaration node, | 543 List<JS.Method> _emitClassMethods(ClassDeclaration node, |
547 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { | 544 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
548 var element = node.element; | 545 var element = node.element; |
549 var type = element.type; | 546 var type = element.type; |
550 var isObject = type.isObject; | 547 var isObject = type.isObject; |
551 var name = node.name.name; | 548 var name = node.name.name; |
552 | 549 |
553 var jsMethods = <JS.Method>[]; | |
554 // Iff no constructor is specified for a class C, it implicitly has a | 550 // Iff no constructor is specified for a class C, it implicitly has a |
555 // default constructor `C() : super() {}`, unless C is class Object. | 551 // default constructor `C() : super() {}`, unless C is class Object. |
| 552 var jsMethods = <JS.Method>[]; |
556 if (ctors.isEmpty && !isObject) { | 553 if (ctors.isEmpty && !isObject) { |
557 jsMethods.add(_emitImplicitConstructor(node, name, fields)); | 554 jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
558 } | 555 } |
| 556 |
559 for (var member in node.members) { | 557 for (var member in node.members) { |
560 if (member is ConstructorDeclaration) { | 558 if (member is ConstructorDeclaration) { |
561 jsMethods.add(_emitConstructor(member, name, fields, isObject)); | 559 jsMethods.add(_emitConstructor(member, name, fields, isObject)); |
562 } else if (member is MethodDeclaration) { | 560 } else if (member is MethodDeclaration) { |
563 jsMethods.add(_emitMethodDeclaration(type, member)); | 561 jsMethods.add(_emitMethodDeclaration(type, member)); |
564 } | 562 } |
565 } | 563 } |
566 | 564 |
567 // Support for adapting dart:core Iterator/Iterable to ES6 versions. | 565 // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
568 // This lets them use for-of loops transparently. | 566 // This lets them use for-of loops transparently. |
569 // https://github.com/lukehoban/es6features#iterators--forof | 567 // https://github.com/lukehoban/es6features#iterators--forof |
570 if (element.library.isDartCore && element.name == 'Iterable') { | 568 if (element.library.isDartCore && element.name == 'Iterable') { |
571 JS.Fun body = js.call('''function() { | 569 JS.Fun body = js.call('''function() { |
572 var iterator = this.iterator; | 570 var iterator = this.iterator; |
573 return { | 571 return { |
574 next() { | 572 next() { |
575 var done = iterator.moveNext(); | 573 var done = iterator.moveNext(); |
576 return { done: done, current: done ? void 0 : iterator.current }; | 574 return { done: done, current: done ? void 0 : iterator.current }; |
577 } | 575 } |
578 }; | 576 }; |
579 }'''); | 577 }'''); |
580 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); | 578 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); |
581 } | 579 } |
582 return jsMethods.where((m) => m != null).toList(growable: false); | 580 return jsMethods.where((m) => m != null).toList(growable: false); |
583 } | 581 } |
584 | 582 |
585 /// Emit class members that need to come after the class declaration, such | 583 /// Emit class members that need to come after the class declaration, such |
586 /// as static fields. See [_emitClassMethods] for things that are emitted | 584 /// as static fields. See [_emitClassMethods] for things that are emitted |
587 /// insite the ES6 `class { ... }` node. | 585 /// inside the ES6 `class { ... }` node. |
588 JS.Statement _finishClassMembers(ClassElement classElem, | 586 JS.Statement _finishClassMembers(ClassElement classElem, |
589 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, | 587 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, |
590 List<FieldDeclaration> staticFields) { | 588 List<FieldDeclaration> fields, List<FieldDeclaration> staticFields) { |
591 var name = classElem.name; | 589 var name = classElem.name; |
592 | |
593 var body = <JS.Statement>[]; | 590 var body = <JS.Statement>[]; |
594 body.add(new JS.ClassDeclaration(cls)); | 591 body.add(new JS.ClassDeclaration(cls)); |
595 | 592 |
596 // Interfaces | 593 // Interfaces |
597 if (classElem.interfaces.isNotEmpty) { | 594 if (classElem.interfaces.isNotEmpty) { |
598 body.add(js.statement('#[dart.implements] = () => #;', [ | 595 body.add(js.statement('#[dart.implements] = () => #;', [ |
599 name, | 596 name, |
600 new JS.ArrayInitializer( | 597 new JS.ArrayInitializer( |
601 classElem.interfaces.map(_emitTypeName).toList()) | 598 classElem.interfaces.map(_emitTypeName).toList()) |
602 ])); | 599 ])); |
603 } | 600 } |
604 | 601 |
605 // Named constructors | 602 // Named constructors |
606 for (ConstructorDeclaration member in ctors) { | 603 for (ConstructorDeclaration member in ctors) { |
607 if (member.name != null) { | 604 if (member.name != null) { |
608 body.add(js.statement('dart.defineNamedConstructor(#, #);', [ | 605 body.add(js.statement('dart.defineNamedConstructor(#, #);', [ |
609 name, | 606 name, |
610 _emitMemberName(member.name.name, isStatic: true) | 607 _emitMemberName(member.name.name, isStatic: true) |
611 ])); | 608 ])); |
612 } | 609 } |
613 } | 610 } |
614 | 611 |
| 612 // Instance fields, if they override getter/setter pairs |
| 613 for (FieldDeclaration member in fields) { |
| 614 for (VariableDeclaration fieldDecl in member.fields.variables) { |
| 615 var field = fieldDecl.element; |
| 616 if (_fieldsNeedingStorage.contains(field)) { |
| 617 body.add(_overrideField(field)); |
| 618 } |
| 619 } |
| 620 } |
| 621 |
615 // Static fields | 622 // Static fields |
616 var lazyStatics = <VariableDeclaration>[]; | 623 var lazyStatics = <VariableDeclaration>[]; |
617 for (FieldDeclaration member in staticFields) { | 624 for (FieldDeclaration member in staticFields) { |
618 for (VariableDeclaration field in member.fields.variables) { | 625 for (VariableDeclaration field in member.fields.variables) { |
619 var fieldName = field.name.name; | 626 var fieldName = field.name.name; |
620 if (field.isConst || _isFieldInitConstant(field)) { | 627 if (field.isConst || _isFieldInitConstant(field)) { |
621 var init = _visit(field.initializer); | 628 var init = _visit(field.initializer); |
622 if (init == null) init = new JS.LiteralNull(); | 629 if (init == null) init = new JS.LiteralNull(); |
623 body.add(js.statement('#.# = #;', [name, fieldName, init])); | 630 body.add(js.statement('#.# = #;', [name, fieldName, init])); |
624 } else { | 631 } else { |
625 lazyStatics.add(field); | 632 lazyStatics.add(field); |
626 } | 633 } |
627 } | 634 } |
628 } | 635 } |
629 var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); | 636 var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); |
630 if (lazy != null) body.add(lazy); | 637 if (lazy != null) body.add(lazy); |
631 | 638 |
632 return _statement(body); | 639 return _statement(body); |
633 } | 640 } |
634 | 641 |
| 642 JS.Statement _overrideField(FieldElement e) { |
| 643 var cls = e.enclosingElement; |
| 644 return js.statement('dart.virtualField(#, #)', [ |
| 645 cls.name, |
| 646 _emitMemberName(e.name, type: cls.type) |
| 647 ]); |
| 648 } |
| 649 |
635 /// Generates the implicit default constructor for class C of the form | 650 /// Generates the implicit default constructor for class C of the form |
636 /// `C() : super() {}`. | 651 /// `C() : super() {}`. |
637 JS.Method _emitImplicitConstructor( | 652 JS.Method _emitImplicitConstructor( |
638 ClassDeclaration node, String name, List<FieldDeclaration> fields) { | 653 ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
639 // If we don't have a method body, skip this. | 654 // If we don't have a method body, skip this. |
640 if (fields.isEmpty) return null; | 655 if (fields.isEmpty) return null; |
641 | 656 |
642 dynamic body = _initializeFields(fields); | 657 dynamic body = _initializeFields(fields); |
643 var superCall = _superConstructorCall(node); | 658 var superCall = _superConstructorCall(node); |
644 if (superCall != null) body = [[body, superCall]]; | 659 if (superCall != null) body = [[body, superCall]]; |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
771 var args = node != null ? _visit(node.argumentList) : []; | 786 var args = node != null ? _visit(node.argumentList) : []; |
772 return js.statement('super.#(#);', [name, args])..sourceInformation = node; | 787 return js.statement('super.#(#);', [name, args])..sourceInformation = node; |
773 } | 788 } |
774 | 789 |
775 /// Initialize fields. They follow the sequence: | 790 /// Initialize fields. They follow the sequence: |
776 /// | 791 /// |
777 /// 1. field declaration initializer if non-const, | 792 /// 1. field declaration initializer if non-const, |
778 /// 2. field initializing parameters, | 793 /// 2. field initializing parameters, |
779 /// 3. constructor field initializers, | 794 /// 3. constructor field initializers, |
780 /// 4. initialize fields not covered in 1-3 | 795 /// 4. initialize fields not covered in 1-3 |
781 JS.Statement _initializeFields(List<FieldDeclaration> fields, | 796 JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls, |
782 [FormalParameterList parameters, | 797 [FormalParameterList parameters, |
783 NodeList<ConstructorInitializer> initializers]) { | 798 NodeList<ConstructorInitializer> initializers]) { |
784 var body = <JS.Statement>[]; | |
785 | 799 |
786 // Run field initializers if they can have side-effects. | 800 // Run field initializers if they can have side-effects. |
| 801 var fields = new Map<FieldElement, JS.Expression>(); |
787 var unsetFields = new Map<FieldElement, VariableDeclaration>(); | 802 var unsetFields = new Map<FieldElement, VariableDeclaration>(); |
788 for (var declaration in fields) { | 803 for (var declaration in fieldDecls) { |
789 for (var field in declaration.fields.variables) { | 804 for (var fieldNode in declaration.fields.variables) { |
790 if (_isFieldInitConstant(field)) { | 805 var element = fieldNode.element; |
791 unsetFields[field.element] = field; | 806 if (_isFieldInitConstant(fieldNode)) { |
| 807 unsetFields[element] = fieldNode; |
792 } else { | 808 } else { |
793 body.add(js.statement( | 809 fields[element] = _visitInitializer(fieldNode); |
794 '# = #;', [_visit(field.name), _visitInitializer(field)])); | |
795 } | 810 } |
796 } | 811 } |
797 } | 812 } |
798 | 813 |
799 // Initialize fields from `this.fieldName` parameters. | 814 // Initialize fields from `this.fieldName` parameters. |
800 if (parameters != null) { | 815 if (parameters != null) { |
801 for (var p in parameters.parameters) { | 816 for (var p in parameters.parameters) { |
802 if (p is DefaultFormalParameter) p = p.parameter; | 817 var element = p.element; |
803 if (p is FieldFormalParameter) { | 818 if (element is FieldFormalParameterElement) { |
804 var field = (p.element as FieldFormalParameterElement).field; | 819 fields[element.field] = _visit(p); |
805 // Use the getter to initialize the field. This is a bit strange, but | |
806 // final fields don't have a setter element that we could use instead. | |
807 | |
808 var memberName = | |
809 _emitMemberName(field.name, type: field.enclosingElement.type); | |
810 body.add(js.statement('this.# = #;', [memberName, _visit(p)])); | |
811 unsetFields.remove(field); | |
812 } | 820 } |
813 } | 821 } |
814 } | 822 } |
815 | 823 |
816 // Run constructor field initializers such as `: foo = bar.baz` | 824 // Run constructor field initializers such as `: foo = bar.baz` |
817 if (initializers != null) { | 825 if (initializers != null) { |
818 for (var init in initializers) { | 826 for (var init in initializers) { |
819 if (init is ConstructorFieldInitializer) { | 827 if (init is ConstructorFieldInitializer) { |
820 body.add(js.statement( | 828 fields[init.fieldName.staticElement] = _visit(init.expression); |
821 '# = #;', [_visit(init.fieldName), _visit(init.expression)])); | |
822 unsetFields.remove(init.fieldName.staticElement); | |
823 } | 829 } |
824 } | 830 } |
825 } | 831 } |
826 | 832 |
| 833 for (var f in fields.keys) unsetFields.remove(f); |
| 834 |
827 // Initialize all remaining fields | 835 // Initialize all remaining fields |
828 unsetFields.forEach((field, fieldNode) { | 836 unsetFields.forEach((element, fieldNode) { |
829 JS.Expression value; | 837 JS.Expression value; |
830 if (fieldNode.initializer != null) { | 838 if (fieldNode.initializer != null) { |
831 value = _visit(fieldNode.initializer); | 839 value = _visit(fieldNode.initializer); |
832 } else { | 840 } else { |
833 var type = rules.elementType(field); | 841 var type = rules.elementType(element); |
834 value = new JS.LiteralNull(); | 842 value = new JS.LiteralNull(); |
835 if (rules.maybeNonNullableType(type)) { | 843 if (rules.maybeNonNullableType(type)) { |
836 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); | 844 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); |
837 } | 845 } |
838 } | 846 } |
839 var memberName = | 847 fields[element] = value; |
840 _emitMemberName(field.name, type: field.enclosingElement.type); | |
841 body.add(js.statement('this.# = #;', [memberName, value])); | |
842 }); | 848 }); |
843 | 849 |
| 850 var body = <JS.Statement>[]; |
| 851 fields.forEach((FieldElement e, JS.Expression initialValue) { |
| 852 var access = _emitMemberName(e.name, type: e.enclosingElement.type); |
| 853 body.add(js.statement('this.# = #;', [access, initialValue])); |
| 854 }); |
844 return _statement(body); | 855 return _statement(body); |
845 } | 856 } |
846 | 857 |
847 FormalParameterList _parametersOf(node) { | 858 FormalParameterList _parametersOf(node) { |
848 // Note: ConstructorDeclaration is intentionally skipped here so we can | 859 // Note: ConstructorDeclaration is intentionally skipped here so we can |
849 // emit the argument initializers in a different place. | 860 // emit the argument initializers in a different place. |
850 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we | 861 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we |
851 // could handle argument initializers more consistently in a separate | 862 // could handle argument initializers more consistently in a separate |
852 // lowering pass. | 863 // lowering pass. |
853 if (node is MethodDeclaration) return node.parameters; | 864 if (node is MethodDeclaration) return node.parameters; |
(...skipping 1344 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2198 /// for this transformation to happen, otherwise binary minus is assumed. | 2209 /// for this transformation to happen, otherwise binary minus is assumed. |
2199 /// | 2210 /// |
2200 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2211 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
2201 /// helper, that checks for null. The user defined method is called '=='. | 2212 /// helper, that checks for null. The user defined method is called '=='. |
2202 /// | 2213 /// |
2203 JS.Expression _emitMemberName(String name, | 2214 JS.Expression _emitMemberName(String name, |
2204 {DartType type, bool unary: false, bool isStatic: false}) { | 2215 {DartType type, bool unary: false, bool isStatic: false}) { |
2205 if (name.startsWith('_')) { | 2216 if (name.startsWith('_')) { |
2206 return _privateNames.putIfAbsent(name, () { | 2217 return _privateNames.putIfAbsent(name, () { |
2207 var t = new JSTemporary(name); | 2218 var t = new JSTemporary(name); |
2208 _pendingPrivateNames.add(t); | 2219 _pendingSymbols.add(t); |
2209 return t; | 2220 return t; |
2210 }); | 2221 }); |
2211 } | 2222 } |
| 2223 |
2212 // Check for extension method: | 2224 // Check for extension method: |
2213 var extLibrary = _findExtensionLibrary(name, type); | 2225 var extLibrary = _findExtensionLibrary(name, type); |
2214 | 2226 |
2215 if (name == '[]') { | 2227 if (name == '[]') { |
2216 name = 'get'; | 2228 name = 'get'; |
2217 } else if (name == '[]=') { | 2229 } else if (name == '[]=') { |
2218 name = 'set'; | 2230 name = 'set'; |
2219 } else if (name == '-' && unary) { | 2231 } else if (name == '-' && unary) { |
2220 name = 'unary-'; | 2232 name = 'unary-'; |
2221 } | 2233 } |
2222 | 2234 |
2223 if (isStatic && invalidJSStaticMethodName(name)) { | 2235 if (isStatic && invalidJSStaticMethodName(name)) { |
2224 // Choose an string name. Use an invalid identifier so it won't conflict | 2236 // Choose an string name. Use an invalid identifier so it won't conflict |
2225 // with any valid member names. | 2237 // with any valid member names. |
2226 // TODO(jmesserly): this works around the problem, but I'm pretty sure we | 2238 // TODO(jmesserly): this works around the problem, but I'm pretty sure we |
2227 // don't need it, as static methods seemed to work. The only concrete | 2239 // don't need it, as static methods seemed to work. The only concrete |
2228 // issue we saw was in the defineNamedConstructor helper function. | 2240 // issue we saw was in the defineNamedConstructor helper function. |
2229 name = '$name*'; | 2241 name = '$name*'; |
2230 } | 2242 } |
2231 | 2243 |
2232 if (extLibrary != null) { | 2244 if (extLibrary != null) { |
2233 return js.call('#.#', [ | 2245 return _extensionMethodName(name, extLibrary); |
2234 _libraryName(extLibrary), | |
2235 _propertyName(_addExtensionMethodName(name, extLibrary)) | |
2236 ]); | |
2237 } | 2246 } |
2238 | 2247 |
2239 return _propertyName(name); | 2248 return _propertyName(name); |
2240 } | 2249 } |
2241 | 2250 |
2242 LibraryElement _findExtensionLibrary(String name, DartType type) { | 2251 LibraryElement _findExtensionLibrary(String name, DartType type) { |
2243 if (type is! InterfaceType) return null; | 2252 if (type is! InterfaceType) return null; |
2244 | 2253 |
2245 var extLibrary = null; | 2254 var extLibrary = null; |
2246 var extensionTypes = _extensionMethods[name]; | 2255 var extensionTypes = _extensionMethods[name]; |
2247 if (extensionTypes != null) { | 2256 if (extensionTypes != null) { |
2248 // Normalize the type to ignore generics. | 2257 // Normalize the type to ignore generics. |
2249 type = fillDynamicTypeArgs(type, types); | 2258 type = fillDynamicTypeArgs(type, types); |
2250 for (var t in extensionTypes) { | 2259 for (var t in extensionTypes) { |
2251 if (rules.isSubTypeOf(type, t)) { | 2260 if (rules.isSubTypeOf(type, t)) { |
2252 assert(extLibrary == null || extLibrary == t.element.library); | 2261 assert(extLibrary == null || extLibrary == t.element.library); |
2253 extLibrary = t.element.library; | 2262 extLibrary = t.element.library; |
2254 } | 2263 } |
2255 } | 2264 } |
2256 } | 2265 } |
2257 return extLibrary; | 2266 return extLibrary; |
2258 } | 2267 } |
2259 | 2268 |
2260 String _addExtensionMethodName(String name, LibraryElement extLibrary) { | 2269 JS.Expression _extensionMethodName(String name, LibraryElement library) { |
2261 var extensionMethodName = '\$$name'; | 2270 var extName = '\$$name'; |
2262 if (extLibrary == currentLibrary) { | 2271 if (library == currentLibrary) { |
2263 // TODO(jacobr): need to do a better job ensuring that extension method | 2272 // TODO(jacobr): need to do a better job ensuring that extension method |
2264 // name symbols do not conflict with other symbols before we can let | 2273 // name symbols do not conflict with other symbols before we can let |
2265 // user defined libraries define extension methods. | 2274 // user defined libraries define extension methods. |
2266 if (_extensionMethodNames.add(extensionMethodName)) { | 2275 if (_extensionMethodNames.add(extName)) { |
2267 _pendingExtensionMethodNames.add(extensionMethodName); | 2276 _pendingSymbols.add(new JS.Identifier(extName)); |
2268 _addExport(extensionMethodName); | 2277 _addExport(extName); |
2269 } | 2278 } |
2270 } | 2279 } |
2271 return extensionMethodName; | 2280 return js.call('#.#', [_libraryName(library), _propertyName(extName)]); |
2272 } | 2281 } |
2273 | 2282 |
2274 bool _externalOrNative(node) => | 2283 bool _externalOrNative(node) => |
2275 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; | 2284 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
2276 | 2285 |
2277 FunctionBody _functionBody(node) => | 2286 FunctionBody _functionBody(node) => |
2278 node is FunctionDeclaration ? node.functionExpression.body : node.body; | 2287 node is FunctionDeclaration ? node.functionExpression.body : node.body; |
2279 | 2288 |
2280 /// Choose a canonical name from the library element. | 2289 /// Choose a canonical name from the library element. |
2281 /// This never uses the library's name (the identifier in the `library` | 2290 /// This never uses the library's name (the identifier in the `library` |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2315 ..addAll(e.accessors.map((m) => m.name)); | 2324 ..addAll(e.accessors.map((m) => m.name)); |
2316 for (var name in names) { | 2325 for (var name in names) { |
2317 _extensionMethods.putIfAbsent(name, () => []).add(type); | 2326 _extensionMethods.putIfAbsent(name, () => []).add(type); |
2318 } | 2327 } |
2319 } | 2328 } |
2320 } | 2329 } |
2321 | 2330 |
2322 TypeProvider get types => rules.provider; | 2331 TypeProvider get types => rules.provider; |
2323 | 2332 |
2324 String generateLibrary(LibraryUnit unit, LibraryInfo info) { | 2333 String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
2325 var codegen = new JSCodegenVisitor(info, rules, options, _extensionMethods); | 2334 var fields = findFieldsNeedingStorage(unit); |
| 2335 var codegen = |
| 2336 new JSCodegenVisitor(options, rules, info, _extensionMethods, fields); |
2326 var module = codegen.emitLibrary(unit); | 2337 var module = codegen.emitLibrary(unit); |
2327 var dir = path.join(outDir, jsOutputPath(info, root)); | 2338 var dir = path.join(outDir, jsOutputPath(info, root)); |
2328 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); | 2339 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); |
2329 } | 2340 } |
2330 } | 2341 } |
2331 | 2342 |
2332 /// Choose a canonical name from the library element. | 2343 /// Choose a canonical name from the library element. |
2333 /// This never uses the library's name (the identifier in the `library` | 2344 /// This never uses the library's name (the identifier in the `library` |
2334 /// declaration) as it doesn't have any meaningful rules enforced. | 2345 /// declaration) as it doesn't have any meaningful rules enforced. |
2335 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 2346 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
(...skipping 22 matching lines...) Expand all Loading... |
2358 return filepath; | 2369 return filepath; |
2359 } | 2370 } |
2360 | 2371 |
2361 // TODO(jmesserly): validate the library. See issue #135. | 2372 // TODO(jmesserly): validate the library. See issue #135. |
2362 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2373 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2363 | 2374 |
2364 // TODO(jacobr): we would like to do something like the following | 2375 // TODO(jacobr): we would like to do something like the following |
2365 // but we don't have summary support yet. | 2376 // but we don't have summary support yet. |
2366 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2377 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2367 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2378 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |