/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; 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; this.sourceStart= condition.sourceStart; this.sourceEnd= valueIfFalse.sourceEnd; } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { int initialComplaintLevel= (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; Constant cst= this.condition.optimizedBooleanConstant(); boolean isConditionOptimizedTrue= cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isConditionOptimizedFalse= cst != Constant.NotAConstant && cst.booleanValue() == false; int mode= flowInfo.reachMode(); flowInfo= this.condition.analyseCode(currentScope, flowContext, flowInfo, cst == Constant.NotAConstant); // process the if-true part FlowInfo trueFlowInfo= flowInfo.initsWhenTrue().copy(); if (isConditionOptimizedFalse) { if ((mode & FlowInfo.UNREACHABLE) == 0) { trueFlowInfo.setReachMode(FlowInfo.UNREACHABLE); } if (!isKnowDeadCodePattern(this.condition) || currentScope.compilerOptions().reportDeadCodeInTrivialIfStatement) { this.valueIfTrue.complainIfUnreachable(trueFlowInfo, currentScope, initialComplaintLevel); } } this.trueInitStateIndex= currentScope.methodScope().recordInitializationStates(trueFlowInfo); trueFlowInfo= this.valueIfTrue.analyseCode(currentScope, flowContext, trueFlowInfo); // process the if-false part FlowInfo falseFlowInfo= flowInfo.initsWhenFalse().copy(); if (isConditionOptimizedTrue) { if ((mode & FlowInfo.UNREACHABLE) == 0) { falseFlowInfo.setReachMode(FlowInfo.UNREACHABLE); } if (!isKnowDeadCodePattern(this.condition) || currentScope.compilerOptions().reportDeadCodeInTrivialIfStatement) { this.valueIfFalse.complainIfUnreachable(falseFlowInfo, currentScope, initialComplaintLevel); } } this.falseInitStateIndex= currentScope.methodScope().recordInitializationStates(falseFlowInfo); falseFlowInfo= this.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 != Constant.NotAConstant && cst.booleanValue() == true; boolean isValueIfTrueOptimizedFalse= cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; cst= this.optimizedIfFalseConstant; boolean isValueIfFalseOptimizedTrue= cst != null && cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isValueIfFalseOptimizedFalse= cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; UnconditionalFlowInfo trueInfoWhenTrue= trueFlowInfo.initsWhenTrue().unconditionalCopy(); UnconditionalFlowInfo falseInfoWhenTrue= falseFlowInfo.initsWhenTrue().unconditionalCopy(); UnconditionalFlowInfo trueInfoWhenFalse= trueFlowInfo.initsWhenFalse().unconditionalInits(); UnconditionalFlowInfo falseInfoWhenFalse= falseFlowInfo.initsWhenFalse().unconditionalInits(); if (isValueIfTrueOptimizedFalse) { trueInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfFalseOptimizedFalse) { falseInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfTrueOptimizedTrue) { trueInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfFalseOptimizedTrue) { falseInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); } mergedInfo= FlowInfo.conditional( trueInfoWhenTrue.mergedWith(falseInfoWhenTrue), trueInfoWhenFalse.mergedWith(falseInfoWhenFalse)); } this.mergedInitStateIndex= currentScope.methodScope().recordInitializationStates(mergedInfo); mergedInfo.setReachMode(mode); return mergedInfo; } /** * Code generation for the conditional operator ?: * * @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; BranchLabel endifLabel, falseLabel; if (this.constant != Constant.NotAConstant) { if (valueRequired) codeStream.generateConstant(this.constant, this.implicitConversion); codeStream.recordPositionsFrom(pc, this.sourceStart); return; } Constant cst= this.condition.optimizedBooleanConstant(); boolean needTruePart= !(cst != Constant.NotAConstant && cst.booleanValue() == false); boolean needFalsePart= !(cst != Constant.NotAConstant && cst.booleanValue() == true); endifLabel= new BranchLabel(codeStream); // Generate code for the condition falseLabel= new BranchLabel(codeStream); falseLabel.tagBits|= BranchLabel.USED; this.condition.generateOptimizedBoolean( currentScope, codeStream, null, falseLabel, cst == Constant.NotAConstant); if (this.trueInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables( currentScope, this.trueInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.trueInitStateIndex); } // Then code generation if (needTruePart) { this.valueIfTrue.generateCode(currentScope, codeStream, valueRequired); if (needFalsePart) { // Jump over the else part int position= codeStream.position; codeStream.goto_(endifLabel); codeStream.updateLastRecordedEndPC(currentScope, position); // Tune codestream stack size if (valueRequired) { switch (this.resolvedType.id) { case TypeIds.T_long: case TypeIds.T_double: codeStream.decrStackSize(2); break; default: codeStream.decrStackSize(1); break; } } } } if (needFalsePart) { if (this.falseInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables( currentScope, this.falseInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.falseInitStateIndex); } if (falseLabel.forwardReferenceCount() > 0) { falseLabel.place(); } this.valueIfFalse.generateCode(currentScope, codeStream, valueRequired); if (valueRequired) { codeStream.recordExpressionType(this.resolvedType); } if (needTruePart) { // End of if statement endifLabel.place(); } } // May loose some local variable initializations : affecting the local variable attributes if (this.mergedInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables( currentScope, this.mergedInitStateIndex); } // implicit conversion if (valueRequired) codeStream.generateImplicitConversion(this.implicitConversion); codeStream.recordPositionsFrom(pc, this.sourceStart); } /** * Optimized boolean code generation for the conditional operator ?: */ public void generateOptimizedBoolean( BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) { if ((this.constant != Constant.NotAConstant) && (this.constant.typeID() == T_boolean) // constant || ((this.valueIfTrue.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) != T_boolean) { // non boolean values super.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); return; } Constant cst= this.condition.constant; Constant condCst= this.condition.optimizedBooleanConstant(); boolean needTruePart= !(((cst != Constant.NotAConstant) && (cst.booleanValue() == false)) || ((condCst != Constant.NotAConstant) && (condCst.booleanValue() == false))); boolean needFalsePart= !(((cst != Constant.NotAConstant) && (cst.booleanValue() == true)) || ((condCst != Constant.NotAConstant) && (condCst.booleanValue() == true))); BranchLabel internalFalseLabel, endifLabel= new BranchLabel(codeStream); // Generate code for the condition boolean needConditionValue= (cst == Constant.NotAConstant) && (condCst == Constant.NotAConstant); this.condition.generateOptimizedBoolean( currentScope, codeStream, null, internalFalseLabel= new BranchLabel(codeStream), needConditionValue); if (this.trueInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables( currentScope, this.trueInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.trueInitStateIndex); } // Then code generation if (needTruePart) { this.valueIfTrue.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired); if (needFalsePart) { // Jump over the else part JumpEndif: { if (falseLabel == null) { if (trueLabel != null) { // implicit falling through the FALSE case cst= this.optimizedIfTrueConstant; boolean isValueIfTrueOptimizedTrue= cst != null && cst != Constant.NotAConstant && cst.booleanValue() == true; if (isValueIfTrueOptimizedTrue) break JumpEndif; // no need to jump over, since branched to true already } } else { // implicit falling through the TRUE case if (trueLabel == null) { cst= this.optimizedIfTrueConstant; boolean isValueIfTrueOptimizedFalse= cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; if (isValueIfTrueOptimizedFalse) break JumpEndif; // no need to jump over, since branched to false already } else { // no implicit fall through TRUE/FALSE --> should never occur } } int position= codeStream.position; codeStream.goto_(endifLabel); codeStream.updateLastRecordedEndPC(currentScope, position); } // No need to decrement codestream stack size // since valueIfTrue was already consumed by branch bytecode } } if (needFalsePart) { internalFalseLabel.place(); if (this.falseInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.falseInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.falseInitStateIndex); } this.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 (this.mergedInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); } // no implicit conversion for boolean values codeStream.updateLastRecordedEndPC(currentScope, codeStream.position); } public int nullStatus(FlowInfo flowInfo) { Constant cst= this.condition.optimizedBooleanConstant(); if (cst != Constant.NotAConstant) { if (cst.booleanValue()) { return this.valueIfTrue.nullStatus(flowInfo); } return this.valueIfFalse.nullStatus(flowInfo); } int ifTrueNullStatus= this.valueIfTrue.nullStatus(flowInfo), ifFalseNullStatus= this.valueIfFalse.nullStatus(flowInfo); if (ifTrueNullStatus == ifFalseNullStatus) { return ifTrueNullStatus; } return FlowInfo.UNKNOWN; // cannot decide which branch to take, and they disagree } public Constant optimizedBooleanConstant() { return this.optimizedBooleanConstant == null ? this.constant : this.optimizedBooleanConstant; } public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) { this.condition.printExpression(indent, output).append(" ? "); //$NON-NLS-1$ this.valueIfTrue.printExpression(0, output).append(" : "); //$NON-NLS-1$ return this.valueIfFalse.printExpression(0, output); } public TypeBinding resolveType(BlockScope scope) { // JLS3 15.25 this.constant= Constant.NotAConstant; LookupEnvironment env= scope.environment(); boolean use15specifics= scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; TypeBinding conditionType= this.condition.resolveTypeExpecting(scope, TypeBinding.BOOLEAN); this.condition.computeConversion(scope, TypeBinding.BOOLEAN, conditionType); if (this.valueIfTrue instanceof CastExpression) this.valueIfTrue.bits|= DisableUnnecessaryCastCheck; // will check later on TypeBinding originalValueIfTrueType= this.valueIfTrue.resolveType(scope); if (this.valueIfFalse instanceof CastExpression) this.valueIfFalse.bits|= DisableUnnecessaryCastCheck; // will check later on TypeBinding originalValueIfFalseType= this.valueIfFalse.resolveType(scope); if (conditionType == null || originalValueIfTrueType == null || originalValueIfFalseType == null) return null; TypeBinding valueIfTrueType= originalValueIfTrueType; TypeBinding valueIfFalseType= originalValueIfFalseType; if (use15specifics && valueIfTrueType != valueIfFalseType) { if (valueIfTrueType.isBaseType()) { if (valueIfFalseType.isBaseType()) { // bool ? baseType : baseType if (valueIfTrueType == TypeBinding.NULL) { // bool ? null : 12 --> Integer valueIfFalseType= env.computeBoxingType(valueIfFalseType); // boxing } else if (valueIfFalseType == TypeBinding.NULL) { // bool ? 12 : null --> Integer valueIfTrueType= env.computeBoxingType(valueIfTrueType); // boxing } } else { // bool ? baseType : nonBaseType TypeBinding unboxedIfFalseType= valueIfFalseType.isBaseType() ? valueIfFalseType : env.computeBoxingType(valueIfFalseType); if (valueIfTrueType.isNumericType() && unboxedIfFalseType.isNumericType()) { valueIfFalseType= unboxedIfFalseType; // unboxing } else if (valueIfTrueType != TypeBinding.NULL) { // bool ? 12 : new Integer(12) --> int valueIfFalseType= env.computeBoxingType(valueIfFalseType); // unboxing } } } else if (valueIfFalseType.isBaseType()) { // bool ? nonBaseType : baseType TypeBinding unboxedIfTrueType= valueIfTrueType.isBaseType() ? valueIfTrueType : env.computeBoxingType(valueIfTrueType); if (unboxedIfTrueType.isNumericType() && valueIfFalseType.isNumericType()) { valueIfTrueType= unboxedIfTrueType; // unboxing } else if (valueIfFalseType != TypeBinding.NULL) { // bool ? new Integer(12) : 12 --> int valueIfTrueType= env.computeBoxingType(valueIfTrueType); // unboxing } } else { // bool ? nonBaseType : nonBaseType TypeBinding unboxedIfTrueType= env.computeBoxingType(valueIfTrueType); TypeBinding unboxedIfFalseType= env.computeBoxingType(valueIfFalseType); if (unboxedIfTrueType.isNumericType() && unboxedIfFalseType.isNumericType()) { valueIfTrueType= unboxedIfTrueType; valueIfFalseType= unboxedIfFalseType; } } } // Propagate the constant value from the valueIfTrue and valueIFFalse expression if it is possible Constant condConstant, trueConstant, falseConstant; if ((condConstant= this.condition.constant) != Constant.NotAConstant && (trueConstant= this.valueIfTrue.constant) != Constant.NotAConstant && (falseConstant= this.valueIfFalse.constant) != Constant.NotAConstant) { // all terms are constant expression so we can propagate the constant // from valueIFTrue or valueIfFalse to the receiver constant this.constant= condConstant.booleanValue() ? trueConstant : falseConstant; } if (valueIfTrueType == valueIfFalseType) { // harmed the implicit conversion this.valueIfTrue.computeConversion(scope, valueIfTrueType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, valueIfFalseType, originalValueIfFalseType); if (valueIfTrueType == TypeBinding.BOOLEAN) { this.optimizedIfTrueConstant= this.valueIfTrue.optimizedBooleanConstant(); this.optimizedIfFalseConstant= this.valueIfFalse.optimizedBooleanConstant(); if (this.optimizedIfTrueConstant != Constant.NotAConstant && this.optimizedIfFalseConstant != Constant.NotAConstant && this.optimizedIfTrueConstant.booleanValue() == this.optimizedIfFalseConstant.booleanValue()) { // a ? true : true / a ? false : false this.optimizedBooleanConstant= this.optimizedIfTrueConstant; } else if ((condConstant= this.condition.optimizedBooleanConstant()) != Constant.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 == TypeBinding.BYTE && valueIfFalseType == TypeBinding.SHORT) || (valueIfTrueType == TypeBinding.SHORT && valueIfFalseType == TypeBinding.BYTE)) { this.valueIfTrue.computeConversion(scope, TypeBinding.SHORT, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, TypeBinding.SHORT, originalValueIfFalseType); return this.resolvedType= TypeBinding.SHORT; } // <Byte|Short|Char> x constant(Int) ---> <Byte|Short|Char> and reciprocally if ((valueIfTrueType == TypeBinding.BYTE || valueIfTrueType == TypeBinding.SHORT || valueIfTrueType == TypeBinding.CHAR) && (valueIfFalseType == TypeBinding.INT && this.valueIfFalse.isConstantValueOfTypeAssignableToType(valueIfFalseType, valueIfTrueType))) { this.valueIfTrue.computeConversion(scope, valueIfTrueType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, valueIfTrueType, originalValueIfFalseType); return this.resolvedType= valueIfTrueType; } if ((valueIfFalseType == TypeBinding.BYTE || valueIfFalseType == TypeBinding.SHORT || valueIfFalseType == TypeBinding.CHAR) && (valueIfTrueType == TypeBinding.INT && this.valueIfTrue.isConstantValueOfTypeAssignableToType(valueIfTrueType, valueIfFalseType))) { this.valueIfTrue.computeConversion(scope, valueIfFalseType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, valueIfFalseType, originalValueIfFalseType); return this.resolvedType= valueIfFalseType; } // Manual binary numeric promotion // int if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_int) && BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_int)) { this.valueIfTrue.computeConversion(scope, TypeBinding.INT, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, TypeBinding.INT, originalValueIfFalseType); return this.resolvedType= TypeBinding.INT; } // long if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_long) && BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_long)) { this.valueIfTrue.computeConversion(scope, TypeBinding.LONG, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, TypeBinding.LONG, originalValueIfFalseType); return this.resolvedType= TypeBinding.LONG; } // float if (BaseTypeBinding.isNarrowing(valueIfTrueType.id, T_float) && BaseTypeBinding.isNarrowing(valueIfFalseType.id, T_float)) { this.valueIfTrue.computeConversion(scope, TypeBinding.FLOAT, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, TypeBinding.FLOAT, originalValueIfFalseType); return this.resolvedType= TypeBinding.FLOAT; } // double this.valueIfTrue.computeConversion(scope, TypeBinding.DOUBLE, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, TypeBinding.DOUBLE, originalValueIfFalseType); return this.resolvedType= TypeBinding.DOUBLE; } // Type references (null null is already tested) if (valueIfTrueType.isBaseType() && valueIfTrueType != TypeBinding.NULL) { if (use15specifics) { valueIfTrueType= env.computeBoxingType(valueIfTrueType); } else { scope.problemReporter().conditionalArgumentsIncompatibleTypes(this, valueIfTrueType, valueIfFalseType); return null; } } if (valueIfFalseType.isBaseType() && valueIfFalseType != TypeBinding.NULL) { if (use15specifics) { valueIfFalseType= env.computeBoxingType(valueIfFalseType); } else { scope.problemReporter().conditionalArgumentsIncompatibleTypes(this, valueIfTrueType, valueIfFalseType); return null; } } if (use15specifics) { // >= 1.5 : LUB(operand types) must exist TypeBinding commonType= null; if (valueIfTrueType == TypeBinding.NULL) { commonType= valueIfFalseType; } else if (valueIfFalseType == TypeBinding.NULL) { commonType= valueIfTrueType; } else { commonType= scope.lowerUpperBound(new TypeBinding[] { valueIfTrueType, valueIfFalseType }); } if (commonType != null) { this.valueIfTrue.computeConversion(scope, commonType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, commonType, originalValueIfFalseType); return this.resolvedType= commonType.capture(scope, this.sourceEnd); } } else { // < 1.5 : one operand must be convertible to the other if (valueIfFalseType.isCompatibleWith(valueIfTrueType)) { this.valueIfTrue.computeConversion(scope, valueIfTrueType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, valueIfTrueType, originalValueIfFalseType); return this.resolvedType= valueIfTrueType; } else if (valueIfTrueType.isCompatibleWith(valueIfFalseType)) { this.valueIfTrue.computeConversion(scope, valueIfFalseType, originalValueIfTrueType); this.valueIfFalse.computeConversion(scope, valueIfFalseType, originalValueIfFalseType); return this.resolvedType= valueIfFalseType; } } scope.problemReporter().conditionalArgumentsIncompatibleTypes( this, valueIfTrueType, valueIfFalseType); return null; } public void traverse(ASTVisitor visitor, BlockScope scope) { if (visitor.visit(this, scope)) { this.condition.traverse(visitor, scope); this.valueIfTrue.traverse(visitor, scope); this.valueIfFalse.traverse(visitor, scope); } visitor.endVisit(this, scope); } }