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 | 2 |
3 // for details. All rights reserved. Use of this source code is governed by a | 3 // for details. All rights reserved. Use of this source code is governed by a |
4 // BSD-style license that can be found in the LICENSE file. | 4 // BSD-style license that can be found in the LICENSE file. |
5 | 5 |
6 import 'dart:collection' show HashMap, HashSet; | 6 import 'dart:collection' show HashMap, HashSet; |
7 import 'dart:math' show min, max; | 7 import 'dart:math' show min, max; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
10 import 'package:analyzer/dart/ast/ast.dart'; | 10 import 'package:analyzer/dart/ast/ast.dart'; |
11 import 'package:analyzer/dart/ast/standard_ast_factory.dart'; | 11 import 'package:analyzer/dart/ast/standard_ast_factory.dart'; |
12 import 'package:analyzer/dart/ast/standard_resolution_map.dart'; | 12 import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
13 import 'package:analyzer/dart/ast/token.dart' show TokenType; | 13 import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; |
14 import 'package:analyzer/dart/element/element.dart'; | 14 import 'package:analyzer/dart/element/element.dart'; |
15 import 'package:analyzer/dart/element/type.dart'; | 15 import 'package:analyzer/dart/element/type.dart'; |
16 import 'package:analyzer/src/dart/ast/token.dart' show StringToken; | 16 import 'package:analyzer/src/dart/ast/token.dart' show StringToken; |
17 import 'package:analyzer/src/dart/element/element.dart' | 17 import 'package:analyzer/src/dart/element/element.dart' |
18 show FieldElementImpl, LocalVariableElementImpl; | 18 show FieldElementImpl, LocalVariableElementImpl; |
19 import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl; | 19 import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl; |
20 import 'package:analyzer/src/dart/sdk/sdk.dart'; | 20 import 'package:analyzer/src/dart/sdk/sdk.dart'; |
21 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; | 21 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
22 import 'package:analyzer/src/generated/resolver.dart' | 22 import 'package:analyzer/src/generated/resolver.dart' |
23 show TypeProvider, NamespaceBuilder; | 23 show TypeProvider, NamespaceBuilder; |
(...skipping 15 matching lines...) Expand all Loading... |
39 import '../js_ast/js_ast.dart' as JS; | 39 import '../js_ast/js_ast.dart' as JS; |
40 import '../js_ast/js_ast.dart' show js; | 40 import '../js_ast/js_ast.dart' show js; |
41 import 'ast_builder.dart' show AstBuilder; | 41 import 'ast_builder.dart' show AstBuilder; |
42 import 'compiler.dart' show BuildUnit, CompilerOptions, JSModuleFile; | 42 import 'compiler.dart' show BuildUnit, CompilerOptions, JSModuleFile; |
43 import 'element_helpers.dart'; | 43 import 'element_helpers.dart'; |
44 import 'extension_types.dart' show ExtensionTypeSet; | 44 import 'extension_types.dart' show ExtensionTypeSet; |
45 import 'js_interop.dart'; | 45 import 'js_interop.dart'; |
46 import 'js_metalet.dart' as JS; | 46 import 'js_metalet.dart' as JS; |
47 import 'js_names.dart' as JS; | 47 import 'js_names.dart' as JS; |
48 import 'js_typeref_codegen.dart' show JsTypeRefCodegen; | 48 import 'js_typeref_codegen.dart' show JsTypeRefCodegen; |
| 49 import 'js_typerep.dart' show JSTypeRep; |
49 import 'module_builder.dart' show pathToJSIdentifier; | 50 import 'module_builder.dart' show pathToJSIdentifier; |
50 import 'nullable_type_inference.dart' show NullableTypeInference; | 51 import 'nullable_type_inference.dart' show NullableTypeInference; |
51 import 'property_model.dart'; | 52 import 'property_model.dart'; |
52 import 'reify_coercions.dart' show CoercionReifier; | 53 import 'reify_coercions.dart' show CoercionReifier; |
53 import 'side_effect_analysis.dart' show ConstFieldVisitor, isStateless; | 54 import 'side_effect_analysis.dart' show ConstFieldVisitor, isStateless; |
54 import 'type_utilities.dart'; | 55 import 'type_utilities.dart'; |
55 | 56 |
56 /// The code generator for Dart Dev Compiler. | 57 /// The code generator for Dart Dev Compiler. |
57 /// | 58 /// |
58 /// Takes as input resolved Dart ASTs for every compilation unit in every | 59 /// Takes as input resolved Dart ASTs for every compilation unit in every |
59 /// library in the module. Produces a single JavaScript AST for the module as | 60 /// library in the module. Produces a single JavaScript AST for the module as |
60 /// output, along with its source map. | 61 // output, along with its source map. |
61 /// | 62 /// |
62 /// This class attempts to preserve identifier names and structure of the input | 63 /// This class attempts to preserve identifier names and structure of the input |
63 /// Dart code, whenever this is possible to do in the generated code. | 64 /// Dart code, whenever this is possible to do in the generated code. |
64 // | 65 /// |
65 // TODO(jmesserly): we should use separate visitors for statements and | 66 // TODO(jmesserly): we should use separate visitors for statements and |
66 // expressions. Declarations are handled directly, and many minor component | 67 // expressions. Declarations are handled directly, and many minor component |
67 // AST nodes aren't visited, so the visitor pattern isn't helping except for | 68 // AST nodes aren't visited, so the visitor pattern isn't helping except for |
68 // expressions (which result in JS.Expression) and statements | 69 // expressions (which result in JS.Expression) and statements |
69 // (which result in (JS.Statement). | 70 // (which result in (JS.Statement). |
70 class CodeGenerator extends Object | 71 class CodeGenerator extends Object |
71 with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference | 72 with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference |
72 implements AstVisitor<JS.Node> { | 73 implements AstVisitor<JS.Node> { |
73 final AnalysisContext context; | 74 final AnalysisContext context; |
74 final SummaryDataStore summaryData; | 75 final SummaryDataStore summaryData; |
75 | 76 |
76 final CompilerOptions options; | 77 final CompilerOptions options; |
77 final StrongTypeSystemImpl rules; | 78 final StrongTypeSystemImpl rules; |
| 79 JSTypeRep typeRep; |
78 | 80 |
79 /// The set of libraries we are currently compiling, and the temporaries used | 81 /// The set of libraries we are currently compiling, and the temporaries used |
80 /// to refer to them. | 82 /// to refer to them. |
81 /// | 83 /// |
82 /// We sometimes special case codegen for a single library, as it simplifies | 84 /// We sometimes special case codegen for a single library, as it simplifies |
83 /// name scoping requirements. | 85 /// name scoping requirements. |
84 final _libraries = new Map<LibraryElement, JS.Identifier>(); | 86 final _libraries = new Map<LibraryElement, JS.Identifier>(); |
85 | 87 |
86 /// Imported libraries, and the temporaries used to refer to them. | 88 /// Imported libraries, and the temporaries used to refer to them. |
87 final _imports = new Map<LibraryElement, JS.TemporaryId>(); | 89 final _imports = new Map<LibraryElement, JS.TemporaryId>(); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 | 127 |
126 /// The type provider from the current Analysis [context]. | 128 /// The type provider from the current Analysis [context]. |
127 final TypeProvider types; | 129 final TypeProvider types; |
128 | 130 |
129 final LibraryElement dartCoreLibrary; | 131 final LibraryElement dartCoreLibrary; |
130 final LibraryElement dartJSLibrary; | 132 final LibraryElement dartJSLibrary; |
131 | 133 |
132 /// The dart:async `StreamIterator<>` type. | 134 /// The dart:async `StreamIterator<>` type. |
133 final InterfaceType _asyncStreamIterator; | 135 final InterfaceType _asyncStreamIterator; |
134 | 136 |
| 137 /// The dart:core `identical` element. |
| 138 final FunctionElement _coreIdentical; |
| 139 |
135 /// The dart:_interceptors JSArray element. | 140 /// The dart:_interceptors JSArray element. |
136 final ClassElement _jsArray; | 141 final ClassElement _jsArray; |
137 | 142 |
138 final ClassElement boolClass; | 143 final ClassElement boolClass; |
139 final ClassElement intClass; | 144 final ClassElement intClass; |
140 final ClassElement interceptorClass; | 145 final ClassElement interceptorClass; |
141 final ClassElement nullClass; | 146 final ClassElement nullClass; |
142 final ClassElement numClass; | 147 final ClassElement numClass; |
143 final ClassElement objectClass; | 148 final ClassElement objectClass; |
144 final ClassElement stringClass; | 149 final ClassElement stringClass; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 /// unit. | 195 /// unit. |
191 final virtualFields = new VirtualFieldModel(); | 196 final virtualFields = new VirtualFieldModel(); |
192 | 197 |
193 CodeGenerator( | 198 CodeGenerator( |
194 AnalysisContext c, this.summaryData, this.options, this._extensionTypes) | 199 AnalysisContext c, this.summaryData, this.options, this._extensionTypes) |
195 : context = c, | 200 : context = c, |
196 rules = new StrongTypeSystemImpl(c.typeProvider), | 201 rules = new StrongTypeSystemImpl(c.typeProvider), |
197 types = c.typeProvider, | 202 types = c.typeProvider, |
198 _asyncStreamIterator = | 203 _asyncStreamIterator = |
199 _getLibrary(c, 'dart:async').getType('StreamIterator').type, | 204 _getLibrary(c, 'dart:async').getType('StreamIterator').type, |
| 205 _coreIdentical = |
| 206 _getLibrary(c, 'dart:core').publicNamespace.get('identical'), |
200 _jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), | 207 _jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), |
201 interceptorClass = | 208 interceptorClass = |
202 _getLibrary(c, 'dart:_interceptors').getType('Interceptor'), | 209 _getLibrary(c, 'dart:_interceptors').getType('Interceptor'), |
203 dartCoreLibrary = _getLibrary(c, 'dart:core'), | 210 dartCoreLibrary = _getLibrary(c, 'dart:core'), |
204 boolClass = _getLibrary(c, 'dart:core').getType('bool'), | 211 boolClass = _getLibrary(c, 'dart:core').getType('bool'), |
205 intClass = _getLibrary(c, 'dart:core').getType('int'), | 212 intClass = _getLibrary(c, 'dart:core').getType('int'), |
206 numClass = _getLibrary(c, 'dart:core').getType('num'), | 213 numClass = _getLibrary(c, 'dart:core').getType('num'), |
207 nullClass = _getLibrary(c, 'dart:core').getType('Null'), | 214 nullClass = _getLibrary(c, 'dart:core').getType('Null'), |
208 objectClass = _getLibrary(c, 'dart:core').getType('Object'), | 215 objectClass = _getLibrary(c, 'dart:core').getType('Object'), |
209 stringClass = _getLibrary(c, 'dart:core').getType('String'), | 216 stringClass = _getLibrary(c, 'dart:core').getType('String'), |
210 functionClass = _getLibrary(c, 'dart:core').getType('Function'), | 217 functionClass = _getLibrary(c, 'dart:core').getType('Function'), |
211 privateSymbolClass = | 218 privateSymbolClass = |
212 _getLibrary(c, 'dart:_internal').getType('PrivateSymbol'), | 219 _getLibrary(c, 'dart:_internal').getType('PrivateSymbol'), |
213 dartJSLibrary = _getLibrary(c, 'dart:js'); | 220 dartJSLibrary = _getLibrary(c, 'dart:js') { |
| 221 typeRep = new JSTypeRep(rules, types); |
| 222 } |
214 | 223 |
215 Element get currentElement => _currentElements.last; | 224 Element get currentElement => _currentElements.last; |
216 | 225 |
217 LibraryElement get currentLibrary => currentElement.library; | 226 LibraryElement get currentLibrary => currentElement.library; |
218 | 227 |
219 /// The main entry point to JavaScript code generation. | 228 /// The main entry point to JavaScript code generation. |
220 /// | 229 /// |
221 /// Takes the metadata for the build unit, as well as resolved trees and | 230 /// Takes the metadata for the build unit, as well as resolved trees and |
222 /// errors, and computes the output module code and optionally the source map. | 231 /// errors, and computes the output module code and optionally the source map. |
223 JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits, | 232 JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits, |
(...skipping 466 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
690 Expression fromExpr = node.expression; | 699 Expression fromExpr = node.expression; |
691 var from = getStaticType(fromExpr); | 700 var from = getStaticType(fromExpr); |
692 var to = node.type.type; | 701 var to = node.type.type; |
693 | 702 |
694 JS.Expression jsFrom = _visit(fromExpr); | 703 JS.Expression jsFrom = _visit(fromExpr); |
695 | 704 |
696 // Skip the cast if it's not needed. | 705 // Skip the cast if it's not needed. |
697 if (rules.isSubtypeOf(from, to)) return jsFrom; | 706 if (rules.isSubtypeOf(from, to)) return jsFrom; |
698 | 707 |
699 // All Dart number types map to a JS double. | 708 // All Dart number types map to a JS double. |
700 if (_isNumberInJS(from) && _isNumberInJS(to)) { | 709 if (typeRep.isNumber(from) && typeRep.isNumber(to)) { |
701 // Make sure to check when converting to int. | 710 // Make sure to check when converting to int. |
702 if (from != types.intType && to == types.intType) { | 711 if (from != types.intType && to == types.intType) { |
703 // TODO(jmesserly): fuse this with notNull check. | 712 // TODO(jmesserly): fuse this with notNull check. |
704 return _callHelper('asInt(#)', jsFrom); | 713 return _callHelper('asInt(#)', jsFrom); |
705 } | 714 } |
706 | 715 |
707 // A no-op in JavaScript. | 716 // A no-op in JavaScript. |
708 return jsFrom; | 717 return jsFrom; |
709 } | 718 } |
710 | 719 |
(...skipping 27 matching lines...) Expand all Loading... |
738 result = js.call('#.is(#)', [castType, lhs]); | 747 result = js.call('#.is(#)', [castType, lhs]); |
739 } | 748 } |
740 | 749 |
741 if (node.notOperator != null) { | 750 if (node.notOperator != null) { |
742 return js.call('!#', result); | 751 return js.call('!#', result); |
743 } | 752 } |
744 return result; | 753 return result; |
745 } | 754 } |
746 | 755 |
747 String _jsTypeofName(DartType t) { | 756 String _jsTypeofName(DartType t) { |
748 if (_isNumberInJS(t)) return 'number'; | 757 if (typeRep.isNumber(t)) return 'number'; |
749 if (t == types.stringType) return 'string'; | 758 if (t == types.stringType) return 'string'; |
750 if (t == types.boolType) return 'boolean'; | 759 if (t == types.boolType) return 'boolean'; |
751 return null; | 760 return null; |
752 } | 761 } |
753 | 762 |
754 @override | 763 @override |
755 visitFunctionTypeAlias(FunctionTypeAlias node) => _emitTypedef(node); | 764 visitFunctionTypeAlias(FunctionTypeAlias node) => _emitTypedef(node); |
756 | 765 |
757 @override | 766 @override |
758 visitGenericTypeAlias(GenericTypeAlias node) => _emitTypedef(node); | 767 visitGenericTypeAlias(GenericTypeAlias node) => _emitTypedef(node); |
(...skipping 2762 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3521 InvocationExpression node, JS.Expression fn, List<JS.Expression> args) { | 3530 InvocationExpression node, JS.Expression fn, List<JS.Expression> args) { |
3522 var typeArgs = _emitInvokeTypeArguments(node); | 3531 var typeArgs = _emitInvokeTypeArguments(node); |
3523 if (typeArgs != null) { | 3532 if (typeArgs != null) { |
3524 return _callHelper( | 3533 return _callHelper( |
3525 'dgcall(#, #, #)', [fn, new JS.ArrayInitializer(typeArgs), args]); | 3534 'dgcall(#, #, #)', [fn, new JS.ArrayInitializer(typeArgs), args]); |
3526 } else { | 3535 } else { |
3527 return _callHelper('dcall(#, #)', [fn, args]); | 3536 return _callHelper('dcall(#, #)', [fn, args]); |
3528 } | 3537 } |
3529 } | 3538 } |
3530 | 3539 |
| 3540 bool _doubleEqIsIdentity(Expression left, Expression right) { |
| 3541 // If we statically know LHS or RHS is null we can use ==. |
| 3542 if (_isNull(left) || _isNull(right)) return true; |
| 3543 // If the representation of the two types will not induce conversion in |
| 3544 // JS then we can use == . |
| 3545 return !typeRep.equalityMayConvert(left.staticType, right.staticType); |
| 3546 } |
| 3547 |
| 3548 bool _tripleEqIsIdentity(Expression left, Expression right) { |
| 3549 // If either is non-nullable, then we don't need to worry about |
| 3550 // equating null and undefined, and so we can use triple equals. |
| 3551 return !isNullable(left) || !isNullable(right); |
| 3552 } |
| 3553 |
| 3554 bool _isCoreIdentical(Expression node) { |
| 3555 return node is Identifier && node.staticElement == _coreIdentical; |
| 3556 } |
| 3557 |
| 3558 JS.Expression _emitJSDoubleEq(List<JS.Expression> args, |
| 3559 {bool negated = false}) { |
| 3560 var op = negated ? '# != #' : '# == #'; |
| 3561 return js.call(op, args); |
| 3562 } |
| 3563 |
| 3564 JS.Expression _emitJSTripleEq(List<JS.Expression> args, |
| 3565 {bool negated = false}) { |
| 3566 var op = negated ? '# !== #' : '# === #'; |
| 3567 return js.call(op, args); |
| 3568 } |
| 3569 |
| 3570 JS.Expression _emitCoreIdenticalCall(List<Expression> arguments, |
| 3571 {bool negated = false}) { |
| 3572 if (arguments.length != 2) { |
| 3573 // Shouldn't happen in typechecked code |
| 3574 return _callHelper( |
| 3575 'throw(Error("compile error: calls to `identical` require 2 args")'); |
| 3576 } |
| 3577 var left = arguments[0]; |
| 3578 var right = arguments[1]; |
| 3579 var args = [_visit(left), _visit(right)]; |
| 3580 if (_tripleEqIsIdentity(left, right)) { |
| 3581 return _emitJSTripleEq(args, negated: negated); |
| 3582 } |
| 3583 if (_doubleEqIsIdentity(left, right)) { |
| 3584 return _emitJSDoubleEq(args, negated: negated); |
| 3585 } |
| 3586 var bang = negated ? '!' : ''; |
| 3587 return js.call( |
| 3588 "${bang}#", new JS.Call(_emitTopLevelName(_coreIdentical), args)); |
| 3589 } |
| 3590 |
3531 /// Emits a function call, to a top-level function, local function, or | 3591 /// Emits a function call, to a top-level function, local function, or |
3532 /// an expression. | 3592 /// an expression. |
3533 JS.Expression _emitFunctionCall(InvocationExpression node, | 3593 JS.Expression _emitFunctionCall(InvocationExpression node, |
3534 [Expression function]) { | 3594 [Expression function]) { |
3535 if (function == null) { | 3595 if (function == null) { |
3536 function = node.function; | 3596 function = node.function; |
3537 } | 3597 } |
| 3598 if (_isCoreIdentical(function)) { |
| 3599 return _emitCoreIdenticalCall(node.argumentList.arguments); |
| 3600 } |
3538 var fn = _visit(function); | 3601 var fn = _visit(function); |
3539 var args = _emitArgumentList(node.argumentList); | 3602 var args = _emitArgumentList(node.argumentList); |
3540 if (isDynamicInvoke(function)) { | 3603 if (isDynamicInvoke(function)) { |
3541 return _emitDynamicInvoke(node, fn, args); | 3604 return _emitDynamicInvoke(node, fn, args); |
3542 } else { | |
3543 return new JS.Call(_applyInvokeTypeArguments(fn, node), args); | |
3544 } | 3605 } |
| 3606 return new JS.Call(_applyInvokeTypeArguments(fn, node), args); |
3545 } | 3607 } |
3546 | 3608 |
3547 JS.Expression _applyInvokeTypeArguments( | 3609 JS.Expression _applyInvokeTypeArguments( |
3548 JS.Expression target, InvocationExpression node) { | 3610 JS.Expression target, InvocationExpression node) { |
3549 var typeArgs = _emitInvokeTypeArguments(node); | 3611 var typeArgs = _emitInvokeTypeArguments(node); |
3550 if (typeArgs == null) return target; | 3612 if (typeArgs == null) return target; |
3551 return new JS.Call(target, typeArgs); | 3613 return new JS.Call(target, typeArgs); |
3552 } | 3614 } |
3553 | 3615 |
3554 List<JS.Expression> _emitInvokeTypeArguments(InvocationExpression node) { | 3616 List<JS.Expression> _emitInvokeTypeArguments(InvocationExpression node) { |
(...skipping 525 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4080 return stringValue != null | 4142 return stringValue != null |
4081 ? js.escapedString(stringValue) | 4143 ? js.escapedString(stringValue) |
4082 : new JS.LiteralNull(); | 4144 : new JS.LiteralNull(); |
4083 } | 4145 } |
4084 throw new StateError('failed to evaluate $node'); | 4146 throw new StateError('failed to evaluate $node'); |
4085 } | 4147 } |
4086 return _emitInstanceCreationExpression( | 4148 return _emitInstanceCreationExpression( |
4087 element, type, name, node.argumentList, node.isConst); | 4149 element, type, name, node.argumentList, node.isConst); |
4088 } | 4150 } |
4089 | 4151 |
4090 /// True if this type is built-in to JS, and we use the values unwrapped. | 4152 bool isPrimitiveType(DartType t) => typeRep.isPrimitive(t); |
4091 /// For these types we generate a calling convention via static | |
4092 /// "extension methods". This allows types to be extended without adding | |
4093 /// extensions directly on the prototype. | |
4094 bool isPrimitiveType(DartType t) => | |
4095 typeIsPrimitiveInJS(t) || t == types.stringType; | |
4096 | |
4097 bool typeIsPrimitiveInJS(DartType t) => | |
4098 _isNumberInJS(t) || t == types.boolType; | |
4099 | |
4100 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => | |
4101 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); | |
4102 | |
4103 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); | |
4104 | 4153 |
4105 JS.Expression notNull(Expression expr) { | 4154 JS.Expression notNull(Expression expr) { |
4106 if (expr == null) return null; | 4155 if (expr == null) return null; |
4107 var jsExpr = _visit(expr); | 4156 var jsExpr = _visit(expr); |
4108 if (!isNullable(expr)) return jsExpr; | 4157 if (!isNullable(expr)) return jsExpr; |
4109 return _callHelper('notNull(#)', jsExpr); | 4158 return _callHelper('notNull(#)', jsExpr); |
4110 } | 4159 } |
4111 | 4160 |
| 4161 JS.Expression _emitEqualityOperator(BinaryExpression node, Token op) { |
| 4162 var left = node.leftOperand; |
| 4163 var right = node.rightOperand; |
| 4164 var leftType = left.staticType; |
| 4165 var negated = op.type == TokenType.BANG_EQ; |
| 4166 |
| 4167 if (left is SuperExpression) { |
| 4168 return _emitSend(left, op.lexeme, [right]); |
| 4169 } |
| 4170 |
| 4171 // Equality on enums and primitives is identity. |
| 4172 // TODO(leafp): Walk the class hierarchy and check to see if == was |
| 4173 // overridden |
| 4174 var isEnum = leftType is InterfaceType && leftType.element.isEnum; |
| 4175 var usesIdentity = typeRep.isPrimitive(leftType) || |
| 4176 isEnum || |
| 4177 _isNull(left) || |
| 4178 _isNull(right); |
| 4179 |
| 4180 // If we know that the left type uses identity for equality, we can |
| 4181 // sometimes emit better code. |
| 4182 if (usesIdentity) { |
| 4183 return _emitCoreIdenticalCall([left, right], negated: negated); |
| 4184 } |
| 4185 |
| 4186 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
| 4187 var leftElement = leftType.element; |
| 4188 |
| 4189 // If either is null, we can use simple equality. |
| 4190 // We need to equate null and undefined, so if both are nullable |
| 4191 // (but not known to be null), we cannot directly use JS == |
| 4192 // unless we know that conversion will not happen. |
| 4193 // Functions may or may not have an [dartx[`==`]] method attached. |
| 4194 // - If they are tearoffs they will, otherwise they won't and equality is |
| 4195 // identity. |
| 4196 // TODO(leafp): consider fixing this. |
| 4197 // |
| 4198 // Native types may not have equality on the prototype. |
| 4199 // If left is not nullable, then we don't need to worry about |
| 4200 // null/undefined. |
| 4201 // TODO(leafp): consider using (left || dart.EQ)['=='](right)) |
| 4202 // when left is nullable but not falsey |
| 4203 if ((leftElement is ClassElement && _isJSNative(leftElement)) || |
| 4204 typeRep.isUnknown(leftType) || |
| 4205 leftType is FunctionType || |
| 4206 isNullable(left)) { |
| 4207 // Fall back to equality for now. |
| 4208 var code = '${bang}#.equals(#, #)'; |
| 4209 return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); |
| 4210 } |
| 4211 |
| 4212 var name = _emitMemberName('==', type: leftType); |
| 4213 var code = '${bang}#[#](#)'; |
| 4214 return js.call(code, [_visit(left), name, _visit(right)]); |
| 4215 } |
| 4216 |
4112 @override | 4217 @override |
4113 JS.Expression visitBinaryExpression(BinaryExpression node) { | 4218 JS.Expression visitBinaryExpression(BinaryExpression node) { |
4114 var op = node.operator; | 4219 var op = node.operator; |
4115 | 4220 |
4116 // The operands of logical boolean operators are subject to boolean | 4221 // The operands of logical boolean operators are subject to boolean |
4117 // conversion. | 4222 // conversion. |
4118 if (op.type == TokenType.BAR_BAR || | 4223 if (op.type == TokenType.BAR_BAR || |
4119 op.type == TokenType.AMPERSAND_AMPERSAND) { | 4224 op.type == TokenType.AMPERSAND_AMPERSAND) { |
4120 return _visitTest(node); | 4225 return _visitTest(node); |
4121 } | 4226 } |
4122 | 4227 |
| 4228 if (op.type.isEqualityOperator) return _emitEqualityOperator(node, op); |
| 4229 |
4123 var left = node.leftOperand; | 4230 var left = node.leftOperand; |
4124 var right = node.rightOperand; | 4231 var right = node.rightOperand; |
4125 | 4232 |
4126 var leftType = getStaticType(left); | |
4127 var rightType = getStaticType(right); | |
4128 | |
4129 var code; | |
4130 if (op.type.isEqualityOperator) { | |
4131 // If we statically know LHS or RHS is null we can generate a clean check. | |
4132 // We can also do this if both sides are the same primitive type. | |
4133 if (_canUsePrimitiveEquality(left, right)) { | |
4134 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; | |
4135 } else if (left is SuperExpression) { | |
4136 return _emitSend(left, op.lexeme, [right]); | |
4137 } else { | |
4138 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; | |
4139 code = '${bang}#.equals(#, #)'; | |
4140 return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); | |
4141 } | |
4142 return js.call(code, [_visit(left), _visit(right)]); | |
4143 } | |
4144 | |
4145 if (op.type.lexeme == '??') { | 4233 if (op.type.lexeme == '??') { |
4146 // TODO(jmesserly): leave RHS for debugging? | 4234 // TODO(jmesserly): leave RHS for debugging? |
4147 // This should be a hint or warning for dead code. | 4235 // This should be a hint or warning for dead code. |
4148 if (!isNullable(left)) return _visit(left); | 4236 if (!isNullable(left)) return _visit(left); |
4149 | 4237 |
4150 var vars = <JS.MetaLetVariable, JS.Expression>{}; | 4238 var vars = <JS.MetaLetVariable, JS.Expression>{}; |
4151 // Desugar `l ?? r` as `l != null ? l : r` | 4239 // Desugar `l ?? r` as `l != null ? l : r` |
4152 var l = _visit(_bindValue(vars, 'l', left, context: left)); | 4240 var l = _visit(_bindValue(vars, 'l', left, context: left)); |
4153 return new JS.MetaLet(vars, [ | 4241 return new JS.MetaLet(vars, [ |
4154 js.call('# != null ? # : #', [l, l, _visit(right)]) | 4242 js.call('# != null ? # : #', [l, l, _visit(right)]) |
4155 ]); | 4243 ]); |
4156 } | 4244 } |
4157 | 4245 |
4158 if (binaryOperationIsPrimitive(leftType, rightType) || | 4246 var leftType = getStaticType(left); |
| 4247 var rightType = getStaticType(right); |
| 4248 |
| 4249 if (typeRep.binaryOperationIsPrimitive(leftType, rightType) || |
4159 leftType == types.stringType && op.type == TokenType.PLUS) { | 4250 leftType == types.stringType && op.type == TokenType.PLUS) { |
4160 // special cases where we inline the operation | 4251 // special cases where we inline the operation |
4161 // these values are assumed to be non-null (determined by the checker) | 4252 // these values are assumed to be non-null (determined by the checker) |
4162 // TODO(jmesserly): it would be nice to just inline the method from core, | 4253 // TODO(jmesserly): it would be nice to just inline the method from core, |
4163 // instead of special cases here. | 4254 // instead of special cases here. |
4164 JS.Expression binary(String code) { | 4255 JS.Expression binary(String code) { |
4165 return js.call(code, [notNull(left), notNull(right)]); | 4256 return js.call(code, [notNull(left), notNull(right)]); |
4166 } | 4257 } |
4167 | 4258 |
4168 JS.Expression bitwise(String code) { | 4259 JS.Expression bitwise(String code) { |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4383 } | 4474 } |
4384 } | 4475 } |
4385 int value = _asIntInRange(expr, 0, 0x7fffffff); | 4476 int value = _asIntInRange(expr, 0, 0x7fffffff); |
4386 if (value != null) return value.bitLength; | 4477 if (value != null) return value.bitLength; |
4387 return MAX; | 4478 return MAX; |
4388 } | 4479 } |
4389 | 4480 |
4390 return bitWidth(expr, 0) < 32; | 4481 return bitWidth(expr, 0) < 32; |
4391 } | 4482 } |
4392 | 4483 |
4393 /// If the type [t] is [int] or [double], or a type parameter | 4484 bool _isNull(Expression expr) => |
4394 /// bounded by [int], [double] or [num] returns [num]. | 4485 expr is NullLiteral || getStaticType(expr).isDartCoreNull; |
4395 /// Otherwise returns [t]. | |
4396 DartType _canonicalizeNumTypes(DartType t) { | |
4397 var numType = types.numType; | |
4398 if (rules.isSubtypeOf(t, numType)) return numType; | |
4399 return t; | |
4400 } | |
4401 | |
4402 bool _canUsePrimitiveEquality(Expression left, Expression right) { | |
4403 if (_isNull(left) || _isNull(right)) return true; | |
4404 | |
4405 var leftType = _canonicalizeNumTypes(getStaticType(left)); | |
4406 var rightType = _canonicalizeNumTypes(getStaticType(right)); | |
4407 return isPrimitiveType(leftType) && leftType == rightType; | |
4408 } | |
4409 | |
4410 bool _isNull(Expression expr) => expr is NullLiteral; | |
4411 | 4486 |
4412 SimpleIdentifier _createTemporary(String name, DartType type, | 4487 SimpleIdentifier _createTemporary(String name, DartType type, |
4413 {bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { | 4488 {bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { |
4414 // We use an invalid source location to signal that this is a temporary. | 4489 // We use an invalid source location to signal that this is a temporary. |
4415 // See [_isTemporary]. | 4490 // See [_isTemporary]. |
4416 // TODO(jmesserly): alternatives are | 4491 // TODO(jmesserly): alternatives are |
4417 // * (ab)use Element.isSynthetic, which isn't currently used for | 4492 // * (ab)use Element.isSynthetic, which isn't currently used for |
4418 // LocalVariableElementImpl, so we could repurpose to mean "temp". | 4493 // LocalVariableElementImpl, so we could repurpose to mean "temp". |
4419 // * add a new property to LocalVariableElementImpl. | 4494 // * add a new property to LocalVariableElementImpl. |
4420 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 4495 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4545 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) | 4620 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
4546 /// | 4621 /// |
4547 /// The [JS.MetaLet] nodes automatically simplify themselves if they can. | 4622 /// The [JS.MetaLet] nodes automatically simplify themselves if they can. |
4548 /// For example, if the result value is not used, then `t` goes away. | 4623 /// For example, if the result value is not used, then `t` goes away. |
4549 @override | 4624 @override |
4550 JS.Expression visitPostfixExpression(PostfixExpression node) { | 4625 JS.Expression visitPostfixExpression(PostfixExpression node) { |
4551 var op = node.operator; | 4626 var op = node.operator; |
4552 var expr = node.operand; | 4627 var expr = node.operand; |
4553 | 4628 |
4554 var dispatchType = getStaticType(expr); | 4629 var dispatchType = getStaticType(expr); |
4555 if (unaryOperationIsPrimitive(dispatchType)) { | 4630 if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
4556 if (!isNullable(expr)) { | 4631 if (!isNullable(expr)) { |
4557 return js.call('#$op', _visit(expr)); | 4632 return js.call('#$op', _visit(expr)); |
4558 } | 4633 } |
4559 } | 4634 } |
4560 | 4635 |
4561 assert(op.lexeme == '++' || op.lexeme == '--'); | 4636 assert(op.lexeme == '++' || op.lexeme == '--'); |
4562 | 4637 |
4563 // Handle the left hand side, to ensure each of its subexpressions are | 4638 // Handle the left hand side, to ensure each of its subexpressions are |
4564 // evaluated only once. | 4639 // evaluated only once. |
4565 var vars = <JS.MetaLetVariable, JS.Expression>{}; | 4640 var vars = <JS.MetaLetVariable, JS.Expression>{}; |
(...skipping 16 matching lines...) Expand all Loading... |
4582 JS.Expression visitPrefixExpression(PrefixExpression node) { | 4657 JS.Expression visitPrefixExpression(PrefixExpression node) { |
4583 var op = node.operator; | 4658 var op = node.operator; |
4584 | 4659 |
4585 // Logical negation, `!e`, is a boolean conversion context since it is | 4660 // Logical negation, `!e`, is a boolean conversion context since it is |
4586 // defined as `e ? false : true`. | 4661 // defined as `e ? false : true`. |
4587 if (op.lexeme == '!') return _visitTest(node); | 4662 if (op.lexeme == '!') return _visitTest(node); |
4588 | 4663 |
4589 var expr = node.operand; | 4664 var expr = node.operand; |
4590 | 4665 |
4591 var dispatchType = getStaticType(expr); | 4666 var dispatchType = getStaticType(expr); |
4592 if (unaryOperationIsPrimitive(dispatchType)) { | 4667 if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
4593 if (op.lexeme == '~') { | 4668 if (op.lexeme == '~') { |
4594 if (_isNumberInJS(dispatchType)) { | 4669 if (typeRep.isNumber(dispatchType)) { |
4595 JS.Expression jsExpr = js.call('~#', notNull(expr)); | 4670 JS.Expression jsExpr = js.call('~#', notNull(expr)); |
4596 return _coerceBitOperationResultToUnsigned(node, jsExpr); | 4671 return _coerceBitOperationResultToUnsigned(node, jsExpr); |
4597 } | 4672 } |
4598 return _emitSend(expr, op.lexeme[0], []); | 4673 return _emitSend(expr, op.lexeme[0], []); |
4599 } | 4674 } |
4600 if (!isNullable(expr)) { | 4675 if (!isNullable(expr)) { |
4601 return js.call('$op#', _visit(expr)); | 4676 return js.call('$op#', _visit(expr)); |
4602 } | 4677 } |
4603 if (op.lexeme == '++' || op.lexeme == '--') { | 4678 if (op.lexeme == '++' || op.lexeme == '--') { |
4604 // We need a null check, so the increment must be expanded out. | 4679 // We need a null check, so the increment must be expanded out. |
(...skipping 692 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5297 /// etc.), where conversions and null checks are implemented via `dart.test` | 5372 /// etc.), where conversions and null checks are implemented via `dart.test` |
5298 /// to give a more helpful message. | 5373 /// to give a more helpful message. |
5299 // TODO(sra): When nullablility is available earlier, it would be cleaner to | 5374 // TODO(sra): When nullablility is available earlier, it would be cleaner to |
5300 // build an input AST where the boolean conversion is a single AST node. | 5375 // build an input AST where the boolean conversion is a single AST node. |
5301 JS.Expression _visitTest(Expression node) { | 5376 JS.Expression _visitTest(Expression node) { |
5302 JS.Expression finish(JS.Expression result) { | 5377 JS.Expression finish(JS.Expression result) { |
5303 return annotate(result, node); | 5378 return annotate(result, node); |
5304 } | 5379 } |
5305 | 5380 |
5306 if (node is PrefixExpression && node.operator.lexeme == '!') { | 5381 if (node is PrefixExpression && node.operator.lexeme == '!') { |
| 5382 // TODO(leafp): consider a peephole opt for identical |
| 5383 // and == here. |
5307 return finish(js.call('!#', _visitTest(node.operand))); | 5384 return finish(js.call('!#', _visitTest(node.operand))); |
5308 } | 5385 } |
5309 if (node is ParenthesizedExpression) { | 5386 if (node is ParenthesizedExpression) { |
5310 return finish(_visitTest(node.expression)); | 5387 return finish(_visitTest(node.expression)); |
5311 } | 5388 } |
5312 if (node is BinaryExpression) { | 5389 if (node is BinaryExpression) { |
5313 JS.Expression shortCircuit(String code) { | 5390 JS.Expression shortCircuit(String code) { |
5314 return finish(js.call(code, | 5391 return finish(js.call(code, |
5315 [_visitTest(node.leftOperand), _visitTest(node.rightOperand)])); | 5392 [_visitTest(node.leftOperand), _visitTest(node.rightOperand)])); |
5316 } | 5393 } |
5317 | 5394 |
5318 var op = node.operator.type.lexeme; | 5395 var op = node.operator.type.lexeme; |
5319 if (op == '&&') return shortCircuit('# && #'); | 5396 if (op == '&&') return shortCircuit('# && #'); |
5320 if (op == '||') return shortCircuit('# || #'); | 5397 if (op == '||') return shortCircuit('# || #'); |
5321 } | 5398 } |
5322 if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { | 5399 if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { |
5323 assert(node.staticType == types.boolType); | 5400 assert(node.staticType == types.boolType); |
5324 return _callHelper('test(#)', _visit(node.expression)); | 5401 return _callHelper('dtest(#)', _visit(node.expression)); |
5325 } | 5402 } |
5326 JS.Expression result = _visit(node); | 5403 JS.Expression result = _visit(node); |
5327 if (isNullable(node)) result = _callHelper('test(#)', result); | 5404 if (isNullable(node)) result = _callHelper('test(#)', result); |
5328 return result; | 5405 return result; |
5329 } | 5406 } |
5330 | 5407 |
5331 /// Like [_emitMemberName], but for declaration sites. | 5408 /// Like [_emitMemberName], but for declaration sites. |
5332 /// | 5409 /// |
5333 /// Unlike call sites, we always have an element available, so we can use it | 5410 /// Unlike call sites, we always have an element available, so we can use it |
5334 /// directly rather than computing the relevant options for [_emitMemberName]. | 5411 /// directly rather than computing the relevant options for [_emitMemberName]. |
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5515 } | 5592 } |
5516 | 5593 |
5517 T annotate<T extends JS.Node>(T node, AstNode original, [Element element]) { | 5594 T annotate<T extends JS.Node>(T node, AstNode original, [Element element]) { |
5518 if (options.closure && element != null) { | 5595 if (options.closure && element != null) { |
5519 node.closureAnnotation = | 5596 node.closureAnnotation = |
5520 closureAnnotationFor(node, original, element, namedArgumentTemp.name); | 5597 closureAnnotationFor(node, original, element, namedArgumentTemp.name); |
5521 } | 5598 } |
5522 return node..sourceInformation = original; | 5599 return node..sourceInformation = original; |
5523 } | 5600 } |
5524 | 5601 |
5525 /// Returns true if this is any kind of object represented by `Number` in JS. | |
5526 /// | |
5527 /// In practice, this is 4 types: num, int, double, and JSNumber. | |
5528 /// | |
5529 /// JSNumber is the type that actually "implements" all numbers, hence it's | |
5530 /// a subtype of int and double (and num). It's in our "dart:_interceptors". | |
5531 bool _isNumberInJS(DartType t) => | |
5532 rules.isSubtypeOf(t, types.numType) && | |
5533 !rules.isSubtypeOf(t, types.nullType); | |
5534 | |
5535 /// Return true if this is one of the methods/properties on all Dart Objects | 5602 /// Return true if this is one of the methods/properties on all Dart Objects |
5536 /// (toString, hashCode, noSuchMethod, runtimeType). | 5603 /// (toString, hashCode, noSuchMethod, runtimeType). |
5537 /// | 5604 /// |
5538 /// Operator == is excluded, as it is handled as part of the equality binary | 5605 /// Operator == is excluded, as it is handled as part of the equality binary |
5539 /// operator. | 5606 /// operator. |
5540 bool isObjectMember(String name) { | 5607 bool isObjectMember(String name) { |
5541 // We could look these up on Object, but we have hard coded runtime helpers | 5608 // We could look these up on Object, but we have hard coded runtime helpers |
5542 // so it's not really providing any benefit. | 5609 // so it's not really providing any benefit. |
5543 switch (name) { | 5610 switch (name) { |
5544 case 'hashCode': | 5611 case 'hashCode': |
(...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5828 if (targetIdentifier.staticElement is! PrefixElement) return false; | 5895 if (targetIdentifier.staticElement is! PrefixElement) return false; |
5829 var prefix = targetIdentifier.staticElement as PrefixElement; | 5896 var prefix = targetIdentifier.staticElement as PrefixElement; |
5830 | 5897 |
5831 // The library the prefix is referring to must come from a deferred import. | 5898 // The library the prefix is referring to must come from a deferred import. |
5832 var containingLibrary = resolutionMap | 5899 var containingLibrary = resolutionMap |
5833 .elementDeclaredByCompilationUnit(target.root as CompilationUnit) | 5900 .elementDeclaredByCompilationUnit(target.root as CompilationUnit) |
5834 .library; | 5901 .library; |
5835 var imports = containingLibrary.getImportsWithPrefix(prefix); | 5902 var imports = containingLibrary.getImportsWithPrefix(prefix); |
5836 return imports.length == 1 && imports[0].isDeferred; | 5903 return imports.length == 1 && imports[0].isDeferred; |
5837 } | 5904 } |
OLD | NEW |