package org.eclipse.jdt.internal.compiler.ast; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
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.*; | |
import org.eclipse.jdt.internal.compiler.util.*; | |
public class EqualExpression extends BinaryExpression { | |
public EqualExpression(Expression left, Expression right,int operator) { | |
super(left,right,operator); | |
} | |
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { | |
if (((bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) { | |
if ((left.constant != NotAConstant) && (left.constant.typeID() == T_boolean)) { | |
if (left.constant.booleanValue()) { // true == anything | |
// this is equivalent to the right argument inits | |
return right.analyseCode(currentScope, flowContext, flowInfo); | |
} else { // false == anything | |
// this is equivalent to the right argument inits negated | |
return right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); | |
} | |
} | |
if ((right.constant != NotAConstant) && (right.constant.typeID() == T_boolean)) { | |
if (right.constant.booleanValue()) { // anything == true | |
// this is equivalent to the right argument inits | |
return left.analyseCode(currentScope, flowContext, flowInfo); | |
} else { // anything == false | |
// this is equivalent to the right argument inits negated | |
return left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); | |
} | |
} | |
return right.analyseCode( | |
currentScope, flowContext, | |
left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).unconditionalInits(); | |
} else { //NOT_EQUAL : | |
if ((left.constant != NotAConstant) && (left.constant.typeID() == T_boolean)) { | |
if (!left.constant.booleanValue()) { // false != anything | |
// this is equivalent to the right argument inits | |
return right.analyseCode(currentScope, flowContext, flowInfo); | |
} else { // true != anything | |
// this is equivalent to the right argument inits negated | |
return right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); | |
} | |
} | |
if ((right.constant != NotAConstant) && (right.constant.typeID() == T_boolean)) { | |
if (!right.constant.booleanValue()) { // anything != false | |
// this is equivalent to the right argument inits | |
return left.analyseCode(currentScope, flowContext, flowInfo); | |
} else { // anything != true | |
// this is equivalent to the right argument inits negated | |
return left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); | |
} | |
} | |
return right.analyseCode( | |
currentScope, flowContext, | |
left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).asNegatedCondition().unconditionalInits(); | |
} | |
} | |
public final boolean areTypesCastCompatible(BlockScope scope, TypeBinding castTb, TypeBinding expressionTb) { | |
//see specifications p.68 | |
//A more complete version of this method is provided on | |
//CastExpression (it deals with constant and need runtime checkcast) | |
//========ARRAY=============== | |
if (expressionTb.isArrayType()) { | |
if (castTb.isArrayType()) { //------- (castTb.isArray) expressionTb.isArray ----------- | |
TypeBinding expressionEltTb = ((ArrayBinding) expressionTb).elementsType(scope); | |
if (expressionEltTb.isBaseType()) | |
// <---stop the recursion------- | |
return ((ArrayBinding) castTb).elementsType(scope) == expressionEltTb; | |
//recursivly on the elts... | |
return areTypesCastCompatible(scope, ((ArrayBinding) castTb).elementsType(scope), expressionEltTb); | |
} | |
if (castTb.isBaseType()) { | |
return false; | |
} | |
if (castTb.isClass()) { //------(castTb.isClass) expressionTb.isArray --------------- | |
if (scope.isJavaLangObject(castTb)) | |
return true; | |
return false; | |
} | |
if (castTb.isInterface()) { //------- (castTb.isInterface) expressionTb.isArray ----------- | |
if (scope.isJavaLangCloneable(castTb) || scope.isJavaIoSerializable(castTb)) { | |
return true; | |
} | |
return false; | |
} | |
//===houps===== | |
return false; | |
} | |
//------------(castType) null-------------- | |
if (expressionTb == NullBinding) { | |
return !castTb.isBaseType(); | |
} | |
//========BASETYPE============== | |
if (expressionTb.isBaseType()) { | |
return false; | |
} | |
//========REFERENCE TYPE=================== | |
if (expressionTb.isClass()) { | |
if (castTb.isArrayType()) { // ---- (castTb.isArray) expressionTb.isClass ------- | |
if (scope.isJavaLangObject(expressionTb)) | |
return true; | |
} | |
if (castTb.isBaseType()) { | |
return false; | |
} | |
if (castTb.isClass()) { // ----- (castTb.isClass) expressionTb.isClass ------ | |
if (scope.areTypesCompatible(expressionTb, castTb)) | |
return true; | |
else { | |
if (scope.areTypesCompatible(castTb, expressionTb)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
if (castTb.isInterface()) { // ----- (castTb.isInterface) expressionTb.isClass ------- | |
if (((ReferenceBinding) expressionTb).isFinal()) { //no subclass for expressionTb, thus compile-time check is valid | |
if (scope.areTypesCompatible(expressionTb, castTb)) | |
return true; | |
return false; | |
} else { | |
return true; | |
} | |
} | |
//=========houps============= | |
return false; | |
} | |
if (expressionTb.isInterface()) { | |
if (castTb.isArrayType()) { // ----- (castTb.isArray) expressionTb.isInterface ------ | |
if (scope.isJavaLangCloneable(expressionTb) || scope.isJavaIoSerializable(expressionTb)) | |
//potential runtime error | |
{ | |
return true; | |
} | |
return false; | |
} | |
if (castTb.isBaseType()) { | |
return false; | |
} | |
if (castTb.isClass()) { // ----- (castTb.isClass) expressionTb.isInterface -------- | |
if (scope.isJavaLangObject(castTb)) | |
return true; | |
if (((ReferenceBinding) castTb).isFinal()) { //no subclass for castTb, thus compile-time check is valid | |
if (scope.areTypesCompatible(castTb, expressionTb)) { | |
return true; | |
} | |
return false; | |
} | |
return true; | |
} | |
if (castTb.isInterface()) { // ----- (castTb.isInterface) expressionTb.isInterface ------- | |
if (castTb != expressionTb && (scope.compareTypes(castTb, expressionTb) == NotRelated)) { | |
MethodBinding[] castTbMethods = ((ReferenceBinding) castTb).methods(); | |
int castTbMethodsLength = castTbMethods.length; | |
MethodBinding[] expressionTbMethods = ((ReferenceBinding) expressionTb).methods(); | |
int expressionTbMethodsLength = expressionTbMethods.length; | |
for (int i = 0; i < castTbMethodsLength; i++) { | |
for (int j = 0; j < expressionTbMethodsLength; j++) { | |
if (castTbMethods[i].selector == expressionTbMethods[j].selector) { | |
if (castTbMethods[i].returnType != expressionTbMethods[j].returnType) { | |
if (castTbMethods[i].areParametersEqual(expressionTbMethods[j])) { | |
return false; | |
} | |
} | |
} | |
} | |
} | |
} | |
return true; | |
} | |
//=========hoops=========== | |
return false; | |
} | |
//==========HOUPS========== | |
return false; | |
} | |
public final void computeConstant(TypeBinding leftTb, TypeBinding rightTb){ | |
if ( (left.constant != NotAConstant) && (right.constant != NotAConstant) ) | |
{ constant = Constant.computeConstantOperationEQUAL_EQUAL(left.constant,rightTb.id,EQUAL_EQUAL,right.constant,rightTb.id); | |
if (((bits & OperatorMASK) >> OperatorSHIFT) == NOT_EQUAL) | |
constant = Constant.fromValue(! constant.booleanValue()) ;} | |
else | |
constant = NotAConstant ; | |
} | |
/** | |
* Normal == or != 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) { | |
if (constant != NotAConstant) { | |
int pc = codeStream.position; | |
if (valueRequired) | |
codeStream.generateConstant(constant, implicitConversion); | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
Label falseLabel = new Label(codeStream); | |
generateOptimizedBoolean(currentScope, codeStream, null, falseLabel, valueRequired); | |
if (valueRequired){ | |
// comparison is TRUE | |
codeStream.iconst_1(); | |
if ((bits & ValueForReturnMASK) != 0){ | |
codeStream.ireturn(); | |
// comparison is FALSE | |
falseLabel.place(); | |
codeStream.iconst_0(); | |
} else { | |
Label endLabel = new Label(codeStream); | |
codeStream.goto_(endLabel); | |
codeStream.decrStackSize(1); | |
// comparison is FALSE | |
falseLabel.place(); | |
codeStream.iconst_0(); | |
endLabel.place(); | |
} | |
} | |
} | |
/** | |
* Boolean operator code generation | |
* Optimized operations are: == and != | |
*/ | |
public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, Label trueLabel, Label falseLabel, boolean valueRequired) { | |
if ((constant != Constant.NotAConstant) && (constant.typeID() == T_boolean)) { | |
super.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
return; | |
} | |
int pc = codeStream.position; | |
if (((bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) { | |
if ((left.implicitConversion & 0xF) /*compile-time*/ == T_boolean) { | |
generateOptimizedBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
} else { | |
generateOptimizedNonBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
} | |
} else { | |
if ((left.implicitConversion & 0xF) /*compile-time*/ == T_boolean) { | |
generateOptimizedBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired); | |
} else { | |
generateOptimizedNonBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired); | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
} | |
/** | |
* Boolean generation for == with boolean operands | |
* | |
* Note this code does not optimize conditional constants !!!! | |
*/ | |
public void generateOptimizedBooleanEqual(BlockScope currentScope, CodeStream codeStream, Label trueLabel, Label falseLabel, boolean valueRequired) { | |
int pc = codeStream.position; | |
// optimized cases: true == x, false == x | |
if (left.constant != NotAConstant) { | |
boolean inline = left.constant.booleanValue(); | |
right.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel), (inline ? falseLabel : trueLabel), valueRequired); | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} // optimized cases: x == true, x == false | |
if (right.constant != NotAConstant) { | |
boolean inline = right.constant.booleanValue(); | |
left.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel), (inline ? falseLabel : trueLabel), valueRequired); | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
// default case | |
left.generateCode(currentScope, codeStream, valueRequired); | |
right.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
codeStream.if_icmpeq(trueLabel); | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
codeStream.if_icmpne(falseLabel); | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
} | |
/** | |
* Boolean generation for == with non-boolean operands | |
* | |
*/ | |
public void generateOptimizedNonBooleanEqual(BlockScope currentScope, CodeStream codeStream, Label trueLabel, Label falseLabel, boolean valueRequired) { | |
int pc = codeStream.position; | |
Constant inline; | |
if ((inline = right.constant) != NotAConstant) { | |
// optimized case: x == null | |
if (right.constant == NullConstant.Default) { | |
left.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
codeStream.ifnull(trueLabel); | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
codeStream.ifnonnull(falseLabel); | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
// optimized case: x == 0 | |
if (((left.implicitConversion >> 4) == T_int) && (inline.intValue() == 0)) { | |
left.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
codeStream.ifeq(trueLabel); | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
codeStream.ifne(falseLabel); | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
} | |
if ((inline = left.constant) != NotAConstant) { | |
// optimized case: null == x | |
if (left.constant == NullConstant.Default) { | |
right.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
codeStream.ifnull(trueLabel); | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
codeStream.ifnonnull(falseLabel); | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
// optimized case: 0 == x | |
if (((left.implicitConversion >> 4) == T_int) | |
&& (inline.intValue() == 0)) { | |
right.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
codeStream.ifeq(trueLabel); | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
codeStream.ifne(falseLabel); | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
return; | |
} | |
} | |
// default case | |
left.generateCode(currentScope, codeStream, valueRequired); | |
right.generateCode(currentScope, codeStream, valueRequired); | |
if (valueRequired) { | |
if (falseLabel == null) { | |
if (trueLabel != null) { | |
// implicit falling through the FALSE case | |
switch (left.implicitConversion >> 4) { // operand runtime type | |
case T_int : | |
codeStream.if_icmpeq(trueLabel); | |
break; | |
case T_float : | |
codeStream.fcmpl(); | |
codeStream.ifeq(trueLabel); | |
break; | |
case T_long : | |
codeStream.lcmp(); | |
codeStream.ifeq(trueLabel); | |
break; | |
case T_double : | |
codeStream.dcmpl(); | |
codeStream.ifeq(trueLabel); | |
break; | |
default : | |
codeStream.if_acmpeq(trueLabel); | |
} | |
} | |
} else { | |
// implicit falling through the TRUE case | |
if (trueLabel == null) { | |
switch (left.implicitConversion >> 4) { // operand runtime type | |
case T_int : | |
codeStream.if_icmpne(falseLabel); | |
break; | |
case T_float : | |
codeStream.fcmpl(); | |
codeStream.ifne(falseLabel); | |
break; | |
case T_long : | |
codeStream.lcmp(); | |
codeStream.ifne(falseLabel); | |
break; | |
case T_double : | |
codeStream.dcmpl(); | |
codeStream.ifne(falseLabel); | |
break; | |
default : | |
codeStream.if_acmpne(falseLabel); | |
} | |
} else { | |
// no implicit fall through TRUE/FALSE --> should never occur | |
} | |
} | |
} | |
codeStream.recordPositionsFrom(pc, this); | |
} | |
public boolean isCompactableOperation() { | |
return false; | |
} | |
public TypeBinding resolveType(BlockScope scope) { | |
// always return BooleanBinding | |
TypeBinding leftTb = left.resolveType(scope); | |
TypeBinding rightTb = right.resolveType(scope); | |
if (leftTb == null || rightTb == null){ | |
constant = NotAConstant; | |
return null; | |
} | |
// both base type | |
if (leftTb.isBaseType() && rightTb.isBaseType()) { | |
// the code is an int | |
// (cast) left == (cast) rigth --> result | |
// 0000 0000 0000 0000 0000 | |
// <<16 <<12 <<8 <<4 <<0 | |
int result = ResolveTypeTables[EQUAL_EQUAL][ (leftTb.id << 4) + rightTb.id]; | |
left.implicitConversion = result >>> 12; | |
right.implicitConversion = (result >>> 4) & 0x000FF; | |
bits |= result & 0xF; | |
if ((result & 0x0000F) == T_undefined) { | |
constant = Constant.NotAConstant; | |
scope.problemReporter().invalidOperator(this, leftTb, rightTb); | |
return null; | |
} | |
computeConstant(leftTb, rightTb); | |
return BooleanBinding; | |
} | |
// Object references | |
// spec 15.20.3 | |
if (areTypesCastCompatible(scope, rightTb, leftTb) || areTypesCastCompatible(scope, leftTb, rightTb)) { | |
// (special case for String) | |
if ((rightTb.id == T_String) && (leftTb.id == T_String)) | |
computeConstant(leftTb, rightTb); | |
else | |
constant = NotAConstant; | |
if (rightTb.id == T_String) | |
right.implicitConversion = String2String; | |
if (leftTb.id == T_String) | |
left.implicitConversion = String2String; | |
return BooleanBinding; | |
} | |
constant = NotAConstant; | |
scope.problemReporter().notCompatibleTypesError(this, leftTb, rightTb); | |
return null; | |
} | |
public void traverse(IAbstractSyntaxTreeVisitor visitor, BlockScope scope) { | |
if (visitor.visit(this, scope)) { | |
left.traverse(visitor, scope); | |
right.traverse(visitor, scope); | |
} | |
visitor.endVisit(this, scope); | |
} | |
} |