/******************************************************************************* | |
* Copyright (c) 2000, 2004 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.wst.jsdt.internal.compiler.ast; | |
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor; | |
import org.eclipse.wst.jsdt.internal.compiler.impl.*; | |
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.*; | |
import org.eclipse.wst.jsdt.internal.compiler.flow.*; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.*; | |
public class ConditionalExpression extends OperatorExpression { | |
public Expression condition, valueIfTrue, valueIfFalse; | |
public Constant optimizedBooleanConstant; | |
public Constant optimizedIfTrueConstant; | |
public Constant optimizedIfFalseConstant; | |
// for local variables table attributes | |
int trueInitStateIndex = -1; | |
int falseInitStateIndex = -1; | |
int mergedInitStateIndex = -1; | |
public ConditionalExpression( | |
Expression condition, | |
Expression valueIfTrue, | |
Expression valueIfFalse) { | |
this.condition = condition; | |
this.valueIfTrue = valueIfTrue; | |
this.valueIfFalse = valueIfFalse; | |
sourceStart = condition.sourceStart; | |
sourceEnd = valueIfFalse.sourceEnd; | |
} | |
public FlowInfo analyseCode( | |
BlockScope currentScope, | |
FlowContext flowContext, | |
FlowInfo flowInfo) { | |
Constant cst = this.condition.optimizedBooleanConstant(); | |
boolean isConditionOptimizedTrue = cst != NotAConstant && cst.booleanValue() == true; | |
boolean isConditionOptimizedFalse = cst != NotAConstant && cst.booleanValue() == false; | |
int mode = flowInfo.reachMode(); | |
flowInfo = condition.analyseCode(currentScope, flowContext, flowInfo, cst == NotAConstant); | |
// process the if-true part | |
FlowInfo trueFlowInfo = flowInfo.initsWhenTrue().copy(); | |
if (isConditionOptimizedFalse) { | |
trueFlowInfo.setReachMode(FlowInfo.UNREACHABLE); | |
} | |
trueInitStateIndex = currentScope.methodScope().recordInitializationStates(trueFlowInfo); | |
trueFlowInfo = valueIfTrue.analyseCode(currentScope, flowContext, trueFlowInfo); | |
// process the if-false part | |
FlowInfo falseFlowInfo = flowInfo.initsWhenFalse().copy(); | |
if (isConditionOptimizedTrue) { | |
falseFlowInfo.setReachMode(FlowInfo.UNREACHABLE); | |
} | |
falseInitStateIndex = currentScope.methodScope().recordInitializationStates(falseFlowInfo); | |
falseFlowInfo = valueIfFalse.analyseCode(currentScope, flowContext, falseFlowInfo); | |
// merge if-true & if-false initializations | |
FlowInfo mergedInfo; | |
if (isConditionOptimizedTrue){ | |
mergedInfo = trueFlowInfo.addPotentialInitializationsFrom(falseFlowInfo); | |
} else if (isConditionOptimizedFalse) { | |
mergedInfo = falseFlowInfo.addPotentialInitializationsFrom(trueFlowInfo); | |
} else { | |
// if ((t && (v = t)) ? t : t && (v = f)) r = v; -- ok | |
cst = this.optimizedIfTrueConstant; | |
boolean isValueIfTrueOptimizedTrue = cst != null && cst != NotAConstant && cst.booleanValue() == true; | |
boolean isValueIfTrueOptimizedFalse = cst != null && cst != NotAConstant && cst.booleanValue() == false; | |
cst = this.optimizedIfFalseConstant; | |
boolean isValueIfFalseOptimizedTrue = cst != null && cst != NotAConstant && cst.booleanValue() == true; | |
boolean isValueIfFalseOptimizedFalse = cst != null && cst != NotAConstant && cst.booleanValue() == false; | |
UnconditionalFlowInfo trueInfoWhenTrue = trueFlowInfo.initsWhenTrue().copy().unconditionalInits(); | |
if (isValueIfTrueOptimizedFalse) trueInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); | |
UnconditionalFlowInfo falseInfoWhenTrue = falseFlowInfo.initsWhenTrue().copy().unconditionalInits(); | |
if (isValueIfFalseOptimizedFalse) falseInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); | |
UnconditionalFlowInfo trueInfoWhenFalse = trueFlowInfo.initsWhenFalse().copy().unconditionalInits(); | |
if (isValueIfTrueOptimizedTrue) trueInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); | |
UnconditionalFlowInfo falseInfoWhenFalse = falseFlowInfo.initsWhenFalse().copy().unconditionalInits(); | |
if (isValueIfFalseOptimizedTrue) falseInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); | |
mergedInfo = | |
FlowInfo.conditional( | |
trueInfoWhenTrue.mergedWith(falseInfoWhenTrue), | |
trueInfoWhenFalse.mergedWith(falseInfoWhenFalse)); | |
} | |
mergedInitStateIndex = | |
currentScope.methodScope().recordInitializationStates(mergedInfo); | |
mergedInfo.setReachMode(mode); | |
return mergedInfo; | |
} | |
/** | |
* Code generation for the conditional operator ?: | |
* | |
* @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope | |
* @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream | |
* @param valueRequired boolean | |
*/ | |
public void generateCode( | |
BlockScope currentScope, | |
CodeStream codeStream, | |
boolean valueRequired) { | |
int pc = codeStream.position; | |
Label endifLabel, falseLabel; | |
if (constant != NotAConstant) { | |
if (valueRequired) | |
codeStream.generateConstant(constant, implicitConversion); | |
codeStream.recordPositionsFrom(pc, this.sourceStart); | |
return; | |
} | |
Constant cst = condition.constant; | |
Constant condCst = condition.optimizedBooleanConstant(); | |
boolean needTruePart = | |
!(((cst != NotAConstant) && (cst.booleanValue() == false)) | |
|| ((condCst != NotAConstant) && (condCst.booleanValue() == false))); | |
boolean needFalsePart = | |
!(((cst != NotAConstant) && (cst.booleanValue() == true)) | |
|| ((condCst != NotAConstant) && (condCst.booleanValue() == true))); | |
endifLabel = new Label(codeStream); | |
// Generate code for the condition | |
boolean needConditionValue = (cst == NotAConstant) && (condCst == NotAConstant); | |
condition.generateOptimizedBoolean( | |
currentScope, | |
codeStream, | |
null, | |
(falseLabel = new Label(codeStream)), | |
needConditionValue); | |
if (trueInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables( | |
currentScope, | |
trueInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, trueInitStateIndex); | |
} | |
// Then code generation | |
if (needTruePart) { | |
valueIfTrue.generateCode(currentScope, codeStream, valueRequired); | |
if (needFalsePart) { | |
// Jump over the else part | |
int position = codeStream.position; | |
codeStream.goto_(endifLabel); | |
codeStream.updateLastRecordedEndPC(position); | |
// Tune codestream stack size | |
if (valueRequired) { | |
codeStream.decrStackSize(this.resolvedType == LongBinding || this.resolvedType == DoubleBinding ? 2 : 1); | |
} | |
} | |
} | |
if (needFalsePart) { | |
falseLabel.place(); | |
if (falseInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables( | |
currentScope, | |
falseInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, falseInitStateIndex); | |
} | |
valueIfFalse.generateCode(currentScope, codeStream, valueRequired); | |
// End of if statement | |
endifLabel.place(); | |
} | |
// May loose some local variable initializations : affecting the local variable attributes | |
if (mergedInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables( | |
currentScope, | |
mergedInitStateIndex); | |
} | |
// implicit conversion | |
if (valueRequired) | |
codeStream.generateImplicitConversion(implicitConversion); | |
codeStream.recordPositionsFrom(pc, this.sourceStart); | |
} | |
/** | |
* Optimized boolean code generation for the conditional operator ?: | |
*/ | |
public void generateOptimizedBoolean( | |
BlockScope currentScope, | |
CodeStream codeStream, | |
Label trueLabel, | |
Label falseLabel, | |
boolean valueRequired) { | |
if ((constant != Constant.NotAConstant) && (constant.typeID() == T_boolean) // constant | |
|| (valueIfTrue.implicitConversion >> 4) != T_boolean) { // non boolean values | |
super.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
return; | |
} | |
Constant cst = condition.constant; | |
Constant condCst = condition.optimizedBooleanConstant(); | |
boolean needTruePart = | |
!(((cst != NotAConstant) && (cst.booleanValue() == false)) | |
|| ((condCst != NotAConstant) && (condCst.booleanValue() == false))); | |
boolean needFalsePart = | |
!(((cst != NotAConstant) && (cst.booleanValue() == true)) | |
|| ((condCst != NotAConstant) && (condCst.booleanValue() == true))); | |
Label internalFalseLabel, endifLabel = new Label(codeStream); | |
// Generate code for the condition | |
boolean needConditionValue = (cst == NotAConstant) && (condCst == NotAConstant); | |
condition.generateOptimizedBoolean( | |
currentScope, | |
codeStream, | |
null, | |
internalFalseLabel = new Label(codeStream), | |
needConditionValue); | |
if (trueInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables( | |
currentScope, | |
trueInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, trueInitStateIndex); | |
} | |
// Then code generation | |
if (needTruePart) { | |
valueIfTrue.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
if (needFalsePart) { | |
// Jump over the else part | |
int position = codeStream.position; | |
codeStream.goto_(endifLabel); | |
codeStream.updateLastRecordedEndPC(position); | |
// No need to decrement codestream stack size | |
// since valueIfTrue was already consumed by branch bytecode | |
} | |
} | |
if (needFalsePart) { | |
internalFalseLabel.place(); | |
if (falseInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables(currentScope, falseInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, falseInitStateIndex); | |
} | |
valueIfFalse.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); | |
// End of if statement | |
endifLabel.place(); | |
} | |
// May loose some local variable initializations : affecting the local variable attributes | |
if (mergedInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); | |
} | |
// no implicit conversion for boolean values | |
codeStream.updateLastRecordedEndPC(codeStream.position); | |
} | |
public Constant optimizedBooleanConstant() { | |
return this.optimizedBooleanConstant == null ? this.constant : this.optimizedBooleanConstant; | |
} | |
public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) { | |
condition.printExpression(indent, output).append(" ? "); //$NON-NLS-1$ | |
valueIfTrue.printExpression(0, output).append(" : "); //$NON-NLS-1$ | |
return valueIfFalse.printExpression(0, output); | |
} | |
public TypeBinding resolveType(BlockScope scope) { | |
// specs p.368 | |
constant = NotAConstant; | |
TypeBinding conditionType = condition.resolveTypeExpecting(scope, BooleanBinding); | |
if (valueIfTrue instanceof CastExpression) valueIfTrue.bits |= IgnoreNeedForCastCheckMASK; // will check later on | |
TypeBinding valueIfTrueType = valueIfTrue.resolveType(scope); | |
if (valueIfFalse instanceof CastExpression) valueIfFalse.bits |= IgnoreNeedForCastCheckMASK; // will check later on | |
TypeBinding valueIfFalseType = valueIfFalse.resolveType(scope); | |
if (conditionType == null || valueIfTrueType == null || valueIfFalseType == null) | |
return null; | |
// Propagate the constant value from the valueIfTrue and valueIFFalse expression if it is possible | |
Constant condConstant, trueConstant, falseConstant; | |
if ((condConstant = condition.constant) != NotAConstant | |
&& (trueConstant = valueIfTrue.constant) != NotAConstant | |
&& (falseConstant = valueIfFalse.constant) != NotAConstant) { | |
// all terms are constant expression so we can propagate the constant | |
// from valueIFTrue or valueIfFalse to teh receiver constant | |
constant = condConstant.booleanValue() ? trueConstant : falseConstant; | |
} | |
if (valueIfTrueType == valueIfFalseType) { // harmed the implicit conversion | |
valueIfTrue.computeConversion(scope, valueIfTrueType, valueIfTrueType); | |
valueIfFalse.implicitConversion = valueIfTrue.implicitConversion; | |
if (valueIfTrueType == BooleanBinding) { | |
this.optimizedIfTrueConstant = valueIfTrue.optimizedBooleanConstant(); | |
this.optimizedIfFalseConstant = valueIfFalse.optimizedBooleanConstant(); | |
if (this.optimizedIfTrueConstant != NotAConstant | |
&& this.optimizedIfFalseConstant != NotAConstant | |
&& this.optimizedIfTrueConstant.booleanValue() == this.optimizedIfFalseConstant.booleanValue()) { | |
// a ? true : true / a ? false : false | |
this.optimizedBooleanConstant = optimizedIfTrueConstant; | |
} else if ((condConstant = condition.optimizedBooleanConstant()) != NotAConstant) { // Propagate the optimized boolean constant if possible | |
this.optimizedBooleanConstant = condConstant.booleanValue() | |
? this.optimizedIfTrueConstant | |
: this.optimizedIfFalseConstant; | |
} | |
} | |
return this.resolvedType = valueIfTrueType; | |
} | |
// Determine the return type depending on argument types | |
// Numeric types | |
if (valueIfTrueType.isNumericType() && valueIfFalseType.isNumericType()) { | |
// (Short x Byte) or (Byte x Short)" | |
if ((valueIfTrueType == ByteBinding && valueIfFalseType == ShortBinding) | |
|| (valueIfTrueType == ShortBinding && valueIfFalseType == ByteBinding)) { | |
valueIfTrue.computeConversion(scope, ShortBinding, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, ShortBinding, valueIfFalseType); | |
return this.resolvedType = ShortBinding; | |
} | |
// <Byte|Short|Char> x constant(Int) ---> <Byte|Short|Char> and reciprocally | |
if ((valueIfTrueType == ByteBinding || valueIfTrueType == ShortBinding || valueIfTrueType == CharBinding) | |
&& (valueIfFalseType == IntBinding | |
&& valueIfFalse.isConstantValueOfTypeAssignableToType(valueIfFalseType, valueIfTrueType))) { | |
valueIfTrue.computeConversion(scope, valueIfTrueType, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, valueIfTrueType, valueIfFalseType); | |
return this.resolvedType = valueIfTrueType; | |
} | |
if ((valueIfFalseType == ByteBinding | |
|| valueIfFalseType == ShortBinding | |
|| valueIfFalseType == CharBinding) | |
&& (valueIfTrueType == IntBinding | |
&& valueIfTrue.isConstantValueOfTypeAssignableToType(valueIfTrueType, valueIfFalseType))) { | |
valueIfTrue.computeConversion(scope, valueIfFalseType, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, valueIfFalseType, valueIfFalseType); | |
return this.resolvedType = valueIfFalseType; | |
} | |
// Manual binary numeric promotion | |
// int | |
if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_int) | |
&& BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_int)) { | |
valueIfTrue.computeConversion(scope, IntBinding, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, IntBinding, valueIfFalseType); | |
return this.resolvedType = IntBinding; | |
} | |
// long | |
if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_long) | |
&& BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_long)) { | |
valueIfTrue.computeConversion(scope, LongBinding, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, LongBinding, valueIfFalseType); | |
return this.resolvedType = LongBinding; | |
} | |
// float | |
if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_float) | |
&& BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_float)) { | |
valueIfTrue.computeConversion(scope, FloatBinding, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, FloatBinding, valueIfFalseType); | |
return this.resolvedType = FloatBinding; | |
} | |
// double | |
valueIfTrue.computeConversion(scope, DoubleBinding, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, DoubleBinding, valueIfFalseType); | |
return this.resolvedType = DoubleBinding; | |
} | |
// Type references (null null is already tested) | |
if ((valueIfTrueType.isBaseType() && valueIfTrueType != NullBinding) | |
|| (valueIfFalseType.isBaseType() && valueIfFalseType != NullBinding)) { | |
scope.problemReporter().conditionalArgumentsIncompatibleTypes( | |
this, | |
valueIfTrueType, | |
valueIfFalseType); | |
return null; | |
} | |
if (valueIfFalseType.isCompatibleWith(valueIfTrueType)) { | |
valueIfTrue.computeConversion(scope, valueIfTrueType, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, valueIfTrueType, valueIfFalseType); | |
return this.resolvedType = valueIfTrueType; | |
} | |
if (valueIfTrueType.isCompatibleWith(valueIfFalseType)) { | |
valueIfTrue.computeConversion(scope, valueIfFalseType, valueIfTrueType); | |
valueIfFalse.computeConversion(scope, valueIfFalseType, valueIfFalseType); | |
return this.resolvedType = valueIfFalseType; | |
} | |
// 1.5 addition: allow most common type | |
if (scope.environment().options.sourceLevel >= ClassFileConstants.JDK1_5) { | |
TypeBinding commonType = scope.lowerUpperBound(new TypeBinding[] { valueIfTrueType, valueIfFalseType }); | |
// TypeBinding commonType = scope.mostSpecificCommonType(new TypeBinding[] { valueIfTrueType, valueIfFalseType }); | |
if (commonType != null) { | |
return this.resolvedType = commonType; | |
} | |
} | |
scope.problemReporter().conditionalArgumentsIncompatibleTypes( | |
this, | |
valueIfTrueType, | |
valueIfFalseType); | |
return null; | |
} | |
public void traverse(ASTVisitor visitor, BlockScope scope) { | |
if (visitor.visit(this, scope)) { | |
condition.traverse(visitor, scope); | |
valueIfTrue.traverse(visitor, scope); | |
valueIfFalse.traverse(visitor, scope); | |
} | |
visitor.endVisit(this, scope); | |
} | |
} |