| 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 |