| /******************************************************************************* |
| * Copyright (c) 2000, 2016 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 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 |
| */ |
| @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; |
| } |
| // <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); |
| } |
| } |
| |