Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(383)

Side by Side Diff: lib/src/codegen/js_codegen.dart

Issue 949383003: use js_ast instead of strings to generate JS (Closed) Base URL: git@github.com:dart-lang/dart-dev-compiler.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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; 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;
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/scanner.dart' 13 import 'package:analyzer/src/generated/scanner.dart'
14 show StringToken, Token, TokenType; 14 show StringToken, Token, TokenType;
15 import 'package:path/path.dart' as path; 15 import 'package:path/path.dart' as path;
16 16
17 // TODO(jmesserly): import from its own package
18 import 'package:dev_compiler/src/js/js_ast.dart' as JS;
19 import 'package:dev_compiler/src/js/js_ast.dart' show js;
20
17 import 'package:dev_compiler/src/checker/rules.dart'; 21 import 'package:dev_compiler/src/checker/rules.dart';
18 import 'package:dev_compiler/src/info.dart'; 22 import 'package:dev_compiler/src/info.dart';
19 import 'package:dev_compiler/src/report.dart'; 23 import 'package:dev_compiler/src/report.dart';
20 import 'package:dev_compiler/src/utils.dart'; 24 import 'package:dev_compiler/src/utils.dart';
21 import 'code_generator.dart'; 25 import 'code_generator.dart';
22 26
23 // This must match the optional parameter name used in runtime.js 27 // This must match the optional parameter name used in runtime.js
24 const String optionalParameters = r'opt$'; 28 const String _jsNamedParameterName = r'opt$';
25 29
26 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { 30 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
27 final LibraryInfo libraryInfo; 31 final LibraryInfo libraryInfo;
28 final TypeRules rules; 32 final TypeRules rules;
29 final OutWriter out;
30 final String _libraryName; 33 final String _libraryName;
31 34
32 /// The variable for the target of the current `..` cascade expression. 35 /// The variable for the target of the current `..` cascade expression.
33 SimpleIdentifier _cascadeTarget; 36 SimpleIdentifier _cascadeTarget;
34 37
35 ClassDeclaration currentClass; 38 ClassDeclaration currentClass;
36 ConstantEvaluator _constEvaluator; 39 ConstantEvaluator _constEvaluator;
37 40
38 final _exports = <String>[]; 41 final _exports = <String>[];
39 final _lazyFields = <VariableDeclaration>[]; 42 final _lazyFields = <VariableDeclaration>[];
40 final _properties = <FunctionDeclaration>[]; 43 final _properties = <FunctionDeclaration>[];
41 44
42 static final int _indexExpressionPrecedence = 45 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules)
43 new IndexExpression.forTarget(null, null, null, null).precedence;
44
45 static final int _prefixExpressionPrecedence =
46 new PrefixExpression(null, null).precedence;
47
48 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules, this.out)
49 : libraryInfo = libraryInfo, 46 : libraryInfo = libraryInfo,
50 rules = rules, 47 rules = rules,
51 _libraryName = jsLibraryName(libraryInfo.library); 48 _libraryName = jsLibraryName(libraryInfo.library);
52 49
53 Element get currentLibrary => libraryInfo.library; 50 Element get currentLibrary => libraryInfo.library;
54 51
55 void generateLibrary( 52 JS.Block generateLibrary(
56 Iterable<CompilationUnit> units, CheckerReporter reporter) { 53 Iterable<CompilationUnit> units, CheckerReporter reporter) {
57 out.write(""" 54 var body = <JS.Statement>[];
58 var $_libraryName;
59 (function ($_libraryName) {
60 'use strict';
61 """, 2);
62
63 for (var unit in units) { 55 for (var unit in units) {
64 // TODO(jmesserly): this is needed because RestrictedTypeRules can send 56 // TODO(jmesserly): this is needed because RestrictedTypeRules can send
65 // messages to CheckerReporter, for things like missing types. 57 // messages to CheckerReporter, for things like missing types.
66 // We should probably refactor so this can't happen. 58 // We should probably refactor so this can't happen.
67
68 var source = unit.element.source; 59 var source = unit.element.source;
69 _constEvaluator = new ConstantEvaluator(source, rules.provider); 60 _constEvaluator = new ConstantEvaluator(source, rules.provider);
70 reporter.enterSource(source); 61 reporter.enterSource(source);
71 unit.accept(this); 62 body.add(unit.accept(this));
72 reporter.leaveSource(); 63 reporter.leaveSource();
73 } 64 }
74 65
75 if (_exports.isNotEmpty) out.write('// Exports:\n'); 66 if (_exports.isNotEmpty) body.add(js.comment('Exports:'));
76 67
77 // TODO(jmesserly): make these immutable in JS? 68 // TODO(jmesserly): make these immutable in JS?
78 for (var name in _exports) { 69 for (var name in _exports) {
79 out.write('${_libraryName}.$name = $name;\n'); 70 body.add(js.statement('#.# = #;', [_libraryName, name, name]));
80 } 71 }
81 72
82 out.write(""" 73 var name = _libraryName;
83 })($_libraryName || ($_libraryName = {})); 74 return new JS.Block([
84 """, -2); 75 js.statement('var #;', name),
76 js.statement('''
77 (function (#) {
78 'use strict';
79 #;
80 })(# || (# = {}));
81 ''', [name, body, name, name])
82 ]);
85 } 83 }
86 84
87 @override 85 @override
88 void visitCompilationUnit(CompilationUnit node) { 86 JS.Statement visitCompilationUnit(CompilationUnit node) {
89 _visitNode(node.scriptTag); 87 // TODO(jmesserly): scriptTag, directives.
90 _visitNodeList(node.directives); 88 var body = <JS.Statement>[];
91 for (var child in node.declarations) { 89 for (var child in node.declarations) {
92 // Attempt to group adjacent fields/properties. 90 // Attempt to group adjacent fields/properties.
93 if (child is! TopLevelVariableDeclaration) _flushLazyFields(); 91 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body);
94 if (child is! FunctionDeclaration) _flushLibraryProperties(); 92 if (child is! FunctionDeclaration) _flushLibraryProperties(body);
95 93
96 child.accept(this); 94 var code = child.accept(this);
95 if (code != null) body.add(code);
97 } 96 }
98 // Flush any unwritten fields/properties. 97 // Flush any unwritten fields/properties.
99 _flushLazyFields(); 98 _flushLazyFields(body);
100 _flushLibraryProperties(); 99 _flushLibraryProperties(body);
100 return _statement(body);
101 } 101 }
102 102
103 bool isPublic(String name) => !name.startsWith('_'); 103 bool isPublic(String name) => !name.startsWith('_');
104 104
105 /// Conversions that we don't handle end up here. 105 /// Conversions that we don't handle end up here.
106 @override 106 @override
107 void visitConversion(Conversion node) { 107 visitConversion(Conversion node) {
108 var from = node.baseType; 108 var from = node.baseType;
109 var to = node.convertedType; 109 var to = node.convertedType;
110 110
111 // All Dart number types map to a JS double. 111 // All Dart number types map to a JS double.
112 if (rules.isNumType(from) && 112 if (rules.isNumType(from) &&
113 (rules.isIntType(to) || rules.isDoubleType(to))) { 113 (rules.isIntType(to) || rules.isDoubleType(to))) {
114 // TODO(jmesserly): a lot of these checks are meaningless, as people use 114 // TODO(jmesserly): a lot of these checks are meaningless, as people use
115 // `num` to mean "any kind of number" rather than "could be null". 115 // `num` to mean "any kind of number" rather than "could be null".
116 // The core libraries especially suffer from this problem, with many of 116 // The core libraries especially suffer from this problem, with many of
117 // the `num` methods returning `num`. 117 // the `num` methods returning `num`.
118 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { 118 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) {
119 // Converting from a nullable number to a non-nullable number 119 // Converting from a nullable number to a non-nullable number
120 // only requires a null check. 120 // only requires a null check.
121 out.write('dart.notNull('); 121 return js.call('dart.notNull(#)', node.expression.accept(this));
122 node.expression.accept(this);
123 out.write(')');
124 } else { 122 } else {
125 // A no-op in JavaScript. 123 // A no-op in JavaScript.
126 node.expression.accept(this); 124 return node.expression.accept(this);
127 } 125 }
128 return;
129 } 126 }
130 127
131 _writeCast(node.expression, to); 128 return _emitCast(node.expression, to);
132 } 129 }
133 130
134 @override 131 @override
135 void visitAsExpression(AsExpression node) { 132 visitAsExpression(AsExpression node) =>
136 _writeCast(node.expression, node.type.type); 133 _emitCast(node.expression, node.type.type);
137 }
138 134
139 void _writeCast(Expression node, DartType type) { 135 _emitCast(Expression node, DartType type) =>
140 out.write('dart.as('); 136 js.call('dart.as(#)', [[node.accept(this), _emitTypeName(type)]]);
141 node.accept(this);
142 out.write(', ');
143 _writeTypeName(type);
144 out.write(')');
145 }
146 137
147 @override 138 @override
148 void visitIsExpression(IsExpression node) { 139 visitIsExpression(IsExpression node) {
149 // Generate `is` as `instanceof` or `typeof` depending on the RHS type. 140 // Generate `is` as `dart.is` or `typeof` depending on the RHS type.
150 if (node.notOperator != null) out.write('!'); 141 JS.Expression result;
151
152 var type = node.type.type; 142 var type = node.type.type;
153 var lhs = node.expression; 143 var lhs = node.expression.accept(this);
154 var typeofName = _jsTypeofName(type); 144 var typeofName = _jsTypeofName(type);
155 if (typeofName != null) { 145 if (typeofName != null) {
156 if (node.notOperator != null) out.write('('); 146 result = js.call('typeof # == #', [lhs, typeofName]);
157 out.write('typeof ');
158 // We're going to replace the `is` operator with higher-precedence prefix
159 // `typeof` operator, so add parens around the left side if necessary.
160 _visitExpression(lhs, _prefixExpressionPrecedence);
161 out.write(' == "$typeofName"');
162 if (node.notOperator != null) out.write(')');
163 } else { 147 } else {
164 // Always go through a runtime helper, because implicit interfaces. 148 // Always go through a runtime helper, because implicit interfaces.
165 out.write('dart.is('); 149 result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]);
166 lhs.accept(this);
167 out.write(', ');
168 _writeTypeName(type);
169 out.write(')');
170 } 150 }
151
152 if (node.notOperator != null) {
153 return js.call('!#', result);
154 }
155 return result;
171 } 156 }
172 157
173 String _jsTypeofName(DartType t) { 158 String _jsTypeofName(DartType t) {
174 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; 159 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number';
175 if (rules.isStringType(t)) return 'string'; 160 if (rules.isStringType(t)) return 'string';
176 if (rules.isBoolType(t)) return 'boolean'; 161 if (rules.isBoolType(t)) return 'boolean';
177 return null; 162 return null;
178 } 163 }
179 164
180 @override 165 @override
181 void visitFunctionTypeAlias(FunctionTypeAlias node) { 166 visitFunctionTypeAlias(FunctionTypeAlias node) {
182 // TODO(vsm): Do we need to record type info the generated code for a 167 // TODO(vsm): Do we need to record type info the generated code for a
183 // typedef? 168 // typedef?
184 } 169 }
185 170
186 @override 171 @override
187 void visitTypeName(TypeName node) { 172 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type);
Jennifer Messerly 2015/02/25 18:36:20 types used to be handled in two ways, here and _wr
188 _visitNode(node.name); 173
189 _visitNode(node.typeArguments); 174 @override
175 JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
176 var name = node.name.name;
177 var heritage =
178 js.call('dart.mixin(#)', [_visitList(node.withClause.mixinTypes)]);
179 var classDecl = new JS.ClassDeclaration(
180 new JS.ClassExpression(new JS.VariableDeclaration(name), heritage, []));
181 if (isPublic(name)) _exports.add(name);
182 return _addTypeParameters(node.typeParameters, name, classDecl);
190 } 183 }
191 184
192 @override 185 @override
193 void visitTypeArgumentList(TypeArgumentList node) { 186 visitTypeParameter(TypeParameter node) => new JS.Parameter(node.name.name);
194 out.write('\$('); 187
195 _visitNodeList(node.arguments, separator: ', '); 188 JS.Statement _addTypeParameters(
196 out.write(')'); 189 TypeParameterList node, String name, JS.Statement clazz) {
190 if (node == null) return clazz;
191
192 var genericName = '$name\$';
193 var genericDef = js.statement(
194 'let # = dart.generic(function(#) { #; return #; });', [
195 genericName,
196 _visitList(node.typeParameters),
197 clazz,
198 name
199 ]);
200
201 // TODO(jmesserly): we may not want this to be `dynamic` if the generic
202 // has a lower bound, e.g. `<T extends SomeType>`.
203 // https://github.com/dart-lang/dart-dev-compiler/issues/38
204 var typeArgs = new List.filled(node.typeParameters.length, 'dynamic');
205
206 var dynInst = js.statement('let # = #(#);', [name, genericName, typeArgs]);
207
208 // TODO(jmesserly): is it worth exporting both names? Alternatively we could
209 // put the generic type constructor on the <dynamic> instance.
210 if (isPublic(name)) _exports.add('${name}\$');
211 return new JS.Block([genericDef, dynInst]);
197 } 212 }
198 213
199 @override 214 @override
200 void visitClassTypeAlias(ClassTypeAlias node) { 215 JS.Statement visitClassDeclaration(ClassDeclaration node) {
201 var name = node.name.name;
202 _beginTypeParameters(node.typeParameters, name);
203 out.write('class $name extends dart.mixin(');
204 _visitNodeList(node.withClause.mixinTypes, separator: ', ');
205 out.write(') {}\n\n');
206 _endTypeParameters(node.typeParameters, name);
207 if (isPublic(name)) _exports.add(name);
208 }
209
210 @override
211 void visitTypeParameter(TypeParameter node) {
212 out.write(node.name.name);
213 }
214
215 void _beginTypeParameters(TypeParameterList node, String name) {
216 if (node == null) return;
217 out.write('let ${name}\$ = dart.generic(function(');
218 _visitNodeList(node.typeParameters, separator: ', ');
219 out.write(') {\n', 2);
220 }
221
222 void _endTypeParameters(TypeParameterList node, String name) {
223 if (node == null) return;
224 // Return the specialized class.
225 out.write('return $name;\n');
226 out.write('});\n', -2);
227 // Construct the "default" version of the generic type for easy interop.
228 out.write('let $name = ${name}\$(');
229 for (int i = 0, len = node.typeParameters.length; i < len; i++) {
230 if (i > 0) out.write(', ');
231 // TODO(jmesserly): we may not want this to be `dynamic` if the generic
232 // has a lower bound, e.g. `<T extends SomeType>`.
233 // https://github.com/dart-lang/dart-dev-compiler/issues/38
234 out.write('dynamic');
235 }
236 out.write(');\n');
237 // TODO(jmesserly): is it worth exporting both names? Alternatively we could
238 // put the generic type constructor on the <dynamic> instance.
239 if (isPublic(name)) _exports.add('${name}\$');
240 }
241
242 @override
243 void visitClassDeclaration(ClassDeclaration node) {
244 currentClass = node; 216 currentClass = node;
245 217
218 var body = <JS.Statement>[];
219
246 var name = node.name.name; 220 var name = node.name.name;
247 _beginTypeParameters(node.typeParameters, name); 221 var ctors = <ConstructorDeclaration>[];
248 out.write('class $name extends '); 222 var fields = <FieldDeclaration>[];
249 223 var staticFields = <FieldDeclaration>[];
250 if (node.withClause != null) {
251 out.write('dart.mixin(');
252 }
253 if (node.extendsClause != null) {
254 _visitNode(node.extendsClause.superclass);
255 } else {
256 out.write('dart.Object');
257 }
258 if (node.withClause != null) {
259 _visitNodeList(node.withClause.mixinTypes, prefix: ', ', separator: ', ');
260 out.write(')');
261 }
262
263 out.write(' {\n', 2);
264
265 var ctors = new List<ConstructorDeclaration>();
266 var fields = new List<FieldDeclaration>();
267 var staticFields = new List<FieldDeclaration>();
268 for (var member in node.members) { 224 for (var member in node.members) {
269 if (member is ConstructorDeclaration) { 225 if (member is ConstructorDeclaration) {
270 ctors.add(member); 226 ctors.add(member);
271 } else if (member is FieldDeclaration) { 227 } else if (member is FieldDeclaration) {
272 (member.isStatic ? staticFields : fields).add(member); 228 (member.isStatic ? staticFields : fields).add(member);
273 } 229 }
274 } 230 }
275 231
232 var jsMethods = <JS.Method>[];
276 // Iff no constructor is specified for a class C, it implicitly has a 233 // Iff no constructor is specified for a class C, it implicitly has a
277 // default constructor `C() : super() {}`, unless C is class Object. 234 // default constructor `C() : super() {}`, unless C is class Object.
278 if (ctors.isEmpty && !node.element.type.isObject) { 235 if (ctors.isEmpty && !node.element.type.isObject) {
279 _generateImplicitConstructor(node, name, fields); 236 jsMethods.add(_emitImplicitConstructor(node, name, fields));
280 } 237 }
281 238
282 for (var member in node.members) { 239 for (var member in node.members) {
283 if (member is ConstructorDeclaration) { 240 if (member is ConstructorDeclaration) {
284 _generateConstructor(member, name, fields); 241 jsMethods.add(_emitConstructor(member, name, fields));
285 } else if (member is MethodDeclaration) { 242 } else if (member is MethodDeclaration) {
286 member.accept(this); 243 jsMethods.add(member.accept(this));
287 } 244 }
288 } 245 }
289 246
290 out.write('}\n', -2); 247 // Support for adapting dart:core Iterator/Iterable to ES6 versions.
248 // This lets them use for-of loops transparently.
249 // https://github.com/lukehoban/es6features#iterators--forof
250 if (node.element.library.isDartCore && node.element.name == 'Iterable') {
251 JS.Fun body = js.call('''function() {
252 var iterator = this.iterator;
253 return {
254 next() {
255 var done = iterator.moveNext();
256 return { done: done, current: done ? void 0 : iterator.current };
257 }
258 };
259 }''');
260 jsMethods.add(new JS.Method(js.call('Symbol.iterator'), body));
261 }
262
263 JS.Expression heritage;
264 if (node.extendsClause != null) {
265 heritage = _visit(node.extendsClause.superclass);
266 } else {
267 heritage = js.call('dart.Object');
268 }
269 if (node.withClause != null) {
270 var mixins = _visitList(node.withClause.mixinTypes);
271 mixins.insert(0, heritage);
272 heritage = js.call('dart.mixin(#)', [mixins]);
273 }
274 body.add(new JS.ClassDeclaration(new JS.ClassExpression(
275 new JS.VariableDeclaration(name), heritage,
276 jsMethods.where((m) => m != null).toList(growable: false))));
291 277
292 if (isPublic(name)) _exports.add(name); 278 if (isPublic(name)) _exports.add(name);
293 279
294 // Named constructors 280 // Named constructors
295 for (ConstructorDeclaration member in ctors) { 281 for (ConstructorDeclaration member in ctors) {
296 if (member.name != null) { 282 if (member.name != null) {
297 var ctorName = member.name.name; 283 body.add(js.statement('dart.defineNamedConstructor(#, #);', [
298 out.write('dart.defineNamedConstructor($name, "$ctorName");\n'); 284 name,
285 js.string(member.name.name, "'")
286 ]));
299 } 287 }
300 } 288 }
301 289
302 // Static fields 290 // Static fields
303 var lazyStatics = <VariableDeclaration>[]; 291 var lazyStatics = <VariableDeclaration>[];
304 for (FieldDeclaration member in staticFields) { 292 for (FieldDeclaration member in staticFields) {
305 for (VariableDeclaration field in member.fields.variables) { 293 for (VariableDeclaration field in member.fields.variables) {
306 var prefix = '$name.${field.name.name}'; 294 var fieldName = field.name.name;
307 if (field.initializer == null) { 295 if (field.isConst || _isFieldInitConstant(field)) {
308 out.write('$prefix = null;\n'); 296 var init = _visit(field.initializer);
309 } else if (field.isConst || _isFieldInitConstant(field)) { 297 if (init == null) init = new JS.LiteralNull();
310 out.write('$prefix = '); 298 body.add(js.statement('#.# = #;', [name, fieldName, init]));
311 field.initializer.accept(this);
312 out.write(';\n');
313 } else { 299 } else {
314 lazyStatics.add(field); 300 lazyStatics.add(field);
315 } 301 }
316 } 302 }
317 } 303 }
318 _writeLazyFields(name, lazyStatics); 304 var lazy = _emitLazyFields(name, lazyStatics);
305 if (lazy != null) body.add(lazy);
319 306
320 // Support for adapting dart:core Iterator/Iterable to ES6 versions.
321 // This lets them use for-of loops transparently.
322 // https://github.com/lukehoban/es6features#iterators--forof
323 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-iterable-interf ace
324 // TODO(jmesserly): put this straight in the class as an instance method,
325 // once V8 supports symbols for method names: `[Symbol.iterator]() { ... }`.
326 if (node.element.library.isDartCore && node.element.name == 'Iterable') {
327 out.write('''
328 $name.prototype[Symbol.iterator] = function() {
329 var iterator = this.iterator;
330 return {
331 next: function() {
332 var done = iterator.moveNext();
333 return { done: done, current: done ? void 0 : iterator.current };
334 }
335 };
336 };
337 ''');
338 }
339
340 _endTypeParameters(node.typeParameters, name);
341
342 out.write('\n');
343 currentClass = null; 307 currentClass = null;
308 return _addTypeParameters(node.typeParameters, name, _statement(body));
344 } 309 }
345 310
346 /// Generates the implicit default constructor for class C of the form 311 /// Generates the implicit default constructor for class C of the form
347 /// `C() : super() {}`. 312 /// `C() : super() {}`.
348 void _generateImplicitConstructor( 313 JS.Method _emitImplicitConstructor(
349 ClassDeclaration node, String name, List<FieldDeclaration> fields) { 314 ClassDeclaration node, String name, List<FieldDeclaration> fields) {
350 // If we don't have a method body, skip this. 315 // If we don't have a method body, skip this.
351 if (fields.isEmpty) return; 316 if (fields.isEmpty) return null;
352 317
353 out.write('$name() {\n', 2); 318 var body = _initializeFields(fields);
354 _initializeFields(fields); 319 var superCall = _superConstructorCall(node);
355 _superConstructorCall(node); 320 if (superCall != null) body = [[body, superCall]];
356 out.write('}\n', -2); 321 return new JS.Method(
322 new JS.PropertyName(name), js.call('function() { #; }', body));
357 } 323 }
358 324
359 void _generateConstructor(ConstructorDeclaration node, String className, 325 JS.Method _emitConstructor(ConstructorDeclaration node, String className,
360 List<FieldDeclaration> fields) { 326 List<FieldDeclaration> fields) {
361 if (node.externalKeyword != null) { 327 if (node.externalKeyword != null) return null;
362 out.write('/* Unimplemented $node */\n'); 328
363 return; 329 var name = _constructorName(className, node.name);
364 }
365 330
366 // We generate constructors as initializer methods in the class; 331 // We generate constructors as initializer methods in the class;
367 // this allows use of `super` for instance methods/properties. 332 // this allows use of `super` for instance methods/properties.
368 // It also avoids V8 restrictions on `super` in default constructors. 333 // It also avoids V8 restrictions on `super` in default constructors.
369 out.write(className); 334 return new JS.Method(new JS.PropertyName(name), new JS.Fun(
370 if (node.name != null) { 335 node.parameters.accept(this), _emitConstructorBody(node, fields)));
371 out.write('\$${node.name.name}');
372 }
373 out.write('(');
374 _visitNode(node.parameters);
375 out.write(') {\n', 2);
376 _generateConstructorBody(node, fields);
377 out.write('}\n', -2);
378 } 336 }
379 337
380 void _generateConstructorBody( 338 String _constructorName(String className, SimpleIdentifier name) {
339 if (name == null) return className;
340 return '$className\$${name.name}';
341 }
342
343 JS.Block _emitConstructorBody(
381 ConstructorDeclaration node, List<FieldDeclaration> fields) { 344 ConstructorDeclaration node, List<FieldDeclaration> fields) {
382 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; 345 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
383 if (node.redirectedConstructor != null) { 346 if (node.redirectedConstructor != null) {
384 out.write('return new '); 347 return js.statement('{ return new #(#); }', [
385 node.redirectedConstructor.accept(this); 348 node.redirectedConstructor.accept(this),
386 out.write('('); 349 node.parameters.accept(this)
387 _visitNode(node.parameters); 350 ]);
388 out.write(');\n');
389 return;
390 } 351 }
391 352
353 var body = <JS.Statement>[];
354
392 // Generate optional/named argument value assignment. These can not have 355 // Generate optional/named argument value assignment. These can not have
393 // side effects, and may be used by the constructor's initializers, so it's 356 // side effects, and may be used by the constructor's initializers, so it's
394 // nice to do them first. 357 // nice to do them first.
395 _generateArgumentInitializers(node.parameters); 358 var init = _emitArgumentInitializers(node.parameters);
359 if (init != null) body.add(init);
396 360
397 // Redirecting constructors: these are not allowed to have initializers, 361 // Redirecting constructors: these are not allowed to have initializers,
398 // and the redirecting ctor invocation runs before field initializers. 362 // and the redirecting ctor invocation runs before field initializers.
399 var redirectCall = node.initializers.firstWhere( 363 var redirectCall = node.initializers.firstWhere(
400 (i) => i is RedirectingConstructorInvocation, orElse: () => null); 364 (i) => i is RedirectingConstructorInvocation, orElse: () => null);
401 365
402 if (redirectCall != null) { 366 if (redirectCall != null) {
403 redirectCall.accept(this); 367 body.add(redirectCall.accept(this));
404 return; 368 return new JS.Block(body);
405 } 369 }
406 370
407 // Initializers only run for non-factory constructors. 371 // Initializers only run for non-factory constructors.
408 if (node.factoryKeyword == null) { 372 if (node.factoryKeyword == null) {
409 // Generate field initializers. 373 // Generate field initializers.
410 // These are expanded into each non-redirecting constructor. 374 // These are expanded into each non-redirecting constructor.
411 // In the future we may want to create an initializer function if we have 375 // In the future we may want to create an initializer function if we have
412 // multiple constructors, but it needs to be balanced against readability. 376 // multiple constructors, but it needs to be balanced against readability.
413 _initializeFields(fields, node.parameters, node.initializers); 377 body.add(_initializeFields(fields, node.parameters, node.initializers));
414 378
415 var superCall = node.initializers.firstWhere( 379 var superCall = node.initializers.firstWhere(
416 (i) => i is SuperConstructorInvocation, orElse: () => null); 380 (i) => i is SuperConstructorInvocation, orElse: () => null);
417 381
418 // If no superinitializer is provided, an implicit superinitializer of the 382 // If no superinitializer is provided, an implicit superinitializer of the
419 // form `super()` is added at the end of the initializer list, unless the 383 // form `super()` is added at the end of the initializer list, unless the
420 // enclosing class is class Object. 384 // enclosing class is class Object.
421 if (superCall == null) { 385 var jsSuper = _superConstructorCall(node.parent, superCall);
422 _superConstructorCall(node.parent); 386 if (jsSuper != null) body.add(jsSuper);
423 } else {
424 _superConstructorCall(node.parent, node.name, superCall.constructorName,
425 superCall.argumentList);
426 }
427 } 387 }
428 388
429 var body = node.body; 389 body.add(node.body.accept(this));
430 if (body is BlockFunctionBody) { 390 return new JS.Block(body);
431 body.block.statements.accept(this);
432 } else if (body is ExpressionFunctionBody) {
433 _visitNode(body.expression, prefix: 'return ', suffix: ';\n');
434 } else {
435 assert(body is EmptyFunctionBody);
436 }
437 } 391 }
438 392
439 @override 393 @override
440 void visitRedirectingConstructorInvocation( 394 JS.Statement visitRedirectingConstructorInvocation(
441 RedirectingConstructorInvocation node) { 395 RedirectingConstructorInvocation node) {
442 var parent = node.parent as ConstructorDeclaration; 396 ClassDeclaration classDecl = node.parent.parent;
397 var className = classDecl.name.name;
443 398
444 if (parent.name != null) { 399 var name = _constructorName(className, node.constructorName);
445 out.write(parent.name.name); 400 return js.statement('this.#(#);', [name, node.argumentList.accept(this)]);
Siggi Cherem (dart-lang) 2015/02/25 18:56:02 would it work if you did '#.call(this, #)'? I'm a
Jennifer Messerly 2015/02/25 19:07:51 ah, good catch! this was actually a bug fix, I sho
446 } else {
447 _writeTypeName((parent.parent as ClassDeclaration).element.type);
448 }
449 out.write('.call(this');
450 _visitArgumentsWithCommaPrefix(node.argumentList);
451 out.write(');\n');
452 } 401 }
453 402
454 void _superConstructorCall(ClassDeclaration clazz, [SimpleIdentifier ctorName, 403 JS.Statement _superConstructorCall(ClassDeclaration clazz,
455 SimpleIdentifier superCtorName, ArgumentList args]) { 404 [SuperConstructorInvocation node]) {
405 var superCtorName = node != null ? node.constructorName : null;
406
456 var element = clazz.element; 407 var element = clazz.element;
457 if (superCtorName == null && 408 if (superCtorName == null &&
458 (element.type.isObject || element.supertype.isObject)) { 409 (element.type.isObject || element.supertype.isObject)) {
459 return; 410 return null;
460 } 411 }
461 412
462 var supertypeName = element.supertype.name; 413 var supertypeName = element.supertype.name;
463 out.write('super.$supertypeName'); 414 var name = _constructorName(supertypeName, superCtorName);
464 if (superCtorName != null) out.write('\$${superCtorName.name}'); 415
465 out.write('('); 416 var args = node != null ? node.argumentList.accept(this) : [];
466 _visitNode(args); 417 return js.statement('super.#(#);', [name, args]);
467 out.write(');\n');
468 } 418 }
469 419
470 /// Initialize fields. They follow the sequence: 420 /// Initialize fields. They follow the sequence:
471 /// 421 ///
472 /// 1. field declaration initializer if non-const, 422 /// 1. field declaration initializer if non-const,
473 /// 2. field initializing parameters, 423 /// 2. field initializing parameters,
474 /// 3. constructor field initializers, 424 /// 3. constructor field initializers,
475 /// 4. initialize fields not covered in 1-3 425 /// 4. initialize fields not covered in 1-3
476 void _initializeFields(List<FieldDeclaration> fields, 426 JS.Statement _initializeFields(List<FieldDeclaration> fields,
477 [FormalParameterList parameters, 427 [FormalParameterList parameters,
478 NodeList<ConstructorInitializer> initializers]) { 428 NodeList<ConstructorInitializer> initializers]) {
429 var body = <JS.Statement>[];
479 430
480 // Run field initializers if they can have side-effects. 431 // Run field initializers if they can have side-effects.
481 var unsetFields = new Map<String, VariableDeclaration>(); 432 var unsetFields = new Map<String, VariableDeclaration>();
482 for (var declaration in fields) { 433 for (var declaration in fields) {
483 for (var field in declaration.fields.variables) { 434 for (var field in declaration.fields.variables) {
484 if (_isFieldInitConstant(field)) { 435 if (_isFieldInitConstant(field)) {
485 unsetFields[field.name.name] = field; 436 unsetFields[field.name.name] = field;
486 } else { 437 } else {
487 _visitNode(field, suffix: ';\n'); 438 body.add(js.statement(
439 '# = #;', [field.name.accept(this), _visitInitializer(field)]));
488 } 440 }
489 } 441 }
490 } 442 }
491 443
492 // Initialize fields from `this.fieldName` parameters. 444 // Initialize fields from `this.fieldName` parameters.
493 if (parameters != null) { 445 if (parameters != null) {
494 for (var p in parameters.parameters) { 446 for (var p in parameters.parameters) {
495 if (p is DefaultFormalParameter) p = p.parameter; 447 if (p is DefaultFormalParameter) p = p.parameter;
496 if (p is FieldFormalParameter) { 448 if (p is FieldFormalParameter) {
497 var name = p.identifier.name; 449 var name = p.identifier.name;
498 out.write('this.$name = $name;\n'); 450 body.add(js.statement('this.# = #;', [name, name]));
499 unsetFields.remove(name); 451 unsetFields.remove(name);
500 } 452 }
501 } 453 }
502 } 454 }
503 455
504 // Run constructor field initializers such as `: foo = bar.baz` 456 // Run constructor field initializers such as `: foo = bar.baz`
505 if (initializers != null) { 457 if (initializers != null) {
506 for (var init in initializers) { 458 for (var init in initializers) {
507 if (init is ConstructorFieldInitializer) { 459 if (init is ConstructorFieldInitializer) {
508 init.fieldName.accept(this); 460 body.add(js.statement('# = #;', [
509 out.write(' = '); 461 init.fieldName.accept(this),
510 init.expression.accept(this); 462 init.expression.accept(this)
511 out.write(';\n'); 463 ]));
512 unsetFields.remove(init.fieldName.name); 464 unsetFields.remove(init.fieldName.name);
513 } 465 }
514 } 466 }
515 } 467 }
516 468
517 // Initialize all remaining fields 469 // Initialize all remaining fields
518 unsetFields.forEach((name, field) { 470 unsetFields.forEach((name, field) {
519 out.write('this.$name = '); 471 JS.Expression value;
520 var expression = field.initializer; 472 if (field.initializer != null) {
521 if (expression != null) { 473 value = field.initializer.accept(this);
522 expression.accept(this);
523 } else { 474 } else {
524 var type = rules.elementType(field.element); 475 var type = rules.elementType(field.element);
525 if (rules.maybeNonNullableType(type)) { 476 if (rules.maybeNonNullableType(type)) {
526 out.write('dart.as(null, '); 477 value = js.call('dart.as(null, #)', _emitTypeName(type));
527 _writeTypeName(type);
528 out.write(')');
529 } else { 478 } else {
530 out.write('null'); 479 value = new JS.LiteralNull();
531 } 480 }
532 } 481 }
533 out.write(';\n'); 482 body.add(js.statement('this.# = #;', [name, value]));
534 }); 483 });
484
485 return _statement(body);
535 } 486 }
536 487
537 FormalParameterList _parametersOf(node) { 488 FormalParameterList _parametersOf(node) {
489 // Note: ConstructorDeclaration is intentionally skipped here so we can
490 // emit the argument initializers in a different place.
491 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we
492 // could handle argument initializers more consistently in a separate
493 // lowering pass.
538 if (node is MethodDeclaration) return node.parameters; 494 if (node is MethodDeclaration) return node.parameters;
539 if (node is FunctionDeclaration) node = node.functionExpression; 495 if (node is FunctionDeclaration) node = node.functionExpression;
540 if (node is FunctionExpression) return node.parameters; 496 if (node is FunctionExpression) return node.parameters;
541 return null; 497 return null;
542 } 498 }
543 499
544 bool _hasArgumentInitializers(FormalParameterList parameters) { 500 bool _hasArgumentInitializers(FormalParameterList parameters) {
545 if (parameters == null) return false; 501 if (parameters == null) return false;
546 return parameters.parameters.any((p) => p.kind != ParameterKind.REQUIRED); 502 return parameters.parameters.any((p) => p.kind != ParameterKind.REQUIRED);
547 } 503 }
548 504
549 void _generateArgumentInitializers(FormalParameterList parameters) { 505 JS.Statement _emitArgumentInitializers(FormalParameterList parameters) {
550 if (parameters == null) return; 506 if (parameters == null || !_hasArgumentInitializers(parameters)) {
507 return null;
508 }
509
510 var body = [];
551 for (var param in parameters.parameters) { 511 for (var param in parameters.parameters) {
552 // TODO(justinfagnani): rename identifier if necessary 512 // TODO(justinfagnani): rename identifier if necessary
553 var name = param.identifier.name; 513 var name = param.identifier.name;
554 514
555 if (param.kind == ParameterKind.NAMED) { 515 if (param.kind == ParameterKind.NAMED) {
556 out.write('let $name = opt\$.$name === undefined ? '); 516 body.add(js.statement('let # = opt\$.# === void 0 ? # : opt\$.#;', [
557 if (param is DefaultFormalParameter && param.defaultValue != null) { 517 name,
558 param.defaultValue.accept(this); 518 name,
559 } else { 519 _defaultParamValue(param),
560 out.write('null'); 520 name
561 } 521 ]));
562 out.write(' : opt\$.$name;\n');
563 } else if (param.kind == ParameterKind.POSITIONAL) { 522 } else if (param.kind == ParameterKind.POSITIONAL) {
564 out.write('if ($name === undefined) $name = '); 523 body.add(js.statement('if (# === void 0) # = #;', [
565 if (param is DefaultFormalParameter && param.defaultValue != null) { 524 name,
566 param.defaultValue.accept(this); 525 name,
567 } else { 526 _defaultParamValue(param)
568 out.write('null'); 527 ]));
569 } 528 }
570 out.write(';\n'); 529 }
571 } 530 return _statement(body);
572 } 531 }
573 } 532
574 533 JS.Expression _defaultParamValue(FormalParameter param) {
575 @override 534 if (param is DefaultFormalParameter && param.defaultValue != null) {
576 void visitMethodDeclaration(MethodDeclaration node) { 535 return param.defaultValue.accept(this);
577 if (node.isAbstract) return; 536 } else {
578 if (node.externalKeyword != null) { 537 return new JS.LiteralNull();
579 out.write('/* Unimplemented $node */\n'); 538 }
580 return; 539 }
581 } 540
582 541 @override
583 if (node.isStatic) { 542 JS.Method visitMethodDeclaration(MethodDeclaration node) {
584 out.write('static '); 543 if (node.isAbstract || node.externalKeyword != null) return null;
585 } 544
586 if (node.isGetter) { 545 var params = _visit(node.parameters);
587 out.write('get '); 546 if (params == null) params = [];
588 } else if (node.isSetter) { 547
589 out.write('set '); 548 return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)),
590 } 549 new JS.Fun(params, node.body.accept(this)),
591 550 isGetter: node.isGetter,
592 var name = _canonicalMethodName(node.name.name); 551 isSetter: node.isSetter,
593 out.write('$name('); 552 isStatic: node.isStatic);
594 _visitNode(node.parameters); 553 }
595 out.write(') '); 554
596 _visitNode(node.body); 555 @override
597 out.write('\n'); 556 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) {
598 }
599
600 @override
601 void visitFunctionDeclaration(FunctionDeclaration node) {
602 assert(node.parent is CompilationUnit); 557 assert(node.parent is CompilationUnit);
603 558
604 if (node.externalKeyword != null) { 559 if (node.externalKeyword != null) return null;
605 // TODO(jmesserly): the toString visitor in Analyzer doesn't include the
606 // external keyword for FunctionDeclaration.
607 out.write('/* Unimplemented external $node */\n');
608 return;
609 }
610 560
611 if (node.isGetter || node.isSetter) { 561 if (node.isGetter || node.isSetter) {
612 // Add these later so we can use getter/setter syntax. 562 // Add these later so we can use getter/setter syntax.
613 _properties.add(node); 563 _properties.add(node);
614 } else { 564 return null;
615 _flushLibraryProperties(); 565 }
616 _writeFunctionDeclaration(node); 566
617 } 567 var body = <JS.Statement>[];
618 } 568 _flushLibraryProperties(body);
619 569
620 void _writeFunctionDeclaration(FunctionDeclaration node) {
621 var name = node.name.name; 570 var name = node.name.name;
622 571 body.add(js.comment('Function $name: ${node.element.type}'));
623 if (node.isGetter) { 572
624 out.write('get '); 573 body.add(new JS.FunctionDeclaration(new JS.VariableDeclaration(name),
625 } else if (node.isSetter) { 574 node.functionExpression.accept(this)));
626 out.write('set '); 575
627 } else { 576 if (isPublic(name)) _exports.add(name);
628 out.write("// Function $name: ${node.element.type}\n"); 577 return _statement(body);
629 out.write('function '); 578 }
630 } 579
631 580 JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
632 out.write('$name'); 581 var name = node.name.name;
633 node.functionExpression.accept(this); 582 if (isPublic(name)) _exports.add(name);
634 583 return new JS.Method(
635 if (!node.isGetter && !node.isSetter) { 584 new JS.PropertyName(name), node.functionExpression.accept(this),
636 out.write('\n'); 585 isGetter: node.isGetter, isSetter: node.isSetter);
637 if (isPublic(name)) _exports.add(name); 586 }
638 out.write('\n'); 587
639 } 588 @override
640 } 589 JS.Expression visitFunctionExpression(FunctionExpression node) {
641 590 var params = _visit(node.parameters);
642 @override 591 if (params == null) params = [];
643 void visitFunctionExpression(FunctionExpression node) { 592
644 if (node.parent is FunctionDeclaration) { 593 if (node.parent is FunctionDeclaration) {
645 out.write('('); 594 return new JS.Fun(params, node.body.accept(this));
646 _visitNode(node.parameters); 595 } else {
647 out.write(') '); 596 var bindThis = _maybeBindThis(node.body);
648 node.body.accept(this); 597
649 } else { 598 String code;
650 var bindThis = _needsBindThis(node.body); 599 AstNode body;
651 if (bindThis) out.write("("); 600 var nodeBody = node.body;
652 out.write("("); 601 if (nodeBody is ExpressionFunctionBody) {
653 _visitNode(node.parameters); 602 code = '(#) => #';
654 out.write(") => "); 603 body = nodeBody.expression;
655 var body = node.body; 604 } else {
656 if (body is ExpressionFunctionBody) body = body.expression; 605 code = '(#) => { #; }';
657 body.accept(this); 606 body = nodeBody;
658 if (bindThis) out.write(").bind(this)"); 607 }
659 } 608 return js.call('($code)$bindThis', [params, body.accept(this)]);
660 } 609 }
661 610 }
662 @override 611
663 void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { 612 @override
613 JS.Statement visitFunctionDeclarationStatement(
614 FunctionDeclarationStatement node) {
664 var func = node.functionDeclaration; 615 var func = node.functionDeclaration;
665 if (func.isGetter || func.isSetter) { 616 if (func.isGetter || func.isSetter) {
666 out.write('/* Unimplemented function get/set statement: $node */'); 617 return js.comment('Unimplemented function get/set statement: $node');
667 return; 618 }
668 } 619
669 620 var name = new JS.VariableDeclaration(func.name.name);
670 var name = func.name.name; 621 return new JS.Block([
671 out.write("// Function $name: ${func.element.type}\n"); 622 js.comment("// Function ${func.name.name}: ${func.element.type}\n"),
672 out.write('function $name'); 623 new JS.FunctionDeclaration(name, func.functionExpression.accept(this))
673 func.functionExpression.accept(this); 624 ]);
674 out.write('\n');
675 } 625 }
676 626
677 /// Writes a simple identifier. This can handle implicit `this` as well as 627 /// Writes a simple identifier. This can handle implicit `this` as well as
678 /// going through the qualified library name if necessary. 628 /// going through the qualified library name if necessary.
679 @override 629 @override
680 void visitSimpleIdentifier(SimpleIdentifier node) { 630 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
681 var e = node.staticElement; 631 var e = node.staticElement;
682 if (e == null) { 632 if (e == null) {
683 out.write('/* Unimplemented unknown name */'); 633 return js.commentExpression(
684 } else { 634 'Unimplemented unknown name', new JS.VariableUse(node.name));
685 if (e.enclosingElement is CompilationUnitElement && 635 }
686 (e.library != libraryInfo.library || _needsModuleGetter(e))) { 636 var name = node.name;
687 out.write('${jsLibraryName(e.library)}.'); 637 if (e.enclosingElement is CompilationUnitElement &&
688 } else if (currentClass != null && _needsImplicitThis(e)) { 638 (e.library != libraryInfo.library || _needsModuleGetter(e))) {
689 out.write('this.'); 639 return js.call('#.#', [jsLibraryName(e.library), name]);
690 } 640 } else if (currentClass != null && _needsImplicitThis(e)) {
691 } 641 return js.call('this.#', name);
692 out.write(node.name); 642 }
693 } 643 return new JS.VariableUse(name);
694 644 }
695 void _writeTypeName(DartType type) { 645
646 JS.Expression _emitTypeName(DartType type) {
696 var name = type.name; 647 var name = type.name;
697 var lib = type.element.library; 648 var lib = type.element.library;
698 if (name == '') { 649 if (name == '') {
699 // TODO(jmesserly): remove when we're using coercion reifier. 650 // TODO(jmesserly): remove when we're using coercion reifier.
700 out.write('/* Unimplemented type $type */'); 651 return _unimplementedCall('Unimplemented type $type');
701 return; 652 }
702 } 653
703 654 var typeArgs = null;
704 if (lib != currentLibrary && lib != null) {
705 out.write(jsLibraryName(lib));
706 out.write('.');
707 }
708 out.write(name);
709
710 if (type is ParameterizedType) { 655 if (type is ParameterizedType) {
711 // TODO(jmesserly): this is a workaround for an analyzer bug, see: 656 // TODO(jmesserly): this is a workaround for an analyzer bug, see:
712 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6 26dd8d16881cdb8e8b9c3fa 657 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6 26dd8d16881cdb8e8b9c3fa
713 if (type is! FunctionType || type.element is FunctionTypeAlias) { 658 if (type is! FunctionType || type.element is FunctionTypeAlias) {
714 var args = type.typeArguments; 659 var args = type.typeArguments;
715 if (args.any((a) => a != rules.provider.dynamicType)) { 660 if (args.any((a) => a != rules.provider.dynamicType)) {
716 out.write('\$('); 661 name = '$name\$';
717 for (var arg in args) { 662 typeArgs = args.map(_emitTypeName);
718 if (arg != args.first) out.write(', ');
719 _writeTypeName(arg);
720 }
721 out.write(')');
722 } 663 }
723 } 664 }
724 } 665 }
725 } 666
726 667 JS.Expression result;
727 @override 668 if (lib != currentLibrary && lib != null) {
728 void visitAssignmentExpression(AssignmentExpression node) { 669 result = js.call('#.#', [jsLibraryName(lib), name]);
670 } else {
671 result = new JS.VariableUse(name);
672 }
673
674 if (typeArgs != null) {
675 result = js.call('#(#)', [result, typeArgs]);
676 }
677 return result;
678 }
679
680 @override
681 JS.Node visitAssignmentExpression(AssignmentExpression node) {
729 var lhs = node.leftHandSide; 682 var lhs = node.leftHandSide;
730 var rhs = node.rightHandSide; 683 var rhs = node.rightHandSide;
731 if (lhs is IndexExpression) { 684 if (lhs is IndexExpression) {
685 String code;
732 var target = _getTarget(lhs); 686 var target = _getTarget(lhs);
733 if (rules.isDynamicTarget(target)) { 687 if (rules.isDynamicTarget(target)) {
734 out.write('dart.dsetindex('); 688 code = 'dart.dsetindex(#, #, #)';
735 target.accept(this);
736 out.write(', ');
737 lhs.index.accept(this);
738 out.write(', ');
739 rhs.accept(this);
740 out.write(')');
741 } else { 689 } else {
742 target.accept(this); 690 code = '#.set(#, #)';
743 out.write('.set('); 691 }
744 lhs.index.accept(this); 692 return js.call(code, [
745 out.write(', '); 693 target.accept(this),
746 rhs.accept(this); 694 lhs.index.accept(this),
747 out.write(')'); 695 rhs.accept(this)
748 } 696 ]);
749 return;
750 } 697 }
751 698
752 if (lhs is PropertyAccess) { 699 if (lhs is PropertyAccess) {
753 var target = _getTarget(lhs); 700 var target = _getTarget(lhs);
754 if (rules.isDynamicTarget(target)) { 701 if (rules.isDynamicTarget(target)) {
755 out.write('dart.dput('); 702 return js.call('dart.dput(#, #, #)', [
756 target.accept(this); 703 target.accept(this),
757 out.write(', "${lhs.propertyName.name}", '); 704 js.string(lhs.propertyName.name, "'"),
758 rhs.accept(this); 705 rhs.accept(this)
759 out.write(')'); 706 ]);
760 return; 707 }
761 } 708 }
762 } 709
763 710 if (node.parent is ExpressionStatement &&
764 lhs.accept(this); 711 rhs is CascadeExpression &&
765 out.write(' = '); 712 _isStateless(lhs, rhs)) {
766 rhs.accept(this); 713 // Special case: cascade assignment to a variable in a statement.
767 } 714 // We can reuse the variable to desugar it:
768 715 // result = []..length = length;
769 @override 716 // becomes:
770 void visitExpressionFunctionBody(ExpressionFunctionBody node) { 717 // result = [];
771 var parameters = _parametersOf(node.parent); 718 // result.length = length;
772 var initArgs = parameters != null && _hasArgumentInitializers(parameters); 719 var savedCascadeTemp = _cascadeTarget;
773 if (initArgs) { 720 _cascadeTarget = lhs;
774 out.write('{\n', 2); 721
775 _generateArgumentInitializers(parameters); 722 var body = [];
776 } else { 723 body.add(
777 out.write('{ '); 724 js.statement('# = #;', [lhs.accept(this), rhs.target.accept(this)]));
778 } 725 for (var section in rhs.cascadeSections) {
779 out.write('return '); 726 body.add(new JS.ExpressionStatement(section.accept(this)));
780 node.expression.accept(this); 727 }
781 if (initArgs) { 728
782 out.write('\n}', -2); 729 _cascadeTarget = savedCascadeTemp;
783 } else { 730 return _statement(body);
784 out.write('; }'); 731 }
785 } 732
786 } 733 return js.call('# = #', [lhs.accept(this), rhs.accept(this)]);
787 734 }
788 @override 735
789 void visitEmptyFunctionBody(EmptyFunctionBody node) { 736 @override
790 out.write('{}'); 737 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) {
791 } 738 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent));
792 739 var ret = new JS.Return(node.expression.accept(this));
793 @override 740 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]);
794 void visitBlockFunctionBody(BlockFunctionBody node) { 741 }
795 out.write('{\n', 2); 742
796 _generateArgumentInitializers(_parametersOf(node.parent)); 743 @override
797 _visitNodeList(node.block.statements); 744 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]);
798 out.write('}', -2); 745
799 } 746 @override
800 747 JS.Block visitBlockFunctionBody(BlockFunctionBody node) {
801 @override 748 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent));
802 void visitBlock(Block node) { 749 var block = visitBlock(node.block);
803 out.write("{\n", 2); 750 if (initArgs != null) return new JS.Block([initArgs, block]);
804 node.statements.accept(this); 751 return block;
805 out.write("}\n", -2); 752 }
806 } 753
807 754 @override
808 @override 755 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements));
809 void visitMethodInvocation(MethodInvocation node) { 756
757 @override
758 JS.Expression visitMethodInvocation(MethodInvocation node) {
810 var target = node.isCascaded ? _cascadeTarget : node.target; 759 var target = node.isCascaded ? _cascadeTarget : node.target;
811 760
812 if (rules.isDynamicCall(node.methodName)) { 761 if (rules.isDynamicCall(node.methodName)) {
762 var args = node.argumentList.accept(this);
813 if (target != null) { 763 if (target != null) {
814 out.write('dart.dinvoke('); 764 return js.call('dart.dinvoke(#, #, #)', [
815 target.accept(this); 765 target.accept(this),
816 out.write(', "${node.methodName.name}"'); 766 js.string(node.methodName.name, "'"),
767 args
768 ]);
817 } else { 769 } else {
818 out.write('dart.dinvokef('); 770 return js.call(
819 node.methodName.accept(this); 771 'dart.dinvokef(#, #)', [node.methodName.accept(this), args]);
820 } 772 }
821
822 _visitArgumentsWithCommaPrefix(node.argumentList);
823 out.write(')');
824 return;
825 } 773 }
826 774
827 // TODO(jmesserly): if this resolves to a getter returning a function with 775 // TODO(jmesserly): if this resolves to a getter returning a function with
828 // a call method, we don't generate the `.call` correctly. 776 // a call method, we don't generate the `.call` correctly.
777
778 var targetJs;
829 if (target != null) { 779 if (target != null) {
830 target.accept(this); 780 targetJs = js.call('#.#', [target.accept(this), node.methodName.name]);
831 out.write('.${node.methodName.name}'); 781 } else {
832 } else { 782 targetJs = node.methodName.accept(this);
833 node.methodName.accept(this); 783 }
834 } 784
835 785 return js.call('#(#)', [targetJs, node.argumentList.accept(this)]);
836 out.write('('); 786 }
837 node.argumentList.accept(this); 787
838 out.write(')'); 788 @override
839 } 789 JS.Expression visitFunctionExpressionInvocation(
840 790 FunctionExpressionInvocation node) {
841 @override 791 var code;
842 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
843 if (rules.isDynamicCall(node.function)) { 792 if (rules.isDynamicCall(node.function)) {
844 out.write('dart.dinvokef('); 793 code = 'dart.dinvokef(#, #)';
845 node.function.accept(this); 794 } else {
846 _visitArgumentsWithCommaPrefix(node.argumentList); 795 code = '#(#)';
847 out.write(')'); 796 }
848 } else { 797 return js.call(
849 node.function.accept(this); 798 code, [node.function.accept(this), node.argumentList.accept(this)]);
850 out.write('('); 799 }
851 node.argumentList.accept(this); 800
852 out.write(')'); 801 @override
853 } 802 List<JS.Expression> visitArgumentList(ArgumentList node) {
854 } 803 var args = <JS.Expression>[];
855 804 var named = <JS.Property>[];
856 /// Writes an argument list. This does not write the parens, because sometimes 805 for (var arg in node.arguments) {
857 /// a parameter will need to be added before the start of the list, so
858 /// writing parens is the responsibility of the parent node.
859 @override
860 void visitArgumentList(ArgumentList node) {
861 var args = node.arguments;
862
863 bool hasNamed = false;
864 for (int i = 0; i < args.length; i++) {
865 if (i > 0) out.write(', ');
866
867 var arg = args[i];
868 if (arg is NamedExpression) { 806 if (arg is NamedExpression) {
869 if (!hasNamed) out.write('{'); 807 named.add(visitNamedExpression(arg));
870 hasNamed = true; 808 } else {
871 } 809 args.add(arg.accept(this));
872 arg.accept(this); 810 }
873 } 811 }
874 if (hasNamed) out.write('}'); 812 if (named.isNotEmpty) {
875 } 813 args.add(new JS.ObjectInitializer(named));
876 814 }
877 void _visitArgumentsWithCommaPrefix(ArgumentList node) { 815 return args;
878 if (node == null) return; 816 }
879 if (node.arguments.isNotEmpty) out.write(', '); 817
880 visitArgumentList(node); 818 @override
881 } 819 JS.Property visitNamedExpression(NamedExpression node) {
882
883 @override
884 void visitNamedExpression(NamedExpression node) {
885 assert(node.parent is ArgumentList); 820 assert(node.parent is ArgumentList);
886 node.name.accept(this); 821 return new JS.Property(new JS.PropertyName(node.name.label.name),
887 out.write(' '); 822 node.expression.accept(this));
888 node.expression.accept(this); 823 }
889 } 824
890 825 @override
891 @override 826 List<JS.Parameter> visitFormalParameterList(FormalParameterList node) {
892 void visitFormalParameterList(FormalParameterList node) { 827 var result = <JS.Parameter>[];
893 int length = node.parameters.length; 828 for (FormalParameter param in node.parameters) {
894 bool hasOptionalParameters = false;
895 bool hasPositionalParameters = false;
896
897 for (int i = 0; i < length; i++) {
898 var param = node.parameters[i];
899 if (param.kind == ParameterKind.NAMED) { 829 if (param.kind == ParameterKind.NAMED) {
900 hasOptionalParameters = true; 830 result.add(new JS.Parameter(_jsNamedParameterName));
901 } else { 831 break;
902 if (hasPositionalParameters) out.write(', '); 832 }
903 hasPositionalParameters = true; 833 result.add(new JS.Parameter(param.identifier.name));
904 param.accept(this); 834 }
905 } 835 return result;
906 } 836 }
907 if (hasOptionalParameters) { 837
908 if (hasPositionalParameters) out.write(', '); 838 @override
909 out.write(optionalParameters); 839 JS.Statement visitExpressionStatement(ExpressionStatement node) =>
910 } 840 _expressionStatement(node.expression.accept(this));
911 } 841
912 842 // Some expressions may choose to generate themselves as JS statements
913 @override 843 // if their parent is in a statement context.
914 void visitFieldFormalParameter(FieldFormalParameter node) { 844 // TODO(jmesserly): refactor so we handle the special cases here, and
915 // Named parameters are handled as a single object, so we skip individual 845 // can use better return types on the expression visit methods.
916 // parameters 846 JS.Statement _expressionStatement(expr) =>
917 if (node.kind != ParameterKind.NAMED) { 847 expr is JS.Statement ? expr : new JS.ExpressionStatement(expr);
918 out.write(node.identifier.name); 848
919 } 849 @override
920 } 850 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) =>
921 851 new JS.EmptyStatement();
922 @override 852
923 void visitDefaultFormalParameter(DefaultFormalParameter node) { 853 @override
924 // Named parameters are handled as a single object, so we skip individual 854 JS.Statement visitAssertStatement(AssertStatement node) =>
925 // parameters 855 // TODO(jmesserly): only emit in checked mode.
926 if (node.kind != ParameterKind.NAMED) { 856 js.statement('dart.assert(#);', node.condition.accept(this));
927 out.write(node.identifier.name); 857
928 } 858 @override
929 } 859 JS.Return visitReturnStatement(ReturnStatement node) =>
930 860 new JS.Return(_visit(node.expression));
931 @override 861
932 void visitExpressionStatement(ExpressionStatement node) { 862 @override
933 node.expression.accept(this); 863 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
934 out.write(';\n'); 864 var body = <JS.Statement>[];
935 } 865
936
937 @override
938 void visitEmptyStatement(EmptyStatement node) {
939 out.write(';\n');
940 }
941
942 @override
943 void visitAssertStatement(AssertStatement node) {
944 // TODO(jmesserly): only emit in checked mode.
945 _visitNode(node.condition, prefix: 'dart.assert(', suffix: ');\n');
946 }
947
948 @override
949 void visitReturnStatement(ReturnStatement node) {
950 out.write('return');
951 _visitNode(node.expression, prefix: ' ');
952 out.write(';\n');
953 }
954
955 @override
956 void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
957 for (var field in node.variables.variables) { 866 for (var field in node.variables.variables) {
958 var name = field.name.name;
959 if (field.isConst) { 867 if (field.isConst) {
960 // constant fields don't change, so we can generate them as `let` 868 // constant fields don't change, so we can generate them as `let`
961 // but add them to the module's exports 869 // but add them to the module's exports
962 _visitNode(field, prefix: 'let ', suffix: ';\n'); 870 var name = field.name.name;
871 body.add(js.statement('let # = #;', [
872 new JS.VariableDeclaration(name),
873 _visitInitializer(field)
874 ]));
963 if (isPublic(name)) _exports.add(name); 875 if (isPublic(name)) _exports.add(name);
964 } else if (_isFieldInitConstant(field)) { 876 } else if (_isFieldInitConstant(field)) {
965 _visitNode(field, suffix: ';\n'); 877 body.add(js.statement(
878 '# = #;', [field.name.accept(this), _visitInitializer(field)]));
966 } else { 879 } else {
967 _lazyFields.add(field); 880 _lazyFields.add(field);
968 } 881 }
969 } 882 }
970 } 883
971 884 return _statement(body);
972 @override 885 }
973 void visitVariableDeclarationList(VariableDeclarationList node) { 886
974 _visitNodeList(node.variables, prefix: 'let ', separator: ', '); 887 @override
975 } 888 visitVariableDeclarationList(VariableDeclarationList node) {
976 889 var last = node.variables.last;
977 @override 890 var lastInitializer = last.initializer;
978 void visitVariableDeclaration(VariableDeclaration node) { 891
979 node.name.accept(this); 892 List<JS.VariableInitialization> variables;
980 out.write(' = '); 893 if (lastInitializer is CascadeExpression &&
981 if (node.initializer != null) { 894 node.parent is VariableDeclarationStatement) {
982 node.initializer.accept(this); 895 // Special case: cascade as variable initializer
983 } else { 896 //
984 // explicitly initialize to null, so we don't need to worry about 897 // We can reuse the variable to desugar it:
985 // `undefined`. 898 // var result = []..length = length;
986 // TODO(jmesserly): do this only for vars that aren't definitely assigned. 899 // becomes:
987 out.write('null'); 900 // var result = [];
988 } 901 // result.length = length;
989 } 902 var savedCascadeTemp = _cascadeTarget;
990 903 _cascadeTarget = last.name;
991 void _flushLazyFields() { 904
992 if (_lazyFields.isEmpty) return; 905 variables = _visitList(node.variables.take(node.variables.length - 1));
993 906 variables.add(new JS.VariableInitialization(
994 _writeLazyFields(_libraryName, _lazyFields); 907 new JS.VariableDeclaration(last.name.name),
995 out.write('\n'); 908 lastInitializer.target.accept(this)));
996 909
910 var result = <JS.Expression>[
911 new JS.VariableDeclarationList('let', variables)
912 ];
913 result.addAll(_visitList(lastInitializer.cascadeSections));
914 _cascadeTarget = savedCascadeTemp;
915 return _statement(result.map((e) => new JS.ExpressionStatement(e)));
916 } else {
917 variables = _visitList(node.variables);
918 }
919
920 return new JS.VariableDeclarationList('let', variables);
921 }
922
923 @override
924 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) {
925 var name = new JS.VariableDeclaration(node.name.name);
926 return new JS.VariableInitialization(name, _visitInitializer(node));
927 }
928
929 JS.Expression _visitInitializer(VariableDeclaration node) {
930 var value = _visit(node.initializer);
931 // explicitly initialize to null, to avoid getting `undefined`.
932 // TODO(jmesserly): do this only for vars that aren't definitely assigned.
933 return value != null ? value : new JS.LiteralNull();
934 }
935
936 void _flushLazyFields(List<JS.Statement> body) {
937 var code = _emitLazyFields(_libraryName, _lazyFields);
938 if (code != null) body.add(code);
997 _lazyFields.clear(); 939 _lazyFields.clear();
998 } 940 }
999 941
1000 void _writeLazyFields(String objExpr, List<VariableDeclaration> fields) { 942 JS.Statement _emitLazyFields(
1001 if (fields.isEmpty) return; 943 String objExpr, List<VariableDeclaration> fields) {
1002 944 if (fields.isEmpty) return null;
1003 out.write('dart.defineLazyProperties($objExpr, {\n', 2); 945
946 var methods = [];
1004 for (var node in fields) { 947 for (var node in fields) {
1005 var name = node.name.name; 948 var name = node.name.name;
1006 out.write('get $name() { return '); 949 methods.add(new JS.Method(new JS.PropertyName(name),
1007 node.initializer.accept(this); 950 js.call('function() { return #; }', node.initializer.accept(this)),
1008 out.write(' },\n'); 951 isGetter: true));
1009 // TODO(jmesserly): we're using a dummy setter to indicate writable. 952
1010 if (!node.isFinal) out.write('set $name(x) {},\n'); 953 // TODO(jmesserly): use a dummy setter to indicate writable.
Jennifer Messerly 2015/02/25 18:36:20 this is the dummy setter code. I agree we should f
1011 } 954 if (!node.isFinal) {
1012 out.write('});\n', -2); 955 methods.add(new JS.Method(
1013 } 956 new JS.PropertyName(name), js.call('function() {}'),
1014 957 isSetter: true));
1015 void _flushLibraryProperties() { 958 }
959 }
960
961 return js.statement(
962 'dart.defineLazyProperties(#, { # })', [objExpr, methods]);
963 }
964
965 void _flushLibraryProperties(List<JS.Statement> body) {
1016 if (_properties.isEmpty) return; 966 if (_properties.isEmpty) return;
1017 967 body.add(js.statement('dart.copyProperties(#, { # });', [
1018 out.write('dart.copyProperties($_libraryName, {\n', 2); 968 _libraryName,
1019 for (var node in _properties) { 969 _properties.map(_emitTopLevelProperty)
1020 _writeFunctionDeclaration(node); 970 ]));
1021 out.write(',\n');
1022 }
1023 out.write('});\n\n', -2);
1024
1025 _properties.clear(); 971 _properties.clear();
1026 } 972 }
1027 973
1028 @override 974 @override
1029 void visitVariableDeclarationStatement(VariableDeclarationStatement node) { 975 JS.Statement visitVariableDeclarationStatement(
1030 _visitNode(node.variables); 976 VariableDeclarationStatement node) =>
1031 out.write(';\n'); 977 _expressionStatement(node.variables.accept(this));
1032 } 978
1033 979 @override
1034 @override 980 visitConstructorName(ConstructorName node) {
1035 void visitConstructorName(ConstructorName node) { 981 var typeName = node.type.name.accept(this);
1036 node.type.name.accept(this);
1037 if (node.name != null) { 982 if (node.name != null) {
1038 out.write('.'); 983 return js.call('#.#', [typeName, node.name.name]);
1039 node.name.accept(this); 984 }
1040 } 985 return typeName;
1041 } 986 }
1042 987
1043 @override 988 @override
1044 void visitInstanceCreationExpression(InstanceCreationExpression node) { 989 visitInstanceCreationExpression(InstanceCreationExpression node) {
1045 out.write('new '); 990 return js.call('new #(#)', [
1046 node.constructorName.accept(this); 991 node.constructorName.accept(this),
1047 out.write('('); 992 node.argumentList.accept(this)
1048 node.argumentList.accept(this); 993 ]);
1049 out.write(')');
1050 } 994 }
1051 995
1052 /// True if this type is built-in to JS, and we use the values unwrapped. 996 /// True if this type is built-in to JS, and we use the values unwrapped.
1053 /// For these types we generate a calling convention via static 997 /// For these types we generate a calling convention via static
1054 /// "extension methods". This allows types to be extended without adding 998 /// "extension methods". This allows types to be extended without adding
1055 /// extensions directly on the prototype. 999 /// extensions directly on the prototype.
1056 bool _isJSBuiltinType(DartType t) => 1000 bool _isJSBuiltinType(DartType t) =>
1057 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t); 1001 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t);
1058 1002
1059 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) && 1003 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) &&
1060 (rules.isIntType(t) || 1004 (rules.isIntType(t) ||
1061 rules.isDoubleType(t) || 1005 rules.isDoubleType(t) ||
1062 rules.isBoolType(t) || 1006 rules.isBoolType(t) ||
1063 rules.isNumType(t)); 1007 rules.isNumType(t));
1064 1008
1065 bool typeIsNonNullablePrimitiveInJS(DartType t) => 1009 bool typeIsNonNullablePrimitiveInJS(DartType t) =>
1066 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); 1010 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t);
1067 1011
1068 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => 1012 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) =>
1069 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); 1013 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT);
1070 1014
1071 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); 1015 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t);
1072 1016
1073 void notNull(Expression expr) { 1017 JS.Expression notNull(Expression expr) {
1074 var type = rules.getStaticType(expr); 1018 var type = rules.getStaticType(expr);
1075 if (rules.isNonNullableType(type)) { 1019 if (rules.isNonNullableType(type)) {
1076 expr.accept(this); 1020 return expr.accept(this);
1077 } else { 1021 } else {
1078 out.write('dart.notNull('); 1022 return js.call('dart.notNull(#)', expr.accept(this));
1079 expr.accept(this);
1080 out.write(')');
1081 } 1023 }
1082 } 1024 }
1083 1025
1084 @override 1026 @override
1085 void visitBinaryExpression(BinaryExpression node) { 1027 JS.Expression visitBinaryExpression(BinaryExpression node) {
1086 var op = node.operator; 1028 var op = node.operator;
1087 var lhs = node.leftOperand; 1029 var left = node.leftOperand;
1088 var rhs = node.rightOperand; 1030 var right = node.rightOperand;
1031 var leftType = rules.getStaticType(left);
1032 var rightType = rules.getStaticType(right);
1089 1033
1090 var dispatchType = rules.getStaticType(lhs); 1034 var code;
1091 var otherType = rules.getStaticType(rhs);
1092
1093 if (op.type.isEqualityOperator) { 1035 if (op.type.isEqualityOperator) {
1094 // If we statically know LHS or RHS is null we can generate a clean check. 1036 // If we statically know LHS or RHS is null we can generate a clean check.
1095 // We can also do this if the left hand side is a primitive type, because 1037 // We can also do this if the left hand side is a primitive type, because
1096 // we know then it doesn't have an overridden. 1038 // we know then it doesn't have an overridden.
1097 if (_isNull(lhs) || _isNull(rhs) || typeIsPrimitiveInJS(dispatchType)) { 1039 if (_isNull(left) || _isNull(right) || typeIsPrimitiveInJS(leftType)) {
1098 lhs.accept(this);
1099 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison 1040 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison
1100 out.write(op.type == TokenType.EQ_EQ ? ' === ' : ' !== '); 1041 code = op.type == TokenType.EQ_EQ ? '# === #' : '# !== #';
1101 rhs.accept(this);
1102 } else { 1042 } else {
1103 // TODO(jmesserly): it would be nice to use just "equals", perhaps 1043 var bang = op.type == TokenType.BANG_EQ ? '!' : '';
1104 // by importing this name. 1044 code = '${bang}dart.equals(#, #)';
1105 if (op.type == TokenType.BANG_EQ) out.write('!');
1106 out.write('dart.equals(');
1107 lhs.accept(this);
1108 out.write(', ');
1109 rhs.accept(this);
1110 out.write(')');
1111 } 1045 }
1112 } else if (binaryOperationIsPrimitive(dispatchType, otherType)) { 1046 return js.call(code, [left.accept(this), right.accept(this)]);
1047 } else if (binaryOperationIsPrimitive(leftType, rightType)) {
1113 // special cases where we inline the operation 1048 // special cases where we inline the operation
1114 // these values are assumed to be non-null (determined by the checker) 1049 // these values are assumed to be non-null (determined by the checker)
1115 // TODO(jmesserly): it would be nice to just inline the method from core, 1050 // TODO(jmesserly): it would be nice to just inline the method from core,
1116 // instead of special cases here. 1051 // instead of special cases here.
1117
1118 if (op.type == TokenType.TILDE_SLASH) { 1052 if (op.type == TokenType.TILDE_SLASH) {
1119 // `a ~/ b` is equivalent to `(a / b).truncate()` 1053 // `a ~/ b` is equivalent to `(a / b).truncate()`
1120 out.write('('); 1054 code = '(# / #).truncate()';
1121 notNull(lhs);
1122 out.write(' / ');
1123 notNull(rhs);
1124 out.write(').truncate()');
1125 } else { 1055 } else {
1126 // TODO(vsm): When do Dart ops not map to JS? 1056 // TODO(vsm): When do Dart ops not map to JS?
1127 notNull(lhs); 1057 code = '# $op #';
1128 out.write(' $op ');
1129 notNull(rhs);
1130 } 1058 }
1131 } else if (rules.isDynamicTarget(lhs)) { 1059 return js.call(code, [notNull(left), notNull(right)]);
1132 // dynamic dispatch
1133 out.write('dart.dbinary(');
1134 lhs.accept(this);
1135 out.write(', "${op.lexeme}", ');
1136 rhs.accept(this);
1137 out.write(')');
1138 } else if (_isJSBuiltinType(dispatchType)) {
1139 // TODO(jmesserly): we'd get better readability from the static-dispatch
1140 // pattern below. Consider:
1141 //
1142 // "hello"['+']"world"
1143 // vs
1144 // core.String['+']("hello", "world")
1145 //
1146 // Infix notation is much more readable, which is a bit part of why
1147 // C# added its extension methods feature. However this would require
1148 // adding these methods to String.prototype/Number.prototype in JS.
1149 _writeTypeName(dispatchType);
1150 out.write(_canonicalMethodInvoke(op.lexeme));
1151 out.write('(');
1152 lhs.accept(this);
1153 out.write(', ');
1154 rhs.accept(this);
1155 out.write(')');
1156 } else { 1060 } else {
1157 // Generic static-dispatch, user-defined operator code path. 1061 var opString = js.string(op.lexeme, "'");
1158 1062 if (rules.isDynamicTarget(left)) {
1159 // We're going to replace the operator with high-precedence "." or "[]", 1063 // dynamic dispatch
1160 // so add parens around the left side if necessary. 1064 return js.call('dart.dbinary(#, #, #)', [
1161 _visitExpression(lhs, _indexExpressionPrecedence); 1065 left.accept(this),
1162 out.write(_canonicalMethodInvoke(op.lexeme)); 1066 opString,
1163 out.write('('); 1067 right.accept(this)
1164 rhs.accept(this); 1068 ]);
1165 out.write(')'); 1069 } else if (_isJSBuiltinType(leftType)) {
1070 // TODO(jmesserly): we'd get better readability from the static-dispatch
1071 // pattern below. Consider:
1072 //
1073 // "hello"['+']"world"
1074 // vs
1075 // core.String['+']("hello", "world")
1076 //
1077 // Infix notation is much more readable, which is a bit part of why
1078 // C# added its extension methods feature. However this would require
1079 // adding these methods to String.prototype/Number.prototype in JS.
1080 return js.call('#.#(#, #)', [
1081 _emitTypeName(leftType),
1082 opString,
1083 left.accept(this),
1084 right.accept(this)
1085 ]);
1086 } else {
1087 // Generic static-dispatch, user-defined operator code path.
1088 return js.call(
1089 '#.#(#)', [left.accept(this), opString, right.accept(this)]);
1090 }
1166 } 1091 }
1167 } 1092 }
1168 1093
1169 bool _isNull(Expression expr) => expr is NullLiteral; 1094 bool _isNull(Expression expr) => expr is NullLiteral;
1170 1095
1171 @override 1096 @override
1172 void visitPostfixExpression(PostfixExpression node) { 1097 JS.Expression visitPostfixExpression(PostfixExpression node) {
1173 var op = node.operator; 1098 var op = node.operator;
1174 var expr = node.operand; 1099 var expr = node.operand;
1175 1100
1176 var dispatchType = rules.getStaticType(expr); 1101 var dispatchType = rules.getStaticType(expr);
1177 if (unaryOperationIsPrimitive(dispatchType)) { 1102 if (unaryOperationIsPrimitive(dispatchType)) {
1178 // TODO(vsm): When do Dart ops not map to JS? 1103 // TODO(vsm): When do Dart ops not map to JS?
1179 notNull(expr); 1104 return js.call('#$op', notNull(expr));
1180 out.write('$op');
1181 } else { 1105 } else {
1182 // TODO(vsm): Figure out operator calling convention / dispatch. 1106 // TODO(vsm): Figure out operator calling convention / dispatch.
1183 out.write('/* Unimplemented postfix operator: $node */'); 1107 return visitExpression(node);
1184 } 1108 }
1185 } 1109 }
1186 1110
1187 @override 1111 @override
1188 void visitPrefixExpression(PrefixExpression node) { 1112 JS.Expression visitPrefixExpression(PrefixExpression node) {
1189 var op = node.operator; 1113 var op = node.operator;
1190 var expr = node.operand; 1114 var expr = node.operand;
1191 1115
1192 var dispatchType = rules.getStaticType(expr); 1116 var dispatchType = rules.getStaticType(expr);
1193 if (unaryOperationIsPrimitive(dispatchType)) { 1117 if (unaryOperationIsPrimitive(dispatchType)) {
1194 // TODO(vsm): When do Dart ops not map to JS? 1118 // TODO(vsm): When do Dart ops not map to JS?
1195 out.write('$op'); 1119 return js.call('$op#', notNull(expr));
1196 notNull(expr);
1197 } else { 1120 } else {
1198 // TODO(vsm): Figure out operator calling convention / dispatch. 1121 // TODO(vsm): Figure out operator calling convention / dispatch.
1199 out.write('/* Unimplemented postfix operator: $node */'); 1122 return visitExpression(node);
1200 } 1123 }
1201 } 1124 }
1202 1125
1203 // Cascades can contain [IndexExpression], [MethodInvocation] and 1126 // Cascades can contain [IndexExpression], [MethodInvocation] and
1204 // [PropertyAccess]. The code generation for those is handled in their 1127 // [PropertyAccess]. The code generation for those is handled in their
1205 // respective visit methods. 1128 // respective visit methods.
1206 @override 1129 @override
1207 void visitCascadeExpression(CascadeExpression node) { 1130 JS.Node visitCascadeExpression(CascadeExpression node) {
1208 var savedCascadeTemp = _cascadeTarget; 1131 var savedCascadeTemp = _cascadeTarget;
1209 1132
1210 var parent = node.parent; 1133 var parent = node.parent;
1211 var grandparent = parent.parent; 1134 JS.Node result;
1212 if (_isStateless(node.target, node)) { 1135 if (_isStateless(node.target, node)) {
1213 // Special case: target is stateless, so we can just reuse it. 1136 // Special case: target is stateless, so we can just reuse it.
1214 _cascadeTarget = node.target; 1137 _cascadeTarget = node.target;
1215 1138
1216 if (parent is ExpressionStatement) { 1139 if (parent is ExpressionStatement) {
1217 _visitNodeList(node.cascadeSections, separator: ';\n'); 1140 var sections = _visitList(node.cascadeSections);
1141 result = _statement(sections.map((e) => new JS.ExpressionStatement(e)));
1218 } else { 1142 } else {
1219 // Use comma expression. For example: 1143 // Use comma expression. For example:
1220 // (sb.write(1), sb.write(2), sb) 1144 // (sb.write(1), sb.write(2), sb)
1221 out.write('('); 1145 var sections = _visitListToBinary(node.cascadeSections, ',');
1222 _visitNodeList(node.cascadeSections, separator: ', ', suffix: ', '); 1146 result = new JS.Binary(',', sections, _cascadeTarget.accept(this));
1223 _cascadeTarget.accept(this);
1224 out.write(')');
1225 } 1147 }
1226 } else if (parent is AssignmentExpression &&
1227 grandparent is ExpressionStatement &&
1228 _isStateless(parent.leftHandSide, node)) {
1229
1230 // Special case: assignment to a variable in a statement.
1231 // We can reuse the variable to desugar it:
1232 // result = []..length = length;
1233 // becomes:
1234 // result = [];
1235 // result.length = length;
1236 _cascadeTarget = parent.leftHandSide;
1237 node.target.accept(this);
1238 out.write(';\n');
1239 _visitNodeList(node.cascadeSections, separator: ';\n');
1240 } else if (parent is VariableDeclaration &&
1241 grandparent is VariableDeclarationList &&
1242 grandparent.variables.last == parent) {
1243
1244 // Special case: variable declaration
1245 // We can reuse the variable to desugar it:
1246 // var result = []..length = length;
1247 // becomes:
1248 // var result = [];
1249 // result.length = length;
1250 _cascadeTarget = parent.name;
1251 node.target.accept(this);
1252 out.write(';\n');
1253 _visitNodeList(node.cascadeSections, separator: ';\n');
1254 } else { 1148 } else {
1255 // In the general case we need to capture the target expression into 1149 // In the general case we need to capture the target expression into
1256 // a temporary. This uses a lambda to get a temporary scope, and it also 1150 // a temporary. This uses a lambda to get a temporary scope, and it also
1257 // remains valid in an expression context. 1151 // remains valid in an expression context.
1258 // TODO(jmesserly): need a better way to handle temps. 1152 // TODO(jmesserly): need a better way to handle temps.
1259 // TODO(jmesserly): special case for parent is ExpressionStatement? 1153 // TODO(jmesserly): special case for parent is ExpressionStatement?
1260 _cascadeTarget = 1154 _cascadeTarget =
1261 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); 1155 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0));
1262 _cascadeTarget.staticElement = 1156 _cascadeTarget.staticElement =
1263 new LocalVariableElementImpl.forNode(_cascadeTarget); 1157 new LocalVariableElementImpl.forNode(_cascadeTarget);
1264 _cascadeTarget.staticType = node.target.staticType; 1158 _cascadeTarget.staticType = node.target.staticType;
1265 1159
1266 out.write('((${_cascadeTarget.name}) => {\n', 2); 1160 var body = _visitList(node.cascadeSections);
1267 _visitNodeList(node.cascadeSections, separator: ';\n', suffix: ';\n');
1268 if (node.parent is! ExpressionStatement) { 1161 if (node.parent is! ExpressionStatement) {
1269 out.write('return ${_cascadeTarget.name};\n'); 1162 body.add(js.statement('return #;', _cascadeTarget.name));
1270 } 1163 }
1271 out.write('})', -2); 1164
1272 if (_needsBindThis(node.cascadeSections)) out.write('.bind(this)'); 1165 var bindThis = _maybeBindThis(node.cascadeSections);
1273 out.write('('); 1166 result = js.call('((#) => { # })$bindThis(#)', [
1274 node.target.accept(this); 1167 _cascadeTarget.name,
1275 out.write(')'); 1168 body,
1169 node.target.accept(this)
1170 ]);
1276 } 1171 }
1277 1172
1278 _cascadeTarget = savedCascadeTemp; 1173 _cascadeTarget = savedCascadeTemp;
1174 return result;
1279 } 1175 }
1280 1176
1281 /// True is the expression can be evaluated multiple times without causing 1177 /// True is the expression can be evaluated multiple times without causing
1282 /// code execution. This is true for final fields. This can be true for local 1178 /// code execution. This is true for final fields. This can be true for local
1283 /// variables, if: 1179 /// variables, if:
1284 /// * they are not assigned within the [context]. 1180 /// * they are not assigned within the [context].
1285 /// * they are not assigned in a function closure anywhere. 1181 /// * they are not assigned in a function closure anywhere.
1286 bool _isStateless(Expression node, [AstNode context]) { 1182 bool _isStateless(Expression node, [AstNode context]) {
1287 if (node is SimpleIdentifier) { 1183 if (node is SimpleIdentifier) {
1288 var e = node.staticElement; 1184 var e = node.staticElement;
1289 if (e is PropertyAccessorElement) e = e.variable; 1185 if (e is PropertyAccessorElement) e = e.variable;
1290 if (e is VariableElementImpl && !e.isSynthetic) { 1186 if (e is VariableElementImpl && !e.isSynthetic) {
1291 if (e.isFinal) return true; 1187 if (e.isFinal) return true;
1292 if (e is LocalVariableElementImpl || e is ParameterElementImpl) { 1188 if (e is LocalVariableElementImpl || e is ParameterElementImpl) {
1293 // make sure the local isn't mutated in the context. 1189 // make sure the local isn't mutated in the context.
1294 return !_isPotentiallyMutated(e, context); 1190 return !_isPotentiallyMutated(e, context);
1295 } 1191 }
1296 } 1192 }
1297 } 1193 }
1298 return false; 1194 return false;
1299 } 1195 }
1300 1196
1301 @override 1197 @override
1302 void visitParenthesizedExpression(ParenthesizedExpression node) { 1198 visitParenthesizedExpression(ParenthesizedExpression node) =>
1303 out.write('('); 1199 // The printer handles precedence so we don't need to.
1304 node.expression.accept(this); 1200 node.expression.accept(this);
1305 out.write(')');
1306 }
1307 1201
1308 @override 1202 @override
1309 void visitSimpleFormalParameter(SimpleFormalParameter node) { 1203 visitSimpleFormalParameter(SimpleFormalParameter node) =>
1310 node.identifier.accept(this); 1204 node.identifier.accept(this);
1311 }
1312 1205
1313 @override 1206 @override
1314 void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { 1207 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) =>
1315 node.identifier.accept(this); 1208 node.identifier.accept(this);
1316 }
1317 1209
1318 @override 1210 @override
1319 void visitThisExpression(ThisExpression node) { 1211 JS.This visitThisExpression(ThisExpression node) => new JS.This();
1320 out.write('this');
1321 }
1322 1212
1323 @override 1213 @override
1324 void visitSuperExpression(SuperExpression node) { 1214 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super();
1325 out.write('super');
1326 }
1327 1215
1328 @override 1216 @override
1329 void visitPrefixedIdentifier(PrefixedIdentifier node) { 1217 visitPrefixedIdentifier(PrefixedIdentifier node) {
1330 if (node.prefix.staticElement is PrefixElement) { 1218 if (node.prefix.staticElement is PrefixElement) {
1331 node.identifier.accept(this); 1219 return node.identifier.accept(this);
1332 } else { 1220 } else {
1333 _visitGet(node.prefix, node.identifier); 1221 return _visitGet(node.prefix, node.identifier);
1334 } 1222 }
1335 } 1223 }
1336 1224
1337 @override 1225 @override
1338 void visitPropertyAccess(PropertyAccess node) { 1226 visitPropertyAccess(PropertyAccess node) =>
1339 _visitGet(_getTarget(node), node.propertyName); 1227 _visitGet(_getTarget(node), node.propertyName);
1340 }
1341 1228
1342 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. 1229 /// Shared code for [PrefixedIdentifier] and [PropertyAccess].
1343 void _visitGet(Expression target, SimpleIdentifier name) { 1230 _visitGet(Expression target, SimpleIdentifier name) {
1344 if (rules.isDynamicTarget(target)) { 1231 if (rules.isDynamicTarget(target)) {
1345 // TODO(jmesserly): this won't work if we're left hand side of assignment. 1232 return js.call(
1346 out.write('dart.dload('); 1233 'dart.dload(#, #)', [target.accept(this), js.string(name.name, "'")]);
1347 target.accept(this);
1348 out.write(', "${name.name}")');
1349 } else { 1234 } else {
1350 target.accept(this); 1235 return js.call('#.#', [target.accept(this), name.name]);
1351 out.write('.${name.name}');
1352 } 1236 }
1353 } 1237 }
1354 1238
1355 @override 1239 @override
1356 void visitIndexExpression(IndexExpression node) { 1240 visitIndexExpression(IndexExpression node) {
1357 var target = _getTarget(node); 1241 var target = _getTarget(node);
1242 var code;
1358 if (rules.isDynamicTarget(target)) { 1243 if (rules.isDynamicTarget(target)) {
1359 out.write('dart.dindex('); 1244 code = 'dart.dindex(#, #)';
1360 target.accept(this);
1361 out.write(', ');
1362 node.index.accept(this);
1363 out.write(')');
1364 } else { 1245 } else {
1365 target.accept(this); 1246 code = '#.get(#)';
1366 out.write(_canonicalMethodInvoke('[]'));
1367 out.write('(');
1368 node.index.accept(this);
1369 out.write(')');
1370 } 1247 }
1248 return js.call(code, [target.accept(this), node.index.accept(this)]);
1371 } 1249 }
1372 1250
1373 /// Gets the target of a [PropertyAccess] or [IndexExpression]. 1251 /// Gets the target of a [PropertyAccess] or [IndexExpression].
1374 /// Those two nodes are special because they're both allowed on left side of 1252 /// Those two nodes are special because they're both allowed on left side of
1375 /// an assignment expression and cascades. 1253 /// an assignment expression and cascades.
1376 Expression _getTarget(node) { 1254 Expression _getTarget(node) {
1377 assert(node is IndexExpression || node is PropertyAccess); 1255 assert(node is IndexExpression || node is PropertyAccess);
1378 return node.isCascaded ? _cascadeTarget : node.target; 1256 return node.isCascaded ? _cascadeTarget : node.target;
1379 } 1257 }
1380 1258
1381 @override 1259 @override
1382 void visitConditionalExpression(ConditionalExpression node) { 1260 visitConditionalExpression(ConditionalExpression node) {
1383 node.condition.accept(this); 1261 return js.call('# ? # : #', [
1384 out.write(' ? '); 1262 node.condition.accept(this),
1385 node.thenExpression.accept(this); 1263 node.thenExpression.accept(this),
1386 out.write(' : '); 1264 node.elseExpression.accept(this)
1387 node.elseExpression.accept(this); 1265 ]);
1388 } 1266 }
1389 1267
1390 @override 1268 @override
1391 void visitThrowExpression(ThrowExpression node) { 1269 visitThrowExpression(ThrowExpression node) {
1270 var expr = node.expression.accept(this);
1392 if (node.parent is ExpressionStatement) { 1271 if (node.parent is ExpressionStatement) {
1393 out.write('throw '); 1272 return js.statement('throw #;', expr);
1394 node.expression.accept(this);
1395 } else { 1273 } else {
1396 // TODO(jmesserly): move this into runtime helper? 1274 return js.call('dart.throw_(#)', expr);
1397 out.write('(function(e) { throw e }('); 1275 }
1398 node.expression.accept(this); 1276 }
1399 out.write(')'); 1277
1400 } 1278 @override
1401 } 1279 JS.If visitIfStatement(IfStatement node) {
1402 1280 return new JS.If(node.condition.accept(this), _visit(node.thenStatement),
1403 @override 1281 _visitOrEmpty(node.elseStatement));
1404 void visitIfStatement(IfStatement node) { 1282 }
1405 out.write('if ('); 1283
1406 node.condition.accept(this); 1284 @override
1407 out.write(') '); 1285 JS.For visitForStatement(ForStatement node) {
1408 var then = node.thenStatement; 1286 var init = _visit(node.initialization);
1409 if (then is Block) { 1287 if (init == null) init = _visit(node.variables);
1410 out.write('{\n', 2); 1288 return new JS.For(init, _visit(node.condition),
1411 _visitNodeList((then as Block).statements); 1289 _visitListToBinary(node.updaters, ','), _visit(node.body));
1412 out.write('}', -2); 1290 }
1413 } else { 1291
1414 _visitNode(then); 1292 @override
1415 } 1293 JS.While visitWhileStatement(WhileStatement node) {
1416 var elseClause = node.elseStatement; 1294 return new JS.While(node.condition.accept(this), node.body.accept(this));
1417 if (elseClause != null) { 1295 }
1418 out.write(' else '); 1296
1419 elseClause.accept(this); 1297 @override
1420 } else if (then is Block) { 1298 JS.Do visitDoStatement(DoStatement node) {
1421 out.write('\n'); 1299 return new JS.Do(node.body.accept(this), node.condition.accept(this));
1422 } 1300 }
1423 } 1301
1424 1302 @override
1425 @override 1303 JS.ForOf visitForEachStatement(ForEachStatement node) {
1426 void visitForStatement(ForStatement node) { 1304 var init = _visit(node.identifier);
1427 Expression initialization = node.initialization; 1305 if (init == null) {
1428 out.write("for ("); 1306 init = js.call('let #', node.loopVariable.identifier.name);
1429 if (initialization != null) { 1307 }
1430 initialization.accept(this); 1308 return new JS.ForOf(
1431 } else if (node.variables != null) { 1309 init, node.iterable.accept(this), node.body.accept(this));
1432 _visitNode(node.variables); 1310 }
1433 } 1311
1434 out.write(";"); 1312 @override
1435 _visitNode(node.condition, prefix: " "); 1313 visitBreakStatement(BreakStatement node) {
1436 out.write(";"); 1314 var label = node.label;
1437 _visitNodeList(node.updaters, prefix: " ", separator: ", "); 1315 return new JS.Break(label != null ? label.name : null);
1438 out.write(") "); 1316 }
1439 _visitNode(node.body); 1317
1440 } 1318 @override
1441 1319 visitContinueStatement(ContinueStatement node) {
1442 @override 1320 var label = node.label;
1443 void visitWhileStatement(WhileStatement node) { 1321 return new JS.Continue(label != null ? label.name : null);
1444 out.write("while ("); 1322 }
1445 _visitNode(node.condition); 1323
1446 out.write(") "); 1324 @override
1447 _visitNode(node.body); 1325 visitTryStatement(TryStatement node) {
1448 } 1326 return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses),
1449 1327 _visit(node.finallyBlock));
1450 @override 1328 }
1451 void visitDoStatement(DoStatement node) { 1329
1452 out.write("do "); 1330 _visitCatch(NodeList<CatchClause> clauses) {
1453 _visitNode(node.body); 1331 if (clauses == null || clauses.isEmpty) return null;
1454 if (node.body is! Block) out.write(' '); 1332
1455 out.write("while ("); 1333 // TODO(jmesserly): need a better way to get a temporary variable.
1456 _visitNode(node.condition); 1334 // This could incorrectly shadow a user's name.
1457 out.write(");\n"); 1335 var name = '\$e';
1458 } 1336
1459 1337 if (clauses.length == 1) {
1460 @override 1338 // Special case for a single catch.
1461 void visitForEachStatement(ForEachStatement node) { 1339 var clause = clauses.single;
1462 out.write('for ('); 1340 if (clause.exceptionParameter != null) {
1463 if (node.loopVariable != null) { 1341 name = clause.exceptionParameter.name;
1464 _visitNode(node.loopVariable.identifier, prefix: 'let '); 1342 }
1465 } else { 1343 }
1466 _visitNode(node.identifier); 1344
1467 } 1345 var catchBody = _statement(clauses.map((c) => _visitCatchClause(c, name)));
1468 out.write(' of '); 1346
1469 _visitNode(node.iterable); 1347 return new JS.Catch(new JS.VariableDeclaration(name), catchBody);
1470 out.write(') '); 1348 }
1471 _visitNode(node.body); 1349
1472 } 1350 JS.Statement _statement(Iterable stmts) {
1473 1351 var s = stmts is List ? stmts : new List<JS.Statement>.from(stmts);
1474 @override 1352 // TODO(jmesserly): empty block singleton?
1475 void visitBreakStatement(BreakStatement node) { 1353 if (s.length == 0) return new JS.Block([]);
1476 out.write("break"); 1354 if (s.length == 1) return s[0];
1477 _visitNode(node.label, prefix: " "); 1355 return new JS.Block(s);
1478 out.write(";\n"); 1356 }
1479 } 1357
1480 1358 JS.Statement _visitCatchClause(CatchClause node, String varName) {
1481 @override 1359 var body = [];
1482 void visitContinueStatement(ContinueStatement node) {
1483 out.write("continue");
1484 _visitNode(node.label, prefix: " ");
1485 out.write(";\n");
1486 }
1487
1488 @override
1489 void visitTryStatement(TryStatement node) {
1490 out.write('try ');
1491 _visitNode(node.body);
1492 if (node.body is! Block) out.write(' ');
1493
1494 var clauses = node.catchClauses;
1495 if (clauses != null && clauses.isNotEmpty) {
1496 // TODO(jmesserly): need a better way to get a temporary variable.
1497 // This could incorrectly shadow a user's name.
1498 var name = '\$e';
1499
1500 if (clauses.length == 1) {
1501 // Special case for a single catch.
1502 var clause = clauses.single;
1503 if (clause.exceptionParameter != null) {
1504 name = clause.exceptionParameter.name;
1505 }
1506 }
1507
1508 out.write('catch ($name) {\n', 2);
1509 for (var clause in clauses) {
1510 _visitCatchClause(clause, name);
1511 }
1512 out.write('}\n', -2);
1513 }
1514 _visitNode(node.finallyBlock, prefix: 'finally ');
1515 }
1516
1517 void _visitCatchClause(CatchClause node, String varName) {
1518 if (node.catchKeyword != null) { 1360 if (node.catchKeyword != null) {
1519 if (node.exceptionType != null) {
1520 out.write('if (dart.is($varName, ');
1521 _writeTypeName(node.exceptionType.type);
1522 out.write(')) {\n', 2);
1523 }
1524
1525 var name = node.exceptionParameter; 1361 var name = node.exceptionParameter;
1526 if (name != null && name.name != varName) { 1362 if (name != null && name.name != varName) {
1527 out.write('let $name = $varName;\n'); 1363 body.add(js.statement('let # = #;', [name.accept(this), varName]));
1528 } 1364 }
1529
1530 if (node.stackTraceParameter != null) { 1365 if (node.stackTraceParameter != null) {
1531 var stackName = node.stackTraceParameter.name; 1366 var stackVar = node.stackTraceParameter.name;
1532 out.write('let $stackName = dart.stackTrace($name);\n'); 1367 body.add(js.statement(
1533 } 1368 'let # = dart.stackTrace(#);', [stackVar, name.accept(this)]));
1534 } 1369 }
1535 1370 }
1536 // If we can, avoid generating a nested { ... } block. 1371
1537 var body = node.body; 1372 body.add(node.body.accept(this));
1538 if (body is Block) { 1373
1539 body.statements.accept(this); 1374 if (node.exceptionType != null) {
1375 return js.statement('if (dart.is(#, #)) #;', [
1376 varName,
1377 _emitTypeName(node.exceptionType.type),
1378 _statement(body)
1379 ]);
1380 }
1381 return _statement(body);
1382 }
1383
1384 @override
1385 JS.Case visitSwitchCase(SwitchCase node) {
1386 var expr = node.expression.accept(this);
1387 var body = _visitList(node.statements);
1388 if (node.labels.isNotEmpty) {
1389 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
1390 }
1391 // TODO(jmesserly): make sure we are statically checking fall through
1392 return new JS.Case(expr, new JS.Block(body));
1393 }
1394
1395 @override
1396 JS.Default visitSwitchDefault(SwitchDefault node) {
1397 var body = _visitList(node.statements);
1398 if (node.labels.isNotEmpty) {
1399 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
1400 }
1401 // TODO(jmesserly): make sure we are statically checking fall through
1402 return new JS.Default(new JS.Block(body));
1403 }
1404
1405 @override
1406 JS.Switch visitSwitchStatement(SwitchStatement node) =>
1407 new JS.Switch(node.expression.accept(this), _visitList(node.members));
1408
1409 @override
1410 JS.Statement visitLabeledStatement(LabeledStatement node) {
1411 var result = _visit(node.statement);
1412 for (var label in node.labels.reversed) {
1413 result = new JS.LabeledStatement(label.label.name, result);
1414 }
1415 return result;
1416 }
1417
1418 @override
1419 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value);
1420
1421 @override
1422 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value);
1423
1424 @override
1425 visitNullLiteral(NullLiteral node) => new JS.LiteralNull();
1426
1427 @override
1428 visitListLiteral(ListLiteral node) {
1429 // TODO(jmesserly): make this faster. We're wasting an array.
1430 var list = js.call('new List.from(#)', [
1431 new JS.ArrayInitializer(_visitList(node.elements))
1432 ]);
1433 if (node.constKeyword != null) {
1434 list = js.commentExpression('Unimplemented const', list);
1435 }
1436 return list;
1437 }
1438
1439 @override
1440 visitMapLiteral(MapLiteral node) {
1441 var entries = node.entries;
1442 var mapArguments = null;
1443 if (entries.isEmpty) return js.call('dart.map()');
1444
1445 // Use JS object literal notation if possible, otherwise use an array.
1446 if (entries.every((e) => e.key is SimpleStringLiteral)) {
1447 var props = [];
1448 for (var e in entries) {
1449 var key = (e.key as SimpleStringLiteral).value;
1450 var value = e.value.accept(this);
1451 props.add(new JS.Property(js.escapedString(key), value));
1452 }
1453 mapArguments = new JS.ObjectInitializer(props);
1540 } else { 1454 } else {
1541 body.accept(this); 1455 var values = [];
1542 } 1456 for (var e in entries) {
1543 1457 values.add(e.key.accept(this));
1544 if (node.exceptionType != null) out.write('}\n', -2); 1458 values.add(e.value.accept(this));
1545 } 1459 }
1546 1460 mapArguments = new JS.ArrayInitializer(values);
1547 @override 1461 }
1548 void visitSwitchCase(SwitchCase node) { 1462 return js.call('dart.map(#)', [mapArguments]);
1549 _visitNodeList(node.labels, separator: " ", suffix: " "); 1463 }
1550 out.write("case "); 1464
1551 _visitNode(node.expression); 1465 @override
1552 out.write(":\n", 2); 1466 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) =>
1553 _visitNodeList(node.statements); 1467 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"');
1554 out.write('', -2); 1468
1555 // TODO(jmesserly): make sure we are statically checking fall through 1469 @override
1556 } 1470 JS.Expression visitAdjacentStrings(AdjacentStrings node) =>
1557 1471 _visitListToBinary(node.strings, '+');
1558 @override 1472
1559 void visitSwitchDefault(SwitchDefault node) { 1473 @override
1560 _visitNodeList(node.labels, separator: " ", suffix: " "); 1474 JS.TemplateString visitStringInterpolation(StringInterpolation node) {
1561 out.write("default:\n", 2);
1562 _visitNodeList(node.statements);
1563 out.write('', -2);
1564 }
1565
1566 @override
1567 void visitSwitchStatement(SwitchStatement node) {
1568 out.write("switch (");
1569 _visitNode(node.expression);
1570 out.write(") {\n", 2);
1571 _visitNodeList(node.members);
1572 out.write("}\n", -2);
1573 }
1574
1575 @override
1576 void visitLabel(Label node) {
1577 _visitNode(node.label);
1578 out.write(':');
1579 }
1580
1581 @override
1582 void visitLabeledStatement(LabeledStatement node) {
1583 _visitNodeList(node.labels, separator: " ", suffix: " ");
1584 _visitNode(node.statement);
1585 }
1586
1587 @override
1588 void visitIntegerLiteral(IntegerLiteral node) {
1589 out.write('${node.value}');
1590 }
1591
1592 @override
1593 void visitDoubleLiteral(DoubleLiteral node) {
1594 out.write('${node.value}');
1595 }
1596
1597 @override
1598 void visitNullLiteral(NullLiteral node) {
1599 out.write('null');
1600 }
1601
1602 @override
1603 void visitListLiteral(ListLiteral node) {
1604 if (node.constKeyword != null) {
1605 out.write('/* Unimplemented const */');
1606 }
1607 // TODO(jmesserly): make this faster.
1608 out.write('new List.from([');
1609 _visitNodeList(node.elements, separator: ', ');
1610 out.write('])');
1611 }
1612
1613 @override
1614 void visitMapLiteral(MapLiteral node) {
1615 out.write('dart.map(');
1616 var entries = node.entries;
1617 if (entries != null && entries.isNotEmpty) {
1618 // Use JS object literal notation if possible, otherwise use an array.
1619 if (entries.every((e) => e.key is SimpleStringLiteral)) {
1620 out.write('{\n', 2);
1621 _visitMapLiteralEntries(entries, separator: ': ');
1622 out.write('\n}', -2);
1623 } else {
1624 out.write('[\n', 2);
1625 _visitMapLiteralEntries(entries, separator: ', ');
1626 out.write('\n]', -2);
1627 }
1628 }
1629 out.write(')');
1630 }
1631
1632 void _visitMapLiteralEntries(NodeList<MapLiteralEntry> nodes,
1633 {String separator}) {
1634 if (nodes == null) return;
1635 int size = nodes.length;
1636 if (size == 0) return;
1637
1638 for (int i = 0; i < size; i++) {
1639 if (i > 0) out.write(',\n');
1640 var node = nodes[i];
1641 node.key.accept(this);
1642 out.write(separator);
1643 node.value.accept(this);
1644 }
1645 }
1646
1647 @override
1648 void visitSimpleStringLiteral(SimpleStringLiteral node) {
1649 if (node.isSingleQuoted) {
1650 var escaped = _escapeForJs(node.stringValue, "'");
1651 out.write("'$escaped'");
1652 } else {
1653 var escaped = _escapeForJs(node.stringValue, '"');
1654 out.write('"$escaped"');
1655 }
1656 }
1657
1658 @override
1659 void visitAdjacentStrings(AdjacentStrings node) {
1660 // These are typically used for splitting long strings across lines, so
1661 // generate accordingly, with each on its own line and +4 indent.
1662
1663 // TODO(jmesserly): we could linebreak before the first string too, but
1664 // that means inserting a linebreak in expression context, which might
1665 // not be valid and leaves trailing whitespace.
1666 for (int i = 0, last = node.strings.length - 1; i <= last; i++) {
1667 if (i == 1) {
1668 out.write(' +\n', 4);
1669 } else if (i > 1) {
1670 out.write(' +\n');
1671 }
1672 node.strings[i].accept(this);
1673 if (i == last && i > 0) {
1674 out.write('', -4);
1675 }
1676 }
1677 }
1678
1679 @override
1680 void visitStringInterpolation(StringInterpolation node) {
1681 out.write('`');
1682 _visitNodeList(node.elements);
1683 out.write('`');
1684 }
1685
1686 @override
1687 void visitInterpolationString(InterpolationString node) {
1688 out.write(_escapeForJs(node.value, '`'));
1689 }
1690
1691 /// Escapes the string from [value], handling escape sequences as needed.
1692 /// The surrounding [quote] style must be supplied to know which quotes to
1693 /// escape, but quotes are not added to the resulting string.
1694 String _escapeForJs(String value, String quote) {
1695 // Start by escaping the backslashes.
1696 String escaped = value.replaceAll('\\', '\\\\');
1697 // Do not escape unicode characters and ' because they are allowed in the
1698 // string literal anyway.
1699 return escaped.replaceAllMapped(new RegExp('\n|$quote|\b|\t|\v'), (m) {
1700 switch (m.group(0)) {
1701 case "\n":
1702 return r"\n";
1703 case "\b":
1704 return r"\b";
1705 case "\t":
1706 return r"\t";
1707 case "\f":
1708 return r"\f";
1709 case "\v":
1710 return r"\v";
1711 // Quotes are only replaced if they conflict with the containing quote
1712 case '"':
1713 return r'\"';
1714 case "'":
1715 return r"\'";
1716 case "`":
1717 return r"\`";
1718 }
1719 });
1720 }
1721
1722 @override
1723 void visitInterpolationExpression(InterpolationExpression node) {
1724 out.write('\${');
1725 node.expression.accept(this);
1726 // Assuming we implement toString() on our objects, we can avoid calling it 1475 // Assuming we implement toString() on our objects, we can avoid calling it
1727 // in most cases. Builtin types may differ though. 1476 // in most cases. Builtin types may differ though. We could handle this with
1728 // For example, Dart's concrete List type does not have the same toString 1477 // a tagged template.
1729 // as Array.prototype.toString(). 1478 return new JS.TemplateString(_visitList(node.elements));
1730 out.write('}'); 1479 }
1731 } 1480
1732 1481 @override
1733 @override 1482 String visitInterpolationString(InterpolationString node) {
1734 void visitBooleanLiteral(BooleanLiteral node) { 1483 // TODO(jmesserly): this call adds quotes, and then we strip them off.
1735 out.write('${node.value}'); 1484 var str = js.escapedString(node.value, '`').value;
1736 } 1485 return str.substring(1, str.length - 1);
1737 1486 }
1738 @override 1487
1739 void visitDirective(Directive node) {} 1488 @override
1740 1489 visitInterpolationExpression(InterpolationExpression node) =>
1741 @override 1490 node.expression.accept(this);
1742 void visitNode(AstNode node) { 1491
1743 out.write('/* Unimplemented ${node.runtimeType}: $node */'); 1492 @override
1493 visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value);
1494
1495 @override
1496 JS.Statement visitDeclaration(Declaration node) =>
1497 js.comment('Unimplemented ${node.runtimeType}: $node');
1498
1499 @override
1500 JS.Statement visitStatement(Statement node) =>
1501 js.comment('Unimplemented ${node.runtimeType}: $node');
1502
1503 @override
1504 JS.Expression visitExpression(Expression node) =>
1505 _unimplementedCall('Unimplemented ${node.runtimeType}: $node');
1506
1507 JS.Expression _unimplementedCall(String comment) {
1508 return js.call('dart.throw_(#)', [js.escapedString(comment)]);
1509 }
1510
1511 @override
1512 visitNode(AstNode node) {
1513 // TODO(jmesserly): verify this is unreachable.
1514 throw 'Unimplemented ${node.runtimeType}: $node';
1744 } 1515 }
1745 1516
1746 // TODO(jmesserly): this is used to determine if the field initialization is 1517 // TODO(jmesserly): this is used to determine if the field initialization is
1747 // side effect free. We should make the check more general, as things like 1518 // side effect free. We should make the check more general, as things like
1748 // list/map literals/regexp are also side effect free and fairly common 1519 // list/map literals/regexp are also side effect free and fairly common
1749 // to use as field initializers. 1520 // to use as field initializers.
1750 bool _isFieldInitConstant(VariableDeclaration field) => 1521 bool _isFieldInitConstant(VariableDeclaration field) =>
1751 field.initializer == null || _computeConstant(field).isValid; 1522 field.initializer == null || _computeConstant(field).isValid;
1752 1523
1753 EvaluationResult _computeConstant(VariableDeclaration field) { 1524 EvaluationResult _computeConstant(VariableDeclaration field) {
(...skipping 14 matching lines...) Expand all
1768 1539
1769 /// Returns true if [element] is a getter in JS, therefore needs 1540 /// Returns true if [element] is a getter in JS, therefore needs
1770 /// `lib.topLevel` syntax instead of just `topLevel`. 1541 /// `lib.topLevel` syntax instead of just `topLevel`.
1771 bool _needsModuleGetter(Element element) { 1542 bool _needsModuleGetter(Element element) {
1772 if (element is PropertyAccessorElement) { 1543 if (element is PropertyAccessorElement) {
1773 element = (element as PropertyAccessorElement).variable; 1544 element = (element as PropertyAccessorElement).variable;
1774 } 1545 }
1775 return element is TopLevelVariableElement && !element.isConst; 1546 return element is TopLevelVariableElement && !element.isConst;
1776 } 1547 }
1777 1548
1778 /// Safely visit the expression, adding parentheses if necessary 1549 _visit(AstNode node) => node != null ? node.accept(this) : null;
1779 void _visitExpression(Expression node, int newPrecedence) {
1780 if (node == null) return;
1781 1550
1782 // If we're going to replace an expression with a higher-precedence 1551 JS.Statement _visitOrEmpty(AstNode node) {
1783 // operator, add parenthesis around it if needed. 1552 if (node == null) return new JS.EmptyStatement();
1784 var needParens = node.precedence < newPrecedence; 1553 return node.accept(this);
1785 if (needParens) out.write('(');
1786 node.accept(this);
1787 if (needParens) out.write(')');
1788 } 1554 }
1789 1555
1790 /// Safely visit the given node, with an optional prefix or suffix. 1556 List _visitList(Iterable<AstNode> nodes) {
1791 void _visitNode(AstNode node, {String prefix: '', String suffix: ''}) { 1557 if (nodes == null) return null;
1792 if (node == null) return; 1558 var result = [];
1793 1559 for (var node in nodes) result.add(node.accept(this));
1794 out.write(prefix); 1560 return result;
1795 node.accept(this);
1796 out.write(suffix);
1797 } 1561 }
1798 1562
1799 /// Print a list of nodes, with an optional prefix, suffix, and separator. 1563 /// Visits a list of expressions, creating a comma expression if needed in JS.
1800 void _visitNodeList(List<AstNode> nodes, 1564 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) {
1801 {String prefix: '', String suffix: '', String separator: ''}) { 1565 if (nodes == null || nodes.isEmpty) return null;
1802 if (nodes == null) return;
1803 1566
1804 int size = nodes.length; 1567 JS.Expression result = null;
1805 if (size == 0) return; 1568 for (var node in nodes) {
1806 1569 var jsExpr = node.accept(this);
1807 out.write(prefix); 1570 if (result == null) {
1808 for (int i = 0; i < size; i++) { 1571 result = jsExpr;
1809 if (i > 0) out.write(separator); 1572 } else {
1810 nodes[i].accept(this); 1573 result = new JS.Binary(operator, result, jsExpr);
1574 }
1811 } 1575 }
1812 out.write(suffix); 1576 return result;
1813 }
1814
1815 /// Safely visit the given node, printing the suffix after the node if it is
1816 /// non-`null`.
1817 void visitToken(Token token, {String prefix: '', String suffix: ''}) {
1818 if (token == null) return;
1819 out.write(prefix);
1820 out.write(token.lexeme);
1821 out.write(suffix);
1822 } 1577 }
1823 1578
1824 /// The following names are allowed for user-defined operators: 1579 /// The following names are allowed for user-defined operators:
1825 /// 1580 ///
1826 /// <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜ 1581 /// <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜
1827 /// 1582 ///
1828 /// For the indexing operators, we use `get` and `set` instead: 1583 /// For the indexing operators, we use `get` and `set` instead:
1829 /// 1584 ///
1830 /// x.get('hi') 1585 /// x.get('hi')
1831 /// x.set('hi', 123) 1586 /// x.set('hi', 123)
1832 /// 1587 ///
1833 /// This follows the same pattern as EcmaScript 6 Map: 1588 /// This follows the same pattern as EcmaScript 6 Map:
1834 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map> 1589 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map>
1835 /// 1590 ///
1836 /// For all others we use the operator name: 1591 /// For all others we use the operator name:
1837 /// 1592 ///
1838 /// x['+'](y) 1593 /// x['+'](y)
1839 /// 1594 ///
1840 /// Equality is a bit special, it is generated via the Dart `equals` runtime 1595 /// Equality is a bit special, it is generated via the Dart `equals` runtime
1841 /// helper, that checks for null. The user defined method is called '=='. 1596 /// helper, that checks for null. The user defined method is called '=='.
1842 String _canonicalMethodName(String name) { 1597 String _jsMethodName(String name) {
1843 switch (name) { 1598 if (name == '[]') return 'get';
1844 case '[]': 1599 if (name == '[]=') return 'set';
1845 return 'get'; 1600 return name;
1846 case '[]=':
1847 return 'set';
1848 case '<':
1849 case '>':
1850 case '<=':
1851 case '>=':
1852 case '==':
1853 case '-':
1854 case '+':
1855 case '/':
1856 case '~/':
1857 case '*':
1858 case '%':
1859 case '|':
1860 case '^':
1861 case '&':
1862 case '<<':
1863 case '>>':
1864 case '~':
1865 return "['$name']";
1866 default:
1867 return name;
1868 }
1869 } 1601 }
1870 1602
1871 /// The string to invoke a canonical method name, for example: 1603 String _maybeBindThis(node) {
1872 /// 1604 if (currentClass == null) return '';
1873 /// "[]" returns ".get"
1874 /// "+" returns "['+']"
1875 String _canonicalMethodInvoke(String name) {
1876 name = _canonicalMethodName(name);
1877 return name.startsWith('[') ? name : '.$name';
1878 }
1879
1880 bool _needsBindThis(node) {
1881 if (currentClass == null) return false;
1882 var visitor = _BindThisVisitor._instance; 1605 var visitor = _BindThisVisitor._instance;
1883 visitor._bindThis = false; 1606 visitor._bindThis = false;
1884 node.accept(visitor); 1607 node.accept(visitor);
1885 return visitor._bindThis; 1608 return visitor._bindThis ? '.bind(this)' : '';
1886 } 1609 }
1887 1610
1888 static bool _needsImplicitThis(Element e) => 1611 static bool _needsImplicitThis(Element e) =>
1889 e is PropertyAccessorElement && !e.variable.isStatic || 1612 e is PropertyAccessorElement && !e.variable.isStatic ||
1890 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; 1613 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement;
1891 } 1614 }
1892 1615
1893 /// Returns true if the local variable is potentially mutated within [context]. 1616 /// Returns true if the local variable is potentially mutated within [context].
1894 /// This accounts for closures that may have been created outside of [context]. 1617 /// This accounts for closures that may have been created outside of [context].
1895 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { 1618 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) {
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
1957 _bindThis = true; 1680 _bindThis = true;
1958 } 1681 }
1959 } 1682 }
1960 1683
1961 class JSGenerator extends CodeGenerator { 1684 class JSGenerator extends CodeGenerator {
1962 JSGenerator(String outDir, Uri root, TypeRules rules) 1685 JSGenerator(String outDir, Uri root, TypeRules rules)
1963 : super(outDir, root, rules); 1686 : super(outDir, root, rules);
1964 1687
1965 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, 1688 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info,
1966 CheckerReporter reporter) { 1689 CheckerReporter reporter) {
1690 JS.Block jsTree =
1691 new JSCodegenVisitor(info, rules).generateLibrary(units, reporter);
1692
1967 var outputPath = path.join(outDir, jsOutputPath(info)); 1693 var outputPath = path.join(outDir, jsOutputPath(info));
1968 new Directory(path.dirname(outputPath)).createSync(recursive: true); 1694 new Directory(path.dirname(outputPath)).createSync(recursive: true);
1969 var out = new OutWriter(outputPath); 1695
1970 new JSCodegenVisitor(info, rules, out).generateLibrary(units, reporter); 1696 var context = new JS.SimpleJavaScriptPrintingContext();
1971 out.close(); 1697 var opts =
1698 new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true);
1699 var printer = new JS.Printer(opts, context);
1700 printer.blockOutWithoutBraces(jsTree);
1701 new File(outputPath).writeAsStringSync(context.getText());
1972 } 1702 }
1973 } 1703 }
1974 1704
1975 /// Choose a canonical name from the library element. 1705 /// Choose a canonical name from the library element.
1976 /// This never uses the library's name (the identifier in the `library` 1706 /// This never uses the library's name (the identifier in the `library`
1977 /// declaration) as it doesn't have any meaningful rules enforced. 1707 /// declaration) as it doesn't have any meaningful rules enforced.
1978 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); 1708 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library);
1979 1709
1980 /// Path to file that will be generated for [info]. 1710 /// Path to file that will be generated for [info].
1981 // TODO(jmesserly): library directory should be relative to its package 1711 // TODO(jmesserly): library directory should be relative to its package
1982 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e: 1712 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e:
1983 // "ddc/src/codegen/js_codegen.js" under the output directory. 1713 // "ddc/src/codegen/js_codegen.js" under the output directory.
1984 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; 1714 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js';
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698