blob: 82f5cc8f53cc379e104e7582c63243ba0bfa2a50 [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JCharLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.RuntimeConstants;
/**
* Type coercion (and operator overloading) semantics differ widely in Java and JavaScript.
* This pass fixes the following mismatches:
* <ul>
* <li>the binary concat operator (+) due to very loose semantics in JavaScript</li>
* <li>integer division, due to integers beign represented as floats in JavaScript</li>
* </ul>
*/
public class TypeCoercionNormalizer {
/**
* Explicitly convert any char or long type expressions within a concat
* operation into strings because normal JavaScript conversion does not work
* correctly.
*/
private class ConcatRewriteVisitor extends JModVisitor {
private final JClassType typeJavaLangString = program.getTypeJavaLangString();
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
if (!isConcatOperation(x.getOp())) {
return;
}
JExpression lhs = x.getLhs();
JExpression rhs = x.getRhs();
JExpression newLhs = coerceToString(lhs);
JExpression newRhs = coerceToString(rhs);
assert !x.getOp().isAssignment() || newLhs == lhs :
"L-values can never be rewritten (CONCAT_ASG can not have an lhs of type char or long).";
SourceInfo sourceInfo = x.getSourceInfo();
if (newLhs != lhs || newRhs != rhs) {
JBinaryOperation newExpr = newStringBinaryOperation(sourceInfo, x.getOp(), newLhs, newRhs);
ctx.replaceMe(newExpr);
} else if (lhs.getType().canBeNull() && rhs.getType().canBeNull()) {
// Replace lhs + rhs with lhs + "" + rhs and lhs += rhs with lhs += "" + rhs
JBinaryOperation newExpr =
newStringBinaryOperation(sourceInfo, x.getOp(),
lhs,
// "" + rhs
newConcatOperation(sourceInfo, emptyString(sourceInfo), rhs));
ctx.replaceMe(newExpr);
}
}
private JBinaryOperation newStringBinaryOperation(SourceInfo sourceInfo, JBinaryOperator op,
JExpression lhs, JExpression rhs) {
return new JBinaryOperation(sourceInfo, typeJavaLangString, op, lhs, rhs);
}
private JBinaryOperation newConcatOperation(
SourceInfo sourceInfo, JExpression lhs, JExpression rhs) {
return newStringBinaryOperation(sourceInfo, JBinaryOperator.CONCAT, lhs, rhs);
}
private JStringLiteral emptyString(SourceInfo sourceInfo) {
return new JStringLiteral(sourceInfo, "", typeJavaLangString);
}
private JExpression coerceToString(JExpression expr) {
final JPrimitiveType typePrimitiveChar = program.getTypePrimitiveChar();
final JPrimitiveType typePrimitiveLong = program.getTypePrimitiveLong();
if (expr instanceof JLongLiteral) {
// Replace the literal by a string containing the literal.
long longValue = ((JLongLiteral) expr).getValue();
return program.getStringLiteral(expr.getSourceInfo(), String.valueOf(longValue));
} else if (expr.getType() == typePrimitiveLong) {
JMethodCall call = new JMethodCall(expr.getSourceInfo(), null,
program.getIndexedMethod(RuntimeConstants.LONG_LIB_TO_STRING), expr);
return call;
} else if (expr instanceof JCharLiteral) {
// Replace the literal by a string containing the literal.
char charValue = ((JCharLiteral) expr).getValue();
return program.getStringLiteral(expr.getSourceInfo(), Character.toString(charValue));
} else if (expr.getType() == typePrimitiveChar) {
// A non literal expression of type Char.
// Replace with Cast.charToString(c)
JMethodCall call = new JMethodCall(expr.getSourceInfo(), null,
program.getIndexedMethod(RuntimeConstants.CAST_CHAR_TO_STRING), expr);
return call;
}
return expr;
}
private boolean isConcatOperation(JBinaryOperator operator) {
switch (operator) {
case CONCAT:
case ASG_CONCAT:
return true;
default:
return false;
}
}
}
/**
* Handle integral divide operations which may have floating point results.
*/
private class DivRewriteVisitor extends JModVisitor {
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
JType type = x.getType();
if (x.getOp() != JBinaryOperator.DIV || type == program.getTypePrimitiveFloat()
|| type == program.getTypePrimitiveDouble()) {
return;
}
/*
* If the numerator was already in range, we can assume the output is
* also in range. Therefore, we don't need to do the full conversion,
* but rather a narrowing int conversion instead.
*/
String methodName = "Cast.narrow_" + type.getName();
JMethod castMethod = program.getIndexedMethod(methodName);
JMethodCall call = new JMethodCall(x.getSourceInfo(), null, castMethod, x);
call.overrideReturnType(type);
x.setType(program.getTypePrimitiveDouble());
ctx.replaceMe(call);
}
}
public static void exec(JProgram program) {
new TypeCoercionNormalizer(program).execImpl();
}
private final JProgram program;
private TypeCoercionNormalizer(JProgram program) {
this.program = program;
}
private void execImpl() {
new ConcatRewriteVisitor().accept(program);
new DivRewriteVisitor().accept(program);
}
}