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). |
/// |