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'; |
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. |
| 57 final HashSet<FieldElement> _fieldsNeedingStorage; |
| 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._fieldsNeedingStorage); |
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 13 matching lines...) Expand all Loading... |
112 if (decl is ClassDeclaration || | 118 if (decl is ClassDeclaration || |
113 decl is ClassTypeAlias || | 119 decl is ClassTypeAlias || |
114 decl is FunctionTypeAlias) { | 120 decl is FunctionTypeAlias) { |
115 _pendingClasses[decl.element] = decl; | 121 _pendingClasses[decl.element] = decl; |
116 } | 122 } |
117 } | 123 } |
118 } | 124 } |
119 | 125 |
120 for (var unit in library.partsThenLibrary) body.add(_visit(unit)); | 126 for (var unit in library.partsThenLibrary) body.add(_visit(unit)); |
121 | 127 |
| 128 // Ensure field slots for any fields we override from other libraries. |
| 129 _fieldsNeedingStorage.forEach(_overrideField); |
| 130 |
122 assert(_pendingClasses.isEmpty); | 131 assert(_pendingClasses.isEmpty); |
123 | 132 |
124 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); | 133 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); |
125 | 134 |
126 // TODO(jmesserly): make these immutable in JS? | 135 // TODO(jmesserly): make these immutable in JS? |
127 for (var name in _exports) { | 136 for (var name in _exports) { |
128 body.add(js.statement('#.# = #;', [_exportsVar, name, name])); | 137 body.add(js.statement('#.# = #;', [_exportsVar, name, name])); |
129 } | 138 } |
130 | 139 |
131 var name = jsLibraryName(libraryInfo.library); | 140 var name = jsLibraryName(libraryInfo.library); |
132 | 141 |
133 var defaultValue = js.call(jsDefaultValue); | 142 var defaultValue = js.call(jsDefaultValue); |
134 return new JS.Program([ | 143 return new JS.Program([ |
135 js.statement('var #;', name), | 144 js.statement('var #;', name), |
136 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ | 145 js.statement("(function(#) { 'use strict'; #; })(# || (# = #));", [ |
137 _exportsVar, | 146 _exportsVar, |
138 body, | 147 body, |
139 name, | 148 name, |
140 name, | 149 name, |
141 defaultValue | 150 defaultValue |
142 ]) | 151 ]) |
143 ]); | 152 ]); |
144 } | 153 } |
145 | 154 |
146 JS.Statement _initPrivateSymbol(JSTemporary tmp) => | 155 JS.Statement _initSymbol(JS.Identifier id) => |
147 js.statement('let # = $_SYMBOL(#);', [tmp, js.string(tmp.name, "'")]); | 156 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 | 157 |
152 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, | 158 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
153 // until we have better name tracking. | 159 // until we have better name tracking. |
154 String get _SYMBOL { | 160 String get _SYMBOL { |
155 var name = currentLibrary.name; | 161 var name = currentLibrary.name; |
156 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; | 162 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
157 return 'Symbol'; | 163 return 'Symbol'; |
158 } | 164 } |
159 | 165 |
160 @override | 166 @override |
161 JS.Statement visitCompilationUnit(CompilationUnit node) { | 167 JS.Statement visitCompilationUnit(CompilationUnit node) { |
162 var source = node.element.source; | 168 var source = node.element.source; |
163 | 169 |
164 _constEvaluator = new ConstantEvaluator(source, types); | 170 _constEvaluator = new ConstantEvaluator(source, types); |
165 | 171 |
166 // TODO(jmesserly): scriptTag, directives. | 172 // TODO(jmesserly): scriptTag, directives. |
167 var body = <JS.Statement>[]; | 173 var body = <JS.Statement>[]; |
168 for (var child in node.declarations) { | 174 for (var child in node.declarations) { |
169 // Attempt to group adjacent fields/properties. | 175 // Attempt to group adjacent fields/properties. |
170 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 176 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
171 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 177 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
172 | 178 |
173 var code = _visit(child); | 179 var code = _visit(child); |
174 | |
175 if (code != null) { | 180 if (code != null) { |
176 if (_pendingPrivateNames.isNotEmpty) { | 181 if (_pendingSymbols.isNotEmpty) { |
177 body.addAll(_pendingPrivateNames.map(_initPrivateSymbol)); | 182 body.addAll(_pendingSymbols.map(_initSymbol)); |
178 _pendingPrivateNames.clear(); | 183 _pendingSymbols.clear(); |
179 } | |
180 if (_pendingExtensionMethodNames.isNotEmpty) { | |
181 body.addAll( | |
182 _pendingExtensionMethodNames.map(_initExtensionMethodSymbol)); | |
183 _pendingExtensionMethodNames.clear(); | |
184 } | 184 } |
185 body.add(code); | 185 body.add(code); |
186 } | 186 } |
187 } | 187 } |
188 | 188 |
189 // Flush any unwritten fields/properties. | 189 // Flush any unwritten fields/properties. |
190 _flushLazyFields(body); | 190 _flushLazyFields(body); |
191 _flushLibraryProperties(body); | 191 _flushLibraryProperties(body); |
192 | 192 |
193 assert(_pendingPrivateNames.isEmpty); | 193 assert(_pendingSymbols.isEmpty); |
194 return _statement(body); | 194 return _statement(body); |
195 } | 195 } |
196 | 196 |
197 bool isPublic(String name) => !name.startsWith('_'); | 197 bool isPublic(String name) => !name.startsWith('_'); |
198 | 198 |
199 /// Conversions that we don't handle end up here. | 199 /// Conversions that we don't handle end up here. |
200 @override | 200 @override |
201 visitConversion(Conversion node) { | 201 visitConversion(Conversion node) { |
202 var from = node.baseType; | 202 var from = node.baseType; |
203 var to = node.convertedType; | 203 var to = node.convertedType; |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 // generate it correctly when we refer to it. | 310 // generate it correctly when we refer to it. |
311 if (isPublic(dartClassName)) _addExport(dartClassName); | 311 if (isPublic(dartClassName)) _addExport(dartClassName); |
312 return js.statement('let # = #;', [dartClassName, jsTypeName]); | 312 return js.statement('let # = #;', [dartClassName, jsTypeName]); |
313 } | 313 } |
314 return null; | 314 return null; |
315 } | 315 } |
316 | 316 |
317 @override | 317 @override |
318 JS.Statement visitClassDeclaration(ClassDeclaration node) { | 318 JS.Statement visitClassDeclaration(ClassDeclaration node) { |
319 // If we've already emitted this class, skip it. | 319 // If we've already emitted this class, skip it. |
320 var type = node.element.type; | 320 var classElem = node.element; |
321 if (_pendingClasses.remove(node.element) == null) return null; | 321 var type = classElem.type; |
| 322 if (_pendingClasses.remove(classElem) == null) return null; |
322 | 323 |
323 var jsName = getAnnotationValue(node, _isJsNameAnnotation); | 324 var jsName = getAnnotationValue(node, _isJsNameAnnotation); |
324 if (jsName != null) return _emitJsType(node.name.name, jsName); | 325 if (jsName != null) return _emitJsType(node.name.name, jsName); |
325 | 326 |
326 currentClass = node; | 327 currentClass = node; |
327 | 328 |
328 var ctors = <ConstructorDeclaration>[]; | 329 var ctors = <ConstructorDeclaration>[]; |
329 var fields = <FieldDeclaration>[]; | 330 var fields = <FieldDeclaration>[]; |
330 var staticFields = <FieldDeclaration>[]; | 331 var staticFields = <FieldDeclaration>[]; |
331 for (var member in node.members) { | 332 for (var member in node.members) { |
332 if (member is ConstructorDeclaration) { | 333 if (member is ConstructorDeclaration) { |
333 ctors.add(member); | 334 ctors.add(member); |
334 } else if (member is FieldDeclaration) { | 335 } else if (member is FieldDeclaration) { |
335 (member.isStatic ? staticFields : fields).add(member); | 336 (member.isStatic ? staticFields : fields).add(member); |
336 } | 337 } |
337 } | 338 } |
338 | 339 |
339 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), | 340 var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
340 _classHeritage(node), _emitClassMethods(node, ctors, fields)); | 341 _classHeritage(node), _emitClassMethods(node, ctors, fields)); |
341 | 342 |
342 var body = | 343 var body = |
343 _finishClassMembers(node.element, classExpr, ctors, staticFields); | 344 _finishClassMembers(classElem, classExpr, ctors, fields, staticFields); |
344 currentClass = null; | 345 currentClass = null; |
345 | 346 |
346 return _finishClassDef(type, body); | 347 return _finishClassDef(type, body); |
347 } | 348 } |
348 | 349 |
349 @override | 350 @override |
350 JS.Statement visitEnumDeclaration(EnumDeclaration node) => | 351 JS.Statement visitEnumDeclaration(EnumDeclaration node) => |
351 _unimplementedCall("Unimplemented enum: $node").toStatement(); | 352 _unimplementedCall("Unimplemented enum: $node").toStatement(); |
352 | 353 |
353 /// Given a class element and body, complete the class declaration. | 354 /// 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; | 545 return heritage; |
545 } | 546 } |
546 | 547 |
547 List<JS.Method> _emitClassMethods(ClassDeclaration node, | 548 List<JS.Method> _emitClassMethods(ClassDeclaration node, |
548 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { | 549 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
549 var element = node.element; | 550 var element = node.element; |
550 var type = element.type; | 551 var type = element.type; |
551 var isObject = type.isObject; | 552 var isObject = type.isObject; |
552 var name = node.name.name; | 553 var name = node.name.name; |
553 | 554 |
554 var jsMethods = <JS.Method>[]; | |
555 // Iff no constructor is specified for a class C, it implicitly has a | 555 // Iff no constructor is specified for a class C, it implicitly has a |
556 // default constructor `C() : super() {}`, unless C is class Object. | 556 // default constructor `C() : super() {}`, unless C is class Object. |
| 557 var jsMethods = <JS.Method>[]; |
557 if (ctors.isEmpty && !isObject) { | 558 if (ctors.isEmpty && !isObject) { |
558 jsMethods.add(_emitImplicitConstructor(node, name, fields)); | 559 jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
559 } | 560 } |
| 561 |
560 for (var member in node.members) { | 562 for (var member in node.members) { |
561 if (member is ConstructorDeclaration) { | 563 if (member is ConstructorDeclaration) { |
562 jsMethods.add(_emitConstructor(member, name, fields, isObject)); | 564 jsMethods.add(_emitConstructor(member, name, fields, isObject)); |
563 } else if (member is MethodDeclaration) { | 565 } else if (member is MethodDeclaration) { |
564 jsMethods.add(_emitMethodDeclaration(type, member)); | 566 jsMethods.add(_emitMethodDeclaration(type, member)); |
565 } | 567 } |
566 } | 568 } |
567 | 569 |
568 // Support for adapting dart:core Iterator/Iterable to ES6 versions. | 570 // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
569 // This lets them use for-of loops transparently. | 571 // This lets them use for-of loops transparently. |
570 // https://github.com/lukehoban/es6features#iterators--forof | 572 // https://github.com/lukehoban/es6features#iterators--forof |
571 if (element.library.isDartCore && element.name == 'Iterable') { | 573 if (element.library.isDartCore && element.name == 'Iterable') { |
572 JS.Fun body = js.call('''function() { | 574 JS.Fun body = js.call('''function() { |
573 var iterator = this.iterator; | 575 var iterator = this.iterator; |
574 return { | 576 return { |
575 next() { | 577 next() { |
576 var done = iterator.moveNext(); | 578 var done = iterator.moveNext(); |
577 return { done: done, current: done ? void 0 : iterator.current }; | 579 return { done: done, current: done ? void 0 : iterator.current }; |
578 } | 580 } |
579 }; | 581 }; |
580 }'''); | 582 }'''); |
581 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); | 583 jsMethods.add(new JS.Method(js.call('$_SYMBOL.iterator'), body)); |
582 } | 584 } |
583 return jsMethods.where((m) => m != null).toList(growable: false); | 585 return jsMethods.where((m) => m != null).toList(growable: false); |
584 } | 586 } |
585 | 587 |
586 /// Emit class members that need to come after the class declaration, such | 588 /// Emit class members that need to come after the class declaration, such |
587 /// as static fields. See [_emitClassMethods] for things that are emitted | 589 /// as static fields. See [_emitClassMethods] for things that are emitted |
588 /// insite the ES6 `class { ... }` node. | 590 /// inside the ES6 `class { ... }` node. |
589 JS.Statement _finishClassMembers(ClassElement classElem, | 591 JS.Statement _finishClassMembers(ClassElement classElem, |
590 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, | 592 JS.ClassExpression cls, List<ConstructorDeclaration> ctors, |
591 List<FieldDeclaration> staticFields) { | 593 List<FieldDeclaration> fields, List<FieldDeclaration> staticFields) { |
592 var name = classElem.name; | 594 var name = classElem.name; |
593 | |
594 var body = <JS.Statement>[]; | 595 var body = <JS.Statement>[]; |
595 body.add(new JS.ClassDeclaration(cls)); | 596 body.add(new JS.ClassDeclaration(cls)); |
596 | 597 |
597 // Interfaces | 598 // Interfaces |
598 if (classElem.interfaces.isNotEmpty) { | 599 if (classElem.interfaces.isNotEmpty) { |
599 body.add(js.statement('#[dart.implements] = () => #;', [ | 600 body.add(js.statement('#[dart.implements] = () => #;', [ |
600 name, | 601 name, |
601 new JS.ArrayInitializer( | 602 new JS.ArrayInitializer( |
602 classElem.interfaces.map(_emitTypeName).toList()) | 603 classElem.interfaces.map(_emitTypeName).toList()) |
603 ])); | 604 ])); |
604 } | 605 } |
605 | 606 |
606 // Named constructors | 607 // Named constructors |
607 for (ConstructorDeclaration member in ctors) { | 608 for (ConstructorDeclaration member in ctors) { |
608 if (member.name != null) { | 609 if (member.name != null) { |
609 body.add(js.statement('dart.defineNamedConstructor(#, #);', [ | 610 body.add(js.statement('dart.defineNamedConstructor(#, #);', [ |
610 name, | 611 name, |
611 _emitMemberName(member.name.name, isStatic: true) | 612 _emitMemberName(member.name.name, isStatic: true) |
612 ])); | 613 ])); |
613 } | 614 } |
614 } | 615 } |
615 | 616 |
| 617 // Instance fields, if they override getter/setter pairs |
| 618 for (FieldDeclaration member in fields) { |
| 619 for (VariableDeclaration fieldDecl in member.fields.variables) { |
| 620 var field = fieldDecl.element; |
| 621 if (_fieldsNeedingStorage.remove(field)) { |
| 622 body.add(_overrideField(field)); |
| 623 } |
| 624 } |
| 625 } |
| 626 |
616 // Static fields | 627 // Static fields |
617 var lazyStatics = <VariableDeclaration>[]; | 628 var lazyStatics = <VariableDeclaration>[]; |
618 for (FieldDeclaration member in staticFields) { | 629 for (FieldDeclaration member in staticFields) { |
619 for (VariableDeclaration field in member.fields.variables) { | 630 for (VariableDeclaration field in member.fields.variables) { |
620 var fieldName = field.name.name; | 631 var fieldName = field.name.name; |
621 if (field.isConst || _isFieldInitConstant(field)) { | 632 if (field.isConst || _isFieldInitConstant(field)) { |
622 var init = _visit(field.initializer); | 633 var init = _visit(field.initializer); |
623 if (init == null) init = new JS.LiteralNull(); | 634 if (init == null) init = new JS.LiteralNull(); |
624 body.add(js.statement('#.# = #;', [name, fieldName, init])); | 635 body.add(js.statement('#.# = #;', [name, fieldName, init])); |
625 } else { | 636 } else { |
626 lazyStatics.add(field); | 637 lazyStatics.add(field); |
627 } | 638 } |
628 } | 639 } |
629 } | 640 } |
630 var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); | 641 var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics); |
631 if (lazy != null) body.add(lazy); | 642 if (lazy != null) body.add(lazy); |
632 | 643 |
633 return _statement(body); | 644 return _statement(body); |
634 } | 645 } |
635 | 646 |
| 647 JS.Statement _overrideField(FieldElement e) { |
| 648 var cls = e.enclosingElement; |
| 649 return js.statement('dart.virtualField(#, #)', [ |
| 650 cls.name, |
| 651 _emitMemberName(e.name, type: cls.type) |
| 652 ]); |
| 653 } |
| 654 |
636 /// Generates the implicit default constructor for class C of the form | 655 /// Generates the implicit default constructor for class C of the form |
637 /// `C() : super() {}`. | 656 /// `C() : super() {}`. |
638 JS.Method _emitImplicitConstructor( | 657 JS.Method _emitImplicitConstructor( |
639 ClassDeclaration node, String name, List<FieldDeclaration> fields) { | 658 ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
640 // If we don't have a method body, skip this. | 659 // If we don't have a method body, skip this. |
641 if (fields.isEmpty) return null; | 660 if (fields.isEmpty) return null; |
642 | 661 |
643 dynamic body = _initializeFields(fields); | 662 dynamic body = _initializeFields(node, fields); |
644 var superCall = _superConstructorCall(node); | 663 var superCall = _superConstructorCall(node); |
645 if (superCall != null) body = [[body, superCall]]; | 664 if (superCall != null) body = [[body, superCall]]; |
646 return new JS.Method( | 665 return new JS.Method( |
647 _propertyName(name), js.call('function() { #; }', body)); | 666 _propertyName(name), js.call('function() { #; }', body)); |
648 } | 667 } |
649 | 668 |
650 JS.Method _emitConstructor(ConstructorDeclaration node, String className, | 669 JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
651 List<FieldDeclaration> fields, bool isObject) { | 670 List<FieldDeclaration> fields, bool isObject) { |
652 if (_externalOrNative(node)) return null; | 671 if (_externalOrNative(node)) return null; |
653 | 672 |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
723 body.add(_visit(redirectCall)); | 742 body.add(_visit(redirectCall)); |
724 return new JS.Block(body); | 743 return new JS.Block(body); |
725 } | 744 } |
726 | 745 |
727 // Initializers only run for non-factory constructors. | 746 // Initializers only run for non-factory constructors. |
728 if (node.factoryKeyword == null) { | 747 if (node.factoryKeyword == null) { |
729 // Generate field initializers. | 748 // Generate field initializers. |
730 // These are expanded into each non-redirecting constructor. | 749 // These are expanded into each non-redirecting constructor. |
731 // In the future we may want to create an initializer function if we have | 750 // In the future we may want to create an initializer function if we have |
732 // multiple constructors, but it needs to be balanced against readability. | 751 // multiple constructors, but it needs to be balanced against readability. |
733 body.add(_initializeFields(fields, node.parameters, node.initializers)); | 752 body.add(_initializeFields( |
| 753 node.parent, fields, node.parameters, node.initializers)); |
734 | 754 |
735 var superCall = node.initializers.firstWhere( | 755 var superCall = node.initializers.firstWhere( |
736 (i) => i is SuperConstructorInvocation, orElse: () => null); | 756 (i) => i is SuperConstructorInvocation, orElse: () => null); |
737 | 757 |
738 // If no superinitializer is provided, an implicit superinitializer of the | 758 // If no superinitializer is provided, an implicit superinitializer of the |
739 // form `super()` is added at the end of the initializer list, unless the | 759 // form `super()` is added at the end of the initializer list, unless the |
740 // enclosing class is class Object. | 760 // enclosing class is class Object. |
741 var jsSuper = _superConstructorCall(node.parent, superCall); | 761 var jsSuper = _superConstructorCall(node.parent, superCall); |
742 if (jsSuper != null) body.add(jsSuper); | 762 if (jsSuper != null) body.add(jsSuper); |
743 } | 763 } |
(...skipping 28 matching lines...) Expand all Loading... |
772 var args = node != null ? _visit(node.argumentList) : []; | 792 var args = node != null ? _visit(node.argumentList) : []; |
773 return js.statement('super.#(#);', [name, args])..sourceInformation = node; | 793 return js.statement('super.#(#);', [name, args])..sourceInformation = node; |
774 } | 794 } |
775 | 795 |
776 /// Initialize fields. They follow the sequence: | 796 /// Initialize fields. They follow the sequence: |
777 /// | 797 /// |
778 /// 1. field declaration initializer if non-const, | 798 /// 1. field declaration initializer if non-const, |
779 /// 2. field initializing parameters, | 799 /// 2. field initializing parameters, |
780 /// 3. constructor field initializers, | 800 /// 3. constructor field initializers, |
781 /// 4. initialize fields not covered in 1-3 | 801 /// 4. initialize fields not covered in 1-3 |
782 JS.Statement _initializeFields(List<FieldDeclaration> fields, | 802 JS.Statement _initializeFields( |
| 803 ClassDeclaration classDecl, List<FieldDeclaration> fieldDecls, |
783 [FormalParameterList parameters, | 804 [FormalParameterList parameters, |
784 NodeList<ConstructorInitializer> initializers]) { | 805 NodeList<ConstructorInitializer> initializers]) { |
785 var body = <JS.Statement>[]; | |
786 | 806 |
787 // Run field initializers if they can have side-effects. | 807 // Run field initializers if they can have side-effects. |
| 808 var fields = new Map<FieldElement, JS.Expression>(); |
788 var unsetFields = new Map<FieldElement, VariableDeclaration>(); | 809 var unsetFields = new Map<FieldElement, VariableDeclaration>(); |
789 for (var declaration in fields) { | 810 for (var declaration in fieldDecls) { |
790 for (var field in declaration.fields.variables) { | 811 for (var fieldNode in declaration.fields.variables) { |
791 if (_isFieldInitConstant(field)) { | 812 var element = fieldNode.element; |
792 unsetFields[field.element] = field; | 813 if (_isFieldInitConstant(fieldNode)) { |
| 814 unsetFields[element] = fieldNode; |
793 } else { | 815 } else { |
794 body.add(js.statement( | 816 fields[element] = _visitInitializer(fieldNode); |
795 '# = #;', [_visit(field.name), _visitInitializer(field)])); | |
796 } | 817 } |
797 } | 818 } |
798 } | 819 } |
799 | 820 |
800 // Initialize fields from `this.fieldName` parameters. | 821 // Initialize fields from `this.fieldName` parameters. |
801 if (parameters != null) { | 822 if (parameters != null) { |
802 for (var p in parameters.parameters) { | 823 for (var p in parameters.parameters) { |
803 if (p is DefaultFormalParameter) p = p.parameter; | 824 var element = p.element; |
804 if (p is FieldFormalParameter) { | 825 if (element is FieldFormalParameterElement) { |
805 var field = (p.element as FieldFormalParameterElement).field; | 826 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 } | 827 } |
814 } | 828 } |
815 } | 829 } |
816 | 830 |
817 // Run constructor field initializers such as `: foo = bar.baz` | 831 // Run constructor field initializers such as `: foo = bar.baz` |
818 if (initializers != null) { | 832 if (initializers != null) { |
819 for (var init in initializers) { | 833 for (var init in initializers) { |
820 if (init is ConstructorFieldInitializer) { | 834 if (init is ConstructorFieldInitializer) { |
821 body.add(js.statement( | 835 fields[init.fieldName.staticElement] = _visit(init.expression); |
822 '# = #;', [_visit(init.fieldName), _visit(init.expression)])); | |
823 unsetFields.remove(init.fieldName.staticElement); | |
824 } | 836 } |
825 } | 837 } |
826 } | 838 } |
827 | 839 |
| 840 for (var f in fields.keys) unsetFields.remove(f); |
| 841 |
828 // Initialize all remaining fields | 842 // Initialize all remaining fields |
829 unsetFields.forEach((field, fieldNode) { | 843 unsetFields.forEach((element, fieldNode) { |
830 JS.Expression value; | 844 JS.Expression value; |
831 if (fieldNode.initializer != null) { | 845 if (fieldNode.initializer != null) { |
832 value = _visit(fieldNode.initializer); | 846 value = _visit(fieldNode.initializer); |
833 } else { | 847 } else { |
834 var type = rules.elementType(field); | 848 var type = rules.elementType(element); |
835 value = new JS.LiteralNull(); | 849 value = new JS.LiteralNull(); |
836 if (rules.maybeNonNullableType(type)) { | 850 if (rules.maybeNonNullableType(type)) { |
837 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); | 851 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]); |
838 } | 852 } |
839 } | 853 } |
840 var memberName = | 854 fields[element] = value; |
841 _emitMemberName(field.name, type: field.enclosingElement.type); | |
842 body.add(js.statement('this.# = #;', [memberName, value])); | |
843 }); | 855 }); |
844 | 856 |
| 857 var classElem = classDecl.element; |
| 858 bool classCanBeMixin = !classElem.type.isObject && |
| 859 classElem.supertype.isObject && |
| 860 classElem.mixins.isEmpty; |
| 861 |
| 862 var body = <JS.Statement>[]; |
| 863 fields.forEach((FieldElement e, JS.Expression initialValue) { |
| 864 var access = _emitMemberName(e.name, type: e.enclosingElement.type); |
| 865 |
| 866 // Fields on private types can't be extended outside this library. |
| 867 // Private fields on public types also can't be extended, as long as the |
| 868 // type can't be mixed in. |
| 869 bool isSealed = classElem.isPrivate || e.isPrivate && !classCanBeMixin; |
| 870 |
| 871 JS.Statement init; |
| 872 if (isSealed && !_fieldsNeedingStorage.contains(e)) { |
| 873 // Concrete field: doesn't override anything else, and is sealed so it |
| 874 // isn't and can't be overridden by anything else. |
| 875 init = js.statement('this.# = #;', [access, initialValue]); |
| 876 } else { |
| 877 init = js.statement('dart.initField(#, this, #, #);', [ |
| 878 classDecl.name.name, |
| 879 access, |
| 880 initialValue |
| 881 ]); |
| 882 } |
| 883 body.add(init); |
| 884 }); |
845 return _statement(body); | 885 return _statement(body); |
846 } | 886 } |
847 | 887 |
848 FormalParameterList _parametersOf(node) { | 888 FormalParameterList _parametersOf(node) { |
849 // Note: ConstructorDeclaration is intentionally skipped here so we can | 889 // Note: ConstructorDeclaration is intentionally skipped here so we can |
850 // emit the argument initializers in a different place. | 890 // emit the argument initializers in a different place. |
851 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we | 891 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we |
852 // could handle argument initializers more consistently in a separate | 892 // could handle argument initializers more consistently in a separate |
853 // lowering pass. | 893 // lowering pass. |
854 if (node is MethodDeclaration) return node.parameters; | 894 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. | 2239 /// for this transformation to happen, otherwise binary minus is assumed. |
2200 /// | 2240 /// |
2201 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2241 /// 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 '=='. | 2242 /// helper, that checks for null. The user defined method is called '=='. |
2203 /// | 2243 /// |
2204 JS.Expression _emitMemberName(String name, | 2244 JS.Expression _emitMemberName(String name, |
2205 {DartType type, bool unary: false, bool isStatic: false}) { | 2245 {DartType type, bool unary: false, bool isStatic: false}) { |
2206 if (name.startsWith('_')) { | 2246 if (name.startsWith('_')) { |
2207 return _privateNames.putIfAbsent(name, () { | 2247 return _privateNames.putIfAbsent(name, () { |
2208 var t = new JSTemporary(name); | 2248 var t = new JSTemporary(name); |
2209 _pendingPrivateNames.add(t); | 2249 _pendingSymbols.add(t); |
2210 return t; | 2250 return t; |
2211 }); | 2251 }); |
2212 } | 2252 } |
| 2253 |
2213 // Check for extension method: | 2254 // Check for extension method: |
2214 var extLibrary = _findExtensionLibrary(name, type); | 2255 var extLibrary = _findExtensionLibrary(name, type); |
2215 | 2256 |
2216 if (name == '[]') { | 2257 if (name == '[]') { |
2217 name = 'get'; | 2258 name = 'get'; |
2218 } else if (name == '[]=') { | 2259 } else if (name == '[]=') { |
2219 name = 'set'; | 2260 name = 'set'; |
2220 } else if (name == '-' && unary) { | 2261 } else if (name == '-' && unary) { |
2221 name = 'unary-'; | 2262 name = 'unary-'; |
2222 } | 2263 } |
2223 | 2264 |
2224 if (isStatic && invalidJSStaticMethodName(name)) { | 2265 if (isStatic && invalidJSStaticMethodName(name)) { |
2225 // Choose an string name. Use an invalid identifier so it won't conflict | 2266 // Choose an string name. Use an invalid identifier so it won't conflict |
2226 // with any valid member names. | 2267 // with any valid member names. |
2227 // TODO(jmesserly): this works around the problem, but I'm pretty sure we | 2268 // 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 | 2269 // don't need it, as static methods seemed to work. The only concrete |
2229 // issue we saw was in the defineNamedConstructor helper function. | 2270 // issue we saw was in the defineNamedConstructor helper function. |
2230 name = '$name*'; | 2271 name = '$name*'; |
2231 } | 2272 } |
2232 | 2273 |
2233 if (extLibrary != null) { | 2274 if (extLibrary != null) { |
2234 return js.call('#.#', [ | 2275 return _extensionMethodName(name, extLibrary); |
2235 _libraryName(extLibrary), | |
2236 _propertyName(_addExtensionMethodName(name, extLibrary)) | |
2237 ]); | |
2238 } | 2276 } |
2239 | 2277 |
2240 return _propertyName(name); | 2278 return _propertyName(name); |
2241 } | 2279 } |
2242 | 2280 |
2243 LibraryElement _findExtensionLibrary(String name, DartType type) { | 2281 LibraryElement _findExtensionLibrary(String name, DartType type) { |
2244 if (type is! InterfaceType) return null; | 2282 if (type is! InterfaceType) return null; |
2245 | 2283 |
2246 var extLibrary = null; | 2284 var extLibrary = null; |
2247 var extensionTypes = _extensionMethods[name]; | 2285 var extensionTypes = _extensionMethods[name]; |
2248 if (extensionTypes != null) { | 2286 if (extensionTypes != null) { |
2249 // Normalize the type to ignore generics. | 2287 // Normalize the type to ignore generics. |
2250 type = fillDynamicTypeArgs(type, types); | 2288 type = fillDynamicTypeArgs(type, types); |
2251 for (var t in extensionTypes) { | 2289 for (var t in extensionTypes) { |
2252 if (rules.isSubTypeOf(type, t)) { | 2290 if (rules.isSubTypeOf(type, t)) { |
2253 assert(extLibrary == null || extLibrary == t.element.library); | 2291 assert(extLibrary == null || extLibrary == t.element.library); |
2254 extLibrary = t.element.library; | 2292 extLibrary = t.element.library; |
2255 } | 2293 } |
2256 } | 2294 } |
2257 } | 2295 } |
2258 return extLibrary; | 2296 return extLibrary; |
2259 } | 2297 } |
2260 | 2298 |
2261 String _addExtensionMethodName(String name, LibraryElement extLibrary) { | 2299 JS.Expression _extensionMethodName(String name, LibraryElement library) { |
2262 var extensionMethodName = '\$$name'; | 2300 var extName = '\$$name'; |
2263 if (extLibrary == currentLibrary) { | 2301 if (library == currentLibrary) { |
2264 // TODO(jacobr): need to do a better job ensuring that extension method | 2302 // 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 | 2303 // name symbols do not conflict with other symbols before we can let |
2266 // user defined libraries define extension methods. | 2304 // user defined libraries define extension methods. |
2267 if (_extensionMethodNames.add(extensionMethodName)) { | 2305 if (_extensionMethodNames.add(extName)) { |
2268 _pendingExtensionMethodNames.add(extensionMethodName); | 2306 _pendingSymbols.add(new JS.Identifier(extName)); |
2269 _addExport(extensionMethodName); | 2307 _addExport(extName); |
2270 } | 2308 } |
2271 } | 2309 } |
2272 return extensionMethodName; | 2310 return js.call('#.#', [_libraryName(library), _propertyName(extName)]); |
2273 } | 2311 } |
2274 | 2312 |
2275 bool _externalOrNative(node) => | 2313 bool _externalOrNative(node) => |
2276 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; | 2314 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
2277 | 2315 |
2278 FunctionBody _functionBody(node) => | 2316 FunctionBody _functionBody(node) => |
2279 node is FunctionDeclaration ? node.functionExpression.body : node.body; | 2317 node is FunctionDeclaration ? node.functionExpression.body : node.body; |
2280 | 2318 |
2281 /// Choose a canonical name from the library element. | 2319 /// Choose a canonical name from the library element. |
2282 /// This never uses the library's name (the identifier in the `library` | 2320 /// 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)); | 2354 ..addAll(e.accessors.map((m) => m.name)); |
2317 for (var name in names) { | 2355 for (var name in names) { |
2318 _extensionMethods.putIfAbsent(name, () => []).add(type); | 2356 _extensionMethods.putIfAbsent(name, () => []).add(type); |
2319 } | 2357 } |
2320 } | 2358 } |
2321 } | 2359 } |
2322 | 2360 |
2323 TypeProvider get types => rules.provider; | 2361 TypeProvider get types => rules.provider; |
2324 | 2362 |
2325 String generateLibrary(LibraryUnit unit, LibraryInfo info) { | 2363 String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
2326 var codegen = new JSCodegenVisitor(info, rules, _extensionMethods); | 2364 var fields = findFieldOverrides(unit); |
| 2365 var codegen = new JSCodegenVisitor(info, rules, _extensionMethods, fields); |
2327 var module = codegen.emitLibrary(unit); | 2366 var module = codegen.emitLibrary(unit); |
2328 var dir = path.join(outDir, jsOutputPath(info, root)); | 2367 var dir = path.join(outDir, jsOutputPath(info, root)); |
2329 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); | 2368 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); |
2330 } | 2369 } |
2331 } | 2370 } |
2332 | 2371 |
2333 /// Choose a canonical name from the library element. | 2372 /// Choose a canonical name from the library element. |
2334 /// This never uses the library's name (the identifier in the `library` | 2373 /// This never uses the library's name (the identifier in the `library` |
2335 /// declaration) as it doesn't have any meaningful rules enforced. | 2374 /// declaration) as it doesn't have any meaningful rules enforced. |
2336 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 2375 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
(...skipping 22 matching lines...) Expand all Loading... |
2359 return filepath; | 2398 return filepath; |
2360 } | 2399 } |
2361 | 2400 |
2362 // TODO(jmesserly): validate the library. See issue #135. | 2401 // TODO(jmesserly): validate the library. See issue #135. |
2363 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2402 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2364 | 2403 |
2365 // TODO(jacobr): we would like to do something like the following | 2404 // TODO(jacobr): we would like to do something like the following |
2366 // but we don't have summary support yet. | 2405 // but we don't have summary support yet. |
2367 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2406 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2368 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2407 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |