/*******************************************************************************
 * Copyright (c) 2000, 2021 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.core.compiler.CharOperation;
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);
		this.condition.updateFlowOnBooleanResult(trueFlowInfo, true);
		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);
		this.condition.updateFlowOnBooleanResult(falseFlowInfo, false);

		EqualExpression simpleCondition = this.condition instanceof EqualExpression ? (EqualExpression) this.condition : null;
		boolean doSyntacticAnalysisForFalseBranch = compilerOptions.enableSyntacticNullAnalysisForFields && simpleCondition != null;
		if (doSyntacticAnalysisForFalseBranch) {
			simpleCondition.syntacticFieldAnalysisForFalseBranch(flowInfo, flowContext);
		}
		falseFlowInfo = this.valueIfFalse.analyseCode(currentScope, flowContext, falseFlowInfo);
		this.valueIfFalse.checkNPEbyUnboxing(currentScope, flowContext, falseFlowInfo);

		// may need to fetch this null status before expireNullCheckedFieldInfo():
		this.ifFalseNullStatus = -1;
		if (doSyntacticAnalysisForFalseBranch) {
			this.ifFalseNullStatus = this.valueIfFalse.nullStatus(falseFlowInfo, flowContext);
			// wipe information that was meant only for valueIfFalse:
			flowContext.expireNullCheckedFieldInfo();
		}
		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);
		}
		if (this.ifFalseNullStatus == -1) { // has this status been pre-computed?
			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();
		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 ?:
	*/
	@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 void addPatternVariables(BlockScope scope, CodeStream codeStream) {
		this.condition.addPatternVariables(scope, codeStream);
		this.valueIfTrue.addPatternVariables(scope, codeStream);
		this.valueIfFalse.addPatternVariables(scope, codeStream);
	}
	@Override
	public void collectPatternVariablesToScope(LocalVariableBinding[] variables, BlockScope scope) {
		// Introduce preceding variables introduced into the scope if this expression is actually evaluated
		this.condition.collectPatternVariablesToScope(variables, scope);
		this.valueIfFalse.collectPatternVariablesToScope(variables, scope);
		this.valueIfTrue.collectPatternVariablesToScope(variables, scope);
		if (this.valueIfFalse.containsPatternVariable() && this.valueIfTrue.containsPatternVariable() ) {
			LocalVariableBinding[] first = this.valueIfTrue.patternVarsWhenTrue;
			LocalVariableBinding[] second = this.valueIfFalse.patternVarsWhenTrue;
			if (first != null && second != null) {
				for (LocalVariableBinding localVariableBinding : first) {
					char[] name = localVariableBinding.name;
					for (LocalVariableBinding localVariableBinding2 : second) {
						if (CharOperation.equals(name, localVariableBinding2.name)) {
							scope.problemReporter().illegalRedeclarationOfPatternVar(localVariableBinding2, localVariableBinding2.declaration);
						}
					}
				}
			}
			first = this.valueIfTrue.patternVarsWhenFalse;
			second = this.valueIfFalse.patternVarsWhenFalse;
			if (first != null && second != null) {
				for (LocalVariableBinding localVariableBinding : first) {
					char[] name = localVariableBinding.name;
					for (LocalVariableBinding localVariableBinding2 : second) {
						if (CharOperation.equals(name, localVariableBinding2.name)) {
							scope.problemReporter().illegalRedeclarationOfPatternVar(localVariableBinding2, localVariableBinding2.declaration);
						}
					}
				}
			}
		}

		if (!this.condition.containsPatternVariable()) {
			return;
		}
		if (this.condition.getPatternVariable() != null) {
			char[] name = this.condition.getPatternVariable().name;
			LocalDeclaration localVar = this.valueIfTrue.getPatternVariable();
			if (localVar != null && CharOperation.equals(name, localVar.name)) {
					scope.problemReporter().illegalRedeclarationOfPatternVar(localVar.binding, localVar);
					return;
			}
			localVar = this.valueIfFalse.getPatternVariable();
			if (localVar != null && CharOperation.equals(name, localVar.name)) {
				scope.problemReporter().illegalRedeclarationOfPatternVar(localVar.binding, localVar);
				return;
			}
		}
		this.condition.collectPatternVariablesToScope(this.patternVarsWhenTrue, scope);

		variables = this.condition.getPatternVariablesWhenTrue();
		this.valueIfTrue.addPatternVariablesWhenTrue(variables);
		this.valueIfFalse.addPatternVariablesWhenFalse(variables);
		this.valueIfTrue.collectPatternVariablesToScope(variables, scope);

		variables = this.condition.getPatternVariablesWhenFalse();
		this.valueIfTrue.addPatternVariablesWhenFalse(variables);
		this.valueIfFalse.addPatternVariablesWhenTrue(variables);
		this.valueIfFalse.collectPatternVariablesToScope(variables, scope);
	}
	@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);
			}
		}

		collectPatternVariablesToScope(null, scope);

		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;
		}
		// 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 (isPolyExpression()) {
			if (this.expectedType == null || !this.expectedType.isProperType(true)) {
				// We will be back here in case of a PolyTypeBinding. So, to enable
				// further processing, set it back to default.
				this.constant = Constant.NotAConstant;
				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;
					}
			}
		}
		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.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull
			} else if (valueIfFalseType == TypeBinding.NULL) {
				commonType = valueIfTrueType.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull
			} 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);
	}
}

