OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library ddc.src.codegen.js_codegen; | 5 library ddc.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:io' show Directory, File; | 7 import 'dart:io' show Directory, File; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
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:source_maps/source_maps.dart' as srcmaps show Printer; | |
16 import 'package:source_maps/source_maps.dart' show SourceMapSpan; | |
17 import 'package:source_span/source_span.dart' show SourceLocation; | |
15 import 'package:path/path.dart' as path; | 18 import 'package:path/path.dart' as path; |
16 | 19 |
17 // TODO(jmesserly): import from its own package | 20 // TODO(jmesserly): import from its own package |
18 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 21 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
19 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 22 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
20 | 23 |
21 import 'package:dev_compiler/src/checker/rules.dart'; | 24 import 'package:dev_compiler/src/checker/rules.dart'; |
22 import 'package:dev_compiler/src/info.dart'; | 25 import 'package:dev_compiler/src/info.dart'; |
26 import 'package:dev_compiler/src/options.dart'; | |
23 import 'package:dev_compiler/src/report.dart'; | 27 import 'package:dev_compiler/src/report.dart'; |
24 import 'package:dev_compiler/src/utils.dart'; | 28 import 'package:dev_compiler/src/utils.dart'; |
25 import 'code_generator.dart'; | 29 import 'code_generator.dart'; |
26 | 30 |
27 // This must match the optional parameter name used in runtime.js | 31 // This must match the optional parameter name used in runtime.js |
28 const String _jsNamedParameterName = r'opt$'; | 32 const String _jsNamedParameterName = r'opt$'; |
29 | 33 |
30 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 34 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
31 final LibraryInfo libraryInfo; | 35 final LibraryInfo libraryInfo; |
32 final TypeRules rules; | 36 final TypeRules rules; |
(...skipping 19 matching lines...) Expand all Loading... | |
52 JS.Block generateLibrary( | 56 JS.Block generateLibrary( |
53 Iterable<CompilationUnit> units, CheckerReporter reporter) { | 57 Iterable<CompilationUnit> units, CheckerReporter reporter) { |
54 var body = <JS.Statement>[]; | 58 var body = <JS.Statement>[]; |
55 for (var unit in units) { | 59 for (var unit in units) { |
56 // TODO(jmesserly): this is needed because RestrictedTypeRules can send | 60 // TODO(jmesserly): this is needed because RestrictedTypeRules can send |
57 // messages to CheckerReporter, for things like missing types. | 61 // messages to CheckerReporter, for things like missing types. |
58 // We should probably refactor so this can't happen. | 62 // We should probably refactor so this can't happen. |
59 var source = unit.element.source; | 63 var source = unit.element.source; |
60 _constEvaluator = new ConstantEvaluator(source, rules.provider); | 64 _constEvaluator = new ConstantEvaluator(source, rules.provider); |
61 reporter.enterSource(source); | 65 reporter.enterSource(source); |
62 body.add(unit.accept(this)); | 66 body.add(_visit(unit)); |
63 reporter.leaveSource(); | 67 reporter.leaveSource(); |
64 } | 68 } |
65 | 69 |
66 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); | 70 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); |
67 | 71 |
68 // TODO(jmesserly): make these immutable in JS? | 72 // TODO(jmesserly): make these immutable in JS? |
69 for (var name in _exports) { | 73 for (var name in _exports) { |
70 body.add(js.statement('#.# = #;', [_libraryName, name, name])); | 74 body.add(js.statement('#.# = #;', [_libraryName, name, name])); |
71 } | 75 } |
72 | 76 |
(...skipping 11 matching lines...) Expand all Loading... | |
84 | 88 |
85 @override | 89 @override |
86 JS.Statement visitCompilationUnit(CompilationUnit node) { | 90 JS.Statement visitCompilationUnit(CompilationUnit node) { |
87 // TODO(jmesserly): scriptTag, directives. | 91 // TODO(jmesserly): scriptTag, directives. |
88 var body = <JS.Statement>[]; | 92 var body = <JS.Statement>[]; |
89 for (var child in node.declarations) { | 93 for (var child in node.declarations) { |
90 // Attempt to group adjacent fields/properties. | 94 // Attempt to group adjacent fields/properties. |
91 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); | 95 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
92 if (child is! FunctionDeclaration) _flushLibraryProperties(body); | 96 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
93 | 97 |
94 var code = child.accept(this); | 98 var code = _visit(child); |
95 if (code != null) body.add(code); | 99 if (code != null) body.add(code); |
96 } | 100 } |
97 // Flush any unwritten fields/properties. | 101 // Flush any unwritten fields/properties. |
98 _flushLazyFields(body); | 102 _flushLazyFields(body); |
99 _flushLibraryProperties(body); | 103 _flushLibraryProperties(body); |
100 return _statement(body); | 104 return _statement(body); |
101 } | 105 } |
102 | 106 |
103 bool isPublic(String name) => !name.startsWith('_'); | 107 bool isPublic(String name) => !name.startsWith('_'); |
104 | 108 |
105 /// Conversions that we don't handle end up here. | 109 /// Conversions that we don't handle end up here. |
106 @override | 110 @override |
107 visitConversion(Conversion node) { | 111 visitConversion(Conversion node) { |
108 var from = node.baseType; | 112 var from = node.baseType; |
109 var to = node.convertedType; | 113 var to = node.convertedType; |
110 | 114 |
111 // All Dart number types map to a JS double. | 115 // All Dart number types map to a JS double. |
112 if (rules.isNumType(from) && | 116 if (rules.isNumType(from) && |
113 (rules.isIntType(to) || rules.isDoubleType(to))) { | 117 (rules.isIntType(to) || rules.isDoubleType(to))) { |
114 // TODO(jmesserly): a lot of these checks are meaningless, as people use | 118 // 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". | 119 // `num` to mean "any kind of number" rather than "could be null". |
116 // The core libraries especially suffer from this problem, with many of | 120 // The core libraries especially suffer from this problem, with many of |
117 // the `num` methods returning `num`. | 121 // the `num` methods returning `num`. |
118 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { | 122 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { |
119 // Converting from a nullable number to a non-nullable number | 123 // Converting from a nullable number to a non-nullable number |
120 // only requires a null check. | 124 // only requires a null check. |
121 return js.call('dart.notNull(#)', node.expression.accept(this)); | 125 return js.call('dart.notNull(#)', _visit(node.expression)); |
122 } else { | 126 } else { |
123 // A no-op in JavaScript. | 127 // A no-op in JavaScript. |
124 return node.expression.accept(this); | 128 return _visit(node.expression); |
125 } | 129 } |
126 } | 130 } |
127 | 131 |
128 return _emitCast(node.expression, to); | 132 return _emitCast(node.expression, to); |
129 } | 133 } |
130 | 134 |
131 @override | 135 @override |
132 visitAsExpression(AsExpression node) => | 136 visitAsExpression(AsExpression node) => |
133 _emitCast(node.expression, node.type.type); | 137 _emitCast(node.expression, node.type.type); |
134 | 138 |
135 _emitCast(Expression node, DartType type) => | 139 _emitCast(Expression node, DartType type) => |
136 js.call('dart.as(#)', [[node.accept(this), _emitTypeName(type)]]); | 140 js.call('dart.as(#)', [[_visit(node), _emitTypeName(type)]]); |
137 | 141 |
138 @override | 142 @override |
139 visitIsExpression(IsExpression node) { | 143 visitIsExpression(IsExpression node) { |
140 // Generate `is` as `dart.is` or `typeof` depending on the RHS type. | 144 // Generate `is` as `dart.is` or `typeof` depending on the RHS type. |
141 JS.Expression result; | 145 JS.Expression result; |
142 var type = node.type.type; | 146 var type = node.type.type; |
143 var lhs = node.expression.accept(this); | 147 var lhs = _visit(node.expression); |
144 var typeofName = _jsTypeofName(type); | 148 var typeofName = _jsTypeofName(type); |
145 if (typeofName != null) { | 149 if (typeofName != null) { |
146 result = js.call('typeof # == #', [lhs, typeofName]); | 150 result = js.call('typeof # == #', [lhs, typeofName]); |
147 } else { | 151 } else { |
148 // Always go through a runtime helper, because implicit interfaces. | 152 // Always go through a runtime helper, because implicit interfaces. |
149 result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]); | 153 result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]); |
150 } | 154 } |
151 | 155 |
152 if (node.notOperator != null) { | 156 if (node.notOperator != null) { |
153 return js.call('!#', result); | 157 return js.call('!#', result); |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
233 // Iff no constructor is specified for a class C, it implicitly has a | 237 // Iff no constructor is specified for a class C, it implicitly has a |
234 // default constructor `C() : super() {}`, unless C is class Object. | 238 // default constructor `C() : super() {}`, unless C is class Object. |
235 if (ctors.isEmpty && !node.element.type.isObject) { | 239 if (ctors.isEmpty && !node.element.type.isObject) { |
236 jsMethods.add(_emitImplicitConstructor(node, name, fields)); | 240 jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
237 } | 241 } |
238 | 242 |
239 for (var member in node.members) { | 243 for (var member in node.members) { |
240 if (member is ConstructorDeclaration) { | 244 if (member is ConstructorDeclaration) { |
241 jsMethods.add(_emitConstructor(member, name, fields)); | 245 jsMethods.add(_emitConstructor(member, name, fields)); |
242 } else if (member is MethodDeclaration) { | 246 } else if (member is MethodDeclaration) { |
243 jsMethods.add(member.accept(this)); | 247 jsMethods.add(_visit(member)); |
244 } | 248 } |
245 } | 249 } |
246 | 250 |
247 // Support for adapting dart:core Iterator/Iterable to ES6 versions. | 251 // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
248 // This lets them use for-of loops transparently. | 252 // This lets them use for-of loops transparently. |
249 // https://github.com/lukehoban/es6features#iterators--forof | 253 // https://github.com/lukehoban/es6features#iterators--forof |
250 if (node.element.library.isDartCore && node.element.name == 'Iterable') { | 254 if (node.element.library.isDartCore && node.element.name == 'Iterable') { |
251 JS.Fun body = js.call('''function() { | 255 JS.Fun body = js.call('''function() { |
252 var iterator = this.iterator; | 256 var iterator = this.iterator; |
253 return { | 257 return { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
324 | 328 |
325 JS.Method _emitConstructor(ConstructorDeclaration node, String className, | 329 JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
326 List<FieldDeclaration> fields) { | 330 List<FieldDeclaration> fields) { |
327 if (_externalOrNative(node)) return null; | 331 if (_externalOrNative(node)) return null; |
328 | 332 |
329 var name = _constructorName(className, node.name); | 333 var name = _constructorName(className, node.name); |
330 | 334 |
331 // We generate constructors as initializer methods in the class; | 335 // We generate constructors as initializer methods in the class; |
332 // this allows use of `super` for instance methods/properties. | 336 // this allows use of `super` for instance methods/properties. |
333 // It also avoids V8 restrictions on `super` in default constructors. | 337 // It also avoids V8 restrictions on `super` in default constructors. |
334 return new JS.Method(new JS.PropertyName(name), new JS.Fun( | 338 return new JS.Method(new JS.PropertyName(name), |
335 node.parameters.accept(this), _emitConstructorBody(node, fields))); | 339 new JS.Fun(_visit(node.parameters), _emitConstructorBody(node, fields))) |
340 ..sourceInformation = node; | |
336 } | 341 } |
337 | 342 |
338 String _constructorName(String className, SimpleIdentifier name) { | 343 String _constructorName(String className, SimpleIdentifier name) { |
339 if (name == null) return className; | 344 if (name == null) return className; |
340 return '$className\$${name.name}'; | 345 return '$className\$${name.name}'; |
341 } | 346 } |
342 | 347 |
343 JS.Block _emitConstructorBody( | 348 JS.Block _emitConstructorBody( |
344 ConstructorDeclaration node, List<FieldDeclaration> fields) { | 349 ConstructorDeclaration node, List<FieldDeclaration> fields) { |
345 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; | 350 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
346 if (node.redirectedConstructor != null) { | 351 if (node.redirectedConstructor != null) { |
347 return js.statement('{ return new #(#); }', [ | 352 return js.statement('{ return new #(#); }', [ |
348 node.redirectedConstructor.accept(this), | 353 _visit(node.redirectedConstructor), |
349 node.parameters.accept(this) | 354 _visit(node.parameters) |
350 ]); | 355 ]); |
351 } | 356 } |
352 | 357 |
353 var body = <JS.Statement>[]; | 358 var body = <JS.Statement>[]; |
354 | 359 |
355 // Generate optional/named argument value assignment. These can not have | 360 // Generate optional/named argument value assignment. These can not have |
356 // side effects, and may be used by the constructor's initializers, so it's | 361 // side effects, and may be used by the constructor's initializers, so it's |
357 // nice to do them first. | 362 // nice to do them first. |
358 var init = _emitArgumentInitializers(node.parameters); | 363 var init = _emitArgumentInitializers(node.parameters); |
359 if (init != null) body.add(init); | 364 if (init != null) body.add(init); |
360 | 365 |
361 // Redirecting constructors: these are not allowed to have initializers, | 366 // Redirecting constructors: these are not allowed to have initializers, |
362 // and the redirecting ctor invocation runs before field initializers. | 367 // and the redirecting ctor invocation runs before field initializers. |
363 var redirectCall = node.initializers.firstWhere( | 368 var redirectCall = node.initializers.firstWhere( |
364 (i) => i is RedirectingConstructorInvocation, orElse: () => null); | 369 (i) => i is RedirectingConstructorInvocation, orElse: () => null); |
365 | 370 |
366 if (redirectCall != null) { | 371 if (redirectCall != null) { |
367 body.add(redirectCall.accept(this)); | 372 body.add(_visit(redirectCall)); |
368 return new JS.Block(body); | 373 return new JS.Block(body); |
369 } | 374 } |
370 | 375 |
371 // Initializers only run for non-factory constructors. | 376 // Initializers only run for non-factory constructors. |
372 if (node.factoryKeyword == null) { | 377 if (node.factoryKeyword == null) { |
373 // Generate field initializers. | 378 // Generate field initializers. |
374 // These are expanded into each non-redirecting constructor. | 379 // These are expanded into each non-redirecting constructor. |
375 // In the future we may want to create an initializer function if we have | 380 // In the future we may want to create an initializer function if we have |
376 // multiple constructors, but it needs to be balanced against readability. | 381 // multiple constructors, but it needs to be balanced against readability. |
377 body.add(_initializeFields(fields, node.parameters, node.initializers)); | 382 body.add(_initializeFields(fields, node.parameters, node.initializers)); |
378 | 383 |
379 var superCall = node.initializers.firstWhere( | 384 var superCall = node.initializers.firstWhere( |
380 (i) => i is SuperConstructorInvocation, orElse: () => null); | 385 (i) => i is SuperConstructorInvocation, orElse: () => null); |
381 | 386 |
382 // If no superinitializer is provided, an implicit superinitializer of the | 387 // If no superinitializer is provided, an implicit superinitializer of the |
383 // form `super()` is added at the end of the initializer list, unless the | 388 // form `super()` is added at the end of the initializer list, unless the |
384 // enclosing class is class Object. | 389 // enclosing class is class Object. |
385 var jsSuper = _superConstructorCall(node.parent, superCall); | 390 var jsSuper = _superConstructorCall(node.parent, superCall); |
386 if (jsSuper != null) body.add(jsSuper); | 391 if (jsSuper != null) body.add(jsSuper); |
387 } | 392 } |
388 | 393 |
389 body.add(node.body.accept(this)); | 394 body.add(_visit(node.body)); |
390 return new JS.Block(body); | 395 return new JS.Block(body)..sourceInformation = node; |
391 } | 396 } |
392 | 397 |
393 @override | 398 @override |
394 JS.Statement visitRedirectingConstructorInvocation( | 399 JS.Statement visitRedirectingConstructorInvocation( |
395 RedirectingConstructorInvocation node) { | 400 RedirectingConstructorInvocation node) { |
396 ClassDeclaration classDecl = node.parent.parent; | 401 ClassDeclaration classDecl = node.parent.parent; |
397 var className = classDecl.name.name; | 402 var className = classDecl.name.name; |
398 | 403 |
399 var name = _constructorName(className, node.constructorName); | 404 var name = _constructorName(className, node.constructorName); |
400 return js.statement('this.#(#);', [name, node.argumentList.accept(this)]); | 405 return js.statement('this.#(#);', [name, _visit(node.argumentList)]); |
401 } | 406 } |
402 | 407 |
403 JS.Statement _superConstructorCall(ClassDeclaration clazz, | 408 JS.Statement _superConstructorCall(ClassDeclaration clazz, |
404 [SuperConstructorInvocation node]) { | 409 [SuperConstructorInvocation node]) { |
405 var superCtorName = node != null ? node.constructorName : null; | 410 var superCtorName = node != null ? node.constructorName : null; |
406 | 411 |
407 var element = clazz.element; | 412 var element = clazz.element; |
408 if (superCtorName == null && | 413 if (superCtorName == null && |
409 (element.type.isObject || element.supertype.isObject)) { | 414 (element.type.isObject || element.supertype.isObject)) { |
410 return null; | 415 return null; |
411 } | 416 } |
412 | 417 |
413 var supertypeName = element.supertype.name; | 418 var supertypeName = element.supertype.name; |
414 var name = _constructorName(supertypeName, superCtorName); | 419 var name = _constructorName(supertypeName, superCtorName); |
415 | 420 |
416 var args = node != null ? node.argumentList.accept(this) : []; | 421 var args = node != null ? _visit(node.argumentList) : []; |
417 return js.statement('super.#(#);', [name, args]); | 422 return js.statement('super.#(#);', [name, args])..sourceInformation = node; |
418 } | 423 } |
419 | 424 |
420 /// Initialize fields. They follow the sequence: | 425 /// Initialize fields. They follow the sequence: |
421 /// | 426 /// |
422 /// 1. field declaration initializer if non-const, | 427 /// 1. field declaration initializer if non-const, |
423 /// 2. field initializing parameters, | 428 /// 2. field initializing parameters, |
424 /// 3. constructor field initializers, | 429 /// 3. constructor field initializers, |
425 /// 4. initialize fields not covered in 1-3 | 430 /// 4. initialize fields not covered in 1-3 |
426 JS.Statement _initializeFields(List<FieldDeclaration> fields, | 431 JS.Statement _initializeFields(List<FieldDeclaration> fields, |
427 [FormalParameterList parameters, | 432 [FormalParameterList parameters, |
428 NodeList<ConstructorInitializer> initializers]) { | 433 NodeList<ConstructorInitializer> initializers]) { |
429 var body = <JS.Statement>[]; | 434 var body = <JS.Statement>[]; |
430 | 435 |
431 // Run field initializers if they can have side-effects. | 436 // Run field initializers if they can have side-effects. |
432 var unsetFields = new Map<String, VariableDeclaration>(); | 437 var unsetFields = new Map<String, VariableDeclaration>(); |
433 for (var declaration in fields) { | 438 for (var declaration in fields) { |
434 for (var field in declaration.fields.variables) { | 439 for (var field in declaration.fields.variables) { |
435 if (_isFieldInitConstant(field)) { | 440 if (_isFieldInitConstant(field)) { |
436 unsetFields[field.name.name] = field; | 441 unsetFields[field.name.name] = field; |
437 } else { | 442 } else { |
438 body.add(js.statement( | 443 body.add(js.statement( |
439 '# = #;', [field.name.accept(this), _visitInitializer(field)])); | 444 '# = #;', [_visit(field.name), _visitInitializer(field)])); |
440 } | 445 } |
441 } | 446 } |
442 } | 447 } |
443 | 448 |
444 // Initialize fields from `this.fieldName` parameters. | 449 // Initialize fields from `this.fieldName` parameters. |
445 if (parameters != null) { | 450 if (parameters != null) { |
446 for (var p in parameters.parameters) { | 451 for (var p in parameters.parameters) { |
447 if (p is DefaultFormalParameter) p = p.parameter; | 452 if (p is DefaultFormalParameter) p = p.parameter; |
448 if (p is FieldFormalParameter) { | 453 if (p is FieldFormalParameter) { |
449 var name = p.identifier.name; | 454 var name = p.identifier.name; |
450 body.add(js.statement('this.# = #;', [name, name])); | 455 body.add(js.statement('this.# = #;', [name, name])); |
451 unsetFields.remove(name); | 456 unsetFields.remove(name); |
452 } | 457 } |
453 } | 458 } |
454 } | 459 } |
455 | 460 |
456 // Run constructor field initializers such as `: foo = bar.baz` | 461 // Run constructor field initializers such as `: foo = bar.baz` |
457 if (initializers != null) { | 462 if (initializers != null) { |
458 for (var init in initializers) { | 463 for (var init in initializers) { |
459 if (init is ConstructorFieldInitializer) { | 464 if (init is ConstructorFieldInitializer) { |
460 body.add(js.statement('# = #;', [ | 465 body.add(js.statement( |
461 init.fieldName.accept(this), | 466 '# = #;', [_visit(init.fieldName), _visit(init.expression)])); |
462 init.expression.accept(this) | |
463 ])); | |
464 unsetFields.remove(init.fieldName.name); | 467 unsetFields.remove(init.fieldName.name); |
465 } | 468 } |
466 } | 469 } |
467 } | 470 } |
468 | 471 |
469 // Initialize all remaining fields | 472 // Initialize all remaining fields |
470 unsetFields.forEach((name, field) { | 473 unsetFields.forEach((name, field) { |
471 JS.Expression value; | 474 JS.Expression value; |
472 if (field.initializer != null) { | 475 if (field.initializer != null) { |
473 value = field.initializer.accept(this); | 476 value = _visit(field.initializer); |
474 } else { | 477 } else { |
475 var type = rules.elementType(field.element); | 478 var type = rules.elementType(field.element); |
476 if (rules.maybeNonNullableType(type)) { | 479 if (rules.maybeNonNullableType(type)) { |
477 value = js.call('dart.as(null, #)', _emitTypeName(type)); | 480 value = js.call('dart.as(null, #)', _emitTypeName(type)); |
478 } else { | 481 } else { |
479 value = new JS.LiteralNull(); | 482 value = new JS.LiteralNull(); |
480 } | 483 } |
481 } | 484 } |
482 body.add(js.statement('this.# = #;', [name, value])); | 485 body.add(js.statement('this.# = #;', [name, value])); |
483 }); | 486 }); |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
525 name, | 528 name, |
526 _defaultParamValue(param) | 529 _defaultParamValue(param) |
527 ])); | 530 ])); |
528 } | 531 } |
529 } | 532 } |
530 return _statement(body); | 533 return _statement(body); |
531 } | 534 } |
532 | 535 |
533 JS.Expression _defaultParamValue(FormalParameter param) { | 536 JS.Expression _defaultParamValue(FormalParameter param) { |
534 if (param is DefaultFormalParameter && param.defaultValue != null) { | 537 if (param is DefaultFormalParameter && param.defaultValue != null) { |
535 return param.defaultValue.accept(this); | 538 return _visit(param.defaultValue); |
536 } else { | 539 } else { |
537 return new JS.LiteralNull(); | 540 return new JS.LiteralNull(); |
538 } | 541 } |
539 } | 542 } |
540 | 543 |
541 @override | 544 @override |
542 JS.Method visitMethodDeclaration(MethodDeclaration node) { | 545 JS.Method visitMethodDeclaration(MethodDeclaration node) { |
543 if (node.isAbstract || _externalOrNative(node)) { | 546 if (node.isAbstract || _externalOrNative(node)) { |
544 return null; | 547 return null; |
545 } | 548 } |
546 | 549 |
547 var params = _visit(node.parameters); | 550 var params = _visit(node.parameters); |
548 if (params == null) params = []; | 551 if (params == null) params = []; |
549 | 552 |
550 return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)), | 553 return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)), |
551 new JS.Fun(params, node.body.accept(this)), | 554 new JS.Fun(params, _visit(node.body)), |
552 isGetter: node.isGetter, | 555 isGetter: node.isGetter, |
553 isSetter: node.isSetter, | 556 isSetter: node.isSetter, |
554 isStatic: node.isStatic); | 557 isStatic: node.isStatic); |
555 } | 558 } |
556 | 559 |
557 @override | 560 @override |
558 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { | 561 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { |
559 assert(node.parent is CompilationUnit); | 562 assert(node.parent is CompilationUnit); |
560 | 563 |
561 if (_externalOrNative(node)) return null; | 564 if (_externalOrNative(node)) return null; |
562 | 565 |
563 if (node.isGetter || node.isSetter) { | 566 if (node.isGetter || node.isSetter) { |
564 // Add these later so we can use getter/setter syntax. | 567 // Add these later so we can use getter/setter syntax. |
565 _properties.add(node); | 568 _properties.add(node); |
566 return null; | 569 return null; |
567 } | 570 } |
568 | 571 |
569 var body = <JS.Statement>[]; | 572 var body = <JS.Statement>[]; |
570 _flushLibraryProperties(body); | 573 _flushLibraryProperties(body); |
571 | 574 |
572 var name = node.name.name; | 575 var name = node.name.name; |
573 body.add(js.comment('Function $name: ${node.element.type}')); | 576 body.add(js.comment('Function $name: ${node.element.type}')); |
574 | 577 |
575 body.add(new JS.FunctionDeclaration(new JS.VariableDeclaration(name), | 578 body.add(new JS.FunctionDeclaration( |
576 node.functionExpression.accept(this))); | 579 new JS.VariableDeclaration(name), _visit(node.functionExpression))); |
577 | 580 |
578 if (isPublic(name)) _exports.add(name); | 581 if (isPublic(name)) _exports.add(name); |
579 return _statement(body); | 582 return _statement(body); |
580 } | 583 } |
581 | 584 |
582 JS.Method _emitTopLevelProperty(FunctionDeclaration node) { | 585 JS.Method _emitTopLevelProperty(FunctionDeclaration node) { |
583 var name = node.name.name; | 586 var name = node.name.name; |
584 if (isPublic(name)) _exports.add(name); | 587 if (isPublic(name)) _exports.add(name); |
585 return new JS.Method( | 588 return new JS.Method( |
586 new JS.PropertyName(name), node.functionExpression.accept(this), | 589 new JS.PropertyName(name), _visit(node.functionExpression), |
587 isGetter: node.isGetter, isSetter: node.isSetter); | 590 isGetter: node.isGetter, isSetter: node.isSetter); |
588 } | 591 } |
589 | 592 |
590 @override | 593 @override |
591 JS.Expression visitFunctionExpression(FunctionExpression node) { | 594 JS.Expression visitFunctionExpression(FunctionExpression node) { |
592 var params = _visit(node.parameters); | 595 var params = _visit(node.parameters); |
593 if (params == null) params = []; | 596 if (params == null) params = []; |
594 | 597 |
595 if (node.parent is FunctionDeclaration) { | 598 if (node.parent is FunctionDeclaration) { |
596 return new JS.Fun(params, node.body.accept(this)); | 599 return new JS.Fun(params, _visit(node.body)); |
597 } else { | 600 } else { |
598 var bindThis = _maybeBindThis(node.body); | 601 var bindThis = _maybeBindThis(node.body); |
599 | 602 |
600 String code; | 603 String code; |
601 AstNode body; | 604 AstNode body; |
602 var nodeBody = node.body; | 605 var nodeBody = node.body; |
603 if (nodeBody is ExpressionFunctionBody) { | 606 if (nodeBody is ExpressionFunctionBody) { |
604 code = '(#) => #'; | 607 code = '(#) => #'; |
605 body = nodeBody.expression; | 608 body = nodeBody.expression; |
606 } else { | 609 } else { |
607 code = '(#) => { #; }'; | 610 code = '(#) => { #; }'; |
608 body = nodeBody; | 611 body = nodeBody; |
609 } | 612 } |
610 return js.call('($code)$bindThis', [params, body.accept(this)]); | 613 return js.call('($code)$bindThis', [params, _visit(body)]); |
611 } | 614 } |
612 } | 615 } |
613 | 616 |
614 @override | 617 @override |
615 JS.Statement visitFunctionDeclarationStatement( | 618 JS.Statement visitFunctionDeclarationStatement( |
616 FunctionDeclarationStatement node) { | 619 FunctionDeclarationStatement node) { |
617 var func = node.functionDeclaration; | 620 var func = node.functionDeclaration; |
618 if (func.isGetter || func.isSetter) { | 621 if (func.isGetter || func.isSetter) { |
619 return js.comment('Unimplemented function get/set statement: $node'); | 622 return js.comment('Unimplemented function get/set statement: $node'); |
620 } | 623 } |
621 | 624 |
622 var name = new JS.VariableDeclaration(func.name.name); | 625 var name = new JS.VariableDeclaration(func.name.name); |
623 return new JS.Block([ | 626 return new JS.Block([ |
624 js.comment("// Function ${func.name.name}: ${func.element.type}\n"), | 627 js.comment("// Function ${func.name.name}: ${func.element.type}\n"), |
625 new JS.FunctionDeclaration(name, func.functionExpression.accept(this)) | 628 new JS.FunctionDeclaration(name, _visit(func.functionExpression)) |
626 ]); | 629 ]); |
627 } | 630 } |
628 | 631 |
629 /// Writes a simple identifier. This can handle implicit `this` as well as | 632 /// Writes a simple identifier. This can handle implicit `this` as well as |
630 /// going through the qualified library name if necessary. | 633 /// going through the qualified library name if necessary. |
631 @override | 634 @override |
632 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { | 635 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { |
633 var e = node.staticElement; | 636 var e = node.staticElement; |
634 if (e == null) { | 637 if (e == null) { |
635 return js.commentExpression( | 638 return js.commentExpression( |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
684 var lhs = node.leftHandSide; | 687 var lhs = node.leftHandSide; |
685 var rhs = node.rightHandSide; | 688 var rhs = node.rightHandSide; |
686 if (lhs is IndexExpression) { | 689 if (lhs is IndexExpression) { |
687 String code; | 690 String code; |
688 var target = _getTarget(lhs); | 691 var target = _getTarget(lhs); |
689 if (rules.isDynamicTarget(target)) { | 692 if (rules.isDynamicTarget(target)) { |
690 code = 'dart.dsetindex(#, #, #)'; | 693 code = 'dart.dsetindex(#, #, #)'; |
691 } else { | 694 } else { |
692 code = '#.set(#, #)'; | 695 code = '#.set(#, #)'; |
693 } | 696 } |
694 return js.call(code, [ | 697 return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]); |
695 target.accept(this), | |
696 lhs.index.accept(this), | |
697 rhs.accept(this) | |
698 ]); | |
699 } | 698 } |
700 | 699 |
701 if (lhs is PropertyAccess) { | 700 if (lhs is PropertyAccess) { |
702 var target = _getTarget(lhs); | 701 var target = _getTarget(lhs); |
703 if (rules.isDynamicTarget(target)) { | 702 if (rules.isDynamicTarget(target)) { |
704 return js.call('dart.dput(#, #, #)', [ | 703 return js.call('dart.dput(#, #, #)', [ |
705 target.accept(this), | 704 _visit(target), |
706 js.string(lhs.propertyName.name, "'"), | 705 js.string(lhs.propertyName.name, "'"), |
707 rhs.accept(this) | 706 _visit(rhs) |
708 ]); | 707 ]); |
709 } | 708 } |
710 } | 709 } |
711 | 710 |
712 if (node.parent is ExpressionStatement && | 711 if (node.parent is ExpressionStatement && |
713 rhs is CascadeExpression && | 712 rhs is CascadeExpression && |
714 _isStateless(lhs, rhs)) { | 713 _isStateless(lhs, rhs)) { |
715 // Special case: cascade assignment to a variable in a statement. | 714 // Special case: cascade assignment to a variable in a statement. |
716 // We can reuse the variable to desugar it: | 715 // We can reuse the variable to desugar it: |
717 // result = []..length = length; | 716 // result = []..length = length; |
718 // becomes: | 717 // becomes: |
719 // result = []; | 718 // result = []; |
720 // result.length = length; | 719 // result.length = length; |
721 var savedCascadeTemp = _cascadeTarget; | 720 var savedCascadeTemp = _cascadeTarget; |
722 _cascadeTarget = lhs; | 721 _cascadeTarget = lhs; |
723 | 722 |
724 var body = []; | 723 var body = []; |
725 body.add( | 724 body.add(js.statement('# = #;', [_visit(lhs), _visit(rhs.target)])); |
726 js.statement('# = #;', [lhs.accept(this), rhs.target.accept(this)])); | |
727 for (var section in rhs.cascadeSections) { | 725 for (var section in rhs.cascadeSections) { |
728 body.add(new JS.ExpressionStatement(section.accept(this))); | 726 body.add(new JS.ExpressionStatement(_visit(section))); |
729 } | 727 } |
730 | 728 |
731 _cascadeTarget = savedCascadeTemp; | 729 _cascadeTarget = savedCascadeTemp; |
732 return _statement(body); | 730 return _statement(body); |
733 } | 731 } |
734 | 732 |
735 return js.call('# = #', [lhs.accept(this), rhs.accept(this)]); | 733 return js.call('# = #', [_visit(lhs), _visit(rhs)]); |
736 } | 734 } |
737 | 735 |
738 @override | 736 @override |
739 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { | 737 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
740 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); | 738 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
741 var ret = new JS.Return(node.expression.accept(this)); | 739 var ret = new JS.Return(_visit(node.expression)); |
742 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); | 740 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
743 } | 741 } |
744 | 742 |
745 @override | 743 @override |
746 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); | 744 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); |
747 | 745 |
748 @override | 746 @override |
749 JS.Block visitBlockFunctionBody(BlockFunctionBody node) { | 747 JS.Block visitBlockFunctionBody(BlockFunctionBody node) { |
750 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); | 748 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
751 var block = visitBlock(node.block); | 749 var block = visitBlock(node.block); |
752 if (initArgs != null) return new JS.Block([initArgs, block]); | 750 if (initArgs != null) return new JS.Block([initArgs, block]); |
753 return block; | 751 return block; |
754 } | 752 } |
755 | 753 |
756 @override | 754 @override |
757 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements)); | 755 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements)); |
758 | 756 |
759 @override | 757 @override |
760 visitMethodInvocation(MethodInvocation node) { | 758 visitMethodInvocation(MethodInvocation node) { |
761 var target = node.isCascaded ? _cascadeTarget : node.target; | 759 var target = node.isCascaded ? _cascadeTarget : node.target; |
762 | 760 |
763 var result = _emitForeignJS(node); | 761 var result = _emitForeignJS(node); |
764 if (result != null) return result; | 762 if (result != null) return result; |
765 | 763 |
766 if (rules.isDynamicCall(node.methodName)) { | 764 if (rules.isDynamicCall(node.methodName)) { |
767 var args = node.argumentList.accept(this); | 765 var args = _visit(node.argumentList); |
768 if (target != null) { | 766 if (target != null) { |
769 return js.call('dart.dinvoke(#, #, #)', [ | 767 return js.call('dart.dinvoke(#, #, #)', [ |
770 target.accept(this), | 768 _visit(target), |
771 js.string(node.methodName.name, "'"), | 769 js.string(node.methodName.name, "'"), |
772 args | 770 args |
773 ]); | 771 ]); |
774 } else { | 772 } else { |
775 return js.call( | 773 return js.call('dart.dinvokef(#, #)', [_visit(node.methodName), args]); |
776 'dart.dinvokef(#, #)', [node.methodName.accept(this), args]); | |
777 } | 774 } |
778 } | 775 } |
779 | 776 |
780 // TODO(jmesserly): if this resolves to a getter returning a function with | 777 // TODO(jmesserly): if this resolves to a getter returning a function with |
781 // a call method, we don't generate the `.call` correctly. | 778 // a call method, we don't generate the `.call` correctly. |
782 | 779 |
783 var targetJs; | 780 var targetJs; |
784 if (target != null) { | 781 if (target != null) { |
785 targetJs = js.call('#.#', [target.accept(this), node.methodName.name]); | 782 targetJs = js.call('#.#', [_visit(target), node.methodName.name]); |
786 } else { | 783 } else { |
787 targetJs = node.methodName.accept(this); | 784 targetJs = _visit(node.methodName); |
788 } | 785 } |
789 | 786 |
790 return js.call('#(#)', [targetJs, node.argumentList.accept(this)]); | 787 return js.call('#(#)', [targetJs, _visit(node.argumentList)]); |
791 } | 788 } |
792 | 789 |
793 /// Emits code for the `JS(...)` builtin. | 790 /// Emits code for the `JS(...)` builtin. |
794 _emitForeignJS(MethodInvocation node) { | 791 _emitForeignJS(MethodInvocation node) { |
795 var e = node.methodName.staticElement; | 792 var e = node.methodName.staticElement; |
796 if (e is FunctionElement && | 793 if (e is FunctionElement && |
797 e.library.name == '_foreign_helper' && | 794 e.library.name == '_foreign_helper' && |
798 e.name == 'JS') { | 795 e.name == 'JS') { |
799 var args = node.argumentList.arguments; | 796 var args = node.argumentList.arguments; |
800 // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer` | 797 // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer` |
(...skipping 10 matching lines...) Expand all Loading... | |
811 | 808 |
812 @override | 809 @override |
813 JS.Expression visitFunctionExpressionInvocation( | 810 JS.Expression visitFunctionExpressionInvocation( |
814 FunctionExpressionInvocation node) { | 811 FunctionExpressionInvocation node) { |
815 var code; | 812 var code; |
816 if (rules.isDynamicCall(node.function)) { | 813 if (rules.isDynamicCall(node.function)) { |
817 code = 'dart.dinvokef(#, #)'; | 814 code = 'dart.dinvokef(#, #)'; |
818 } else { | 815 } else { |
819 code = '#(#)'; | 816 code = '#(#)'; |
820 } | 817 } |
821 return js.call( | 818 return js.call(code, [_visit(node.function), _visit(node.argumentList)]); |
822 code, [node.function.accept(this), node.argumentList.accept(this)]); | |
823 } | 819 } |
824 | 820 |
825 @override | 821 @override |
826 List<JS.Expression> visitArgumentList(ArgumentList node) { | 822 List<JS.Expression> visitArgumentList(ArgumentList node) { |
827 var args = <JS.Expression>[]; | 823 var args = <JS.Expression>[]; |
828 var named = <JS.Property>[]; | 824 var named = <JS.Property>[]; |
829 for (var arg in node.arguments) { | 825 for (var arg in node.arguments) { |
830 if (arg is NamedExpression) { | 826 if (arg is NamedExpression) { |
831 named.add(visitNamedExpression(arg)); | 827 named.add(visitNamedExpression(arg)); |
832 } else { | 828 } else { |
833 args.add(arg.accept(this)); | 829 args.add(_visit(arg)); |
834 } | 830 } |
835 } | 831 } |
836 if (named.isNotEmpty) { | 832 if (named.isNotEmpty) { |
837 args.add(new JS.ObjectInitializer(named)); | 833 args.add(new JS.ObjectInitializer(named)); |
838 } | 834 } |
839 return args; | 835 return args; |
840 } | 836 } |
841 | 837 |
842 @override | 838 @override |
843 JS.Property visitNamedExpression(NamedExpression node) { | 839 JS.Property visitNamedExpression(NamedExpression node) { |
844 assert(node.parent is ArgumentList); | 840 assert(node.parent is ArgumentList); |
845 return new JS.Property(new JS.PropertyName(node.name.label.name), | 841 return new JS.Property( |
846 node.expression.accept(this)); | 842 new JS.PropertyName(node.name.label.name), _visit(node.expression)); |
847 } | 843 } |
848 | 844 |
849 @override | 845 @override |
850 List<JS.Parameter> visitFormalParameterList(FormalParameterList node) { | 846 List<JS.Parameter> visitFormalParameterList(FormalParameterList node) { |
851 var result = <JS.Parameter>[]; | 847 var result = <JS.Parameter>[]; |
852 for (FormalParameter param in node.parameters) { | 848 for (FormalParameter param in node.parameters) { |
853 if (param.kind == ParameterKind.NAMED) { | 849 if (param.kind == ParameterKind.NAMED) { |
854 result.add(new JS.Parameter(_jsNamedParameterName)); | 850 result.add(new JS.Parameter(_jsNamedParameterName)); |
855 break; | 851 break; |
856 } | 852 } |
857 result.add(new JS.Parameter(param.identifier.name)); | 853 result.add(new JS.Parameter(param.identifier.name)); |
858 } | 854 } |
859 return result; | 855 return result; |
860 } | 856 } |
861 | 857 |
862 @override | 858 @override |
863 JS.Statement visitExpressionStatement(ExpressionStatement node) => | 859 JS.Statement visitExpressionStatement(ExpressionStatement node) => |
864 _expressionStatement(node.expression.accept(this)); | 860 _expressionStatement(_visit(node.expression)); |
865 | 861 |
866 // Some expressions may choose to generate themselves as JS statements | 862 // Some expressions may choose to generate themselves as JS statements |
867 // if their parent is in a statement context. | 863 // if their parent is in a statement context. |
868 // TODO(jmesserly): refactor so we handle the special cases here, and | 864 // TODO(jmesserly): refactor so we handle the special cases here, and |
869 // can use better return types on the expression visit methods. | 865 // can use better return types on the expression visit methods. |
870 JS.Statement _expressionStatement(expr) => | 866 JS.Statement _expressionStatement(expr) => |
871 expr is JS.Statement ? expr : new JS.ExpressionStatement(expr); | 867 expr is JS.Statement ? expr : new JS.ExpressionStatement(expr); |
872 | 868 |
873 @override | 869 @override |
874 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => | 870 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => |
875 new JS.EmptyStatement(); | 871 new JS.EmptyStatement(); |
876 | 872 |
877 @override | 873 @override |
878 JS.Statement visitAssertStatement(AssertStatement node) => | 874 JS.Statement visitAssertStatement(AssertStatement node) => |
879 // TODO(jmesserly): only emit in checked mode. | 875 // TODO(jmesserly): only emit in checked mode. |
880 js.statement('dart.assert(#);', node.condition.accept(this)); | 876 js.statement('dart.assert(#);', _visit(node.condition)); |
881 | 877 |
882 @override | 878 @override |
883 JS.Return visitReturnStatement(ReturnStatement node) => | 879 JS.Return visitReturnStatement(ReturnStatement node) => |
884 new JS.Return(_visit(node.expression)); | 880 new JS.Return(_visit(node.expression)); |
885 | 881 |
886 @override | 882 @override |
887 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | 883 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
888 var body = <JS.Statement>[]; | 884 var body = <JS.Statement>[]; |
889 | 885 |
890 for (var field in node.variables.variables) { | 886 for (var field in node.variables.variables) { |
891 if (field.isConst) { | 887 if (field.isConst) { |
892 // constant fields don't change, so we can generate them as `let` | 888 // constant fields don't change, so we can generate them as `let` |
893 // but add them to the module's exports | 889 // but add them to the module's exports |
894 var name = field.name.name; | 890 var name = field.name.name; |
895 body.add(js.statement('let # = #;', [ | 891 body.add(js.statement('let # = #;', [ |
896 new JS.VariableDeclaration(name), | 892 new JS.VariableDeclaration(name), |
897 _visitInitializer(field) | 893 _visitInitializer(field) |
898 ])); | 894 ])); |
899 if (isPublic(name)) _exports.add(name); | 895 if (isPublic(name)) _exports.add(name); |
900 } else if (_isFieldInitConstant(field)) { | 896 } else if (_isFieldInitConstant(field)) { |
901 body.add(js.statement( | 897 body.add(js.statement( |
902 '# = #;', [field.name.accept(this), _visitInitializer(field)])); | 898 '# = #;', [_visit(field.name), _visitInitializer(field)])); |
903 } else { | 899 } else { |
904 _lazyFields.add(field); | 900 _lazyFields.add(field); |
905 } | 901 } |
906 } | 902 } |
907 | 903 |
908 return _statement(body); | 904 return _statement(body); |
909 } | 905 } |
910 | 906 |
911 @override | 907 @override |
912 visitVariableDeclarationList(VariableDeclarationList node) { | 908 visitVariableDeclarationList(VariableDeclarationList node) { |
913 var last = node.variables.last; | 909 var last = node.variables.last; |
914 var lastInitializer = last.initializer; | 910 var lastInitializer = last.initializer; |
915 | 911 |
916 List<JS.VariableInitialization> variables; | 912 List<JS.VariableInitialization> variables; |
917 if (lastInitializer is CascadeExpression && | 913 if (lastInitializer is CascadeExpression && |
918 node.parent is VariableDeclarationStatement) { | 914 node.parent is VariableDeclarationStatement) { |
919 // Special case: cascade as variable initializer | 915 // Special case: cascade as variable initializer |
920 // | 916 // |
921 // We can reuse the variable to desugar it: | 917 // We can reuse the variable to desugar it: |
922 // var result = []..length = length; | 918 // var result = []..length = length; |
923 // becomes: | 919 // becomes: |
924 // var result = []; | 920 // var result = []; |
925 // result.length = length; | 921 // result.length = length; |
926 var savedCascadeTemp = _cascadeTarget; | 922 var savedCascadeTemp = _cascadeTarget; |
927 _cascadeTarget = last.name; | 923 _cascadeTarget = last.name; |
928 | 924 |
929 variables = _visitList(node.variables.take(node.variables.length - 1)); | 925 variables = _visitList(node.variables.take(node.variables.length - 1)); |
930 variables.add(new JS.VariableInitialization( | 926 variables.add(new JS.VariableInitialization( |
931 new JS.VariableDeclaration(last.name.name), | 927 new JS.VariableDeclaration(last.name.name), |
932 lastInitializer.target.accept(this))); | 928 _visit(lastInitializer.target))); |
933 | 929 |
934 var result = <JS.Expression>[ | 930 var result = <JS.Expression>[ |
935 new JS.VariableDeclarationList('let', variables) | 931 new JS.VariableDeclarationList('let', variables) |
936 ]; | 932 ]; |
937 result.addAll(_visitList(lastInitializer.cascadeSections)); | 933 result.addAll(_visitList(lastInitializer.cascadeSections)); |
938 _cascadeTarget = savedCascadeTemp; | 934 _cascadeTarget = savedCascadeTemp; |
939 return _statement(result.map((e) => new JS.ExpressionStatement(e))); | 935 return _statement(result.map((e) => new JS.ExpressionStatement(e))); |
940 } else { | 936 } else { |
941 variables = _visitList(node.variables); | 937 variables = _visitList(node.variables); |
942 } | 938 } |
(...skipping 21 matching lines...) Expand all Loading... | |
964 } | 960 } |
965 | 961 |
966 JS.Statement _emitLazyFields( | 962 JS.Statement _emitLazyFields( |
967 String objExpr, List<VariableDeclaration> fields) { | 963 String objExpr, List<VariableDeclaration> fields) { |
968 if (fields.isEmpty) return null; | 964 if (fields.isEmpty) return null; |
969 | 965 |
970 var methods = []; | 966 var methods = []; |
971 for (var node in fields) { | 967 for (var node in fields) { |
972 var name = node.name.name; | 968 var name = node.name.name; |
973 methods.add(new JS.Method(new JS.PropertyName(name), | 969 methods.add(new JS.Method(new JS.PropertyName(name), |
974 js.call('function() { return #; }', node.initializer.accept(this)), | 970 js.call('function() { return #; }', _visit(node.initializer)), |
975 isGetter: true)); | 971 isGetter: true)); |
976 | 972 |
977 // TODO(jmesserly): use a dummy setter to indicate writable. | 973 // TODO(jmesserly): use a dummy setter to indicate writable. |
978 if (!node.isFinal) { | 974 if (!node.isFinal) { |
979 methods.add(new JS.Method( | 975 methods.add(new JS.Method( |
980 new JS.PropertyName(name), js.call('function() {}'), | 976 new JS.PropertyName(name), js.call('function() {}'), |
981 isSetter: true)); | 977 isSetter: true)); |
982 } | 978 } |
983 } | 979 } |
984 | 980 |
985 return js.statement( | 981 return js.statement( |
986 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); | 982 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); |
987 } | 983 } |
988 | 984 |
989 void _flushLibraryProperties(List<JS.Statement> body) { | 985 void _flushLibraryProperties(List<JS.Statement> body) { |
990 if (_properties.isEmpty) return; | 986 if (_properties.isEmpty) return; |
991 body.add(js.statement('dart.copyProperties(#, { # });', [ | 987 body.add(js.statement('dart.copyProperties(#, { # });', [ |
992 _libraryName, | 988 _libraryName, |
993 _properties.map(_emitTopLevelProperty) | 989 _properties.map(_emitTopLevelProperty) |
994 ])); | 990 ])); |
995 _properties.clear(); | 991 _properties.clear(); |
996 } | 992 } |
997 | 993 |
998 @override | 994 @override |
999 JS.Statement visitVariableDeclarationStatement( | 995 JS.Statement visitVariableDeclarationStatement( |
1000 VariableDeclarationStatement node) => | 996 VariableDeclarationStatement node) => |
1001 _expressionStatement(node.variables.accept(this)); | 997 _expressionStatement(_visit(node.variables)); |
1002 | 998 |
1003 @override | 999 @override |
1004 visitConstructorName(ConstructorName node) { | 1000 visitConstructorName(ConstructorName node) { |
1005 var typeName = node.type.name.accept(this); | 1001 var typeName = _visit(node.type.name); |
1006 if (node.name != null) { | 1002 if (node.name != null) { |
1007 return js.call('#.#', [typeName, node.name.name]); | 1003 return js.call('#.#', [typeName, node.name.name]); |
1008 } | 1004 } |
1009 return typeName; | 1005 return typeName; |
1010 } | 1006 } |
1011 | 1007 |
1012 @override | 1008 @override |
1013 visitInstanceCreationExpression(InstanceCreationExpression node) { | 1009 visitInstanceCreationExpression(InstanceCreationExpression node) { |
1014 return js.call('new #(#)', [ | 1010 return js.call( |
1015 node.constructorName.accept(this), | 1011 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); |
1016 node.argumentList.accept(this) | |
1017 ]); | |
1018 } | 1012 } |
1019 | 1013 |
1020 /// True if this type is built-in to JS, and we use the values unwrapped. | 1014 /// True if this type is built-in to JS, and we use the values unwrapped. |
1021 /// For these types we generate a calling convention via static | 1015 /// For these types we generate a calling convention via static |
1022 /// "extension methods". This allows types to be extended without adding | 1016 /// "extension methods". This allows types to be extended without adding |
1023 /// extensions directly on the prototype. | 1017 /// extensions directly on the prototype. |
1024 bool _isJSBuiltinType(DartType t) => | 1018 bool _isJSBuiltinType(DartType t) => |
1025 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t); | 1019 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t); |
1026 | 1020 |
1027 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) && | 1021 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) && |
1028 (rules.isIntType(t) || | 1022 (rules.isIntType(t) || |
1029 rules.isDoubleType(t) || | 1023 rules.isDoubleType(t) || |
1030 rules.isBoolType(t) || | 1024 rules.isBoolType(t) || |
1031 rules.isNumType(t)); | 1025 rules.isNumType(t)); |
1032 | 1026 |
1033 bool typeIsNonNullablePrimitiveInJS(DartType t) => | 1027 bool typeIsNonNullablePrimitiveInJS(DartType t) => |
1034 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); | 1028 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); |
1035 | 1029 |
1036 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => | 1030 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
1037 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); | 1031 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
1038 | 1032 |
1039 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); | 1033 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
1040 | 1034 |
1041 JS.Expression notNull(Expression expr) { | 1035 JS.Expression notNull(Expression expr) { |
1042 var type = rules.getStaticType(expr); | 1036 var type = rules.getStaticType(expr); |
1043 if (rules.isNonNullableType(type)) { | 1037 if (rules.isNonNullableType(type)) { |
1044 return expr.accept(this); | 1038 return _visit(expr); |
1045 } else { | 1039 } else { |
1046 return js.call('dart.notNull(#)', expr.accept(this)); | 1040 return js.call('dart.notNull(#)', _visit(expr)); |
1047 } | 1041 } |
1048 } | 1042 } |
1049 | 1043 |
1050 @override | 1044 @override |
1051 JS.Expression visitBinaryExpression(BinaryExpression node) { | 1045 JS.Expression visitBinaryExpression(BinaryExpression node) { |
1052 var op = node.operator; | 1046 var op = node.operator; |
1053 var left = node.leftOperand; | 1047 var left = node.leftOperand; |
1054 var right = node.rightOperand; | 1048 var right = node.rightOperand; |
1055 var leftType = rules.getStaticType(left); | 1049 var leftType = rules.getStaticType(left); |
1056 var rightType = rules.getStaticType(right); | 1050 var rightType = rules.getStaticType(right); |
1057 | 1051 |
1058 var code; | 1052 var code; |
1059 if (op.type.isEqualityOperator) { | 1053 if (op.type.isEqualityOperator) { |
1060 // If we statically know LHS or RHS is null we can generate a clean check. | 1054 // If we statically know LHS or RHS is null we can generate a clean check. |
1061 // We can also do this if the left hand side is a primitive type, because | 1055 // We can also do this if the left hand side is a primitive type, because |
1062 // we know then it doesn't have an overridden. | 1056 // we know then it doesn't have an overridden. |
1063 if (_isNull(left) || _isNull(right) || typeIsPrimitiveInJS(leftType)) { | 1057 if (_isNull(left) || _isNull(right) || typeIsPrimitiveInJS(leftType)) { |
1064 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison | 1058 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison |
1065 code = op.type == TokenType.EQ_EQ ? '# === #' : '# !== #'; | 1059 code = op.type == TokenType.EQ_EQ ? '# === #' : '# !== #'; |
1066 } else { | 1060 } else { |
1067 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; | 1061 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
1068 code = '${bang}dart.equals(#, #)'; | 1062 code = '${bang}dart.equals(#, #)'; |
1069 } | 1063 } |
1070 return js.call(code, [left.accept(this), right.accept(this)]); | 1064 return js.call(code, [_visit(left), _visit(right)]); |
1071 } else if (binaryOperationIsPrimitive(leftType, rightType)) { | 1065 } else if (binaryOperationIsPrimitive(leftType, rightType)) { |
1072 // special cases where we inline the operation | 1066 // special cases where we inline the operation |
1073 // these values are assumed to be non-null (determined by the checker) | 1067 // these values are assumed to be non-null (determined by the checker) |
1074 // TODO(jmesserly): it would be nice to just inline the method from core, | 1068 // TODO(jmesserly): it would be nice to just inline the method from core, |
1075 // instead of special cases here. | 1069 // instead of special cases here. |
1076 if (op.type == TokenType.TILDE_SLASH) { | 1070 if (op.type == TokenType.TILDE_SLASH) { |
1077 // `a ~/ b` is equivalent to `(a / b).truncate()` | 1071 // `a ~/ b` is equivalent to `(a / b).truncate()` |
1078 code = '(# / #).truncate()'; | 1072 code = '(# / #).truncate()'; |
1079 } else { | 1073 } else { |
1080 // TODO(vsm): When do Dart ops not map to JS? | 1074 // TODO(vsm): When do Dart ops not map to JS? |
1081 code = '# $op #'; | 1075 code = '# $op #'; |
1082 } | 1076 } |
1083 return js.call(code, [notNull(left), notNull(right)]); | 1077 return js.call(code, [notNull(left), notNull(right)]); |
1084 } else { | 1078 } else { |
1085 var opString = js.string(op.lexeme, "'"); | 1079 var opString = js.string(op.lexeme, "'"); |
1086 if (rules.isDynamicTarget(left)) { | 1080 if (rules.isDynamicTarget(left)) { |
1087 // dynamic dispatch | 1081 // dynamic dispatch |
1088 return js.call('dart.dbinary(#, #, #)', [ | 1082 return js.call( |
1089 left.accept(this), | 1083 'dart.dbinary(#, #, #)', [_visit(left), opString, _visit(right)]); |
1090 opString, | |
1091 right.accept(this) | |
1092 ]); | |
1093 } else if (_isJSBuiltinType(leftType)) { | 1084 } else if (_isJSBuiltinType(leftType)) { |
1094 // TODO(jmesserly): we'd get better readability from the static-dispatch | 1085 // TODO(jmesserly): we'd get better readability from the static-dispatch |
1095 // pattern below. Consider: | 1086 // pattern below. Consider: |
1096 // | 1087 // |
1097 // "hello"['+']"world" | 1088 // "hello"['+']"world" |
1098 // vs | 1089 // vs |
1099 // core.String['+']("hello", "world") | 1090 // core.String['+']("hello", "world") |
1100 // | 1091 // |
1101 // Infix notation is much more readable, which is a bit part of why | 1092 // Infix notation is much more readable, which is a bit part of why |
1102 // C# added its extension methods feature. However this would require | 1093 // C# added its extension methods feature. However this would require |
1103 // adding these methods to String.prototype/Number.prototype in JS. | 1094 // adding these methods to String.prototype/Number.prototype in JS. |
1104 return js.call('#.#(#, #)', [ | 1095 return js.call('#.#(#, #)', [ |
1105 _emitTypeName(leftType), | 1096 _emitTypeName(leftType), |
1106 opString, | 1097 opString, |
1107 left.accept(this), | 1098 _visit(left), |
1108 right.accept(this) | 1099 _visit(right) |
1109 ]); | 1100 ]); |
1110 } else { | 1101 } else { |
1111 // Generic static-dispatch, user-defined operator code path. | 1102 // Generic static-dispatch, user-defined operator code path. |
1112 return js.call( | 1103 return js.call('#.#(#)', [_visit(left), opString, _visit(right)]); |
1113 '#.#(#)', [left.accept(this), opString, right.accept(this)]); | |
1114 } | 1104 } |
1115 } | 1105 } |
1116 } | 1106 } |
1117 | 1107 |
1118 bool _isNull(Expression expr) => expr is NullLiteral; | 1108 bool _isNull(Expression expr) => expr is NullLiteral; |
1119 | 1109 |
1120 @override | 1110 @override |
1121 JS.Expression visitPostfixExpression(PostfixExpression node) { | 1111 JS.Expression visitPostfixExpression(PostfixExpression node) { |
1122 var op = node.operator; | 1112 var op = node.operator; |
1123 var expr = node.operand; | 1113 var expr = node.operand; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1160 // Special case: target is stateless, so we can just reuse it. | 1150 // Special case: target is stateless, so we can just reuse it. |
1161 _cascadeTarget = node.target; | 1151 _cascadeTarget = node.target; |
1162 | 1152 |
1163 if (parent is ExpressionStatement) { | 1153 if (parent is ExpressionStatement) { |
1164 var sections = _visitList(node.cascadeSections); | 1154 var sections = _visitList(node.cascadeSections); |
1165 result = _statement(sections.map((e) => new JS.ExpressionStatement(e))); | 1155 result = _statement(sections.map((e) => new JS.ExpressionStatement(e))); |
1166 } else { | 1156 } else { |
1167 // Use comma expression. For example: | 1157 // Use comma expression. For example: |
1168 // (sb.write(1), sb.write(2), sb) | 1158 // (sb.write(1), sb.write(2), sb) |
1169 var sections = _visitListToBinary(node.cascadeSections, ','); | 1159 var sections = _visitListToBinary(node.cascadeSections, ','); |
1170 result = new JS.Binary(',', sections, _cascadeTarget.accept(this)); | 1160 result = new JS.Binary(',', sections, _visit(_cascadeTarget)); |
1171 } | 1161 } |
1172 } else { | 1162 } else { |
1173 // In the general case we need to capture the target expression into | 1163 // In the general case we need to capture the target expression into |
1174 // a temporary. This uses a lambda to get a temporary scope, and it also | 1164 // a temporary. This uses a lambda to get a temporary scope, and it also |
1175 // remains valid in an expression context. | 1165 // remains valid in an expression context. |
1176 // TODO(jmesserly): need a better way to handle temps. | 1166 // TODO(jmesserly): need a better way to handle temps. |
1177 // TODO(jmesserly): special case for parent is ExpressionStatement? | 1167 // TODO(jmesserly): special case for parent is ExpressionStatement? |
1178 _cascadeTarget = | 1168 _cascadeTarget = |
1179 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); | 1169 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); |
1180 _cascadeTarget.staticElement = | 1170 _cascadeTarget.staticElement = |
1181 new LocalVariableElementImpl.forNode(_cascadeTarget); | 1171 new LocalVariableElementImpl.forNode(_cascadeTarget); |
1182 _cascadeTarget.staticType = node.target.staticType; | 1172 _cascadeTarget.staticType = node.target.staticType; |
1183 | 1173 |
1184 var body = _visitList(node.cascadeSections); | 1174 var body = _visitList(node.cascadeSections); |
1185 if (node.parent is! ExpressionStatement) { | 1175 if (node.parent is! ExpressionStatement) { |
1186 body.add(js.statement('return #;', _cascadeTarget.name)); | 1176 body.add(js.statement('return #;', _cascadeTarget.name)); |
1187 } | 1177 } |
1188 | 1178 |
1189 var bindThis = _maybeBindThis(node.cascadeSections); | 1179 var bindThis = _maybeBindThis(node.cascadeSections); |
1190 result = js.call('((#) => { # })$bindThis(#)', [ | 1180 result = js.call('((#) => { # })$bindThis(#)', [ |
1191 _cascadeTarget.name, | 1181 _cascadeTarget.name, |
1192 body, | 1182 body, |
1193 node.target.accept(this) | 1183 _visit(node.target) |
1194 ]); | 1184 ]); |
1195 } | 1185 } |
1196 | 1186 |
1197 _cascadeTarget = savedCascadeTemp; | 1187 _cascadeTarget = savedCascadeTemp; |
1198 return result; | 1188 return result; |
1199 } | 1189 } |
1200 | 1190 |
1201 /// True is the expression can be evaluated multiple times without causing | 1191 /// True is the expression can be evaluated multiple times without causing |
1202 /// code execution. This is true for final fields. This can be true for local | 1192 /// code execution. This is true for final fields. This can be true for local |
1203 /// variables, if: | 1193 /// variables, if: |
(...skipping 10 matching lines...) Expand all Loading... | |
1214 return !_isPotentiallyMutated(e, context); | 1204 return !_isPotentiallyMutated(e, context); |
1215 } | 1205 } |
1216 } | 1206 } |
1217 } | 1207 } |
1218 return false; | 1208 return false; |
1219 } | 1209 } |
1220 | 1210 |
1221 @override | 1211 @override |
1222 visitParenthesizedExpression(ParenthesizedExpression node) => | 1212 visitParenthesizedExpression(ParenthesizedExpression node) => |
1223 // The printer handles precedence so we don't need to. | 1213 // The printer handles precedence so we don't need to. |
1224 node.expression.accept(this); | 1214 _visit(node.expression); |
1225 | 1215 |
1226 @override | 1216 @override |
1227 visitSimpleFormalParameter(SimpleFormalParameter node) => | 1217 visitSimpleFormalParameter(SimpleFormalParameter node) => |
1228 node.identifier.accept(this); | 1218 _visit(node.identifier); |
1229 | 1219 |
1230 @override | 1220 @override |
1231 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) => | 1221 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) => |
1232 node.identifier.accept(this); | 1222 _visit(node.identifier); |
1233 | 1223 |
1234 @override | 1224 @override |
1235 JS.This visitThisExpression(ThisExpression node) => new JS.This(); | 1225 JS.This visitThisExpression(ThisExpression node) => new JS.This(); |
1236 | 1226 |
1237 @override | 1227 @override |
1238 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); | 1228 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); |
1239 | 1229 |
1240 @override | 1230 @override |
1241 visitPrefixedIdentifier(PrefixedIdentifier node) { | 1231 visitPrefixedIdentifier(PrefixedIdentifier node) { |
1242 if (node.prefix.staticElement is PrefixElement) { | 1232 if (node.prefix.staticElement is PrefixElement) { |
1243 return node.identifier.accept(this); | 1233 return _visit(node.identifier); |
1244 } else { | 1234 } else { |
1245 return _visitGet(node.prefix, node.identifier); | 1235 return _visitGet(node.prefix, node.identifier); |
1246 } | 1236 } |
1247 } | 1237 } |
1248 | 1238 |
1249 @override | 1239 @override |
1250 visitPropertyAccess(PropertyAccess node) => | 1240 visitPropertyAccess(PropertyAccess node) => |
1251 _visitGet(_getTarget(node), node.propertyName); | 1241 _visitGet(_getTarget(node), node.propertyName); |
1252 | 1242 |
1253 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. | 1243 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. |
1254 _visitGet(Expression target, SimpleIdentifier name) { | 1244 _visitGet(Expression target, SimpleIdentifier name) { |
1255 if (rules.isDynamicTarget(target)) { | 1245 if (rules.isDynamicTarget(target)) { |
1256 return js.call( | 1246 return js.call( |
1257 'dart.dload(#, #)', [target.accept(this), js.string(name.name, "'")]); | 1247 'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]); |
1258 } else { | 1248 } else { |
1259 return js.call('#.#', [target.accept(this), name.name]); | 1249 return js.call('#.#', [_visit(target), name.name]); |
1260 } | 1250 } |
1261 } | 1251 } |
1262 | 1252 |
1263 @override | 1253 @override |
1264 visitIndexExpression(IndexExpression node) { | 1254 visitIndexExpression(IndexExpression node) { |
1265 var target = _getTarget(node); | 1255 var target = _getTarget(node); |
1266 var code; | 1256 var code; |
1267 if (rules.isDynamicTarget(target)) { | 1257 if (rules.isDynamicTarget(target)) { |
1268 code = 'dart.dindex(#, #)'; | 1258 code = 'dart.dindex(#, #)'; |
1269 } else { | 1259 } else { |
1270 code = '#.get(#)'; | 1260 code = '#.get(#)'; |
1271 } | 1261 } |
1272 return js.call(code, [target.accept(this), node.index.accept(this)]); | 1262 return js.call(code, [_visit(target), _visit(node.index)]); |
1273 } | 1263 } |
1274 | 1264 |
1275 /// Gets the target of a [PropertyAccess] or [IndexExpression]. | 1265 /// Gets the target of a [PropertyAccess] or [IndexExpression]. |
1276 /// Those two nodes are special because they're both allowed on left side of | 1266 /// Those two nodes are special because they're both allowed on left side of |
1277 /// an assignment expression and cascades. | 1267 /// an assignment expression and cascades. |
1278 Expression _getTarget(node) { | 1268 Expression _getTarget(node) { |
1279 assert(node is IndexExpression || node is PropertyAccess); | 1269 assert(node is IndexExpression || node is PropertyAccess); |
1280 return node.isCascaded ? _cascadeTarget : node.target; | 1270 return node.isCascaded ? _cascadeTarget : node.target; |
1281 } | 1271 } |
1282 | 1272 |
1283 @override | 1273 @override |
1284 visitConditionalExpression(ConditionalExpression node) { | 1274 visitConditionalExpression(ConditionalExpression node) { |
1285 return js.call('# ? # : #', [ | 1275 return js.call('# ? # : #', [ |
1286 node.condition.accept(this), | 1276 _visit(node.condition), |
1287 node.thenExpression.accept(this), | 1277 _visit(node.thenExpression), |
1288 node.elseExpression.accept(this) | 1278 _visit(node.elseExpression) |
1289 ]); | 1279 ]); |
1290 } | 1280 } |
1291 | 1281 |
1292 @override | 1282 @override |
1293 visitThrowExpression(ThrowExpression node) { | 1283 visitThrowExpression(ThrowExpression node) { |
1294 var expr = node.expression.accept(this); | 1284 var expr = _visit(node.expression); |
1295 if (node.parent is ExpressionStatement) { | 1285 if (node.parent is ExpressionStatement) { |
1296 return js.statement('throw #;', expr); | 1286 return js.statement('throw #;', expr); |
1297 } else { | 1287 } else { |
1298 return js.call('dart.throw_(#)', expr); | 1288 return js.call('dart.throw_(#)', expr); |
1299 } | 1289 } |
1300 } | 1290 } |
1301 | 1291 |
1302 @override | 1292 @override |
1303 JS.If visitIfStatement(IfStatement node) { | 1293 JS.If visitIfStatement(IfStatement node) { |
1304 return new JS.If(node.condition.accept(this), _visit(node.thenStatement), | 1294 return new JS.If(_visit(node.condition), _visit(node.thenStatement), |
1305 _visitOrEmpty(node.elseStatement)); | 1295 _visitOrEmpty(node.elseStatement)); |
1306 } | 1296 } |
1307 | 1297 |
1308 @override | 1298 @override |
1309 JS.For visitForStatement(ForStatement node) { | 1299 JS.For visitForStatement(ForStatement node) { |
1310 var init = _visit(node.initialization); | 1300 var init = _visit(node.initialization); |
1311 if (init == null) init = _visit(node.variables); | 1301 if (init == null) init = _visit(node.variables); |
1312 return new JS.For(init, _visit(node.condition), | 1302 return new JS.For(init, _visit(node.condition), |
1313 _visitListToBinary(node.updaters, ','), _visit(node.body)); | 1303 _visitListToBinary(node.updaters, ','), _visit(node.body)); |
1314 } | 1304 } |
1315 | 1305 |
1316 @override | 1306 @override |
1317 JS.While visitWhileStatement(WhileStatement node) { | 1307 JS.While visitWhileStatement(WhileStatement node) { |
1318 return new JS.While(node.condition.accept(this), node.body.accept(this)); | 1308 return new JS.While(_visit(node.condition), _visit(node.body)); |
1319 } | 1309 } |
1320 | 1310 |
1321 @override | 1311 @override |
1322 JS.Do visitDoStatement(DoStatement node) { | 1312 JS.Do visitDoStatement(DoStatement node) { |
1323 return new JS.Do(node.body.accept(this), node.condition.accept(this)); | 1313 return new JS.Do(_visit(node.body), _visit(node.condition)); |
1324 } | 1314 } |
1325 | 1315 |
1326 @override | 1316 @override |
1327 JS.ForOf visitForEachStatement(ForEachStatement node) { | 1317 JS.ForOf visitForEachStatement(ForEachStatement node) { |
1328 var init = _visit(node.identifier); | 1318 var init = _visit(node.identifier); |
1329 if (init == null) { | 1319 if (init == null) { |
1330 init = js.call('let #', node.loopVariable.identifier.name); | 1320 init = js.call('let #', node.loopVariable.identifier.name); |
1331 } | 1321 } |
1332 return new JS.ForOf( | 1322 return new JS.ForOf(init, _visit(node.iterable), _visit(node.body)); |
1333 init, node.iterable.accept(this), node.body.accept(this)); | |
1334 } | 1323 } |
1335 | 1324 |
1336 @override | 1325 @override |
1337 visitBreakStatement(BreakStatement node) { | 1326 visitBreakStatement(BreakStatement node) { |
1338 var label = node.label; | 1327 var label = node.label; |
1339 return new JS.Break(label != null ? label.name : null); | 1328 return new JS.Break(label != null ? label.name : null); |
1340 } | 1329 } |
1341 | 1330 |
1342 @override | 1331 @override |
1343 visitContinueStatement(ContinueStatement node) { | 1332 visitContinueStatement(ContinueStatement node) { |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1377 if (s.length == 0) return new JS.Block([]); | 1366 if (s.length == 0) return new JS.Block([]); |
1378 if (s.length == 1) return s[0]; | 1367 if (s.length == 1) return s[0]; |
1379 return new JS.Block(s); | 1368 return new JS.Block(s); |
1380 } | 1369 } |
1381 | 1370 |
1382 JS.Statement _visitCatchClause(CatchClause node, String varName) { | 1371 JS.Statement _visitCatchClause(CatchClause node, String varName) { |
1383 var body = []; | 1372 var body = []; |
1384 if (node.catchKeyword != null) { | 1373 if (node.catchKeyword != null) { |
1385 var name = node.exceptionParameter; | 1374 var name = node.exceptionParameter; |
1386 if (name != null && name.name != varName) { | 1375 if (name != null && name.name != varName) { |
1387 body.add(js.statement('let # = #;', [name.accept(this), varName])); | 1376 body.add(js.statement('let # = #;', [_visit(name), varName])); |
1388 } | 1377 } |
1389 if (node.stackTraceParameter != null) { | 1378 if (node.stackTraceParameter != null) { |
1390 var stackVar = node.stackTraceParameter.name; | 1379 var stackVar = node.stackTraceParameter.name; |
1391 body.add(js.statement( | 1380 body.add(js.statement( |
1392 'let # = dart.stackTrace(#);', [stackVar, name.accept(this)])); | 1381 'let # = dart.stackTrace(#);', [stackVar, _visit(name)])); |
1393 } | 1382 } |
1394 } | 1383 } |
1395 | 1384 |
1396 body.add(node.body.accept(this)); | 1385 body.add(_visit(node.body)); |
1397 | 1386 |
1398 if (node.exceptionType != null) { | 1387 if (node.exceptionType != null) { |
1399 return js.statement('if (dart.is(#, #)) #;', [ | 1388 return js.statement('if (dart.is(#, #)) #;', [ |
1400 varName, | 1389 varName, |
1401 _emitTypeName(node.exceptionType.type), | 1390 _emitTypeName(node.exceptionType.type), |
1402 _statement(body) | 1391 _statement(body) |
1403 ]); | 1392 ]); |
1404 } | 1393 } |
1405 return _statement(body); | 1394 return _statement(body); |
1406 } | 1395 } |
1407 | 1396 |
1408 @override | 1397 @override |
1409 JS.Case visitSwitchCase(SwitchCase node) { | 1398 JS.Case visitSwitchCase(SwitchCase node) { |
1410 var expr = node.expression.accept(this); | 1399 var expr = _visit(node.expression); |
1411 var body = _visitList(node.statements); | 1400 var body = _visitList(node.statements); |
1412 if (node.labels.isNotEmpty) { | 1401 if (node.labels.isNotEmpty) { |
1413 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); | 1402 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
1414 } | 1403 } |
1415 // TODO(jmesserly): make sure we are statically checking fall through | 1404 // TODO(jmesserly): make sure we are statically checking fall through |
1416 return new JS.Case(expr, new JS.Block(body)); | 1405 return new JS.Case(expr, new JS.Block(body)); |
1417 } | 1406 } |
1418 | 1407 |
1419 @override | 1408 @override |
1420 JS.Default visitSwitchDefault(SwitchDefault node) { | 1409 JS.Default visitSwitchDefault(SwitchDefault node) { |
1421 var body = _visitList(node.statements); | 1410 var body = _visitList(node.statements); |
1422 if (node.labels.isNotEmpty) { | 1411 if (node.labels.isNotEmpty) { |
1423 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); | 1412 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
1424 } | 1413 } |
1425 // TODO(jmesserly): make sure we are statically checking fall through | 1414 // TODO(jmesserly): make sure we are statically checking fall through |
1426 return new JS.Default(new JS.Block(body)); | 1415 return new JS.Default(new JS.Block(body)); |
1427 } | 1416 } |
1428 | 1417 |
1429 @override | 1418 @override |
1430 JS.Switch visitSwitchStatement(SwitchStatement node) => | 1419 JS.Switch visitSwitchStatement(SwitchStatement node) => |
1431 new JS.Switch(node.expression.accept(this), _visitList(node.members)); | 1420 new JS.Switch(_visit(node.expression), _visitList(node.members)); |
1432 | 1421 |
1433 @override | 1422 @override |
1434 JS.Statement visitLabeledStatement(LabeledStatement node) { | 1423 JS.Statement visitLabeledStatement(LabeledStatement node) { |
1435 var result = _visit(node.statement); | 1424 var result = _visit(node.statement); |
1436 for (var label in node.labels.reversed) { | 1425 for (var label in node.labels.reversed) { |
1437 result = new JS.LabeledStatement(label.label.name, result); | 1426 result = new JS.LabeledStatement(label.label.name, result); |
1438 } | 1427 } |
1439 return result; | 1428 return result; |
1440 } | 1429 } |
1441 | 1430 |
(...skipping 22 matching lines...) Expand all Loading... | |
1464 visitMapLiteral(MapLiteral node) { | 1453 visitMapLiteral(MapLiteral node) { |
1465 var entries = node.entries; | 1454 var entries = node.entries; |
1466 var mapArguments = null; | 1455 var mapArguments = null; |
1467 if (entries.isEmpty) return js.call('dart.map()'); | 1456 if (entries.isEmpty) return js.call('dart.map()'); |
1468 | 1457 |
1469 // Use JS object literal notation if possible, otherwise use an array. | 1458 // Use JS object literal notation if possible, otherwise use an array. |
1470 if (entries.every((e) => e.key is SimpleStringLiteral)) { | 1459 if (entries.every((e) => e.key is SimpleStringLiteral)) { |
1471 var props = []; | 1460 var props = []; |
1472 for (var e in entries) { | 1461 for (var e in entries) { |
1473 var key = (e.key as SimpleStringLiteral).value; | 1462 var key = (e.key as SimpleStringLiteral).value; |
1474 var value = e.value.accept(this); | 1463 var value = _visit(e.value); |
1475 props.add(new JS.Property(js.escapedString(key), value)); | 1464 props.add(new JS.Property(js.escapedString(key), value)); |
1476 } | 1465 } |
1477 mapArguments = new JS.ObjectInitializer(props); | 1466 mapArguments = new JS.ObjectInitializer(props); |
1478 } else { | 1467 } else { |
1479 var values = []; | 1468 var values = []; |
1480 for (var e in entries) { | 1469 for (var e in entries) { |
1481 values.add(e.key.accept(this)); | 1470 values.add(_visit(e.key)); |
1482 values.add(e.value.accept(this)); | 1471 values.add(_visit(e.value)); |
1483 } | 1472 } |
1484 mapArguments = new JS.ArrayInitializer(values); | 1473 mapArguments = new JS.ArrayInitializer(values); |
1485 } | 1474 } |
1486 return js.call('dart.map(#)', [mapArguments]); | 1475 return js.call('dart.map(#)', [mapArguments]); |
1487 } | 1476 } |
1488 | 1477 |
1489 @override | 1478 @override |
1490 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => | 1479 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
1491 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); | 1480 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
1492 | 1481 |
(...skipping 11 matching lines...) Expand all Loading... | |
1504 | 1493 |
1505 @override | 1494 @override |
1506 String visitInterpolationString(InterpolationString node) { | 1495 String visitInterpolationString(InterpolationString node) { |
1507 // TODO(jmesserly): this call adds quotes, and then we strip them off. | 1496 // TODO(jmesserly): this call adds quotes, and then we strip them off. |
1508 var str = js.escapedString(node.value, '`').value; | 1497 var str = js.escapedString(node.value, '`').value; |
1509 return str.substring(1, str.length - 1); | 1498 return str.substring(1, str.length - 1); |
1510 } | 1499 } |
1511 | 1500 |
1512 @override | 1501 @override |
1513 visitInterpolationExpression(InterpolationExpression node) => | 1502 visitInterpolationExpression(InterpolationExpression node) => |
1514 node.expression.accept(this); | 1503 _visit(node.expression); |
1515 | 1504 |
1516 @override | 1505 @override |
1517 visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value); | 1506 visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value); |
1518 | 1507 |
1519 @override | 1508 @override |
1520 JS.Statement visitDeclaration(Declaration node) => | 1509 JS.Statement visitDeclaration(Declaration node) => |
1521 js.comment('Unimplemented ${node.runtimeType}: $node'); | 1510 js.comment('Unimplemented ${node.runtimeType}: $node'); |
1522 | 1511 |
1523 @override | 1512 @override |
1524 JS.Statement visitStatement(Statement node) => | 1513 JS.Statement visitStatement(Statement node) => |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1563 | 1552 |
1564 /// Returns true if [element] is a getter in JS, therefore needs | 1553 /// Returns true if [element] is a getter in JS, therefore needs |
1565 /// `lib.topLevel` syntax instead of just `topLevel`. | 1554 /// `lib.topLevel` syntax instead of just `topLevel`. |
1566 bool _needsModuleGetter(Element element) { | 1555 bool _needsModuleGetter(Element element) { |
1567 if (element is PropertyAccessorElement) { | 1556 if (element is PropertyAccessorElement) { |
1568 element = (element as PropertyAccessorElement).variable; | 1557 element = (element as PropertyAccessorElement).variable; |
1569 } | 1558 } |
1570 return element is TopLevelVariableElement && !element.isConst; | 1559 return element is TopLevelVariableElement && !element.isConst; |
1571 } | 1560 } |
1572 | 1561 |
1573 _visit(AstNode node) => node != null ? node.accept(this) : null; | 1562 _visit(AstNode node) { |
1563 if (node == null) return null; | |
1564 var result = node.accept(this); | |
1565 if (result is JS.Node) result.sourceInformation = node; | |
1566 return result; | |
1567 } | |
1574 | 1568 |
1575 JS.Statement _visitOrEmpty(AstNode node) { | 1569 JS.Statement _visitOrEmpty(Statement node) { |
1576 if (node == null) return new JS.EmptyStatement(); | 1570 if (node == null) return new JS.EmptyStatement(); |
1577 return node.accept(this); | 1571 return _visit(node); |
1578 } | 1572 } |
1579 | 1573 |
1580 List _visitList(Iterable<AstNode> nodes) { | 1574 List _visitList(Iterable<AstNode> nodes) { |
1581 if (nodes == null) return null; | 1575 if (nodes == null) return null; |
1582 var result = []; | 1576 var result = []; |
1583 for (var node in nodes) result.add(node.accept(this)); | 1577 for (var node in nodes) result.add(_visit(node)); |
1584 return result; | 1578 return result; |
1585 } | 1579 } |
1586 | 1580 |
1587 /// Visits a list of expressions, creating a comma expression if needed in JS. | 1581 /// Visits a list of expressions, creating a comma expression if needed in JS. |
1588 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { | 1582 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { |
1589 if (nodes == null || nodes.isEmpty) return null; | 1583 if (nodes == null || nodes.isEmpty) return null; |
1590 | 1584 |
1591 JS.Expression result = null; | 1585 JS.Expression result = null; |
1592 for (var node in nodes) { | 1586 for (var node in nodes) { |
1593 var jsExpr = node.accept(this); | 1587 var jsExpr = _visit(node); |
1594 if (result == null) { | 1588 if (result == null) { |
1595 result = jsExpr; | 1589 result = jsExpr; |
1596 } else { | 1590 } else { |
1597 result = new JS.Binary(operator, result, jsExpr); | 1591 result = new JS.Binary(operator, result, jsExpr); |
1598 } | 1592 } |
1599 } | 1593 } |
1600 return result; | 1594 return result; |
1601 } | 1595 } |
1602 | 1596 |
1603 /// The following names are allowed for user-defined operators: | 1597 /// The following names are allowed for user-defined operators: |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1705 } | 1699 } |
1706 } | 1700 } |
1707 | 1701 |
1708 @override | 1702 @override |
1709 visitThisExpression(ThisExpression node) { | 1703 visitThisExpression(ThisExpression node) { |
1710 _bindThis = true; | 1704 _bindThis = true; |
1711 } | 1705 } |
1712 } | 1706 } |
1713 | 1707 |
1714 class JSGenerator extends CodeGenerator { | 1708 class JSGenerator extends CodeGenerator { |
1715 JSGenerator(String outDir, Uri root, TypeRules rules) | 1709 final JSCodeOptions options; |
1710 | |
1711 JSGenerator(String outDir, Uri root, TypeRules rules, this.options) | |
1716 : super(outDir, root, rules); | 1712 : super(outDir, root, rules); |
1717 | 1713 |
1718 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, | 1714 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, |
1719 CheckerReporter reporter) { | 1715 CheckerReporter reporter) { |
1720 JS.Block jsTree = | 1716 JS.Block jsTree = |
1721 new JSCodegenVisitor(info, rules).generateLibrary(units, reporter); | 1717 new JSCodegenVisitor(info, rules).generateLibrary(units, reporter); |
1722 | 1718 |
1723 var outputPath = path.join(outDir, jsOutputPath(info)); | 1719 var outputPath = path.join(outDir, jsOutputPath(info)); |
1724 new Directory(path.dirname(outputPath)).createSync(recursive: true); | 1720 new Directory(path.dirname(outputPath)).createSync(recursive: true); |
1725 | 1721 |
1726 var context = new JS.SimpleJavaScriptPrintingContext(); | 1722 if (options.emitSourceMaps) { |
1723 var outFilename = path.basename(outputPath); | |
1724 var printer = new srcmaps.Printer(outFilename); | |
1725 var context = | |
1726 new SourceMapPrintingContext(printer, path.dirname(outputPath)); | |
1727 _writeLibrary(context, jsTree); | |
1728 printer.add('//# sourceMappingURL=$outFilename.map'); | |
1729 // Write output file and source map | |
1730 new File(outputPath).writeAsStringSync(printer.text); | |
1731 new File('$outputPath.map').writeAsStringSync(printer.map); | |
1732 } else { | |
1733 var context = new JS.SimpleJavaScriptPrintingContext(); | |
1734 _writeLibrary(context, jsTree); | |
1735 // Write output file and source map | |
1736 new File(outputPath).writeAsStringSync(context.getText()); | |
1737 } | |
1738 } | |
1739 | |
1740 void _writeLibrary(JS.JavaScriptPrintingContext context, JS.Block jsTree) { | |
1727 var opts = | 1741 var opts = |
1728 new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); | 1742 new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); |
1729 var printer = new JS.Printer(opts, context); | 1743 new JS.Printer(opts, context).blockOutWithoutBraces(jsTree); |
1730 printer.blockOutWithoutBraces(jsTree); | |
1731 new File(outputPath).writeAsStringSync(context.getText()); | |
1732 } | 1744 } |
1733 } | 1745 } |
1734 | 1746 |
1747 /// This is a debugging helper to print a JS node. | |
1748 String debugJsNodeToString(JS.Node node) { | |
1749 var context = new JS.SimpleJavaScriptPrintingContext(); | |
1750 var opts = new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); | |
1751 new JS.Printer(opts, context).visit(node); | |
1752 // Write output file and source map | |
1753 return context.getText(); | |
1754 } | |
1755 | |
1735 /// Choose a canonical name from the library element. | 1756 /// Choose a canonical name from the library element. |
1736 /// This never uses the library's name (the identifier in the `library` | 1757 /// This never uses the library's name (the identifier in the `library` |
1737 /// declaration) as it doesn't have any meaningful rules enforced. | 1758 /// declaration) as it doesn't have any meaningful rules enforced. |
1738 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 1759 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
1739 | 1760 |
1740 /// Path to file that will be generated for [info]. | 1761 /// Path to file that will be generated for [info]. |
1741 // TODO(jmesserly): library directory should be relative to its package | 1762 // TODO(jmesserly): library directory should be relative to its package |
1742 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e: | 1763 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e: |
1743 // "ddc/src/codegen/js_codegen.js" under the output directory. | 1764 // "ddc/src/codegen/js_codegen.js" under the output directory. |
1744 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; | 1765 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; |
1766 | |
1767 class SourceMapPrintingContext extends JS.JavaScriptPrintingContext { | |
1768 final srcmaps.Printer printer; | |
1769 final String outputDir; | |
1770 | |
1771 CompilationUnit unit; | |
1772 Uri uri; | |
1773 | |
1774 SourceMapPrintingContext(this.printer, this.outputDir); | |
1775 | |
1776 void emit(String string) { | |
1777 printer.add(string); | |
1778 } | |
1779 | |
1780 void enterNode(JS.Node jsNode) { | |
1781 AstNode node = jsNode.sourceInformation; | |
1782 if (node is CompilationUnit) { | |
1783 unit = node; | |
1784 uri = _makeRelativeUri(unit.element.source.uri); | |
1785 return; | |
1786 } | |
1787 if (unit == null || node == null || node.offset == -1) return; | |
1788 | |
1789 var loc = _location(node.offset); | |
1790 var name = _getIdentifier(node); | |
1791 if (name != null) { | |
1792 // TODO(jmesserly): mark only uses the beginning of the span, but | |
1793 // we're required to pass this as a valid span. | |
1794 var end = _location(node.end); | |
1795 printer.mark(new SourceMapSpan(loc, end, name, isIdentifier: true)); | |
Siggi Cherem (dart-lang)
2015/02/27 23:26:23
btw - I just realized this, in reporter.dart we do
Jennifer Messerly
2015/02/27 23:30:45
yes probably :)
opened https://github.com/dart-lan
| |
1796 } else { | |
1797 printer.mark(loc); | |
1798 } | |
1799 } | |
1800 | |
1801 SourceLocation _location(int offset) { | |
1802 var lineInfo = unit.lineInfo.getLocation(offset); | |
1803 return new SourceLocation(offset, | |
1804 sourceUrl: uri, | |
1805 line: lineInfo.lineNumber - 1, | |
1806 column: lineInfo.columnNumber - 1); | |
1807 } | |
1808 | |
1809 Uri _makeRelativeUri(Uri src) { | |
1810 return new Uri(path: path.relative(src.path, from: outputDir)); | |
1811 } | |
1812 | |
1813 void exitNode(JS.Node jsNode) { | |
1814 AstNode node = jsNode.sourceInformation; | |
1815 if (node is CompilationUnit) { | |
1816 unit = null; | |
1817 uri = null; | |
1818 return; | |
1819 } | |
1820 if (unit == null || node == null || node.offset == -1) return; | |
1821 | |
1822 // TODO(jmesserly): in many cases marking the end will be unncessary. | |
1823 printer.mark(_location(node.end)); | |
1824 } | |
1825 | |
1826 String _getIdentifier(AstNode node) { | |
1827 if (node is SimpleIdentifier) return node.name; | |
1828 return null; | |
1829 } | |
1830 } | |
OLD | NEW |