/******************************************************************************* * 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 * * This is an implementation of an early-draft specification developed under the Java * Community Process (JCP) and is made available for testing and evaluation purposes * only. The code is not compatible with any specification of the JCP. * * 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; } @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 ?: * * @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(); 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 ?: */ @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; } // 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; } @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); } }