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 ddc.src.codegen.js_codegen; | 5 library ddc.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:io' show Directory, File; | 7 import 'dart:io' show Directory, File; |
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 12 matching lines...) Expand all Loading... |
23 import 'package:dev_compiler/src/report.dart'; | 23 import 'package:dev_compiler/src/report.dart'; |
24 import 'package:dev_compiler/src/utils.dart'; | 24 import 'package:dev_compiler/src/utils.dart'; |
25 import 'code_generator.dart'; | 25 import 'code_generator.dart'; |
26 | 26 |
27 // This must match the optional parameter name used in runtime.js | 27 // This must match the optional parameter name used in runtime.js |
28 const String _jsNamedParameterName = r'opt$'; | 28 const String _jsNamedParameterName = r'opt$'; |
29 | 29 |
30 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 30 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
31 final LibraryInfo libraryInfo; | 31 final LibraryInfo libraryInfo; |
32 final TypeRules rules; | 32 final TypeRules rules; |
33 final String _libraryName; | |
34 | 33 |
35 /// The variable for the target of the current `..` cascade expression. | 34 /// The variable for the target of the current `..` cascade expression. |
36 SimpleIdentifier _cascadeTarget; | 35 SimpleIdentifier _cascadeTarget; |
37 | 36 |
38 ClassDeclaration currentClass; | 37 ClassDeclaration currentClass; |
39 ConstantEvaluator _constEvaluator; | 38 ConstantEvaluator _constEvaluator; |
40 | 39 |
41 final _exports = <String>[]; | 40 final _exports = <String>[]; |
42 final _lazyFields = <VariableDeclaration>[]; | 41 final _lazyFields = <VariableDeclaration>[]; |
43 final _properties = <FunctionDeclaration>[]; | 42 final _properties = <FunctionDeclaration>[]; |
44 | 43 |
45 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules) | 44 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules) |
46 : libraryInfo = libraryInfo, | 45 : libraryInfo = libraryInfo, |
47 rules = rules, | 46 rules = rules; |
48 _libraryName = jsLibraryName(libraryInfo.library); | |
49 | 47 |
50 Element get currentLibrary => libraryInfo.library; | 48 Element get currentLibrary => libraryInfo.library; |
51 | 49 |
52 JS.Block generateLibrary( | 50 JS.Block generateLibrary( |
53 Iterable<CompilationUnit> units, CheckerReporter reporter) { | 51 Iterable<CompilationUnit> units, CheckerReporter reporter) { |
54 var body = <JS.Statement>[]; | 52 var body = <JS.Statement>[]; |
55 for (var unit in units) { | 53 for (var unit in units) { |
56 // TODO(jmesserly): this is needed because RestrictedTypeRules can send | 54 // TODO(jmesserly): this is needed because RestrictedTypeRules can send |
57 // messages to CheckerReporter, for things like missing types. | 55 // messages to CheckerReporter, for things like missing types. |
58 // We should probably refactor so this can't happen. | 56 // We should probably refactor so this can't happen. |
59 var source = unit.element.source; | 57 var source = unit.element.source; |
60 _constEvaluator = new ConstantEvaluator(source, rules.provider); | 58 _constEvaluator = new ConstantEvaluator(source, rules.provider); |
61 reporter.enterSource(source); | 59 reporter.enterSource(source); |
62 body.add(unit.accept(this)); | 60 body.add(unit.accept(this)); |
63 reporter.leaveSource(); | 61 reporter.leaveSource(); |
64 } | 62 } |
65 | 63 |
66 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); | 64 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); |
67 | 65 |
68 // TODO(jmesserly): make these immutable in JS? | 66 // TODO(jmesserly): make these immutable in JS? |
69 for (var name in _exports) { | 67 for (var name in _exports) { |
70 body.add(js.statement('#.# = #;', [_libraryName, name, name])); | 68 body.add(js.statement('$_EXPORTS.# = #;', [name, name])); |
71 } | 69 } |
72 | 70 |
73 var name = _libraryName; | 71 var name = jsLibraryName(libraryInfo.library); |
74 return new JS.Block([ | 72 return new JS.Block([ |
75 js.statement('var #;', name), | 73 js.statement('var #;', name), |
76 js.statement(''' | 74 js.statement(''' |
77 (function (#) { | 75 (function ($_EXPORTS) { |
78 'use strict'; | 76 'use strict'; |
79 #; | 77 #; |
80 })(# || (# = {})); | 78 })(# || (# = {})); |
81 ''', [name, body, name, name]) | 79 ''', [body, name, name]) |
82 ]); | 80 ]); |
83 } | 81 } |
84 | 82 |
85 @override | 83 @override |
86 JS.Statement visitCompilationUnit(CompilationUnit node) { | 84 JS.Statement visitCompilationUnit(CompilationUnit node) { |
87 // TODO(jmesserly): scriptTag, directives. | 85 // TODO(jmesserly): scriptTag, directives. |
88 var body = <JS.Statement>[]; | 86 var body = <JS.Statement>[]; |
89 for (var child in node.declarations) { | 87 for (var child in node.declarations) { |
90 // Attempt to group adjacent fields/properties. | 88 // Attempt to group adjacent fields/properties. |
91 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 89 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
(...skipping 539 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
631 @override | 629 @override |
632 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { | 630 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { |
633 var e = node.staticElement; | 631 var e = node.staticElement; |
634 if (e == null) { | 632 if (e == null) { |
635 return js.commentExpression( | 633 return js.commentExpression( |
636 'Unimplemented unknown name', new JS.VariableUse(node.name)); | 634 'Unimplemented unknown name', new JS.VariableUse(node.name)); |
637 } | 635 } |
638 var name = node.name; | 636 var name = node.name; |
639 if (e.enclosingElement is CompilationUnitElement && | 637 if (e.enclosingElement is CompilationUnitElement && |
640 (e.library != libraryInfo.library || _needsModuleGetter(e))) { | 638 (e.library != libraryInfo.library || _needsModuleGetter(e))) { |
641 return js.call('#.#', [jsLibraryName(e.library), name]); | 639 return js.call('#.#', [_libraryName(e.library), name]); |
642 } else if (currentClass != null && _needsImplicitThis(e)) { | 640 } else if (currentClass != null && _needsImplicitThis(e)) { |
643 return js.call('this.#', name); | 641 return js.call('this.#', name); |
644 } | 642 } |
645 return new JS.VariableUse(name); | 643 return new JS.VariableUse(name); |
646 } | 644 } |
647 | 645 |
648 JS.Expression _emitTypeName(DartType type) { | 646 JS.Expression _emitTypeName(DartType type) { |
649 var name = type.name; | 647 var name = type.name; |
650 var lib = type.element.library; | 648 var lib = type.element.library; |
651 if (name == '') { | 649 if (name == '') { |
652 // TODO(jmesserly): remove when we're using coercion reifier. | 650 // TODO(jmesserly): remove when we're using coercion reifier. |
653 return _unimplementedCall('Unimplemented type $type'); | 651 return _unimplementedCall('Unimplemented type $type'); |
654 } | 652 } |
655 | 653 |
656 var typeArgs = null; | 654 var typeArgs = null; |
657 if (type is ParameterizedType) { | 655 if (type is ParameterizedType) { |
658 // TODO(jmesserly): this is a workaround for an analyzer bug, see: | 656 // TODO(jmesserly): this is a workaround for an analyzer bug, see: |
659 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6
26dd8d16881cdb8e8b9c3fa | 657 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6
26dd8d16881cdb8e8b9c3fa |
660 if (type is! FunctionType || type.element is FunctionTypeAlias) { | 658 if (type is! FunctionType || type.element is FunctionTypeAlias) { |
661 var args = type.typeArguments; | 659 var args = type.typeArguments; |
662 if (args.any((a) => a != rules.provider.dynamicType)) { | 660 if (args.any((a) => a != rules.provider.dynamicType)) { |
663 name = '$name\$'; | 661 name = '$name\$'; |
664 typeArgs = args.map(_emitTypeName); | 662 typeArgs = args.map(_emitTypeName); |
665 } | 663 } |
666 } | 664 } |
667 } | 665 } |
668 | 666 |
669 JS.Expression result; | 667 JS.Expression result; |
670 if (lib != currentLibrary && lib != null) { | 668 if (lib != currentLibrary && lib != null) { |
671 result = js.call('#.#', [jsLibraryName(lib), name]); | 669 result = js.call('#.#', [_libraryName(lib), name]); |
672 } else { | 670 } else { |
673 result = new JS.VariableUse(name); | 671 result = new JS.VariableUse(name); |
674 } | 672 } |
675 | 673 |
676 if (typeArgs != null) { | 674 if (typeArgs != null) { |
677 result = js.call('#(#)', [result, typeArgs]); | 675 result = js.call('#(#)', [result, typeArgs]); |
678 } | 676 } |
679 return result; | 677 return result; |
680 } | 678 } |
681 | 679 |
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
951 } | 949 } |
952 | 950 |
953 JS.Expression _visitInitializer(VariableDeclaration node) { | 951 JS.Expression _visitInitializer(VariableDeclaration node) { |
954 var value = _visit(node.initializer); | 952 var value = _visit(node.initializer); |
955 // explicitly initialize to null, to avoid getting `undefined`. | 953 // explicitly initialize to null, to avoid getting `undefined`. |
956 // TODO(jmesserly): do this only for vars that aren't definitely assigned. | 954 // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
957 return value != null ? value : new JS.LiteralNull(); | 955 return value != null ? value : new JS.LiteralNull(); |
958 } | 956 } |
959 | 957 |
960 void _flushLazyFields(List<JS.Statement> body) { | 958 void _flushLazyFields(List<JS.Statement> body) { |
961 var code = _emitLazyFields(_libraryName, _lazyFields); | 959 var code = _emitLazyFields(_EXPORTS, _lazyFields); |
962 if (code != null) body.add(code); | 960 if (code != null) body.add(code); |
963 _lazyFields.clear(); | 961 _lazyFields.clear(); |
964 } | 962 } |
965 | 963 |
966 JS.Statement _emitLazyFields( | 964 JS.Statement _emitLazyFields( |
967 String objExpr, List<VariableDeclaration> fields) { | 965 String objExpr, List<VariableDeclaration> fields) { |
968 if (fields.isEmpty) return null; | 966 if (fields.isEmpty) return null; |
969 | 967 |
970 var methods = []; | 968 var methods = []; |
971 for (var node in fields) { | 969 for (var node in fields) { |
972 var name = node.name.name; | 970 var name = node.name.name; |
973 methods.add(new JS.Method(new JS.PropertyName(name), | 971 methods.add(new JS.Method(new JS.PropertyName(name), |
974 js.call('function() { return #; }', node.initializer.accept(this)), | 972 js.call('function() { return #; }', node.initializer.accept(this)), |
975 isGetter: true)); | 973 isGetter: true)); |
976 | 974 |
977 // TODO(jmesserly): use a dummy setter to indicate writable. | 975 // TODO(jmesserly): use a dummy setter to indicate writable. |
978 if (!node.isFinal) { | 976 if (!node.isFinal) { |
979 methods.add(new JS.Method( | 977 methods.add(new JS.Method( |
980 new JS.PropertyName(name), js.call('function() {}'), | 978 new JS.PropertyName(name), js.call('function() {}'), |
981 isSetter: true)); | 979 isSetter: true)); |
982 } | 980 } |
983 } | 981 } |
984 | 982 |
985 return js.statement( | 983 return js.statement( |
986 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); | 984 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); |
987 } | 985 } |
988 | 986 |
989 void _flushLibraryProperties(List<JS.Statement> body) { | 987 void _flushLibraryProperties(List<JS.Statement> body) { |
990 if (_properties.isEmpty) return; | 988 if (_properties.isEmpty) return; |
991 body.add(js.statement('dart.copyProperties(#, { # });', [ | 989 body.add(js.statement('dart.copyProperties($_EXPORTS, { # });', [ |
992 _libraryName, | |
993 _properties.map(_emitTopLevelProperty) | 990 _properties.map(_emitTopLevelProperty) |
994 ])); | 991 ])); |
995 _properties.clear(); | 992 _properties.clear(); |
996 } | 993 } |
997 | 994 |
998 @override | 995 @override |
999 JS.Statement visitVariableDeclarationStatement( | 996 JS.Statement visitVariableDeclarationStatement( |
1000 VariableDeclarationStatement node) => | 997 VariableDeclarationStatement node) => |
1001 _expressionStatement(node.variables.accept(this)); | 998 _expressionStatement(node.variables.accept(this)); |
1002 | 999 |
(...skipping 620 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1623 if (name == '[]=') return 'set'; | 1620 if (name == '[]=') return 'set'; |
1624 return name; | 1621 return name; |
1625 } | 1622 } |
1626 | 1623 |
1627 bool _externalOrNative(node) => | 1624 bool _externalOrNative(node) => |
1628 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; | 1625 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
1629 | 1626 |
1630 FunctionBody _functionBody(node) => | 1627 FunctionBody _functionBody(node) => |
1631 node is FunctionDeclaration ? node.functionExpression.body : node.body; | 1628 node is FunctionDeclaration ? node.functionExpression.body : node.body; |
1632 | 1629 |
| 1630 /// Choose a canonical name from the library element. |
| 1631 /// This never uses the library's name (the identifier in the `library` |
| 1632 /// declaration) as it doesn't have any meaningful rules enforced. |
| 1633 String _libraryName(LibraryElement library) { |
| 1634 if (library == libraryInfo.library) return _EXPORTS; |
| 1635 return jsLibraryName(library); |
| 1636 } |
| 1637 |
1633 String _maybeBindThis(node) { | 1638 String _maybeBindThis(node) { |
1634 if (currentClass == null) return ''; | 1639 if (currentClass == null) return ''; |
1635 var visitor = _BindThisVisitor._instance; | 1640 var visitor = _BindThisVisitor._instance; |
1636 visitor._bindThis = false; | 1641 visitor._bindThis = false; |
1637 node.accept(visitor); | 1642 node.accept(visitor); |
1638 return visitor._bindThis ? '.bind(this)' : ''; | 1643 return visitor._bindThis ? '.bind(this)' : ''; |
1639 } | 1644 } |
1640 | 1645 |
| 1646 /// The name for the library's exports inside itself. |
| 1647 /// This much be a constant because we interpolate it into template strings, |
| 1648 /// and otherwise it would break caching for them. |
| 1649 /// `exports` was chosen as the most similar to ES module patterns. |
| 1650 static const String _EXPORTS = 'exports'; |
| 1651 |
1641 static bool _needsImplicitThis(Element e) => | 1652 static bool _needsImplicitThis(Element e) => |
1642 e is PropertyAccessorElement && !e.variable.isStatic || | 1653 e is PropertyAccessorElement && !e.variable.isStatic || |
1643 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; | 1654 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; |
1644 } | 1655 } |
1645 | 1656 |
1646 /// Returns true if the local variable is potentially mutated within [context]. | 1657 /// Returns true if the local variable is potentially mutated within [context]. |
1647 /// This accounts for closures that may have been created outside of [context]. | 1658 /// This accounts for closures that may have been created outside of [context]. |
1648 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { | 1659 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { |
1649 if (e.isPotentiallyMutatedInClosure) { | 1660 if (e.isPotentiallyMutatedInClosure) { |
1650 // TODO(jmesserly): this returns true incorrectly in some cases, because | 1661 // TODO(jmesserly): this returns true incorrectly in some cases, because |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1735 /// Choose a canonical name from the library element. | 1746 /// Choose a canonical name from the library element. |
1736 /// This never uses the library's name (the identifier in the `library` | 1747 /// This never uses the library's name (the identifier in the `library` |
1737 /// declaration) as it doesn't have any meaningful rules enforced. | 1748 /// declaration) as it doesn't have any meaningful rules enforced. |
1738 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 1749 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
1739 | 1750 |
1740 /// Path to file that will be generated for [info]. | 1751 /// Path to file that will be generated for [info]. |
1741 // TODO(jmesserly): library directory should be relative to its package | 1752 // TODO(jmesserly): library directory should be relative to its package |
1742 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b
e: | 1753 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b
e: |
1743 // "ddc/src/codegen/js_codegen.js" under the output directory. | 1754 // "ddc/src/codegen/js_codegen.js" under the output directory. |
1744 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; | 1755 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; |
OLD | NEW |