blob: 519d8f5692deeefa1bda6144666123ba1bc131ba [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import org.eclipse.jdt.internal.compiler.IAbstractSyntaxTreeVisitor;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
public class CastExpression extends Expression {
public Expression expression;
public Expression type;
//expression.implicitConversion holds the cast for baseType casting
public CastExpression(Expression expression, Expression type) {
this.expression = expression;
this.type = type;
//due to the fact an expression may start with ( and that a cast also start with (
//the field is an expression....it can be a TypeReference OR a NameReference Or
//an expression <--this last one is invalid.......
//if (type instanceof TypeReference )
// flag = IsTypeReference ;
//else
// if (type instanceof NameReference)
// flag = IsNameReference ;
// else
// flag = IsExpression ;
}
public FlowInfo analyseCode(
BlockScope currentScope,
FlowContext flowContext,
FlowInfo flowInfo) {
return expression
.analyseCode(currentScope, flowContext, flowInfo)
.unconditionalInits();
}
/**
* Returns false if the cast is unnecessary
*/
public final boolean checkCastTypesCompatibility(
BlockScope scope,
TypeBinding castType,
TypeBinding expressionType) {
// see specifications 5.5
// handle errors and process constant when needed
// if either one of the type is null ==>
// some error has been already reported some where ==>
// we then do not report an obvious-cascade-error.
if (castType == null || expressionType == null) return true;
// identity conversion cannot be performed upfront, due to side-effects
// like constant propagation
if (castType.isBaseType()) {
if (expressionType.isBaseType()) {
if (expressionType == castType) {
expression.implicitWidening(castType, expressionType);
constant = expression.constant; //use the same constant
return false;
}
boolean necessary = false;
if (expressionType.isCompatibleWith(castType)
|| (necessary = BaseTypeBinding.isNarrowing(castType.id, expressionType.id))) {
expression.implicitConversion = (castType.id << 4) + expressionType.id;
if (expression.constant != Constant.NotAConstant) {
constant = expression.constant.castTo(expression.implicitConversion);
}
return necessary;
}
}
scope.problemReporter().typeCastError(this, castType, expressionType);
return true;
}
//-----------cast to something which is NOT a base type--------------------------
if (expressionType == NullBinding) {
// if (castType.isArrayType()){ // 26903 - need checkcast when casting null to array type
// needRuntimeCheckcast = true;
// }
return false; //null is compatible with every thing
}
if (expressionType.isBaseType()) {
scope.problemReporter().typeCastError(this, castType, expressionType);
return true;
}
if (expressionType.isArrayType()) {
if (castType == expressionType) return false; // identity conversion
if (castType.isArrayType()) {
//------- (castType.isArray) expressionType.isArray -----------
TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType(scope);
if (exprElementType.isBaseType()) {
// <---stop the recursion-------
if (((ArrayBinding) castType).elementsType(scope) == exprElementType) {
this.bits |= NeedRuntimeCheckCastMASK;
} else {
scope.problemReporter().typeCastError(this, castType, expressionType);
}
return true;
}
// recursively on the elements...
return checkCastTypesCompatibility(
scope,
((ArrayBinding) castType).elementsType(scope),
exprElementType);
} else if (
castType.isClass()) {
//------(castType.isClass) expressionType.isArray ---------------
if (scope.isJavaLangObject(castType)) {
return false;
}
} else { //------- (castType.isInterface) expressionType.isArray -----------
if (scope.isJavaLangCloneable(castType) || scope.isJavaIoSerializable(castType)) {
this.bits |= NeedRuntimeCheckCastMASK;
return true;
}
}
scope.problemReporter().typeCastError(this, castType, expressionType);
return true;
}
if (expressionType.isClass()) {
if (castType.isArrayType()) {
// ---- (castType.isArray) expressionType.isClass -------
if (scope.isJavaLangObject(expressionType)) { // potential runtime error
this.bits |= NeedRuntimeCheckCastMASK;
return true;
}
} else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isClass ------
if (expressionType.isCompatibleWith(castType)){ // no runtime error
if (castType.id == T_String) constant = expression.constant; // (String) cst is still a constant
return false;
}
if (castType.isCompatibleWith(expressionType)) {
// potential runtime error
this.bits |= NeedRuntimeCheckCastMASK;
return true;
}
} else { // ----- (castType.isInterface) expressionType.isClass -------
if (((ReferenceBinding) expressionType).isFinal()) {
// no subclass for expressionType, thus compile-time check is valid
if (expressionType.isCompatibleWith(castType))
return false;
} else { // a subclass may implement the interface ==> no check at compile time
this.bits |= NeedRuntimeCheckCastMASK;
return true;
}
}
scope.problemReporter().typeCastError(this, castType, expressionType);
return true;
}
// if (expressionType.isInterface()) { cannot be anything else
if (castType.isArrayType()) {
// ----- (castType.isArray) expressionType.isInterface ------
if (scope.isJavaLangCloneable(expressionType)
|| scope.isJavaIoSerializable(expressionType)) {// potential runtime error
this.bits |= NeedRuntimeCheckCastMASK;
} else {
scope.problemReporter().typeCastError(this, castType, expressionType);
}
return true;
} else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isInterface --------
if (scope.isJavaLangObject(castType)) { // no runtime error
return false;
}
if (((ReferenceBinding) castType).isFinal()) {
// no subclass for castType, thus compile-time check is valid
if (!castType.isCompatibleWith(expressionType)) {
// potential runtime error
scope.problemReporter().typeCastError(this, castType, expressionType);
return true;
}
}
} else { // ----- (castType.isInterface) expressionType.isInterface -------
if (castType == expressionType) {
return false; // identity conversion
}
if (Scope.compareTypes(castType, expressionType) == NotRelated) {
MethodBinding[] castTypeMethods = ((ReferenceBinding) castType).methods();
MethodBinding[] expressionTypeMethods =
((ReferenceBinding) expressionType).methods();
int exprMethodsLength = expressionTypeMethods.length;
for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++)
for (int j = 0; j < exprMethodsLength; j++) {
if ((castTypeMethods[i].returnType != expressionTypeMethods[j].returnType)
&& (castTypeMethods[i].selector == expressionTypeMethods[j].selector)
&& castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) {
scope.problemReporter().typeCastError(this, castType, expressionType);
}
}
}
}
this.bits |= NeedRuntimeCheckCastMASK;
return true;
}
/**
* Cast expressions will considered as useful if removing them all would actually bind to a different method
* (no fine grain analysis on per casted argument basis, simply separate widening cast from narrowing ones)
*/
public static void checkNeedForArgumentCasts(BlockScope scope, Expression receiver, ReferenceBinding receiverType, MethodBinding binding, Expression[] arguments, TypeBinding[] argumentTypes, final InvocationSite invocationSite) {
if (scope.environment().options.getSeverity(CompilerOptions.UnnecessaryTypeCheck) == ProblemSeverities.Ignore) return;
int length = argumentTypes.length;
// iterate over arguments, and retrieve original argument types (before cast)
TypeBinding[] rawArgumentTypes = argumentTypes;
for (int i = 0; i < length; i++) {
Expression argument = arguments[i];
if (argument instanceof CastExpression) {
// narrowing conversion on base type may change value, thus necessary
if ((argument.bits & UnnecessaryCastMask) == 0 && argument.resolvedType.isBaseType()) {
continue;
}
TypeBinding castedExpressionType = ((CastExpression)argument).expression.resolvedType;
if (castedExpressionType == null) return; // cannot do better
// obvious identity cast
if (castedExpressionType == argumentTypes[i]) {
scope.problemReporter().unnecessaryCast((CastExpression)argument);
} else {
if (rawArgumentTypes == argumentTypes) {
System.arraycopy(rawArgumentTypes, 0, rawArgumentTypes = new TypeBinding[length], 0, length);
}
// retain original argument type
rawArgumentTypes[i] = castedExpressionType;
}
}
}
// perform alternate lookup with original types
if (rawArgumentTypes != argumentTypes) {
checkAlternateBinding(scope, receiver, receiverType, binding, arguments, argumentTypes, rawArgumentTypes, invocationSite);
}
}
/**
* Check binary operator casted arguments
*/
public static void checkNeedForArgumentCasts(BlockScope scope, int operator, int operatorSignature, Expression left, int leftTypeId, boolean leftIsCast, Expression right, int rightTypeId, boolean rightIsCast) {
if (scope.environment().options.getSeverity(CompilerOptions.UnnecessaryTypeCheck) == ProblemSeverities.Ignore) return;
// check need for left operand cast
int alternateLeftTypeId = leftTypeId;
if (leftIsCast) {
if ((left.bits & UnnecessaryCastMask) == 0 && left.resolvedType.isBaseType()) {
// narrowing conversion on base type may change value, thus necessary
leftIsCast = false;
} else {
TypeBinding alternateLeftType = ((CastExpression)left).expression.resolvedType;
if (alternateLeftType == null) return; // cannot do better
if ((alternateLeftTypeId = alternateLeftType.id) == leftTypeId) { // obvious identity cast
scope.problemReporter().unnecessaryCast((CastExpression)left);
leftIsCast = false;
}
}
}
// check need for right operand cast
int alternateRightTypeId = rightTypeId;
if (rightIsCast) {
if ((right.bits & UnnecessaryCastMask) == 0 && right.resolvedType.isBaseType()) {
// narrowing conversion on base type may change value, thus necessary
rightIsCast = false;
} else {
TypeBinding alternateRightType = ((CastExpression)right).expression.resolvedType;
if (alternateRightType == null) return; // cannot do better
if ((alternateRightTypeId = alternateRightType.id) == rightTypeId) { // obvious identity cast
scope.problemReporter().unnecessaryCast((CastExpression)right);
rightIsCast = false;
}
}
}
if (leftIsCast || rightIsCast) {
int alternateOperatorSignature = OperatorExpression.OperatorSignatures[operator][(alternateLeftTypeId << 4) + alternateRightTypeId];
// (cast) left Op (cast) right --> result
// 1111 0000 1111 0000 1111
// <<16 <<12 <<8 <<4 <<0
final int CompareMASK = (0xF<<16) + (0xF<<8) + 0xF; // mask hiding compile-time types
if ((operatorSignature & CompareMASK) == (alternateOperatorSignature & CompareMASK)) { // same promotions and result
if (leftIsCast) scope.problemReporter().unnecessaryCastForArgument((CastExpression)left, TypeBinding.wellKnownType(scope, left.implicitConversion >> 4));
if (rightIsCast) scope.problemReporter().unnecessaryCastForArgument((CastExpression)right, TypeBinding.wellKnownType(scope, right.implicitConversion >> 4));
}
}
}
private static void checkAlternateBinding(BlockScope scope, Expression receiver, ReferenceBinding receiverType, MethodBinding binding, Expression[] arguments, TypeBinding[] originalArgumentTypes, TypeBinding[] alternateArgumentTypes, final InvocationSite invocationSite) {
InvocationSite fakeInvocationSite = new InvocationSite(){
public boolean isSuperAccess(){ return invocationSite.isSuperAccess(); }
public boolean isTypeAccess() { return invocationSite.isTypeAccess(); }
public void setActualReceiverType(ReferenceBinding actualReceiverType) {}
public void setDepth(int depth) {}
public void setFieldIndex(int depth){}
};
MethodBinding bindingIfNoCast;
if (binding.isConstructor()) {
bindingIfNoCast = scope.getConstructor(receiverType, alternateArgumentTypes, fakeInvocationSite);
} else {
bindingIfNoCast = receiver.isImplicitThis()
? scope.getImplicitMethod(binding.selector, alternateArgumentTypes, fakeInvocationSite)
: scope.getMethod(receiverType, binding.selector, alternateArgumentTypes, fakeInvocationSite);
}
if (bindingIfNoCast == binding) {
for (int i = 0, length = originalArgumentTypes.length; i < length; i++) {
if (originalArgumentTypes[i] != alternateArgumentTypes[i]) {
scope.problemReporter().unnecessaryCastForArgument((CastExpression)arguments[i], binding.parameters[i]);
}
}
}
}
/**
* Cast expression code generation
*
* @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
* @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
* @param valueRequired boolean
*/
public void generateCode(
BlockScope currentScope,
CodeStream codeStream,
boolean valueRequired) {
int pc = codeStream.position;
boolean needRuntimeCheckcast = (this.bits & NeedRuntimeCheckCastMASK) != 0;
if (constant != NotAConstant) {
if (valueRequired || needRuntimeCheckcast) { // Added for: 1F1W9IG: IVJCOM:WINNT - Compiler omits casting check
codeStream.generateConstant(constant, implicitConversion);
if (needRuntimeCheckcast) {
codeStream.checkcast(this.resolvedType);
if (!valueRequired)
codeStream.pop();
}
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
return;
}
expression.generateCode(
currentScope,
codeStream,
valueRequired || needRuntimeCheckcast);
if (needRuntimeCheckcast) {
codeStream.checkcast(this.resolvedType);
if (!valueRequired)
codeStream.pop();
} else {
if (valueRequired)
codeStream.generateImplicitConversion(implicitConversion);
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
public Expression innermostCastedExpression(){
Expression current = this.expression;
while (current instanceof CastExpression) {
current = ((CastExpression) current).expression;
}
return current;
}
public StringBuffer printExpression(int indent, StringBuffer output) {
output.append('(');
type.print(0, output).append(") "); //$NON-NLS-1$
return expression.printExpression(0, output);
}
public TypeBinding resolveType(BlockScope scope) {
// compute a new constant if the cast is effective
// due to the fact an expression may start with ( and that a cast can also start with (
// the field is an expression....it can be a TypeReference OR a NameReference Or
// any kind of Expression <-- this last one is invalid.......
constant = Constant.NotAConstant;
implicitConversion = T_undefined;
if ((type instanceof TypeReference) || (type instanceof NameReference)
&& ((type.bits & AstNode.ParenthesizedMASK) >> AstNode.ParenthesizedSHIFT) == 0) { // no extra parenthesis around type: ((A))exp
this.resolvedType = type.resolveType(scope);
TypeBinding expressionType = expression.resolveType(scope);
if (this.resolvedType != null && expressionType != null) {
boolean necessary = checkCastTypesCompatibility(scope, this.resolvedType, expressionType);
if (!necessary && this.expression.resolvedType != null) { // cannot do better if expression is not bound
this.bits |= UnnecessaryCastMask;
if ((this.bits & IgnoreNeedForCastCheckMASK) == 0) {
scope.problemReporter().unnecessaryCast(this);
}
}
}
return this.resolvedType;
} else { // expression as a cast !!!!!!!!
TypeBinding expressionType = expression.resolveType(scope);
if (expressionType == null) return null;
scope.problemReporter().invalidTypeReference(type);
return null;
}
}
public void traverse(
IAbstractSyntaxTreeVisitor visitor,
BlockScope blockScope) {
if (visitor.visit(this, blockScope)) {
type.traverse(visitor, blockScope);
expression.traverse(visitor, blockScope);
}
visitor.endVisit(this, blockScope);
}
}