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 c0f8872a6f89e05965c08f0fa41ae09adbd0ab30..6f2c2d1cd8dedcb5c0079afd775497b6c1cd194a 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
@@ -10,7 +10,7 @@ import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
import 'package:analyzer/dart/ast/ast.dart'; |
import 'package:analyzer/dart/ast/standard_ast_factory.dart'; |
import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
-import 'package:analyzer/dart/ast/token.dart' show TokenType; |
+import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; |
import 'package:analyzer/dart/element/element.dart'; |
import 'package:analyzer/dart/element/type.dart'; |
import 'package:analyzer/src/dart/ast/token.dart' show StringToken; |
@@ -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,11 +58,11 @@ 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. |
-// |
+/// |
// TODO(jmesserly): we should use separate visitors for statements and |
// expressions. Declarations are handled directly, and many minor component |
// AST nodes aren't visited, so the visitor pattern isn't helping except for |
@@ -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. |
@@ -132,6 +134,9 @@ class CodeGenerator extends Object |
/// The dart:async `StreamIterator<>` type. |
final InterfaceType _asyncStreamIterator; |
+ /// The dart:core `identical` element. |
+ final FunctionElement _coreIdentical; |
+ |
/// The dart:_interceptors JSArray element. |
final ClassElement _jsArray; |
@@ -197,6 +202,8 @@ class CodeGenerator extends Object |
types = c.typeProvider, |
_asyncStreamIterator = |
_getLibrary(c, 'dart:async').getType('StreamIterator').type, |
+ _coreIdentical = |
+ _getLibrary(c, 'dart:core').publicNamespace.get('identical'), |
_jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), |
interceptorClass = |
_getLibrary(c, 'dart:_interceptors').getType('Interceptor'), |
@@ -210,7 +217,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; |
@@ -697,7 +706,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. |
@@ -745,7 +754,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; |
@@ -3528,6 +3537,57 @@ class CodeGenerator extends Object |
} |
} |
+ bool _doubleEqIsIdentity(Expression left, Expression right) { |
+ // 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 == . |
+ return !typeRep.equalityMayConvert(left.staticType, right.staticType); |
+ } |
+ |
+ bool _tripleEqIsIdentity(Expression left, Expression right) { |
+ // If either is non-nullable, then we don't need to worry about |
+ // equating null and undefined, and so we can use triple equals. |
+ return !isNullable(left) || !isNullable(right); |
+ } |
+ |
+ bool _isCoreIdentical(Expression node) { |
+ return node is Identifier && node.staticElement == _coreIdentical; |
+ } |
+ |
+ JS.Expression _emitJSDoubleEq(List<JS.Expression> args, |
+ {bool negated = false}) { |
+ var op = negated ? '# != #' : '# == #'; |
+ return js.call(op, args); |
+ } |
+ |
+ JS.Expression _emitJSTripleEq(List<JS.Expression> args, |
+ {bool negated = false}) { |
+ var op = negated ? '# !== #' : '# === #'; |
+ return js.call(op, args); |
+ } |
+ |
+ JS.Expression _emitCoreIdenticalCall(List<Expression> arguments, |
+ {bool negated = false}) { |
+ if (arguments.length != 2) { |
+ // Shouldn't happen in typechecked code |
+ return _callHelper( |
+ 'throw(Error("compile error: calls to `identical` require 2 args")'); |
+ } |
+ var left = arguments[0]; |
+ var right = arguments[1]; |
+ var args = [_visit(left), _visit(right)]; |
+ if (_tripleEqIsIdentity(left, right)) { |
+ return _emitJSTripleEq(args, negated: negated); |
+ } |
+ if (_doubleEqIsIdentity(left, right)) { |
+ return _emitJSDoubleEq(args, negated: negated); |
+ } |
+ var bang = negated ? '!' : ''; |
+ return js.call( |
+ "${bang}#", new JS.Call(_emitTopLevelName(_coreIdentical), args)); |
+ } |
+ |
/// Emits a function call, to a top-level function, local function, or |
/// an expression. |
JS.Expression _emitFunctionCall(InvocationExpression node, |
@@ -3535,13 +3595,15 @@ class CodeGenerator extends Object |
if (function == null) { |
function = node.function; |
} |
+ if (_isCoreIdentical(function)) { |
+ return _emitCoreIdenticalCall(node.argumentList.arguments); |
+ } |
var fn = _visit(function); |
var args = _emitArgumentList(node.argumentList); |
if (isDynamicInvoke(function)) { |
return _emitDynamicInvoke(node, fn, args); |
- } else { |
- return new JS.Call(_applyInvokeTypeArguments(fn, node), args); |
} |
+ return new JS.Call(_applyInvokeTypeArguments(fn, node), args); |
} |
JS.Expression _applyInvokeTypeArguments( |
@@ -4087,20 +4149,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; |
@@ -4109,6 +4158,62 @@ class CodeGenerator extends Object |
return _callHelper('notNull(#)', jsExpr); |
} |
+ JS.Expression _emitEqualityOperator(BinaryExpression node, Token op) { |
+ var left = node.leftOperand; |
+ var right = node.rightOperand; |
+ var leftType = left.staticType; |
+ var negated = op.type == TokenType.BANG_EQ; |
+ |
+ 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 || |
+ _isNull(left) || |
+ _isNull(right); |
+ |
+ // If we know that the left type uses identity for equality, we can |
+ // sometimes emit better code. |
+ if (usesIdentity) { |
+ return _emitCoreIdenticalCall([left, right], negated: negated); |
+ } |
+ |
+ var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
+ var leftElement = leftType.element; |
+ |
+ // 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. |
+ // TODO(leafp): consider fixing this. |
+ // |
+ // Native types may not have equality on the prototype. |
+ // 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 ((leftElement is ClassElement && _isJSNative(leftElement)) || |
+ typeRep.isUnknown(leftType) || |
+ leftType is FunctionType || |
+ isNullable(left)) { |
+ // Fall back to equality for now. |
+ var code = '${bang}#.equals(#, #)'; |
+ return js.call(code, [_runtimeModule, _visit(left), _visit(right)]); |
+ } |
+ |
+ var name = _emitMemberName('==', type: leftType); |
+ var code = '${bang}#[#](#)'; |
+ return js.call(code, [_visit(left), name, _visit(right)]); |
+ } |
+ |
@override |
JS.Expression visitBinaryExpression(BinaryExpression node) { |
var op = node.operator; |
@@ -4120,28 +4225,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. |
@@ -4155,7 +4243,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) |
@@ -4390,24 +4481,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; |
SimpleIdentifier _createTemporary(String name, DartType type, |
{bool nullable: true, JS.Expression variable, bool dynamicInvoke}) { |
@@ -4552,7 +4627,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)); |
} |
@@ -4589,9 +4664,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); |
} |
@@ -5304,6 +5379,8 @@ class CodeGenerator extends Object |
} |
if (node is PrefixExpression && node.operator.lexeme == '!') { |
+ // TODO(leafp): consider a peephole opt for identical |
+ // and == here. |
return finish(js.call('!#', _visitTest(node.operand))); |
} |
if (node is ParenthesizedExpression) { |
@@ -5321,7 +5398,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); |
@@ -5522,16 +5599,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). |
/// |