Copyright (c) 2000, 2019 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 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
/******************************************************************************* * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Stephen Herrmann <stephan@cs.tu-berlin.de> - 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; } @Override 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; } @Override 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 combinedStatus = this.ifTrueNullStatus|this.ifFalseNullStatus; int status = Expression.computeNullStatus(0, combinedStatus); if (status > 0) this.nullStatus = status; }
Code generation for the conditional operator ?:
Params:
  • currentScope – org.eclipse.jdt.internal.compiler.lookup.BlockScope
  • codeStream – org.eclipse.jdt.internal.compiler.codegen.CodeStream
  • valueRequired – boolean
/** * 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 */
@Override 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(); if (cst == Constant.NotAConstant) { cst = this.condition.optimizedNullComparisonConstant(); } 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 ?:
/** * Optimized boolean code generation for the conditional operator ?: */
@Override 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); } @Override public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { if ((this.implicitConversion & TypeIds.BOXING) != 0) return FlowInfo.NON_NULL; return this.nullStatus; } @Override public Constant optimizedBooleanConstant() { return this.optimizedBooleanConstant == null ? this.constant : this.optimizedBooleanConstant; } @Override 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); } @Override 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; } // <Byte|Short|Char> x constant(Int) ---> <Byte|Short|Char> 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; } @Override public void setExpectedType(TypeBinding expectedType) { this.expectedType = expectedType; } @Override public void setExpressionContext(ExpressionContext context) { this.expressionContext = context; } @Override 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; } @Override 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 } @Override 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; } @Override 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); } @Override 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; } @Override 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); } }