Chromium Code Reviews| Index: pkg/dev_compiler/lib/src/compiler/code_generator.dart |
| diff --git a/pkg/dev_compiler/lib/src/compiler/code_generator.dart b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
| index 9b1c72fe7718123745657f34ee42af5a0433cf74..1bc01925d19b05ed4a4c9dbab07f810a3f9d9e9a 100644 |
| --- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
| +++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
| @@ -46,6 +46,7 @@ import 'js_interop.dart'; |
| import 'js_metalet.dart' as JS; |
| import 'js_names.dart' as JS; |
| import 'js_typeref_codegen.dart' show JsTypeRefCodegen; |
| +import 'js_typerep.dart' show JSTypeRep; |
| import 'module_builder.dart' show pathToJSIdentifier; |
| import 'nullable_type_inference.dart' show NullableTypeInference; |
| import 'property_model.dart'; |
| @@ -57,7 +58,7 @@ import 'type_utilities.dart'; |
| /// |
| /// Takes as input resolved Dart ASTs for every compilation unit in every |
| /// library in the module. Produces a single JavaScript AST for the module as |
| -/// output, along with its source map. |
| +// output, along with its source map. |
| /// |
| /// This class attempts to preserve identifier names and structure of the input |
| /// Dart code, whenever this is possible to do in the generated code. |
| @@ -75,6 +76,7 @@ class CodeGenerator extends Object |
| final CompilerOptions options; |
| final StrongTypeSystemImpl rules; |
| + JSTypeRep typeRep; |
| /// The set of libraries we are currently compiling, and the temporaries used |
| /// to refer to them. |
| @@ -210,7 +212,9 @@ class CodeGenerator extends Object |
| functionClass = _getLibrary(c, 'dart:core').getType('Function'), |
| privateSymbolClass = |
| _getLibrary(c, 'dart:_internal').getType('PrivateSymbol'), |
| - dartJSLibrary = _getLibrary(c, 'dart:js'); |
| + dartJSLibrary = _getLibrary(c, 'dart:js') { |
| + typeRep = new JSTypeRep(rules, types); |
| + } |
| Element get currentElement => _currentElements.last; |
| @@ -698,7 +702,7 @@ class CodeGenerator extends Object |
| if (rules.isSubtypeOf(from, to)) return jsFrom; |
| // All Dart number types map to a JS double. |
| - if (_isNumberInJS(from) && _isNumberInJS(to)) { |
| + if (typeRep.isNumber(from) && typeRep.isNumber(to)) { |
| // Make sure to check when converting to int. |
| if (from != types.intType && to == types.intType) { |
| // TODO(jmesserly): fuse this with notNull check. |
| @@ -746,7 +750,7 @@ class CodeGenerator extends Object |
| } |
| String _jsTypeofName(DartType t) { |
| - if (_isNumberInJS(t)) return 'number'; |
| + if (typeRep.isNumber(t)) return 'number'; |
| if (t == types.stringType) return 'string'; |
| if (t == types.boolType) return 'boolean'; |
| return null; |
| @@ -3519,6 +3523,50 @@ class CodeGenerator extends Object |
| } |
| } |
| + bool _doubleEqIsIdentity(Expression left, Expression right, DartType leftType, |
| + 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.
|
| + // If we statically know LHS or RHS is null we can use ==. |
| + if (_isNull(left) || _isNull(right)) return true; |
| + // If the representation of the two types will not induce conversion in |
| + // JS then we can use == . |
| + 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.
|
| + return false; |
| + } |
| + |
| + bool _tripleEqIsIdentity(Expression left, Expression right, DartType leftType, |
| + DartType rightType) { |
|
Jennifer Messerly
2017/06/26 22:42:57
leftType and rightType are unused
Leaf
2017/06/30 21:55:32
Done.
|
| + // If either is non-nullable, then we don't need to worry about |
| + // equating null and undefined, and so we can use triple equals. |
| + 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!
|
| + return false; |
| + } |
| + |
| + bool _isCoreIdentical(Expression function) { |
| + 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.
|
| + var element = function.staticElement; |
| + var name = element?.name; |
| + var inCore = element?.library?.isDartCore; |
| + return name == "identical" && inCore == true; |
| + } |
| + return false; |
| + } |
| + |
| + JS.Expression _emitCoreIdenticalCall( |
| + 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.
|
| + 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.
|
| + var left = node.argumentList.arguments[0]; |
| + var right = node.argumentList.arguments[1]; |
| + var leftType = getStaticType(left); |
| + 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.
|
| + if (_tripleEqIsIdentity(left, right, leftType, rightType)) { |
| + 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'
|
| + } |
| + if (_doubleEqIsIdentity(left, right, leftType, rightType)) { |
| + return js.call('# == #', args); |
| + } |
| + 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.
|
| + } |
| + |
| /// Emits a function call, to a top-level function, local function, or |
| /// an expression. |
| JS.Expression _emitFunctionCall(InvocationExpression node, |
| @@ -3530,6 +3578,8 @@ class CodeGenerator extends Object |
| var args = _emitArgumentList(node.argumentList); |
| if (isDynamicInvoke(function)) { |
| return _emitDynamicInvoke(node, fn, args); |
| + } else if (_isCoreIdentical(function)) { |
| + return _emitCoreIdenticalCall(node, fn, args); |
| } else { |
| return new JS.Call(_applyInvokeTypeArguments(fn, node), args); |
| } |
| @@ -4079,20 +4129,7 @@ class CodeGenerator extends Object |
| element, type, name, node.argumentList, node.isConst); |
| } |
| - /// True if this type is built-in to JS, and we use the values unwrapped. |
| - /// For these types we generate a calling convention via static |
| - /// "extension methods". This allows types to be extended without adding |
| - /// extensions directly on the prototype. |
| - bool isPrimitiveType(DartType t) => |
| - typeIsPrimitiveInJS(t) || t == types.stringType; |
| - |
| - bool typeIsPrimitiveInJS(DartType t) => |
| - _isNumberInJS(t) || t == types.boolType; |
| - |
| - bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
| - typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
| - |
| - bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
| + bool isPrimitiveType(DartType t) => typeRep.isPrimitive(t); |
| JS.Expression notNull(Expression expr) { |
| if (expr == null) return null; |
| @@ -4101,6 +4138,75 @@ class CodeGenerator extends Object |
| return _callHelper('notNull(#)', jsExpr); |
| } |
| + JS.Expression _emitEqualityOperator(BinaryExpression node, Token op) { |
| + var left = node.leftOperand; |
| + var right = node.rightOperand; |
| + |
| + JS.Expression doOperator(String eq, String neq) { |
| + var code = op.type == TokenType.EQ_EQ ? '# $eq #' : '# $neq #'; |
| + return js.call(code, [_visit(left), _visit(right)]); |
| + } |
| + |
| + JS.Expression doubleEq() => doOperator('==', '!='); |
| + JS.Expression tripleEq() => doOperator('===', '!=='); |
| + |
| + // All number types are the same to us. |
| + var leftType = typeRep.canonicalizeNumTypes(getStaticType(left)); |
| + var rightType = typeRep.canonicalizeNumTypes(getStaticType(right)); |
| + |
| + // If either is null, we can use simple equality. |
| + // We need to equate null and undefined, so if both are nullable |
| + // (but not known to be null), we cannot directly use JS == |
| + // unless we know that conversion will not happen. |
| + // Functions may or may not have an [dartx[`==`]] method attached. |
| + // - If they are tearoffs they will, otherwise they won't and equality is |
| + // identity. |
| + // |
| + |
| + // If we statically know LHS or RHS is null we can use ==. |
| + if (_isNull(left) || _isNull(right)) return doubleEq(); |
| + |
| + if (left is SuperExpression) { |
| + return _emitSend(left, op.lexeme, [right]); |
| + } |
| + |
| + // Equality on enums and primitives is identity. |
| + // TODO(leafp): Walk the class hierarchy and check to see if == was |
| + // overridden |
| + var isEnum = leftType is InterfaceType && leftType.element.isEnum; |
| + var usesIdentity = typeRep.isPrimitive(leftType) || isEnum; |
| + |
| + // If we know that the left type uses identity for equality, we can |
| + // sometimes emit better code. |
| + if (usesIdentity) { |
| + 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:
|
| + return tripleEq(); |
| + } |
| + if (_doubleEqIsIdentity(left, right, leftType, rightType)) { |
| + return doubleEq(); |
| + } |
| + } |
| + |
| + var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
| + |
| + 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.
|
| + // If left is not nullable, then we don't need to worry about |
| + // null/undefined. |
| + // TODO(leafp): consider using (left || dart.EQ)['=='](right)) |
| + // when left is nullable but not falsey |
| + if (!isNullable(left)) { |
| + var name = _emitMemberName('==', type: leftType); |
| + var code = '${bang}#[#](#)'; |
| + return js.call(code, [_visit(left), name, _visit(right)]); |
| + } |
| + } |
| + // Fall back to equality for now. |
| + { |
|
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
|
| + var code = '${bang}#.equals(#, #)'; |
| + return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); |
| + } |
| + } |
| + |
| @override |
| JS.Expression visitBinaryExpression(BinaryExpression node) { |
| var op = node.operator; |
| @@ -4112,28 +4218,11 @@ class CodeGenerator extends Object |
| return _visitTest(node); |
| } |
| + if (op.type.isEqualityOperator) return _emitEqualityOperator(node, op); |
| + |
| var left = node.leftOperand; |
| var right = node.rightOperand; |
| - var leftType = getStaticType(left); |
| - var rightType = getStaticType(right); |
| - |
| - var code; |
| - if (op.type.isEqualityOperator) { |
| - // If we statically know LHS or RHS is null we can generate a clean check. |
| - // We can also do this if both sides are the same primitive type. |
| - if (_canUsePrimitiveEquality(left, right)) { |
| - code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; |
| - } else if (left is SuperExpression) { |
| - return _emitSend(left, op.lexeme, [right]); |
| - } else { |
| - var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
| - code = '${bang}#.equals(#, #)'; |
| - return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); |
| - } |
| - return js.call(code, [_visit(left), _visit(right)]); |
| - } |
| - |
| if (op.type.lexeme == '??') { |
| // TODO(jmesserly): leave RHS for debugging? |
| // This should be a hint or warning for dead code. |
| @@ -4147,7 +4236,10 @@ class CodeGenerator extends Object |
| ]); |
| } |
| - if (binaryOperationIsPrimitive(leftType, rightType) || |
| + var leftType = getStaticType(left); |
| + var rightType = getStaticType(right); |
| + |
| + if (typeRep.binaryOperationIsPrimitive(leftType, rightType) || |
| leftType == types.stringType && op.type == TokenType.PLUS) { |
| // special cases where we inline the operation |
| // these values are assumed to be non-null (determined by the checker) |
| @@ -4382,24 +4474,8 @@ class CodeGenerator extends Object |
| return bitWidth(expr, 0) < 32; |
| } |
| - /// If the type [t] is [int] or [double], or a type parameter |
| - /// bounded by [int], [double] or [num] returns [num]. |
| - /// Otherwise returns [t]. |
| - DartType _canonicalizeNumTypes(DartType t) { |
| - var numType = types.numType; |
| - if (rules.isSubtypeOf(t, numType)) return numType; |
| - return t; |
| - } |
| - |
| - bool _canUsePrimitiveEquality(Expression left, Expression right) { |
| - if (_isNull(left) || _isNull(right)) return true; |
| - |
| - var leftType = _canonicalizeNumTypes(getStaticType(left)); |
| - var rightType = _canonicalizeNumTypes(getStaticType(right)); |
| - return isPrimitiveType(leftType) && leftType == rightType; |
| - } |
| - |
| - bool _isNull(Expression expr) => expr is NullLiteral; |
| + bool _isNull(Expression expr) => |
| + 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.
|
| SimpleIdentifier _createTemporary(String name, DartType type, |
| {bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { |
| @@ -4544,7 +4620,7 @@ class CodeGenerator extends Object |
| var expr = node.operand; |
| var dispatchType = getStaticType(expr); |
| - if (unaryOperationIsPrimitive(dispatchType)) { |
| + if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
| if (!isNullable(expr)) { |
| return js.call('#$op', _visit(expr)); |
| } |
| @@ -4581,9 +4657,9 @@ class CodeGenerator extends Object |
| var expr = node.operand; |
| var dispatchType = getStaticType(expr); |
| - if (unaryOperationIsPrimitive(dispatchType)) { |
| + if (typeRep.unaryOperationIsPrimitive(dispatchType)) { |
| if (op.lexeme == '~') { |
| - if (_isNumberInJS(dispatchType)) { |
| + if (typeRep.isNumber(dispatchType)) { |
| JS.Expression jsExpr = js.call('~#', notNull(expr)); |
| return _coerceBitOperationResultToUnsigned(node, jsExpr); |
| } |
| @@ -5327,7 +5403,7 @@ class CodeGenerator extends Object |
| } |
| if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { |
| assert(node.staticType == types.boolType); |
| - return _callHelper('test(#)', _visit(node.expression)); |
| + return _callHelper('dtest(#)', _visit(node.expression)); |
| } |
| JS.Expression result = _visit(node); |
| if (isNullable(node)) result = _callHelper('test(#)', result); |
| @@ -5530,16 +5606,6 @@ class CodeGenerator extends Object |
| return node..sourceInformation = original; |
| } |
| - /// Returns true if this is any kind of object represented by `Number` in JS. |
| - /// |
| - /// In practice, this is 4 types: num, int, double, and JSNumber. |
| - /// |
| - /// JSNumber is the type that actually "implements" all numbers, hence it's |
| - /// a subtype of int and double (and num). It's in our "dart:_interceptors". |
| - bool _isNumberInJS(DartType t) => |
| - rules.isSubtypeOf(t, types.numType) && |
| - !rules.isSubtypeOf(t, types.nullType); |
| - |
| /// Return true if this is one of the methods/properties on all Dart Objects |
| /// (toString, hashCode, noSuchMethod, runtimeType). |
| /// |