/*
* 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.devtools.j2objc.translate;
import com.google.devtools.j2objc.ast.Assignment;
import com.google.devtools.j2objc.ast.CastExpression;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.ConditionalExpression;
import com.google.devtools.j2objc.ast.ConstructorInvocation;
import com.google.devtools.j2objc.ast.EnumConstantDeclaration;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.ExpressionStatement;
import com.google.devtools.j2objc.ast.FieldAccess;
import com.google.devtools.j2objc.ast.FunctionInvocation;
import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.InfixExpression.Operator;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.ParenthesizedExpression;
import com.google.devtools.j2objc.ast.ReturnStatement;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.SuperConstructorInvocation;
import com.google.devtools.j2objc.ast.SuperMethodInvocation;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TypeLiteral;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.types.ExecutablePair;
import com.google.devtools.j2objc.types.FunctionElement;
import com.google.devtools.j2objc.types.GeneratedExecutableElement;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.Iterator;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
/**
* Adds cast checks to existing java cast expressions.
* Adds casts as needed for Objective-C compilation. Usually this occurs when a
* method has a declared return type that is more generic than the resolved type
* of the expression.
*/
public class CastResolver extends UnitTreeVisitor {
public CastResolver(CompilationUnit unit) {
super(unit);
}
@Override
public void endVisit(CastExpression node) {
TypeMirror type = node.getType().getTypeMirror();
Expression expr = node.getExpression();
TypeMirror exprType = expr.getTypeMirror();
if (TypeUtil.isFloatingPoint(exprType)) {
// Java wouldn't allow a cast from primitive to non-primitive.
assert type.getKind().isPrimitive();
switch (type.getKind()) {
case LONG:
node.replaceWith(rewriteFloatToIntegralCast(type, expr, "JreFpToLong", type));
return;
case CHAR:
node.replaceWith(rewriteFloatToIntegralCast(type, expr, "JreFpToChar", type));
return;
case BYTE:
case SHORT:
case INT:
node.replaceWith(rewriteFloatToIntegralCast(type, expr, "JreFpToInt", typeUtil.getInt()));
return;
default: // Fall through.
}
}
// Lean on Java's type-checking.
if (!type.getKind().isPrimitive() && typeUtil.isAssignable(exprType, typeUtil.erasure(type))) {
node.replaceWith(TreeUtil.remove(expr));
return;
}
FunctionInvocation castCheck = createCastCheck(type, expr);
if (castCheck != null) {
node.setExpression(castCheck);
}
}
private Expression rewriteFloatToIntegralCast(
TypeMirror castType, Expression expr, String funcName, TypeMirror funcReturnType) {
FunctionElement element = new FunctionElement(funcName, funcReturnType, null)
.addParameters(typeUtil.getDouble());
FunctionInvocation invocation = new FunctionInvocation(element, funcReturnType);
invocation.addArgument(TreeUtil.remove(expr));
Expression newExpr = invocation;
if (!castType.equals(funcReturnType)) {
newExpr = new CastExpression(castType, newExpr);
}
return newExpr;
}
private static boolean isObjectArray(TypeMirror type) {
return TypeUtil.isArray(type) && !((ArrayType) type).getComponentType().getKind().isPrimitive();
}
private FunctionInvocation createCastCheck(TypeMirror type, Expression expr) {
type = typeUtil.erasure(type);
TypeMirror idType = TypeUtil.ID_TYPE;
if (TypeUtil.isInterface(type) || isObjectArray(type)) {
// Interfaces and object arrays requre a isInstance call.
FunctionElement element = new FunctionElement("cast_check", idType, null)
.addParameters(idType, TypeUtil.IOS_CLASS.asType());
FunctionInvocation invocation = new FunctionInvocation(element, idType);
invocation.addArgument(TreeUtil.remove(expr));
invocation.addArgument(new TypeLiteral(type, typeUtil));
return invocation;
} else if (TypeUtil.isArray(type) || TypeUtil.isDeclaredType(type)) {
// Primitive array and non-interface type casts are checked using Objective-C's
// isKindOfClass:.
TypeElement objcClass = typeUtil.getObjcClass(type);
FunctionElement checkFunction = new FunctionElement("cast_chk", idType, null)
.addParameters(idType, idType);
FunctionInvocation invocation = new FunctionInvocation(checkFunction, idType);
invocation.addArgument(TreeUtil.remove(expr));
ExecutableElement classElement =
GeneratedExecutableElement.newMethodWithSelector("class", idType, objcClass)
.addModifiers(Modifier.STATIC);
MethodInvocation classInvocation =
new MethodInvocation(new ExecutablePair(classElement), new SimpleName(objcClass));
invocation.addArgument(classInvocation);
return invocation;
}
return null;
}
private void addCast(Expression expr) {
CastExpression castExpr = new CastExpression(expr.getTypeMirror(), null);
expr.replaceWith(ParenthesizedExpression.parenthesize(castExpr));
castExpr.setExpression(expr);
}
private void maybeAddCast(Expression expr, TypeMirror expectedType, boolean shouldCastFromId) {
if (expr instanceof ConditionalExpression) {
ConditionalExpression condExpr = (ConditionalExpression) expr;
maybeAddCast(condExpr.getThenExpression(), expectedType, shouldCastFromId);
maybeAddCast(condExpr.getElseExpression(), expectedType, shouldCastFromId);
return;
}
if (needsCast(expr, expectedType, shouldCastFromId)) {
addCast(expr);
}
}
private boolean needsCast(Expression expr, TypeMirror expectedType, boolean shouldCastFromId) {
TypeMirror declaredType = getDeclaredType(expr);
if (declaredType == null) {
return false;
}
TypeMirror exprType = expr.getTypeMirror();
if (
// In general we do not need to cast primitive types.
exprType.getKind().isPrimitive()
// In most cases we don't need to cast from an id type. However, if the
// expression is being dereferenced then the compiler needs the type
// info.
|| (typeUtil.isDeclaredAsId(declaredType) && !shouldCastFromId)
// If the declared type can be assigned into the actual type, or the
// expected type, then the compiler already has sufficient type info.
|| typeUtil.isObjcAssignable(declaredType, exprType)
|| (expectedType != null && typeUtil.isObjcAssignable(declaredType, expectedType))) {
return false;
}
return true;
}
private TypeMirror getDeclaredType(Expression expr) {
VariableElement var = TreeUtil.getVariableElement(expr);
if (var != null) {
return var.asType();
}
switch (expr.getKind()) {
case CLASS_INSTANCE_CREATION:
return TypeUtil.ID_TYPE;
case FUNCTION_INVOCATION:
return ((FunctionInvocation) expr).getFunctionElement().getReturnType();
case LAMBDA_EXPRESSION:
// Lambda expressions are generated as function calls that return "id".
return TypeUtil.ID_TYPE;
case METHOD_INVOCATION: {
MethodInvocation invocation = (MethodInvocation) expr;
ExecutableElement method = invocation.getExecutableElement();
// Object receiving the message, or null if it's a method in this class.
Expression receiver = invocation.getExpression();
TypeMirror receiverType = receiver != null ? receiver.getTypeMirror()
: ElementUtil.getDeclaringClass(method).asType();
return getDeclaredReturnType(method, receiverType);
}
case PARENTHESIZED_EXPRESSION:
return getDeclaredType(((ParenthesizedExpression) expr).getExpression());
case SUPER_METHOD_INVOCATION: {
SuperMethodInvocation invocation = (SuperMethodInvocation) expr;
return getDeclaredReturnType(
invocation.getExecutableElement(),
TreeUtil.getEnclosingTypeElement(invocation).getSuperclass());
}
default:
return null;
}
}
private TypeMirror getDeclaredReturnType(ExecutableElement method, TypeMirror receiverType) {
// Check if the method is declared on the receiver type.
if (ElementUtil.getDeclaringClass(method).equals(TypeUtil.asTypeElement(receiverType))) {
return method.getReturnType();
}
// Search all inherited types for matching method declarations. Choose the
// most narrow return type, because AbstractMethodRewriter will ensure that
// a declaration exists with the most narrow return type.
ExecutableType methodType = (ExecutableType) method.asType();
String selector = nameTable.getMethodSelector(method);
for (TypeMirror typeBound : typeUtil.getUpperBounds(receiverType)) {
if (TypeUtil.isDeclaredType(typeBound)) {
// Normalize any parameterized types before searching for method declarations.
typeBound = ((DeclaredType) typeBound).asElement().asType();
}
TypeMirror returnType = null;
for (DeclaredType inheritedType : typeUtil.getObjcOrderedInheritedTypes(typeBound)) {
TypeElement inheritedElem = (TypeElement) inheritedType.asElement();
for (ExecutableElement currentMethod : ElementUtil.getMethods(inheritedElem)) {
ExecutableType currentMethodType = typeUtil.asMemberOf(inheritedType, currentMethod);
if (typeUtil.isSubsignature(methodType, currentMethodType)
&& nameTable.getMethodSelector(currentMethod).equals(selector)) {
TypeMirror newReturnType = typeUtil.erasure(currentMethodType.getReturnType());
if (returnType == null || typeUtil.isSubtype(newReturnType, returnType)) {
returnType = newReturnType;
}
}
}
}
if (returnType != null) {
return returnType;
}
}
// Last resort. Might be a GeneratedExecutableElement.
return method.getReturnType();
}
// Some native objective-c methods are declared to return NSUInteger.
private boolean returnValueNeedsIntCast(Expression arg) {
ExecutableElement methodElement = TreeUtil.getExecutableElement(arg);
assert methodElement != null;
if (arg.getParent() instanceof ExpressionStatement) {
// Avoid "unused return value" warning.
return false;
}
String methodName = nameTable.getMethodSelector(methodElement);
if (methodName.equals("hash") && methodElement.getReturnType().getKind() == TypeKind.INT) {
return true;
}
return false;
}
private void maybeCastArguments(
List<Expression> args, Iterable<? extends TypeMirror> paramTypes) {
Iterator<Expression> argIter = args.iterator();
Iterator<? extends TypeMirror> paramTypeIter = paramTypes.iterator();
// Implicit assert that size(paramTypes) >= size(args). Don't cast vararg arguments.
while (paramTypeIter.hasNext()) {
maybeAddCast(argIter.next(), paramTypeIter.next(), false);
}
}
private void maybeCastArguments(List<Expression> args, ExecutableElement method) {
maybeCastArguments(args, ElementUtil.asTypes(method.getParameters()));
}
@Override
public void endVisit(ClassInstanceCreation node) {
maybeCastArguments(node.getArguments(), node.getExecutableElement());
}
@Override
public void endVisit(ConstructorInvocation node) {
maybeCastArguments(node.getArguments(), node.getExecutableElement());
}
@Override
public void endVisit(EnumConstantDeclaration node) {
maybeCastArguments(node.getArguments(), node.getExecutableElement());
}
@Override
public void endVisit(FieldAccess node) {
maybeAddCast(node.getExpression(), null, true);
}
@Override
public void endVisit(FunctionInvocation node) {
maybeCastArguments(node.getArguments(), node.getFunctionElement().getParameterTypes());
}
@Override
public void endVisit(MethodInvocation node) {
Expression receiver = node.getExpression();
if (receiver != null && !ElementUtil.isStatic(node.getExecutableElement())) {
maybeAddCast(receiver, null, true);
}
maybeCastArguments(node.getArguments(), node.getExecutableElement());
if (returnValueNeedsIntCast(node)) {
addCast(node);
}
}
@Override
public void endVisit(ReturnStatement node) {
Expression expr = node.getExpression();
if (expr != null) {
maybeAddCast(expr, TreeUtil.getOwningReturnType(node), false);
}
}
@Override
public void endVisit(SuperConstructorInvocation node) {
maybeCastArguments(node.getArguments(), node.getExecutableElement());
}
@Override
public void endVisit(SuperMethodInvocation node) {
maybeCastArguments(node.getArguments(), node.getExecutableElement());
if (returnValueNeedsIntCast(node)) {
addCast(node);
}
}
@Override
public void endVisit(Assignment node) {
maybeAddCast(node.getRightHandSide(), node.getTypeMirror(), false);
}
@Override
public void endVisit(VariableDeclarationFragment node) {
Expression initializer = node.getInitializer();
if (initializer != null) {
maybeAddCast(initializer, node.getVariableElement().asType(), false);
}
}
/**
* Adds a cast check to compareTo methods. This helps Comparable types behave
* well in sorted collections which rely on Java's runtime type checking.
*/
@Override
public void endVisit(MethodDeclaration node) {
ExecutableElement element = node.getExecutableElement();
if (!ElementUtil.getName(element).equals("compareTo") || node.getBody() == null) {
return;
}
DeclaredType comparableType = typeUtil.findSupertype(
ElementUtil.getDeclaringClass(element).asType(), "java.lang.Comparable");
if (comparableType == null) {
return;
}
List<? extends TypeMirror> typeArguments = comparableType.getTypeArguments();
List<? extends VariableElement> parameters = element.getParameters();
if (typeArguments.size() != 1 || parameters.size() != 1
|| !typeArguments.get(0).equals(parameters.get(0).asType())) {
return;
}
VariableElement param = node.getParameter(0).getVariableElement();
FunctionInvocation castCheck = createCastCheck(typeArguments.get(0), new SimpleName(param));
if (castCheck != null) {
node.getBody().addStatement(0, new ExpressionStatement(castCheck));
}
}
@Override
public void endVisit(InfixExpression node) {
// Clang reports an incompatible pointer comparison when comparing two
// objects with different interface types. That's potentially wrong both
// in Java and Objective-C, since a single class can implement both
// interfaces. CastResolverTest.testInterfaceComparisons() demonstrates
// the problem.
Operator operator = node.getOperator();
if (operator == InfixExpression.Operator.EQUALS
|| operator == InfixExpression.Operator.NOT_EQUALS) {
List<Expression> operands = node.getOperands();
if (incompatibleTypes(operands.get(0), operands.get(1))) {
// Add (id) cast to right-hand operand(s).
operands.add(1, new CastExpression(TypeUtil.ID_TYPE, operands.remove(1)));
}
}
}
@Override
public void endVisit(ConditionalExpression node) {
Expression thenExpr = node.getThenExpression();
Expression elseExpr = node.getElseExpression();
if (incompatibleTypes(thenExpr, elseExpr)) {
// Add (id) cast to else expression.
node.setElseExpression(new CastExpression(TypeUtil.ID_TYPE, TreeUtil.remove(elseExpr)));
}
}
private boolean incompatibleTypes(Expression a, Expression b) {
TypeMirror aType = a.getTypeMirror();
TypeMirror bType = b.getTypeMirror();
return TypeUtil.isReferenceType(aType) && TypeUtil.isReferenceType(bType)
&& !typeUtil.isObjcAssignable(aType, bType) && !typeUtil.isObjcAssignable(bType, aType);
}
}