Chromium Code Reviews| 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 |