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

Side by Side Diff: pkg/dev_compiler/lib/src/compiler/code_generator.dart

Issue 2926613003: Generate better code for '=='. (Closed)
Patch Set: Identical, dtest Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 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
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
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
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
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
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
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698