org.eclipse.jdt.internal.compiler.ast.ConditionalExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
This is Eclipse JDT Core Batch Compiler used by Scout SDK
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2016 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
* Stephen Herrmann - Contributions for
* bug 133125 - [compiler][null] need to report the null status of expressions and analyze them simultaneously
* bug 292478 - Report potentially null across variable assignment
* bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself
* bug 354554 - [null] conditional with redundant condition yields weak error message
* bug 349326 - [1.7] new warning for missing try-with-resources
* bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null"
* bug 383368 - [compiler][null] syntactic null analysis for field references
* bug 400761 - [compiler][null] null may be return as boolean without a diagnostic
* Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
* Bug 415043 - [1.8][null] Follow-up re null type annotations after bug 392099
* Bug 417295 - [1.8[[null] Massage type annotated null analysis to gel well with deep encoded type bindings.
* Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec)
* Bug 426078 - [1.8] VerifyError when conditional expression passed as an argument
* Bug 427438 - [1.8][compiler] NPE at org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:280)
* Bug 418537 - [1.8][null] Fix null type annotation analysis for poly conditional expressions
* Bug 428352 - [1.8][compiler] Resolution errors don't always surface
* Bug 407414 - [compiler][null] Incorrect warning on a primitive type being null
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.*;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
public class ConditionalExpression extends OperatorExpression implements IPolyExpression {
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;
// we compute and store the null status during analyseCode (https://bugs.eclipse.org/324178):
private int nullStatus = FlowInfo.UNKNOWN;
int ifFalseNullStatus;
int ifTrueNullStatus;
private TypeBinding expectedType;
private ExpressionContext expressionContext = VANILLA_CONTEXT;
private boolean isPolyExpression = false;
private TypeBinding originalValueIfTrueType;
private TypeBinding originalValueIfFalseType;
private boolean use18specifics;
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);
flowContext.conditionalLevel++;
// process the if-true part
FlowInfo trueFlowInfo = flowInfo.initsWhenTrue().copy();
final CompilerOptions compilerOptions = currentScope.compilerOptions();
if (isConditionOptimizedFalse) {
if ((mode & FlowInfo.UNREACHABLE) == 0) {
trueFlowInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
if (!isKnowDeadCodePattern(this.condition) || compilerOptions.reportDeadCodeInTrivialIfStatement) {
this.valueIfTrue.complainIfUnreachable(trueFlowInfo, currentScope, initialComplaintLevel, false);
}
}
this.trueInitStateIndex = currentScope.methodScope().recordInitializationStates(trueFlowInfo);
trueFlowInfo = this.valueIfTrue.analyseCode(currentScope, flowContext, trueFlowInfo);
this.valueIfTrue.checkNPEbyUnboxing(currentScope, flowContext, trueFlowInfo);
// may need to fetch this null status before expireNullCheckedFieldInfo():
this.ifTrueNullStatus = -1;
if (compilerOptions.enableSyntacticNullAnalysisForFields) {
this.ifTrueNullStatus = this.valueIfTrue.nullStatus(trueFlowInfo, flowContext);
// wipe information that was meant only for valueIfTrue:
flowContext.expireNullCheckedFieldInfo();
}
// process the if-false part
FlowInfo falseFlowInfo = flowInfo.initsWhenFalse().copy();
if (isConditionOptimizedTrue) {
if ((mode & FlowInfo.UNREACHABLE) == 0) {
falseFlowInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
if (!isKnowDeadCodePattern(this.condition) || compilerOptions.reportDeadCodeInTrivialIfStatement) {
this.valueIfFalse.complainIfUnreachable(falseFlowInfo, currentScope, initialComplaintLevel, true);
}
}
this.falseInitStateIndex = currentScope.methodScope().recordInitializationStates(falseFlowInfo);
falseFlowInfo = this.valueIfFalse.analyseCode(currentScope, flowContext, falseFlowInfo);
this.valueIfFalse.checkNPEbyUnboxing(currentScope, flowContext, falseFlowInfo);
flowContext.conditionalLevel--;
// merge if-true & if-false initializations
FlowInfo mergedInfo;
if (isConditionOptimizedTrue){
mergedInfo = trueFlowInfo.addPotentialInitializationsFrom(falseFlowInfo);
if (this.ifTrueNullStatus != -1) {
this.nullStatus = this.ifTrueNullStatus;
} else {
this.nullStatus = this.valueIfTrue.nullStatus(trueFlowInfo, flowContext);
}
} else if (isConditionOptimizedFalse) {
mergedInfo = falseFlowInfo.addPotentialInitializationsFrom(trueFlowInfo);
this.nullStatus = this.valueIfFalse.nullStatus(falseFlowInfo, flowContext);
} else {
// this block must meet two conflicting requirements (see https://bugs.eclipse.org/324178):
// (1) For null analysis of "Object o2 = (o1 != null) ? o1 : new Object();" we need to distinguish
// the paths *originating* from the evaluation of the condition to true/false respectively.
// This is used to determine the possible null status of the entire conditional expression.
// (2) For definite assignment analysis (JLS 16.1.5) of boolean conditional expressions of the form
// "if (c1 ? expr1 : expr2) use(v);" we need to check whether any variable v will be definitely
// assigned whenever the entire conditional expression evaluates to true (to reach the then branch).
// I.e., we need to collect flowInfo *towards* the overall outcome true/false
// (regardless of the evaluation of the condition).
// to support (1) use the infos of both branches originating from the condition for computing the nullStatus:
computeNullStatus(trueFlowInfo, falseFlowInfo, flowContext);
// to support (2) we split the true/false branches according to their inner structure. Consider this:
// if (b ? false : (true && (v = false))) return v; -- ok
// - expr1 ("false") has no path towards true (mark as unreachable)
// - expr2 ("(true && (v = false))") has a branch towards true on which v is assigned.
// -> merging these two branches yields: v is assigned
// - the paths towards false are irrelevant since the enclosing if has no else.
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 trueFlowTowardsTrue = trueFlowInfo.initsWhenTrue().unconditionalCopy();
UnconditionalFlowInfo falseFlowTowardsTrue = falseFlowInfo.initsWhenTrue().unconditionalCopy();
UnconditionalFlowInfo trueFlowTowardsFalse = trueFlowInfo.initsWhenFalse().unconditionalInits();
UnconditionalFlowInfo falseFlowTowardsFalse = falseFlowInfo.initsWhenFalse().unconditionalInits();
if (isValueIfTrueOptimizedFalse) {
trueFlowTowardsTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
if (isValueIfFalseOptimizedFalse) {
falseFlowTowardsTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
if (isValueIfTrueOptimizedTrue) {
trueFlowTowardsFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
if (isValueIfFalseOptimizedTrue) {
falseFlowTowardsFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
mergedInfo =
FlowInfo.conditional(
trueFlowTowardsTrue.mergedWith(falseFlowTowardsTrue),
trueFlowTowardsFalse.mergedWith(falseFlowTowardsFalse));
}
this.mergedInitStateIndex =
currentScope.methodScope().recordInitializationStates(mergedInfo);
mergedInfo.setReachMode(mode);
return mergedInfo;
}
public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) {
if ((this.nullStatus & FlowInfo.NULL) != 0)
scope.problemReporter().expressionNullReference(this);
else if ((this.nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
scope.problemReporter().expressionPotentialNullReference(this);
return true; // all checking done
}
private void computeNullStatus(FlowInfo trueBranchInfo, FlowInfo falseBranchInfo, FlowContext flowContext) {
// given that the condition cannot be optimized to a constant
// we now merge the nullStatus from both branches:
if (this.ifTrueNullStatus == -1) { // has this status been pre-computed?
this.ifTrueNullStatus = this.valueIfTrue.nullStatus(trueBranchInfo, flowContext);
}
this.ifFalseNullStatus = this.valueIfFalse.nullStatus(falseBranchInfo, flowContext);
if (this.ifTrueNullStatus == this.ifFalseNullStatus) {
this.nullStatus = this.ifTrueNullStatus;
return;
}
if (trueBranchInfo.reachMode() != FlowInfo.REACHABLE) {
this.nullStatus = this.ifFalseNullStatus;
return;
}
if (falseBranchInfo.reachMode() != FlowInfo.REACHABLE) {
this.nullStatus = this.ifTrueNullStatus;
return;
}
// is there a chance of null (or non-null)? -> potentially null etc.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=133125
int status = 0;
int combinedStatus = this.ifTrueNullStatus|this.ifFalseNullStatus;
if ((combinedStatus & (FlowInfo.NULL|FlowInfo.POTENTIALLY_NULL)) != 0)
status |= FlowInfo.POTENTIALLY_NULL;
if ((combinedStatus & (FlowInfo.NON_NULL|FlowInfo.POTENTIALLY_NON_NULL)) != 0)
status |= FlowInfo.POTENTIALLY_NON_NULL;
if ((combinedStatus & (FlowInfo.UNKNOWN|FlowInfo.POTENTIALLY_UNKNOWN)) != 0)
status |= FlowInfo.POTENTIALLY_UNKNOWN;
if (status > 0)
this.nullStatus = status;
}
/**
* 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.recordPositionsFrom(position, this.valueIfTrue.sourceEnd);
// 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) {
int pc = codeStream.position;
if ((this.constant != Constant.NotAConstant) && (this.constant.typeID() == T_boolean) // constant
|| ((this.valueIfTrue.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) != T_boolean
|| ((this.valueIfFalse.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.recordPositionsFrom(position, this.valueIfTrue.sourceEnd);
}
// 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.recordPositionsFrom(pc, this.sourceEnd);
}
public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) {
if ((this.implicitConversion & TypeIds.BOXING) != 0)
return FlowInfo.NON_NULL;
return this.nullStatus;
}
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
LookupEnvironment env = scope.environment();
final long sourceLevel = scope.compilerOptions().sourceLevel;
boolean use15specifics = sourceLevel >= ClassFileConstants.JDK1_5;
this.use18specifics = sourceLevel >= ClassFileConstants.JDK1_8;
if (this.use18specifics) {
if (this.expressionContext == ASSIGNMENT_CONTEXT || this.expressionContext == INVOCATION_CONTEXT) {
this.valueIfTrue.setExpressionContext(this.expressionContext);
this.valueIfTrue.setExpectedType(this.expectedType);
this.valueIfFalse.setExpressionContext(this.expressionContext);
this.valueIfFalse.setExpectedType(this.expectedType);
}
}
if (this.constant != Constant.NotAConstant) {
this.constant = Constant.NotAConstant;
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
this.originalValueIfTrueType = this.valueIfTrue.resolveType(scope);
if (this.valueIfFalse instanceof CastExpression) this.valueIfFalse.bits |= DisableUnnecessaryCastCheck; // will check later on
this.originalValueIfFalseType = this.valueIfFalse.resolveType(scope);
if (conditionType == null || this.originalValueIfTrueType == null || this.originalValueIfFalseType == null)
return null;
} else {
if (this.originalValueIfTrueType.kind() == Binding.POLY_TYPE)
this.originalValueIfTrueType = this.valueIfTrue.resolveType(scope);
if (this.originalValueIfFalseType.kind() == Binding.POLY_TYPE)
this.originalValueIfFalseType = this.valueIfFalse.resolveType(scope);
if (this.originalValueIfTrueType == null || !this.originalValueIfTrueType.isValidBinding())
return this.resolvedType = null;
if (this.originalValueIfFalseType == null || !this.originalValueIfFalseType.isValidBinding())
return this.resolvedType = null;
}
if (isPolyExpression()) {
if (this.expectedType == null || !this.expectedType.isProperType(true)) {
return new PolyTypeBinding(this);
}
return this.resolvedType = computeConversions(scope, this.expectedType) ? this.expectedType : null;
}
TypeBinding valueIfTrueType = this.originalValueIfTrueType;
TypeBinding valueIfFalseType = this.originalValueIfFalseType;
if (use15specifics && TypeBinding.notEquals(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 (TypeBinding.equalsEquals(valueIfTrueType, valueIfFalseType)) { // harmed the implicit conversion
this.valueIfTrue.computeConversion(scope, valueIfTrueType, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, valueIfFalseType, this.originalValueIfFalseType);
if (TypeBinding.equalsEquals(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 = NullAnnotationMatching.moreDangerousType(valueIfTrueType, valueIfFalseType);
}
// Determine the return type depending on argument types
// Numeric types
if (valueIfTrueType.isNumericType() && valueIfFalseType.isNumericType()) {
// (Short x Byte) or (Byte x Short)"
if ((TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.BYTE) && TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.SHORT))
|| (TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.SHORT) && TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.BYTE))) {
this.valueIfTrue.computeConversion(scope, TypeBinding.SHORT, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, TypeBinding.SHORT, this.originalValueIfFalseType);
return this.resolvedType = TypeBinding.SHORT;
}
// x constant(Int) ---> and reciprocally
if ((TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.BYTE) || TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.SHORT) || TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.CHAR))
&& (TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.INT)
&& this.valueIfFalse.isConstantValueOfTypeAssignableToType(valueIfFalseType, valueIfTrueType))) {
this.valueIfTrue.computeConversion(scope, valueIfTrueType, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, valueIfTrueType, this.originalValueIfFalseType);
return this.resolvedType = valueIfTrueType;
}
if ((TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.BYTE)
|| TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.SHORT)
|| TypeBinding.equalsEquals(valueIfFalseType, TypeBinding.CHAR))
&& (TypeBinding.equalsEquals(valueIfTrueType, TypeBinding.INT)
&& this.valueIfTrue.isConstantValueOfTypeAssignableToType(valueIfTrueType, valueIfFalseType))) {
this.valueIfTrue.computeConversion(scope, valueIfFalseType, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, valueIfFalseType, this.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, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, TypeBinding.INT, this.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, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, TypeBinding.LONG, this.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, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, TypeBinding.FLOAT, this.originalValueIfFalseType);
return this.resolvedType = TypeBinding.FLOAT;
}
// double
this.valueIfTrue.computeConversion(scope, TypeBinding.DOUBLE, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, TypeBinding.DOUBLE, this.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, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, commonType, this.originalValueIfFalseType);
return this.resolvedType = commonType.capture(scope, this.sourceStart, this.sourceEnd);
}
} else {
// < 1.5 : one operand must be convertible to the other
if (valueIfFalseType.isCompatibleWith(valueIfTrueType)) {
this.valueIfTrue.computeConversion(scope, valueIfTrueType, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, valueIfTrueType, this.originalValueIfFalseType);
return this.resolvedType = valueIfTrueType;
} else if (valueIfTrueType.isCompatibleWith(valueIfFalseType)) {
this.valueIfTrue.computeConversion(scope, valueIfFalseType, this.originalValueIfTrueType);
this.valueIfFalse.computeConversion(scope, valueIfFalseType, this.originalValueIfFalseType);
return this.resolvedType = valueIfFalseType;
}
}
scope.problemReporter().conditionalArgumentsIncompatibleTypes(
this,
valueIfTrueType,
valueIfFalseType);
return null;
}
protected boolean computeConversions(BlockScope scope, TypeBinding targetType) {
boolean ok = true;
if (this.originalValueIfTrueType != null && this.originalValueIfTrueType.isValidBinding()) {
if (this.valueIfTrue.isConstantValueOfTypeAssignableToType(this.originalValueIfTrueType, targetType)
|| this.originalValueIfTrueType.isCompatibleWith(targetType)) {
this.valueIfTrue.computeConversion(scope, targetType, this.originalValueIfTrueType);
if (this.originalValueIfTrueType.needsUncheckedConversion(targetType)) {
scope.problemReporter().unsafeTypeConversion(this.valueIfTrue, this.originalValueIfTrueType, targetType);
}
if (this.valueIfTrue instanceof CastExpression
&& (this.valueIfTrue.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(scope, targetType, (CastExpression) this.valueIfTrue);
}
} else if (isBoxingCompatible(this.originalValueIfTrueType, targetType, this.valueIfTrue, scope)) {
this.valueIfTrue.computeConversion(scope, targetType, this.originalValueIfTrueType);
if (this.valueIfTrue instanceof CastExpression
&& (this.valueIfTrue.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(scope, targetType, (CastExpression) this.valueIfTrue);
}
} else {
scope.problemReporter().typeMismatchError(this.originalValueIfTrueType, targetType, this.valueIfTrue, null);
ok = false;
}
}
if (this.originalValueIfFalseType != null && this.originalValueIfFalseType.isValidBinding()) {
if (this.valueIfFalse.isConstantValueOfTypeAssignableToType(this.originalValueIfFalseType, targetType)
|| this.originalValueIfFalseType.isCompatibleWith(targetType)) {
this.valueIfFalse.computeConversion(scope, targetType, this.originalValueIfFalseType);
if (this.originalValueIfFalseType.needsUncheckedConversion(targetType)) {
scope.problemReporter().unsafeTypeConversion(this.valueIfFalse, this.originalValueIfFalseType, targetType);
}
if (this.valueIfFalse instanceof CastExpression
&& (this.valueIfFalse.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(scope, targetType, (CastExpression) this.valueIfFalse);
}
} else if (isBoxingCompatible(this.originalValueIfFalseType, targetType, this.valueIfFalse, scope)) {
this.valueIfFalse.computeConversion(scope, targetType, this.originalValueIfFalseType);
if (this.valueIfFalse instanceof CastExpression
&& (this.valueIfFalse.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(scope, targetType, (CastExpression) this.valueIfFalse);
}
} else {
scope.problemReporter().typeMismatchError(this.originalValueIfFalseType, targetType, this.valueIfFalse, null);
ok = false;
}
}
return ok;
}
public void setExpectedType(TypeBinding expectedType) {
this.expectedType = expectedType;
}
public void setExpressionContext(ExpressionContext context) {
this.expressionContext = context;
}
public ExpressionContext getExpressionContext() {
return this.expressionContext;
}
@Override
public Expression[] getPolyExpressions() {
Expression [] truePolys = this.valueIfTrue.getPolyExpressions();
Expression [] falsePolys = this.valueIfFalse.getPolyExpressions();
if (truePolys.length == 0)
return falsePolys;
if (falsePolys.length == 0)
return truePolys;
Expression [] allPolys = new Expression [truePolys.length + falsePolys.length];
System.arraycopy(truePolys, 0, allPolys, 0, truePolys.length);
System.arraycopy(falsePolys, 0, allPolys, truePolys.length, falsePolys.length);
return allPolys;
}
public boolean isPertinentToApplicability(TypeBinding targetType, MethodBinding method) {
return this.valueIfTrue.isPertinentToApplicability(targetType, method)
&& this.valueIfFalse.isPertinentToApplicability(targetType, method);
}
@Override
public boolean isPotentiallyCompatibleWith(TypeBinding targetType, Scope scope) {
return this.valueIfTrue.isPotentiallyCompatibleWith(targetType, scope)
&& this.valueIfFalse.isPotentiallyCompatibleWith(targetType, scope);
}
@Override
public boolean isFunctionalType() {
return this.valueIfTrue.isFunctionalType() || this.valueIfFalse.isFunctionalType(); // Even if only one arm is functional type, this will require a functional interface target
}
public boolean isPolyExpression() throws UnsupportedOperationException {
if (!this.use18specifics)
return false;
if (this.isPolyExpression)
return true;
if (this.expressionContext != ASSIGNMENT_CONTEXT && this.expressionContext != INVOCATION_CONTEXT)
return false;
if (this.originalValueIfTrueType == null || this.originalValueIfFalseType == null) // resolution error.
return false;
if (this.valueIfTrue.isPolyExpression() || this.valueIfFalse.isPolyExpression())
return true;
// "... unless both operands produce primitives (or boxed primitives)":
if (this.originalValueIfTrueType.isBaseType() || (this.originalValueIfTrueType.id >= TypeIds.T_JavaLangByte && this.originalValueIfTrueType.id <= TypeIds.T_JavaLangBoolean)) {
if (this.originalValueIfFalseType.isBaseType() || (this.originalValueIfFalseType.id >= TypeIds.T_JavaLangByte && this.originalValueIfFalseType.id <= TypeIds.T_JavaLangBoolean))
return false;
}
// clause around generic method's return type prior to instantiation needs double check.
return this.isPolyExpression = true;
}
public boolean isCompatibleWith(TypeBinding left, Scope scope) {
return isPolyExpression() ? this.valueIfTrue.isCompatibleWith(left, scope) && this.valueIfFalse.isCompatibleWith(left, scope) :
super.isCompatibleWith(left, scope);
}
@Override
public boolean isBoxingCompatibleWith(TypeBinding targetType, Scope scope) {
// Note: compatibility check may have failed in just one arm and we may have reached here.
return isPolyExpression() ? (this.valueIfTrue.isCompatibleWith(targetType, scope) ||
this.valueIfTrue.isBoxingCompatibleWith(targetType, scope)) &&
(this.valueIfFalse.isCompatibleWith(targetType, scope) ||
this.valueIfFalse.isBoxingCompatibleWith(targetType, scope)) :
super.isBoxingCompatibleWith(targetType, scope);
}
public boolean sIsMoreSpecific(TypeBinding s, TypeBinding t, Scope scope) {
if (super.sIsMoreSpecific(s, t, scope))
return true;
return isPolyExpression() ?
this.valueIfTrue.sIsMoreSpecific(s, t, scope) && this.valueIfFalse.sIsMoreSpecific(s, t, scope):
false;
}
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);
}
}