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

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

Issue 1083763003: refactor emitMemberName to be used more consistently (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 8 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
« no previous file with comments | « lib/runtime/dart_runtime.js ('k') | lib/src/utils.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 dev_compiler.src.codegen.js_codegen; 5 library dev_compiler.src.codegen.js_codegen;
6 6
7 import 'dart:collection' show HashSet, HashMap; 7 import 'dart:collection' show HashSet, HashMap;
8 import 'dart:io' show Directory, File; 8 import 'dart:io' show Directory, File;
9 9
10 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; 10 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
11 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; 11 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator;
12 import 'package:analyzer/src/generated/constant.dart'; 12 import 'package:analyzer/src/generated/constant.dart';
13 import 'package:analyzer/src/generated/element.dart'; 13 import 'package:analyzer/src/generated/element.dart';
14 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
14 import 'package:analyzer/src/generated/scanner.dart' 15 import 'package:analyzer/src/generated/scanner.dart'
15 show StringToken, Token, TokenType; 16 show StringToken, Token, TokenType;
16 import 'package:source_maps/source_maps.dart' as srcmaps show Printer; 17 import 'package:source_maps/source_maps.dart' as srcmaps show Printer;
17 import 'package:source_maps/source_maps.dart' show SourceMapSpan; 18 import 'package:source_maps/source_maps.dart' show SourceMapSpan;
18 import 'package:source_span/source_span.dart' show SourceLocation; 19 import 'package:source_span/source_span.dart' show SourceLocation;
19 import 'package:path/path.dart' as path; 20 import 'package:path/path.dart' as path;
20 21
21 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; 22 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder;
22 23
23 // TODO(jmesserly): import from its own package 24 // TODO(jmesserly): import from its own package
(...skipping 16 matching lines...) Expand all
40 orElse: () => null); 41 orElse: () => null);
41 42
42 Annotation _getJsNameAnnotation(AnnotatedNode node) => 43 Annotation _getJsNameAnnotation(AnnotatedNode node) =>
43 _getAnnotation(node, "JsName"); 44 _getAnnotation(node, "JsName");
44 45
45 // TODO(jacobr): we would like to do something like the following 46 // TODO(jacobr): we would like to do something like the following
46 // but we don't have summary support yet. 47 // but we don't have summary support yet.
47 // bool _supportJsExtensionMethod(AnnotatedNode node) => 48 // bool _supportJsExtensionMethod(AnnotatedNode node) =>
48 // _getAnnotation(node, "SupportJsExtensionMethod") != null; 49 // _getAnnotation(node, "SupportJsExtensionMethod") != null;
49 50
51 // Various dynamic helpers we call.
52 // If renaming these, make sure to check other places like the
53 // dart_runtime.js file and comments.
54 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can
55 // import and generate calls to, rather than dart_runtime.js
56 const DPUT = 'dput';
57 const DLOAD = 'dload';
58 const DINDEX = 'dindex';
59 const DSETINDEX = 'dsetindex';
60 const DCALL = 'dcall';
61 const DSEND = 'dsend';
62
50 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { 63 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
51 final LibraryInfo libraryInfo; 64 final LibraryInfo libraryInfo;
52 final TypeRules rules; 65 final TypeRules rules;
53 66
67 /// The global extension method table.
68 final HashMap<String, List<InterfaceType>> _extensionMethods;
69
54 /// The variable for the target of the current `..` cascade expression. 70 /// The variable for the target of the current `..` cascade expression.
55 SimpleIdentifier _cascadeTarget; 71 SimpleIdentifier _cascadeTarget;
56 /// The variable for the current catch clause 72 /// The variable for the current catch clause
57 SimpleIdentifier _catchParameter; 73 SimpleIdentifier _catchParameter;
58 74
59 ClassDeclaration currentClass; 75 ClassDeclaration currentClass;
60 ConstantEvaluator _constEvaluator; 76 ConstantEvaluator _constEvaluator;
61 77
62 final _exports = new Set<String>(); 78 final _exports = new Set<String>();
63 final _lazyFields = <VariableDeclaration>[]; 79 final _lazyFields = <VariableDeclaration>[];
64 final _properties = <FunctionDeclaration>[]; 80 final _properties = <FunctionDeclaration>[];
65 final _privateNames = new HashSet<String>(); 81 final _privateNames = new HashSet<String>();
66 final _pendingPrivateNames = <String>[]; 82 final _pendingPrivateNames = <String>[];
67 final _extensionMethodNames = new HashSet<String>(); 83 final _extensionMethodNames = new HashSet<String>();
68 final _pendingExtensionMethodNames = <String>[]; 84 final _pendingExtensionMethodNames = <String>[];
69 85
70 InterfaceType _fillDynamicTypeArgs(InterfaceType t) { 86 /// The name for the library's exports inside itself.
71 var d = rules.provider.dynamicType; 87 /// This much be a constant because we interpolate it into template strings,
72 return t.substitute4(new List.filled(t.typeArguments.length, d)); 88 /// and otherwise it would break caching for them.
73 } 89 /// `exports` was chosen as the most similar to ES module patterns.
74 // TODO(jacobr): determine the the set of types with extension methods from 90 final JSTemporary _exportsVar = new JSTemporary('exports');
75 // the annotations rather than hard coding the list once the analyzer 91 final JSTemporary _namedArgTemp = new JSTemporary('opts');
76 // supports summaries.
77 List<InterfaceType> _jsExtensionMethodTypes;
78 List<InterfaceType> get jsExtensionMethodTypes {
79 if (_jsExtensionMethodTypes != null) return _jsExtensionMethodTypes;
80 _jsExtensionMethodTypes = <InterfaceType>[
81 rules.provider.listType,
82 rules.provider.iterableType
83 ].map(_fillDynamicTypeArgs).toList();
84 return _jsExtensionMethodTypes;
85 }
86
87 Map<ClassElement, Set<String>> _extensionMethods;
88
89 Map<ClassElement, Set<String>> get extensionMethods {
90 if (_extensionMethods != null) return _extensionMethods;
91 _extensionMethods = new HashMap<ClassElement, HashSet<String>>();
92
93 for (var type in jsExtensionMethodTypes) {
94 var names = new HashSet<String>();
95 var e = type.element;
96 names.addAll(e.methods.map((m) => m.name));
97 names.addAll(e.accessors.map((m) => m.name));
98 _extensionMethods[e] = names;
99 }
100 return _extensionMethods;
101 }
102 92
103 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or 93 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or
104 /// [ClassTypeAlias]. 94 /// [ClassTypeAlias].
105 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); 95 final _pendingClasses = new HashMap<Element, CompilationUnitMember>();
106 96
107 /// Memoized results of [_lazyClass]. 97 /// Memoized results of [_lazyClass].
108 final _lazyClassMemo = new HashMap<Element, bool>(); 98 final _lazyClassMemo = new HashMap<Element, bool>();
109 99
110 /// Memoized results of [_inLibraryCycle]. 100 /// Memoized results of [_inLibraryCycle].
111 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); 101 final _libraryCycleMemo = new HashMap<LibraryElement, bool>();
112 102
113 JSCodegenVisitor(this.libraryInfo, this.rules); 103 JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods);
114 104
115 LibraryElement get currentLibrary => libraryInfo.library; 105 LibraryElement get currentLibrary => libraryInfo.library;
116 106 TypeProvider get types => rules.provider;
117 /// The name for the library's exports inside itself.
118 /// This much be a constant because we interpolate it into template strings,
119 /// and otherwise it would break caching for them.
120 /// `exports` was chosen as the most similar to ES module patterns.
121 final JSTemporary _exportsVar = new JSTemporary('exports');
122 final JSTemporary _namedArgTemp = new JSTemporary('opts');
123 107
124 JS.Program emitLibrary(LibraryUnit library) { 108 JS.Program emitLibrary(LibraryUnit library) {
125 var jsDefaultValue = '{}'; 109 var jsDefaultValue = '{}';
126 var unit = library.library; 110 var unit = library.library;
127 if (unit.directives.isNotEmpty) { 111 if (unit.directives.isNotEmpty) {
128 var annotation = _getJsNameAnnotation(unit.directives.first); 112 var annotation = _getJsNameAnnotation(unit.directives.first);
129 if (annotation != null) { 113 if (annotation != null) {
130 var arguments = annotation.arguments.arguments; 114 var arguments = annotation.arguments.arguments;
131 if (!arguments.isEmpty) { 115 if (!arguments.isEmpty) {
132 var namedExpression = arguments.first as NamedExpression; 116 var namedExpression = arguments.first as NamedExpression;
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
187 String get _SYMBOL { 171 String get _SYMBOL {
188 var name = currentLibrary.name; 172 var name = currentLibrary.name;
189 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; 173 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol';
190 return 'Symbol'; 174 return 'Symbol';
191 } 175 }
192 176
193 @override 177 @override
194 JS.Statement visitCompilationUnit(CompilationUnit node) { 178 JS.Statement visitCompilationUnit(CompilationUnit node) {
195 var source = node.element.source; 179 var source = node.element.source;
196 180
197 _constEvaluator = new ConstantEvaluator(source, rules.provider); 181 _constEvaluator = new ConstantEvaluator(source, types);
198 182
199 // TODO(jmesserly): scriptTag, directives. 183 // TODO(jmesserly): scriptTag, directives.
200 var body = <JS.Statement>[]; 184 var body = <JS.Statement>[];
201 for (var child in node.declarations) { 185 for (var child in node.declarations) {
202 // Attempt to group adjacent fields/properties. 186 // Attempt to group adjacent fields/properties.
203 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); 187 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body);
204 if (child is! FunctionDeclaration) _flushLibraryProperties(body); 188 if (child is! FunctionDeclaration) _flushLibraryProperties(body);
205 189
206 var code = _visit(child); 190 var code = _visit(child);
207 191
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
405 // The base class and all mixins must be declared before this class. 389 // The base class and all mixins must be declared before this class.
406 if (_lazyClass(type)) { 390 if (_lazyClass(type)) {
407 // TODO(jmesserly): the lazy class def is a simple solution for now. 391 // TODO(jmesserly): the lazy class def is a simple solution for now.
408 // We may want to consider other options in the future. 392 // We may want to consider other options in the future.
409 393
410 if (genericDef != null) { 394 if (genericDef != null) {
411 return js.statement( 395 return js.statement(
412 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [ 396 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [
413 genericDef, 397 genericDef,
414 _exportsVar, 398 _exportsVar,
415 js.string(name, "'"), 399 _propertyName(name),
416 genericName 400 genericName
417 ]); 401 ]);
418 } 402 }
419 403
420 return js.statement( 404 return js.statement(
421 'dart.defineLazyClass(#, { get #() { #; return #; } });', [ 405 'dart.defineLazyClass(#, { get #() { #; return #; } });', [
422 _exportsVar, 406 _exportsVar,
423 _propertyName(name), 407 _propertyName(name),
424 body, 408 body,
425 name 409 name
(...skipping 10 matching lines...) Expand all
436 420
437 // If we're not lazy, we still need to ensure our dependencies are 421 // If we're not lazy, we still need to ensure our dependencies are
438 // generated first. 422 // generated first.
439 var classDefs = <JS.Statement>[]; 423 var classDefs = <JS.Statement>[];
440 if (type is InterfaceType) { 424 if (type is InterfaceType) {
441 _emitClassIfNeeded(classDefs, type.superclass); 425 _emitClassIfNeeded(classDefs, type.superclass);
442 for (var m in type.element.mixins) { 426 for (var m in type.element.mixins) {
443 _emitClassIfNeeded(classDefs, m); 427 _emitClassIfNeeded(classDefs, m);
444 } 428 }
445 } else if (type is FunctionType) { 429 } else if (type is FunctionType) {
446 _emitClassIfNeeded(classDefs, rules.provider.functionType); 430 _emitClassIfNeeded(classDefs, types.functionType);
447 } 431 }
448 classDefs.add(body); 432 classDefs.add(body);
449 return _statement(classDefs); 433 return _statement(classDefs);
450 } 434 }
451 435
452 void _emitClassIfNeeded(List<JS.Statement> defs, DartType base) { 436 void _emitClassIfNeeded(List<JS.Statement> defs, DartType base) {
453 // We can only emit classes from this library. 437 // We can only emit classes from this library.
454 if (base.element.library != currentLibrary) return; 438 if (base.element.library != currentLibrary) return;
455 439
456 var baseNode = _pendingClasses[base.element]; 440 var baseNode = _pendingClasses[base.element];
457 if (baseNode != null) defs.add(visitClassDeclaration(baseNode)); 441 if (baseNode != null) defs.add(visitClassDeclaration(baseNode));
458 } 442 }
459 443
460 /// Returns true if the supertype or mixins aren't loaded. 444 /// Returns true if the supertype or mixins aren't loaded.
461 /// If that is the case, we'll emit a lazy class definition. 445 /// If that is the case, we'll emit a lazy class definition.
462 bool _lazyClass(DartType type) { 446 bool _lazyClass(DartType type) {
463 if (type.isObject) return false; 447 if (type.isObject) return false;
464 448
465 // Use the element as the key, as those are unique whereas generic types 449 // Use the element as the key, as those are unique whereas generic types
466 // can have their arguments substituted. 450 // can have their arguments substituted.
467 assert(type.element.library == currentLibrary); 451 assert(type.element.library == currentLibrary);
468 var result = _lazyClassMemo[type.element]; 452 var result = _lazyClassMemo[type.element];
469 if (result != null) return result; 453 if (result != null) return result;
470 454
471 if (type is InterfaceType) { 455 if (type is InterfaceType) {
472 result = _typeMightNotBeLoaded(type.superclass) || 456 result = _typeMightNotBeLoaded(type.superclass) ||
473 type.mixins.any(_typeMightNotBeLoaded); 457 type.mixins.any(_typeMightNotBeLoaded);
474 } else if (type is FunctionType) { 458 } else if (type is FunctionType) {
475 result = _typeMightNotBeLoaded(rules.provider.functionType); 459 result = _typeMightNotBeLoaded(types.functionType);
476 } 460 }
477 return _lazyClassMemo[type.element] = result; 461 return _lazyClassMemo[type.element] = result;
478 } 462 }
479 463
480 /// Curated order to minimize lazy classes needed by dart:core and its 464 /// Curated order to minimize lazy classes needed by dart:core and its
481 /// transitive SDK imports. 465 /// transitive SDK imports.
482 static const CORELIB_ORDER = const [ 466 static const CORELIB_ORDER = const [
483 'dart.core', 467 'dart.core',
484 'dart.collection', 468 'dart.collection',
485 'dart._internal' 469 'dart._internal'
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
561 ]); 545 ]);
562 } 546 }
563 547
564 JS.Expression _classHeritage(ClassDeclaration node) { 548 JS.Expression _classHeritage(ClassDeclaration node) {
565 if (node.element.type.isObject) return null; 549 if (node.element.type.isObject) return null;
566 550
567 JS.Expression heritage = null; 551 JS.Expression heritage = null;
568 if (node.extendsClause != null) { 552 if (node.extendsClause != null) {
569 heritage = _visit(node.extendsClause.superclass); 553 heritage = _visit(node.extendsClause.superclass);
570 } else { 554 } else {
571 heritage = _emitTypeName(rules.provider.objectType); 555 heritage = _emitTypeName(types.objectType);
572 } 556 }
573 if (node.withClause != null) { 557 if (node.withClause != null) {
574 var mixins = _visitList(node.withClause.mixinTypes); 558 var mixins = _visitList(node.withClause.mixinTypes);
575 mixins.insert(0, heritage); 559 mixins.insert(0, heritage);
576 heritage = js.call('dart.mixin(#)', [mixins]); 560 heritage = js.call('dart.mixin(#)', [mixins]);
577 } 561 }
578 return heritage; 562 return heritage;
579 } 563 }
580 564
581 /// Emit class members that can be generated as methods.
582 /// Anything not handled here will be addressed in [_finishClassMembers].
583 Iterable<InterfaceType> getMatchingExtensionMethodTypes(InterfaceType type) =>
584 jsExtensionMethodTypes.where((t) => rules.isSubTypeOf(type, t));
585
586 LibraryElement getExtensionLibrary(
587 Iterable<InterfaceType> extensionTypes, String name) {
588 var library = null;
589 for (var type in extensionTypes) {
590 var element = type.element;
591 if (extensionMethods[element].contains(name)) {
592 assert(library == null || library == element.library);
593 library = element.library;
594 }
595 }
596 return library;
597 }
598
599 JS.Expression nameIfExtension(Expression target, String name) {
600 var targetType = rules.getStaticType(target);
601 if (targetType is! InterfaceType) return null;
602 var extensionLibrary =
603 getExtensionLibrary(getMatchingExtensionMethodTypes(targetType), name);
604 if (extensionLibrary == null) return null;
605 return js.call('#.#', [
606 _libraryName(extensionLibrary),
607 _emitExtensionMethodName(name)
608 ]);
609 }
610
611 List<JS.Method> _emitClassMethods(ClassDeclaration node, 565 List<JS.Method> _emitClassMethods(ClassDeclaration node,
612 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { 566 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
613 var element = node.element; 567 var element = node.element;
614 var isObject = element.type.isObject; 568 var type = element.type;
569 var isObject = type.isObject;
615 var name = node.name.name; 570 var name = node.name.name;
616 571
617 var jsMethods = <JS.Method>[]; 572 var jsMethods = <JS.Method>[];
618 // Iff no constructor is specified for a class C, it implicitly has a 573 // Iff no constructor is specified for a class C, it implicitly has a
619 // default constructor `C() : super() {}`, unless C is class Object. 574 // default constructor `C() : super() {}`, unless C is class Object.
620 if (ctors.isEmpty && !isObject) { 575 if (ctors.isEmpty && !isObject) {
621 jsMethods.add(_emitImplicitConstructor(node, name, fields)); 576 jsMethods.add(_emitImplicitConstructor(node, name, fields));
622 } 577 }
623 var extensionTypes = getMatchingExtensionMethodTypes(element.type);
624 for (var member in node.members) { 578 for (var member in node.members) {
625 if (member is ConstructorDeclaration) { 579 if (member is ConstructorDeclaration) {
626 jsMethods.add(_emitConstructor(member, name, fields, isObject)); 580 jsMethods.add(_emitConstructor(member, name, fields, isObject));
627 } else if (member is MethodDeclaration) { 581 } else if (member is MethodDeclaration) {
628 jsMethods.add(_emitMethodDeclaration(member, extensionTypes)); 582 jsMethods.add(_emitMethodDeclaration(type, member));
629 } 583 }
630 } 584 }
631 585
632 // Support for adapting dart:core Iterator/Iterable to ES6 versions. 586 // Support for adapting dart:core Iterator/Iterable to ES6 versions.
633 // This lets them use for-of loops transparently. 587 // This lets them use for-of loops transparently.
634 // https://github.com/lukehoban/es6features#iterators--forof 588 // https://github.com/lukehoban/es6features#iterators--forof
635 if (element.library.isDartCore && element.name == 'Iterable') { 589 if (element.library.isDartCore && element.name == 'Iterable') {
636 JS.Fun body = js.call('''function() { 590 JS.Fun body = js.call('''function() {
637 var iterator = this.iterator; 591 var iterator = this.iterator;
638 return { 592 return {
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
725 node.name == null) { 679 node.name == null) {
726 // Implements Dart constructor behavior. Because of V8 `super` 680 // Implements Dart constructor behavior. Because of V8 `super`
727 // [constructor restrictions] 681 // [constructor restrictions]
728 // (https://code.google.com/p/v8/issues/detail?id=3330#c65) 682 // (https://code.google.com/p/v8/issues/detail?id=3330#c65)
729 // we cannot currently emit actual ES6 constructors with super calls. 683 // we cannot currently emit actual ES6 constructors with super calls.
730 // Instead we use the same trick as named constructors, and do them as 684 // Instead we use the same trick as named constructors, and do them as
731 // instance methods that perform initialization. 685 // instance methods that perform initialization.
732 // TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8 686 // TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8
733 // settles. See <https://github.com/dart-lang/dev_compiler/issues/51>. 687 // settles. See <https://github.com/dart-lang/dev_compiler/issues/51>.
734 // Performance of this pattern is likely to be bad. 688 // Performance of this pattern is likely to be bad.
735 name = js.string('constructor', "'"); 689 name = _propertyName('constructor');
736 // Mark the parameter as no-rename. 690 // Mark the parameter as no-rename.
737 var args = new JS.Identifier('arguments', allowRename: false); 691 var args = new JS.Identifier('arguments', allowRename: false);
738 body = js.statement('''{ 692 body = js.statement('''{
739 // Get the class name for this instance. 693 // Get the class name for this instance.
740 let name = this.constructor.name; 694 let name = this.constructor.name;
741 // Call the default constructor. 695 // Call the default constructor.
742 let init = this[name]; 696 let init = this[name];
743 let result = void 0; 697 let result = void 0;
744 if (init) result = init.apply(this, #); 698 if (init) result = init.apply(this, #);
745 return result === void 0 ? this : result; 699 return result === void 0 ? this : result;
746 }''', args); 700 }''', args);
747 } else { 701 } else {
748 body = _emitConstructorBody(node, fields); 702 body = _emitConstructorBody(node, fields);
749 } 703 }
750 704
751 // We generate constructors as initializer methods in the class; 705 // We generate constructors as initializer methods in the class;
752 // this allows use of `super` for instance methods/properties. 706 // this allows use of `super` for instance methods/properties.
753 // It also avoids V8 restrictions on `super` in default constructors. 707 // It also avoids V8 restrictions on `super` in default constructors.
754 return new JS.Method(name, new JS.Fun(_visit(node.parameters), body)) 708 return new JS.Method(name, new JS.Fun(_visit(node.parameters), body))
755 ..sourceInformation = node; 709 ..sourceInformation = node;
756 } 710 }
757 711
758 JS.Expression _constructorName(String className, SimpleIdentifier name) { 712 JS.Expression _constructorName(String className, SimpleIdentifier name) {
759 if (name == null) return js.string(className, "'"); 713 if (name == null) return _propertyName(className);
760 return _emitMemberName(name.name, isStatic: true); 714 return _emitMemberName(name.name, isStatic: true);
761 } 715 }
762 716
763 JS.Block _emitConstructorBody( 717 JS.Block _emitConstructorBody(
764 ConstructorDeclaration node, List<FieldDeclaration> fields) { 718 ConstructorDeclaration node, List<FieldDeclaration> fields) {
765 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; 719 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
766 if (node.redirectedConstructor != null) { 720 if (node.redirectedConstructor != null) {
767 return js.statement('{ return new #(#); }', [ 721 return js.statement('{ return new #(#); }', [
768 _visit(node.redirectedConstructor), 722 _visit(node.redirectedConstructor),
769 _visit(node.parameters) 723 _visit(node.parameters)
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
842 /// 1. field declaration initializer if non-const, 796 /// 1. field declaration initializer if non-const,
843 /// 2. field initializing parameters, 797 /// 2. field initializing parameters,
844 /// 3. constructor field initializers, 798 /// 3. constructor field initializers,
845 /// 4. initialize fields not covered in 1-3 799 /// 4. initialize fields not covered in 1-3
846 JS.Statement _initializeFields(List<FieldDeclaration> fields, 800 JS.Statement _initializeFields(List<FieldDeclaration> fields,
847 [FormalParameterList parameters, 801 [FormalParameterList parameters,
848 NodeList<ConstructorInitializer> initializers]) { 802 NodeList<ConstructorInitializer> initializers]) {
849 var body = <JS.Statement>[]; 803 var body = <JS.Statement>[];
850 804
851 // Run field initializers if they can have side-effects. 805 // Run field initializers if they can have side-effects.
852 var unsetFields = new Map<String, VariableDeclaration>(); 806 var unsetFields = new Map<FieldElement, VariableDeclaration>();
853 for (var declaration in fields) { 807 for (var declaration in fields) {
854 for (var field in declaration.fields.variables) { 808 for (var field in declaration.fields.variables) {
855 if (_isFieldInitConstant(field)) { 809 if (_isFieldInitConstant(field)) {
856 unsetFields[field.name.name] = field; 810 unsetFields[field.element] = field;
857 } else { 811 } else {
858 body.add(js.statement( 812 body.add(js.statement(
859 '# = #;', [_visit(field.name), _visitInitializer(field)])); 813 '# = #;', [_visit(field.name), _visitInitializer(field)]));
860 } 814 }
861 } 815 }
862 } 816 }
863 817
864 // Initialize fields from `this.fieldName` parameters. 818 // Initialize fields from `this.fieldName` parameters.
865 if (parameters != null) { 819 if (parameters != null) {
866 for (var p in parameters.parameters) { 820 for (var p in parameters.parameters) {
867 if (p is DefaultFormalParameter) p = p.parameter; 821 if (p is DefaultFormalParameter) p = p.parameter;
868 if (p is FieldFormalParameter) { 822 if (p is FieldFormalParameter) {
869 var name = p.identifier.name; 823 var field = (p.element as FieldFormalParameterElement).field;
870 body.add( 824 // Use the getter to initialize the field. This is a bit strange, but
871 js.statement('this.# = #;', [_emitMemberName(name), _visit(p)])); 825 // final fields don't have a setter element that we could use instead.
872 unsetFields.remove(name); 826
827 var memberName =
828 _emitMemberName(field.name, type: field.enclosingElement.type);
829 body.add(js.statement('this.# = #;', [memberName, _visit(p)]));
830 unsetFields.remove(field);
873 } 831 }
874 } 832 }
875 } 833 }
876 834
877 // Run constructor field initializers such as `: foo = bar.baz` 835 // Run constructor field initializers such as `: foo = bar.baz`
878 if (initializers != null) { 836 if (initializers != null) {
879 for (var init in initializers) { 837 for (var init in initializers) {
880 if (init is ConstructorFieldInitializer) { 838 if (init is ConstructorFieldInitializer) {
881 body.add(js.statement( 839 body.add(js.statement(
882 '# = #;', [_visit(init.fieldName), _visit(init.expression)])); 840 '# = #;', [_visit(init.fieldName), _visit(init.expression)]));
883 unsetFields.remove(init.fieldName.name); 841 unsetFields.remove(init.fieldName.staticElement);
884 } 842 }
885 } 843 }
886 } 844 }
887 845
888 // Initialize all remaining fields 846 // Initialize all remaining fields
889 unsetFields.forEach((name, field) { 847 unsetFields.forEach((field, fieldNode) {
890 JS.Expression value; 848 JS.Expression value;
891 if (field.initializer != null) { 849 if (fieldNode.initializer != null) {
892 value = _visit(field.initializer); 850 value = _visit(fieldNode.initializer);
893 } else { 851 } else {
894 var type = rules.elementType(field.element); 852 var type = rules.elementType(field);
853 value = new JS.LiteralNull();
895 if (rules.maybeNonNullableType(type)) { 854 if (rules.maybeNonNullableType(type)) {
896 value = js.call('dart.as(null, #)', _emitTypeName(type)); 855 value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]);
897 } else {
898 value = new JS.LiteralNull();
899 } 856 }
900 } 857 }
901 body.add(js.statement('this.# = #;', [_emitMemberName(name), value])); 858 var memberName =
859 _emitMemberName(field.name, type: field.enclosingElement.type);
860 body.add(js.statement('this.# = #;', [memberName, value]));
902 }); 861 });
903 862
904 return _statement(body); 863 return _statement(body);
905 } 864 }
906 865
907 FormalParameterList _parametersOf(node) { 866 FormalParameterList _parametersOf(node) {
908 // Note: ConstructorDeclaration is intentionally skipped here so we can 867 // Note: ConstructorDeclaration is intentionally skipped here so we can
909 // emit the argument initializers in a different place. 868 // emit the argument initializers in a different place.
910 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we 869 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we
911 // could handle argument initializers more consistently in a separate 870 // could handle argument initializers more consistently in a separate
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
953 } 912 }
954 913
955 JS.Expression _defaultParamValue(FormalParameter param) { 914 JS.Expression _defaultParamValue(FormalParameter param) {
956 if (param is DefaultFormalParameter && param.defaultValue != null) { 915 if (param is DefaultFormalParameter && param.defaultValue != null) {
957 return _visit(param.defaultValue); 916 return _visit(param.defaultValue);
958 } else { 917 } else {
959 return new JS.LiteralNull(); 918 return new JS.LiteralNull();
960 } 919 }
961 } 920 }
962 921
963 JS.Method _emitMethodDeclaration( 922 JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
964 MethodDeclaration node, Iterable<InterfaceType> extensionTypes) {
965 if (node.isAbstract || _externalOrNative(node)) { 923 if (node.isAbstract || _externalOrNative(node)) {
966 return null; 924 return null;
967 } 925 }
968 926
969 var params = _visit(node.parameters); 927 var params = _visit(node.parameters);
970 if (params == null) params = []; 928 if (params == null) params = [];
971 929
972 var memberName; 930 var memberName = _emitMemberName(node.name.name,
973 var extensionLibrary; 931 type: type, unary: params.isEmpty, isStatic: node.isStatic);
974
975 if (!node.isStatic) {
976 extensionLibrary = getExtensionLibrary(extensionTypes, node.name.name);
977 }
978
979 if (extensionLibrary != null) {
980 var extensionMethodName = _extensionMethodName(node.name.name);
981 if (extensionLibrary == libraryInfo.library.library) {
982 // TODO(jacobr): need to do a better job ensuring that extension method
983 // name symbols do not conflict with other symbols before we can let
984 // user defined libraries define extension methods.
985 if (_extensionMethodNames.add(extensionMethodName)) {
986 _pendingExtensionMethodNames.add(extensionMethodName);
987 _addExport(extensionMethodName);
988 }
989 }
990 memberName = js.call('#.#', [
991 _libraryName(extensionLibrary),
992 js.string(extensionMethodName, "'")
993 ]);
994 } else {
995 memberName = _emitMemberName(node.name.name, isStatic: node.isStatic);
996 }
997 return new JS.Method(memberName, new JS.Fun(params, _visit(node.body)), 932 return new JS.Method(memberName, new JS.Fun(params, _visit(node.body)),
998 isGetter: node.isGetter, 933 isGetter: node.isGetter,
999 isSetter: node.isSetter, 934 isSetter: node.isSetter,
1000 isStatic: node.isStatic); 935 isStatic: node.isStatic);
1001 } 936 }
1002 937
1003 @override 938 @override
1004 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { 939 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) {
1005 assert(node.parent is CompilationUnit); 940 assert(node.parent is CompilationUnit);
1006 941
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
1071 /// Writes a simple identifier. This can handle implicit `this` as well as 1006 /// Writes a simple identifier. This can handle implicit `this` as well as
1072 /// going through the qualified library name if necessary. 1007 /// going through the qualified library name if necessary.
1073 @override 1008 @override
1074 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { 1009 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
1075 var e = node.staticElement; 1010 var e = node.staticElement;
1076 if (e == null) { 1011 if (e == null) {
1077 return js.commentExpression( 1012 return js.commentExpression(
1078 'Unimplemented unknown name', new JS.Identifier(node.name)); 1013 'Unimplemented unknown name', new JS.Identifier(node.name));
1079 } 1014 }
1080 1015
1081 var name = node.name;
1082 var variable = e is PropertyAccessorElement ? e.variable : e; 1016 var variable = e is PropertyAccessorElement ? e.variable : e;
1017 var name = variable.name;
1083 1018
1084 // library member 1019 // library member
1085 if (e.enclosingElement is CompilationUnitElement && 1020 if (e.enclosingElement is CompilationUnitElement &&
1086 (e.library != libraryInfo.library || 1021 (e.library != libraryInfo.library ||
1087 variable is TopLevelVariableElement && !variable.isConst)) { 1022 variable is TopLevelVariableElement && !variable.isConst)) {
1088 return js.call('#.#', [_libraryName(e.library), name]); 1023 return js.call('#.#', [_libraryName(e.library), name]);
1089 } 1024 }
1090 1025
1091 // instance member 1026 // instance member
1092 if (currentClass != null && _needsImplicitThis(e)) { 1027 if (currentClass != null && _needsImplicitThis(e)) {
1093 return js.call('this.#', _emitMemberName(name)); 1028 return js.call(
1029 'this.#', _emitMemberName(name, type: currentClass.element.type));
1094 } 1030 }
1095 1031
1096 // static member 1032 // static member
1097 if (e is ExecutableElement && 1033 if (e is ExecutableElement &&
1098 e.isStatic && 1034 e.isStatic &&
1099 variable.enclosingElement is ClassElement) { 1035 variable.enclosingElement is ClassElement) {
1100 var className = (variable.enclosingElement as ClassElement).name; 1036 var className = (variable.enclosingElement as ClassElement).name;
1101 return js.call('#.#', [className, _emitMemberName(name, isStatic: true)]); 1037 return js.call('#.#', [className, _emitMemberName(name, isStatic: true)]);
1102 } 1038 }
1103 1039
1104 // initializing formal parameter, e.g. `Point(this.x)` 1040 // initializing formal parameter, e.g. `Point(this.x)`
1105 if (e is ParameterElement && e.isInitializingFormal && e.isPrivate) { 1041 if (e is ParameterElement && e.isInitializingFormal && e.isPrivate) {
1106 /// Rename private names so they don't shadow the private field symbol. 1042 /// Rename private names so they don't shadow the private field symbol.
1107 /// The renamer would handle this, but it would prefer to rename the 1043 /// The renamer would handle this, but it would prefer to rename the
1108 /// temporary used for the private symbol. Instead rename the parameter. 1044 /// temporary used for the private symbol. Instead rename the parameter.
1109 return new JSTemporary('${name.substring(1)}'); 1045 return new JSTemporary('${name.substring(1)}');
1110 } 1046 }
1111 1047
1112 if (_isTemporary(e)) { 1048 if (_isTemporary(e)) {
1113 if (name[0] == '#') { 1049 if (name[0] == '#') {
1114 return new JS.InterpolatedExpression(name.substring(1)); 1050 return new JS.InterpolatedExpression(name.substring(1));
1115 } else { 1051 } else {
1116 return new JSTemporary(e.name); 1052 return new JSTemporary(name);
1117 } 1053 }
1118 } 1054 }
1119 1055
1120 return new JS.Identifier(name); 1056 return new JS.Identifier(name);
1121 } 1057 }
1122 1058
1123 JS.ArrayInitializer _emitTypeNames(List<DartType> types) { 1059 JS.ArrayInitializer _emitTypeNames(List<DartType> types) {
1124 return new JS.ArrayInitializer(types.map(_emitTypeName).toList()); 1060 return new JS.ArrayInitializer(types.map(_emitTypeName).toList());
1125 } 1061 }
1126 1062
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
1176 // TODO(jmesserly): remove when we're using coercion reifier. 1112 // TODO(jmesserly): remove when we're using coercion reifier.
1177 return _unimplementedCall('Unimplemented type $type'); 1113 return _unimplementedCall('Unimplemented type $type');
1178 } 1114 }
1179 1115
1180 var typeArgs = null; 1116 var typeArgs = null;
1181 if (type is ParameterizedType) { 1117 if (type is ParameterizedType) {
1182 // TODO(jmesserly): this is a workaround for an analyzer bug, see: 1118 // TODO(jmesserly): this is a workaround for an analyzer bug, see:
1183 // https://github.com/dart-lang/dev_compiler/commit/a212d59ad046085a626dd8 d16881cdb8e8b9c3fa 1119 // https://github.com/dart-lang/dev_compiler/commit/a212d59ad046085a626dd8 d16881cdb8e8b9c3fa
1184 if (type is! FunctionType || element is FunctionTypeAlias) { 1120 if (type is! FunctionType || element is FunctionTypeAlias) {
1185 var args = type.typeArguments; 1121 var args = type.typeArguments;
1186 if (args.any((a) => a != rules.provider.dynamicType)) { 1122 if (args.any((a) => a != types.dynamicType)) {
1187 name = '$name\$'; 1123 name = '$name\$';
1188 typeArgs = args.map(_emitTypeName); 1124 typeArgs = args.map(_emitTypeName);
1189 } 1125 }
1190 } 1126 }
1191 } 1127 }
1192 1128
1193 JS.Expression result; 1129 JS.Expression result;
1194 if (_needQualifiedName(element)) { 1130 if (_needQualifiedName(element)) {
1195 result = js.call('#.#', [_libraryName(element.library), name]); 1131 result = js.call('#.#', [_libraryName(element.library), name]);
1196 } else { 1132 } else {
1197 result = new JS.Identifier(name); 1133 result = new JS.Identifier(name);
1198 } 1134 }
1199 1135
1200 if (typeArgs != null) { 1136 if (typeArgs != null) {
1201 result = js.call('#(#)', [result, typeArgs]); 1137 result = js.call('#(#)', [result, typeArgs]);
1202 } 1138 }
1203 return result; 1139 return result;
1204 } 1140 }
1205 1141
1206 bool _needQualifiedName(Element element) { 1142 bool _needQualifiedName(Element element) {
1207 var lib = element.library; 1143 var lib = element.library;
1208 if (lib == null) return false; 1144 if (lib == null) return false;
1209 if (lib != currentLibrary) return true; 1145 if (lib != currentLibrary) return true;
1210 if (element is ClassElement) return _lazyClass(element.type); 1146 if (element is ClassElement) return _lazyClass(element.type);
1211 if (element is FunctionTypeAliasElement) return _lazyClass(element.type); 1147 if (element is FunctionTypeAliasElement) return _lazyClass(element.type);
1212 return false; 1148 return false;
1213 } 1149 }
1214 1150
1215 JS.Node _emitDSetIfDynamic(
1216 Expression target, SimpleIdentifier id, Expression rhs) {
1217 if (rules.isDynamicTarget(target)) {
1218 return js.call('dart.dput(#, #, #)', [
1219 _visit(target),
1220 js.string(id.name, "'"),
1221 _visit(rhs)
1222 ]);
1223 } else {
1224 return null;
1225 }
1226 }
1227
1228 @override 1151 @override
1229 JS.Expression visitAssignmentExpression(AssignmentExpression node) { 1152 JS.Expression visitAssignmentExpression(AssignmentExpression node) {
1230 var left = node.leftHandSide; 1153 var left = node.leftHandSide;
1231 var right = node.rightHandSide; 1154 var right = node.rightHandSide;
1232 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); 1155 if (node.operator.type == TokenType.EQ) return _emitSet(left, right);
1233 return _emitOpAssign(left, right, node.operator.lexeme[0], context: node); 1156 return _emitOpAssign(
1157 left, right, node.operator.lexeme[0], node.staticElement,
1158 context: node);
1234 } 1159 }
1235 1160
1236 JSMetaLet _emitOpAssign(Expression left, Expression right, String op, 1161 JSMetaLet _emitOpAssign(
1162 Expression left, Expression right, String op, ExecutableElement element,
1237 {Expression context}) { 1163 {Expression context}) {
1238 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions 1164 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
1239 // (for example, x is IndexExpression) we evaluate those once. 1165 // (for example, x is IndexExpression) we evaluate those once.
1240 var vars = {}; 1166 var vars = {};
1241 var lhs = _bindLeftHandSide(vars, left, context: context); 1167 var lhs = _bindLeftHandSide(vars, left, context: context);
1242 var inc = AstBuilder.binaryExpression(lhs, op, right); 1168 var inc = AstBuilder.binaryExpression(lhs, op, right);
1243 inc.staticType = rules.getStaticType(left); 1169 inc.staticElement = element;
1170 inc.staticType = getStaticType(left);
1244 return new JSMetaLet(vars, [_emitSet(lhs, inc)]); 1171 return new JSMetaLet(vars, [_emitSet(lhs, inc)]);
1245 } 1172 }
1246 1173
1247 JS.Expression _emitSet(Expression lhs, Expression rhs) { 1174 JS.Expression _emitSet(Expression lhs, Expression rhs) {
1248 if (lhs is IndexExpression) { 1175 if (lhs is IndexExpression) {
1249 String code; 1176 return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]);
1250 var target = _getTarget(lhs); 1177 }
1251 if (rules.isDynamicTarget(target)) { 1178
1252 code = 'dart.dsetindex(#, #, #)'; 1179 Expression target = null;
1253 return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]); 1180 SimpleIdentifier id;
1254 } 1181 if (lhs is PropertyAccess) {
1255 return js.call('#.#(#, #)', [ 1182 target = _getTarget(lhs);
1183 id = lhs.propertyName;
1184 } else if (lhs is PrefixedIdentifier) {
1185 target = lhs.prefix;
1186 id = lhs.identifier;
1187 }
1188
1189 if (target != null && rules.isDynamicTarget(target)) {
1190 return js.call('dart.$DPUT(#, #, #)', [
1256 _visit(target), 1191 _visit(target),
1257 _emitMemberName('[]=', target: target), 1192 _emitMemberName(id.name, type: getStaticType(target)),
1258 _visit(lhs.index),
1259 _visit(rhs) 1193 _visit(rhs)
1260 ]); 1194 ]);
1261 } 1195 }
1262 1196
1263 if (lhs is PropertyAccess) {
1264 var result = _emitDSetIfDynamic(_getTarget(lhs), lhs.propertyName, rhs);
1265 if (result != null) return result;
1266 } else if (lhs is PrefixedIdentifier) {
1267 // TODO(vsm): Is this the right code if the prefix is a library?
1268 var result = _emitDSetIfDynamic(lhs.prefix, lhs.identifier, rhs);
1269 if (result != null) return result;
1270 }
1271 return _visit(rhs).toAssignExpression(_visit(lhs)); 1197 return _visit(rhs).toAssignExpression(_visit(lhs));
1272 } 1198 }
1273 1199
1274 @override 1200 @override
1275 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { 1201 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) {
1276 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); 1202 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent));
1277 var ret = new JS.Return(_visit(node.expression)); 1203 var ret = new JS.Return(_visit(node.expression));
1278 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); 1204 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]);
1279 } 1205 }
1280 1206
(...skipping 11 matching lines...) Expand all
1292 @override 1218 @override
1293 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements)); 1219 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements));
1294 1220
1295 @override 1221 @override
1296 visitMethodInvocation(MethodInvocation node) { 1222 visitMethodInvocation(MethodInvocation node) {
1297 var target = node.isCascaded ? _cascadeTarget : node.target; 1223 var target = node.isCascaded ? _cascadeTarget : node.target;
1298 1224
1299 var result = _emitForeignJS(node); 1225 var result = _emitForeignJS(node);
1300 if (result != null) return result; 1226 if (result != null) return result;
1301 1227
1302 if (rules.isDynamicCall(node.methodName)) { 1228 // TODO(jmesserly): if we try to call a getter returning a function with
1303 var args = _visit(node.argumentList); 1229 // a call method, we don't generate the `.call` correctly.
1304 if (target != null) { 1230 String code;
1305 return js.call('dart.dsend(#, #, #)', [ 1231 if (target == null) {
1306 _visit(target), 1232 if (rules.isDynamicCall(node.methodName)) {
1307 js.string(node.methodName.name, "'"), 1233 code = 'dart.$DCALL(#, #)';
1308 args
1309 ]);
1310 } else { 1234 } else {
1311 return js.call('dart.dcall(#, #)', [_visit(node.methodName), args]); 1235 code = '#(#)';
1312 } 1236 }
1237 return js.call(
1238 code, [_visit(node.methodName), _visit(node.argumentList)]);
1313 } 1239 }
1314 1240
1315 // TODO(jmesserly): if this resolves to a getter returning a function with 1241 // TODO(jmesserly): if the methodName resolves statically but the call is
1316 // a call method, we don't generate the `.call` correctly. 1242 // dynamic (e.g. `obj.method` is resolved to a field of type `Function`), we
1317 1243 // could generate call(#.#, #). Not sure if that's worth it.
1318 var targetJs; 1244 if (rules.isDynamicCall(node.methodName)) {
1319 if (target != null) { 1245 code = 'dart.$DSEND(#, #, #)';
1320 targetJs = js.call('#.#', [
1321 _visit(target),
1322 _emitMemberName(node.methodName.name, target: target)
1323 ]);
1324 } else { 1246 } else {
1325 targetJs = _visit(node.methodName); 1247 code = '#.#(#)';
1326 } 1248 }
1327 1249 return js.call(code, [
1328 return js.call('#(#)', [targetJs, _visit(node.argumentList)]); 1250 _visit(target),
1251 _emitMemberName(node.methodName.name, type: getStaticType(target)),
1252 _visit(node.argumentList)
1253 ]);
1329 } 1254 }
1330 1255
1331 /// Emits code for the `JS(...)` builtin. 1256 /// Emits code for the `JS(...)` builtin.
1332 _emitForeignJS(MethodInvocation node) { 1257 _emitForeignJS(MethodInvocation node) {
1333 var e = node.methodName.staticElement; 1258 var e = node.methodName.staticElement;
1334 if (e is FunctionElement && 1259 if (e is FunctionElement &&
1335 e.library.name == '_foreign_helper' && 1260 e.library.name == '_foreign_helper' &&
1336 e.name == 'JS') { 1261 e.name == 'JS') {
1337 var args = node.argumentList.arguments; 1262 var args = node.argumentList.arguments;
1338 // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer` 1263 // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer`
1339 var code = args[1] as StringLiteral; 1264 var code = args[1] as StringLiteral;
1340 1265
1341 var template = js.parseForeignJS(code.stringValue); 1266 var template = js.parseForeignJS(code.stringValue);
1342 var result = template.instantiate(_visitList(args.skip(2))); 1267 var result = template.instantiate(_visitList(args.skip(2)));
1343 // `throw` is emitted as a statement by `parseForeignJS`. 1268 // `throw` is emitted as a statement by `parseForeignJS`.
1344 assert(result is JS.Expression || node.parent is ExpressionStatement); 1269 assert(result is JS.Expression || node.parent is ExpressionStatement);
1345 return result; 1270 return result;
1346 } 1271 }
1347 return null; 1272 return null;
1348 } 1273 }
1349 1274
1350 @override 1275 @override
1351 JS.Expression visitFunctionExpressionInvocation( 1276 JS.Expression visitFunctionExpressionInvocation(
1352 FunctionExpressionInvocation node) { 1277 FunctionExpressionInvocation node) {
1353 var code; 1278 var code;
1354 if (rules.isDynamicCall(node.function)) { 1279 if (rules.isDynamicCall(node.function)) {
1355 code = 'dart.dcall(#, #)'; 1280 code = 'dart.$DCALL(#, #)';
1356 } else { 1281 } else {
1357 code = '#(#)'; 1282 code = '#(#)';
1358 } 1283 }
1359 return js.call(code, [_visit(node.function), _visit(node.argumentList)]); 1284 return js.call(code, [_visit(node.function), _visit(node.argumentList)]);
1360 } 1285 }
1361 1286
1362 @override 1287 @override
1363 List<JS.Expression> visitArgumentList(ArgumentList node) { 1288 List<JS.Expression> visitArgumentList(ArgumentList node) {
1364 var args = <JS.Expression>[]; 1289 var args = <JS.Expression>[];
1365 var named = <JS.Property>[]; 1290 var named = <JS.Property>[];
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
1511 _exportsVar, 1436 _exportsVar,
1512 _properties.map(_emitTopLevelProperty) 1437 _properties.map(_emitTopLevelProperty)
1513 ])); 1438 ]));
1514 _properties.clear(); 1439 _properties.clear();
1515 } 1440 }
1516 1441
1517 @override 1442 @override
1518 visitConstructorName(ConstructorName node) { 1443 visitConstructorName(ConstructorName node) {
1519 var typeName = _visit(node.type); 1444 var typeName = _visit(node.type);
1520 if (node.name != null) { 1445 if (node.name != null) {
1521 return js.call( 1446 return js.call('#.#', [typeName, _emitMemberName(node.name.name)]);
1522 '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]);
1523 } 1447 }
1524 return typeName; 1448 return typeName;
1525 } 1449 }
1526 1450
1527 @override 1451 @override
1528 visitInstanceCreationExpression(InstanceCreationExpression node) { 1452 visitInstanceCreationExpression(InstanceCreationExpression node) {
1529 return js.call( 1453 return js.call(
1530 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]); 1454 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]);
1531 } 1455 }
1532 1456
(...skipping 13 matching lines...) Expand all
1546 bool typeIsNonNullablePrimitiveInJS(DartType t) => 1470 bool typeIsNonNullablePrimitiveInJS(DartType t) =>
1547 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); 1471 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t);
1548 1472
1549 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => 1473 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) =>
1550 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); 1474 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT);
1551 1475
1552 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); 1476 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t);
1553 1477
1554 bool _isNonNullableExpression(Expression expr) { 1478 bool _isNonNullableExpression(Expression expr) {
1555 // If the type is non-nullable, no further checking needed. 1479 // If the type is non-nullable, no further checking needed.
1556 if (rules.isNonNullableType(rules.getStaticType(expr))) return true; 1480 if (rules.isNonNullableType(getStaticType(expr))) return true;
1557 1481
1558 // TODO(vsm): Revisit whether we really need this when we get 1482 // TODO(vsm): Revisit whether we really need this when we get
1559 // better non-nullability in the type system. 1483 // better non-nullability in the type system.
1560 1484
1561 if (expr is Literal && expr is! NullLiteral) { 1485 if (expr is Literal && expr is! NullLiteral) return true;
1562 return true; 1486 if (expr is IsExpression) return true;
1563 }
1564 if (expr is ParenthesizedExpression) { 1487 if (expr is ParenthesizedExpression) {
1565 return _isNonNullableExpression(expr.expression); 1488 return _isNonNullableExpression(expr.expression);
1566 } 1489 }
1567 if (expr is Conversion) { 1490 if (expr is Conversion) {
1568 return _isNonNullableExpression(expr.expression); 1491 return _isNonNullableExpression(expr.expression);
1569 } 1492 }
1570 DartType type = null; 1493 DartType type = null;
1571 if (expr is BinaryExpression) { 1494 if (expr is BinaryExpression) {
1572 type = rules.getStaticType(expr.leftOperand); 1495 type = getStaticType(expr.leftOperand);
1573 } else if (expr is PrefixExpression) { 1496 } else if (expr is PrefixExpression) {
1574 type = rules.getStaticType(expr.operand); 1497 type = getStaticType(expr.operand);
1575 } else if (expr is PostfixExpression) { 1498 } else if (expr is PostfixExpression) {
1576 type = rules.getStaticType(expr.operand); 1499 type = getStaticType(expr.operand);
1577 } 1500 }
1578 if (type != null && typeIsPrimitiveInJS(type)) { 1501 if (type != null && typeIsPrimitiveInJS(type)) {
1579 return true; 1502 return true;
1580 } 1503 }
1581 if (expr is MethodInvocation) { 1504 if (expr is MethodInvocation) {
1582 // TODO(vsm): This logic overlaps with the resolver. 1505 // TODO(vsm): This logic overlaps with the resolver.
1583 // Where is the best place to put this? 1506 // Where is the best place to put this?
1584 var e = expr.methodName.staticElement; 1507 var e = expr.methodName.staticElement;
1585 if (e is FunctionElement && 1508 if (e is FunctionElement &&
1586 e.library.name == '_foreign_helper' && 1509 e.library.name == '_foreign_helper' &&
(...skipping 23 matching lines...) Expand all
1610 } else { 1533 } else {
1611 return js.call('dart.notNull(#)', _visit(expr)); 1534 return js.call('dart.notNull(#)', _visit(expr));
1612 } 1535 }
1613 } 1536 }
1614 1537
1615 @override 1538 @override
1616 JS.Expression visitBinaryExpression(BinaryExpression node) { 1539 JS.Expression visitBinaryExpression(BinaryExpression node) {
1617 var op = node.operator; 1540 var op = node.operator;
1618 var left = node.leftOperand; 1541 var left = node.leftOperand;
1619 var right = node.rightOperand; 1542 var right = node.rightOperand;
1620 var leftType = rules.getStaticType(left); 1543 var leftType = getStaticType(left);
1621 var rightType = rules.getStaticType(right); 1544 var rightType = getStaticType(right);
1622 1545
1623 var code; 1546 var code;
1624 if (op.type.isEqualityOperator) { 1547 if (op.type.isEqualityOperator) {
1625 // If we statically know LHS or RHS is null we can generate a clean check. 1548 // If we statically know LHS or RHS is null we can generate a clean check.
1626 // We can also do this if both sides are the same primitive type. 1549 // We can also do this if both sides are the same primitive type.
1627 if (_canUsePrimitiveEquality(left, right)) { 1550 if (_canUsePrimitiveEquality(left, right)) {
1628 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; 1551 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #';
1629 } else { 1552 } else {
1630 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; 1553 var bang = op.type == TokenType.BANG_EQ ? '!' : '';
1631 code = '${bang}dart.equals(#, #)'; 1554 code = '${bang}dart.equals(#, #)';
1632 } 1555 }
1633 return js.call(code, [_visit(left), _visit(right)]); 1556 return js.call(code, [_visit(left), _visit(right)]);
1634 } else if (binaryOperationIsPrimitive(leftType, rightType)) { 1557 }
1558
1559 if (binaryOperationIsPrimitive(leftType, rightType)) {
1635 // special cases where we inline the operation 1560 // special cases where we inline the operation
1636 // these values are assumed to be non-null (determined by the checker) 1561 // these values are assumed to be non-null (determined by the checker)
1637 // TODO(jmesserly): it would be nice to just inline the method from core, 1562 // TODO(jmesserly): it would be nice to just inline the method from core,
1638 // instead of special cases here. 1563 // instead of special cases here.
1639 if (op.type == TokenType.TILDE_SLASH) { 1564 if (op.type == TokenType.TILDE_SLASH) {
1640 // `a ~/ b` is equivalent to `(a / b).truncate()` 1565 // `a ~/ b` is equivalent to `(a / b).truncate()`
1641 code = '(# / #).truncate()'; 1566 code = '(# / #).truncate()';
1642 } else { 1567 } else {
1643 // TODO(vsm): When do Dart ops not map to JS? 1568 // TODO(vsm): When do Dart ops not map to JS?
1644 code = '# $op #'; 1569 code = '# $op #';
1645 } 1570 }
1646 return js.call(code, [notNull(left), notNull(right)]); 1571 return js.call(code, [notNull(left), notNull(right)]);
1647 } else {
1648 var opString = js.string(op.lexeme, "'");
1649 if (rules.isDynamicTarget(left)) {
1650 // dynamic dispatch
1651 return js.call(
1652 'dart.dsend(#, #, #)', [_visit(left), opString, _visit(right)]);
1653 } else if (_isJSBuiltinType(leftType)) {
1654 // TODO(jmesserly): we'd get better readability from the static-dispatch
1655 // pattern below. Consider:
1656 //
1657 // "hello"['+']"world"
1658 // vs
1659 // core.String['+']("hello", "world")
1660 //
1661 // Infix notation is much more readable, which is a bit part of why
1662 // C# added its extension methods feature. However this would require
1663 // adding these methods to String.prototype/Number.prototype in JS.
1664 return js.call('#.#(#, #)', [
1665 _emitTypeName(leftType),
1666 opString,
1667 _visit(left),
1668 _visit(right)
1669 ]);
1670 } else {
1671 // Generic static-dispatch, user-defined operator code path.
1672 return js.call('#.#(#)', [_visit(left), opString, _visit(right)]);
1673 }
1674 } 1572 }
1573
1574 return _emitSend(left, op.lexeme, [right]);
1675 } 1575 }
1676 1576
1677 /// If the type [t] is [int] or [double], returns [num]. 1577 /// If the type [t] is [int] or [double], returns [num].
1678 /// Otherwise returns [t]. 1578 /// Otherwise returns [t].
1679 DartType _canonicalizeNumTypes(DartType t) { 1579 DartType _canonicalizeNumTypes(DartType t) {
1680 var numType = rules.provider.numType; 1580 var numType = types.numType;
1681 if (t is InterfaceType && t.superclass == numType) return numType; 1581 if (t is InterfaceType && t.superclass == numType) return numType;
1682 return t; 1582 return t;
1683 } 1583 }
1684 1584
1685 bool _canUsePrimitiveEquality(Expression left, Expression right) { 1585 bool _canUsePrimitiveEquality(Expression left, Expression right) {
1686 if (_isNull(left) || _isNull(right)) return true; 1586 if (_isNull(left) || _isNull(right)) return true;
1687 1587
1688 var leftType = _canonicalizeNumTypes(rules.getStaticType(left)); 1588 var leftType = _canonicalizeNumTypes(getStaticType(left));
1689 var rightType = _canonicalizeNumTypes(rules.getStaticType(right)); 1589 var rightType = _canonicalizeNumTypes(getStaticType(right));
1690 return _isJSBuiltinType(leftType) && leftType == rightType; 1590 return _isJSBuiltinType(leftType) && leftType == rightType;
1691 } 1591 }
1692 1592
1693 bool _isNull(Expression expr) => expr is NullLiteral; 1593 bool _isNull(Expression expr) => expr is NullLiteral;
1694 1594
1695 SimpleIdentifier _createTemporary(String name, DartType type) { 1595 SimpleIdentifier _createTemporary(String name, DartType type) {
1696 // We use an invalid source location to signal that this is a temporary. 1596 // We use an invalid source location to signal that this is a temporary.
1697 // See [_isTemporary]. 1597 // See [_isTemporary].
1698 // TODO(jmesserly): alternatives are 1598 // TODO(jmesserly): alternatives are
1699 // * (ab)use Element.isSynthetic, which isn't currently used for 1599 // * (ab)use Element.isSynthetic, which isn't currently used for
1700 // LocalVariableElementImpl, so we could repurpose to mean "temp". 1600 // LocalVariableElementImpl, so we could repurpose to mean "temp".
1701 // * add a new property to LocalVariableElementImpl. 1601 // * add a new property to LocalVariableElementImpl.
1702 // * create a new subtype of LocalVariableElementImpl to mark a temp. 1602 // * create a new subtype of LocalVariableElementImpl to mark a temp.
1703 var id = 1603 var id =
1704 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); 1604 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
1705 id.staticElement = new LocalVariableElementImpl.forNode(id); 1605 id.staticElement = new LocalVariableElementImpl.forNode(id);
1706 id.staticType = type; 1606 id.staticType = type;
1707 return id; 1607 return id;
1708 } 1608 }
1709 1609
1710 bool _isTemporary(Element node) => node.nameOffset == -1; 1610 bool _isTemporary(Element node) => node.nameOffset == -1;
1711 1611
1712 /// Desugars postfix increment.
1713 ///
1714 /// In the general case [expr] can be one of [IndexExpression],
1715 /// [PrefixExpression] or [PropertyAccess] and we need to
1716 /// ensure sub-expressions are evaluated once.
1717 ///
1718 /// We also need to ensure we can return the original value of the expression,
1719 /// and that it is only evaluated once.
1720 ///
1721 /// We desugar this using let*.
1722 ///
1723 /// For example, `expr1[expr2]++` can be transformed to this:
1724 ///
1725 /// // psuedocode mix of Scheme and JS:
1726 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
1727 ///
1728 /// The [JSMetaLet] nodes automatically simplify themselves if they can.
1729 /// For example, if the result value is not used, then `t` goes away.
1730 JSMetaLet _emitPostfixIncrement(Expression expr, Token op) {
1731 var type = rules.getStaticType(expr);
1732 assert(type != null);
1733
1734 // Handle the left hand side, to ensure each of its subexpressions are
1735 // evaluated only once.
1736 var vars = {};
1737 var left = _bindLeftHandSide(vars, expr, context: expr);
1738
1739 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
1740 // and `x1` is the new value for `x`.
1741 var x = _bindValue(vars, 'x', left, context: expr);
1742
1743 var one = AstBuilder.integerLiteral(1);
1744 one.staticType = rules.provider.intType;
1745 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one);
1746 increment.staticType = type;
1747
1748 var body = [_emitSet(left, increment), _visit(x)];
1749 return new JSMetaLet(vars, body, statelessResult: true);
1750 }
1751
1752 /// Returns a new expression, which can be be used safely *once* on the 1612 /// Returns a new expression, which can be be used safely *once* on the
1753 /// left hand side, and *once* on the right side of an assignment. 1613 /// left hand side, and *once* on the right side of an assignment.
1754 /// For example: `expr1[expr2] += y` can be compiled as 1614 /// For example: `expr1[expr2] += y` can be compiled as
1755 /// `expr1[expr2] = expr1[expr2] + y`. 1615 /// `expr1[expr2] = expr1[expr2] + y`.
1756 /// 1616 ///
1757 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated 1617 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated
1758 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. 1618 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`.
1759 /// 1619 ///
1760 /// If the expression does not end up using `x1` or `x2` more than once, or 1620 /// If the expression does not end up using `x1` or `x2` more than once, or
1761 /// if those expressions can be treated as stateless (e.g. they are 1621 /// if those expressions can be treated as stateless (e.g. they are
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
1802 Map<String, JS.Expression> scope, String name, Expression expr, 1662 Map<String, JS.Expression> scope, String name, Expression expr,
1803 {Expression context}) { 1663 {Expression context}) {
1804 // No need to do anything for stateless expressions. 1664 // No need to do anything for stateless expressions.
1805 if (_isStateless(expr, context)) return expr; 1665 if (_isStateless(expr, context)) return expr;
1806 1666
1807 var t = _createTemporary('#$name', expr.staticType); 1667 var t = _createTemporary('#$name', expr.staticType);
1808 scope[name] = _visit(expr); 1668 scope[name] = _visit(expr);
1809 return t; 1669 return t;
1810 } 1670 }
1811 1671
1672 /// Desugars postfix increment.
1673 ///
1674 /// In the general case [expr] can be one of [IndexExpression],
1675 /// [PrefixExpression] or [PropertyAccess] and we need to
1676 /// ensure sub-expressions are evaluated once.
1677 ///
1678 /// We also need to ensure we can return the original value of the expression,
1679 /// and that it is only evaluated once.
1680 ///
1681 /// We desugar this using let*.
1682 ///
1683 /// For example, `expr1[expr2]++` can be transformed to this:
1684 ///
1685 /// // psuedocode mix of Scheme and JS:
1686 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
1687 ///
1688 /// The [JSMetaLet] nodes automatically simplify themselves if they can.
1689 /// For example, if the result value is not used, then `t` goes away.
1812 @override 1690 @override
1813 JS.Expression visitPostfixExpression(PostfixExpression node) { 1691 JS.Expression visitPostfixExpression(PostfixExpression node) {
1814 var op = node.operator; 1692 var op = node.operator;
1815 var expr = node.operand; 1693 var expr = node.operand;
1816 1694
1817 var dispatchType = rules.getStaticType(expr); 1695 var dispatchType = getStaticType(expr);
1818 if (unaryOperationIsPrimitive(dispatchType)) { 1696 if (unaryOperationIsPrimitive(dispatchType)) {
1819 if (_isNonNullableExpression(expr)) { 1697 if (_isNonNullableExpression(expr)) {
1820 return js.call('#$op', _visit(expr)); 1698 return js.call('#$op', _visit(expr));
1821 } 1699 }
1822 } 1700 }
1823 1701
1824 assert(op.lexeme == '++' || op.lexeme == '--'); 1702 assert(op.lexeme == '++' || op.lexeme == '--');
1825 return _emitPostfixIncrement(expr, op); 1703
1704 // Handle the left hand side, to ensure each of its subexpressions are
1705 // evaluated only once.
1706 var vars = {};
1707 var left = _bindLeftHandSide(vars, expr, context: expr);
1708
1709 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
1710 // and `x1` is the new value for `x`.
1711 var x = _bindValue(vars, 'x', left, context: expr);
1712
1713 var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
1714 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
1715 ..staticElement = node.staticElement
1716 ..staticType = getStaticType(expr);
1717
1718 var body = [_emitSet(left, increment), _visit(x)];
1719 return new JSMetaLet(vars, body, statelessResult: true);
1826 } 1720 }
1827 1721
1828 @override 1722 @override
1829 JS.Expression visitPrefixExpression(PrefixExpression node) { 1723 JS.Expression visitPrefixExpression(PrefixExpression node) {
1830 return _emitPrefixExpression(node.operator, node.operand); 1724 var op = node.operator;
1831 } 1725 var expr = node.operand;
1832 1726
1833 JS.Expression _emitPrefixExpression(Token op, Expression expr) { 1727 var dispatchType = getStaticType(expr);
1834 var dispatchType = rules.getStaticType(expr);
1835 if (unaryOperationIsPrimitive(dispatchType)) { 1728 if (unaryOperationIsPrimitive(dispatchType)) {
1836 if (_isNonNullableExpression(expr)) { 1729 if (_isNonNullableExpression(expr)) {
1837 return js.call('$op#', _visit(expr)); 1730 return js.call('$op#', _visit(expr));
1838 } else if (op.lexeme == '++' || op.lexeme == '--') { 1731 } else if (op.lexeme == '++' || op.lexeme == '--') {
1839 // We need a null check, so the increment must be expanded out. 1732 // We need a null check, so the increment must be expanded out.
1840 var mathop = op.lexeme[0]; 1733 var mathop = op.lexeme[0];
1841 var vars = {}; 1734 var vars = {};
1842 var x = _bindLeftHandSide(vars, expr, context: expr); 1735 var x = _bindLeftHandSide(vars, expr, context: expr);
1843 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); 1736 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]);
1844 return new JSMetaLet(vars, [body]); 1737 return new JSMetaLet(vars, [body]);
1845 } else { 1738 } else {
1846 return js.call('$op#', notNull(expr)); 1739 return js.call('$op#', notNull(expr));
1847 } 1740 }
1848 } 1741 }
1849 1742
1850 if (op.lexeme == '++' || op.lexeme == '--') { 1743 if (op.lexeme == '++' || op.lexeme == '--') {
1851 // Increment or decrement requires expansion. 1744 // Increment or decrement requires expansion.
1852 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions 1745 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions
1853 // (for example, x is IndexExpression) we evaluate those once. 1746 // (for example, x is IndexExpression) we evaluate those once.
1854 var one = AstBuilder.integerLiteral(1) 1747 var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
1855 ..staticType = rules.provider.intType; 1748 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement,
1856 return _emitOpAssign(expr, one, op.lexeme[0], context: expr); 1749 context: expr);
1857 } 1750 }
1858 1751
1859 // Call the operator 1752 return _emitSend(expr, op.lexeme[0], []);
1860 var opString = _emitMemberName(op.lexeme, unary: true);
1861 if (rules.isDynamicTarget(expr)) {
1862 // dynamic dispatch
1863 return js.call('dart.dsend(#, #)', [_visit(expr), opString]);
1864 } else if (_isJSBuiltinType(dispatchType)) {
1865 return js.call(
1866 '#.#(#)', [_emitTypeName(dispatchType), opString, _visit(expr)]);
1867 } else {
1868 // Generic static-dispatch, user-defined operator code path.
1869 return js.call('#.#()', [_visit(expr), opString]);
1870 }
1871 } 1753 }
1872 1754
1873 // Cascades can contain [IndexExpression], [MethodInvocation] and 1755 // Cascades can contain [IndexExpression], [MethodInvocation] and
1874 // [PropertyAccess]. The code generation for those is handled in their 1756 // [PropertyAccess]. The code generation for those is handled in their
1875 // respective visit methods. 1757 // respective visit methods.
1876 @override 1758 @override
1877 JS.Node visitCascadeExpression(CascadeExpression node) { 1759 JS.Node visitCascadeExpression(CascadeExpression node) {
1878 var savedCascadeTemp = _cascadeTarget; 1760 var savedCascadeTemp = _cascadeTarget;
1879 1761
1880 var vars = {}; 1762 var vars = {};
(...skipping 18 matching lines...) Expand all
1899 JS.This visitThisExpression(ThisExpression node) => new JS.This(); 1781 JS.This visitThisExpression(ThisExpression node) => new JS.This();
1900 1782
1901 @override 1783 @override
1902 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); 1784 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super();
1903 1785
1904 @override 1786 @override
1905 visitPrefixedIdentifier(PrefixedIdentifier node) { 1787 visitPrefixedIdentifier(PrefixedIdentifier node) {
1906 if (node.prefix.staticElement is PrefixElement) { 1788 if (node.prefix.staticElement is PrefixElement) {
1907 return _visit(node.identifier); 1789 return _visit(node.identifier);
1908 } else { 1790 } else {
1909 return _emitGet(node.prefix, node.identifier); 1791 return _emitGet(node.prefix, node.identifier.name);
1910 } 1792 }
1911 } 1793 }
1912 1794
1913 @override 1795 @override
1914 visitPropertyAccess(PropertyAccess node) => 1796 visitPropertyAccess(PropertyAccess node) =>
1915 _emitGet(_getTarget(node), node.propertyName); 1797 _emitGet(_getTarget(node), node.propertyName.name);
1916 1798
1917 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. 1799 /// Shared code for [PrefixedIdentifier] and [PropertyAccess].
1918 _emitGet(Expression target, SimpleIdentifier name) { 1800 JS.Expression _emitGet(Expression target, String memberName) {
1801 var name = _emitMemberName(memberName, type: getStaticType(target));
1919 if (rules.isDynamicTarget(target)) { 1802 if (rules.isDynamicTarget(target)) {
1920 return js.call( 1803 return js.call('dart.$DLOAD(#, #)', [_visit(target), name]);
1921 'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]);
1922 } else { 1804 } else {
1923 var e = name.staticElement; 1805 return js.call('#.#', [_visit(target), name]);
1924 var ret = js.call('#.#', [ 1806 }
1807 }
1808
1809 JS.Expression _emitSend(
1810 Expression target, String name, List<Expression> args) {
1811 var type = getStaticType(target);
1812 var memberName = _emitMemberName(name, unary: args.isEmpty, type: type);
1813 if (rules.isDynamicTarget(target)) {
1814 // dynamic dispatch
1815 var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name];
1816 if (dynamicHelper != null) {
1817 return js.call(
1818 'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]);
1819 }
1820 return js.call('dart.$DSEND(#, #, #)', [
1925 _visit(target), 1821 _visit(target),
1926 _emitMemberName(name.name, 1822 memberName,
1927 isStatic: e is ExecutableElement && e.isStatic, target: target) 1823 _visitList(args)
1928 ]); 1824 ]);
1929 return ret;
1930 } 1825 }
1826 if (_isJSBuiltinType(type)) {
1827 // static call pattern for bultins.
1828 return js.call('#.#(#, #)', [
1829 _emitTypeName(type),
1830 memberName,
1831 _visit(target),
1832 _visitList(args)
1833 ]);
1834 }
1835 // Generic dispatch to a statically known method.
1836 return js.call('#.#(#)', [_visit(target), memberName, _visitList(args)]);
1931 } 1837 }
1932 1838
1933 @override 1839 @override
1934 visitIndexExpression(IndexExpression node) { 1840 visitIndexExpression(IndexExpression node) {
1935 var target = _getTarget(node); 1841 return _emitSend(_getTarget(node), '[]', [node.index]);
1936 if (rules.isDynamicTarget(target)) {
1937 return js.call('dart.dindex(#, #)', [_visit(target), _visit(node.index)]);
1938 }
1939
1940 return js.call('#.#(#)', [
1941 _visit(target),
1942 _emitMemberName('[]', target: target),
1943 _visit(node.index)
1944 ]);
1945 } 1842 }
1946 1843
1947 /// Gets the target of a [PropertyAccess] or [IndexExpression]. 1844 /// Gets the target of a [PropertyAccess] or [IndexExpression].
1948 /// Those two nodes are special because they're both allowed on left side of 1845 /// Those two nodes are special because they're both allowed on left side of
1949 /// an assignment expression and cascades. 1846 /// an assignment expression and cascades.
1950 Expression _getTarget(node) { 1847 Expression _getTarget(node) {
1951 assert(node is IndexExpression || node is PropertyAccess); 1848 assert(node is IndexExpression || node is PropertyAccess);
1952 return node.isCascaded ? _cascadeTarget : node.target; 1849 return node.isCascaded ? _cascadeTarget : node.target;
1953 } 1850 }
1954 1851
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
2036 if (clauses == null || clauses.isEmpty) return null; 1933 if (clauses == null || clauses.isEmpty) return null;
2037 1934
2038 // TODO(jmesserly): need a better way to get a temporary variable. 1935 // TODO(jmesserly): need a better way to get a temporary variable.
2039 // This could incorrectly shadow a user's name. 1936 // This could incorrectly shadow a user's name.
2040 var savedCatch = _catchParameter; 1937 var savedCatch = _catchParameter;
2041 1938
2042 if (clauses.length == 1 && clauses.single.exceptionParameter != null) { 1939 if (clauses.length == 1 && clauses.single.exceptionParameter != null) {
2043 // Special case for a single catch. 1940 // Special case for a single catch.
2044 _catchParameter = clauses.single.exceptionParameter; 1941 _catchParameter = clauses.single.exceptionParameter;
2045 } else { 1942 } else {
2046 _catchParameter = _createTemporary('e', rules.provider.dynamicType); 1943 _catchParameter = _createTemporary('e', types.dynamicType);
2047 } 1944 }
2048 1945
2049 JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter)); 1946 JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter));
2050 for (var clause in clauses.reversed) { 1947 for (var clause in clauses.reversed) {
2051 catchBody = _catchClauseGuard(clause, catchBody); 1948 catchBody = _catchClauseGuard(clause, catchBody);
2052 } 1949 }
2053 1950
2054 var catchVarDecl = _visit(_catchParameter); 1951 var catchVarDecl = _visit(_catchParameter);
2055 _catchParameter = savedCatch; 1952 _catchParameter = savedCatch;
2056 return new JS.Catch(catchVarDecl, new JS.Block([catchBody])); 1953 return new JS.Catch(catchVarDecl, new JS.Block([catchBody]));
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after
2312 /// This follows the same pattern as EcmaScript 6 Map: 2209 /// This follows the same pattern as EcmaScript 6 Map:
2313 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map> 2210 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map>
2314 /// 2211 ///
2315 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed 2212 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed
2316 /// for this transformation to happen, otherwise binary minus is assumed. 2213 /// for this transformation to happen, otherwise binary minus is assumed.
2317 /// 2214 ///
2318 /// Equality is a bit special, it is generated via the Dart `equals` runtime 2215 /// Equality is a bit special, it is generated via the Dart `equals` runtime
2319 /// helper, that checks for null. The user defined method is called '=='. 2216 /// helper, that checks for null. The user defined method is called '=='.
2320 /// 2217 ///
2321 JS.Expression _emitMemberName(String name, 2218 JS.Expression _emitMemberName(String name,
2322 {bool unary: false, bool isStatic: false, Expression target}) { 2219 {DartType type, bool unary: false, bool isStatic: false}) {
2323 if (isStatic == false && target != null) {
2324 var ret = nameIfExtension(target, name);
2325 if (ret != null) return ret;
2326 }
2327 if (name.startsWith('_')) { 2220 if (name.startsWith('_')) {
2328 if (_privateNames.add(name)) _pendingPrivateNames.add(name); 2221 if (_privateNames.add(name)) _pendingPrivateNames.add(name);
2329 return new JSTemporary(name); 2222 return new JSTemporary(name);
2330 } 2223 }
2331 return _propertyName(_jsMemberName(name, unary: unary, isStatic: isStatic)); 2224 // Check for extension method:
2332 } 2225 var extLibrary = _findExtensionLibrary(name, type);
2333 2226
2334 String _jsMemberName(String name, {bool unary: false, bool isStatic: false}) { 2227 if (name == '[]') {
2335 if (name == '[]') return 'get'; 2228 name = 'get';
2336 if (name == '[]=') return 'set'; 2229 } else if (name == '[]=') {
2337 if (unary && name == '-') return 'unary-'; 2230 name = 'set';
2231 } else if (name == '-' && unary) {
2232 name = 'unary-';
2233 }
2234
2338 if (isStatic && invalidJSStaticMethodName(name)) { 2235 if (isStatic && invalidJSStaticMethodName(name)) {
2339 // Choose an string name. Use an invalid identifier so it won't conflict 2236 // Choose an string name. Use an invalid identifier so it won't conflict
2340 // with any valid member names. 2237 // with any valid member names.
2341 // TODO(jmesserly): this works around the problem, but I'm pretty sure we 2238 // TODO(jmesserly): this works around the problem, but I'm pretty sure we
2342 // don't need it, as static methods seemed to work. The only concrete 2239 // don't need it, as static methods seemed to work. The only concrete
2343 // issue we saw was in the defineNamedConstructor helper function. 2240 // issue we saw was in the defineNamedConstructor helper function.
2344 return '$name*'; 2241 name = '$name*';
2345 } 2242 }
2346 return name; 2243
2244 if (extLibrary != null) {
2245 return js.call('#.#', [
2246 _libraryName(extLibrary),
2247 _propertyName(_addExtensionMethodName(name, extLibrary))
2248 ]);
2249 }
2250
2251 return _propertyName(name);
2347 } 2252 }
2348 2253
2349 JS.LiteralString _emitExtensionMethodName(String name) => 2254 LibraryElement _findExtensionLibrary(String name, DartType type) {
2350 js.string(_extensionMethodName(name), "'"); 2255 if (type is! InterfaceType) return null;
2351 2256
2352 String _extensionMethodName(String name) => '\$${_jsMemberName(name)}'; 2257 var extLibrary = null;
2258 var extensionTypes = _extensionMethods[name];
2259 if (extensionTypes != null) {
2260 // Normalize the type to ignore generics.
2261 type = fillDynamicTypeArgs(type, types);
2262 for (var t in extensionTypes) {
2263 if (rules.isSubTypeOf(type, t)) {
2264 assert(extLibrary == null || extLibrary == t.element.library);
2265 extLibrary = t.element.library;
2266 }
2267 }
2268 }
2269 return extLibrary;
2270 }
2271
2272 String _addExtensionMethodName(String name, LibraryElement extLibrary) {
2273 var extensionMethodName = '\$$name';
2274 if (extLibrary == currentLibrary) {
2275 // TODO(jacobr): need to do a better job ensuring that extension method
2276 // name symbols do not conflict with other symbols before we can let
2277 // user defined libraries define extension methods.
2278 if (_extensionMethodNames.add(extensionMethodName)) {
2279 _pendingExtensionMethodNames.add(extensionMethodName);
2280 _addExport(extensionMethodName);
2281 }
2282 }
2283 return extensionMethodName;
2284 }
2353 2285
2354 bool _externalOrNative(node) => 2286 bool _externalOrNative(node) =>
2355 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; 2287 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
2356 2288
2357 FunctionBody _functionBody(node) => 2289 FunctionBody _functionBody(node) =>
2358 node is FunctionDeclaration ? node.functionExpression.body : node.body; 2290 node is FunctionDeclaration ? node.functionExpression.body : node.body;
2359 2291
2360 /// Choose a canonical name from the library element. 2292 /// Choose a canonical name from the library element.
2361 /// This never uses the library's name (the identifier in the `library` 2293 /// This never uses the library's name (the identifier in the `library`
2362 /// declaration) as it doesn't have any meaningful rules enforced. 2294 /// declaration) as it doesn't have any meaningful rules enforced.
2363 JS.Identifier _libraryName(LibraryElement library) { 2295 JS.Identifier _libraryName(LibraryElement library) {
2364 if (library == libraryInfo.library) return _exportsVar; 2296 if (library == libraryInfo.library) return _exportsVar;
2365 return new JS.Identifier(jsLibraryName(library)); 2297 return new JS.Identifier(jsLibraryName(library));
2366 } 2298 }
2367 2299
2300 DartType getStaticType(Expression e) => rules.getStaticType(e);
2301
2368 static bool _needsImplicitThis(Element e) => 2302 static bool _needsImplicitThis(Element e) =>
2369 e is PropertyAccessorElement && !e.variable.isStatic || 2303 e is PropertyAccessorElement && !e.variable.isStatic ||
2370 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; 2304 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement;
2371 } 2305 }
2372 2306
2373 class JSGenerator extends CodeGenerator { 2307 class JSGenerator extends CodeGenerator {
2374 final JSCodeOptions options; 2308 final JSCodeOptions options;
2375 2309
2310 /// For fast lookup of extension methods, we first check the name, then do a
2311 /// (possibly expensive) subtype test to see if it matches one of the types
2312 /// that declares that method.
2313 final _extensionMethods = new HashMap<String, List<InterfaceType>>();
2314
2376 JSGenerator(String outDir, Uri root, TypeRules rules, this.options) 2315 JSGenerator(String outDir, Uri root, TypeRules rules, this.options)
2377 : super(outDir, root, rules); 2316 : super(outDir, root, rules) {
2317
2318 // TODO(jacobr): determine the the set of types with extension methods from
2319 // the annotations rather than hard coding the list once the analyzer
2320 // supports summaries.
2321 var extensionTypes = [types.listType, types.iterableType];
2322 for (var type in extensionTypes) {
2323 type = fillDynamicTypeArgs(type, rules.provider);
2324 var e = type.element;
2325 var names = new HashSet<String>()
2326 ..addAll(e.methods.map((m) => m.name))
2327 ..addAll(e.accessors.map((m) => m.name));
2328 for (var name in names) {
2329 _extensionMethods.putIfAbsent(name, () => []).add(type);
2330 }
2331 }
2332 }
2333
2334 TypeProvider get types => rules.provider;
2378 2335
2379 String generateLibrary(LibraryUnit unit, LibraryInfo info) { 2336 String generateLibrary(LibraryUnit unit, LibraryInfo info) {
2380 var jsTree = new JSCodegenVisitor(info, rules).emitLibrary(unit); 2337 var jsTree =
2338 new JSCodegenVisitor(info, rules, _extensionMethods).emitLibrary(unit);
2381 2339
2382 var outputPath = path.join(outDir, jsOutputPath(info, root)); 2340 var outputPath = path.join(outDir, jsOutputPath(info, root));
2383 new Directory(path.dirname(outputPath)).createSync(recursive: true); 2341 new Directory(path.dirname(outputPath)).createSync(recursive: true);
2384 2342
2385 if (options.emitSourceMaps) { 2343 if (options.emitSourceMaps) {
2386 var outFilename = path.basename(outputPath); 2344 var outFilename = path.basename(outputPath);
2387 var printer = new srcmaps.Printer(outFilename); 2345 var printer = new srcmaps.Printer(outFilename);
2388 _writeNode( 2346 _writeNode(
2389 new SourceMapPrintingContext(printer, path.dirname(outputPath)), 2347 new SourceMapPrintingContext(printer, path.dirname(outputPath)),
2390 jsTree); 2348 jsTree);
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after
2600 if (args.isNotEmpty && args[0] is NamedExpression) { 2558 if (args.isNotEmpty && args[0] is NamedExpression) {
2601 NamedExpression named = args[0]; 2559 NamedExpression named = args[0];
2602 if (named.name.label.name == argName && 2560 if (named.name.label.name == argName &&
2603 named.expression is StringLiteral) { 2561 named.expression is StringLiteral) {
2604 return (named.expression as StringLiteral).stringValue; 2562 return (named.expression as StringLiteral).stringValue;
2605 } 2563 }
2606 } 2564 }
2607 } 2565 }
2608 return null; 2566 return null;
2609 } 2567 }
OLDNEW
« no previous file with comments | « lib/runtime/dart_runtime.js ('k') | lib/src/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698