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'; |
(...skipping 28 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 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
198 _asyncStreamIterator = | 200 _asyncStreamIterator = |
199 _getLibrary(c, 'dart:async').getType('StreamIterator').type, | 201 _getLibrary(c, 'dart:async').getType('StreamIterator').type, |
200 _jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), | 202 _jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), |
201 interceptorClass = | 203 interceptorClass = |
202 _getLibrary(c, 'dart:_interceptors').getType('Interceptor'), | 204 _getLibrary(c, 'dart:_interceptors').getType('Interceptor'), |
203 dartCoreLibrary = _getLibrary(c, 'dart:core'), | 205 dartCoreLibrary = _getLibrary(c, 'dart:core'), |
204 boolClass = _getLibrary(c, 'dart:core').getType('bool'), | 206 boolClass = _getLibrary(c, 'dart:core').getType('bool'), |
205 intClass = _getLibrary(c, 'dart:core').getType('int'), | 207 intClass = _getLibrary(c, 'dart:core').getType('int'), |
206 numClass = _getLibrary(c, 'dart:core').getType('num'), | 208 numClass = _getLibrary(c, 'dart:core').getType('num'), |
207 nullClass = _getLibrary(c, 'dart:core').getType('Null'), | 209 nullClass = _getLibrary(c, 'dart:core').getType('Null'), |
208 objectClass = _getLibrary(c, 'dart:core').getType('Object'), | 210 objectClass = _getLibrary(c, 'dart:core').getType('Object'), |
Jennifer Messerly
2017/06/26 22:42:57
we may want to grab the FunctionElement for `ident
Leaf
2017/06/30 21:55:31
Done.
| |
209 stringClass = _getLibrary(c, 'dart:core').getType('String'), | 211 stringClass = _getLibrary(c, 'dart:core').getType('String'), |
210 functionClass = _getLibrary(c, 'dart:core').getType('Function'), | 212 functionClass = _getLibrary(c, 'dart:core').getType('Function'), |
211 privateSymbolClass = | 213 privateSymbolClass = |
212 _getLibrary(c, 'dart:_internal').getType('PrivateSymbol'), | 214 _getLibrary(c, 'dart:_internal').getType('PrivateSymbol'), |
213 dartJSLibrary = _getLibrary(c, 'dart:js'); | 215 dartJSLibrary = _getLibrary(c, 'dart:js') { |
216 typeRep = new JSTypeRep(rules, types); | |
217 } | |
214 | 218 |
215 Element get currentElement => _currentElements.last; | 219 Element get currentElement => _currentElements.last; |
216 | 220 |
217 LibraryElement get currentLibrary => currentElement.library; | 221 LibraryElement get currentLibrary => currentElement.library; |
218 | 222 |
219 /// The main entry point to JavaScript code generation. | 223 /// The main entry point to JavaScript code generation. |
220 /// | 224 /// |
221 /// Takes the metadata for the build unit, as well as resolved trees and | 225 /// 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. | 226 /// errors, and computes the output module code and optionally the source map. |
223 JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits, | 227 JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits, |
(...skipping 467 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
691 Expression fromExpr = node.expression; | 695 Expression fromExpr = node.expression; |
692 var from = getStaticType(fromExpr); | 696 var from = getStaticType(fromExpr); |
693 var to = node.type.type; | 697 var to = node.type.type; |
694 | 698 |
695 JS.Expression jsFrom = _visit(fromExpr); | 699 JS.Expression jsFrom = _visit(fromExpr); |
696 | 700 |
697 // Skip the cast if it's not needed. | 701 // Skip the cast if it's not needed. |
698 if (rules.isSubtypeOf(from, to)) return jsFrom; | 702 if (rules.isSubtypeOf(from, to)) return jsFrom; |
699 | 703 |
700 // All Dart number types map to a JS double. | 704 // All Dart number types map to a JS double. |
701 if (_isNumberInJS(from) && _isNumberInJS(to)) { | 705 if (typeRep.isNumber(from) && typeRep.isNumber(to)) { |
702 // Make sure to check when converting to int. | 706 // Make sure to check when converting to int. |
703 if (from != types.intType && to == types.intType) { | 707 if (from != types.intType && to == types.intType) { |
704 // TODO(jmesserly): fuse this with notNull check. | 708 // TODO(jmesserly): fuse this with notNull check. |
705 return _callHelper('asInt(#)', jsFrom); | 709 return _callHelper('asInt(#)', jsFrom); |
706 } | 710 } |
707 | 711 |
708 // A no-op in JavaScript. | 712 // A no-op in JavaScript. |
709 return jsFrom; | 713 return jsFrom; |
710 } | 714 } |
711 | 715 |
(...skipping 27 matching lines...) Expand all Loading... | |
739 result = js.call('#.is(#)', [castType, lhs]); | 743 result = js.call('#.is(#)', [castType, lhs]); |
740 } | 744 } |
741 | 745 |
742 if (node.notOperator != null) { | 746 if (node.notOperator != null) { |
743 return js.call('!#', result); | 747 return js.call('!#', result); |
744 } | 748 } |
745 return result; | 749 return result; |
746 } | 750 } |
747 | 751 |
748 String _jsTypeofName(DartType t) { | 752 String _jsTypeofName(DartType t) { |
749 if (_isNumberInJS(t)) return 'number'; | 753 if (typeRep.isNumber(t)) return 'number'; |
750 if (t == types.stringType) return 'string'; | 754 if (t == types.stringType) return 'string'; |
751 if (t == types.boolType) return 'boolean'; | 755 if (t == types.boolType) return 'boolean'; |
752 return null; | 756 return null; |
753 } | 757 } |
754 | 758 |
755 @override | 759 @override |
756 visitFunctionTypeAlias(FunctionTypeAlias node) => _emitTypedef(node); | 760 visitFunctionTypeAlias(FunctionTypeAlias node) => _emitTypedef(node); |
757 | 761 |
758 @override | 762 @override |
759 visitGenericTypeAlias(GenericTypeAlias node) => _emitTypedef(node); | 763 visitGenericTypeAlias(GenericTypeAlias node) => _emitTypedef(node); |
(...skipping 2752 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3512 InvocationExpression node, JS.Expression fn, List<JS.Expression> args) { | 3516 InvocationExpression node, JS.Expression fn, List<JS.Expression> args) { |
3513 var typeArgs = _emitInvokeTypeArguments(node); | 3517 var typeArgs = _emitInvokeTypeArguments(node); |
3514 if (typeArgs != null) { | 3518 if (typeArgs != null) { |
3515 return _callHelper( | 3519 return _callHelper( |
3516 'dgcall(#, #, #)', [fn, new JS.ArrayInitializer(typeArgs), args]); | 3520 'dgcall(#, #, #)', [fn, new JS.ArrayInitializer(typeArgs), args]); |
3517 } else { | 3521 } else { |
3518 return _callHelper('dcall(#, #)', [fn, args]); | 3522 return _callHelper('dcall(#, #)', [fn, args]); |
3519 } | 3523 } |
3520 } | 3524 } |
3521 | 3525 |
3526 bool _doubleEqIsIdentity(Expression left, Expression right, DartType leftType, | |
3527 DartType rightType) { | |
Jennifer Messerly
2017/06/26 22:42:58
personally I'd refactor this: remove leftType and
Leaf
2017/06/30 21:55:32
Done.
| |
3528 // If we statically know LHS or RHS is null we can use ==. | |
3529 if (_isNull(left) || _isNull(right)) return true; | |
3530 // If the representation of the two types will not induce conversion in | |
3531 // JS then we can use == . | |
3532 if (!typeRep.equalityMayConvert(leftType, rightType)) return true; | |
Jennifer Messerly
2017/06/26 22:42:57
this can be simplified too:
return !typeRep.equal
Leaf
2017/06/30 21:55:31
Doh. How embarrassing, I hate code like this.
| |
3533 return false; | |
3534 } | |
3535 | |
3536 bool _tripleEqIsIdentity(Expression left, Expression right, DartType leftType, | |
3537 DartType rightType) { | |
Jennifer Messerly
2017/06/26 22:42:57
leftType and rightType are unused
Leaf
2017/06/30 21:55:32
Done.
| |
3538 // If either is non-nullable, then we don't need to worry about | |
3539 // equating null and undefined, and so we can use triple equals. | |
3540 if (!isNullable(left) || !isNullable(right)) return true; | |
Jennifer Messerly
2017/06/26 22:42:58
return !isNullable(left) || !isNullable(right);
Leaf
2017/06/30 21:55:32
Aargh! Strong No Hire!
| |
3541 return false; | |
3542 } | |
3543 | |
3544 bool _isCoreIdentical(Expression function) { | |
3545 if (function is Identifier) { | |
Jennifer Messerly
2017/06/26 22:42:57
based on suggestion above, this can be just:
Leaf
2017/06/30 21:55:31
Done.
| |
3546 var element = function.staticElement; | |
3547 var name = element?.name; | |
3548 var inCore = element?.library?.isDartCore; | |
3549 return name == "identical" && inCore == true; | |
3550 } | |
3551 return false; | |
3552 } | |
3553 | |
3554 JS.Expression _emitCoreIdenticalCall( | |
3555 InvocationExpression node, JS.Expression fn, List<JS.Expression> args) { | |
Jennifer Messerly
2017/06/26 22:42:58
based on suggestions below, the "fn" parameter can
Leaf
2017/06/30 21:55:32
Done.
| |
3556 if (args.length != 2) return new JS.Call(fn, args); | |
Jennifer Messerly
2017/06/26 22:42:58
this can only come up with unsafe flags. I think w
Leaf
2017/06/30 21:55:31
Done.
| |
3557 var left = node.argumentList.arguments[0]; | |
3558 var right = node.argumentList.arguments[1]; | |
3559 var leftType = getStaticType(left); | |
3560 var rightType = getStaticType(right); | |
Jennifer Messerly
2017/06/26 22:42:58
these two lines will go away based on suggestions
Leaf
2017/06/30 21:55:32
Done.
| |
3561 if (_tripleEqIsIdentity(left, right, leftType, rightType)) { | |
3562 return js.call('# === #', args); | |
Jennifer Messerly
2017/06/26 22:42:58
should we also optimize `!identical(left, right)`
Leaf
2017/06/30 21:55:31
It's a bit awkward to code up (at first glance, I'
| |
3563 } | |
3564 if (_doubleEqIsIdentity(left, right, leftType, rightType)) { | |
3565 return js.call('# == #', args); | |
3566 } | |
3567 return new JS.Call(fn, args); | |
Jennifer Messerly
2017/06/26 22:42:58
once we have _coreIdentical, it's possible to just
Leaf
2017/06/30 21:55:31
Done.
| |
3568 } | |
3569 | |
3522 /// Emits a function call, to a top-level function, local function, or | 3570 /// Emits a function call, to a top-level function, local function, or |
3523 /// an expression. | 3571 /// an expression. |
3524 JS.Expression _emitFunctionCall(InvocationExpression node, | 3572 JS.Expression _emitFunctionCall(InvocationExpression node, |
3525 [Expression function]) { | 3573 [Expression function]) { |
3526 if (function == null) { | 3574 if (function == null) { |
3527 function = node.function; | 3575 function = node.function; |
3528 } | 3576 } |
3529 var fn = _visit(function); | 3577 var fn = _visit(function); |
3530 var args = _emitArgumentList(node.argumentList); | 3578 var args = _emitArgumentList(node.argumentList); |
3531 if (isDynamicInvoke(function)) { | 3579 if (isDynamicInvoke(function)) { |
3532 return _emitDynamicInvoke(node, fn, args); | 3580 return _emitDynamicInvoke(node, fn, args); |
3581 } else if (_isCoreIdentical(function)) { | |
3582 return _emitCoreIdenticalCall(node, fn, args); | |
3533 } else { | 3583 } else { |
3534 return new JS.Call(_applyInvokeTypeArguments(fn, node), args); | 3584 return new JS.Call(_applyInvokeTypeArguments(fn, node), args); |
3535 } | 3585 } |
3536 } | 3586 } |
3537 | 3587 |
3538 JS.Expression _applyInvokeTypeArguments( | 3588 JS.Expression _applyInvokeTypeArguments( |
3539 JS.Expression target, InvocationExpression node) { | 3589 JS.Expression target, InvocationExpression node) { |
3540 var typeArgs = _emitInvokeTypeArguments(node); | 3590 var typeArgs = _emitInvokeTypeArguments(node); |
3541 if (typeArgs == null) return target; | 3591 if (typeArgs == null) return target; |
3542 return new JS.Call(target, typeArgs); | 3592 return new JS.Call(target, typeArgs); |
(...skipping 529 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4072 return stringValue != null | 4122 return stringValue != null |
4073 ? js.escapedString(stringValue) | 4123 ? js.escapedString(stringValue) |
4074 : new JS.LiteralNull(); | 4124 : new JS.LiteralNull(); |
4075 } | 4125 } |
4076 throw new StateError('failed to evaluate $node'); | 4126 throw new StateError('failed to evaluate $node'); |
4077 } | 4127 } |
4078 return _emitInstanceCreationExpression( | 4128 return _emitInstanceCreationExpression( |
4079 element, type, name, node.argumentList, node.isConst); | 4129 element, type, name, node.argumentList, node.isConst); |
4080 } | 4130 } |
4081 | 4131 |
4082 /// True if this type is built-in to JS, and we use the values unwrapped. | 4132 bool isPrimitiveType(DartType t) => typeRep.isPrimitive(t); |
4083 /// For these types we generate a calling convention via static | |
4084 /// "extension methods". This allows types to be extended without adding | |
4085 /// extensions directly on the prototype. | |
4086 bool isPrimitiveType(DartType t) => | |
4087 typeIsPrimitiveInJS(t) || t == types.stringType; | |
4088 | |
4089 bool typeIsPrimitiveInJS(DartType t) => | |
4090 _isNumberInJS(t) || t == types.boolType; | |
4091 | |
4092 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => | |
4093 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); | |
4094 | |
4095 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); | |
4096 | 4133 |
4097 JS.Expression notNull(Expression expr) { | 4134 JS.Expression notNull(Expression expr) { |
4098 if (expr == null) return null; | 4135 if (expr == null) return null; |
4099 var jsExpr = _visit(expr); | 4136 var jsExpr = _visit(expr); |
4100 if (!isNullable(expr)) return jsExpr; | 4137 if (!isNullable(expr)) return jsExpr; |
4101 return _callHelper('notNull(#)', jsExpr); | 4138 return _callHelper('notNull(#)', jsExpr); |
4102 } | 4139 } |
4103 | 4140 |
4141 JS.Expression _emitEqualityOperator(BinaryExpression node, Token op) { | |
4142 var left = node.leftOperand; | |
4143 var right = node.rightOperand; | |
4144 | |
4145 JS.Expression doOperator(String eq, String neq) { | |
4146 var code = op.type == TokenType.EQ_EQ ? '# $eq #' : '# $neq #'; | |
4147 return js.call(code, [_visit(left), _visit(right)]); | |
4148 } | |
4149 | |
4150 JS.Expression doubleEq() => doOperator('==', '!='); | |
4151 JS.Expression tripleEq() => doOperator('===', '!=='); | |
4152 | |
4153 // All number types are the same to us. | |
4154 var leftType = typeRep.canonicalizeNumTypes(getStaticType(left)); | |
4155 var rightType = typeRep.canonicalizeNumTypes(getStaticType(right)); | |
4156 | |
4157 // If either is null, we can use simple equality. | |
4158 // We need to equate null and undefined, so if both are nullable | |
4159 // (but not known to be null), we cannot directly use JS == | |
4160 // unless we know that conversion will not happen. | |
4161 // Functions may or may not have an [dartx[`==`]] method attached. | |
4162 // - If they are tearoffs they will, otherwise they won't and equality is | |
4163 // identity. | |
4164 // | |
4165 | |
4166 // If we statically know LHS or RHS is null we can use ==. | |
4167 if (_isNull(left) || _isNull(right)) return doubleEq(); | |
4168 | |
4169 if (left is SuperExpression) { | |
4170 return _emitSend(left, op.lexeme, [right]); | |
4171 } | |
4172 | |
4173 // Equality on enums and primitives is identity. | |
4174 // TODO(leafp): Walk the class hierarchy and check to see if == was | |
4175 // overridden | |
4176 var isEnum = leftType is InterfaceType && leftType.element.isEnum; | |
4177 var usesIdentity = typeRep.isPrimitive(leftType) || isEnum; | |
4178 | |
4179 // If we know that the left type uses identity for equality, we can | |
4180 // sometimes emit better code. | |
4181 if (usesIdentity) { | |
4182 if (_tripleEqIsIdentity(left, right, leftType, rightType)) { | |
Jennifer Messerly
2017/06/26 22:42:57
it would be really great if this could reuse _emit
Leaf
2017/06/30 21:55:31
Done. It changes the generated code a little bit:
| |
4183 return tripleEq(); | |
4184 } | |
4185 if (_doubleEqIsIdentity(left, right, leftType, rightType)) { | |
4186 return doubleEq(); | |
4187 } | |
4188 } | |
4189 | |
4190 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; | |
4191 | |
4192 if (typeRep.objectMethodsOnPrototype(leftType)) { | |
Jennifer Messerly
2017/06/26 22:42:58
It looks like you're trying to make sure there's a
Leaf
2017/06/30 21:55:31
Done.
| |
4193 // If left is not nullable, then we don't need to worry about | |
4194 // null/undefined. | |
4195 // TODO(leafp): consider using (left || dart.EQ)['=='](right)) | |
4196 // when left is nullable but not falsey | |
4197 if (!isNullable(left)) { | |
4198 var name = _emitMemberName('==', type: leftType); | |
4199 var code = '${bang}#[#](#)'; | |
4200 return js.call(code, [_visit(left), name, _visit(right)]); | |
4201 } | |
4202 } | |
4203 // Fall back to equality for now. | |
4204 { | |
Jennifer Messerly
2017/06/26 22:42:58
is this block needed?
Leaf
2017/06/30 21:55:32
Probably not. I prefer limiting the scope of loca
| |
4205 var code = '${bang}#.equals(#, #)'; | |
4206 return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); | |
4207 } | |
4208 } | |
4209 | |
4104 @override | 4210 @override |
4105 JS.Expression visitBinaryExpression(BinaryExpression node) { | 4211 JS.Expression visitBinaryExpression(BinaryExpression node) { |
4106 var op = node.operator; | 4212 var op = node.operator; |
4107 | 4213 |
4108 // The operands of logical boolean operators are subject to boolean | 4214 // The operands of logical boolean operators are subject to boolean |
4109 // conversion. | 4215 // conversion. |
4110 if (op.type == TokenType.BAR_BAR || | 4216 if (op.type == TokenType.BAR_BAR || |
4111 op.type == TokenType.AMPERSAND_AMPERSAND) { | 4217 op.type == TokenType.AMPERSAND_AMPERSAND) { |
4112 return _visitTest(node); | 4218 return _visitTest(node); |
4113 } | 4219 } |
4114 | 4220 |
4221 if (op.type.isEqualityOperator) return _emitEqualityOperator(node, op); | |
4222 | |
4115 var left = node.leftOperand; | 4223 var left = node.leftOperand; |
4116 var right = node.rightOperand; | 4224 var right = node.rightOperand; |
4117 | 4225 |
4118 var leftType = getStaticType(left); | |
4119 var rightType = getStaticType(right); | |
4120 | |
4121 var code; | |
4122 if (op.type.isEqualityOperator) { | |
4123 // If we statically know LHS or RHS is null we can generate a clean check. | |
4124 // We can also do this if both sides are the same primitive type. | |
4125 if (_canUsePrimitiveEquality(left, right)) { | |
4126 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; | |
4127 } else if (left is SuperExpression) { | |
4128 return _emitSend(left, op.lexeme, [right]); | |
4129 } else { | |
4130 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; | |
4131 code = '${bang}#.equals(#, #)'; | |
4132 return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); | |
4133 } | |
4134 return js.call(code, [_visit(left), _visit(right)]); | |
4135 } | |
4136 | |
4137 if (op.type.lexeme == '??') { | 4226 if (op.type.lexeme == '??') { |
4138 // TODO(jmesserly): leave RHS for debugging? | 4227 // TODO(jmesserly): leave RHS for debugging? |
4139 // This should be a hint or warning for dead code. | 4228 // This should be a hint or warning for dead code. |
4140 if (!isNullable(left)) return _visit(left); | 4229 if (!isNullable(left)) return _visit(left); |
4141 | 4230 |
4142 var vars = <JS.MetaLetVariable, JS.Expression>{}; | 4231 var vars = <JS.MetaLetVariable, JS.Expression>{}; |
4143 // Desugar `l ?? r` as `l != null ? l : r` | 4232 // Desugar `l ?? r` as `l != null ? l : r` |
4144 var l = _visit(_bindValue(vars, 'l', left, context: left)); | 4233 var l = _visit(_bindValue(vars, 'l', left, context: left)); |
4145 return new JS.MetaLet(vars, [ | 4234 return new JS.MetaLet(vars, [ |
4146 js.call('# != null ? # : #', [l, l, _visit(right)]) | 4235 js.call('# != null ? # : #', [l, l, _visit(right)]) |
4147 ]); | 4236 ]); |
4148 } | 4237 } |
4149 | 4238 |
4150 if (binaryOperationIsPrimitive(leftType, rightType) || | 4239 var leftType = getStaticType(left); |
4240 var rightType = getStaticType(right); | |
4241 | |
4242 if (typeRep.binaryOperationIsPrimitive(leftType, rightType) || | |
4151 leftType == types.stringType && op.type == TokenType.PLUS) { | 4243 leftType == types.stringType && op.type == TokenType.PLUS) { |
4152 // special cases where we inline the operation | 4244 // special cases where we inline the operation |
4153 // these values are assumed to be non-null (determined by the checker) | 4245 // these values are assumed to be non-null (determined by the checker) |
4154 // TODO(jmesserly): it would be nice to just inline the method from core, | 4246 // TODO(jmesserly): it would be nice to just inline the method from core, |
4155 // instead of special cases here. | 4247 // instead of special cases here. |
4156 JS.Expression binary(String code) { | 4248 JS.Expression binary(String code) { |
4157 return js.call(code, [notNull(left), notNull(right)]); | 4249 return js.call(code, [notNull(left), notNull(right)]); |
4158 } | 4250 } |
4159 | 4251 |
4160 JS.Expression bitwise(String code) { | 4252 JS.Expression bitwise(String code) { |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4375 } | 4467 } |
4376 } | 4468 } |
4377 int value = _asIntInRange(expr, 0, 0x7fffffff); | 4469 int value = _asIntInRange(expr, 0, 0x7fffffff); |
4378 if (value != null) return value.bitLength; | 4470 if (value != null) return value.bitLength; |
4379 return MAX; | 4471 return MAX; |
4380 } | 4472 } |
4381 | 4473 |
4382 return bitWidth(expr, 0) < 32; | 4474 return bitWidth(expr, 0) < 32; |
4383 } | 4475 } |
4384 | 4476 |
4385 /// If the type [t] is [int] or [double], or a type parameter | 4477 bool _isNull(Expression expr) => |
4386 /// bounded by [int], [double] or [num] returns [num]. | 4478 expr is NullLiteral || getStaticType(expr).isDartCoreNull; |
Jennifer Messerly
2017/06/26 22:42:57
FYI. We may need to also change our nullable analy
Leaf
2017/06/30 21:55:31
Acknowledged.
| |
4387 /// Otherwise returns [t]. | |
4388 DartType _canonicalizeNumTypes(DartType t) { | |
4389 var numType = types.numType; | |
4390 if (rules.isSubtypeOf(t, numType)) return numType; | |
4391 return t; | |
4392 } | |
4393 | |
4394 bool _canUsePrimitiveEquality(Expression left, Expression right) { | |
4395 if (_isNull(left) || _isNull(right)) return true; | |
4396 | |
4397 var leftType = _canonicalizeNumTypes(getStaticType(left)); | |
4398 var rightType = _canonicalizeNumTypes(getStaticType(right)); | |
4399 return isPrimitiveType(leftType) && leftType == rightType; | |
4400 } | |
4401 | |
4402 bool _isNull(Expression expr) => expr is NullLiteral; | |
4403 | 4479 |
4404 SimpleIdentifier _createTemporary(String name, DartType type, | 4480 SimpleIdentifier _createTemporary(String name, DartType type, |
4405 {bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { | 4481 {bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { |
4406 // We use an invalid source location to signal that this is a temporary. | 4482 // We use an invalid source location to signal that this is a temporary. |
4407 // See [_isTemporary]. | 4483 // See [_isTemporary]. |
4408 // TODO(jmesserly): alternatives are | 4484 // TODO(jmesserly): alternatives are |
4409 // * (ab)use Element.isSynthetic, which isn't currently used for | 4485 // * (ab)use Element.isSynthetic, which isn't currently used for |
4410 // LocalVariableElementImpl, so we could repurpose to mean "temp". | 4486 // LocalVariableElementImpl, so we could repurpose to mean "temp". |
4411 // * add a new property to LocalVariableElementImpl. | 4487 // * add a new property to LocalVariableElementImpl. |
4412 // * create a new subtype of LocalVariableElementImpl to mark a temp. | 4488 // * create a new subtype of LocalVariableElementImpl to mark a temp. |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4537 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) | 4613 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
4538 /// | 4614 /// |
4539 /// The [JS.MetaLet] nodes automatically simplify themselves if they can. | 4615 /// The [JS.MetaLet] nodes automatically simplify themselves if they can. |
4540 /// For example, if the result value is not used, then `t` goes away. | 4616 /// For example, if the result value is not used, then `t` goes away. |
4541 @override | 4617 @override |
4542 JS.Expression visitPostfixExpression(PostfixExpression node) { | 4618 JS.Expression visitPostfixExpression(PostfixExpression node) { |
4543 var op = node.operator; | 4619 var op = node.operator; |
4544 var expr = node.operand; | 4620 var expr = node.operand; |
4545 | 4621 |
4546 var dispatchType = getStaticType(expr); | 4622 var dispatchType = getStaticType(expr); |
4547 if (unaryOperationIsPrimitive(dispatchType)) { | 4623 if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
4548 if (!isNullable(expr)) { | 4624 if (!isNullable(expr)) { |
4549 return js.call('#$op', _visit(expr)); | 4625 return js.call('#$op', _visit(expr)); |
4550 } | 4626 } |
4551 } | 4627 } |
4552 | 4628 |
4553 assert(op.lexeme == '++' || op.lexeme == '--'); | 4629 assert(op.lexeme == '++' || op.lexeme == '--'); |
4554 | 4630 |
4555 // Handle the left hand side, to ensure each of its subexpressions are | 4631 // Handle the left hand side, to ensure each of its subexpressions are |
4556 // evaluated only once. | 4632 // evaluated only once. |
4557 var vars = <JS.MetaLetVariable, JS.Expression>{}; | 4633 var vars = <JS.MetaLetVariable, JS.Expression>{}; |
(...skipping 16 matching lines...) Expand all Loading... | |
4574 JS.Expression visitPrefixExpression(PrefixExpression node) { | 4650 JS.Expression visitPrefixExpression(PrefixExpression node) { |
4575 var op = node.operator; | 4651 var op = node.operator; |
4576 | 4652 |
4577 // Logical negation, `!e`, is a boolean conversion context since it is | 4653 // Logical negation, `!e`, is a boolean conversion context since it is |
4578 // defined as `e ? false : true`. | 4654 // defined as `e ? false : true`. |
4579 if (op.lexeme == '!') return _visitTest(node); | 4655 if (op.lexeme == '!') return _visitTest(node); |
4580 | 4656 |
4581 var expr = node.operand; | 4657 var expr = node.operand; |
4582 | 4658 |
4583 var dispatchType = getStaticType(expr); | 4659 var dispatchType = getStaticType(expr); |
4584 if (unaryOperationIsPrimitive(dispatchType)) { | 4660 if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
4585 if (op.lexeme == '~') { | 4661 if (op.lexeme == '~') { |
4586 if (_isNumberInJS(dispatchType)) { | 4662 if (typeRep.isNumber(dispatchType)) { |
4587 JS.Expression jsExpr = js.call('~#', notNull(expr)); | 4663 JS.Expression jsExpr = js.call('~#', notNull(expr)); |
4588 return _coerceBitOperationResultToUnsigned(node, jsExpr); | 4664 return _coerceBitOperationResultToUnsigned(node, jsExpr); |
4589 } | 4665 } |
4590 return _emitSend(expr, op.lexeme[0], []); | 4666 return _emitSend(expr, op.lexeme[0], []); |
4591 } | 4667 } |
4592 if (!isNullable(expr)) { | 4668 if (!isNullable(expr)) { |
4593 return js.call('$op#', _visit(expr)); | 4669 return js.call('$op#', _visit(expr)); |
4594 } | 4670 } |
4595 if (op.lexeme == '++' || op.lexeme == '--') { | 4671 if (op.lexeme == '++' || op.lexeme == '--') { |
4596 // We need a null check, so the increment must be expanded out. | 4672 // We need a null check, so the increment must be expanded out. |
(...skipping 723 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5320 return finish(js.call(code, | 5396 return finish(js.call(code, |
5321 [_visitTest(node.leftOperand), _visitTest(node.rightOperand)])); | 5397 [_visitTest(node.leftOperand), _visitTest(node.rightOperand)])); |
5322 } | 5398 } |
5323 | 5399 |
5324 var op = node.operator.type.lexeme; | 5400 var op = node.operator.type.lexeme; |
5325 if (op == '&&') return shortCircuit('# && #'); | 5401 if (op == '&&') return shortCircuit('# && #'); |
5326 if (op == '||') return shortCircuit('# || #'); | 5402 if (op == '||') return shortCircuit('# || #'); |
5327 } | 5403 } |
5328 if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { | 5404 if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { |
5329 assert(node.staticType == types.boolType); | 5405 assert(node.staticType == types.boolType); |
5330 return _callHelper('test(#)', _visit(node.expression)); | 5406 return _callHelper('dtest(#)', _visit(node.expression)); |
5331 } | 5407 } |
5332 JS.Expression result = _visit(node); | 5408 JS.Expression result = _visit(node); |
5333 if (isNullable(node)) result = _callHelper('test(#)', result); | 5409 if (isNullable(node)) result = _callHelper('test(#)', result); |
5334 return result; | 5410 return result; |
5335 } | 5411 } |
5336 | 5412 |
5337 /// Like [_emitMemberName], but for declaration sites. | 5413 /// Like [_emitMemberName], but for declaration sites. |
5338 /// | 5414 /// |
5339 /// Unlike call sites, we always have an element available, so we can use it | 5415 /// Unlike call sites, we always have an element available, so we can use it |
5340 /// directly rather than computing the relevant options for [_emitMemberName]. | 5416 /// directly rather than computing the relevant options for [_emitMemberName]. |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5523 JS.Node/*=T*/ annotate/*<T extends JS.Node>*/( | 5599 JS.Node/*=T*/ annotate/*<T extends JS.Node>*/( |
5524 JS.Node/*=T*/ node, AstNode original, | 5600 JS.Node/*=T*/ node, AstNode original, |
5525 [Element element]) { | 5601 [Element element]) { |
5526 if (options.closure && element != null) { | 5602 if (options.closure && element != null) { |
5527 node = node.withClosureAnnotation(closureAnnotationFor( | 5603 node = node.withClosureAnnotation(closureAnnotationFor( |
5528 node, original, element, namedArgumentTemp.name)) as dynamic/*=T*/; | 5604 node, original, element, namedArgumentTemp.name)) as dynamic/*=T*/; |
5529 } | 5605 } |
5530 return node..sourceInformation = original; | 5606 return node..sourceInformation = original; |
5531 } | 5607 } |
5532 | 5608 |
5533 /// Returns true if this is any kind of object represented by `Number` in JS. | |
5534 /// | |
5535 /// In practice, this is 4 types: num, int, double, and JSNumber. | |
5536 /// | |
5537 /// JSNumber is the type that actually "implements" all numbers, hence it's | |
5538 /// a subtype of int and double (and num). It's in our "dart:_interceptors". | |
5539 bool _isNumberInJS(DartType t) => | |
5540 rules.isSubtypeOf(t, types.numType) && | |
5541 !rules.isSubtypeOf(t, types.nullType); | |
5542 | |
5543 /// Return true if this is one of the methods/properties on all Dart Objects | 5609 /// Return true if this is one of the methods/properties on all Dart Objects |
5544 /// (toString, hashCode, noSuchMethod, runtimeType). | 5610 /// (toString, hashCode, noSuchMethod, runtimeType). |
5545 /// | 5611 /// |
5546 /// Operator == is excluded, as it is handled as part of the equality binary | 5612 /// Operator == is excluded, as it is handled as part of the equality binary |
5547 /// operator. | 5613 /// operator. |
5548 bool isObjectMember(String name) { | 5614 bool isObjectMember(String name) { |
5549 // We could look these up on Object, but we have hard coded runtime helpers | 5615 // We could look these up on Object, but we have hard coded runtime helpers |
5550 // so it's not really providing any benefit. | 5616 // so it's not really providing any benefit. |
5551 switch (name) { | 5617 switch (name) { |
5552 case 'hashCode': | 5618 case 'hashCode': |
(...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5836 if (targetIdentifier.staticElement is! PrefixElement) return false; | 5902 if (targetIdentifier.staticElement is! PrefixElement) return false; |
5837 var prefix = targetIdentifier.staticElement as PrefixElement; | 5903 var prefix = targetIdentifier.staticElement as PrefixElement; |
5838 | 5904 |
5839 // The library the prefix is referring to must come from a deferred import. | 5905 // The library the prefix is referring to must come from a deferred import. |
5840 var containingLibrary = resolutionMap | 5906 var containingLibrary = resolutionMap |
5841 .elementDeclaredByCompilationUnit(target.root as CompilationUnit) | 5907 .elementDeclaredByCompilationUnit(target.root as CompilationUnit) |
5842 .library; | 5908 .library; |
5843 var imports = containingLibrary.getImportsWithPrefix(prefix); | 5909 var imports = containingLibrary.getImportsWithPrefix(prefix); |
5844 return imports.length == 1 && imports[0].isDeferred; | 5910 return imports.length == 1 && imports[0].isDeferred; |
5845 } | 5911 } |
OLD | NEW |