OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dev_compiler.src.codegen.js_codegen; | 5 library dev_compiler.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:collection' show HashSet, HashMap; | 7 import 'dart:collection' show HashSet, HashMap; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
11 import 'package:analyzer/src/generated/constant.dart'; | 11 import 'package:analyzer/src/generated/constant.dart'; |
12 import 'package:analyzer/src/generated/element.dart'; | 12 import 'package:analyzer/src/generated/element.dart'; |
13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | 13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
14 import 'package:analyzer/src/generated/scanner.dart' | 14 import 'package:analyzer/src/generated/scanner.dart' |
15 show StringToken, Token, TokenType; | 15 show StringToken, Token, TokenType; |
16 import 'package:path/path.dart' as path; | 16 import 'package:path/path.dart' as path; |
17 | 17 |
18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; | 18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; |
19 | 19 |
20 // TODO(jmesserly): import from its own package | 20 // TODO(jmesserly): import from its own package |
21 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 21 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
22 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 22 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
23 | 23 |
24 import 'package:dev_compiler/src/checker/rules.dart'; | 24 import 'package:dev_compiler/src/checker/rules.dart'; |
25 import 'package:dev_compiler/src/info.dart'; | 25 import 'package:dev_compiler/src/info.dart'; |
26 import 'package:dev_compiler/src/options.dart'; | 26 import 'package:dev_compiler/src/options.dart'; |
27 import 'package:dev_compiler/src/utils.dart'; | 27 import 'package:dev_compiler/src/utils.dart'; |
28 | 28 |
29 import 'code_generator.dart'; | 29 import 'code_generator.dart'; |
| 30 import 'js_field_storage.dart' show findFieldsNeedingStorage; |
30 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; | 31 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; |
31 import 'js_metalet.dart'; | 32 import 'js_metalet.dart'; |
32 import 'js_printer.dart' show writeJsLibrary; | 33 import 'js_printer.dart' show writeJsLibrary; |
33 import 'side_effect_analysis.dart'; | 34 import 'side_effect_analysis.dart'; |
34 | 35 |
35 // Various dynamic helpers we call. | 36 // Various dynamic helpers we call. |
36 // If renaming these, make sure to check other places like the | 37 // If renaming these, make sure to check other places like the |
37 // dart_runtime.js file and comments. | 38 // dart_runtime.js file and comments. |
38 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 39 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
39 // import and generate calls to, rather than dart_runtime.js | 40 // import and generate calls to, rather than dart_runtime.js |
40 const DPUT = 'dput'; | 41 const DPUT = 'dput'; |
41 const DLOAD = 'dload'; | 42 const DLOAD = 'dload'; |
42 const DINDEX = 'dindex'; | 43 const DINDEX = 'dindex'; |
43 const DSETINDEX = 'dsetindex'; | 44 const DSETINDEX = 'dsetindex'; |
44 const DCALL = 'dcall'; | 45 const DCALL = 'dcall'; |
45 const DSEND = 'dsend'; | 46 const DSEND = 'dsend'; |
46 | 47 |
47 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 48 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
48 final LibraryInfo libraryInfo; | 49 final LibraryInfo libraryInfo; |
49 final TypeRules rules; | 50 final TypeRules rules; |
50 | 51 |
51 /// The global extension method table. | 52 /// The global extension method table. |
52 final HashMap<String, List<InterfaceType>> _extensionMethods; | 53 final HashMap<String, List<InterfaceType>> _extensionMethods; |
53 | 54 |
| 55 /// Information that is precomputed for this library, indicates which fields |
| 56 /// need storage slots. See [_fieldStorage] for more explanation. |
| 57 final HashSet<FieldElement> _fieldNeedsStorage; |
| 58 |
54 /// The variable for the target of the current `..` cascade expression. | 59 /// The variable for the target of the current `..` cascade expression. |
55 SimpleIdentifier _cascadeTarget; | 60 SimpleIdentifier _cascadeTarget; |
| 61 |
56 /// The variable for the current catch clause | 62 /// The variable for the current catch clause |
57 SimpleIdentifier _catchParameter; | 63 SimpleIdentifier _catchParameter; |
58 | 64 |
59 ClassDeclaration currentClass; | 65 ClassDeclaration currentClass; |
60 ConstantEvaluator _constEvaluator; | 66 ConstantEvaluator _constEvaluator; |
61 | 67 |
62 final _exports = new Set<String>(); | 68 final _exports = new Set<String>(); |
63 final _lazyFields = <VariableDeclaration>[]; | 69 final _lazyFields = <VariableDeclaration>[]; |
64 final _properties = <FunctionDeclaration>[]; | 70 final _properties = <FunctionDeclaration>[]; |
65 final _privateNames = new HashMap<String, JSTemporary>(); | 71 final _privateNames = new HashMap<String, JSTemporary>(); |
66 final _pendingPrivateNames = <JSTemporary>[]; | |
67 final _extensionMethodNames = new HashSet<String>(); | 72 final _extensionMethodNames = new HashSet<String>(); |
68 final _pendingExtensionMethodNames = <String>[]; | 73 final _pendingSymbols = <JS.Identifier>[]; |
69 final _temps = new HashMap<Element, JSTemporary>(); | 74 final _temps = new HashMap<Element, JSTemporary>(); |
70 | 75 |
71 /// The name for the library's exports inside itself. | 76 /// The name for the library's exports inside itself. |
72 /// This much be a constant because we interpolate it into template strings, | 77 /// This much be a constant because we interpolate it into template strings, |
73 /// and otherwise it would break caching for them. | 78 /// and otherwise it would break caching for them. |
74 /// `exports` was chosen as the most similar to ES module patterns. | 79 /// `exports` was chosen as the most similar to ES module patterns. |
75 final JSTemporary _exportsVar = new JSTemporary('exports'); | 80 final JSTemporary _exportsVar = new JSTemporary('exports'); |
76 final JSTemporary _namedArgTemp = new JSTemporary('opts'); | 81 final JSTemporary _namedArgTemp = new JSTemporary('opts'); |
77 | 82 |
78 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 83 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or |
79 /// [ClassTypeAlias]. | 84 /// [ClassTypeAlias]. |
80 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | 85 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); |
81 | 86 |
82 /// Memoized results of [_lazyClass]. | 87 /// Memoized results of [_lazyClass]. |
83 final _lazyClassMemo = new HashMap<Element, bool>(); | 88 final _lazyClassMemo = new HashMap<Element, bool>(); |
84 | 89 |
85 /// Memoized results of [_inLibraryCycle]. | 90 /// Memoized results of [_inLibraryCycle]. |
86 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | 91 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); |
87 | 92 |
88 JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods); | 93 JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods, |
| 94 this._fieldNeedsStorage); |
89 | 95 |
90 LibraryElement get currentLibrary => libraryInfo.library; | 96 LibraryElement get currentLibrary => libraryInfo.library; |
91 TypeProvider get types => rules.provider; | 97 TypeProvider get types => rules.provider; |
92 | 98 |
93 JS.Program emitLibrary(LibraryUnit library) { | 99 JS.Program emitLibrary(LibraryUnit library) { |
94 String jsDefaultValue = null; | 100 String jsDefaultValue = null; |
95 var unit = library.library; | 101 var unit = library.library; |
96 if (unit.directives.isNotEmpty) { | 102 if (unit.directives.isNotEmpty) { |
97 var libraryDir = unit.directives.first; | 103 var libraryDir = unit.directives.first; |
98 if (libraryDir is LibraryDirective) { | 104 if (libraryDir is LibraryDirective) { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ | 142 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ |
137 _exportsVar, | 143 _exportsVar, |
138 body, | 144 body, |
139 name, | 145 name, |
140 name, | 146 name, |
141 defaultValue | 147 defaultValue |
142 ]) | 148 ]) |
143 ]); | 149 ]); |
144 } | 150 } |
145 | 151 |
146 JS.Statement _initPrivateSymbol(JSTemporary tmp) => | 152 JS.Statement _initSymbol(JS.Identifier id) => |
147 js.statement('let # = $_SYMBOL(#);', [tmp, js.string(tmp.name, "'")]); | 153 js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
148 | |
149 JS.Statement _initExtensionMethodSymbol(String name) => js.statement( | |
150 'let # = $_SYMBOL(#);', [new JS.Identifier(name), js.string(name, "'")]); | |
151 | 154 |
152 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 155 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
153 // until we have better name tracking. | 156 // until we have better name tracking. |
154 String get _SYMBOL { | 157 String get _SYMBOL { |
155 var name = currentLibrary.name; | 158 var name = currentLibrary.name; |
156 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 159 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
157 return 'Symbol'; | 160 return 'Symbol'; |
158 } | 161 } |
159 | 162 |
160 @override | 163 @override |
161 JS.Statement visitCompilationUnit(CompilationUnit node) { | 164 JS.Statement visitCompilationUnit(CompilationUnit node) { |
162 var source = node.element.source; | 165 var source = node.element.source; |
163 | 166 |
164 _constEvaluator = new ConstantEvaluator(source, types); | 167 _constEvaluator = new ConstantEvaluator(source, types); |
165 | 168 |
166 // TODO(jmesserly): scriptTag, directives. | 169 // TODO(jmesserly): scriptTag, directives. |
167 var body = <JS.Statement>[]; | 170 var body = <JS.Statement>[]; |
168 for (var child in node.declarations) { | 171 for (var child in node.declarations) { |
169 // Attempt to group adjacent fields/properties. | 172 // Attempt to group adjacent fields/properties. |
170 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 173 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
171 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 174 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
172 | 175 |
173 var code = _visit(child); | 176 var code = _visit(child); |
174 | |
175 if (code != null) { | 177 if (code != null) { |
176 if (_pendingPrivateNames.isNotEmpty) { | 178 if (_pendingSymbols.isNotEmpty) { |
177 body.addAll(_pendingPrivateNames.map(_initPrivateSymbol)); | 179 body.addAll(_pendingSymbols.map(_initSymbol)); |
178 _pendingPrivateNames.clear(); | 180 _pendingSymbols.clear(); |
179 } | |
180 if (_pendingExtensionMethodNames.isNotEmpty) { | |
181 body.addAll( | |
182 _pendingExtensionMethodNames.map(_initExtensionMethodSymbol)); | |
183 _pendingExtensionMethodNames.clear(); | |
184 } | 181 } |
185 body.add(code); | 182 body.add(code); |
186 } | 183 } |
187 } | 184 } |
188 | 185 |
189 // Flush any unwritten fields/properties. | 186 // Flush any unwritten fields/properties. |
190 _flushLazyFields(body); | 187 _flushLazyFields(body); |
191 _flushLibraryProperties(body); | 188 _flushLibraryProperties(body); |
192 | 189 |
193 assert(_pendingPrivateNames.isEmpty); | 190 assert(_pendingSymbols.isEmpty); |
194 return _statement(body); | 191 return _statement(body); |
195 } | 192 } |
196 | 193 |
197 bool isPublic(String name) => !name.startsWith('_'); | 194 bool isPublic(String name) => !name.startsWith('_'); |
198 | 195 |
199 /// Conversions that we don't handle end up here. | 196 /// Conversions that we don't handle end up here. |
200 @override | 197 @override |
201 visitConversion(Conversion node) { | 198 visitConversion(Conversion node) { |
202 var from = node.baseType; | 199 var from = node.baseType; |
203 var to = node.convertedType; | 200 var to = node.convertedType; |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
332 if (member is ConstructorDeclaration) { | 329 if (member is ConstructorDeclaration) { |
333 ctors.add(member); | 330 ctors.add(member); |
334 } else if (member is FieldDeclaration) { | 331 } else if (member is FieldDeclaration) { |
335 (member.isStatic ? staticFields : fields).add(member); | 332 (member.isStatic ? staticFields : fields).add(member); |
336 } | 333 } |
337 } | 334 } |
338 | 335 |
339 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), | 336 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
340 _classHeritage(node), _emitClassMethods(node, ctors, fields)); | 337 _classHeritage(node), _emitClassMethods(node, ctors, fields)); |
341 | 338 |
342 var body = | 339 var body = _finishClassMembers( |
343 _finishClassMembers(node.element, classExpr, ctors, staticFields); | 340 node.element, classExpr, ctors, fields, staticFields); |
344 currentClass = null; | 341 currentClass = null; |
345 | 342 |
346 return _finishClassDef(type, body); | 343 return _finishClassDef(type, body); |
347 } | 344 } |
348 | 345 |
349 @override | 346 @override |
350 JS.Statement visitEnumDeclaration(EnumDeclaration node) => | 347 JS.Statement visitEnumDeclaration(EnumDeclaration node) => |
351 _unimplementedCall("Unimplemented enum: $node").toStatement(); | 348 _unimplementedCall("Unimplemented enum: $node").toStatement(); |
352 | 349 |
353 /// Given a class element and body, complete the class declaration. | 350 /// Given a class element and body, complete the class declaration. |
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
544 return heritage; | 541 return heritage; |
545 } | 542 } |
546 | 543 |
547 List<JS.Method> _emitClassMethods(ClassDeclaration node, | 544 List<JS.Method> _emitClassMethods(ClassDeclaration node, |
548 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { | 545 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
549 var element = node.element; | 546 var element = node.element; |
550 var type = element.type; | 547 var type = element.type; |
551 var isObject = type.isObject; | 548 var isObject = type.isObject; |
552 var name = node.name.name; | 549 var name = node.name.name; |
553 | 550 |
554 var jsMethods = <JS.Method>[]; | |
555 // Iff no constructor is specified for a class C, it implicitly has a | 551 // Iff no constructor is specified for a class C, it implicitly has a |
556 // default constructor `C() : super() {}`, unless C is class Object. | 552 // default constructor `C() : super() {}`, unless C is class Object. |
| 553 var jsMethods = <JS.Method>[]; |
557 if (ctors.isEmpty && !isObject) { | 554 if (ctors.isEmpty && !isObject) { |
558 jsMethods.add(_emitImplicitConstructor(node, name, fields)); | 555 jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
559 } | 556 } |
| 557 |
560 for (var member in node.members) { | 558 for (var member in node.members) { |
561 if (member is ConstructorDeclaration) { | 559 if (member is ConstructorDeclaration) { |
562 jsMethods.add(_emitConstructor(member, name, fields, isObject)); | 560 jsMethods.add(_emitConstructor(member, name, fields, isObject)); |
563 } else if (member is MethodDeclaration) { | 561 } else if (member is MethodDeclaration) { |
564 jsMethods.add(_emitMethodDeclaration(type, member)); | 562 jsMethods.add(_emitMethodDeclaration(type, member)); |
| 563 } else if (member is FieldDeclaration) { |
| 564 _emitFieldDeclaration(jsMethods, member); |
565 } | 565 } |
566 } | 566 } |
567 | 567 |
568 // Support for adapting dart:core Iterator/Iterable to ES6 versions. | 568 // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
569 // This lets them use for-of loops transparently. | 569 // This lets them use for-of loops transparently. |
570 // https://github.com/lukehoban/es6features#iterators--forof | 570 // https://github.com/lukehoban/es6features#iterators--forof |
571 if (element.library.isDartCore && element.name == 'Iterable') { | 571 if (element.library.isDartCore && element.name == 'Iterable') { |
572 JS.Fun body = js.call('''function() { | 572 JS.Fun body = js.call('''function() { |
573 var iterator = this.iterator; | 573 var iterator = this.iterator; |
574 return { | 574 return { |
575 next() { | 575 next() { |
576 var done = iterator.moveNext(); | 576 var done = iterator.moveNext(); |
577 return { done: done, current: done ? void 0 : iterator.current }; | 577 return { done: done, current: done ? void 0 : iterator.current }; |
578 } | 578 } |
579 }; | 579 }; |
580 }'''); | 580 }'''); |
581 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); | 581 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); |
582 } | 582 } |
583 return jsMethods.where((m) => m != null).toList(growable: false); | 583 return jsMethods.where((m) => m != null).toList(growable: false); |
584 } | 584 } |
585 | 585 |
586 /// Emit class members that need to come after the class declaration, such | 586 /// Emit class members that need to come after the class declaration, such |
587 /// as static fields. See [_emitClassMethods] for things that are emitted | 587 /// as static fields. See [_emitClassMethods] for things that are emitted |
588 /// insite the ES6 `class { ... }` node. | 588 /// inside the ES6 `class { ... }` node. |
589 JS.Statement _finishClassMembers(ClassElement classElem, | 589 JS.Statement _finishClassMembers(ClassElement classElem, |
590 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, | 590 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, |
591 List<FieldDeclaration> staticFields) { | 591 List<FieldDeclaration> fields, List<FieldDeclaration> staticFields) { |
592 var name = classElem.name; | 592 var name = classElem.name; |
593 | |
594 var body = <JS.Statement>[]; | 593 var body = <JS.Statement>[]; |
595 body.add(new JS.ClassDeclaration(cls)); | 594 body.add(new JS.ClassDeclaration(cls)); |
596 | 595 |
597 // Interfaces | 596 // Interfaces |
598 if (classElem.interfaces.isNotEmpty) { | 597 if (classElem.interfaces.isNotEmpty) { |
599 body.add(js.statement('#[dart.implements] = () => #;', [ | 598 body.add(js.statement('#[dart.implements] = () => #;', [ |
600 name, | 599 name, |
601 new JS.ArrayInitializer( | 600 new JS.ArrayInitializer( |
602 classElem.interfaces.map(_emitTypeName).toList()) | 601 classElem.interfaces.map(_emitTypeName).toList()) |
603 ])); | 602 ])); |
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
766 return null; | 765 return null; |
767 } | 766 } |
768 | 767 |
769 var supertypeName = element.supertype.name; | 768 var supertypeName = element.supertype.name; |
770 var name = _constructorName(supertypeName, superCtorName); | 769 var name = _constructorName(supertypeName, superCtorName); |
771 | 770 |
772 var args = node != null ? _visit(node.argumentList) : []; | 771 var args = node != null ? _visit(node.argumentList) : []; |
773 return js.statement('super.#(#);', [name, args])..sourceInformation = node; | 772 return js.statement('super.#(#);', [name, args])..sourceInformation = node; |
774 } | 773 } |
775 | 774 |
| 775 /// Emits the [field]'s induced getter/setter into [methods]. |
| 776 void _emitFieldDeclaration(List<JS.Method> methods, FieldDeclaration fields) { |
| 777 if (fields.isStatic) return; |
| 778 |
| 779 for (var field in fields.fields.variables) { |
| 780 // For fields that use storage slots, we need to emit a getter/setter |
| 781 // pair, otherwise we don't. |
| 782 var storage = _fieldStorage(field.element); |
| 783 if (storage == null) continue; |
| 784 |
| 785 var e = field.element; |
| 786 var memberName = _emitMemberName(e.name, type: e.enclosingElement.type); |
| 787 methods.add(new JS.Method( |
| 788 memberName, js.call('function() { return this[#]; }', storage), |
| 789 isGetter: true)); |
| 790 if (!field.isFinal) { |
| 791 methods.add(new JS.Method(memberName, |
| 792 js.call('function(value) { this[#] = value; }', storage), |
| 793 isSetter: true)); |
| 794 } |
| 795 } |
| 796 } |
| 797 |
| 798 /// We use a storage slot for fields that override or can be overridden by |
| 799 /// getter/setter pairs. If the field element [e] needs storage, this will |
| 800 /// return a temporary that contains the JS `Symbol` with storage. Otherwise |
| 801 /// it will return null to indicate that no storage is needed. |
| 802 JSTemporary _fieldStorage(FieldElement e) { |
| 803 if (!_fieldNeedsStorage.contains(e)) return null; |
| 804 return _temps.putIfAbsent(e, () { |
| 805 var t = new JSTemporary(e.name); |
| 806 _pendingSymbols.add(t); |
| 807 return t; |
| 808 }); |
| 809 } |
| 810 |
776 /// Initialize fields. They follow the sequence: | 811 /// Initialize fields. They follow the sequence: |
777 /// | 812 /// |
778 /// 1. field declaration initializer if non-const, | 813 /// 1. field declaration initializer if non-const, |
779 /// 2. field initializing parameters, | 814 /// 2. field initializing parameters, |
780 /// 3. constructor field initializers, | 815 /// 3. constructor field initializers, |
781 /// 4. initialize fields not covered in 1-3 | 816 /// 4. initialize fields not covered in 1-3 |
782 JS.Statement _initializeFields(List<FieldDeclaration> fields, | 817 JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls, |
783 [FormalParameterList parameters, | 818 [FormalParameterList parameters, |
784 NodeList<ConstructorInitializer> initializers]) { | 819 NodeList<ConstructorInitializer> initializers]) { |
785 var body = <JS.Statement>[]; | |
786 | 820 |
787 // Run field initializers if they can have side-effects. | 821 // Run field initializers if they can have side-effects. |
| 822 var fields = new Map<FieldElement, JS.Expression>(); |
788 var unsetFields = new Map<FieldElement, VariableDeclaration>(); | 823 var unsetFields = new Map<FieldElement, VariableDeclaration>(); |
789 for (var declaration in fields) { | 824 for (var declaration in fieldDecls) { |
790 for (var field in declaration.fields.variables) { | 825 for (var fieldNode in declaration.fields.variables) { |
791 if (_isFieldInitConstant(field)) { | 826 var element = fieldNode.element; |
792 unsetFields[field.element] = field; | 827 if (_isFieldInitConstant(fieldNode)) { |
| 828 unsetFields[element] = fieldNode; |
793 } else { | 829 } else { |
794 body.add(js.statement( | 830 fields[element] = _visitInitializer(fieldNode); |
795 '# = #;', [_visit(field.name), _visitInitializer(field)])); | |
796 } | 831 } |
797 } | 832 } |
798 } | 833 } |
799 | 834 |
800 // Initialize fields from `this.fieldName` parameters. | 835 // Initialize fields from `this.fieldName` parameters. |
801 if (parameters != null) { | 836 if (parameters != null) { |
802 for (var p in parameters.parameters) { | 837 for (var p in parameters.parameters) { |
803 if (p is DefaultFormalParameter) p = p.parameter; | 838 var element = p.element; |
804 if (p is FieldFormalParameter) { | 839 if (element is FieldFormalParameterElement) { |
805 var field = (p.element as FieldFormalParameterElement).field; | 840 fields[element.field] = _visit(p); |
806 // Use the getter to initialize the field. This is a bit strange, but | |
807 // final fields don't have a setter element that we could use instead. | |
808 | |
809 var memberName = | |
810 _emitMemberName(field.name, type: field.enclosingElement.type); | |
811 body.add(js.statement('this.# = #;', [memberName, _visit(p)])); | |
812 unsetFields.remove(field); | |
813 } | 841 } |
814 } | 842 } |
815 } | 843 } |
816 | 844 |
817 // Run constructor field initializers such as `: foo = bar.baz` | 845 // Run constructor field initializers such as `: foo = bar.baz` |
818 if (initializers != null) { | 846 if (initializers != null) { |
819 for (var init in initializers) { | 847 for (var init in initializers) { |
820 if (init is ConstructorFieldInitializer) { | 848 if (init is ConstructorFieldInitializer) { |
821 body.add(js.statement( | 849 fields[init.fieldName.staticElement] = _visit(init.expression); |
822 '# = #;', [_visit(init.fieldName), _visit(init.expression)])); | |
823 unsetFields.remove(init.fieldName.staticElement); | |
824 } | 850 } |
825 } | 851 } |
826 } | 852 } |
827 | 853 |
| 854 for (var f in fields.keys) unsetFields.remove(f); |
| 855 |
828 // Initialize all remaining fields | 856 // Initialize all remaining fields |
829 unsetFields.forEach((field, fieldNode) { | 857 unsetFields.forEach((element, fieldNode) { |
830 JS.Expression value; | 858 JS.Expression value; |
831 if (fieldNode.initializer != null) { | 859 if (fieldNode.initializer != null) { |
832 value = _visit(fieldNode.initializer); | 860 value = _visit(fieldNode.initializer); |
833 } else { | 861 } else { |
834 var type = rules.elementType(field); | 862 var type = rules.elementType(element); |
835 value = new JS.LiteralNull(); | 863 value = new JS.LiteralNull(); |
836 if (rules.maybeNonNullableType(type)) { | 864 if (rules.maybeNonNullableType(type)) { |
837 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); | 865 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); |
838 } | 866 } |
839 } | 867 } |
840 var memberName = | 868 fields[element] = value; |
841 _emitMemberName(field.name, type: field.enclosingElement.type); | |
842 body.add(js.statement('this.# = #;', [memberName, value])); | |
843 }); | 869 }); |
844 | 870 |
| 871 var body = <JS.Statement>[]; |
| 872 fields.forEach((FieldElement e, JS.Expression initialValue) { |
| 873 var access = _fieldStorage(e); |
| 874 if (access == null) { |
| 875 access = _emitMemberName(e.name, type: e.enclosingElement.type); |
| 876 } |
| 877 body.add(js.statement('this.# = #;', [access, initialValue])); |
| 878 }); |
845 return _statement(body); | 879 return _statement(body); |
846 } | 880 } |
847 | 881 |
848 FormalParameterList _parametersOf(node) { | 882 FormalParameterList _parametersOf(node) { |
849 // Note: ConstructorDeclaration is intentionally skipped here so we can | 883 // Note: ConstructorDeclaration is intentionally skipped here so we can |
850 // emit the argument initializers in a different place. | 884 // emit the argument initializers in a different place. |
851 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we | 885 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we |
852 // could handle argument initializers more consistently in a separate | 886 // could handle argument initializers more consistently in a separate |
853 // lowering pass. | 887 // lowering pass. |
854 if (node is MethodDeclaration) return node.parameters; | 888 if (node is MethodDeclaration) return node.parameters; |
(...skipping 1344 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2199 /// for this transformation to happen, otherwise binary minus is assumed. | 2233 /// for this transformation to happen, otherwise binary minus is assumed. |
2200 /// | 2234 /// |
2201 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2235 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
2202 /// helper, that checks for null. The user defined method is called '=='. | 2236 /// helper, that checks for null. The user defined method is called '=='. |
2203 /// | 2237 /// |
2204 JS.Expression _emitMemberName(String name, | 2238 JS.Expression _emitMemberName(String name, |
2205 {DartType type, bool unary: false, bool isStatic: false}) { | 2239 {DartType type, bool unary: false, bool isStatic: false}) { |
2206 if (name.startsWith('_')) { | 2240 if (name.startsWith('_')) { |
2207 return _privateNames.putIfAbsent(name, () { | 2241 return _privateNames.putIfAbsent(name, () { |
2208 var t = new JSTemporary(name); | 2242 var t = new JSTemporary(name); |
2209 _pendingPrivateNames.add(t); | 2243 _pendingSymbols.add(t); |
2210 return t; | 2244 return t; |
2211 }); | 2245 }); |
2212 } | 2246 } |
| 2247 |
2213 // Check for extension method: | 2248 // Check for extension method: |
2214 var extLibrary = _findExtensionLibrary(name, type); | 2249 var extLibrary = _findExtensionLibrary(name, type); |
2215 | 2250 |
2216 if (name == '[]') { | 2251 if (name == '[]') { |
2217 name = 'get'; | 2252 name = 'get'; |
2218 } else if (name == '[]=') { | 2253 } else if (name == '[]=') { |
2219 name = 'set'; | 2254 name = 'set'; |
2220 } else if (name == '-' && unary) { | 2255 } else if (name == '-' && unary) { |
2221 name = 'unary-'; | 2256 name = 'unary-'; |
2222 } | 2257 } |
2223 | 2258 |
2224 if (isStatic && invalidJSStaticMethodName(name)) { | 2259 if (isStatic && invalidJSStaticMethodName(name)) { |
2225 // Choose an string name. Use an invalid identifier so it won't conflict | 2260 // Choose an string name. Use an invalid identifier so it won't conflict |
2226 // with any valid member names. | 2261 // with any valid member names. |
2227 // TODO(jmesserly): this works around the problem, but I'm pretty sure we | 2262 // TODO(jmesserly): this works around the problem, but I'm pretty sure we |
2228 // don't need it, as static methods seemed to work. The only concrete | 2263 // don't need it, as static methods seemed to work. The only concrete |
2229 // issue we saw was in the defineNamedConstructor helper function. | 2264 // issue we saw was in the defineNamedConstructor helper function. |
2230 name = '$name*'; | 2265 name = '$name*'; |
2231 } | 2266 } |
2232 | 2267 |
2233 if (extLibrary != null) { | 2268 if (extLibrary != null) { |
2234 return js.call('#.#', [ | 2269 return _extensionMethodName(name, extLibrary); |
2235 _libraryName(extLibrary), | |
2236 _propertyName(_addExtensionMethodName(name, extLibrary)) | |
2237 ]); | |
2238 } | 2270 } |
2239 | 2271 |
2240 return _propertyName(name); | 2272 return _propertyName(name); |
2241 } | 2273 } |
2242 | 2274 |
2243 LibraryElement _findExtensionLibrary(String name, DartType type) { | 2275 LibraryElement _findExtensionLibrary(String name, DartType type) { |
2244 if (type is! InterfaceType) return null; | 2276 if (type is! InterfaceType) return null; |
2245 | 2277 |
2246 var extLibrary = null; | 2278 var extLibrary = null; |
2247 var extensionTypes = _extensionMethods[name]; | 2279 var extensionTypes = _extensionMethods[name]; |
2248 if (extensionTypes != null) { | 2280 if (extensionTypes != null) { |
2249 // Normalize the type to ignore generics. | 2281 // Normalize the type to ignore generics. |
2250 type = fillDynamicTypeArgs(type, types); | 2282 type = fillDynamicTypeArgs(type, types); |
2251 for (var t in extensionTypes) { | 2283 for (var t in extensionTypes) { |
2252 if (rules.isSubTypeOf(type, t)) { | 2284 if (rules.isSubTypeOf(type, t)) { |
2253 assert(extLibrary == null || extLibrary == t.element.library); | 2285 assert(extLibrary == null || extLibrary == t.element.library); |
2254 extLibrary = t.element.library; | 2286 extLibrary = t.element.library; |
2255 } | 2287 } |
2256 } | 2288 } |
2257 } | 2289 } |
2258 return extLibrary; | 2290 return extLibrary; |
2259 } | 2291 } |
2260 | 2292 |
2261 String _addExtensionMethodName(String name, LibraryElement extLibrary) { | 2293 JS.Expression _extensionMethodName(String name, LibraryElement library) { |
2262 var extensionMethodName = '\$$name'; | 2294 var extName = '\$$name'; |
2263 if (extLibrary == currentLibrary) { | 2295 if (library == currentLibrary) { |
2264 // TODO(jacobr): need to do a better job ensuring that extension method | 2296 // TODO(jacobr): need to do a better job ensuring that extension method |
2265 // name symbols do not conflict with other symbols before we can let | 2297 // name symbols do not conflict with other symbols before we can let |
2266 // user defined libraries define extension methods. | 2298 // user defined libraries define extension methods. |
2267 if (_extensionMethodNames.add(extensionMethodName)) { | 2299 if (_extensionMethodNames.add(extName)) { |
2268 _pendingExtensionMethodNames.add(extensionMethodName); | 2300 _pendingSymbols.add(new JS.Identifier(extName)); |
2269 _addExport(extensionMethodName); | 2301 _addExport(extName); |
2270 } | 2302 } |
2271 } | 2303 } |
2272 return extensionMethodName; | 2304 return js.call('#.#', [_libraryName(library), _propertyName(extName)]); |
2273 } | 2305 } |
2274 | 2306 |
2275 bool _externalOrNative(node) => | 2307 bool _externalOrNative(node) => |
2276 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; | 2308 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
2277 | 2309 |
2278 FunctionBody _functionBody(node) => | 2310 FunctionBody _functionBody(node) => |
2279 node is FunctionDeclaration ? node.functionExpression.body : node.body; | 2311 node is FunctionDeclaration ? node.functionExpression.body : node.body; |
2280 | 2312 |
2281 /// Choose a canonical name from the library element. | 2313 /// Choose a canonical name from the library element. |
2282 /// This never uses the library's name (the identifier in the `library` | 2314 /// 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... |
2316 ..addAll(e.accessors.map((m) => m.name)); | 2348 ..addAll(e.accessors.map((m) => m.name)); |
2317 for (var name in names) { | 2349 for (var name in names) { |
2318 _extensionMethods.putIfAbsent(name, () => []).add(type); | 2350 _extensionMethods.putIfAbsent(name, () => []).add(type); |
2319 } | 2351 } |
2320 } | 2352 } |
2321 } | 2353 } |
2322 | 2354 |
2323 TypeProvider get types => rules.provider; | 2355 TypeProvider get types => rules.provider; |
2324 | 2356 |
2325 String generateLibrary(LibraryUnit unit, LibraryInfo info) { | 2357 String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
2326 var codegen = new JSCodegenVisitor(info, rules, _extensionMethods); | 2358 var fields = findFieldsNeedingStorage(unit); |
| 2359 var codegen = new JSCodegenVisitor(info, rules, _extensionMethods, fields); |
2327 var module = codegen.emitLibrary(unit); | 2360 var module = codegen.emitLibrary(unit); |
2328 var dir = path.join(outDir, jsOutputPath(info, root)); | 2361 var dir = path.join(outDir, jsOutputPath(info, root)); |
2329 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); | 2362 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); |
2330 } | 2363 } |
2331 } | 2364 } |
2332 | 2365 |
2333 /// Choose a canonical name from the library element. | 2366 /// Choose a canonical name from the library element. |
2334 /// This never uses the library's name (the identifier in the `library` | 2367 /// This never uses the library's name (the identifier in the `library` |
2335 /// declaration) as it doesn't have any meaningful rules enforced. | 2368 /// declaration) as it doesn't have any meaningful rules enforced. |
2336 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 2369 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
(...skipping 22 matching lines...) Expand all Loading... |
2359 return filepath; | 2392 return filepath; |
2360 } | 2393 } |
2361 | 2394 |
2362 // TODO(jmesserly): validate the library. See issue #135. | 2395 // TODO(jmesserly): validate the library. See issue #135. |
2363 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2396 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2364 | 2397 |
2365 // TODO(jacobr): we would like to do something like the following | 2398 // TODO(jacobr): we would like to do something like the following |
2366 // but we don't have summary support yet. | 2399 // but we don't have summary support yet. |
2367 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2400 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2368 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2401 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |