/* * 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); } }